cmux NEXT composer: New Tab gets the chat's chip states; the caret shows only with the focused ring

Merged
TE
18d ago
+620-72

Leo (2026-10-19): "need to hide caret and deal with more focus stuff for composer and merge remaining styling stuff for it." The chat composer was right. The New Tab composer still drew the model and effort chips with a fill and the brand mark, and it could show a caret while the box looked unfocused.

What was wrong in New Tab

The New Tab button row is .nt-pickers. The chat's row is .acpmux-composer-bar. Every chip-state rule was scoped to .acpmux-composer-bar, so New Tab skipped all of them:

  • The plain footer look (data-footer-look="plain", on by default, composerSettings.ts). Before #22107 that look was the only thing that removed the resting fill, which is why the chat had no pills and New Tab did. It also hides the model's brand mark and mutes the chip ink. New Tab never set the attribute, so it kept the mark and the bright ink.
  • #22107's states: the hover pill on the mic and Plan, the keyboard-focus pill on every button, and the bar's outline on the pill's edge (New Tab drew the shared ring 2 px off bare text).
  • #22114's glass focus: over art, focus brightens the card's top-lit rim. That rule names .acpmux-composer-box only.

The row now takes the chat's footer look, and those rules name :is(.acpmux-composer-bar, .nt-pickers). The specificity is unchanged. Layout rules stay per row.

Each state (both composers, every composer instance)

  • Rest: each button shows only its icon or label. No pill, fill or edge. The model chip is a muted label with no brand mark at a roomy width (the mark comes back at the narrow fit steps, as before).
  • Hover: only the hovered button gets the soft pill (--acpmux-pill-hover), the mic included. The box never changes under the pointer.
  • Open / pressed: the chip keeps that pill with no ring. A press scales the chip to 0.975 with a 0.96 opacity dip and springs back (#22107, now in New Tab too).
  • Keyboard focus: the same pill, plus the bar's outline at offset 0.
  • Caret: shows only while the composer shows its focused ring. The rule is the Claude app's: no caret unless the composer is truly focused. A field can keep DOM focus while the page isn't the key one (the window is in the background, another pane has the keyboard, or New Tab focused its field before the user came to it). pageKey.ts marks :root[data-page-inactive] when the window blurs, when the page is hidden, or when document.hasFocus() is false. A key press, a pointer press or the window's focus event clears the mark. While it is set, a focused field gets caret-color: transparent and every composer draws its resting ring. That covers the focused edge tone, the shell ring, the Increase Contrast ring and the glass rim. The two always change together.
  • Focus ring: #22114's subtle glassy step, now on the New Tab card too.

Tests

test/composer-caret-focus.test.ts (Chromium + WebKit, gallery stage) runs every state for the chat composer and the New Tab composer. It checks:

  • the plain footer and the muted model label
  • the hover pill on one button only, with the box unchanged
  • the keyboard-focus pill and the outline on its edge
  • caret and ring together: focused, focused in the background, focused while not key and then key, and on open
  • the glass rim on focus, resting in the background

The red run at the first commit showed 20 of 28 failing on Huge Blue; it now passes 28/28. I also ran the neighbouring browser suites (quiet chips, chip press, New Tab quiet focus, rim hairline, footer plain, narrow layouts, no system blue, text field focus, focus visuals, New Tab field on art, location strip, reasoning chip steps, gallery coverage and manifest). Results are below.

Gallery

New variants composer-rest, composer-hover, composer-open, composer-focus, composer-keyboard and composer-background on agent-pane.composer and agent-pane.new-tab (composerStatePlays.ts). The gallery advisory run on Linux Chromium captures the two composers side by side.

Other open composer-styling PRs

  • #21986: closed with a comment. #22107 already folded in its no-move check and its EffortPicker comment; against today's base its diff would only undo them.
  • #21200 (canonical Composer + orchestration gallery, Codex, 88 files, conflicting) is broader than chip styling. Left open.
  • #20903 and #22010 (menu Undo/Redo) are not styling.

Base reds that also fail at feat-cmux-next: check:browser-lanes (agent-pane-older-rows-fade), check:text-focus (CookieImportConsentPanel.swift), check:type-scale (syncMotion.gallery.css). #22118 covers them.

🤖 Generated with Claude Code


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need.


Summary by cubic

Makes the New Tab composer match the chat composer's chip states and ties the caret to the focused ring.

The chat's chip-state rules were scoped to .acpmux-composer-bar, so the New Tab row (.nt-pickers) kept a resting pill, the brand mark, and bright ink. The row now takes the chat's plain footer look and the same state rules cover both rows: rest is a bare muted label, hover and keyboard focus draw the soft pill, press scales the chip, and open chips keep the pill with no ring.

The caret now shows only while the composer draws its focused ring. pageKey.ts marks the root data-page-inactive when the window blurs, the page is hidden, or document.hasFocus() is false; a key press, pointer press, or window focus clears the mark. While inactive, focused fields get caret-color: transparent and every composer draws its resting ring. The glass focus rim now covers the New Tab card too, lifting only for the prompt's focus or keyboard focus of a control—not a chip clicked with the pointer.

Adds gallery variants (composer-rest, composer-hover, composer-open, composer-focus, composer-keyboard, composer-background) for both composers and a Chromium + WebKit test covering every state, including the glass rim where the engine draws it.