mirror of
https://github.com/stablyai/orca.git
synced 2026-11-02 10:08:13 +00:00
perf(cmd-j): preload the workspace jump palette before first use (#30935)
* perf(cmd-j): preload the jump palette so its first open renders without React's reveal delay * fix: reuse lazy retry wrapper for preloaded components
This commit is contained in:
@@ -18,6 +18,7 @@ import { shouldRenderPetOverlay } from '../components/pet/pet-overlay-visibility
|
||||
import { useAppStore } from '../store'
|
||||
import type { UpdateStatus } from '../../../shared/update-status-types'
|
||||
import { useLazyModalMounts } from './use-lazy-modal-mounts'
|
||||
import { usePreloadWorktreeJumpPalette, WorktreeJumpPalette } from './worktree-jump-palette-preload'
|
||||
import {
|
||||
selectAppRootSurfacePetEnabled,
|
||||
selectAppRootSurfaceTelemetryOptedIn,
|
||||
@@ -27,7 +28,6 @@ import type { FloatingWorkspacePanelState } from './use-floating-workspace-panel
|
||||
import type { OnboardingGate } from './use-onboarding-and-feature-tips'
|
||||
|
||||
const QuickOpen = lazy(() => import('../components/QuickOpen'))
|
||||
const WorktreeJumpPalette = lazy(() => import('../components/WorktreeJumpPalette'))
|
||||
const WorkspaceCleanupDialog = lazy(
|
||||
() => import('../components/workspace-cleanup/WorkspaceCleanupDialog')
|
||||
)
|
||||
@@ -137,6 +137,7 @@ export function AppRootSurfaces(props: {
|
||||
}): React.JSX.Element {
|
||||
const { floatingWorkspace, onboardingGate } = props
|
||||
const { mountedLazyModalIds, shouldMountAddRepoDialog } = useLazyModalMounts()
|
||||
usePreloadWorktreeJumpPalette()
|
||||
const activeView = useAppStore((s) => s.activeView)
|
||||
const activeModal = useAppStore((s) => s.activeModal)
|
||||
// Keep this always-mounted surface subscribed only to the settings fields it reads. A
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
import { useEffect } from 'react'
|
||||
import { scheduleAfterInputQuiet } from '@/lib/input-quiet-scheduler'
|
||||
import { lazyWithPreload } from '@/lib/lazy-with-retry'
|
||||
|
||||
export const WorktreeJumpPalette = lazyWithPreload(
|
||||
() => import('../components/WorktreeJumpPalette')
|
||||
)
|
||||
|
||||
/** Loads the Cmd+J palette after startup settles so its first open renders without suspending. */
|
||||
export function usePreloadWorktreeJumpPalette(): void {
|
||||
useEffect(
|
||||
() =>
|
||||
scheduleAfterInputQuiet(WorktreeJumpPalette.preload, {
|
||||
delayMs: 500,
|
||||
quietMs: 300,
|
||||
idleTimeoutMs: 1_000,
|
||||
maxWaitMs: 2_000
|
||||
}),
|
||||
[]
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
// @vitest-environment happy-dom
|
||||
import { act, Suspense } from 'react'
|
||||
import { createRoot } from 'react-dom/client'
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
|
||||
import { lazyWithPreload } from './lazy-with-retry'
|
||||
|
||||
Object.assign(globalThis, { IS_REACT_ACT_ENVIRONMENT: true })
|
||||
|
||||
function Palette(): React.JSX.Element {
|
||||
return <input aria-label="palette" />
|
||||
}
|
||||
|
||||
describe('lazyWithPreload', () => {
|
||||
it('renders a preloaded component in the same commit, without the Suspense fallback', async () => {
|
||||
const factory = vi.fn(() => Promise.resolve({ default: Palette }))
|
||||
const Preloadable = lazyWithPreload(factory)
|
||||
Preloadable.preload()
|
||||
await act(async () => {})
|
||||
|
||||
const container = document.createElement('div')
|
||||
const root = createRoot(container)
|
||||
// A synchronous act flushes one render with no chance to retry a suspended one.
|
||||
act(() => {
|
||||
root.render(
|
||||
<Suspense fallback={<span>loading</span>}>
|
||||
<Preloadable />
|
||||
</Suspense>
|
||||
)
|
||||
})
|
||||
|
||||
expect(container.querySelector('input[aria-label="palette"]')).not.toBeNull()
|
||||
expect(container.textContent).not.toContain('loading')
|
||||
expect(factory).toHaveBeenCalledTimes(1)
|
||||
act(() => root.unmount())
|
||||
})
|
||||
})
|
||||
@@ -1,4 +1,12 @@
|
||||
import { lazy, type ComponentType, type LazyExoticComponent } from 'react'
|
||||
import {
|
||||
createElement,
|
||||
lazy,
|
||||
useState,
|
||||
type ComponentProps,
|
||||
type ComponentType,
|
||||
type LazyExoticComponent,
|
||||
type ReactNode
|
||||
} from 'react'
|
||||
|
||||
import {
|
||||
requestLazyChunkRecoveryReload,
|
||||
@@ -256,3 +264,45 @@ export function lazyWithRetry<T extends AnyComponent>(
|
||||
): LazyExoticComponent<T> {
|
||||
return lazy(() => loadLazyWithRetry(factory, options))
|
||||
}
|
||||
|
||||
export type PreloadableLazyComponent<T extends AnyComponent> = ComponentType<ComponentProps<T>> & {
|
||||
/** Loads the chunk ahead of first render; failures stay silent so the real render owns recovery. */
|
||||
preload: () => void
|
||||
}
|
||||
|
||||
/**
|
||||
* lazyWithRetry plus preload(). Once preloaded, the component renders the loaded module
|
||||
* directly instead of through React.lazy, so its first render never suspends: React holds
|
||||
* a newly resolved Suspense reveal for up to 300 ms, which is what made first opens slow.
|
||||
*/
|
||||
export function lazyWithPreload<T extends AnyComponent>(
|
||||
factory: LazyFactory<T>,
|
||||
options?: LazyWithRetryOptions
|
||||
): PreloadableLazyComponent<T> {
|
||||
let loaded: T | null = null
|
||||
const LazyComponent = lazyWithRetry(
|
||||
() =>
|
||||
factory().then((module) => {
|
||||
loaded ??= module.default
|
||||
return module
|
||||
}),
|
||||
options
|
||||
)
|
||||
function PreloadableComponent(props: ComponentProps<T>): ReactNode {
|
||||
// Why: pinned per mount so a module resolving mid-life cannot swap the element type and remount the subtree.
|
||||
const [Component] = useState(() => loaded ?? LazyComponent)
|
||||
return createElement(Component, props)
|
||||
}
|
||||
PreloadableComponent.preload = (): void => {
|
||||
if (loaded) {
|
||||
return
|
||||
}
|
||||
factory().then(
|
||||
(module) => {
|
||||
loaded ??= module.default
|
||||
},
|
||||
() => undefined
|
||||
)
|
||||
}
|
||||
return PreloadableComponent
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user