From 1d41a50e1ff50b6569785328e9a91c77a3afb1dc Mon Sep 17 00:00:00 2001 From: Mohamed Boudra Date: Fri, 10 Apr 2026 11:01:01 +0700 Subject: [PATCH] Background revalidate provider models when model selector opens On fresh startup the initial provider snapshot request can return before providers finish loading, leaving the model picker empty. The server broadcasts updates once providers are ready, but if the user opens the picker in the interim they see "No models match". Invalidate the React Query cache (background refetch, no loading flash) every time the model selector popup opens so stale data is silently refreshed. --- packages/app/src/components/agent-status-bar.tsx | 10 ++++++++++ .../app/src/components/combined-model-selector.tsx | 8 ++++++-- packages/app/src/hooks/use-agent-form-state.ts | 8 ++++++++ packages/app/src/hooks/use-providers-snapshot.ts | 6 ++++++ packages/app/src/screens/agent/draft-agent-screen.tsx | 2 ++ .../screens/workspace/workspace-draft-agent-tab.tsx | 2 ++ 6 files changed, 34 insertions(+), 2 deletions(-) diff --git a/packages/app/src/components/agent-status-bar.tsx b/packages/app/src/components/agent-status-bar.tsx index cc2f22917..402e1ca40 100644 --- a/packages/app/src/components/agent-status-bar.tsx +++ b/packages/app/src/components/agent-status-bar.tsx @@ -88,6 +88,7 @@ type ControlledAgentStatusBarProps = { features?: AgentFeature[]; onSetFeature?: (featureId: string, value: unknown) => void; onDropdownClose?: () => void; + onModelSelectorOpen?: () => void; }; export interface DraftAgentStatusBarProps { @@ -110,6 +111,7 @@ export interface DraftAgentStatusBarProps { features?: AgentFeature[]; onSetFeature?: (featureId: string, value: unknown) => void; onDropdownClose?: () => void; + onModelSelectorOpen?: () => void; disabled?: boolean; } @@ -217,6 +219,7 @@ function ControlledStatusBar({ features, onSetFeature, onDropdownClose, + onModelSelectorOpen, }: ControlledAgentStatusBarProps) { const { theme } = useUnistyles(); const isWeb = Platform.OS === "web"; @@ -411,6 +414,7 @@ function ControlledStatusBar({ onToggleFavorite={onToggleFavoriteModel} isLoading={isModelLoading} disabled={modelDisabled} + onOpen={onModelSelectorOpen} onClose={onDropdownClose} /> @@ -662,6 +666,7 @@ function ControlledStatusBar({ onToggleFavorite={onToggleFavoriteModel} isLoading={isModelLoading} disabled={modelDisabled} + onOpen={onModelSelectorOpen} onClose={onDropdownClose} renderTrigger={({ selectedModelLabel }) => ( { @@ -1035,6 +1041,7 @@ export function AgentStatusBar({ agentId, serverId, onDropdownClose }: AgentStat }); }} isModelLoading={snapshotIsLoading || snapshotIsFetching} + onModelSelectorOpen={invalidateSnapshot} onDropdownClose={onDropdownClose} disabled={!client} /> @@ -1061,6 +1068,7 @@ export function DraftAgentStatusBar({ features, onSetFeature, onDropdownClose, + onModelSelectorOpen, disabled = false, }: DraftAgentStatusBarProps) { const isWeb = Platform.OS === "web"; @@ -1105,6 +1113,7 @@ export function DraftAgentStatusBar({ }} isLoading={isAllModelsLoading} disabled={disabled} + onOpen={onModelSelectorOpen} onClose={onDropdownClose} /> diff --git a/packages/app/src/components/combined-model-selector.tsx b/packages/app/src/components/combined-model-selector.tsx index 745e0369a..c3dbdea3c 100644 --- a/packages/app/src/components/combined-model-selector.tsx +++ b/packages/app/src/components/combined-model-selector.tsx @@ -59,6 +59,7 @@ interface CombinedModelSelectorProps { disabled: boolean; isOpen: boolean; }) => React.ReactNode; + onOpen?: () => void; onClose?: () => void; disabled?: boolean; } @@ -517,6 +518,7 @@ export function CombinedModelSelector({ favoriteKeys = new Set(), onToggleFavorite, renderTrigger, + onOpen, onClose, disabled = false, }: CombinedModelSelectorProps) { @@ -541,12 +543,14 @@ export function CombinedModelSelector({ (open: boolean) => { setIsOpen(open); setView(singleProviderView ?? { kind: "all" }); - if (!open) { + if (open) { + onOpen?.(); + } else { setSearchQuery(""); onClose?.(); } }, - [onClose, singleProviderView], + [onOpen, onClose, singleProviderView], ); const handleSelect = useCallback( diff --git a/packages/app/src/hooks/use-agent-form-state.ts b/packages/app/src/hooks/use-agent-form-state.ts index fcf939101..e9fa25965 100644 --- a/packages/app/src/hooks/use-agent-form-state.ts +++ b/packages/app/src/hooks/use-agent-form-state.ts @@ -93,6 +93,7 @@ type UseAgentFormStateResult = { isModelLoading: boolean; modelError: string | null; refreshProviderModels: () => void; + invalidateProviderModels: () => void; setProviderAndModelFromUser: (provider: AgentProvider, modelId: string) => void; workingDirIsEmpty: boolean; persistFormPreferences: () => Promise; @@ -375,6 +376,7 @@ export function useAgentFormState(options: UseAgentFormStateOptions = {}): UseAg isFetching: snapshotIsFetching, error: snapshotError, refresh: refreshSnapshot, + invalidate: invalidateSnapshot, } = useProvidersSnapshot(formState.serverId); const allProviderEntries = useMemo(() => snapshotEntries ?? [], [snapshotEntries]); @@ -648,6 +650,10 @@ export function useAgentFormState(options: UseAgentFormStateOptions = {}): UseAg refreshSnapshot(); }, [refreshSnapshot]); + const invalidateProviderModels = useCallback(() => { + invalidateSnapshot(); + }, [invalidateSnapshot]); + const persistFormPreferences = useCallback(async () => { const resolvedModel = resolveEffectiveModel(availableModels, formState.model); const modelId = resolvedModel?.id ?? formState.model; @@ -714,6 +720,7 @@ export function useAgentFormState(options: UseAgentFormStateOptions = {}): UseAg isModelLoading, modelError, refreshProviderModels, + invalidateProviderModels, setProviderAndModelFromUser, workingDirIsEmpty, persistFormPreferences, @@ -745,6 +752,7 @@ export function useAgentFormState(options: UseAgentFormStateOptions = {}): UseAg isModelLoading, modelError, refreshProviderModels, + invalidateProviderModels, setProviderAndModelFromUser, workingDirIsEmpty, persistFormPreferences, diff --git a/packages/app/src/hooks/use-providers-snapshot.ts b/packages/app/src/hooks/use-providers-snapshot.ts index ffe23fa78..afee8096b 100644 --- a/packages/app/src/hooks/use-providers-snapshot.ts +++ b/packages/app/src/hooks/use-providers-snapshot.ts @@ -17,6 +17,7 @@ interface UseProvidersSnapshotResult { error: string | null; supportsSnapshot: boolean; refresh: () => void; + invalidate: () => void; } export function useProvidersSnapshot(serverId: string | null): UseProvidersSnapshotResult { @@ -66,6 +67,10 @@ export function useProvidersSnapshot(serverId: string | null): UseProvidersSnaps void client.refreshProvidersSnapshot(); }, [client]); + const invalidate = useCallback(() => { + void queryClient.invalidateQueries({ queryKey }); + }, [queryClient, queryKey]); + return { entries: snapshotQuery.data?.entries ?? undefined, isLoading: snapshotQuery.isLoading, @@ -73,6 +78,7 @@ export function useProvidersSnapshot(serverId: string | null): UseProvidersSnaps error: snapshotQuery.error instanceof Error ? snapshotQuery.error.message : null, supportsSnapshot, refresh, + invalidate, }; } diff --git a/packages/app/src/screens/agent/draft-agent-screen.tsx b/packages/app/src/screens/agent/draft-agent-screen.tsx index 534bf759b..696cd3705 100644 --- a/packages/app/src/screens/agent/draft-agent-screen.tsx +++ b/packages/app/src/screens/agent/draft-agent-screen.tsx @@ -226,6 +226,7 @@ function DraftAgentScreenContent({ isModelLoading, modelError, refreshProviderModels, + invalidateProviderModels, setProviderAndModelFromUser, persistFormPreferences, } = useAgentFormState({ @@ -1268,6 +1269,7 @@ function DraftAgentScreenContent({ onSelectThinkingOption: setThinkingOptionFromUser, features: draftFeatures, onSetFeature: setDraftFeatureValue, + onModelSelectorOpen: invalidateProviderModels, disabled: isSubmitting, }} /> diff --git a/packages/app/src/screens/workspace/workspace-draft-agent-tab.tsx b/packages/app/src/screens/workspace/workspace-draft-agent-tab.tsx index c681c62eb..84a559cea 100644 --- a/packages/app/src/screens/workspace/workspace-draft-agent-tab.tsx +++ b/packages/app/src/screens/workspace/workspace-draft-agent-tab.tsx @@ -80,6 +80,7 @@ export function WorkspaceDraftAgentTab({ availableThinkingOptions, isModelLoading, setProviderAndModelFromUser, + invalidateProviderModels, persistFormPreferences, } = useAgentFormState({ initialServerId: serverId, @@ -373,6 +374,7 @@ export function WorkspaceDraftAgentTab({ features: draftFeatures, onSetFeature: handleSetFeatureWithFocus, onDropdownClose: () => focusInputRef.current?.(), + onModelSelectorOpen: invalidateProviderModels, disabled: isSubmitting, }} />