feat(windows): split maximize toggle into maximize + unmaximize cmds
Toggle semantics make the cmd panel surprising — a user typing
"maximize" expects maximize, not "if-already-maximized-do-the-opposite".
Two separate commands, both non-toggling.
- `maximize window` / `unmaximize window` cmds call the existing
`api.window.maximize` / new `api.window.unmaximize` IPC. The IPC
resolves the target window and is the single choke point — both the
OS-side bounds change AND the targeted main→renderer notification
fire from there. No pubsub broadcast: the IPC already knows which
window, so it sends to that webContents only.
- `tile:window:maximize` (IPC) snapshots the window's current bounds
into the windowRegistry params (`preMaximizeBounds`) — but only when
the bounds aren't already at the work area, so the user can resize a
maximized window smaller, hit `maximize` again, and have the smaller
size become the new pre-maximize snapshot. Then `setBounds(workArea)`
+ `webContents.send('tile:window:maximize-request')` so the
renderer's internal layout (FSM, screenBounds, centerColumn)
participates. Doesn't call `BrowserWindow.maximize()` — frameless /
panel-style windows on macOS don't reliably flip Electron's internal
isMaximized flag (the same caveat is documented in `ipc.ts` for the
open-time `maximize: true` path).
- `tile:window:unmaximize` (IPC, new) restores the stashed bounds via
`setBounds`, falling back to `BrowserWindow.unmaximize()` if the
registry has nothing recorded (window was OS-maximized via dbl-click
on a framed window). Also fires the targeted unmaximize-request
notification.
- `api.window.unmaximize(id?)`, `api.window.onMaximizeRequest(cb)`, and
`api.window.onUnmaximizeRequest(cb)` exposed on the tile preload
alongside `api.window.maximize`. Same `window.manage` capability gate
on the IPC; the listener bridges return an unsubscribe fn.
- `app/page/page.js` `toggleMaximize` is split into `doMaximize`,
`doUnmaximize`, and a wrapper that flips. The renderer subscribes
via `api.window.onMaximizeRequest` / `onUnmaximizeRequest` — non-
toggling, directed at this window only. Navbar dbl-click stays bound
to `toggleMaximize` (toggle is the right semantics for a single user
gesture).
Tests pin both halves of the contract — `maximize` twice keeps the
window maximized; `unmaximize` restores the pre-maximize bounds
(x/y/width/height) exactly.