cmux next: the model menu shows harness, model, effort and account at once
The model button's menu asked for one thing at a time: a harness on the rail, a model, then effort in a side menu, Fast in a checkbox row and the account in a submenu. This PR replaces that menu, wherever it has a harness rail, with columns: the harness rail, the harness's models, then the shown model's effort and Fast. The account is an avatar in the search row's corner, as a browser's profile button.
- Columns (
QuickModelColumns.tsx,quickModelColumns.css): the rail (Starred always first), the models with their Legacy ones under a caption, and an Effort column (levels highest first, "None" when a model has none, provider rows for a model several providers serve, then Fast). Each column scrolls on its own and fades the edge that has more rows. The menu keeps its largest size while open, so its far edge never jumps. - Picks: a click lands at once and the menu stays open: a model, then its level, Fast or provider, each as it is clicked. A picked model carries the last level picked here, else the chat's, when it has it, else takes its recommended one (Opus 5.5 on Extra High to Sonnet 5.5 on Extra High). Ultracode carries as Extra High.
- No shift: the menu stays where it opened while it is open, so a chip renamed by a pick never moves it. In the chat's quiet row (packed to its end) it lines up with the chip's end, so a reopen after a switch opens in the same place. The Effort column has a fixed width.
- Account avatar: it shows the account of the shown model's harness, with the harness's mark at its corner. Its card lists the harness's other accounts and routes, Add account…, and for a signed-out harness "Sign in to {harness}…" and "Use an API key…". An empty Starred tab or a harness this Mac does not have shows a blank person ring.
- Keys: Up and Down move the highlight only (never a switch, never a launch); Left and Right move between columns; Return lands and closes; 1-9 and ⌘1-9 pick a row; Escape closes the card, then clears the search, then closes. The card's rows and Add harness take Tab.
- Wiring:
ModelPickerrendersQuickModelColumns; with no rail (a started chat with no fork target) or for the Chief, it falls back toQuickModelMenu, which is unchanged apart from exports and an optionalaccountFamilyonQuickFamily. - Shared part (
src/ui/DrivenList.tsx, new):DrivenList(alistboxortablist),DrivenRow(option,tab,radio,switch,button) andInlineCard(a non-modaldialoginside a popup), for lists a search field drives (it keeps the keyboard and names the active row). Base UI 1.8 has no such list, asDrillListnotes, so the columns set no popup role by hand (#33701). - Strings: 9 new keys in
Localizable.xcstrings, all 21 languages. 4 of them cut from existing translations.
Unchanged: the full picker, RetryModelMenu, and the list menu where there is no rail. The list menu's layout settings (harnessPlacement, effortLook, the effort slider, routeLayout, widthLook) do not apply to the columns.
Changelog
- Changed: The model button's menu shows harnesses, models, effort and Fast side by side, with the account as an avatar that switches or adds accounts.
Tests
src/agent-session/acpmux/QuickModelColumns.test.tsx(new): Starred always first and its blank avatar; the session's levels with the current one checked; "None"; arrows highlight without landing; a click lands at once and keeps the menu open; model, then level, then Fast each land at once; the carried or recommended level, a level picked here carried to the next model, Ultracode carried as Extra High; Fast flips at once; the account card's rows and Add account; a missing harness's blank avatar; the Escape order; session levels when the running model has no row.- Rewritten for the columns menu, same behaviors:
model-menu-compact,model-menu-started-chat-fork,new-tab-deferred-pickers,new-tab-ask-model,new-tab-harness-rail,model-menu-providers,model-menu-short-list,model-picker-all-harnesses,model-picker-idle,signed-out-rail,ultracode-rides-effort,auth-health-gallery. - Browser tests (Playwright on gallery pages) updated for the columns:
picker-row-geometry,model-menu-bounded-height,model-search.browser,model-menu-density.browser,composer-search-row-geometry,model-menu-submenu-up,new-tab-menu-over-suggestions,new-tab-picker-clip,pane-menus-styled,retry-model-menu,reasoning-menu-checkandcomposer-footer-harnesspassed locally withCMUX_BROWSER_TESTS=1before the base stopped Playwright on Macs.composer-chip-press,composer-menu-dismiss,composer-type-scale,menu-pointer-close-focus,model-menu-update-footer,model-pick-prompt-focusandmodel-picker-round-railrun in CI only. src/ui/DrivenList.test.tsx(new): roles and names, Tab only on tabbable rows, a press keeps the field's focus, Return and Space pick.QuickModelColumns.test.tsxalso: a re-render never takes the keyboard back from a control outside the menu (fails with the old inline ref).- Updated for the columns and the carried level:
classic-new-tab-pickers,agent-pane-no-capsules(the avatar, its badge and the Fast track are round on purpose), and the browser testsagent-pane-harness-catalog,agent-pane-model-sticks,reasoning-own-control,model-search.browser. - Gallery health:
invariant-baseline.jsonlists the "More models" row in the density states (they now open the list menu) and the menu width ofauth-health#expired(now the columns), as its sibling states do. The New Tabcomposer-openplay waits for either menu. - Removed by design: the effort slider case and the "no number keys while filtering" case in
model-menu-compact. - Gallery: the plays of
agent-pane.model-picker,agent-pane.new-tab,agent-pane.composerandagent-pane.auth-healthopen the columns menu;agent-pane.model-menu-densitykeeps the list menu (harnessFixed).
Observed / Not run
- Ran:
bun run typecheck,scripts/web-lint.sh,prepush-gate.py, every test file that touches the model menu (each on its own), and the browser tests above. The non-browser tests all pass exceptagent-pane-no-capsules(1 case), which fails the same way on the base. Two read-only reviews: no P0 or P1; the P2s are fixed. - Dogfooded in a tagged build on this Mac: New Tab and a chat, Claude, Codex and OpenCode tabs, the account card, Starred, a missing harness.
- Not run: a What's New clip (it needs the capture fleet); the signed-out card in a real signed-out harness (covered by
signed-out-railonly);QuickModelMenu's own layouts against the columns in the full gallery sweep. - Known: a click on each of two models of another harness starts one harness switch per click. A level or Fast clicked while a model switch is in flight waits for the model, then applies (the newest one wins). The account card sits 4 px in from the avatar, not 6 px: the spacing scale.
- CI: the
cmux-next checksred (a fleet host name inHarnessStatusCard.gallery.tsx) is on the base too.
![Autofix with [code]smith](https://pr-comments-assets.blacksmith.sh/codesmith/autofix-with-codesmith-dark-3x.png)
Open in [code]smith · Need help on this PR? Tag @codesmith-bot with what you need.
Summary by cubic
The model button's menu now shows harness, model, effort, and Fast mode side by side in columns, with the account as an avatar in the corner. Previously each was picked one at a time through submenus.
Behavior
- Picking a model shows its check at once and lands once with its level and Fast when the menu closes: one harness switch, not one per click; a re-pick of a model still switching keeps the picked level.
- The chip's predicted model counts as running until the session reports one, and the menu stays where it opened so a renamed chip never moves it.
- The chat's auth readiness shows under the search: the auth note, sign in again and check again, for every verdict, on every tab and while filtering.
- Up and Down move the highlight only; Left and Right move between columns; Return lands and closes; Escape closes the account card, then clears search, then closes.
- Each column scrolls independently and fades the edge that has more rows.
Wiring
- Menus with a harness rail use the new columns, built on new shared
src/ui/DrivenListlist parts; started chats with no rail and the Chief fall back to the unchanged list menu. - 9 new localized strings across all 21 languages; 4 cut from existing translations.