cmux-next composer: one gap and grouping across the footer row

Merged
TE
22d ago
+337-19

The agent pane's controls row looked unevenly spaced. Every hit box sat 8 px from the next, but chips, icon buttons and Interrupt each had their own padding and glyph size. So the space a person actually sees between two controls ran from 13 to 23 px, and nothing grouped related controls.

The row now has one gap scale, all in tokens on .acpmux-composer-bar:

  • Every control draws its text or icon --acpmux-row-inset (6 px) in from its own edge.
  • Controls in a group sit --acpmux-row-gap (4 px) apart, so 16 px between what they draw.
  • Groups sit --acpmux-row-group-gap (12 px) apart, so 24 px.
  • Inside a chip, an icon or chevron sits --acpmux-row-icon-gap (4 px) from the text.

The groups are [+] [model · effort] [permission mode · Plan] ……… [Interrupt · mic · Send]. The trailing cluster ends at the bar's content edge. It takes the row's free space with margin-inline-start: auto, so this also holds in RTL.

What changed to get there:

  • Chips: model, effort, permission mode, Plan and Interrupt all use the same 6 px inset on both sides. Before, chips had 6/4 and Interrupt had 10/10.
  • Icon buttons: + and the mic now draw Send's 16 px glyph instead of 18 px.
  • Send: its disc fills its box, so it keeps its inset outside, inside the actions cluster's box.
  • Bar edges: the bar's padding puts the + glyph and Send's disc both 12 px from the card's sides. Before, they were 17 px and 12 px.

The permission-mode group's extra space is padding on the chip's wrapper, not a margin. That way the hover wash stays the button's, and composerRowFit.ts counts it when it decides to drop the mode label. Only CSS changed; there is no markup change.

Measured (Chromium, the real composer from the gallery's agent-pane.composer-row entry)

Drawn content (text, icons, Send's disc), the same at 760, 600, 480 and 420 px, in light and dark:

BetweenBeforeAfter
+ → model (between groups)1924
model → effort (in group)1816
effort → permission mode (between groups)1824
Interrupt → mic (in group)2316
mic → Send (in group)1316
bar side → + glyph1712
Send disc → bar side1212
chip insets lead / trail (model, effort, mode)6 / 46 / 6
Interrupt inset lead / trail10 / 106 / 6
icon → text, text → chevron4 / 44 / 4

Captures

Real cmux NEXT, from fleet DEV builds made on the Mac builder: the merge base 18260a5d9bc0 (tag gapbase) and this branch at 244f1613bec3 (tag gaphead). Since that build, the head has changed only in tests, a base merge, and an RTL-only margin swap (margin-left: auto → margin-inline-start: auto), so the build still matches the head in LTR. The head build's agent-pane bundle contains --acpmux-row-group-gap; the base build's does not. Each capture shows a running Claude chat from the --showcase mock daemon (Opus 5.5, Medium, Full access) with a prompt typed, so Interrupt, the mic and Send all show. One pane, window widths 975, 815 and 695 (pane about 760, 600 and 480 px). The crops show the composer card only.

At 480 px, "Me…" and the lock-only mode are PR 1's territory (#20802, label step-down), and they're the same before and after.

app light

app dark

Gallery stills (same entry the test renders)

gallery light

gallery dark

Borrowed from shadcn AI Elements

From the prompt input:

  • Taken:
    • one gap scale shared by the whole toolbar, instead of per-control spacing;
    • restraint: no new surfaces, borders or weights; the chips keep their quiet ghost look and weights;
    • related controls read as a group.
  • Not taken:
    • outline buttons, because our chips stay borderless at rest;
    • moving model and effort to the right beside Send, because they stay on the left pending the cg-design-judge verdicts;
    • shadcn's lack of group wrappers: we have groups, made from the same tokens.

Tests

webviews/test/composer-row-rhythm.test.ts (new) renders the real composer in Chromium at 760, 600, 480 and 420 px, light and dark. The fixture is a running Claude chat with a prompt typed, so Interrupt, the mic and Send all show. The test measures what each control draws and checks:

  • one gap inside groups and one wider gap between groups, each within 1 px;
  • the same insets in every chip;
  • Send at the bar's content edge;
  • the + glyph and Send's disc the same distance from the sides.

It does not assume the effort chip or the mic is present.

Same command on the Linux builder, red then green:

bun run check; bun test test/composer-row-rhythm.test.ts test/composer-menu-width.test.ts test/ui-rules.test.ts test/pane-english.test.ts
  • Red on the base, with the test commit 77e168974939 alone: 8 fail. For example: uneven gaps inside groups: model→effort 18.0, steer→mic 23.0, mic→send 13.0; plus→model 19.0 is not wider than the in-group gap; chips' lead differs … steer 10.0; the row's drawn edges differ: 17.0 px leading, 12.0 px trailing.
  • Green on the fix commit 244f1613bec3: 18 pass, 0 fail. bun run check passes.

composer-row-layout.test.ts checked that the left cluster's hit boxes sat 8 to 12 px apart, evenly. That rule is what this PR replaces. The test now only bounds those gaps to 4 to 12 px and leaves the evenness to the new test. It passes, along with composer-turn-cycle, composer-location-strip, composer-send-stop, composer-row-fit and new-tab-composer-layout: 51 pass.

Re-run on the merged head d19eebce5810 (with base 1cb7e587bffe): the same command gives 18 pass, and composer-row-layout gives 27 pass.

The footer label step-down (PR 1, #20802) lands first; this branch will merge it and re-check the gaps with its steps in place.

no bead

Changelog

  • Changed: the composer's controls row spaces its controls evenly, groups related ones, and lines up the + button and Send with the card's edges.

🤖 Generated with Claude Code