diff --git a/emain/emain-tabview.ts b/emain/emain-tabview.ts
index e4c068bf78..86bb179090 100644
--- a/emain/emain-tabview.ts
+++ b/emain/emain-tabview.ts
@@ -307,7 +307,7 @@ export function clearTabCache() {
// returns [tabview, initialized]
export async function getOrCreateWebViewForTab(waveWindowId: string, tabId: string): Promise<[WaveTabView, boolean]> {
let tabView = getWaveTabView(tabId);
- if (tabView && !tabView.isDestroyed) {
+ if (tabView && !tabView.isDestroyed && !tabView.webContents?.isDestroyed()) {
return [tabView, true];
}
const fullConfig = await RpcApi.GetFullConfigCommand(ElectronWshClient);
@@ -346,7 +346,7 @@ export async function getOrCreateWebViewForTab(waveWindowId: string, tabId: stri
}
}
});
- tabView.webContents.setWindowOpenHandler(({ url, frameName }) => {
+ tabView.webContents.setWindowOpenHandler(({ url }) => {
if (url.startsWith("http://") || url.startsWith("https://") || url.startsWith("file://")) {
console.log("openExternal fallback", url);
shell.openExternal(url);
diff --git a/emain/emain-window.ts b/emain/emain-window.ts
index d4d64e2d4f..15143f9ff7 100644
--- a/emain/emain-window.ts
+++ b/emain/emain-window.ts
@@ -493,6 +493,7 @@ export class WaveBrowserWindow extends BaseWindow {
this.finalizePositioning();
} else {
console.log("reusing an existing tab, calling wave-init", tabView.waveTabId);
+ this.contentView.addChildView(tabView);
tabView.webContents.send("wave-init", tabView.savedInitOpts); // reinit
this.finalizePositioning();
}
@@ -605,10 +606,7 @@ export class WaveBrowserWindow extends BaseWindow {
if (!newWs) {
return;
}
- console.log("processActionQueue switchworkspace newWs", newWs, "keep-alive tabs:", this.allLoadedTabViews.size);
- // Keep BrowserViews alive (don't destroy) — position off-screen instead.
- // They stay in contentView and wcvCache, and will be positioned on-screen
- // when switching back to this workspace (setTabViewIntoWindow → finalizePositioning).
+ console.log("processActionQueue switchworkspace keep-alive tabs:", this.allLoadedTabViews.size);
const curBounds = this.getContentBounds();
for (const tabView of this.allLoadedTabViews.values()) {
tabView.isActiveTab = false;
diff --git a/frontend/app/app.tsx b/frontend/app/app.tsx
index 36c1ba6480..4829772bc2 100644
--- a/frontend/app/app.tsx
+++ b/frontend/app/app.tsx
@@ -43,19 +43,25 @@ const dlog = debug("wave:app");
const focusLog = debug("wave:focus");
const App = ({ onFirstRender }: { onFirstRender: () => void }) => {
+ return (
+
+
+
+ );
+};
+
+const AppContextProviders = ({ onFirstRender }: { onFirstRender: () => void }) => {
const tabId = useAtomValue(atoms.staticTabId);
const waveEnvRef = useRef(makeWaveEnvImpl());
useEffect(() => {
onFirstRender();
}, []);
return (
-
-
-
-
-
-
-
+
+
+
+
+
);
};
diff --git a/frontend/app/store/global-atoms.ts b/frontend/app/store/global-atoms.ts
index 01fe12800e..6ea9e538fd 100644
--- a/frontend/app/store/global-atoms.ts
+++ b/frontend/app/store/global-atoms.ts
@@ -12,14 +12,15 @@ const ConnStatusMapAtom = atom(new Map>());
const orefAtomCache = new Map>>();
function initGlobalAtoms(initOpts: GlobalInitOptions) {
- const windowIdAtom = atom(initOpts.windowId) as PrimitiveAtom;
const builderIdAtom = atom(initOpts.builderId) as PrimitiveAtom;
const builderAppIdAtom = atom(null) as PrimitiveAtom;
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 uiContextAtom = atom((get) => {
const uiContext: UIContext = {
windowid: initOpts.windowId,
- activetabid: initOpts.tabId,
+ activetabid: get(staticTabIdAtom),
};
return uiContext;
}) as Atom;
@@ -43,10 +44,7 @@ function initGlobalAtoms(initOpts: GlobalInitOptions) {
console.log("failed to initialize zoomFactorAtom", e);
}
- const workspaceIdAtom: Atom = atom((get) => {
- const windowData = WOS.getObjectValue(WOS.makeORef("window", get(windowIdAtom)), get);
- return windowData?.workspaceid ?? null;
- });
+ const workspaceIdAtom = atom(null) as PrimitiveAtom;
const workspaceAtom: Atom = atom((get) => {
const workspaceId = get(workspaceIdAtom);
if (workspaceId == null) {
@@ -75,8 +73,6 @@ function initGlobalAtoms(initOpts: GlobalInitOptions) {
const fullConfig = get(fullConfigAtom);
return fullConfig?.configerrors != null && fullConfig.configerrors.length > 0;
}) as Atom;
- // this is *the* tab that this tabview represents. it should never change.
- const staticTabIdAtom: Atom = atom(initOpts.tabId);
const controlShiftDelayAtom = atom(false);
const updaterStatusAtom = atom("up-to-date") as PrimitiveAtom;
try {
diff --git a/frontend/preview/mock/mockwaveenv.ts b/frontend/preview/mock/mockwaveenv.ts
index 123b9d3144..fbce812574 100644
--- a/frontend/preview/mock/mockwaveenv.ts
+++ b/frontend/preview/mock/mockwaveenv.ts
@@ -154,7 +154,8 @@ function makeMockGlobalAtoms(
}
const fullConfigAtom = atom(fullConfig) as PrimitiveAtom;
const settingsAtom = atom((get) => get(fullConfigAtom)?.settings ?? {}) as Atom;
- const workspaceIdAtom: Atom = atomOverrides?.workspaceId ?? (atom(null as string) as Atom);
+ const workspaceIdAtom: PrimitiveAtom =
+ atomOverrides?.workspaceId ?? (atom(null as string) as PrimitiveAtom);
const workspaceAtom: Atom = atom((get) => {
const wsId = get(workspaceIdAtom);
if (wsId == null) {
diff --git a/frontend/types/custom.d.ts b/frontend/types/custom.d.ts
index 06157e2566..1179a42435 100644
--- a/frontend/types/custom.d.ts
+++ b/frontend/types/custom.d.ts
@@ -11,14 +11,14 @@ 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
- workspaceId: jotai.Atom; // derived from window WOS object
+ workspaceId: jotai.PrimitiveAtom;
workspace: jotai.Atom; // driven from workspaceId via WOS
fullConfigAtom: jotai.PrimitiveAtom; // driven from WOS, settings -- updated via WebSocket
waveaiModeConfigAtom: jotai.PrimitiveAtom>; // resolved AI mode configs -- updated via WebSocket
settingsAtom: jotai.Atom; // derrived from fullConfig
hasCustomAIPresetsAtom: jotai.Atom; // derived from fullConfig
hasConfigErrors: jotai.Atom; // derived from fullConfig
- staticTabId: jotai.Atom;
+ staticTabId: jotai.PrimitiveAtom;
isFullScreen: jotai.PrimitiveAtom;
zoomFactorAtom: jotai.PrimitiveAtom;
controlShiftDelayAtom: jotai.PrimitiveAtom;
diff --git a/frontend/wave.ts b/frontend/wave.ts
index 20ee2ba97a..e0f522b0a2 100644
--- a/frontend/wave.ts
+++ b/frontend/wave.ts
@@ -39,6 +39,7 @@ import { createRoot } from "react-dom/client";
const platform = getApi().getPlatform();
document.title = `Wave Terminal`;
let savedInitOpts: WaveInitOpts = null;
+let unsubscribeWorkspace: () => void = null;
(window as any).WOS = WOS;
(window as any).globalStore = globalStore;
@@ -80,6 +81,8 @@ document.addEventListener("DOMContentLoaded", initBare);
async function initWaveWrap(initOpts: WaveInitOpts) {
try {
if (savedInitOpts) {
+ globalStore.set(activeTabIdAtom, initOpts.tabId);
+ globalStore.set(atoms.staticTabId, initOpts.tabId);
await reinitWave();
return;
}
@@ -92,6 +95,7 @@ async function initWaveWrap(initOpts: WaveInitOpts) {
document.body.style.visibility = null;
document.body.style.opacity = null;
document.body.classList.remove("is-transparent");
+ document.body.classList.remove("init");
}
}
@@ -109,35 +113,53 @@ async function reinitWave() {
await WOS.reloadWaveObject(WOS.makeORef("client", savedInitOpts.clientId));
const waveWindow = await WOS.reloadWaveObject(WOS.makeORef("window", savedInitOpts.windowId));
+ if (waveWindow == null) {
+ return;
+ }
const ws = await WOS.reloadWaveObject(WOS.makeORef("workspace", waveWindow.workspaceid));
- const initialTab = await WOS.reloadWaveObject(WOS.makeORef("tab", savedInitOpts.tabId));
+ if (ws == null) {
+ return;
+ }
+ globalStore.set(atoms.workspaceId, ws.oid);
+ const activeTabId = ws.activetabid || savedInitOpts.tabId;
+ const initialTab = await WOS.reloadWaveObject(WOS.makeORef("tab", activeTabId));
+ if (initialTab == null) {
+ return;
+ }
await WOS.reloadWaveObject(WOS.makeORef("layout", initialTab.layoutstate));
- reloadAllWorkspaceTabs(ws);
+ await reloadWorkspaceTabsAndLayouts(ws);
document.title = `Wave Terminal - ${initialTab.name}`; // TODO update with tab name change
getApi().setWindowInitStatus("wave-ready");
+ globalStore.set(activeTabIdAtom, activeTabId);
+ globalStore.set(atoms.staticTabId, activeTabId);
globalStore.set(atoms.reinitVersion, globalStore.get(atoms.reinitVersion) + 1);
globalStore.set(atoms.updaterStatusAtom, getApi().getUpdaterStatus());
+ subscribeToWorkspace(ws.oid);
setTimeout(() => {
globalRefocus();
}, 50);
}
-function reloadAllWorkspaceTabs(ws: Workspace) {
- if (ws == null || !ws.tabids?.length) {
+function subscribeToWorkspace(workspaceId: string) {
+ if (workspaceId == null) {
return;
}
- ws.tabids?.forEach((tabid) => {
- WOS.reloadWaveObject(WOS.makeORef("tab", tabid));
- });
+ unsubscribeWorkspace?.();
+ unsubscribeWorkspace = WOS.wpsSubscribeToObject(WOS.makeORef("workspace", workspaceId));
}
-function loadAllWorkspaceTabs(ws: Workspace) {
+async function reloadWorkspaceTabsAndLayouts(ws: Workspace) {
if (ws == null || !ws.tabids?.length) {
return;
}
- ws.tabids?.forEach((tabid) => {
- WOS.getObjectValue(WOS.makeORef("tab", tabid));
- });
+ await Promise.all(
+ ws.tabids.map(async (tabid) => {
+ const tab = await WOS.reloadWaveObject(WOS.makeORef("tab", tabid));
+ if (tab?.layoutstate) {
+ await WOS.reloadWaveObject(WOS.makeORef("layout", tab.layoutstate));
+ }
+ })
+ );
}
async function initWave(initOpts: WaveInitOpts) {
@@ -180,8 +202,9 @@ async function initWave(initOpts: WaveInitOpts) {
WOS.loadAndPinWaveObject(WOS.makeORef("workspace", waveWindow.workspaceid)),
WOS.reloadWaveObject(WOS.makeORef("layout", initialTab.layoutstate)),
]);
- loadAllWorkspaceTabs(ws);
- WOS.wpsSubscribeToObject(WOS.makeORef("workspace", waveWindow.workspaceid));
+ globalStore.set(atoms.workspaceId, ws.oid);
+ await reloadWorkspaceTabsAndLayouts(ws);
+ subscribeToWorkspace(ws.oid);
document.title = `Wave Terminal - ${initialTab.name}`; // TODO update with tab name change
} catch (e) {
console.error("Failed initialization error", e);