cmux NEXT composer: quiet model button, and the quiet row fits on its own room

Merged
TE
8d ago
+618-38

Leo, of #23742's after-shot: "WTF IS THIS UGLY BULLSHIT. WE ALREADY FIXED THE FONTS. THE BOLT DOESN'T GO THERE." That shot showed "GPT-6 Astra Medium" in a bright outline, a bolt inside the box next to the mic, and a loose context ring. Lawrence's build also read "Opus 5.5 • Medium" ("WE FUCKING HATE THE DOT"). Live-qa on nightly 3a63d6d found a 410 px composer reading "Opus Med" with about 250 px of the row free.

Now the quiet row reads Opus 5.5 Medium as plain text: the effort is in secondary ink, there is no edge, and the context ring follows on the same line. A 410 px composer keeps the full name.

This PR was split to stay under ~800 changed lines (it is 10 files, 526 changed). The bolt moved to a follow-up: Codex's bolt inside the model button, the removal of FastToggle.tsx (the button next to the mic), the Ultrafast menu row, the shared fastModeToggle helper for the full picker, and their tests are on branch composer-look-codex-bolt-in-model. It will be opened from feat-cmux-next once this lands. Removing the three strings only FastToggle.tsx uses comes after that, on composer-look-speed-strings. The dot between the name and the effort is already gone on base (#24376).

  • Quiet button. The outline in the shot was keyboard focus, not the resting state. The gallery play closed the menu with Esc, which handed focus back to the button, and the shared ring (pages/shared/desktop.css: 1.5 px at 48%, !important) drew around bare text. In the quiet row, focus is now the soft pill plus a 1 px hairline at 22%; Increase Contrast keeps the shared ring. The effort is one step back from the name in every theme.
  • Fonts. Nothing regressed. The composer names the app's UI font once and #23742 changed no font. (It named a literal system-ui from #20374 until base moved it to the shared --font-ui token, which resolves to Inter then the system stack, so SF Pro in the app's WebKit; the tests here read the token, not a literal, so a later change to the stack cannot redden them.) The after-shot came from headless Linux Chromium, which has no SF Pro and fell back to Liberation Sans for the whole page. One real gap: the quiet row sits outside .acpmux-composer-box button { font: inherit }, so its buttons used the engine's control font. They now inherit the composer's family. The new composer-app-font test keeps it that way, over base's new composerFooterShared.css too.
  • Truncation. COMPOSER_FIT_STEPS shortened labels by composer width, which made sense for #20802's single row. Since #22285 the quiet row holds only the mode, the model button and the ring, so those widths were stale. The quiet row now takes no width step: paneWidth is unbounded for it, and the container queries skip it. Both guards key on data-footer-look="plain", not on the element (see the base merge section below). useRowFit fits it from the units' natural widths against the row's measured room, and it watches the row's own items through the display: contents wrapper a ResizeObserver never reports, so a label that sets wider late (a font that loads, Linux's wider metrics) runs the fit again. A 410 px composer reads "Auto · Opus 5.5 Medium" whole, and so does a 264 px one; at 204 and 164 px it shortens whole units with nothing overlapping. The box's own row keeps its steps.

What base changed under this PR (merged in at 87770f7bd9f)

This branch sat for two days, so three landed changes rewrote parts of it. Dropped, not reconciled:

  • #24938 took the harness label out of the quiet row ("the quiet row must not repeat the harness"). Its own commit here, "the quiet row's harness name follows the row's room", is therefore gone: composer-footer-harness.test.ts takes base's version unchanged, and the 460 px harness container query goes with it. With the name out of the row there is ~70 px more room, so the measured widths moved: the quiet row now keeps every whole label down to a 244 px composer, where it used to shorten at 264.
  • composerFooterShared.css gives the context affordance one compact size and a 2 px margin that outranks composerLocation.css's legacy rule. That closes the loose gap Leo saw, so this PR no longer edits the margin (base's own composer-foot-pill-inset test asserts the legacy 4 px literal). The ring's glyph now sits 15 px after the effort instead of 17.
  • The gallery stage loads no pages/shared/desktop.css, so no shared focus ring reaches that page: the drawn test now puts that sheet in the stage as the app's pane has it, and measures what the ring then draws (1 px at 22%, not its own 1.5 px at 48%). Increase Contrast stays a CSS-text assertion, because prefers-contrast does not reach the gallery frame even with Playwright's emulation on.

