Agent pane: the bottom stack on one edge as one glass, with a compact N PRs control back

TE
5d ago
+1783-148

Leo, 2026-10-30, on the agent pane's bottom stack:

"ugh it's so ugly
i almost hate having these running things all at the bottom but idk what we can do better
the session info bar is there i guess... maybe i just dont like the styling
and the fact that im still on the version with the stupid f***ing gap at the bottom plus the stupid right indent misalignemnt"

"and these pr things idk if we care to have them at all"

"you guys fing removed the # prs thing entirely instead of just simply fing doing it"

"like / you all f***ing just ripped that out entirely instead of / idk / doing it well"

Follow-ups: "stupid" / "whatever" / "idek what's the best ui here"

And on an earlier head of this PR, which gave the count a full-width row:

"BUT IF IT'S ONLY N PRS DON'T FIGN TAKE THE ENTIRE ROW AND FING BLANK IT OUT"

What changed

The chat's pull requests are back as a compact 2 PRs › control (summary/PullRequestStack.tsx).

  • Folded, it takes no row. It is one content-sized button (the PR mark, the count and a chevron) in the composer's quiet row, right of the permission mode, which is a row that is already there. The stack is exactly as tall with it as with no pull requests, with or without the running tray. Its hover and press pill covers only the control.
  • Its click opens the chips, on the click, with no fade-in. There is one chip per pull request at the composer card's full width, right over the composer, each with the reply links' hover card. The click no longer opens Session Info > Changes, which stays the full list.
  • No control repeats on every row. A chip is its link alone: no ×, not even on hover. A row's own menu (right-click, or Shift-F10 on a focused row) holds its one action, "Dismiss pull request #N", and Delete or Backspace on a focused row does the same. Dismissing hides only that shortcut, using the dock's existing per-chat dismissal list; the count still names every pull request Changes names, and Session Info > Changes keeps its own per-PR Dismiss.
  • No pull requests without the composer. The count and the chips share one provider that is the composer element's !reviewing && (…) branch in App.tsx, so nothing shows in a handoff review, while the Sandboxed receipt shows, in a subagent's read-only pane or in the Quick Composer.
  • The tray and the background bar stay. #29614 (base) has since removed #26082's per-message pills (Leo, 2026-10-31: "a) remove the chips they're so bad"); this PR keeps that removal, and the count, which is not on a message's line, is now the chat's one PR shortcut over the composer.

Every card over the composer (the tray, open or folded, and the opened chips) stands on the composer card's two edges, --acpmux-stack-gap apart (bottomStack.css). The selected stack look is one glass: no gap, square joins and one hairline between parts, the way the queue already meets the card. The tray's header chevron and each line's chevron share one x.

Gallery (agent-pane.bottom-stack-styles)

Every variant uses synthetic data: nine background subagents, one chat waiting on you, two PRs and the composer.

  • Where the folded count sits:
    • Quiet row (selected, live): collapsed, no-tray, expanded and prs-open.
    • On the tray's header line, trailing before its time (in-tray). With no tray it falls back to the quiet row (in-tray-no-tray).
    • Over the composer's left edge, a bare control with no row background (above, above-no-tray).
  • Stack look: one glass (selected), and separate cards 4px apart (cards).

Why the quiet row: it is the only one of the three that adds no row in every case and needs no fallback. "Over the left edge" adds a 28px line (the stack measures 144.8px against 116.8px). "On the tray's line" adds no row but competes with the tray's own title and time for the header, crowds it at 420px, and has to move to another place whenever the tray is absent, so the control would jump between two homes. The quiet row is always present under the card, already holds per-chat chips with the same pill, and has free room between the mode on the left and the model on the right.

Each item: fixed here or already on base

