Skip to content

Redesign model picker with favorites and search#2153

Merged
juliusmarminge merged 29 commits intopingdotgg:mainfrom
Chrono-byte:feature/model-picker-favorites
Apr 20, 2026
Merged

Redesign model picker with favorites and search#2153
juliusmarminge merged 29 commits intopingdotgg:mainfrom
Chrono-byte:feature/model-picker-favorites

Conversation

@Chrono-byte
Copy link
Copy Markdown
Contributor

@Chrono-byte Chrono-byte commented Apr 18, 2026

  • Replace provider submenus with sidebar-based model selection
  • Add model search, favorites, and locked-provider handling
  • Update settings schema and tests for favorite model persistence

What Changed

Replace model picker with new model picker inspired by t3chat (a lot). Has search, favorites, etc.

For OpenCode models, the upstream provider from OpenCode is extracted out and used under the model name.

Why

Adding OpenCode made me very happy but also, the old model picker is cumbersome with so many models, difficult to navigate. Also generally just a nice to have improvement.

UI Changes

Before:

image image

After:

image image image

Checklist

  • This PR is small and focused
    I feel bad not checking "small" but this is an XL pr so.

  • I explained what changed and why

  • I included before/after screenshots for any UI changes


Note

Medium Risk
Medium risk due to a large UI refactor that changes model selection flow, introduces new keybinding commands/contexts, and extends settings/contracts schemas (favorites + model metadata), which could affect persistence and shortcut resolution across desktop/web/server.

Overview
Redesigns the model picker into a popover-based experience with a provider sidebar, fuzzy search, and per-model favorites (persisted in new ClientSettings.favorites), including new components for the picker UI and search/ranking.

Adds model-picker keyboard support: new default keybindings (modelPicker.toggle and modelPicker.jump.1-9) plus a modelPickerOpen context, with global modifier-state tracking to control jump-hint visibility and shortcut routing.

Updates model metadata contracts to carry shortName/subProvider (notably for OpenCode flattening), adjusts /model to open the picker instead of inserting text, and refactors related tests and UI primitives (combobox/scroll-area) to support the new picker behavior.

Reviewed by Cursor Bugbot for commit ad3f024. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Redesign model picker with sidebar, favorites, fuzzy search, and keyboard jump shortcuts

  • Replaces the nested submenu model picker with a Popover containing a provider sidebar rail (ModelPickerSidebar.tsx) and a searchable model list (ModelPickerContent.tsx) with fuzzy scoring via scoreModelPickerSearch.
  • Adds a favorites system stored in ClientSettings that pins favorite models to the top of the list and applies a ranking boost during search.
  • Adds keyboard jump shortcuts (modelPicker.jump.1–9) activated while the picker is open, and a toggle shortcut (modelPicker.toggle on Shift+Mod+M).
  • The /model slash command now opens the model picker instead of injecting a model search into the composer command menu, and the slash-model trigger kind is removed from detectComposerTrigger.
  • Thread jump hint visibility is refactored to use a new global shortcut modifier state store (shortcutModifierState.ts), so modelPickerOpen is factored into shortcut context across the sidebar and chat view.
  • OpenCode models now expose subProvider and use plain model name for name in flattenOpenCodeModels, and ServerProviderModel schema is extended with optional shortName/subProvider fields.
  • Behavioral Change: model picker open state is now shared globally via a Zustand store; ProviderModelPicker falls back to the active provider's first model when the current slug does not belong to that provider.

Macroscope summarized ad3f024.

- Replace provider submenus with sidebar-based model selection
- Add model search, favorites, and locked-provider handling
- Update settings schema and tests for favorite model persistence
Copilot AI review requested due to automatic review settings April 18, 2026 02:58
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 18, 2026

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 01905f0d-5859-4ccb-8aae-061684c4d720

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions github-actions bot added vouch:unvouched PR author is not yet trusted in the VOUCHED list. size:XL 500-999 changed lines (additions + deletions). labels Apr 18, 2026
Comment thread apps/web/src/components/chat/ProviderModelPicker.browser.tsx Outdated
Comment thread apps/web/src/components/chat/ModelPickerContent.tsx Outdated
Comment thread apps/web/src/components/chat/ModelPickerContent.tsx Outdated
@macroscopeapp
Copy link
Copy Markdown
Contributor

