fix(scroll): coalesce pinned-mode shrinks to stop double-Esc flicker#666
Conversation
When an Esc abort tears down multiple subagent cards at once, each card's re-measurement called setMaxScroll synchronously, snapping scrollRows N times and bouncing the viewport past one screen. Holding a trailing-edge target on a microtask flush collapses the burst into one settled transition while keeping grows immediate so streaming output stays pinned without latency.
|
Thanks — clean fix, and the test split is exactly what I'd want for this layer. The cause-analysis matches hypothesis #1 in #653 (N subagent cards collapsing in the same render burst), and piggybacking on the existing Worth calling out for anyone reading the diff later: keeping grows synchronous matters more than it might look. Pinned streaming during a long completion can't pay 16ms of latency per maxScroll bump, or the bottom-pin visibly lags the cursor. Glad to see that's preserved. Five-case test coverage (burst, grow-immediate, unpinned-still-synchronous, grow-flushes-shrink, jumpToBottom-cancels) hits every invariant I'd have asked for. Merging. |
…il, CardStream fix (#705) npm-only release. The Tauri desktop source is in the repo and the CLI subcommand works, but installer bundles for macOS / Windows / Linux don't ship this round (separate release once signing's settled). Highlights: - Tauri desktop client with multi-tab concurrent runtimes (#689) plus a near-full polish pass: wallet balance, version chip, active- plan rail, abortable pause-gates, edit-gate pill, en + zh-CN i18n, shared pause-policy module dedup'd with the CLI TUI (#701) - checkpoint API + git-changes panel in the embedded dashboard (#682) - outside-sandbox file access approval modal (#696) - MCP loading pill + readiness gate on tool dispatch (#687) - escalate-after flag for flash → pro threshold (#699) Fixes: - CardStream Maximum-update-depth crash, quantize window so boundary cards stop oscillating (#700, #702) - `reasonix code` bridges config key to env + lazy subagent client so fresh installs can reach the setup wizard (#703) - pinned-mode scroll shrinks coalesced (#666), generic CSI key decode (#692), shell-confirm preview clamp (#691), frontmatter BOM/folded lines (#690), MCP error classification (#688), and more
…sengine#666) When an Esc abort tears down multiple subagent cards at once, each card's re-measurement called setMaxScroll synchronously, snapping scrollRows N times and bouncing the viewport past one screen. Holding a trailing-edge target on a microtask flush collapses the burst into one settled transition while keeping grows immediate so streaming output stays pinned without latency.
…il, CardStream fix (esengine#705) npm-only release. The Tauri desktop source is in the repo and the CLI subcommand works, but installer bundles for macOS / Windows / Linux don't ship this round (separate release once signing's settled). Highlights: - Tauri desktop client with multi-tab concurrent runtimes (esengine#689) plus a near-full polish pass: wallet balance, version chip, active- plan rail, abortable pause-gates, edit-gate pill, en + zh-CN i18n, shared pause-policy module dedup'd with the CLI TUI (esengine#701) - checkpoint API + git-changes panel in the embedded dashboard (esengine#682) - outside-sandbox file access approval modal (esengine#696) - MCP loading pill + readiness gate on tool dispatch (esengine#687) - escalate-after flag for flash → pro threshold (esengine#699) Fixes: - CardStream Maximum-update-depth crash, quantize window so boundary cards stop oscillating (esengine#700, esengine#702) - `reasonix code` bridges config key to env + lazy subagent client so fresh installs can reach the setup wizard (esengine#703) - pinned-mode scroll shrinks coalesced (esengine#666), generic CSI key decode (esengine#692), shell-confirm preview clamp (esengine#691), frontmatter BOM/folded lines (esengine#690), MCP error classification (esengine#688), and more
Summary
Double-Esc aborting a multi-subagent run past one screen would flicker and bounce the scroll position: each card teardown re-measured and called
setMaxScrollsynchronously, snappingscrollRowsN times in pinned mode. This change coalesces those shrinks behind the sameCOALESCE_MStrailing-edge timer the delta path already uses, so a burst of re-measurements collapses into one settled transition.Changes
src/cli/ui/state/chat-scroll-store.ts: addpendingMaxShrink/shrinkTimerand aflushShrink()helper; insetMaxScroll, defer pinned-mode shrinks to a trailingsetTimeout(COALESCE_MS)flush while keeping grows immediate; flush any pending shrink before applying a non-shrink update so its trailing state can't clobber a fresh value; clear the pending shrink injumpToBottomso an explicit pin isn't undone by a stale target.tests/modal-scroll-reset.test.ts: add asetMaxScroll coalescing (issue #653)suite covering the burst-of-shrinks case (one trailing transition at the final target), grows applying immediately, the unpinned path still clampingscrollRowssynchronously, a grow flushing a pending shrink, andjumpToBottomdropping a pending shrink.Testing
npm run test -- tests/modal-scroll-reset.test.ts— the new coalescing suite passes alongside the existingjumpToBottom(issue #642) tests. The burst test usesvi.useFakeTimers()and advances 32ms to assert exactly one subscriber notification at the final target.Notes
The fix piggybacks on the existing
COALESCE_MSwindow rather than introducing a separate knob, to keep the timing model in the store consistent. Grows are intentionally left synchronous so streaming output stays pinned without added latency; only pinned-mode shrinks are deferred.Closes #653