()
| 26 | * Create the main window |
| 27 | */ |
| 28 | export function createWindow(): void { |
| 29 | const isDev = !app.isPackaged; |
| 30 | const iconPath = getIconPath(); |
| 31 | |
| 32 | // Load and validate saved window bounds |
| 33 | const savedBounds = loadWindowBounds(); |
| 34 | const validBounds = savedBounds ? validateBounds(savedBounds) : null; |
| 35 | |
| 36 | const windowOptions: Electron.BrowserWindowConstructorOptions = { |
| 37 | width: validBounds?.width ?? DEFAULT_WIDTH, |
| 38 | height: validBounds?.height ?? DEFAULT_HEIGHT, |
| 39 | x: validBounds?.x, |
| 40 | y: validBounds?.y, |
| 41 | minWidth: MIN_WIDTH_COLLAPSED, // Small minimum - horizontal scrolling handles overflow |
| 42 | minHeight: MIN_HEIGHT, |
| 43 | webPreferences: { |
| 44 | // __dirname is apps/ui/dist-electron (Vite bundles all into single file) |
| 45 | preload: path.join(__dirname, 'preload.js'), |
| 46 | contextIsolation: true, |
| 47 | nodeIntegration: false, |
| 48 | }, |
| 49 | // titleBarStyle is macOS-only; use hiddenInset for native look on macOS |
| 50 | ...(process.platform === 'darwin' && { titleBarStyle: 'hiddenInset' as const }), |
| 51 | backgroundColor: '#0a0a0a', |
| 52 | }; |
| 53 | |
| 54 | if (iconPath) { |
| 55 | windowOptions.icon = iconPath; |
| 56 | } |
| 57 | |
| 58 | state.mainWindow = new BrowserWindow(windowOptions); |
| 59 | |
| 60 | // Restore maximized state if previously maximized |
| 61 | if (validBounds?.isMaximized) { |
| 62 | state.mainWindow.maximize(); |
| 63 | } |
| 64 | |
| 65 | // Load Vite dev server in development or static server in production |
| 66 | if (VITE_DEV_SERVER_URL) { |
| 67 | state.mainWindow.loadURL(VITE_DEV_SERVER_URL); |
| 68 | } else if (isDev) { |
| 69 | // Fallback for dev without Vite server URL |
| 70 | state.mainWindow.loadURL(`http://localhost:${state.staticPort}`); |
| 71 | } else { |
| 72 | state.mainWindow.loadURL(`http://localhost:${state.staticPort}`); |
| 73 | } |
| 74 | |
| 75 | if (isDev && process.env.OPEN_DEVTOOLS === 'true') { |
| 76 | state.mainWindow.webContents.openDevTools(); |
| 77 | } |
| 78 | |
| 79 | // Save window bounds on close, resize, and move |
| 80 | state.mainWindow.on('close', () => { |
| 81 | // Save immediately before closing (not debounced) |
| 82 | if (state.mainWindow && !state.mainWindow.isDestroyed()) { |
| 83 | const isMaximized = state.mainWindow.isMaximized(); |
| 84 | const bounds = isMaximized |
| 85 | ? state.mainWindow.getNormalBounds() |
no test coverage detected