Files
paseo/packages/app/src/workspace/use-workspace-archive.ts
T
Mohamed BoudraandGitHub 03dcda7c41 Archive any workspace from the app (#2003)
* feat(app): expose archive for every workspace

Workspace actions now archive one workspace by id. The daemon remains responsible for backing-specific cleanup, including removing a managed worktree only after its final workspace is archived.

* refactor(app): clarify archive descriptor name

* fix(app): preserve workspace archive safety

* fix(app): allow archive without upstream

* refactor(app): remove stale archive callback

* test(app): cover workspace archive ownership
2026-07-11 18:22:31 +02:00

117 lines
3.2 KiB
TypeScript

import { useCallback } from "react";
import { useTranslation } from "react-i18next";
import { getHostRuntimeStore } from "@/runtime/host-runtime";
import { useToast } from "@/contexts/toast-context";
import {
confirmRiskyWorktreeArchive,
DEFAULT_WORKTREE_ARCHIVE_WARNING_LABELS,
type WorktreeArchiveWarningLabels,
} from "@/git/worktree-archive-warning";
import type { WorkspaceDescriptor } from "@/stores/session-store";
import {
buildWorkspaceTabPersistenceKey,
useWorkspaceLayoutStore,
} from "@/stores/workspace-layout-store";
import { useWorkspaceTabsStore } from "@/stores/workspace-tabs-store";
import { archiveWorkspaceOptimistically } from "@/workspace/workspace-archive";
function purgeArchivedWorkspaceState(input: { serverId: string; workspaceId: string }): void {
const workspaceKey = buildWorkspaceTabPersistenceKey(input);
if (workspaceKey) {
useWorkspaceLayoutStore.getState().purgeWorkspace(workspaceKey);
}
useWorkspaceTabsStore.getState().purgeWorkspace(input);
}
export interface ArchiveWorkspaceInput {
serverId: string;
workspaceId: string;
workspaceKind: WorkspaceDescriptor["workspaceKind"];
name: string;
isDirty?: boolean | null;
aheadOfOrigin?: number | null;
diffStat?: { additions: number; deletions: number } | null;
warningLabels?: WorktreeArchiveWarningLabels;
onArchiveStarted: () => void;
onSetHiding?: (hiding: boolean) => void;
}
export interface WorkspaceArchiveController {
archive: () => void;
}
export function useWorkspaceArchive(input: ArchiveWorkspaceInput): WorkspaceArchiveController {
const {
serverId,
workspaceId,
workspaceKind,
name,
isDirty,
aheadOfOrigin,
diffStat,
warningLabels = DEFAULT_WORKTREE_ARCHIVE_WARNING_LABELS,
onArchiveStarted,
onSetHiding,
} = input;
const { t } = useTranslation();
const toast = useToast();
const archiveWorkspaceRecord = useCallback(async () => {
const client = getHostRuntimeStore().getClient(serverId);
if (!client) {
toast.error(t("sidebar.workspace.toasts.hostDisconnected"));
return;
}
onSetHiding?.(true);
try {
onArchiveStarted();
await archiveWorkspaceOptimistically({
client,
workspace: {
serverId,
workspaceId,
},
});
purgeArchivedWorkspaceState({ serverId, workspaceId });
} catch (error) {
toast.error(
error instanceof Error ? error.message : t("sidebar.workspace.toasts.archiveFailed"),
);
} finally {
onSetHiding?.(false);
}
}, [onArchiveStarted, onSetHiding, serverId, t, toast, workspaceId]);
const archive = useCallback(() => {
void (async () => {
if (workspaceKind === "worktree") {
const confirmed = await confirmRiskyWorktreeArchive(
{
workspaceName: name,
isDirty,
aheadOfOrigin,
diffStat,
},
warningLabels,
);
if (!confirmed) {
return;
}
}
await archiveWorkspaceRecord();
})();
}, [
aheadOfOrigin,
archiveWorkspaceRecord,
diffStat,
isDirty,
name,
warningLabels,
workspaceKind,
]);
return {
archive,
};
}