diff --git a/packages/ui/src/lib/storage.ts b/packages/ui/src/lib/storage.ts index 569c4e0e..4616bc94 100644 --- a/packages/ui/src/lib/storage.ts +++ b/packages/ui/src/lib/storage.ts @@ -4,7 +4,6 @@ import { serverEvents } from "./server-events" import { getLogger } from "./logger" const log = getLogger("actions") -const UI_CONFIG_CACHE_KEY = "codenomad:config-owner:ui" export type OwnerBucket = Record @@ -58,23 +57,6 @@ export class ServerStorage { }) } - readCachedConfigOwner(owner: string): OwnerBucket | undefined { - if (owner !== "ui" || typeof window === "undefined" || !window.localStorage) { - return undefined - } - - try { - const raw = window.localStorage.getItem(UI_CONFIG_CACHE_KEY) - if (!raw) { - return undefined - } - const parsed = JSON.parse(raw) as OwnerBucket - return parsed && typeof parsed === "object" ? parsed : undefined - } catch { - return undefined - } - } - async loadConfigOwner(owner: string): Promise { const cached = this.configOwnerCache.get(owner) if (cached) return cached @@ -236,9 +218,6 @@ export class ServerStorage { return } cache.set(owner, value) - if (kind === "config" && owner === "ui") { - this.persistUiConfigCache(value) - } const bucket = listeners.get(owner) if (!bucket) return for (const listener of bucket) { @@ -246,18 +225,6 @@ export class ServerStorage { } } - private persistUiConfigCache(value: OwnerBucket) { - if (typeof window === "undefined" || !window.localStorage) { - return - } - - try { - window.localStorage.setItem(UI_CONFIG_CACHE_KEY, JSON.stringify(value)) - } catch { - /* noop */ - } - } - private normalizeInstanceData(data?: InstanceData | null): InstanceData { const source = data ?? DEFAULT_INSTANCE_DATA const messageHistory = Array.isArray(source.messageHistory) ? [...source.messageHistory] : [] diff --git a/packages/ui/src/lib/ui-config-bootstrap-sync.tsx b/packages/ui/src/lib/ui-config-bootstrap-sync.tsx new file mode 100644 index 00000000..2df1f97e --- /dev/null +++ b/packages/ui/src/lib/ui-config-bootstrap-sync.tsx @@ -0,0 +1,28 @@ +import { createEffect } from "solid-js" +import { useGlobalCache } from "./hooks/use-global-cache" +import { useConfig } from "../stores/preferences" +import type { UiBootstrapConfig } from "./ui-config-bootstrap" + +export function UiConfigBootstrapSync() { + const { isLoaded, preferences, themePreference } = useConfig() + const cache = useGlobalCache({ + scope: "ui-bootstrap", + cacheId: "ui-config", + version: "1", + }) + + createEffect(() => { + if (!isLoaded()) { + return + } + + const next: UiBootstrapConfig = { + theme: themePreference(), + locale: preferences().locale, + } + + cache.set(next) + }) + + return null +} diff --git a/packages/ui/src/lib/ui-config-bootstrap.ts b/packages/ui/src/lib/ui-config-bootstrap.ts new file mode 100644 index 00000000..e8f483a8 --- /dev/null +++ b/packages/ui/src/lib/ui-config-bootstrap.ts @@ -0,0 +1,20 @@ +import { getCacheEntry } from "./global-cache" + +export interface UiBootstrapConfig { + theme?: "light" | "dark" | "system" + locale?: string +} + +const UI_BOOTSTRAP_CACHE_ENTRY = { + scope: "ui-bootstrap", + cacheId: "ui-config", + version: "1", +} as const + +export function readUiBootstrapConfig(): UiBootstrapConfig { + return getCacheEntry(UI_BOOTSTRAP_CACHE_ENTRY) ?? {} +} + +export function getUiBootstrapCacheEntry() { + return UI_BOOTSTRAP_CACHE_ENTRY +} diff --git a/packages/ui/src/main.tsx b/packages/ui/src/main.tsx index 9fd7c153..72da86dc 100644 --- a/packages/ui/src/main.tsx +++ b/packages/ui/src/main.tsx @@ -6,6 +6,8 @@ import { InstanceConfigProvider } from "./stores/instance-config" import { runtimeEnv } from "./lib/runtime-env" import { I18nProvider, preloadLocaleMessages } from "./lib/i18n" import { storage } from "./lib/storage" +import { readUiBootstrapConfig } from "./lib/ui-config-bootstrap" +import { UiConfigBootstrapSync } from "./lib/ui-config-bootstrap-sync" import "./index.css" import "@git-diff-view/solid/styles/diff-view-pure.css" @@ -29,14 +31,26 @@ async function bootstrap() { // (and then refine once persisted config loads). document.documentElement.removeAttribute("data-theme") - const cachedUiConfig = storage.readCachedConfigOwner("ui") - const theme = - cachedUiConfig?.theme === "light" || cachedUiConfig?.theme === "dark" || cachedUiConfig?.theme === "system" - ? cachedUiConfig.theme - : "system" - const locale = typeof cachedUiConfig?.settings?.locale === "string" ? cachedUiConfig.settings.locale : undefined + const cachedUiConfig = readUiBootstrapConfig() + let theme = cachedUiConfig.theme + let locale = cachedUiConfig.locale - if (theme === "system") { + if (theme === undefined || locale === undefined) { + try { + const uiConfig = await storage.loadConfigOwner("ui") + if (theme === undefined) { + const nextTheme = (uiConfig as any)?.theme + theme = nextTheme === "light" || nextTheme === "dark" || nextTheme === "system" ? nextTheme : undefined + } + if (locale === undefined) { + locale = typeof (uiConfig as any)?.settings?.locale === "string" ? (uiConfig as any).settings.locale : undefined + } + } catch { + // If config fails to load, fall back to CSS defaults. + } + } + + if (!theme || theme === "system") { document.documentElement.removeAttribute("data-theme") } else { document.documentElement.setAttribute("data-theme", theme) @@ -48,6 +62,7 @@ async function bootstrap() { render( () => ( +