@dimina-kit/electron-deck 0.1.0-dev.20260702175315 → 0.1.0-dev.20260703051110

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. package/README.md +45 -15
  2. package/dist/client/index.d.ts +8 -2
  3. package/dist/client/index.d.ts.map +1 -1
  4. package/dist/client/index.js +1 -0
  5. package/dist/client/index.js.map +1 -1
  6. package/dist/dock-react/dock-view.d.ts +43 -19
  7. package/dist/dock-react/dock-view.d.ts.map +1 -1
  8. package/dist/dock-react/drag-redock.d.ts +18 -0
  9. package/dist/dock-react/drag-redock.d.ts.map +1 -1
  10. package/dist/dock-react/group-view.d.ts +18 -0
  11. package/dist/dock-react/group-view.d.ts.map +1 -0
  12. package/dist/dock-react/index.d.ts +3 -2
  13. package/dist/dock-react/index.d.ts.map +1 -1
  14. package/dist/dock-react/index.js +384 -289
  15. package/dist/dock-react/index.js.map +1 -1
  16. package/dist/dock-react/panel-body.d.ts +24 -0
  17. package/dist/dock-react/panel-body.d.ts.map +1 -0
  18. package/dist/dock-react/split-sizing.d.ts +109 -0
  19. package/dist/dock-react/split-sizing.d.ts.map +1 -0
  20. package/dist/dock-react/split-view.d.ts +15 -0
  21. package/dist/dock-react/split-view.d.ts.map +1 -0
  22. package/dist/{electron-deck-B9fun93z.js → electron-deck-Bi-u-Aze.js} +175 -79
  23. package/dist/electron-deck-Bi-u-Aze.js.map +1 -0
  24. package/dist/electron-deck.d.ts +5 -0
  25. package/dist/electron-deck.d.ts.map +1 -1
  26. package/dist/events.d.ts +1 -0
  27. package/dist/events.d.ts.map +1 -1
  28. package/dist/host/index.d.ts +5 -0
  29. package/dist/host/index.d.ts.map +1 -1
  30. package/dist/host/index.js +1 -1
  31. package/dist/index.js +1 -1
  32. package/dist/internal/deck-app-view-move.d.ts +17 -0
  33. package/dist/internal/deck-app-view-move.d.ts.map +1 -0
  34. package/dist/internal/deck-app.d.ts +58 -1
  35. package/dist/internal/deck-app.d.ts.map +1 -1
  36. package/dist/layout/index.d.ts +1 -0
  37. package/dist/layout/index.d.ts.map +1 -1
  38. package/dist/layout/index.js +2 -2
  39. package/dist/layout/model.d.ts.map +1 -1
  40. package/dist/layout/mutations.d.ts.map +1 -1
  41. package/dist/layout/tree-query.d.ts +18 -0
  42. package/dist/layout/tree-query.d.ts.map +1 -0
  43. package/dist/layout/types.d.ts +11 -9
  44. package/dist/layout/types.d.ts.map +1 -1
  45. package/dist/{layout-ByOiOdnc.js → layout-D-LYJIpM.js} +57 -41
  46. package/dist/layout-D-LYJIpM.js.map +1 -0
  47. package/dist/main/compositor.d.ts.map +1 -1
  48. package/dist/main/index.js +1 -1
  49. package/dist/main/view-handle.d.ts.map +1 -1
  50. package/dist/preload/index.cjs +5 -0
  51. package/dist/preload/index.cjs.map +1 -1
  52. package/dist/preload/index.d.ts +5 -0
  53. package/dist/preload/index.d.ts.map +1 -1
  54. package/dist/preload/index.js +5 -0
  55. package/dist/preload/index.js.map +1 -1
  56. package/dist/types.d.ts +32 -10
  57. package/dist/types.d.ts.map +1 -1
  58. package/dist/{view-handle-DDhpBuW-.js → view-handle-zav25k3-.js} +141 -86
  59. package/dist/view-handle-zav25k3-.js.map +1 -0
  60. package/package.json +11 -5
  61. package/dist/electron-deck-B9fun93z.js.map +0 -1
  62. package/dist/layout-ByOiOdnc.js.map +0 -1
  63. package/dist/view-handle-DDhpBuW-.js.map +0 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"electron-deck-Bi-u-Aze.js","names":[],"sources":["../src/events.ts","../src/internal/deck-app-view-move.ts","../src/internal/event-bus.ts","../src/internal/ipc-registry-memory.ts","../src/internal/trust-set.ts","../src/internal/lifecycle-manager.ts","../src/internal/resource-registry.ts","../src/host/capability.ts","../src/internal/wire-transport.ts","../src/host/control-bus.ts","../src/internal/deck-app.ts","../src/electron-deck.ts"],"sourcesContent":["import { EventNotBoundError } from './errors.js'\nimport type { Disposable, HostEvent, JsonValue } from './types.js'\n\n/**\n * Module-private internals。把 publisher 和 listener 集合放在 WeakMap 而不是\n * 类实例字段上,外部即便拿到 HostEvent 也无法通过 `as any` / 原型链触达。\n *\n * Limitation:同 process 内若加载了**两份**不同 instance 的 `@dimina-kit/electron-deck`,\n * 它们的 WeakMap 互不相通——这是 packaging 问题,不是 API 设计问题。framework\n * 期待 monorepo + peer-dep 锁定单实例。\n */\ninterface EventInternals {\n\tpublisher: ((payload: JsonValue) => void) | null\n\tlisteners: Set<(payload: JsonValue) => void>\n}\n\nconst internals: WeakMap<HostEvent<JsonValue>, EventInternals> = new WeakMap()\n\nclass HostEventImpl<P extends JsonValue> implements HostEvent<P> {\n\treadonly name: string\n\n\tconstructor(name: string) {\n\t\tthis.name = name\n\t\tinternals.set(this as HostEvent<JsonValue>, {\n\t\t\tpublisher: null,\n\t\t\tlisteners: new Set(),\n\t\t})\n\t}\n\n\tpublish(payload: P): void {\n\t\tconst it = internals.get(this as HostEvent<JsonValue>)\n\t\tif (!it || it.publisher === null) throw new EventNotBoundError(this.name)\n\t\tit.publisher(payload as JsonValue)\n\t\tfor (const l of it.listeners) {\n\t\t\ttry {\n\t\t\t\tl(payload as JsonValue)\n\t\t\t}\n\t\t\tcatch (e) {\n\t\t\t\tconsole.error(`[electron-deck] HostEvent \"${this.name}\" listener threw:`, e)\n\t\t\t}\n\t\t}\n\t}\n\n\ton(listener: (payload: P) => void): Disposable {\n\t\tconst it = internals.get(this as HostEvent<JsonValue>)\n\t\tif (!it) {\n\t\t\tthrow new TypeError(\n\t\t\t\t`HostEvent \"${this.name}\" has no internals — was it produced by defineEvent()?`,\n\t\t\t)\n\t\t}\n\t\tit.listeners.add(listener as (payload: JsonValue) => void)\n\t\treturn {\n\t\t\tdispose: () => {\n\t\t\t\tit.listeners.delete(listener as (payload: JsonValue) => void)\n\t\t\t},\n\t\t}\n\t}\n}\n\n/** @experimental No production consumer yet — see the note on {@link HostEvent}. */\nexport function defineEvent<P extends JsonValue>(name: string): HostEvent<P> {\n\tif (typeof name !== 'string' || name.length === 0) {\n\t\tthrow new TypeError('defineEvent(name): name must be a non-empty string')\n\t}\n\treturn new HostEventImpl<P>(name)\n}\n\n/**\n * 校验 `ev` 是否真的来自 `defineEvent()`。判定方式是 WeakMap 身份,而非\n * `instanceof` —— 这样跨 vm.Context(同 module instance)也能工作。\n *\n * @internal\n */\nexport function isHostEvent(ev: unknown): ev is HostEvent<JsonValue> {\n\tif (ev === null || typeof ev !== 'object') return false\n\treturn internals.has(ev as HostEvent<JsonValue>)\n}\n\n/** @internal framework 在 Bind 阶段调;同实例重复 bind 覆盖前任 */\nexport function bindHostEvent<P extends JsonValue>(\n\tevent: HostEvent<P>,\n\tpublisher: (payload: P) => void,\n): void {\n\tconst it = internals.get(event as HostEvent<JsonValue>)\n\tif (!it) {\n\t\tthrow new TypeError(\n\t\t\t'bindHostEvent: argument is not a HostEvent produced by defineEvent()',\n\t\t)\n\t}\n\tit.publisher = publisher as (payload: JsonValue) => void\n}\n\n/** @internal framework 在 Cleanup 阶段调 */\nexport function unbindHostEvent<P extends JsonValue>(event: HostEvent<P>): void {\n\tconst it = internals.get(event as HostEvent<JsonValue>)\n\tif (it) {\n\t\tit.publisher = null\n\t\tit.listeners.clear()\n\t}\n}\n","import type { MinimalBrowserWindow, MinimalWebContentsView } from './electron-types.js'\nimport type { ViewSubstrate } from './deck-app.js'\n\n/**\n * `DeckViewHandle.moveTo` failure cleanup. `inner.moveTo` does the atomic\n * native Compositor migration + Scope adopt/rollback; when it rejects, the\n * Compositor's own rollback only reverts state it TRACKED (`order`) — a\n * native `addChildView` that throws MID-APPLY can leave the WCV attached to\n * the dest window's `contentView` with no tracked record to remove it. This\n * balances that leak with an explicit dest detach, then undoes the dest\n * substrate registration made before the move attempt.\n *\n * Each step is independently guarded (its own try/catch) so a cleanup\n * failure in one step never masks the other step or the ORIGINAL move\n * error — `originalErr` is always rethrown, never shadowed.\n */\nexport function cleanupDestOnMoveToFailure(\n\tdestWin: MinimalBrowserWindow,\n\tdestSub: ViewSubstrate,\n\twcv: MinimalWebContentsView,\n\tviewId: string,\n\toriginalErr: unknown,\n): never {\n\ttry {\n\t\t// Only remove when the WCV is a KNOWN child, or membership is UNKNOWN\n\t\t// (`children` absent → can't verify, but a leaked mid-apply add must\n\t\t// still be detached; removeChildView of a non-child is a no-op in real\n\t\t// Electron). Check isDestroyed() first: reading `.contentView` on an\n\t\t// already-destroyed dest window throws, and a dead dest window's native\n\t\t// child is gone anyway — nothing to detach.\n\t\tif (!destWin.isDestroyed()) {\n\t\t\tconst destChildren = destWin.contentView.children\n\t\t\tconst maybeAttached = destChildren == null || destChildren.includes(wcv)\n\t\t\tif (maybeAttached) {\n\t\t\t\tdestWin.contentView.removeChildView(wcv)\n\t\t\t}\n\t\t}\n\t}\n\tcatch (cleanupErr) {\n\t\tconsole.error('[electron-deck] moveTo dest detach failed (original error rethrown):', cleanupErr)\n\t}\n\ttry {\n\t\t// ALWAYS undo the dest registration so the dest substrate never tracks a\n\t\t// view it doesn't host — independent of the detach above.\n\t\tdestSub.unregisterView(viewId)\n\t}\n\tcatch (cleanupErr) {\n\t\tconsole.error('[electron-deck] moveTo dest unregister failed (original error rethrown):', cleanupErr)\n\t}\n\tthrow originalErr\n}\n","import { bindHostEvent, unbindHostEvent } from '../events.js'\nimport type { Disposable, HostEvent, JsonValue } from '../types.js'\n\n/**\n * Framework-internal event bus。\n *\n * 责任:\n * - `bindDeclaredEvents(events)`:在 Bind 阶段调;对每个声明的 HostEvent,调用\n * framework binder,让该 event 的 `publish()` 走 bus。\n * - `publish(name, payload)`:通知 `subscribe(name, ...)` 注册的所有 listener。\n * - `subscribe(name, listener)`:返回 `Disposable`,dispose 后停止通知。\n * - `unbindAll()`:解绑所有 declared event publisher + 清空 subscribers。\n *\n * @internal\n */\nexport class EventBus {\n\tprivate readonly subscribers = new Map<string, Set<(payload: JsonValue) => void>>()\n\tprivate readonly allSubscribers = new Set<(name: string, payload: JsonValue) => void>()\n\tprivate readonly inFlight = new Set<string>()\n\tprivate boundEvents: HostEvent<JsonValue>[] = []\n\n\tbindDeclaredEvents(events: readonly HostEvent<JsonValue>[]): void {\n\t\tfor (const ev of events) {\n\t\t\tbindHostEvent(ev, (payload: JsonValue) => this.publish(ev.name, payload))\n\t\t\tthis.boundEvents.push(ev)\n\t\t}\n\t}\n\n\tpublish(name: string, payload: JsonValue): void {\n\t\t// 重入守护:listener 内同步再 publish 同一 event 会无限递归。检测到后\n\t\t// log + drop,不抛错(避免破坏 fire-and-forget 语义中已经在跑的\n\t\t// listener 链)。\n\t\tif (this.inFlight.has(name)) {\n\t\t\tconsole.error(`[electron-deck] reentrant publish on \"${name}\" dropped (listener republished the same event synchronously)`)\n\t\t\treturn\n\t\t}\n\t\tthis.inFlight.add(name)\n\t\ttry {\n\t\t\tconst subs = this.subscribers.get(name)\n\t\t\tif (subs && subs.size > 0) {\n\t\t\t\t// snapshot 防止 listener 中 dispose 影响迭代\n\t\t\t\tconst snapshot = Array.from(subs)\n\t\t\t\tfor (const l of snapshot) {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tl(payload)\n\t\t\t\t\t}\n\t\t\t\t\tcatch (e) {\n\t\t\t\t\t\tconsole.error(`[electron-deck] subscriber for \"${name}\" threw:`, e)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (this.allSubscribers.size > 0) {\n\t\t\t\tconst snapshot = Array.from(this.allSubscribers)\n\t\t\t\tfor (const l of snapshot) {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tl(name, payload)\n\t\t\t\t\t}\n\t\t\t\t\tcatch (e) {\n\t\t\t\t\t\tconsole.error(`[electron-deck] catch-all subscriber threw on \"${name}\":`, e)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tfinally {\n\t\t\tthis.inFlight.delete(name)\n\t\t}\n\t}\n\n\tsubscribe(name: string, listener: (payload: JsonValue) => void): Disposable {\n\t\tlet subs = this.subscribers.get(name)\n\t\tif (!subs) {\n\t\t\tsubs = new Set()\n\t\t\tthis.subscribers.set(name, subs)\n\t\t}\n\t\tsubs.add(listener)\n\t\tconst set = subs\n\t\treturn {\n\t\t\tdispose: () => {\n\t\t\t\tset.delete(listener)\n\t\t\t},\n\t\t}\n\t}\n\n\t/**\n\t * 订阅所有 channel 的 publish —— framework wire-transport 把 declared\n\t * HostEvent.publish() 桥到 webContents.send 时用。\n\t */\n\tsubscribeAll(listener: (name: string, payload: JsonValue) => void): Disposable {\n\t\tthis.allSubscribers.add(listener)\n\t\treturn {\n\t\t\tdispose: () => {\n\t\t\t\tthis.allSubscribers.delete(listener)\n\t\t\t},\n\t\t}\n\t}\n\n\tunbindAll(): void {\n\t\tfor (const ev of this.boundEvents) unbindHostEvent(ev)\n\t\tthis.boundEvents = []\n\t\tthis.subscribers.clear()\n\t\tthis.allSubscribers.clear()\n\t}\n}\n","import type { BrowserWindow } from 'electron'\nimport type {\n\tDisposable,\n\tJsonValue,\n\tMaybePromise,\n\tTypedIpcRegistry,\n} from '../types.js'\n\ntype AnyHandler = (...args: JsonValue[]) => MaybePromise<JsonValue>\ntype AnyListener = (payload: JsonValue) => void\n\n/**\n * In-memory `TypedIpcRegistry` impl —— Phase 2 不接真 Electron `ipcMain`,提\n * 供 main-process 同进程 `handle` / `on` / `send` / `invoke` 的 fake。\n *\n * - `handle` / `invoke` 与 `on` / `send` 是**独立 channel space**:send 只触\n * 发 on listener;invoke 只调 handle handler;同 channel 名互不影响。\n * - audience / validator option Phase 2 不强制,仅 accept;Phase 3 接 trusted\n * sender set 时再实施。\n *\n * @internal\n */\nexport class InMemoryTypedIpcRegistry implements TypedIpcRegistry {\n\tprivate readonly handlers = new Map<string, AnyHandler>()\n\tprivate readonly listeners = new Map<string, Set<AnyListener>>()\n\n\thandle<A extends JsonValue[], R extends JsonValue>(\n\t\tchannel: string,\n\t\thandler: (...args: A) => MaybePromise<R>,\n\t\t_options?: {\n\t\t\tvalidator?: (args: unknown[]) => A\n\t\t},\n\t): Disposable {\n\t\tif (this.handlers.has(channel)) {\n\t\t\tthrow new Error(`channel already handled: \"${channel}\"`)\n\t\t}\n\t\tconst cast = handler as unknown as AnyHandler\n\t\tthis.handlers.set(channel, cast)\n\t\treturn {\n\t\t\tdispose: () => {\n\t\t\t\tif (this.handlers.get(channel) === cast) this.handlers.delete(channel)\n\t\t\t},\n\t\t}\n\t}\n\n\ton<P extends JsonValue>(channel: string, listener: (payload: P) => void): Disposable {\n\t\tlet set = this.listeners.get(channel)\n\t\tif (!set) {\n\t\t\tset = new Set()\n\t\t\tthis.listeners.set(channel, set)\n\t\t}\n\t\tconst cast = listener as unknown as AnyListener\n\t\tset.add(cast)\n\t\tconst ref = set\n\t\treturn {\n\t\t\tdispose: () => {\n\t\t\t\tref.delete(cast)\n\t\t\t},\n\t\t}\n\t}\n\n\tsend(\n\t\t_target: 'mainWindow' | BrowserWindow,\n\t\tchannel: string,\n\t\tpayload: JsonValue,\n\t): void {\n\t\tconst set = this.listeners.get(channel)\n\t\tif (!set) return\n\t\tconst snapshot = Array.from(set)\n\t\tfor (const l of snapshot) {\n\t\t\ttry {\n\t\t\t\tl(payload)\n\t\t\t}\n\t\t\tcatch (e) {\n\t\t\t\tconsole.error(`[electron-deck] ipc on \"${channel}\" listener threw:`, e)\n\t\t\t}\n\t\t}\n\t}\n\n\t/** @internal main-internal helper used by Phase 2 setup tests / framework */\n\tasync invoke<R extends JsonValue>(channel: string, ...args: JsonValue[]): Promise<R> {\n\t\tconst handler = this.handlers.get(channel)\n\t\tif (!handler) throw new Error(`no handler for channel: \"${channel}\"`)\n\t\tconst r = await handler(...args)\n\t\treturn r as R\n\t}\n}\n","/**\n * TrustSet — SEALED refcount membership of trusted webContents.\n *\n * Refcount membership (Map<wc, refcount>) factored into a standalone primitive\n * so it can back both `DeckApp` and the\n * domain-neutral `ControlBus` facade.\n *\n * SEALED WRITE MODEL: there is exactly ONE writer — `admit(wc, owner)`. It forces\n * every trust lease to be OWNED by a `Scope`, so a lease can never outlive its\n * owner and no un-owned / leaked lease is possible. The old public `add(wc)`\n * (un-owned lease minting) and the `deleteEntry(wc)` imperative wipe are GONE:\n * - releasing a lease early → use the Disposable `admit` returns;\n * - releasing ALL of an owner's leases → close/reset that owner Scope (the\n * refcount-- disposers are owned by it, so its teardown zeroes them).\n *\n * Semantics (unchanged refcount core):\n * - `admit(wc, owner)` increments the wc's refcount and owns the matching\n * refcount-- on `owner`; the entry is removed when the count reaches zero.\n * - `isTrusted(id)` is true iff any live entry's `.id === id`.\n * - `snapshot()` returns the live keys (used for event fanout).\n *\n * Idempotency note: each refcount-- disposer is one-shot and guarded by a\n * `c === undefined` check — disposing a handle after the entry has already been\n * dropped (e.g. via the owner's teardown) reads `refs.get(wc)`, sees `undefined`,\n * and returns without touching the map, so a residual handle can never resurrect\n * or mis-decrement a re-added entry.\n *\n * @internal\n */\n\nimport type { Disposable } from '../types.js'\nimport type { MinimalWebContents } from './wire-transport.js'\nimport type { Scope } from '../main/scope.js'\n\n/** Read-only trust index (gate + fanout). No write surface. */\nexport interface TrustIndex {\n\t/** true iff some live entry's `.id === id`. */\n\tisTrusted(id: number): boolean\n\t/** live keys, for event fanout. */\n\tsnapshot(): readonly MinimalWebContents[]\n}\n\n/** Admit-capable trust set. The ONLY writer is `admit`, which forces an owner\n * Scope so a trust lease can never outlive its owner (no un-owned lease). */\nexport interface TrustSet extends TrustIndex {\n\t/**\n\t * Admit `wc` to the trust set (refcount++). The refcount-- release is OWNED by\n\t * `owner` (so `owner.reset()`/`close()` releases it automatically). Returns a\n\t * one-shot Disposable that releases JUST this lease early (idempotent; never\n\t * double-decrements, and a later `owner` teardown over an already-released\n\t * lease is a safe no-op via the refs-undefined guard).\n\t */\n\tadmit(wc: MinimalWebContents, owner: Scope): Disposable\n}\n\nexport function createTrustSet(): TrustSet {\n\tconst refs = new Map<MinimalWebContents, number>()\n\treturn {\n\t\tadmit(wc: MinimalWebContents, owner: Scope): Disposable {\n\t\t\tconst cur = refs.get(wc) ?? 0\n\t\t\trefs.set(wc, cur + 1)\n\t\t\tlet disposed = false\n\t\t\t// Raw refcount-- disposable (the former `add` body). One-shot + the\n\t\t\t// `c === undefined` guard keeps it safe across owner teardown / stale handles.\n\t\t\tconst rawDisposable: Disposable = {\n\t\t\t\tdispose: () => {\n\t\t\t\t\tif (disposed) return\n\t\t\t\t\tdisposed = true\n\t\t\t\t\tconst c = refs.get(wc)\n\t\t\t\t\tif (c === undefined) return\n\t\t\t\t\tif (c <= 1) refs.delete(wc)\n\t\t\t\t\telse refs.set(wc, c - 1)\n\t\t\t\t},\n\t\t\t}\n\t\t\t// Owner owns the refcount-- so its reset()/close() releases this lease.\n\t\t\tconst lease = owner.own(rawDisposable)\n\t\t\tlet released = false\n\t\t\treturn {\n\t\t\t\tdispose: () => {\n\t\t\t\t\tif (released) return\n\t\t\t\t\treleased = true\n\t\t\t\t\tlease.dispose()\n\t\t\t\t},\n\t\t\t}\n\t\t},\n\t\tisTrusted(id: number): boolean {\n\t\t\tfor (const wc of refs.keys()) {\n\t\t\t\tif (wc.id === id) return true\n\t\t\t}\n\t\t\treturn false\n\t\t},\n\t\tsnapshot(): readonly MinimalWebContents[] {\n\t\t\treturn Array.from(refs.keys())\n\t\t},\n\t}\n}\n","/**\n * Lifecycle phase machine — see workbench-model.md §5.\n *\n * 8 个 phase 严格递增,无跳跃:\n * init → bind → setup → ready → drain → cleanup → destroy → quit\n *\n * @internal\n */\n\nexport type LifecyclePhase =\n\t| 'init'\n\t| 'bind'\n\t| 'setup'\n\t| 'ready'\n\t| 'drain'\n\t| 'cleanup'\n\t| 'destroy'\n\t| 'quit'\n\nconst ORDER: readonly LifecyclePhase[] = [\n\t'init',\n\t'bind',\n\t'setup',\n\t'ready',\n\t'drain',\n\t'cleanup',\n\t'destroy',\n\t'quit',\n]\n\nexport class LifecyclePhaseError extends Error {\n\treadonly from: LifecyclePhase\n\treadonly to: LifecyclePhase\n\tconstructor(from: LifecyclePhase, to: LifecyclePhase) {\n\t\tsuper(`Illegal lifecycle transition: ${from} → ${to}`)\n\t\tthis.name = 'LifecyclePhaseError'\n\t\tthis.from = from\n\t\tthis.to = to\n\t}\n}\n\nexport function phaseOrder(p: LifecyclePhase): number {\n\tconst idx = ORDER.indexOf(p)\n\tif (idx === -1) throw new Error(`unknown lifecycle phase: ${String(p)}`)\n\treturn idx\n}\n\nexport class LifecycleManager {\n\tcurrent: LifecyclePhase = 'init'\n\n\tenter(to: LifecyclePhase): void {\n\t\tconst fromOrder = phaseOrder(this.current)\n\t\tconst toOrder = phaseOrder(to)\n\t\tif (toOrder !== fromOrder + 1) {\n\t\t\tthrow new LifecyclePhaseError(this.current, to)\n\t\t}\n\t\tthis.current = to\n\t}\n\n\tassertAtLeast(p: LifecyclePhase): void {\n\t\tif (phaseOrder(this.current) < phaseOrder(p)) {\n\t\t\tthrow new Error(\n\t\t\t\t`lifecycle assertion failed: current=${this.current}, required >= ${p}`,\n\t\t\t)\n\t\t}\n\t}\n\n\t/**\n\t * Abnormal-path setter:setup throw / 紧急 shutdown 跳过中间 phase 直达\n\t * 指定 teardown phase。不做顺序校验,仅 framework 内部使用。\n\t *\n\t * @internal\n\t */\n\t_force(to: LifecyclePhase): void {\n\t\tthis.current = to\n\t}\n}\n","import type { Disposable, MaybePromise, ResourceRegistry } from '../types.js'\n\n/**\n * I3: LIFO disposable registry。\n *\n * 合同:\n * - `add(d)` 接受 `Disposable` 或 nullary cleanup function;返回 wrapper\n * `Disposable`,dispose 自身会 (a) 调用 underlying dispose, (b) 从 registry\n * 除名,避免 disposeAll 再次调。\n * - `disposeAll()` 按**注册逆序**调 dispose(LIFO);任一 dispose 抛错都不阻断\n * 后续,全部跑完再 throw `AggregateError`(仅当至少一个失败时);调过一次后\n * 再次调是 no-op resolve。\n * - 在 disposeAll 已跑后,`add()` 仍然要立即 dispose 新资源(不泄漏)。\n *\n * @deprecated 地基层的承重 disposable 原语已收敛到 `src/main/disposable.ts`\n * 的 `DisposableRegistry`(foundation.md §11 决策①:连接层选用 disposed-后-add-抛错\n * 语义 + reset=换实例)。本实现的 disposed-后-add-立即-dispose 语义与之互斥,\n * 仅 `deck-app.ts` 旧路径仍在用,不再用于新代码。后续期删除。\n * @internal\n */\ninterface Entry {\n\treadonly id: number\n\treadonly dispose: () => MaybePromise<void>\n}\n\nexport class ResourceRegistryImpl implements ResourceRegistry {\n\tprivate entries: Entry[] = []\n\tprivate nextId = 1\n\tprivate allDisposed = false\n\n\tadd(d: Disposable | (() => MaybePromise<void>)): Disposable {\n\t\tconst underlying: () => MaybePromise<void>\n\t\t\t= typeof d === 'function' ? d : () => d.dispose()\n\n\t\tif (this.allDisposed) {\n\t\t\t// add() after disposeAll() → 立即 dispose,不进 registry(防泄漏)\n\t\t\tvoid runQuietly(underlying)\n\t\t\treturn { dispose: () => {} }\n\t\t}\n\n\t\tconst id = this.nextId++\n\t\tconst entry: Entry = { id, dispose: underlying }\n\t\tthis.entries.push(entry)\n\t\tlet disposedThis = false\n\t\treturn {\n\t\t\tdispose: async () => {\n\t\t\t\tif (disposedThis) return\n\t\t\t\tdisposedThis = true\n\t\t\t\tconst idx = this.entries.findIndex(e => e.id === id)\n\t\t\t\tif (idx !== -1) this.entries.splice(idx, 1)\n\t\t\t\tawait underlying()\n\t\t\t},\n\t\t}\n\t}\n\n\tasync disposeAll(): Promise<void> {\n\t\tif (this.allDisposed) return\n\t\tthis.allDisposed = true\n\t\tconst toDispose = this.entries.slice().reverse()\n\t\tthis.entries = []\n\t\tconst errors: unknown[] = []\n\t\tfor (const entry of toDispose) {\n\t\t\ttry {\n\t\t\t\tawait entry.dispose()\n\t\t\t}\n\t\t\tcatch (e) {\n\t\t\t\terrors.push(e)\n\t\t\t}\n\t\t}\n\t\tif (errors.length > 0) {\n\t\t\tthrow new AggregateError(\n\t\t\t\terrors,\n\t\t\t\t`ResourceRegistry.disposeAll: ${errors.length} dispose(s) failed`,\n\t\t\t)\n\t\t}\n\t}\n}\n\nfunction runQuietly(fn: () => MaybePromise<void>): MaybePromise<void> {\n\ttry {\n\t\tconst r = fn()\n\t\tif (r && typeof (r as Promise<void>).then === 'function') {\n\t\t\treturn (r as Promise<void>).catch(() => {})\n\t\t}\n\t\treturn undefined\n\t}\n\tcatch {\n\t\treturn undefined\n\t}\n}\n","/**\n * Capability grant registry.\n *\n * Default-DENY authorization for privileged control commands. A command is\n * allowed for a sender ONLY while a LIVE grant naming that command (exact,\n * whitelisted) is bound to that exact senderId. A grant dies the instant its\n * `senderScope` generation ends: navigation soft-reuse (`reset`), window\n * destroy (`closed`), or early `dispose()`.\n *\n * Each grant is a DISTINCT object stored in a Set, so two grants for the same\n * senderId union their commands, and revoking one (its `senderScope` died)\n * never touches another grant for that same senderId. The per-grant `revoked`\n * guard makes a closed scope's revoke a no-op against a newly-issued grant —\n * the wc.id-reuse safety property.\n *\n * @internal exported via `/host`.\n */\n\nimport type { Disposable } from '../types.js'\nimport type { Scope } from '../main/scope.js'\n\nexport interface Grant {\n\treadonly senderId: number\n\t/** grant lifetime — revoked on this scope's reset/closed. */\n\treadonly senderScope: Scope\n\t/**\n\t * OPTIONAL — stored as the authorization boundary for FUTURE per-target\n\t * view-command checks; the current grant gate authorizes by (senderId,\n\t * command-name) only — no command resolves a target view yet, so targetScope is\n\t * not consulted at dispatch. Reserved.\n\t */\n\treadonly targetScope?: Scope\n\treadonly commands: ReadonlySet<string>\n}\n\nexport interface CapabilityPolicy {\n\t/** true iff a LIVE grant exists with grant.senderId === senderId AND name ∈ grant.commands. default-DENY. */\n\tallows(senderId: number, name: string): boolean\n}\n\nexport function createCapabilityRegistry(): {\n\tpolicy: CapabilityPolicy\n\tissue(grant: Grant): Disposable\n\t/**\n\t * Synchronously revoke EVERY live grant whose `grant.senderId === senderId`.\n\t * Mirrors the trust path's synchronous window-'closed' revocation so a grant\n\t * is gone the instant a wc is destroyed — before the async wcScope cascade\n\t * fires `'closed'`. This closes the wc.id-reuse privilege-escalation window\n\t * (a reused wc.id can never inherit the old, not-yet-revoked grant). Each\n\t * per-grant `revoke` is idempotent, so the later async `'closed'` is a no-op.\n\t */\n\trevokeBySenderId(senderId: number): void\n} {\n\t// Track each grant WITH its revoke fn so the registry can revoke by senderId\n\t// synchronously (bulk), independent of the per-grant scope listeners.\n\tconst entries = new Set<{ grant: Grant, revoke: () => void }>()\n\treturn {\n\t\tpolicy: {\n\t\t\tallows(senderId, name) {\n\t\t\t\tfor (const e of entries) {\n\t\t\t\t\tif (e.grant.senderId === senderId && e.grant.commands.has(name)) return true\n\t\t\t\t}\n\t\t\t\treturn false\n\t\t\t},\n\t\t},\n\t\tissue(grant) {\n\t\t\tlet revoked = false\n\t\t\tconst off1 = grant.senderScope.on('reset', () => revoke())\n\t\t\tconst off2 = grant.senderScope.on('closed', () => revoke())\n\t\t\tfunction revoke() {\n\t\t\t\tif (revoked) return\n\t\t\t\trevoked = true\n\t\t\t\tentries.delete(entry)\n\t\t\t\toff1.dispose()\n\t\t\t\toff2.dispose()\n\t\t\t}\n\t\t\tconst entry = { grant, revoke }\n\t\t\tentries.add(entry)\n\t\t\treturn { dispose: revoke }\n\t\t},\n\t\trevokeBySenderId(senderId) {\n\t\t\t// Snapshot the matching entries first to avoid mutate-during-iterate\n\t\t\t// (each revoke deletes its own entry); each revoke is idempotent.\n\t\t\tconst matching: Array<{ grant: Grant, revoke: () => void }> = []\n\t\t\tfor (const e of entries) {\n\t\t\t\tif (e.grant.senderId === senderId) matching.push(e)\n\t\t\t}\n\t\t\tfor (const e of matching) e.revoke()\n\t\t},\n\t}\n}\n","/**\n * WireTransport — main 端的真 Electron wire 桥接。\n *\n * 责任:\n * - 在 `ipcMain.handle('__electron-deck:invoke')` / `'__electron-deck:probe'` 上路由\n * webview → main 的 RPC 请求,按 `senderPolicy` gating + kind 派发到 host /\n * simulator handler,序列化成 `InvokeResponse` 帧。\n * - 订阅 framework `EventBus`,把 declared `HostEvent.publish(payload)` 通过\n * `webContents.send('__electron-deck:event', envelope)` 推送给所有 trusted\n * webContents。\n *\n * 该类只持有 deps 引用,不直接 import 'electron';测试用 plain DI 注入 fake。\n * 生命周期一次性:start → dispose 后不能再 start(避免 listener 泄露 / 句柄\n * 双重注册);hot-restart 由 host 重新构造新实例。\n *\n * @internal\n */\n\nimport { DeckRemoteError } from '../errors.js'\nimport {\n\tBRIDGE_PROTOCOL_VERSION,\n\tDeckChannel,\n} from '../shared/protocol.js'\nimport type {\n\tEventEnvelope,\n\tInvokeFailure,\n\tInvokeRequest,\n\tInvokeResponse,\n\tProbeResponse,\n} from '../shared/protocol.js'\nimport type { Disposable, JsonValue, SenderPolicy } from '../types.js'\nimport type { EventBus } from './event-bus.js'\n\n/**\n * Framework-reserved invoke failure codes —— 全部带 `DECK_` 前缀,避免与\n * host 自定义 code 冲突。Client / host 都不应抛同前缀。\n */\nexport const DECK_CODE = {\n\tUntrustedSender: 'DECK_UNTRUSTED_SENDER',\n\tUntrustedFrame: 'DECK_UNTRUSTED_FRAME',\n\tUnknownKind: 'DECK_UNKNOWN_KIND',\n\tBadRequest: 'DECK_BAD_REQUEST',\n\t/** The grant gate denied a privileged command for this sender. */\n\tForbidden: 'DECK_FORBIDDEN',\n} as const\n\n/** Minimal RenderFrameHost identity used for main-frame discrimination. */\nexport interface FrameRef {\n\treadonly routingId: number\n\treadonly processId: number\n}\n\n/**\n * Per-invoke context threaded from {@link WireTransport.handleInvoke} into the\n * host/simulator invoke seams. Constructed ONLY after the wire's trust gate +\n * main-frame gate have both passed, so `senderId` is a real trusted webContents\n * id (never undefined). The grant gate (in `ControlBus.dispatch`) reads\n * this; it is a REQUIRED param so a missing call\n * site is a COMPILE error, not a silent security downgrade.\n */\nexport interface InvokeCtx {\n\t/** webContents id of the invoke sender (after the wire's trust + main-frame gates). */\n\treadonly senderId: number\n\t/** sender frame ref; main-frame already validated. Kept for future per-frame use; may be null. */\n\treadonly senderFrame: FrameRef | null\n}\n\nexport interface MinimalIpcMain {\n\thandle(\n\t\tchannel: string,\n\t\thandler: (\n\t\t\t// `mainFrame`/`senderFrame` are optional: real Electron events always\n\t\t\t// carry both (object or null after navigation), but frame-unaware unit\n\t\t\t// stubs model neither — the gate skips the frame check only for the\n\t\t\t// latter (see `isMainFrameSender`).\n\t\t\tevent: { sender: { id: number, mainFrame?: FrameRef | null }, senderFrame?: FrameRef | null },\n\t\t\t...args: unknown[]\n\t\t) => unknown | Promise<unknown>,\n\t): void\n\tremoveHandler(channel: string): void\n}\n\nexport interface MinimalWebContents {\n\treadonly id: number\n\tisDestroyed(): boolean\n\tsend(channel: string, payload: unknown): void\n}\n\nexport interface WireTransportDeps {\n\treadonly ipcMain: MinimalIpcMain\n\treadonly bus: EventBus\n\treadonly senderPolicy: SenderPolicy\n\t/** 取当前 trusted webContents 快照;用于 event push 广播。lazy:每次 publish 重调。 */\n\treadonly trustedWebContents: () => readonly MinimalWebContents[]\n\t/** 路由 host kind 调用;handler 抛错由 WireTransport 接住 → InvokeFailure。\n\t * `ctx` 必填:携带已过 trust + main-frame gate 的 senderId(授权门读它)。 */\n\treadonly invokeHost: (name: string, args: readonly JsonValue[], ctx: InvokeCtx) => Promise<JsonValue>\n\t/** 路由 simulator kind 调用;同上(`ctx` 必填)。 */\n\treadonly invokeSimulator: (name: string, args: readonly JsonValue[], ctx: InvokeCtx) => Promise<JsonValue>\n\t/**\n\t * 已声明的 event name 集合,作为 wire fanout 的 allowlist。**必填,\n\t * default-deny** —— 未在该集合内的 event name 不会跨进程下发(防止\n\t * framework 内部代码意外调 `bus.publish('foo')` 时 leak 给 webview)。\n\t * 调用方需保证每次返回的是当前 declared event 名字列表(lazy 快照,每次\n\t * publish 重读)。\n\t */\n\treadonly declaredEvents: () => readonly string[]\n\t/**\n\t * OPTIONAL slot-token inbound: `__electron-deck:snapshot` apply path. When\n\t * provided, `start()` registers the `Snapshot` handler (same trust + main-frame\n\t * gate as invoke). On gate pass → `onSnapshot(senderId, rawSnapshot)`. The\n\t * payload (the renderer's window-level placement table) is opaque here and\n\t * authorized/validated downstream — the wire only enforces the trust boundary.\n\t */\n\treadonly onSnapshot?: (senderId: number, rawSnapshot: unknown) => void\n\t/**\n\t * OPTIONAL slot-token inbound: `__electron-deck:layout-subscribe` per-wc\n\t * replay request. When provided, `start()` registers the `LayoutSubscribe`\n\t * handler (same gate). On gate pass → `onLayoutSubscribe(senderId)`.\n\t */\n\treadonly onLayoutSubscribe?: (senderId: number) => void\n}\n\ntype LifecycleState = 'idle' | 'started' | 'disposed'\n\nexport class WireTransport {\n\tprivate readonly deps: WireTransportDeps\n\tprivate state: LifecycleState = 'idle'\n\tprivate busSubscription: Disposable | null = null\n\t/** Whether the optional Snapshot / LayoutSubscribe handlers were registered\n\t * (either eagerly at start() via deps, or lazily via armSlotChannels) — so\n\t * dispose() only removes channels it actually registered. */\n\tprivate snapshotRegistered = false\n\tprivate layoutSubscribeRegistered = false\n\t/** Effective slot callbacks. Populated from deps at start() (eager path, used\n\t * by the wire unit tests) OR by {@link armSlotChannels} (lazy path, used by\n\t * deck-app so an app with no anchored views registers only Invoke + Probe). */\n\tprivate onSnapshotCb: ((senderId: number, rawSnapshot: unknown) => void) | null = null\n\tprivate onLayoutSubscribeCb: ((senderId: number) => void) | null = null\n\n\tconstructor(deps: WireTransportDeps) {\n\t\tthis.deps = deps\n\t}\n\n\tstart(): void {\n\t\tif (this.state === 'started') {\n\t\t\tthrow new Error('WireTransport already started')\n\t\t}\n\t\tif (this.state === 'disposed') {\n\t\t\tthrow new Error('WireTransport already disposed (single-use lifecycle); construct a new instance to restart')\n\t\t}\n\t\tthis.state = 'started'\n\n\t\t// 注册过程中任何一步抛错,必须回滚已成功的副作用(handler / subscription),\n\t\t// 否则会留下半状态:dispose() 看到 state !== 'started' 走 no-op 分支,造成\n\t\t// 已注册的 ipcMain handler 永远不被清除。\n\t\tconst cleanups: Array<() => void> = []\n\t\ttry {\n\t\t\tthis.deps.ipcMain.handle(DeckChannel.Probe, () => this.handleProbe())\n\t\t\tcleanups.push(() => this.tryRemoveHandler(DeckChannel.Probe))\n\n\t\t\tthis.deps.ipcMain.handle(DeckChannel.Invoke, (event, ...args) =>\n\t\t\t\tthis.handleInvoke(event?.sender?.id, event?.senderFrame, event?.sender?.mainFrame, args[0]))\n\t\t\tcleanups.push(() => this.tryRemoveHandler(DeckChannel.Invoke))\n\n\t\t\t// Eager path: deps carry the slot callbacks → register at start() (the\n\t\t\t// WireTransport unit tests drive this). deck-app instead leaves the deps\n\t\t\t// undefined and arms lazily via armSlotChannels() on the first anchored\n\t\t\t// placeIn, so a slot-less app registers only Invoke + Probe.\n\t\t\tif (this.deps.onSnapshot) {\n\t\t\t\tthis.onSnapshotCb = this.deps.onSnapshot\n\t\t\t\tthis.registerSnapshotHandler()\n\t\t\t\tcleanups.push(() => this.unregisterSnapshotHandler())\n\t\t\t}\n\t\t\tif (this.deps.onLayoutSubscribe) {\n\t\t\t\tthis.onLayoutSubscribeCb = this.deps.onLayoutSubscribe\n\t\t\t\tthis.registerLayoutSubscribeHandler()\n\t\t\t\tcleanups.push(() => this.unregisterLayoutSubscribeHandler())\n\t\t\t}\n\n\t\t\tthis.busSubscription = this.deps.bus.subscribeAll((name, payload) => {\n\t\t\t\tthis.fanoutEvent(name, payload)\n\t\t\t})\n\t\t\tcleanups.push(() => {\n\t\t\t\tif (this.busSubscription) {\n\t\t\t\t\tthis.busSubscription.dispose()\n\t\t\t\t\tthis.busSubscription = null\n\t\t\t\t}\n\t\t\t})\n\t\t}\n\t\tcatch (err) {\n\t\t\t// 反向回滚已成功的步骤;任何 cleanup 自身抛错都吞掉(仅 log),\n\t\t\t// 保证主 rethrow 链路不被淹没。\n\t\t\tfor (let i = cleanups.length - 1; i >= 0; i -= 1) {\n\t\t\t\ttry {\n\t\t\t\t\tcleanups[i]?.()\n\t\t\t\t}\n\t\t\t\tcatch (cleanupErr) {\n\t\t\t\t\tconsole.error('[electron-deck] start() rollback cleanup failed:', cleanupErr)\n\t\t\t\t}\n\t\t\t}\n\t\t\tthis.state = 'idle'\n\t\t\tthrow err\n\t\t}\n\t}\n\n\tprivate tryRemoveHandler(channel: string): void {\n\t\ttry {\n\t\t\tthis.deps.ipcMain.removeHandler(channel)\n\t\t}\n\t\tcatch (e) {\n\t\t\tconsole.error(`[electron-deck] removeHandler(${channel}) failed:`, e)\n\t\t}\n\t}\n\n\tprivate registerSnapshotHandler(): void {\n\t\tif (this.snapshotRegistered) return\n\t\tthis.deps.ipcMain.handle(DeckChannel.Snapshot, (event, ...args) =>\n\t\t\tthis.handleSnapshot(event?.sender?.id, event?.senderFrame, event?.sender?.mainFrame, args[0]))\n\t\tthis.snapshotRegistered = true\n\t}\n\n\tprivate unregisterSnapshotHandler(): void {\n\t\tif (!this.snapshotRegistered) return\n\t\tthis.snapshotRegistered = false\n\t\tthis.tryRemoveHandler(DeckChannel.Snapshot)\n\t}\n\n\tprivate registerLayoutSubscribeHandler(): void {\n\t\tif (this.layoutSubscribeRegistered) return\n\t\tthis.deps.ipcMain.handle(DeckChannel.LayoutSubscribe, event =>\n\t\t\tthis.handleLayoutSubscribe(event?.sender?.id, event?.senderFrame, event?.sender?.mainFrame))\n\t\tthis.layoutSubscribeRegistered = true\n\t}\n\n\tprivate unregisterLayoutSubscribeHandler(): void {\n\t\tif (!this.layoutSubscribeRegistered) return\n\t\tthis.layoutSubscribeRegistered = false\n\t\tthis.tryRemoveHandler(DeckChannel.LayoutSubscribe)\n\t}\n\n\t/**\n\t * Lazily register the slot-token inbound channels (`Place` + `LayoutSubscribe`)\n\t * on an already-started wire, wiring them to the given callbacks. Idempotent:\n\t * subsequent calls just refresh the callbacks (the handlers are registered\n\t * once). deck-app calls this on the FIRST anchored `placeIn`, so an app with\n\t * no anchored views never registers these channels (keeping the wire's\n\t * handler footprint at Invoke + Probe until a slot is actually minted).\n\t */\n\tarmSlotChannels(\n\t\tonSnapshot: (senderId: number, rawSnapshot: unknown) => void,\n\t\tonLayoutSubscribe: (senderId: number) => void,\n\t): void {\n\t\tthis.onSnapshotCb = onSnapshot\n\t\tthis.onLayoutSubscribeCb = onLayoutSubscribe\n\t\tif (this.state !== 'started') return\n\t\tthis.registerSnapshotHandler()\n\t\tthis.registerLayoutSubscribeHandler()\n\t}\n\n\tdispose(): void {\n\t\tif (this.state !== 'started') {\n\t\t\t// idle → 标记为 disposed 但不动 ipcMain;disposed → idempotent no-op\n\t\t\tthis.state = 'disposed'\n\t\t\treturn\n\t\t}\n\t\tthis.state = 'disposed'\n\n\t\ttry {\n\t\t\tthis.deps.ipcMain.removeHandler(DeckChannel.Invoke)\n\t\t}\n\t\tcatch (e) {\n\t\t\tconsole.error('[electron-deck] removeHandler(invoke) failed:', e)\n\t\t}\n\t\ttry {\n\t\t\tthis.deps.ipcMain.removeHandler(DeckChannel.Probe)\n\t\t}\n\t\tcatch (e) {\n\t\t\tconsole.error('[electron-deck] removeHandler(probe) failed:', e)\n\t\t}\n\t\t// Only remove the optional channels we actually registered, so a wire with\n\t\t// no onSnapshot/onLayoutSubscribe deps removes exactly Invoke + Probe.\n\t\tif (this.snapshotRegistered) {\n\t\t\tthis.snapshotRegistered = false\n\t\t\ttry {\n\t\t\t\tthis.deps.ipcMain.removeHandler(DeckChannel.Snapshot)\n\t\t\t}\n\t\t\tcatch (e) {\n\t\t\t\tconsole.error('[electron-deck] removeHandler(snapshot) failed:', e)\n\t\t\t}\n\t\t}\n\t\tif (this.layoutSubscribeRegistered) {\n\t\t\tthis.layoutSubscribeRegistered = false\n\t\t\ttry {\n\t\t\t\tthis.deps.ipcMain.removeHandler(DeckChannel.LayoutSubscribe)\n\t\t\t}\n\t\t\tcatch (e) {\n\t\t\t\tconsole.error('[electron-deck] removeHandler(layout-subscribe) failed:', e)\n\t\t\t}\n\t\t}\n\n\t\tif (this.busSubscription) {\n\t\t\tthis.busSubscription.dispose()\n\t\t\tthis.busSubscription = null\n\t\t}\n\t}\n\n\tprivate handleProbe(): ProbeResponse {\n\t\treturn { ready: true, version: BRIDGE_PROTOCOL_VERSION }\n\t}\n\n\t/**\n\t * Defense-in-depth main-frame check (mirrors devtools' verified\n\t * `isMainFrameSender`). A trusted webContents may embed a sub-frame of\n\t * arbitrary origin; only its top frame should reach gated invoke, so a\n\t * sub-frame can't spoof the trusted sender id.\n\t *\n\t * - NEITHER field modeled → frame-unaware stub → not a real frame boundary;\n\t * the sender-id gate is the boundary → allow (back-compat with the legacy\n\t * `{ sender: { id } }` stubs).\n\t * - either null (incl. a real event whose `senderFrame` resolved to null after\n\t * navigate-after-send / frame destruction, or a partial/malformed event) →\n\t * fail-closed → reject.\n\t * - main frame (routingId + processId match) → allow; else (sub frame) reject.\n\t */\n\tprivate isMainFrameSender(senderFrame: FrameRef | null | undefined, main: FrameRef | null | undefined): boolean {\n\t\tif (senderFrame === undefined && main === undefined) return true\n\t\tif (senderFrame == null || main == null) return false\n\t\treturn senderFrame.routingId === main.routingId && senderFrame.processId === main.processId\n\t}\n\n\tprivate async handleInvoke(\n\t\tsenderId: number | undefined,\n\t\tsenderFrame: FrameRef | null | undefined,\n\t\tmainFrame: FrameRef | null | undefined,\n\t\trawReq: unknown,\n\t): Promise<InvokeResponse> {\n\t\tconst validation = validateRequest(rawReq)\n\t\tif (!validation.ok) {\n\t\t\treturn failure(validation.name, validation.message, DECK_CODE.BadRequest)\n\t\t}\n\t\tconst req = validation.req\n\n\t\tif (typeof senderId !== 'number' || !this.deps.senderPolicy.isTrusted(senderId)) {\n\t\t\treturn failure(req.name, `untrusted sender (id=${String(senderId)})`, DECK_CODE.UntrustedSender)\n\t\t}\n\n\t\t// Frame-level gate sits ON TOP of the sender-id gate (not a replacement).\n\t\tif (!this.isMainFrameSender(senderFrame, mainFrame)) {\n\t\t\treturn failure(req.name, `untrusted frame (sender id=${String(senderId)})`, DECK_CODE.UntrustedFrame)\n\t\t}\n\n\t\t// Both gates passed → senderId is a real trusted number. Build the ctx once\n\t\t// and thread it into either seam (the grant gate reads ctx.senderId).\n\t\tconst ctx: InvokeCtx = { senderId, senderFrame: senderFrame ?? null }\n\n\t\ttry {\n\t\t\tif (req.kind === 'host') {\n\t\t\t\tconst result = await this.deps.invokeHost(req.name, req.args, ctx)\n\t\t\t\treturn { ok: true, result }\n\t\t\t}\n\t\t\tif (req.kind === 'simulator') {\n\t\t\t\tconst result = await this.deps.invokeSimulator(req.name, req.args, ctx)\n\t\t\t\treturn { ok: true, result }\n\t\t\t}\n\t\t\treturn failure(req.name, `unknown invoke kind: ${String(req.kind)}`, DECK_CODE.UnknownKind)\n\t\t}\n\t\tcatch (err) {\n\t\t\treturn serializeError(req.name, err)\n\t\t}\n\t}\n\n\t/**\n\t * `__electron-deck:snapshot` inbound. Same gate as {@link handleInvoke} (trust +\n\t * main-frame). On any gate failure (non-object payload, untrusted sender,\n\t * sub-frame) → DROP silently. The payload's inner shape is opaque to the wire —\n\t * authorized + validated downstream.\n\t */\n\tprivate handleSnapshot(\n\t\tsenderId: number | undefined,\n\t\tsenderFrame: FrameRef | null | undefined,\n\t\tmainFrame: FrameRef | null | undefined,\n\t\trawSnapshot: unknown,\n\t): void {\n\t\tif (rawSnapshot === null || typeof rawSnapshot !== 'object' || Array.isArray(rawSnapshot)) return\n\n\t\tif (typeof senderId !== 'number' || !this.deps.senderPolicy.isTrusted(senderId)) return\n\t\tif (!this.isMainFrameSender(senderFrame, mainFrame)) return\n\n\t\tthis.onSnapshotCb?.(senderId, rawSnapshot)\n\t}\n\n\t/**\n\t * `__electron-deck:layout-subscribe` inbound. Same gate as {@link handleInvoke}.\n\t * On gate pass → `onLayoutSubscribe(senderId)`; else DROP silently.\n\t */\n\tprivate handleLayoutSubscribe(\n\t\tsenderId: number | undefined,\n\t\tsenderFrame: FrameRef | null | undefined,\n\t\tmainFrame: FrameRef | null | undefined,\n\t): void {\n\t\tif (typeof senderId !== 'number' || !this.deps.senderPolicy.isTrusted(senderId)) return\n\t\tif (!this.isMainFrameSender(senderFrame, mainFrame)) return\n\n\t\tthis.onLayoutSubscribeCb?.(senderId)\n\t}\n\n\tprivate fanoutEvent(name: string, payload: JsonValue): void {\n\t\tif (this.state !== 'started') return\n\t\t// declaredEvents allowlist (default-deny):框架内部任何代码调\n\t\t// bus.publish(name) 而 name 未在 config.events 声明时,跨进程下发就是\n\t\t// leak,drop + warn。\n\t\tconst declared = this.deps.declaredEvents()\n\t\tif (!declared.includes(name)) {\n\t\t\tconsole.warn(`[electron-deck] dropping undeclared event \"${name}\" from wire fanout`)\n\t\t\treturn\n\t\t}\n\t\tconst env: EventEnvelope = { name, payload }\n\t\tlet targets: readonly MinimalWebContents[]\n\t\ttry {\n\t\t\ttargets = this.deps.trustedWebContents()\n\t\t}\n\t\tcatch (e) {\n\t\t\tconsole.error('[electron-deck] trustedWebContents() threw:', e)\n\t\t\treturn\n\t\t}\n\t\tfor (const wc of targets) {\n\t\t\tif (wc.isDestroyed()) continue\n\t\t\ttry {\n\t\t\t\twc.send(DeckChannel.Event, env)\n\t\t\t}\n\t\t\tcatch (e) {\n\t\t\t\tconsole.error(`[electron-deck] webContents.send(\"${DeckChannel.Event}\") failed for wc#${wc.id}:`, e)\n\t\t\t}\n\t\t}\n\t}\n}\n\n// ── helpers ─────────────────────────────────────────────────────────────────\n\ntype ValidationResult =\n\t| { ok: true, req: InvokeRequest }\n\t| { ok: false, name: string, message: string }\n\nfunction validateRequest(raw: unknown): ValidationResult {\n\tif (raw === null || typeof raw !== 'object' || Array.isArray(raw)) {\n\t\treturn { ok: false, name: 'unknown', message: 'invoke request must be a plain object' }\n\t}\n\tconst r = raw as Record<string, unknown>\n\tconst { kind, name, args } = r\n\tif (typeof kind !== 'string') {\n\t\treturn {\n\t\t\tok: false,\n\t\t\tname: typeof name === 'string' ? name : 'unknown',\n\t\t\tmessage: 'invoke request.kind must be a string',\n\t\t}\n\t}\n\tif (typeof name !== 'string') {\n\t\treturn { ok: false, name: 'unknown', message: 'invoke request.name must be a string' }\n\t}\n\tif (!Array.isArray(args)) {\n\t\treturn { ok: false, name, message: 'invoke request.args must be an array' }\n\t}\n\t// kind 字符串但值非 host/simulator 不在这里 reject —— handler 走 UNKNOWN_KIND 路径\n\treturn {\n\t\tok: true,\n\t\treq: {\n\t\t\tkind: kind as InvokeRequest['kind'],\n\t\t\tname,\n\t\t\targs: args as readonly JsonValue[],\n\t\t},\n\t}\n}\n\nfunction failure(remoteName: string, message: string, code?: string): InvokeFailure {\n\treturn { ok: false, error: { remoteName, message, code } }\n}\n\nfunction serializeError(invokeName: string, err: unknown): InvokeFailure {\n\t// host 抛 DeckRemoteError 时保留它自带的 remoteName + code(host 用同\n\t// 一个 error 类做\"再次远调\"的封装/重抛时,源信息不被中间环节覆盖)。\n\tif (err instanceof DeckRemoteError) {\n\t\t// `??` 而非 `||`:host 显式抛 `new DeckRemoteError('', ...)` 时(空\n\t\t// 字符串表 \"未知来源\" 的意图)保留 '',不被 invokeName 友好覆盖。\n\t\treturn failure(err.remoteName ?? invokeName, err.message, err.code)\n\t}\n\tif (err instanceof Error) {\n\t\tconst errAny = err as Error & { code?: unknown }\n\t\tconst code = typeof errAny.code === 'string' ? errAny.code : undefined\n\t\treturn failure(invokeName, err.message, code)\n\t}\n\tlet message: string\n\ttry {\n\t\tmessage = String(err)\n\t}\n\tcatch {\n\t\tmessage = 'unknown error (failed to stringify)'\n\t}\n\treturn failure(invokeName, message)\n}\n","/**\n * ControlBus — a thin, domain-neutral facade over the existing\n * {@link WireTransport} + {@link EventBus} + an injectable refcount\n * {@link TrustSet}.\n *\n * Three verbs, no new gating logic of its own:\n *\n * - `command(name, handler)` : webview → main RPC. The facade owns the single\n * command table and exposes {@link ControlBus.dispatch}; the wire's\n * `invokeHost` (and `invokeSimulator`) seam calls `dispatch(name, args)` so a\n * real webview→main invoke (after the wire's sender + main-frame gate) lands on\n * the registered handler. Callers never see the wire `kind`. Trust + main-frame\n * gating are entirely the wire's (`handleInvoke`): a trusted main-frame sender\n * reaches the handler, an untrusted sender → `DECK_UNTRUSTED_SENDER`, a\n * sub-frame → `DECK_UNTRUSTED_FRAME`.\n * - `event(name)` : main → webview push, default-deny. `name` is added to the\n * declared-event allowlist the wire reads; `publish` goes through `bus.publish`\n * → wire fanout (undeclared names are dropped by the wire). `dispose` revokes\n * the declaration.\n * - `trust(wc, owner)` : delegates to `trustSet.admit` (refcount membership owned\n * by `owner` Scope, so the lease is released when the owner tears down).\n *\n * Real wiring (Bug C): the command table is the SOLE command authority. The\n * production caller builds the {@link WireTransport} with\n * `invokeHost = (name, args) => controlBus.dispatch(name, args)` (and the same\n * for `invokeSimulator`, since the facade hides the kind), and `declaredEvents`\n * reading {@link ControlBus.declaredEvents}. So a real IPC invoke reaches the\n * command handler through the wire — not a private test-only seam. Any\n * config-declared host services register into this SAME table via `command()`,\n * so there is one namespace (the host keeps names unique), never two registries\n * that can collide.\n *\n * @internal exported via `/host`.\n */\n\nimport { DeckRemoteError } from '../errors.js'\nimport type { Disposable, JsonValue } from '../types.js'\nimport type { EventBus } from '../internal/event-bus.js'\nimport { DECK_CODE } from '../internal/wire-transport.js'\nimport type { InvokeCtx, MinimalWebContents, WireTransport } from '../internal/wire-transport.js'\nimport type { TrustSet } from '../internal/trust-set.js'\nimport type { Scope } from '../main/scope.js'\nimport type { CapabilityPolicy } from './capability.js'\n\nexport type { TrustSet }\nexport { createTrustSet } from '../internal/trust-set.js'\n\ntype CommandHandler = (...args: JsonValue[]) => JsonValue | Promise<JsonValue>\n\nexport interface ControlBusEventHandle<P extends JsonValue> {\n\tpublish(payload: P): void\n\tdispose(): void\n}\n\nexport interface ControlBus {\n\t/** Register a webview → main RPC handler under a domain-neutral `name`. */\n\tcommand(name: string, handler: CommandHandler): Disposable\n\t/** Declare a main → webview push event (default-deny allowlist). */\n\tevent<P extends JsonValue>(name: string): ControlBusEventHandle<P>\n\t/** Admit a webContents into the refcount trust set, owned by `owner` Scope\n\t * (the lease is released when `owner` resets/closes). */\n\ttrust(wc: MinimalWebContents, owner: Scope): Disposable\n\t/**\n\t * Real wire entry point — the {@link WireTransport}'s `invokeHost` /\n\t * `invokeSimulator` seam calls this with a domain-neutral `name` AFTER its\n\t * trust + main-frame gate. Resolves the command table; throws if `name` is\n\t * unregistered (the wire serialises that into an `InvokeFailure`). `ctx`\n\t * carries the gated senderId (the grant gate reads it when a policy is\n\t * configured; otherwise it is plumbed through unused).\n\t */\n\tdispatch(name: string, args: readonly JsonValue[], ctx: InvokeCtx): Promise<JsonValue>\n\t/**\n\t * Current declared-event allowlist snapshot — the wire's `declaredEvents`\n\t * seam reads this (lazy, per publish) so default-deny tracks `event()` /\n\t * `dispose()` in real time.\n\t */\n\tdeclaredEvents(): readonly string[]\n}\n\nexport interface CreateControlBusDeps {\n\t/**\n\t * Vestigial — the facade never calls into the wire from inside `dispatch` /\n\t * `command` / `event` / `trust` (the wire is the one that calls `dispatch`,\n\t * not the reverse). Made OPTIONAL so deck-app can construct the ControlBus\n\t * BEFORE the WireTransport (which references the ControlBus from its\n\t * `invokeHost` seam) — no circular dependency at construction time.\n\t */\n\treadonly transport?: WireTransport\n\treadonly bus: EventBus\n\treadonly trustSet: TrustSet\n\t/**\n\t * Privileged-command grant gate. When provided, `dispatch`\n\t * default-DENIES any command not authorized by a live grant for the gated\n\t * `ctx.senderId`. Omitted → no gate (backward-compatible \"trusted may\n\t * dispatch\" behaviour).\n\t */\n\treadonly policy?: CapabilityPolicy\n}\n\nexport function createControlBus(deps: CreateControlBusDeps): ControlBus {\n\tconst { bus, trustSet, policy } = deps\n\t// The facade owns the single command table + declared-event set. The wire\n\t// resolves invokes through `dispatch()` and reads the allowlist through\n\t// `declaredEvents()` — no shared mutable Map handed across the boundary.\n\tconst commandRegistry = new Map<string, CommandHandler>()\n\tconst declaredEventSet = new Set<string>()\n\n\treturn {\n\t\tcommand(name: string, handler: CommandHandler): Disposable {\n\t\t\t// Last-writer-wins, but dispose only unregisters if still the live owner —\n\t\t\t// a re-register under the same name must not be clobbered by a stale dispose.\n\t\t\tcommandRegistry.set(name, handler)\n\t\t\tlet disposed = false\n\t\t\treturn {\n\t\t\t\tdispose: () => {\n\t\t\t\t\tif (disposed) return\n\t\t\t\t\tdisposed = true\n\t\t\t\t\tif (commandRegistry.get(name) === handler) {\n\t\t\t\t\t\tcommandRegistry.delete(name)\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t}\n\t\t},\n\n\t\tevent<P extends JsonValue>(name: string): ControlBusEventHandle<P> {\n\t\t\tdeclaredEventSet.add(name)\n\t\t\tlet disposed = false\n\t\t\treturn {\n\t\t\t\tpublish: (payload: P) => {\n\t\t\t\t\t// Always route through the bus; the wire drops it if `name` is no\n\t\t\t\t\t// longer declared (default-deny), so a disposed handle can't fan out.\n\t\t\t\t\tbus.publish(name, payload as JsonValue)\n\t\t\t\t},\n\t\t\t\tdispose: () => {\n\t\t\t\t\tif (disposed) return\n\t\t\t\t\tdisposed = true\n\t\t\t\t\tdeclaredEventSet.delete(name)\n\t\t\t\t},\n\t\t\t}\n\t\t},\n\n\t\ttrust(wc: MinimalWebContents, owner: Scope): Disposable {\n\t\t\treturn trustSet.admit(wc, owner)\n\t\t},\n\n\t\tasync dispatch(name: string, args: readonly JsonValue[], ctx: InvokeCtx): Promise<JsonValue> {\n\t\t\tconst handler = commandRegistry.get(name)\n\t\t\tif (!handler) {\n\t\t\t\tthrow new Error(`no command registered: ${name}`)\n\t\t\t}\n\t\t\t// Grant gate. After resolving the command but\n\t\t\t// BEFORE running it, default-DENY any command the sender lacks a live\n\t\t\t// grant for. No policy injected → no gate (backward compatible).\n\t\t\tif (policy && !policy.allows(ctx.senderId, name)) {\n\t\t\t\tthrow new DeckRemoteError(name, `forbidden: ${name}`, DECK_CODE.Forbidden)\n\t\t\t}\n\t\t\treturn handler(...(args as JsonValue[]))\n\t\t},\n\n\t\tdeclaredEvents(): readonly string[] {\n\t\t\treturn Array.from(declaredEventSet)\n\t\t},\n\t}\n}\n","import { randomUUID } from 'node:crypto'\nimport type { NativeImage, WebContents } from 'electron'\nimport type {\n\tDisposable,\n\tFrameworkEvents,\n\tJsonValue,\n\tMaybePromise,\n\tRuntimeBackend,\n\tRuntime,\n\tSenderPolicy,\n\tWebviewSource,\n\tWindowContribution,\n\tWindowCreateOptions,\n\tDeckConfig,\n\tDeckContext,\n\tDeckViewHandle,\n\tDeckSession,\n\tDeckWindow,\n\tWindowCloseDecider,\n\tWindowCloseDecision,\n\tViewBounds,\n\tViewPlacement,\n} from '../types.js'\nimport type { BrowserWindow } from 'electron'\nimport { validateConfig } from '../electron-deck.js'\nimport { DeckChannel } from '../shared/protocol.js'\nimport type {\n\tMinimalApp,\n\tMinimalBrowserWindow,\n\tMinimalBrowserWindowOptions,\n\tMinimalElectron,\n\tMinimalWebContentsLike,\n\tMinimalWebContentsView,\n} from './electron-types.js'\nimport { cleanupDestOnMoveToFailure } from './deck-app-view-move.js'\nimport { EventBus } from './event-bus.js'\nimport { InMemoryTypedIpcRegistry } from './ipc-registry-memory.js'\nimport { createTrustSet } from './trust-set.js'\nimport type { TrustSet } from './trust-set.js'\nimport { LifecycleManager } from './lifecycle-manager.js'\nimport type { LifecyclePhase } from './lifecycle-manager.js'\nimport { ResourceRegistryImpl } from './resource-registry.js'\nimport { createScope, type Scope } from '../main/scope.js'\nimport {\n\tcreateCompositor,\n\ttype Compositor,\n\ttype ContentViewHost,\n\ttype NativeViewRef,\n} from '../main/compositor.js'\nimport { createViewHandle } from '../main/view-handle.js'\nimport {\n\tcreateInitialState,\n\treconcile,\n\tcleanSnapshot,\n\tdispatchOps,\n\ttype ReconcilerState,\n} from '../layout/index.js'\nimport {\n\tcreateCapabilityRegistry,\n\ttype CapabilityPolicy,\n} from '../host/capability.js'\nimport {\n\tcreateControlBus,\n\ttype ControlBus,\n} from '../host/control-bus.js'\nimport {\n\tWireTransport,\n\ttype MinimalIpcMain,\n\ttype MinimalWebContents,\n} from './wire-transport.js'\n\nconst SIMULATOR_CHANNEL_PREFIX = '__electron-deck:simulator:'\nconst HOST_CHANNEL_PREFIX = '__electron-deck:host:'\n\nconst DEFAULT_MAIN_WIDTH = 1024\nconst DEFAULT_MAIN_HEIGHT = 768\n\n/**\n * Optional dependencies for {@link DeckApp} —— 用于注入真 (或\n * mock) Electron `ipcMain` + trusted webContents 集合,让 framework 接通跨进程\n * wire transport。不注入则保持 main-internal-only 行为。\n *\n * @internal\n */\nexport interface DeckAppOptions {\n\treadonly wireTransport?: {\n\t\treadonly ipcMain: MinimalIpcMain\n\t\t/** 默认返回 framework 内部维护的 trusted set(由 windows.trust 填)。 */\n\t\treadonly trustedWebContents?: () => readonly MinimalWebContents[]\n\t\t/** 自定义 senderPolicy(默认按 trusted set 判断)。 */\n\t\treadonly senderPolicy?: SenderPolicy\n\t}\n\t/**\n\t * 注入真 (或 fake) Electron `BrowserWindow` / `WebContentsView`\n\t * 构造器;提供后 framework 会装配 mainWindow / toolbarView / declared\n\t * windows,否则保持 electron-unavailable 行为。\n\t */\n\treadonly electron?: MinimalElectron\n\t/**\n\t * 领域 backend。提供后 framework 在 whenReady 前跑 `beforeReady`,\n\t * 在 setup 阶段跑 `assemble(runtime)`。不提供则退化为纯框架(桩 context,\n\t * 仅测试/演示用)。\n\t */\n\treadonly backend?: RuntimeBackend\n}\n\n/**\n * Internal record for replaying window-created events into setup-time listeners:\n * bind 期间 ctor 已经完成时,setup 还没机会订阅;framework 缓存\n * baseline window-created 事件,setup 期间第一个 listener 注册时一次性消费\n * 整个队列(splice 0)。后续 listener 注册时队列已空,不重复 replay。\n *\n * **限制(CONTRACT)**:host 若在 setup 内注册多个 'window-created' listener,\n * 只有第一个会拿到 baseline replay;其它 listener 仅接收 runtime.windows.create()\n * 实时 emit。建议 host 用单一 listener 入口。\n */\ninterface PendingWindowCreated {\n\twindow: MinimalBrowserWindow\n\trole: 'main' | 'toolbar' | 'host'\n}\n\n/**\n * Internal record for buffering load-failed events that fired before any\n * 'load-failed' listener was registered. bind 阶段\n * loadAssembledSources() 触发的 loadURL/loadFile 是异步,rejection 的 microtask\n * 可能在 setup callback 的 await 点之间先跑,listener 还没注册。framework 在\n * fwListeners 上无 'load-failed' listener 时把 payload push 进队列;第一个\n * listener 注册时 splice 消费整个队列重放。\n */\ninterface PendingLoadFailed {\n\tsource: WebviewSource\n\terror: unknown\n}\n\n/**\n * Per-window native-view substrate. Each tracked window\n * gets ONE `Compositor` whose {@link ContentViewHost} adapts that window's\n * `contentView`. The minimal `contentView` has no `children()`, so the host\n * adapter TRACKS the Compositor-managed views' z-order itself (the toolbar,\n * added directly to `contentView`, is invisible to this `order`). `registerView`\n * binds a view id to its native `WebContentsView` so the adapter can translate a\n * Compositor `NativeViewRef` into the real `addChildView`/`removeChildView` call.\n */\nexport interface ViewSubstrate {\n\tcompositor: Compositor\n\twindowScope: Scope\n\tregisterView(id: string, wcv: MinimalWebContentsView): void\n\t/** Drop a disposed/detached view from this\n\t * substrate's registry + tracked z-order, so a long-lived window doesn't\n\t * accumulate dead views. Safe to call twice (Map.delete + guarded splice). */\n\tunregisterView(id: string): void\n}\n\n/**\n * Framework-internal \"app\" object —— `electronDeck(config)` 顶层入口的 plain-class\n * 形态,便于测试驱动 lifecycle 转换。加 wireTransport 注入\n * 后可接真 ipcMain;加 electron 注入后可装配 mainWindow / toolbarView\n * / declared windows。两者都不注入时退化为同进程内存 fake。\n *\n * @internal\n */\nexport class DeckApp {\n\treadonly config: DeckConfig\n\n\tprivate readonly lifecycle = new LifecycleManager()\n\tprivate readonly registry = new ResourceRegistryImpl()\n\tprivate readonly bus = new EventBus()\n\tprivate readonly ipc = new InMemoryTypedIpcRegistry()\n\tprivate readonly fwListeners = new Map<keyof FrameworkEvents, Set<(payload: unknown) => void>>()\n\tprivate readonly trustSet: TrustSet = createTrustSet()\n\t/** Privileged-command grant registry. The policy gates\n\t * ControlBus.dispatch; grants are minted via `runtime.grants.issue`. */\n\tprivate readonly capability = createCapabilityRegistry()\n\t/** The grant-gated command bus for PRIVILEGED `layout.*`\n\t * commands. Constructed in `bindWireTransport` with the capability policy\n\t * injected, so `dispatch` default-DENIES any command lacking a live grant.\n\t * Privileged commands are registered via `runtime.layout.command`; ordinary\n\t * domain APIs stay on the un-gated `InMemoryTypedIpcRegistry`. */\n\tprivate controlBus: ControlBus | null = null\n\t/** Per-webContents backend `onWindowTrusted` Disposable, so a window's\n\t * trust mirror is undone when THAT window closes (not only at teardown). */\n\tprivate readonly backendTrustDisposables = new Map<MinimalWebContents, Disposable>()\n\tprivate readonly options: DeckAppOptions\n\tprivate wireTransport: WireTransport | null = null\n\t/** The live wire senderPolicy, reused by buildRuntime so\n\t * `context._senderPolicy` reflects real trust instead of a `() => true` stub. */\n\tprivate wireSenderPolicy: SenderPolicy | null = null\n\tprivate mainWindow: MinimalBrowserWindow | null = null\n\tprivate toolbarView: MinimalWebContentsView | null = null\n\tprivate readonly declaredWindows = new Map<string, MinimalBrowserWindow>()\n\tprivate readonly trackedWindows = new Set<MinimalBrowserWindow>()\n\t/**\n\t * The root lifetime scope of the app + a shadow map mirroring `trackedWindows`,\n\t * keyed by each window's webContents, carrying a per-window child Scope. The\n\t * shadow's key set is kept in lock-step with `trackedWindows` at every\n\t * maintenance point.\n\t */\n\tprivate readonly rootScope: Scope = createScope()\n\tprivate readonly lifetimeShadow = new Map<\n\t\tMinimalWebContents,\n\t\t{ window: MinimalBrowserWindow, windowScope: Scope }\n\t>()\n\t/**\n\t * Per-trusted-webContents trust record. `wcScope` is a\n\t * child of the owning window's `windowScope`; it OWNS the wc's trust ref-count\n\t * lease(s). When the window closes, `windowScope.close()` cascades into this\n\t * `wcScope` (children-first LIFO), disposing every lease → ref-count hits 0 →\n\t * the wc leaves the trust set.\n\t * A wc can be trusted more than once (framework auto-trust + host\n\t * `windows.trust`), so `leases` is a Set.\n\t */\n\tprivate readonly wcRecords = new Map<\n\t\tMinimalWebContents,\n\t\t{ wcScope: Scope, leases: Set<Disposable>, windowScope: Scope }\n\t>()\n\tprivate readonly pendingWindowCreated: PendingWindowCreated[] = []\n\tprivate readonly pendingLoadFailed: PendingLoadFailed[] = []\n\t/**\n\t * Per-window native-view substrate, keyed by the window's\n\t * webContents (same key discipline as `lifetimeShadow`). Created at both\n\t * window-construction sites; dropped in `handleSubWindowClosed`.\n\t */\n\tprivate readonly windowSubstrates = new Map<MinimalWebContents, ViewSubstrate>()\n\t/**\n\t * Per-adopted-window registration handle, keyed by the adopted window's\n\t * webContents. `runtime.windows.adopt` is idempotent by wc identity: a second\n\t * adopt of the same window returns this stored Disposable (no double-admit, no\n\t * second substrate). The entry is removed when the registration is disposed\n\t * (early un-adopt) or the window's windowScope closes.\n\t */\n\tprivate readonly adoptedWindows = new Map<MinimalWebContents, Disposable>()\n\t/**\n\t * Per-registered-window {@link DeckWindow} facade + its per-window\n\t * close deciders, keyed by the window's control wc. Populated by\n\t * {@link registerWindow} for framework-created and adopted windows, plus the\n\t * framework-built main window. `deciders` is an ORDERED list run in\n\t * registration order on a close attempt; `closing` is the per-window in-flight\n\t * decision latch (mirrors the main-window `closingDecisionPromise`).\n\t */\n\tprivate readonly windowRegistrations = new Map<\n\t\tMinimalWebContents,\n\t\t{\n\t\t\twindow: MinimalBrowserWindow\n\t\t\tcontrolWc: MinimalWebContents\n\t\t\twindowScope: Scope\n\t\t\tdeckWindow: DeckWindow\n\t\t\tdeciders: WindowCloseDecider[]\n\t\t\tclosing: Promise<WindowCloseDecision> | null\n\t\t\tcommittedClose: boolean\n\t\t}\n\t>()\n\t/** The framework-built main window's control wc, so `runtime.windows.main`\n\t * can resolve its {@link DeckWindow}. Null under an `ownsWindows:true` backend. */\n\tprivate mainControlWc: MinimalWebContents | null = null\n\t/** Monotonic id source for `runtime.view` native views. */\n\tprivate viewSeq = 0\n\t/**\n\t * Provenance map for `runtime.scopes.create()` sessions: maps an opaque\n\t * {@link DeckSession} to its internal `rootScope.child()` Scope. A WeakMap so a\n\t * dropped session is GC'd; the framework holds the scope's lifetime via\n\t * rootScope anyway (the session scope is a rootScope child). `runtime.view`\n\t * resolves a passed session through this map — a foreign/raw Scope is absent\n\t * and therefore REJECTED.\n\t */\n\tprivate readonly sessions = new WeakMap<DeckSession, Scope>()\n\t/**\n\t * keepAlive「opt-in helper:runtime.view({ keepAlive })」 — opt-in per-group LRU of HIDDEN keep-alive views. Group key is\n\t * `lru:${max}` (all `keepAlive:{policy:'lru',max:N}` views share one group per\n\t * `max`). Each group holds an ORDERED list of HIDDEN view ids (front = least\n\t * recently visible = first to evict) + a map from view id to its host handle so\n\t * an eviction can dispose it (→ its WebContents is destroyed). Views created\n\t * without `keepAlive` never participate.\n\t */\n\tprivate readonly keepAliveGroups = new Map<\n\t\tstring,\n\t\t{ hidden: string[], handles: Map<string, DeckViewHandle> }\n\t>()\n\t/** Wcs that already have the `did-start-navigation` grant-reset hook bound,\n\t * so {@link bindNavigationGrantReset} is idempotent — a wc admitted to trust\n\t * more than once (e.g. constructed `autoTrust:false` then `windows.trust()`ed,\n\t * or un-adopted then re-adopted) never accumulates duplicate nav listeners. */\n\tprivate readonly navHookBound = new WeakSet<MinimalWebContents>()\n\t/**\n\t * slot-token registry (view-handle.md「slot-token 握手」/ capability-and-lifecycle.md「anchor slotToken 原子下发」):\n\t * each anchored `placeIn` mints an unguessable token bound to (viewId, slotId,\n\t * authorizedWcId, zone). The `__electron-deck:snapshot` apply path authorizes\n\t * each of the renderer's per-view tokens (token known AND authorized to the\n\t * sender wc), DERIVES the view's identity (viewId) + z-order (zone→layer) from\n\t * this registry (never trusting the renderer-reported fields), reconciles the\n\t * cleaned window-level table, and drives each view's `apply`. `resend` re-pushes\n\t * the slot-grant (layout-subscribe replay).\n\t */\n\tprivate readonly slotTokens = new Map<\n\t\tstring,\n\t\t{ viewId: string, slotId: string, authorizedWcId: number, zone: number, resend: () => void, apply: (placement: unknown) => void }\n\t>()\n\tprivate slotSeq = 0\n\t/**\n\t * Per-control-wc level-triggered reconciler state (keyed by authorizedWcId).\n\t * The renderer publishes a whole window-level desired-placement table each\n\t * frame; `reconcile` diffs it against the last-applied actual and emits ops, so\n\t * a lost or spurious per-view edge self-corrects instead of sticking a view\n\t * detached (the white-screen failure mode). Reset on layout-subscribe (reload)\n\t * and dropped on wc revoke.\n\t */\n\tprivate readonly reconcileStates = new Map<number, ReconcilerState>()\n\t/**\n\t * Per-control-wc strictly-monotonic generation counter. Bumped on every\n\t * layout-subscribe and stamped into each slot-grant, so a reload's higher\n\t * generation resets the reconciler regardless of IPC ordering (a late in-flight\n\t * pre-reload snapshot carries a lower generation → rejected).\n\t */\n\tprivate readonly perWcGeneration = new Map<number, number>()\n\tprivate _runtime: Runtime | null = null\n\tprivate startCalled = false\n\tprivate shutdownPromise: Promise<void> | null = null\n\t/** Set when shutdown is driven by the `will-quit` handler — the app is already\n\t * quitting, so `doShutdown()` must NOT re-`app.quit()` (re-entrant quit). */\n\tprivate quitInitiated = false\n\t/** Close machine: non-null while a close decision is awaiting (in-flight latch). */\n\tprivate closingDecisionPromise: Promise<'keep' | 'close'> | null = null\n\t/** Close machine: set once a 'close' decision is committed (guards the\n\t * window between decision-resolve and the 'closed' event). Never resets. */\n\tprivate shuttingDown = false\n\t/** Idempotency latch for {@link runShutdownCleanup}: the cleanup body\n\t * (beforeClose → backend.onShutdown → rootScope.close) must run AT MOST ONCE.\n\t * Both `doShutdown` (single-flight via shutdownPromise) AND `cleanupOnError`\n\t * (which bypasses that promise) reach here; and `rootScope.close()` inside the\n\t * body can synchronously fire a framework window's `'closed'` → `shutdown()`\n\t * re-entry. Without this latch that re-entry would invoke `backend.onShutdown`\n\t * a second time.\n\t *\n\t * This is a JOIN, not a boolean early-return: a re-entrant caller AWAITS the\n\t * SAME in-flight cleanup promise instead of returning immediately. A plain\n\t * `if (ran) return` let a concurrent shutdown skip past an in-flight cleanup\n\t * and force `app.quit()` mid-teardown (truncation). Never resets. */\n\tprivate cleanupPromise: Promise<void> | null = null\n\n\tconstructor(config: DeckConfig, options: DeckAppOptions = {}) {\n\t\tthis.config = config\n\t\tthis.options = options\n\t\t// rootScope owns the app-level teardown resources (event bus + resource\n\t\t// registry). Owned here so they dispose as rootScope RESOURCES — which, per\n\t\t// Scope's children-first LIFO, run AFTER every windowScope child has torn\n\t\t// down its window. This reproduces the \"destroy all windows BEFORE\n\t\t// registry.disposeAll()\" ordering structurally, without a manual loop. Own\n\t\t// order encodes reverse disposal order: bus first (disposed last), then\n\t\t// registry (disposed first) → registry.disposeAll runs before bus.unbindAll.\n\t\tthis.rootScope.own(() => {\n\t\t\tthis.bus.unbindAll()\n\t\t})\n\t\tthis.rootScope.own(async () => {\n\t\t\ttry {\n\t\t\t\tawait this.registry.disposeAll()\n\t\t\t}\n\t\t\tcatch (e) {\n\t\t\t\tconsole.error('[electron-deck] registry.disposeAll failed:', e)\n\t\t\t}\n\t\t})\n\t}\n\n\tget phase(): LifecyclePhase {\n\t\treturn this.lifecycle.current\n\t}\n\n\tget runtime(): Runtime {\n\t\tif (this._runtime === null) {\n\t\t\tthrow new Error('DeckApp.runtime is unavailable before setup phase')\n\t\t}\n\t\treturn this._runtime\n\t}\n\n\tasync start(): Promise<void> {\n\t\tif (this.startCalled) {\n\t\t\tthrow new Error('DeckApp.start() already called')\n\t\t}\n\t\tthis.startCalled = true\n\n\t\t// 校验在 lifecycle 转换之前;invalid config → reject,phase 不动\n\t\tvalidateConfig(this.config)\n\t\tthis.assertWireTransportPresentForWebviewContent()\n\n\t\t// pre-ready + whenReady gate. Only runs when a real `app` surface is\n\t\t// injected (production path / gate tests); fakes without `app` skip it. The\n\t\t// framework must NOT construct any BrowserWindow before `whenReady` resolves\n\t\t// in a real main process.\n\t\tconst app = this.options.electron?.app\n\t\tif (app) {\n\t\t\tconst shouldContinue = await this.runPreReadyGate(app)\n\t\t\t// Single-instance gate lost the lock and already quit — stop here,\n\t\t\t// phase stays `init` (mirrors the original early-return contract).\n\t\t\tif (!shouldContinue) return\n\t\t}\n\n\t\t// Wrap assemble/bind in a single try; on any failure run\n\t\t// cleanupOnError() so partially-constructed windows / handlers get\n\t\t// disposed before the start() promise rejects.\n\t\ttry {\n\t\t\tawait this.runBindAndSetupPhases()\n\t\t}\n\t\tcatch (err) {\n\t\t\tawait this.cleanupOnError()\n\t\t\tthrow err\n\t\t}\n\t}\n\n\t/**\n\t * half-state guard: electron + (toolbar | windows) without wireTransport\n\t * would leave the host with webviews that can never reach back via\n\t * __electron-deck:invoke. Reject early with a clear msg.\n\t */\n\tprivate assertWireTransportPresentForWebviewContent(): void {\n\t\tconst hasWebviewContent = !!(this.config.toolbar || this.config.windows)\n\t\tif (this.options.electron && hasWebviewContent && !this.options.wireTransport) {\n\t\t\tthrow new Error(\n\t\t\t\t'DeckAppOptions: wireTransport.ipcMain is required when config has toolbar or windows',\n\t\t\t)\n\t\t}\n\t}\n\n\t/**\n\t * Pre-`whenReady` sequencing: single-instance lock (opt-in, must quit\n\t * BEFORE any other side effect) → `backend.beforeReady` (or the framework's\n\t * best-effort `app.setName`) → `await app.whenReady()` → app-level lifecycle\n\t * bindings. Returns `false` when the single-instance lock was lost (the\n\t * second instance already called `app.quit()` — `start()` must stop with\n\t * phase still `init`), `true` otherwise.\n\t */\n\tprivate async runPreReadyGate(app: MinimalApp): Promise<boolean> {\n\t\tif (!this.acquireSingleInstanceLock(app)) {\n\t\t\treturn false\n\t\t}\n\t\tif (this.options.backend?.beforeReady) {\n\t\t\tawait this.options.backend.beforeReady(app)\n\t\t}\n\t\telse if (this.config.app?.name) {\n\t\t\ttry { app.setName(this.config.app.name) }\n\t\t\tcatch { /* best-effort */ }\n\t\t}\n\t\tawait app.whenReady()\n\t\tthis.bindAppLifecycle(app)\n\t\treturn true\n\t}\n\n\t/**\n\t * Single-instance gate (opt-in): a second instance must quit BEFORE any\n\t * side effect (beforeReady bootstrap / whenReady / window). Returns `true`\n\t * when the caller should continue (not opted in, or this instance holds the\n\t * lock); `false` after the losing instance has called `app.quit()`.\n\t */\n\tprivate acquireSingleInstanceLock(app: MinimalApp): boolean {\n\t\tif (!this.config.app?.singleInstance || typeof app.requestSingleInstanceLock !== 'function') {\n\t\t\treturn true\n\t\t}\n\t\tif (app.requestSingleInstanceLock()) {\n\t\t\treturn true\n\t\t}\n\t\ttry { app.quit() }\n\t\tcatch { /* best-effort */ }\n\t\treturn false\n\t}\n\n\t/** Init → Bind → Setup → Ready. Assembly/bind must run AFTER the\n\t * whenReady gate (handled by the caller); loadURL/loadFile must run\n\t * AFTER bindWireTransport (a preload calling the bridge before its\n\t * ipcMain handler is registered would see a \"no handler\" reject). */\n\tprivate async runBindAndSetupPhases(): Promise<void> {\n\t\tthis.lifecycle.enter('bind')\n\t\tthis.bindDeclarativeFields()\n\t\tthis.assembleElectron()\n\t\tthis.bindWireTransport()\n\t\tthis.loadAssembledSources()\n\n\t\tthis.lifecycle.enter('setup')\n\t\tthis._runtime = this.buildRuntime()\n\n\t\t// Domain assembly before the host's imperative setup escape.\n\t\tif (this.options.backend) {\n\t\t\tawait this.options.backend.assemble(this._runtime)\n\t\t}\n\t\tif (this.config.setup) {\n\t\t\tawait this.config.setup(this._runtime)\n\t\t}\n\n\t\tthis.lifecycle.enter('ready')\n\t}\n\n\t/**\n\t * Process-level Electron lifecycle bindings (post-whenReady, independent of\n\t * `ownsWindows` — these are app events, not window events):\n\t * - `will-quit` → framework teardown (idempotent via `shutdownPromise`).\n\t * - `window-all-closed` → bound ONLY when `quitOnAllWindowsClosed` is set\n\t * (opt-in; omitted leaves Electron's default / the consumer's own handler).\n\t * - `second-instance` → backend hook, bound only under `singleInstance`.\n\t */\n\tprivate bindAppLifecycle(app: MinimalApp): void {\n\t\tapp.on('will-quit', () => {\n\t\t\t// The app is already quitting — teardown must NOT re-`app.quit()`.\n\t\t\tthis.quitInitiated = true\n\t\t\tvoid this.shutdown()\n\t\t})\n\n\t\tconst quitOnAllClosed = this.config.app?.quitOnAllWindowsClosed\n\t\tif (quitOnAllClosed !== undefined) {\n\t\t\tapp.on('window-all-closed', () => {\n\t\t\t\tif (quitOnAllClosed) {\n\t\t\t\t\ttry { app.quit() }\n\t\t\t\t\tcatch { /* best-effort */ }\n\t\t\t\t}\n\t\t\t})\n\t\t}\n\n\t\tif (this.config.app?.singleInstance) {\n\t\t\tapp.on('second-instance', () => {\n\t\t\t\tthis.options.backend?.onSecondInstance?.()\n\t\t\t})\n\t\t}\n\t}\n\n\tshutdown(): Promise<void> {\n\t\tif (this.shutdownPromise) return this.shutdownPromise\n\t\tthis.shutdownPromise = this.doShutdown()\n\t\treturn this.shutdownPromise\n\t}\n\n\t/**\n\t * @internal windows.trust() / framework 内部添加 trusted webContents.\n\t * Backend-owned / untracked-window fallback: there is no framework windowScope\n\t * for these (the backend manages their lifetime), so the trust lease is owned\n\t * by `rootScope` as an app-shutdown backstop. The backend still disposes the\n\t * returned handle early when it untrusts / destroys its own window.\n\t */\n\t_trustWebContents(wc: MinimalWebContents): Disposable {\n\t\treturn this.trustSet.admit(wc, this.rootScope)\n\t}\n\n\t/**\n\t * Admit `wc` to the trust set under `windowScope`. Gets\n\t * or creates the wc's `wcScope` (a child of `windowScope`), takes a fresh\n\t * `trustSet.admit(wc, wcScope)` ref-count lease OWNED BY that wcScope, and returns a\n\t * one-shot host-facing Disposable that releases just THIS lease early. On\n\t * `wcScope.close()` (window-close cascade) every still-held lease is disposed\n\t * → ref-count zeroes → the wc leaves the set (driven by Scope teardown,\n\t * covering partially-built windows too). Idempotent registry cleanup is owned\n\t * by the wcScope.\n\t */\n\tprivate admitTrust(wc: MinimalWebContents, windowScope: Scope): Disposable {\n\t\t// Never trust an already-destroyed webContents. Trust is keyed/observed by\n\t\t// wc.id, which Electron REUSES — admitting a dead wc would leave a trusted\n\t\t// entry that a later window reusing the same id would inherit (privilege\n\t\t// escalation). The window's 'closed' handler is registered BEFORE this call\n\t\t// (see constructWindow), so any destruction after admit is revoked; this\n\t\t// guard closes the \"destroyed before/at admit\" half. Returns a no-op handle.\n\t\tif (wc.isDestroyed()) {\n\t\t\treturn { dispose: () => {} }\n\t\t}\n\t\tlet rec = this.wcRecords.get(wc)\n\t\tif (!rec) {\n\t\t\tconst wcScope = windowScope.child()\n\t\t\trec = { wcScope, leases: new Set<Disposable>(), windowScope }\n\t\t\tthis.wcRecords.set(wc, rec)\n\t\t\t// Drop the registry entry when the wcScope tears down (any trigger).\n\t\t\twcScope.own(() => {\n\t\t\t\tthis.wcRecords.delete(wc)\n\t\t\t})\n\t\t}\n\t\tconst record = rec\n\t\tconst lease = this.trustSet.admit(wc, record.wcScope)\n\t\trecord.leases.add(lease)\n\t\tlet released = false\n\t\treturn {\n\t\t\tdispose: () => {\n\t\t\t\tif (released) return\n\t\t\t\treleased = true\n\t\t\t\trecord.leases.delete(lease)\n\t\t\t\tlease.dispose()\n\t\t\t},\n\t\t}\n\t}\n\n\t/**\n\t * Bind the main-frame cross-document navigation grant-reset\n\t * hook on a control `wc`. On a MAIN-FRAME CROSS-DOCUMENT navigation\n\t * (`isMainFrame && !isInPlace`) this SYNCHRONOUSLY revokes the wc's capability\n\t * grants (`capability.revokeBySenderId`) and slot tokens, so the navigated-to\n\t * document can't inherit the prior page's privileges. Trust is LEFT INTACT (the\n\t * wc stays the framework's control surface) — we deliberately do NOT tear down\n\t * its `wcScope`, because that would dispose the trust lease and force an async\n\t * re-admit gap that breaks `runtime.grants.issue`. In-place (hash/pushState) and\n\t * sub-frame navigations are ignored; the initial document load is a no-op (no\n\t * grants exist yet).\n\t *\n\t * Must be called AFTER {@link admitTrust} so the wc's `wcScope` exists. The\n\t * `wc.on` guard tolerates the minimal fakes that lack an EventEmitter surface.\n\t */\n\tprivate bindNavigationGrantReset(wc: MinimalWebContents): void {\n\t\tconst on = (wc as unknown as { on?: (event: string, listener: (...a: unknown[]) => void) => unknown }).on\n\t\tif (typeof on !== 'function') return\n\t\t// Idempotent: bind at most one nav hook per wc (defends re-trust / re-adopt).\n\t\tif (this.navHookBound.has(wc)) return\n\t\tthis.navHookBound.add(wc)\n\t\ton.call(wc, 'did-start-navigation', (\n\t\t\t_event: unknown,\n\t\t\t_url: unknown,\n\t\t\tisInPlace: unknown,\n\t\t\tisMainFrame: unknown,\n\t\t) => {\n\t\t\tif (isMainFrame !== true || isInPlace === true) return\n\t\t\tconst rec = this.wcRecords.get(wc)\n\t\t\tif (!rec) return\n\t\t\t// Main-frame cross-document navigation: the navigated-to document must\n\t\t\t// NOT inherit the prior page's privileges. SYNCHRONOUSLY revoke this\n\t\t\t// wc's grants + slot tokens. Trust is LEFT INTACT — the wc is still the\n\t\t\t// framework's control surface, so we do NOT tear down its wcScope. A\n\t\t\t// `wcScope.reset()` would dispose the trust lease and require an ASYNC\n\t\t\t// re-admit, opening a window where the wc has no usable wcScope —\n\t\t\t// `runtime.grants.issue` would then fail mid-flight, and the very first\n\t\t\t// `config.app.source` load (a main-frame cross-doc navigation) would\n\t\t\t// trip it. Revoking exactly the privileges (grants + slot tokens) is the\n\t\t\t// complete, gap-free fix; on the initial document load there are no\n\t\t\t// grants yet → no-op.\n\t\t\tthis.capability.revokeBySenderId(wc.id)\n\t\t\tthis.revokeSlotTokensForWc(wc.id)\n\t\t})\n\t}\n\n\t/**\n\t * Build the {@link DeckWindow} facade over a registered window and record\n\t * it (so `runtime.windows.create()` / `runtime.windows.main` can return it and\n\t * the per-window close machine can find its deciders). `newSession()` mints a\n\t * window-rooted {@link DeckSession} (a `windowScope.child()` registered in the\n\t * SAME provenance WeakMap as `runtime.scopes.create()`); `onClose()` appends a\n\t * per-window decider.\n\t */\n\tprivate buildDeckWindow(\n\t\twin: MinimalBrowserWindow,\n\t\tcontrolWc: MinimalWebContents,\n\t\twindowScope: Scope,\n\t): DeckWindow {\n\t\tconst facade: DeckWindow = {\n\t\t\twindow: win as unknown as BrowserWindow,\n\t\t\tcontrolWc: controlWc as unknown as WebContents,\n\t\t\tnewSession: (): DeckSession => {\n\t\t\t\tconst scope = windowScope.child()\n\t\t\t\tconst session: DeckSession = {\n\t\t\t\t\treset: () => scope.reset(),\n\t\t\t\t\tdispose: () => scope.close(),\n\t\t\t\t}\n\t\t\t\tthis.sessions.set(session, scope)\n\t\t\t\treturn session\n\t\t\t},\n\t\t\tonClose: (decider: WindowCloseDecider): Disposable => {\n\t\t\t\tconst rec = this.windowRegistrations.get(controlWc)\n\t\t\t\tif (!rec) {\n\t\t\t\t\t// Window already torn down — a decider can never fire; no-op handle.\n\t\t\t\t\treturn { dispose: () => {} }\n\t\t\t\t}\n\t\t\t\trec.deciders.push(decider)\n\t\t\t\tlet removed = false\n\t\t\t\treturn {\n\t\t\t\t\tdispose: () => {\n\t\t\t\t\t\tif (removed) return\n\t\t\t\t\t\tremoved = true\n\t\t\t\t\t\tconst i = rec.deciders.indexOf(decider)\n\t\t\t\t\t\tif (i >= 0) rec.deciders.splice(i, 1)\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t},\n\t\t}\n\t\tconst deckWindow = facade\n\t\tthis.windowRegistrations.set(controlWc, {\n\t\t\twindow: win,\n\t\t\tcontrolWc,\n\t\t\twindowScope,\n\t\t\tdeckWindow,\n\t\t\tdeciders: [],\n\t\t\tclosing: null,\n\t\t\tcommittedClose: false,\n\t\t})\n\t\twindowScope.own(() => {\n\t\t\tthis.windowRegistrations.delete(controlWc)\n\t\t})\n\t\treturn deckWindow\n\t}\n\n\t/**\n\t * Resolve a window argument that may be either a raw `BrowserWindow` (the\n\t * historical `runtime.view().placeIn(window)` shape) OR a {@link DeckWindow}\n\t * handle (the `runtime.windows.create()` return). A DeckWindow exposes the\n\t * underlying window via `.window` and has no `.webContents` of its own; a raw\n\t * window has `.webContents`. Unwrap so the substrate lookup keys off the real\n\t * control webContents either way — keeping the `placeIn(deckWindow)` and\n\t * `placeIn(deckWindow.window)` call shapes both valid (additive, no break).\n\t */\n\tprivate resolveWindowArg(win: unknown): MinimalBrowserWindow {\n\t\tconst maybe = win as { webContents?: unknown, window?: unknown }\n\t\tif (maybe && maybe.webContents === undefined && maybe.window !== undefined) {\n\t\t\treturn maybe.window as MinimalBrowserWindow\n\t\t}\n\t\treturn win as MinimalBrowserWindow\n\t}\n\n\t/**\n\t * Run a registered window's per-window close deciders (registration order;\n\t * any `'keep'` vetoes), with an in-flight latch so a re-entrant close during a\n\t * pending decision decides ONCE. A throw / rejection fails CLOSED. Returns the\n\t * committed decision, or `null` when the decision is still in flight / there are\n\t * no deciders (caller falls back). Mirrors the main-window decision machine.\n\t */\n\tprivate async runWindowCloseDeciders(\n\t\tcontrolWc: MinimalWebContents,\n\t): Promise<WindowCloseDecision> {\n\t\tconst rec = this.windowRegistrations.get(controlWc)\n\t\tif (!rec) return 'close'\n\t\tconst deciders = rec.deciders.slice()\n\t\tfor (const decide of deciders) {\n\t\t\tlet decision: WindowCloseDecision\n\t\t\ttry {\n\t\t\t\tdecision = await decide()\n\t\t\t}\n\t\t\tcatch (err) {\n\t\t\t\tconsole.error('[electron-deck] per-window onClose decider threw; closing:', err)\n\t\t\t\tdecision = 'close'\n\t\t\t}\n\t\t\tif (decision === 'keep') return 'keep'\n\t\t}\n\t\treturn 'close'\n\t}\n\n\t/**\n\t * Arm the per-window cancelable close-decision machine on a CREATED /\n\t * adopted window. On a `close` attempt: `preventDefault`, dispatch the\n\t * per-window deciders behind an in-flight latch (re-entrant closes swallowed),\n\t * and on a `'close'` decision `win.destroy()` (→ `'closed'` → revoke +\n\t * windowScope cascade). A `'keep'` clears the latch so the next close re-decides.\n\t * Only armed when at least one decider can exist (i.e. the window has a\n\t * registration) — windows with no per-window deciders fall through to Electron's\n\t * default close (the framework does not preventDefault).\n\t */\n\tprivate armSubWindowCloseMachine(\n\t\twin: MinimalBrowserWindow,\n\t\tcontrolWc: MinimalWebContents,\n\t\twindowScope: Scope,\n\t): void {\n\t\tconst onClose = (e?: { preventDefault(): void }): void => {\n\t\t\tconst rec = this.windowRegistrations.get(controlWc)\n\t\t\t// No registration or no live decider → let Electron close it normally\n\t\t\t// (do NOT preventDefault — preserves the default-close path).\n\t\t\tif (!rec || rec.deciders.length === 0) return\n\t\t\te?.preventDefault?.()\n\t\t\tif (rec.closing) return // decision in flight\n\t\t\tif (rec.committedClose) return // already committed to close\n\t\t\tlet settle!: (d: WindowCloseDecision) => void\n\t\t\trec.closing = new Promise<WindowCloseDecision>((res) => { settle = res })\n\t\t\tvoid this.runWindowCloseDeciders(controlWc).then(\n\t\t\t\td => settle(d),\n\t\t\t\t(err: unknown) => {\n\t\t\t\t\tconsole.error('[electron-deck] per-window close decision rejected; closing:', err)\n\t\t\t\t\tsettle('close')\n\t\t\t\t},\n\t\t\t)\n\t\t\tvoid rec.closing.then((decision) => {\n\t\t\t\tif (decision === 'keep') {\n\t\t\t\t\trec.closing = null // stay; next close re-decides\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\trec.committedClose = true\n\t\t\t\trec.closing = null\n\t\t\t\tif (!win.isDestroyed()) win.destroy() // → fires 'closed'\n\t\t\t})\n\t\t}\n\t\twin.on('close', onClose)\n\t\t// The 'close' listener's ownership belongs to the windowScope: its teardown\n\t\t// (un-adopt / shutdown) removes the listener, so re-adopting the same window\n\t\t// never accumulates stale close listeners. A fake window lacking\n\t\t// removeListener/off degrades to leaving the listener (harmless — a torn-down\n\t\t// or destroyed window's listeners are never consulted again).\n\t\twindowScope.own(() => this.removeWindowListener(win, 'close', onClose))\n\t}\n\n\t/**\n\t * Remove a previously-registered window event listener (Electron's\n\t * `removeListener`/`off`). Tolerates minimal test fakes that expose neither by\n\t * degrading to a no-op — the caller's ownership contract is best-effort cleanup.\n\t */\n\tprivate removeWindowListener(\n\t\twin: MinimalBrowserWindow,\n\t\tevent: string,\n\t\tlistener: (e?: { preventDefault(): void }) => void,\n\t): void {\n\t\tconst w = win as unknown as {\n\t\t\t// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type\n\t\t\tremoveListener?: Function\n\t\t\t// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type\n\t\t\toff?: Function\n\t\t}\n\t\tconst remove = w.removeListener ?? w.off\n\t\tif (typeof remove === 'function') remove.call(win, event, listener)\n\t}\n\n\t/**\n\t * @internal Lifetime accessors. The live shadow map mirroring `trackedWindows`,\n\t * the root Scope, and a consistency assertion.\n\t */\n\t__lifetimeShadow(): Map<MinimalWebContents, { window: MinimalBrowserWindow, windowScope: Scope }> {\n\t\treturn this.lifetimeShadow\n\t}\n\n\t/** @internal The root lifetime Scope. */\n\t__rootScope(): Scope {\n\t\treturn this.rootScope\n\t}\n\n\t/** @internal The per-trusted-wc trust records. */\n\t__wcRecords(): Map<MinimalWebContents, { wcScope: Scope, leases: Set<Disposable>, windowScope: Scope }> {\n\t\treturn this.wcRecords\n\t}\n\n\t/** @internal The live capability policy (grant gate reads it). */\n\t__capabilityPolicy(): CapabilityPolicy {\n\t\treturn this.capability.policy\n\t}\n\n\t/**\n\t * @internal Lifetime invariant: the shadow's per-window set\n\t * (the `window` of each entry) must equal `trackedWindows` membership. Throws\n\t * if violated; no-op otherwise.\n\t */\n\t__assertLifetimeConsistent(): void {\n\t\tconst shadowWindows = new Set<MinimalBrowserWindow>()\n\t\tfor (const entry of this.lifetimeShadow.values()) {\n\t\t\tshadowWindows.add(entry.window)\n\t\t}\n\t\tif (shadowWindows.size !== this.trackedWindows.size) {\n\t\t\tthrow new Error(\n\t\t\t\t`lifetime shadow inconsistent: shadow has ${shadowWindows.size} windows, trackedWindows has ${this.trackedWindows.size}`,\n\t\t\t)\n\t\t}\n\t\tfor (const win of this.trackedWindows) {\n\t\t\tif (!shadowWindows.has(win)) {\n\t\t\t\tthrow new Error('lifetime shadow inconsistent: a trackedWindows member is absent from the shadow')\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate bindDeclarativeFields(): void {\n\t\tif (this.config.events) {\n\t\t\tthis.bus.bindDeclaredEvents(this.config.events)\n\t\t}\n\t\tif (this.config.simulatorApis) {\n\t\t\tfor (const [name, handler] of Object.entries(this.config.simulatorApis)) {\n\t\t\t\tconst d = this.ipc.handle(\n\t\t\t\t\t`${SIMULATOR_CHANNEL_PREFIX}${name}`,\n\t\t\t\t\thandler as (...args: JsonValue[]) => MaybePromise<JsonValue>,\n\t\t\t\t)\n\t\t\t\tthis.registry.add(d)\n\t\t\t}\n\t\t}\n\t\tif (this.config.hostServices) {\n\t\t\tfor (const [name, handler] of Object.entries(this.config.hostServices)) {\n\t\t\t\t// ENFORCE the layout.* boundary at registration. `layout.*` names\n\t\t\t\t// are reserved for the grant-gated ControlBus (runtime.layout.command);\n\t\t\t\t// they must NEVER be registered on the un-gated declarative hostServices\n\t\t\t\t// route, or a privileged name would be reachable without a grant.\n\t\t\t\t// Together with runtime.layout.command's throw, this makes the invariant\n\t\t\t\t// `layout.* ⟺ gated` total.\n\t\t\t\tif (name.startsWith('layout.')) {\n\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t`config.hostServices: \"layout.*\" names are reserved for privileged ControlBus commands `\n\t\t\t\t\t\t+ `(use runtime.layout.command); got: ${name}`,\n\t\t\t\t\t)\n\t\t\t\t}\n\t\t\t\tconst d = this.ipc.handle(\n\t\t\t\t\t`${HOST_CHANNEL_PREFIX}${name}`,\n\t\t\t\t\thandler as (...args: JsonValue[]) => MaybePromise<JsonValue>,\n\t\t\t\t)\n\t\t\t\tthis.registry.add(d)\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Build a per-window {@link ViewSubstrate}. The\n\t * `ContentViewHost` adapts `win.contentView` and tracks the Compositor-managed\n\t * z-order in `order` (the minimal `contentView` has no `children()`). The\n\t * substrate's `detachAll` is owned on `windowScope` AFTER the window's\n\t * `win.destroy` own, so LIFO teardown runs detachAll BEFORE destroy.\n\t */\n\tprivate createWindowSubstrate(win: MinimalBrowserWindow, windowScope: Scope): ViewSubstrate {\n\t\tconst wcvById = new Map<string, MinimalWebContentsView>()\n\t\tconst order: string[] = []\n\t\tconst host: ContentViewHost = {\n\t\t\taddChildView: (ref: NativeViewRef) => {\n\t\t\t\tconst wcv = wcvById.get(ref.id)\n\t\t\t\tif (!wcv) return\n\t\t\t\t// Native call FIRST — if it throws, the tracked `order` stays\n\t\t\t\t// consistent with Electron reality (the op didn't happen) so the\n\t\t\t\t// Compositor's diff/rollback works against a truthful order. Only\n\t\t\t\t// mutate `order` AFTER the native add returns successfully.\n\t\t\t\twin.contentView.addChildView(wcv)\n\t\t\t\tconst i = order.indexOf(ref.id)\n\t\t\t\tif (i >= 0) order.splice(i, 1)\n\t\t\t\torder.push(ref.id)\n\t\t\t},\n\t\t\tremoveChildView: (ref: NativeViewRef) => {\n\t\t\t\tconst wcv = wcvById.get(ref.id)\n\t\t\t\tif (!wcv) return\n\t\t\t\t// Native remove FIRST, then splice `order` — a native throw\n\t\t\t\t// leaves `order` consistent with reality.\n\t\t\t\twin.contentView.removeChildView(wcv)\n\t\t\t\tconst i = order.indexOf(ref.id)\n\t\t\t\tif (i >= 0) order.splice(i, 1)\n\t\t\t},\n\t\t\tget isDestroyed() {\n\t\t\t\treturn win.isDestroyed()\n\t\t\t},\n\t\t\tchildren: () => order.map(id => ({ id })),\n\t\t}\n\t\tconst compositor = createCompositor(host)\n\t\t// Owned AFTER the win.destroy own ⇒ runs BEFORE destroy (LIFO).\n\t\t// `detachAll` is optional on the interface but always present on the real\n\t\t// `createCompositor`; guard for the type only.\n\t\twindowScope.own(() => {\n\t\t\tcompositor.detachAll?.()\n\t\t})\n\t\treturn {\n\t\t\tcompositor,\n\t\t\twindowScope,\n\t\t\tregisterView: (id: string, wcv: MinimalWebContentsView) => {\n\t\t\t\twcvById.set(id, wcv)\n\t\t\t},\n\t\t\tunregisterView: (id: string) => {\n\t\t\t\twcvById.delete(id)\n\t\t\t\tconst i = order.indexOf(id)\n\t\t\t\tif (i >= 0) order.splice(i, 1)\n\t\t\t},\n\t\t}\n\t}\n\n\tprivate assembleElectron(): void {\n\t\tconst electron = this.options.electron\n\t\tif (!electron) return\n\n\t\t// A window-owning backend builds the real (domain) main window in\n\t\t// `assemble()` via its own factory. The framework must NOT create its own\n\t\t// here, or the app would show a second, empty BrowserWindow. The framework\n\t\t// still provides lifecycle / wire / trust. Backends that only react to\n\t\t// close (onMainWindowClose) leave `ownsWindows` false and use the\n\t\t// framework's window. (`runtime.mainWindow` is unset under ownsWindows.)\n\t\tif (this.options.backend?.ownsWindows) return\n\n\t\t// mainWindow ── framework 不主动 load 内容,host 在 setup 里自管\n\t\tconst appCfg = this.config.app ?? {}\n\t\tconst winCfg = appCfg.window ?? {}\n\t\t// Only write keys that are actually defined: Electron distinguishes an\n\t\t// omitted option from an explicit `undefined` (e.g. `show: undefined`\n\t\t// is treated as a provided value, not the default), so we never spread\n\t\t// `undefined` into the ctor options.\n\t\tconst mainOpts: MinimalBrowserWindowOptions = {\n\t\t\t...(appCfg.name !== undefined ? { title: appCfg.name } : {}),\n\t\t\t...(appCfg.icon !== undefined ? { icon: appCfg.icon } : {}),\n\t\t\twidth: winCfg.width ?? DEFAULT_MAIN_WIDTH,\n\t\t\theight: winCfg.height ?? DEFAULT_MAIN_HEIGHT,\n\t\t}\n\t\tif (winCfg.minWidth !== undefined) mainOpts.minWidth = winCfg.minWidth\n\t\tif (winCfg.minHeight !== undefined) mainOpts.minHeight = winCfg.minHeight\n\t\tif (winCfg.show !== undefined) mainOpts.show = winCfg.show\n\t\tif (winCfg.backgroundColor !== undefined) mainOpts.backgroundColor = winCfg.backgroundColor\n\t\t// webPreferences merge: config.app.window.webPreferences first, then the\n\t\t// backend's mainWindowWebPreferences() (backend keys win on collision).\n\t\tconst backendPrefs = this.options.backend?.mainWindowWebPreferences?.()\n\t\tif (winCfg.webPreferences || backendPrefs) {\n\t\t\tmainOpts.webPreferences = { ...winCfg.webPreferences, ...backendPrefs }\n\t\t}\n\t\tconst main = new electron.BrowserWindow(mainOpts)\n\t\tthis.mainWindow = main\n\t\tthis.trackedWindows.add(main)\n\t\t// The window's lifetime is a child Scope of rootScope,\n\t\t// and that windowScope now OWNS the window's destruction. rootScope.close()\n\t\t// (shutdown) cascades into it, destroying the window as part of Scope\n\t\t// teardown rather than a manual loop. The isDestroyed() guard means an\n\t\t// externally-closed or already-destroyed window is never double-destroyed.\n\t\tconst mainWindowScope = this.rootScope.child()\n\t\tmainWindowScope.own(() => {\n\t\t\tif (!main.isDestroyed()) main.destroy()\n\t\t})\n\t\tthis.lifetimeShadow.set(main.webContents as unknown as MinimalWebContents, {\n\t\t\twindow: main,\n\t\t\twindowScope: mainWindowScope,\n\t\t})\n\t\t// Per-window native-view substrate. Created AFTER the\n\t\t// `win.destroy` own above so its `detachAll` (owned inside) runs FIRST in\n\t\t// the LIFO teardown, before win.destroy.\n\t\tthis.windowSubstrates.set(\n\t\t\tmain.webContents as unknown as MinimalWebContents,\n\t\t\tthis.createWindowSubstrate(main, mainWindowScope),\n\t\t)\n\t\t// Arm trust + grant revocation as the FIRST 'closed' listener — registered\n\t\t// BEFORE the backend's onMainWindowCreated hook (which may register its own\n\t\t// 'closed' listener) AND before admitTrust. This guarantees revokeWindowTrust\n\t\t// (which synchronously drops BOTH trust leases AND capability grants for the\n\t\t// window's wcs) runs FIRST in the 'closed' tick, so no other 'closed' listener\n\t\t// — backend or framework — can observe a stale trust/grant for a wc whose id\n\t\t// Electron may immediately reuse. Idempotent with the async wcScope cascade\n\t\t// and the later close-decision handler (revoke is one-shot / by-senderId).\n\t\t// Capture the main wc.id WHILE the window is alive: reading\n\t\t// `main.webContents` inside the 'closed' handler is a\n\t\t// post-destroy access that can throw — leaving tokens un-revoked until\n\t\t// shutdown, where a reused wc.id could then pass the authorizedWcId check.\n\t\tconst mainWcId = (main.webContents as unknown as MinimalWebContents).id\n\t\tmain.on('closed', () => {\n\t\t\tthis.revokeWindowTrust(mainWindowScope)\n\t\t\t// slot-token leak hygiene — drop tokens authorized to the main wc (using\n\t\t\t// the captured id, never a post-destroy webContents read).\n\t\t\tthis.revokeSlotTokensForWc(mainWcId)\n\t\t})\n\t\t// Backend post-ctor / pre-load hook (synchronous, exactly once):\n\t\t// runs after the window exists but before any source load, so the backend\n\t\t// can attach views / listeners before the renderer starts.\n\t\tthis.options.backend?.onMainWindowCreated?.(\n\t\t\tmain as unknown as Parameters<NonNullable<RuntimeBackend['onMainWindowCreated']>>[0],\n\t\t\telectron as unknown as Parameters<NonNullable<RuntimeBackend['onMainWindowCreated']>>[1],\n\t\t)\n\t\t// auto-trust — the framework's ref-count lease is OWNED by the main window's\n\t\t// wcScope (child of mainWindowScope), so the window-close cascade zeroes it.\n\t\tthis.admitTrust(main.webContents as unknown as MinimalWebContents, mainWindowScope)\n\t\t// Main-frame cross-document navigation grant-reset on the main control\n\t\t// wc (after admitTrust so its wcScope exists). Record the main window's\n\t\t// DeckWindow facade (newSession + per-window onClose) so `runtime.windows.main`\n\t\t// resolves it and the close machine below can consult its per-window deciders.\n\t\tconst mainControlWc = main.webContents as unknown as MinimalWebContents\n\t\tthis.bindNavigationGrantReset(mainControlWc)\n\t\tthis.buildDeckWindow(main, mainControlWc, mainWindowScope)\n\t\tthis.mainControlWc = mainControlWc\n\t\t// Let the backend mirror main-window trust into its domain set.\n\t\t// This onWindowTrusted call belongs to the **main-window-assembly seam**\n\t\t// and is therefore ownsWindows-gated: under `ownsWindows:true` we\n\t\t// early-return above (the backend builds + trusts its own main window via\n\t\t// runtime.windows.trust / runtime.windows.create), so this line is never\n\t\t// reached. The framework only auto-trusts — and notifies the backend about\n\t\t// — a main window it built itself.\n\t\tthis._notifyBackendTrusted(main.webContents)\n\t\tthis.pendingWindowCreated.push({ window: main, role: 'main' })\n\t\t// Toolbar follows mainWindow resize\n\t\tmain.on('resize', () => {\n\t\t\t// Backend repositions its overlays against the main window.\n\t\t\tthis.options.backend?.repositionOverlays?.(\n\t\t\t\tmain as unknown as Parameters<NonNullable<RuntimeBackend['repositionOverlays']>>[0],\n\t\t\t)\n\t\t\tif (!this.toolbarView || !this.config.toolbar) return\n\t\t\tconst b = main.getContentBounds()\n\t\t\tthis.toolbarView.setBounds({\n\t\t\t\tx: 0,\n\t\t\t\ty: 0,\n\t\t\t\twidth: b.width,\n\t\t\t\theight: this.config.toolbar.height,\n\t\t\t})\n\t\t})\n\t\t// Close-decision machine. `close` is cancelable: we always\n\t\t// preventDefault first, then ask the backend whether to keep the window\n\t\t// (e.g. tear down the active session and stay) or actually close. Only a\n\t\t// 'close' decision destroys the window → fires 'closed' → shutdown.\n\t\t// [B] closingDecisionPromise — in-flight latch: swallows re-entrant\n\t\t// close attempts during the (possibly slow) decision.\n\t\t// [C] shuttingDown — covers the window between decision-resolve and\n\t\t// the 'closed' event; never resets.\n\t\tmain.on('close', (e?: { preventDefault(): void }) => {\n\t\t\te?.preventDefault?.()\n\t\t\tif (this.closingDecisionPromise) return // [B] decision in flight\n\t\t\tif (this.shuttingDown) return // [C] already committed to close\n\t\t\t// Arm the in-flight latch SYNCHRONOUSLY (via a deferred) BEFORE invoking\n\t\t\t// the hook, so a 'close' re-entered during a synchronous hook body can't\n\t\t\t// slip past [B] before the latch is set.\n\t\t\tlet settle!: (d: 'keep' | 'close') => void\n\t\t\tthis.closingDecisionPromise = new Promise<'keep' | 'close'>((res) => { settle = res })\n\t\t\t// A LIVE per-window onClose decider STRICTLY supersedes\n\t\t\t// backend.onMainWindowClose: when one is registered on the main window's\n\t\t\t// DeckWindow, run the per-window deciders (registration order, any 'keep'\n\t\t\t// vetoes, throw/reject fail-closed) and DO NOT call the backend hook. The\n\t\t\t// backend hook is the fallback only when no live per-window decider exists.\n\t\t\t// A SYNCHRONOUS throw from onMainWindowClose must not escape the\n\t\t\t// listener (it already preventDefault'd, so the window would be stuck\n\t\t\t// open with no recovery). Fail-closed to 'close', same as a rejection.\n\t\t\tlet decide: MaybePromise<'keep' | 'close'>\n\t\t\tconst mainReg = this.windowRegistrations.get(mainControlWc)\n\t\t\tif (mainReg && mainReg.deciders.length > 0) {\n\t\t\t\tdecide = this.runWindowCloseDeciders(mainControlWc)\n\t\t\t}\n\t\t\telse {\n\t\t\t\ttry {\n\t\t\t\t\tdecide = this.options.backend?.onMainWindowClose?.() ?? 'close'\n\t\t\t\t}\n\t\t\t\tcatch (err) {\n\t\t\t\t\tconsole.error('[electron-deck] onMainWindowClose threw (sync); closing:', err)\n\t\t\t\t\tdecide = 'close'\n\t\t\t\t}\n\t\t\t}\n\t\t\tPromise.resolve(decide).then(\n\t\t\t\td => settle(d === 'keep' ? 'keep' : 'close'),\n\t\t\t\t(err: unknown) => {\n\t\t\t\t\tconsole.error('[electron-deck] onMainWindowClose threw; closing:', err)\n\t\t\t\t\tsettle('close')\n\t\t\t\t},\n\t\t\t)\n\t\t\tvoid this.closingDecisionPromise.then((decision) => {\n\t\t\t\tif (decision === 'keep') {\n\t\t\t\t\tthis.closingDecisionPromise = null // stay; next close re-decides\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tthis.shuttingDown = true\n\t\t\t\tthis.closingDecisionPromise = null\n\t\t\t\tif (!main.isDestroyed()) main.destroy() // → fires 'closed'\n\t\t\t})\n\t\t})\n\t\t// 'closed' revokes trust + triggers shutdown. The 'keep' path never\n\t\t// destroys, so it never reaches here. Close mainWindowScope and AWAIT\n\t\t// its cascade BEFORE starting shutdown. The cascade closes the main +\n\t\t// toolbar wcScopes (its children, LIFO) → disposes their trust leases →\n\t\t// ref-count zeroes → untrusted. Awaiting to COMPLETION before `shutdown()`\n\t\t// matters: a fire-and-forget `void close()` would pause at the\n\t\t// async boundary between the two child wcScopes while shutdown's synchronous\n\t\t// `beforeClose` prefix runs — leaving the main wc briefly trusted. The\n\t\t// cascade is cheap (trust leases + the\n\t\t// isDestroyed-guarded win.destroy no-op). `shutdown()` is reached via\n\t\t// `.finally` so it still runs if the cascade throws; rootScope.close() during\n\t\t// shutdown is idempotent over the already-closed mainWindowScope.\n\t\tmain.on('closed', () => {\n\t\t\t// Synchronously revoke main + toolbar trust the instant the window is\n\t\t\t// destroyed, so no async-cascade race can leave a destroyed wc trusted\n\t\t\t// during a later beforeClose.\n\t\t\tthis.revokeWindowTrust(mainWindowScope)\n\t\t\t// Then the async windowScope teardown (idempotent re-dispose of the now\n\t\t\t// already-revoked leases + win.destroy + wcRecords cleanup) and shutdown.\n\t\t\tvoid mainWindowScope\n\t\t\t\t.close()\n\t\t\t\t.catch((e: unknown) => { console.error('[electron-deck] mainWindowScope.close() failed:', e) })\n\t\t\t\t.finally(() => { void this.shutdown() })\n\t\t})\n\n\t\t// Toolbar contentWebview —— 装好 view + addChildView + setBounds + trust,\n\t\t// 但 loadURL/loadFile 推迟到 loadAssembledSources() 在 wireTransport.start\n\t\t// 之后调,避免 preload 先于 ipcMain handler 注册触发 invoke。\n\t\tif (this.config.toolbar) {\n\t\t\tconst tb = this.config.toolbar\n\t\t\tconst view = new electron.WebContentsView({\n\t\t\t\twebPreferences: { preload: tb.preloadPath },\n\t\t\t})\n\t\t\tthis.toolbarView = view\n\t\t\tmain.contentView.addChildView(view)\n\t\t\tconst bounds = main.getContentBounds()\n\t\t\tview.setBounds({ x: 0, y: 0, width: bounds.width, height: tb.height })\n\t\t\t// toolbar lives in the main window → its wcScope parents under the SAME\n\t\t\t// mainWindowScope, so the main window's close revokes toolbar trust too.\n\t\t\tthis.admitTrust(view.webContents as unknown as MinimalWebContents, mainWindowScope)\n\t\t\tthis.pendingWindowCreated.push({ window: main, role: 'toolbar' })\n\t\t}\n\n\t\t// Declared windows —— ctor + trust,loadURL 同样推迟\n\t\tif (this.config.windows) {\n\t\t\tfor (const [key, contrib] of Object.entries(this.config.windows)) {\n\t\t\t\tconst win = this.constructWindow(contrib, /* autoTrust */ true, /* deferLoad */ true)\n\t\t\t\tthis.declaredWindows.set(key, win)\n\t\t\t\tthis.pendingWindowCreated.push({ window: win, role: 'host' })\n\t\t\t}\n\t\t}\n\t}\n\n\t/** 在 wireTransport.start 之后再 loadURL/loadFile,避免 preload 先于 ipcMain handler 注册触发 invoke。 */\n\tprivate loadAssembledSources(): void {\n\t\tif (this.toolbarView && this.config.toolbar) {\n\t\t\tthis.safeLoad(this.toolbarView.webContents, this.config.toolbar.source)\n\t\t}\n\t\tif (this.config.windows) {\n\t\t\tfor (const [key, contrib] of Object.entries(this.config.windows)) {\n\t\t\t\tconst win = this.declaredWindows.get(key)\n\t\t\t\tif (win) this.safeLoad(win.webContents, contrib.source)\n\t\t\t}\n\t\t}\n\t\t// config.app.source: auto-load the framework-built main window, mirroring the\n\t\t// toolbar path. `this.mainWindow` is null under an `ownsWindows:true` backend\n\t\t// (constructRuntime returns early before building it), so this naturally\n\t\t// skips that case — the backend builds + loads its own window.\n\t\tif (this.mainWindow && this.config.app?.source) {\n\t\t\tthis.safeLoad(this.mainWindow.webContents, this.config.app.source)\n\t\t}\n\t}\n\n\t/**\n\t * Load is best-effort: log + emit `load-failed` if it rejects,\n\t * but never let start() reject because of a renderer load issue (we'd\n\t * leave the host blocked indefinitely while the framework is otherwise\n\t * happy).\n\t */\n\tprivate safeLoad(wc: MinimalWebContentsLike, source: WebviewSource): void {\n\t\tif ('url' in source) {\n\t\t\twc.loadURL(source.url).catch((err: unknown) => {\n\t\t\t\tconsole.error(`[electron-deck] loadURL(\"${source.url}\") failed:`, err)\n\t\t\t\tthis.surfaceLoadFailed(source, err)\n\t\t\t})\n\t\t}\n\t\telse {\n\t\t\twc.loadFile(source.file).catch((err: unknown) => {\n\t\t\t\tconsole.error(`[electron-deck] loadFile(\"${source.file}\") failed:`, err)\n\t\t\t\tthis.surfaceLoadFailed(source, err)\n\t\t\t})\n\t\t}\n\t}\n\n\t/**\n\t * D1 fix: load-failed 在 listener 注册之前到达时 buffer 到 pendingLoadFailed;\n\t * 第一个 listener 注册时 splice 消费整个队列。已有 listener 时直接 emit。\n\t */\n\tprivate surfaceLoadFailed(source: WebviewSource, error: unknown): void {\n\t\tconst listeners = this.fwListeners.get('load-failed')\n\t\tif (listeners && listeners.size > 0) {\n\t\t\tthis.emitFrameworkEvent('load-failed', { source, error })\n\t\t\treturn\n\t\t}\n\t\tthis.pendingLoadFailed.push({ source, error })\n\t}\n\n\tprivate bindWireTransport(): void {\n\t\tconst wireOpts = this.options.wireTransport\n\t\tif (!wireOpts) return\n\n\t\t// Single trust authority. Two clean branches, never split:\n\t\t//\n\t\t// - DEFAULT (no override): the internal `trustSet` is the sole authority.\n\t\t// `senderPolicy` reads `trustSet.isTrusted(id)` and `trustedWebContents`\n\t\t// reads `trustSet.snapshot()` — gate (isTrusted) and fanout (snapshot)\n\t\t// are the SAME source, so `_trustWebContents()` / `runtime.windows.trust()`\n\t\t// writes are visible to both. (Previously the default policy chained off\n\t\t// the `trustedWebContents` *closure* instead of `trustSet`, which let a\n\t\t// consumer override desync the gate from the internal set.)\n\t\t//\n\t\t// - OVERRIDE: the consumer takes full authority for whatever it overrides.\n\t\t// A custom `trustedWebContents` becomes the membership source for fanout\n\t\t// AND (when no explicit `senderPolicy` is given) the gate derives from\n\t\t// that SAME closure — so policy and fanout stay同源 on the override side\n\t\t// too, never one-from-trustSet / one-from-closure. A custom `senderPolicy`\n\t\t// replaces the gate outright. The internal `trustSet` is simply not the\n\t\t// authority on the override branch (`windows.trust()` writes it but the\n\t\t// consumer-supplied closure governs), which is the consumer's contract.\n\t\tconst trustedWebContents = wireOpts.trustedWebContents\n\t\t\t?? ((): readonly MinimalWebContents[] => this.trustSet.snapshot())\n\t\tconst defaultSenderPolicy: SenderPolicy = wireOpts.trustedWebContents\n\t\t\t? {\n\t\t\t\t\t// override fanout source → derive the gate from the SAME closure.\n\t\t\t\t\tisTrusted: (id: number): boolean => {\n\t\t\t\t\t\tfor (const wc of wireOpts.trustedWebContents!()) {\n\t\t\t\t\t\t\tif (wc.id === id) return true\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn false\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t: {\n\t\t\t\t\t// default → trustSet is the single authority for both gate + fanout.\n\t\t\t\t\tisTrusted: (id: number): boolean => this.trustSet.isTrusted(id),\n\t\t\t\t}\n\t\tconst senderPolicy = wireOpts.senderPolicy ?? defaultSenderPolicy\n\t\t// Expose the live policy to buildRuntime (replaces `() => true` stub).\n\t\tthis.wireSenderPolicy = senderPolicy\n\n\t\t// Construct the grant-gated ControlBus BEFORE the\n\t\t// WireTransport. `CreateControlBusDeps.transport` is vestigial (the facade\n\t\t// never calls back into the wire; the wire calls `dispatch`, not the\n\t\t// reverse), so there is NO circular dependency: the ControlBus is built\n\t\t// first, then the wire's `invokeHost` reads `this.controlBus` lazily at\n\t\t// call time. Injecting `this.capability.policy` arms the grant gate.\n\t\tthis.controlBus = createControlBus({\n\t\t\tbus: this.bus,\n\t\t\ttrustSet: this.trustSet,\n\t\t\tpolicy: this.capability.policy,\n\t\t})\n\n\t\tconst transport = new WireTransport({\n\t\t\tipcMain: wireOpts.ipcMain,\n\t\t\tbus: this.bus,\n\t\t\tsenderPolicy,\n\t\t\ttrustedWebContents,\n\t\t\t// The two-route boundary (「两条 invoke 路由的硬边界」): the wire's host `invokeHost`\n\t\t\t// seam FORKS by command name.\n\t\t\t// - PRIVILEGED `layout.*` names route through `controlBus.dispatch`,\n\t\t\t// which applies the grant gate (DECK_FORBIDDEN when no live grant\n\t\t\t// covers (ctx.senderId, name)). These names MUST NOT be registered\n\t\t\t// in `hostServices` — they live only in the gated ControlBus command\n\t\t\t// table (`runtime.layout.command`).\n\t\t\t// - ORDINARY domain APIs keep the existing un-gated declarative\n\t\t\t// `hostServices` route over InMemoryTypedIpcRegistry (trusted may\n\t\t\t// call, no grant gate).\n\t\t\tinvokeHost: (name, args, ctx) => {\n\t\t\t\tif (this.isPrivilegedCommandName(name)) {\n\t\t\t\t\treturn this.controlBus!.dispatch(name, args, ctx)\n\t\t\t\t}\n\t\t\t\treturn this.ipc.invoke<JsonValue>(`${HOST_CHANNEL_PREFIX}${name}`, ...args)\n\t\t\t},\n\t\t\t// simulator APIs are not privileged layout commands — unchanged.\n\t\t\tinvokeSimulator: (name, args, _ctx) =>\n\t\t\t\tthis.ipc.invoke<JsonValue>(`${SIMULATOR_CHANNEL_PREFIX}${name}`, ...args),\n\t\t\tdeclaredEvents: () =>\n\t\t\t\tthis.config.events ? this.config.events.map(ev => ev.name) : [],\n\t\t})\n\t\ttransport.start()\n\t\tthis.wireTransport = transport\n\t\t// Arm the slot-token Place / LayoutSubscribe channels at\n\t\t// framework START (right after the wire binds) instead of lazily on the\n\t\t// first anchored placeIn. This lets a `createDeckLayoutClient` subscribe\n\t\t// before any view is placed without hitting a \"no handler\" reject. The\n\t\t// gate (trust + main-frame + token) lives in the handlers and is unchanged\n\t\t// by arm timing — eager arming widens no attack surface.\n\t\tthis.ensureSlotChannelsArmed()\n\t\tthis.registry.add({\n\t\t\tdispose: () => {\n\t\t\t\ttransport.dispose()\n\t\t\t},\n\t\t})\n\t}\n\n\t/**\n\t * The two-route boundary's privileged-name predicate (「两条 invoke 路由的硬边界」). A\n\t * PRIVILEGED command name — by convention `layout.*` — routes through the\n\t * grant-gated {@link ControlBus} (`controlBus.dispatch`). Ordinary domain\n\t * APIs (any other name) stay on the un-gated declarative `hostServices`\n\t * route. Privileged names MUST NOT be registered in `hostServices`.\n\t */\n\tprivate isPrivilegedCommandName(name: string): boolean {\n\t\treturn name.startsWith('layout.')\n\t}\n\n\t/**\n\t * Warn ONCE about an invalid `keepAlive.max` (negative / non-integer /\n\t * NaN). Such a view is not keep-alive-managed (the group is skipped); warning\n\t * once avoids log spam when many views share the same bad config.\n\t */\n\tprivate warnedInvalidKeepAliveMax = false\n\tprivate warnInvalidKeepAliveMax(max: number): void {\n\t\tif (this.warnedInvalidKeepAliveMax) return\n\t\tthis.warnedInvalidKeepAliveMax = true\n\t\tconsole.warn(\n\t\t\t`[electron-deck] runtime.view keepAlive.max must be a non-negative integer (got: ${max}); `\n\t\t\t+ 'the view is NOT keep-alive-managed (no eviction).',\n\t\t)\n\t}\n\n\t/** Drop `viewId` from `groupKey`'s HIDDEN list only (the group's `handles`\n\t * entry stays — the view is still keep-alive-managed, just no longer\n\t * evictable while visible). Idempotent: a no-op when the view isn't on the\n\t * hidden list or the group doesn't exist. */\n\tprivate dropViewFromKeepAliveHidden(groupKey: string, viewId: string): void {\n\t\tconst group = this.keepAliveGroups.get(groupKey)\n\t\tif (!group) return\n\t\tconst i = group.hidden.indexOf(viewId)\n\t\tif (i >= 0) group.hidden.splice(i, 1)\n\t}\n\n\tprivate constructWindow(\n\t\topts: WindowContribution | (WindowCreateOptions & { title?: string }),\n\t\tautoTrust: boolean,\n\t\tdeferLoad = false,\n\t): MinimalBrowserWindow {\n\t\tconst electron = this.options.electron\n\t\tif (!electron) {\n\t\t\tthrow new Error('DeckApp.constructWindow called without electron injection')\n\t\t}\n\t\tconst browserOpts: MinimalBrowserWindowOptions = {}\n\t\tif (opts.title !== undefined) browserOpts.title = opts.title\n\t\tif (opts.width !== undefined) browserOpts.width = opts.width\n\t\tif (opts.height !== undefined) browserOpts.height = opts.height\n\t\tif (opts.modal !== undefined) browserOpts.modal = opts.modal\n\t\tif (opts.preloadPath !== undefined) {\n\t\t\tbrowserOpts.webPreferences = { preload: opts.preloadPath }\n\t\t}\n\t\tif ('parent' in opts && opts.parent !== undefined) {\n\t\t\tbrowserOpts.parent = opts.parent as unknown as MinimalBrowserWindow\n\t\t}\n\t\tconst win = new electron.BrowserWindow(browserOpts)\n\t\tthis.trackedWindows.add(win)\n\t\t// Capture the webContents ONCE while the window is alive. Real Electron\n\t\t// fires 'closed' AFTER the window is destroyed, at which point reading\n\t\t// `win.webContents` may throw / return a different value — so every later\n\t\t// keyed lookup (shadow, wcRecords, backend-trust mirror) MUST use this\n\t\t// captured reference, never re-read `win.webContents` post-destroy. (A\n\t\t// re-read in the 'closed' handler is what silently skipped windowScope.close\n\t\t// → trust revocation leak under real Electron.)\n\t\tconst wc = win.webContents as unknown as MinimalWebContents\n\t\t// windowScope (child of rootScope) owns this window's\n\t\t// destruction; rootScope.close() cascades the destroy. Declared and\n\t\t// runtime.windows.create() windows both flow through here. The isDestroyed()\n\t\t// guard avoids double-destroy when the window was already closed externally.\n\t\tconst windowScope = this.rootScope.child()\n\t\twindowScope.own(() => {\n\t\t\tif (!win.isDestroyed()) win.destroy()\n\t\t})\n\t\tthis.lifetimeShadow.set(wc, {\n\t\t\twindow: win,\n\t\t\twindowScope,\n\t\t})\n\t\t// Per-window native-view substrate, keyed by the captured `wc`. Created\n\t\t// AFTER the `win.destroy` own above so its `detachAll` runs FIRST in the\n\t\t// LIFO teardown before destroy.\n\t\tthis.windowSubstrates.set(wc, this.createWindowSubstrate(win, windowScope))\n\t\t// Declared / runtime-created window 'closed' only cleans up its own\n\t\t// tracked state; full framework shutdown is reserved for mainWindow. Pass the\n\t\t// CAPTURED `wc` (not a post-destroy re-read). Registered BEFORE admitTrust so\n\t\t// the revocation hook is armed before trust is admitted: combined with\n\t\t// admitTrust's isDestroyed() guard, a window destroyed at any point can never\n\t\t// leave a trusted-but-unrevoked wc (no construction gap).\n\t\twin.on('closed', () => {\n\t\t\tthis.handleSubWindowClosed(win, wc)\n\t\t})\n\t\tif (autoTrust) {\n\t\t\tthis.admitTrust(wc, windowScope)\n\t\t\t// Let the backend mirror trust for framework-built windows.\n\t\t\t// This onWindowTrusted call is **orthogonal to ownsWindows**: it fires\n\t\t\t// for any window the framework itself constructs and trusts — declared\n\t\t\t// `config.windows` (assembleElectron) and imperative\n\t\t\t// `runtime.windows.create()` (buildRuntime). A window-owning backend\n\t\t\t// that calls runtime.windows.create() in its assemble() has explicitly\n\t\t\t// asked the framework to build + trust that window, so notifying it is\n\t\t\t// correct even under ownsWindows:true. (Contrast the main-window\n\t\t\t// auto-trust above, which IS ownsWindows-gated.)\n\t\t\t// Construction-time (window alive) → reading win.webContents is safe and\n\t\t\t// keeps the Like type; the map key is the same object identity as `wc`.\n\t\t\tthis._notifyBackendTrusted(win.webContents)\n\t\t\t// Main-frame cross-document navigation grant-reset on this window's\n\t\t\t// control wc (after admitTrust so its wcScope exists).\n\t\t\tthis.bindNavigationGrantReset(wc)\n\t\t}\n\t\t// Record the DeckWindow facade (newSession + per-window onClose) and\n\t\t// arm the per-window cancelable close machine. The machine only preventDefaults\n\t\t// when a live per-window decider exists, so declared windows (no decider) keep\n\t\t// Electron's default close.\n\t\tthis.buildDeckWindow(win, wc, windowScope)\n\t\tthis.armSubWindowCloseMachine(win, wc, windowScope)\n\t\tif (!deferLoad) {\n\t\t\tthis.safeLoad(win.webContents, opts.source)\n\t\t}\n\t\treturn win\n\t}\n\n\t/**\n\t * Register an EXTERNALLY-created window into the framework so\n\t * `runtime.view().placeIn(win)` works for it: a `rootScope.child()`\n\t * windowScope, a per-window {@link ViewSubstrate}, and the TRUST lifecycle —\n\t * mirroring {@link constructWindow}'s registration, but for a window the host\n\t * built (e.g. under `ownsWindows:true`). The framework holds NO windowScope /\n\t * substrate / trust for such a window until this call.\n\t *\n\t * Ordering MIRRORS the framework's own windows (constructWindow / main-window):\n\t * 1) build the windowScope (child of rootScope so shutdown cascades it),\n\t * 2) build + register the per-window substrate (so placeIn can resolve it),\n\t * 3) for `ownership:'transfer'` ONLY, OWN `() => win.destroy()` on the\n\t * windowScope so app shutdown destroys the window; for `'observe'` the host\n\t * keeps lifetime control (the framework never destroys it),\n\t * 4) arm trust+grant+slot-token revocation as the FIRST `'closed'` listener via\n\t * `prependListener` (runs before any external `'closed'` listener),\n\t * 5) `admitTrust` AFTER the revoke listener is registered (constructWindow order).\n\t *\n\t * Idempotent by webContents identity: a second adopt of the same window returns\n\t * the existing registration. Throws if the window/webContents is destroyed.\n\t */\n\tprivate adoptWindow(\n\t\twin: MinimalBrowserWindow,\n\t\topts?: { ownership?: 'transfer' | 'observe' },\n\t): Disposable {\n\t\tconst wc = win.webContents as unknown as MinimalWebContents\n\t\t// SECURITY: never adopt a dead window. wc.id is reused by Electron, so\n\t\t// admitting a destroyed wc would leave trust a later window could inherit.\n\t\tif (win.isDestroyed?.() || wc.isDestroyed?.()) {\n\t\t\tthrow new Error('runtime.windows.adopt: window is already destroyed')\n\t\t}\n\t\t// Idempotent by wc identity: an already-adopted window returns its existing\n\t\t// registration (no second substrate, no second trust lease).\n\t\tconst existing = this.adoptedWindows.get(wc)\n\t\tif (existing) return existing\n\t\t// A window the framework ALREADY tracks (its own\n\t\t// constructed main/toolbar/declared window) must NOT be re-adopted — that\n\t\t// would replace its substrate and add a SECOND trust lease for the same wc.\n\t\tif (this.windowSubstrates.has(wc)) {\n\t\t\tthrow new Error('runtime.windows.adopt: window is already framework-tracked (cannot adopt a framework-owned window)')\n\t\t}\n\n\t\t// (1) windowScope — a child of rootScope so rootScope.close() (shutdown)\n\t\t// cascades the adopted window's teardown automatically.\n\t\tconst windowScope = this.rootScope.child()\n\t\t// (2) ownership: OWN the destroy FIRST (before the substrate's detachAll own\n\t\t// below) when TRANSFERRING the window's lifetime to the framework. Under\n\t\t// Scope LIFO the LAST-owned disposer runs FIRST, so registering destroy\n\t\t// BEFORE the substrate makes detachAll run BEFORE win.destroy — matching\n\t\t// constructWindow's ordering. The reverse order would destroy the window\n\t\t// before the substrate detached, and detachAll would then no-op against a\n\t\t// destroyed host. For 'observe' (default) the HOST owns the window — the\n\t\t// framework tears down substrate+trust but NEVER destroys the window itself.\n\t\tif (opts?.ownership === 'transfer') {\n\t\t\twindowScope.own(() => {\n\t\t\t\tif (!win.isDestroyed()) win.destroy()\n\t\t\t})\n\t\t}\n\t\t// (3) per-window substrate (mirror constructWindow) so placeIn resolves it.\n\t\t// Created AFTER the destroy-own above so its detachAll runs FIRST in LIFO\n\t\t// teardown before win.destroy.\n\t\tthis.windowSubstrates.set(wc, this.createWindowSubstrate(win, windowScope))\n\t\t// (4) Arm trust+grant+slot-token revocation as the FIRST 'closed' listener via\n\t\t// prependListener: it MUST run before any external 'closed' listener\n\t\t// the host registered earlier, so that listener never observes the adopted wc\n\t\t// still trusted (its id may be reused by Electron). Capture wc.id WHILE the\n\t\t// window is alive — a post-destroy webContents read inside 'closed' can throw.\n\t\tconst adoptedWcId = wc.id\n\t\tconst revoke = (): void => {\n\t\t\tthis.revokeWindowTrust(windowScope)\n\t\t\tthis.revokeSlotTokensForWc(adoptedWcId)\n\t\t}\n\t\tif (typeof win.prependListener === 'function') {\n\t\t\twin.prependListener('closed', revoke)\n\t\t}\n\t\telse {\n\t\t\t// Real Electron BrowserWindow always has prependListener; a fake lacking it\n\t\t\t// can't guarantee revoke-first, but trust must still be revoked on close.\n\t\t\twin.on('closed', revoke)\n\t\t}\n\t\t// The 'closed' revoke listener's ownership belongs to the windowScope: its\n\t\t// teardown (un-adopt) removes it so re-adopting the same window never leaves a\n\t\t// stale revoke listener behind.\n\t\twindowScope.own(() => this.removeWindowListener(win, 'closed', revoke))\n\t\t// (5) admit trust AFTER the revoke listener is armed (constructWindow order):\n\t\t// combined with admitTrust's isDestroyed() guard, a window destroyed at any\n\t\t// point can never leave a trusted-but-unrevoked wc.\n\t\tthis.admitTrust(wc, windowScope)\n\t\t// Main-frame cross-document navigation grant-reset on the adopted\n\t\t// control wc (after admitTrust so its wcScope exists). Record its\n\t\t// DeckWindow facade + arm the per-window close machine (only preventDefaults\n\t\t// when a live per-window decider exists, so an adopted window with no decider\n\t\t// keeps the host's own close handling).\n\t\tthis.bindNavigationGrantReset(wc)\n\t\tthis.buildDeckWindow(win, wc, windowScope)\n\t\tthis.armSubWindowCloseMachine(win, wc, windowScope)\n\t\t// Also run the synchronous revoke during the windowScope's teardown cascade\n\t\t// (un-adopt / app shutdown), so substrate+trust are torn down even when no\n\t\t// 'closed' fires (e.g. 'observe' shutdown where the host never closes it).\n\t\t// The async wcScope cascade (own() in admitTrust) is idempotent with it.\n\t\twindowScope.own(() => {\n\t\t\tthis.windowSubstrates.delete(wc)\n\t\t\tthis.adoptedWindows.delete(wc)\n\t\t\tthis.revokeSlotTokensForWc(adoptedWcId)\n\t\t})\n\n\t\tlet disposed = false\n\t\tconst registration: Disposable = {\n\t\t\tdispose: () => {\n\t\t\t\tif (disposed) return\n\t\t\t\tdisposed = true\n\t\t\t\t// Synchronously revoke trust + grants + slot\n\t\t\t\t// tokens BEFORE the async windowScope.close(), so an un-adopt leaves NO\n\t\t\t\t// window where the wc is still observable-as-trusted after dispose()\n\t\t\t\t// returns (mirrors the synchronous 'closed' revoke). Idempotent with the\n\t\t\t\t// windowScope.close() cascade below.\n\t\t\t\tthis.revokeWindowTrust(windowScope)\n\t\t\t\tthis.revokeSlotTokensForWc(adoptedWcId)\n\t\t\t\t// Un-adopt: close the windowScope → cascades substrate detachAll +\n\t\t\t\t// (transfer) win.destroy + trust leases + the cleanup own above.\n\t\t\t\tvoid windowScope.close().catch((e: unknown) => {\n\t\t\t\t\tconsole.error('[electron-deck] adopted windowScope.close() failed:', e)\n\t\t\t\t})\n\t\t\t},\n\t\t}\n\t\tthis.adoptedWindows.set(wc, registration)\n\t\treturn registration\n\t}\n\n\t/**\n\t * Synchronously revoke BOTH trust leases AND capability\n\t * grants for every wc admitted under `windowScope` (the window's control wc +\n\t * any siblings like the toolbar wc). Called from the window's 'closed' handler\n\t * so both authorizations are gone the instant the window is destroyed,\n\t * closing every async-cascade race (a destroyed wc can never be observed trusted OR granted\n\t * by a later shutdown/beforeClose, nor by a NEW window that reuses the same\n\t * wc.id before the async scope cascade revokes it). The leases are ALSO owned\n\t * by their wcScope, so the async `windowScope.close()` still runs (it disposes\n\t * the already-disposed leases idempotently, plus win.destroy + wcRecords\n\t * cleanup) and remains the teardown/partial-init fallback for paths where no\n\t * 'closed' fires (cleanupOnError, shutdown of a never-closed window).\n\t * `lease.dispose()` is synchronous (trustSet ref-count--) and one-shot, so no\n\t * double-decrement; `revokeBySenderId` is likewise idempotent, so the grant's\n\t * own async `senderScope.on('closed')` revoke later becomes a no-op.\n\t */\n\tprivate revokeWindowTrust(windowScope: Scope): void {\n\t\tfor (const [wc, rec] of this.wcRecords) {\n\t\t\tif (rec.windowScope === windowScope) {\n\t\t\t\tfor (const lease of rec.leases) {\n\t\t\t\t\tlease.dispose()\n\t\t\t\t}\n\t\t\t\t// wc.id-reuse safety: synchronously drop this wc's grants too,\n\t\t\t\t// mirroring the trust-lease revocation above. `wc.id` is the\n\t\t\t\t// senderId the grant was issued with.\n\t\t\t\tthis.capability.revokeBySenderId(wc.id)\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Arm the wire's slot-token inbound channels (idempotent). Called once at\n\t * framework start() (bindWireTransport, right after\n\t * transport.start()) so a layout client can subscribe before the first\n\t * anchored placeIn; the anchored `placeIn` call site is now a redundant no-op\n\t * (kept for safety). No-op when there's no wire transport (main-internal-only\n\t * builds can't serve a renderer slot anyway).\n\t */\n\tprivate ensureSlotChannelsArmed(): void {\n\t\tthis.wireTransport?.armSlotChannels(\n\t\t\t(senderId, rawSnapshot) => this.handleSnapshot(senderId, rawSnapshot),\n\t\t\tsenderId => this.handleLayoutSubscribe(senderId),\n\t\t)\n\t}\n\n\t/**\n\t * slot-token apply path (`__electron-deck:snapshot`). The renderer publishes a\n\t * whole window-level desired-placement table; each entry's slotToken is the\n\t * credential. Steps:\n\t * 1. AUTHORIZE + clean: `cleanSnapshot` keeps only views whose token is known\n\t * AND authorized to `senderId` (anti-spoof), deriving each view's identity\n\t * (viewId) + z-order (zone→layer) from the registry — never from the\n\t * renderer-reported fields. A non-empty snapshot that fully fails\n\t * authorization is rejected wholesale (returns null) so a transient token\n\t * failure is NOT read as \"detach everything\".\n\t * 2. RECONCILE the cleaned table against this wc's last-applied actual state\n\t * → an ordered op list (level-triggered; a lost/spurious frame self-heals).\n\t * 3. DISPATCH the ops onto each view's `applyPlacement` sink.\n\t */\n\tprivate handleSnapshot(senderId: number, rawSnapshot: unknown): void {\n\t\tconst clean = cleanSnapshot(rawSnapshot, (slotToken) => {\n\t\t\tconst entry = this.slotTokens.get(slotToken)\n\t\t\tif (!entry || entry.authorizedWcId !== senderId) return null\n\t\t\treturn { viewId: entry.viewId, layer: entry.zone }\n\t\t})\n\t\tif (!clean) return // malformed OR fully-unauthorized → drop (never detach-all)\n\n\t\tconst prev = this.reconcileStates.get(senderId) ?? createInitialState()\n\t\tconst { state, ops } = reconcile(prev, clean)\n\t\tthis.reconcileStates.set(senderId, state)\n\n\t\t// Resolve a viewId back to its apply sink. Built from ALL of this wc's live\n\t\t// tokens (not just the cleaned snapshot) so a detach op for a view the\n\t\t// renderer dropped can still reach its sink.\n\t\tconst applyByViewId = new Map<string, (p: unknown) => void>()\n\t\tfor (const entry of this.slotTokens.values()) {\n\t\t\tif (entry.authorizedWcId === senderId) applyByViewId.set(entry.viewId, entry.apply)\n\t\t}\n\t\tdispatchOps(ops, state, (viewId) => applyByViewId.get(viewId) ?? null)\n\t}\n\n\t/**\n\t * Per-wc layout-subscribe replay (`__electron-deck:layout-subscribe`). When the\n\t * authorized control wc (re)subscribes (e.g. after reload), BUMP its generation\n\t * and RESET its reconciler (a reload restarts the renderer's epoch at 0; without\n\t * the reset the fresh low-epoch snapshots would be rejected as stale), then\n\t * re-push every grant it owns — now carrying the bumped generation — so it can\n\t * re-bind its DOM slots. A DIFFERENT wc's subscribe never touches another wc's\n\t * state or grants.\n\t */\n\tprivate handleLayoutSubscribe(senderId: number): void {\n\t\tthis.perWcGeneration.set(senderId, (this.perWcGeneration.get(senderId) ?? 0) + 1)\n\t\tthis.reconcileStates.delete(senderId)\n\t\tfor (const entry of this.slotTokens.values()) {\n\t\t\tif (entry.authorizedWcId === senderId) entry.resend()\n\t\t}\n\t}\n\n\t/** Drop every slot token + reconciler/generation state authorized to `wcId`\n\t * (window-close / shutdown hygiene). */\n\tprivate revokeSlotTokensForWc(wcId: number): void {\n\t\tfor (const [token, entry] of this.slotTokens) {\n\t\t\tif (entry.authorizedWcId === wcId) this.slotTokens.delete(token)\n\t\t}\n\t\tthis.reconcileStates.delete(wcId)\n\t\tthis.perWcGeneration.delete(wcId)\n\t}\n\n\tprivate handleSubWindowClosed(win: MinimalBrowserWindow, wc: MinimalWebContents): void {\n\t\tthis.trackedWindows.delete(win)\n\t\t// Drop this window's view substrate (keyed by the captured `wc`). The\n\t\t// Compositor's `detachAll` already ran via the windowScope cascade; this\n\t\t// just clears the registry entry.\n\t\tthis.windowSubstrates.delete(wc)\n\t\t// slot-token leak hygiene — drop every token authorized to this window's\n\t\t// control wc (a closed wc can't send `place` anyway, but don't leak the map).\n\t\tthis.revokeSlotTokensForWc(wc.id)\n\t\t// Close + drop this window's child Scope, in lock-step with\n\t\t// trackedWindows. Idempotent: a repeated 'closed' finds no entry and returns\n\t\t// without touching the scope twice. Keyed by the CAPTURED `wc` — re-reading\n\t\t// `win.webContents` here (post-destroy) could miss the entry and silently\n\t\t// skip windowScope.close(), leaking the window's trust.\n\t\tconst shadowEntry = this.lifetimeShadow.get(wc)\n\t\tif (shadowEntry) {\n\t\t\tthis.lifetimeShadow.delete(wc)\n\t\t\t// Synchronously revoke this window's trust the instant it is destroyed,\n\t\t\t// then run the async windowScope teardown (idempotent re-dispose + clean\n\t\t\t// up). The sync revoke means no shutdown/beforeClose can observe this\n\t\t\t// destroyed wc as trusted, regardless of cascade timing.\n\t\t\tthis.revokeWindowTrust(shadowEntry.windowScope)\n\t\t\tvoid shadowEntry.windowScope.close()\n\t\t}\n\t\tfor (const [key, w] of this.declaredWindows) {\n\t\t\tif (w === win) {\n\t\t\t\tthis.declaredWindows.delete(key)\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\t// Trust revocation is a pure Scope-teardown effect — the\n\t\t// `void shadowEntry.windowScope.close()` above cascades into this wc's\n\t\t// wcScope, disposing every lease → ref-count zeroes → untrusted (wc.id-reuse\n\t\t// + leak safety).\n\t\t// Undo the backend trust mirror for THIS window now (not at teardown).\n\t\tthis.backendTrustDisposables.get(wc)?.dispose()\n\t\tthis.emitFrameworkEvent('window-closed', { window: win as unknown as FrameworkEvents['window-closed']['window'] })\n\t}\n\n\t/**\n\t * Notify the backend that the framework has edge-trusted one of its\n\t * (framework-built) webContents, so the backend can mirror it into the domain\n\t * trust set. The returned Disposable is disposed when THAT window closes\n\t * ({@link handleSubWindowClosed}) — honouring the contract \"framework disposes\n\t * on untrust / window destroy\" — and is also added to the registry as a\n\t * teardown safety net (e.g. the main window, which dies with the app). The\n\t * wrapper is one-shot so the two paths don't double-dispose.\n\t */\n\tprivate _notifyBackendTrusted(wc: MinimalWebContentsLike): void {\n\t\tconst hook = this.options.backend?.onWindowTrusted\n\t\tif (!hook) return\n\t\tconst d = hook(wc as unknown as MinimalWebContents)\n\t\tif (!d) return\n\t\tconst key = wc as unknown as MinimalWebContents\n\t\tlet disposed = false\n\t\tconst once: Disposable = {\n\t\t\tdispose: () => {\n\t\t\t\tif (disposed) return\n\t\t\t\tdisposed = true\n\t\t\t\tthis.backendTrustDisposables.delete(key)\n\t\t\t\td.dispose()\n\t\t\t},\n\t\t}\n\t\tthis.backendTrustDisposables.set(key, once)\n\t\tthis.registry.add(once)\n\t}\n\n\tprivate emitFrameworkEvent<E extends keyof FrameworkEvents>(\n\t\tevent: E,\n\t\tpayload: FrameworkEvents[E],\n\t): void {\n\t\tconst set = this.fwListeners.get(event)\n\t\tif (!set || set.size === 0) return\n\t\t// Copy first to be safe against listener-time mutation\n\t\tfor (const fn of Array.from(set)) {\n\t\t\ttry {\n\t\t\t\tfn(payload as unknown)\n\t\t\t}\n\t\t\tcatch (err) {\n\t\t\t\tconsole.error(`[electron-deck] framework listener for \"${event}\" threw:`, err)\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate async cleanupOnError(): Promise<void> {\n\t\tthis.lifecycle._force('cleanup')\n\t\tawait this.runShutdownCleanup()\n\t\tthis.lifecycle._force('destroy')\n\t\tthis.lifecycle._force('quit')\n\t}\n\n\tprivate async doShutdown(): Promise<void> {\n\t\tif (this.lifecycle.current === 'quit') return\n\n\t\tconst cur = this.lifecycle.current\n\t\tif (cur === 'ready') {\n\t\t\tthis.lifecycle.enter('drain')\n\t\t\tthis.lifecycle.enter('cleanup')\n\t\t}\n\t\telse {\n\t\t\t// 紧急 shutdown(尚未到 ready)\n\t\t\tthis.lifecycle._force('cleanup')\n\t\t}\n\n\t\tawait this.runShutdownCleanup()\n\n\t\tthis.lifecycle._force('destroy')\n\t\tthis.lifecycle._force('quit')\n\n\t\t// As the sole orchestrator, the framework owns process exit: once teardown\n\t\t// completes, quit Electron so a framework-owned app doesn't linger with no\n\t\t// windows. No-op when no app is injected (window-only fakes / hosts like\n\t\t// devtools that own their window + drive their own app lifecycle).\n\t\t// Skip when shutdown was DRIVEN BY `will-quit` (the app is already quitting):\n\t\t// re-calling `app.quit()` mid-quit re-enters the quit sequence.\n\t\tif (!this.quitInitiated) {\n\t\t\ttry { this.options.electron?.app?.quit() }\n\t\t\tcatch (e) { console.error('[electron-deck] app.quit() failed:', e) }\n\t\t}\n\t}\n\n\tprivate runShutdownCleanup(): Promise<void> {\n\t\t// Idempotency = JOIN: run the cleanup body at most once, and make a re-entrant\n\t\t// caller AWAIT the in-flight run rather than skip past it. `cleanupOnError`\n\t\t// reaches here outside the single-flight `shutdownPromise`, and a concurrent\n\t\t// `shutdown()` can arrive while the first cleanup is still parked (e.g. on a\n\t\t// slow disposer). A plain boolean early-return let that second path race ahead\n\t\t// to `app.quit()` mid-teardown (truncation). Caching the promise merges them.\n\t\tif (this.cleanupPromise) return this.cleanupPromise\n\t\tthis.cleanupPromise = this.doShutdownCleanup()\n\t\treturn this.cleanupPromise\n\t}\n\n\tprivate async doShutdownCleanup(): Promise<void> {\n\t\t// No pre-beforeClose trust fence is needed here. Trust\n\t\t// for a destroyed window is revoked SYNCHRONOUSLY in its 'closed' handler\n\t\t// (revokeWindowTrust), so any already-destroyed window is already untrusted\n\t\t// before beforeClose runs — regardless of which shutdown trigger (a window's\n\t\t// 'closed' vs `will-quit`) got here first. LIVE windows stay correctly\n\t\t// trusted through beforeClose (their wc is still usable) and are revoked when\n\t\t// rootScope.close() below destroys them (→ 'closed' → revokeWindowTrust).\n\n\t\t// beforeClose await + timeout\n\t\tif (this.config.lifecycle?.beforeClose) {\n\t\t\tconst timeoutMs = this.config.lifecycle.timeoutMs ?? 10_000\n\t\t\ttry {\n\t\t\t\tawait runWithTimeout(this.config.lifecycle.beforeClose(), timeoutMs)\n\t\t\t}\n\t\t\tcatch (e) {\n\t\t\t\tconsole.error('[electron-deck] lifecycle.beforeClose failed/timed out:', e)\n\t\t\t}\n\t\t}\n\n\t\t// backend.onShutdown — AWAITED exactly once (runShutdownCleanup runs once,\n\t\t// fenced by shutdown()'s shutdownPromise), consistently with beforeClose.\n\t\t// Best-effort: a throw/reject is logged and never aborts the rest of\n\t\t// shutdown. Runs regardless of `ownsWindows`.\n\t\tconst onShutdown = this.options.backend?.onShutdown\n\t\tif (onShutdown) {\n\t\t\ttry {\n\t\t\t\tawait onShutdown.call(this.options.backend)\n\t\t\t}\n\t\t\tcatch (e) {\n\t\t\t\tconsole.error('[electron-deck] backend.onShutdown failed:', e)\n\t\t\t}\n\t\t}\n\n\t\t// Teardown via rootScope.close(). rootScope disposes children-first LIFO:\n\t\t// every windowScope (each owning () => win.destroy()) tears down its window\n\t\t// BEFORE rootScope's directly-owned resources run — and those resources are\n\t\t// registry.disposeAll() then bus.unbindAll() (owned in the ctor,\n\t\t// reverse-disposal order). So the critical ordering — windows destroyed\n\t\t// BEFORE WireTransport / ipcMain handlers are removed — is preserved\n\t\t// STRUCTURALLY, with no manual loop. Each windowScope's destroy disposer is\n\t\t// isDestroyed()-guarded, so an already-closed window is skipped (no\n\t\t// double-destroy).\n\t\t//\n\t\t// NOTE (teardown order): rootScope tears children down LIFO = reverse\n\t\t// creation order (last-created window destroyed first), the standard stack\n\t\t// discipline. No consumer contract pins inter-window destroy order at app\n\t\t// shutdown.\n\t\t//\n\t\t// NOTE (`mainWindow`/`toolbarView` nulled AFTER close, not before): real\n\t\t// Electron fires `'closed'` synchronously inside `win.destroy()`, so a host\n\t\t// `window-closed` listener runs DURING rootScope.close()'s cascade. Nulling\n\t\t// these refs before close would expose a prematurely-null ref to such a\n\t\t// listener, so we null AFTER close.\n\t\tawait this.rootScope.close()\n\t\tthis.mainWindow = null\n\t\tthis.toolbarView = null\n\t\tthis.trackedWindows.clear()\n\t\tthis.declaredWindows.clear()\n\t\tthis.lifetimeShadow.clear()\n\t\t// Sub-window substrates self-delete per-close in\n\t\t// handleSubWindowClosed, but the MAIN window's substrate entry is never\n\t\t// removed there (main 'closed' → shutdown → here). Clear the map so no\n\t\t// substrate (main or residual) survives the app's lifetime.\n\t\tthis.windowSubstrates.clear()\n\t\t// Adopted-window registrations self-delete via their windowScope cleanup\n\t\t// during the rootScope cascade; clear any residue.\n\t\tthis.adoptedWindows.clear()\n\t\t// wcRecords entries self-delete via the wcScope cleanup during the rootScope\n\t\t// cascade; clear any untracked residue.\n\t\tthis.wcRecords.clear()\n\t\t// slot-token registry — drop any residual tokens at app teardown.\n\t\tthis.slotTokens.clear()\n\t\t// Drop any residual keep-alive groups at app teardown (each view's\n\t\t// dispose self-deletes its empty group, but clear belt-and-suspenders so no\n\t\t// group survives the app's lifetime).\n\t\tthis.keepAliveGroups.clear()\n\t}\n\n\tprivate buildRuntime(): Runtime {\n\t\tconst { ipc, registry, fwListeners } = this\n\t\tconst electronModule = this.options.electron\n\t\tconst wireIpcMain = this.options.wireTransport?.ipcMain\n\n\t\tconst electronUnavailable = (field: string): never => {\n\t\t\tthrow new Error(`runtime.${field} is unavailable in this build (no Electron)`)\n\t\t}\n\t\tconst rawIpcMainUnavailable = (): never => {\n\t\t\tthrow new Error('runtime.rawIpcMain is unavailable (no wireTransport.ipcMain injected)')\n\t\t}\n\n\t\tconst context: DeckContext = {\n\t\t\tsettings: { theme: 'light' },\n\t\t\ttheme: 'light',\n\t\t\t_registry: this.registry,\n\t\t\t// Forward the live wire senderPolicy (fail-closed when no wire).\n\t\t\t_senderPolicy: this.wireSenderPolicy ?? { isTrusted: () => false },\n\t\t}\n\n\t\tconst trackedWindows = this.trackedWindows\n\t\tconst declaredWindows = this.declaredWindows\n\t\tconst getMainWindow = (): MinimalBrowserWindow | null => this.mainWindow\n\t\tconst getToolbarView = (): MinimalWebContentsView | null => this.toolbarView\n\t\tconst constructWindow = (opts: WindowCreateOptions, autoTrust: boolean): MinimalBrowserWindow => {\n\t\t\tconst win = this.constructWindow(opts, autoTrust)\n\t\t\t// runtime.windows.create() runs in setup/ready phase — emit\n\t\t\t// window-created in real time (no replay needed).\n\t\t\tthis.emitFrameworkEvent('window-created', {\n\t\t\t\twindow: win as unknown as FrameworkEvents['window-created']['window'],\n\t\t\t\trole: 'host',\n\t\t\t})\n\t\t\treturn win\n\t\t}\n\t\tconst getMainDeckWindow = (): DeckWindow | null => {\n\t\t\tif (!this.mainControlWc) return null\n\t\t\treturn this.windowRegistrations.get(this.mainControlWc)?.deckWindow ?? null\n\t\t}\n\t\tconst emitPendingFor = (event: keyof FrameworkEvents): void => {\n\t\t\t// 消费式 splice(0):第一个 listener 注册时 drain 全队列,后续 listener\n\t\t\t// 注册时队列空,不重复 replay(D2 修复:避免 isFirst && inSetupPhase\n\t\t\t// 多次回放同一份 baseline)。\n\t\t\tif (event === 'window-created') {\n\t\t\t\tif (this.pendingWindowCreated.length === 0) return\n\t\t\t\tconst queue = this.pendingWindowCreated.splice(0)\n\t\t\t\tfor (const item of queue) {\n\t\t\t\t\tthis.emitFrameworkEvent('window-created', {\n\t\t\t\t\t\twindow: item.window as unknown as FrameworkEvents['window-created']['window'],\n\t\t\t\t\t\trole: item.role,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t}\n\t\t\telse if (event === 'load-failed') {\n\t\t\t\tif (this.pendingLoadFailed.length === 0) return\n\t\t\t\tconst queue = this.pendingLoadFailed.splice(0)\n\t\t\t\tfor (const item of queue) {\n\t\t\t\t\tthis.emitFrameworkEvent('load-failed', { source: item.source, error: item.error })\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tconst runtime: Runtime = {\n\t\t\tget electron() {\n\t\t\t\tif (!electronModule) return electronUnavailable('electron')\n\t\t\t\treturn electronModule as unknown as typeof import('electron')\n\t\t\t},\n\t\t\tget mainWindow() {\n\t\t\t\tconst mw = getMainWindow()\n\t\t\t\tif (!mw) return electronUnavailable('mainWindow')\n\t\t\t\treturn mw as unknown as typeof runtime.mainWindow\n\t\t\t},\n\t\t\tget toolbarView() {\n\t\t\t\treturn (getToolbarView() ?? null) as unknown as typeof runtime.toolbarView\n\t\t\t},\n\t\t\tipc,\n\t\t\tget rawIpcMain() {\n\t\t\t\tif (!wireIpcMain) return rawIpcMainUnavailable()\n\t\t\t\treturn wireIpcMain as unknown as typeof import('electron').ipcMain\n\t\t\t},\n\t\t\tcall: {\n\t\t\t\tsimulator: async (name, ...args) =>\n\t\t\t\t\tipc.invoke<JsonValue>(`${SIMULATOR_CHANNEL_PREFIX}${name}`, ...args),\n\t\t\t\thost: async (name, ...args) =>\n\t\t\t\t\tipc.invoke<JsonValue>(`${HOST_CHANNEL_PREFIX}${name}`, ...args),\n\t\t\t},\n\t\t\twindows: {\n\t\t\t\tcreate: (opts): DeckWindow => {\n\t\t\t\t\tif (!electronModule) return electronUnavailable('windows.create')\n\t\t\t\t\tconst autoTrust = opts.autoTrust ?? true\n\t\t\t\t\tconst win = constructWindow(opts, autoTrust)\n\t\t\t\t\t// constructWindow recorded the DeckWindow facade in\n\t\t\t\t\t// windowRegistrations (keyed by control wc); return it.\n\t\t\t\t\tconst wc = win.webContents as unknown as MinimalWebContents\n\t\t\t\t\tconst reg = this.windowRegistrations.get(wc)\n\t\t\t\t\tif (!reg) {\n\t\t\t\t\t\tthrow new Error('runtime.windows.create: DeckWindow registration missing (internal invariant)')\n\t\t\t\t\t}\n\t\t\t\t\treturn reg.deckWindow\n\t\t\t\t},\n\t\t\t\tget main(): DeckWindow | null {\n\t\t\t\t\treturn getMainDeckWindow()\n\t\t\t\t},\n\t\t\t\tget: (id): typeof runtime.mainWindow | undefined => {\n\t\t\t\t\tconst w = declaredWindows.get(id)\n\t\t\t\t\treturn w ? (w as unknown as typeof runtime.mainWindow) : undefined\n\t\t\t\t},\n\t\t\t\tall: (): typeof runtime.mainWindow[] => {\n\t\t\t\t\tconst result: typeof runtime.mainWindow[] = []\n\t\t\t\t\tfor (const w of trackedWindows) {\n\t\t\t\t\t\tif (!w.isDestroyed()) result.push(w as unknown as typeof runtime.mainWindow)\n\t\t\t\t\t}\n\t\t\t\t\treturn result\n\t\t\t\t},\n\t\t\t\ttrust: (win): Disposable => {\n\t\t\t\t\tconst w = win as unknown as MinimalBrowserWindow\n\t\t\t\t\tconst wc = w.webContents as unknown as MinimalWebContents\n\t\t\t\t\t// A framework-tracked window has a windowScope → admit the\n\t\t\t\t\t// trust lease under it so the window's close revokes it. A\n\t\t\t\t\t// backend-owned window (ownsWindows:true builds its own main\n\t\t\t\t\t// window with no framework windowScope) is NOT tracked; fall back\n\t\t\t\t\t// to a raw ref the BACKEND owns/disposes (the framework never\n\t\t\t\t\t// manages those windows' lifetime).\n\t\t\t\t\tconst tracked = this.lifetimeShadow.get(wc)\n\t\t\t\t\tif (tracked) {\n\t\t\t\t\t\tconst lease = this.admitTrust(wc, tracked.windowScope)\n\t\t\t\t\t\t// A window constructed `autoTrust:false` then trusted late\n\t\t\t\t\t\t// here MUST also get the navigation grant-reset hook, else its\n\t\t\t\t\t\t// control page could navigate away carrying its grants. Idempotent,\n\t\t\t\t\t\t// so a window already auto-trusted (hook bound) is unaffected.\n\t\t\t\t\t\tthis.bindNavigationGrantReset(wc)\n\t\t\t\t\t\treturn lease\n\t\t\t\t\t}\n\t\t\t\t\t// An ADOPTED window is not in lifetimeShadow (only framework-built\n\t\t\t\t\t// windows are), but `adopt()` already admitted trust under its\n\t\t\t\t\t// windowScope — recorded in wcRecords. Route the explicit lease through\n\t\t\t\t\t// that SAME windowScope so it is revoked when the window closes, not\n\t\t\t\t\t// left on rootScope until shutdown where a reused wc.id could inherit\n\t\t\t\t\t// stale trust. `admitTrust` reuses the existing wcScope for a known wc,\n\t\t\t\t\t// so this only adds a fresh lease under the window's lifetime.\n\t\t\t\t\tconst adopted = this.wcRecords.get(wc)\n\t\t\t\t\tif (adopted) {\n\t\t\t\t\t\tconst lease = this.admitTrust(wc, adopted.windowScope)\n\t\t\t\t\t\tthis.bindNavigationGrantReset(wc)\n\t\t\t\t\t\treturn lease\n\t\t\t\t\t}\n\t\t\t\t\treturn this._trustWebContents(wc)\n\t\t\t\t},\n\t\t\t\tadopt: (win, opts): Disposable => {\n\t\t\t\t\tconst w = win as unknown as MinimalBrowserWindow\n\t\t\t\t\treturn this.adoptWindow(w, opts)\n\t\t\t\t},\n\t\t\t},\n\t\t\tview: (opts): DeckViewHandle => {\n\t\t\t\tif (!electronModule) return electronUnavailable('view')\n\t\t\t\t// `opts.scope` is a DeckSession (or undefined). Resolve it to\n\t\t\t\t// the internal Scope through the provenance map. A foreign / raw Scope\n\t\t\t\t// (e.g. createScope(), or an adopted one) is absent → REJECTED, so a host\n\t\t\t\t// can't smuggle in a scope the framework didn't mint. Undefined → bind to\n\t\t\t\t// the app root (default).\n\t\t\t\tlet displayScope: Scope = this.rootScope\n\t\t\t\tif (opts.scope !== undefined) {\n\t\t\t\t\tconst resolved = this.sessions.get(opts.scope)\n\t\t\t\t\tif (!resolved) {\n\t\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t\t'runtime.view: `scope` must be a DeckSession from runtime.scopes.create() '\n\t\t\t\t\t\t\t+ '(a foreign/raw Scope is rejected)',\n\t\t\t\t\t\t)\n\t\t\t\t\t}\n\t\t\t\t\tdisplayScope = resolved\n\t\t\t\t}\n\t\t\t\tconst wcv = new electronModule.WebContentsView({})\n\t\t\t\tthis.safeLoad(wcv.webContents, opts.source)\n\t\t\t\tconst viewId = `view:${++this.viewSeq}`\n\t\t\t\t// A never-placed DEFAULT (root-scope) view has no viewScope (placeIn makes it) and\n\t\t\t\t// no session scope to cascade its teardown, so its WebContents would leak past app\n\t\t\t\t// shutdown. Register a guarded WC-close on rootScope, and REMOVE\n\t\t\t\t// it once the view is placed (viewScope takes over the destroy) or disposed — so\n\t\t\t\t// the long-lived rootScope never accumulates disposers for placed/disposed views.\n\t\t\t\t//\n\t\t\t\t// NOTE: `Scope.own` (DisposableRegistry) RUNS the disposer when its handle is\n\t\t\t\t// disposed early, so we can't \"silently unregister\" via the handle. Instead a\n\t\t\t\t// `rootCloseDisarmed` flag makes the shutdown-time disposer a no-op once the\n\t\t\t\t// view is placed/disposed (the viewScope / catch-all then owns the close), and\n\t\t\t\t// the handle is disposed only to splice the now-no-op entry off rootScope so it\n\t\t\t\t// doesn't accumulate for the process lifetime.\n\t\t\t\tlet rootCloseReg: Disposable | null = null\n\t\t\t\tlet rootCloseDisarmed = false\n\t\t\t\tconst disarmRootClose = (): void => {\n\t\t\t\t\trootCloseDisarmed = true\n\t\t\t\t\trootCloseReg?.dispose()\n\t\t\t\t\trootCloseReg = null\n\t\t\t\t}\n\t\t\t\tif (displayScope === this.rootScope) {\n\t\t\t\t\trootCloseReg = this.rootScope.own(() => {\n\t\t\t\t\t\tif (rootCloseDisarmed) return\n\t\t\t\t\t\t// `wc &&` guard (real-electron demo finding): on a double-teardown\n\t\t\t\t\t\t// (session-bound view at shutdown) Electron may already have nulled\n\t\t\t\t\t\t// `wcv.webContents`, so reading `.isDestroyed()` on it would throw.\n\t\t\t\t\t\tconst wc = wcv.webContents\n\t\t\t\t\t\tif (wc && !wc.isDestroyed()) wc.close?.()\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t\t// keepAlive「保活寿命归 Scope、淘汰策略归 host」: destroy the backing WebContents (guarded -> idempotent,\n\t\t\t\t// never double-closed) so a view's\n\t\t\t\t// renderer for the app's life on detach.\n\t\t\t\tconst closeNativeWc = (): void => {\n\t\t\t\t\t// `wc &&` guard (real-electron demo finding): a double-teardown\n\t\t\t\t\t// (e.g. a session-bound view closed by both its session scope AND the\n\t\t\t\t\t// rootScope cascade at shutdown) can see `wcv.webContents` already\n\t\t\t\t\t// nulled by Electron — reading `.isDestroyed()` on undefined throws.\n\t\t\t\t\tconst wc = wcv.webContents\n\t\t\t\t\tif (wc && !wc.isDestroyed()) wc.close?.()\n\t\t\t\t}\n\t\t\t\tconst nativeView = {\n\t\t\t\t\tref: { id: viewId } as NativeViewRef,\n\t\t\t\t\tsetBounds: (b: { x: number, y: number, width: number, height: number }) => wcv.setBounds(b),\n\t\t\t\t\t// Owned by the viewScope (AFTER detach, via view-handle's teardown order), so\n\t\t\t\t\t// an explicit dispose / window-close cascade / explicit-scope close\n\t\t\t\t\t// destroys the wc, not just detaches it.\n\t\t\t\t\tdestroy: closeNativeWc,\n\t\t\t\t\t// Handle-accessor pass-throughs: the native view's WebContents +\n\t\t\t\t\t// screenshot, so a caller can recover them from the handle without\n\t\t\t\t\t// re-deriving the WCV from the window's content view.\n\t\t\t\t\twebContents: wcv.webContents,\n\t\t\t\t\tcapturePage: () => {\n\t\t\t\t\t\t// `wc &&` guard (same idiom as closeNativeWc above): after teardown\n\t\t\t\t\t\t// Electron may have nulled `wcv.webContents`, so re-reading + calling\n\t\t\t\t\t\t// `.capturePage()` would throw a raw TypeError. Reject cleanly instead.\n\t\t\t\t\t\tconst wc = wcv.webContents as unknown as { isDestroyed?(): boolean, capturePage(): Promise<unknown> } | null\n\t\t\t\t\t\tif (!wc || wc.isDestroyed?.()) {\n\t\t\t\t\t\t\treturn Promise.reject(new Error('ViewHandle.capturePage: native view destroyed'))\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn wc.capturePage()\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t\t// keepAlive「opt-in helper:runtime.view({ keepAlive })」: opt-in LRU group, only when configured. Views sharing\n\t\t\t\t// the same `max` form one group keyed `lru:${max}`.\n\t\t\t\t//\n\t\t\t\t// Validate `max`. A negative `max` would make the eviction\n\t\t\t\t// `while (hidden.length > max)` loop evict every hidden view (and a\n\t\t\t\t// fractional / NaN max is meaningless). A `max` that is not a finite\n\t\t\t\t// integer >= 0 is NOT keep-alive-managed — the view is treated as if no\n\t\t\t\t// keepAlive was passed (skip the group entirely), warned once. This is\n\t\t\t\t// safer than silently clamping (which would change LRU semantics).\n\t\t\t\tconst keepAlive = opts.keepAlive\n\t\t\t\tconst keepAliveMaxValid = keepAlive\n\t\t\t\t\t? Number.isInteger(keepAlive.max) && keepAlive.max >= 0\n\t\t\t\t\t: false\n\t\t\t\tif (keepAlive && !keepAliveMaxValid) {\n\t\t\t\t\tthis.warnInvalidKeepAliveMax(keepAlive.max)\n\t\t\t\t}\n\t\t\t\tconst groupKey = keepAlive && keepAliveMaxValid ? `lru:${keepAlive.max}` : null\n\t\t\t\tconst keepAliveGroup = (): { hidden: string[], handles: Map<string, DeckViewHandle> } => {\n\t\t\t\t\tlet g = this.keepAliveGroups.get(groupKey!)\n\t\t\t\t\tif (!g) {\n\t\t\t\t\t\tg = { hidden: [], handles: new Map() }\n\t\t\t\t\t\tthis.keepAliveGroups.set(groupKey!, g)\n\t\t\t\t\t}\n\t\t\t\t\treturn g\n\t\t\t\t}\n\t\t\t\t// Drop this view from its keepAlive group (hidden list + handles\n\t\t\t\t// map), deleting the group when it empties. IDEMPOTENT so it is safe to\n\t\t\t\t// fire from EVERY teardown path: the viewScope's onDispose (window-close\n\t\t\t\t// cascade OR explicit dispose OR LRU eviction → hostHandle.dispose →\n\t\t\t\t// viewScope.close) AND hostHandle.dispose directly (the never-placed view\n\t\t\t\t// has no viewScope, so onDispose never runs for it). A window-close\n\t\t\t\t// cascades the inner viewScope WITHOUT going through hostHandle.dispose,\n\t\t\t\t// so this MUST hang off the scope or the group would leak a dead handle.\n\t\t\t\tconst removeFromKeepAliveGroup = (id: string): void => {\n\t\t\t\t\tif (!groupKey) return\n\t\t\t\t\tconst group = this.keepAliveGroups.get(groupKey)\n\t\t\t\t\tif (!group) return\n\t\t\t\t\tgroup.handles.delete(id)\n\t\t\t\t\tconst i = group.hidden.indexOf(id)\n\t\t\t\t\tif (i >= 0) group.hidden.splice(i, 1)\n\t\t\t\t\tif (group.handles.size === 0) {\n\t\t\t\t\t\tthis.keepAliveGroups.delete(groupKey)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tconst inner = createViewHandle({\n\t\t\t\t\tnativeView,\n\t\t\t\t\tscope: displayScope,\n\t\t\t\t\t// Fire group cleanup on viewScope teardown (covers window-close,\n\t\t\t\t\t// explicit dispose, AND LRU eviction). Idempotent with the hostHandle\n\t\t\t\t\t// .dispose() call below.\n\t\t\t\t\tonDispose: () => removeFromKeepAliveGroup(viewId),\n\t\t\t\t})\n\t\t\t\t// Remember WHICH substrate this view was placed into, so\n\t\t\t\t// dispose() can unregister its WCV from that substrate's registry +\n\t\t\t\t// z-order (preventing a long-lived window from accumulating disposed\n\t\t\t\t// views). null until first placeIn → if never placed, nothing to\n\t\t\t\t// unregister.\n\t\t\t\tlet placedSubstrate: ViewSubstrate | null = null\n\t\t\t\t// slot-token minted by an anchored placeIn (undefined for un-anchored\n\t\t\t\t// placeIn). Captured so dispose() can revoke it from `slotTokens`.\n\t\t\t\tlet slotToken: string | undefined\n\t\t\t\t// One placeIn per host handle. A second placeIn THROWS (re-placement\n\t\t\t\t// is moveTo's job) — never overwrite the inner current/viewScope.\n\t\t\t\tlet placed = false\n\t\t\t\t// Mint + register + push a slot-token anchor for an anchored placement on\n\t\t\t\t// `controlWc`/`anchor`. Shared by placeIn (first placement) and moveTo\n\t\t\t\t// (re-anchor for the dest window). Sets the captured `slotToken`.\n\t\t\t\tconst mintSlotToken = (controlWc: MinimalWebContents, anchor: string, zone: number): void => {\n\t\t\t\t\t// The wire's Snapshot / LayoutSubscribe channels are\n\t\t\t\t\t// armed eagerly at framework start() (see bindWireTransport).\n\t\t\t\t\t// This call is kept for safety/idempotency — it is a no-op when the\n\t\t\t\t\t// channels are already armed.\n\t\t\t\t\tthis.ensureSlotChannelsArmed()\n\t\t\t\t\tconst authorizedWcId = controlWc.id\n\t\t\t\t\tconst token = randomUUID()\n\t\t\t\t\tslotToken = token\n\t\t\t\t\tvoid ++this.slotSeq\n\t\t\t\t\t// Grant carries the wc's CURRENT generation. `resend` re-reads it each\n\t\t\t\t\t// push so a layout-subscribe replay (which bumps the generation first)\n\t\t\t\t\t// re-issues the grant at the new generation.\n\t\t\t\t\tconst resend = (): void => {\n\t\t\t\t\t\tconst grant = {\n\t\t\t\t\t\t\tviewId,\n\t\t\t\t\t\t\tslotId: anchor,\n\t\t\t\t\t\t\tslotToken: token,\n\t\t\t\t\t\t\tgeneration: this.perWcGeneration.get(authorizedWcId) ?? 0,\n\t\t\t\t\t\t}\n\t\t\t\t\t\tcontrolWc.send(DeckChannel.SlotGrant, grant)\n\t\t\t\t\t}\n\t\t\t\t\tthis.slotTokens.set(token, {\n\t\t\t\t\t\tviewId,\n\t\t\t\t\t\tslotId: anchor,\n\t\t\t\t\t\tauthorizedWcId,\n\t\t\t\t\t\tzone,\n\t\t\t\t\t\tresend,\n\t\t\t\t\t\tapply: p => { hostHandle.applyPlacement(p as ViewPlacement) },\n\t\t\t\t\t})\n\t\t\t\t\tresend()\n\t\t\t\t}\n\t\t\t\t// Chainable host-API wrapper: placeIn resolves the target window's\n\t\t\t\t// per-window substrate, registers the native view, then delegates to\n\t\t\t\t// the inner ViewHandle. placeIn/applyPlacement both return the handle.\n\t\t\t\tconst hostHandle: DeckViewHandle = {\n\t\t\t\t\tplaceIn: (win, placeOpts) => {\n\t\t\t\t\t\t// Re-placement is disallowed at the host level too — guard\n\t\t\t\t\t\t// BEFORE any substrate registration so a rejected re-placeIn leaves\n\t\t\t\t\t\t// no partial state. moveTo() is the only migration path.\n\t\t\t\t\t\tif (placed) {\n\t\t\t\t\t\t\tthrow new Error('DeckViewHandle.placeIn: view already placed — use moveTo() to migrate')\n\t\t\t\t\t\t}\n\t\t\t\t\t\tconst targetWin = this.resolveWindowArg(win)\n\t\t\t\t\t\tconst controlWc = targetWin.webContents\n\t\t\t\t\t\tconst wc = controlWc as unknown as MinimalWebContents\n\t\t\t\t\t\tconst substrate = this.windowSubstrates.get(wc)\n\t\t\t\t\t\tif (!substrate) {\n\t\t\t\t\t\t\tthrow new Error('runtime.view().placeIn: window is not framework-tracked')\n\t\t\t\t\t\t}\n\t\t\t\t\t\tsubstrate.registerView(viewId, wcv)\n\t\t\t\t\t\tplacedSubstrate = substrate\n\t\t\t\t\t\tinner.placeIn(\n\t\t\t\t\t\t\t{ compositor: substrate.compositor, windowScope: substrate.windowScope },\n\t\t\t\t\t\t\t{ zone: placeOpts.zone },\n\t\t\t\t\t\t)\n\t\t\t\t\t\t// Mark placed the INSTANT the CORE placement\n\t\t\t\t\t\t// succeeds (registerView + inner.placeIn), BEFORE the best-effort slot\n\t\t\t\t\t\t// -grant mint/push below. The view IS placed now; if the grant `send`\n\t\t\t\t\t\t// throws, the handle must NOT stay re-placeable (a retry's public guard\n\t\t\t\t\t\t// would otherwise pass and overwrite placedSubstrate → corruption). A\n\t\t\t\t\t\t// failed slot-grant push is recoverable WITHOUT re-placing — the\n\t\t\t\t\t\t// renderer can re-subscribe (layout-subscribe replay) to get the grant.\n\t\t\t\t\t\tplaced = true\n\t\t\t\t\t\t// The viewScope (created by inner.placeIn) now owns\n\t\t\t\t\t\t// nativeView.destroy → wc.close at shutdown (windowScope→viewScope\n\t\t\t\t\t\t// cascade), so the rootScope guard is redundant. DISARM + drop it to\n\t\t\t\t\t\t// avoid double-handling + disposer accumulation on the long-lived\n\t\t\t\t\t\t// rootScope.\n\t\t\t\t\t\tdisarmRootClose()\n\t\t\t\t\t\t// slot-token (view-handle.md「slot-token 握手」): an anchored placeIn binds a DOM\n\t\t\t\t\t\t// slot in the control wc to this native view. Mint an unguessable\n\t\t\t\t\t\t// token, register it (authorized to that wc), and PUSH a slot-grant\n\t\t\t\t\t\t// so the renderer learns (viewId, slotId, slotToken). The token is\n\t\t\t\t\t\t// the only credential the renderer needs to drive `place`.\n\t\t\t\t\t\tconst anchor = placeOpts.anchor\n\t\t\t\t\t\tif (typeof anchor === 'string' && anchor.length > 0) {\n\t\t\t\t\t\t\tmintSlotToken(controlWc, anchor, placeOpts.zone ?? 0)\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn hostHandle\n\t\t\t\t\t},\n\t\t\t\t\tmoveTo: async (win, moveOpts) => {\n\t\t\t\t\t\t// Host-level orchestration around the inner (atomic) cross-window\n\t\t\t\t\t\t// move. The inner.moveTo does the native Compositor migration +\n\t\t\t\t\t\t// (rehome) Scope.adopt + rollback; the host only mutates its local\n\t\t\t\t\t\t// substrate/token bookkeeping AFTER the inner move resolves, so a\n\t\t\t\t\t\t// dest failure (inner rolls back to src) never corrupts host state.\n\t\t\t\t\t\tconst destWin = this.resolveWindowArg(win)\n\t\t\t\t\t\tconst destControlWc = destWin.webContents\n\t\t\t\t\t\tconst destWc = destControlWc as unknown as MinimalWebContents\n\t\t\t\t\t\tconst destSub = this.windowSubstrates.get(destWc)\n\t\t\t\t\t\tif (!destSub) {\n\t\t\t\t\t\t\tthrow new Error('runtime.view().moveTo: window is not framework-tracked')\n\t\t\t\t\t\t}\n\t\t\t\t\t\tconst srcSub = placedSubstrate\n\t\t\t\t\t\t// Register the WCV in the DEST substrate BEFORE the inner move: the\n\t\t\t\t\t\t// dest Compositor's host adapter resolves `ref.id → wcv` to do the\n\t\t\t\t\t\t// real `addChildView`, so an unregistered dest add is a silent no-op.\n\t\t\t\t\t\t// The SRC registration stays until the move succeeds (a rollback\n\t\t\t\t\t\t// re-mounts on src, which needs its WCV still resolvable).\n\t\t\t\t\t\tdestSub.registerView(viewId, wcv)\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t// Atomic native + lifetime migration. If this REJECTS, the inner\n\t\t\t\t\t\t\t// already rolled back native/scope state to src.\n\t\t\t\t\t\t\tawait inner.moveTo(\n\t\t\t\t\t\t\t\t{ compositor: destSub.compositor, windowScope: destSub.windowScope },\n\t\t\t\t\t\t\t\t{ zone: moveOpts.zone, rehome: moveOpts.rehome },\n\t\t\t\t\t\t\t)\n\t\t\t\t\t\t}\n\t\t\t\t\t\tcatch (e) {\n\t\t\t\t\t\t\t// Dest failed → inner rolled back to src. The dest Compositor\n\t\t\t\t\t\t\t// snapshots/rolls back its OWN tracked order, but a native\n\t\t\t\t\t\t\t// addChildView that throws MID-APPLY may have leaked the WCV into\n\t\t\t\t\t\t\t// the dest window's contentView before throwing (the substrate's\n\t\t\t\t\t\t\t// tracked `order` never recorded the failed add, so the\n\t\t\t\t\t\t\t// Compositor rollback can't remove it) — `cleanupDestOnMoveToFailure`\n\t\t\t\t\t\t\t// balances that leak with an explicit dest detach + registry\n\t\t\t\t\t\t\t// unregister, then rethrows the ORIGINAL `e` (never masked).\n\t\t\t\t\t\t\tcleanupDestOnMoveToFailure(destWin, destSub, wcv, viewId, e)\n\t\t\t\t\t\t}\n\t\t\t\t\t\t// inner.moveTo succeeded → move the substrate registration: drop\n\t\t\t\t\t\t// from src now that the view lives in dest. For a SAME-WINDOW move\n\t\t\t\t\t\t// (src === dest, only re-anchor/zone) the dest registration above IS\n\t\t\t\t\t\t// the src entry — unregistering it would erase the very entry we just\n\t\t\t\t\t\t// (re)registered, so a later dispose can't resolve viewId → wcv and\n\t\t\t\t\t\t// the native detach is silently skipped. Skip the unregister when the\n\t\t\t\t\t\t// substrate is unchanged.\n\t\t\t\t\t\tif (srcSub && srcSub !== destSub) {\n\t\t\t\t\t\t\tsrcSub.unregisterView(viewId)\n\t\t\t\t\t\t}\n\t\t\t\t\t\tplacedSubstrate = destSub\n\t\t\t\t\t\t// A successful move ALWAYS re-mounts the\n\t\t\t\t\t\t// view VISIBLE in dest, so it is no longer hidden/evictable in its\n\t\t\t\t\t\t// (window-independent `lru:${max}`) keepAlive group. If it was on the\n\t\t\t\t\t\t// group's `hidden` list (moved while hidden), drop it — otherwise a\n\t\t\t\t\t\t// stale hidden entry would let a later eviction dispose a now-visible\n\t\t\t\t\t\t// view (and skew the LRU order). Idempotent: a no-op when the view was\n\t\t\t\t\t\t// already visible (not in the list) or has no group.\n\t\t\t\t\t\tif (groupKey) this.dropViewFromKeepAliveHidden(groupKey, viewId)\n\t\t\t\t\t\t// Re-issue the slot-token anchor for the dest. Revoke the OLD token\n\t\t\t\t\t\t// first (a stale `place` from the src renderer drops), then mint a\n\t\t\t\t\t\t// fresh one bound to the dest control wc + dest slot.\n\t\t\t\t\t\tif (slotToken) {\n\t\t\t\t\t\t\tthis.slotTokens.delete(slotToken)\n\t\t\t\t\t\t\tslotToken = undefined\n\t\t\t\t\t\t}\n\t\t\t\t\t\tconst anchor = moveOpts.anchor\n\t\t\t\t\t\tif (typeof anchor === 'string' && anchor.length > 0) {\n\t\t\t\t\t\t\tmintSlotToken(destControlWc, anchor, moveOpts.zone ?? 0)\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t\tapplyPlacement: (p) => {\n\t\t\t\t\t\tinner.applyPlacement(p)\n\t\t\t\t\t\t// keepAlive「opt-in helper:runtime.view({ keepAlive })」: maintain this group's LRU of HIDDEN views.\n\t\t\t\t\t\tif (groupKey) {\n\t\t\t\t\t\t\tconst group = keepAliveGroup()\n\t\t\t\t\t\t\tconst dropFromHidden = (): void => {\n\t\t\t\t\t\t\t\tconst i = group.hidden.indexOf(viewId)\n\t\t\t\t\t\t\t\tif (i >= 0) group.hidden.splice(i, 1)\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif (p.visible) {\n\t\t\t\t\t\t\t\t// Recently used + currently visible: never evictable.\n\t\t\t\t\t\t\t\tdropFromHidden()\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\telse {\n\t\t\t\t\t\t\t\t// Append only on the VISIBLE→HIDDEN transition. A repeated\n\t\t\t\t\t\t\t\t// `visible:false` for an ALREADY-hidden view is a no-op for LRU\n\t\t\t\t\t\t\t\t// ordering — re-appending would move it to most-recently-hidden,\n\t\t\t\t\t\t\t\t// corrupting \"least-recently-VISIBLE\" order. Membership guard:\n\t\t\t\t\t\t\t\t// push only if not already in the hidden list.\n\t\t\t\t\t\t\t\tif (!group.hidden.includes(viewId)) {\n\t\t\t\t\t\t\t\t\tgroup.hidden.push(viewId)\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t// Over budget: evict the FRONT (least-recently-visible) hidden\n\t\t\t\t\t\t\t\t// view -> dispose it (its WebContents is destroyed). `keepAlive.max`\n\t\t\t\t\t\t\t\t// is a validated non-negative integer here (groupKey is null for an\n\t\t\t\t\t\t\t\t// invalid max, so this block never runs for one).\n\t\t\t\t\t\t\t\twhile (keepAlive && group.hidden.length > keepAlive.max) {\n\t\t\t\t\t\t\t\t\tconst victimId = group.hidden.shift()!\n\t\t\t\t\t\t\t\t\tconst victim = group.handles.get(victimId)\n\t\t\t\t\t\t\t\t\t// A fire-and-forget eviction: catch the victim's dispose\n\t\t\t\t\t\t\t\t\t// rejection (its native teardown may throw) and log it, so an\n\t\t\t\t\t\t\t\t\t// eviction never leaks an unhandled promise rejection.\n\t\t\t\t\t\t\t\t\tif (victim) {\n\t\t\t\t\t\t\t\t\t\tvoid victim.dispose().catch((e) => {\n\t\t\t\t\t\t\t\t\t\t\tconsole.error('[electron-deck] keepAlive eviction dispose failed:', e)\n\t\t\t\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn hostHandle\n\t\t\t\t\t},\n\t\t\t\t\t// Detach (inner, idempotent) THEN unregister from the\n\t\t\t\t\t// substrate so the disposed view leaves `wcvById` + `order`.\n\t\t\t\t\t// `unregisterView` is itself idempotent (Map.delete + guarded\n\t\t\t\t\t// splice), so a double-dispose is harmless.\n\t\t\t\t\tdispose: async () => {\n\t\t\t\t\t\tawait inner.dispose()\n\t\t\t\t\t\t// Dispose already closes the WC via closeNativeWc\n\t\t\t\t\t\t// below, so the rootScope guard is now redundant — disarm + drop it\n\t\t\t\t\t\t// (no double-close, and no disposer left on the long-lived rootScope).\n\t\t\t\t\t\tdisarmRootClose()\n\t\t\t\t\t\t// keepAlive「保活寿命归 Scope、淘汰策略归 host」: catch-all destroy for a NEVER-PLACED view (no\n\t\t\t\t\t\t// viewScope ran the destroy own). Guarded -> a no-op when the\n\t\t\t\t\t\t// viewScope already closed the wc (never double-closed).\n\t\t\t\t\t\tcloseNativeWc()\n\t\t\t\t\t\tplacedSubstrate?.unregisterView(viewId)\n\t\t\t\t\t\t// Revoke the slot token so a stale `place` after dispose drops.\n\t\t\t\t\t\tif (slotToken) this.slotTokens.delete(slotToken)\n\t\t\t\t\t\t// keepAlive「opt-in helper:runtime.view({ keepAlive })」: drop this view from its LRU group. Idempotent\n\t\t\t\t\t\t// + redundant-but-safe for placed views (the viewScope's onDispose\n\t\t\t\t\t\t// already ran it on inner.dispose above); REQUIRED for a never-placed\n\t\t\t\t\t\t// keepAlive view whose viewScope never existed (no onDispose fired).\n\t\t\t\t\t\tremoveFromKeepAliveGroup(viewId)\n\t\t\t\t\t},\n\t\t\t\t\t// Additive handle accessors — delegate to the inner ViewHandle, which\n\t\t\t\t\t// owns the native WebContentsView, the live-bounds tracking, and the\n\t\t\t\t\t// capturePage pass-through.\n\t\t\t\t\tget webContents(): WebContents {\n\t\t\t\t\t\treturn inner.webContents as WebContents\n\t\t\t\t\t},\n\t\t\t\t\tbounds: (): ViewBounds | null => inner.bounds() as ViewBounds | null,\n\t\t\t\t\tcapturePage: (): Promise<NativeImage> => inner.capturePage() as Promise<NativeImage>,\n\t\t\t\t}\n\t\t\t\t// keepAlive「opt-in helper:runtime.view({ keepAlive })」: register this handle so its group can evict/dispose it.\n\t\t\t\tif (groupKey) keepAliveGroup().handles.set(viewId, hostHandle)\n\t\t\t\t// Bind the display lifetime to an EXPLICIT session scope only.\n\t\t\t\t// When a DeckSession was passed, `displayScope` is its internal child of\n\t\t\t\t// rootScope; closing the session (→ scope.close()) OR app shutdown\n\t\t\t\t// cascading detaches + unregisters the view AND closes its native\n\t\t\t\t// WebContents. Exclude the rootScope default: rootScope lives the whole\n\t\t\t\t// app, so a self-dispose there would be a pointless per-view disposer that\n\t\t\t\t// accumulates on the root for the process lifetime.\n\t\t\t\tif (displayScope !== this.rootScope) {\n\t\t\t\t\t// RETURN the dispose promise so Scope.own awaits it — the\n\t\t\t\t\t// session's close() then fences the WebContents close (it does not\n\t\t\t\t\t// resolve until dispose settles). CATCH rejection so a dispose failure\n\t\t\t\t\t// is logged, not an unhandled promise rejection.\n\t\t\t\t\tdisplayScope.own(() => hostHandle.dispose().catch((e) => {\n\t\t\t\t\t\tconsole.error('[electron-deck] view session-scope dispose failed:', e)\n\t\t\t\t\t}))\n\t\t\t\t}\n\t\t\t\treturn hostHandle\n\t\t\t},\n\t\t\tscopes: {\n\t\t\t\t// Mint an opaque DeckSession. Internally a child of the app root,\n\t\t\t\t// so disposing it (→ scope.close()) OR app shutdown (rootScope.close\n\t\t\t\t// cascade) tears down every view bound to it. The returned handle\n\t\t\t\t// exposes ONLY dispose() — the internal Scope's adopt/child/reset\n\t\t\t\t// escape surface is never leaked. Tracked in the provenance WeakMap so\n\t\t\t\t// `runtime.view` can resolve it (and reject a foreign/raw Scope).\n\t\t\t\tcreate: (): DeckSession => {\n\t\t\t\t\tconst scope = this.rootScope.child()\n\t\t\t\t\tconst session: DeckSession = {\n\t\t\t\t\t\treset: () => scope.reset(),\n\t\t\t\t\t\tdispose: () => scope.close(),\n\t\t\t\t\t}\n\t\t\t\t\tthis.sessions.set(session, scope)\n\t\t\t\t\treturn session\n\t\t\t\t},\n\t\t\t},\n\t\t\tgrants: {\n\t\t\t\tissue: (controlWc, opts): Disposable => {\n\t\t\t\t\tconst wc = controlWc as unknown as MinimalWebContents\n\t\t\t\t\tconst rec = this.wcRecords.get(wc)\n\t\t\t\t\tif (!rec) {\n\t\t\t\t\t\t// Cannot grant an untrusted sender — there is no wcScope to bind\n\t\t\t\t\t\t// the grant's lifetime to (wc.id-reuse safety REQUIRES the grant\n\t\t\t\t\t\t// die with the wc). Refuse rather than mint an unrevocable grant.\n\t\t\t\t\t\tthrow new Error('runtime.grants.issue: webContents is not trusted (no wcScope to bind the grant to)')\n\t\t\t\t\t}\n\t\t\t\t\t// `targetScope` is optional + reserved (not consulted at\n\t\t\t\t\t// dispatch). When supplied it is a DeckSession; resolve it to the\n\t\t\t\t\t// internal Scope for storage, ignore an unresolvable/foreign one\n\t\t\t\t\t// (the grant gate doesn't read it yet).\n\t\t\t\t\tconst targetScope = opts.targetScope ? this.sessions.get(opts.targetScope) : undefined\n\t\t\t\t\treturn this.capability.issue({\n\t\t\t\t\t\tsenderId: wc.id,\n\t\t\t\t\t\tsenderScope: rec.wcScope,\n\t\t\t\t\t\ttargetScope,\n\t\t\t\t\t\tcommands: new Set(opts.commands),\n\t\t\t\t\t})\n\t\t\t\t},\n\t\t\t},\n\t\t\tlayout: {\n\t\t\t\t// Register a PRIVILEGED (`layout.*`) command into the\n\t\t\t\t// grant-gated ControlBus command table. A real webview→main invoke of\n\t\t\t\t// this name (after the wire's trust + main-frame gate) reaches the\n\t\t\t\t// handler ONLY when a live grant covers (senderId, name); otherwise\n\t\t\t\t// ControlBus.dispatch throws DECK_FORBIDDEN.\n\t\t\t\tcommand: (\n\t\t\t\t\tname: string,\n\t\t\t\t\thandler: (...args: JsonValue[]) => JsonValue | Promise<JsonValue>,\n\t\t\t\t): Disposable => {\n\t\t\t\t\t// ENFORCE the layout.* boundary (not just convention). A\n\t\t\t\t\t// privileged command name MUST start with `layout.` so it always\n\t\t\t\t\t// routes to the grant-gated ControlBus dispatch (isPrivilegedCommandName\n\t\t\t\t\t// forks `layout.*` there). Reject any other name at registration.\n\t\t\t\t\tif (!name.startsWith('layout.')) {\n\t\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t\t`runtime.layout.command: privileged command names must start with \"layout.\" (got: ${name})`,\n\t\t\t\t\t\t)\n\t\t\t\t\t}\n\t\t\t\t\treturn this.controlBus!.command(name, handler)\n\t\t\t\t},\n\t\t\t},\n\t\t\tcontext,\n\t\t\ton: <E extends keyof FrameworkEvents>(\n\t\t\t\tevent: E,\n\t\t\t\tlistener: (payload: FrameworkEvents[E]) => void,\n\t\t\t): Disposable => {\n\t\t\t\tlet set = fwListeners.get(event)\n\t\t\t\tconst isFirst = !set\n\t\t\t\tif (!set) {\n\t\t\t\t\tset = new Set()\n\t\t\t\t\tfwListeners.set(event, set)\n\t\t\t\t}\n\t\t\t\tconst cast = listener as unknown as (payload: unknown) => void\n\t\t\t\tset.add(cast)\n\t\t\t\t// bind 期间已发生的 baseline 事件\n\t\t\t\t// (window-created / load-failed)在第一个 listener 注册时\n\t\t\t\t// 一次性消费式 replay。后续 listener 注册时队列空,no-op。\n\t\t\t\t// 不再检查 inSetupPhase:host 在 ready 之后注册的 first listener\n\t\t\t\t// 仍可拿到尚未被消费的 baseline(如果队列非空)。\n\t\t\t\tif (isFirst) emitPendingFor(event)\n\t\t\t\tconst ref = set\n\t\t\t\treturn {\n\t\t\t\t\tdispose: () => {\n\t\t\t\t\t\tref.delete(cast)\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t},\n\t\t\tadd: d => registry.add(d),\n\t\t}\n\t\treturn runtime\n\t}\n}\n\nasync function runWithTimeout<T>(work: MaybePromise<T>, timeoutMs: number): Promise<T> {\n\tlet timer: ReturnType<typeof setTimeout> | undefined\n\tconst timeout = new Promise<never>((_, rej) => {\n\t\ttimer = setTimeout(() => rej(new Error(`timed out after ${timeoutMs}ms`)), timeoutMs)\n\t})\n\ttry {\n\t\treturn await Promise.race([Promise.resolve(work), timeout])\n\t}\n\tfinally {\n\t\tif (timer !== undefined) clearTimeout(timer)\n\t}\n}\n","import { UndeclaredHostEventError } from './errors.js'\nimport { isHostEvent } from './events.js'\nimport type { MinimalElectron } from './internal/electron-types.js'\nimport type { MinimalIpcMain } from './internal/wire-transport.js'\nimport { DeckApp } from './internal/deck-app.js'\nimport type { DeckAppOptions } from './internal/deck-app.js'\nimport type { DeckConfig, DeckOptions, HostEvent, JsonValue, Runtime, ToolbarContribution } from './types.js'\n\n/**\n * `electronDeck(config, options?)` 是 framework 唯一入口(见 README §3)。\n *\n * - Invalid config → reject `TypeError`,phase 不前进\n * - Valid config → 装配 runtime + 调 `config.setup(runtime)` await 完成 →\n * resolve。不接 Electron 时,resolve 后 framework 仍持有运行时;\n * 接 Electron app lifecycle 后,由 Electron event loop 撑住进程,\n * `electronDeck()` 同样 resolve(host 的 main 文件不需 await 阻塞)。\n *\n * backend host(如 devtools)走 `electronDeck({ backend })`——backend 是\n * {@link DeckConfig} 的字段,不再需要空 `{}` + options。\n *\n * `options` 是测试 / 非 Electron 环境用的注入点(见 {@link DeckOptions})。\n * 生产路径不传 options:framework `await import('electron')` 取真 `ipcMain` /\n * `BrowserWindow` / `WebContentsView`。\n */\nexport async function electronDeck(config: DeckConfig, options?: DeckOptions): Promise<void> {\n\t// Validate config BEFORE attempting electron resolution — invalid configs\n\t// must reject with TypeError regardless of environment. Electron load\n\t// failure is reported separately, only for\n\t// configs that would otherwise be acceptable.\n\tvalidateConfig(config)\n\tconst resolved = await resolveAppOptions(options)\n\tif (config.backend) {\n\t\t(resolved as Mutable<DeckAppOptions>).backend = config.backend\n\t}\n\tconst app = new DeckApp(config, resolved)\n\tawait app.start()\n}\n\n/**\n * Synchronous launch handle for `electronDeck`.\n *\n * `electronDeck(config)` is `async` and internally `await app.start()` →\n * `await app.whenReady()`. A host ESM main entry doing `await electronDeck(config)`\n * SUSPENDS module evaluation on the whenReady gate — but Electron's `ready` only\n * fires once module evaluation finishes, so the gate never resolves: HARD DEADLOCK.\n *\n * `startElectronDeck` returns a plain handle SYNCHRONOUSLY (NOT a thenable), so a\n * host's top-level `await handle.ready` never sits on the whenReady gate. Assembly\n * still runs STRICTLY AFTER `app.whenReady()` resolves (gating intact inside\n * `app.start()`); `handle.ready` resolves with the {@link Runtime}; `handle.dispose()`\n * tears the app down even if called before the in-flight start finished.\n *\n * Invalid config throws a `TypeError` synchronously (matching `electronDeck`'s\n * validate-first contract) — the error surfaces, never silently deadlocks.\n *\n * `@experimental` No production consumer yet — the only caller in this repo is\n * `packages/devtools/spike/popout/harness.mjs`; devtools' real entry\n * (`launch.ts`) uses `electronDeck({ backend })` instead. Contract may change\n * until a second real consumer adopts it.\n */\nexport function startElectronDeck(\n\tconfig: DeckConfig,\n\toptions?: DeckOptions,\n): { ready: Promise<Runtime>, dispose(): Promise<void> } {\n\t// Validate config SYNCHRONOUSLY (before returning the handle) — invalid configs\n\t// surface a TypeError at the call, never a silent deadlock.\n\tvalidateConfig(config)\n\n\tlet app: DeckApp | null = null\n\tconst startPromise: Promise<DeckApp> = (async () => {\n\t\tconst resolved = await resolveAppOptions(options)\n\t\tif (config.backend) {\n\t\t\t(resolved as Mutable<DeckAppOptions>).backend = config.backend\n\t\t}\n\t\tapp = new DeckApp(config, resolved)\n\t\t// `app.start()` internally `await app.whenReady()` THEN assembles — gating\n\t\t// stays intact: no window is constructed before whenReady resolves.\n\t\tawait app.start()\n\t\treturn app\n\t})()\n\n\tconst ready = startPromise.then(a => a.runtime)\n\t// Mark `ready` as handled so a fire-and-forget caller (who never reads `ready`,\n\t// e.g. `startElectronDeck(...)` then `dispose()`) does NOT trigger an\n\t// unhandledRejection if startup fails — which under strict Electron handling can\n\t// terminate the process. The caller's own `await handle.ready`\n\t// still observes the rejection; this extra no-op handler only suppresses the\n\t// \"unhandled\" classification, it does not swallow the error for the caller.\n\tvoid ready.catch(() => {})\n\n\treturn {\n\t\tready,\n\t\tasync dispose(): Promise<void> {\n\t\t\t// dispose-before-ready safety: AWAIT the in-flight start (swallow its error\n\t\t\t// so a failed start still lets dispose clean up), THEN shut down — no race\n\t\t\t// with the rootScope teardown that start() set up. `shutdown()` is\n\t\t\t// idempotent (a second call is a no-op).\n\t\t\tconst started = await startPromise.catch(() => null)\n\t\t\tif (started) {\n\t\t\t\tawait started.shutdown()\n\t\t\t}\n\t\t\telse if (app) {\n\t\t\t\t// start threw post-construction → still tear down the partially-built app.\n\t\t\t\tawait app.shutdown()\n\t\t\t}\n\t\t},\n\t}\n}\n\n/**\n * 把公共 `DeckOptions` 解析成 internal {@link DeckAppOptions} —— 在\n * 显式注入不足时 lazy `import('electron')` 兜底。\n *\n * 决策 matrix:\n * - `electron` + `ipcMain` 都显式注入 → 不 lazy import(测试 / production override)\n * - 任一缺失 → `await import('electron')`,用 imported module 兜底缺失字段\n *\n * vitest 下 `await import('electron')` 解析到安装包 entry stub(导出可执行路径\n * 字符串),所以 `ipcMain` / `BrowserWindow` 都是 undefined。我们检测到关键\n * 字段缺失会显式 reject,提示注入 options。\n */\nasync function resolveAppOptions(opts?: DeckOptions): Promise<DeckAppOptions> {\n\tif (opts?.electron && opts?.ipcMain) {\n\t\treturn buildAppOptions(opts.electron, opts.ipcMain, opts)\n\t}\n\n\tlet imported: unknown\n\ttry {\n\t\timported = await import('electron')\n\t}\n\tcatch (e) {\n\t\tthrow new Error(\n\t\t\t'electronDeck(): unable to load electron — pass options.electron / options.ipcMain '\n\t\t\t+ 'for non-Electron environments. Underlying: ' + String(e),\n\t\t\t{ cause: e },\n\t\t)\n\t}\n\n\tconst m = imported as { ipcMain?: unknown, BrowserWindow?: unknown, WebContentsView?: unknown }\n\tconst resolvedElectron = opts?.electron ?? (asMinimalElectron(m))\n\tconst resolvedIpcMain = opts?.ipcMain ?? (m.ipcMain as MinimalIpcMain | undefined)\n\n\tif (!resolvedIpcMain || !resolvedElectron.BrowserWindow || !resolvedElectron.WebContentsView) {\n\t\tthrow new Error(\n\t\t\t'electronDeck(): loaded \"electron\" but it does not expose the main-process surface '\n\t\t\t+ '(ipcMain / BrowserWindow / WebContentsView). This typically means you are running '\n\t\t\t+ 'outside an Electron main process (e.g. vitest under node). Pass options.electron '\n\t\t\t+ 'and options.ipcMain to inject a fake.',\n\t\t)\n\t}\n\n\treturn buildAppOptions(resolvedElectron, resolvedIpcMain, opts)\n}\n\nfunction asMinimalElectron(m: { BrowserWindow?: unknown, WebContentsView?: unknown }): MinimalElectron {\n\t// We do not validate shape here — `resolveAppOptions` does the BrowserWindow /\n\t// WebContentsView presence check before returning. Cast is intentional.\n\treturn m as unknown as MinimalElectron\n}\n\nfunction buildAppOptions(\n\telectron: MinimalElectron,\n\tipcMain: MinimalIpcMain,\n\topts: DeckOptions | undefined,\n): DeckAppOptions {\n\tconst wireTransport: DeckAppOptions['wireTransport'] = { ipcMain }\n\tconst out: DeckAppOptions = { electron, wireTransport }\n\tif (opts?.trustedWebContents) {\n\t\t(out.wireTransport as Mutable<NonNullable<DeckAppOptions['wireTransport']>>)\n\t\t\t.trustedWebContents = opts.trustedWebContents\n\t}\n\tif (opts?.senderPolicy) {\n\t\t(out.wireTransport as Mutable<NonNullable<DeckAppOptions['wireTransport']>>)\n\t\t\t.senderPolicy = opts.senderPolicy\n\t}\n\treturn out\n}\n\ntype Mutable<T> = { -readonly [K in keyof T]: T[K] }\n\n/**\n * Pure validation:不依赖 Electron / IPC / 任何 framework state,可单独 unit\n * test。每一条检查对应 doc §3.3 字段约束。\n *\n * 验证语义保证:通过 validateConfig 的 config 不会在 framework Bind 阶段被拒。\n * 含 HostEvent 来源、source 互斥、map vs array 等所有\"shape 就绪\"检查。\n *\n * @internal exported for tests\n */\nexport function validateConfig(config: DeckConfig): void {\n\tif (config === null || typeof config !== 'object') {\n\t\tthrow new TypeError('electronDeck(config): config must be an object')\n\t}\n\n\tvalidateBackendField(config.backend)\n\n\tif (config.simulatorApis !== undefined) {\n\t\tvalidateHandlerMap('simulatorApis', config.simulatorApis)\n\t}\n\tif (config.hostServices !== undefined) {\n\t\tvalidateHandlerMap('hostServices', config.hostServices)\n\t}\n\tif (config.events !== undefined) {\n\t\tvalidateEventsField(config.events)\n\t}\n\tif (config.toolbar !== undefined) {\n\t\tvalidateToolbarField(config.toolbar)\n\t}\n}\n\nfunction validateBackendField(backend: DeckConfig['backend']): void {\n\tif (backend === undefined) return\n\tconst candidate = backend as { assemble?: unknown }\n\tif (candidate === null || typeof candidate !== 'object' || typeof candidate.assemble !== 'function') {\n\t\tthrow new TypeError('config.backend must be a RuntimeBackend (an object with an assemble() function)')\n\t}\n}\n\nfunction validateHandlerMap(fieldName: string, value: unknown): void {\n\tif (value === null || typeof value !== 'object' || Array.isArray(value)) {\n\t\tthrow new TypeError(`${fieldName} must be an object of handlers`)\n\t}\n\tfor (const [name, handler] of Object.entries(value as Record<string, unknown>)) {\n\t\tif (typeof handler !== 'function') {\n\t\t\tthrow new TypeError(`${fieldName}[\"${name}\"] must be a function`)\n\t\t}\n\t}\n}\n\nfunction validateEventsField(events: unknown): void {\n\tif (!Array.isArray(events)) {\n\t\tthrow new TypeError('events must be an array of HostEvent (from defineEvent())')\n\t}\n\tassertAllHostEvents(events)\n\tassertUniqueEventNames(events as readonly HostEvent<JsonValue>[])\n}\n\n/** duck-typed 事件形状会被拒绝——只接受 `defineEvent()` 产出的真实 HostEvent,避免 bind-time 才炸。 */\nfunction assertAllHostEvents(events: readonly unknown[]): void {\n\tfor (const ev of events) {\n\t\tif (!isHostEvent(ev)) {\n\t\t\tthrow new TypeError(\n\t\t\t\t'events: every entry must be a HostEvent produced by defineEvent() — '\n\t\t\t\t+ 'duck-typed shapes are rejected to avoid bind-time failures',\n\t\t\t)\n\t\t}\n\t}\n}\n\nfunction assertUniqueEventNames(events: readonly HostEvent<JsonValue>[]): void {\n\tconst names = new Set<string>()\n\tfor (const ev of events) {\n\t\tif (names.has(ev.name)) {\n\t\t\tthrow new Error(`events: duplicate HostEvent name \"${ev.name}\"`)\n\t\t}\n\t\tnames.add(ev.name)\n\t}\n}\n\nfunction validateToolbarField(toolbar: ToolbarContribution): void {\n\tassertToolbarSourceShape(toolbar.source)\n\tif (typeof toolbar.preloadPath !== 'string' || toolbar.preloadPath.length === 0) {\n\t\tthrow new TypeError('toolbar.preloadPath is required and must be a non-empty string')\n\t}\n\tif (typeof toolbar.height !== 'number' || !Number.isFinite(toolbar.height) || toolbar.height <= 0) {\n\t\tthrow new TypeError('toolbar.height is required and must be a positive finite number')\n\t}\n}\n\nfunction assertToolbarSourceShape(source: unknown): void {\n\tif (source === null || typeof source !== 'object') {\n\t\tthrow new TypeError('toolbar.source must be { url } or { file }')\n\t}\n\tconst hasUrl = 'url' in source && typeof (source as { url?: unknown }).url === 'string'\n\tconst hasFile = 'file' in source && typeof (source as { file?: unknown }).file === 'string'\n\tif (hasUrl && hasFile) {\n\t\tthrow new TypeError('toolbar.source must be either { url } or { file }, not both')\n\t}\n\tif (!hasUrl && !hasFile) {\n\t\tthrow new TypeError('toolbar.source must be { url } or { file }')\n\t}\n}\n\n/**\n * publish-time guard:HostEvent 未在 `config.events` 中显式列出时阻止 publish。\n * 避免 module-load-order 隐式注册。\n *\n * @internal exported for tests\n */\nexport function assertEventDeclared(\n\tdeclared: ReadonlySet<string>,\n\teventName: string,\n): void {\n\tif (!declared.has(eventName)) {\n\t\tthrow new UndeclaredHostEventError(eventName)\n\t}\n}\n"],"mappings":"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"}
