perf(ui): trim hidden session and bootstrap work
This commit is contained in:
@@ -36,7 +36,6 @@ import { useI18n } from "../../lib/i18n"
|
|||||||
import { getPermissionQueueLength, getQuestionQueueLength } from "../../stores/instances"
|
import { getPermissionQueueLength, getQuestionQueueLength } from "../../stores/instances"
|
||||||
import SessionSidebar from "./shell/SessionSidebar"
|
import SessionSidebar from "./shell/SessionSidebar"
|
||||||
import { useSessionSidebarRequests } from "./shell/useSessionSidebarRequests"
|
import { useSessionSidebarRequests } from "./shell/useSessionSidebarRequests"
|
||||||
import RightPanel from "./shell/right-panel/RightPanel"
|
|
||||||
import { useDrawerChrome } from "./shell/useDrawerChrome"
|
import { useDrawerChrome } from "./shell/useDrawerChrome"
|
||||||
import { getSessionStatus } from "../../stores/session-status"
|
import { getSessionStatus } from "../../stores/session-status"
|
||||||
import { Maximize2, ShieldAlert } from "lucide-solid"
|
import { Maximize2, ShieldAlert } from "lucide-solid"
|
||||||
@@ -64,6 +63,11 @@ const LazyBackgroundProcessOutputDialog = lazy(() =>
|
|||||||
import("../background-process-output-dialog").then((module) => ({ default: module.BackgroundProcessOutputDialog })),
|
import("../background-process-output-dialog").then((module) => ({ default: module.BackgroundProcessOutputDialog })),
|
||||||
)
|
)
|
||||||
const LazyPermissionApprovalModal = lazy(() => import("../permission-approval-modal"))
|
const LazyPermissionApprovalModal = lazy(() => import("../permission-approval-modal"))
|
||||||
|
const LazyRightPanel = lazy(() => import("./shell/right-panel/RightPanel"))
|
||||||
|
|
||||||
|
function RightPanelFallback() {
|
||||||
|
return <div class="flex-1 min-h-0" />
|
||||||
|
}
|
||||||
|
|
||||||
interface InstanceShellProps {
|
interface InstanceShellProps {
|
||||||
instance: Instance
|
instance: Instance
|
||||||
@@ -499,28 +503,30 @@ const InstanceShell2: Component<InstanceShellProps> = (props) => {
|
|||||||
role="presentation"
|
role="presentation"
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
/>
|
/>
|
||||||
<RightPanel
|
<Suspense fallback={<RightPanelFallback />}>
|
||||||
t={t}
|
<LazyRightPanel
|
||||||
instanceId={props.instance.id}
|
t={t}
|
||||||
instance={props.instance}
|
instanceId={props.instance.id}
|
||||||
activeSessionId={activeSessionIdForInstance}
|
instance={props.instance}
|
||||||
activeSession={activeSessionForInstance}
|
activeSessionId={activeSessionIdForInstance}
|
||||||
activeSessionDiffs={activeSessionDiffs}
|
activeSession={activeSessionForInstance}
|
||||||
latestTodoState={latestTodoState}
|
activeSessionDiffs={activeSessionDiffs}
|
||||||
backgroundProcessList={backgroundProcessList}
|
latestTodoState={latestTodoState}
|
||||||
onOpenBackgroundOutput={openBackgroundOutput}
|
backgroundProcessList={backgroundProcessList}
|
||||||
onStopBackgroundProcess={stopBackgroundProcess}
|
onOpenBackgroundOutput={openBackgroundOutput}
|
||||||
onTerminateBackgroundProcess={terminateBackgroundProcess}
|
onStopBackgroundProcess={stopBackgroundProcess}
|
||||||
isPhoneLayout={isPhoneLayout}
|
onTerminateBackgroundProcess={terminateBackgroundProcess}
|
||||||
rightDrawerWidth={rightDrawerWidth}
|
isPhoneLayout={isPhoneLayout}
|
||||||
rightDrawerWidthInitialized={rightDrawerWidthInitialized}
|
rightDrawerWidth={rightDrawerWidth}
|
||||||
rightDrawerState={rightDrawerState}
|
rightDrawerWidthInitialized={rightDrawerWidthInitialized}
|
||||||
rightPinned={rightPinned}
|
rightDrawerState={rightDrawerState}
|
||||||
onCloseRightDrawer={closeRightDrawer}
|
rightPinned={rightPinned}
|
||||||
onPinRightDrawer={pinRightDrawer}
|
onCloseRightDrawer={closeRightDrawer}
|
||||||
onUnpinRightDrawer={unpinRightDrawer}
|
onPinRightDrawer={pinRightDrawer}
|
||||||
setContentEl={setRightDrawerContentEl}
|
onUnpinRightDrawer={unpinRightDrawer}
|
||||||
/>
|
setContentEl={setRightDrawerContentEl}
|
||||||
|
/>
|
||||||
|
</Suspense>
|
||||||
</Box>
|
</Box>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -560,28 +566,32 @@ const InstanceShell2: Component<InstanceShellProps> = (props) => {
|
|||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
/>
|
/>
|
||||||
</Show>
|
</Show>
|
||||||
<RightPanel
|
<Show when={rightOpen() || rightPinned()} fallback={<RightPanelFallback />}>
|
||||||
t={t}
|
<Suspense fallback={<RightPanelFallback />}>
|
||||||
instanceId={props.instance.id}
|
<LazyRightPanel
|
||||||
instance={props.instance}
|
t={t}
|
||||||
activeSessionId={activeSessionIdForInstance}
|
instanceId={props.instance.id}
|
||||||
activeSession={activeSessionForInstance}
|
instance={props.instance}
|
||||||
activeSessionDiffs={activeSessionDiffs}
|
activeSessionId={activeSessionIdForInstance}
|
||||||
latestTodoState={latestTodoState}
|
activeSession={activeSessionForInstance}
|
||||||
backgroundProcessList={backgroundProcessList}
|
activeSessionDiffs={activeSessionDiffs}
|
||||||
onOpenBackgroundOutput={openBackgroundOutput}
|
latestTodoState={latestTodoState}
|
||||||
onStopBackgroundProcess={stopBackgroundProcess}
|
backgroundProcessList={backgroundProcessList}
|
||||||
onTerminateBackgroundProcess={terminateBackgroundProcess}
|
onOpenBackgroundOutput={openBackgroundOutput}
|
||||||
isPhoneLayout={isPhoneLayout}
|
onStopBackgroundProcess={stopBackgroundProcess}
|
||||||
rightDrawerWidth={rightDrawerWidth}
|
onTerminateBackgroundProcess={terminateBackgroundProcess}
|
||||||
rightDrawerWidthInitialized={rightDrawerWidthInitialized}
|
isPhoneLayout={isPhoneLayout}
|
||||||
rightDrawerState={rightDrawerState}
|
rightDrawerWidth={rightDrawerWidth}
|
||||||
rightPinned={rightPinned}
|
rightDrawerWidthInitialized={rightDrawerWidthInitialized}
|
||||||
onCloseRightDrawer={closeRightDrawer}
|
rightDrawerState={rightDrawerState}
|
||||||
onPinRightDrawer={pinRightDrawer}
|
rightPinned={rightPinned}
|
||||||
onUnpinRightDrawer={unpinRightDrawer}
|
onCloseRightDrawer={closeRightDrawer}
|
||||||
setContentEl={setRightDrawerContentEl}
|
onPinRightDrawer={pinRightDrawer}
|
||||||
/>
|
onUnpinRightDrawer={unpinRightDrawer}
|
||||||
|
setContentEl={setRightDrawerContentEl}
|
||||||
|
/>
|
||||||
|
</Suspense>
|
||||||
|
</Show>
|
||||||
</Drawer>
|
</Drawer>
|
||||||
|
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -2,10 +2,30 @@ import { createEffect, createSignal, type Accessor } from "solid-js"
|
|||||||
import { messageStoreBus } from "../../../stores/message-v2/bus"
|
import { messageStoreBus } from "../../../stores/message-v2/bus"
|
||||||
import { clearSessionRenderCache } from "../../message-block"
|
import { clearSessionRenderCache } from "../../message-block"
|
||||||
import { getLogger } from "../../../lib/logger"
|
import { getLogger } from "../../../lib/logger"
|
||||||
|
import { runtimeEnv } from "../../../lib/runtime-env"
|
||||||
|
|
||||||
const log = getLogger("session")
|
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 = {
|
type SessionCacheOptions = {
|
||||||
instanceId: Accessor<string>
|
instanceId: Accessor<string>
|
||||||
|
|||||||
@@ -131,9 +131,15 @@ export const SessionView: Component<SessionViewProps> = (props) => {
|
|||||||
|
|
||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
const currentSession = session()
|
const currentSession = session()
|
||||||
if (currentSession) {
|
if (!currentSession) {
|
||||||
loadMessages(props.instanceId, currentSession.id).catch((error) => log.error("Failed to load messages", error))
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (props.isActive === false) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
loadMessages(props.instanceId, currentSession.id).catch((error) => log.error("Failed to load messages", error))
|
||||||
})
|
})
|
||||||
|
|
||||||
function registerPromptInputApi(api: PromptInputApi) {
|
function registerPromptInputApi(api: PromptInputApi) {
|
||||||
|
|||||||
48
packages/ui/src/lib/ui-bootstrap-cache.ts
Normal file
48
packages/ui/src/lib/ui-bootstrap-cache.ts
Normal file
@@ -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 */
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,7 +5,7 @@ import { ConfigProvider } from "./stores/preferences"
|
|||||||
import { InstanceConfigProvider } from "./stores/instance-config"
|
import { InstanceConfigProvider } from "./stores/instance-config"
|
||||||
import { runtimeEnv } from "./lib/runtime-env"
|
import { runtimeEnv } from "./lib/runtime-env"
|
||||||
import { I18nProvider, preloadLocaleMessages } from "./lib/i18n"
|
import { I18nProvider, preloadLocaleMessages } from "./lib/i18n"
|
||||||
import { storage } from "./lib/storage"
|
import { readUiBootstrapCache } from "./lib/ui-bootstrap-cache"
|
||||||
import "./index.css"
|
import "./index.css"
|
||||||
import "@git-diff-view/solid/styles/diff-view-pure.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).
|
// (and then refine once persisted config loads).
|
||||||
document.documentElement.removeAttribute("data-theme")
|
document.documentElement.removeAttribute("data-theme")
|
||||||
|
|
||||||
try {
|
const bootstrapCache = readUiBootstrapCache()
|
||||||
const uiConfig = await storage.loadConfigOwner("ui")
|
const theme = bootstrapCache.theme ?? "system"
|
||||||
const theme = (uiConfig as any)?.theme ?? "system"
|
|
||||||
const locale = (uiConfig as any)?.settings?.locale
|
|
||||||
|
|
||||||
if (theme === "system") {
|
if (theme === "system") {
|
||||||
document.documentElement.removeAttribute("data-theme")
|
document.documentElement.removeAttribute("data-theme")
|
||||||
} else {
|
} else {
|
||||||
document.documentElement.setAttribute("data-theme", theme)
|
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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await preloadLocaleMessages(bootstrapCache.locale)
|
||||||
}
|
}
|
||||||
|
|
||||||
render(
|
render(
|
||||||
|
|||||||
@@ -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 type { Accessor, ParentComponent } from "solid-js"
|
||||||
import { storage, type OwnerBucket } from "../lib/storage"
|
import { storage, type OwnerBucket } from "../lib/storage"
|
||||||
|
import { writeUiBootstrapCache } from "../lib/ui-bootstrap-cache"
|
||||||
import {
|
import {
|
||||||
ensureInstanceConfigLoaded,
|
ensureInstanceConfigLoaded,
|
||||||
getInstanceConfig,
|
getInstanceConfig,
|
||||||
@@ -598,6 +599,14 @@ const configContextValue: ConfigContextValue = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const ConfigProvider: ParentComponent = (props) => {
|
export const ConfigProvider: ParentComponent = (props) => {
|
||||||
|
createEffect(() => {
|
||||||
|
const bucket = uiConfigBucket()
|
||||||
|
writeUiBootstrapCache({
|
||||||
|
theme: bucket.theme,
|
||||||
|
locale: bucket.settings?.locale,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
ensureLoaded().catch((error: unknown) => {
|
ensureLoaded().catch((error: unknown) => {
|
||||||
log.error("Failed to initialize settings", error)
|
log.error("Failed to initialize settings", error)
|
||||||
|
|||||||
Reference in New Issue
Block a user