Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 49 additions & 9 deletions emain/emain-window.ts
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ export class WaveBrowserWindow extends BaseWindow {
waveWindowId: string;
workspaceId: string;
allLoadedTabViews: Map<string, WaveTabView>;
allTabViewsCache: Map<string, WaveTabView>; // Cache for preserving tab views across workspace switches
activeTabView: WaveTabView;
private canClose: boolean;
private deleteAllowed: boolean;
Expand Down Expand Up @@ -218,6 +219,7 @@ export class WaveBrowserWindow extends BaseWindow {
this.waveWindowId = waveWindow.oid;
this.workspaceId = waveWindow.workspaceid;
this.allLoadedTabViews = new Map<string, WaveTabView>();
this.allTabViewsCache = new Map<string, WaveTabView>();
const winBoundsPoller = setInterval(() => {
if (this.isDestroyed()) {
clearInterval(winBoundsPoller);
Expand Down Expand Up @@ -352,6 +354,11 @@ export class WaveBrowserWindow extends BaseWindow {
}
tabView?.destroy();
}
// Also destroy any cached views
for (const tabView of this.allTabViewsCache.values()) {
tabView?.destroy();
}
this.allTabViewsCache.clear();
}

async switchWorkspace(workspaceId: string) {
Expand Down Expand Up @@ -576,8 +583,27 @@ export class WaveBrowserWindow extends BaseWindow {
return;
}
console.log("processActionQueue switchworkspace newWs", newWs);
this.removeAllChildViews();
console.log("destroyed all tabs", this.waveWindowId);
// Move current tab views to cache instead of destroying them
// This preserves terminal state (including alternate screen mode) across workspace switches
for (const [tabId, tabView] of this.allLoadedTabViews.entries()) {
// Position off-screen but don't destroy
if (!this.isDestroyed()) {
const bounds = this.getContentBounds();
tabView.positionTabOffScreen(bounds);
}
// If tabId already in cache (edge case with rapid workspace switching), destroy the old cached view first
const existingCachedView = this.allTabViewsCache.get(tabId);
if (existingCachedView) {
existingCachedView.destroy();
}
this.allTabViewsCache.set(tabId, tabView);
}
console.log("cached", this.allLoadedTabViews.size, "tabs for workspace", this.workspaceId, this.waveWindowId);
// Note: Cached views are kept alive indefinitely and only destroyed when:
// 1. The tab is explicitly closed by the user
// 2. The window is closed (via removeAllChildViews)
// This matches how traditional terminal apps work and prevents terminal state loss

this.workspaceId = entry.workspaceId;
this.allLoadedTabViews = new Map();
tabId = newWs.activetabid;
Expand All @@ -586,7 +612,15 @@ export class WaveBrowserWindow extends BaseWindow {
if (tabId == null) {
return;
}
const [tabView, tabInitialized] = await getOrCreateWebViewForTab(this.waveWindowId, tabId);
// Check cache first to reuse existing tab views across workspace switches
let tabView = this.allTabViewsCache.get(tabId);
let tabInitialized = true;
if (tabView) {
console.log("reusing cached tab view", tabId, this.waveWindowId);
this.allTabViewsCache.delete(tabId);
} else {
[tabView, tabInitialized] = await getOrCreateWebViewForTab(this.waveWindowId, tabId);
}
const primaryStartupTabFlag = entry.op === "switchtab" ? (entry.primaryStartupTab ?? false) : false;
await this.setTabViewIntoWindow(tabView, tabInitialized, primaryStartupTabFlag);
} catch (e) {
Expand Down Expand Up @@ -618,14 +652,20 @@ export class WaveBrowserWindow extends BaseWindow {
console.log("cannot remove active tab", tabId, this.waveWindowId);
return;
}
const tabView = this.allLoadedTabViews.get(tabId);
let tabView = this.allLoadedTabViews.get(tabId);
if (tabView == null) {
console.log("removeTabView -- tabView not found", tabId, this.waveWindowId);
// the tab was never loaded, so just return
return;
// Check cache - tab might be from a different workspace
tabView = this.allTabViewsCache.get(tabId);
if (tabView == null) {
console.log("removeTabView -- tabView not found in loaded or cache", tabId, this.waveWindowId);
return;
}
console.log("removeTabView -- removing from cache", tabId, this.waveWindowId);
this.allTabViewsCache.delete(tabId);
} else {
this.contentView.removeChildView(tabView);
this.allLoadedTabViews.delete(tabId);
}
this.contentView.removeChildView(tabView);
this.allLoadedTabViews.delete(tabId);
tabView.destroy();
Comment on lines +655 to 669
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Potential issue: Missing contentView.removeChildView for cached tabs.

When removing a cached tab (lines 652-659), contentView.removeChildView(tabView) is not called before destroy(). However, cached tabs were never removed from contentView during the workspace switch—they were only positioned off-screen.

This could potentially cause issues when destroying a view that's still attached to its parent. Consider adding the removal:

🔎 Proposed fix
         tabView = this.allTabViewsCache.get(tabId);
         if (tabView == null) {
             console.log("removeTabView -- tabView not found in loaded or cache", tabId, this.waveWindowId);
             return;
         }
         console.log("removeTabView -- removing from cache", tabId, this.waveWindowId);
+        if (!this.isDestroyed()) {
+            this.contentView.removeChildView(tabView);
+        }
         this.allTabViewsCache.delete(tabId);
     } else {
         this.contentView.removeChildView(tabView);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
let tabView = this.allLoadedTabViews.get(tabId);
if (tabView == null) {
console.log("removeTabView -- tabView not found", tabId, this.waveWindowId);
// the tab was never loaded, so just return
return;
// Check cache - tab might be from a different workspace
tabView = this.allTabViewsCache.get(tabId);
if (tabView == null) {
console.log("removeTabView -- tabView not found in loaded or cache", tabId, this.waveWindowId);
return;
}
console.log("removeTabView -- removing from cache", tabId, this.waveWindowId);
this.allTabViewsCache.delete(tabId);
} else {
this.contentView.removeChildView(tabView);
this.allLoadedTabViews.delete(tabId);
}
this.contentView.removeChildView(tabView);
this.allLoadedTabViews.delete(tabId);
tabView.destroy();
let tabView = this.allLoadedTabViews.get(tabId);
if (tabView == null) {
// Check cache - tab might be from a different workspace
tabView = this.allTabViewsCache.get(tabId);
if (tabView == null) {
console.log("removeTabView -- tabView not found in loaded or cache", tabId, this.waveWindowId);
return;
}
console.log("removeTabView -- removing from cache", tabId, this.waveWindowId);
if (!this.isDestroyed()) {
this.contentView.removeChildView(tabView);
}
this.allTabViewsCache.delete(tabId);
} else {
this.contentView.removeChildView(tabView);
this.allLoadedTabViews.delete(tabId);
}
tabView.destroy();
🤖 Prompt for AI Agents
In emain/emain-window.ts around lines 650 to 664, when a tabView is found in the
cache you delete it from allTabViewsCache and immediately call
tabView.destroy(), but you never remove it from contentView; before destroying a
cached tab you should call this.contentView.removeChildView(tabView) (and then
delete it from this.allTabViewsCache) so the view is detached from its parent
prior to destroy to avoid destroying an attached view.

}

Expand Down
Loading