import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import '@/styles/fonts';
import '@/index.css';
import '@/lib/debug';
import { DiffWorkerProvider } from '@/contexts/DiffWorkerProvider';
import { ThemeProvider } from '@/components/providers/ThemeProvider';
import { ThemeSystemProvider } from '@/contexts/ThemeSystemContext';
import type { RuntimeAPIs } from '@/lib/api/types';
import { startAppearanceAutoSave } from '@/lib/appearanceAutoSave';
import { getDeviceInfo } from '@/lib/device';
import { markAppBootReady } from './appBootReady';
import { installMobileWidgetSnapshotBridge } from './mobileWidgetSnapshot';
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 { preloadMarkdownRenderer } from '@/components/chat/markdownRendererLoader';
import { SessionAuthGate } from '@/components/auth/SessionAuthGate';
import { MobileApp } from './MobileApp';
const initializeSharedPreferences = () => {
initializeLocale();
void initializeAppearancePreferences().then(() => {
void Promise.all([
syncDesktopSettings(),
applyPersistedDirectoryPreferences(),
]).catch((err) => {
console.error('[mobile-main] settings init failed:', err);
});
startAppearanceAutoSave();
startModelPrefsAutoSave();
startTypographyWatcher();
}).catch((err) => {
console.error('[mobile-main] appearance init failed:', err);
}).finally(() => {
// Persisted typography/appearance is now applied — release the splash gate so the
// first UI paint is already at its final sizes.
markAppBootReady();
});
};
export function renderMobileApp(apis: RuntimeAPIs) {
// Stamp the surface before anything else reads it: perf tuning, sync paging,
// and device info all key off isMobileSurfaceRuntime(), and without the stamp
// a wide native device (iPad landscape) would fall out of the mobile branch.
window.__OPENCHAMBER_SURFACE__ = 'mobile';
preloadMarkdownRenderer();
initializeSharedPreferences();
// Expose the widget snapshot builder so the native shell can read the session overview
// (attention count + recent sessions) and feed the home/lock-screen/Control Center widgets.
installMobileWidgetSnapshotBridge();
// Apply the device classes (`device-mobile`, `mobile-pointer`) to BEFORE the
// first React paint. They gate the mobile typography rules in mobile.css (larger
// --text-* sizes); applied late from a hook effect, they bumped text size a frame
// after mount and shifted the layout (connect / scan / saved-connection labels).
getDeviceInfo();
const rootElement = document.getElementById('root');
if (!rootElement) {
throw new Error('Root element not found');
}
// The native Capacitor app delivers notifications via APNs only (background, server-side
// focus-gated). Disable the in-app notification dispatch on native with a no-op
// notifications API: scheduling local notifications can't tell foreground from background
// in a WKWebView and leaked while the app was open. (The Web Notifications API the web
// runtime uses also doesn't display inside a WKWebView.)
const capacitor = (window as typeof window & { Capacitor?: { isNativePlatform?: () => boolean } }).Capacitor;
const isNativeShell = capacitor?.isNativePlatform?.() === true || window.location.protocol === 'capacitor:';
const resolvedApis = isNativeShell
? { ...apis, notifications: { notifyAgentCompletion: async () => false, canNotify: () => false } }
: apis;
// Auth gating differs by shell: the native Capacitor app authenticates via
// its own instance-connect flow (MobileConnectionWelcome asks for the
// password per instance), while the plain mobile BROWSER against a
// --ui-password server must keep the classic SessionAuthGate unlock page.
const app = ;
createRoot(rootElement).render(
{isNativeShell ? app : {app}}
,
);
}