import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import type { ExecutionWorkspace } from "@penclipai/shared"; import { Link } from "@/lib/router"; import { Loader2 } from "lucide-react"; import { useTranslation } from "react-i18next"; import { executionWorkspacesApi } from "../api/execution-workspaces"; import { useToastActions } from "../context/ToastContext"; import { queryKeys } from "../lib/queryKeys"; import { formatDateTime, issueUrl } from "../lib/utils"; import { Button } from "./ui/button"; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from "./ui/dialog"; type ExecutionWorkspaceCloseDialogProps = { workspaceId: string; workspaceName: string; currentStatus: ExecutionWorkspace["status"]; open: boolean; onOpenChange: (open: boolean) => void; onClosed?: (workspace: ExecutionWorkspace) => void; }; function readinessTone(state: "ready" | "ready_with_warnings" | "blocked") { if (state === "blocked") { return "border-destructive/30 bg-destructive/5 text-destructive"; } if (state === "ready_with_warnings") { return "border-amber-500/30 bg-amber-500/10 text-amber-800 dark:text-amber-300"; } return "border-emerald-500/30 bg-emerald-500/10 text-emerald-700 dark:text-emerald-300"; } export function ExecutionWorkspaceCloseDialog({ workspaceId, workspaceName, currentStatus, open, onOpenChange, onClosed, }: ExecutionWorkspaceCloseDialogProps) { const { t } = useTranslation(); const queryClient = useQueryClient(); const { pushToast } = useToastActions(); const actionLabel = currentStatus === "cleanup_failed" ? t("execCloseDialog.retryClose") : t("execCloseDialog.closeWorkspace"); const readinessQuery = useQuery({ queryKey: queryKeys.executionWorkspaces.closeReadiness(workspaceId), queryFn: () => executionWorkspacesApi.getCloseReadiness(workspaceId), enabled: open, }); const closeWorkspace = useMutation({ mutationFn: () => executionWorkspacesApi.update(workspaceId, { status: "archived" }), onSuccess: (workspace) => { queryClient.setQueryData(queryKeys.executionWorkspaces.detail(workspace.id), workspace); queryClient.invalidateQueries({ queryKey: queryKeys.executionWorkspaces.closeReadiness(workspace.id) }); pushToast({ title: currentStatus === "cleanup_failed" ? t("execCloseDialog.workspaceCloseRetried") : t("execCloseDialog.workspaceClosed"), tone: "success", }); onOpenChange(false); onClosed?.(workspace); }, onError: (error) => { pushToast({ title: t("execCloseDialog.failedToClose"), body: error instanceof Error ? error.message : "Unknown error", tone: "error", }); }, }); const readiness = readinessQuery.data ?? null; const blockingIssues = readiness?.linkedIssues.filter((issue) => !issue.isTerminal) ?? []; const otherLinkedIssues = readiness?.linkedIssues.filter((issue) => issue.isTerminal) ?? []; const confirmDisabled = currentStatus === "archived" || closeWorkspace.isPending || readinessQuery.isLoading || readiness == null || readiness.state === "blocked"; return ( { if (!closeWorkspace.isPending) onOpenChange(nextOpen); }}> {actionLabel} {t("execCloseDialog.archiveDescription_pre", { defaultValue: "Archive" })}{" "} {workspaceName}{" "} {t("execCloseDialog.archiveDescription_post", { defaultValue: "and clean up any owned workspace artifacts. Paperclip keeps the workspace record and issue history, but removes it from active workspace views.", })} {readinessQuery.isLoading ? (
Checking whether this workspace is safe to close...
) : readinessQuery.error ? (
{readinessQuery.error instanceof Error ? readinessQuery.error.message : t("execCloseDialog.readinessFailed", { defaultValue: "Failed to inspect workspace close readiness.", })}
) : readiness ? (
{readiness.state === "blocked" ? t("execCloseDialog.stateBlocked") : readiness.state === "ready_with_warnings" ? t("execCloseDialog.stateWarning") : t("execCloseDialog.stateReady")}
{readiness.isSharedWorkspace ? t("execCloseDialog.sharedSession") : readiness.git?.workspacePath && readiness.git.repoRoot && readiness.git.workspacePath !== readiness.git.repoRoot ? t("execCloseDialog.separateCheckout") : readiness.isProjectPrimaryWorkspace ? t("execCloseDialog.primaryWorkspace") : t("execCloseDialog.disposable")}
{blockingIssues.length > 0 ? (

{t("execCloseDialog.blockingIssues", { defaultValue: "Blocking issues" })}

{blockingIssues.map((issue) => (
{issue.identifier ?? issue.id} · {issue.title} {issue.status}
))}
) : null} {readiness.blockingReasons.length > 0 ? (

{t("execCloseDialog.blockingReasons", { defaultValue: "Blocking reasons" })}

    {readiness.blockingReasons.map((reason, idx) => (
  • {reason}
  • ))}
) : null} {readiness.warnings.length > 0 ? (

{t("execCloseDialog.warnings", { defaultValue: "Warnings" })}

    {readiness.warnings.map((warning, idx) => (
  • {warning}
  • ))}
) : null} {readiness.git ? (

Git status

Branch
{readiness.git.branchName ?? "Unknown"}
{t("execCloseDialog.mergedIntoBase")}
{readiness.git.isMergedIntoBase == null ? t("Unknown") : readiness.git.isMergedIntoBase ? t("execCloseDialog.yes") : t("execCloseDialog.no")}
{t("execCloseDialog.aheadBehind")}
{(readiness.git.aheadCount ?? 0).toString()} / {(readiness.git.behindCount ?? 0).toString()}
{t("execCloseDialog.dirtyTrackedFiles")}
{readiness.git.dirtyEntryCount}
{t("execCloseDialog.untrackedFiles")}
{readiness.git.untrackedEntryCount}
) : null} {otherLinkedIssues.length > 0 ? (

{t("execCloseDialog.otherLinkedIssues", { defaultValue: "Other linked issues" })}

{otherLinkedIssues.map((issue) => (
{issue.identifier ?? issue.id} · {issue.title} {issue.status}
))}
) : null} {readiness.runtimeServices.length > 0 ? (

{t("execCloseDialog.attachedRuntimeServices", { defaultValue: "Attached runtime services" })}

{readiness.runtimeServices.map((service) => (
{service.serviceName} {service.status} · {service.lifecycle}
{service.url ?? service.command ?? service.cwd ?? t("execCloseDialog.noAdditionalDetails")}
))}
) : null}

{t("execCloseDialog.cleanupActions", { defaultValue: "Cleanup actions" })}

{readiness.plannedActions.map((action, index) => (
{action.label}
{action.description}
{action.command ? (
                        {action.command}
                      
) : null}
))}
{currentStatus === "cleanup_failed" ? (
{t("execCloseDialog.cleanupFailedRetry", { defaultValue: "Cleanup previously failed on this workspace. Retrying close will rerun the cleanup flow and update the workspace status if it succeeds.", })}
) : null} {currentStatus === "archived" ? (
This workspace is already archived.
) : null} {readiness.git?.repoRoot ? (
{t("execCloseDialog.repoRoot", { defaultValue: "Repo root:" })}{" "} {readiness.git.repoRoot} {readiness.git.workspacePath ? ( <> {" · "}{t("execCloseDialog.workspacePath")} {readiness.git.workspacePath} ) : null}
) : null}
{t("execCloseDialog.lastChecked")} {formatDateTime(new Date())}
) : null}
); }