Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 12 additions & 13 deletions src/browser/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down Expand Up @@ -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 (
<ErrorBoundary
workspaceInfo={`${selectedWorkspace.projectName}/${workspaceName}`}
Expand All @@ -591,9 +590,9 @@ function AppInner() {
projectName={selectedWorkspace.projectName}
branch={workspaceName}
namedWorkspacePath={workspacePath}
runtimeConfig={currentMetadata?.runtimeConfig}
incompatibleRuntime={currentMetadata?.incompatibleRuntime}
status={currentMetadata?.status}
runtimeConfig={currentMetadata.runtimeConfig}
incompatibleRuntime={currentMetadata.incompatibleRuntime}
status={currentMetadata.status}
/>
</ErrorBoundary>
);
Expand Down
12 changes: 9 additions & 3 deletions src/browser/contexts/WorkspaceContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<string, FrontendWorkspaceMetadata>();
Expand All @@ -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();
Expand Down