Files
CherryHQ-cherry-studio/docs/references/window-manager/window-manager-migration-guide.md
fullex 87b2b64ba5 refactor(process-entry): rename main/preload entries to named files per naming §6.4
src/main/index.ts → main.ts and src/preload/index.ts → preload.ts, so the process entries stop occupying the barrel-reserved index filename (§6.4) without needing an exemption list. The rename is wired through electron.vite (main build.lib.entry — rollupOptions.input would bypass electron-vite's output-format detection — and the preload input key), package.json main (out/main/main.js), WindowManager's default preload filename, MainWindowService's webview preload path, and DbService's slow-query stack filter, which matches the bundled artifact name. preload.d.ts becomes globals.d.ts: sharing preload.ts's basename makes TypeScript drop the .d.ts as that file's presumed output declaration, and tsgo only applies its global Window augmentation when the declaration sorts before its importer, so the file keeps a distinct, alphabetically-early name. Entry-path mentions in code comments and reference docs follow.
2026-07-04 20:18:14 -07:00

136 lines
5.9 KiB
Markdown

# 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