From 95f21c1099c9e28b95ee7c6d2f0a6ac49c333485 Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Sun, 1 Mar 2026 19:00:53 -0800 Subject: [PATCH] fix: make default-branch confirmation copy action-aware Co-authored-by: codex --- .../GitActionsControl.logic.test.ts | 48 +++++++++++++++++ .../src/components/GitActionsControl.logic.ts | 51 +++++++++++++++++++ apps/web/src/components/GitActionsControl.tsx | 51 +++++++++++-------- 3 files changed, 130 insertions(+), 20 deletions(-) diff --git a/apps/web/src/components/GitActionsControl.logic.test.ts b/apps/web/src/components/GitActionsControl.logic.test.ts index c8a9a55c49cd0ad000f0c1f22b2645f365701feb..011733413e87b365576482cb08b27b03a2e26329 100644 --- a/apps/web/src/components/GitActionsControl.logic.test.ts +++ b/apps/web/src/components/GitActionsControl.logic.test.ts @@ -5,6 +5,7 @@ import { buildMenuItems, requiresDefaultBranchConfirmation, resolveAutoFeatureBranchName, + resolveDefaultBranchActionDialogCopy, resolveQuickAction, summarizeGitResult, } from "./GitActionsControl.logic"; @@ -744,6 +745,53 @@ describe("requiresDefaultBranchConfirmation", () => { }); }); +describe("resolveDefaultBranchActionDialogCopy", () => { + it("uses push-only copy when pushing without a commit", () => { + const copy = resolveDefaultBranchActionDialogCopy({ + action: "commit_push", + branchName: "main", + includesCommit: false, + }); + + assert.deepEqual(copy, { + title: "Push to default branch?", + description: + 'This action will push local commits on "main". You can continue on this branch or create a feature branch and run the same action there.', + continueLabel: "Push to main", + }); + }); + + it("uses push-and-pr copy when creating a PR without a commit", () => { + const copy = resolveDefaultBranchActionDialogCopy({ + action: "commit_push_pr", + branchName: "main", + includesCommit: false, + }); + + assert.deepEqual(copy, { + title: "Push & create PR from default branch?", + description: + 'This action will push local commits and create a PR on "main". You can continue on this branch or create a feature branch and run the same action there.', + continueLabel: "Push & create PR", + }); + }); + + it("keeps commit copy when the action includes a commit", () => { + const copy = resolveDefaultBranchActionDialogCopy({ + action: "commit_push_pr", + branchName: "main", + includesCommit: true, + }); + + assert.deepEqual(copy, { + title: "Commit, push & create PR from default branch?", + description: + 'This action will commit, push, and create a PR on "main". You can continue on this branch or create a feature branch and run the same action there.', + continueLabel: "Commit, push & create PR", + }); + }); +}); + describe("buildGitActionProgressStages", () => { it("shows only push progress when push-only is forced", () => { const stages = buildGitActionProgressStages({ diff --git a/apps/web/src/components/GitActionsControl.logic.ts b/apps/web/src/components/GitActionsControl.logic.ts index b0868ded51d34b78b2e72367193dbd3b74edb9b9..7b454a50b8e8597abccf8e2edbc34f048f7f87f2 100644 --- a/apps/web/src/components/GitActionsControl.logic.ts +++ b/apps/web/src/components/GitActionsControl.logic.ts @@ -25,6 +25,12 @@ export interface GitQuickAction { hint?: string; } +export interface DefaultBranchActionDialogCopy { + title: string; + description: string; + continueLabel: string; +} + const SHORT_SHA_LENGTH = 7; const TOAST_DESCRIPTION_MAX = 72; const AUTO_FEATURE_BRANCH_PREFIX = "feature/stacked"; @@ -276,6 +282,51 @@ export function requiresDefaultBranchConfirmation( return action === "commit_push" || action === "commit_push_pr"; } +export function resolveDefaultBranchActionDialogCopy(input: { + action: GitStackedAction; + branchName: string; + includesCommit: boolean; +}): DefaultBranchActionDialogCopy { + const branchLabel = input.branchName; + const suffix = ` on "${branchLabel}". You can continue on this branch or create a feature branch and run the same action there.`; + + if (input.action === "commit") { + return { + title: "Commit to default branch?", + description: `This action will commit changes${suffix}`, + continueLabel: `Commit to ${branchLabel}`, + }; + } + + if (input.action === "commit_push") { + if (input.includesCommit) { + return { + title: "Commit & push to default branch?", + description: `This action will commit and push changes${suffix}`, + continueLabel: `Commit & push to ${branchLabel}`, + }; + } + return { + title: "Push to default branch?", + description: `This action will push local commits${suffix}`, + continueLabel: `Push to ${branchLabel}`, + }; + } + + if (input.includesCommit) { + return { + title: "Commit, push & create PR from default branch?", + description: `This action will commit, push, and create a PR${suffix}`, + continueLabel: `Commit, push & create PR`, + }; + } + return { + title: "Push & create PR from default branch?", + description: `This action will push local commits and create a PR${suffix}`, + continueLabel: "Push & create PR", + }; +} + function formatDateForBranch(now: Date): string { const year = now.getUTCFullYear(); const month = String(now.getUTCMonth() + 1).padStart(2, "0"); diff --git a/apps/web/src/components/GitActionsControl.tsx b/apps/web/src/components/GitActionsControl.tsx index 9e194b8899a3555949193ad43cc7ad4aabff4154..1b508f01d389d19c8460b13bdf951b11b8be10af 100644 --- a/apps/web/src/components/GitActionsControl.tsx +++ b/apps/web/src/components/GitActionsControl.tsx @@ -11,6 +11,7 @@ import { type GitQuickAction, requiresDefaultBranchConfirmation, resolveAutoFeatureBranchName, + resolveDefaultBranchActionDialogCopy, resolveQuickAction, summarizeGitResult, } from "./GitActionsControl.logic"; @@ -50,6 +51,8 @@ interface GitActionsControlProps { interface PendingDefaultBranchAction { action: GitStackedAction; + branchName: string; + includesCommit: boolean; commitMessage?: string; forcePushOnlyProgress: boolean; onConfirmed?: () => void; @@ -115,12 +118,6 @@ const COMMIT_DIALOG_TITLE = "Commit changes"; const COMMIT_DIALOG_DESCRIPTION = "Review and confirm your commit. Leave the message blank to auto-generate one."; -const DEFAULT_BRANCH_ACTION_DESCRIPTION: Record = { - commit: "commit changes", - commit_push: "commit and push changes", - commit_push_pr: "commit, push, and create a PR", -}; - function GitActionItemIcon({ icon }: { icon: GitActionIconName }) { if (icon === "commit") return ; if (icon === "push") return ; @@ -202,6 +199,13 @@ export default function GitActionsControl({ gitCwd, activeThreadId }: GitActions const quickActionDisabledReason = quickAction.disabled ? (quickAction.hint ?? "This action is currently unavailable.") : null; + const pendingDefaultBranchActionCopy = pendingDefaultBranchAction + ? resolveDefaultBranchActionDialogCopy({ + action: pendingDefaultBranchAction.action, + branchName: pendingDefaultBranchAction.branchName, + includesCommit: pendingDefaultBranchAction.includesCommit, + }) + : null; const openExistingPr = useCallback(async () => { const api = readNativeApi(); @@ -253,6 +257,8 @@ export default function GitActionsControl({ gitCwd, activeThreadId }: GitActions const actionStatus = statusOverride ?? gitStatusForActions; const actionIsDefaultBranch = isDefaultBranchOverride ?? isDefaultBranch; const actionBranch = actionStatus?.branch ?? null; + const includesCommit = + !forcePushOnlyProgress && (action === "commit" || !!actionStatus?.hasWorkingTreeChanges); if ( !skipDefaultBranchPrompt && requiresDefaultBranchConfirmation(action, actionIsDefaultBranch) && @@ -260,6 +266,8 @@ export default function GitActionsControl({ gitCwd, activeThreadId }: GitActions ) { setPendingDefaultBranchAction({ action, + branchName: actionBranch, + includesCommit, ...(commitMessage ? { commitMessage } : {}), forcePushOnlyProgress, ...(onConfirmed ? { onConfirmed } : {}), @@ -398,10 +406,13 @@ export default function GitActionsControl({ gitCwd, activeThreadId }: GitActions const continuePendingDefaultBranchAction = useCallback(() => { if (!pendingDefaultBranchAction) return; - const pendingAction = pendingDefaultBranchAction; + const { action, commitMessage, forcePushOnlyProgress, onConfirmed } = pendingDefaultBranchAction; setPendingDefaultBranchAction(null); void runGitActionWithToast({ - ...pendingAction, + action, + ...(commitMessage ? { commitMessage } : {}), + forcePushOnlyProgress, + ...(onConfirmed ? { onConfirmed } : {}), skipDefaultBranchPrompt: true, }); }, [pendingDefaultBranchAction, runGitActionWithToast]); @@ -456,9 +467,14 @@ export default function GitActionsControl({ gitCwd, activeThreadId }: GitActions const checkoutFeatureBranchAndContinuePendingAction = useCallback(() => { if (!pendingDefaultBranchAction) return; - const pendingAction = pendingDefaultBranchAction; + const { action, commitMessage, forcePushOnlyProgress, onConfirmed } = pendingDefaultBranchAction; setPendingDefaultBranchAction(null); - checkoutNewBranchAndRunAction(pendingAction); + checkoutNewBranchAndRunAction({ + action, + ...(commitMessage ? { commitMessage } : {}), + forcePushOnlyProgress, + ...(onConfirmed ? { onConfirmed } : {}), + }); }, [pendingDefaultBranchAction, checkoutNewBranchAndRunAction]); const runDialogActionOnNewBranch = useCallback(() => { @@ -801,22 +817,17 @@ export default function GitActionsControl({ gitCwd, activeThreadId }: GitActions > - Commit to default branch? - - This action will{" "} - {pendingDefaultBranchAction - ? DEFAULT_BRANCH_ACTION_DESCRIPTION[pendingDefaultBranchAction.action] - : "push changes"}{" "} - on "{gitStatusForActions?.branch ?? "default"}". You can continue on this branch or - create a feature branch and run the same action there. - + + {pendingDefaultBranchActionCopy?.title ?? "Run action on default branch?"} + + {pendingDefaultBranchActionCopy?.description}