@dimina-kit/electron-deck 0.1.0-dev.20260610082053

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 (77) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +39 -0
  3. package/dist/client/index.d.ts +20 -0
  4. package/dist/client/index.d.ts.map +1 -0
  5. package/dist/client/index.js +310 -0
  6. package/dist/client/index.js.map +1 -0
  7. package/dist/client/layout-client.d.ts +51 -0
  8. package/dist/client/layout-client.d.ts.map +1 -0
  9. package/dist/electron-deck-DfFKeFEC.js +2295 -0
  10. package/dist/electron-deck-DfFKeFEC.js.map +1 -0
  11. package/dist/electron-deck.d.ts +57 -0
  12. package/dist/electron-deck.d.ts.map +1 -0
  13. package/dist/errors-NE2ig7L5.js +53 -0
  14. package/dist/errors-NE2ig7L5.js.map +1 -0
  15. package/dist/errors.d.ts +33 -0
  16. package/dist/errors.d.ts.map +1 -0
  17. package/dist/events.d.ts +14 -0
  18. package/dist/events.d.ts.map +1 -0
  19. package/dist/host/capability.d.ts +50 -0
  20. package/dist/host/capability.d.ts.map +1 -0
  21. package/dist/host/control-bus.d.ts +92 -0
  22. package/dist/host/control-bus.d.ts.map +1 -0
  23. package/dist/host/index.d.ts +42 -0
  24. package/dist/host/index.d.ts.map +1 -0
  25. package/dist/host/index.js +2 -0
  26. package/dist/index.d.ts +6 -0
  27. package/dist/index.d.ts.map +1 -0
  28. package/dist/index.js +3 -0
  29. package/dist/internal/deck-app.d.ts +415 -0
  30. package/dist/internal/deck-app.d.ts.map +1 -0
  31. package/dist/internal/electron-types.d.ts +131 -0
  32. package/dist/internal/electron-types.d.ts.map +1 -0
  33. package/dist/internal/event-bus.d.ts +29 -0
  34. package/dist/internal/event-bus.d.ts.map +1 -0
  35. package/dist/internal/ipc-registry-memory.d.ts +25 -0
  36. package/dist/internal/ipc-registry-memory.d.ts.map +1 -0
  37. package/dist/internal/lifecycle-manager.d.ts +28 -0
  38. package/dist/internal/lifecycle-manager.d.ts.map +1 -0
  39. package/dist/internal/resource-registry.d.ts +9 -0
  40. package/dist/internal/resource-registry.d.ts.map +1 -0
  41. package/dist/internal/trust-set.d.ts +53 -0
  42. package/dist/internal/trust-set.d.ts.map +1 -0
  43. package/dist/internal/wire-transport.d.ts +159 -0
  44. package/dist/internal/wire-transport.d.ts.map +1 -0
  45. package/dist/main/compositor.d.ts +123 -0
  46. package/dist/main/compositor.d.ts.map +1 -0
  47. package/dist/main/connection.d.ts +37 -0
  48. package/dist/main/connection.d.ts.map +1 -0
  49. package/dist/main/debug-tap.d.ts +55 -0
  50. package/dist/main/debug-tap.d.ts.map +1 -0
  51. package/dist/main/disposable.d.ts +18 -0
  52. package/dist/main/disposable.d.ts.map +1 -0
  53. package/dist/main/index.d.ts +16 -0
  54. package/dist/main/index.d.ts.map +1 -0
  55. package/dist/main/index.js +152 -0
  56. package/dist/main/index.js.map +1 -0
  57. package/dist/main/logger.d.ts +15 -0
  58. package/dist/main/logger.d.ts.map +1 -0
  59. package/dist/main/scope.d.ts +52 -0
  60. package/dist/main/scope.d.ts.map +1 -0
  61. package/dist/main/view-handle.d.ts +131 -0
  62. package/dist/main/view-handle.d.ts.map +1 -0
  63. package/dist/preload/index.cjs +105 -0
  64. package/dist/preload/index.cjs.map +1 -0
  65. package/dist/preload/index.d.ts +43 -0
  66. package/dist/preload/index.d.ts.map +1 -0
  67. package/dist/preload/index.js +80 -0
  68. package/dist/preload/index.js.map +1 -0
  69. package/dist/protocol-BJqo-vP2.js +23 -0
  70. package/dist/protocol-BJqo-vP2.js.map +1 -0
  71. package/dist/shared/protocol.d.ts +76 -0
  72. package/dist/shared/protocol.d.ts.map +1 -0
  73. package/dist/types.d.ts +504 -0
  74. package/dist/types.d.ts.map +1 -0
  75. package/dist/view-handle-CR-yWNfr.js +701 -0
  76. package/dist/view-handle-CR-yWNfr.js.map +1 -0
  77. package/package.json +86 -0
