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.
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.