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:
@@ -26,8 +26,6 @@ const EXECUTION_WORKSPACE_OPTIONS = [
|
|||||||
{ value: "shared_workspace", label: "Project default" },
|
{ value: "shared_workspace", label: "Project default" },
|
||||||
{ value: "isolated_workspace", label: "New isolated workspace" },
|
{ value: "isolated_workspace", label: "New isolated workspace" },
|
||||||
{ value: "reuse_existing", label: "Reuse existing workspace" },
|
{ value: "reuse_existing", label: "Reuse existing workspace" },
|
||||||
{ value: "operator_branch", label: "Operator branch" },
|
|
||||||
{ value: "agent_default", label: "Agent default" },
|
|
||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
function defaultProjectWorkspaceIdForProject(project: {
|
function defaultProjectWorkspaceIdForProject(project: {
|
||||||
@@ -237,7 +235,19 @@ export function IssueProperties({ issue, onUpdate, inline }: IssuePropertiesProp
|
|||||||
}),
|
}),
|
||||||
enabled: Boolean(companyId) && Boolean(issue.projectId),
|
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,
|
(workspace) => workspace.id === issue.executionWorkspaceId,
|
||||||
);
|
);
|
||||||
const projectLink = (id: string | null) => {
|
const projectLink = (id: string | null) => {
|
||||||
@@ -630,7 +640,7 @@ export function IssueProperties({ issue, onUpdate, inline }: IssuePropertiesProp
|
|||||||
value={issue.executionWorkspaceId ?? ""}
|
value={issue.executionWorkspaceId ?? ""}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
const nextExecutionWorkspaceId = e.target.value || null;
|
const nextExecutionWorkspaceId = e.target.value || null;
|
||||||
const nextExecutionWorkspace = (reusableExecutionWorkspaces ?? []).find(
|
const nextExecutionWorkspace = deduplicatedReusableWorkspaces.find(
|
||||||
(workspace) => workspace.id === nextExecutionWorkspaceId,
|
(workspace) => workspace.id === nextExecutionWorkspaceId,
|
||||||
);
|
);
|
||||||
onUpdate({
|
onUpdate({
|
||||||
@@ -643,7 +653,7 @@ export function IssueProperties({ issue, onUpdate, inline }: IssuePropertiesProp
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<option value="">Choose an existing workspace</option>
|
<option value="">Choose an existing workspace</option>
|
||||||
{(reusableExecutionWorkspaces ?? []).map((workspace) => (
|
{deduplicatedReusableWorkspaces.map((workspace) => (
|
||||||
<option key={workspace.id} value={workspace.id}>
|
<option key={workspace.id} value={workspace.id}>
|
||||||
{workspace.name} · {workspace.status} · {workspace.branchName ?? workspace.cwd ?? workspace.id.slice(0, 8)}
|
{workspace.name} · {workspace.status} · {workspace.branchName ?? workspace.cwd ?? workspace.id.slice(0, 8)}
|
||||||
</option>
|
</option>
|
||||||
|
|||||||
@@ -242,8 +242,6 @@ const EXECUTION_WORKSPACE_MODES = [
|
|||||||
{ value: "shared_workspace", label: "Project default" },
|
{ value: "shared_workspace", label: "Project default" },
|
||||||
{ value: "isolated_workspace", label: "New isolated workspace" },
|
{ value: "isolated_workspace", label: "New isolated workspace" },
|
||||||
{ value: "reuse_existing", label: "Reuse existing workspace" },
|
{ value: "reuse_existing", label: "Reuse existing workspace" },
|
||||||
{ value: "operator_branch", label: "Operator branch" },
|
|
||||||
{ value: "agent_default", label: "Agent default" },
|
|
||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
function defaultProjectWorkspaceIdForProject(project: { workspaces?: Array<{ id: string; isPrimary: boolean }>; executionWorkspacePolicy?: { defaultProjectWorkspaceId?: string | null } | null } | null | undefined) {
|
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 selectedProject = orderedProjects.find((project) => project.id === projectId);
|
||||||
const executionWorkspacePolicy = selectedProject?.executionWorkspacePolicy ?? null;
|
const executionWorkspacePolicy = selectedProject?.executionWorkspacePolicy ?? null;
|
||||||
const selectedReusableExecutionWorkspace = (reusableExecutionWorkspaces ?? []).find(
|
const selectedReusableExecutionWorkspace = deduplicatedReusableWorkspaces.find(
|
||||||
(workspace) => workspace.id === selectedExecutionWorkspaceId,
|
(workspace) => workspace.id === selectedExecutionWorkspaceId,
|
||||||
);
|
);
|
||||||
const requestedExecutionWorkspaceMode =
|
const requestedExecutionWorkspaceMode =
|
||||||
@@ -747,7 +745,19 @@ export function NewIssueDialog() {
|
|||||||
const currentProject = orderedProjects.find((project) => project.id === projectId);
|
const currentProject = orderedProjects.find((project) => project.id === projectId);
|
||||||
const currentProjectExecutionWorkspacePolicy = currentProject?.executionWorkspacePolicy ?? null;
|
const currentProjectExecutionWorkspacePolicy = currentProject?.executionWorkspacePolicy ?? null;
|
||||||
const currentProjectSupportsExecutionWorkspace = Boolean(currentProjectExecutionWorkspacePolicy?.enabled);
|
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,
|
(workspace) => workspace.id === selectedExecutionWorkspaceId,
|
||||||
);
|
);
|
||||||
const assigneeOptionsTitle =
|
const assigneeOptionsTitle =
|
||||||
@@ -1126,7 +1136,7 @@ export function NewIssueDialog() {
|
|||||||
onChange={(e) => setSelectedExecutionWorkspaceId(e.target.value)}
|
onChange={(e) => setSelectedExecutionWorkspaceId(e.target.value)}
|
||||||
>
|
>
|
||||||
<option value="">Choose an existing workspace</option>
|
<option value="">Choose an existing workspace</option>
|
||||||
{(reusableExecutionWorkspaces ?? []).map((workspace) => (
|
{deduplicatedReusableWorkspaces.map((workspace) => (
|
||||||
<option key={workspace.id} value={workspace.id}>
|
<option key={workspace.id} value={workspace.id}>
|
||||||
{workspace.name} · {workspace.status} · {workspace.branchName ?? workspace.cwd ?? workspace.id.slice(0, 8)}
|
{workspace.name} · {workspace.status} · {workspace.branchName ?? workspace.cwd ?? workspace.id.slice(0, 8)}
|
||||||
</option>
|
</option>
|
||||||
|
|||||||
Reference in New Issue
Block a user