fix(desktop): stop every chat row re-rendering on each streamed token
Refs SUPER-2637 (chat lag when scrolling and typing in Chat v3).
Problem
TranscriptRowItem is memoized, but its comparator checked snapshot by identity. useChatSession replaces the snapshot once per animation frame while an agent streams, so every mounted row re-rendered on every streamed frame, even when nothing it shows had changed.
Rows stay mounted once seen (useTranscriptWindow only grows, and useNearRows keeps seen rows rendered). After a reader scrolls through a long chat, a stream therefore re-renders hundreds of rows per frame. That starves the main thread, which is the "typing and scrolling lag" in the report.
Change
TurnGroupSection now reads the snapshot only through rowSnapshotView(row, snapshot). It returns the harness, the live text of the row's items (or of each tool-run step), and, for approval rows, the resolved asker and target. The row memo compares the previous and next views (sameRowSnapshot), so the comparator cannot drift from what the row renders.
Nested components that need the live snapshot already read it from NestedItemsProvider, which this change does not touch.
Measurements
These come from a synthetic benchmark, not the running app. It renders the real Transcript (happy-dom, React Profiler and DevTools-hook render counts) with a generated chat: each turn is a user message, reasoning, 3 tools and a markdown reply with a code fence. It then applies 60 text deltas to the streaming reply through the real reduceMany. Two samples each:
| Scenario | Row renders / frame | React commit ms / frame | Wall ms / frame |
|---|---|---|---|
| 150 turns, at bottom: before | 30 | 4.4 / 5.2 | 25 / 30 |
| 150 turns, at bottom: after | 1 | 5.0 / 3.0 | 26 / 21 |
| 150 turns, scrolled through: before | 750 | 18.0 / 18.4 | 86 / 91 |
| 150 turns, scrolled through: after | 1 | 7.6 / 14.6 | 55 / 62 |
| 300 turns, scrolled through: before | 1500 | 35.2 / 35.5 | 172 / 173 |
| 300 turns, scrolled through: after | 1 | 11.1 / 13.4 | 101 / 115 |
The remaining cost when scrolled through is reconciling every mounted MessageScroller.Item about 5 times per frame. The window never shrinks; that is tracked as the next fix on the investigation page.
Validation
bun test sameRowSnapshot: new unit test, 5 passbun test src/renderer/routes/_authenticated/_dashboard/v2-workspace: 1205 pass, 0 fail- Benchmark rerun after the review refactor: still 1 row render per frame (7.1 ms React at 150 turns, 11.5 ms at 300)
bunx tsc --noEmitinapps/desktop: cleanbiome checkon the changed folder: clean- Not verified end-to-end in the desktop app. The numbers above are synthetic.
Summary by CodeRabbit
- Bug Fixes
- Chat transcripts now reflect changes to the relevant message text and tool reasoning without being affected by unrelated row updates.
- Approval rows now update when the related tool call changes, and transcript rows reflect changes to the session harness.
- Reply and tool-run rows display the text associated with their respective transcript entries.