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