diff --git a/packages/ui/src/components/layout/VSCodeLayout.tsx b/packages/ui/src/components/layout/VSCodeLayout.tsx index 0be18d2f..46fa223c 100644 --- a/packages/ui/src/components/layout/VSCodeLayout.tsx +++ b/packages/ui/src/components/layout/VSCodeLayout.tsx @@ -2,8 +2,6 @@ import React from 'react'; import { ErrorBoundary } from '../ui/ErrorBoundary'; import { SessionSidebar } from '@/components/session/SessionSidebar'; import { ChatView } from '@/components/views'; - -const SettingsView = React.lazy(() => import('@/components/views/SettingsView').then(m => ({ default: m.SettingsView }))); import { useSessionUIStore } from '@/sync/session-ui-store'; import { useViewportStore } from '@/sync/viewport-store'; import { useSessions, useDirectorySync } from '@/sync/sync-context'; @@ -29,6 +27,8 @@ import { updateDesktopSettings } from '@/lib/persistence'; import type { UsageWindow } from '@/types'; import { RiAddLine, RiArrowLeftLine, RiRefreshLine, RiRobot2Line, RiSettings3Line, RiTimerLine } from '@remixicon/react'; +const SettingsView = React.lazy(() => import('@/components/views/SettingsView').then(m => ({ default: m.SettingsView }))); + const formatTime = (timestamp: number | null) => { if (!timestamp) return '-'; try { diff --git a/packages/ui/src/main.tsx b/packages/ui/src/main.tsx index f7af904b..9128e569 100644 --- a/packages/ui/src/main.tsx +++ b/packages/ui/src/main.tsx @@ -24,20 +24,22 @@ const runtimeAPIs = (typeof window !== 'undefined' && window.__OPENCHAMBER_RUNTI throw new Error('Runtime APIs not provided for legacy UI entrypoint.'); })(); -// Keep appearance preferences blocking to avoid FOUC (flash of -// unstyled content) for users with non-default themes. Defer the -// remaining settings so they don't block first paint. +// Initialize settings asynchronously — the app renders with defaults first +// and hydrates once persisted preferences are applied. Users with non-default +// themes may briefly see default appearance on cold start; accepted trade-off +// for faster time-to-first-paint. void initializeAppearancePreferences().then(() => { void Promise.all([ syncDesktopSettings(), applyPersistedDirectoryPreferences(), - ]).then(() => { - startAppearanceAutoSave(); - startModelPrefsAutoSave(); - startTypographyWatcher(); - }).catch((err) => { + ]).catch((err) => { console.error('[main] settings init failed:', err); }); + + // Start watchers regardless of whether secondary settings succeed. + startAppearanceAutoSave(); + startModelPrefsAutoSave(); + startTypographyWatcher(); }).catch((err) => { console.error('[main] appearance init failed:', err); }); diff --git a/packages/ui/src/sync/bootstrap.ts b/packages/ui/src/sync/bootstrap.ts index 4608381a..be3a4f8a 100644 --- a/packages/ui/src/sync/bootstrap.ts +++ b/packages/ui/src/sync/bootstrap.ts @@ -168,9 +168,11 @@ export async function bootstrapDirectory(input: { .filter((r): r is PromiseRejectedResult => r.status === "rejected") .map((r) => r.reason) - // path.get (index 3) and session.status (index 4) have no global-state - // fallback. If either fails, the UI cannot safely advance to "complete". - const criticalPhase1Failed = phase1Results[3].status === "rejected" || phase1Results[4].status === "rejected" + // path.get and session.status have no global-state fallback. + // If either fails, the UI cannot safely advance to "complete". + const [, , , pathResult, sessionStatusResult] = phase1Results + const criticalPhase1Failed = + pathResult.status === "rejected" || sessionStatusResult.status === "rejected" if (phase1Errors.length === phase1Results.length || criticalPhase1Failed) { console.error(`[bootstrap] directory bootstrap failed for ${directory}`, phase1Errors[0])