Migrate window-bounds persistence off the electron-window-state library into
a WindowManager built-in `rememberBounds` capability, backed by the main-process
persist cache (`window.bounds` key — its first real consumer).
- New `windowBoundsTracker` free-function module: validates the stored record
(including displayBounds), restores onto the display the window was last on
(clamping into its work area, never resetting to primary), and snapshots at
teardown via getNormalBounds + isMaximized.
- Singleton-only gate (dev warning for non-singleton types). Runtime toggle
`wm.setRememberBounds` (orthogonal to the registry flag; OFF drops only that
type's slot) plus `wm.peekWindowBounds`.
- Persist at three teardown exits: native close (singletons), before
window.destroy() in destroyWindow (programmatic destroys), and a new onStop
so shutdown writes land before CacheService flushes its persist map.
- Wire Main + QuickAssistant. Main re-applies maximize consumer-side on its own
show schedule (tray-on-launch defers to first show); remove electron-window-state
and its orphaned keepers/constants/comments.
Fullscreen is not persisted and old *-state.json is not migrated (one-time
reset, loseable). Adds tracker/integration/persist tests, extends the main
CacheService mock with persist methods, and documents the capability plus a
breaking-change note.
Flip getWindowsByType to return live BrowserWindow[] (skipping destroyed) and
add getWindowInfosByType for the serializable WindowInfo[] snapshot, so the
"Info-suffixed = snapshot / unsuffixed = instance" convention holds on both the
by-id and by-type axes. Remove getAllWindows(): ManagedWindow[], whose only
production consumer was an AppMenuService workaround reaching into the internal
record to grab .window.
Migrate all callers off the deleted/old shapes:
- AppMenuService zoom and SettingsWindowService/SubWindowService main-window
lookups use getWindowsByType(Main) directly; the two-step getWindow(id)
collapses to one call.
- SubWindowService sub-window membership check uses getWindowInfosByType, which
still carries the WindowManager UUID it compares against.
- navigate.ts deep-link resolves via getWindowsByType(Main)[0].
- mcpInstall.ts switches to broadcastToType(Main, ...) since it was a pure
renderer IPC send.
With those two callers gone, delete the deprecated MainWindowService.getMainWindow().
Update WindowManager unit tests, the affected service mocks, and the
window-manager API reference table.
packages/shared was never a real pnpm workspace package (no package.json); it was referenced only through the @shared TypeScript path alias. Relocate it under src/ via git mv (143 files, detected as pure renames).
Repoint the @shared alias and include globs to src/shared across electron.vite.config.ts, tsconfig.{json,node,web}.json and vitest.config.ts; update scripts/check-custom-exts.ts, scripts/update-languages.ts, the eslint.config.mjs generated-file globs, the data-classify generator output targets, .github/CODEOWNERS path rules, and CLAUDE.md/docs/source-comment references.
The @shared alias name is unchanged, so all 1403 @shared/* import sites resolve without modification. Verified with typecheck:node, typecheck:web and the full test suite (700 files, 9739 tests passing).
Extend the pool warmup state machine to support singleton windows via an
optional `singletonConfig` (eager pre-warm + retentionTime-based close→hide
with delayed destroy). Generalize shared symbols (PoolState→WarmupState,
lastOpenAt→lastActivityAt, pool*→warmup* on non-pool-specific methods) and
fix a half-bug where pool inactivity only counted since last open, not
since last open or close.
Op naming convention: lifecycle-specific ops carry `pool-*` / `singleton-*`
prefixes; ops shared by both (create-idle, release-skip, inactivity-trim,
warmup) are unprefixed so log greps stay precise.
Main is intentionally NOT migrated — its close handler reads tray
preferences, quits the app on Win/Linux, guards on isFullScreen, and
toggles Dock visibility; none of that fits the declarative `retentionTime`
contract. Registry entry now documents this decision inline.
Expose pushInitData(windowId, data) and pushInitDataToType(type, data)
as main-process public methods on WindowManager. Both reuse the existing
initDataStore plus WindowManager_Reused IPC channel, so the renderer's
useWindowInitData hook picks up the new payload in-place without any
remount or DOM churn. This closes the gap between cold-start and reuse
paths: previously, updating an already-visible window required close()
plus open(), which lost interaction state on singletons.
Signatures forbid undefined to keep "push nothing" from silently
no-opping; unlike the reuse path, undefined has no meaningful semantics
here. pushInitDataToType does not filter by visibility — idle pooled
windows still receive the payload so they have the latest data when
taken out of the pool next.
Move runtime overrides (setHideOnBlur / setAlwaysOnTop /
setMacShowInDockByType) off the flat WindowManager API onto a
`wm.behavior` sub-namespace backed by a new BehaviorController class
in behavior.ts. The controller owns the hideOnBlur override map and
the per-type macShowInDock override map; WindowManager keeps only the
Dock commit-state flag and the visibility predicate.
Surfacing the three-layer declarative split (windowOptions / behavior
/ quirks) at the API level lets consumers tell at a glance which
layer a runtime setter belongs to, and gives future behavior setters
a natural home without inflating the flat WindowManager namespace.
`setTitleBarOverlay` and `setInitData` stay on the flat namespace
since they do not target the behavior layer.
Hard-cut migration: 5 production call sites (SelectionService,
MainWindowService) and the window / MainWindowService test suites
updated in place; no deprecation alias kept. Documentation across
the window-manager reference set rewritten to the new API shape.
Drive-by: add missing `setIsPinned` dep to HomeWindow.tsx
`baseFooterProps` useMemo to silence a pre-existing
react-hooks/exhaustive-deps warning surfaced while verifying lint.
- Rename WindowService -> MainWindowService (git mv, preserves blame/log)
- Register WindowType.Main in windowRegistry (singleton, showMode: manual);
MainWindowService drives construction via wm.open and retains business
logic only: IPC handlers, tray-aware close, crash recovery, show/toggle
- 50 call sites renamed (@DependsOn, application.get keys, imports)
WM Dock visibility refactored from visibility-based to existence-based:
- updateDockVisibility predicate drops isVisible/isMinimized check — a
hidden main window must not remove the Dock icon (Cmd+W semantics)
- Add wm.setMacShowInDockByType(type, value) for tray-mode transitions;
keyed by type so services can suppress Dock before the first instance
exists (tray-on-launch path)
- Reduce triggers to window creation, destruction, and type-override
changes; show/hide/minimize/restore no longer affect Dock state
- dockShouldBeVisible initializes true to match Electron's default
MainWindowService uses setMacShowInDockByType for tray-on-launch, close-
to-tray, and reopen-from-tray paths, replacing manual app.dock?.hide()
calls that raced against WM.
Docs updated across window-manager/{README, overview, platform,
api-reference, migration-guide}. New MainWindowService unit test covers
the close matrix (isQuitting, win/linux tray on/off, mac default, mac
tray on_close, fullscreen edge, Dock override assertions) and crash
recovery (first reload vs second-within-60s forceExit).
WindowTypeMetadata now declares per-type config across three orthogonal
layers, chosen by what goes wrong if misconfigured:
- windowOptions: BrowserWindow constructor parameters (Electron-native).
- behavior: cross-platform declarative WM behavior that the constructor
cannot express — hideOnBlur, alwaysOnTop level/relativeLevel,
visibleOnAllWorkspaces options, macShowInDock.
- quirks: OS-specific monkey-patches around hide/show/close.
`macReapplyAlwaysOnTop` is now a pure boolean — the level it re-applies
reads from `behavior.alwaysOnTop`, eliminating the previous mixing of
hack flag and semantic value.
Field renames in the registry:
- defaultConfig → windowOptions
- show → showMode (also gains 'immediate' | 'manual' string variants
in place of true/false; 'auto' unchanged)
- showInDock (top-level) → behavior.macShowInDock
- preload now accepts a plain filename ('index.js' / 'simplest.js'),
mirroring htmlPath; empty string disables, omit defaults to
'index.js'. Removes the variant→file mapping switch.
- mergeWindowConfig → mergeWindowOptions
Two runtime setters added on WindowManager:
- setHideOnBlur(id, enabled): runtime override on behavior.hideOnBlur.
Override map is cleared on destroy and on releaseToPool, so pool
consumers re-applying after open() see a clean default.
- setAlwaysOnTop(id, enabled): toggles using level/relativeLevel from
behavior — registry is the single source of truth.
setVisibleOnAllWorkspaces intentionally has no WM setter: its options
differ per call in real usage (SelectionAction's full-screen show
sequence) and WM has no state to maintain.
Types derived from Electron's signatures so they stay in sync with
@types/electron:
- AlwaysOnTopLevel = NonNullable<Parameters<BW['setAlwaysOnTop']>[1]>
- VisibleOnAllWorkspacesOptions imported directly
applyWindowQuirks is split into applyWindowBehavior (new, behavior.ts)
+ applyWindowQuirks. Behavior runs first so monkey-patches wrap any
subsequent show/hide rather than the initial setter calls. Behavior
takes a closure for reading the override map, avoiding a reverse
WindowManager dependency.
Consumer changes:
- SelectionToolbar: declares behavior.hideOnBlur; mouse-key hook
lifecycle moves from showToolbarAtPosition/hideToolbar call sites
to window 'show'/'hide' event listeners, so any hide path (WM-
driven blur included) triggers cleanup symmetrically.
- SelectionAction: pinActionWindow routes through wm.setAlwaysOnTop
via getWindowId. Show-sequence setVisibleOnAllWorkspaces calls
stay direct (true/false options differ per call). Hardcoded
'floating' level dropped from the show sequence; Electron's
default is identical.
- QuickAssistant: removes the inline setVisibleOnAllWorkspaces and
setAlwaysOnTop calls (now declared in registry behavior). The
blur handler stays in the service because hideQuickAssistant() is
a platform-specific business flow (Windows minimize+setOpacity,
macOS<26 app.hide()) that a generic window.hide() cannot express.
QuickAssistant pin/blur on macOS NSPanel — separate concern, addressed
in the same change because the diagnosis surfaced during refactor:
Electron's blur tracker can get stuck after (outside-click → intra-
panel pin/unpin click → outside-click). Cocoa fires
windowDidResignKey: for the second outside-click but Electron filters
it — its tracker thinks the window was already blurred (the intra-
panel click silently re-keyed the panel without firing
windowDidBecomeKey:). Upstream marked wontfix (electron/electron#3222).
Workaround: after un-pinning, poll window.isFocused() at 200ms,
capped at 30s. isFocused() reads Cocoa state directly, bypassing
the broken Electron tracker. Triggered only when hasBlurredSinceShow
is true (the failure precondition), so the common open→pin→unpin
path runs no timer.
Renderer side: HomeWindow's pin sync moves from useEffect (deferred
by one render cycle) to a setter wrapper, so IPC fires synchronously
inside the click handler — closes a small race where a fast outside
click could see a stale main flag.
Tests: 15 new specs covering behavior layer (declarative + runtime
override, pool recycle reset, level derivation, initial
setVisibleOnAllWorkspaces application, macReapplyAlwaysOnTop reading
from behavior). Existing fixtures mechanically renamed.
Docs: 6 window-manager docs synchronized; README adds sections on
configuration layers, "WM does not know pin", behavior/quirks API,
when to provide a runtime setter, type derivation convention, and
Electron edge cases.
Add thin wrapper methods on WindowManager that subscribe to a specific
WindowType, eliminating the boilerplate `if (managed.type !== X) return`
guard at every consumer call site. The underlying `onWindowCreated` /
`onWindowDestroyed` events remain available for the rare "observe all
windows" use case.
The new variants are additive and non-breaking — same Disposable return
contract, same ManagedWindow callback payload, just with an up-front type
filter baked in.
Documentation updates landing in the same commit:
- window-manager-usage.md: recommend the byType variants as the canonical
single-type subscription pattern; add a "Callback styles" section covering
destructuring vs `mw` shorthand for accessing ManagedWindow fields.
- window-manager-api-reference.md: list the byType variants in the Events
table; update `create`'s rationale to point at byType.
- window-manager-migration-guide.md: Step 3 example uses byType +
destructuring in the After block.
- docs README and window-manager-overview.md: anti-pattern and feature
rows point to the byType variants as the consumer-facing primary form.
Move the 756-line src/main/core/window/README.md into a dedicated
docs/references/window-manager/ directory organized by concern
(overview, usage, pool mechanics, platform, API reference, migration),
following the docs/references/lifecycle/ pattern. The in-source README
shrinks to a 14-line pointer.
Along the way:
- Reframe onWindowCreated + open()/close() as the canonical consumer
pattern, and demote create()/destroy() to internal primitives with an
explicit anti-pattern section for direct-ID attachment at call sites.
- Add a previously-undocumented "Renderer IPC Surface" section covering
WindowManager_Open/Close/Show/Hide/etc., with target-resolution rules
(bare sender vs singleton type).
- Make the pool-recycle-does-not-re-fire-onWindowCreated consequence
explicit in the Event Timing Contract guarantees.
- Update @see links in WindowManager.ts and types.ts to point at the new
doc locations directly instead of bouncing through the in-source README.