Repository navigation
feat(tui): make session tab switching fast for long transcripts - #39568
Conversation
fe0fdc3 to
2feb6e5
Compare
|
Reworked to be front-end only per discussion with Dax: reverted the data-context changes (sync boolean return, Tail-first mounting is unchanged, so warm switches stay flat regardless of transcript length. The post-sync re-reduce is back (it needed the data change to skip), but it runs after the switch paints so it is not visible. Cold first visits fetch inside the gesture as before; hiding that behind prefetch is deferred to a proper data-layer paging/prefetch API. |
2feb6e5 to
48d9514
Compare
|
Ran a simplification review pass (three parallel reviewers: reuse, quality, efficiency). Applied:
Declined with reasons: promoting Tests/typecheck unchanged (same single pre-existing flake). |
What
Switching session tabs could visibly stall on long transcripts. This PR makes tab switching roughly constant-time — the visible switch mounts a fixed-size tail regardless of transcript size.
Front-end only by design: an earlier revision also added data-context prefetch and sync-dedup plumbing, but paging/prefetch on the data layer deserves its own API design (and is being looked at separately), so this version touches nothing outside the session route.
Before / After
Measured with an isolated opencode-drive harness (three sessions of 51 / 123 / 243 transcript rows with realistic markdown + code-fence content, 160×45 viewport; sync = blocked main thread inside the switch, paint = next flushed frame):
Warm-switch numbers were measured with an earlier revision that also skipped a post-sync re-reduce; this version keeps that re-reduce (it needs a data-context change), adding ~6–23ms of CPU after the switch paints — the visible switch is unaffected.
Before: every switch tore down and rebuilt the entire
<Session />tree under the keyed routeShow, eagerly mounting every transcript row — markdown parsing, tree-sitter highlighting, wrapping, and layout for up to 200 messages, scaling ~0.65ms/row.After: a switch mounts only the newest 40 rows and backfills the rest in 60-row chunks shortly after, invisibly (bottom-anchored readers are held by sticky scroll; scrolled-up readers get scroll compensation). The switch cost no longer depends on transcript length.
Cold first visits still fetch inside the switch gesture (unchanged from before); hiding that behind prefetch is the data-layer follow-up.
How
packages/tui/src/routes/session/index.tsx— tail-first transcript mounting.hiddenderives from the row count until backfill pins it (no effect-ordering dependence), clamped so a shrinking re-reduce always leaves a tail visible. A 120ms timer drains 60 rows per chunk, pausing while streaming appends reset it.afterLayoutconsolidates the existing double-rAF “wait for layout” pattern (also adopted byalignMessage).jumpToMessage/scrollToMessagewrap inensureAllRows, which force-mounts the full transcript before message navigation walks or jumps.packages/tui/src/routes/session/rows.ts— defers the first run of the four reactive re-reduce effects, which each duplicated the mount reduce.sequenceDiagram participant U as user participant T as SessionTabs participant S as Session route participant D as DataProvider Note over U,S: on tab switch U->>T: leader+digit / click T->>S: route.navigate (keyed remount) S->>D: rows from store (cached if visited) S->>S: mount newest 40 rows (~40ms) loop every 120ms until drained S->>S: mount 60 older rows above viewport endScope
Datatype change. Paging/prefetch belongs in a designed data-layer API (follow-up).boundaryIDindex mapping stays positional; backfill briefly re-evaluates it per mounted row per chunk (bounded small at the message cap).Testing
bun typecheckinpackages/tuibun run testinpackages/tui: 562 pass, 5 skip, 1 fail —restores queued compaction from durable pending inputfails identically on cleanv2HEAD (3c259fc552), pre-existing and unrelatedensureAllRowsunder message navigationDemo
https://assets.kitlangton.dev/anomalyco/opencode/pr-39568/tab-switch-demo-e5c2cbf8eb06.mp4
Recorded from the PR branch: cycling
<leader>1/2/3between three sessions (150 / 60 / 250 tool-call transcripts with heavy markdown) — every switch paints instantly.