diff --git a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspaceItem/DashboardSidebarWorkspaceItem.tsx b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspaceItem/DashboardSidebarWorkspaceItem.tsx
index e6afb5f8e8d..748ccaa887f 100644
--- a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspaceItem/DashboardSidebarWorkspaceItem.tsx
+++ b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspaceItem/DashboardSidebarWorkspaceItem.tsx
@@ -4,7 +4,7 @@ import { useOptimisticCollectionActions } from "renderer/routes/_authenticated/h
import { useDeletingWorkspaces } from "renderer/routes/_authenticated/providers/DeletingWorkspacesProvider";
import { RenameBranchDialog } from "renderer/screens/main/components/WorkspaceSidebar/WorkspaceListItem/components";
import { useV2WorkspaceNotificationStatus } from "renderer/stores/v2-notifications";
-import { useWorkspaceCreateFailuresStore } from "renderer/stores/workspace-creates";
+import { useWorkspaceCreatesStore } from "renderer/stores/workspace-creates";
import { useDashboardSidebarHover } from "../../providers/DashboardSidebarHoverProvider";
import type { DashboardSidebarWorkspace } from "../../types";
import { DashboardSidebarDeleteDialog } from "../DashboardSidebarDeleteDialog";
@@ -83,7 +83,7 @@ export function DashboardSidebarWorkspaceItem({
const isDeleting = useDeletingWorkspaces().isDeleting(id);
const handleDismissInFlight = useCallback(() => {
- useWorkspaceCreateFailuresStore.getState().clear(id);
+ useWorkspaceCreatesStore.getState().remove(id);
}, [id]);
const {
diff --git a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/hooks/useDashboardSidebarData/useDashboardSidebarData.ts b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/hooks/useDashboardSidebarData/useDashboardSidebarData.ts
index f5b427c5def..7d2189cfedb 100644
--- a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/hooks/useDashboardSidebarData/useDashboardSidebarData.ts
+++ b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/hooks/useDashboardSidebarData/useDashboardSidebarData.ts
@@ -8,7 +8,7 @@ import { useDashboardSidebarState } from "renderer/routes/_authenticated/hooks/u
import { useCollections } from "renderer/routes/_authenticated/providers/CollectionsProvider";
import { getVisibleSidebarWorkspaces } from "renderer/routes/_authenticated/providers/CollectionsProvider/dashboardSidebarLocal";
import { useLocalHostService } from "renderer/routes/_authenticated/providers/LocalHostServiceProvider";
-import { useWorkspaceCreateFailuresStore } from "renderer/stores/workspace-creates";
+import { useWorkspaceCreatesStore } from "renderer/stores/workspace-creates";
import type {
DashboardSidebarProject,
DashboardSidebarProjectChild,
@@ -132,23 +132,26 @@ export function useDashboardSidebarData() {
const { toggleProjectCollapsed } = useDashboardSidebarState();
const queryClient = useQueryClient();
- // Failed workspace.create operations — backing v2_workspaces row was rolled
- // back, but we keep the snapshot in renderer memory so the user can retry
- // from the detail page or dismiss from the sidebar.
- const failuresMap = useWorkspaceCreateFailuresStore(
- (store) => store.failures,
- );
- const failureSidebarRows = useMemo(
+ // In-flight workspace.create operations. These don't have a backing DB row
+ // — they're kept in renderer memory until the real v2Workspaces row arrives
+ // via Electric sync (or until error/dismiss).
+ const inFlightEntries = useWorkspaceCreatesStore((store) => store.entries);
+ const inFlightSidebarRows = useMemo(
() =>
- Object.entries(failuresMap).map(([id, entry]) => ({
- id,
- projectId: entry.snapshot.projectId,
- hostId: entry.hostId,
- name: entry.snapshot.name ?? "New workspace",
- branchName:
- entry.snapshot.branch ?? entry.snapshot.name ?? "New workspace",
- })),
- [failuresMap],
+ inFlightEntries
+ .filter((entry) => entry.snapshot.id !== undefined)
+ .map((entry) => ({
+ id: entry.snapshot.id as string,
+ projectId: entry.snapshot.projectId,
+ name: entry.snapshot.name ?? "New workspace",
+ branchName:
+ entry.snapshot.branch ?? entry.snapshot.name ?? "New workspace",
+ status:
+ entry.state === "creating"
+ ? ("creating" as const)
+ : ("failed" as const),
+ })),
+ [inFlightEntries],
);
const { data: hosts = [] } = useLiveQuery(
@@ -243,7 +246,6 @@ export function useDashboardSidebarData() {
branch: workspaces.branch,
createdAt: workspaces.createdAt,
updatedAt: workspaces.updatedAt,
- synced: workspaces.$synced,
tabOrder: sidebarWorkspaces.sidebarState.tabOrder,
sectionId: sidebarWorkspaces.sidebarState.sectionId,
isHidden: sidebarWorkspaces.sidebarState.isHidden,
@@ -261,13 +263,14 @@ export function useDashboardSidebarData() {
[rawSidebarWorkspaces],
);
- const { data: localWorkspaceCandidates = [] } = useLiveQuery(
+ const { data: localMainWorkspaces = [] } = useLiveQuery(
(q) =>
q
.from({ workspaces: collections.v2Workspaces })
.innerJoin({ hosts: collections.v2Hosts }, ({ workspaces, hosts }) =>
eq(workspaces.hostId, hosts.machineId),
)
+ .where(({ workspaces }) => eq(workspaces.type, "main"))
.select(({ workspaces, hosts }) => ({
id: workspaces.id,
projectId: workspaces.projectId,
@@ -278,7 +281,8 @@ export function useDashboardSidebarData() {
branch: workspaces.branch,
createdAt: workspaces.createdAt,
updatedAt: workspaces.updatedAt,
- synced: workspaces.$synced,
+ tabOrder: MAIN_WORKSPACE_TAB_ORDER,
+ sectionId: null as string | null,
})),
[collections],
);
@@ -287,40 +291,17 @@ export function useDashboardSidebarData() {
const sidebarProjectIds = new Set(
sidebarProjects.map((project) => project.id),
);
- const autoIncluded = localWorkspaceCandidates
- .filter((workspace) => {
- if (localStateWorkspaceIds.has(workspace.id)) return false;
- if (workspace.hostId !== machineId) return false;
- if (!sidebarProjectIds.has(workspace.projectId)) return false;
- if (workspace.type === "main") return true;
- return workspace.type === "worktree" && workspace.synced === false;
- })
- .map((workspace) => ({
- ...workspace,
- tabOrder:
- workspace.type === "main"
- ? MAIN_WORKSPACE_TAB_ORDER
- : PENDING_WORKSPACE_TAB_ORDER,
- sectionId: null as string | null,
- creationStatus:
- workspace.synced === false ? ("creating" as const) : undefined,
- }));
- // Pinned rows (those with v2WorkspaceLocalState) keep showing the
- // creating spinner until Electric confirms `$synced`. The detail page
- // reads `$synced` directly off the row, so without this the sidebar
- // would clear its spinner the moment local state was inserted in
- // `onInsert`, while the detail page would still show
- // `WorkspaceCreatingState` until the shape stream caught up.
- const sidebarWithSyncMeta = sidebarWorkspaces.map((workspace) => ({
- ...workspace,
- creationStatus:
- workspace.synced === false ? ("creating" as const) : undefined,
- }));
-
- return [...autoIncluded, ...sidebarWithSyncMeta];
+ const autoLocalMainWorkspaces = localMainWorkspaces.filter(
+ (workspace) =>
+ !localStateWorkspaceIds.has(workspace.id) &&
+ workspace.hostId === machineId &&
+ sidebarProjectIds.has(workspace.projectId),
+ );
+
+ return [...autoLocalMainWorkspaces, ...sidebarWorkspaces];
}, [
+ localMainWorkspaces,
localStateWorkspaceIds,
- localWorkspaceCandidates,
machineId,
sidebarProjects,
sidebarWorkspaces,
@@ -461,7 +442,6 @@ export function useDashboardSidebarData() {
behindCount: null,
createdAt: workspace.createdAt,
updatedAt: workspace.updatedAt,
- creationStatus: workspace.creationStatus,
};
if (workspace.sectionId) {
@@ -484,25 +464,23 @@ export function useDashboardSidebarData() {
});
}
- // Inject failed workspace.create rows from the renderer failure store.
- // The optimistic v2_workspaces row was rolled back, so the only signal
- // left is the snapshot we kept in memory for retry/dismiss.
- for (const failure of failureSidebarRows) {
- if (localStateWorkspaceIds.has(failure.id)) continue;
- const project = projectsById.get(failure.projectId);
+ // Inject in-flight workspaces (creating / failed) from the renderer-side
+ // in-flight store.
+ for (const pw of inFlightSidebarRows) {
+ if (localStateWorkspaceIds.has(pw.id)) continue;
+ const project = projectsById.get(pw.projectId);
if (!project) continue;
- const failedItem: DashboardSidebarWorkspace = {
- id: failure.id,
- projectId: failure.projectId,
- hostId: failure.hostId,
- hostType:
- failure.hostId === machineId ? "local-device" : "remote-device",
+ const pendingItem: DashboardSidebarWorkspace = {
+ id: pw.id,
+ projectId: pw.projectId,
+ hostId: "",
+ hostType: "local-device",
type: "worktree",
hostIsOnline: null,
accentColor: null,
- name: failure.name,
- branch: failure.branchName,
+ name: pw.name,
+ branch: pw.branchName,
pullRequest: null,
repoUrl:
project.githubOwner && project.githubRepoName
@@ -514,14 +492,14 @@ export function useDashboardSidebarData() {
behindCount: null,
createdAt: new Date(),
updatedAt: new Date(),
- creationStatus: "failed",
+ creationStatus: pw.status,
};
project.childEntries.push({
tabOrder: PENDING_WORKSPACE_TAB_ORDER,
child: {
type: "workspace",
- workspace: failedItem,
+ workspace: pendingItem,
},
});
}
@@ -564,7 +542,7 @@ export function useDashboardSidebarData() {
}, [
machineId,
pullRequestsByWorkspaceId,
- failureSidebarRows,
+ inFlightSidebarRows,
localStateWorkspaceIds,
sidebarProjects,
sidebarSections,
diff --git a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/layout.tsx b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/layout.tsx
index e0544216216..f3cdabbafce 100644
--- a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/layout.tsx
+++ b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/layout.tsx
@@ -14,6 +14,7 @@ import { ResizablePanel } from "renderer/screens/main/components/ResizablePanel"
import { WorkspaceSidebar } from "renderer/screens/main/components/WorkspaceSidebar";
import { DeleteWorkspaceDialog } from "renderer/screens/main/components/WorkspaceSidebar/WorkspaceListItem/components";
import { useOpenNewWorkspaceModal } from "renderer/stores/new-workspace-modal";
+import { WorkspaceCreatesManager } from "renderer/stores/workspace-creates";
import {
COLLAPSED_WORKSPACE_SIDEBAR_WIDTH,
DEFAULT_WORKSPACE_SIDEBAR_WIDTH,
@@ -127,6 +128,7 @@ function DashboardLayout() {
return (
+
{sidebarOutsideColumn && sidebarPanel}
diff --git a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/components/WorkspaceCreateErrorState/WorkspaceCreateErrorState.tsx b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/components/WorkspaceCreateErrorState/WorkspaceCreateErrorState.tsx
index c10b19a9714..282c9f5c5c9 100644
--- a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/components/WorkspaceCreateErrorState/WorkspaceCreateErrorState.tsx
+++ b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/components/WorkspaceCreateErrorState/WorkspaceCreateErrorState.tsx
@@ -1,10 +1,7 @@
import { Button } from "@superset/ui/button";
+import { useNavigate } from "@tanstack/react-router";
import { AlertCircle, GitBranch } from "lucide-react";
-import { useNavigateAwayFromWorkspace } from "renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/hooks/useNavigateAwayFromWorkspace";
-import {
- useWorkspaceCreateFailuresStore,
- useWorkspaceCreates,
-} from "renderer/stores/workspace-creates";
+import { useWorkspaceCreates } from "renderer/stores/workspace-creates";
interface WorkspaceCreateErrorStateProps {
workspaceId: string;
@@ -19,22 +16,12 @@ export function WorkspaceCreateErrorState({
branch,
error,
}: WorkspaceCreateErrorStateProps) {
- const { submit } = useWorkspaceCreates();
- const navigateAway = useNavigateAwayFromWorkspace();
-
- const handleRetry = () => {
- const failure =
- useWorkspaceCreateFailuresStore.getState().failures[workspaceId];
- if (!failure) return;
- void submit({ hostId: failure.hostId, snapshot: failure.snapshot });
- };
+ const navigate = useNavigate();
+ const { retry, dismiss } = useWorkspaceCreates();
const handleDismiss = () => {
- useWorkspaceCreateFailuresStore.getState().clear(workspaceId);
- // `navigateAway` jumps to the next sidebar workspace when we're viewing
- // the one being dismissed — falls back to the top sidebar entry since
- // the failed id was never in the sidebar list, then to "/" if empty.
- navigateAway(workspaceId);
+ dismiss(workspaceId);
+ void navigate({ to: "/v2-workspaces" });
};
return (
@@ -79,7 +66,7 @@ export function WorkspaceCreateErrorState({
-