Skip to content

perf(desktop): virtual list with Virtuoso for message thread#2056

Merged
esengine merged 1 commit into
esengine:mainfrom
CVEngineer66:pr-b65cb55a
May 27, 2026
Merged

perf(desktop): virtual list with Virtuoso for message thread#2056
esengine merged 1 commit into
esengine:mainfrom
CVEngineer66:pr-b65cb55a

Conversation

@CVEngineer66

@CVEngineer66 CVEngineer66 commented May 27, 2026

Copy link
Copy Markdown
Contributor

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-virtuoso virtual 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).

@esengine

Copy link
Copy Markdown
Owner

This now conflicts with #2055 (just merged) — both rewrote the streaming reducer cases in desktop/src/App.tsx. The backwards-walk reducers + virtuoso virtual list are actually the right combo for the long-session pressure (#2001, #2012, #2013), so I'd love to take both — could you rebase onto current main?

One thing to think about while you're at it: Virtuoso needs stable, monotonic item heights for followOutput="auto" to track the bottom correctly when streaming. The current state.messages[index] lookup re-renders the same item on every reducer update, which Virtuoso will then re-measure. If you can wrap itemContent in a memoized renderer keyed by (turn, segment count, pending), the scroll-anchor stability gets much more predictable on fast streams.

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.

@CVEngineer66

Copy link
Copy Markdown
Contributor Author

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).

@esengine esengine merged commit fa66bc4 into esengine:main May 27, 2026
4 checks passed
esengine pushed a commit that referenced this pull request May 29, 2026
…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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants