diff --git a/packages/ui/src/components/instance/instance-shell2.tsx b/packages/ui/src/components/instance/instance-shell2.tsx index d8407b1f..8ac7858d 100644 --- a/packages/ui/src/components/instance/instance-shell2.tsx +++ b/packages/ui/src/components/instance/instance-shell2.tsx @@ -36,7 +36,6 @@ import { useI18n } from "../../lib/i18n" import { getPermissionQueueLength, getQuestionQueueLength } from "../../stores/instances" import SessionSidebar from "./shell/SessionSidebar" import { useSessionSidebarRequests } from "./shell/useSessionSidebarRequests" -import RightPanel from "./shell/right-panel/RightPanel" import { useDrawerChrome } from "./shell/useDrawerChrome" import { getSessionStatus } from "../../stores/session-status" import { Maximize2, ShieldAlert } from "lucide-solid" @@ -64,6 +63,11 @@ const LazyBackgroundProcessOutputDialog = lazy(() => import("../background-process-output-dialog").then((module) => ({ default: module.BackgroundProcessOutputDialog })), ) const LazyPermissionApprovalModal = lazy(() => import("../permission-approval-modal")) +const LazyRightPanel = lazy(() => import("./shell/right-panel/RightPanel")) + +function RightPanelFallback() { + return
+} interface InstanceShellProps { instance: Instance @@ -499,28 +503,30 @@ const InstanceShell2: Component = (props) => { role="presentation" aria-hidden="true" /> - + }> + + ) } @@ -560,28 +566,32 @@ const InstanceShell2: Component = (props) => { aria-hidden="true" /> - + }> + }> + + + ) diff --git a/packages/ui/src/components/instance/shell/useSessionCache.ts b/packages/ui/src/components/instance/shell/useSessionCache.ts index 0e3a3476..8839d569 100644 --- a/packages/ui/src/components/instance/shell/useSessionCache.ts +++ b/packages/ui/src/components/instance/shell/useSessionCache.ts @@ -2,10 +2,30 @@ import { createEffect, createSignal, type Accessor } from "solid-js" import { messageStoreBus } from "../../../stores/message-v2/bus" import { clearSessionRenderCache } from "../../message-block" import { getLogger } from "../../../lib/logger" +import { runtimeEnv } from "../../../lib/runtime-env" const log = getLogger("session") -const SESSION_CACHE_LIMIT = 5 +function getSessionCacheLimit() { + if (runtimeEnv.platform === "mobile") { + return 2 + } + + if (runtimeEnv.host === "tauri") { + return 3 + } + + if (typeof navigator !== "undefined") { + const deviceMemory = (navigator as Navigator & { deviceMemory?: number }).deviceMemory + if (typeof deviceMemory === "number" && deviceMemory <= 4) { + return 3 + } + } + + return 5 +} + +const SESSION_CACHE_LIMIT = getSessionCacheLimit() type SessionCacheOptions = { instanceId: Accessor diff --git a/packages/ui/src/components/session/session-view.tsx b/packages/ui/src/components/session/session-view.tsx index ba78382c..b234b09b 100644 --- a/packages/ui/src/components/session/session-view.tsx +++ b/packages/ui/src/components/session/session-view.tsx @@ -131,9 +131,15 @@ export const SessionView: Component = (props) => { createEffect(() => { const currentSession = session() - if (currentSession) { - loadMessages(props.instanceId, currentSession.id).catch((error) => log.error("Failed to load messages", error)) + if (!currentSession) { + return } + + if (props.isActive === false) { + return + } + + loadMessages(props.instanceId, currentSession.id).catch((error) => log.error("Failed to load messages", error)) }) function registerPromptInputApi(api: PromptInputApi) { diff --git a/packages/ui/src/lib/ui-bootstrap-cache.ts b/packages/ui/src/lib/ui-bootstrap-cache.ts new file mode 100644 index 00000000..422d3887 --- /dev/null +++ b/packages/ui/src/lib/ui-bootstrap-cache.ts @@ -0,0 +1,48 @@ +export type UiBootstrapTheme = "light" | "dark" | "system" + +export interface UiBootstrapCacheSnapshot { + theme?: UiBootstrapTheme + locale?: string +} + +const UI_BOOTSTRAP_CACHE_KEY = "codenomad:ui-bootstrap" + +export function readUiBootstrapCache(): UiBootstrapCacheSnapshot { + if (typeof window === "undefined" || typeof window.localStorage === "undefined") { + return {} + } + + try { + const raw = window.localStorage.getItem(UI_BOOTSTRAP_CACHE_KEY) + if (!raw) { + return {} + } + + const parsed = JSON.parse(raw) as UiBootstrapCacheSnapshot + if (!parsed || typeof parsed !== "object") { + return {} + } + + return { + theme: + parsed.theme === "light" || parsed.theme === "dark" || parsed.theme === "system" + ? parsed.theme + : undefined, + locale: typeof parsed.locale === "string" ? parsed.locale : undefined, + } + } catch { + return {} + } +} + +export function writeUiBootstrapCache(snapshot: UiBootstrapCacheSnapshot) { + if (typeof window === "undefined" || typeof window.localStorage === "undefined") { + return + } + + try { + window.localStorage.setItem(UI_BOOTSTRAP_CACHE_KEY, JSON.stringify(snapshot)) + } catch { + /* noop */ + } +} diff --git a/packages/ui/src/main.tsx b/packages/ui/src/main.tsx index cc6c6f1e..6ddc40e1 100644 --- a/packages/ui/src/main.tsx +++ b/packages/ui/src/main.tsx @@ -5,7 +5,7 @@ import { ConfigProvider } from "./stores/preferences" import { InstanceConfigProvider } from "./stores/instance-config" import { runtimeEnv } from "./lib/runtime-env" import { I18nProvider, preloadLocaleMessages } from "./lib/i18n" -import { storage } from "./lib/storage" +import { readUiBootstrapCache } from "./lib/ui-bootstrap-cache" import "./index.css" import "@git-diff-view/solid/styles/diff-view-pure.css" @@ -29,22 +29,16 @@ async function bootstrap() { // (and then refine once persisted config loads). document.documentElement.removeAttribute("data-theme") - try { - const uiConfig = await storage.loadConfigOwner("ui") - const theme = (uiConfig as any)?.theme ?? "system" - const locale = (uiConfig as any)?.settings?.locale + const bootstrapCache = readUiBootstrapCache() + const theme = bootstrapCache.theme ?? "system" - if (theme === "system") { - document.documentElement.removeAttribute("data-theme") - } else { - document.documentElement.setAttribute("data-theme", theme) - } - - await preloadLocaleMessages(typeof locale === "string" ? locale : undefined) - } catch { - // If config fails to load, fall back to CSS defaults. - await preloadLocaleMessages(undefined) + if (theme === "system") { + document.documentElement.removeAttribute("data-theme") + } else { + document.documentElement.setAttribute("data-theme", theme) } + + await preloadLocaleMessages(bootstrapCache.locale) } render( diff --git a/packages/ui/src/stores/preferences.tsx b/packages/ui/src/stores/preferences.tsx index 8ac2ead0..442adfca 100644 --- a/packages/ui/src/stores/preferences.tsx +++ b/packages/ui/src/stores/preferences.tsx @@ -1,6 +1,7 @@ -import { createContext, createMemo, createSignal, onMount, useContext } from "solid-js" +import { createContext, createEffect, createMemo, createSignal, onMount, useContext } from "solid-js" import type { Accessor, ParentComponent } from "solid-js" import { storage, type OwnerBucket } from "../lib/storage" +import { writeUiBootstrapCache } from "../lib/ui-bootstrap-cache" import { ensureInstanceConfigLoaded, getInstanceConfig, @@ -598,6 +599,14 @@ const configContextValue: ConfigContextValue = { } export const ConfigProvider: ParentComponent = (props) => { + createEffect(() => { + const bucket = uiConfigBucket() + writeUiBootstrapCache({ + theme: bucket.theme, + locale: bucket.settings?.locale, + }) + }) + onMount(() => { ensureLoaded().catch((error: unknown) => { log.error("Failed to initialize settings", error)