fix: keep archived worktrees optimistically hidden (#640)

This commit is contained in:
Mohamed Boudra
2026-04-30 21:36:41 +08:00
committed by GitHub
parent 92afb826d9
commit 93c6c3e5f8
32 changed files with 712 additions and 112 deletions
@@ -7,6 +7,7 @@ import type { WorkspaceDescriptor } from "@/stores/session-store";
import {
__resetCheckoutGitActionsStoreForTests,
invalidateCheckoutGitQueriesForClient,
isLocalWorktreeArchivePending,
useCheckoutGitActionsStore,
} from "@/stores/checkout-git-actions-store";
@@ -39,6 +40,7 @@ function workspace(input: Partial<WorkspaceDescriptor> & Pick<WorkspaceDescripto
workspaceKind: input.workspaceKind ?? "worktree",
name: input.name ?? input.id,
status: input.status ?? "done",
archivingAt: input.archivingAt ?? null,
diffStat: input.diffStat ?? null,
scripts: input.scripts ?? [],
} satisfies WorkspaceDescriptor;
@@ -207,6 +209,7 @@ describe("checkout-git-actions-store", () => {
expect(appQueryClient.getQueryData(["sidebarPaseoWorktreeList", serverId, "/tmp"])).toEqual([
{ worktreePath: "/tmp/other" },
]);
expect(isLocalWorktreeArchivePending({ serverId, cwd })).toBe(true);
deferred.resolve({});
await archive;
@@ -233,4 +236,25 @@ describe("checkout-git-actions-store", () => {
listSnapshot,
);
});
it("reports local archive pending only while the archive action is in flight", async () => {
const deferred = createDeferred<Record<string, never>>();
const client = {
archivePaseoWorktree: vi.fn(() => deferred.promise),
};
const featureWorkspace = workspace({ id: cwd, name: "feature" });
useSessionStore.getState().initializeSession(serverId, client as unknown as DaemonClient);
useSessionStore.getState().setWorkspaces(serverId, new Map([[cwd, featureWorkspace]]));
const archive = useCheckoutGitActionsStore
.getState()
.archiveWorktree({ serverId, cwd, worktreePath: cwd });
expect(isLocalWorktreeArchivePending({ serverId, cwd })).toBe(true);
deferred.resolve({});
await archive;
expect(isLocalWorktreeArchivePending({ serverId, cwd })).toBe(false);
});
});
@@ -191,7 +191,6 @@ function removeWorktreeFromSessionStore(input: { serverId: string; worktreePath:
function restoreWorktreeArchiveState(input: {
serverId: string;
worktreePath: string;
snapshot: WorktreeArchiveSnapshot;
}): void {
if (input.snapshot.workspace) {
@@ -223,6 +222,16 @@ function inFlightKey(key: CheckoutKey, actionId: CheckoutGitAsyncActionId): stri
return `${key}::${actionId}`;
}
export function isLocalWorktreeArchivePending(input: { serverId: string; cwd: string }): boolean {
return (
useCheckoutGitActionsStore.getState().getStatus({
serverId: input.serverId,
cwd: input.cwd,
actionId: "archive-worktree",
}) === "pending"
);
}
interface CheckoutGitActionsStoreState {
statusByCheckout: Record<CheckoutKey, StatusMap>;
@@ -436,7 +445,7 @@ export const useCheckoutGitActionsStore = create<CheckoutGitActionsStoreState>()
throw new Error(payload.error.message);
}
} catch (error) {
restoreWorktreeArchiveState({ serverId, worktreePath, snapshot });
restoreWorktreeArchiveState({ serverId, snapshot });
throw error;
}
invalidateWorktreeList();
@@ -34,6 +34,7 @@ function createWorkspace(
workspaceKind: input.workspaceKind ?? "local_checkout",
name: input.name ?? "main",
status: input.status ?? "done",
archivingAt: input.archivingAt ?? null,
diffStat: input.diffStat ?? null,
scripts: input.scripts ?? [],
};
@@ -23,6 +23,7 @@ function createWorkspace(
workspaceKind: input.workspaceKind ?? "local_checkout",
name: input.name ?? "main",
status: input.status ?? "done",
archivingAt: input.archivingAt ?? null,
diffStat: input.diffStat ?? null,
scripts: input.scripts ?? [],
};
@@ -73,6 +74,7 @@ describe("normalizeWorkspaceDescriptor", () => {
projectKind: "git",
workspaceKind: "checkout",
name: "main",
archivingAt: null,
status: "running",
activityAt: "not-a-date",
diffStat: null,
@@ -105,6 +107,7 @@ describe("normalizeWorkspaceDescriptor", () => {
projectKind: "git",
workspaceKind: "checkout",
name: "main",
archivingAt: null,
status: "done",
activityAt: null,
diffStat: null,
@@ -116,6 +119,27 @@ describe("normalizeWorkspaceDescriptor", () => {
expect(workspace.scripts).toEqual([]);
});
it("defaults missing archivingAt to null", () => {
const payload = {
id: "1",
projectId: "1",
projectDisplayName: "Project 1",
projectRootPath: "/repo",
workspaceDirectory: "/repo",
projectKind: "git",
workspaceKind: "checkout",
name: "main",
status: "done",
activityAt: null,
diffStat: null,
scripts: [],
} as unknown as WorkspaceDescriptorPayload;
const workspace = normalizeWorkspaceDescriptor(payload);
expect(workspace.archivingAt).toBeNull();
});
it("preserves project placement from workspace descriptor payloads", () => {
const workspace = normalizeWorkspaceDescriptor({
id: "1",
@@ -126,6 +150,7 @@ describe("normalizeWorkspaceDescriptor", () => {
projectKind: "git",
workspaceKind: "local_checkout",
name: "main",
archivingAt: null,
status: "done",
activityAt: null,
diffStat: null,
+2
View File
@@ -117,6 +117,7 @@ export interface WorkspaceDescriptor {
workspaceKind: WorkspaceDescriptorPayload["workspaceKind"];
name: string;
status: WorkspaceDescriptorPayload["status"];
archivingAt: string | null;
diffStat: { additions: number; deletions: number } | null;
scripts: WorkspaceDescriptorPayload["scripts"];
gitRuntime?: WorkspaceDescriptorPayload["gitRuntime"];
@@ -137,6 +138,7 @@ export function normalizeWorkspaceDescriptor(
workspaceKind: payload.workspaceKind,
name: payload.name,
status: payload.status,
archivingAt: payload.archivingAt ?? null,
diffStat: payload.diffStat ?? null,
scripts: (payload.scripts ?? []).map((s) => Object.assign({}, s)),
gitRuntime: payload.gitRuntime,