Review fix

Review (codex gpt-6-astra xhigh): FIX FIRST at 7256729901 found a real cascade regression in this PR
(not in the base merge): .acpmux-composer-bar:not(.acpmux-composer-foot) adds a class, so each
container-query step tied the hiding rules above it at (0,4,0) and, coming later in the file, won. At a
370-440 px composer whose measured fit had already chosen the model's mark (data-fit-model="icon"),
the step would have brought the family label back. Fixed with :where(:not(.acpmux-composer-foot)),
which adds no specificity, so the steps keep losing to the fit exactly as they did before this PR.
composer-fit-steps now refuses a bare :not(.acpmux-composer-foot) in any step; with the bare
selector restored the guard fails, so it catches the regression. The reviewer's second point (prove the
hairline, don't read it) is in the bullet above. Still open as a follow-up, not done here: a gallery
fixture with a model name long enough to force the icon form inside that 370-440 px window, so the tie
is covered by a drawn test too.

Second base merge, at 966ee3ed929 (825 commits)

The branch sat again, so base was merged a second time. One conflict, in composerLocation.css: base's
#24661 gave the context ring one compact 22 px box with an 11 px end margin, checked by
composer-alignment-centers.test.ts; this branch still had the old 24 px. Base's side was taken whole.

That merge then exposed a real defect in this PR, found by measuring rather than by review:

  • The quiet row is a look, not an element. ComposerFoot.tsx:23 draws the same
    .acpmux-composer-bar.acpmux-composer-foot in both looks and marks only the plain one
    data-footer-look="plain". So keying this PR's step guards on the class — paneWidth's
    classList.contains("acpmux-composer-foot") and the container queries'
    :where(:not(.acpmux-composer-foot)) — also skipped the steps with the plain footer off, where
    every control shares that one row and the steps must still apply. The stepped row kept whole labels
    it had no room for, and the mark-hiding rule hid the harness mark that look draws. Both guards and
    the mark rule now read data-footer-look="plain". composer-fit-steps keeps the :where() (no
    added specificity, so the steps still lose to the measured fit) and now refuses the element
    form outright, so this exact mistake cannot come back. composer-row-steps's second table is
    renamed quiet/stepped — "in-box" was wrong, since the row is never in the box — and it reads
    the look off the attribute instead of off which element holds the chips.

Tests at this head (15549a04d1e)

Measured in Chromium on huge-blue (CMUX_BROWSER_TESTS=1), over composer-model-quiet,
composer-row-steps, composer-fit-steps, reasoning-chip-steps, composer-footer-plain,
composer-alignment-centers, composer-app-font, composer-caret-focus:

passfail
base e3134fd6a63450
the raw base merge 966ee3ed9294617
this head 15549a04d1e870

Base green on all of them means none of those 17 was a base red: all 17 were this PR's, and all 17 are
fixed. 16 were the one look-vs-element defect above; the 17th was the font test's literal. web-lint.sh
and scripts/cmux-next/prepush-gate.py pass.

Changelog

  • Fixed: The composer's model button is quiet text again ("Opus 5.5 Medium", effort in secondary text, no outline). A narrow composer keeps the full name while the row has room.

Tests

