Rework provider selector and settings UX

Surface all enabled providers in the model selector with inline loading
and error states instead of silently hiding non-ready ones. Tap any row
to drill in; loading and error states show in the drill-down body, with
a Retry button on error. Providers with no models render a synthetic
"Default" row so every provider's drill-down has symmetric structure.

Redesign the per-provider settings sheet: search lives in the header,
discovered and custom models share one scrolling list, and the footer
hosts action buttons. Diagnostic and "Add model" each open as their own
sub-sheet, so neither expands inline or shifts the surrounding layout.

Snapshot refresh now keeps cached data visible: setQueryData replaces
the cache atomically and sibling-scope caches are invalidated (not
removed), so refreshing a provider no longer blanks its row. Search
inputs across the app are uncontrolled — onChangeText drives parent
state, and resetKey remounts the field when an explicit reset is needed
— to avoid RN's controlled-input flicker.

Adds a dev-only mock-slow provider whose discovery never resolves, so
the 30s snapshot timeout produces a real error and lets loading and
timeout-error UI be exercised end to end.
This commit is contained in:
Mohamed Boudra
2026-05-27 22:00:34 +07:00
parent 5696cdb455
commit a4cb7431d8
18 changed files with 1018 additions and 622 deletions
@@ -93,6 +93,7 @@ interface ControlledAgentControlsProps {
onModelSelectorOpen?: () => void;
/** Extra elements rendered inline with the agent controls (desktop only). */
desktopExtras?: ReactNode;
modelSelectorServerId?: string | null;
}
export interface DraftAgentControlsProps {
@@ -117,6 +118,7 @@ export interface DraftAgentControlsProps {
onDropdownClose?: () => void;
onModelSelectorOpen?: () => void;
disabled?: boolean;
modelSelectorServerId?: string | null;
}
interface AgentControlsProps {
@@ -410,6 +412,7 @@ function ControlledAgentControls({
onDropdownClose,
onModelSelectorOpen,
desktopExtras,
modelSelectorServerId = null,
}: ControlledAgentControlsProps) {
const { theme } = useUnistyles();
const isCompact = useIsCompactFormFactor();
@@ -613,6 +616,7 @@ function ControlledAgentControls({
handleOpenChange={handleOpenChange}
renderThinkingOption={renderThinkingOption}
extras={desktopExtras}
modelSelectorServerId={modelSelectorServerId}
/>
) : (
<SheetAgentControlsContent
@@ -641,6 +645,7 @@ function ControlledAgentControls({
handleSelectThinkingAndClose={handleSelectThinkingAndClose}
handleOpenChange={handleOpenChange}
renderThinkingOption={renderThinkingOption}
modelSelectorServerId={modelSelectorServerId}
/>
)}
</View>
@@ -693,6 +698,7 @@ interface DesktopAgentControlsContentProps {
onPress: () => void;
}) => ReactElement;
extras?: ReactNode;
modelSelectorServerId: string | null;
}
const DESKTOP_SEARCH_THRESHOLD = 6;
@@ -739,6 +745,7 @@ function DesktopAgentControlsContent(props: DesktopAgentControlsContentProps) {
handleOpenChange,
renderThinkingOption,
extras,
modelSelectorServerId,
} = props;
return (
@@ -791,6 +798,7 @@ function DesktopAgentControlsContent(props: DesktopAgentControlsContentProps) {
disabled={modelDisabled}
onOpen={onModelSelectorOpen}
onClose={onDropdownClose}
serverId={modelSelectorServerId}
/>
</View>
</TooltipTrigger>
@@ -884,6 +892,7 @@ interface SheetAgentControlsContentProps {
active: boolean;
onPress: () => void;
}) => ReactElement;
modelSelectorServerId: string | null;
}
function SheetAgentControlsContent(props: SheetAgentControlsContentProps) {
@@ -914,6 +923,7 @@ function SheetAgentControlsContent(props: SheetAgentControlsContentProps) {
handleSelectThinkingAndClose,
handleOpenChange,
renderThinkingOption,
modelSelectorServerId,
} = props;
const thinkingAnchorRef = useRef<View | null>(null);
@@ -983,6 +993,7 @@ function SheetAgentControlsContent(props: SheetAgentControlsContentProps) {
onOpen={onModelSelectorOpen}
onClose={onDropdownClose}
renderTrigger={renderModelTrigger}
serverId={modelSelectorServerId}
/>
) : null}
@@ -1521,6 +1532,7 @@ export const AgentControls = memo(function AgentControls({
onDropdownClose={onDropdownClose}
disabled={!client}
desktopExtras={modeChip}
modelSelectorServerId={serverId}
/>
);
});
@@ -1547,6 +1559,7 @@ export function DraftAgentControls({
onDropdownClose,
onModelSelectorOpen,
disabled = false,
modelSelectorServerId = null,
}: DraftAgentControlsProps) {
const { preferences, updatePreferences } = useFormPreferences();
const isCompact = useIsCompactFormFactor();
@@ -1614,6 +1627,7 @@ export function DraftAgentControls({
disabled={disabled}
onOpen={onModelSelectorOpen}
onClose={onDropdownClose}
serverId={modelSelectorServerId}
/>
{selectedProvider ? (
<ControlledAgentControls
@@ -1650,6 +1664,7 @@ export function DraftAgentControls({
onSetFeature={onSetFeature}
onModelSelectorOpen={onModelSelectorOpen}
disabled={disabled}
modelSelectorServerId={modelSelectorServerId}
/>
);
}
@@ -187,13 +187,12 @@ function AgentModeControlView({
() => ({
title: "Mode",
search: {
value: searchQuery,
onChange: setSearchQuery,
placeholder: "Search modes...",
testID: "mode-search-input",
},
}),
[searchQuery],
[],
);
if (!selectedMode) return null;
@@ -46,6 +46,7 @@ export function buildDraftAgentControls(input: {
onSetFeature,
onDropdownClose,
onModelSelectorOpen: formState.refetchProviderModelsIfStale,
modelSelectorServerId: formState.selectedServerId,
};
}