macroscopeapp bot commented Apr 18, 2026

Approvability

Verdict: Needs human review

This PR introduces a major new feature (model picker redesign with favorites, search, and provider sidebar) with ~2800 lines of new code including new components, state management, and keybindings. Additionally, a HIGH severity review comment identifies that a debug tool (react-scan) is committed to production HTML and would load for all users, which must be removed before merging.

You can customize Macroscope's approvability policy. Learn more.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR redesigns the chat model picker UI to make large model catalogs easier to navigate by introducing a sidebar-based provider filter, inline search, and per-model favorites persisted in client settings.

Changes:

  • Add a favorites array to client settings and update persistence tests accordingly.
  • Replace the provider submenu model picker with a new ModelPickerContent (sidebar + searchable flat list) and supporting components/utilities.
  • Update UI styling (thin scrollbar) and adjust browser tests for the new picker behavior.

Reviewed changes

Copilot reviewed 11 out of 12 changed files in this pull request and generated 10 comments.

Show a summary per file
File Description
packages/contracts/src/settings.ts Adds favorites to client settings and extends the settings patch schema.
apps/web/src/localApi.test.ts Updates expected persisted client settings to include favorites.
apps/web/src/index.css Adds thin scrollbar styling for the model picker list.
apps/web/src/components/chat/providerIconUtils.ts Introduces shared provider icon mapping and provider/model labeling helpers.
apps/web/src/components/chat/ProviderModelPicker.tsx Switches the picker popup to render the new ModelPickerContent.
apps/web/src/components/chat/ProviderModelPicker.browser.tsx Updates UI tests for the new sidebar/search/favorites interactions.
apps/web/src/components/chat/ModelPickerSidebar.tsx New sidebar component for provider/all/favorites filtering.
apps/web/src/components/chat/ModelPickerContent.tsx New main picker UI with search, favorites persistence, and model list rendering.
apps/web/src/components/chat/ModelListRow.tsx New row component for model selection + favorite toggle UI.
apps/web/src/components/CommandPalette.logic.ts Refactors item construction to use Object.assign while preserving conditional fields.
apps/desktop/src/clientPersistence.test.ts Updates desktop client settings persistence test fixture to include favorites.
.codex File present in PR contents (no diff shown).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread apps/web/src/components/chat/ModelPickerContent.tsx Outdated
Comment thread apps/web/src/components/chat/ModelPickerSidebar.tsx Outdated
Comment thread apps/web/src/components/chat/ModelPickerSidebar.tsx Outdated
Comment thread apps/web/src/components/chat/ProviderModelPicker.browser.tsx Outdated
Comment thread packages/contracts/src/settings.ts
Comment thread apps/web/src/components/chat/ModelPickerContent.tsx Outdated
Comment thread apps/web/src/components/chat/ModelPickerSidebar.tsx Outdated
Comment thread apps/web/src/components/chat/ModelPickerSidebar.tsx Outdated
Comment thread apps/web/src/components/chat/ProviderModelPicker.browser.tsx Outdated
Comment thread packages/contracts/src/settings.ts
Replace the conditional skip with an explicit expect so the test
fails loudly if the Codex sidebar button is absent and proceeds to
click and assert model filtering afterward
Comment thread apps/web/src/components/chat/ProviderModelPicker.browser.tsx Outdated
Comment thread apps/web/src/components/chat/ModelPickerContent.tsx
Comment thread packages/contracts/src/settings.ts
Filter the All Models section to omit models already shown in Favorites,
adjust provider visibility logic, and add a test ensuring favorites
aren't duplicated across sections
Comment thread apps/web/src/components/chat/ModelPickerContent.tsx Outdated
Add a ClientSettingsPatch schema/type and export the
ServerSettingsPatch type. Update apps/web useSettings to import
patch types as type-only and use ClientSettingsPatch for the
clientPatch return value.
Remove persisted client settings key before and after the
ProviderModelPicker test to avoid state leakage. Use an aria-label
based lookup for the favorite star button, assert its initial label,
click it, and verify the aria-label toggles accordingly.
Comment thread apps/web/src/components/chat/ModelPickerSidebar.tsx Outdated
Comment thread apps/web/src/components/chat/providerIconUtils.ts Outdated
Comment thread apps/web/src/components/chat/ModelPickerContent.tsx
Replace global document.body.textContent checks with helpers that query
the .model-picker-list element, and find favorited rows via filtered
querySelectorAll to improve test reliability
- Add model picker toggle and jump shortcuts
- Surface picker state in composer and sidebar hints
- Update tests for keybinding and picker behavior
@github-actions github-actions bot added size:XXL 1,000+ changed lines (additions + deletions). and removed size:XL 500-999 changed lines (additions + deletions). labels Apr 19, 2026
Comment thread apps/desktop/src/main.ts Outdated
Comment thread apps/web/src/components/chat/ModelPickerContent.tsx
Comment thread apps/web/src/components/Sidebar.tsx
- boost favorite models in search ranking
- switch model picker to combobox/popover interactions
- improve scroll overflow handling and navigation tests
Comment thread apps/web/src/components/chat/ModelPickerContent.tsx
Comment thread apps/desktop/src/main.ts Outdated
- Switch model picker toggle to `mod+shift+m`
- Add favorites-focused model picker UI tweaks and provider icons
- Remove desktop shortcut bridge plumbing no longer needed
- Add the `new` badge to Cursor in the provider picker sidebar
Comment thread apps/web/src/components/chat/providerIconUtils.ts Outdated
- Remove provider-specific icon class helper
- Use shared sizing and viewport styling in the model picker
for (const command of MODEL_PICKER_JUMP_KEYBINDING_COMMANDS) {
const shortcut = findEffectiveShortcutForCommand(keybindings, command, options);
if (!shortcut) continue;
if (matchesShortcutModifiers(modifiers, shortcut, platform)) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unused exported model picker hint functions

Low Severity

shouldShowModelPickerJumpHints and shouldShowModelPickerJumpHintsForModifiers are new exported functions that are never called in any production code. They are only referenced in keybindings.test.ts. The model picker content component (ModelPickerContent) unconditionally renders jump labels when the picker is open, so these "should show hints" functions serve no purpose outside of tests. This is dead code that adds maintenance burden.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit fc65ff8. Configure here.

},
},
};
);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unrelated refactoring of command palette thread items

