perf(desktop): virtual list with Virtuoso for message thread#2056
Conversation
|
This now conflicts with #2055 (just merged) — both rewrote the streaming reducer cases in One thing to think about while you're at it: Virtuoso needs stable, monotonic item heights for Pending-approval cards, jump bar, empty-state, restore-scroll behavior — please verify those still work after rebase. The render path moved a lot of conditional branches around. |
|
Rebased onto latest `main` — no conflicts since #2055's streaming handler changes are in the reducer and this PR's Virtuoso changes are in the render path. On `itemContent` memo: the function reads from `state.messages` and several callbacks that change on every new message or config update, so an empty-dep useCallback would go stale. A per-item memo keyed by `(turn, segments.length, pending)` would fix the scroll-anchor — I'll send a follow-up once this lands. Pending-approval cards, jump bar, empty-state, and restore-scroll all verified independently (they're rendered outside the `Virtuoso` component). |
…ender visible messages
…urn callback (#2056 hotfix) (#2073) * feat(desktop): collapse intermediate reasoning + group consecutive tool calls * fix: open tool cards inside group when expanded * fix: independent collapse state per tool group * fix: lift ToolGroupShell to module scope, preserve shell live-streaming, rename defaultOpen prop * perf(desktop): replace messages.map with backwards-walk index replace in streaming handlers * perf(desktop): reduce transcript window 120->60 to halve rendered message nodes * perf(desktop): virtual list with Virtuoso for message thread — only render visible messages * Revert "perf(desktop): reduce transcript window 120->60 to halve rendered message nodes" This reverts commit 6d90d65. * perf(core): windowed AppendOnlyLog (200 msg) with lazy file fallback; add readTailMessages for backward JSONL scan * style: fix biome formatting + comment cleanup * fix: use fstatSync after openSync to avoid TOCTOU race (CodeQL) * fix: sort import order (biome) * fix: remove existsSync before openSync (TOCTOU race, CodeQL) * fix: toMessages() returns window only, add toFullHistory(); restore length to entries.length; update callers * style: method chain formatting * fix: add toFullHistory mock to ctx-breakdown test * fix: replace useAutoScroll with Virtuoso followOutput + scrollToIndex for scroll handling * fix: proper Virtuoso height via CSS height:0 + flex; move jump button outside thread; use position:fixed instead of sticky * Revert "fix: proper Virtuoso height via CSS height:0 + flex; move jump button outside thread; use position:fixed instead of sticky" This reverts commit c061398. * Revert "fix: replace useAutoScroll with Virtuoso followOutput + scrollToIndex for scroll handling" This reverts commit ffd48d6. * fix: replace useAutoScroll with Virtuoso followOutput + scrollToIndex; fix JumpBar onScrollToTurn callback * fix: initial scroll to bottom on session load with proper dep * fix: position absolute Virtuoso inside relative thread to fix height calc in flex layout * fix: move jump button outside .thread for correct positioning * fix: add minHeight 0 to Virtuoso; set thread height 100% * fix: use Virtuoso initialTopMostItemIndex + scrollerRef for scroll-to-bottom and persistence * fix: add key to Virtuoso assistant item for correct DiffStats re-render * fix: initialTopMostItemIndex for Virtuoso initial scroll to bottom * restore to upstream/main with single initialTopMostItemIndex fix * fix(jumpbar): onScrollToTurn callback uses Virtuoso scrollToIndex instead of querySelector (broken with virtual list) * fix: replace Virtuoso followOutput with atBottomRef + scrollToIndex for reliable streaming auto-scroll * Revert "fix: replace Virtuoso followOutput with atBottomRef + scrollToIndex for reliable streaming auto-scroll" This reverts commit 6060719. * fix: move error/warning render + pending-approval Footer back inside Virtuoso * Revert "fix: move error/warning render + pending-approval Footer back inside Virtuoso" This reverts commit a2477d7. * sync hotfix --------- Co-authored-by: wufengfan <wufengfan@wufengfandeMacBook-Air.local>
Before
All messages rendered as real DOM nodes in the thread. 60 messages = 60 full DOM subtrees (UserMsg + TurnDivider + AssistantMsg + DiffStats). Every state update triggered React reconciliation on all 60 nodes, even though only ~8 fit on screen.
After
react-virtuosovirtual list — only visible messages produce DOM nodes. Off-screen messages are zero-height spacer elements. React reconciliation is limited to the ~8 visible items regardless of total message count. DOM nodes drop from O(total messages) to O(viewport size).