Transcript Incremental Projection
Nucleic-Session: 0FFD007B-0696-4517-9429-129C7B0FD5AC Co-authored-by: Nucleic <[email protected]>
This commit is contained in:
@@ -494,37 +494,15 @@ struct TranscriptList: View {
|
||||
/// settling — a `.task(id:)` debounces it to detect when the opening layout has come to rest.
|
||||
@State private var transcriptContentHeight: CGFloat = 0
|
||||
|
||||
/// Memoized transcript projection. `TranscriptProjection.build` folds the entire raw event
|
||||
/// stream into rows on every read, but `body` re-evaluates far more often than `events`
|
||||
/// actually changes: while the chat is opening, `onScrollGeometryChange` bumps
|
||||
/// `transcriptContentHeight` on every settling-layout frame, and each bump re-runs `body` —
|
||||
/// so the untouched event stream was re-folded dozens of times during the exact window the
|
||||
/// tab bar is sliding away, starving that animation of the main thread and making the
|
||||
/// load-in jitter. Reuse the last fold whenever the stream is unchanged. A class held in
|
||||
/// `@State` so reading/updating it from `body` doesn't itself invalidate the view (SwiftUI
|
||||
/// stores the reference and never diffs its interior).
|
||||
private final class ProjectionCache {
|
||||
/// The event stream is append-only with strictly increasing `seq`, so `(count, lastSeq)`
|
||||
/// uniquely pins it; `firstSeq` guards the open reset (stream cleared to `[]`, then
|
||||
/// refilled). The two display toggles are inputs to the fold, so they key it too.
|
||||
private struct Key: Equatable {
|
||||
var count: Int; var firstSeq: UInt64; var lastSeq: UInt64
|
||||
var showRaw: Bool; var showLockEvents: Bool
|
||||
}
|
||||
private var key: Key?
|
||||
private var value: [TranscriptItem] = []
|
||||
|
||||
func items(for events: [AgentEvent], showRaw: Bool, showLockEvents: Bool) -> [TranscriptItem] {
|
||||
let k = Key(count: events.count, firstSeq: events.first?.seq ?? 0,
|
||||
lastSeq: events.last?.seq ?? 0, showRaw: showRaw, showLockEvents: showLockEvents)
|
||||
if key == k { return value }
|
||||
let built = TranscriptProjection.build(events, showRaw: showRaw, showLockEvents: showLockEvents)
|
||||
key = k
|
||||
value = built
|
||||
return built
|
||||
}
|
||||
}
|
||||
@State private var projectionCache = ProjectionCache()
|
||||
/// Incremental transcript projection (docs/TRANSCRIPT_INCREMENTAL_PROJECTION.md). Two layers:
|
||||
/// a read memo that collapses the redundant `body` re-evaluations (settling layout bumps
|
||||
/// `transcriptContentHeight` every frame while opening, and each bump re-runs `body` against
|
||||
/// an unchanged stream), and — when the stream *has* grown — a stable-prefix fold that seals
|
||||
/// everything before the live turn once and re-folds only the tail, so a streaming delta
|
||||
/// costs O(live-tail) instead of re-folding all N events (which made long sessions cost
|
||||
/// O(N²) over their lifetime). A class held in `@State` so reading/updating it from `body`
|
||||
/// doesn't itself invalidate the view (SwiftUI stores the reference, never diffs interior).
|
||||
@State private var projectionCache = IncrementalTranscriptProjection()
|
||||
|
||||
/// The in-flight background Markdown pre-warm (below), cancelled when a newer one supersedes
|
||||
/// it or the transcript goes away — so a long warm can't outlive the view or stack up behind
|
||||
|
||||
Reference in New Issue
Block a user