cmux NEXT model menu: four models, each once, the rest under More models; Effort and Fast name their state; submenus open up from the composer

Merged
TE
12d ago
+1253-88

Leo-ask: ask-98ecbe7758

Leo on the nightly, 2026-10-21: "why are there so many models!? it should be truncating and then showing more models.. this effort should open up top instead of bottom i think", and "this should just show the effort too right? and then the effort tells you what you're already currently on?"

His claude-sr2 chat's model menu listed nine numbered models, with Opus 5.5 twice (row 1 and a checked row 9). Its Effort submenu opened downward, toward the window's edge.

Composer ownership follow-up

Leo's exact words: "One model button and one menu; plain Send/Stop/Queue glyphs; subtle hairline focus ring; typed text aligned with +; model picker and mode menu work."

Lawrence's cmux16 capture showed Fast as a standalone action-row control above GPT-6 Astra Extra High and Collaboration mode: Default. The original #24224 head still kept Codex Fast in that action row. The current base reconciliation moves Codex Fast beside the model picker and leaves the action row to the mic and send/stop controls; the composer gallery and focused Fast tests cover that placement and the active tier state.

Design reference matched: Claude's live model-picker-open.png and model-effort-submenu.png keep model, effort, and Fast choices in one anchored model menu.

What it does now

  • Four models, each once. The menu shows the top picks plus the running model, numbered 1 to 4. Plain models come before variants. Everything else sits under More models ›, and number keys apply only to the shown rows.
  • Submenus grow toward the room their row has. From a composer at the window's foot, Effort (and More models and Account) opens upward from its row, as in Claude Code. Near the top of the window it opens downward. Base UI's collision avoidance still flips or shifts it at the edges. This lives in the shared ui/Menu.tsx Submenu, so every menu gets it.
  • Effort names the current level ("Effort Medium ›"), and its submenu checks that level. Base already did this. The test now holds it.
  • Fast mode names its state: "Off" in secondary ink, or "On" in the theme accent, in the same place Effort and Account show their values. The glass and the full-height row highlight are unchanged. The checks use the theme accent, never system blue.

Root cause

  • The duplicate Opus 5.5. foldAliases (webviews/src/agent-session/acpmux/modelCatalogData.ts, base line 405-407) folds Claude Code's opus alias, which Claude Code names "Opus 5.5", into the catalog release claude-opus-5-5. When the session pinned that release itself, the fold skipped it, so both the alias row and the release row stayed (introduced in 0fe538aa684, cx-jqkx). A Claude profile the catalog does not map (claude-sr2, Leo's chat) went through uncataloguedHarness, which never folded aliases. It also got no curated top list, because topModelsFor(entry.id) in ComposerPickers.tsx:228 keys on claude only. That left the chat on the raw alias list plus the pinned id. Now the fold keeps the running release's row (checked) and folds the alias into it. A profile of a catalogued agent folds against that agent's releases (kin) and lists its curated top models (composer and Retry).
  • Nine rows. ModelPicker.tsx (base lines 546 and 806) handed the compact menu latest.slice(0, 9). It now hands it quickSections (QUICK_ROWS = 4).
  • Effort opened downward. ui/Menu.tsx Submenu (base line 565) always used align="start".

Tests

Red on base, green on head (huge-blue):

  • bun test test/model-menu-short-list.test.tsx: base 5 fail / 1 pass (9 rows, "claude-opus-5-5" listed; Fast row has no state). Head 6 pass. The 9-model fixture includes the alias/release duplicate and covers both claude and a claude-sr2 profile.
  • CMUX_BROWSER_TESTS=1 bun test test/model-menu-submenu-up.test.ts (Chromium and WebKit; composer pinned to the viewport's bottom, then its top): base 2 fail / 2 pass (submenu top below its row at the foot). Head 4 pass.
  • The neighbours all pass on head: model-menu-compact, composer-fast-toggle, model-picker-picks, model-button-opens, the retry, new-tab and harness model tests (138 tests across 27 files), plus model-picker-round-rail, composer-menu-dismiss and menu-pointer-close-focus (browser). bun run check passes.
  • Gallery: agent-pane.composer-pickers gains the claude-model-menu and claude-effort-up variants (Leo's claude-sr2 chat).

Observed / Not run

  • Observed: gallery captures before and after (headless Chromium, real app font). They are private and kept off this PR.
  • Not run: the native app (webview-only change).

No capture: webview-only change; gallery compare at agent-pane.composer-pickers (variants claude-model-menu and claude-effort-up).

Changelog

  • Fixed: the composer's model menu lists four models, each once, with the rest under More models. Its submenus open upward from a composer at the window's foot, and Fast mode shows On or Off.

Overlap: #24151 (composer-look-model-quiet) also makes Fast mode the menu's last row. With both merged there is one Fast row. Its new composer-fast-toggle Codex test expects the bare text "Fast mode", so whichever PR lands second needs a one-line update to "Fast modeOff".

Agent session: cosmic-gray

🤖 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

Fixes the composer's model menu to show at most four models — the running model and the top picks, plain models before variants, each once — with everything else under More models and number keys applying only to the shown rows. The running model's row is checked. Effort and Fast mode rows name their current state, and submenus open upward from a composer at the window's foot.

Also fixes the duplicate "Opus 5.5" row that followed from Claude Code's opus alias folding into the pinned release the chat ran, for catalogued agents and for profiles like claude-sr2 the catalog doesn't map. A folded alias keeps what the harness declares and the user overrides for it, whether keyed by the alias or its release, and hides a release whose reported alias the user hid; a hidden release stays hidden even when a session's model option lists it. A session reporting the pinned release keeps that release's checked row, and a model set to no effort levels shows no Effort row.

Fast mode is now the menu's last row for every harness, saying Off or On; the Fast bolt left of the mic is removed from the composer and the new-tab screen.

Note: #24151 also adds Fast mode as the menu's last row; whichever PR lands second needs a one-line test update ("Fast modeOff").