# Window Migration Guide How to migrate an existing window from direct `BrowserWindow` creation to WindowManager. ## Step 1: Add the WindowType In `types.ts`, add a new enum value: ```typescript export enum WindowType { // ... MyWindow = 'myWindow', } ``` ## Step 2: Register in windowRegistry.ts Define the window's metadata and default configuration: ```typescript WINDOW_TYPE_REGISTRY[WindowType.MyWindow] = { type: WindowType.MyWindow, lifecycle: 'singleton', // or 'default' or 'pooled' htmlPath: 'my-window.html', // preload omitted → defaults to 'preload.js'. Write basename (with extension) // to select a different file in src/preload/. Empty string → no preload. // preload: 'simplest.js', showMode: 'auto', // 'auto' | 'immediate' | 'manual' windowOptions: { ...DEFAULT_WINDOW_CONFIG, width: 800, height: 600, }, behavior: { // Declarative WM-level behaviors (all optional). See the README "Configuration Layers" section. // hideOnBlur: true, // auto-hide on blur (runtime override: wm.behavior.setHideOnBlur) // alwaysOnTop: { level: 'floating' }, // level/relativeLevel for setAlwaysOnTop (runtime override: wm.behavior.setAlwaysOnTop) // visibleOnAllWorkspaces: { enabled: true, visibleOnFullScreen: true }, // macShowInDock: false, // do not contribute to Dock visibility (macOS helper windows only; default true) // // runtime override: wm.behavior.setMacShowInDockByType(type, value) for tray-mode transitions }, // quirks: { ... }, // OS hacks — see Platform Configuration } ``` See [Lifecycle Modes](./window-manager-overview.md#three-lifecycle-modes) for choosing between `default` / `singleton` / `pooled`. Optional: for singleton types that benefit from pre-warm or close→hide, set `singletonConfig`. See [Warmup Mechanics → Singleton Variant](./window-manager-warmup-mechanics.md#singleton-variant). ## Step 3: Move domain logic to onWindowCreated Replace direct `new BrowserWindow()` + setup code with an `onWindowCreated` subscription in your domain service: **Before:** ```typescript class MyService { private window: BrowserWindow | null = null createWindow() { this.window = new BrowserWindow({ width: 800, height: 600, ... }) this.window.loadFile('my-window.html') this.window.on('closed', () => { this.window = null }) } } ``` **After:** ```typescript @Injectable('MyService') @ServicePhase(Phase.WhenReady) class MyService extends BaseService { private windowId: string | undefined protected override onInit(): void { const wm = application.get('WindowManager') wm.onWindowCreatedByType(WindowType.MyWindow, ({ window, id }) => { this.windowId = id // attach listeners here — use `window` directly, or switch to the `mw` shorthand // if the callback body has inner closures (see Usage Guide → Callback styles). }) wm.onWindowDestroyedByType(WindowType.MyWindow, () => { this.windowId = undefined }) } openWindow(): void { const wm = application.get('WindowManager') this.windowId = wm.open(WindowType.MyWindow) } } ``` See [Injecting behavior: `onWindowCreated` is the canonical hook](./window-manager-usage.md#injecting-behavior-onwindowcreated-is-the-canonical-hook) for the full rationale behind this pattern. ## Step 4: Replace direct BrowserWindow references | Old Pattern | New Pattern | |-------------|-------------| | `this.window = new BrowserWindow(...)` | `wm.open(WindowType.MyWindow)` | | `this.window.show()` | `wm.show(windowId)` | | `this.window.hide()` | `wm.hide(windowId)` | | `this.window.close()` | `wm.close(windowId)` | | `this.window.webContents.send(...)` | `wm.getWindow(windowId)?.webContents.send(...)` or `wm.broadcastToType(...)` | | `BrowserWindow.fromWebContents(e.sender)` | `wm.getWindowIdByWebContents(e.sender)` | Note: there is intentionally no entry for `this.window.destroy()`. `wm.close()` already handles destruction for non-pooled windows and pool-return for pooled windows. `wm.destroy()` is an internal primitive — see [Window API layers](./window-manager-usage.md#window-api-layers-consumer-vs-internal). ## Step 5: Handle show behavior Remove manual `show` / `ready-to-show` logic if using `showMode: 'auto'` (the default). WindowManager handles: - Creating the window hidden - Showing on `ready-to-show` (fresh path) or immediately (recycled path) If your window needs custom show timing, set `showMode: 'manual'` in the registry and manage visibility yourself. ## Checklist - [ ] Added `WindowType` enum value in `types.ts` - [ ] Registered metadata in `WINDOW_TYPE_REGISTRY` in `windowRegistry.ts` - [ ] Chose the correct lifecycle mode (`default` / `singleton` / `pooled`) - [ ] Set `preload` filename if not using the default (`'preload.js'`) - [ ] Set `showMode` behavior (`'auto'` / `'immediate'` / `'manual'`) - [ ] Set `behavior.macShowInDock: false` ONLY for helper windows (floating panels, selection overlays); primary app windows leave it at the default `true`. Use `wm.behavior.setMacShowInDockByType(type, value)` for runtime tray-mode transitions, not a different registry default. - [ ] Declared `behavior.hideOnBlur` / `behavior.alwaysOnTop` / `behavior.visibleOnAllWorkspaces` as needed - [ ] Moved domain logic from constructor to `onWindowCreated` hook - [ ] Replaced direct `BrowserWindow` references with WindowManager API calls - [ ] Removed manual `ready-to-show` handling (if using `showMode: 'auto'`) - [ ] If the window consumes init data: replaced hand-rolled `getInitData` + reset IPC wiring with the `useWindowInitData` hook - [ ] If pooled: chose appropriate `PoolConfig` axes (`standbySize` for active pre-warm, `recycleMinSize`/`recycleMaxSize` for recycling). Leave `recycleMaxSize` unset for one-shot "close destroys" semantics; set `standbySize` when zero-wait matters under concurrent opens. - [ ] Verified `onWindowDestroyed` cleanup in the domain service