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 7d2189cfedb..a6abab383da 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 @@ -134,12 +134,17 @@ export function useDashboardSidebarData() { // 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). + // via Electric sync (or until error/dismiss). Entries that have already + // resolved on the host service carry `cloudRow`; those are surfaced as + // real synced rows below so the sidebar doesn't stick on "creating" when + // Electric is slow. const inFlightEntries = useWorkspaceCreatesStore((store) => store.entries); const inFlightSidebarRows = useMemo( () => inFlightEntries .filter((entry) => entry.snapshot.id !== undefined) + // Entries with a cloudRow are rendered via the synced fallback below. + .filter((entry) => !(entry.state === "creating" && entry.cloudRow)) .map((entry) => ({ id: entry.snapshot.id as string, projectId: entry.snapshot.projectId, @@ -287,6 +292,44 @@ export function useDashboardSidebarData() { [collections], ); + // Cloud-row fallback: when workspaces.create has resolved on the host + // service but Electric hasn't yet delivered the v2Workspaces row, surface + // the cloud row cached on the in-flight entry so the sidebar renders the + // workspace as fully synced. Manager.tsx removes the entry once Electric + // catches up, at which point the live query takes over seamlessly. + const cloudRowFallbackWorkspaces = useMemo(() => { + if (inFlightEntries.length === 0) return []; + const hostByMachineId = new Map( + hosts.map((host) => [host.machineId, host]), + ); + const rows = inFlightEntries.flatMap((entry) => { + const cloudRow = entry.cloudRow; + if (!cloudRow) return []; + // Electric already delivered; let the live query own this row. + if (localStateWorkspaceIds.has(cloudRow.id)) return []; + const localState = collections.v2WorkspaceLocalState.get(cloudRow.id); + const host = hostByMachineId.get(cloudRow.hostId); + return [ + { + id: cloudRow.id, + projectId: localState?.sidebarState.projectId ?? cloudRow.projectId, + hostId: cloudRow.hostId, + type: cloudRow.type, + hostIsOnline: host?.isOnline ?? false, + name: cloudRow.name, + branch: cloudRow.branch, + createdAt: cloudRow.createdAt, + updatedAt: cloudRow.updatedAt, + tabOrder: + localState?.sidebarState.tabOrder ?? PENDING_WORKSPACE_TAB_ORDER, + sectionId: localState?.sidebarState.sectionId ?? null, + isHidden: localState?.sidebarState.isHidden ?? false, + }, + ]; + }); + return getVisibleSidebarWorkspaces(rows); + }, [collections, hosts, inFlightEntries, localStateWorkspaceIds]); + const visibleSidebarWorkspaces = useMemo(() => { const sidebarProjectIds = new Set( sidebarProjects.map((project) => project.id), @@ -298,8 +341,13 @@ export function useDashboardSidebarData() { sidebarProjectIds.has(workspace.projectId), ); - return [...autoLocalMainWorkspaces, ...sidebarWorkspaces]; + return [ + ...autoLocalMainWorkspaces, + ...sidebarWorkspaces, + ...cloudRowFallbackWorkspaces, + ]; }, [ + cloudRowFallbackWorkspaces, localMainWorkspaces, localStateWorkspaceIds, machineId,