authorgravatar for julius0216@outlook.comJulius Marminge <julius0216@outlook.com> 2026-03-01 19:00:53-08:00
committergravatar for jmarminge@gmail.comJulius Marminge <jmarminge@gmail.com> 2026-03-01 20:06:01-08:00
log95f21c1099c9e28b95ee7c6d2f0a6ac49c333485
tree2165c0fb43c4825922675f4bd34ec06c607363d2
parent8cd92150b13a9b129b8d5cb26f91444d60ea6f55

fix: make default-branch confirmation copy action-aware

Co-authored-by: codex <codex@users.noreply.github.com>

3 files changed, 130 insertions(+), 20 deletions(-)

apps/web/src/components/GitActionsControl.logic.test.ts+48
......@@ -5,6 +5,7 @@ import {
55 buildMenuItems,
66 requiresDefaultBranchConfirmation,
77 resolveAutoFeatureBranchName,
8 resolveDefaultBranchActionDialogCopy,
89 resolveQuickAction,
910 summarizeGitResult,
1011} from "./GitActionsControl.logic";
......@@ -744,6 +745,53 @@ describe("requiresDefaultBranchConfirmation", () => {
744745 });
745746});
746747
748describe("resolveDefaultBranchActionDialogCopy", () => {
749 it("uses push-only copy when pushing without a commit", () => {
750 const copy = resolveDefaultBranchActionDialogCopy({
751 action: "commit_push",
752 branchName: "main",
753 includesCommit: false,
754 });
755
756 assert.deepEqual(copy, {
757 title: "Push to default branch?",
758 description:
759 '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.',
760 continueLabel: "Push to main",
761 });
762 });
763
764 it("uses push-and-pr copy when creating a PR without a commit", () => {
765 const copy = resolveDefaultBranchActionDialogCopy({
766 action: "commit_push_pr",
767 branchName: "main",
768 includesCommit: false,
769 });
770
771 assert.deepEqual(copy, {
772 title: "Push & create PR from default branch?",
773 description:
774 '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.',
775 continueLabel: "Push & create PR",
776 });
777 });
778
779 it("keeps commit copy when the action includes a commit", () => {
780 const copy = resolveDefaultBranchActionDialogCopy({
781 action: "commit_push_pr",
782 branchName: "main",
783 includesCommit: true,
784 });
785
786 assert.deepEqual(copy, {
787 title: "Commit, push & create PR from default branch?",
788 description:
789 '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.',
790 continueLabel: "Commit, push & create PR",
791 });
792 });
793});
794
747795describe("buildGitActionProgressStages", () => {
748796 it("shows only push progress when push-only is forced", () => {
749797 const stages = buildGitActionProgressStages({
apps/web/src/components/GitActionsControl.logic.ts+51
......@@ -25,6 +25,12 @@ export interface GitQuickAction {
2525 hint?: string;
2626}
2727
28export interface DefaultBranchActionDialogCopy {
29 title: string;
30 description: string;
31 continueLabel: string;
32}
33
2834const SHORT_SHA_LENGTH = 7;
2935const TOAST_DESCRIPTION_MAX = 72;
3036const AUTO_FEATURE_BRANCH_PREFIX = "feature/stacked";
......@@ -276,6 +282,51 @@ export function requiresDefaultBranchConfirmation(
276282 return action === "commit_push" || action === "commit_push_pr";
277283}
278284
285export function resolveDefaultBranchActionDialogCopy(input: {
286 action: GitStackedAction;
287 branchName: string;
288 includesCommit: boolean;
289}): DefaultBranchActionDialogCopy {
290 const branchLabel = input.branchName;
291 const suffix = ` on "${branchLabel}". You can continue on this branch or create a feature branch and run the same action there.`;
292
293 if (input.action === "commit") {
294 return {
295 title: "Commit to default branch?",
296 description: `This action will commit changes${suffix}`,
297 continueLabel: `Commit to ${branchLabel}`,
298 };
299 }
300
301 if (input.action === "commit_push") {
302 if (input.includesCommit) {
303 return {
304 title: "Commit & push to default branch?",
305 description: `This action will commit and push changes${suffix}`,
306 continueLabel: `Commit & push to ${branchLabel}`,
307 };
308 }
309 return {
310 title: "Push to default branch?",
311 description: `This action will push local commits${suffix}`,
312 continueLabel: `Push to ${branchLabel}`,
313 };
314 }
315
316 if (input.includesCommit) {
317 return {
318 title: "Commit, push & create PR from default branch?",
319 description: `This action will commit, push, and create a PR${suffix}`,
320 continueLabel: `Commit, push & create PR`,
321 };
322 }
323 return {
324 title: "Push & create PR from default branch?",
325 description: `This action will push local commits and create a PR${suffix}`,
326 continueLabel: "Push & create PR",
327 };
328}
329
279330function formatDateForBranch(now: Date): string {
280331 const year = now.getUTCFullYear();
281332 const month = String(now.getUTCMonth() + 1).padStart(2, "0");
apps/web/src/components/GitActionsControl.tsx+31-20
......@@ -11,6 +11,7 @@ import {
1111 type GitQuickAction,
1212 requiresDefaultBranchConfirmation,
1313 resolveAutoFeatureBranchName,
14 resolveDefaultBranchActionDialogCopy,
1415 resolveQuickAction,
1516 summarizeGitResult,
1617} from "./GitActionsControl.logic";
......@@ -50,6 +51,8 @@ interface GitActionsControlProps {
5051
5152interface PendingDefaultBranchAction {
5253 action: GitStackedAction;
54 branchName: string;
55 includesCommit: boolean;
5356 commitMessage?: string;
5457 forcePushOnlyProgress: boolean;
5558 onConfirmed?: () => void;
......@@ -115,12 +118,6 @@ const COMMIT_DIALOG_TITLE = "Commit changes";
115118const COMMIT_DIALOG_DESCRIPTION =
116119 "Review and confirm your commit. Leave the message blank to auto-generate one.";
117120
118const DEFAULT_BRANCH_ACTION_DESCRIPTION: Record<GitStackedAction, string> = {
119 commit: "commit changes",
120 commit_push: "commit and push changes",
121 commit_push_pr: "commit, push, and create a PR",
122};
123
124121function GitActionItemIcon({ icon }: { icon: GitActionIconName }) {
125122 if (icon === "commit") return <GitCommitIcon />;
126123 if (icon === "push") return <CloudUploadIcon />;
......@@ -202,6 +199,13 @@ export default function GitActionsControl({ gitCwd, activeThreadId }: GitActions
202199 const quickActionDisabledReason = quickAction.disabled
203200 ? (quickAction.hint ?? "This action is currently unavailable.")
204201 : null;
202 const pendingDefaultBranchActionCopy = pendingDefaultBranchAction
203 ? resolveDefaultBranchActionDialogCopy({
204 action: pendingDefaultBranchAction.action,
205 branchName: pendingDefaultBranchAction.branchName,
206 includesCommit: pendingDefaultBranchAction.includesCommit,
207 })
208 : null;
205209
206210 const openExistingPr = useCallback(async () => {
207211 const api = readNativeApi();
......@@ -253,6 +257,8 @@ export default function GitActionsControl({ gitCwd, activeThreadId }: GitActions
253257 const actionStatus = statusOverride ?? gitStatusForActions;
254258 const actionIsDefaultBranch = isDefaultBranchOverride ?? isDefaultBranch;
255259 const actionBranch = actionStatus?.branch ?? null;
260 const includesCommit =
261 !forcePushOnlyProgress && (action === "commit" || !!actionStatus?.hasWorkingTreeChanges);
256262 if (
257263 !skipDefaultBranchPrompt &&
258264 requiresDefaultBranchConfirmation(action, actionIsDefaultBranch) &&
......@@ -260,6 +266,8 @@ export default function GitActionsControl({ gitCwd, activeThreadId }: GitActions
260266 ) {
261267 setPendingDefaultBranchAction({
262268 action,
269 branchName: actionBranch,
270 includesCommit,
263271 ...(commitMessage ? { commitMessage } : {}),
264272 forcePushOnlyProgress,
265273 ...(onConfirmed ? { onConfirmed } : {}),
......@@ -398,10 +406,13 @@ export default function GitActionsControl({ gitCwd, activeThreadId }: GitActions
398406
399407 const continuePendingDefaultBranchAction = useCallback(() => {
400408 if (!pendingDefaultBranchAction) return;
401 const pendingAction = pendingDefaultBranchAction;
409 const { action, commitMessage, forcePushOnlyProgress, onConfirmed } = pendingDefaultBranchAction;
402410 setPendingDefaultBranchAction(null);
403411 void runGitActionWithToast({
404 ...pendingAction,
412 action,
413 ...(commitMessage ? { commitMessage } : {}),
414 forcePushOnlyProgress,
415 ...(onConfirmed ? { onConfirmed } : {}),
405416 skipDefaultBranchPrompt: true,
406417 });
407418 }, [pendingDefaultBranchAction, runGitActionWithToast]);
......@@ -456,9 +467,14 @@ export default function GitActionsControl({ gitCwd, activeThreadId }: GitActions
456467
457468 const checkoutFeatureBranchAndContinuePendingAction = useCallback(() => {
458469 if (!pendingDefaultBranchAction) return;
459 const pendingAction = pendingDefaultBranchAction;
470 const { action, commitMessage, forcePushOnlyProgress, onConfirmed } = pendingDefaultBranchAction;
460471 setPendingDefaultBranchAction(null);
461 checkoutNewBranchAndRunAction(pendingAction);
472 checkoutNewBranchAndRunAction({
473 action,
474 ...(commitMessage ? { commitMessage } : {}),
475 forcePushOnlyProgress,
476 ...(onConfirmed ? { onConfirmed } : {}),
477 });
462478 }, [pendingDefaultBranchAction, checkoutNewBranchAndRunAction]);
463479
464480 const runDialogActionOnNewBranch = useCallback(() => {
......@@ -801,22 +817,17 @@ export default function GitActionsControl({ gitCwd, activeThreadId }: GitActions
801817 >
802818 <DialogPopup>
803819 <DialogHeader>
804 <DialogTitle>Commit to default branch?</DialogTitle>
805 <DialogDescription>
806 This action will{" "}
807 {pendingDefaultBranchAction
808 ? DEFAULT_BRANCH_ACTION_DESCRIPTION[pendingDefaultBranchAction.action]
809 : "push changes"}{" "}
810 on "{gitStatusForActions?.branch ?? "default"}". You can continue on this branch or
811 create a feature branch and run the same action there.
812 </DialogDescription>
820 <DialogTitle>
821 {pendingDefaultBranchActionCopy?.title ?? "Run action on default branch?"}
822 </DialogTitle>
823 <DialogDescription>{pendingDefaultBranchActionCopy?.description}</DialogDescription>
813824 </DialogHeader>
814825 <DialogFooter>
815826 <Button variant="outline" size="sm" onClick={() => setPendingDefaultBranchAction(null)}>
816827 Abort
817828 </Button>
818829 <Button variant="outline" size="sm" onClick={continuePendingDefaultBranchAction}>
819 Commit to {gitStatusForActions?.branch ?? "default"}
830 {pendingDefaultBranchActionCopy?.continueLabel ?? "Continue"}
820831 </Button>
821832 <Button size="sm" onClick={checkoutFeatureBranchAndContinuePendingAction}>
822833 Checkout feature branch & continue