refactor: split vscode into a dedicated app root
Renders VS Code through its own app entrypoint Keeps shared app effects focused and reusable Avoids importing the full desktop app root in VS Code
This commit is contained in:
+3
-117
@@ -8,16 +8,11 @@ import { MemoryDebugPanel } from '@/components/ui/MemoryDebugPanel';
|
||||
import { setStreamPerfEnabled } from '@/stores/utils/streamDebug';
|
||||
import { ErrorBoundary } from '@/components/ui/ErrorBoundary';
|
||||
// useEventStream removed — replaced by SyncProvider + SyncBridge
|
||||
import { useKeyboardShortcuts } from '@/hooks/useKeyboardShortcuts';
|
||||
import { useMenuActions } from '@/hooks/useMenuActions';
|
||||
import { useSessionStatusBootstrap } from '@/hooks/useSessionStatusBootstrap';
|
||||
import { useSessionAutoCleanup } from '@/hooks/useSessionAutoCleanup';
|
||||
import { useQueuedMessageAutoSend } from '@/hooks/useQueuedMessageAutoSend';
|
||||
import { useRouter } from '@/hooks/useRouter';
|
||||
import { usePushVisibilityBeacon } from '@/hooks/usePushVisibilityBeacon';
|
||||
import { usePwaManifestSync } from '@/hooks/usePwaManifestSync';
|
||||
import { usePwaInstallPrompt } from '@/hooks/usePwaInstallPrompt';
|
||||
import { useWindowControlsOverlayLayout } from '@/hooks/useWindowControlsOverlayLayout';
|
||||
import { useWindowTitle } from '@/hooks/useWindowTitle';
|
||||
import { useConfigStore } from '@/stores/useConfigStore';
|
||||
import { hasModifier } from '@/lib/utils';
|
||||
@@ -37,11 +32,6 @@ import { useDirectoryStore } from '@/stores/useDirectoryStore';
|
||||
import { useProjectsStore } from '@/stores/useProjectsStore';
|
||||
import { opencodeClient } from '@/lib/opencode/client';
|
||||
import { SyncProvider, useSessions } from '@/sync/sync-context';
|
||||
import { useSync } from '@/sync/use-sync';
|
||||
import { setOptimisticRefs } from '@/sync/session-actions';
|
||||
import { useFontPreferences } from '@/hooks/useFontPreferences';
|
||||
import { CODE_FONT_OPTION_MAP, DEFAULT_MONO_FONT, DEFAULT_UI_FONT, UI_FONT_OPTION_MAP } from '@/lib/fontOptions';
|
||||
import { loadMonoFont, loadUiFont } from '@/lib/fontLoader';
|
||||
import { ConfigUpdateOverlay } from '@/components/ui/ConfigUpdateOverlay';
|
||||
import { AboutDialog } from '@/components/ui/AboutDialog';
|
||||
import { RuntimeAPIProvider } from '@/contexts/RuntimeAPIProvider';
|
||||
@@ -57,20 +47,14 @@ import { MCP_OAUTH_CALLBACK_PATH } from '@/components/sections/mcp/mcpOAuth';
|
||||
import { lazyWithChunkRecovery } from '@/lib/chunkLoadRecovery';
|
||||
import { useI18n } from '@/lib/i18n';
|
||||
import { applyMobileKeyboardMode } from '@/lib/mobileKeyboardMode';
|
||||
import { SyncAppEffects } from '@/apps/AppEffects';
|
||||
import { useAppFontEffects } from '@/apps/useAppFontEffects';
|
||||
|
||||
// Lazy-loaded heavy views — loaded on demand to reduce initial bundle size.
|
||||
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);
|
||||
@@ -178,35 +162,6 @@ const EmbeddedSessionSelectionGate: React.FC<{
|
||||
return null;
|
||||
};
|
||||
|
||||
const SyncOptimisticBridge: React.FC = () => {
|
||||
const sync = useSync();
|
||||
const addRef = React.useRef(sync.optimistic.add);
|
||||
const removeRef = React.useRef(sync.optimistic.remove);
|
||||
addRef.current = sync.optimistic.add;
|
||||
removeRef.current = sync.optimistic.remove;
|
||||
|
||||
React.useEffect(() => {
|
||||
setOptimisticRefs(
|
||||
(input) => addRef.current(input),
|
||||
(input) => removeRef.current(input),
|
||||
);
|
||||
}, []);
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
function SyncAppEffects({ embeddedBackgroundWorkEnabled }: {
|
||||
embeddedBackgroundWorkEnabled: boolean;
|
||||
}) {
|
||||
usePwaManifestSync();
|
||||
useWindowControlsOverlayLayout();
|
||||
useSessionAutoCleanup(embeddedBackgroundWorkEnabled);
|
||||
useQueuedMessageAutoSend(embeddedBackgroundWorkEnabled);
|
||||
useKeyboardShortcuts();
|
||||
|
||||
return <SyncOptimisticBridge />;
|
||||
}
|
||||
|
||||
function App({ apis }: AppProps) {
|
||||
const initializeApp = useConfigStore((s) => s.initializeApp);
|
||||
const isInitialized = useConfigStore((s) => s.isInitialized);
|
||||
@@ -221,7 +176,6 @@ function App({ apis }: AppProps) {
|
||||
const setDirectory = useDirectoryStore((state) => state.setDirectory);
|
||||
const isSwitchingDirectory = useDirectoryStore((state) => state.isSwitchingDirectory);
|
||||
const [showMemoryDebug, setShowMemoryDebug] = React.useState(false);
|
||||
const { uiFont, monoFont } = useFontPreferences();
|
||||
const refreshGitHubAuthStatus = useGitHubAuthStore((state) => state.refreshStatus);
|
||||
const [isVSCodeRuntime, setIsVSCodeRuntime] = React.useState<boolean>(() => apis.runtime.isVSCode);
|
||||
const [isEmbeddedVisible, setIsEmbeddedVisible] = React.useState(true);
|
||||
@@ -281,27 +235,7 @@ function App({ apis }: AppProps) {
|
||||
void refreshGitHubAuthStatus(apis.github, { force: true });
|
||||
}, [apis.github, embeddedSessionChat, refreshGitHubAuthStatus]);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (typeof document === 'undefined') {
|
||||
return;
|
||||
}
|
||||
const root = document.documentElement;
|
||||
const uiStack = UI_FONT_OPTION_MAP[uiFont]?.stack ?? UI_FONT_OPTION_MAP[DEFAULT_UI_FONT].stack;
|
||||
const monoStack = CODE_FONT_OPTION_MAP[monoFont]?.stack ?? CODE_FONT_OPTION_MAP[DEFAULT_MONO_FONT].stack;
|
||||
void loadUiFont(uiFont);
|
||||
void loadMonoFont(monoFont);
|
||||
|
||||
root.style.setProperty('--font-sans', uiStack);
|
||||
root.style.setProperty('--font-heading', uiStack);
|
||||
root.style.setProperty('--font-family-sans', uiStack);
|
||||
root.style.setProperty('--font-mono', monoStack);
|
||||
root.style.setProperty('--font-family-mono', monoStack);
|
||||
root.style.setProperty('--ui-regular-font-weight', '400');
|
||||
|
||||
if (document.body) {
|
||||
document.body.style.fontFamily = uiStack;
|
||||
}
|
||||
}, [uiFont, monoFont]);
|
||||
useAppFontEffects();
|
||||
|
||||
const bootOutcomeKnown = bootInjectionStatus === 'valid';
|
||||
const bootViewIsMain = bootView?.screen === 'main';
|
||||
@@ -848,54 +782,6 @@ function App({ apis }: AppProps) {
|
||||
);
|
||||
}
|
||||
|
||||
// VS Code runtime - simplified layout without git/terminal views
|
||||
if (isVSCodeRuntime) {
|
||||
// Check if this is the Agent Manager panel
|
||||
const panelType = typeof window !== 'undefined'
|
||||
? (window as { __OPENCHAMBER_PANEL_TYPE__?: 'chat' | 'agentManager' }).__OPENCHAMBER_PANEL_TYPE__
|
||||
: 'chat';
|
||||
|
||||
if (panelType === 'agentManager') {
|
||||
return (
|
||||
<ErrorBoundary>
|
||||
<SyncProvider sdk={opencodeClient.getSdkClient()} directory={currentDirectory || ''}>
|
||||
<RuntimeAPIProvider apis={apis}>
|
||||
<TooltipProvider delayDuration={300} skipDelayDuration={150}>
|
||||
<div className="h-full text-foreground bg-background">
|
||||
<SyncAppEffects embeddedBackgroundWorkEnabled={embeddedBackgroundWorkEnabled} />
|
||||
<React.Suspense fallback={<div className="h-full" />}>
|
||||
<AgentManagerViewLazy />
|
||||
</React.Suspense>
|
||||
<Toaster />
|
||||
</div>
|
||||
</TooltipProvider>
|
||||
</RuntimeAPIProvider>
|
||||
</SyncProvider>
|
||||
</ErrorBoundary>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<ErrorBoundary>
|
||||
<SyncProvider sdk={opencodeClient.getSdkClient()} directory={currentDirectory || ''}>
|
||||
<RuntimeAPIProvider apis={apis}>
|
||||
<FireworksProvider>
|
||||
<TooltipProvider delayDuration={300} skipDelayDuration={150}>
|
||||
<div className="h-full text-foreground bg-background">
|
||||
<SyncAppEffects embeddedBackgroundWorkEnabled={embeddedBackgroundWorkEnabled} />
|
||||
<React.Suspense fallback={<div className="h-full" />}>
|
||||
<VSCodeLayoutLazy />
|
||||
</React.Suspense>
|
||||
<Toaster />
|
||||
</div>
|
||||
</TooltipProvider>
|
||||
</FireworksProvider>
|
||||
</RuntimeAPIProvider>
|
||||
</SyncProvider>
|
||||
</ErrorBoundary>
|
||||
);
|
||||
}
|
||||
|
||||
// Always mount the full provider tree to avoid remounts when isInitialized
|
||||
// flips from false → true. FireworksProvider and VoiceProvider are lightweight
|
||||
// shells; their heavy children are only activated when actually needed.
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
import React from 'react';
|
||||
import { useKeyboardShortcuts } from '@/hooks/useKeyboardShortcuts';
|
||||
import { usePwaManifestSync } from '@/hooks/usePwaManifestSync';
|
||||
import { useQueuedMessageAutoSend } from '@/hooks/useQueuedMessageAutoSend';
|
||||
import { useSessionAutoCleanup } from '@/hooks/useSessionAutoCleanup';
|
||||
import { useWindowControlsOverlayLayout } from '@/hooks/useWindowControlsOverlayLayout';
|
||||
import { setOptimisticRefs } from '@/sync/session-actions';
|
||||
import { useSync } from '@/sync/use-sync';
|
||||
|
||||
const SyncOptimisticBridge: React.FC = () => {
|
||||
const sync = useSync();
|
||||
const addRef = React.useRef(sync.optimistic.add);
|
||||
const removeRef = React.useRef(sync.optimistic.remove);
|
||||
addRef.current = sync.optimistic.add;
|
||||
removeRef.current = sync.optimistic.remove;
|
||||
|
||||
React.useEffect(() => {
|
||||
setOptimisticRefs(
|
||||
(input) => addRef.current(input),
|
||||
(input) => removeRef.current(input),
|
||||
);
|
||||
}, []);
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
export function SyncAppEffects({ embeddedBackgroundWorkEnabled }: {
|
||||
embeddedBackgroundWorkEnabled: boolean;
|
||||
}) {
|
||||
usePwaManifestSync();
|
||||
useWindowControlsOverlayLayout();
|
||||
useSessionAutoCleanup(embeddedBackgroundWorkEnabled);
|
||||
useQueuedMessageAutoSend(embeddedBackgroundWorkEnabled);
|
||||
useKeyboardShortcuts();
|
||||
|
||||
return <SyncOptimisticBridge />;
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
import React from 'react';
|
||||
import { AgentManagerView } from '@/components/views/agent-manager';
|
||||
import { FireworksProvider } from '@/contexts/FireworksContext';
|
||||
import { RuntimeAPIProvider } from '@/contexts/RuntimeAPIProvider';
|
||||
import { registerRuntimeAPIs } from '@/contexts/runtimeAPIRegistry';
|
||||
import { TooltipProvider } from '@/components/ui/tooltip';
|
||||
import { Toaster } from '@/components/ui/sonner';
|
||||
import { ErrorBoundary } from '@/components/ui/ErrorBoundary';
|
||||
import { VSCodeLayout } from '@/components/layout/VSCodeLayout';
|
||||
import { usePushVisibilityBeacon } from '@/hooks/usePushVisibilityBeacon';
|
||||
import { useRouter } from '@/hooks/useRouter';
|
||||
import { useWindowTitle } from '@/hooks/useWindowTitle';
|
||||
import { opencodeClient } from '@/lib/opencode/client';
|
||||
import type { RuntimeAPIs } from '@/lib/api/types';
|
||||
import { useFeatureFlagsStore } from '@/stores/useFeatureFlagsStore';
|
||||
import { useGitHubAuthStore } from '@/stores/useGitHubAuthStore';
|
||||
import { useDirectoryStore } from '@/stores/useDirectoryStore';
|
||||
import { useUIStore } from '@/stores/useUIStore';
|
||||
import { useSessionUIStore } from '@/sync/session-ui-store';
|
||||
import { SyncProvider } from '@/sync/sync-context';
|
||||
import { SyncAppEffects } from './AppEffects';
|
||||
import { useAppFontEffects } from './useAppFontEffects';
|
||||
|
||||
type VSCodePanelType = 'chat' | 'agentManager';
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
__OPENCHAMBER_PANEL_TYPE__?: VSCodePanelType;
|
||||
}
|
||||
}
|
||||
|
||||
type VSCodeAppProps = {
|
||||
apis: RuntimeAPIs;
|
||||
};
|
||||
|
||||
export function VSCodeApp({ apis }: VSCodeAppProps) {
|
||||
const currentDirectory = useDirectoryStore((state) => state.currentDirectory);
|
||||
const error = useSessionUIStore((state) => state.error);
|
||||
const clearError = useSessionUIStore((state) => state.clearError);
|
||||
const wideChatLayoutEnabled = useUIStore((state) => state.wideChatLayoutEnabled);
|
||||
const refreshGitHubAuthStatus = useGitHubAuthStore((state) => state.refreshStatus);
|
||||
const setPlanModeEnabled = useFeatureFlagsStore((state) => state.setPlanModeEnabled);
|
||||
const panelType = typeof window !== 'undefined'
|
||||
? window.__OPENCHAMBER_PANEL_TYPE__
|
||||
: 'chat';
|
||||
|
||||
React.useEffect(() => {
|
||||
registerRuntimeAPIs(apis);
|
||||
return () => registerRuntimeAPIs(null);
|
||||
}, [apis]);
|
||||
|
||||
useAppFontEffects();
|
||||
usePushVisibilityBeacon({ enabled: true });
|
||||
useWindowTitle();
|
||||
useRouter();
|
||||
|
||||
React.useEffect(() => {
|
||||
document.documentElement.classList.toggle('wide-chat-layout', wideChatLayoutEnabled);
|
||||
return () => {
|
||||
document.documentElement.classList.remove('wide-chat-layout');
|
||||
};
|
||||
}, [wideChatLayoutEnabled]);
|
||||
|
||||
React.useEffect(() => {
|
||||
void refreshGitHubAuthStatus(apis.github, { force: true });
|
||||
}, [apis.github, refreshGitHubAuthStatus]);
|
||||
|
||||
React.useEffect(() => {
|
||||
let cancelled = false;
|
||||
|
||||
const run = async () => {
|
||||
const res = await fetch('/health', { method: 'GET' }).catch(() => null);
|
||||
if (!res || !res.ok || cancelled) return;
|
||||
const data = (await res.json().catch(() => null)) as null | {
|
||||
planModeExperimentalEnabled?: unknown;
|
||||
};
|
||||
if (!data || cancelled) return;
|
||||
const raw = data.planModeExperimentalEnabled;
|
||||
const enabled = raw === true || raw === 1 || raw === '1' || raw === 'true';
|
||||
setPlanModeEnabled(enabled);
|
||||
};
|
||||
|
||||
void run();
|
||||
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, [setPlanModeEnabled]);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (!error) {
|
||||
return;
|
||||
}
|
||||
|
||||
const timeout = window.setTimeout(() => clearError(), 5000);
|
||||
return () => window.clearTimeout(timeout);
|
||||
}, [clearError, error]);
|
||||
|
||||
if (panelType === 'agentManager') {
|
||||
return (
|
||||
<ErrorBoundary>
|
||||
<SyncProvider sdk={opencodeClient.getSdkClient()} directory={currentDirectory || ''}>
|
||||
<RuntimeAPIProvider apis={apis}>
|
||||
<TooltipProvider delayDuration={300} skipDelayDuration={150}>
|
||||
<div className="h-full text-foreground bg-background">
|
||||
<SyncAppEffects embeddedBackgroundWorkEnabled={true} />
|
||||
<AgentManagerView />
|
||||
<Toaster />
|
||||
</div>
|
||||
</TooltipProvider>
|
||||
</RuntimeAPIProvider>
|
||||
</SyncProvider>
|
||||
</ErrorBoundary>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<ErrorBoundary>
|
||||
<SyncProvider sdk={opencodeClient.getSdkClient()} directory={currentDirectory || ''}>
|
||||
<RuntimeAPIProvider apis={apis}>
|
||||
<FireworksProvider>
|
||||
<TooltipProvider delayDuration={300} skipDelayDuration={150}>
|
||||
<div className="h-full text-foreground bg-background">
|
||||
<SyncAppEffects embeddedBackgroundWorkEnabled={true} />
|
||||
<VSCodeLayout />
|
||||
<Toaster />
|
||||
</div>
|
||||
</TooltipProvider>
|
||||
</FireworksProvider>
|
||||
</RuntimeAPIProvider>
|
||||
</SyncProvider>
|
||||
</ErrorBoundary>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
import { StrictMode } from 'react';
|
||||
import { createRoot } from 'react-dom/client';
|
||||
import '@/styles/fonts';
|
||||
import '@/index.css';
|
||||
import '@/lib/debug';
|
||||
import { SessionAuthGate } from '@/components/auth/SessionAuthGate';
|
||||
import { ThemeProvider } from '@/components/providers/ThemeProvider';
|
||||
import { ThemeSystemProvider } from '@/contexts/ThemeSystemContext';
|
||||
import type { RuntimeAPIs } from '@/lib/api/types';
|
||||
import { startAppearanceAutoSave } from '@/lib/appearanceAutoSave';
|
||||
import { applyPersistedDirectoryPreferences } from '@/lib/directoryPersistence';
|
||||
import { initializeLocale, I18nProvider } from '@/lib/i18n';
|
||||
import { initializeAppearancePreferences, syncDesktopSettings } from '@/lib/persistence';
|
||||
import { startModelPrefsAutoSave } from '@/lib/modelPrefsAutoSave';
|
||||
import { startTypographyWatcher } from '@/lib/typographyWatcher';
|
||||
import { VSCodeApp } from './VSCodeApp';
|
||||
|
||||
const initializeSharedPreferences = () => {
|
||||
initializeLocale();
|
||||
|
||||
void initializeAppearancePreferences().then(() => {
|
||||
void Promise.all([
|
||||
syncDesktopSettings(),
|
||||
applyPersistedDirectoryPreferences(),
|
||||
]).catch((err) => {
|
||||
console.error('[vscode-main] settings init failed:', err);
|
||||
});
|
||||
|
||||
startAppearanceAutoSave();
|
||||
startModelPrefsAutoSave();
|
||||
startTypographyWatcher();
|
||||
}).catch((err) => {
|
||||
console.error('[vscode-main] appearance init failed:', err);
|
||||
});
|
||||
};
|
||||
|
||||
export function renderVSCodeApp(apis: RuntimeAPIs) {
|
||||
initializeSharedPreferences();
|
||||
|
||||
const rootElement = document.getElementById('root');
|
||||
if (!rootElement) {
|
||||
throw new Error('Root element not found');
|
||||
}
|
||||
|
||||
createRoot(rootElement).render(
|
||||
<StrictMode>
|
||||
<I18nProvider>
|
||||
<ThemeSystemProvider>
|
||||
<ThemeProvider>
|
||||
<SessionAuthGate>
|
||||
<VSCodeApp apis={apis} />
|
||||
</SessionAuthGate>
|
||||
</ThemeProvider>
|
||||
</ThemeSystemProvider>
|
||||
</I18nProvider>
|
||||
</StrictMode>,
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
import React from 'react';
|
||||
import { useFontPreferences } from '@/hooks/useFontPreferences';
|
||||
import { CODE_FONT_OPTION_MAP, DEFAULT_MONO_FONT, DEFAULT_UI_FONT, UI_FONT_OPTION_MAP } from '@/lib/fontOptions';
|
||||
import { loadMonoFont, loadUiFont } from '@/lib/fontLoader';
|
||||
|
||||
export function useAppFontEffects() {
|
||||
const { uiFont, monoFont } = useFontPreferences();
|
||||
|
||||
React.useEffect(() => {
|
||||
if (typeof document === 'undefined') {
|
||||
return;
|
||||
}
|
||||
|
||||
const root = document.documentElement;
|
||||
const uiStack = UI_FONT_OPTION_MAP[uiFont]?.stack ?? UI_FONT_OPTION_MAP[DEFAULT_UI_FONT].stack;
|
||||
const monoStack = CODE_FONT_OPTION_MAP[monoFont]?.stack ?? CODE_FONT_OPTION_MAP[DEFAULT_MONO_FONT].stack;
|
||||
void loadUiFont(uiFont);
|
||||
void loadMonoFont(monoFont);
|
||||
|
||||
root.style.setProperty('--font-sans', uiStack);
|
||||
root.style.setProperty('--font-heading', uiStack);
|
||||
root.style.setProperty('--font-family-sans', uiStack);
|
||||
root.style.setProperty('--font-mono', monoStack);
|
||||
root.style.setProperty('--font-family-mono', monoStack);
|
||||
root.style.setProperty('--ui-regular-font-weight', '400');
|
||||
|
||||
if (document.body) {
|
||||
document.body.style.fontFamily = uiStack;
|
||||
}
|
||||
}, [uiFont, monoFont]);
|
||||
}
|
||||
@@ -60,7 +60,7 @@ const bootstrapConnectionStatus = () => {
|
||||
|
||||
bootstrapConnectionStatus();
|
||||
|
||||
// Expose panel type globally for App.tsx to conditionally render
|
||||
// Expose panel type globally for the VS Code app root to conditionally render.
|
||||
window.__OPENCHAMBER_PANEL_TYPE__ = (window.__VSCODE_CONFIG__?.panelType as PanelType) || 'chat';
|
||||
|
||||
const handleConnectionMessage = (event: MessageEvent) => {
|
||||
@@ -1242,8 +1242,9 @@ onCommand('activeEditorFile', (payload) => {
|
||||
});
|
||||
});
|
||||
|
||||
import('@/main')
|
||||
.then(async () => {
|
||||
import('@openchamber/ui/apps/renderVSCodeApp')
|
||||
.then(async ({ renderVSCodeApp }) => {
|
||||
renderVSCodeApp(window.__OPENCHAMBER_RUNTIME_APIS__ ?? createVSCodeAPIs());
|
||||
await waitForUiMount();
|
||||
uiMounted = true;
|
||||
maybeHideLoadingOverlay();
|
||||
|
||||
Reference in New Issue
Block a user