fix(picker): group model search by harness and tell twin rows apart

Merged
TE
7d ago
+752-81

Summary

Lawrence, via Leo (2026-10-28): "lawrence says search feels confusing here in the model picker". In his screenshot, searching "sol 6.1" left "Claude Code" lit on the rail with nothing matching there, headed the results with the typed query, and listed near-duplicates ("GPT-6.1 Sol / OpenCode · OpenRouter" twice, three "OpenAI: GPT-6.1 Sol …" rows) that could not be told apart.

The compact model menu's search now:

  • Heads each harness's matches with the harness name, in rail order, and the rail keeps only the harnesses that have matches. No tab is lit while a search shows. If nothing matches, the rail keeps every harness.
  • Drops the harness from a row's detail, since the heading already names it. The detail is the provider (plus the effort and Fast mode the query named).
  • Shows the model id under the name for rows that would still read the same (same name, harness and provider), so the two OpenCode "GPT-6.1 Sol · OpenRouter" routes read apart.
  • Wraps a long searched name onto a second line instead of ending it in "…", so Pi's "OpenAI: GPT-6.1 Sol Thinking / Mini / Nano" keep the part that tells them apart. Hover shows the whole row: model, harness, provider and id.
  • Says what to do when nothing matches: "No models match “sol 9.9”. Try part of a name, or clear the search to browse by harness."
  • Still picks the top row on Enter, which is now the first group's first match.

A single list whose rows say "Model · Harness · Provider" stays available as a gallery version (searchGroups: "flat"). The menu's density ("tonka toys") is a separate PR (linked below).

Related open picker PRs, which this does not overlap in behavior: #27140 (native popup migration of the same menus; whichever lands second carries the grouping over), #26969 (one Claude / one Codex harness entry), #26962 (the empty-list vs. empty-search message in the same menu; this PR changes only the typed-search case), #27232 (checkmark centring).

Testing

  • webviews/test/model-search.browser.test.ts (new, real Chromium, Lawrence's catalog in src/gallery/fixtures/modelSearch.ts): checks the harness grouping and filtered rail with no tab lit, that twin rows show distinct ids, that no searched row's name or detail is cut (scrollWidth <= clientWidth), the one-list version, the empty-search copy, and Enter picking the top match (codex / gpt-6.1-sol). It is committed before the fix; on the base it fails, because the base heads results with “sol 6.1” and lights Claude Code.
  • Updated test/model-menu-compact.test.tsx and test/signed-out-rail.test.tsx for the per-harness headings.
  • Ran on the fleet (cmux-ci test, 32-vCPU Linux): bun run check passes (format, lint, type check, design tokens, catalog keys). The browser test and the compact-menu, signed-out, providers, retry, short-list, fork, searchable-pickers and classic New Tab picker suites pass, except the optional reasoning slider replaces Effort's levels only when enabled, which fails the same way on the base feat-cmux-next.

Observed / Not run

  • Observed: the fleet runs above, plus the before/after stills below from the same harness at the base and at this head.
  • Not run: a native app build. This change is webview-only, and the agent pane loads it unchanged.

Changelog

Changed: The model menu's search groups its matches under each harness, keeps only matching harnesses on the rail, and shows a model's id when two matches would otherwise look the same

Proof

Same harness (Lawrence's nine harnesses, "sol 6.1"), real Chromium, dark theme, at the base and at this head. The menu here is still at today's density; the density fix is its own PR.

BeforeAfter
search beforesearch after
empty search beforeempty search after

Gallery versions: agent-pane.model-picker states search-grouped (ships), search-grouped-top-rail, search-flat and search-empty.

Localization: one new string, picker.noMatchesFor, translated into all 21 catalog languages (non-English marked needs_review).

🤖 Generated with Claude Code

Agent session: agent session