Agent pane: the subagent tray's header stays pinned, its list scrolls under it, and a line says what the agent does
Leo, 2026-10-25, word for word:
"Okay, I think the animations and whatnot for, like, pushing in, when the possession rail pushes in the account thing, like, that's pretty decent. But this section, the whatever background tasks, that sort of area, it's kind of wack. It doesn't quite work. Likewise, I feel like, you know, when it's all just these tons of agents, that the sort of collapse at the top section, like that disappears for some reason at the top. You know, like, I don't know. It's just this, like, weird situation where sometimes it disappears, like, often it's there, but sometimes it's just sometimes it disappears. I'm not sure when it disappears, but it's, like, not always 100% reliable."
Why the header disappeared
There were two causes:
- The whole tray scrolled, header included.
bottomStack.cssgives every part of the bottom stackoverflow-y: autoandflex-shrink: 1000. A short pane, or a list of ended cards, squeezes the tray below its content height, so the<section>itself becomes a scroller. A wheel over it then scrolled the "N subagents running ⌄" row up and out. That's Leo's screenshot: the header is gone and "View all 118" sits at the bottom. The new browser test reproduces it on base: in a 120px dock, the tray scrolls 56px and the header leaves (running-strip.test.ts, "Expected 0, Received 56"). It passes on this head. - The header only drew with two or more running lines (
headed = items.length > 1). Once one of two running agents ended, the header and its collapse unmounted while the ended cards stayed (see "One running" below).
What changes
- The tray is a column that never scrolls (
overflow-y: hiddenin the dock,display: flex; flex-direction: column). The header isflex: noneand pinned at the top. The list in the middle has a fixed max height (5 lines),min-height: 0and its own scroll, and is the only part that scrolls. In a short pane it gives up its height first. View all N moved out of the list into its own pinned row at the foot. - The header shows whenever the tray has more than one line, running or ended. While a turn runs it counts what runs and what waits on you apart ("3 subagents running · 1 waiting · 1 command", existing key
summary.subagents.waiting), so a waiting chat is never called running. With nothing running it reads "Subagents", never an empty title. - Order: running first, then the agents that ended last (newest first), so the three shown are the latest. Before, the oldest ended ones showed. A nested agent stays under its parent, and the parent's place is the latest end in its subtree. Ended chats go newest first (acpmux gives no end time for a chat), and chats with no creation time go last.
- Row text: only a tool call's title is ever turned into a command, using provenance. acpmux's doing line is "plan step · latest tool title" (
hub/activity.rsdoing_line).sessionList.tsnow also carries the tool part (doingTool, fromturn.activity), and the tray changes only that part. The plan-step prose is never touched or split. A tool title that starts with a lowercase program, a path or an assignment is a shell call's command as typed. A chat's tool title has no kind from acpmux, so it also needs a known program or shell structure ("find the issue" stays words). ACP titles every other call in capitalized words ("Read AgentCard.tsx", "Run cargo test"). That command becomes "Running <program …>" (conversation/shortCommand.ts, the background-task note's short label), so "cd /Users/…/checkouts/… && sed -i …" reads "Running sed -i '' 's/old/new/' broker.ts". A running Claude subagent's action gets the same treatment, but only when its newest call is a shell call (ACP kindexecute, the newactionShellinsubagentFold.ts). A progress summary or a workflow step shows as written. Ended rows show their kind, never "Running …". The whole raw line stays the tooltip. Command rows (kind "Command") are unchanged. - Unchanged: the rail push-in animation. No component is removed, no default is flipped, and nothing fades in on open (the tray's existing
ui-enteris untouched).
Before / after
Gallery agent-pane.subagent-tray, new variants panel-two, panel-one-running and panel-118, all synthetic. The same gallery file was rendered with base and head sources (Linux Chromium, 720px, Apple System Colors dark and light). panel-118 opens View all and scrolls the list to its end.
| Before (base) | After | |
|---|---|---|
| Two running (dark) | ![]() | ![]() |
| Two running (light) | ![]() | ![]() |
| One running, three ended (dark) | ![]() | ![]() |
| One running, three ended (light) | ![]() | ![]() |
| 118 agents, all opened, list scrolled to end (dark) | ![]() | ![]() |
| 118 agents (light) | ![]() | ![]() |
Before: the raw cd … && line, the oldest ended agents first, no header once only one runs, and View all scrolls with the list. After: "Running sed -i …", the latest ended agents first, the header always there, and View all / Show fewer pinned under a divider. In the gallery's tall stage the 118 case doesn't squeeze the tray, so the base header still shows there. The squeeze is covered by the browser test above.
Three looks (experiment subagent-tray-panel, gallery Compare arms)
Five lines (shipped, pinned) | Eight lines (tall) | Ended set apart (sectioned) |
|---|---|---|
![]() | ![]() | ![]() |
I shipped Five lines. It keeps the tray's old 5-line budget, so the transcript loses no room. The eight-line look covers more of the transcript and shifts the layout when View all opens (its play reports a 0.037 layout shift). The sectioned look's dim ended names read as disabled. Light: 
Performance, observers
No new timer, observer or listener. toolDetail runs per rendered row on render (one regex test and a short-command cut), not per event, the same O(rows) render the tray already did. The experiment arm is read once per tray mount (useState initializer), not per one-second tick.
Risk
Small and contained: the tray component, its CSS and the order of its ended cards. One thing to watch: the tray no longer scrolls as a whole (overflow: clip). In a short pane the list gives up its height first. Past that the tray keeps only the stack's own one-row minimum, so the composer keeps its priority and the header, on top, is what stays.
Tests
test/subagent-tray.test.tsx(jsdom), new: with 118 agents the header is the tray's first child, outside the list scroller, and still mounted after the list scrolls to its end; View all sits after the list; running lines come first, then the latest ended. One of two running ending keeps the header and its collapse. A line shows words, never a raw shell line. The existing order expectations are updated to newest-ended first.test/running-strip.test.ts(Playwright Chromium), new: 118 agents in a 400px, a 120px and a 72px dock. View all stays whole at the foot. Wheel to the end, then force the tray's own scrollTop: the list scrolled, the tray's scrollTop is 0, the header is at the tray's top and is the element drawn at its point. It fails on base (120px: the tray scrolled 56px) and passes here.- Gallery plays:
panel-two(header in view, no "cd " detail),panel-one-running(header mounted; fails on base),panel-118(open all, scroll, header and View all in place). - Via
cmux-ci test(Linux):vp fmt --check,vp lintandtsc --noEmitare clean. subagent-tray, running-strip, dock-gutters and bottom-stack: 48/48 pass. The jsdom file adds tool-title-vs-prose (plan steps like "find the bug inX· make sure --help …" stay as written), escaped-quote, ended-row, nested-order and a completion-transition test (header node, focus, aria-expanded, aria-controls kept).agent-pane-working-countshas 2 failures, identical on base.gallery-coverageandpane-englishfail with lists identical to base (diff empty).
Agent session: cosmic-gray
🤖 Generated with Claude Code













