diff --git a/src/browser/App.tsx b/src/browser/App.tsx index f7fb5498bb..20874b4c3b 100644 --- a/src/browser/App.tsx +++ b/src/browser/App.tsx @@ -121,14 +121,6 @@ function AppInner() { prevWorkspaceRef.current = selectedWorkspace; }, [selectedWorkspace, telemetry]); - // Validate selectedWorkspace when metadata changes - // Clear selection if workspace was deleted - useEffect(() => { - if (selectedWorkspace && !workspaceMetadata.has(selectedWorkspace.workspaceId)) { - setSelectedWorkspace(null); - } - }, [selectedWorkspace, workspaceMetadata, setSelectedWorkspace]); - // Track last-read timestamps for unread indicators const { lastReadTimestamps, onToggleUnread } = useUnreadTracking(selectedWorkspace); @@ -571,15 +563,22 @@ function AppInner() { {selectedWorkspace ? ( (() => { const currentMetadata = workspaceMetadata.get(selectedWorkspace.workspaceId); + // Guard: Don't render AIView if workspace metadata not found. + // This can happen when selectedWorkspace (from localStorage) refers to a + // deleted workspace, or during a race condition on reload before the + // validation effect clears the stale selection. + if (!currentMetadata) { + return null; + } // Use metadata.name for workspace name (works for both worktree and local runtimes) // Fallback to path-based derivation for legacy compatibility const workspaceName = - currentMetadata?.name ?? + currentMetadata.name ?? selectedWorkspace.namedWorkspacePath?.split("/").pop() ?? selectedWorkspace.workspaceId; // Use live metadata path (updates on rename) with fallback to initial path const workspacePath = - currentMetadata?.namedWorkspacePath ?? selectedWorkspace.namedWorkspacePath ?? ""; + currentMetadata.namedWorkspacePath ?? selectedWorkspace.namedWorkspacePath ?? ""; return ( ); diff --git a/src/browser/contexts/WorkspaceContext.tsx b/src/browser/contexts/WorkspaceContext.tsx index 293308db24..b62990427d 100644 --- a/src/browser/contexts/WorkspaceContext.tsx +++ b/src/browser/contexts/WorkspaceContext.tsx @@ -118,7 +118,7 @@ export function WorkspaceProvider(props: WorkspaceProviderProps) { ); const loadWorkspaceMetadata = useCallback(async () => { - if (!api) return; + if (!api) return false; // Return false to indicate metadata wasn't loaded try { const metadataList = await api.workspace.list(undefined); const metadataMap = new Map(); @@ -128,16 +128,22 @@ export function WorkspaceProvider(props: WorkspaceProviderProps) { metadataMap.set(metadata.id, metadata); } setWorkspaceMetadata(metadataMap); + return true; // Return true to indicate metadata was loaded } catch (error) { console.error("Failed to load workspace metadata:", error); setWorkspaceMetadata(new Map()); + return true; // Still return true - we tried to load, just got empty result } }, [setWorkspaceMetadata, api]); - // Load metadata once on mount + // Load metadata once on mount (and again when api becomes available) useEffect(() => { void (async () => { - await loadWorkspaceMetadata(); + const loaded = await loadWorkspaceMetadata(); + if (!loaded) { + // api not available yet - effect will run again when api connects + return; + } // After loading metadata (which may trigger migration), reload projects // to ensure frontend has the updated config with workspace IDs await refreshProjects();