@@ -0,0 +1,9 @@
1
+ import type { Disposable, MaybePromise, ResourceRegistry } from '../types.js';
2
+ export declare class ResourceRegistryImpl implements ResourceRegistry {
3
+ private entries;
4
+ private nextId;
5
+ private allDisposed;
6
+ add(d: Disposable | (() => MaybePromise<void>)): Disposable;
7
+ disposeAll(): Promise<void>;
8
+ }
9
+ //# sourceMappingURL=resource-registry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resource-registry.d.ts","sourceRoot":"","sources":["../../src/internal/resource-registry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAA;AAyB7E,qBAAa,oBAAqB,YAAW,gBAAgB;IAC5D,OAAO,CAAC,OAAO,CAAc;IAC7B,OAAO,CAAC,MAAM,CAAI;IAClB,OAAO,CAAC,WAAW,CAAQ;IAE3B,GAAG,CAAC,CAAC,EAAE,UAAU,GAAG,CAAC,MAAM,YAAY,CAAC,IAAI,CAAC,CAAC,GAAG,UAAU;IAyBrD,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;CAqBjC"}
@@ -0,0 +1,53 @@
1
+ /**
2
+ * TrustSet — SEALED refcount membership of trusted webContents (P2).
3
+ *
4
+ * Refcount membership extracted from `DeckApp`'s former private `trustedWcRefs`
5
+ * (Map<wc, refcount>) so the same primitive can back both `DeckApp` and the
6
+ * domain-neutral `ControlBus` facade.
7
+ *
8
+ * SEALED WRITE MODEL: there is exactly ONE writer — `admit(wc, owner)`. It forces
9
+ * every trust lease to be OWNED by a `Scope`, so a lease can never outlive its
10
+ * owner and no un-owned / leaked lease is possible. The old public `add(wc)`
11
+ * (un-owned lease minting) and the `deleteEntry(wc)` imperative wipe are GONE:
12
+ * - releasing a lease early → use the Disposable `admit` returns;
13
+ * - releasing ALL of an owner's leases → close/reset that owner Scope (the
14
+ * refcount-- disposers are owned by it, so its teardown zeroes them).
15
+ *
16
+ * Semantics (unchanged refcount core):
17
+ * - `admit(wc, owner)` increments the wc's refcount and owns the matching
18
+ * refcount-- on `owner`; the entry is removed when the count reaches zero.
19
+ * - `isTrusted(id)` is true iff any live entry's `.id === id`.
20
+ * - `snapshot()` returns the live keys (used for event fanout).
21
+ *
22
+ * Idempotency note: each refcount-- disposer is one-shot and guarded by a
23
+ * `c === undefined` check — disposing a handle after the entry has already been
24
+ * dropped (e.g. via the owner's teardown) reads `refs.get(wc)`, sees `undefined`,
25
+ * and returns without touching the map, so a residual handle can never resurrect
26
+ * or mis-decrement a re-added entry.
27
+ *
28
+ * @internal
29
+ */
30
+ import type { Disposable } from '../types.js';
31
+ import type { MinimalWebContents } from './wire-transport.js';
32
+ import type { Scope } from '../main/scope.js';
33
+ /** Read-only trust index (gate + fanout). No write surface. */
34
+ export interface TrustIndex {
35
+ /** true iff some live entry's `.id === id`. */
36
+ isTrusted(id: number): boolean;
37
+ /** live keys, for event fanout. */
38
+ snapshot(): readonly MinimalWebContents[];
39
+ }
40
+ /** Admit-capable trust set. The ONLY writer is `admit`, which forces an owner
41
+ * Scope so a trust lease can never outlive its owner (no un-owned lease). */
42
+ export interface TrustSet extends TrustIndex {
43
+ /**
44
+ * Admit `wc` to the trust set (refcount++). The refcount-- release is OWNED by
45
+ * `owner` (so `owner.reset()`/`close()` releases it automatically). Returns a
46
+ * one-shot Disposable that releases JUST this lease early (idempotent; never
47
+ * double-decrements, and a later `owner` teardown over an already-released
48
+ * lease is a safe no-op via the refs-undefined guard).
49
+ */
50
+ admit(wc: MinimalWebContents, owner: Scope): Disposable;
51
+ }
52
+ export declare function createTrustSet(): TrustSet;
53
+ //# sourceMappingURL=trust-set.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"trust-set.d.ts","sourceRoot":"","sources":["../../src/internal/trust-set.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AAC7C,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAA;AAC7D,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAA;AAE7C,+DAA+D;AAC/D,MAAM,WAAW,UAAU;IAC1B,+CAA+C;IAC/C,SAAS,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAA;IAC9B,mCAAmC;IACnC,QAAQ,IAAI,SAAS,kBAAkB,EAAE,CAAA;CACzC;AAED;8EAC8E;AAC9E,MAAM,WAAW,QAAS,SAAQ,UAAU;IAC3C;;;;;;OAMG;IACH,KAAK,CAAC,EAAE,EAAE,kBAAkB,EAAE,KAAK,EAAE,KAAK,GAAG,UAAU,CAAA;CACvD;AAED,wBAAgB,cAAc,IAAI,QAAQ,CAwCzC"}
@@ -0,0 +1,159 @@
1
+ /**
2
+ * WireTransport — main 端的真 Electron wire 桥接(Phase 3b)。
3
+ *
4
+ * 责任:
5
+ * - 在 `ipcMain.handle('__electron-deck:invoke')` / `'__electron-deck:probe'` 上路由
6
+ * webview → main 的 RPC 请求,按 `senderPolicy` gating + kind 派发到 host /
7
+ * simulator handler,序列化成 `InvokeResponse` 帧。
8
+ * - 订阅 framework `EventBus`,把 declared `HostEvent.publish(payload)` 通过
9
+ * `webContents.send('__electron-deck:event', envelope)` 推送给所有 trusted
10
+ * webContents。
11
+ *
12
+ * 该类只持有 deps 引用,不直接 import 'electron';测试用 plain DI 注入 fake。
13
+ * 生命周期一次性:start → dispose 后不能再 start(避免 listener 泄露 / 句柄
14
+ * 双重注册);hot-restart 由 host 重新构造新实例。
15
+ *
16
+ * @internal
17
+ */
18
+ import type { JsonValue, SenderPolicy } from '../types.js';
19
+ import type { EventBus } from './event-bus.js';
20
+ /**
21
+ * Framework-reserved invoke failure codes —— 全部带 `DECK_` 前缀,避免与
22
+ * host 自定义 code 冲突。Client / host 都不应抛同前缀。
23
+ */
24
+ export declare const DECK_CODE: {
25
+ readonly UntrustedSender: "DECK_UNTRUSTED_SENDER";
26
+ readonly UntrustedFrame: "DECK_UNTRUSTED_FRAME";
27
+ readonly UnknownKind: "DECK_UNKNOWN_KIND";
28
+ readonly BadRequest: "DECK_BAD_REQUEST";
29
+ /** P4 Phase B — the grant gate denied a privileged command for this sender. */
30
+ readonly Forbidden: "DECK_FORBIDDEN";
31
+ };
32
+ /** Minimal RenderFrameHost identity used for main-frame discrimination. */
33
+ export interface FrameRef {
34
+ readonly routingId: number;
35
+ readonly processId: number;
36
+ }
37
+ /**
38
+ * Per-invoke context threaded from {@link WireTransport.handleInvoke} into the
39
+ * host/simulator invoke seams. Constructed ONLY after the wire's trust gate +
40
+ * main-frame gate have both passed, so `senderId` is a real trusted webContents
41
+ * id (never undefined). Phase B's grant gate (in `ControlBus.dispatch`) reads
42
+ * this; Phase A just plumbs it through as a REQUIRED param so a missing call
43
+ * site is a COMPILE error, not a silent security downgrade.
44
+ */
45
+ export interface InvokeCtx {
46
+ /** webContents id of the invoke sender (after the wire's trust + main-frame gates). */
47
+ readonly senderId: number;
48
+ /** sender frame ref; main-frame already validated. Kept for future per-frame use; may be null. */
49
+ readonly senderFrame: FrameRef | null;
50
+ }
51
+ export interface MinimalIpcMain {
52
+ handle(channel: string, handler: (event: {
53
+ sender: {
54
+ id: number;
55
+ mainFrame?: FrameRef | null;
56
+ };
57
+ senderFrame?: FrameRef | null;
58
+ }, ...args: unknown[]) => unknown | Promise<unknown>): void;
59
+ removeHandler(channel: string): void;
60
+ }
61
+ export interface MinimalWebContents {
62
+ readonly id: number;
63
+ isDestroyed(): boolean;
64
+ send(channel: string, payload: unknown): void;
65
+ }
66
+ export interface WireTransportDeps {
67
+ readonly ipcMain: MinimalIpcMain;
68
+ readonly bus: EventBus;
69
+ readonly senderPolicy: SenderPolicy;
70
+ /** 取当前 trusted webContents 快照;用于 event push 广播。lazy:每次 publish 重调。 */
71
+ readonly trustedWebContents: () => readonly MinimalWebContents[];
72
+ /** 路由 host kind 调用;handler 抛错由 WireTransport 接住 → InvokeFailure。
73
+ * `ctx` 必填:携带已过 trust + main-frame gate 的 senderId(Phase B 授权门读它)。 */
74
+ readonly invokeHost: (name: string, args: readonly JsonValue[], ctx: InvokeCtx) => Promise<JsonValue>;
75
+ /** 路由 simulator kind 调用;同上(`ctx` 必填)。 */
76
+ readonly invokeSimulator: (name: string, args: readonly JsonValue[], ctx: InvokeCtx) => Promise<JsonValue>;
77
+ /**
78
+ * 已声明的 event name 集合,作为 wire fanout 的 allowlist。**必填,
79
+ * default-deny** —— 未在该集合内的 event name 不会跨进程下发(防止
80
+ * framework 内部代码意外调 `bus.publish('foo')` 时 leak 给 webview)。
81
+ * 调用方需保证每次返回的是当前 declared event 名字列表(lazy 快照,每次
82
+ * publish 重读)。
83
+ */
84
+ readonly declaredEvents: () => readonly string[];
85
+ /**
86
+ * OPTIONAL slot-token inbound: `__electron-deck:place` apply path. When
87
+ * provided, `start()` registers the `Place` handler (same trust + main-frame
88
+ * gate as invoke). On gate pass → `onPlace(senderId, slotToken, placement)`.
89
+ */
90
+ readonly onPlace?: (senderId: number, slotToken: string, placement: unknown) => void;
91
+ /**
92
+ * OPTIONAL slot-token inbound: `__electron-deck:layout-subscribe` per-wc
93
+ * replay request. When provided, `start()` registers the `LayoutSubscribe`
94
+ * handler (same gate). On gate pass → `onLayoutSubscribe(senderId)`.
95
+ */
96
+ readonly onLayoutSubscribe?: (senderId: number) => void;
97
+ }
98
+ export declare class WireTransport {
99
+ private readonly deps;
100
+ private state;
101
+ private busSubscription;
102
+ /** Whether the optional Place / LayoutSubscribe handlers were registered
103
+ * (either eagerly at start() via deps, or lazily via armSlotChannels) — so
104
+ * dispose() only removes channels it actually registered. */
105
+ private placeRegistered;
106
+ private layoutSubscribeRegistered;
107
+ /** Effective slot callbacks. Populated from deps at start() (eager path, used
108
+ * by the wire unit tests) OR by {@link armSlotChannels} (lazy path, used by
109
+ * deck-app so an app with no anchored views registers only Invoke + Probe). */
110
+ private onPlaceCb;
111
+ private onLayoutSubscribeCb;
112
+ constructor(deps: WireTransportDeps);
113
+ start(): void;
114
+ private tryRemoveHandler;
115
+ private registerPlaceHandler;
116
+ private unregisterPlaceHandler;
117
+ private registerLayoutSubscribeHandler;
118
+ private unregisterLayoutSubscribeHandler;
119
+ /**
120
+ * Lazily register the slot-token inbound channels (`Place` + `LayoutSubscribe`)
121
+ * on an already-started wire, wiring them to the given callbacks. Idempotent:
122
+ * subsequent calls just refresh the callbacks (the handlers are registered
123
+ * once). deck-app calls this on the FIRST anchored `placeIn`, so an app with
124
+ * no anchored views never registers these channels (keeping the wire's
125
+ * handler footprint at Invoke + Probe until a slot is actually minted).
126
+ */
127
+ armSlotChannels(onPlace: (senderId: number, slotToken: string, placement: unknown) => void, onLayoutSubscribe: (senderId: number) => void): void;
128
+ dispose(): void;
129
+ private handleProbe;
130
+ /**
131
+ * Defense-in-depth main-frame check (mirrors devtools' verified
132
+ * `isMainFrameSender`). A trusted webContents may embed a sub-frame of
133
+ * arbitrary origin; only its top frame should reach gated invoke, so a
134
+ * sub-frame can't spoof the trusted sender id.
135
+ *
136
+ * - NEITHER field modeled → frame-unaware stub → not a real frame boundary;
137
+ * the sender-id gate is the boundary → allow (back-compat with the legacy
138
+ * `{ sender: { id } }` stubs).
139
+ * - either null (incl. a real event whose `senderFrame` resolved to null after
140
+ * navigate-after-send / frame destruction, or a partial/malformed event) →
141
+ * fail-closed → reject.
142
+ * - main frame (routingId + processId match) → allow; else (sub frame) reject.
143
+ */
144
+ private isMainFrameSender;
145
+ private handleInvoke;
146
+ /**
147
+ * `__electron-deck:place` inbound. Same gate as {@link handleInvoke}
148
+ * (trust + main-frame). On any failure (malformed payload, untrusted sender,
149
+ * sub-frame) → DROP silently (return undefined; never call `onPlace`).
150
+ */
151
+ private handlePlace;
152
+ /**
153
+ * `__electron-deck:layout-subscribe` inbound. Same gate as {@link handleInvoke}.
154
+ * On gate pass → `onLayoutSubscribe(senderId)`; else DROP silently.
155
+ */
156
+ private handleLayoutSubscribe;
157
+ private fanoutEvent;
158
+ }
159
+ //# sourceMappingURL=wire-transport.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wire-transport.d.ts","sourceRoot":"","sources":["../../src/internal/wire-transport.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,123 @@
1
+ /**
2
+ * `Compositor` — engine-agnostic z-order planner for a window's native child
3
+ * views (foundation: the spikes in `.repro/electron-deck-spikes/`).
4
+ *
5
+ * It separates INTENT (mount / unmount / reorder a view into a zone, at a
6
+ * relative position) from APPLICATION (`commit()` computes the minimal sequence
7
+ * of host add/remove calls that transforms the host's current child order into
8
+ * the target order).
9
+ *
10
+ * The host's observable z-semantics this planner is built on (from the spikes):
11
+ * - `addChildView` of an ALREADY-mounted child raises it to the top WITHOUT a
12
+ * remove first and WITHOUT reloading it.
13
+ * - `addChildView` of a NEW child appends it to the end (= topmost).
14
+ * - A batch of remove/add in ONE synchronous tick re-lands at the target order
15
+ * with zero renderer reloads.
16
+ * - `addChildView` into a destroyed contentView throws synchronously.
17
+ *
18
+ * Ordering model. Every mounted view carries `(zone, orderKey, viewId)`. The
19
+ * total render order is that triple, ascending: lower zone renders BELOW higher
20
+ * zone (zones stack), `orderKey` orders within a zone, and `viewId` is a pure
21
+ * tiebreak so the order is deterministic even if two keys collide. `orderKey` is
22
+ * a FRACTIONAL key: reorder-before(X) sets the moved view's key to the midpoint
23
+ * between X's key and its in-zone predecessor's key, so a reorder is O(1) and
24
+ * perturbs no other view's key. When repeated midpoints exhaust float precision
25
+ * in a gap, the affected zone is RENUMBERED (rebalance) to evenly-spaced integer
26
+ * keys — invisible, because the renumber preserves the existing relative order.
27
+ *
28
+ * mount epoch. A genuinely-new mount gets a fresh monotonically-increasing
29
+ * `mountSeq` and lands at the top of its zone. Re-mounting a view that is STILL
30
+ * mounted is a pure no-op (same orderKey/mountSeq, zero host churn). But an
31
+ * `unmount(id)` followed by `mount(id)` is a NEW instance: it gets a new
32
+ * mountSeq and lands at the top — it never resumes the old slot.
33
+ *
34
+ * commit. We fold the batch of intents into the FINAL target state (last-state,
35
+ * not a write-log replay), then diff the host's current children against that
36
+ * target. The longest increasing subsequence (LIS) of views already in correct
37
+ * relative order — computed over the current∩target intersection — is left
38
+ * untouched; every other shared view is `removeChildView` + `addChildView`, and
39
+ * each brand-new view gets one explicit `addChildView`. All in one synchronous
40
+ * pass. On failure `commit()` throws a typed {@link CommitError}: a destroyed
41
+ * host with additions pending throws `host-destroyed` BEFORE touching native
42
+ * (applied:false); a native call that throws mid-apply is caught and the host is
43
+ * rolled back to its pre-apply snapshot, then `apply-failed` is thrown with
44
+ * whether the rollback recovered the snapshot. A no-op commit, and a destroyed
45
+ * host with only removals pending, are SILENT (teardown-friendly).
46
+ */
47
+ /** A handle to a native child view. Identity is by `id`. */
48
+ export type NativeViewRef = {
49
+ readonly id: string;
50
+ };
51
+ /**
52
+ * Typed failure thrown by {@link Compositor.commit}. `kind` distinguishes a
53
+ * preflight refusal (`host-destroyed`, native byte-for-byte pre-commit,
54
+ * `applied:false`) from a mid-apply native throw (`apply-failed`,
55
+ * `applied:'partial'`); for `apply-failed`, `recovered` reports whether the
56
+ * best-effort rollback restored the pre-apply snapshot order (`false` ⇒ native
57
+ * is untrusted and the host must be treated as dead).
58
+ */
59
+ export declare class CommitError extends Error {
60
+ readonly kind: 'host-destroyed' | 'apply-failed';
61
+ readonly applied: false | 'partial';
62
+ readonly recovered?: boolean;
63
+ constructor(args: {
64
+ kind: 'host-destroyed' | 'apply-failed';
65
+ applied: false | 'partial';
66
+ recovered?: boolean;
67
+ message?: string;
68
+ });
69
+ }
70
+ /**
71
+ * The native content-view surface the Compositor drives. In production this is
72
+ * an Electron `contentView` (`addChildView` / `removeChildView`); in tests it is
73
+ * a faithful fake. `children()` returns the current order, LAST = topmost.
74
+ */
75
+ export interface ContentViewHost {
76
+ /** Already-mounted ref → raise to top (no remove, no reload); new ref →
77
+ * append to the end (top). */
78
+ addChildView(v: NativeViewRef): void;
79
+ removeChildView(v: NativeViewRef): void;
80
+ readonly isDestroyed: boolean;
81
+ /** Current child order; LAST element is the topmost. */
82
+ children(): readonly NativeViewRef[];
83
+ }
84
+ export interface Compositor {
85
+ /** Idempotent attach. A view STILL mounted → pure no-op (unchanged
86
+ * orderKey/mountSeq, zero host calls). A new view (or one re-mounted after
87
+ * unmount) → fresh mountSeq, lands at the top (end) of its zone. */
88
+ mount(view: NativeViewRef, opts?: {
89
+ zone?: number;
90
+ }): void;
91
+ /** Detach a view. A subsequent mount of the same id is a NEW instance. */
92
+ unmount(viewId: string): void;
93
+ /** Move a view: `before` slots it immediately before that anchor (midpoint of
94
+ * the anchor and its predecessor); `before: null` sends it to the end (top)
95
+ * of the zone. `zone` moves it to a different zone. An illegal `before`
96
+ * (unknown / unmounted id, or one whose zone conflicts with an explicit
97
+ * `zone`) throws SYNCHRONOUSLY. */
98
+ reorder(viewId: string, opts: {
99
+ zone?: number;
100
+ before?: string | null;
101
+ }): void;
102
+ /** Apply the folded target state to the host with the minimal add/remove
103
+ * sequence (LIS-preserving). Returns void on success. Throws a typed
104
+ * {@link CommitError} on failure: `kind:'host-destroyed'` (applied:false) when
105
+ * the host is destroyed and there are ADDITIONS to apply — thrown BEFORE
106
+ * touching native; `kind:'apply-failed'` (applied:'partial') when a native
107
+ * call throws mid-apply, after a best-effort rollback to the pre-apply
108
+ * snapshot (`recovered` flags whether the snapshot was restored). A no-op
109
+ * commit, and a destroyed host with ONLY removals pending, are SILENT. */
110
+ commit(): void;
111
+ /** Fold the intent state to EMPTY and commit, removing every native view
112
+ * this window's compositor mounted from the host. The resulting commit is
113
+ * REMOVALS-ONLY, so it reuses {@link commit}'s teardown-friendly
114
+ * "destroyed host + only removals → silent" path (A1.2.1): on an
115
+ * already-destroyed host it makes zero host calls and throws nothing.
116
+ *
117
+ * Optional on the interface so a partial test double (or a caller that only
118
+ * needs mount/unmount/reorder/commit) still structurally satisfies
119
+ * `Compositor`; the real {@link createCompositor} always implements it. */
120
+ detachAll?(): void;
121
+ }
122
+ export declare function createCompositor(host: ContentViewHost): Compositor;
123
+ //# sourceMappingURL=compositor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"compositor.d.ts","sourceRoot":"","sources":["../../src/main/compositor.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6CG;AAEH,4DAA4D;AAC5D,MAAM,MAAM,aAAa,GAAG;IAAE,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;CAAE,CAAA;AAEnD;;;;;;;GAOG;AACH,qBAAa,WAAY,SAAQ,KAAK;IACrC,QAAQ,CAAC,IAAI,EAAE,gBAAgB,GAAG,cAAc,CAAA;IAChD,QAAQ,CAAC,OAAO,EAAE,KAAK,GAAG,SAAS,CAAA;IACnC,QAAQ,CAAC,SAAS,CAAC,EAAE,OAAO,CAAA;gBAChB,IAAI,EAAE;QACjB,IAAI,EAAE,gBAAgB,GAAG,cAAc,CAAA;QACvC,OAAO,EAAE,KAAK,GAAG,SAAS,CAAA;QAC1B,SAAS,CAAC,EAAE,OAAO,CAAA;QACnB,OAAO,CAAC,EAAE,MAAM,CAAA;KAChB;CAOD;AAED;;;;GAIG;AACH,MAAM,WAAW,eAAe;IAC9B;kCAC8B;IAC9B,YAAY,CAAC,CAAC,EAAE,aAAa,GAAG,IAAI,CAAA;IACpC,eAAe,CAAC,CAAC,EAAE,aAAa,GAAG,IAAI,CAAA;IACvC,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAA;IAC7B,wDAAwD;IACxD,QAAQ,IAAI,SAAS,aAAa,EAAE,CAAA;CACrC;AAED,MAAM,WAAW,UAAU;IACzB;;wEAEoE;IACpE,KAAK,CAAC,IAAI,EAAE,aAAa,EAAE,IAAI,CAAC,EAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAA;IAC1D,0EAA0E;IAC1E,OAAO,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAA;IAC7B;;;;uCAImC;IACnC,OAAO,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,GAAG,IAAI,CAAA;IAC9E;;;;;;;8EAO0E;IAC1E,MAAM,IAAI,IAAI,CAAA;IACd;;;;;;;;+EAQ2E;IAC3E,SAAS,CAAC,IAAI,IAAI,CAAA;CACnB;AAkBD,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,eAAe,GAAG,UAAU,CA2PlE"}
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Connection-layer primitive (`Connection` / `ConnectionRegistry`) described in
3
+ * packages/electron-deck/docs/foundation.md §4.
4
+ *
5
+ * A `Connection` is one trusted webContents (keyed by the unforgeable `wc.id`).
6
+ * It holds a single `DisposableRegistry` as its "lifetime segment" container.
7
+ * The segment tears down deterministically (LIFO) on hard-destroy
8
+ * (`wc.once('destroyed')`) or soft-reuse (`reset(id)`):
9
+ * - hard destroy → dispose segment, fire 'closed' once, de-register.
10
+ * - soft reset → dispose segment, swap in a fresh registry, fire 'reset',
11
+ * connection stays alive & registered.
12
+ *
13
+ * §4.3: the terminal hook is `'destroyed'` (NOT `'render-process-gone'`), and
14
+ * `DisposableRegistry.disposeAll` is async (LIFO) — so close/reset are async.
15
+ */
16
+ import type { WebContents } from 'electron';
17
+ import { type Disposable } from './disposable.js';
18
+ export interface Connection {
19
+ readonly id: number;
20
+ readonly webContents: WebContents;
21
+ readonly alive: boolean;
22
+ /** Register a resource cleaned up with the current lifetime segment; the
23
+ * returned Disposable releases it early. */
24
+ own(d: Disposable | (() => void)): Disposable;
25
+ /** Connection-level lifecycle events; returns an unsubscribe Disposable. */
26
+ on(ev: 'reset' | 'closed', cb: () => void): Disposable;
27
+ }
28
+ export interface ConnectionRegistry {
29
+ /** Build/fetch the connection for a (trusted) webContents; idempotent. */
30
+ acquire(wc: WebContents): Connection;
31
+ get(id: number): Connection | undefined;
32
+ all(): readonly Connection[];
33
+ /** Soft reuse: dispose the current segment, then swap in a fresh registry. */
34
+ reset(id: number): void;
35
+ }
36
+ export declare function createConnectionRegistry(): ConnectionRegistry;
37
+ //# sourceMappingURL=connection.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"connection.d.ts","sourceRoot":"","sources":["../../src/main/connection.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,UAAU,CAAA;AAE3C,OAAO,EAAsB,KAAK,UAAU,EAAE,MAAM,iBAAiB,CAAA;AAKrE,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;IACnB,QAAQ,CAAC,WAAW,EAAE,WAAW,CAAA;IACjC,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAA;IACvB;gDAC4C;IAC5C,GAAG,CAAC,CAAC,EAAE,UAAU,GAAG,CAAC,MAAM,IAAI,CAAC,GAAG,UAAU,CAAA;IAC7C,4EAA4E;IAC5E,EAAE,CAAC,EAAE,EAAE,OAAO,GAAG,QAAQ,EAAE,EAAE,EAAE,MAAM,IAAI,GAAG,UAAU,CAAA;CACvD;AAED,MAAM,WAAW,kBAAkB;IACjC,0EAA0E;IAC1E,OAAO,CAAC,EAAE,EAAE,WAAW,GAAG,UAAU,CAAA;IACpC,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,UAAU,GAAG,SAAS,CAAA;IACvC,GAAG,IAAI,SAAS,UAAU,EAAE,CAAA;IAC5B,8EAA8E;IAC9E,KAAK,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAAA;CACxB;AAmBD,wBAAgB,wBAAwB,IAAI,kBAAkB,CAmJ7D"}
@@ -0,0 +1,55 @@
1
+ /**
2
+ * debugTap (foundation.md §7) — a flag-gated ring buffer for observing an
3
+ * IPC / cross-wc bridge message stream.
4
+ *
5
+ * Hung on a dispatch chokepoint (e.g. bridge-router's SERVICE_INVOKE /
6
+ * RENDER_INVOKE / PUBLISH / API_RESPONSE handlers), it records per-message
7
+ * metadata — connection id / channel / direction / appSession / duration /
8
+ * error — into a bounded ring so a hidden panel (or a test) can inspect the
9
+ * last N messages when debugging the cross-wc state machine.
10
+ *
11
+ * Design constraints:
12
+ * - Default OFF: `record()` is a near-free no-op when disabled, so the hot
13
+ * path costs nothing in production unless the flag is set.
14
+ * - Caller-supplied `ts`: the electron-deck package never calls `Date.now()`
15
+ * (determinism / resumability), so each entry carries a timestamp the
16
+ * caller stamps.
17
+ * - Bounded: a finite ring (default 1000) evicts oldest-first; it never grows
18
+ * without bound.
19
+ */
20
+ export interface DebugTapEntry {
21
+ /** Caller-stamped timestamp (ms). The primitive never reads the clock. */
22
+ ts: number;
23
+ /** The channel / message kind (e.g. 'SERVICE_INVOKE', 'API_RESPONSE'). */
24
+ channel: string;
25
+ /** Ingress (main received) vs egress (main sent). */
26
+ direction: 'in' | 'out';
27
+ /** webContents.id of the connection this message is attributed to. */
28
+ connectionId?: number;
29
+ /** bridge-router app session this message routes within, if known. */
30
+ appSessionId?: string;
31
+ /** For request/response pairs: how long the handler took (ms). */
32
+ durationMs?: number;
33
+ /** Error message if the message produced a failure. */
34
+ error?: string;
35
+ /** Short human-readable summary of the payload (no large blobs). */
36
+ summary?: string;
37
+ }
38
+ export interface DebugTap {
39
+ /** Whether recording is on. When false, `record` is a no-op. */
40
+ readonly enabled: boolean;
41
+ /** Append one entry to the ring (no-op when disabled). */
42
+ record(entry: DebugTapEntry): void;
43
+ /** Snapshot of buffered entries, oldest → newest. Safe to retain/mutate. */
44
+ entries(): readonly DebugTapEntry[];
45
+ /** Empty the ring. */
46
+ clear(): void;
47
+ }
48
+ export interface DebugTapOptions {
49
+ /** Default false — the tap is OFF unless explicitly enabled. */
50
+ enabled?: boolean;
51
+ /** Max retained entries; oldest evicted past this. Default 1000. */
52
+ capacity?: number;
53
+ }
54
+ export declare function createDebugTap(options?: DebugTapOptions): DebugTap;
55
+ //# sourceMappingURL=debug-tap.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"debug-tap.d.ts","sourceRoot":"","sources":["../../src/main/debug-tap.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,MAAM,WAAW,aAAa;IAC5B,0EAA0E;IAC1E,EAAE,EAAE,MAAM,CAAA;IACV,0EAA0E;IAC1E,OAAO,EAAE,MAAM,CAAA;IACf,qDAAqD;IACrD,SAAS,EAAE,IAAI,GAAG,KAAK,CAAA;IACvB,sEAAsE;IACtE,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,sEAAsE;IACtE,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,kEAAkE;IAClE,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,uDAAuD;IACvD,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,oEAAoE;IACpE,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB;AAED,MAAM,WAAW,QAAQ;IACvB,gEAAgE;IAChE,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAA;IACzB,0DAA0D;IAC1D,MAAM,CAAC,KAAK,EAAE,aAAa,GAAG,IAAI,CAAA;IAClC,4EAA4E;IAC5E,OAAO,IAAI,SAAS,aAAa,EAAE,CAAA;IACnC,sBAAsB;IACtB,KAAK,IAAI,IAAI,CAAA;CACd;AAED,MAAM,WAAW,eAAe;IAC9B,gEAAgE;IAChE,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,oEAAoE;IACpE,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB;AAID,wBAAgB,cAAc,CAAC,OAAO,GAAE,eAAoB,GAAG,QAAQ,CA8BtE"}
@@ -0,0 +1,18 @@
1
+ export interface Disposable {
2
+ dispose(): void | Promise<void>;
3
+ }
4
+ export type DisposeFn = () => void | Promise<void>;
5
+ export declare function toDisposable(fn: DisposeFn): Disposable;
6
+ export declare class DisposableRegistry implements Disposable {
7
+ private entries;
8
+ private _disposed;
9
+ /**
10
+ * Number of live entries. A wrapper's `dispose` splices its entry out and
11
+ * `disposeAll` clears the array, so `entries.length` is the live count.
12
+ */
13
+ get size(): number;
14
+ add(d: Disposable | DisposeFn): Disposable;
15
+ disposeAll(): Promise<void>;
16
+ dispose(): Promise<void>;
17
+ }
18
+ //# sourceMappingURL=disposable.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"disposable.d.ts","sourceRoot":"","sources":["../../src/main/disposable.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,UAAU;IACzB,OAAO,IAAI,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CAChC;AAED,MAAM,MAAM,SAAS,GAAG,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;AAElD,wBAAgB,YAAY,CAAC,EAAE,EAAE,SAAS,GAAG,UAAU,CAStD;AAOD,qBAAa,kBAAmB,YAAW,UAAU;IACnD,OAAO,CAAC,OAAO,CAAc;IAC7B,OAAO,CAAC,SAAS,CAAQ;IAEzB;;;OAGG;IACH,IAAI,IAAI,IAAI,MAAM,CAEjB;IAED,GAAG,CAAC,CAAC,EAAE,UAAU,GAAG,SAAS,GAAG,UAAU;IAmBpC,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAuBjC,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;CAGzB"}
@@ -0,0 +1,16 @@
1
+ /**
2
+ * `@dimina-kit/electron-deck/main` — main-process foundation primitives.
3
+ *
4
+ * The connection layer (foundation.md §4): one `Connection` per trusted
5
+ * webContents, owning a single `DisposableRegistry` lifetime segment that tears
6
+ * down deterministically on hard-destroy or soft-reuse. devtools (and qdmp)
7
+ * consume this as the substrate for connection-scoped resource ownership.
8
+ */
9
+ export { createConnectionRegistry, type Connection, type ConnectionRegistry, } from './connection.js';
10
+ export { DisposableRegistry, toDisposable, type Disposable, type DisposeFn, } from './disposable.js';
11
+ export { createScope, type Scope } from './scope.js';
12
+ export { createCompositor, CommitError, type Compositor, type ContentViewHost, type NativeViewRef, } from './compositor.js';
13
+ export { createViewHandle, type ViewHandle, type ViewHandleDeps, type NativeView, type PlaceTarget, type Placement, type Bounds, } from './view-handle.js';
14
+ export { createLogger, setLogLevel, type Logger } from './logger.js';
15
+ export { createDebugTap, type DebugTap, type DebugTapEntry, type DebugTapOptions, } from './debug-tap.js';
16
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/main/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EACL,wBAAwB,EACxB,KAAK,UAAU,EACf,KAAK,kBAAkB,GACxB,MAAM,iBAAiB,CAAA;AACxB,OAAO,EACL,kBAAkB,EAClB,YAAY,EACZ,KAAK,UAAU,EACf,KAAK,SAAS,GACf,MAAM,iBAAiB,CAAA;AACxB,OAAO,EAAE,WAAW,EAAE,KAAK,KAAK,EAAE,MAAM,YAAY,CAAA;AACpD,OAAO,EACL,gBAAgB,EAChB,WAAW,EACX,KAAK,UAAU,EACf,KAAK,eAAe,EACpB,KAAK,aAAa,GACnB,MAAM,iBAAiB,CAAA;AACxB,OAAO,EACL,gBAAgB,EAChB,KAAK,UAAU,EACf,KAAK,cAAc,EACnB,KAAK,UAAU,EACf,KAAK,WAAW,EAChB,KAAK,SAAS,EACd,KAAK,MAAM,GACZ,MAAM,kBAAkB,CAAA;AACzB,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,KAAK,MAAM,EAAE,MAAM,aAAa,CAAA;AACpE,OAAO,EACL,cAAc,EACd,KAAK,QAAQ,EACb,KAAK,aAAa,EAClB,KAAK,eAAe,GACrB,MAAM,gBAAgB,CAAA"}