diff --git a/src/renderer/src/components/GitHubItemDialog.tsx b/src/renderer/src/components/GitHubItemDialog.tsx index dfc2fe90afe..f17e104ddfa 100644 --- a/src/renderer/src/components/GitHubItemDialog.tsx +++ b/src/renderer/src/components/GitHubItemDialog.tsx @@ -57,7 +57,7 @@ import { Button } from '@/components/ui/button' import { ButtonGroup } from '@/components/ui/button-group' import { Input } from '@/components/ui/input' import { useMountedRef } from '@/hooks/useMountedRef' -import { useConfirmationDialog } from '@/components/confirmation-dialog' +import { useConfirmationDialog } from '@/components/confirmation-dialog-context' import { Accordion, AccordionContent, diff --git a/src/renderer/src/components/PullRequestPage.tsx b/src/renderer/src/components/PullRequestPage.tsx index c00abfad692..8e6063583fd 100644 --- a/src/renderer/src/components/PullRequestPage.tsx +++ b/src/renderer/src/components/PullRequestPage.tsx @@ -50,7 +50,7 @@ import { Button } from '@/components/ui/button' import { ButtonGroup } from '@/components/ui/button-group' import { Input } from '@/components/ui/input' import { useMountedRef } from '@/hooks/useMountedRef' -import { useConfirmationDialog } from '@/components/confirmation-dialog' +import { useConfirmationDialog } from '@/components/confirmation-dialog-context' import { Accordion, AccordionContent, diff --git a/src/renderer/src/components/TaskPage.tsx b/src/renderer/src/components/TaskPage.tsx index 5c30fa545d0..3ff8a909b7e 100644 --- a/src/renderer/src/components/TaskPage.tsx +++ b/src/renderer/src/components/TaskPage.tsx @@ -103,7 +103,7 @@ import type { TaskSourceAvailabilityNotice, TaskSourceHostAvailability } from './task-source-context-summary' -import { useConfirmationDialog } from '@/components/confirmation-dialog' +import { useConfirmationDialog } from '@/components/confirmation-dialog-context' import { getGitHubPRPrimaryReviewer, getGitHubPRReviewerRows, diff --git a/src/renderer/src/components/confirmation-dialog-context.ts b/src/renderer/src/components/confirmation-dialog-context.ts new file mode 100644 index 00000000000..1ec6eacfdb1 --- /dev/null +++ b/src/renderer/src/components/confirmation-dialog-context.ts @@ -0,0 +1,29 @@ +import { createContext, useContext } from 'react' + +// Why: the context stays in a component-free module so React Fast Refresh never +// rebuilds it. A module exporting both a component and this hook can never be a +// refresh boundary, so Vite applies its updates in two waves under different +// `?t=` stamps; importers refreshed in different waves then hold different +// context objects and consumers throw below until a full reload. + +export type ConfirmationDialogOptions = { + title: string + description?: string + confirmLabel?: string + cancelLabel?: string + confirmVariant?: 'default' | 'destructive' +} + +export type ConfirmationDialogContextValue = ( + options: ConfirmationDialogOptions +) => Promise + +export const ConfirmationDialogContext = createContext(null) + +export function useConfirmationDialog(): ConfirmationDialogContextValue { + const confirm = useContext(ConfirmationDialogContext) + if (!confirm) { + throw new Error('useConfirmationDialog must be used inside ConfirmationDialogProvider') + } + return confirm +} diff --git a/src/renderer/src/components/confirmation-dialog.tsx b/src/renderer/src/components/confirmation-dialog.tsx index f13285c4907..3011078c33b 100644 --- a/src/renderer/src/components/confirmation-dialog.tsx +++ b/src/renderer/src/components/confirmation-dialog.tsx @@ -1,4 +1,4 @@ -import React, { createContext, useCallback, useContext, useEffect, useRef, useState } from 'react' +import React, { useCallback, useEffect, useRef, useState } from 'react' import { Button } from '@/components/ui/button' import { @@ -9,27 +9,20 @@ import { DialogHeader, DialogTitle } from '@/components/ui/dialog' +import { + ConfirmationDialogContext, + type ConfirmationDialogContextValue, + type ConfirmationDialogOptions +} from '@/components/confirmation-dialog-context' import { useAppStore } from '@/store' import { translate } from '@/i18n/i18n' -type ConfirmationDialogOptions = { - title: string - description?: string - confirmLabel?: string - cancelLabel?: string - confirmVariant?: 'default' | 'destructive' -} - type ConfirmationDialogRequest = { id: number options: ConfirmationDialogOptions resolve: (confirmed: boolean) => void } -type ConfirmationDialogContextValue = (options: ConfirmationDialogOptions) => Promise - -const ConfirmationDialogContext = createContext(null) - export function ConfirmationDialogProvider({ children }: { @@ -116,11 +109,3 @@ export function ConfirmationDialogProvider({ ) } - -export function useConfirmationDialog(): ConfirmationDialogContextValue { - const confirm = useContext(ConfirmationDialogContext) - if (!confirm) { - throw new Error('useConfirmationDialog must be used inside ConfirmationDialogProvider') - } - return confirm -} diff --git a/src/renderer/src/components/right-sidebar/ChecksPanel.tsx b/src/renderer/src/components/right-sidebar/ChecksPanel.tsx index ac5ad324211..35b66c448a6 100644 --- a/src/renderer/src/components/right-sidebar/ChecksPanel.tsx +++ b/src/renderer/src/components/right-sidebar/ChecksPanel.tsx @@ -89,7 +89,7 @@ import { normalizeGlobalWindowsRuntimeDefault } from '../../../../shared/project import { normalizeHostedReviewHeadRef } from '../../../../shared/hosted-review-refs' import { getHostedReviewCacheKey, refreshHostedReviewCard } from '@/store/slices/hosted-review' import { toast } from 'sonner' -import { useConfirmationDialog } from '@/components/confirmation-dialog' +import { useConfirmationDialog } from '@/components/confirmation-dialog-context' import { type ChecksPanelReview, selectChecksPanelReview } from './checks-panel-review' import { selectReviewCacheEntry } from './review-cache-entry-selection' import { diff --git a/src/renderer/src/components/right-sidebar/SourceControl.open-file-highlight.test.tsx b/src/renderer/src/components/right-sidebar/SourceControl.open-file-highlight.test.tsx index bddef9ef29c..8624d25fae2 100644 --- a/src/renderer/src/components/right-sidebar/SourceControl.open-file-highlight.test.tsx +++ b/src/renderer/src/components/right-sidebar/SourceControl.open-file-highlight.test.tsx @@ -61,7 +61,7 @@ vi.mock('@/store/selectors', () => ({ useWorktreeMap: () => new Map([[mocks.activeWorktree.id, mocks.activeWorktree]]) })) -vi.mock('@/components/confirmation-dialog', () => ({ +vi.mock('@/components/confirmation-dialog-context', () => ({ useConfirmationDialog: () => vi.fn().mockResolvedValue(true) })) diff --git a/src/renderer/src/components/right-sidebar/SourceControl.preview-open.test.tsx b/src/renderer/src/components/right-sidebar/SourceControl.preview-open.test.tsx index 6d0121157bb..22bc4954b20 100644 --- a/src/renderer/src/components/right-sidebar/SourceControl.preview-open.test.tsx +++ b/src/renderer/src/components/right-sidebar/SourceControl.preview-open.test.tsx @@ -78,7 +78,7 @@ vi.mock('@/store/selectors', () => ({ useWorktreeMap: () => new Map([[mocks.activeWorktree.id, mocks.activeWorktree]]) })) -vi.mock('@/components/confirmation-dialog', () => ({ +vi.mock('@/components/confirmation-dialog-context', () => ({ useConfirmationDialog: () => vi.fn().mockResolvedValue(true) })) diff --git a/src/renderer/src/components/right-sidebar/SourceControl.tsx b/src/renderer/src/components/right-sidebar/SourceControl.tsx index 6f5a4cc5fb1..05a9f488bb4 100644 --- a/src/renderer/src/components/right-sidebar/SourceControl.tsx +++ b/src/renderer/src/components/right-sidebar/SourceControl.tsx @@ -149,7 +149,7 @@ import { DialogTitle } from '@/components/ui/dialog' import { BaseRefPicker } from '@/components/settings/BaseRefPicker' -import { useConfirmationDialog } from '@/components/confirmation-dialog' +import { useConfirmationDialog } from '@/components/confirmation-dialog-context' import { formatDiffComment, formatDiffComments } from '@/lib/diff-comments-format' import { getDiffCommentLineLabel, getDiffCommentSource } from '@/lib/diff-comment-compat' import { DiffNotesSendMenu } from '@/components/editor/DiffNotesSendMenu' diff --git a/src/renderer/src/components/right-sidebar/SourceControl.virtual-file-list.test.tsx b/src/renderer/src/components/right-sidebar/SourceControl.virtual-file-list.test.tsx index 2dc5f0bc474..7e1d4c7d2ec 100644 --- a/src/renderer/src/components/right-sidebar/SourceControl.virtual-file-list.test.tsx +++ b/src/renderer/src/components/right-sidebar/SourceControl.virtual-file-list.test.tsx @@ -66,7 +66,7 @@ vi.mock('@/store/selectors', () => ({ useWorktreeMap: () => new Map([[mocks.activeWorktree.id, mocks.activeWorktree]]) })) -vi.mock('@/components/confirmation-dialog', () => ({ +vi.mock('@/components/confirmation-dialog-context', () => ({ useConfirmationDialog: () => vi.fn().mockResolvedValue(true) })) diff --git a/src/renderer/src/components/right-sidebar/use-hosted-review-actions.test.tsx b/src/renderer/src/components/right-sidebar/use-hosted-review-actions.test.tsx index 82527dc8ed5..1eae2a150bc 100644 --- a/src/renderer/src/components/right-sidebar/use-hosted-review-actions.test.tsx +++ b/src/renderer/src/components/right-sidebar/use-hosted-review-actions.test.tsx @@ -14,7 +14,7 @@ const runtimeRpcMocks = vi.hoisted(() => ({ callRuntimeRpc: vi.fn() })) -vi.mock('@/components/confirmation-dialog', () => ({ +vi.mock('@/components/confirmation-dialog-context', () => ({ useConfirmationDialog: () => confirmationMocks.confirm })) diff --git a/src/renderer/src/components/right-sidebar/use-hosted-review-actions.ts b/src/renderer/src/components/right-sidebar/use-hosted-review-actions.ts index 331ada85d34..eec79060650 100644 --- a/src/renderer/src/components/right-sidebar/use-hosted-review-actions.ts +++ b/src/renderer/src/components/right-sidebar/use-hosted-review-actions.ts @@ -1,6 +1,6 @@ import { useCallback, useState } from 'react' import { toast } from 'sonner' -import { useConfirmationDialog } from '@/components/confirmation-dialog' +import { useConfirmationDialog } from '@/components/confirmation-dialog-context' import type { GitHubPRAutoMergeAction } from '@/components/github-pr-merge-state' import type { HostedReviewInfo } from '../../../../shared/hosted-review' import type { PRInfo, Repo, GitHubPRMergeMethod } from '../../../../shared/types' diff --git a/src/renderer/src/components/right-sidebar/useFileDeletion.ts b/src/renderer/src/components/right-sidebar/useFileDeletion.ts index 4c5a0c9ec3f..6e5dcc5af54 100644 --- a/src/renderer/src/components/right-sidebar/useFileDeletion.ts +++ b/src/renderer/src/components/right-sidebar/useFileDeletion.ts @@ -1,7 +1,7 @@ import { useCallback, useMemo, useRef } from 'react' import { toast } from 'sonner' import { useAppStore } from '@/store' -import { useConfirmationDialog } from '@/components/confirmation-dialog' +import { useConfirmationDialog } from '@/components/confirmation-dialog-context' import { dirname } from '@/lib/path' import { useShortcutLabel } from '@/hooks/useShortcutLabel' import { isPathEqualOrDescendant } from './file-explorer-paths' diff --git a/src/renderer/src/components/settings/QuickCommandsPane.tsx b/src/renderer/src/components/settings/QuickCommandsPane.tsx index 4b70c72c900..526255160b6 100644 --- a/src/renderer/src/components/settings/QuickCommandsPane.tsx +++ b/src/renderer/src/components/settings/QuickCommandsPane.tsx @@ -9,7 +9,7 @@ import { import { useAppStore } from '../../store' import { Button } from '../ui/button' import { Label } from '../ui/label' -import { useConfirmationDialog } from '@/components/confirmation-dialog' +import { useConfirmationDialog } from '@/components/confirmation-dialog-context' import { getSettingOwnershipSummary } from './setting-ownership' import { translate } from '@/i18n/i18n' import { QuickCommandsList } from './QuickCommandsList' diff --git a/src/renderer/src/components/settings/Settings.tsx b/src/renderer/src/components/settings/Settings.tsx index 17a251bea15..fe894fa07e6 100644 --- a/src/renderer/src/components/settings/Settings.tsx +++ b/src/renderer/src/components/settings/Settings.tsx @@ -22,7 +22,7 @@ import { useAppStore } from '../../store' import { useSystemPrefersDark } from '@/components/terminal-pane/use-system-prefers-dark' import { isMacUserAgent, isWindowsUserAgent } from '@/components/terminal-pane/pane-helpers' import { applyDocumentTheme } from '@/lib/document-theme' -import { useConfirmationDialog } from '@/components/confirmation-dialog' +import { useConfirmationDialog } from '@/components/confirmation-dialog-context' import { SCROLLBACK_PRESETS_ROWS, getFallbackTerminalFonts, diff --git a/src/renderer/src/components/tab-bar/TabBarQuickCommandsButton.tsx b/src/renderer/src/components/tab-bar/TabBarQuickCommandsButton.tsx index 0810ea9c2b2..d972edda0d0 100644 --- a/src/renderer/src/components/tab-bar/TabBarQuickCommandsButton.tsx +++ b/src/renderer/src/components/tab-bar/TabBarQuickCommandsButton.tsx @@ -14,7 +14,7 @@ import { getRepoIdFromWorktreeId } from '../../../../shared/worktree-id' import { FLOATING_TERMINAL_WORKTREE_ID } from '../../../../shared/constants' import { runQuickCommandInNewTab } from '@/lib/run-quick-command-in-new-tab' import type { TerminalQuickCommand } from '../../../../shared/types' -import { useConfirmationDialog } from '@/components/confirmation-dialog' +import { useConfirmationDialog } from '@/components/confirmation-dialog-context' import { translate } from '@/i18n/i18n' import { TabBarQuickCommandsMenu } from './TabBarQuickCommandsMenu' diff --git a/src/renderer/src/runtime/file-explorer-delete-owner-provenance.test.ts b/src/renderer/src/runtime/file-explorer-delete-owner-provenance.test.ts index ce426695e6f..20a325a5d7d 100644 --- a/src/renderer/src/runtime/file-explorer-delete-owner-provenance.test.ts +++ b/src/renderer/src/runtime/file-explorer-delete-owner-provenance.test.ts @@ -23,7 +23,9 @@ const fsDeletePath = vi.fn() const fsRenamePath = vi.fn() const runtimeEnvironmentCall = vi.fn() -vi.mock('@/components/confirmation-dialog', () => ({ useConfirmationDialog: () => confirm })) +vi.mock('@/components/confirmation-dialog-context', () => ({ + useConfirmationDialog: () => confirm +})) vi.mock('@/hooks/useShortcutLabel', () => ({ useShortcutLabel: () => 'Delete' })) vi.mock('@/components/editor/editor-autosave', () => ({ requestEditorFileSave: vi.fn().mockResolvedValue(undefined), diff --git a/src/renderer/src/runtime/remote-host-file-delete-repro.test.ts b/src/renderer/src/runtime/remote-host-file-delete-repro.test.ts index a3aa3592a97..a9c144499c9 100644 --- a/src/renderer/src/runtime/remote-host-file-delete-repro.test.ts +++ b/src/renderer/src/runtime/remote-host-file-delete-repro.test.ts @@ -18,7 +18,7 @@ const fsReadFile = vi.fn() const fsDeletePath = vi.fn() const runtimeEnvironmentCall = vi.fn() -vi.mock('@/components/confirmation-dialog', () => ({ +vi.mock('@/components/confirmation-dialog-context', () => ({ useConfirmationDialog: () => confirm })) vi.mock('@/hooks/useShortcutLabel', () => ({ useShortcutLabel: () => 'Delete' }))