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
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export function DashboardSidebarWorkspaceItem({
hostIsOnline,
name,
branch,
isSynced,
pendingTransaction,
pullRequest,
} = workspace;
const isMainWorkspace = workspace.type === "main";
Expand Down Expand Up @@ -76,7 +76,7 @@ export function DashboardSidebarWorkspaceItem({
const handleAfterBranchRename = (newBranchName: string) => {
v2WorkspaceActions.updateWorkspace(id, { branch: newBranchName });
};
const isPending = !isSynced;
const isPending = pendingTransaction?.type === "insert";
// Keep the delete dialog outside the hidden wrapper below — the destroy
// flow reopens it into an error pane on conflict/teardown-failed.
const isDeleting = useDeletingWorkspaces().isDeleting(id);
Expand Down Expand Up @@ -136,7 +136,7 @@ export function DashboardSidebarWorkspaceItem({
isActive={isActive}
workspaceStatus={workspaceStatus}
onClick={handleClick}
isSynced={isSynced}
isCreatePending={isPending}
pullRequestState={pullRequest?.state ?? null}
aria-label={isPending ? `Creating workspace: ${name}` : undefined}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ interface DashboardSidebarCollapsedWorkspaceButtonProps
hostIsOnline: boolean | null;
isActive: boolean;
workspaceStatus?: ActivePaneStatus | null;
isSynced: boolean;
isCreatePending: boolean;
pullRequestState?: DashboardSidebarWorkspacePullRequest["state"] | null;
}

Expand All @@ -30,7 +30,7 @@ export const DashboardSidebarCollapsedWorkspaceButton = forwardRef<
hostIsOnline,
isActive,
workspaceStatus = null,
isSynced,
isCreatePending,
pullRequestState = null,
className,
...props
Expand All @@ -56,7 +56,7 @@ export const DashboardSidebarCollapsedWorkspaceButton = forwardRef<
isActive={isActive}
variant="collapsed"
workspaceStatus={workspaceStatus}
isSynced={isSynced}
isCreatePending={isCreatePending}
pullRequestState={pullRequestState}
/>
</button>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,9 +81,9 @@ export const DashboardSidebarExpandedWorkspaceRow = forwardRef<
name,
branch,
pullRequest,
isSynced,
pendingTransaction,
} = workspace;
const isPending = !isSynced;
const isPending = pendingTransaction?.type === "insert";
const showsStandaloneActiveStripe = accentColor == null;
const localRef = useRef<HTMLDivElement>(null);
const openUrl = electronTrpc.external.openUrl.useMutation();
Expand Down Expand Up @@ -170,7 +170,7 @@ export const DashboardSidebarExpandedWorkspaceRow = forwardRef<
isActive={isActive}
variant="expanded"
workspaceStatus={workspaceStatus}
isSynced={isSynced}
isCreatePending={isPending}
pullRequestState={pullRequest.state}
/>
</button>
Expand All @@ -183,7 +183,7 @@ export const DashboardSidebarExpandedWorkspaceRow = forwardRef<
isActive={isActive}
variant="expanded"
workspaceStatus={workspaceStatus}
isSynced={isSynced}
isCreatePending={isPending}
pullRequestState={null}
/>
</div>
Expand Down Expand Up @@ -265,7 +265,7 @@ export const DashboardSidebarExpandedWorkspaceRow = forwardRef<
/>
)
)}
{isSynced && (
{!isPending && (
<div className="hidden items-center justify-end gap-1.5 group-hover:flex">
{shortcutLabel && (
<span className="shrink-0 font-mono text-[10px] tabular-nums text-muted-foreground">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ interface DashboardSidebarWorkspaceIconProps {
isActive: boolean;
variant: "collapsed" | "expanded";
workspaceStatus?: ActivePaneStatus | null;
isSynced: boolean;
isCreatePending: boolean;
pullRequestState?: DashboardSidebarWorkspacePullRequest["state"] | null;
}

Expand Down Expand Up @@ -54,7 +54,7 @@ export function DashboardSidebarWorkspaceIcon({
isActive,
variant,
workspaceStatus = null,
isSynced,
isCreatePending,
pullRequestState = null,
}: DashboardSidebarWorkspaceIconProps) {
const overlayPosition = OVERLAY_POSITION[variant];
Expand Down Expand Up @@ -102,7 +102,7 @@ export function DashboardSidebarWorkspaceIcon({

return (
<>
{!isSynced || workspaceStatus === "working" ? (
{isCreatePending || workspaceStatus === "working" ? (
<AsciiSpinner className="text-base" />
) : (
renderPrimaryIcon()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import { eq } from "@tanstack/db";
import { useLiveQuery } from "@tanstack/react-db";
import { useQueries, useQueryClient } from "@tanstack/react-query";
import { useCallback, useMemo, useRef } from "react";
import { useCallback, useEffect, useMemo, useRef } from "react";
import { useRelayUrl } from "renderer/hooks/useRelayUrl";
import { getHostServiceClientByUrl } from "renderer/lib/host-service-client";
import { useDashboardSidebarState } from "renderer/routes/_authenticated/hooks/useDashboardSidebarState";
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 { useWorkspaceTransactionsStore } from "renderer/stores/workspace-creates";
import type {
DashboardSidebarProject,
DashboardSidebarProjectChild,
Expand Down Expand Up @@ -128,6 +129,12 @@ export function useDashboardSidebarData() {
const relayUrl = useRelayUrl();
const { toggleProjectCollapsed } = useDashboardSidebarState();
const queryClient = useQueryClient();
const workspaceTransactionsById = useWorkspaceTransactionsStore(
(state) => state.byWorkspaceId,
);
const clearWorkspaceTransaction = useWorkspaceTransactionsStore(
(state) => state.clear,
);

const { data: hosts = [] } = useLiveQuery(
(q) =>
Expand Down Expand Up @@ -234,8 +241,9 @@ export function useDashboardSidebarData() {
rawSidebarWorkspaces.map((workspace) => ({
...workspace,
hostIsOnline: hostsByMachineId.get(workspace.hostId)?.isOnline ?? false,
pendingTransaction: workspaceTransactionsById[workspace.id] ?? null,
})),
[hostsByMachineId, rawSidebarWorkspaces],
[hostsByMachineId, rawSidebarWorkspaces, workspaceTransactionsById],
);

const sidebarWorkspaces = useMemo(
Expand Down Expand Up @@ -274,10 +282,28 @@ export function useDashboardSidebarData() {
rawLocalMainWorkspaces.map((workspace) => ({
...workspace,
hostIsOnline: hostsByMachineId.get(workspace.hostId)?.isOnline ?? false,
pendingTransaction: workspaceTransactionsById[workspace.id] ?? null,
})),
[hostsByMachineId, rawLocalMainWorkspaces],
[hostsByMachineId, rawLocalMainWorkspaces, workspaceTransactionsById],
);

useEffect(() => {
for (const workspace of [
...rawSidebarWorkspaces,
...rawLocalMainWorkspaces,
]) {
const transaction = workspaceTransactionsById[workspace.id];
if (workspace.isSynced && transaction?.type === "insert") {
clearWorkspaceTransaction(workspace.id);
}
}
}, [
clearWorkspaceTransaction,
rawLocalMainWorkspaces,
rawSidebarWorkspaces,
workspaceTransactionsById,
]);

const visibleSidebarWorkspaces = useMemo(() => {
const sidebarProjectIds = new Set(
sidebarProjects.map((project) => project.id),
Expand Down Expand Up @@ -435,6 +461,7 @@ export function useDashboardSidebarData() {
updatedAt: workspace.updatedAt,
taskId: workspace.taskId,
isSynced: workspace.isSynced,
pendingTransaction: workspace.pendingTransaction,
};

if (workspace.sectionId) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,11 @@ export function useDashboardSidebarShortcuts(
() =>
groups
.flatMap((project) => getProjectChildrenWorkspaces(project.children))
.filter((workspace) => workspace.isSynced && !isDeleting(workspace.id)),
.filter(
(workspace) =>
workspace.pendingTransaction?.type !== "insert" &&
!isDeleting(workspace.id),
),
[groups, isDeleting],
);
const workspaceShortcutLabels =
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import type { WorkspaceTransactionSnapshot } from "renderer/stores/workspace-creates";

export type DashboardSidebarWorkspaceHostType =
| "local-device"
| "remote-device"
Expand Down Expand Up @@ -42,6 +44,7 @@ export interface DashboardSidebarWorkspace {
updatedAt: Date;
taskId: string | null;
isSynced: boolean;
pendingTransaction: WorkspaceTransactionSnapshot | null;
}

export interface DashboardSidebarSection {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { createFileRoute, Outlet, useMatchRoute } from "@tanstack/react-router";
import { useEffect, useRef } from "react";
import { useDashboardSidebarState } from "renderer/routes/_authenticated/hooks/useDashboardSidebarState";
import { useCollections } from "renderer/routes/_authenticated/providers/CollectionsProvider";
import { useWorkspaceTransactionsStore } from "renderer/stores/workspace-creates";
import { WorkspaceCreateErrorState } from "./components/WorkspaceCreateErrorState";
import { WorkspaceCreatingState } from "./components/WorkspaceCreatingState";
import { WorkspaceHostIncompatibleState } from "./components/WorkspaceHostIncompatibleState";
Expand All @@ -26,6 +27,13 @@ function V2WorkspaceLayout() {
workspaceMatch !== false ? workspaceMatch.workspaceId : null;
const collections = useCollections();
const { ensureWorkspaceInSidebar } = useDashboardSidebarState();
const pendingTransaction = useWorkspaceTransactionsStore((state) =>
workspaceId ? (state.byWorkspaceId[workspaceId] ?? null) : null,
);
const clearWorkspaceTransaction = useWorkspaceTransactionsStore(
(state) => state.clear,
);
const isCreatePending = pendingTransaction?.type === "insert";

const { data: workspaces, isReady } = useLiveQuery(
(q) =>
Expand All @@ -43,21 +51,29 @@ function V2WorkspaceLayout() {
);
const workspace = workspaces?.[0] ?? null;
const failedEntry = failedEntries?.[0] ?? null;
const isSynced = workspace?.$synced === true;
const canUseWorkspace =
workspace !== null &&
(workspace.$synced === true || pendingTransaction?.type === "update");
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: The new canUseWorkspace condition can lock valid workspaces behind a blank state when $synced is false but no in-memory transaction exists (for example after transaction settle or app reload). Gate on create-pending instead of requiring an update transaction.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/layout.tsx, line 56:

<comment>The new `canUseWorkspace` condition can lock valid workspaces behind a blank state when `$synced` is false but no in-memory transaction exists (for example after transaction settle or app reload). Gate on create-pending instead of requiring an `update` transaction.</comment>

<file context>
@@ -51,6 +51,9 @@ function V2WorkspaceLayout() {
 	const failedEntry = failedEntries?.[0] ?? null;
+	const canUseWorkspace =
+		workspace !== null &&
+		(workspace.$synced === true || pendingTransaction?.type === "update");
 
 	useEffect(() => {
</file context>
Suggested change
(workspace.$synced === true || pendingTransaction?.type === "update");
pendingTransaction?.type !== "insert";


useEffect(() => {
if (workspace?.$synced === true && pendingTransaction?.type === "insert") {
clearWorkspaceTransaction(workspace.id);
}
}, [clearWorkspaceTransaction, pendingTransaction, workspace]);

const lastEnsuredWorkspaceIdRef = useRef<string | null>(null);
useEffect(() => {
if (
!workspace ||
!isSynced ||
!canUseWorkspace ||
lastEnsuredWorkspaceIdRef.current === workspace.id
)
return;
lastEnsuredWorkspaceIdRef.current = workspace.id;
ensureWorkspaceInSidebar(workspace.id, workspace.projectId);
}, [ensureWorkspaceInSidebar, workspace, isSynced]);
}, [ensureWorkspaceInSidebar, workspace, canUseWorkspace]);

const hostStatus = useRemoteHostStatus(isSynced ? workspace : null);
const hostStatus = useRemoteHostStatus(canUseWorkspace ? workspace : null);

if (!workspaceId || !isReady || !workspaces) {
return <div className="flex h-full w-full" />;
Expand All @@ -70,7 +86,7 @@ function V2WorkspaceLayout() {
return <WorkspaceNotFoundState workspaceId={workspaceId} />;
}

if (!isSynced) {
if (isCreatePending) {
return (
<WorkspaceCreatingState
name={workspace.name}
Expand All @@ -80,6 +96,10 @@ function V2WorkspaceLayout() {
);
}

if (!canUseWorkspace) {
return <div className="flex h-full w-full" />;
}

if (hostStatus.status === "incompatible") {
return (
<WorkspaceHostIncompatibleState
Expand Down
Loading
Loading