authorgravatar for julius0216@outlook.comJulius Marminge <julius0216@outlook.com> 2026-02-13 15:07:17-08:00
committergravatar for jmarminge@gmail.comJulius Marminge <jmarminge@gmail.com> 2026-02-13 15:26:36-08:00
log38de3417b8fe79ff97711631718d0a4929b079a7
treec67ffb4ffe300f75df7ae0155bfc0386af98ccf3
parent4fff00c8c3bed8864530d31617fb45e4788b9d83

Move git action progress to toast and stop auto-opening PRs

- Keep the git action modal in selection mode while actions run in a dismissible progress toast - Remove automatic `gh pr view --web` calls; only return PR metadata and open links on explicit user action - Update server/web tests to assert no implicit PR browser open behavior

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

apps/server/src/gitManager.test.ts+3-4
...@@ -343,7 +343,7 @@ describe("GitManager", () => {...@@ -343,7 +343,7 @@ describe("GitManager", () => {
343 expect(result.push.status).toBe("skipped_up_to_date");343 expect(result.push.status).toBe("skipped_up_to_date");
344 });344 });
345345
346 it("opens existing PR for commit/push/pr action", async () => {346 it("returns existing PR metadata for commit/push/pr action", async () => {
347 const repoDir = makeTempDir("t3code-git-manager-");347 const repoDir = makeTempDir("t3code-git-manager-");
348 await initRepo(repoDir);348 await initRepo(repoDir);
349 await runGit(repoDir, ["checkout", "-b", "feature/existing-pr"]);349 await runGit(repoDir, ["checkout", "-b", "feature/existing-pr"]);
...@@ -376,9 +376,7 @@ describe("GitManager", () => {...@@ -376,9 +376,7 @@ describe("GitManager", () => {
376376
377 expect(result.pr.status).toBe("opened_existing");377 expect(result.pr.status).toBe("opened_existing");
378 expect(result.pr.number).toBe(42);378 expect(result.pr.number).toBe(42);
379 expect(379 expect(ghCalls.some((call) => call.startsWith("pr view "))).toBe(false);
380 ghCalls.some((call) => call.startsWith("pr view feature/existing-pr --web")),
381 ).toBe(true);
382 });380 });
383381
384 it("creates PR when one does not already exist", async () => {382 it("creates PR when one does not already exist", async () => {
...@@ -422,6 +420,7 @@ describe("GitManager", () => {...@@ -422,6 +420,7 @@ describe("GitManager", () => {
422 expect(420 expect(
423 ghCalls.some((call) => call.includes("pr create --base main --head feature-create-pr")),421 ghCalls.some((call) => call.includes("pr create --base main --head feature-create-pr")),
424 ).toBe(true);422 ).toBe(true);
423 expect(ghCalls.some((call) => call.startsWith("pr view "))).toBe(false);
425 });424 });
426425
427 it("rejects push/pr actions from detached HEAD", async () => {426 it("rejects push/pr actions from detached HEAD", async () => {
apps/server/src/gitManager.ts-10
...@@ -325,7 +325,6 @@ export class GitManager {...@@ -325,7 +325,6 @@ export class GitManager {
325325
326 const existing = await this.findOpenPr(cwd, branch);326 const existing = await this.findOpenPr(cwd, branch);
327 if (existing) {327 if (existing) {
328 await this.openPrInBrowser(cwd, branch);
329 return {328 return {
330 status: "opened_existing",329 status: "opened_existing",
331 url: existing.url,330 url: existing.url,
...@@ -381,7 +380,6 @@ export class GitManager {...@@ -381,7 +380,6 @@ export class GitManager {
381 }380 }
382381
383 const created = await this.findOpenPr(cwd, branch);382 const created = await this.findOpenPr(cwd, branch);
384 await this.openPrInBrowser(cwd, branch);
385383
386 if (!created) {384 if (!created) {
387 return {385 return {
...@@ -402,14 +400,6 @@ export class GitManager {...@@ -402,14 +400,6 @@ export class GitManager {
402 };400 };
403 }401 }
404402
405 private async openPrInBrowser(cwd: string, branch: string): Promise<void> {
406 try {
407 await this.runGh(cwd, ["pr", "view", branch, "--web"]);
408 } catch {
409 // Opening the browser is best-effort.
410 }
411 }
412
413 private async findOpenPr(403 private async findOpenPr(
414 cwd: string,404 cwd: string,
415 branch: string,405 branch: string,
apps/web/src/components/GitActionsControl.tsx+182-116
...@@ -64,6 +64,22 @@ function GitActionIcon(props: { icon: GitActionIconName; disabled: boolean }) {...@@ -64,6 +64,22 @@ function GitActionIcon(props: { icon: GitActionIconName; disabled: boolean }) {
64 return <GitHubIcon className={`h-5 w-5 shrink-0 ${toneClass}`} />;64 return <GitHubIcon className={`h-5 w-5 shrink-0 ${toneClass}`} />;
65}65}
6666
67function GitProgressStatusIcon(props: { status: GitProgressStepStatus }) {
68 if (props.status === "running") {
69 return <Loader2Icon className="h-4 w-4 animate-spin text-foreground" />;
70 }
71 if (props.status === "completed") {
72 return <CheckIcon className="h-4 w-4 text-emerald-600 dark:text-emerald-300" />;
73 }
74 if (props.status === "skipped") {
75 return <MinusIcon className="h-4 w-4 text-muted-foreground/70" />;
76 }
77 if (props.status === "failed") {
78 return <XIcon className="h-4 w-4 text-rose-500 dark:text-rose-300" />;
79 }
80 return <CircleIcon className="h-4 w-4 text-muted-foreground/60" />;
81}
82
67function gitActionModalTitle(): string {83function gitActionModalTitle(): string {
68 return "Commit your changes";84 return "Commit your changes";
69}85}
...@@ -249,6 +265,7 @@ export default function GitActionsControl({ api, gitCwd }: GitActionsControlProp...@@ -249,6 +265,7 @@ export default function GitActionsControl({ api, gitCwd }: GitActionsControlProp
249 const [gitModalProgress, setGitModalProgress] = useState<GitProgressStep[]>([]);265 const [gitModalProgress, setGitModalProgress] = useState<GitProgressStep[]>([]);
250 const [gitModalError, setGitModalError] = useState<string | null>(null);266 const [gitModalError, setGitModalError] = useState<string | null>(null);
251 const [gitModalResult, setGitModalResult] = useState<GitRunStackedActionResult | null>(null);267 const [gitModalResult, setGitModalResult] = useState<GitRunStackedActionResult | null>(null);
268 const [isGitProgressToastVisible, setIsGitProgressToastVisible] = useState(false);
252 const gitMenuRef = useRef<HTMLDivElement>(null);269 const gitMenuRef = useRef<HTMLDivElement>(null);
253270
254 const { data: gitStatus = null, error: gitStatusError } = useQuery(271 const { data: gitStatus = null, error: gitStatusError } = useQuery(
...@@ -291,10 +308,13 @@ export default function GitActionsControl({ api, gitCwd }: GitActionsControlProp...@@ -291,10 +308,13 @@ export default function GitActionsControl({ api, gitCwd }: GitActionsControlProp
291 [gitModalActionOptions, gitModalSelectedAction],308 [gitModalActionOptions, gitModalSelectedAction],
292 );309 );
293 const visibleGitActionError = gitActionError ?? gitStatusError?.message;310 const visibleGitActionError = gitActionError ?? gitStatusError?.message;
294 const gitModalOpenPrUrl = gitModalResult?.pr.url ?? gitStatus?.openPr?.url ?? null;311 const gitProgressToastOpenPrUrl = gitModalResult?.pr.url ?? gitStatus?.openPr?.url ?? null;
295 const gitModalHasProgress = gitModalProgress.length > 0;312 const showGitProgressToast =
296 const gitModalSelectionMode = !gitModalHasProgress && gitModalResult === null;313 isGitProgressToastVisible &&
297 const gitModalSteps = gitModalProgress;314 (isGitModalActionRunning ||
315 gitModalProgress.length > 0 ||
316 gitModalError !== null ||
317 gitModalResult !== null);
298318
299 const refreshGitStatus = useCallback(async () => {319 const refreshGitStatus = useCallback(async () => {
300 if (!api || !gitCwd) return;320 if (!api || !gitCwd) return;
...@@ -307,6 +327,7 @@ export default function GitActionsControl({ api, gitCwd }: GitActionsControlProp...@@ -307,6 +327,7 @@ export default function GitActionsControl({ api, gitCwd }: GitActionsControlProp
307 setIsGitModalOpen(true);327 setIsGitModalOpen(true);
308 setGitModalSelectedAction("commit");328 setGitModalSelectedAction("commit");
309 setGitModalCommitMessage("");329 setGitModalCommitMessage("");
330 setIsGitProgressToastVisible(false);
310 setGitModalProgress([]);331 setGitModalProgress([]);
311 setGitModalError(null);332 setGitModalError(null);
312 setGitModalResult(null);333 setGitModalResult(null);
...@@ -318,9 +339,6 @@ export default function GitActionsControl({ api, gitCwd }: GitActionsControlProp...@@ -318,9 +339,6 @@ export default function GitActionsControl({ api, gitCwd }: GitActionsControlProp
318 setIsGitModalOpen(false);339 setIsGitModalOpen(false);
319 setGitModalSelectedAction("commit");340 setGitModalSelectedAction("commit");
320 setGitModalCommitMessage("");341 setGitModalCommitMessage("");
321 setGitModalProgress([]);
322 setGitModalError(null);
323 setGitModalResult(null);
324 }, [isGitActionRunning]);342 }, [isGitActionRunning]);
325343
326 const runGitActionImmediately = useCallback(344 const runGitActionImmediately = useCallback(
...@@ -349,28 +367,39 @@ export default function GitActionsControl({ api, gitCwd }: GitActionsControlProp...@@ -349,28 +367,39 @@ export default function GitActionsControl({ api, gitCwd }: GitActionsControlProp
349 });367 });
350 }, [api, gitStatus?.openPr?.url]);368 }, [api, gitStatus?.openPr?.url]);
351369
352 const openPrFromModal = useCallback(() => {370 const openPrFromToast = useCallback(() => {
353 if (!api) {371 if (!api) {
354 setGitModalError("Link opening is unavailable.");372 setGitModalError("Link opening is unavailable.");
355 return;373 return;
356 }374 }
357375
358 const prUrl = gitModalOpenPrUrl;376 const prUrl = gitProgressToastOpenPrUrl;
359 if (!prUrl) return;377 if (!prUrl) return;
360378
361 void api.shell.openExternal(prUrl).catch((error) => {379 void api.shell.openExternal(prUrl).catch((error) => {
362 setGitModalError(error instanceof Error ? error.message : "Unable to open PR link.");380 setGitModalError(error instanceof Error ? error.message : "Unable to open PR link.");
363 });381 });
364 }, [api, gitModalOpenPrUrl]);382 }, [api, gitProgressToastOpenPrUrl]);
383
384 const dismissGitProgressToast = useCallback(() => {
385 if (isGitModalActionRunning) return;
386 setIsGitProgressToastVisible(false);
387 setGitModalProgress([]);
388 setGitModalError(null);
389 setGitModalResult(null);
390 }, [isGitModalActionRunning]);
365391
366 const runGitAction = useCallback(async () => {392 const runGitAction = useCallback(async () => {
367 if (!api || !gitCwd || !isGitModalOpen) return;393 if (!api || !gitCwd) return;
368 if (!selectedGitModalActionOption || selectedGitModalActionOption.disabled) return;394 if (!selectedGitModalActionOption || selectedGitModalActionOption.disabled) return;
369 const actionCwd = gitCwd;395 const actionCwd = gitCwd;
370 const action = selectedGitModalActionOption.action;396 const action = selectedGitModalActionOption.action;
371 const commitMessage = gitModalCommitMessage.trim();397 const commitMessage = gitModalCommitMessage.trim();
372 const includeGeneratedCommitMessage = commitMessage.length === 0;398 const includeGeneratedCommitMessage = commitMessage.length === 0;
373399
400 setIsGitMenuOpen(false);
401 setIsGitModalOpen(false);
402 setIsGitProgressToastVisible(true);
374 setIsGitModalActionRunning(true);403 setIsGitModalActionRunning(true);
375 setGitModalError(null);404 setGitModalError(null);
376 setGitActionError(null);405 setGitActionError(null);
...@@ -488,7 +517,6 @@ export default function GitActionsControl({ api, gitCwd }: GitActionsControlProp...@@ -488,7 +517,6 @@ export default function GitActionsControl({ api, gitCwd }: GitActionsControlProp
488 api,517 api,
489 gitCwd,518 gitCwd,
490 gitModalCommitMessage,519 gitModalCommitMessage,
491 isGitModalOpen,
492 refreshGitStatus,520 refreshGitStatus,
493 selectedGitModalActionOption,521 selectedGitModalActionOption,
494 ]);522 ]);
...@@ -497,6 +525,7 @@ export default function GitActionsControl({ api, gitCwd }: GitActionsControlProp...@@ -497,6 +525,7 @@ export default function GitActionsControl({ api, gitCwd }: GitActionsControlProp
497 setGitActionError(null);525 setGitActionError(null);
498 setGitModalError(null);526 setGitModalError(null);
499 setIsGitModalOpen(false);527 setIsGitModalOpen(false);
528 setIsGitProgressToastVisible(false);
500 setGitModalSelectedAction("commit");529 setGitModalSelectedAction("commit");
501 setGitModalCommitMessage("");530 setGitModalCommitMessage("");
502 setGitModalProgress([]);531 setGitModalProgress([]);
...@@ -690,92 +719,57 @@ export default function GitActionsControl({ api, gitCwd }: GitActionsControlProp...@@ -690,92 +719,57 @@ export default function GitActionsControl({ api, gitCwd }: GitActionsControlProp
690 placeholder="Leave blank to autogenerate a commit message"719 placeholder="Leave blank to autogenerate a commit message"
691 value={gitModalCommitMessage}720 value={gitModalCommitMessage}
692 onChange={(event) => setGitModalCommitMessage(event.target.value)}721 onChange={(event) => setGitModalCommitMessage(event.target.value)}
693 disabled={!gitModalSelectionMode || isGitActionRunning}722 disabled={isGitActionRunning}
694 />723 />
695 </div>724 </div>
696 <div className="mt-6">725 <div className="mt-6">
697 <p className="text-sm font-medium text-foreground">Next steps</p>726 <p className="text-sm font-medium text-foreground">Next steps</p>
698 <div className="mt-2 overflow-hidden rounded-2xl border border-border">727 <div className="mt-2 overflow-hidden rounded-2xl border border-border">
699 {gitModalSelectionMode &&728 {gitModalActionOptions.map((option, index) => {
700 gitModalActionOptions.map((option, index) => {729 const borderClass =
701 const borderClass =730 index < gitModalActionOptions.length - 1 ? "border-b border-border/70" : "";
702 index < gitModalActionOptions.length - 1 ? "border-b border-border/70" : "";731 const selected = option.action === gitModalSelectedAction && !option.disabled;
703 const selected = option.action === gitModalSelectedAction && !option.disabled;732 const disabled = option.disabled || isGitActionRunning;
704 const disabled = option.disabled || isGitActionRunning;733
705734 return (
706 return (735 <button
707 <button736 key={option.action}
708 key={option.action}737 type="button"
709 type="button"738 className={`flex w-full items-start gap-3 px-4 py-3 text-left transition-colors duration-150 ${borderClass} ${
710 className={`flex w-full items-start gap-3 px-4 py-3 text-left transition-colors duration-150 ${borderClass} ${739 selected
711 selected740 ? "bg-accent/55"
712 ? "bg-accent/55"741 : disabled
713 : disabled742 ? "bg-card/25"
714 ? "bg-card/25"743 : "bg-card/45 hover:bg-accent/35"
715 : "bg-card/45 hover:bg-accent/35"744 } disabled:cursor-not-allowed`}
716 } disabled:cursor-not-allowed`}745 disabled={disabled}
717 disabled={disabled}746 onClick={() => {
718 onClick={() => {747 setGitModalSelectedAction(option.action);
719 setGitModalSelectedAction(option.action);748 }}
720 }}749 >
721 >750 <span className="mt-0.5">
722 <span className="mt-0.5">751 <GitActionIcon icon={option.icon} disabled={option.disabled} />
723 <GitActionIcon icon={option.icon} disabled={option.disabled} />752 </span>
724 </span>753 <div className="min-w-0 flex-1">
725 <div className="min-w-0 flex-1">754 <p
755 className={`text-sm ${disabled ? "text-muted-foreground/60" : "text-foreground"}`}
756 >
757 {option.label}
758 </p>
759 {option.detail && (
726 <p760 <p
727 className={`text-sm ${disabled ? "text-muted-foreground/60" : "text-foreground"}`}761 className={`mt-0.5 text-xs ${disabled ? "text-muted-foreground/55" : "text-muted-foreground/70"}`}
728 >762 >
729 {option.label}763 {option.detail}
730 </p>764 </p>
731 {option.detail && (765 )}
732 <p
733 className={`mt-0.5 text-xs ${disabled ? "text-muted-foreground/55" : "text-muted-foreground/70"}`}
734 >
735 {option.detail}
736 </p>
737 )}
738 </div>
739 {selected && !disabled ? (
740 <CheckIcon className="mt-0.5 h-4 w-4 shrink-0 text-foreground" />
741 ) : null}
742 </button>
743 );
744 })}
745 {!gitModalSelectionMode &&
746 gitModalSteps.map((step, index) => {
747 const borderClass =
748 index < gitModalSteps.length - 1 ? "border-b border-border/70" : "";
749
750 return (
751 <div
752 key={step.id}
753 className={`flex items-start gap-3 bg-card/45 px-4 py-3 ${borderClass}`}
754 >
755 <span className="mt-0.5">
756 {step.status === "running" ? (
757 <Loader2Icon className="h-4 w-4 animate-spin text-foreground" />
758 ) : step.status === "completed" ? (
759 <CheckIcon className="h-4 w-4 text-emerald-600 dark:text-emerald-300" />
760 ) : step.status === "skipped" ? (
761 <MinusIcon className="h-4 w-4 text-muted-foreground/70" />
762 ) : step.status === "failed" ? (
763 <XIcon className="h-4 w-4 text-rose-500 dark:text-rose-300" />
764 ) : (
765 <CircleIcon className="h-4 w-4 text-muted-foreground/60" />
766 )}
767 </span>
768 <div className="min-w-0">
769 <p className="text-sm text-foreground">{step.label}</p>
770 {step.status === "failed" && step.detail && (
771 <p className="mt-0.5 text-xs text-rose-500 dark:text-rose-300">
772 {step.detail}
773 </p>
774 )}
775 </div>
776 </div>766 </div>
777 );767 {selected && !disabled ? (
778 })}768 <CheckIcon className="mt-0.5 h-4 w-4 shrink-0 text-foreground" />
769 ) : null}
770 </button>
771 );
772 })}
779 </div>773 </div>
780 </div>774 </div>
781 {(gitModalError ?? visibleGitActionError) && (775 {(gitModalError ?? visibleGitActionError) && (
...@@ -784,44 +778,116 @@ export default function GitActionsControl({ api, gitCwd }: GitActionsControlProp...@@ -784,44 +778,116 @@ export default function GitActionsControl({ api, gitCwd }: GitActionsControlProp
784 </div>778 </div>
785 )}779 )}
786 <div className="mt-6 flex justify-end gap-2">780 <div className="mt-6 flex justify-end gap-2">
787 {gitModalResult && gitModalOpenPrUrl && (
788 <button
789 type="button"
790 className="rounded-xl border border-border px-4 py-2 text-sm text-foreground transition-colors duration-150 hover:bg-accent disabled:cursor-not-allowed disabled:opacity-60"
791 onClick={openPrFromModal}
792 disabled={isGitActionRunning}
793 >
794 Open PR
795 </button>
796 )}
797 <button781 <button
798 type="button"782 type="button"
799 className={783 className="rounded-xl border border-border px-4 py-2 text-sm text-foreground transition-colors duration-150 hover:bg-accent disabled:cursor-not-allowed disabled:opacity-60"
800 gitModalResult
801 ? "rounded-xl bg-foreground px-4 py-2 text-sm font-medium text-background transition-colors duration-150 hover:bg-foreground/90 disabled:cursor-not-allowed disabled:opacity-60"
802 : "rounded-xl border border-border px-4 py-2 text-sm text-foreground transition-colors duration-150 hover:bg-accent disabled:cursor-not-allowed disabled:opacity-60"
803 }
804 onClick={closeGitActionModal}784 onClick={closeGitActionModal}
805 disabled={isGitActionRunning}785 disabled={isGitActionRunning}
806 >786 >
807 {gitModalResult ? "Done" : "Cancel"}787 Cancel
788 </button>
789 <button
790 type="button"
791 className="rounded-xl bg-foreground px-4 py-2 text-sm font-medium text-background transition-colors duration-150 hover:bg-foreground/90 disabled:cursor-not-allowed disabled:opacity-60"
792 onClick={() => {
793 void runGitAction();
794 }}
795 disabled={isGitActionRunning || !selectedGitModalActionOption}
796 >
797 {runActionLabel(gitModalSelectedAction)}
798 </button>
799 </div>
800 </div>
801 </div>
802 )}
803
804 {showGitProgressToast && (
805 <div
806 className="fixed bottom-4 right-4 z-[90] w-[min(420px,calc(100vw-2rem))]"
807 role="status"
808 aria-live="polite"
809 >
810 <div className="rounded-2xl border border-border bg-popover/95 p-4 shadow-2xl backdrop-blur">
811 <div className="flex items-start justify-between gap-3">
812 <div className="min-w-0">
813 <p className="text-[11px] uppercase tracking-[0.14em] text-muted-foreground/65">
814 Git actions
815 </p>
816 <p className="mt-1 text-sm font-medium text-foreground">
817 {isGitModalActionRunning
818 ? "Running action..."
819 : gitModalError
820 ? "Action failed"
821 : "Action complete"}
822 </p>
823 </div>
824 <button
825 type="button"
826 className="rounded-md p-1 text-muted-foreground/60 transition-colors duration-150 hover:bg-accent hover:text-foreground disabled:cursor-not-allowed disabled:opacity-50"
827 onClick={dismissGitProgressToast}
828 disabled={isGitModalActionRunning}
829 aria-label="Dismiss git action progress toast"
830 >
831 <XIcon className="h-4 w-4" />
808 </button>832 </button>
809 {!gitModalResult && (833 </div>
834 {gitModalProgress.length > 0 && (
835 <div className="mt-3 overflow-hidden rounded-xl border border-border/80">
836 {gitModalProgress.map((step, index) => {
837 const borderClass =
838 index < gitModalProgress.length - 1 ? "border-b border-border/70" : "";
839
840 return (
841 <div
842 key={step.id}
843 className={`flex items-start gap-3 bg-card/45 px-3.5 py-2.5 ${borderClass}`}
844 >
845 <span className="mt-0.5">
846 <GitProgressStatusIcon status={step.status} />
847 </span>
848 <div className="min-w-0">
849 <p className="text-sm text-foreground">{step.label}</p>
850 {step.detail && (
851 <p
852 className={`mt-0.5 text-xs ${
853 step.status === "failed"
854 ? "text-rose-500 dark:text-rose-300"
855 : "text-muted-foreground/70"
856 }`}
857 >
858 {step.detail}
859 </p>
860 )}
861 </div>
862 </div>
863 );
864 })}
865 </div>
866 )}
867 {gitModalError && (
868 <div className="mt-3 rounded-lg border border-rose-300/50 bg-rose-500/10 px-3 py-2 text-xs text-rose-600 dark:text-rose-200">
869 {gitModalError}
870 </div>
871 )}
872 <div className="mt-3 flex justify-end gap-2">
873 {!isGitModalActionRunning && gitModalResult && gitProgressToastOpenPrUrl && (
810 <button874 <button
811 type="button"875 type="button"
812 className="rounded-xl bg-foreground px-4 py-2 text-sm font-medium text-background transition-colors duration-150 hover:bg-foreground/90 disabled:cursor-not-allowed disabled:opacity-60"876 className="rounded-xl border border-border px-4 py-2 text-sm text-foreground transition-colors duration-150 hover:bg-accent disabled:cursor-not-allowed disabled:opacity-60"
813 onClick={() => {877 onClick={openPrFromToast}
814 void runGitAction();878 disabled={isGitModalActionRunning}
815 }}
816 disabled={isGitActionRunning || !selectedGitModalActionOption}
817 >879 >
818 {isGitActionRunning880 Open PR
819 ? "Running..."
820 : gitModalSelectionMode
821 ? "Continue"
822 : runActionLabel(gitModalSelectedAction)}
823 </button>881 </button>
824 )}882 )}
883 <button
884 type="button"
885 className="rounded-xl bg-foreground px-4 py-2 text-sm font-medium text-background transition-colors duration-150 hover:bg-foreground/90 disabled:cursor-not-allowed disabled:opacity-60"
886 onClick={dismissGitProgressToast}
887 disabled={isGitModalActionRunning}
888 >
889 Done
890 </button>
825 </div>891 </div>
826 </div>892 </div>
827 </div>893 </div>