diff --git a/apps/desktop/src/lib/trpc/routers/workspaces/utils/db-helpers.ts b/apps/desktop/src/lib/trpc/routers/workspaces/utils/db-helpers.ts index 065e76b4e86..b5156dfde95 100644 --- a/apps/desktop/src/lib/trpc/routers/workspaces/utils/db-helpers.ts +++ b/apps/desktop/src/lib/trpc/routers/workspaces/utils/db-helpers.ts @@ -109,13 +109,21 @@ export function hideProjectIfNoWorkspaces(projectId: string): void { /** * Select the next active workspace after the current one is removed. * Returns the ID of the next workspace to activate, or null if none. - * Selects the most recently opened workspace (excluding those being deleted). + * Selects the most recently opened workspace from VISIBLE projects only + * (projects with tabOrder != null). This ensures the selected workspace + * will appear in the sidebar and can be properly displayed by the frontend. */ export function selectNextActiveWorkspace(): string | null { const sorted = localDb - .select() + .select({ id: workspaces.id, lastOpenedAt: workspaces.lastOpenedAt }) .from(workspaces) - .where(isNull(workspaces.deletingAt)) + .innerJoin(projects, eq(workspaces.projectId, projects.id)) + .where( + and( + isNull(workspaces.deletingAt), + isNotNull(projects.tabOrder), // Only visible projects + ), + ) .orderBy(desc(workspaces.lastOpenedAt)) .all(); return sorted[0]?.id ?? null; diff --git a/apps/desktop/src/renderer/react-query/workspaces/useCloseWorkspace.ts b/apps/desktop/src/renderer/react-query/workspaces/useCloseWorkspace.ts index c67674c4324..27deddecacc 100644 --- a/apps/desktop/src/renderer/react-query/workspaces/useCloseWorkspace.ts +++ b/apps/desktop/src/renderer/react-query/workspaces/useCloseWorkspace.ts @@ -64,34 +64,40 @@ export function useCloseWorkspace( ); } - // If closing the active workspace, switch to another workspace optimistically - // This prevents a flash of "no workspace" state while the backend processes + // Switch to next workspace to prevent "no workspace" flash if (previousActive?.id === id) { - // Find the next workspace to switch to (matches backend logic: most recently opened) const remainingWorkspaces = previousAll ?.filter((w) => w.id !== id) .sort((a, b) => b.lastOpenedAt - a.lastOpenedAt); if (remainingWorkspaces && remainingWorkspaces.length > 0) { - const nextWorkspace = remainingWorkspaces[0]; - // Find the project info for the next workspace from grouped data - const projectGroup = previousGrouped?.find((g) => - g.workspaces.some((w) => w.id === nextWorkspace.id), - ); - const workspaceFromGrouped = projectGroup?.workspaces.find( - (w) => w.id === nextWorkspace.id, - ); + // Find a workspace with full data available in previousGrouped + let selectedWorkspace = null; + let projectGroup = null; + let workspaceFromGrouped = null; - if (projectGroup && workspaceFromGrouped) { - // For worktree-type workspaces, provide minimal worktree data to prevent - // hasIncompleteInit from triggering the initialization view + for (const candidate of remainingWorkspaces) { + const group = previousGrouped?.find((g) => + g.workspaces.some((w) => w.id === candidate.id), + ); + if (group) { + selectedWorkspace = candidate; + projectGroup = group; + workspaceFromGrouped = group.workspaces.find( + (w) => w.id === candidate.id, + ); + break; + } + } + + if (selectedWorkspace && projectGroup && workspaceFromGrouped) { const worktreeData = workspaceFromGrouped.type === "worktree" ? { - branch: nextWorkspace.branch, + branch: selectedWorkspace.branch, baseBranch: null, gitStatus: { - branch: nextWorkspace.branch, + branch: selectedWorkspace.branch, needsRebase: false, lastRefreshed: Date.now(), }, @@ -99,7 +105,7 @@ export function useCloseWorkspace( : null; utils.workspaces.getActive.setData(undefined, { - ...nextWorkspace, + ...selectedWorkspace, type: workspaceFromGrouped.type, worktreePath: workspaceFromGrouped.worktreePath, project: { @@ -110,11 +116,17 @@ export function useCloseWorkspace( worktree: worktreeData, }); } else { - // Fallback: just clear it and let invalidate handle it - utils.workspaces.getActive.setData(undefined, null); + // Fallback: set minimal data to prevent StartView flash (refetch will populate full data) + const fallback = remainingWorkspaces[0]; + utils.workspaces.getActive.setData(undefined, { + ...fallback, + type: fallback.type === "branch" ? "branch" : "worktree", + worktreePath: "", + project: null, + worktree: null, + }); } } else { - // No remaining workspaces utils.workspaces.getActive.setData(undefined, null); } } diff --git a/apps/desktop/src/renderer/react-query/workspaces/useDeleteWorkspace.ts b/apps/desktop/src/renderer/react-query/workspaces/useDeleteWorkspace.ts index 27e97119f55..bfa21e95f26 100644 --- a/apps/desktop/src/renderer/react-query/workspaces/useDeleteWorkspace.ts +++ b/apps/desktop/src/renderer/react-query/workspaces/useDeleteWorkspace.ts @@ -66,22 +66,33 @@ export function useDeleteWorkspace( .sort((a, b) => b.lastOpenedAt - a.lastOpenedAt); if (remainingWorkspaces && remainingWorkspaces.length > 0) { - const nextWorkspace = remainingWorkspaces[0]; - const projectGroup = previousGrouped?.find((g) => - g.workspaces.some((w) => w.id === nextWorkspace.id), - ); - const workspaceFromGrouped = projectGroup?.workspaces.find( - (w) => w.id === nextWorkspace.id, - ); + // Find a workspace with full data available in previousGrouped + let selectedWorkspace = null; + let projectGroup = null; + let workspaceFromGrouped = null; - if (projectGroup && workspaceFromGrouped) { + for (const candidate of remainingWorkspaces) { + const group = previousGrouped?.find((g) => + g.workspaces.some((w) => w.id === candidate.id), + ); + if (group) { + selectedWorkspace = candidate; + projectGroup = group; + workspaceFromGrouped = group.workspaces.find( + (w) => w.id === candidate.id, + ); + break; + } + } + + if (selectedWorkspace && projectGroup && workspaceFromGrouped) { const worktreeData = workspaceFromGrouped.type === "worktree" ? { - branch: nextWorkspace.branch, + branch: selectedWorkspace.branch, baseBranch: null, gitStatus: { - branch: nextWorkspace.branch, + branch: selectedWorkspace.branch, needsRebase: false, lastRefreshed: Date.now(), }, @@ -89,7 +100,7 @@ export function useDeleteWorkspace( : null; utils.workspaces.getActive.setData(undefined, { - ...nextWorkspace, + ...selectedWorkspace, type: workspaceFromGrouped.type, worktreePath: workspaceFromGrouped.worktreePath, project: { @@ -100,7 +111,15 @@ export function useDeleteWorkspace( worktree: worktreeData, }); } else { - utils.workspaces.getActive.setData(undefined, null); + // Fallback: set minimal data to prevent StartView flash (refetch will populate full data) + const fallback = remainingWorkspaces[0]; + utils.workspaces.getActive.setData(undefined, { + ...fallback, + type: fallback.type === "branch" ? "branch" : "worktree", + worktreePath: "", + project: null, + worktree: null, + }); } } else { utils.workspaces.getActive.setData(undefined, null);