Repository navigation
perf(flow-chat): smooth session switching, scrolling and running tool groups - #3888
Merged
bobleer merged 9 commits intoNov 12, 2026
Merged
Conversation
…session Switching sessions remounts the transcript, which used to discard the virtualizer's measured sizes and the timeline projection. A reopened session then re-reserved estimates, and the opening reveal corrected every mounted row and dropped rows it had selected from those estimates. Keep the projection and the measured row sizes (keyed by stable item key) for the three most recently left views, keyed by the view's reader store. A mount claims its entry and returns it on unmount. Retained sizes seed the rows' reservations, so mounted rows are still measured and still replace them. Follow, anchor, opening and viewport ownership stay per mount, and scroll restoration is unchanged. In the supplied-geometry fixture, a native-history reopen goes from 7 corrections / 6 commits / 7 mounts / 3 dropped rows to 0 / 5 / 3 / 0, and a live reopen goes from 6 corrections / 6 mounts to 0 / 5. The Turn rail no longer rebuilds on every streamed flush: its dialog-turn input is kept while Turn ids and user messages are unchanged, capsule previews are parsed once per stored presentation, and FlowChatTurnRail is memoized with a stable navigation callback.
A row restored from a recently left session was measured by an earlier mount. Cover that it costs no correction, is handed back on unmount, and that its next resize while partly visible does not shift the viewport, while a row's real first measurement above the reader still does.
… groups Session switching with several streaming sessions: - Bind the viewport ResizeObserver to the scroller lifetime and keep the Turn-boundary list stable, so a streamed flush no longer rebuilds the observer and re-measures every mounted row. - Keep streaming Markdown section boundaries in a separate small cache so intermediate prefixes do not evict settled replies' sections. - Skip unchanged sources in the timeline prewarm walk. - Clean only a bounded tail for the agent companion snippet. Scroll jitter: - A row's first measurement replaces an estimate, so a row that starts above the viewport owes its whole delta instead of being judged by its estimated end. Running tool groups: - Automatic disclosure of a running group lists only in-flight members; settled calls fold into the header summary. Explicit expansion, held groups, expanded capsules and failed file revisions still list all.
…at-switch-scroll-groups # Conflicts: # src/web-ui/src/flow_chat/components/modern/useFlowChatVirtualizer.ts
…rrative A reveal or reader hold expanded a running group to every finished call, then folded it back when the hold released. The transcript crossed the viewport edge on each streamed reveal, flashing the scrollbar thumb and moving the content. Holds now keep a live group live, and only settled tool calls fold; the assistant's text and thinking stay visible.
Settled thoughts accumulated as rows while a group ran. Fold them with the finished calls; keep a streaming thought and one that just finished with nothing after it, and keep the assistant's text.
…izes - A running group keeps the member row that holds focus, a selection or a live instance, so a settling call no longer drops the reader's caret or selection. - Retained row sizes record their content width; at another width they only reserve space and their rows owe the first-measurement repair. - A retained key that leaves and returns no longer hands its estimate to the next mount as a measurement.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
FlowChat performance and presentation fixes found while two or more sessions stream at once.
Session switching while several sessions stream
ResizeObserverinVirtualMessageListis bound to the scroller lifetime and reads its callbacks through a ref, and the Turn-boundary list is kept stable. Before, every streamed flush rebuilt the observer, whose initial callback re-measured every mounted row (forced layout ~30×/s).<thousands of characters earlier, can differ in the 512-character snippet).Scroll jitter
Running tool groups
Verification
src/web-ui: 5382 flow_chat and markdown tests pass;pnpm run type-check:web,pnpm run theme:color-audit:all,pnpm run check:repo-hygienepass.pnpm run check:webstops at the existing typography audit violation indesign-system/packages/ui/src/components/Dialog/Dialog.module.css:77, which is also onmain.Remote scenarios: exercised only through local and simulated Peer surfaces in DOM fixtures; no live SSH workspace, Remote Control, Peer Device or Detached Dispatch run. No persisted shape, transport or IPC contract changes.
AI-assisted. Testing level: lightly tested — automated fixtures plus a short manual desktop run by the author; native WebView frame timing is not measured.
Not covered: the bottom-origin (native history) layout does not get the first-measurement rule; it is unchanged from
main.Known gap left as is: a scroll without an intent event (overlay scrollbar drag on WebKit) is still pulled back by follow-output while it holds the tail (documented in
FLOWCHAT_SCROLL_STABILITY.md).