diff --git a/server/src/__tests__/execution-workspaces-service.test.ts b/server/src/__tests__/execution-workspaces-service.test.ts index a6f18892..c031ad58 100644 --- a/server/src/__tests__/execution-workspaces-service.test.ts +++ b/server/src/__tests__/execution-workspaces-service.test.ts @@ -295,7 +295,7 @@ describeEmbeddedPostgres("executionWorkspaceService.getCloseReadiness", () => { workspaceId: executionWorkspaceId, state: "ready_with_warnings", isSharedWorkspace: false, - isProjectPrimaryWorkspace: true, + isProjectPrimaryWorkspace: false, isDestructiveCloseAllowed: true, git: { workspacePath: worktreePath, diff --git a/server/src/services/execution-workspaces.ts b/server/src/services/execution-workspaces.ts index 758a7e09..8b0cebde 100644 --- a/server/src/services/execution-workspaces.ts +++ b/server/src/services/execution-workspaces.ts @@ -439,7 +439,15 @@ export function executionWorkspaceService(db: Db) { const warnings = [...gitWarnings]; const blockingReasons: string[] = []; const isSharedWorkspace = executionWorkspace.mode === "shared_workspace"; - const isProjectPrimaryWorkspace = workspace.projectWorkspaceId != null && workspace.projectWorkspaceId === primaryProjectWorkspace?.id; + const workspacePath = readNullableString(executionWorkspace.providerRef) ?? readNullableString(executionWorkspace.cwd); + const resolvedWorkspacePath = workspacePath ? path.resolve(workspacePath) : null; + const resolvedPrimaryWorkspacePath = projectWorkspace?.cwd ? path.resolve(projectWorkspace.cwd) : null; + const isProjectPrimaryWorkspace = + workspace.projectWorkspaceId != null + && workspace.projectWorkspaceId === primaryProjectWorkspace?.id + && resolvedWorkspacePath != null + && resolvedPrimaryWorkspacePath != null + && resolvedWorkspacePath === resolvedPrimaryWorkspacePath; const linkedIssueSummaries = linkedIssues.map((issue) => ({ ...issue, @@ -546,7 +554,6 @@ export function executionWorkspaceService(db: Db) { }); } - const workspacePath = readNullableString(executionWorkspace.providerRef) ?? readNullableString(executionWorkspace.cwd); if (executionWorkspace.providerType === "git_worktree" && workspacePath) { plannedActions.push({ kind: "git_worktree_remove", diff --git a/ui/src/components/ExecutionWorkspaceCloseDialog.tsx b/ui/src/components/ExecutionWorkspaceCloseDialog.tsx index d44ba698..4ad9a5bb 100644 --- a/ui/src/components/ExecutionWorkspaceCloseDialog.tsx +++ b/ui/src/components/ExecutionWorkspaceCloseDialog.tsx @@ -75,6 +75,8 @@ export function ExecutionWorkspaceCloseDialog({ }); 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 || @@ -86,10 +88,10 @@ export function ExecutionWorkspaceCloseDialog({ { if (!closeWorkspace.isPending) onOpenChange(nextOpen); }}> - + {actionLabel} - + Archive {workspaceName} and clean up any owned workspace artifacts. Paperclip keeps the workspace record and issue history, but removes it from active workspace views. @@ -116,19 +118,39 @@ export function ExecutionWorkspaceCloseDialog({
{readiness.isSharedWorkspace - ? "This workspace is attached to shared project infrastructure." - : readiness.isProjectPrimaryWorkspace - ? "This workspace is based on the project's primary workspace." - : "This workspace is disposable and can be archived."} + ? "This is the shared project workspace session, so destructive close is blocked." + : readiness.git?.workspacePath && readiness.git.repoRoot && readiness.git.workspacePath !== readiness.git.repoRoot + ? "This execution workspace has its own checkout path and can be archived independently." + : readiness.isProjectPrimaryWorkspace + ? "This execution workspace currently points at the project's primary workspace path." + : "This workspace is disposable and can be archived."}
+ {blockingIssues.length > 0 ? ( +
+

Blocking issues

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

Blocking reasons

    {readiness.blockingReasons.map((reason) => ( -
  • +
  • {reason}
  • ))} @@ -141,7 +163,7 @@ export function ExecutionWorkspaceCloseDialog({

    Warnings

      {readiness.warnings.map((warning) => ( -
    • +
    • {warning}
    • ))} @@ -185,14 +207,14 @@ export function ExecutionWorkspaceCloseDialog({
) : null} - {readiness.linkedIssues.length > 0 ? ( + {otherLinkedIssues.length > 0 ? (
-

Linked issues

+

Other linked issues

- {readiness.linkedIssues.map((issue) => ( + {otherLinkedIssues.map((issue) => (
-
- +
+ {issue.identifier ?? issue.id} · {issue.title} {issue.status} @@ -209,11 +231,11 @@ export function ExecutionWorkspaceCloseDialog({
{readiness.runtimeServices.map((service) => (
-
+
{service.serviceName} {service.status} · {service.lifecycle}
-
+
{service.url ?? service.command ?? service.cwd ?? "No additional details"}
@@ -228,9 +250,9 @@ export function ExecutionWorkspaceCloseDialog({ {readiness.plannedActions.map((action, index) => (
{action.label}
-
{action.description}
+
{action.description}
{action.command ? ( -
+                      
                         {action.command}
                       
) : null} @@ -253,11 +275,11 @@ export function ExecutionWorkspaceCloseDialog({ ) : null} {readiness.git?.repoRoot ? ( -
- Repo root: {readiness.git.repoRoot} +
+ Repo root: {readiness.git.repoRoot} {readiness.git.workspacePath ? ( <> - {" · "}Workspace path: {readiness.git.workspacePath} + {" · "}Workspace path: {readiness.git.workspacePath} ) : null}