@@ -32,6 +32,11 @@ export declare function electronDeck(config: DeckConfig, options?: DeckOptions):
32
32
  *
33
33
  * Invalid config throws a `TypeError` synchronously (matching `electronDeck`'s
34
34
  * validate-first contract) — the error surfaces, never silently deadlocks.
35
+ *
36
+ * `@experimental` No production consumer yet — the only caller in this repo is
37
+ * `packages/devtools/spike/popout/harness.mjs`; devtools' real entry
38
+ * (`launch.ts`) uses `electronDeck({ backend })` instead. Contract may change
39
+ * until a second real consumer adopts it.
35
40
  */
36
41
  export declare function startElectronDeck(config: DeckConfig, options?: DeckOptions): {
37
42
  ready: Promise<Runtime>;
@@ -1 +1 @@
1
- {"version":3,"file":"electron-deck.d.ts","sourceRoot":"","sources":["../src/electron-deck.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,UAAU,EAAE,WAAW,EAAE,OAAO,EAAE,MAAM,YAAY,CAAA;AAElE;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,YAAY,CAAC,MAAM,EAAE,UAAU,EAAE,OAAO,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAY3F;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,iBAAiB,CAChC,MAAM,EAAE,UAAU,EAClB,OAAO,CAAC,EAAE,WAAW,GACnB;IAAE,KAAK,EAAE,OAAO,CAAC,OAAO,CAAC,CAAC;IAAC,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;CAAE,CA4CvD;AAyED;;;;;;;;GAQG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,UAAU,GAAG,IAAI,CA6DvD;AAaD;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAClC,QAAQ,EAAE,WAAW,CAAC,MAAM,CAAC,EAC7B,SAAS,EAAE,MAAM,GACf,IAAI,CAIN"}
1
+ {"version":3,"file":"electron-deck.d.ts","sourceRoot":"","sources":["../src/electron-deck.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,UAAU,EAAE,WAAW,EAAwB,OAAO,EAAuB,MAAM,YAAY,CAAA;AAE7G;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,YAAY,CAAC,MAAM,EAAE,UAAU,EAAE,OAAO,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAY3F;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,iBAAiB,CAChC,MAAM,EAAE,UAAU,EAClB,OAAO,CAAC,EAAE,WAAW,GACnB;IAAE,KAAK,EAAE,OAAO,CAAC,OAAO,CAAC,CAAC;IAAC,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;CAAE,CA4CvD;AAyED;;;;;;;;GAQG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,UAAU,GAAG,IAAI,CAmBvD;AA2ED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAClC,QAAQ,EAAE,WAAW,CAAC,MAAM,CAAC,EAC7B,SAAS,EAAE,MAAM,GACf,IAAI,CAIN"}
package/dist/events.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import type { HostEvent, JsonValue } from './types.js';
2
+ /** @experimental No production consumer yet — see the note on {@link HostEvent}. */
2
3
  export declare function defineEvent<P extends JsonValue>(name: string): HostEvent<P>;
3
4
  /**
4
5
  * 校验 `ev` 是否真的来自 `defineEvent()`。判定方式是 WeakMap 身份,而非