feat(desktop): one picker style across Settings: the app's own menus, a segmented effort, and an outline Coding agents icon

Merged
DA
9d ago
+716-341

Builds on #1885's shared SearchableMenu and orderedModels, now merged into coding-agents; this branch is rebuilt on top of that merge and carries only this PR's own diff.

Every Settings dropdown is now the app's own menu, and Settings › Coding agents uses the same row layout, picker, and segmented control as the rest of the app.

What Dean saw

  • Default model opened a native macOS <select>: ~40 models in the system's popup list, and scrolling it scrolled the whole Settings page.
  • The effort chips were lowercase pills that clipped and ran under the menu.
  • "Coding agents" in the sidebar had a filled play triangle beside four outline icons.

The layout now

Every picker is one control (settings/picker-row.tsx): a chip the width of the old pop-up column (190px) showing the current value and a chevron, built on plan-compose-chip, and under it the shared SearchableMenu from the repository picker. The Voice page's voice pop-up and the Appearance page's pop-up use it with search off (the list holds focus, highlight on the checked row); Coding agents' Default model uses it with the search field first and focused, the provider's mark on every row, Anthropic then OpenAI with the newest version first within each (orderedModels), a check on the current default, and the list scrolling inside the menu (overscroll-behavior: contain, bounded height) so the page behind never moves.

The menu fits the page. It hangs from the chip's trailing edge, and stands above the chip instead where the page leaves more room there (the compact panel is 520px tall at most). The room is measured against the nearest scroller rather than the window, because the page column and the panel's own scroller clip what runs past their edge, and divided by the page's zoom; the list is capped to it through --settings-picker-list-max. The chip asks the window to be key as it takes focus, as the old pop-up did, so a non-key panel's search field takes keystrokes.

Default effort is a segmented control (.settings-segments), the chosen segment filled the way the side panel's shown tab is, labels in sentence case ("Low", "Medium", "High", "Xhigh", "Max"). Its options are still the chosen model's own; a model that does not list the current effort falls to its first. The row wraps, so a narrow page drops the control under its label instead of clipping it.

The sidebar's Coding agents glyph is an outline terminal (TerminalIcon), drawn in the same box and stroke as General, Voice, Appearance, and Keyboard shortcuts.

No native <select> remains in the renderer, and scripts/repository-checks.sh refuses one from now on.

Tests

  • settings/select-row.test.ts: the row draws a chip and no select; the menu opens with no search over a short set; a pick stores the parsed value and returns focus to the chip; Escape closes the menu and never reaches the window's own Escape (which is what would unwind Settings); the menu stands above the chip near the foot of the window, or of the scroller it is in, with its list capped to the room.
  • settings/coding-agents-page.test.ts: the search is first and focused, rows carry marks newest first, the check sits on the current default; the search filters by name and by provider; ArrowDown + Enter picks; Escape closes the menu without reaching the window; the effort falls back to the model's first when the model lacks the current one; the failure and retry notes stand inside the menu.
  • The native-select check is a repository check (the smallest layer: one grep).

CI could not verify the Mac: verify.sh was waived by Dean for this PR. The screenshots below are the real components mounted in jsdom and rendered with the built stylesheet in headless Chrome, so they show the layout but not Electron's own window chrome.

Screenshots

Settings › Coding agents, as the real components render under the built stylesheet in headless Chrome (jsdom mount, no Electron window):

Coding agents: the model chip and the segmented effort

The model menu open: search first and focused, provider marks, newest first within each provider, the check on the current default, the list scrolling inside the menu:

Coding agents: the model menu open

Settings › Voice, the same picker with search off over a short set:

Voice: the voice picker open

A narrow page: the effort control drops under its label rather than clipping:

Coding agents, narrow

🤖 Generated with Claude Code