Agent chat: closing the queue at the latest row keeps the last row on the composer
Leo, 2026-10-21: "there's something just a bit awkward about the UI of this queuing and steering and expanding and, like, scrolling. It doesn't scroll to the very bottom. I mean, it's almost like it's allowed to, but then it just shoots back up ... it cuts off at the bottom."
Leo-ask: ask-a3f974b779
Leo-ask: ask-5ea5632ee9 (the new lines in the composer make the scrollbar jump)
Leo-ask: ask-a532e95ee7 (the wheel beside the composer should just scroll)
What was wrong
When you clicked Show less on a long queue at the latest message, the last row stayed a fold row (about 22 px) above the composer for a second. Then the rows dropped down onto it. Scrolling down in that second did nothing, because the view was already at its end.
Root cause: VirtualTranscript.noteQueue (webviews/src/agent-session/acpmux/App.tsx:1824 on feat-cmux-next 1f4437e6f08) collects the prompt ids of the .acpmux-queued cards in the bottom stack. Any id that disappears is treated as a sent prompt on its way into the transcript (heldFor / queueLeft), and the room under the last row is kept for it (floor, FLOOR_HOLD_MS = 1 s). Closing the fold unmounts rows 3 to N (ComposerQueue.tsx, visible = shown.slice(0, steering + FOLD_SHOWS)), so every hidden row looked like a card that had left. The view held a stack's worth of room that no prompt would ever fill, then glided down when the hold ran out.
Fix
ComposerQueueputs the ids its fold hides on the list asdata-folded-ids.noteQueuecounts those prompts as still queued.
Closing the fold now shrinks the room in the same frame, just as opening it grows the room. A card that really leaves (Steer, a sent prompt) still keeps its room as before.
What I checked and left alone
- Newline in the composer (ask-5ea5632ee9): already fixed on base by #23097, #23186 and #23302. With the latest row in view and scrolled up, Shift-Return leaves
scrollTopand the distance from the end unchanged in every painted frame. The new test pins this in place. - Wheel beside the composer (ask-a532e95ee7): already fixed on base by #22341 and #23243 (
pointer-events: noneon the floating stack, andwheelPassThrough.ts). The wheel left and right of the queue, its fold bar and the card scrolls the transcript. The new test pins this for all three bands. - Open: #23807 (teamleaderleo, "the view stays pinned to the latest line as it grows") changes the follow and glide code in the same
VirtualTranscript, but notnoteQueueor the queue. The two changes don't overlap.
Changelog
- Fixed: Closing a long queue's "Show less" at the latest message keeps the last row on the composer instead of leaving a gap that drops a second later.
Test
New webviews/test/agent-pane-queue-scroll-bottom.test.ts, run in Chromium and WebKit. It has its own stand-in acpmux, one chat of 40 turns plus a running one, and six queued prompts. Every read is of a painted frame (support/paintedFrames.ts).
cd webviews && CMUX_BROWSER_TESTS=1 bun test test/agent-pane-queue-scroll-bottom.test.ts
- Red on base (feat-cmux-next 87bf8e9656c plus the test commit): 6 pass, 2 fail. "the queue closed at the latest row ..." fails in both engines: the frames after the click hold the last row at
gap46.25 px against its rest of 24.25 px (45.25 against 23.25 in WebKit). - Green on head: 8 pass, 0 fail.
The four cases:
- Queue opened, then wheel scrolled to the end: the last row is whole above the stack in every frame for 500 ms.
- Queue closed at the latest row: no frame in 1.5 s keeps the room.
- A new line in the composer:
fromEndandscrollTopstay the same, both at the latest row and scrolled up. - The wheel left and right of the queue, its fold bar and the card: the pointer reaches the transcript, which scrolls both ways.
Observed / Not run
Observed on huge-blue, with the head over feat-cmux-next 87bf8e9656c:
bun run checkpasses. One base file,test/subagent-stopped-after-cancel.test.ts, has a formatting issue on base, and I masked it for the run.- With
CMUX_BROWSER_TESTS=1, these pass on the head:agent-pane-queue-collapse13/13,agent-pane-queue-steer7/7,agent-pane-queue6/6,agent-pane-bottom-stack2/2,agent-pane-scrollbar-full-height10/10,agent-pane-permission-queue2/2 and the new file 8/8. agent-pane-dock-guttersandagent-pane-wheel-pass-throughtime out inbeforeAllon huge-blue. Their sharedpapercutPanewaits for every finite animation to end, and the running turn's line never settles there. This happens on base too, before any change. The new test gets its gutter coverage through its own fixture.
Not run: the app itself (no tagged build) and an independent review.
No capture: webview-only change. The before and after frames (300 ms after Show less) are kept privately by the lane.
Agent session: cosmic-gray
🤖 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 agent chat transcript holding phantom gaps after closing "Show less" on a long queue, and when a queued prompt runs on another client while its card is out of sight.
ComposerQueuereports the prompts its fold hides as still queued (data-folded-ids), so closing the fold shrinks the room under the last row in the same frame instead of holding it for about a second.VirtualTranscriptre-notes the queued prompts on each queue change, not only on resize, so a prompt run elsewhere while folded or past the list's cap leaves no phantom room when the stack shrinks later.noteQueuenow tracks the chat and stack it noted, so switching to a chat with its own queue no longer holds room for the previous chat's prompts.- Queue prompt IDs reach
VirtualTranscriptthrough a memoized snapshot (queueIds), so the notes re-run only when the queue changes.
Tests
New browser test covers closing the queue at the latest row, opening and scrolling, prompts run elsewhere, switching chats, newlines in the composer, and the wheel beside the composer; it passes in Chromium and WebKit, with the queue-close case failing on base.