authorgravatar for julius0216@outlook.comJulius Marminge <julius0216@outlook.com> 2026-03-02 20:04:54-08:00
committergravatar for jmarminge@gmail.comJulius Marminge <jmarminge@gmail.com> 2026-03-02 20:46:05-08:00
logf11764c517732b1c02057f28ff1a10f03f8157da
treec75cf72a7089bf042f345cdf584dacef408b85d7
parent77c81b7468003e7b7d56fd4dd123c499fdf3e073

Fix PR CTAs and quick action after push

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

3 files changed, 53 insertions(+), 10 deletions(-)

apps/web/src/components/GitActionsControl.logic.test.ts+21-4
......@@ -250,15 +250,20 @@ describe("when: branch is clean, ahead, and has no open PR", () => {
250250});
251251
252252describe("when: branch is clean, up to date, and has no open PR", () => {
253 it("resolveQuickAction returns disabled no-action state", () => {
253 it("resolveQuickAction offers create PR", () => {
254254 const quick = resolveQuickAction(
255255 status({ aheadCount: 0, behindCount: 0, hasWorkingTreeChanges: false, pr: null }),
256256 false,
257257 );
258 assert.deepInclude(quick, { kind: "show_hint", label: "Commit", disabled: true });
258 assert.deepInclude(quick, {
259 kind: "run_action",
260 action: "commit_push_pr",
261 label: "Create PR",
262 disabled: false,
263 });
259264 });
260265
261 it("buildMenuItems disables commit, push, and create PR", () => {
266 it("buildMenuItems enables create PR and keeps commit/push disabled", () => {
262267 const items = buildMenuItems(status({ aheadCount: 0, behindCount: 0, pr: null }), false);
263268 assert.deepEqual(items, [
264269 {
......@@ -280,13 +285,25 @@ describe("when: branch is clean, up to date, and has no open PR", () => {
280285 {
281286 id: "pr",
282287 label: "Create PR",
283 disabled: true,
288 disabled: false,
284289 icon: "pr",
285290 kind: "open_dialog",
286291 dialogAction: "create_pr",
287292 },
288293 ]);
289294 });
295
296 it("buildMenuItems keeps create PR disabled on default branch", () => {
297 const items = buildMenuItems(status({ branch: "main", aheadCount: 0, behindCount: 0 }), false, true);
298 assert.deepEqual(items[2], {
299 id: "pr",
300 label: "Create PR",
301 disabled: true,
302 icon: "pr",
303 kind: "open_dialog",
304 dialogAction: "create_pr",
305 });
306 });
290307});
291308
292309describe("when: branch is behind upstream", () => {
apps/web/src/components/GitActionsControl.logic.ts+13-2
......@@ -113,6 +113,7 @@ export function summarizeGitResult(result: GitRunStackedActionResult): {
113113export function buildMenuItems(
114114 gitStatus: GitStatusResult | null,
115115 isBusy: boolean,
116 isDefaultBranch = false,
116117): GitActionMenuItem[] {
117118 if (!gitStatus) return [];
118119
......@@ -127,8 +128,9 @@ export function buildMenuItems(
127128 hasBranch &&
128129 !hasChanges &&
129130 !hasOpenPr &&
130 gitStatus.aheadCount > 0 &&
131 !isBehind;
131 !isBehind &&
132 !isDefaultBranch &&
133 (gitStatus.aheadCount > 0 || gitStatus.hasUpstream);
132134 const canOpenPr = !isBusy && hasOpenPr;
133135
134136 return [
......@@ -265,6 +267,15 @@ export function resolveQuickAction(
265267 };
266268 }
267269
270 if (!hasOpenPr && !isDefaultBranch && gitStatus.hasUpstream) {
271 return {
272 label: "Create PR",
273 disabled: false,
274 kind: "run_action",
275 action: "commit_push_pr",
276 };
277 }
278
268279 if (hasOpenPr && gitStatus.hasUpstream) {
269280 return { label: "Open PR", disabled: false, kind: "open_pr" };
270281 }
apps/web/src/components/GitActionsControl.tsx+19-4
......@@ -180,10 +180,19 @@ export default function GitActionsControl({ gitCwd, activeThreadId }: GitActions
180180 const current = branchList?.branches.find((branch) => branch.name === branchName);
181181 return current?.isDefault ?? (branchName === "main" || branchName === "master");
182182 }, [branchList?.branches, gitStatusForActions?.branch]);
183 const isDefaultBranchName = useCallback(
184 (branchName: string | null | undefined) => {
185 if (!branchName) return false;
186 const defaultBranchName = branchList?.branches.find((branch) => branch.isDefault)?.name;
187 if (defaultBranchName) return branchName === defaultBranchName;
188 return branchName === "main" || branchName === "master";
189 },
190 [branchList?.branches],
191 );
183192
184193 const gitActionMenuItems = useMemo(
185 () => buildMenuItems(gitStatusForActions, isGitActionRunning),
186 [gitStatusForActions, isGitActionRunning],
194 () => buildMenuItems(gitStatusForActions, isGitActionRunning, isDefaultBranch),
195 [gitStatusForActions, isGitActionRunning, isDefaultBranch],
187196 );
188197 const quickAction = useMemo(
189198 () => resolveQuickAction(gitStatusForActions, isGitActionRunning, isDefaultBranch),
......@@ -237,6 +246,7 @@ export default function GitActionsControl({ gitCwd, activeThreadId }: GitActions
237246 onConfirmed,
238247 skipDefaultBranchPrompt = false,
239248 featureBranch = false,
249 isDefaultBranchOverride,
240250 }: {
241251 action: GitStackedAction;
242252 commitMessage?: string;
......@@ -244,10 +254,12 @@ export default function GitActionsControl({ gitCwd, activeThreadId }: GitActions
244254 onConfirmed?: () => void;
245255 skipDefaultBranchPrompt?: boolean;
246256 featureBranch?: boolean;
257 isDefaultBranchOverride?: boolean;
247258 }) => {
248259 const actionStatus = gitStatusForActions;
249 const actionIsDefaultBranch = featureBranch ? false : isDefaultBranch;
250260 const actionBranch = actionStatus?.branch ?? null;
261 const actionIsDefaultBranch =
262 isDefaultBranchOverride ?? (featureBranch ? false : isDefaultBranchName(actionBranch));
251263 const includesCommit =
252264 !forcePushOnlyProgress && (action === "commit" || !!actionStatus?.hasWorkingTreeChanges);
253265 if (
......@@ -348,6 +360,7 @@ export default function GitActionsControl({ gitCwd, activeThreadId }: GitActions
348360 forcePushOnlyProgress: true,
349361 onConfirmed: closeResultToast,
350362 ...(featureBranch ? { skipDefaultBranchPrompt: true } : {}),
363 ...(featureBranch ? { isDefaultBranchOverride: false } : {}),
351364 });
352365 },
353366 },
......@@ -374,6 +387,7 @@ export default function GitActionsControl({ gitCwd, activeThreadId }: GitActions
374387 action: "commit_push_pr",
375388 forcePushOnlyProgress: true,
376389 ...(featureBranch ? { skipDefaultBranchPrompt: true } : {}),
390 ...(featureBranch ? { isDefaultBranchOverride: false } : {}),
377391 });
378392 },
379393 },
......@@ -392,7 +406,7 @@ export default function GitActionsControl({ gitCwd, activeThreadId }: GitActions
392406 },
393407
394408 [
395 isDefaultBranch,
409 isDefaultBranchName,
396410 runImmediateGitActionMutation,
397411 setPendingDefaultBranchAction,
398412 threadToastData,
......@@ -410,6 +424,7 @@ export default function GitActionsControl({ gitCwd, activeThreadId }: GitActions
410424 forcePushOnlyProgress,
411425 ...(onConfirmed ? { onConfirmed } : {}),
412426 skipDefaultBranchPrompt: true,
427 isDefaultBranchOverride: true,
413428 });
414429 }, [pendingDefaultBranchAction, runGitActionWithToast]);
415430