perf(vscode): virtualize bounded transcript rows - #11094
Conversation
Code Review SummaryStatus: 2 Suggestions | Recommendation: Merge Overview
Issue Details (click to expand)SUGGESTION
Other Observations (not in diff)
Files Reviewed (13 files)
Fix these issues in Kilo Cloud Reviewed by claude-4.6-sonnet-20260217 · 2,596,060 tokens Review guidance: REVIEW.md from base branch |
…ipt-model perf(vscode): virtualize bounded transcript rows
Kilo uses the same chat transcript renderer in the sidebar, editor tabs, and Agent Manager. Very large sessions currently virtualize whole user turns while keeping an active tail outside Virtua. A single turn can contain hundreds of tools and thousands of elements, so navigating or switching sessions may still mount most of a transcript even when only one small area intersects the viewport.
This introduces a viewport-first transcript model for every Kilo VS Code chat surface, based on the proven OpenCode desktop architecture. Complete turns are flattened into stable, bounded rows for user messages, assistant part groups, diffs, and errors. Virtua mounts only visible rows plus two rows of overscan, while
keepMountedis limited to the genuinely active streaming rows. Completed history no longer has an unbounded direct tail.The implementation preserves lazy
PartStashhydration and adds exact session-keyed measurement and scroll caches. Cached heights are accepted only when the complete row-key and layout fingerprints match, and scroll state independently preserves bottom-follow or a row anchor plus intra-row offset. The task timeline is rendered as bounded grouped SVG paths instead of one DOM bar per activity, while retaining hover, drag, wheel, keyboard navigation, colors, active state, and accessible labeling.Measured impact
All comparisons used real Agent Manager sessions in a metadata-guarded shadow workspace, the exact Agent Manager
fake.html?id=<uuid>frame, a 505 px transcript viewport, closed diff/review/terminal surfaces, stable source and target IDs, and multiple samples. The baseline is commitf180658b99.Heavy transcript DOM
The baseline heavy target had only one viewport-intersecting turn but retained:
The final viewport typically mounts 3 to 6 rows. A second session that previously exposed an unusually large offscreen row settled at 421 transcript elements and 15 tool wrappers after reducing overscan to two rows.
Comparable CPU traces
For the same warmed source-to-heavy-target switch, the first bounded-row stage changed the trace distribution as follows:
The final strictly valid traced stage had no switch-overlapping main-thread task above 50 ms. Its three-stable-frame qualification was 95.0 ms p50, 99.1 ms p95, and 99.6 ms max, with work split across frames instead of one blocking task.
Final warm activation latency
After reducing overscan to two rows, 30 repeated heavy worktree activations measured:
All 30 samples activated the expected target. Compared with the previous three-row overscan stage, this is 26.1% lower at p50, 12.4% lower at p95, and 23.1% lower at max.
Sessions within one worktree
Thirty direct switches from one 240-message session to each of two other 240-message tabs in the same worktree measured:
Broad real-session traversal
A single traversal across 26 real worktrees representing 37 open session tabs produced:
A separate repeated traversal of nine large sessions for 20 cycles, 180 switches total, kept mounted rows between 2 and 9 and produced 25.9 ms p50, 61.2 ms p95, and 96.2 ms max across sessions with very different visible content. Per-session p95 ranged from 17.8 ms to 77.8 ms, with the slower cases corresponding to heavier visible rows rather than retained transcript size.
Memory signals
The 20-cycle traversal showed a cycle-end heap slope of approximately +0.04 MiB per cycle. Comparable cycle-end samples ranged from 144.5 to 310.3 MiB and ended at 176.0 MiB after starting at 228.0 MiB. A one-pass 26-worktree traversal ranged from 145.8 to 393.2 MiB and ended at 283.1 MiB. These process-wide figures are GC-sensitive, but the repeated traversal did not show unbounded cycle-over-cycle growth.
Architecture details
keepMountedindexes rather than a direct tail.Measurement limits
CPU profiler startup work was excluded from switch-task attribution. Captures with frame or document teardown were rejected. Direct activation measurements cover synchronous selection and render dispatch, while the stable-frame metric additionally waits for three unchanged animation frames. Streaming was not exercised with paid prompts; active-row pinning and queue handoff are covered deterministically, while real-session profiling used existing settled sessions.