Resolve lazy modal mounts before commit

This commit is contained in:
Neil
2026-05-31 05:51:32 -07:00
parent 0fd484c75d
commit 36ace3f225
3 changed files with 78 additions and 29 deletions
+14 -29
View File
@@ -128,6 +128,7 @@ import {
} from '../../shared/keybindings'
import { isGitRepoKind } from '../../shared/repo-kind'
import { showTerminalShortcutCaptureNotification } from '@/lib/terminal-shortcut-capture-notification'
import { resolveMountedLazyModalIds, type LazyModalId } from './lazy-modal-mount-state'
const isMac = navigator.userAgent.includes('Mac')
const isWindows = !isMac && navigator.userAgent.includes('Windows')
@@ -473,7 +474,7 @@ function App(): React.JSX.Element {
const canGoForwardWorktree = useAppStore(canGoForwardWorktreeHistory)
const titlebarLeftControlsRef = useRef<HTMLDivElement | null>(null)
const [collapsedSidebarHeaderWidth, setCollapsedSidebarHeaderWidth] = useState(0)
const [mountedLazyModalIds, setMountedLazyModalIds] = useState(() => new Set<string>())
const [mountedLazyModalIds, setMountedLazyModalIds] = useState<Set<LazyModalId>>(() => new Set())
const [onboarding, setOnboarding] = useState<OnboardingState | null>(null)
const featureTipsPromptedThisSessionRef = useRef(false)
const featureTipsSuppressedByOnboardingThisSessionRef = useRef(false)
@@ -1407,28 +1408,12 @@ function App(): React.JSX.Element {
return () => observer.disconnect()
}, [isFullScreen, settings?.showTitlebarAppName, showSidebar, workspaceActive, sidebarOpen])
useEffect(() => {
if (
activeModal !== 'quick-open' &&
activeModal !== 'worktree-palette' &&
activeModal !== 'new-workspace-composer' &&
activeModal !== 'workspace-cleanup' &&
activeModal !== 'feature-wall' &&
activeModal !== 'feature-tips'
) {
return
}
setMountedLazyModalIds((currentIds) => {
if (currentIds.has(activeModal)) {
return currentIds
}
const nextIds = new Set(currentIds)
// Why: lazy-load these modals only after first use, then keep them mounted
// so repeat opens preserve their local state and avoid re-fetch flashes.
nextIds.add(activeModal)
return nextIds
})
}, [activeModal])
const resolvedMountedLazyModalIds = resolveMountedLazyModalIds(activeModal, mountedLazyModalIds)
if (resolvedMountedLazyModalIds !== mountedLazyModalIds) {
// Why: lazy-load these modals only after first use, then keep them mounted
// so repeat opens preserve their local state and avoid re-fetch flashes.
setMountedLazyModalIds(new Set(resolvedMountedLazyModalIds))
}
// Why: extracted so both the full-width titlebar (settings/landing) and
// the sidebar-width left header (workspace view) can share the same
@@ -1850,7 +1835,7 @@ function App(): React.JSX.Element {
{/* Why: root overlays can render Radix <Tooltip>s; keep them inside
the shared provider so lazy surfaces mount safely from any entry point. */}
<Suspense fallback={null}>
{mountedLazyModalIds.has('new-workspace-composer') ? (
{resolvedMountedLazyModalIds.has('new-workspace-composer') ? (
<RecoverableRenderErrorBoundary
boundaryId="modal.new-workspace-composer"
surface="modal"
@@ -1860,7 +1845,7 @@ function App(): React.JSX.Element {
<NewWorkspaceComposerModal />
</RecoverableRenderErrorBoundary>
) : null}
{mountedLazyModalIds.has('workspace-cleanup') ? (
{resolvedMountedLazyModalIds.has('workspace-cleanup') ? (
<RecoverableRenderErrorBoundary
boundaryId="modal.workspace-cleanup"
surface="modal"
@@ -1872,7 +1857,7 @@ function App(): React.JSX.Element {
) : null}
</Suspense>
<Suspense fallback={null}>
{mountedLazyModalIds.has('quick-open') ? (
{resolvedMountedLazyModalIds.has('quick-open') ? (
<RecoverableRenderErrorBoundary
boundaryId="modal.quick-open"
surface="modal"
@@ -1882,7 +1867,7 @@ function App(): React.JSX.Element {
<QuickOpen />
</RecoverableRenderErrorBoundary>
) : null}
{mountedLazyModalIds.has('worktree-palette') ? (
{resolvedMountedLazyModalIds.has('worktree-palette') ? (
<RecoverableRenderErrorBoundary
boundaryId="modal.worktree-palette"
surface="modal"
@@ -1892,7 +1877,7 @@ function App(): React.JSX.Element {
<WorktreeJumpPalette />
</RecoverableRenderErrorBoundary>
) : null}
{mountedLazyModalIds.has('feature-wall') ? (
{resolvedMountedLazyModalIds.has('feature-wall') ? (
<RecoverableRenderErrorBoundary
boundaryId="modal.feature-wall"
surface="modal"
@@ -1902,7 +1887,7 @@ function App(): React.JSX.Element {
<FeatureWallModal />
</RecoverableRenderErrorBoundary>
) : null}
{mountedLazyModalIds.has('feature-tips') ? (
{resolvedMountedLazyModalIds.has('feature-tips') ? (
<RecoverableRenderErrorBoundary
boundaryId="modal.feature-tips"
surface="modal"
@@ -0,0 +1,38 @@
import { describe, expect, it } from 'vitest'
import {
isLazyModalId,
resolveMountedLazyModalIds,
type LazyModalId
} from './lazy-modal-mount-state'
describe('isLazyModalId', () => {
it('recognizes only lazily retained root modal ids', () => {
expect(isLazyModalId('quick-open')).toBe(true)
expect(isLazyModalId('feature-tips')).toBe(true)
expect(isLazyModalId('delete-worktree')).toBe(false)
expect(isLazyModalId('none')).toBe(false)
})
})
describe('resolveMountedLazyModalIds', () => {
it('preserves set identity when the active modal is not lazy-mounted', () => {
const mounted = new Set<LazyModalId>(['quick-open'])
expect(resolveMountedLazyModalIds('none', mounted)).toBe(mounted)
})
it('preserves set identity when the lazy modal is already mounted', () => {
const mounted = new Set<LazyModalId>(['workspace-cleanup'])
expect(resolveMountedLazyModalIds('workspace-cleanup', mounted)).toBe(mounted)
})
it('adds newly opened lazy modal ids without mutating the existing set', () => {
const mounted = new Set<LazyModalId>(['quick-open'])
const resolved = resolveMountedLazyModalIds('feature-wall', mounted)
expect(resolved).toEqual(new Set(['quick-open', 'feature-wall']))
expect(resolved).not.toBe(mounted)
expect(mounted).toEqual(new Set(['quick-open']))
})
})
@@ -0,0 +1,26 @@
const LAZY_MODAL_IDS = [
'quick-open',
'worktree-palette',
'new-workspace-composer',
'workspace-cleanup',
'feature-wall',
'feature-tips'
] as const
export type LazyModalId = (typeof LAZY_MODAL_IDS)[number]
const LAZY_MODAL_ID_SET = new Set<string>(LAZY_MODAL_IDS)
export function isLazyModalId(value: string): value is LazyModalId {
return LAZY_MODAL_ID_SET.has(value)
}
export function resolveMountedLazyModalIds(
activeModal: string,
mountedIds: ReadonlySet<LazyModalId>
): ReadonlySet<LazyModalId> {
if (!isLazyModalId(activeModal) || mountedIds.has(activeModal)) {
return mountedIds
}
return new Set([...mountedIds, activeModal])
}