From 41960cb691424002200ed7d3cdabc72026d0be0a Mon Sep 17 00:00:00 2001 From: Dara Adedeji Date: Thu, 16 Jul 2026 14:07:29 -0700 Subject: [PATCH 1/3] redesign(model-picker): compact rows, shortened model names, refined selection styling - Shorten display names in the picker (GPT-5.6-Sol -> 5.6 Sol, Claude Fable 5 -> Fable 5) via getModelPickerDisplayAlias, with unit tests - Hide the per-row provider line on single-provider tabs (the sidebar already shows the provider); keep it for search, favorites, and sub-provider models - Blue-tinted selected-row state, tighter row metrics (rounded-sm, text-sm) - Fix LegendList stale-row renders by passing all row-render inputs through extraData instead of only favoritesSet - Move the scrollbar styling class onto the actual LegendList scroll element; widen the scrollbar hit area with an inset pill thumb Co-Authored-By: Claude Fable 5 --- apps/web/src/components/chat/ModelListRow.tsx | 10 ++-- .../components/chat/ModelPickerContent.tsx | 40 ++++++++++++++-- .../components/chat/ModelPickerSidebar.tsx | 9 ++-- .../components/chat/providerIconUtils.test.ts | 48 +++++++++++++++++++ .../src/components/chat/providerIconUtils.ts | 18 ++++++- apps/web/src/index.css | 21 +++++--- 6 files changed, 123 insertions(+), 23 deletions(-) create mode 100644 apps/web/src/components/chat/providerIconUtils.test.ts diff --git a/apps/web/src/components/chat/ModelListRow.tsx b/apps/web/src/components/chat/ModelListRow.tsx index 3f8915e5d8b..de7c04ee8ca 100644 --- a/apps/web/src/components/chat/ModelListRow.tsx +++ b/apps/web/src/components/chat/ModelListRow.tsx @@ -50,15 +50,15 @@ export const ModelListRow = memo(function ModelListRow(props: { disabled={Boolean(props.disabledReason)} contentClassName="flex w-full items-center gap-3" className={cn( - "group relative w-full !min-w-0 max-w-full cursor-pointer rounded-md px-2 py-2.5 transition-[background-color,box-shadow,color]", - "data-highlighted:bg-muted/56 data-selected:bg-transparent data-selected:text-foreground data-selected:ring-0", + "group relative my-0.5 w-full !min-w-0 max-w-full cursor-pointer rounded-sm px-2 py-2 transition-[background-color,box-shadow,color]", + "data-highlighted:bg-muted/56 data-selected:bg-blue-500/10 data-selected:text-foreground data-selected:ring-0 [&[data-highlighted][data-selected]]:bg-blue-500/15", props.disabledReason && "data-disabled:pointer-events-auto data-disabled:cursor-not-allowed data-disabled:hover:bg-transparent", )} >
-
+
{props.useTriggerLabel ? getTriggerDisplayModelLabel(props.model) : getDisplayModelName( @@ -69,7 +69,7 @@ export const ModelListRow = memo(function ModelListRow(props: { {props.isSelected ? : null} {props.showNewBadge ? ( New @@ -97,7 +97,7 @@ export const ModelListRow = memo(function ModelListRow(props: { size="icon-xs" variant="ghost" className={cn( - "-mr-1 shrink-0 text-muted-foreground/70 opacity-64 transition-[color,opacity] hover:text-foreground hover:opacity-100 group-hover:opacity-100", + "-mr-1 shrink-0 rounded-sm text-muted-foreground/70 opacity-64 transition-[color,opacity] before:rounded-[calc(var(--radius-sm)-1px)] hover:text-foreground hover:opacity-100 group-hover:opacity-100", props.isFavorite && "text-foreground opacity-100", )} onClick={(event) => { diff --git a/apps/web/src/components/chat/ModelPickerContent.tsx b/apps/web/src/components/chat/ModelPickerContent.tsx index e0d6b9afc5e..3320eed8b63 100644 --- a/apps/web/src/components/chat/ModelPickerContent.tsx +++ b/apps/web/src/components/chat/ModelPickerContent.tsx @@ -504,6 +504,28 @@ export const ModelPickerContent = memo(function ModelPickerContent(props: { }; }, [handleModelSelect, keybindings, modelJumpModelKeys, modelJumpShortcutContext]); + // LegendList only re-renders a row when its data item or `extraData` + // changes — a new `renderItem` closure alone is ignored. Everything the + // row render reads from surrounding state must therefore be represented + // here, or rows that keep their list position render stale output. + const listExtraData = useMemo( + () => ({ + favoritesSet, + isSearching, + selectedInstanceId, + activeModelKey: `${props.activeInstanceId}:${props.model}`, + modelJumpLabelByKey, + }), + [ + favoritesSet, + isSearching, + selectedInstanceId, + props.activeInstanceId, + props.model, + modelJumpLabelByKey, + ], + ); + useLayoutEffect(() => { setShowTopScrollFade(false); setShowBottomScrollFade(filteredModelKeys.length > 5); @@ -619,11 +641,11 @@ export const ModelPickerContent = memo(function ModelPickerContent(props: { {/* Model list */}
- + ref={modelListRef} data={filteredModelKeys} - extraData={favoritesSet} + extraData={listExtraData} keyExtractor={(modelKey) => modelKey} renderItem={({ item: modelKey, index }) => { const model = filteredModelByKey.get(modelKey); @@ -643,7 +665,17 @@ export const ModelPickerContent = memo(function ModelPickerContent(props: { providerAccentColor={model.instanceAccentColor} isFavorite={favoritesSet.has(modelKey)} isSelected={modelKey === `${props.activeInstanceId}:${props.model}`} - showProvider + showProvider={ + // When browsing a single provider's tab the sidebar + // already says which provider these models belong + // to, so the per-row provider line is redundant. + // Keep it in mixed-provider views (favorites, + // search) and for models that carry a sub-provider + // distinction (e.g. "Claude · Bedrock"). + isSearching || + selectedInstanceId === "favorites" || + Boolean(model.subProvider) + } preferShortName={!isLocked} useTriggerLabel={false} showNewBadge={isModelPickerNewModel(model.driverKind, model.slug)} @@ -659,7 +691,7 @@ export const ModelPickerContent = memo(function ModelPickerContent(props: { onLayout={updateModelListScrollFades} onScroll={updateModelListScrollFades} className={cn( - "scrollbar-gutter-both h-full overflow-x-hidden overscroll-y-contain py-1.5 [--fade-size:1.5rem]", + "model-picker-list scrollbar-gutter-both h-full overflow-x-hidden overscroll-y-contain py-1.5 [--fade-size:1.5rem]", showTopScrollFade && "mask-t-from-[calc(100%-var(--fade-size))]", showBottomScrollFade && "mask-b-from-[calc(100%-var(--fade-size))]", )} diff --git a/apps/web/src/components/chat/ModelPickerSidebar.tsx b/apps/web/src/components/chat/ModelPickerSidebar.tsx index e5555cb0115..c0a479e62ea 100644 --- a/apps/web/src/components/chat/ModelPickerSidebar.tsx +++ b/apps/web/src/components/chat/ModelPickerSidebar.tsx @@ -102,10 +102,7 @@ export const ModelPickerSidebar = memo(function ModelPickerSidebar(props: { data-model-picker-sidebar="true" >
-
+
{selectedIndicatorTop !== null ? (
handleSelect("favorites")} type="button" @@ -172,7 +169,7 @@ export const ModelPickerSidebar = memo(function ModelPickerSidebar(props: {