You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Unifies the two chat model pickers (in-session ChatModelSwitcher, new-chat NewChatModelPicker) onto one governed dropdown form. Their grouped list, provider headings, and checkmark rows now wear the shared .settingsSelectMenu* recipe in the settings-select family — replacing the bespoke .maka-model-switcher-* popup CSS — and group headings carry the provider brand mark.
Why
Two chat pickers each hand-rolled their own popup/heading/row CSS, drifting from the governed settings-select look. Folding the grouped-menu look into that one CSS family gives every picker the same list, headings, and checkmark selection, with brand logos on the provider groups, instead of a parallel recipe each future reader must decode.
Closes # (no linked issue)
Scope
Changed:
feat(ui) — add the .settingsSelectMenu* CSS recipe (grouped popup geometry, provider heading with a brand-mark gutter, checkmark rows) to the settings-select family.
refactor(ui) — render both chat pickers' grouped list through it via the shared ModelChoiceOptions; delete the bespoke .maka-model-switcher-* popup/positioner/heading/row CSS (trigger CSS kept). Headings drop the uppercase eyebrow for sentence-case at weight 500. Brand mark injected from the desktop app via the existing renderProviderMark seam so the provider SVG library stays out of @maka/ui. ModelMenuGroup gains providerType to pick the mark.
fix(ui) — make the capped menu popup the scroll container; it capped at 420px with overflow visible, so a provider with many models spilled its overflow rows below the rounded box. Now they clip and scroll (keyboard nav to the last row verified over CDP).
test(ui) — behavioral coverage for ModelMenuGroup.providerType; contract tests re-pointed to the new chrome.
Not included:
The composer pill trigger is deliberately untouched — its 22px pill chrome (status dot, pending state, press feedback) is composer-layout concern, not model selection, and folding it into the settings trigger would add CSS with no payoff. This PR governs the list, not the trigger.
The sticky-model send-path contract guards (session-sticky-model-contract.test.ts) are intact — only the picker-chrome assertions move.
No new React render-test harness: the repo has none (pure-logic node:test + source contracts + CDP visual smoke). Interactive behavior is verified over CDP; introducing RTL is out of scope.
Verification
npm run typecheck (all workspaces) — clean
npm run -w @maka/desktop test — 1608 pass
npm run -w @maka/ui test — 13 pass
Live-app CDP (turn-narrative fixture): grouped menu renders Z.AI / 自定义 / Anthropic / OpenAI headings with brand marks (Anthropic in brand orange) + checkmark on the selected row; overflow clips + scrolls; ArrowDown/End reaches and reveals the last row (lastFullyVisibleInPopup: true).
User-facing impact
The in-session and new-chat model menus now group models by provider with a brand mark on each heading; selection is the standard checkmark. No behavior change to model switching or new-chat creation. No docs / CHANGELOG / migration.
Reviewer notes
Heading weight is 500 (not the old 600): 600 propped up an uppercase eyebrow; sentence-case + a colored brand mark + muted ink already differentiate headings from full-ink rows, so 500 is the calmer, lower-surprise choice (matches the base SelectGroupLabel default).
No premature abstraction: the grouped body lives in ModelChoiceOptions (shared by both pickers); the only cross-cutting governance is the .settingsSelectMenu* CSS recipe. A reusable component waits for a second, non-chat grouped select.
Net production LOC is roughly flat — the value is consolidating the grouped-menu look into one owned style family, not a raw line reduction.
Add SettingsSelectGroups — a grouped popup body (provider-style headings
with an optional injected brand mark over the shared SelectItem checkmark
rows) — plus the .settingsSelectMenu* CSS recipe that pins its roomier
geometry. This folds the grouped-menu look into the one settings-select
family so any picker with a bespoke trigger can render the same governed
list without a parallel CSS recipe.
…ect form
The in-session and new-chat model pickers rendered their grouped list with a
parallel '.maka-model-switcher-*' popup recipe. Route both through the shared
SettingsSelectGroups menu body so the list, headings, and checkmark rows are
the one governed look; the composer pill trigger stays bespoke and untouched.
- ModelChoiceOptions delegates to SettingsSelectGroups; popups use the
'.settingsSelectMenu*' classes; the bespoke popup/positioner/heading/row CSS
is deleted (trigger CSS kept).
- Group headings carry the provider brand mark, injected from the desktop app
via the existing renderProviderMark seam (threaded through the Composer) so
the provider SVG library stays out of @maka/ui. Headings drop the uppercase
eyebrow for sentence-case at weight 500 — the mark now anchors scanning.
- ModelMenuGroup carries providerType so the menu can pick the right mark.
- Contract tests: re-point only the popup-chrome assertions (the grouped list
now flows through the shared form); the sticky-model send-path guards are
untouched.
The .settingsSelectMenuPopup capped its height at 420px but kept overflow
visible, and the inner SelectList's --available-height ceiling was too tall
to engage — so when a provider exposes many models the overflow rows spilled
out below the rounded box (verified over CDP: scrollHeight 479 > clientHeight
418, last row bottom 806 vs popup bottom 754). Make the capped popup the
scroll container so the rows clip and scroll cleanly.
Review (P3, Occam): SettingsSelectGroups + SettingsSelectGroup wrapped what is
only shared inside the model menu into a 'generic' component with its own type,
adding a production layer whose sole consumer was ModelChoiceOptions. Render the
provider headings, brand mark, and rows directly in ModelChoiceOptions; the
shared governance stays in the .settingsSelectMenu* CSS recipe. Extract a
component again only when a second, non-chat grouped select appears.
Both pickers still share one ModelChoiceOptions; grouping, the selected check,
and the unknown-current-model fallback are unchanged.
Review (P3): add behavioral coverage for the ModelMenuGroup.providerType field
introduced for the grouped menu — a dropped/wrong providerType would silently
render the wrong provider logo. Pure-logic test alongside the existing
heading/leak-safety coverage (the repo has no React render harness; interactive
behavior — open, headings, brand mark, selected row, keyboard scroll — is
verified over CDP).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Unifies the two chat model pickers (in-session
ChatModelSwitcher, new-chatNewChatModelPicker) onto one governed dropdown form. Their grouped list, provider headings, and checkmark rows now wear the shared.settingsSelectMenu*recipe in thesettings-selectfamily — replacing the bespoke.maka-model-switcher-*popup CSS — and group headings carry the provider brand mark.Why
Two chat pickers each hand-rolled their own popup/heading/row CSS, drifting from the governed
settings-selectlook. Folding the grouped-menu look into that one CSS family gives every picker the same list, headings, and checkmark selection, with brand logos on the provider groups, instead of a parallel recipe each future reader must decode.Closes # (no linked issue)
Scope
Changed:
feat(ui)— add the.settingsSelectMenu*CSS recipe (grouped popup geometry, provider heading with a brand-mark gutter, checkmark rows) to thesettings-selectfamily.refactor(ui)— render both chat pickers' grouped list through it via the sharedModelChoiceOptions; delete the bespoke.maka-model-switcher-*popup/positioner/heading/row CSS (trigger CSS kept). Headings drop the uppercase eyebrow for sentence-case at weight 500. Brand mark injected from the desktop app via the existingrenderProviderMarkseam so the provider SVG library stays out of@maka/ui.ModelMenuGroupgainsproviderTypeto pick the mark.fix(ui)— make the capped menu popup the scroll container; it capped at 420px with overflow visible, so a provider with many models spilled its overflow rows below the rounded box. Now they clip and scroll (keyboard nav to the last row verified over CDP).test(ui)— behavioral coverage forModelMenuGroup.providerType; contract tests re-pointed to the new chrome.Not included:
session-sticky-model-contract.test.ts) are intact — only the picker-chrome assertions move.node:test+ source contracts + CDP visual smoke). Interactive behavior is verified over CDP; introducing RTL is out of scope.Verification
npm run typecheck(all workspaces) — cleannpm run -w @maka/desktop test— 1608 passnpm run -w @maka/ui test— 13 passlastFullyVisibleInPopup: true).User-facing impact
The in-session and new-chat model menus now group models by provider with a brand mark on each heading; selection is the standard checkmark. No behavior change to model switching or new-chat creation. No docs / CHANGELOG / migration.
Reviewer notes
SelectGroupLabeldefault).ModelChoiceOptions(shared by both pickers); the only cross-cutting governance is the.settingsSelectMenu*CSS recipe. A reusable component waits for a second, non-chat grouped select.