ItemStatus
Gap under the collapsed "Running in background" barAlready on base: #27103 removed the tray's 8px bottom margin, and #27672 removed the 34px floor. On base the tray stands 0px on the card at 420, 760 and 1100.
Right indent: the bar's right edge stops short of the cardAlready on base for the tray's edge (#26068's --cv-inset-end): 0px at all three widths. The geometry test now pins it for every bottom card.
Right indent between stacked chevronsFixed here. An earlier head put a row's chevron 25px right of the tray header's; the count now lives in the quiet row, and the tray's header and line chevrons are held to one x.
PR chips stacked above the composer, each a different widthFixed here: chips open at the card's full width (0px off both edges).
"# PRs" control removed by #26082Fixed here, restored as a compact control that adds no row.
PRs showing without the composer (handoff review, Sandboxed receipt)Fixed here (review finding on an earlier head).
Running tray durations on one line (#28133)Landed on base (094dd560); this PR builds on it.

Measurements (gallery, Chromium, test/agent-pane-bottom-stack-edges.test.ts)

1100760420
Stack height: count folded / no PRs (with tray)116.8 / 116.8116.8 / 116.8144.8 / 144.8
Stack height: count folded / no PRs (no tray)88.8 / 88.888.8 / 88.888.8 / 88.8
Count width / its content86.8 / 86.886.8 / 86.886.8 / 86.8
Tray and chips, left / right off the card0 / 00 / 00 / 0
Gaps between parts0 (token 0)00
Tray chevrons (header + 10 lines), spread of x000
Quiet row to the pane's bottom888

Before (base ac6b39be): no count anywhere. The tray was already 0/0 off the card with a 0 gap. The bars before #26082 were 291px and 394px wide at 760px (429px and 326px short of the right edge).

Stills (2x, light and dark, synthetic data)

Viewed after image: https://raw.githubusercontent.com/manaflow-ai/cmux/pr-media/28533/v2-collapsed-dark.png and https://raw.githubusercontent.com/manaflow-ai/cmux/pr-media/28533/ux-bs3-28533-prs-open-light.png (the hovered chip lights, with no × on it or any row).

Visual verdict (ux-owner, Claude Opus 5.5): SHIP at 310620504c049a84351afbe56f94b7fd02ffc2fe. Viewed v2-collapsed-light, v2-no-tray-dark and v2-prs-open-light. The folded "2 PRs ›" is one quiet inline control in the composer's quiet row with no blank row, the open state reads as one surface, and nothing is repeated, clipped or off-screen.

Review note. An earlier head drew a hover-only × on every chip. Review BLOCKed it at 1b674d05 on #28587 as a control repeated on every row, and Leo's words on this surface never asked for a per-row × (his quotes above are about the count and the row it took). Since e61c418e the rows carry no control: dismissal stays (nothing is removed) but moved into each row's own menu and its Delete key. Visual verdict above predates this; the new opened state is the prs-open row below. The folded control is one content-sized button that adds no row, and its hover/press highlight covers only that control.

LightDark
Before (base): folded tray
Before #26082: PR bars
After (selected): folded tray, 2 PRs in the quiet row
After (selected): no tray
After (selected): open trayCapture withdrawn: the earlier stills (this row and base's open tray) showed a Stop on every running row. At 8cb59b1abdf3 the rows carry no Stop; each running line stops from its own right-click menu (Shift-F10 on the focused line), shown by the row-menu variant in webviews/src/agent-session/acpmux/subagents/SubagentTray.gallery.ts.(same)
After (selected): PRs opened, first row hovered (no control on any row)
Alternative: on the tray's header line
Alternative: on the tray's line, no tray (falls back)
Alternative: over the composer's left edge
Alternative: over the left edge, no tray
Alternative stack look: separate cards

Tests

  • test/agent-pane-bottom-stack-edges.test.ts, at 1100/760/420:
    • every bottom card is within 1px of the card's edges, with every gap equal to --acpmux-stack-gap;
    • the stack is as tall with the folded count as without PRs, with and without the tray;
    • the count sits in the quiet row and is as wide as its content;
    • stacked chevrons share one x within 1px;
    • opened chips are at full width.
    • Its second test covers the click itself: the count's own pill lights but the quiet row's doesn't; the list opens with no running animation at opacity 1; no row holds a button or second link at rest, hovered or focused; Tab goes from row to row; Shift-F10 on a row opens its menu with its one item, "Dismiss pull request #129", and Return dismisses it, focus moving to the row now in its place; Delete dismisses the last row and focus goes on to the composer's prompt; the count still names both PRs; and the per-message pills remain.
  • test/agent-pane-pr-stack-gate.test.ts reads App.tsx's syntax tree:
    • the provider is mounted once, inside the composer element, and holds every part;
    • it sits in the !reviewing branch;
    • every place composer is drawn is guarded by !viewedSandbox;
    • the Quick Composer gets no PRs.
  • Red on base 93c712e1 (run 41130597970): there was no count and no stack gap token.
  • Green at 3ed11dbd (run 41138572319): 135 pass, 0 fail, across those two tests and the stack's neighbours: agent-pane-column-edges, pr-dock-inline-layout, agent-pane-bottom-stack, subagent-tray-layout, agent-pane-dock-gutters, agent-pane-pr-dock, subagent-tray, composer-bottom-space, pr-dock-stale-state, composer-foot-pill-inset and composer-alignment-centers.
  • Green at the head 31062050 (run 41140838570): the two new files, 7 pass. That head differs from 3ed11dbd only in the stills step.
  • Green at the head e61c418e: the two new files pass with #28587's tests on top (run 41170446218, 56/56 at c86b0e6b, which carries this head unchanged). At 0b58acb1 the right-click step failed: the second row's hover card, opened by its keyboard focus, sat over the first row, so the test now opens the menu from the keyboard (run 41169110770).
  • scripts/web-lint.sh and prepush-gate.py pass.

Observed / Not run

  • gallery-coverage.test.ts fails with the same 108 lines on base ac6b39be and on this branch, so it is a base red, compared line by line.
  • Not run: a native build or a Mac capture. This is a web-only change, and the stills come from the gallery.

Overlap: Austin's open #27930 moves the tray's chevron past its Stop slot to the right edge. This PR doesn't touch the tray's tracks, and with the count out of the stack no row here depends on that column, so the two land in either order.

Spec: design/composer.md rules 9 and 10 and its "Bottom stack" section, plus session-info.md and session-panel.md, whose old line is now listed under Replaced (leo-workspace#1751). This PR maps bottomStack.css and the PR stack to the composer surface, and adds a running-tray surface, in .github/design-surfaces.json.

Takes back #26082: only its removal of the dock's collapsed "N PRs" count, which Leo did not ask for ("you guys f***ing removed the # prs thing entirely"). Its per-message pills stay removed, as #29614 left them.

Risk: normal. This is web only. A chat with no pull requests renders exactly as before, and the count adds no row.

Changelog

Changed: the chat's pull requests are back as a compact "N PRs" control in the composer's footer that opens to full-width links, and the parts over the composer share its edges as one glass stack.

Agent session: agent session

🤖 Generated with Claude Code