mirror of
https://github.com/stablyai/orca.git
synced 2026-10-20 00:03:45 +00:00
fix(dev): split the confirmation dialog so Fast Refresh can accept it
`confirmation-dialog.tsx` exported both `ConfirmationDialogProvider` and `useConfirmationDialog`, so React Fast Refresh could never treat it as a boundary and Vite applied every edit to it in two passes under two `?t=` stamps. When a second file in the same subtree changed in one watcher batch, `createContext` ran twice and the provider published one context object while the consumer read the other — `useContext` returned null and the hook threw. Two field crash reports hit this at `ChecksPanel`, both dev-server sessions. The context and hook move to a new component-free `confirmation-dialog-context.ts`; `confirmation-dialog.tsx` keeps the provider and now exports only a component, so the refresh runtime accepts it. Not one line of the provider body changes — the 16 hook importers just point at the new module, `vi.mock` targets included, and `App.tsx` is untouched.
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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<boolean>
|
||||
|
||||
export const ConfirmationDialogContext = createContext<ConfirmationDialogContextValue | null>(null)
|
||||
|
||||
export function useConfirmationDialog(): ConfirmationDialogContextValue {
|
||||
const confirm = useContext(ConfirmationDialogContext)
|
||||
if (!confirm) {
|
||||
throw new Error('useConfirmationDialog must be used inside ConfirmationDialogProvider')
|
||||
}
|
||||
return confirm
|
||||
}
|
||||
@@ -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<boolean>
|
||||
|
||||
const ConfirmationDialogContext = createContext<ConfirmationDialogContextValue | null>(null)
|
||||
|
||||
export function ConfirmationDialogProvider({
|
||||
children
|
||||
}: {
|
||||
@@ -116,11 +109,3 @@ export function ConfirmationDialogProvider({
|
||||
</ConfirmationDialogContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
export function useConfirmationDialog(): ConfirmationDialogContextValue {
|
||||
const confirm = useContext(ConfirmationDialogContext)
|
||||
if (!confirm) {
|
||||
throw new Error('useConfirmationDialog must be used inside ConfirmationDialogProvider')
|
||||
}
|
||||
return confirm
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
+1
-1
@@ -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)
|
||||
}))
|
||||
|
||||
|
||||
@@ -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)
|
||||
}))
|
||||
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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)
|
||||
}))
|
||||
|
||||
|
||||
@@ -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
|
||||
}))
|
||||
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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'
|
||||
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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' }))
|
||||
|
||||
Reference in New Issue
Block a user