perf: reduce duplicate app initialization and heavy view imports
Deduplicates concurrent app initialization in the config store Lazy-loads VS Code-only app surfaces Avoids broad view barrel imports for better chunk isolation
This commit is contained in:
+23
-38
@@ -1,8 +1,6 @@
|
||||
import React from 'react';
|
||||
import { MainLayout } from '@/components/layout/MainLayout';
|
||||
import { VSCodeLayout } from '@/components/layout/VSCodeLayout';
|
||||
import { AgentManagerView } from '@/components/views/agent-manager';
|
||||
import { ChatView } from '@/components/views';
|
||||
import { ChatView } from '@/components/views/ChatView';
|
||||
import { FireworksProvider } from '@/contexts/FireworksContext';
|
||||
import { Toaster } from '@/components/ui/sonner';
|
||||
import { Button } from '@/components/ui/button';
|
||||
@@ -65,6 +63,14 @@ const OnboardingScreen = lazyWithChunkRecovery(() =>
|
||||
import('@/components/onboarding/OnboardingScreen').then((m) => ({ default: m.OnboardingScreen })),
|
||||
);
|
||||
|
||||
const VSCodeLayoutLazy = lazyWithChunkRecovery(() =>
|
||||
import('@/components/layout/VSCodeLayout').then((m) => ({ default: m.VSCodeLayout })),
|
||||
);
|
||||
|
||||
const AgentManagerViewLazy = lazyWithChunkRecovery(() =>
|
||||
import('@/components/views/agent-manager').then((m) => ({ default: m.AgentManagerView })),
|
||||
);
|
||||
|
||||
const AboutDialogWrapper: React.FC = () => {
|
||||
const isAboutDialogOpen = useUIStore((s) => s.isAboutDialogOpen);
|
||||
const setAboutDialogOpen = useUIStore((s) => s.setAboutDialogOpen);
|
||||
@@ -236,7 +242,6 @@ function App({ apis }: AppProps) {
|
||||
: null;
|
||||
});
|
||||
const appReadyDispatchedRef = React.useRef(false);
|
||||
const initializationInFlightRef = React.useRef(false);
|
||||
const embeddedSessionChat = React.useMemo<EmbeddedSessionChatConfig | null>(() => readEmbeddedSessionChatConfig(), []);
|
||||
const embeddedBackgroundWorkEnabled = !embeddedSessionChat || isEmbeddedVisible;
|
||||
const isMcpOAuthCallback = React.useMemo(() => isMcpOAuthCallbackPath(), []);
|
||||
@@ -383,24 +388,12 @@ function App({ apis }: AppProps) {
|
||||
}, [setPlanModeEnabled]);
|
||||
|
||||
React.useEffect(() => {
|
||||
const init = async () => {
|
||||
// VS Code runtime bootstraps config + sessions after the managed OpenCode instance reports "connected".
|
||||
// Doing the default initialization here can race with startup and lead to one-shot failures.
|
||||
if (isVSCodeRuntime) {
|
||||
return;
|
||||
}
|
||||
if (initializationInFlightRef.current) {
|
||||
return;
|
||||
}
|
||||
initializationInFlightRef.current = true;
|
||||
try {
|
||||
await initializeApp();
|
||||
} finally {
|
||||
initializationInFlightRef.current = false;
|
||||
}
|
||||
};
|
||||
|
||||
init();
|
||||
// VS Code runtime bootstraps config + sessions after the managed OpenCode instance reports "connected".
|
||||
// Doing the default initialization here can race with startup and lead to one-shot failures.
|
||||
if (isVSCodeRuntime) {
|
||||
return;
|
||||
}
|
||||
void initializeApp();
|
||||
}, [initializeApp, isVSCodeRuntime]);
|
||||
|
||||
React.useEffect(() => {
|
||||
@@ -423,18 +416,8 @@ function App({ apis }: AppProps) {
|
||||
setInitRetryExhausted(false);
|
||||
return;
|
||||
}
|
||||
if (initializationInFlightRef.current) {
|
||||
retryTimer = setTimeout(retryInitialization, BASE_DELAY_MS);
|
||||
return;
|
||||
}
|
||||
|
||||
retryCount += 1;
|
||||
initializationInFlightRef.current = true;
|
||||
try {
|
||||
await state.initializeApp();
|
||||
} finally {
|
||||
initializationInFlightRef.current = false;
|
||||
}
|
||||
await state.initializeApp();
|
||||
|
||||
const next = useConfigStore.getState();
|
||||
if (!active) return;
|
||||
@@ -760,15 +743,13 @@ function App({ apis }: AppProps) {
|
||||
}, []);
|
||||
|
||||
const handleManualInitRetry = React.useCallback(async () => {
|
||||
if (manualInitRetrying || initializationInFlightRef.current) return;
|
||||
if (manualInitRetrying) return;
|
||||
|
||||
setInitRetryExhausted(false);
|
||||
setManualInitRetrying(true);
|
||||
initializationInFlightRef.current = true;
|
||||
try {
|
||||
await useConfigStore.getState().initializeApp();
|
||||
} finally {
|
||||
initializationInFlightRef.current = false;
|
||||
setManualInitRetrying(false);
|
||||
}
|
||||
|
||||
@@ -882,7 +863,9 @@ function App({ apis }: AppProps) {
|
||||
<TooltipProvider delayDuration={300} skipDelayDuration={150}>
|
||||
<div className="h-full text-foreground bg-background">
|
||||
<SyncAppEffects embeddedBackgroundWorkEnabled={embeddedBackgroundWorkEnabled} />
|
||||
<AgentManagerView />
|
||||
<React.Suspense fallback={<div className="h-full" />}>
|
||||
<AgentManagerViewLazy />
|
||||
</React.Suspense>
|
||||
<Toaster />
|
||||
</div>
|
||||
</TooltipProvider>
|
||||
@@ -900,7 +883,9 @@ function App({ apis }: AppProps) {
|
||||
<TooltipProvider delayDuration={300} skipDelayDuration={150}>
|
||||
<div className="h-full text-foreground bg-background">
|
||||
<SyncAppEffects embeddedBackgroundWorkEnabled={embeddedBackgroundWorkEnabled} />
|
||||
<VSCodeLayout />
|
||||
<React.Suspense fallback={<div className="h-full" />}>
|
||||
<VSCodeLayoutLazy />
|
||||
</React.Suspense>
|
||||
<Toaster />
|
||||
</div>
|
||||
</TooltipProvider>
|
||||
|
||||
@@ -4,7 +4,9 @@ import { RiArrowLeftRightLine, RiChat4Line, RiCloseLine, RiDonutChartFill, RiFil
|
||||
import { FileTypeIcon } from '@/components/icons/FileTypeIcon';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { SortableTabsStrip } from '@/components/ui/sortable-tabs-strip';
|
||||
import { DiffView, FilesView, PlanView } from '@/components/views';
|
||||
import { DiffView } from '@/components/views/DiffView';
|
||||
import { FilesView } from '@/components/views/FilesView';
|
||||
import { PlanView } from '@/components/views/PlanView';
|
||||
import { useThemeSystem } from '@/contexts/useThemeSystem';
|
||||
import { openExternalUrl } from '@/lib/url';
|
||||
import { copyTextToClipboard } from '@/lib/clipboard';
|
||||
|
||||
@@ -25,7 +25,7 @@ import { cn } from '@/lib/utils';
|
||||
import { isDesktopShell } from '@/lib/desktop';
|
||||
import { lazyWithChunkRecovery } from '@/lib/chunkLoadRecovery';
|
||||
|
||||
import { ChatView } from '@/components/views';
|
||||
import { ChatView } from '@/components/views/ChatView';
|
||||
|
||||
// Heavy views loaded on-demand to reduce initial bundle parse time.
|
||||
const PlanView = lazyWithChunkRecovery(() => import('@/components/views/PlanView').then(m => ({ default: m.PlanView })));
|
||||
|
||||
@@ -3,7 +3,7 @@ import { RiBookletLine, RiFolder3Line, RiGitBranchLine } from '@remixicon/react'
|
||||
|
||||
import { SortableTabsStrip } from '@/components/ui/sortable-tabs-strip';
|
||||
import { ProjectNotesTodoPanel } from '@/components/session/ProjectNotesTodoPanel';
|
||||
import { GitView } from '@/components/views';
|
||||
import { GitView } from '@/components/views/GitView';
|
||||
import { useGitStore } from '@/stores/useGitStore';
|
||||
import { useProjectsStore } from '@/stores/useProjectsStore';
|
||||
import { useDirectoryStore } from '@/stores/useDirectoryStore';
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React from 'react';
|
||||
import { ErrorBoundary } from '../ui/ErrorBoundary';
|
||||
import { SessionSidebar } from '@/components/session/SessionSidebar';
|
||||
import { ChatView } from '@/components/views';
|
||||
import { ChatView } from '@/components/views/ChatView';
|
||||
import { useSessionUIStore } from '@/sync/session-ui-store';
|
||||
import { useViewportStore } from '@/sync/viewport-store';
|
||||
import { useSessions, useDirectorySync, useSessionMessages, useSessionMessagesResolved } from '@/sync/sync-context';
|
||||
|
||||
@@ -1,9 +1,2 @@
|
||||
/** Entry-level views only. Avoid barrel re-exports that pull heavy modules into the main chunk. */
|
||||
export { ChatView } from './ChatView';
|
||||
export { PlanView } from './PlanView';
|
||||
export { GitView } from './GitView';
|
||||
export { DiffView, useDiffFileCount } from './DiffView';
|
||||
export { TerminalView } from './TerminalView';
|
||||
export { FilesView } from './FilesView';
|
||||
export { SettingsView } from './SettingsView';
|
||||
export { SettingsWindow } from './SettingsWindow';
|
||||
export { MultiRunWindow } from './MultiRunWindow';
|
||||
|
||||
@@ -583,6 +583,7 @@ declare global {
|
||||
// In-flight dedup: prevent concurrent duplicate loadProviders/loadAgents calls for the same directory
|
||||
const _inFlightProviders = new Map<string, Promise<void>>();
|
||||
const _inFlightAgents = new Map<string, Promise<boolean>>();
|
||||
let _initializeAppInFlight: Promise<void> | null = null;
|
||||
|
||||
export const useConfigStore = create<ConfigStore>()(
|
||||
devtools(
|
||||
@@ -1965,43 +1966,54 @@ export const useConfigStore = create<ConfigStore>()(
|
||||
},
|
||||
|
||||
initializeApp: async () => {
|
||||
try {
|
||||
const debug = streamDebugEnabled();
|
||||
if (debug) console.log("Starting app initialization...");
|
||||
if (_initializeAppInFlight) {
|
||||
return _initializeAppInFlight;
|
||||
}
|
||||
|
||||
const isConnected = await get().checkConnection();
|
||||
if (debug) console.log("Connection check result:", isConnected);
|
||||
const run = (async () => {
|
||||
try {
|
||||
const debug = streamDebugEnabled();
|
||||
if (debug) console.log("Starting app initialization...");
|
||||
|
||||
if (!isConnected) {
|
||||
if (debug) console.log("Server not connected");
|
||||
// checkConnection already set lastDisconnectReason; do not overwrite.
|
||||
const isConnected = await get().checkConnection();
|
||||
if (debug) console.log("Connection check result:", isConnected);
|
||||
|
||||
if (!isConnected) {
|
||||
if (debug) console.log("Server not connected");
|
||||
// checkConnection already set lastDisconnectReason; do not overwrite.
|
||||
set({
|
||||
isConnected: false,
|
||||
connectionPhase: get().hasEverConnected ? "reconnecting" : "connecting",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (debug) console.log("Initializing app...");
|
||||
await opencodeClient.initApp();
|
||||
|
||||
if (debug) console.log("Loading providers...");
|
||||
await get().loadProviders();
|
||||
|
||||
if (debug) console.log("Loading agents...");
|
||||
await get().loadAgents();
|
||||
|
||||
set({ isInitialized: true, isConnected: true, hasEverConnected: true, connectionPhase: "connected" });
|
||||
if (debug) console.log("App initialized successfully");
|
||||
} catch (error) {
|
||||
console.error("Failed to initialize app:", error);
|
||||
set({
|
||||
isInitialized: false,
|
||||
isConnected: false,
|
||||
connectionPhase: get().hasEverConnected ? "reconnecting" : "connecting",
|
||||
lastDisconnectReason: 'init_error',
|
||||
});
|
||||
return;
|
||||
}
|
||||
})().finally(() => {
|
||||
_initializeAppInFlight = null;
|
||||
});
|
||||
|
||||
if (debug) console.log("Initializing app...");
|
||||
await opencodeClient.initApp();
|
||||
|
||||
if (debug) console.log("Loading providers...");
|
||||
await get().loadProviders();
|
||||
|
||||
if (debug) console.log("Loading agents...");
|
||||
await get().loadAgents();
|
||||
|
||||
set({ isInitialized: true, isConnected: true, hasEverConnected: true, connectionPhase: "connected" });
|
||||
if (debug) console.log("App initialized successfully");
|
||||
} catch (error) {
|
||||
console.error("Failed to initialize app:", error);
|
||||
set({
|
||||
isInitialized: false,
|
||||
isConnected: false,
|
||||
connectionPhase: get().hasEverConnected ? "reconnecting" : "connecting",
|
||||
lastDisconnectReason: 'init_error',
|
||||
});
|
||||
}
|
||||
_initializeAppInFlight = run;
|
||||
return run;
|
||||
},
|
||||
|
||||
getCurrentProvider: () => {
|
||||
|
||||
Reference in New Issue
Block a user