import { useId, useRef, useState } from "react"; import { isValidExistingBranchName } from "@paperclipai/shared"; import { Check, ChevronDown, GitBranch, Loader2, TriangleAlert } from "lucide-react"; import { Button } from "./ui/button"; import { Input } from "./ui/input"; import { Label } from "./ui/label"; import { SystemNoticeMetadataSections } from "./SystemNotice"; import { cn } from "../lib/utils"; export interface WorkspaceBaseRefRecoveryNoticeProps { requestedRef: string; repository: string; /** Only supply a default confirmed by the repository, never a guessed main/master. */ defaultBranch?: string | null; agentName: string; /** Verified provenance, when available. */ configuredBy?: string | null; taskOverride?: boolean; failureKind?: "missing_branch" | "unresolved_ref"; unavailableReason?: string | null; /** Resolve only after both the task setting and retry have server receipts. */ onRepair: (branch: string) => Promise; } /** Inline task repair, also used by the interactive Storybook preview. */ export function WorkspaceBaseRefRecoveryNotice({ requestedRef, repository, defaultBranch, agentName, configuredBy, taskOverride = true, failureKind = "unresolved_ref", unavailableReason, onRepair, }: WorkspaceBaseRefRecoveryNoticeProps) { const id = useId(); const [editing, setEditing] = useState(!defaultBranch); const [branch, setBranch] = useState(defaultBranch ?? ""); const [details, setDetails] = useState(false); const [pending, setPending] = useState(false); const [error, setError] = useState(null); const [repairedBranch, setRepairedBranch] = useState(null); const inFlight = useRef(false); const changed = branch.trim().length > 0 && branch.trim() !== requestedRef; async function repair(nextBranch: string) { const value = nextBranch.trim(); if (!value || value === requestedRef || unavailableReason || inFlight.current || repairedBranch) return; if (!isValidExistingBranchName(value)) { setError("Enter a valid branch name, such as master or release/next."); return; } inFlight.current = true; setPending(true); setError(null); try { await onRepair(value); setRepairedBranch(value); } catch (cause) { setError(cause instanceof Error ? cause.message : "Refresh the task to check its current state, then try again."); } finally { inFlight.current = false; setPending(false); } } return (
{repairedBranch ? : }

{repairedBranch ? "Branch updated · retry requested" : failureKind === "missing_branch" ? <>Branch {requestedRef} was not found : <>Couldn’t use branch {requestedRef}}

{repairedBranch ? <>This task now starts from {repairedBranch}. A retry has been requested for {agentName}. : <>{repository}{failureKind === "missing_branch" ? <> has no branch named {requestedRef}. : " could not resolve the starting branch. Check the branch name and repository access."} {agentName} hasn’t started.}

{!repairedBranch && ( <> {defaultBranch && !editing &&

{defaultBranch}is the repository’s default branch.

}

Changes only this task’s starting branch.

{unavailableReason &&

{unavailableReason}

} {error && } {editing ? (
{ event.preventDefault(); void repair(branch); }}>
setBranch(event.target.value)} placeholder="Branch name" className="font-mono" aria-describedby={error ? `${id}-error` : undefined} autoComplete="off" spellCheck={false} />
{defaultBranch ? : }
) : (
)} )}
{details &&
}
); }