On the fleet (cmux-ci test --setup playwright, Linux Chromium + WebKit, CMUX_BROWSER_TESTS=1, one process per file), at head f608a6d387, and at base 87770f7bd9f in a detached checkout for every red:

  • Green at the head (run 40238116186, 10 files, 9 pass): composer-model-quiet (new, the real composer in Chromium, the injected-desktop.css hairline included), composer-app-font (new), composer-fit-steps (with the new specificity guard), composer-row-steps, reasoning-chip-steps, composer-footer-plain, composer-footer-harness (base's file, unchanged here), composer-row-fit, composer-row-layout.
  • Base reds, the identical failures at 87770f7bd9f: composer-quiet-chips 30 pass / 2 fail (run 40238701883; "plus: the focus outline stays", Chromium and WebKit — the same gallery fact: no desktop.css in the stage, so the + draws no outline there); composer-caret-focus 22 pass / 20 fail (run 40236159655, the identical 20 test names); composer-foot-pill-inset 4 pass / 2 fail (it reads --acpmux-row-inset and a 22 px ring from composerLocation.css, whose rules are byte-identical to base's here).
  • The earlier red runs are the record of the work: 40234992634 found base's three changes (3 failures in composer-model-quiet, 2 in composer-row-steps), and 40236827907 was green on the merged row before the review fix.
  • This PR's own reds: none. Its CI reds are cmux-next guards / release-notary, cmux-next guards / Guard status and cmux-next checks (god files, concurrency, crash safety, l10n) — the last fails on one sub-check, "Script tests (pin-cmux-tui, embed-cef, ensure-cef store)", which is the same single sub-check PR #25124 fails on current base; all three are red on feat-cmux-next push runs and named in base-red-watch comments here. Three more jobs (fast gates / post cmux-next/fast-gates, gallery visual evidence / mark-stale, high-signal review link / link) failed with GitHub's API rate limit exceeded for installation while patching a PR comment: infrastructure, same SHA re-runnable.
  • webviews lint and format pass on every file this PR touches. The area-wide vp lint stops on three jsx-a11y warnings in src/agent-session/acpmux/conversation/DynamicWorkflow.gallery.tsx, which this PR does not touch and which landed on base in #24764/#24766.

web / react-apps-check and its base-check leg

That job's two base-check shards attribute 6 webview tests to this change ("3 red on this change, 64 red
on the base too" and "3 on this change, 49 on the base"), reproducibly across two attempts. Each one was
then run directly, the same files on this head and on a detached checkout of base 87770f7bd9f, same
fleet image and runner class. All six are base reds or contention flakes, not this PR's:

attributed testheadbase
composer-mention (unnamed)failfailidentical message, press Control+j: layout shift 0.0066 (limit 0.001)
agent-pane-stream-jitter webkit "settling short, long, and tool-ending replies does not move the message or composer"failfailbase red
accent-glass-invariants "no orange or system accent…"failfailhead's violation list is a strict subset of base's (16 shared, 1 only on base, 0 only on head)
agent-pane-turn-tokens-muted (2 tests)passfailbase red this head happens to pass
transcript-pinned-bottom chromium "a send while the chat pages back to the reader's saved place…"passpasspasses on both when the file runs alone; it fails on both sides only under contention

Runs: head 40244766793 and 40243342616, base 40245155679 and 40244279238. Nothing in this PR touches those
surfaces (its 10 files are the quiet composer row's CSS, composerRowFit.ts and composer tests), and the
focused composer suites are green at this head. The aggregator is a wrapper over that comparison, and its
base leg flakes green on exactly these six while its head leg flakes red; the direct runs above are the
measurement.

Observed / Not run

  • Observed: the three failures the base merge introduced, measured and fixed (a 264 px composer no longer shortens; the ring gap is 15 px; the gallery has no shared focus ring); the review's cascade finding, fixed and guarded (the guard fails with the bare :not() restored and passes again when reverted); the hairline drawn at 1 px / 22% with desktop.css in the page.
  • Not run: the full webviews suite (CI runs it), a native app build (no Swift change), a fresh before/after capture (the earlier gallery compare predates base's harness removal), and the Increase-Contrast branch of the focus ring (prefers-contrast does not reach the gallery frame; read from the CSS instead).

No capture: webview-only change (pr-capture-check passes).

Leo-ask: ask-3e7a3c450b
Leo-ask: ask-98ecbe7758
Leo-ask: ask-f8d7fe1c14
Leo-ask: ask-45c7e2e017

Adopted from cmux16/composer-inspect (its session is gone) by the stall reconciler.

Agent session: agent session

🤖 Generated with Claude Code