Composer: Fast/Ultrafast lightning toggle left of the mic

Merged
TE
19d ago
+906-6

Refresh on 2026-10-16

Replayed the focused feature on bf891a838c4, preserving the newer reasoning slider behavior. Current head: 2066b724dd92a27bba3b667161a948c3fa7f7cea. The original test-first proof remains as commit 392abb7a1ad, followed by the implementation and hosted gallery behavior coverage.

The three speed gallery variants now click the real control, check the native chat.effort command, deliver the confirmed session option, and check the rendered tier and pressed state. They return to their starting tier for the screenshot. The narrowest pane still verifies that the bolt gives way below 370 px.

Current verification: git diff --check, syntax parsing of the six changed source TS/TSX files, and node scripts/pages/gen-strings.mjs agentPane --check pass. Catalog comparison confirms only the three intended keys, each with 21 locales. Conflict-only gate passes against base be15287c3e44a05b28144d54154dd360a87d8e99 and head 2066b724dd92a27bba3b667161a948c3fa7f7cea (speculative merge 691d7a2d81d11faa6ef9fc7949309f7e497fa279). cmux-next checks executed: all sibling checks passed, including 21-language tables and package conventions, but Type scale failed on Packages/macOS/CmuxNext/Sources/CmuxNextTabs/TabOverflowHintView.swift:51 (one raw font weight, baseline zero), outside this PR diff. That source fix is tracked separately in https://github.com/manaflow-ai/cmux/pull/21152. Gallery rendering is still pending. Current-head Freestyle behavior/type/browser checks are unverified: this Mac lacks the configured credential file and environment key. The historical test results and stills below describe the earlier implementation head.

Leo's 10-15 composer feedback, item 5: "Add Fast/Ultrafast: a lightning-bolt toggle at the bottom right, just left of the mic button."

The composer's trailing cluster now has a round bolt button immediately left of the mic, shown only when the running model offers a speed tier (Claude Code's Fast Mode, Codex's service tier). A click steps through the tiers the session's fast-mode option offers: off, Fast, Ultrafast (where offered), then back to off. With one tier, which is what every harness offers today, it's an on/off toggle. Off, the bolt is muted like the mic. On, it takes --agent-accent; Ultrafast fills the bolt. In the agent pane's themes --agent-accent is the text ink, so on reads as a full-ink bolt against the muted mic. The aria-label and tooltip name the state ("Speed: Fast. Click for Standard."), it sets aria-pressed, and Tab reaches it with the mic's focus ring.

The effort chip no longer says "Fast" ("Medium Fast" is now "Medium"), since the bolt shows that state. The reasoning menu's Fast Mode and Service Tier rows are unchanged.

How it works

  • Same command: the bolt sends what the menu's speed rows already send, onEffort(fastOption.id, value), which the host routes to chat.effort. This adds no protocol. Its tiers are the option's own values past off, in the agent's order. It shows where the menu's speed section shows: when the catalog doesn't mark the model fast: false, or while a tier is still on.
  • Placement: ComposerPickers owns the speed state and passes it up through a SpeedToggleHost context setter (useSpeedToggle). It republishes only when the current tier or the offered tiers change. Composer then draws FastToggle in its trailing cluster, right before {accessory} (the mic). There's no portal: ui-rules bans them outside src/ui. The cluster's --acpmux-row-gap rhythm from #20785 stays as it was. The glyph is 16 px, like the mic and Send.
  • Narrow panes: below 370 px the row's controls are already at their minimum widths (the model chip at "O.", effort at "M.", the mode as a lock), and the 28 px bolt pushed the cluster over the access chip. So the bolt hides below 370 px, and the reasoning menu's speed rows still set it there. At that width the old chip had already cut "Medium Fast" down to "M.", so the state wasn't visible before either. I measured the gap between the chips and the trailing cluster in Chromium from 320 to 420 px: it holds at the row's 12 px from 340 px up, both with and without the bolt. composerRowFit.ts is untouched.
  • Limits: the New Tab page's composers (NewTabScreen, NewTabPage) don't use Composer's bar, so they show no bolt and no longer show "Fast" on the chip either. There, the reasoning menu shows the speed. No harness offers a tier past Fast yet: acpmux's Claude translator accepts only on/off. The bolt handles more tiers, but the menu's speed section and the typed-combo memory still assume on/off.
  • New strings: composer.speedLabel, composer.speedTooltip, picker.tierUltrafast, in all 21 locales. The catalog diff against base contains only these three keys.

Reference

The Codex desktop captures in cmux-app-screenshots (captures/codex/app-20261003) don't show a speed control. The closest Codex control is its composer's trailing mic: a borderless, muted, single-glyph icon button sitting right of the model chip and left of the round voice/send button. The bolt copies that treatment: no bezel, muted ink, the same box and glyph size.

AI Elements borrowed: the prompt input toolbar's icon-only tool buttons (ghost, round, tooltip-labelled, aria-pressed for toggles). Its model select and submit weren't changed here.

Tests

webviews/test/composer-fast-toggle.test.tsx renders the real Composer with the real ComposerPickers and a host that records what the pane sends. It covers:

  • the bolt is the button right before the mic;
  • clicks cycle off → on → off with one tier, and standard → fast → ultrafast → standard with two, sending fast-mode=<value> each time;
  • there's no bolt when the session has no speed option;
  • the chips' text has no "Fast" with a tier on.

Same command for both runs, through sbx (cd webviews && bun test test/composer-fast-toggle.test.tsx):

  • Red, at the test commit 7179b066174d on base ba64441671fd: 1 pass, 2 fail. "a speed toggle in the trailing cluster: Received: null" in both bolt tests; the no-tier test passes, as it should on base.
  • Green, at the head: 3 pass, 0 fail.

Also green at the head, after merging base ed2adf3d5591: bun run check, plus composer-row-rhythm, composer-row-layout, ultracode-rides-effort and new-tab-composer-first-frame. That's 41 pass, 0 fail, including the browser lanes in Chromium.

Stills

Gallery stills, before (base) and after (this branch), of the footer in the agent-pane.composer-row entry's new speed-off, speed-fast and speed-ultrafast variants, at 760, 480 and 340 px in light and dark. Each sheet stacks before/after for off, Fast and Ultrafast. These are gallery renders only. I took no real-app stills, because fleet DEV builds on capture slots have been trapping at launch.

760 px

LightDark
light 760dark 760

480 px

LightDark
light 480dark 480

340 px (the bolt gives way below 370 px, so both columns match)

LightDark
light 340dark 340

Changelog

  • Added: a Fast/Ultrafast lightning toggle left of the mic in the composer.

🤖 Generated with Claude Code