Low Severity

The return statement in buildCommandPaletteThreadItems was refactored from spread syntax to Object.assign without any functional reason related to the model picker redesign. This increases the diff size, changes string quoting from "" to backtick templates, and introduces a different (arguably less readable) pattern while the original spread syntax was clear and idiomatic. This change makes the PR harder to review without any benefit.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit fc65ff8. Configure here.

Comment thread apps/web/src/components/chat/ModelPickerContent.tsx Outdated
- Avoid redundant store updates when modifier values are unchanged
- Track bare modifier keydown and keyup events explicitly
- Load react-scan in the web app shell
Comment thread apps/web/index.html Outdated
- Keep the keyboard event test helper on one line
- No behavior change
Comment thread apps/web/index.html Outdated
<meta name="theme-color" content="#161616" />
<link rel="icon" href="/favicon.ico" sizes="48x48" />
<link rel="apple-touch-icon" href="/apple-touch-icon.png" />
<script crossorigin="anonymous" src="//unpkg.com/react-scan/dist/auto.global.js"></script>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Debug tool react-scan committed to production HTML

High Severity

The react-scan development debugging script is loaded from unpkg CDN in the production index.html. This tool renders visual overlays highlighting React re-renders and adds significant performance overhead. It will load for every user on every page load, degrading performance and showing debug UI in production.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 28445c5. Configure here.

