@dimina-kit/electron-deck 0.1.0-dev.20260716214527 → 0.1.0-dev.20260718085557
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.
- package/dist/{electron-deck-Bnm8LqQh.js → electron-deck-BZ9UG2eL.js} +2 -2
- package/dist/{electron-deck-Bnm8LqQh.js.map → electron-deck-BZ9UG2eL.js.map} +1 -1
- package/dist/host/index.js +1 -1
- package/dist/index.js +1 -1
- package/dist/main/disposable.d.ts +52 -3
- package/dist/main/disposable.d.ts.map +1 -1
- package/dist/main/index.d.ts +1 -1
- package/dist/main/index.d.ts.map +1 -1
- package/dist/main/index.js +2 -2
- package/dist/{view-handle-zav25k3-.js → view-handle-guwncgRV.js} +67 -5
- package/dist/view-handle-guwncgRV.js.map +1 -0
- package/package.json +2 -2
- package/dist/view-handle-zav25k3-.js.map +0 -1
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"view-handle-zav25k3-.js","names":[],"sources":["../src/main/disposable.ts","../src/main/logger.ts","../src/main/scope.ts","../src/main/compositor.ts","../src/main/view-handle.ts"],"sourcesContent":["export interface Disposable {\n dispose(): void | Promise<void>\n}\n\nexport type DisposeFn = () => void | Promise<void>\n\nexport function toDisposable(fn: DisposeFn): Disposable {\n let done = false\n return {\n dispose() {\n if (done) return\n done = true\n return fn()\n },\n }\n}\n\ninterface Entry {\n fn: DisposeFn\n released: boolean\n}\n\nexport class DisposableRegistry implements Disposable {\n private entries: Entry[] = []\n private _disposed = false\n\n /**\n * Number of live entries. A wrapper's `dispose` splices its entry out and\n * `disposeAll` clears the array, so `entries.length` is the live count.\n */\n get size(): number {\n return this.entries.length\n }\n\n add(d: Disposable | DisposeFn): Disposable {\n if (this._disposed) {\n throw new Error('cannot add to disposed registry')\n }\n\n const fn: DisposeFn = typeof d === 'function' ? d : () => d.dispose()\n const entry: Entry = { fn, released: false }\n this.entries.push(entry)\n return {\n dispose: () => {\n if (entry.released) return\n entry.released = true\n const i = this.entries.indexOf(entry)\n if (i >= 0) this.entries.splice(i, 1)\n return fn()\n },\n }\n }\n\n async disposeAll(): Promise<void> {\n if (this._disposed) return\n this._disposed = true\n\n const items = this.entries.slice().reverse()\n this.entries = []\n\n const errors: unknown[] = []\n for (const entry of items) {\n if (entry.released) continue\n entry.released = true\n try {\n await entry.fn()\n } catch (e) {\n errors.push(e)\n }\n }\n\n if (errors.length > 0) {\n throw new AggregateError(errors, 'DisposableRegistry encountered errors during disposeAll')\n }\n }\n\n dispose(): Promise<void> {\n return this.disposeAll()\n }\n}\n","type LogLevel = 'debug' | 'info' | 'warn' | 'error'\n\nconst LEVEL_PRIORITY: Record<LogLevel, number> = {\n debug: 0,\n info: 1,\n warn: 2,\n error: 3,\n}\n\nconst LEVEL_PREFIX: Record<LogLevel, string> = {\n debug: '[DEBUG]',\n info: '[INFO]',\n warn: '[WARN]',\n error: '[ERROR]',\n}\n\nlet currentLevel: LogLevel = 'debug'\n\nfunction shouldLog(level: LogLevel): boolean {\n return LEVEL_PRIORITY[level] >= LEVEL_PRIORITY[currentLevel]\n}\n\nfunction formatMessage(level: LogLevel, tag: string, message: string): string {\n const ts = new Date().toISOString()\n return `${ts} ${LEVEL_PREFIX[level]} [${tag}] ${message}`\n}\n\nexport interface Logger {\n debug(message: string, ...args: unknown[]): void\n info(message: string, ...args: unknown[]): void\n warn(message: string, ...args: unknown[]): void\n error(message: string, ...args: unknown[]): void\n}\n\n/**\n * Create a tagged logger for a specific module.\n * All output goes to console with structured prefix.\n */\nexport function createLogger(tag: string): Logger {\n return {\n debug(message: string, ...args: unknown[]) {\n if (shouldLog('debug')) console.debug(formatMessage('debug', tag, message), ...args)\n },\n info(message: string, ...args: unknown[]) {\n if (shouldLog('info')) console.info(formatMessage('info', tag, message), ...args)\n },\n warn(message: string, ...args: unknown[]) {\n if (shouldLog('warn')) console.warn(formatMessage('warn', tag, message), ...args)\n },\n error(message: string, ...args: unknown[]) {\n if (shouldLog('error')) console.error(formatMessage('error', tag, message), ...args)\n },\n }\n}\n\nexport function setLogLevel(level: LogLevel): void {\n currentLevel = level\n}\n","/**\n * `Scope` — engine-agnostic nested-lifetime primitive, generalizing the\n * Connection/Disposable semantics in this package (foundation.md §4).\n *\n * A `Scope` owns a single \"lifetime segment\". Resources bound via `own()` and\n * sub-scopes created via `child()` are released together — both on `reset()`\n * (soft reuse: dispose the segment, open a fresh one, stay alive) and `close()`\n * (terminal: dispose everything, die). Teardown is LIFO and, crucially,\n * `reset()`/`close()` are COMPLETION FENCES: their Promise resolves (and the\n * `'reset'`/`'closed'` listeners fire) only AFTER the underlying async\n * disposeAll has fully finished — unlike `connection.ts`, which fires the\n * teardown and forgets (`void ...disposeAll()`).\n *\n * Cross-layer LIFO: a scope's teardown disposes its child sub-scopes (deepest\n * first, recursively) BEFORE its own directly-owned resources, so a grandchild\n * tears down before a child, which tears down before the root.\n */\nimport { DisposableRegistry, type Disposable, type DisposeFn } from './disposable.js'\nimport { createLogger } from './logger.js'\n\nconst log = createLogger('scope')\n\nexport interface Scope {\n readonly alive: boolean\n /** Bind a resource to the current lifetime segment; released by both reset()\n * and close() (LIFO). The returned Disposable releases it early (once). After\n * the scope is closed, the resource is disposed immediately (leak protection)\n * and a no-op handle is returned. */\n own(d: Disposable | (() => void)): Disposable\n /** Create a sub-scope bound to the current segment: a parent reset()/close()\n * cascades into it. A child close() does not affect the parent. */\n child(): Scope\n /** Soft reuse: await LIFO disposeAll of the current segment (children first,\n * then owned resources — async disposers truly complete), THEN open a fresh\n * segment and fire 'reset'. Scope stays alive. */\n reset(): Promise<void>\n /** Terminal: await LIFO disposeAll of the segment, mark dead, fire 'closed'\n * once. Idempotent. */\n close(): Promise<void>\n /** Subscribe to a lifecycle event; returns an unsubscribe Disposable. */\n on(event: 'reset' | 'closed', cb: () => void): Disposable\n /**\n * Re-parent `child` from THIS scope's current segment onto `newParent`'s\n * current segment WITHOUT resetting or closing it. The child's own()ed\n * resources stay live and neither 'reset' nor 'closed' fire on it; only the\n * cascade ownership moves (who tears it down from now on).\n *\n * If `this` or `newParent` has a teardown in flight, adopt WAITS for that\n * fence (it does not throw), then re-reads/re-validates against the fresh\n * segment. Validation failures (dead this/newParent, non-direct-child, cycle)\n * reject. On every path the child stays attached to EXACTLY one segment.\n */\n adopt(child: Scope, newParent: Scope): Promise<void>\n}\n\n/**\n * Internal view of a scope, exposing the structural hooks `adopt` needs to\n * reach across scope instances (current segment, parent pointer maintenance,\n * child-removal subscriptions). Not part of the public `Scope` contract.\n */\ninterface ScopeInternal extends Scope {\n /** The live segment (children/resources). Re-read after any fence wait. */\n __currentSegment(): Segment\n /** Whether a teardown is in flight, and a Promise to await it (the fence). */\n __inFlight(): Promise<void> | null\n /** This scope's current parent + the parent segment it is attached to. */\n __parent(): { parent: ScopeInternal | null; owningSegment: Segment | null }\n /** Attach `child` into `seg` as a tracked child, (re)binding the\n * on('closed') removal hook and recording it. Sets child's parent pointer. */\n __attachChild(child: ScopeInternal, seg: Segment): void\n /** Detach `child`: splice it from `seg.children`, dispose+drop its removal\n * hook, clear its parent pointer. */\n __detachChild(child: ScopeInternal, seg: Segment): void\n /** Set this scope's parent pointer (used when (re)attaching). */\n __setParent(parent: ScopeInternal | null, owningSegment: Segment | null): void\n}\n\nfunction asInternal(s: Scope): ScopeInternal {\n return s as ScopeInternal\n}\n\n/** True if `maybeAncestor` is `node` or an ancestor of it (walking parents). */\nfunction isSelfOrAncestor(maybeAncestor: ScopeInternal, node: ScopeInternal): boolean {\n let cur: ScopeInternal | null = node\n while (cur) {\n if (cur === maybeAncestor) return true\n cur = cur.__parent().parent\n }\n return false\n}\n\ntype LifecycleEvent = 'reset' | 'closed'\n\nconst NOOP_DISPOSABLE: Disposable = { dispose() {} }\n\nfunction toDispose(d: Disposable | (() => void)): DisposeFn {\n return typeof d === 'function' ? d : () => d.dispose()\n}\n\n/**\n * Dispose a resource handed to a dead scope's `own()` — immediately, exactly\n * once, never delegating to a disposed segment (that would throw). Both sync\n * throws and async rejections are caught/logged so a late teardown can never\n * escape as an unhandledRejection in the main process.\n */\nfunction disposeLate(d: Disposable | (() => void)): void {\n try {\n const r = toDispose(d)()\n if (r && typeof (r as Promise<void>).then === 'function') {\n ;(r as Promise<void>).catch((e) => log.error('late own() async disposer rejected', e))\n }\n } catch (e) {\n log.error('late own() resource disposer threw', e)\n }\n}\n\n/**\n * One lifetime segment: a set of child sub-scopes plus a `DisposableRegistry`\n * of directly-owned resources. Teardown disposes children first (deepest-first\n * cascade) then owned resources, both LIFO, awaiting each so async disposers\n * truly finish before the fence resolves.\n */\ninterface Segment {\n /** Insertion-ordered children created in this segment. */\n children: Scope[]\n resources: DisposableRegistry\n}\n\nfunction newSegment(): Segment {\n return { children: [], resources: new DisposableRegistry() }\n}\n\nasync function disposeSegment(segment: Segment): Promise<void> {\n // Children first (LIFO), so a grandchild tears down before its child before\n // the parent's own resources. Each child.close() is itself a completion\n // fence, recursively awaiting its subtree.\n const children = segment.children.slice().reverse()\n segment.children = []\n for (const child of children) {\n try {\n await child.close()\n } catch (e) {\n log.error('child scope close threw during segment teardown', e)\n }\n }\n // Then the directly-owned resources (LIFO, async-aware via disposeAll).\n await segment.resources.disposeAll()\n}\n\nexport function createScope(): Scope {\n let segment = newSegment()\n let alive = true\n\n // Single-flight teardown state. At most one teardown (reset OR close) runs at\n // a time; concurrent callers join the in-flight Promise instead of launching\n // a second, overlapping disposeAll. This is what makes a parent's\n // `await child.close()` a TRUE wait: a second close() on an already-closing\n // child returns that child's in-flight Promise (which resolves only after its\n // disposer truly finishes) rather than early-returning on the `alive` guard.\n //\n // `inFlight` is the Promise of the teardown currently running (null when\n // idle). `inFlightKind` distinguishes a soft reset (the scope stays alive, so\n // a later close must still run) from a terminal close (fully absorbs repeats).\n let inFlight: Promise<void> | null = null\n let inFlightKind: 'reset' | 'close' | null = null\n\n const resetListeners = new Set<() => void>()\n const closedListeners = new Set<() => void>()\n\n // This scope's place in the tree: its unique parent and the parent segment it\n // is attached to. Maintained by child()/adopt() (and the parent's removal hook\n // on this scope's close). Used by adopt() to validate direct-child membership,\n // detect cycles (walking parents), and re-home the child atomically.\n let parent: ScopeInternal | null = null\n let owningSegment: Segment | null = null\n\n // For each child this scope owns, the Disposable that unsubscribes the\n // on('closed') removal hook we bound when attaching it. child() must retain\n // this subscription; adopt() requires it so a re-parent can\n // unbind the stale hook. Keyed by child scope.\n const childRemovers = new Map<ScopeInternal, Disposable>()\n\n function emit(ev: LifecycleEvent): void {\n const set = ev === 'reset' ? resetListeners : closedListeners\n // Isolate faults so one throwing listener can't block the rest.\n for (const cb of [...set]) {\n try {\n cb()\n } catch (e) {\n log.error(`listener for \"${ev}\" threw`, e)\n }\n }\n }\n\n // Start a soft-reset teardown and register it as the in-flight single-flight\n // Promise. Swap in a fresh segment synchronously (so concurrent own()/child()\n // land in the new one), THEN await the old segment's full teardown before\n // firing 'reset'. The scope stays alive.\n function runReset(): Promise<void> {\n const old = segment\n segment = newSegment()\n inFlightKind = 'reset'\n const p = (async () => {\n // The disposer may throw (a disposeAll AggregateError); the error must\n // still reach the first caller, but the in-flight state MUST be cleared\n // and 'reset' fired regardless — otherwise the scope wedges permanently\n // (stale rejection forever, new segment leaked). finally guarantees both.\n try {\n await disposeSegment(old)\n } finally {\n // A concurrent close() may have upgraded this teardown to a terminal\n // close while we were awaiting; if so it owns clearing inFlight/emitting.\n if (inFlightKind === 'reset') {\n inFlight = null\n inFlightKind = null\n emit('reset')\n }\n }\n })()\n inFlight = p\n return p\n }\n\n // Start a terminal close teardown and register it as in-flight. Mark dead\n // synchronously so concurrent own() hits the leak-protection path, swap in a\n // fresh (poisoned) segment, then await the old segment's full teardown before\n // firing 'closed'.\n function runClose(): Promise<void> {\n alive = false\n const old = segment\n segment = newSegment()\n inFlightKind = 'close'\n const p = (async () => {\n // As in runReset: the disposer may throw (KA-5 lets a CommitError reach\n // the caller), but 'closed' must still fire and the in-flight state must\n // clear so a later close() is idempotent rather than a stale rejection.\n try {\n await disposeSegment(old)\n } finally {\n inFlight = null\n inFlightKind = null\n emit('closed')\n }\n })()\n inFlight = p\n return p\n }\n\n // Attach `child` into `seg` as a tracked child of THIS scope: push it onto the\n // segment, bind an on('closed') hook that splices it out (and drops its\n // remover) when the child dies, record that hook so adopt() can unbind it, and\n // set the child's parent pointer to this scope. Keeps the child in EXACTLY one\n // segment.\n function attachChild(child: ScopeInternal, seg: Segment): void {\n seg.children.push(child)\n const remover = child.on('closed', () => {\n const i = seg.children.indexOf(child)\n if (i >= 0) seg.children.splice(i, 1)\n childRemovers.delete(child)\n })\n childRemovers.set(child, remover)\n child.__setParent(scope as ScopeInternal, seg)\n }\n\n // Detach `child` from `seg`: splice it out, dispose+drop its removal hook, and\n // clear its parent pointer. Used by adopt() before re-homing it elsewhere.\n function detachChild(child: ScopeInternal, seg: Segment): void {\n const i = seg.children.indexOf(child)\n if (i >= 0) seg.children.splice(i, 1)\n const remover = childRemovers.get(child)\n if (remover) {\n remover.dispose()\n childRemovers.delete(child)\n }\n child.__setParent(null, null)\n }\n\n const scope: ScopeInternal = {\n get alive() {\n return alive\n },\n\n __currentSegment() {\n return segment\n },\n __inFlight() {\n return inFlight\n },\n __parent() {\n return { parent, owningSegment }\n },\n __attachChild(child, seg) {\n attachChild(child, seg)\n },\n __detachChild(child, seg) {\n detachChild(child, seg)\n },\n __setParent(p, seg) {\n parent = p\n owningSegment = seg\n },\n\n own(d) {\n // Leak protection: after close, do not delegate to the disposed segment\n // (that throws). Dispose the late resource immediately, return a no-op.\n if (!alive) {\n disposeLate(d)\n return NOOP_DISPOSABLE\n }\n return segment.resources.add(d)\n },\n\n child() {\n if (!alive) {\n // A child of a dead scope is born dead and pre-disposed.\n const dead = createScope()\n void dead.close()\n return dead\n }\n const sub = asInternal(createScope())\n // Bind the child to the current segment (tracked child + removal hook +\n // parent pointer). See attachChild.\n attachChild(sub, segment)\n return sub\n },\n\n reset() {\n if (!alive) return inFlight ?? Promise.resolve()\n // Single-flight: if a teardown is already running, join it. A reset in\n // flight already does what this call wants; a close in flight is terminal\n // (stronger) — either way the caller's intent (the current segment goes\n // away) is satisfied by awaiting the in-flight Promise.\n if (inFlight) return inFlight\n return runReset()\n },\n\n close() {\n // Single-flight + close-priority. close() must resolve only after the\n // scope's disposer has TRULY finished, so it never early-returns while a\n // teardown is mid-flight.\n if (!alive) {\n // Already dead. If a close is still completing, join it (true-wait);\n // otherwise the prior close fully finished — resolve immediately.\n return inFlight ?? Promise.resolve()\n }\n if (inFlight && inFlightKind === 'close') return inFlight\n if (inFlight && inFlightKind === 'reset') {\n // Upgrade an in-flight reset to a close. Mark dead now (terminal +\n // leak-protection: concurrent own() hits the disposeLate path). The\n // reset already swapped in a fresh `segment` and is tearing the old one\n // down; once it finishes we tear down that fresh segment too and fire\n // 'closed'. We chain rather than overlap so the two disposeAll passes\n // never run concurrently.\n alive = false\n const afterReset = inFlight\n inFlightKind = 'close'\n const upgraded = (async () => {\n // The in-flight reset may reject (a throwing disposer in the old\n // segment). Swallow that here — the upgrade still owns tearing down\n // the leftover (fresh) segment and firing 'closed'. The reset's own\n // rejection already reached its first caller.\n await afterReset.catch(() => {})\n const leftover = segment\n segment = newSegment()\n // Clear state + emit even if the leftover teardown throws, so a later\n // close() is idempotent rather than a permanent stale rejection.\n try {\n await disposeSegment(leftover)\n } finally {\n inFlight = null\n inFlightKind = null\n emit('closed')\n }\n })()\n inFlight = upgraded\n return upgraded\n }\n return runClose()\n },\n\n on(ev, cb) {\n const set = ev === 'reset' ? resetListeners : closedListeners\n set.add(cb)\n let removed = false\n return {\n dispose() {\n if (removed) return\n removed = true\n set.delete(cb)\n },\n }\n },\n\n async adopt(childPublic, newParentPublic) {\n const child = asInternal(childPublic)\n const newParent = asInternal(newParentPublic)\n const self = scope\n\n // ── Atomicity = WAIT not throw ─────────────────────────────────────────\n // If either endpoint has a teardown in flight, park behind its fence\n // BEFORE reading any segment. We re-read segments AFTER waiting so we\n // attach to/detach from the fresh post-fence segment, never a stale one.\n // Loop because waiting on one fence may leave the other mid-flight (or a\n // new one may have started in the meantime).\n while (true) {\n const f = self.__inFlight() ?? newParent.__inFlight()\n if (!f) break\n // The fence resolves only after its disposeAll truly finishes; tolerate\n // rejection (we re-validate alive below regardless).\n await f.catch(() => {})\n }\n\n // ── Pre-validation; failures reject, leaving child untouched ────────────\n if (!self.alive) {\n throw new Error('adopt: donor scope is not alive')\n }\n if (!newParent.alive) {\n throw new Error('adopt: newParent scope is not alive')\n }\n\n // Re-read the (post-fence) live segments.\n const fromSegment = self.__currentSegment()\n const toSegment = newParent.__currentSegment()\n\n // `child` must be a direct child of THIS scope's current segment.\n if (child.__parent().parent !== self || fromSegment.children.indexOf(child) < 0) {\n throw new Error('adopt: child is not a direct child of this scope current segment')\n }\n\n // No cycle: newParent must not be the child itself nor a descendant of it\n // (that would make the child contain itself). Walk newParent's ancestors.\n if (isSelfOrAncestor(child, newParent)) {\n throw new Error('adopt: would create a cycle (newParent is child or a descendant of it)')\n }\n\n // ── Atomic detach + re-attach ──────────────────────────────────────────\n // Splice out of the old segment + unbind the stale old-parent removal hook\n // (detachChild), then push into the new segment + bind a fresh removal hook\n // (newParent.__attachChild). The child is never in both lists and never in\n // neither — exactly one segment throughout. It is NOT reset/closed and its\n // own()ed resources are untouched, so 'reset'/'closed' never fire on it.\n self.__detachChild(child, fromSegment)\n newParent.__attachChild(child, toSegment)\n },\n }\n\n return scope\n}\n","/**\n * `Compositor` — engine-agnostic z-order planner for a window's native child\n * views (foundation: the spikes in `.repro/electron-deck-spikes/`).\n *\n * It separates INTENT (mount / unmount / reorder a view into a zone, at a\n * relative position) from APPLICATION (`commit()` computes the minimal sequence\n * of host add/remove calls that transforms the host's current child order into\n * the target order).\n *\n * The host's observable z-semantics this planner is built on (from the spikes):\n * - `addChildView` of an ALREADY-mounted child raises it to the top WITHOUT a\n * remove first and WITHOUT reloading it.\n * - `addChildView` of a NEW child appends it to the end (= topmost).\n * - A batch of remove/add in ONE synchronous tick re-lands at the target order\n * with zero renderer reloads.\n * - `addChildView` into a destroyed contentView throws synchronously.\n *\n * Ordering model. Every mounted view carries `(zone, orderKey, viewId)`. The\n * total render order is that triple, ascending: lower zone renders BELOW higher\n * zone (zones stack), `orderKey` orders within a zone, and `viewId` is a pure\n * tiebreak so the order is deterministic even if two keys collide. `orderKey` is\n * a FRACTIONAL key: reorder-before(X) sets the moved view's key to the midpoint\n * between X's key and its in-zone predecessor's key, so a reorder is O(1) and\n * perturbs no other view's key. When repeated midpoints exhaust float precision\n * in a gap, the affected zone is RENUMBERED (rebalance) to evenly-spaced integer\n * keys — invisible, because the renumber preserves the existing relative order.\n *\n * mount epoch. A genuinely-new mount gets a fresh monotonically-increasing\n * `mountSeq` and lands at the top of its zone. Re-mounting a view that is STILL\n * mounted is a pure no-op (same orderKey/mountSeq, zero host churn). But an\n * `unmount(id)` followed by `mount(id)` is a NEW instance: it gets a new\n * mountSeq and lands at the top — it never resumes the old slot.\n *\n * commit. We fold the batch of intents into the FINAL target state (last-state,\n * not a write-log replay), then diff the host's current children against that\n * target. The longest increasing subsequence (LIS) of views already in correct\n * relative order — computed over the current∩target intersection — is left\n * untouched; every other shared view is `removeChildView` + `addChildView`, and\n * each brand-new view gets one explicit `addChildView`. All in one synchronous\n * pass. On failure `commit()` throws a typed {@link CommitError}: a destroyed\n * host with additions pending throws `host-destroyed` BEFORE touching native\n * (applied:false); a native call that throws mid-apply is caught and the host is\n * rolled back to its pre-apply snapshot, then `apply-failed` is thrown with\n * whether the rollback recovered the snapshot. A no-op commit, and a destroyed\n * host with only removals pending, are SILENT (teardown-friendly).\n */\n\n/** A handle to a native child view. Identity is by `id`. */\nexport type NativeViewRef = { readonly id: string }\n\n/**\n * Typed failure thrown by {@link Compositor.commit}. `kind` distinguishes a\n * preflight refusal (`host-destroyed`, native byte-for-byte pre-commit,\n * `applied:false`) from a mid-apply native throw (`apply-failed`,\n * `applied:'partial'`); for `apply-failed`, `recovered` reports whether the\n * best-effort rollback restored the pre-apply snapshot order (`false` ⇒ native\n * is untrusted and the host must be treated as dead).\n */\nexport class CommitError extends Error {\n\treadonly kind: 'host-destroyed' | 'apply-failed'\n\treadonly applied: false | 'partial'\n\treadonly recovered?: boolean\n\tconstructor(args: {\n\t\tkind: 'host-destroyed' | 'apply-failed'\n\t\tapplied: false | 'partial'\n\t\trecovered?: boolean\n\t\tmessage?: string\n\t}) {\n\t\tsuper(args.message ?? `commit failed: ${args.kind}`)\n\t\tthis.name = 'CommitError'\n\t\tthis.kind = args.kind\n\t\tthis.applied = args.applied\n\t\tif (args.recovered !== undefined) this.recovered = args.recovered\n\t}\n}\n\n/**\n * The native content-view surface the Compositor drives. In production this is\n * an Electron `contentView` (`addChildView` / `removeChildView`); in tests it is\n * a faithful fake. `children()` returns the current order, LAST = topmost.\n */\nexport interface ContentViewHost {\n /** Already-mounted ref → raise to top (no remove, no reload); new ref →\n * append to the end (top). */\n addChildView(v: NativeViewRef): void\n removeChildView(v: NativeViewRef): void\n readonly isDestroyed: boolean\n /** Current child order; LAST element is the topmost. */\n children(): readonly NativeViewRef[]\n}\n\nexport interface Compositor {\n /** Idempotent attach. A view STILL mounted → pure no-op (unchanged\n * orderKey/mountSeq, zero host calls). A new view (or one re-mounted after\n * unmount) → fresh mountSeq, lands at the top (end) of its zone. */\n mount(view: NativeViewRef, opts?: { zone?: number }): void\n /** Detach a view. A subsequent mount of the same id is a NEW instance. */\n unmount(viewId: string): void\n /** Move a view: `before` slots it immediately before that anchor (midpoint of\n * the anchor and its predecessor); `before: null` sends it to the end (top)\n * of the zone. `zone` moves it to a different zone. An illegal `before`\n * (unknown / unmounted id, or one whose zone conflicts with an explicit\n * `zone`) throws SYNCHRONOUSLY. */\n reorder(viewId: string, opts: { zone?: number; before?: string | null }): void\n /** Apply the folded target state to the host with the minimal add/remove\n * sequence (LIS-preserving). Returns void on success. Throws a typed\n * {@link CommitError} on failure: `kind:'host-destroyed'` (applied:false) when\n * the host is destroyed and there are ADDITIONS to apply — thrown BEFORE\n * touching native; `kind:'apply-failed'` (applied:'partial') when a native\n * call throws mid-apply, after a best-effort rollback to the pre-apply\n * snapshot (`recovered` flags whether the snapshot was restored). A no-op\n * commit, and a destroyed host with ONLY removals pending, are SILENT. */\n commit(): void\n /** Fold the intent state to EMPTY and commit, removing every native view\n * this window's compositor mounted from the host. The resulting commit is\n * REMOVALS-ONLY, so it reuses {@link commit}'s teardown-friendly\n * \"destroyed host + only removals → silent\" path (commit failure semantics): on an\n * already-destroyed host it makes zero host calls and throws nothing.\n *\n * Optional on the interface so a partial test double (or a caller that only\n * needs mount/unmount/reorder/commit) still structurally satisfies\n * `Compositor`; the real {@link createCompositor} always implements it. */\n detachAll?(): void\n}\n\ninterface MountedView {\n ref: NativeViewRef\n zone: number\n orderKey: number\n mountSeq: number\n}\n\nconst DEFAULT_ZONE = 0\n\n/**\n * Smallest representable gap between fractional keys before we renumber. Once a\n * midpoint would round to (or below) this distance from a neighbor, the zone is\n * rebalanced rather than risk key collision / loss of ordering.\n */\nconst MIN_KEY_GAP = 1e-9\n\n/**\n * The longest PREFIX of `shared` (target order) that is already an in-order\n * subsequence of the host's current children — the largest set the\n * append-to-top host primitive can leave untouched. `currentIndexOf` maps id\n * → its position in the host's CURRENT children.\n */\nfunction computeKeepIds(shared: readonly string[], currentIndexOf: Map<string, number>): Set<string> {\n const keepIds = new Set<string>()\n let prevPos = -1\n for (const id of shared) {\n const pos = currentIndexOf.get(id)!\n if (pos <= prevPos) break\n keepIds.add(id)\n prevPos = pos\n }\n return keepIds\n}\n\n/** Views the host currently has but the target drops. */\nfunction planRemovals(\n hostChildren: readonly NativeViewRef[],\n targetSet: ReadonlySet<string>,\n): NativeViewRef[] {\n const removals: NativeViewRef[] = []\n for (const v of hostChildren) {\n if (!targetSet.has(v.id)) removals.push(v)\n }\n return removals\n}\n\n/**\n * Shared views NOT in the kept prefix must be re-added in target order;\n * brand-new views (not currently mounted) get one explicit add. Walked in\n * target order so each addition carries its final index.\n */\nfunction planAdditions(\n target: readonly MountedView[],\n currentSet: ReadonlySet<string>,\n keepIds: ReadonlySet<string>,\n): { ref: NativeViewRef; atIndex: number }[] {\n const additions: { ref: NativeViewRef; atIndex: number }[] = []\n target.forEach((v, i) => {\n const isNew = !currentSet.has(v.ref.id)\n const mustMove = currentSet.has(v.ref.id) && !keepIds.has(v.ref.id)\n if (isNew || mustMove) additions.push({ ref: v.ref, atIndex: i })\n })\n return additions\n}\n\n/**\n * Plan the minimal host removals/additions that realize `target` (see\n * {@link Compositor.commit}'s doc-comment for the LIS-preserving strategy).\n * Pure planning — never touches the host.\n */\nfunction planCommit(\n target: readonly MountedView[],\n host: ContentViewHost,\n): { removals: NativeViewRef[]; additions: { ref: NativeViewRef; atIndex: number }[] } {\n const targetIds = target.map((v) => v.ref.id)\n const current = host.children().map((v) => v.id)\n const targetSet = new Set(targetIds)\n const currentSet = new Set(current)\n\n const shared = targetIds.filter((id) => currentSet.has(id))\n const currentIndexOf = new Map<string, number>()\n current.forEach((id, i) => currentIndexOf.set(id, i))\n const keepIds = computeKeepIds(shared, currentIndexOf)\n\n return {\n removals: planRemovals(host.children(), targetSet),\n additions: planAdditions(target, currentSet, keepIds),\n }\n}\n\n/** Best-effort rollback to the pre-apply snapshot order; `false` if the\n * rollback itself throws (native is left untrusted). */\nfunction rollbackToSnapshot(host: ContentViewHost, snapshot: readonly NativeViewRef[]): boolean {\n try {\n for (const v of host.children().slice()) host.removeChildView(v)\n for (const v of snapshot) host.addChildView(v)\n return true\n } catch {\n return false\n }\n}\n\n/**\n * Apply a planned removals/additions batch to the host in one synchronous\n * pass, with the no-op / destroyed-host / rollback semantics documented on\n * {@link Compositor.commit}.\n */\nfunction applyCommitPlan(\n host: ContentViewHost,\n removals: readonly NativeViewRef[],\n additions: { ref: NativeViewRef; atIndex: number }[],\n): void {\n // no-op: nothing to apply → silent, never touches host (even if destroyed).\n if (removals.length === 0 && additions.length === 0) return\n\n if (host.isDestroyed) {\n // Teardown-friendly (A4.3): a destroyed host with ONLY removals is a\n // no-op — the views are already gone with the contentView, so there is\n // nothing to detach. Silently return (teardown commits must never throw).\n if (additions.length === 0) return\n // There are additions to apply to a destroyed host — impossible.\n // Preflight throw BEFORE touching the host: native is byte-for-byte\n // pre-commit.\n throw new CommitError({\n kind: 'host-destroyed',\n applied: false,\n message: 'commit: contentView is destroyed',\n })\n }\n\n // Apply phase. Snapshot the pre-apply native order so we can roll back if a\n // native call unexpectedly throws (host destroyed in the narrow plan→apply\n // window). One synchronous pass: removals first, then additions in target\n // order.\n const snapshot = host.children().slice()\n additions.sort((a, b) => a.atIndex - b.atIndex)\n try {\n for (const r of removals) host.removeChildView(r)\n for (const a of additions) host.addChildView(a.ref)\n } catch (applyErr) {\n // Best-effort rollback to snapshot: clear current children, re-add the\n // snapshot order. If rollback itself throws (host fully dying), native is\n // left untrusted (recovered:false) — the caller must treat this host as\n // dead.\n const recovered = rollbackToSnapshot(host, snapshot)\n throw new CommitError({\n kind: 'apply-failed',\n applied: 'partial',\n recovered,\n message: `commit apply failed: ${(applyErr as Error)?.message ?? applyErr}`,\n })\n }\n}\n\nexport function createCompositor(host: ContentViewHost): Compositor {\n // Current intent state. This is the TARGET the next commit() will realize —\n // intents mutate it in place (last-state), so a commit always diffs the host's\n // real children against the latest fold.\n const views = new Map<string, MountedView>()\n let mountSeqCounter = 0\n\n /** Views in a zone, sorted by the in-zone order `(orderKey, viewId)`. */\n function zoneSorted(zone: number): MountedView[] {\n const out: MountedView[] = []\n for (const v of views.values()) if (v.zone === zone) out.push(v)\n out.sort(compareInZone)\n return out\n }\n\n function compareInZone(a: MountedView, b: MountedView): number {\n if (a.orderKey !== b.orderKey) return a.orderKey - b.orderKey\n return a.ref.id < b.ref.id ? -1 : a.ref.id > b.ref.id ? 1 : 0\n }\n\n /** Full target order: zones ascending (low renders first/bottom), then\n * in-zone order. */\n function targetOrder(): MountedView[] {\n const all = [...views.values()]\n all.sort((a, b) => {\n if (a.zone !== b.zone) return a.zone - b.zone\n return compareInZone(a, b)\n })\n return all\n }\n\n /** Next order key that lands a view at the TOP (end) of its zone. */\n function topKey(zone: number): number {\n const z = zoneSorted(zone)\n const last = z[z.length - 1]\n return last === undefined ? 0 : last.orderKey + 1\n }\n\n /**\n * Renumber a zone to evenly-spaced integer keys, preserving the current\n * relative order exactly. Invisible: the visible order is unchanged, so a\n * commit after a rebalance produces no extra host churn.\n */\n function rebalanceZone(zone: number): void {\n const z = zoneSorted(zone)\n z.forEach((v, i) => {\n v.orderKey = i\n })\n }\n\n /**\n * Compute the fractional key that places a view immediately before `anchor`\n * within its zone (the midpoint between `anchor` and its predecessor), or at\n * the zone top when `anchor` is null. If the midpoint would exhaust precision,\n * rebalance the zone and recompute on the fresh integer keys.\n */\n function keyBefore(zone: number, anchor: MountedView | null, movingId: string): number {\n if (anchor === null) return topKey(zone)\n\n const midpointBefore = (anchorId: string): number | null => {\n const z = zoneSorted(zone).filter((v) => v.ref.id !== movingId)\n const idx = z.findIndex((v) => v.ref.id === anchorId)\n const at = z[idx]\n // Anchor must still be present (caller validates), but guard defensively.\n if (idx < 0 || at === undefined) return null\n const hiKey = at.orderKey\n const prev = z[idx - 1]\n const loKey = prev !== undefined ? prev.orderKey : hiKey - 1\n const mid = (loKey + hiKey) / 2\n // Precision exhausted: the midpoint is indistinguishable from a neighbor.\n if (mid <= loKey + MIN_KEY_GAP || mid >= hiKey - MIN_KEY_GAP) return null\n return mid\n }\n\n const first = midpointBefore(anchor.ref.id)\n if (first === null && zoneSorted(zone).some((v) => v.ref.id === anchor.ref.id)) {\n // Precision exhausted in this gap: renumber the zone (invisible — relative\n // order preserved) and retry on the spaced-out integer keys.\n rebalanceZone(zone)\n const retry = midpointBefore(anchor.ref.id)\n if (retry !== null) return retry\n }\n return first ?? topKey(zone)\n }\n\n const compositor: Compositor = {\n mount(view, opts) {\n const zone = opts?.zone ?? DEFAULT_ZONE\n const existing = views.get(view.id)\n // Idempotent: a view that is STILL mounted is a pure no-op — keep its\n // orderKey/mountSeq, keep its ref, do not raise to top. (An unmount→mount\n // is a different code path: the map has no entry, so we fall through to a\n // fresh instance below.)\n if (existing) return\n\n views.set(view.id, {\n ref: view,\n zone,\n orderKey: topKey(zone),\n mountSeq: ++mountSeqCounter,\n })\n },\n\n unmount(viewId) {\n views.delete(viewId)\n },\n\n reorder(viewId, opts) {\n const mv = views.get(viewId)\n if (!mv) {\n throw new Error(`reorder: view \"${viewId}\" is not mounted`)\n }\n\n const before = opts.before\n const targetZone = opts.zone ?? mv.zone\n\n if (before === undefined || before === null) {\n // No anchor: send to the end (top) of the (possibly new) zone.\n mv.zone = targetZone\n mv.orderKey = topKey(targetZone)\n return\n }\n\n const anchor = views.get(before)\n if (!anchor) {\n throw new Error(`reorder: before anchor \"${before}\" is not mounted`)\n }\n // An explicit `zone` that disagrees with the anchor's zone is\n // contradictory — never silently pick one.\n if (opts.zone !== undefined && anchor.zone !== opts.zone) {\n throw new Error(\n `reorder: before anchor \"${before}\" is in zone ${anchor.zone}, ` +\n `which conflicts with the requested zone ${opts.zone}`,\n )\n }\n\n mv.zone = anchor.zone\n mv.orderKey = keyBefore(anchor.zone, anchor, viewId)\n },\n\n commit() {\n // The host can only `addChildView` to the TOP (append / raise). So a\n // commit keeps a set of views in place and piles the movers on top in\n // target order; for that to reproduce the target, the kept views must be\n // exactly the longest PREFIX of the target that is already an in-order\n // subsequence of the host's current children (i.e. the longest strictly\n // increasing prefix, by current position, of the shared views taken in\n // target order). Everything after that prefix — plus every brand-new view\n // — is a mover, re-added once in target order. This is the minimal host\n // churn: the LIS of the current∩target intersection that append-to-top can\n // leave untouched. (See compositor.test.ts's \"commit — minimal host\n // operations\" section.) Plan the host calls first,\n // THEN apply — a no-op commit must neither throw nor touch the host.\n const { removals, additions } = planCommit(targetOrder(), host)\n applyCommitPlan(host, removals, additions)\n },\n\n detachAll() {\n // Fold the intent to EMPTY, then run the normal commit. With an empty\n // target the only pending work is removals, so on a destroyed host the\n // commit hits the teardown-friendly removals-only-on-destroyed path\n // (silent, zero host calls); on a live host it removes every child.\n for (const id of [...views.keys()]) views.delete(id)\n compositor.commit()\n },\n }\n\n return compositor\n}\n","/**\n * `ViewHandle` — the per-view orchestrator (view-handle.md「placeIn 与挂载」/「dispose(viewScope LIFO 序)」/「moveTo 跨窗迁移」). One handle\n * owns ONE native view and threads it through a window's z-planner. It composes\n * three INJECTED primitives and nothing else (no deck-app, no Electron):\n * - a {@link NativeView} (its `ref` + a `setBounds` sink) — the native surface;\n * - a {@link Scope} (`deps.scope`) — the view's home/native-view lifetime;\n * - a {@link PlaceTarget} = `{ compositor, windowScope }` — a window's\n * z-planner + lifetime, handed to {@link ViewHandle.placeIn}.\n *\n * Lifetime. `placeIn` adopts a `viewScope` that is a CHILD of the target\n * window's `windowScope`, so closing the windowScope cascades into the handle\n * (scope.ts cross-layer LIFO): the native view is detached and the placement\n * sink goes inert.\n *\n * handle 直接驱动 bounds. The handle drives `setBounds` DIRECTLY on its native view; the\n * Compositor stays a pure z-order planner (mount/unmount/commit only) and never\n * sees geometry.\n *\n * per-window teardown 顺序 (view-handle.md「dispose(viewScope LIFO 序)」). The viewScope owns the native detach\n * FIRST and the sink-disable LAST, so LIFO teardown runs the sink-disable\n * (STEP0) BEFORE the detach (STEP1): a late `place` frame can never drive a\n * native effect on a half-torn-down view.\n *\n * Cross-window move (view-handle.md「moveTo 跨窗迁移」/ compositor-and-teardown.md「moveTo 事务状态机」). {@link ViewHandle.moveTo}\n * migrates the view to another `{ compositor, windowScope }` as TWO independent\n * Compositor commits, guarded by a per-view async mutex (THE migrationLock —\n * each handle is one view). The current placement is a MUTABLE token so the\n * detach `own()` and `applyPlacement` always follow the CURRENT window after a\n * move. `rehome:true` re-parents the viewScope via {@link Scope.adopt} so\n * lifetime follows display; without it, lifetime stays under the src window.\n */\nimport type { Scope } from './scope.js'\nimport type { Compositor, NativeViewRef } from './compositor.js'\n\n/** A screen-space rectangle, in CSS px. Mirrors `@dimina-kit/view-anchor`'s\n * `Bounds` (electron-deck does not depend on view-anchor in this increment). */\nexport interface Bounds {\n x: number\n y: number\n width: number\n height: number\n}\n\n/**\n * Explicit visibility + geometry for a native view. Structurally identical to\n * the `@dimina-kit/view-anchor` `Placement` export; mirrored locally so this\n * increment adds no new package dependency.\n */\nexport type Placement = { visible: true; bounds: Bounds } | { visible: false }\n\n/** The native surface a handle drives: its z-order identity (`ref`) plus the\n * `setBounds` sink the handle calls directly (handle 直接驱动 bounds). `destroy` (optional)\n * destroys the backing native view (its WebContents) — owned by the viewScope so\n * it runs on teardown AFTER the detach (keepAlive「保活寿命归 Scope、淘汰策略归 host」lifetime/leak fix).\n * Optional so fakes that don't model a native WebContents stay valid.\n *\n * `webContents` / `capturePage` (optional) expose the backing native view's\n * WebContents and a screenshot pass-through, so a handle accessor can recover\n * them without re-deriving the WebContentsView from the window's content view.\n * Optional so geometry-only fakes stay valid. */\nexport interface NativeView {\n readonly ref: NativeViewRef\n setBounds(b: Bounds): void\n destroy?(): void\n readonly webContents?: unknown\n capturePage?(): Promise<unknown>\n}\n\n/** A window's z-planner + lifetime, handed to {@link ViewHandle.placeIn}. The\n * handle's per-placement teardown scope is a CHILD of `windowScope`. */\nexport interface PlaceTarget {\n compositor: Compositor\n windowScope: Scope\n}\n\nexport interface ViewHandle {\n /** Mount the native view into the target window (mount + commit) and adopt a\n * per-placement viewScope under the target's `windowScope`. Chainable. */\n placeIn(target: PlaceTarget, opts: { zone?: number }): ViewHandle\n /** The placement sink. Drops frames once disposed (idempotent late IPC).\n * `visible:true` ensures mounted + drives `setBounds` directly; `visible:false`\n * detaches (unmount + commit) but keeps the native view alive. */\n applyPlacement(p: Placement): void\n /**\n * Cross-window move (view-handle.md「moveTo 跨窗迁移」/ compositor-and-teardown.md「moveTo 事务状态机」). Migrate the view from its\n * current placement (`src`) to `dest` as TWO independent Compositor commits,\n * serialized by a per-view async mutex (migrationLock):\n *\n * AT_SRC → DETACHED → AT_DEST (happy path)\n * └→ (src.commit throws) → AT_SRC (rethrow, no side effect)\n * DETACHED → (dest.commit throws) → ROLLBACK → AT_SRC (rethrow dest error)\n * └→ CLOSED (src re-mount ALSO throws)\n *\n * On success the compositor token moves to `dest` (later `applyPlacement`\n * drives the dest host). With `rehome:true`, the viewScope is re-parented under\n * dest's `windowScope` (lifetime follows display). moveTo 迁移显示而非寿命: moveTo moves DISPLAY\n * (and, with rehome, LIFETIME) — it does NOT carry capability grants; the dest\n * window's own control layer issues its own grant. Terminal (Promise<void>, not\n * chainable).\n */\n moveTo(dest: PlaceTarget, opts: { zone?: number; rehome?: boolean }): Promise<void>\n /** Tear down this placement: run the viewScope's A4 owns (sink-disable then\n * native detach, via the LIFO completion fence). Idempotent. */\n dispose(): Promise<void>\n /** The backing native view's WebContents (pass-through from {@link NativeView}).\n * Available immediately — the handle owns its view before any placeIn. */\n readonly webContents: unknown\n /** The view's LIVE screen-space rect when it is currently placed AND visible;\n * `null` before the first placement, after `applyPlacement({visible:false})`,\n * and after `dispose()`. Tracks the last applied `visible:true` bounds. */\n bounds(): Bounds | null\n /** Screenshot pass-through to the native view's `capturePage()`. */\n capturePage(): Promise<unknown>\n}\n\nexport interface ViewHandleDeps {\n nativeView: NativeView\n scope: Scope\n /** Optional bookkeeping hook, fired whenever the viewScope tears down (window-\n * close cascade OR explicit dispose). The deck-app uses it to drop the view\n * from its keepAlive group — a window-close cascades the viewScope directly\n * (NOT via the host wrapper's dispose), so group cleanup must hang off the\n * scope to fire on that path too (KA-2). Any order — it is pure bookkeeping. */\n onDispose?(): void\n}\n\nexport function createViewHandle(deps: ViewHandleDeps): ViewHandle {\n const { nativeView } = deps\n const ref = nativeView.ref\n\n // The CURRENT placement (mutable so a move re-points detach + applyPlacement at\n // the new window). `current` holds the live { compositor, windowScope }; the\n // viewScope's detach own() reads it at TEARDOWN time, so after a move it tears\n // down against whichever window the view now lives in.\n let current: { compositor: Compositor; windowScope: Scope } | null = null\n // The zone the view is currently placed/moved with — used to restore the src\n // intent on a move rollback (re-mount the view at its prior zone).\n let currentZone: number | undefined\n // The per-placement teardown scope (a child of the current target windowScope).\n let viewScope: Scope | null = null\n // The windowScope the viewScope is ACTUALLY parented under (its lifetime owner).\n // Distinct from `current.windowScope` (the display window): a display-only move\n // updates `current` but NOT `owning`, so a later `rehome:true` adopt uses the\n // viewScope's real parent as the donor (scope.adopt requires donor === parent).\n let owningWindowScope: Scope | null = null\n\n // The placement sink's gate. Goes false the instant the per-window teardown runs\n // (STEP0), so a late applyPlacement after dispose/cascade is a no-op.\n let active = false\n\n // The LIVE on-screen rect: the last `visible:true` bounds applied while the\n // view is active. Set on applyPlacement({visible:true}); cleared on\n // applyPlacement({visible:false}); read by bounds() (which also returns null\n // once the view is no longer active — never placed or disposed).\n let visibleBounds: Bounds | null = null\n\n // True WHILE a moveTo migration is in flight (the\n // migrationLock is held). `applyPlacement` drops place frames while migrating —\n // during the awaited dest commit + (rehome) adopt window, `current` already\n // points at dest but the SOURCE slot token may still be registered, so a stale\n // `place` from the source renderer could otherwise drive the view mid-migration\n // (setBounds against a half-migrated host). A place frame during a move is stale\n // by definition; drop it. Closes the window independent of token-revoke timing.\n let migrating = false\n\n // Per-view async mutex (THE migrationLock — a handle is one view). Serializes\n // moveTo calls FIFO: each runs only after the prior fully settles (success OR\n // failure), so the view is never being migrated from two places at once.\n let lockChain: Promise<unknown> = Promise.resolve()\n function withLock<T>(fn: () => Promise<T>): Promise<T> {\n const run = lockChain.then(fn, fn)\n lockChain = run.then(\n () => {},\n () => {},\n )\n return run\n }\n\n function ensureMounted(): void {\n if (!current) return\n current.compositor.mount(ref, { zone: currentZone })\n current.compositor.commit()\n }\n\n // The actual teardown (viewScope.close A4 fence). Factored out so doMove's\n // CLOSED path can dispose WHILE it holds the migrationLock (calling the\n // lock-acquiring public `dispose()` from inside the lock would deadlock), and\n // the public `dispose()` can serialize itself behind the lock.\n async function doDispose(): Promise<void> {\n // Idempotent: a dispose before placeIn (or a second dispose) is a no-op.\n if (!viewScope) return\n await viewScope.close()\n }\n\n /**\n * Re-mount `ref` on `src` and commit. If that re-commit ALSO throws, the src\n * host is unrecoverable — close the view (viewScope.close ⇒ dispose) so it\n * doesn't stay homeless. Either way this always resolves (never rethrows);\n * the caller re-throws whatever ORIGINAL error triggered the rollback.\n */\n async function restoreSrcOrDispose(src: PlaceTarget, srcZone: number | undefined): Promise<void> {\n src.compositor.mount(ref, { zone: srcZone })\n try {\n src.compositor.commit()\n } catch {\n await doDispose()\n }\n }\n\n /**\n * Undo a landed dest commit (adopt failed AFTER the dest attach succeeded):\n * unmount + commit on dest to actually reverse the native attach (best-effort\n * — a failed detach commit leaves the host byte-for-byte and the planner\n * already dropped the intent), then restore src and point `current` back at\n * it BEFORE the src re-commit, so a CLOSED dispose tears down against the\n * right window.\n */\n async function rollbackDestAndRestoreSrc(dest: PlaceTarget, src: PlaceTarget, srcZone: number | undefined): Promise<void> {\n dest.compositor.unmount(ref.id)\n try {\n dest.compositor.commit()\n } catch {\n // best-effort — see doc-comment above.\n }\n src.compositor.mount(ref, { zone: srcZone })\n current = src\n currentZone = srcZone\n try {\n src.compositor.commit()\n } catch {\n await doDispose()\n }\n }\n\n async function doMove(\n dest: PlaceTarget,\n opts: { zone?: number; rehome?: boolean },\n ): Promise<void> {\n // Guard: can't move an unplaced/disposed view.\n if (!active || !current || !viewScope) {\n throw new Error('moveTo: the view is not currently placed (disposed or never placed)')\n }\n\n const src = current\n const srcZone = currentZone\n const destZone = opts.zone\n const vs = viewScope\n\n // ── STEP 1: detach from src (AT_SRC → DETACHED). ───────────────────────────\n src.compositor.unmount(ref.id)\n try {\n src.compositor.commit()\n } catch (e) {\n // src.commit threw → the view never left src (CommitError leaves the host\n // byte-for-byte pre-commit). Restore the src intent + re-commit so the\n // planner is consistent with the host again. Stays AT_SRC; rethrow.\n await restoreSrcOrDispose(src, srcZone)\n throw e\n }\n\n // ── STEP 2: attach to dest (DETACHED → AT_DEST). ───────────────────────────\n dest.compositor.mount(ref, { zone: destZone })\n try {\n dest.compositor.commit()\n } catch (destErr) {\n // dest.commit threw → ROLLBACK: re-mount on src so the view is never\n // dangling (I2). Drop the failed dest intent first.\n dest.compositor.unmount(ref.id)\n await restoreSrcOrDispose(src, srcZone)\n // Rolled back to AT_SRC (or CLOSED, if the restore also failed): rethrow\n // the dest error.\n throw destErr\n }\n\n // ── AT_DEST: native commit landed. The compositor token moves to dest. ─────\n current = { compositor: dest.compositor, windowScope: dest.windowScope }\n currentZone = destZone\n // rehome: re-parent the viewScope under dest's windowScope so dest-close\n // tears it down (lifetime follows display). Without rehome it stays under\n // src.windowScope (display moved, lifetime did not).\n //\n // adopt comes AFTER the native dest commit, so an\n // adopt failure must FULLY roll back the native dest commit + restore\n // `current`/`currentZone` to source — otherwise the view is detached from src\n // while compositor/`current` point at dest (native + lifetime diverge, and the\n // host catch would wrongly remove a dest child). moveTo's post-condition is\n // all-or-nothing: either dest + rehome both land, or we roll back to the\n // pre-move source state (same end-state as a dest-commit failure). Mirrors the\n // STEP-2 ROLLBACK arm exactly so the two failure paths converge.\n if (opts.rehome) {\n // The adopt donor MUST be the viewScope's ACTUAL parent (owningWindowScope),\n // not the current display window (src). After a display-only move, src is the\n // display window but the viewScope still lives under owningWindowScope, so\n // adopt(donor=src) would reject \"child is not a direct child …\".\n const donor = owningWindowScope ?? src.windowScope\n try {\n await donor.adopt(vs, dest.windowScope)\n } catch (adoptErr) {\n await rollbackDestAndRestoreSrc(dest, src, srcZone)\n // Rolled back to AT_SRC (or CLOSED, if the restore also failed):\n // rethrow the adopt error.\n throw adoptErr\n }\n // Adopt landed: the viewScope's lifetime now lives under dest's windowScope.\n owningWindowScope = dest.windowScope\n }\n // moveTo 迁移显示而非寿命: moveTo does NOT touch capability grants — the dest window's control\n // layer issues its own. Out of scope by construction.\n }\n\n const handle: ViewHandle = {\n placeIn(target, opts) {\n // One placeIn per handle: a SECOND placeIn must NOT silently overwrite\n // `current`/`viewScope` (the N3 corruption — the old viewScope would stay\n // alive under the old window and tear down the moved view on close). moveTo\n // is the ONLY migration path.\n if (viewScope) {\n throw new Error('ViewHandle.placeIn: view already placed — use moveTo() to migrate')\n }\n current = { compositor: target.compositor, windowScope: target.windowScope }\n currentZone = opts.zone\n\n // Appear in the host: mount + commit (against the current placement).\n ensureMounted()\n active = true\n\n // The handle's lifetime is a CHILD of the target window's scope, so the\n // windowScope's close() cascades into it.\n const vs = target.windowScope.child()\n viewScope = vs\n // The viewScope is born parented under the target window's scope.\n owningWindowScope = target.windowScope\n\n // A4 order: own the combined DETACH+DESTROY FIRST → LIFO runs it LAST, after\n // the sink-disable (STEP0). The sink-disable is owned LAST → LIFO runs it\n // FIRST. The detach reads `current` at teardown time (NOT a captured\n // compositor), so after a move it detaches from whichever window now hosts\n // the view. `destroy` is optional + idempotent (guarded by the caller).\n //\n // KA-5: detach and destroy are ONE disposer so destroy is reached only if\n // detach completes without throwing. Scope teardown runs disposers LIFO and\n // CONTINUES past a throwing one (disposable.ts) — so if these were separate\n // owns, a native commit() apply-failure on a LIVE host (compositor rollback\n // restores the attached snapshot) would still run a separate destroy own →\n // webContents.close() while the view is STILL attached to a live contentView\n // (dangling child). Combined, the throw propagates and the destroy line is\n // NOT reached: never destroy a WebContents while its view is still attached.\n // On an already-destroyed host the detach commit is the silent removals-only\n // path, so it doesn't throw and destroy runs correctly.\n vs.own(() => {\n if (current) {\n current.compositor.unmount(ref.id)\n current.compositor.commit()\n }\n deps.nativeView.destroy?.()\n })\n vs.own(() => {\n active = false\n })\n\n // Bookkeeping hook (KA-2): fires on ANY viewScope teardown — window-close\n // cascade OR explicit dispose. Order is irrelevant (it touches no native\n // state). The deck-app uses it to drop the view from its keepAlive group.\n vs.own(() => {\n deps.onDispose?.()\n })\n\n return handle\n },\n\n applyPlacement(p) {\n // Disposed / never-placed: drop the frame (idempotent late IPC).\n if (!active || !current) return\n // Drop place frames while a moveTo is in flight.\n // Mid-migration `current` may point at dest while the source token is still\n // live — a stale source `place` must NOT drive setBounds during the move.\n if (migrating) return\n if (p.visible) {\n // Set bounds BEFORE mounting so a fresh attach composites at its correct\n // geometry (avoids the attach-then-resize flicker the reconciler's op order\n // is built to prevent). setBounds on a not-yet-attached native view is\n // valid; when already mounted it's a plain resize and the mount is a no-op.\n // handle 直接驱动 bounds — bounds go straight to the native view, not the compositor.\n nativeView.setBounds(p.bounds)\n ensureMounted()\n // Track the live on-screen rect for bounds(). Copy so a later caller\n // mutation can't alter the recorded rect.\n visibleBounds = { x: p.bounds.x, y: p.bounds.y, width: p.bounds.width, height: p.bounds.height }\n } else {\n // Detach-but-keep: remove from the host, do NOT destroy the native view.\n current.compositor.unmount(ref.id)\n current.compositor.commit()\n // Not on screen → no live rect.\n visibleBounds = null\n }\n },\n\n moveTo(dest, opts) {\n // Terminal (Promise<void>, not chainable). Serialized by the per-view\n // migrationLock so two concurrent moves run FIFO (the view is never being\n // migrated from two hosts at once).\n //\n // Raise the `migrating` flag for the FULL duration\n // the lock holds this move (set/cleared inside the locked region so the flag\n // tracks exactly \"a move is mid-flight\"), so applyPlacement drops stale place\n // frames throughout the migration window — including the awaited adopt.\n return withLock(async () => {\n migrating = true\n try {\n return await doMove(dest, opts)\n } finally {\n migrating = false\n }\n })\n },\n\n dispose() {\n // Serialize dispose with the migrationLock so it\n // runs AFTER any in-flight moveTo fully settles (success OR rollback), not\n // concurrently — closing the viewScope independently would race a\n // move that is mid-migrating `current`/`viewScope` (e.g. the awaited adopt),\n // and a concurrent teardown could corrupt the move or double-tear-down. Routing\n // through `withLock` parks the dispose behind the move's lock segment; the\n // viewScope.close A4 fence (sink-disable then native detach) then runs once,\n // cleanly, on the settled post-move state. doMove's own CLOSED-path disposal\n // calls the un-locked `doDispose()` directly (it already holds the lock), so\n // there is no self-deadlock. doDispose is idempotent (no viewScope → no-op).\n return withLock(() => doDispose())\n },\n\n // ── Additive accessors (handle-level view recovery) ───────────────────────\n // The backing native view's WebContents, exposed immediately (the handle\n // owns its view before any placeIn).\n get webContents() {\n return nativeView.webContents\n },\n // The live screen-space rect: the last `visible:true` bounds applied while\n // the view is active. null before any placement, after `visible:false`, and\n // after dispose (active goes false on the viewScope's per-window teardown).\n bounds(): Bounds | null {\n if (!active || !visibleBounds) return null\n return { ...visibleBounds }\n },\n // Screenshot pass-through to the native view's webContents.capturePage().\n capturePage(): Promise<unknown> {\n if (!nativeView.capturePage) {\n return Promise.reject(new Error('ViewHandle.capturePage: native view has no capturePage'))\n }\n return nativeView.capturePage()\n },\n }\n\n return handle\n}\n"],"mappings":"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"}
|