Commit Graph

11 Commits

Author SHA1 Message Date
fullex
32e8ef273c feat(window-manager): add declarative rememberBounds persistence
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.
2026-06-26 22:39:52 -07:00
fullex
8a163e8b11 refactor(window-manager): unify query currency and retire deprecated getMainWindow
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.
2026-06-05 06:45:43 -07:00
fullex
c514dcc049 refactor(shared): move packages/shared to src/shared
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).
2026-05-28 21:02:49 -07:00
fullex
c3c6cf6b8d feat(window-manager): add singleton warmup and delayed destroy
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.
2026-04-24 09:56:45 -07:00
fullex
6d42ab5822 feat(window-manager): add pushInitData for already-open windows
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.
2026-04-22 06:02:23 -07:00
fullex
e666470794 refactor(sub-window): rename DetachedWindowManager to SubWindowService
Unify the "window detached from main" concept under the SubWindow name,
pairing it with MainWindow. Rename the service class, WindowType enum
value, HTML entry, renderer directory, React components, logger contexts,
and window-manager docs accordingly.

Scope of rename (noun-only):
- DetachedWindowManager -> SubWindowService
- DetachedWindowState -> SubWindowState
- WindowType.DetachedTab -> WindowType.SubWindow (value 'subWindow')
- detachedWindow.html / detachedWindow/ -> subWindow.html / subWindow/
- DetachedAppShell -> SubWindowAppShell
- DetachedTabApp -> SubWindowApp
- DETACHED_DEFAULT_WIDTH/HEIGHT -> SUB_WINDOW_DEFAULT_WIDTH/HEIGHT
- Logger contexts / sources and all "detached window" prose

Preserved (interaction-specific, not window-type nouns):
- IPC channels Tab_Detach / Tab_Attach / Tab_MoveWindow / Tab_TryAttach /
  Tab_DragEnd and their 'tab:*' literals - these describe the drag-tab
  interaction, not the SubWindow concept
- Component prop isDetached and useTabDrag.detachedCreated state
- The DevTools "detached" and Node spawn "detached" unrelated usages
2026-04-21 19:32:47 -07:00
fullex
f533e9259f refactor(window-manager): group behavior runtime setters under wm.behavior
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.
2026-04-19 08:10:52 -07:00
fullex
bafed0d0e1 refactor(window-manager): migrate main window & fix macOS Dock semantics
- 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).
2026-04-19 06:52:03 -07:00
fullex
62d39d41f7 refactor(window-manager): split metadata into windowOptions/behavior/quirks
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.
2026-04-19 01:27:26 -07:00
fullex
e9518e80c1 feat(window-manager): add onWindowCreatedByType/onWindowDestroyedByType
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.
2026-04-18 09:48:45 -07:00
fullex
d2f629799c docs(window-manager): split README into docs/references/window-manager/
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.
2026-04-18 08:35:17 -07:00