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:
Bohdan Triapitsyn
2026-05-07 01:31:17 +03:00
parent c902dea89e
commit 4cc2f1bff6
6 changed files with 267 additions and 120 deletions
+37
View File
@@ -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 />;
}
+134
View File
@@ -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>
);
}
+58
View File
@@ -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>,
);
}
+31
View File
@@ -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]);
}