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 cc2f22917a..402e1ca407 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 745e0369a1..c3dbdea3c3 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 fcf939101a..e9fa259651 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 ffe23fa781..afee8096b9 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 534bf759bf..696cd37051 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 c681c62ebc..84a559cea4 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, }} />