feat(desktop): persist and restore window state

Fixes #1223
This commit is contained in:
Éverton Toffanetto
2026-06-01 12:04:58 +00:00
committed by GitHub
parent a5c2f39f65
commit 1ede1bccf7
5 changed files with 608 additions and 3 deletions
+34 -3
View File
@@ -10,7 +10,17 @@ import path from "node:path";
import { pathToFileURL } from "node:url";
import { existsSync } from "node:fs";
import { execFileSync } from "node:child_process";
import { app, BrowserWindow, Menu, ipcMain, nativeImage, net, protocol, session } from "electron";
import {
app,
BrowserWindow,
Menu,
ipcMain,
nativeImage,
net,
protocol,
screen,
session,
} from "electron";
import { createDaemonCommandHandlers, registerDaemonManager } from "./daemon/daemon-manager.js";
import { parsePassthroughCliArgsFromArgv, runPassthroughCli } from "./daemon/cli/passthrough.js";
import { closeAllTransportSessions } from "./daemon/local-transport.js";
@@ -19,7 +29,9 @@ import {
getMainWindowChromeOptions,
getWindowBackgroundColor,
resolveSystemWindowTheme,
resolveWindowBounds,
setupWindowResizeEvents,
setupWindowStatePersistence,
setupDefaultContextMenu,
setupDragDropPrevention,
buildStandardContextMenuItems,
@@ -41,6 +53,7 @@ import {
} from "./features/browser-webviews.js";
import { parseOpenProjectPathFromArgv } from "./open-project-routing.js";
import { getDesktopSettingsStore } from "./settings/desktop-settings-electron.js";
import { clampWindowStateToWorkAreas, createWindowStateStore } from "./settings/window-state.js";
import {
isDesktopManagedDaemonRunningSync,
stopDesktopDaemonViaCli,
@@ -372,15 +385,28 @@ function applyAppIcon(): void {
app.dock?.setIcon(icon);
}
// Work areas with the primary display first, so window-state clamping treats
// it as the fallback. getAllDisplays() order is not guaranteed to lead with it.
function getWorkAreasPrimaryFirst(): Electron.Rectangle[] {
const primary = screen.getPrimaryDisplay();
const others = screen.getAllDisplays().filter((display) => display.id !== primary.id);
return [primary, ...others].map((display) => display.workArea);
}
async function createMainWindow(): Promise<void> {
const iconPath = getWindowIconPath();
const systemTheme = resolveSystemWindowTheme();
const windowStateStore = createWindowStateStore({ userDataPath: app.getPath("userData") });
const savedWindowState = await windowStateStore.load();
const restoredWindowState = savedWindowState
? clampWindowStateToWorkAreas(savedWindowState, getWorkAreasPrimaryFirst())
: null;
const title = devWorktreeName ? `${APP_NAME} (${devWorktreeName})` : APP_NAME;
const mainWindow = new BrowserWindow({
title,
width: 1200,
height: 800,
...resolveWindowBounds(restoredWindowState),
show: false,
backgroundColor: getWindowBackgroundColor(systemTheme),
...(iconPath ? { icon: iconPath } : {}),
@@ -400,8 +426,13 @@ async function createMainWindow(): Promise<void> {
app.dock?.setBadge(devWorktreeName);
}
if (restoredWindowState?.isMaximized) {
mainWindow.maximize();
}
setupDarwinCompositorWatchdog(mainWindow);
setupWindowResizeEvents(mainWindow);
setupWindowStatePersistence(mainWindow, windowStateStore);
setupDefaultContextMenu(mainWindow);
setupDragDropPrevention(mainWindow);
mainWindow.webContents.on("will-attach-webview", (event, webPreferences, params) => {