fix(agent-pane): the summary card holds still while the chat scrolls
Leo, 2026-10-25: "session summary on top right flickers when im scrolling thru the chat"
Leo, 2026-10-22: "did we get this top right side summary fixed yet? also the initial load is kinda slow it like flickers the initial thing/paints and renders it..." (This PR covers the summary half. The initial-load and black-screen half belongs to another lane.)
Cause
Scrolling over rows that are already loaded changes nothing in the card; base is steady there, and the browser test shows it. The flicker comes from scrolling up. Attach sends only a chat's newest records. As the reader scrolls up, direct.ts loadOlder pages in older history, and each page changes snapshot.rows, which the Summary card and Session Info read. Page after page, mid-gesture, the card:
- slid in from its reserved column;
- flipped the header toggle's
aria-pressed; - added head and body sections;
- grew. Session Info went from 74 px to 275 px tall.
Fix
summary/heldWhileScrolling.ts: one page-wide ReaderScroll flag. It is set by the reader's own scrolls (onScroll when the scroll is not programmatic) and by the reader's input (wheel, keys, touch, pointer). Input counts because wheeling at the very top of the loaded rows fires no scroll event while the older page lands.
useHeldWhileScrolling(snapshot.rows, shownSessionId)gives the Summary card, its content flag and Session Info the rows as they stood when the scroll began.- They take what the scroll paged in once,
ScrollPacing.settleMs(250 ms) after the last reader input. - A streamed reply that keeps the view at its end, Jump to latest's glide and every live update outside a reader scroll still land at once.
- Another chat takes its own rows at once.
Timer: one setTimeout, re-armed on each reader event, so only one is ever pending.
- Stop condition: it fires 250 ms after the last reader event and is not re-armed.
- Cleanup:
releaseReaderScrollclears it. It runs from auseEffectcleanup keyed onshownSessionId, so on a chat change and on unmount.
The flag notifies twice per gesture (on, then off), never once per scroll event. No new work runs per scroll frame or per token.
Performance
Harness: test/summary-scroll-steady.test.ts, run in real Chromium and WebKit on fast-checks. It watches 100 painted frames of wheel scrolling over a 60-turn chat whose attach sends 40 turns, so older history pages in under the scroll. Unit harness: test/summary-held-while-scrolling.test.tsx.
| metric | before (91684367) | after (5adcbd7) | delta | harness or run link |
|---|---|---|---|---|
| DOM changes in the inline Summary card and toggle while history pages in (100 frames, Chromium) | 15 | 0 | -15 | before, after |
| Session Info boxes seen while history pages in (Chromium) | 2 (74 ā 275 px tall) | 1 | -1 | same |
| Card re-renders with new rows during a 100-event scroll with 10 pages landing (unit) | 10 | 0 (1 on settle) | -10 | summary-held-while-scrolling.test.tsx |
| DOM changes over 100 frames of plain scrolling (inline, popover, Session Info) | 0 | 0 | 0 | same browser test |
Before / after
Filmstrips are being captured (they follow in an edit).
Tests
- Red at 91684367, green at the head:
cd webviews && CMUX_BROWSER_TESTS=1 bun test test/summary-scroll-steady.test.ts. 10 tests across Chromium and WebKit. The two "while older history pages in" cases fail at 91684367 and pass at the head. The plain-scroll cases pass at both. bun test test/summary-held-while-scrolling.test.tsx: the flag notifies twice per gesture. The card is handed the same rows through 100 scroll events while 10 history pages land, takes them once on settle, and another chat's rows land at once.bun run checkpasses at the head, in the same fast-checks run (41191863690), together with both test files.
Observed: the fast-checks runs above (Linux, headless Chromium and WebKit, the stand-in acpmux from test/support/papercutChat.ts).
Not run: the native app on a Mac. The change is webview-only.
Design
The rule is added to the Session Info spec (design/session-panel.md) in leo-workspace: the right-hand card never changes under the reader's own scroll. This PR is a behaviour fix with no visual design choice, so it has no gallery variants.
Risk: normal. Webview-only, and it touches only what the right-hand card reads. Worst case, the card shows new content 250 ms after a reader scroll ends instead of mid-scroll.
Agent session: agent session
š¤ Generated with Claude Code
![View with [code]smith](https://pr-comments-assets.blacksmith.sh/codesmith/view-with-codesmith-dark-v3-3x.png)
![Autofix with [code]smith](https://pr-comments-assets.blacksmith.sh/codesmith/autofix-with-codesmith-dark-3x.png)
Need help on this PR? Tag @codesmith-bot with what you need.
Summary by cubic
Fixes the Summary card, Session Info, and the card's Changes list flickering while scrolling through a long chat. Scrolling up pages older history in, and each page that landed mid-gesture changed the card: it slid in, flipped the header toggle, added sections, and grew.
The card now reads the chat's rows as they stood when the scroll began and takes what the scroll paged in once, 250 ms after the last reader scroll or input. The Changes list is held the same way, so an older edit paged in under a scroll no longer changes a showing card mid-gesture. Streamed replies, Jump to latest, and live updates outside a reader scroll still land immediately, and another chat's rows land at once.
Tests
- Browser tests in real Chromium and WebKit watch 100 painted frames of wheel scrolling and assert no DOM changes in the card, including while older history pages in and an older edit pages in under a showing card.
- Unit tests cover the hold flag notifying twice per gesture and the settle timer with a manual clock.