From efa033fe643170e8a916b85f68aaef681b70fc48 Mon Sep 17 00:00:00 2001 From: lyx-tec Date: Thu, 16 Jul 2026 20:11:52 +0800 Subject: [PATCH] refactor: clarify state ownership - promote windowId to explicit lifecycle atom --- frontend/app/app.tsx | 2 +- frontend/app/store/global-atoms.ts | 12 +++++++++++- frontend/app/store/global-model.ts | 22 ---------------------- frontend/preview/mock/mockwaveenv.ts | 15 ++++++++++++++- frontend/types/custom.d.ts | 2 ++ frontend/wave.ts | 1 + 6 files changed, 29 insertions(+), 25 deletions(-) diff --git a/frontend/app/app.tsx b/frontend/app/app.tsx index 4829772bc2..62cad4ed23 100644 --- a/frontend/app/app.tsx +++ b/frontend/app/app.tsx @@ -364,7 +364,7 @@ const BadgeAutoClearing = () => { const AppInner = () => { const prefersReducedMotion = useAtomValue(atoms.prefersReducedMotionAtom); const client = useAtomValue(ClientModel.getInstance().clientAtom); - const windowData = useAtomValue(GlobalModel.getInstance().windowDataAtom); + const windowData = useAtomValue(atoms.windowData); const isFullScreen = useAtomValue(atoms.isFullScreen); if (client == null || windowData == null) { diff --git a/frontend/app/store/global-atoms.ts b/frontend/app/store/global-atoms.ts index 6ea9e538fd..f8a16f82d3 100644 --- a/frontend/app/store/global-atoms.ts +++ b/frontend/app/store/global-atoms.ts @@ -17,13 +17,21 @@ function initGlobalAtoms(initOpts: GlobalInitOptions) { setWaveWindowType(initOpts.isPreview ? "preview" : initOpts.builderId != null ? "builder" : "tab"); // Tab views are reused across workspace switches, so reinit updates this to the workspace active tab. const staticTabIdAtom = atom(initOpts.tabId) as PrimitiveAtom; + const windowIdAtom = atom(initOpts.windowId) as PrimitiveAtom; const uiContextAtom = atom((get) => { const uiContext: UIContext = { - windowid: initOpts.windowId, + windowid: get(windowIdAtom), activetabid: get(staticTabIdAtom), }; return uiContext; }) as Atom; + const windowDataAtom: Atom = atom((get) => { + const windowId = get(windowIdAtom); + if (windowId == null) { + return null; + } + return WOS.getObjectValue(WOS.makeORef("window", windowId), get); + }); const isFullScreenAtom = atom(false) as PrimitiveAtom; try { @@ -127,6 +135,8 @@ function initGlobalAtoms(initOpts: GlobalInitOptions) { builderId: builderIdAtom, builderAppId: builderAppIdAtom, uiContext: uiContextAtom, + windowId: windowIdAtom, + windowData: windowDataAtom, workspaceId: workspaceIdAtom, workspace: workspaceAtom, fullConfigAtom, diff --git a/frontend/app/store/global-model.ts b/frontend/app/store/global-model.ts index 05e84e3774..90104e591c 100644 --- a/frontend/app/store/global-model.ts +++ b/frontend/app/store/global-model.ts @@ -1,24 +1,18 @@ // Copyright 2025, Command Line Inc // SPDX-License-Identifier: Apache-2.0 -import * as WOS from "@/app/store/wos"; import { ClientModel } from "@/app/store/client-model"; import { getApi } from "@/store/global"; import * as util from "@/util/util"; -import { atom, Atom } from "jotai"; class GlobalModel { private static instance: GlobalModel; static readonly IsActiveThrottleMs = 5000; - windowId: string; builderId: string; platform: NodeJS.Platform; lastSetIsActiveTs = 0; - windowDataAtom!: Atom; - workspaceAtom!: Atom; - private constructor() { // private constructor for singleton pattern } @@ -32,24 +26,8 @@ class GlobalModel { async initialize(initOpts: GlobalInitOptions): Promise { ClientModel.getInstance().initialize(initOpts.clientId); - this.windowId = initOpts.windowId; this.builderId = initOpts.builderId; this.platform = initOpts.platform; - - this.windowDataAtom = atom((get) => { - if (this.windowId == null) { - return null; - } - return WOS.getObjectValue(WOS.makeORef("window", this.windowId), get); - }); - - this.workspaceAtom = atom((get) => { - const windowData = get(this.windowDataAtom); - if (windowData == null) { - return null; - } - return WOS.getObjectValue(WOS.makeORef("workspace", windowData.workspaceid), get); - }); } setIsActive(): void { diff --git a/frontend/preview/mock/mockwaveenv.ts b/frontend/preview/mock/mockwaveenv.ts index fbce812574..ab659f770b 100644 --- a/frontend/preview/mock/mockwaveenv.ts +++ b/frontend/preview/mock/mockwaveenv.ts @@ -163,10 +163,23 @@ function makeMockGlobalAtoms( } return get(getWaveObjectAtom("workspace:" + wsId)); }); + const windowIdAtomMock = atom(null) as PrimitiveAtom; + const windowDataAtomMock: Atom = atom((get) => { + const windowId = get(windowIdAtomMock); + if (windowId == null) { + return null; + } + return get(getWaveObjectAtom("window:" + windowId)); + }); const defaults: GlobalAtomsType = { builderId: atom(""), builderAppId: atom("") as any, - uiContext: atom({ windowid: "", activetabid: tabId ?? "" } as UIContext), + uiContext: atom((get) => ({ + windowid: get(windowIdAtomMock), + activetabid: tabId ?? "", + })) as Atom, + windowId: windowIdAtomMock, + windowData: windowDataAtomMock, workspaceId: workspaceIdAtom, workspace: workspaceAtom, fullConfigAtom, diff --git a/frontend/types/custom.d.ts b/frontend/types/custom.d.ts index 1179a42435..aa7b057306 100644 --- a/frontend/types/custom.d.ts +++ b/frontend/types/custom.d.ts @@ -11,6 +11,8 @@ declare global { builderId: jotai.Atom; // readonly (for builder mode) builderAppId: jotai.PrimitiveAtom; // app being edited in builder mode uiContext: jotai.Atom; // driven from windowId, tabId + windowId: jotai.PrimitiveAtom; // lifecycle: set by applyWorkspaceContext + windowData: jotai.Atom; // derived from windowId via WOS workspaceId: jotai.PrimitiveAtom; workspace: jotai.Atom; // driven from workspaceId via WOS fullConfigAtom: jotai.PrimitiveAtom; // driven from WOS, settings -- updated via WebSocket diff --git a/frontend/wave.ts b/frontend/wave.ts index 3e625f547d..8e69b9aaad 100644 --- a/frontend/wave.ts +++ b/frontend/wave.ts @@ -148,6 +148,7 @@ async function loadWorkspaceContext(opts: { function applyWorkspaceContext(ctx: WorkspaceContext, opts: { tabContext: "own" | "active" }) { const tabIdForRenderer = opts.tabContext === "active" ? ctx.activeTabId : ctx.ownTabId; + globalStore.set(atoms.windowId, ctx.waveWindow.oid); globalStore.set(atoms.workspaceId, ctx.workspace.oid); globalStore.set(activeTabIdAtom, ctx.activeTabId); globalStore.set(atoms.staticTabId, tabIdForRenderer);