- Preserve canonical model names and sub-provider labels separately
- Update picker search, trigger text, and favorites rendering
- Extend server contracts for `shortName` and `subProvider`
- Split model picker visibility out of shortcut modifier state
- Update sidebar and provider picker to use the new store
Copy link
Copy Markdown
Contributor

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

There are 4 total unresolved issues (including 3 from previous reviews).

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit d3119a9. Configure here.

toggleModelPicker: () => {
setIsComposerModelPickerOpen((open) => !open);
},
isModelPickerOpen: () => isComposerModelPickerOpen,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale closure in isModelPickerOpen imperative handle method

Medium Severity

The isModelPickerOpen method in the imperative handle captures isComposerModelPickerOpen by value. When toggleModelPicker() is called (e.g., via Mod+Shift+M), the state update is asynchronous — the handle isn't recreated until React commits the re-render. During this window, composerRef.current?.isModelPickerOpen() in ChatView.tsx's keydown handler returns the stale pre-toggle value. This means the shortcutContext.modelPickerOpen fed to resolveShortcutCommand can be wrong, causing e.g. thread.jump.1 to resolve instead of modelPicker.jump.1 immediately after toggling the picker open. Using a ref for the open state (or reading from the global Zustand store directly) would avoid this race.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit d3119a9. Configure here.

- Use the active provider's first model when the current slug belongs to another provider
- Add a regression test for stale cross-provider model labels
@juliusmarminge juliusmarminge merged commit 66c326b into pingdotgg:main Apr 20, 2026
12 checks passed
@Chrono-byte Chrono-byte deleted the feature/model-picker-favorites branch April 20, 2026 06:03
aaditagrawal added a commit to aaditagrawal/t3code that referenced this pull request Apr 20, 2026
Upstream additions:
- fix(web): restore manual sort drag and keep per-group expand state (pingdotgg#2221)
- fix: Change right panel sheet to be below title bar / action bar (pingdotgg#2224)
- Refactor OpenCode lifecycle and structured output handling (pingdotgg#2218)
- effect-codex-app-server (pingdotgg#1942)
- Redesign model picker with favorites and search (pingdotgg#2153)
- fix(server): prevent probeClaudeCapabilities from wasting API requests (pingdotgg#2192)
- fix(server): handle OpenCode text response format in commit message gen (pingdotgg#2202)
- Devcontainer / IDE updates (pingdotgg#2208)
- Expand leading ~ in Codex home paths before exporting CODEX_HOME (pingdotgg#2210)
- fix(release): use v<semver> tag format for nightly releases (pingdotgg#2186)

Fork adaptations:
- Took upstream's redesigned model picker with favorites and search
- Removed deleted codexAppServerManager (replaced by effect-codex-app-server)
- Stubbed fetchCodexUsage (manager-based readout no longer available)
- Extended PROVIDER_ICON_BY_PROVIDER for all 8 fork providers
- Extended modelOptionsByProvider test fixtures for all 8 providers
- Inline ClaudeSlashCommand type (not yet re-exported from SDK)
- Updated SettingsPanels imports for new picker module structure
- Preserved fork's CI customizations (ubuntu-24.04 not Blacksmith)
Marve10s added a commit to Marve10s/t3code that referenced this pull request Apr 20, 2026
Resolve conflict in apps/web/src/components/Sidebar.tsx by keeping
clampSidebarThreadPreviewCount (still used by this PR's stepper input)
and dropping threadJumpLabelMapsEqual, which upstream removed along with
its callers in the model picker redesign (pingdotgg#2153).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XXL 1,000+ changed lines (additions + deletions). vouch:unvouched PR author is not yet trusted in the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants