Simplify execution workspace chooser and deduplicate reusable workspaces

- Remove "Operator branch" and "Agent default" options from the workspace
  mode dropdown in both NewIssueDialog and IssueProperties, keeping only
  "Project default", "New isolated workspace", and "Reuse existing workspace"
- Deduplicate reusable workspaces by space identity (cwd path) so the
  "choose existing workspace" dropdown shows unique worktrees instead of
  duplicate entries from multiple issues sharing the same local folder

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
Dotta
2026-03-17 07:46:40 -05:00
parent 88bf1b23a3
commit ce105d32c3
2 changed files with 30 additions and 10 deletions

View File

@@ -26,8 +26,6 @@ const EXECUTION_WORKSPACE_OPTIONS = [
{ value: "shared_workspace", label: "Project default" },
{ value: "isolated_workspace", label: "New isolated workspace" },
{ value: "reuse_existing", label: "Reuse existing workspace" },
{ value: "operator_branch", label: "Operator branch" },
{ value: "agent_default", label: "Agent default" },
] as const;
function defaultProjectWorkspaceIdForProject(project: {
@@ -237,7 +235,19 @@ export function IssueProperties({ issue, onUpdate, inline }: IssuePropertiesProp
}),
enabled: Boolean(companyId) && Boolean(issue.projectId),
});
const selectedReusableExecutionWorkspace = (reusableExecutionWorkspaces ?? []).find(
const deduplicatedReusableWorkspaces = useMemo(() => {
const workspaces = reusableExecutionWorkspaces ?? [];
const seen = new Map<string, typeof workspaces[number]>();
for (const ws of workspaces) {
const key = ws.cwd ?? ws.id;
const existing = seen.get(key);
if (!existing || new Date(ws.lastUsedAt) > new Date(existing.lastUsedAt)) {
seen.set(key, ws);
}
}
return Array.from(seen.values());
}, [reusableExecutionWorkspaces]);
const selectedReusableExecutionWorkspace = deduplicatedReusableWorkspaces.find(
(workspace) => workspace.id === issue.executionWorkspaceId,
);
const projectLink = (id: string | null) => {
@@ -630,7 +640,7 @@ export function IssueProperties({ issue, onUpdate, inline }: IssuePropertiesProp
value={issue.executionWorkspaceId ?? ""}
onChange={(e) => {
const nextExecutionWorkspaceId = e.target.value || null;
const nextExecutionWorkspace = (reusableExecutionWorkspaces ?? []).find(
const nextExecutionWorkspace = deduplicatedReusableWorkspaces.find(
(workspace) => workspace.id === nextExecutionWorkspaceId,
);
onUpdate({
@@ -643,7 +653,7 @@ export function IssueProperties({ issue, onUpdate, inline }: IssuePropertiesProp
}}
>
<option value="">Choose an existing workspace</option>
{(reusableExecutionWorkspaces ?? []).map((workspace) => (
{deduplicatedReusableWorkspaces.map((workspace) => (
<option key={workspace.id} value={workspace.id}>
{workspace.name} · {workspace.status} · {workspace.branchName ?? workspace.cwd ?? workspace.id.slice(0, 8)}
</option>

View File

@@ -242,8 +242,6 @@ const EXECUTION_WORKSPACE_MODES = [
{ value: "shared_workspace", label: "Project default" },
{ value: "isolated_workspace", label: "New isolated workspace" },
{ value: "reuse_existing", label: "Reuse existing workspace" },
{ value: "operator_branch", label: "Operator branch" },
{ value: "agent_default", label: "Agent default" },
] as const;
function defaultProjectWorkspaceIdForProject(project: { workspaces?: Array<{ id: string; isPrimary: boolean }>; executionWorkspacePolicy?: { defaultProjectWorkspaceId?: string | null } | null } | null | undefined) {
@@ -638,7 +636,7 @@ export function NewIssueDialog() {
});
const selectedProject = orderedProjects.find((project) => project.id === projectId);
const executionWorkspacePolicy = selectedProject?.executionWorkspacePolicy ?? null;
const selectedReusableExecutionWorkspace = (reusableExecutionWorkspaces ?? []).find(
const selectedReusableExecutionWorkspace = deduplicatedReusableWorkspaces.find(
(workspace) => workspace.id === selectedExecutionWorkspaceId,
);
const requestedExecutionWorkspaceMode =
@@ -747,7 +745,19 @@ export function NewIssueDialog() {
const currentProject = orderedProjects.find((project) => project.id === projectId);
const currentProjectExecutionWorkspacePolicy = currentProject?.executionWorkspacePolicy ?? null;
const currentProjectSupportsExecutionWorkspace = Boolean(currentProjectExecutionWorkspacePolicy?.enabled);
const selectedReusableExecutionWorkspace = (reusableExecutionWorkspaces ?? []).find(
const deduplicatedReusableWorkspaces = useMemo(() => {
const workspaces = reusableExecutionWorkspaces ?? [];
const seen = new Map<string, typeof workspaces[number]>();
for (const ws of workspaces) {
const key = ws.cwd ?? ws.id;
const existing = seen.get(key);
if (!existing || new Date(ws.lastUsedAt) > new Date(existing.lastUsedAt)) {
seen.set(key, ws);
}
}
return Array.from(seen.values());
}, [reusableExecutionWorkspaces]);
const selectedReusableExecutionWorkspace = deduplicatedReusableWorkspaces.find(
(workspace) => workspace.id === selectedExecutionWorkspaceId,
);
const assigneeOptionsTitle =
@@ -1126,7 +1136,7 @@ export function NewIssueDialog() {
onChange={(e) => setSelectedExecutionWorkspaceId(e.target.value)}
>
<option value="">Choose an existing workspace</option>
{(reusableExecutionWorkspaces ?? []).map((workspace) => (
{deduplicatedReusableWorkspaces.map((workspace) => (
<option key={workspace.id} value={workspace.id}>
{workspace.name} · {workspace.status} · {workspace.branchName ?? workspace.cwd ?? workspace.id.slice(0, 8)}
</option>