From 603a31c92fe90695e66afd26ea9bf6e22e82ea48 Mon Sep 17 00:00:00 2001 From: Aaron Date: Tue, 17 Mar 2026 16:26:11 +0700 Subject: [PATCH] [WEB-6634] feat: restore tabs from previous session on relaunch (#6317) * [WEB-6634] feat: restore tabs from previous session on relaunch * [WEB-6634] fix: cap closed window/tab history and bump schema version Address review feedback: - Cap closedWindows to 10 entries (oldest trimmed on push) - Cap closedTabs to 20 per window (oldest trimmed on push) - Bump STORE_SCHEMA_VERSION from 1 to 2 for the new persisted keys --- apps/desktop/src/main.ts | 2 ++ apps/desktop/src/stores/migrations.ts | 2 +- apps/desktop/src/stores/tab-store.ts | 45 ++++++++++++++++++++++++++- apps/desktop/src/stores/types.ts | 6 ++++ 4 files changed, 53 insertions(+), 2 deletions(-) diff --git a/apps/desktop/src/main.ts b/apps/desktop/src/main.ts index f0ce411fa2..42f1bec4aa 100644 --- a/apps/desktop/src/main.ts +++ b/apps/desktop/src/main.ts @@ -29,6 +29,8 @@ const persistStore = new Store({ defaults: { instanceUrl: undefined, windows: [], + closedWindows: [], + closedTabs: {}, schemaVersion: STORE_SCHEMA_VERSION, }, }); diff --git a/apps/desktop/src/stores/migrations.ts b/apps/desktop/src/stores/migrations.ts index 556a8e6512..d0d10fe3d7 100644 --- a/apps/desktop/src/stores/migrations.ts +++ b/apps/desktop/src/stores/migrations.ts @@ -14,7 +14,7 @@ import type ElectronStore from "electron-store"; import type { StoreSchema } from "./types"; -export const STORE_SCHEMA_VERSION = 1; +export const STORE_SCHEMA_VERSION = 2; export type StoreMigration = { from: number; diff --git a/apps/desktop/src/stores/tab-store.ts b/apps/desktop/src/stores/tab-store.ts index 39bbbf805e..1696ed0dbf 100644 --- a/apps/desktop/src/stores/tab-store.ts +++ b/apps/desktop/src/stores/tab-store.ts @@ -27,6 +27,12 @@ export interface TabState { windows: Map; } +/** Maximum number of closed windows to remember for "restore closed window". */ +const MAX_CLOSED_WINDOWS = 10; + +/** Maximum number of closed tabs to remember per window for "restore closed tab". */ +const MAX_CLOSED_TABS_PER_WINDOW = 20; + function generateTabId(): string { return `tab-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`; } @@ -52,6 +58,15 @@ export class TabStore extends Store { super({ windows }); this.persistStore = persistStore; + + this.#closedWindows = persistStore.get("closedWindows") ?? []; + + const persistedClosedTabs = persistStore.get("closedTabs") ?? {}; + for (const [windowId, tabs] of Object.entries(persistedClosedTabs)) { + if (tabs.length > 0) { + this.#closedTabsByWindow.set(windowId, tabs); + } + } } registerWindow(windowId: string): void { @@ -109,17 +124,30 @@ export class TabStore extends Store { }; this.#closedWindows.push(closedWindow); + if (this.#closedWindows.length > MAX_CLOSED_WINDOWS) { + this.#closedWindows.splice(0, this.#closedWindows.length - MAX_CLOSED_WINDOWS); + } this.#closedTabsByWindow.delete(windowId); + this.#persistClosedWindows(); + this.#persistClosedTabs(); } popClosedWindow(): PersistedWindow | undefined { - return this.#closedWindows.pop(); + const window = this.#closedWindows.pop(); + if (window) { + this.#persistClosedWindows(); + } + return window; } pushClosedTab(windowId: string, tab: PersistedTab): void { const closedTabs = this.#closedTabsByWindow.get(windowId) ?? []; closedTabs.push(tab); + if (closedTabs.length > MAX_CLOSED_TABS_PER_WINDOW) { + closedTabs.splice(0, closedTabs.length - MAX_CLOSED_TABS_PER_WINDOW); + } this.#closedTabsByWindow.set(windowId, closedTabs); + this.#persistClosedTabs(); } popClosedTab(windowId: string): PersistedTab | undefined { @@ -132,6 +160,7 @@ export class TabStore extends Store { if (closedTabs.length === 0) { this.#closedTabsByWindow.delete(windowId); } + this.#persistClosedTabs(); return tab; } @@ -142,6 +171,8 @@ export class TabStore extends Store { this.#persistWindows(windows); this.#closedTabsByWindow.clear(); this.#closedWindows = []; + this.#persistClosedWindows(); + this.#persistClosedTabs(); } addTab(windowId: string, path: string = "/", title: string = "New Tab"): string { @@ -171,6 +202,18 @@ export class TabStore extends Store { this.persistStore.set("windows", persistedWindows); } + #persistClosedWindows(): void { + this.persistStore.set("closedWindows", this.#closedWindows); + } + + #persistClosedTabs(): void { + const obj: Record = {}; + for (const [windowId, tabs] of this.#closedTabsByWindow) { + obj[windowId] = tabs; + } + this.persistStore.set("closedTabs", obj); + } + #updateWindowState( windowId: string, nextState: WindowTabState, diff --git a/apps/desktop/src/stores/types.ts b/apps/desktop/src/stores/types.ts index 2db012518f..9a9eeb3540 100644 --- a/apps/desktop/src/stores/types.ts +++ b/apps/desktop/src/stores/types.ts @@ -33,9 +33,15 @@ export interface TabWithFavicon extends Tab { favicon?: string; } +export interface PersistedClosedTabs { + [windowId: string]: PersistedTab[]; +} + export interface StoreSchema { instanceUrl: string | undefined; windows: PersistedWindow[]; + closedWindows: PersistedWindow[]; + closedTabs: PersistedClosedTabs; schemaVersion: number; }