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