import { type GitRunStackedActionResult, type GitStackedAction, type GitStatusResult, type NativeApi,} from "@t3tools/contracts";import { queryOptions, useMutation, useQuery, useQueryClient } from "@tanstack/react-query";import { useCallback, useEffect, useMemo, useRef, useState } from "react";import { CheckIcon, CircleIcon, CloudUploadIcon, GitCommitIcon, Loader2Icon, MinusIcon, XIcon,} from "lucide-react";import { GitHubIcon } from "./Icons"; interface GitActionsControlProps { api: NativeApi | undefined; gitCwd: string | null;} type GitActionIconName = "commit" | "push" | "pr"; interface GitActionMenuItem { id: "commit" | "push" | "pr"; label: string; disabled: boolean; icon: GitActionIconName; kind: "open_modal" | "run_action" | "open_pr"; action?: GitStackedAction;} interface GitModalActionOption { action: GitStackedAction; label: string; disabled: boolean; icon: GitActionIconName; detail?: string;} type GitProgressStepStatus = "pending" | "running" | "completed" | "skipped" | "failed"; interface GitProgressStep { id: "generate" | "commit" | "push" | "pr"; label: string; status: GitProgressStepStatus; detail?: string;} function GitActionIcon(props: { icon: GitActionIconName; disabled: boolean }) { const toneClass = props.disabled ? "text-muted-foreground/45" : "text-foreground/85"; if (props.icon === "commit") { return <GitCommitIcon className={`h-5 w-5 shrink-0 ${toneClass}`} />; } if (props.icon === "push") { return <CloudUploadIcon className={`h-5 w-5 shrink-0 ${toneClass}`} />; } return <GitHubIcon className={`h-5 w-5 shrink-0 ${toneClass}`} />;} function gitActionModalTitle(): string { return "Commit your changes";} function initialGitProgressSteps( action: GitStackedAction, commitMessage: string,): GitProgressStep[] { const hasCustomMessage = commitMessage.trim().length > 0; const steps: GitProgressStep[] = []; if (!hasCustomMessage) { steps.push({ id: "generate", label: "Generate commit message", status: "pending", }); } steps.push({ id: "commit", label: "Commit changes", status: "pending", }); if (action !== "commit") { steps.push({ id: "push", label: "Push branch", status: "pending", }); } if (action === "commit_push_pr") { steps.push({ id: "pr", label: "Create or open PR", status: "pending", }); } return steps;} function updateProgressStep( steps: GitProgressStep[], id: GitProgressStep["id"], status: GitProgressStepStatus, detail?: string,): GitProgressStep[] { return steps.map((step) => { if (step.id !== id) return step; return { ...step, status, ...(detail ? { detail } : {}), }; });} function runActionLabel(action: GitStackedAction): string { if (action === "commit") return "Commit changes"; if (action === "commit_push") return "Commit and push"; return "Commit and create PR";} function buildGitActionMenuItems( gitStatus: GitStatusResult | null, isDisabled: boolean,): GitActionMenuItem[] { if (!gitStatus) return []; const hasBranch = gitStatus.branch !== null; const hasOpenPr = gitStatus.openPr !== null; const hasChanges = gitStatus.hasWorkingTreeChanges; const hasAhead = gitStatus.aheadCount > 0; const canCommit = !isDisabled && hasChanges; const canPush = !isDisabled && hasBranch && !hasChanges && hasAhead; const canOpenPr = !isDisabled && hasOpenPr; const canCreatePr = !isDisabled && hasBranch && !hasOpenPr && !hasChanges && gitStatus.hasUpstream && gitStatus.behindCount === 0; return [ { id: "commit", label: "Commit", disabled: !canCommit, icon: "commit", kind: "open_modal", }, { id: "push", label: "Push", disabled: !canPush, icon: "push", kind: "run_action", action: "commit_push", }, hasOpenPr ? { id: "pr", label: "View PR", disabled: !canOpenPr, icon: "pr", kind: "open_pr", } : { id: "pr", label: "Create PR", disabled: !canCreatePr, icon: "pr", kind: "run_action", action: "commit_push_pr", }, ];} function buildGitModalActionOptions( gitStatus: GitStatusResult | null, isDisabled: boolean,): GitModalActionOption[] { const hasBranch = gitStatus?.branch !== null; const hasOpenPr = gitStatus?.openPr !== null; return [ { action: "commit", label: "Commit", disabled: isDisabled, icon: "commit", }, { action: "commit_push", label: "Commit and push", disabled: isDisabled || !hasBranch, icon: "push", ...(!hasBranch ? { detail: "Requires an attached branch." } : {}), }, { action: "commit_push_pr", label: "Commit and create PR", disabled: isDisabled || !hasBranch || hasOpenPr, icon: "pr", ...(!hasBranch ? { detail: "Requires an attached branch." } : hasOpenPr ? { detail: "A PR is already open for this branch." } : {}), }, ];} function getGitStatusQueryKey(gitCwd: string | null): readonly ["git", "status", string | null] { return ["git", "status", gitCwd];} function gitStatusQueryOptions(api: NativeApi | undefined, gitCwd: string | null) { return queryOptions({ queryKey: getGitStatusQueryKey(gitCwd), queryFn: async () => { if (!api || !gitCwd) { throw new Error("Git status is unavailable."); } return api.git.status({ cwd: gitCwd }); }, enabled: !!api && !!gitCwd, });} export default function GitActionsControl({ api, gitCwd }: GitActionsControlProps) { const queryClient = useQueryClient(); const [isGitMenuOpen, setIsGitMenuOpen] = useState(false); const [isGitModalActionRunning, setIsGitModalActionRunning] = useState(false); const [gitActionError, setGitActionError] = useState<string | null>(null); const [isGitModalOpen, setIsGitModalOpen] = useState(false); const [gitModalSelectedAction, setGitModalSelectedAction] = useState<GitStackedAction>("commit"); const [gitModalCommitMessage, setGitModalCommitMessage] = useState(""); const [gitModalProgress, setGitModalProgress] = useState<GitProgressStep[]>([]); const [gitModalError, setGitModalError] = useState<string | null>(null); const [gitModalResult, setGitModalResult] = useState<GitRunStackedActionResult | null>(null); const gitMenuRef = useRef<HTMLDivElement>(null); const { data: gitStatus = null, error: gitStatusError } = useQuery( gitStatusQueryOptions(api, gitCwd), ); const runImmediateGitActionMutation = useMutation({ mutationFn: async (action: GitStackedAction) => { if (!api || !gitCwd) { throw new Error("Git action is unavailable."); } return api.git.runStackedAction({ cwd: gitCwd, action }); }, onMutate: () => { setIsGitMenuOpen(false); setGitActionError(null); setGitModalError(null); }, onSuccess: async () => { await queryClient.invalidateQueries({ queryKey: ["git"] }); }, onError: (error) => { setGitActionError(error instanceof Error ? error.message : "Git action failed."); }, }); const isGitActionRunning = isGitModalActionRunning || runImmediateGitActionMutation.isPending; const gitBaseDisabled = !api || !gitCwd || !gitStatus || isGitActionRunning; const gitActionMenuItems = useMemo( () => buildGitActionMenuItems(gitStatus, gitBaseDisabled), [gitBaseDisabled, gitStatus], ); const gitModalActionOptions = useMemo( () => buildGitModalActionOptions(gitStatus, !gitStatus || isGitActionRunning), [gitStatus, isGitActionRunning], ); const selectedGitModalActionOption = useMemo( () => gitModalActionOptions.find((option) => option.action === gitModalSelectedAction) ?? null, [gitModalActionOptions, gitModalSelectedAction], ); const visibleGitActionError = gitActionError ?? gitStatusError?.message; const gitModalOpenPrUrl = gitModalResult?.pr.url ?? gitStatus?.openPr?.url ?? null; const gitModalHasProgress = gitModalProgress.length > 0; const gitModalSelectionMode = !gitModalHasProgress && gitModalResult === null; const gitModalSteps = gitModalProgress; const refreshGitStatus = useCallback(async () => { if (!api || !gitCwd) return; await queryClient.invalidateQueries({ queryKey: ["git"] }); setGitActionError(null); }, [api, gitCwd, queryClient]); const openGitActionModal = useCallback(() => { setIsGitMenuOpen(false); setIsGitModalOpen(true); setGitModalSelectedAction("commit"); setGitModalCommitMessage(""); setGitModalProgress([]); setGitModalError(null); setGitModalResult(null); setGitActionError(null); }, []); const closeGitActionModal = useCallback(() => { if (isGitActionRunning) return; setIsGitModalOpen(false); setGitModalSelectedAction("commit"); setGitModalCommitMessage(""); setGitModalProgress([]); setGitModalError(null); setGitModalResult(null); }, [isGitActionRunning]); const runGitActionImmediately = useCallback( (action: GitStackedAction) => { runImmediateGitActionMutation.mutate(action); }, [runImmediateGitActionMutation], ); const openExistingPr = useCallback(() => { setIsGitMenuOpen(false); setGitActionError(null); if (!api) { setGitActionError("Link opening is unavailable."); return; } const prUrl = gitStatus?.openPr?.url ?? null; if (!prUrl) { setGitActionError("No open PR found for the current branch."); return; } void api.shell.openExternal(prUrl).catch((error) => { setGitActionError(error instanceof Error ? error.message : "Unable to open PR link."); }); }, [api, gitStatus?.openPr?.url]); const openPrFromModal = useCallback(() => { if (!api) { setGitModalError("Link opening is unavailable."); return; } const prUrl = gitModalOpenPrUrl; if (!prUrl) return; void api.shell.openExternal(prUrl).catch((error) => { setGitModalError(error instanceof Error ? error.message : "Unable to open PR link."); }); }, [api, gitModalOpenPrUrl]); const runGitAction = useCallback(async () => { if (!api || !gitCwd || !isGitModalOpen) return; if (!selectedGitModalActionOption || selectedGitModalActionOption.disabled) return; const actionCwd = gitCwd; const action = selectedGitModalActionOption.action; const commitMessage = gitModalCommitMessage.trim(); const includeGeneratedCommitMessage = commitMessage.length === 0; setIsGitModalActionRunning(true); setGitModalError(null); setGitActionError(null); setGitModalResult(null); setGitModalProgress(initialGitProgressSteps(action, commitMessage)); let commit: GitRunStackedActionResult["commit"] = { status: "skipped_no_changes", }; let push: GitRunStackedActionResult["push"] = { status: "skipped_not_requested", }; let pr: GitRunStackedActionResult["pr"] = { status: "skipped_not_requested", }; const updateStep = ( id: GitProgressStep["id"], status: GitProgressStepStatus, detail?: string, ) => { setGitModalProgress((steps) => updateProgressStep(steps, id, status, detail)); }; try { if (includeGeneratedCommitMessage) { updateStep("generate", "running"); } else { updateStep("commit", "running"); } const commitRun = await api.git.runStackedAction({ cwd: actionCwd, action: "commit", ...(commitMessage.length > 0 ? { commitMessage } : {}), }); commit = commitRun.commit; if (includeGeneratedCommitMessage) { if (commitRun.commit.status === "created") { if (commitRun.commit.subject) { setGitModalCommitMessage(commitRun.commit.subject); } updateStep("generate", "completed"); } else { updateStep("generate", "skipped", "No local changes to commit."); } } if (commitRun.commit.status === "created") { updateStep("commit", "completed", commitRun.commit.subject ?? "Committed local changes."); } else { updateStep("commit", "skipped", "No local changes to commit."); } if (action !== "commit") { updateStep("push", "running"); const pushRun = await api.git.runStackedAction({ cwd: actionCwd, action: "commit_push", }); push = pushRun.push; if (pushRun.push.status === "pushed") { updateStep( "push", "completed", pushRun.push.upstreamBranch ? `Pushed to ${pushRun.push.upstreamBranch}.` : "Pushed latest commits.", ); } else { updateStep("push", "skipped", "Branch already up to date."); } } if (action === "commit_push_pr") { updateStep("pr", "running"); const prRun = await api.git.runStackedAction({ cwd: actionCwd, action: "commit_push_pr", }); pr = prRun.pr; if (prRun.pr.status === "opened_existing") { updateStep( "pr", "completed", prRun.pr.number ? `Opened existing PR #${prRun.pr.number}.` : "Opened existing PR.", ); } else if (prRun.pr.status === "created") { updateStep( "pr", "completed", prRun.pr.number ? `Created PR #${prRun.pr.number}.` : "Created PR.", ); } else { updateStep("pr", "skipped", "PR step was not requested."); } } setGitModalResult({ action, commit, push, pr }); } catch (error) { const message = error instanceof Error ? error.message : "Git action failed."; setGitModalError(message); setGitActionError(message); setGitModalProgress((steps) => { const active = steps.find((step) => step.status === "running"); if (!active) return steps; return updateProgressStep(steps, active.id, "failed", message); }); } finally { setIsGitModalActionRunning(false); await refreshGitStatus().catch(() => undefined); } }, [ api, gitCwd, gitModalCommitMessage, isGitModalOpen, refreshGitStatus, selectedGitModalActionOption, ]); useEffect(() => { setGitActionError(null); setGitModalError(null); setIsGitModalOpen(false); setGitModalSelectedAction("commit");Lines 1–500 of 831. Load more
import { type GitRunStackedActionResult, type GitStackedAction, type GitStatusResult, type NativeApi, } from "@t3tools/contracts"; import { queryOptions, useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { CheckIcon, CircleIcon, CloudUploadIcon, GitCommitIcon, Loader2Icon, MinusIcon, XIcon, } from "lucide-react"; import { GitHubIcon } from "./Icons"; interface GitActionsControlProps { api: NativeApi | undefined; gitCwd: string | null; } type GitActionIconName = "commit" | "push" | "pr"; interface GitActionMenuItem { id: "commit" | "push" | "pr"; label: string; disabled: boolean; icon: GitActionIconName; kind: "open_modal" | "run_action" | "open_pr"; action?: GitStackedAction; } interface GitModalActionOption { action: GitStackedAction; label: string; disabled: boolean; icon: GitActionIconName; detail?: string; } type GitProgressStepStatus = "pending" | "running" | "completed" | "skipped" | "failed"; interface GitProgressStep { id: "generate" | "commit" | "push" | "pr"; label: string; status: GitProgressStepStatus; detail?: string; } function GitActionIcon(props: { icon: GitActionIconName; disabled: boolean }) { const toneClass = props.disabled ? "text-muted-foreground/45" : "text-foreground/85"; if (props.icon === "commit") { return <GitCommitIcon className={`h-5 w-5 shrink-0 ${toneClass}`} />; } if (props.icon === "push") { return <CloudUploadIcon className={`h-5 w-5 shrink-0 ${toneClass}`} />; } return <GitHubIcon className={`h-5 w-5 shrink-0 ${toneClass}`} />; } function gitActionModalTitle(): string { return "Commit your changes"; } function initialGitProgressSteps( action: GitStackedAction, commitMessage: string, ): GitProgressStep[] { const hasCustomMessage = commitMessage.trim().length > 0; const steps: GitProgressStep[] = []; if (!hasCustomMessage) { steps.push({ id: "generate", label: "Generate commit message", status: "pending", }); } steps.push({ id: "commit", label: "Commit changes", status: "pending", }); if (action !== "commit") { steps.push({ id: "push", label: "Push branch", status: "pending", }); } if (action === "commit_push_pr") { steps.push({ id: "pr", label: "Create or open PR", status: "pending", }); } return steps; } function updateProgressStep( steps: GitProgressStep[], id: GitProgressStep["id"], status: GitProgressStepStatus, detail?: string, ): GitProgressStep[] { return steps.map((step) => { if (step.id !== id) return step; return { ...step, status, ...(detail ? { detail } : {}), }; }); } function runActionLabel(action: GitStackedAction): string { if (action === "commit") return "Commit changes"; if (action === "commit_push") return "Commit and push"; return "Commit and create PR"; } function buildGitActionMenuItems( gitStatus: GitStatusResult | null, isDisabled: boolean, ): GitActionMenuItem[] { if (!gitStatus) return []; const hasBranch = gitStatus.branch !== null; const hasOpenPr = gitStatus.openPr !== null; const hasChanges = gitStatus.hasWorkingTreeChanges; const hasAhead = gitStatus.aheadCount > 0; const canCommit = !isDisabled && hasChanges; const canPush = !isDisabled && hasBranch && !hasChanges && hasAhead; const canOpenPr = !isDisabled && hasOpenPr; const canCreatePr = !isDisabled && hasBranch && !hasOpenPr && !hasChanges && gitStatus.hasUpstream && gitStatus.behindCount === 0; return [ { id: "commit", label: "Commit", disabled: !canCommit, icon: "commit", kind: "open_modal", }, { id: "push", label: "Push", disabled: !canPush, icon: "push", kind: "run_action", action: "commit_push", }, hasOpenPr ? { id: "pr", label: "View PR", disabled: !canOpenPr, icon: "pr", kind: "open_pr", } : { id: "pr", label: "Create PR", disabled: !canCreatePr, icon: "pr", kind: "run_action", action: "commit_push_pr", }, ]; } function buildGitModalActionOptions( gitStatus: GitStatusResult | null, isDisabled: boolean, ): GitModalActionOption[] { const hasBranch = gitStatus?.branch !== null; const hasOpenPr = gitStatus?.openPr !== null; return [ { action: "commit", label: "Commit", disabled: isDisabled, icon: "commit", }, { action: "commit_push", label: "Commit and push", disabled: isDisabled || !hasBranch, icon: "push", ...(!hasBranch ? { detail: "Requires an attached branch." } : {}), }, { action: "commit_push_pr", label: "Commit and create PR", disabled: isDisabled || !hasBranch || hasOpenPr, icon: "pr", ...(!hasBranch ? { detail: "Requires an attached branch." } : hasOpenPr ? { detail: "A PR is already open for this branch." } : {}), }, ]; } function getGitStatusQueryKey(gitCwd: string | null): readonly ["git", "status", string | null] { return ["git", "status", gitCwd]; } function gitStatusQueryOptions(api: NativeApi | undefined, gitCwd: string | null) { return queryOptions({ queryKey: getGitStatusQueryKey(gitCwd), queryFn: async () => { if (!api || !gitCwd) { throw new Error("Git status is unavailable."); } return api.git.status({ cwd: gitCwd }); }, enabled: !!api && !!gitCwd, }); } export default function GitActionsControl({ api, gitCwd }: GitActionsControlProps) { const queryClient = useQueryClient(); const [isGitMenuOpen, setIsGitMenuOpen] = useState(false); const [isGitModalActionRunning, setIsGitModalActionRunning] = useState(false); const [gitActionError, setGitActionError] = useState<string | null>(null); const [isGitModalOpen, setIsGitModalOpen] = useState(false); const [gitModalSelectedAction, setGitModalSelectedAction] = useState<GitStackedAction>("commit"); const [gitModalCommitMessage, setGitModalCommitMessage] = useState(""); const [gitModalProgress, setGitModalProgress] = useState<GitProgressStep[]>([]); const [gitModalError, setGitModalError] = useState<string | null>(null); const [gitModalResult, setGitModalResult] = useState<GitRunStackedActionResult | null>(null); const gitMenuRef = useRef<HTMLDivElement>(null); const { data: gitStatus = null, error: gitStatusError } = useQuery( gitStatusQueryOptions(api, gitCwd), ); const runImmediateGitActionMutation = useMutation({ mutationFn: async (action: GitStackedAction) => { if (!api || !gitCwd) { throw new Error("Git action is unavailable."); } return api.git.runStackedAction({ cwd: gitCwd, action }); }, onMutate: () => { setIsGitMenuOpen(false); setGitActionError(null); setGitModalError(null); }, onSuccess: async () => { await queryClient.invalidateQueries({ queryKey: ["git"] }); }, onError: (error) => { setGitActionError(error instanceof Error ? error.message : "Git action failed."); }, }); const isGitActionRunning = isGitModalActionRunning || runImmediateGitActionMutation.isPending; const gitBaseDisabled = !api || !gitCwd || !gitStatus || isGitActionRunning; const gitActionMenuItems = useMemo( () => buildGitActionMenuItems(gitStatus, gitBaseDisabled), [gitBaseDisabled, gitStatus], ); const gitModalActionOptions = useMemo( () => buildGitModalActionOptions(gitStatus, !gitStatus || isGitActionRunning), [gitStatus, isGitActionRunning], ); const selectedGitModalActionOption = useMemo( () => gitModalActionOptions.find((option) => option.action === gitModalSelectedAction) ?? null, [gitModalActionOptions, gitModalSelectedAction], ); const visibleGitActionError = gitActionError ?? gitStatusError?.message; const gitModalOpenPrUrl = gitModalResult?.pr.url ?? gitStatus?.openPr?.url ?? null; const gitModalHasProgress = gitModalProgress.length > 0; const gitModalSelectionMode = !gitModalHasProgress && gitModalResult === null; const gitModalSteps = gitModalProgress; const refreshGitStatus = useCallback(async () => { if (!api || !gitCwd) return; await queryClient.invalidateQueries({ queryKey: ["git"] }); setGitActionError(null); }, [api, gitCwd, queryClient]); const openGitActionModal = useCallback(() => { setIsGitMenuOpen(false); setIsGitModalOpen(true); setGitModalSelectedAction("commit"); setGitModalCommitMessage(""); setGitModalProgress([]); setGitModalError(null); setGitModalResult(null); setGitActionError(null); }, []); const closeGitActionModal = useCallback(() => { if (isGitActionRunning) return; setIsGitModalOpen(false); setGitModalSelectedAction("commit"); setGitModalCommitMessage(""); setGitModalProgress([]); setGitModalError(null); setGitModalResult(null); }, [isGitActionRunning]); const runGitActionImmediately = useCallback( (action: GitStackedAction) => { runImmediateGitActionMutation.mutate(action); }, [runImmediateGitActionMutation], ); const openExistingPr = useCallback(() => { setIsGitMenuOpen(false); setGitActionError(null); if (!api) { setGitActionError("Link opening is unavailable."); return; } const prUrl = gitStatus?.openPr?.url ?? null; if (!prUrl) { setGitActionError("No open PR found for the current branch."); return; } void api.shell.openExternal(prUrl).catch((error) => { setGitActionError(error instanceof Error ? error.message : "Unable to open PR link."); }); }, [api, gitStatus?.openPr?.url]); const openPrFromModal = useCallback(() => { if (!api) { setGitModalError("Link opening is unavailable."); return; } const prUrl = gitModalOpenPrUrl; if (!prUrl) return; void api.shell.openExternal(prUrl).catch((error) => { setGitModalError(error instanceof Error ? error.message : "Unable to open PR link."); }); }, [api, gitModalOpenPrUrl]); const runGitAction = useCallback(async () => { if (!api || !gitCwd || !isGitModalOpen) return; if (!selectedGitModalActionOption || selectedGitModalActionOption.disabled) return; const actionCwd = gitCwd; const action = selectedGitModalActionOption.action; const commitMessage = gitModalCommitMessage.trim(); const includeGeneratedCommitMessage = commitMessage.length === 0; setIsGitModalActionRunning(true); setGitModalError(null); setGitActionError(null); setGitModalResult(null); setGitModalProgress(initialGitProgressSteps(action, commitMessage)); let commit: GitRunStackedActionResult["commit"] = { status: "skipped_no_changes", }; let push: GitRunStackedActionResult["push"] = { status: "skipped_not_requested", }; let pr: GitRunStackedActionResult["pr"] = { status: "skipped_not_requested", }; const updateStep = ( id: GitProgressStep["id"], status: GitProgressStepStatus, detail?: string, ) => { setGitModalProgress((steps) => updateProgressStep(steps, id, status, detail)); }; try { if (includeGeneratedCommitMessage) { updateStep("generate", "running"); } else { updateStep("commit", "running"); } const commitRun = await api.git.runStackedAction({ cwd: actionCwd, action: "commit", ...(commitMessage.length > 0 ? { commitMessage } : {}), }); commit = commitRun.commit; if (includeGeneratedCommitMessage) { if (commitRun.commit.status === "created") { if (commitRun.commit.subject) { setGitModalCommitMessage(commitRun.commit.subject); } updateStep("generate", "completed"); } else { updateStep("generate", "skipped", "No local changes to commit."); } } if (commitRun.commit.status === "created") { updateStep("commit", "completed", commitRun.commit.subject ?? "Committed local changes."); } else { updateStep("commit", "skipped", "No local changes to commit."); } if (action !== "commit") { updateStep("push", "running"); const pushRun = await api.git.runStackedAction({ cwd: actionCwd, action: "commit_push", }); push = pushRun.push; if (pushRun.push.status === "pushed") { updateStep( "push", "completed", pushRun.push.upstreamBranch ? `Pushed to ${pushRun.push.upstreamBranch}.` : "Pushed latest commits.", ); } else { updateStep("push", "skipped", "Branch already up to date."); } } if (action === "commit_push_pr") { updateStep("pr", "running"); const prRun = await api.git.runStackedAction({ cwd: actionCwd, action: "commit_push_pr", }); pr = prRun.pr; if (prRun.pr.status === "opened_existing") { updateStep( "pr", "completed", prRun.pr.number ? `Opened existing PR #${prRun.pr.number}.` : "Opened existing PR.", ); } else if (prRun.pr.status === "created") { updateStep( "pr", "completed", prRun.pr.number ? `Created PR #${prRun.pr.number}.` : "Created PR.", ); } else { updateStep("pr", "skipped", "PR step was not requested."); } } setGitModalResult({ action, commit, push, pr }); } catch (error) { const message = error instanceof Error ? error.message : "Git action failed."; setGitModalError(message); setGitActionError(message); setGitModalProgress((steps) => { const active = steps.find((step) => step.status === "running"); if (!active) return steps; return updateProgressStep(steps, active.id, "failed", message); }); } finally { setIsGitModalActionRunning(false); await refreshGitStatus().catch(() => undefined); } }, [ api, gitCwd, gitModalCommitMessage, isGitModalOpen, refreshGitStatus, selectedGitModalActionOption, ]); useEffect(() => { setGitActionError(null); setGitModalError(null); setIsGitModalOpen(false); setGitModalSelectedAction("commit");