| ... | ... | @@ -38,7 +38,7 @@ import { Menu, MenuItem, MenuPopup, MenuTrigger } from "~/components/ui/menu"; |
| 38 | 38 | import { Popover, PopoverPopup, PopoverTrigger } from "~/components/ui/popover"; |
| 39 | 39 | import { ScrollArea } from "~/components/ui/scroll-area"; |
| 40 | 40 | import { Textarea } from "~/components/ui/textarea"; |
| 41 | | import { toastManager } from "~/components/ui/toast"; |
| 41 | import { toastManager, type ThreadToastData } from "~/components/ui/toast"; |
| 42 | 42 | import { openInPreferredEditor } from "~/editorPreferences"; |
| 43 | 43 | import { |
| 44 | 44 | gitBranchesQueryOptions, |
| ... | ... | @@ -73,6 +73,7 @@ type GitActionToastId = ReturnType<typeof toastManager.add>; |
| 73 | 73 | |
| 74 | 74 | interface ActiveGitActionProgress { |
| 75 | 75 | toastId: GitActionToastId; |
| 76 | toastData: ThreadToastData | undefined; |
| 76 | 77 | actionId: string; |
| 77 | 78 | title: string; |
| 78 | 79 | phaseStartedAtMs: number | null; |
| ... | ... | @@ -223,6 +224,7 @@ export default function GitActionsControl({ gitCwd, activeThreadId }: GitActions |
| 223 | 224 | const [pendingDefaultBranchAction, setPendingDefaultBranchAction] = |
| 224 | 225 | useState<PendingDefaultBranchAction | null>(null); |
| 225 | 226 | const activeGitActionProgressRef = useRef<ActiveGitActionProgress | null>(null); |
| 227 | let runGitActionWithToast: (input: RunGitActionWithToastInput) => Promise<void>; |
| 226 | 228 | |
| 227 | 229 | const updateActiveProgressToast = useCallback(() => { |
| 228 | 230 | const progress = activeGitActionProgressRef.current; |
| ... | ... | @@ -234,9 +236,9 @@ export default function GitActionsControl({ gitCwd, activeThreadId }: GitActions |
| 234 | 236 | title: progress.title, |
| 235 | 237 | description: resolveProgressDescription(progress), |
| 236 | 238 | timeout: 0, |
| 237 | | data: threadToastData, |
| 239 | data: progress.toastData, |
| 238 | 240 | }); |
| 239 | | }, [threadToastData]); |
| 241 | }, []); |
| 240 | 242 | |
| 241 | 243 | const persistThreadBranchSync = useCallback( |
| 242 | 244 | (branch: string | null) => { |
| ... | ... | @@ -402,7 +404,7 @@ export default function GitActionsControl({ gitCwd, activeThreadId }: GitActions |
| 402 | 404 | }); |
| 403 | 405 | }, [gitStatusForActions, threadToastData]); |
| 404 | 406 | |
| 405 | | const runGitActionWithToast = useEffectEvent( |
| 407 | runGitActionWithToast = useEffectEvent( |
| 406 | 408 | async ({ |
| 407 | 409 | action, |
| 408 | 410 | commitMessage, |
| ... | ... | @@ -455,6 +457,7 @@ export default function GitActionsControl({ gitCwd, activeThreadId }: GitActions |
| 455 | 457 | action === "create_pr" && |
| 456 | 458 | (!actionStatus?.hasUpstream || (actionStatus?.aheadCount ?? 0) > 0), |
| 457 | 459 | }); |
| 460 | const scopedToastData = threadToastData ? { ...threadToastData } : undefined; |
| 458 | 461 | const actionId = randomUUID(); |
| 459 | 462 | const resolvedProgressToastId = |
| 460 | 463 | progressToastId ?? |
| ... | ... | @@ -463,11 +466,12 @@ export default function GitActionsControl({ gitCwd, activeThreadId }: GitActions |
| 463 | 466 | title: progressStages[0] ?? "Running git action...", |
| 464 | 467 | description: "Waiting for Git...", |
| 465 | 468 | timeout: 0, |
| 466 | | data: threadToastData, |
| 469 | data: scopedToastData, |
| 467 | 470 | }); |
| 468 | 471 | |
| 469 | 472 | activeGitActionProgressRef.current = { |
| 470 | 473 | toastId: resolvedProgressToastId, |
| 474 | toastData: scopedToastData, |
| 471 | 475 | actionId, |
| 472 | 476 | title: progressStages[0] ?? "Running git action...", |
| 473 | 477 | phaseStartedAtMs: null, |
| ... | ... | @@ -483,7 +487,7 @@ export default function GitActionsControl({ gitCwd, activeThreadId }: GitActions |
| 483 | 487 | title: progressStages[0] ?? "Running git action...", |
| 484 | 488 | description: "Waiting for Git...", |
| 485 | 489 | timeout: 0, |
| 486 | | data: threadToastData, |
| 490 | data: scopedToastData, |
| 487 | 491 | }); |
| 488 | 492 | } |
| 489 | 493 | |
| ... | ... | @@ -587,30 +591,38 @@ export default function GitActionsControl({ gitCwd, activeThreadId }: GitActions |
| 587 | 591 | }; |
| 588 | 592 | } |
| 589 | 593 | |
| 590 | | toastManager.update(resolvedProgressToastId, { |
| 594 | const successToastBase = { |
| 591 | 595 | type: "success", |
| 592 | 596 | title: result.toast.title, |
| 593 | 597 | description: result.toast.description, |
| 594 | 598 | timeout: 0, |
| 595 | 599 | data: { |
| 596 | | ...threadToastData, |
| 600 | ...scopedToastData, |
| 597 | 601 | dismissAfterVisibleMs: 10_000, |
| 598 | 602 | }, |
| 599 | | ...(toastActionProps ? { actionProps: toastActionProps } : {}), |
| 600 | | }); |
| 603 | } as const; |
| 604 | |
| 605 | if (toastActionProps) { |
| 606 | toastManager.update(resolvedProgressToastId, { |
| 607 | ...successToastBase, |
| 608 | actionProps: toastActionProps, |
| 609 | }); |
| 610 | } else { |
| 611 | toastManager.update(resolvedProgressToastId, successToastBase); |
| 612 | } |
| 601 | 613 | } catch (err) { |
| 602 | 614 | activeGitActionProgressRef.current = null; |
| 603 | 615 | toastManager.update(resolvedProgressToastId, { |
| 604 | 616 | type: "error", |
| 605 | 617 | title: "Action failed", |
| 606 | 618 | description: err instanceof Error ? err.message : "An error occurred.", |
| 607 | | data: threadToastData, |
| 619 | data: scopedToastData, |
| 608 | 620 | }); |
| 609 | 621 | } |
| 610 | 622 | }, |
| 611 | 623 | ); |
| 612 | 624 | |
| 613 | | const continuePendingDefaultBranchAction = useCallback(() => { |
| 625 | const continuePendingDefaultBranchAction = () => { |
| 614 | 626 | if (!pendingDefaultBranchAction) return; |
| 615 | 627 | const { action, commitMessage, onConfirmed, filePaths } = pendingDefaultBranchAction; |
| 616 | 628 | setPendingDefaultBranchAction(null); |
| ... | ... | @@ -621,9 +633,9 @@ export default function GitActionsControl({ gitCwd, activeThreadId }: GitActions |
| 621 | 633 | ...(filePaths ? { filePaths } : {}), |
| 622 | 634 | skipDefaultBranchPrompt: true, |
| 623 | 635 | }); |
| 624 | | }, [pendingDefaultBranchAction]); |
| 636 | }; |
| 625 | 637 | |
| 626 | | const checkoutFeatureBranchAndContinuePendingAction = useCallback(() => { |
| 638 | const checkoutFeatureBranchAndContinuePendingAction = () => { |
| 627 | 639 | if (!pendingDefaultBranchAction) return; |
| 628 | 640 | const { action, commitMessage, onConfirmed, filePaths } = pendingDefaultBranchAction; |
| 629 | 641 | setPendingDefaultBranchAction(null); |
| ... | ... | @@ -635,9 +647,9 @@ export default function GitActionsControl({ gitCwd, activeThreadId }: GitActions |
| 635 | 647 | featureBranch: true, |
| 636 | 648 | skipDefaultBranchPrompt: true, |
| 637 | 649 | }); |
| 638 | | }, [pendingDefaultBranchAction]); |
| 650 | }; |
| 639 | 651 | |
| 640 | | const runDialogActionOnNewBranch = useCallback(() => { |
| 652 | const runDialogActionOnNewBranch = () => { |
| 641 | 653 | if (!isCommitDialogOpen) return; |
| 642 | 654 | const commitMessage = dialogCommitMessage.trim(); |
| 643 | 655 | |
| ... | ... | @@ -653,9 +665,9 @@ export default function GitActionsControl({ gitCwd, activeThreadId }: GitActions |
| 653 | 665 | featureBranch: true, |
| 654 | 666 | skipDefaultBranchPrompt: true, |
| 655 | 667 | }); |
| 656 | | }, [allSelected, isCommitDialogOpen, dialogCommitMessage, selectedFiles]); |
| 668 | }; |
| 657 | 669 | |
| 658 | | const runQuickAction = useCallback(() => { |
| 670 | const runQuickAction = () => { |
| 659 | 671 | if (quickAction.kind === "open_pr") { |
| 660 | 672 | void openExistingPr(); |
| 661 | 673 | return; |
| ... | ... | @@ -693,31 +705,28 @@ export default function GitActionsControl({ gitCwd, activeThreadId }: GitActions |
| 693 | 705 | if (quickAction.action) { |
| 694 | 706 | void runGitActionWithToast({ action: quickAction.action }); |
| 695 | 707 | } |
| 696 | | }, [openExistingPr, pullMutation, quickAction, threadToastData]); |
| 708 | }; |
| 697 | 709 | |
| 698 | | const openDialogForMenuItem = useCallback( |
| 699 | | (item: GitActionMenuItem) => { |
| 700 | | if (item.disabled) return; |
| 701 | | if (item.kind === "open_pr") { |
| 702 | | void openExistingPr(); |
| 703 | | return; |
| 704 | | } |
| 705 | | if (item.dialogAction === "push") { |
| 706 | | void runGitActionWithToast({ action: "push" }); |
| 707 | | return; |
| 708 | | } |
| 709 | | if (item.dialogAction === "create_pr") { |
| 710 | | void runGitActionWithToast({ action: "create_pr" }); |
| 711 | | return; |
| 712 | | } |
| 713 | | setExcludedFiles(new Set()); |
| 714 | | setIsEditingFiles(false); |
| 715 | | setIsCommitDialogOpen(true); |
| 716 | | }, |
| 717 | | [openExistingPr, setIsCommitDialogOpen], |
| 718 | | ); |
| 710 | const openDialogForMenuItem = (item: GitActionMenuItem) => { |
| 711 | if (item.disabled) return; |
| 712 | if (item.kind === "open_pr") { |
| 713 | void openExistingPr(); |
| 714 | return; |
| 715 | } |
| 716 | if (item.dialogAction === "push") { |
| 717 | void runGitActionWithToast({ action: "push" }); |
| 718 | return; |
| 719 | } |
| 720 | if (item.dialogAction === "create_pr") { |
| 721 | void runGitActionWithToast({ action: "create_pr" }); |
| 722 | return; |
| 723 | } |
| 724 | setExcludedFiles(new Set()); |
| 725 | setIsEditingFiles(false); |
| 726 | setIsCommitDialogOpen(true); |
| 727 | }; |
| 719 | 728 | |
| 720 | | const runDialogAction = useCallback(() => { |
| 729 | const runDialogAction = () => { |
| 721 | 730 | if (!isCommitDialogOpen) return; |
| 722 | 731 | const commitMessage = dialogCommitMessage.trim(); |
| 723 | 732 | setIsCommitDialogOpen(false); |
| ... | ... | @@ -729,14 +738,7 @@ export default function GitActionsControl({ gitCwd, activeThreadId }: GitActions |
| 729 | 738 | ...(commitMessage ? { commitMessage } : {}), |
| 730 | 739 | ...(!allSelected ? { filePaths: selectedFiles.map((f) => f.path) } : {}), |
| 731 | 740 | }); |
| 732 | | }, [ |
| 733 | | allSelected, |
| 734 | | dialogCommitMessage, |
| 735 | | isCommitDialogOpen, |
| 736 | | selectedFiles, |
| 737 | | setDialogCommitMessage, |
| 738 | | setIsCommitDialogOpen, |
| 739 | | ]); |
| 741 | }; |
| 740 | 742 | |
| 741 | 743 | const openChangedFileInEditor = useCallback( |
| 742 | 744 | (filePath: string) => { |