fix(desktop): stop every chat row re-rendering on each streamed token

HA
9h ago
+215-20

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:

ScenarioRow renders / frameReact commit ms / frameWall ms / frame
150 turns, at bottom: before304.4 / 5.225 / 30
150 turns, at bottom: after15.0 / 3.026 / 21
150 turns, scrolled through: before75018.0 / 18.486 / 91
150 turns, scrolled through: after17.6 / 14.655 / 62
300 turns, scrolled through: before150035.2 / 35.5172 / 173
300 turns, scrolled through: after111.1 / 13.4101 / 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 pass
  • bun 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 --noEmit in apps/desktop: clean
  • biome check on 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.