authorgravatar for julius0216@outlook.comJulius Marminge <julius0216@outlook.com> 2026-03-02 20:17:47-08:00
committergravatar for jmarminge@gmail.comJulius Marminge <jmarminge@gmail.com> 2026-03-02 20:46:05-08:00
logebe44d03a5fa6c34fdae2a67d53ef2ed6ba32737
treed97a56df23267689fba8634e231f913b06a3bfac
parentdcd2d50768363e61fdf556e81abada8aa54996de

Revert git action button logic toward main behavior

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

3 files changed, 46 insertions(+), 69 deletions(-)

apps/web/src/components/GitActionsControl.logic.test.ts+7-24
......@@ -250,20 +250,15 @@ 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 offers create PR", () => {
253 it("resolveQuickAction returns disabled no-action state", () => {
254254 const quick = resolveQuickAction(
255255 status({ aheadCount: 0, behindCount: 0, hasWorkingTreeChanges: false, pr: null }),
256256 false,
257257 );
258 assert.deepInclude(quick, {
259 kind: "run_action",
260 action: "commit_push_pr",
261 label: "Create PR",
262 disabled: false,
263 });
258 assert.deepInclude(quick, { kind: "show_hint", label: "Commit", disabled: true });
264259 });
265260
266 it("buildMenuItems enables create PR and keeps commit/push disabled", () => {
261 it("buildMenuItems disables commit, push, and create PR", () => {
267262 const items = buildMenuItems(status({ aheadCount: 0, behindCount: 0, pr: null }), false);
268263 assert.deepEqual(items, [
269264 {
......@@ -285,25 +280,13 @@ describe("when: branch is clean, up to date, and has no open PR", () => {
285280 {
286281 id: "pr",
287282 label: "Create PR",
288 disabled: false,
283 disabled: true,
289284 icon: "pr",
290285 kind: "open_dialog",
291286 dialogAction: "create_pr",
292287 },
293288 ]);
294289 });
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 });
307290});
308291
309292describe("when: branch is behind upstream", () => {
......@@ -365,7 +348,7 @@ describe("when: working tree has local changes", () => {
365348 });
366349 });
367350
368 it("resolveQuickAction keeps full commit/push/pr flow on non-default branches even when PR exists", () => {
351 it("resolveQuickAction returns commit and push when open PR exists", () => {
369352 const quick = resolveQuickAction(
370353 status({
371354 hasWorkingTreeChanges: true,
......@@ -382,8 +365,8 @@ describe("when: working tree has local changes", () => {
382365 );
383366 assert.deepInclude(quick, {
384367 kind: "run_action",
385 action: "commit_push_pr",
386 label: "Commit, push & create PR",
368 action: "commit_push",
369 label: "Commit & push",
387370 });
388371 });
389372
apps/web/src/components/GitActionsControl.logic.ts+3-14
......@@ -113,7 +113,6 @@ export function summarizeGitResult(result: GitRunStackedActionResult): {
113113export function buildMenuItems(
114114 gitStatus: GitStatusResult | null,
115115 isBusy: boolean,
116 isDefaultBranch = false,
117116): GitActionMenuItem[] {
118117 if (!gitStatus) return [];
119118
......@@ -128,9 +127,8 @@ export function buildMenuItems(
128127 hasBranch &&
129128 !hasChanges &&
130129 !hasOpenPr &&
131 !isBehind &&
132 !isDefaultBranch &&
133 (gitStatus.aheadCount > 0 || gitStatus.hasUpstream);
130 gitStatus.aheadCount > 0 &&
131 !isBehind;
134132 const canOpenPr = !isBusy && hasOpenPr;
135133
136134 return [
......@@ -204,7 +202,7 @@ export function resolveQuickAction(
204202 }
205203
206204 if (hasChanges) {
207 if (isDefaultBranch) {
205 if (hasOpenPr || isDefaultBranch) {
208206 return { label: "Commit & push", disabled: false, kind: "run_action", action: "commit_push" };
209207 }
210208 return {
......@@ -267,15 +265,6 @@ export function resolveQuickAction(
267265 };
268266 }
269267
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
279268 if (hasOpenPr && gitStatus.hasUpstream) {
280269 return { label: "Open PR", disabled: false, kind: "open_pr" };
281270 }
apps/web/src/components/GitActionsControl.tsx+36-31
......@@ -57,6 +57,8 @@ interface PendingDefaultBranchAction {
5757 onConfirmed?: () => void;
5858}
5959
60type GitActionToastId = ReturnType<typeof toastManager.add>;
61
6062function getMenuActionDisabledReason(
6163 item: GitActionMenuItem,
6264 gitStatus: GitStatusResult | null,
......@@ -180,19 +182,10 @@ export default function GitActionsControl({ gitCwd, activeThreadId }: GitActions
180182 const current = branchList?.branches.find((branch) => branch.name === branchName);
181183 return current?.isDefault ?? (branchName === "main" || branchName === "master");
182184 }, [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 );
192185
193186 const gitActionMenuItems = useMemo(
194 () => buildMenuItems(gitStatusForActions, isGitActionRunning, isDefaultBranch),
195 [gitStatusForActions, isGitActionRunning, isDefaultBranch],
187 () => buildMenuItems(gitStatusForActions, isGitActionRunning),
188 [gitStatusForActions, isGitActionRunning],
196189 );
197190 const quickAction = useMemo(
198191 () => resolveQuickAction(gitStatusForActions, isGitActionRunning, isDefaultBranch),
......@@ -245,26 +238,28 @@ export default function GitActionsControl({ gitCwd, activeThreadId }: GitActions
245238 forcePushOnlyProgress = false,
246239 onConfirmed,
247240 skipDefaultBranchPrompt = false,
241 statusOverride,
248242 featureBranch = false,
249243 isDefaultBranchOverride,
244 progressToastId,
250245 }: {
251246 action: GitStackedAction;
252247 commitMessage?: string;
253248 forcePushOnlyProgress?: boolean;
254249 onConfirmed?: () => void;
255250 skipDefaultBranchPrompt?: boolean;
251 statusOverride?: GitStatusResult | null;
256252 featureBranch?: boolean;
257253 isDefaultBranchOverride?: boolean;
254 progressToastId?: GitActionToastId;
258255 }) => {
259 const actionStatus = gitStatusForActions;
256 const actionStatus = statusOverride ?? gitStatusForActions;
260257 const actionBranch = actionStatus?.branch ?? null;
261 const actionIsDefaultBranch =
262 isDefaultBranchOverride ?? (featureBranch ? false : isDefaultBranchName(actionBranch));
258 const actionIsDefaultBranch = isDefaultBranchOverride ?? (featureBranch ? false : isDefaultBranch);
263259 const includesCommit =
264260 !forcePushOnlyProgress && (action === "commit" || !!actionStatus?.hasWorkingTreeChanges);
265261 if (
266262 !skipDefaultBranchPrompt &&
267 !featureBranch &&
268263 requiresDefaultBranchConfirmation(action, actionIsDefaultBranch) &&
269264 actionBranch
270265 ) {
......@@ -292,17 +287,28 @@ export default function GitActionsControl({ gitCwd, activeThreadId }: GitActions
292287 featureBranch,
293288 ...(pushTarget ? { pushTarget } : {}),
294289 });
295 const progressToastId = toastManager.add({
296 type: "loading",
297 title: progressStages[0] ?? "Running git action...",
298 timeout: 0,
299 data: threadToastData,
300 });
290 const resolvedProgressToastId =
291 progressToastId ??
292 toastManager.add({
293 type: "loading",
294 title: progressStages[0] ?? "Running git action...",
295 timeout: 0,
296 data: threadToastData,
297 });
298
299 if (progressToastId) {
300 toastManager.update(progressToastId, {
301 type: "loading",
302 title: progressStages[0] ?? "Running git action...",
303 timeout: 0,
304 data: threadToastData,
305 });
306 }
301307
302308 let stageIndex = 0;
303309 const stageInterval = setInterval(() => {
304310 stageIndex = Math.min(stageIndex + 1, progressStages.length - 1);
305 toastManager.update(progressToastId, {
311 toastManager.update(resolvedProgressToastId, {
306312 title: progressStages[stageIndex] ?? "Running git action...",
307313 type: "loading",
308314 timeout: 0,
......@@ -338,10 +344,10 @@ export default function GitActionsControl({ gitCwd, activeThreadId }: GitActions
338344 result.push.status === "pushed" &&
339345 !actionIsDefaultBranch;
340346 const closeResultToast = () => {
341 toastManager.close(progressToastId);
347 toastManager.close(resolvedProgressToastId);
342348 };
343349
344 toastManager.update(progressToastId, {
350 toastManager.update(resolvedProgressToastId, {
345351 type: "success",
346352 title: resultToast.title,
347353 description: resultToast.description,
......@@ -359,8 +365,8 @@ export default function GitActionsControl({ gitCwd, activeThreadId }: GitActions
359365 action: "commit_push",
360366 forcePushOnlyProgress: true,
361367 onConfirmed: closeResultToast,
362 ...(featureBranch ? { skipDefaultBranchPrompt: true } : {}),
363 ...(featureBranch ? { isDefaultBranchOverride: false } : {}),
368 statusOverride: actionStatus,
369 isDefaultBranchOverride: actionIsDefaultBranch,
364370 });
365371 },
366372 },
......@@ -386,8 +392,8 @@ export default function GitActionsControl({ gitCwd, activeThreadId }: GitActions
386392 void runGitActionWithToast({
387393 action: "commit_push_pr",
388394 forcePushOnlyProgress: true,
389 ...(featureBranch ? { skipDefaultBranchPrompt: true } : {}),
390 ...(featureBranch ? { isDefaultBranchOverride: false } : {}),
395 statusOverride: actionStatus,
396 isDefaultBranchOverride: actionIsDefaultBranch,
391397 });
392398 },
393399 },
......@@ -396,7 +402,7 @@ export default function GitActionsControl({ gitCwd, activeThreadId }: GitActions
396402 });
397403 } catch (err) {
398404 stopProgressUpdates();
399 toastManager.update(progressToastId, {
405 toastManager.update(resolvedProgressToastId, {
400406 type: "error",
401407 title: "Action failed",
402408 description: err instanceof Error ? err.message : "An error occurred.",
......@@ -406,7 +412,7 @@ export default function GitActionsControl({ gitCwd, activeThreadId }: GitActions
406412 },
407413
408414 [
409 isDefaultBranchName,
415 isDefaultBranch,
410416 runImmediateGitActionMutation,
411417 setPendingDefaultBranchAction,
412418 threadToastData,
......@@ -424,7 +430,6 @@ export default function GitActionsControl({ gitCwd, activeThreadId }: GitActions
424430 forcePushOnlyProgress,
425431 ...(onConfirmed ? { onConfirmed } : {}),
426432 skipDefaultBranchPrompt: true,
427 isDefaultBranchOverride: true,
428433 });
429434 }, [pendingDefaultBranchAction, runGitActionWithToast]);
430435