import { useMemo, type CSSProperties } from "react"; import { Folder, GitBranch, GitBranchPlus, Monitor } from "lucide-react"; import type { ExecutionWorkspaceSummary } from "@paperclipai/shared"; import { SearchableSelect, type SearchableSelectOption } from "@/components/SearchableSelect"; import { orderReusableExecutionWorkspaces } from "@/lib/reusable-execution-workspaces"; interface WorktreeOption extends SearchableSelectOption { mode: string; workspaceId?: string; description: string; } interface ComposerWorktreePickerProps { mode: string; workspaceId: string; workspaces: readonly ExecutionWorkspaceSummary[]; onChange: (mode: string, workspaceId: string) => void; loading: boolean; error: boolean; onRetry: () => void; disabled?: boolean; mobile?: boolean; contentStyle?: CSSProperties; selectedWorkspaceLabel?: string; } const MODE_OPTIONS: WorktreeOption[] = [ { key: "isolated_workspace", value: "isolated_workspace", mode: "isolated_workspace", label: "New worktree", description: "Start in an isolated checkout." }, { key: "shared_workspace", value: "shared_workspace", mode: "shared_workspace", label: "Project workspace", description: "Use the project's shared checkout." }, { key: "operator_branch", value: "operator_branch", mode: "operator_branch", label: "New branch", description: "Create a branch in the project workspace." }, { key: "agent_default", value: "agent_default", mode: "agent_default", label: "Agent default", description: "Let the agent choose its workspace." }, { key: "inherit", value: "inherit", mode: "inherit", label: "Project default", description: "Use the project's worktree settings." }, ]; /** Reuses the searchable selector and the existing workspace recency ordering. */ export function ComposerWorktreePicker({ mode, workspaceId, workspaces, onChange, loading, error, onRetry, disabled, mobile, contentStyle, selectedWorkspaceLabel, }: ComposerWorktreePickerProps) { const groups = useMemo(() => [ { id: "new", options: [MODE_OPTIONS[0]!] }, { id: "reuse", label: "Reuse a worktree", options: orderReusableExecutionWorkspaces(workspaces).map((workspace): WorktreeOption => ({ key: `reuse:${workspace.id}`, value: `reuse:${workspace.id}`, workspaceId: workspace.id, mode: "reuse_existing", label: workspace.name, description: workspace.branchName ?? "Existing workspace", searchText: `${workspace.branchName ?? ""} ${workspace.id}`, })), }, { id: "other", label: "Other options", options: MODE_OPTIONS.filter((option) => option.mode === "shared_workspace" || (option.mode !== "isolated_workspace" && option.mode === mode)), }, ], [mode, workspaces]); const value = mode === "reuse_existing" ? `reuse:${workspaceId}` : mode; return ( value={value} groups={groups} onValueChange={(_, option) => onChange(option.mode, option.workspaceId ?? "")} placeholder="Worktrees" triggerAriaLabel="Worktrees" mobileTitle="Worktrees" searchPlaceholder="Search worktrees..." emptyMessage="No matching worktrees." disabled={disabled} modal={mobile} contentStyle={contentStyle} contentWidth="auto" className="min-w-0 flex-1 sm:flex-none sm:max-w-72" triggerClassName="h-8 gap-1.5 border-0 bg-transparent px-2 text-xs font-medium shadow-none hover:bg-accent focus-visible:bg-accent focus-visible:ring-0" renderValue={(option) => ( Worktrees · {option?.label ?? selectedWorkspaceLabel ?? "Select worktree"} )} renderOption={(option) => { const Icon = option.workspaceId ? GitBranch : option.mode === "isolated_workspace" ? GitBranchPlus : option.mode === "agent_default" ? Monitor : option.mode === "operator_branch" ? GitBranch : Folder; return ( <> {option.label} {option.description} ); }} listFooter={loading || error || workspaces.length === 0 ? (
{loading ? "Loading existing worktrees…" : error ? ( Couldn't load worktrees. ) : "No existing worktrees yet. Choose New worktree to start one."}
) : undefined} /> ); }