Repository navigation
fix: stop transcript jumps while subagent cards stream - #14166
Conversation
Virtualizer handoff rebuilt every row of a turn when the turn completed, remounting the rows at the 260px estimate before they were measured, and task cards returned collapsed because their auto-open state was local to the mount. Persist the card auto-open state, mount a known-open body synchronously, and key the virtualizer by row key instead of row identity. Measured on a three-subagent run: scroll height dropped 703px and scrollTop snapped to 0 at the handoff, then regrew. The same run no longer moves the scroll position at the handoff. The transcript now keeps its position and the task cards stay expanded while subagents run.
Code Review SummaryStatus: No Issues Found | Recommendation: Merge Incremental review of Files Reviewed (1 file)
Previous Review Summaries (4 snapshots, latest commit 5ceef03)Current summary above is authoritative. Previous snapshots are kept for context only. Previous review (commit 5ceef03)Status: No Issues Found | Recommendation: Merge Incremental review of The reworked hydration tests now drive the extracted production helper ( The store writes remain bounded by the existing 2000-entry FIFO in Files Reviewed (3 files)
Previous review (commit b98ed5c)Status: No Issues Found | Recommendation: Merge Incremental review of
The shared Files Reviewed (3 files)
Previous review (commit e4f66af)Status: 3 Issues Found | Recommendation: Address before merge Overview
Issue Details (click to expand)WARNING
SUGGESTION
Files Reviewed (6 files)
No new changed code since the previous review ( Fix these issues in Kilo Cloud Previous review (commit b4df88d)Status: 3 Issues Found | Recommendation: Address before merge Overview
Issue Details (click to expand)WARNING
SUGGESTION
Files Reviewed (5 files)
Notes: the virtualizer change ( Reviewed by deepseek-v4.1-flash · Input: 36.9K · Output: 9.2K · Cached: 249.3K Review guidance: REVIEW.md from base branch |
The virtualizer child and the live tail rendered the same TranscriptRowView props, which the duplication check flagged once both read the row through a key lookup. Extract a single Row component so the two paths cannot drift.
Record the collapse when a foreground card is promoted to a background task, so a later remount does not read the earlier open value and expand a card that must stay collapsed. Defer the body only when the stored state is open, so a card the user collapsed still mounts its body lazily.
A kilo-vscode unit test cannot import the kilo-ui component module: that file has no jsxImportSource pragma and relies on kilo-ui's tsconfig, so Bun resolved react/jsx-dev-runtime for it and the test run failed on CI without React installed. Move the persist decision into a dependency-free helper in task-tool-state and cover it there, along with the promoted background collapse.
The swap test required the spinner to still be at its starting position in the first frame it could measure. That frame is not guaranteed to be the frame of the swap: it drifts by a frame or two between machines, and each frame of drift is one step of the width glide, so the test failed on CI with a 7px move that is only the animation progressing. Assert the shape of the motion instead. The glide covers a fraction of the distance in the first observed frame and is still travelling when the sampled frames end, while a teleport covers all of it at once. Verified that the new assertions still fail when the glide is made near-instant.
What Problem This Solves
While subagents run, the chat transcript flickered up and down. The jump happened the moment the parent started its next assistant step (after subagents returned, or between tool rounds), and again when the turn completed.
Why This Change Was Made
Instrumenting a live three-subagent run in an isolated VS Code instance (sidebar and Agent Manager share the same
MessageListand task-card renderer) with per-framescrollTop,scrollHeight, and per-element height observers showed two deterministic layout collapses, both from rows remounting:partitionRowshands the previous assistant message from the live tail to Virtua when a new step starts. The cards remounted there, but their auto-open was a localsetOpen(true), soinitialOpen()found nothing stored andauto()was alreadyfalsefor the completed task. Three 260px cards came back collapsed: scroll height 1260px to 557px andscrollTop703 to 0 in one frame. The transcript then regrew as the next message streamed.transcriptRowsrebuilds row objects when turn meta changes (liveflips,copyanchor). Virtua's<For>keys by object identity, so all rows remounted at the 260px estimate until measured: a 708px shrink followed by a 708px regrow.A third, smaller transient remained after restoring the open state:
BasicTooldeferpainted a remounted card at header height for one frame (28px to 260px) before its body landed, which still moved the pinned scroll position.Changes
rememberOpen) so it survives a remount instead of being re-derived from the now-falseauto().defer={!remount}), so the first painted frame is already at full height.User Impact
The transcript keeps its scroll position while subagents run, and task cards stay expanded across assistant steps. No more up and down bouncing when the parent resumes after a subagent returns.
Evidence
Measured with a per-frame recorder in isolated VS Code, same prompt fanning out three explore subagents, before and after:
scrollHeight-703px,scrollTop703 to 0scrollHeight-642px then +644pxscrollHeight-708px then +708pxbun run typecheck,bun run lint,bun run knip,bun run check-kilocode-change, andbun run test:unit(5713 pass) inpackages/kilo-vscode/.Caveat
In one of four runs a single card body shrank for one frame when its child finished, as if the result markdown briefly replaced the child list. It did not recur with finer instrumentation and is left for follow-up.