@demlik/tea 0.3.0 → 0.5.0

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 (117) hide show
  1. package/dist/agent/index.d.ts +5 -5
  2. package/dist/agent/index.js +10 -10
  3. package/dist/authed-call/index.d.ts +2 -2
  4. package/dist/authed-call/index.js +7 -7
  5. package/dist/await-terminal/index.d.ts +2 -2
  6. package/dist/await-terminal/index.js +2 -2
  7. package/dist/batch-window/index.d.ts +3 -3
  8. package/dist/batch-window/index.js +3 -3
  9. package/dist/cache/index.d.ts +2 -2
  10. package/dist/cache/index.js +4 -4
  11. package/dist/{chunk-S4O26D7B.js → chunk-362SZ6CS.js} +3 -3
  12. package/dist/{chunk-S4O26D7B.js.map → chunk-362SZ6CS.js.map} +1 -1
  13. package/dist/{chunk-JFFVBEDI.js → chunk-62QBHM3T.js} +3 -3
  14. package/dist/{chunk-JFFVBEDI.js.map → chunk-62QBHM3T.js.map} +1 -1
  15. package/dist/{chunk-TPGASTFF.js → chunk-7A74NSG4.js} +3 -3
  16. package/dist/{chunk-TPGASTFF.js.map → chunk-7A74NSG4.js.map} +1 -1
  17. package/dist/{chunk-2KNLKQVN.js → chunk-7UU5P2CX.js} +3 -3
  18. package/dist/{chunk-2KNLKQVN.js.map → chunk-7UU5P2CX.js.map} +1 -1
  19. package/dist/{chunk-NPERUUUE.js → chunk-CIDS43FH.js} +3 -3
  20. package/dist/{chunk-NPERUUUE.js.map → chunk-CIDS43FH.js.map} +1 -1
  21. package/dist/{chunk-PBDYW2TU.js → chunk-DBOJ2UC6.js} +14 -3
  22. package/dist/chunk-DBOJ2UC6.js.map +1 -0
  23. package/dist/{chunk-WGGAMW7Y.js → chunk-KEFMI24G.js} +7 -7
  24. package/dist/{chunk-WGGAMW7Y.js.map → chunk-KEFMI24G.js.map} +1 -1
  25. package/dist/{chunk-MVY5DAMF.js → chunk-KWPB6BM6.js} +4 -4
  26. package/dist/{chunk-MVY5DAMF.js.map → chunk-KWPB6BM6.js.map} +1 -1
  27. package/dist/{chunk-C6QC273X.js → chunk-LFW66IIV.js} +4 -4
  28. package/dist/{chunk-C6QC273X.js.map → chunk-LFW66IIV.js.map} +1 -1
  29. package/dist/{chunk-BP2R7TPP.js → chunk-PBAYOJJO.js} +150 -38
  30. package/dist/chunk-PBAYOJJO.js.map +1 -0
  31. package/dist/{chunk-SK5JGUY7.js → chunk-PUTHY7LR.js} +3 -3
  32. package/dist/{chunk-SK5JGUY7.js.map → chunk-PUTHY7LR.js.map} +1 -1
  33. package/dist/{chunk-LYLDCIPH.js → chunk-QKUWDC4P.js} +32 -25
  34. package/dist/chunk-QKUWDC4P.js.map +1 -0
  35. package/dist/{chunk-VBUCHVCV.js → chunk-T6HDAPMR.js} +3 -3
  36. package/dist/{chunk-VBUCHVCV.js.map → chunk-T6HDAPMR.js.map} +1 -1
  37. package/dist/{chunk-IXBKFQH5.js → chunk-W2S4HK7J.js} +6 -6
  38. package/dist/{chunk-IXBKFQH5.js.map → chunk-W2S4HK7J.js.map} +1 -1
  39. package/dist/{chunk-CAXMQJJ3.js → chunk-ZILCH3WS.js} +4 -4
  40. package/dist/{chunk-CAXMQJJ3.js.map → chunk-ZILCH3WS.js.map} +1 -1
  41. package/dist/{chunk-RMX4EOQK.js → chunk-ZT3MGQB7.js} +3 -3
  42. package/dist/{chunk-RMX4EOQK.js.map → chunk-ZT3MGQB7.js.map} +1 -1
  43. package/dist/{core-42kapW0n.d.ts → core-DPYOyOJv.d.ts} +8 -5
  44. package/dist/deadline/index.d.ts +2 -2
  45. package/dist/deadline/index.js +3 -3
  46. package/dist/do/index.d.ts +6 -6
  47. package/dist/do/index.js +10 -10
  48. package/dist/{durable-effects-CFRngTNA.d.ts → durable-effects-B0YpoCHg.d.ts} +1 -1
  49. package/dist/extension/index.d.ts +2 -2
  50. package/dist/extension/index.js +3 -3
  51. package/dist/extension/react.js +3 -3
  52. package/dist/extension/subs/index.d.ts +2 -2
  53. package/dist/fan-out/index.d.ts +2 -2
  54. package/dist/idempotent-intake/index.d.ts +2 -2
  55. package/dist/index.d.ts +18 -4
  56. package/dist/index.js +2 -2
  57. package/dist/llm-call/index.d.ts +2 -2
  58. package/dist/llm-call/index.js +7 -7
  59. package/dist/{machine-B_BF4jij.d.ts → machine-Q_Bsm7Vu.d.ts} +1 -1
  60. package/dist/machine-viz/index.d.ts +1 -1
  61. package/dist/machine-viz/index.js +2 -2
  62. package/dist/mem/index.d.ts +2 -2
  63. package/dist/monitored-run/index.d.ts +3 -3
  64. package/dist/monitored-run/index.js +5 -5
  65. package/dist/node/index.d.ts +2 -2
  66. package/dist/paginated-walk/index.d.ts +2 -2
  67. package/dist/paginated-walk/index.js +7 -7
  68. package/dist/parity/index.d.ts +2 -2
  69. package/dist/parity/index.js +3 -3
  70. package/dist/pbt/arbitraries/index.d.ts +1 -1
  71. package/dist/pbt/index.d.ts +1 -1
  72. package/dist/pbt/index.js +3 -3
  73. package/dist/pbt/runners/index.d.ts +1 -1
  74. package/dist/pbt/runners/index.js +3 -3
  75. package/dist/poller/index.d.ts +2 -2
  76. package/dist/poller/index.js +3 -3
  77. package/dist/prediction/index.d.ts +1 -1
  78. package/dist/prediction/index.js +2 -2
  79. package/dist/pure/index.d.ts +1 -1
  80. package/dist/pure/index.js +2 -2
  81. package/dist/react/index.d.ts +9 -3
  82. package/dist/react/index.js +2 -2
  83. package/dist/react/index.js.map +1 -1
  84. package/dist/reconciler/index.d.ts +2 -2
  85. package/dist/reconciler/index.js +7 -7
  86. package/dist/recorder/index.d.ts +2 -2
  87. package/dist/resilient-call/index.d.ts +2 -2
  88. package/dist/resilient-call/index.js +6 -6
  89. package/dist/{runtime-types-gsog27b6.d.ts → runtime-types-uBuVvZqa.d.ts} +117 -3
  90. package/dist/saga/index.d.ts +2 -2
  91. package/dist/snapshot/index.d.ts +1 -1
  92. package/dist/snapshot/index.js +3 -3
  93. package/dist/subs/index.d.ts +16 -9
  94. package/dist/subs/index.js +3 -3
  95. package/dist/testing/index.d.ts +2 -2
  96. package/dist/testing/index.js +2 -2
  97. package/dist/throttled-input/index.d.ts +2 -2
  98. package/dist/throttled-input/index.js +6 -6
  99. package/dist/token-refresh/index.d.ts +1 -1
  100. package/dist/token-refresh/index.js +3 -3
  101. package/dist/trace-replay/index.d.ts +2 -2
  102. package/dist/trace-replay/index.js +3 -3
  103. package/dist/{types-KbL3W1mj.d.ts → types-C6mXL9wA.d.ts} +1 -1
  104. package/dist/with-deadline/index.d.ts +1 -1
  105. package/dist/with-deadline/index.js +2 -2
  106. package/dist/with-resilience/index.d.ts +2 -2
  107. package/dist/with-resilience/index.js +6 -6
  108. package/dist/with-telemetry/index.d.ts +1 -1
  109. package/dist/with-telemetry/index.js +2 -2
  110. package/dist/work-queue/adapter.d.ts +2 -2
  111. package/dist/work-queue/index.d.ts +2 -2
  112. package/dist/work-queue/ops.d.ts +2 -2
  113. package/dist/workflow/index.d.ts +3 -3
  114. package/package.json +1 -1
  115. package/dist/chunk-BP2R7TPP.js.map +0 -1
  116. package/dist/chunk-LYLDCIPH.js.map +0 -1
  117. package/dist/chunk-PBDYW2TU.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/extension/bridge.ts"],"names":[],"mappings":"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","file":"chunk-NPERUUUE.js","sourcesContent":["/**\n * Runtime bridge — expose a `@demlik/tea` runtime running in one chrome context\n * (typically the background service worker) to consumers in other contexts\n * (sidepanel, popup, devtools panel) over `chrome.runtime.sendMessage` /\n * `chrome.runtime.onMessage`.\n *\n * Two halves, one channel name string.\n *\n * ┌──── background (host) ────┐ ┌──── react surface ────┐\n * │ bridgeRuntime(rt, {ch}) │ chrome.* │ useBackgroundRuntime │\n * │ ├ observe → broadcast │ ──────────▶ │ ├ hydrate (initial) │\n * │ └ onMessage(\":dispatch\") │ ◀────────── │ └ dispatch (msgs) │\n * │ └ onMessage(\":hydrate\") │ │ │\n * └────────────────────────────┘ └───────────────────────┘\n *\n * Wire-shape (single `channel` string, three messages):\n *\n * { type: channel, msg, state } // background → surfaces (broadcast)\n * { type: `${channel}:hydrate` } // surface → background (request)\n * { type: `${channel}:dispatch`, msg } // surface → background (input)\n *\n * The `:hydrate` reply is the current `runtime.getState()` snapshot —\n * lets a newly-opened surface render the right view BEFORE the next\n * transition broadcast arrives.\n *\n * The bridge is intentionally agnostic to `chrome.runtime.sendMessage`\n * failure modes (no receiver, sender gone, etc.) — every cross-context send\n * is fire-and-forget with `.catch(() => {})`. Surfaces that need\n * delivery guarantees should layer their own retry, not bake it here.\n */\n\nimport { describeError } from \"../describe-error\";\nimport { type HistoryTracker, historyTracker, type Runtime } from \"../index\";\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Wire types — internal. Consumers don't import these directly; they pick\n// the channel string and the bridge handles serialization.\n// ─────────────────────────────────────────────────────────────────────────────\n\ninterface BroadcastEnvelope<S, M> {\n type: string; // === `channel`\n msg: M | null;\n state: S;\n}\n\ninterface DispatchEnvelope<M> {\n type: string; // === `${channel}:dispatch`\n msg: M;\n}\n\ninterface HydrateRequest {\n type: string; // === `${channel}:hydrate`\n}\n\n/**\n * Wire shape of one entry in the hydrate `backlog`. Mirrors a single\n * broadcast envelope, but without the `type` discriminator (the parent\n * `HydrateResponse` already identified the channel).\n */\ninterface BacklogEntry<V, M> {\n msg: M | null;\n state: V;\n}\n\n/**\n * Single source for the two derived wire-type strings. The `:dispatch` /\n * `:hydrate` suffix convention lives here and nowhere else — `bridgeRuntime`\n * and both clients derive their type constants from this so the convention\n * can't drift across the three functions (or the wire-shape comments above).\n */\nfunction channelTypes(channel: string): {\n dispatchType: string;\n hydrateType: string;\n} {\n return {\n dispatchType: `${channel}:dispatch`,\n hydrateType: `${channel}:hydrate`,\n };\n}\n\ninterface HydrateResponse<S, M> {\n state: S;\n /**\n * The runtime's history (most recent transitions, oldest first) projected\n * through `serialize` if provided. Present when the host runtime was\n * started with `historySize > 0`; an empty array otherwise. The surface\n * replays each entry through registered listeners before firing the\n * current-state marker, so a late subscriber sees the recent msg log AND\n * the current state on connect — not just the current state.\n */\n backlog: BacklogEntry<S, M>[];\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Background side — bridgeRuntime(runtime, { channel, serialize? })\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport interface BridgeRuntimeOpts<S, M, V = S> {\n channel: string;\n /**\n * REQUIRED parse for inbound `msg` payloads on `:dispatch` envelopes.\n * Chrome runtime messages are an `unknown` boundary (Inv 8) — the same\n * JSON/chrome serialization seam the symmetric client side parses via\n * `BridgeClientOpts.parseMsg`. An untrusted surface can drive the host\n * reducer with an arbitrary `M`, so the host parses too — never casts. A\n * `msg` that parses to `null` is dropped: the dispatch never reaches the\n * reducer and the surface's `dispatch()` rejects with `{ ok: false }`.\n *\n * Hosts that trust the dispatch channel and treat the msg as opaque opt\n * out EXPLICITLY by passing the {@link passThroughMsg} helper — the unsound\n * cast is then a visible call-site decision, not a silent default.\n */\n parseMsg: (raw: unknown) => M | null;\n /**\n * Optional projection from the runtime's live state `S` to a JSON-safe\n * view `V` that goes over the wire (broadcast + hydrate). Required when\n * `S` carries non-serializable values (DOM refs, Map, Date, …).\n *\n * Identity passthrough by default — existing callers with POJO state pass\n * `serialize` unset and `V = S`.\n *\n * Wire shape with `serialize`:\n * { type: channel, msg, state: serialize(currentState) } // broadcast\n * { type: hydrate } → { state: serialize(currentState) } // hydrate\n *\n * Dispatch is NOT affected — it carries `M` end-to-end. Surfaces parse the\n * `V` they receive (they should pick a type parameter matching the\n * projection).\n */\n serialize?: (state: S) => V;\n /**\n * If > 0, the bridge composes a `historyTracker(runtime, size)` over the\n * runtime and returns the captured `(msg, state)` backlog in every\n * `:hydrate` reply. Late subscribers receive recent traffic on connect —\n * the inspector then sees prior tool calls / phase transitions instead\n * of just the current state snapshot.\n *\n * Defaults to 0 (no tracker, empty backlog). Composition stays opt-in:\n * the substrate is untouched, the dispatch loop is untouched, and the\n * bridge attaches the tracker only when this opt is set. Cleanup is\n * the bridge's responsibility — the disposer returned by `bridgeRuntime`\n * stops the tracker along with the observer.\n *\n * Memory cost: ~size × (msg + state) bytes, in-memory only, dies with\n * the bridge. Privacy: backlog entries flow to every `:hydrate` reply —\n * any surface that can hydrate sees them. Do not enable on bridges\n * carrying user secrets.\n */\n historySize?: number;\n}\n\n/**\n * Install the bridge for a runtime. Returns a cleanup function that removes\n * the observer and the chrome listener.\n *\n * - **Broadcast:** every (msg, state) transition emits\n * `{ type: channel, msg, state }` via `chrome.runtime.sendMessage`. The\n * send is fire-and-forget — when no surface is listening, chrome.runtime\n * rejects with \"Could not establish connection. Receiving end does not\n * exist.\" which we swallow. If `serialize` is provided, `state` carries\n * `serialize(state)` instead of the raw state.\n * - **Hydrate:** `{ type: \":hydrate\" }` is answered via `sendResponse` with\n * the current (optionally projected) `runtime.getState()`. If the runtime\n * is mid-boot (store load not yet resolved), `getState()` throws — we\n * swallow and reply with `state: null`; surfaces should treat null as\n * \"not ready yet\" and wait for the first broadcast.\n * - **Dispatch:** `{ type: \":dispatch\", msg }` is parsed through `parseMsg`\n * (Inv 8 — inbound msgs are an `unknown` boundary) and, if non-null, routes\n * to `runtime.dispatch`. Returns `true` from the listener so chrome keeps the\n * message channel open until dispatch's promise settles (we ack with\n * `{ ok: true }`). A msg that fails the parse is dropped — the reducer never\n * sees it and we reply `{ ok: false }`.\n */\nexport function bridgeRuntime<S, M extends { type: string }, V = S>(\n runtime: Runtime<S, M>,\n { channel, parseMsg, serialize, historySize }: BridgeRuntimeOpts<S, M, V>,\n): () => void {\n const { dispatchType, hydrateType } = channelTypes(channel);\n // Default to identity passthrough when no projection is provided. The\n // type parameter defaults `V = S` so callers with no projection see no\n // change in the wire-shape — the broadcast still carries `S`.\n const project: (state: S) => V =\n serialize ?? ((state: S): V => state as unknown as V);\n // Compose a history tracker only when the caller opted in. Substrate\n // (`@demlik/tea`'s `run()`) knows nothing about history — the tracker is a\n // pure composition over `runtime.observe(...)`, owned by the bridge.\n // `null` when unset means \"no backlog in :hydrate replies\"; the wire\n // shape stays uniform (backlog is always an array, possibly empty).\n const tracker: HistoryTracker<S, M> | null =\n historySize !== undefined && historySize > 0\n ? historyTracker(runtime, historySize)\n : null;\n\n // The broadcast wire still carries `msg: M | null` — surfaces depend on the\n // boot frame (`msg: null`) to hydrate from the bridge. Applied transitions now\n // arrive via `observe` with a total `msg`; the boot frame arrives via `onBoot`\n // (#47). One helper builds the envelope for both.\n const sendBroadcast = (msg: M | null, state: S): void => {\n const envelope: BroadcastEnvelope<V, M | null> = {\n type: channel,\n msg,\n state: project(state),\n };\n chrome.runtime.sendMessage(envelope).catch(() => {\n // No surface listening — drop silently. Costs are negligible at\n // typical audit-event rates; surfaces poll on hydrate on open.\n });\n };\n const unobserve = runtime.observe((msg, state) => {\n sendBroadcast(msg, state);\n });\n const unboot = runtime.onBoot((state) => {\n sendBroadcast(null, state);\n });\n\n const onMessage = (\n message: unknown,\n _sender: chrome.runtime.MessageSender,\n sendResponse: (response: unknown) => void,\n ): boolean | undefined => {\n if (!message || typeof message !== \"object\") return undefined;\n const obj = message as Record<string, unknown>;\n\n if (obj.type === hydrateType) {\n let state: V | null;\n try {\n state = project(runtime.getState());\n } catch {\n // Runtime still booting (store.load pending). Surfaces fall back to\n // waiting for the first broadcast.\n state = null;\n }\n // Read the tracker's snapshot and project each entry's state through\n // `serialize`. When `historySize` was unset (tracker === null) we\n // send `[]` — uniform wire shape means surfaces never branch on\n // \"did the bridge enable history\" — they always iterate.\n const backlog: BacklogEntry<V, M>[] = tracker\n ? tracker\n .snapshot()\n .map((entry) => ({ msg: entry.msg, state: project(entry.state) }))\n : [];\n const response: HydrateResponse<V | null, M> = { state, backlog };\n sendResponse(response);\n return false;\n }\n\n if (obj.type === dispatchType) {\n // Inv 8: the inbound msg crosses the same JSON/chrome boundary the\n // client side parses — parse here too, never cast. A null parse drops\n // the dispatch (the reducer never sees an arbitrary/malformed `M`) and\n // the surface's dispatch() rejects on the `{ ok: false }` reply.\n const msg = parseMsg(obj.msg);\n if (msg === null) {\n sendResponse({ ok: false, error: \"dispatch msg failed parse\" });\n return false;\n }\n runtime\n .dispatch(msg)\n .then(() => {\n sendResponse({ ok: true });\n })\n .catch((err: unknown) => {\n sendResponse({\n ok: false,\n error: describeError(err),\n });\n });\n return true; // keep channel open for async sendResponse\n }\n\n return undefined;\n };\n\n chrome.runtime.onMessage.addListener(onMessage);\n\n return () => {\n unobserve();\n unboot();\n chrome.runtime.onMessage.removeListener(onMessage);\n // Detach the history observer too — leaving it attached after the\n // bridge tears down would silently retain memory and keep observing\n // a runtime whose backlog can no longer reach any consumer.\n tracker?.stop();\n };\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Surface side — the bridge half that lives in a React surface. Exported as\n// a pure helper so the React adapter (or a test) wires the listeners and the\n// hydrate round-trip with the same wire format the host emits.\n//\n// Note: this file deliberately has NO React import. The React hook lives in\n// `react.ts` and consumes these helpers. Keeps the bridge testable in plain\n// vitest without happy-dom + React renderers.\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * The explicit opt-out of inbound msg parsing: an identity pass-through that\n * casts the raw `unknown` to `M`. Surfaces that gate only on state and treat\n * the broadcast msg as advisory pass this as `parseMsg` to acknowledge — at\n * the call site, in the types — that they are NOT parsing the msg boundary.\n *\n * This is the same behavior the old optional-`parseMsg` default had, but the\n * unsound cast now lives at one named, greppable, opt-in seam instead of being\n * the silent default for everyone. (Inv 8: the boundary parses; this helper is\n * the documented exception, chosen explicitly.)\n */\nexport function passThroughMsg<M>(raw: unknown): M | null {\n return raw as M;\n}\n\nexport interface BridgeClient<S, M> {\n /** Send the hydrate request; resolves with the initial snapshot (or null\n * if the host is still booting OR the response failed to parse). Also\n * primes `getSnapshot()` so React's `useSyncExternalStore` sees the\n * hydrated value on the next render. */\n hydrate(): Promise<S | null>;\n /** Send a dispatch envelope; resolves once the host's dispatch settles. */\n dispatch(msg: M): Promise<void>;\n /** Subscribe to broadcasts. Returns a cleanup function. Broadcasts whose\n * payload fails `parseState` or `parseMsg` are dropped silently — the\n * listener is not called. Each successful broadcast updates\n * `getSnapshot()`. */\n subscribe(listener: (msg: M | null, state: S) => void): () => void;\n /** Synchronous accessor for the latest known state, or `null` until the\n * first successful hydrate or broadcast lands. Required reference stability\n * for `useSyncExternalStore`: returns the SAME object reference when the\n * underlying state hasn't changed since the prior call. */\n getSnapshot(): S | null;\n}\n\nexport interface BridgeClientOpts<S, M> {\n channel: string;\n /**\n * REQUIRED parse for inbound `state` payloads (from `:hydrate` reply AND\n * every broadcast). Chrome runtime messages are an `unknown` boundary\n * (Inv 8) — the surface is the only party that knows the domain type `S`,\n * so it owns the boundary parse. Returning `null` drops the payload\n * silently (subscriber not called; hydrate resolves to `null`). The\n * parser MUST NOT throw — model rejection as `null`.\n */\n parseState: (raw: unknown) => S | null;\n /**\n * REQUIRED parse for inbound `msg` payloads on broadcasts. Chrome runtime\n * messages are an `unknown` boundary (Inv 8) just like the `state` payload,\n * so the msg crosses the same JSON serialization seam and must be parsed —\n * never cast. Returning `null` drops the broadcast silently.\n *\n * Surfaces that treat the msg union as advisory and only gate on state opt\n * out EXPLICITLY by passing the {@link passThroughMsg} helper — the opt-out\n * is now a visible call-site decision, not a hidden default that lets an\n * unparsed `unknown` reach listeners typed as `M`.\n */\n parseMsg: (raw: unknown) => M | null;\n}\n\n/**\n * The state+msg boundary parse (Inv 8) for one inbound envelope, in ONE\n * place. Chrome runtime messages are an `unknown` boundary: a failed\n * `parseState` drops the envelope (null state is indistinguishable from\n * \"host still booting\" and is not a useful signal), and a non-null `msg`\n * that fails `parseMsg` drops it too. `msg: null | undefined` on the wire is\n * the boot/current-state marker and passes through as `null`.\n *\n * Returns `null` to signal \"drop\"; callers translate that to `return`\n * (live `subscribe`) or `continue` (backlog replay). Living here keeps the\n * live-broadcast and backlog-replay paths from drifting apart.\n */\nfunction parseEnvelope<S, M extends { type: string }>(\n obj: Record<string, unknown>,\n parseState: (raw: unknown) => S | null,\n parseMsg: (raw: unknown) => M | null,\n): { msg: M | null; state: S } | null {\n const state = parseState(obj.state);\n if (state === null) return null;\n let msg: M | null;\n if (obj.msg === null || obj.msg === undefined) {\n msg = null;\n } else {\n msg = parseMsg(obj.msg);\n if (msg === null) return null;\n }\n return { msg, state };\n}\n\n/**\n * Shared backlog-replay helper used by both bridge clients. Iterates raw\n * entries from the wire, runs each through the same boundary-parse rules a\n * live broadcast would (via {@link parseEnvelope}), and fires the listeners\n * with the parsed `(msg, state)` pair. Updates the closure-held `latestState`\n * via `commitState` so post-replay `getSnapshot()` returns the most recent\n * backlog state if the current-state marker is absent or unparseable.\n *\n * Extracted so the two clients can't drift in how they parse backlog\n * payloads — both must stay symmetric with their live `subscribe` parse.\n */\nfunction replayBacklog<S, M extends { type: string }>(\n raw: unknown,\n parseState: (raw: unknown) => S | null,\n parseMsg: (raw: unknown) => M | null,\n listeners: Set<(msg: M | null, state: S) => void>,\n commitState: (next: S) => void,\n): void {\n if (!Array.isArray(raw)) return;\n for (const entryRaw of raw) {\n if (!entryRaw || typeof entryRaw !== \"object\") continue;\n const entry = entryRaw as Record<string, unknown>;\n const parsed = parseEnvelope(entry, parseState, parseMsg);\n if (parsed === null) continue;\n commitState(parsed.state);\n for (const l of listeners) l(parsed.msg, parsed.state);\n }\n}\n\n/**\n * Options for the shared client factory. On top of the public\n * `BridgeClient` contract (channel + the two boundary parsers), the factory\n * takes the two axes on which the runtime and tab clients actually differ:\n *\n * - `send` abstracts the request transport — `chrome.runtime.sendMessage`\n * for the runtime client, `chrome.tabs.sendMessage(tabId, …)` for the tab\n * client — used by both `hydrate()` and `dispatch()`.\n * - `senderMatches` filters inbound broadcasts by their `sender`. Defaults\n * to accept-all (runtime client); the tab client passes\n * `sender => sender.tab?.id === tabId` so a per-tab client only sees its\n * own tab's broadcasts.\n */\ninterface MakeBridgeClientOpts<S, M> {\n channel: string;\n parseState: (raw: unknown) => S | null;\n parseMsg: (raw: unknown) => M | null;\n send: (message: unknown) => Promise<unknown>;\n senderMatches?: (sender: chrome.runtime.MessageSender) => boolean;\n}\n\n/**\n * The single client implementation behind {@link bridgeClient} and\n * {@link bridgeTabClient}. Both public constructors are thin wrappers that\n * pick the transport (`send`) and, for the tab client, the sender filter.\n * Everything else — the `latestState`/`listeners` closures, the hydrate\n * round-trip + backlog replay, dispatch, the live-broadcast parse, and\n * `getSnapshot` reference stability — lives here so the two clients cannot\n * drift.\n */\nfunction makeBridgeClient<S, M extends { type: string }>({\n channel,\n parseState,\n parseMsg,\n send,\n senderMatches = () => true,\n}: MakeBridgeClientOpts<S, M>): BridgeClient<S, M> {\n const { dispatchType, hydrateType } = channelTypes(channel);\n // Closure-held latest snapshot. `useSyncExternalStore` reads this via\n // `getSnapshot`; the reference is stable across calls until a hydrate\n // reply or broadcast overwrites it. Identity matters: returning a new\n // object for the same logical state would loop React. `null` is the\n // pre-hydrate sentinel.\n let latestState: S | null = null;\n // Each active subscribe() registers a listener. Hydrate replies notify\n // every subscriber with `msg: null` (no host transition occurred — the\n // change source is the hydrate round-trip) so `useSyncExternalStore`\n // sees the snapshot update on first mount.\n const listeners = new Set<(msg: M | null, state: S) => void>();\n\n const client: BridgeClient<S, M> = {\n async hydrate(): Promise<S | null> {\n const req: HydrateRequest = { type: hydrateType };\n const res = (await send(req)) as\n | { state: unknown; backlog?: unknown }\n | undefined;\n // Replay the backlog FIRST — listeners observing for a msg log see\n // historical (msg, state) pairs in oldest-first order before the\n // current-state marker. Backlog entries that fail boundary parse are\n // dropped silently (same posture as live broadcasts). When the host\n // didn't enable history, `backlog` is `[]` and this loop is a no-op.\n replayBacklog(res?.backlog, parseState, parseMsg, listeners, (next) => {\n latestState = next;\n });\n const raw = res?.state;\n if (raw === undefined || raw === null) return null;\n const parsed = parseState(raw);\n if (parsed === null) return null;\n latestState = parsed;\n // Fire the current-state marker (msg = null) — distinct from any\n // backlog entry. Surfaces that show \"X just connected\" key off this.\n for (const l of listeners) l(null, parsed);\n return parsed;\n },\n\n async dispatch(msg: M): Promise<void> {\n const env: DispatchEnvelope<M> = { type: dispatchType, msg };\n await send(env);\n },\n\n subscribe(listener: (msg: M | null, state: S) => void): () => void {\n listeners.add(listener);\n const onMessage = (\n message: unknown,\n sender: chrome.runtime.MessageSender,\n ): void => {\n if (!senderMatches(sender)) return;\n if (!message || typeof message !== \"object\") return;\n const obj = message as Record<string, unknown>;\n if (obj.type !== channel) return;\n const parsed = parseEnvelope(obj, parseState, parseMsg);\n if (parsed === null) return;\n latestState = parsed.state;\n listener(parsed.msg, parsed.state);\n };\n chrome.runtime.onMessage.addListener(onMessage);\n return () => {\n chrome.runtime.onMessage.removeListener(onMessage);\n listeners.delete(listener);\n };\n },\n\n getSnapshot(): S | null {\n return latestState;\n },\n };\n\n return client;\n}\n\nexport function bridgeClient<S, M extends { type: string }>({\n channel,\n parseState,\n parseMsg,\n}: BridgeClientOpts<S, M>): BridgeClient<S, M> {\n return makeBridgeClient({\n channel,\n parseState,\n parseMsg,\n send: (message) => chrome.runtime.sendMessage(message),\n });\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// SW-side client of a content-script-hosted runtime.\n//\n// `bridgeClient` above sends via `chrome.runtime.sendMessage`, which lands on\n// the SW's `chrome.runtime.onMessage`. That's the surface-to-SW direction.\n//\n// For the SW-to-content direction we need:\n// - SENDS via `chrome.tabs.sendMessage(tabId, …)` — `chrome.runtime.sendMessage`\n// does NOT reach content scripts running in a tab.\n// - BROADCAST subscribers must filter on `sender.tab?.id === tabId`, since\n// every tab's content script broadcasts through the same SW\n// `chrome.runtime.onMessage`. Without the filter, a SW client subscribed\n// to \"tab 17\" would also see broadcasts from tabs 22, 31, ….\n//\n// Shape mirrors `BridgeClient<S, M>` so consumers can pick the constructor by\n// context and use the rest of the surface identically.\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport interface BridgeTabClientOpts<S, M> {\n tabId: number;\n channel: string;\n /**\n * REQUIRED parse for inbound `state` payloads. See `BridgeClientOpts.parseState`\n * — same contract, same rationale. Chrome runtime messages from a tab are\n * an `unknown` boundary; the SW client owns the parse.\n */\n parseState: (raw: unknown) => S | null;\n /**\n * REQUIRED parse for inbound `msg` payloads. See `BridgeClientOpts.parseMsg`\n * — same contract, same rationale. Opt out of msg parsing with\n * {@link passThroughMsg}.\n */\n parseMsg: (raw: unknown) => M | null;\n}\n\n/**\n * SW-side client of a `bridgeRuntime` hosted in a content script.\n *\n * Identical contract to `bridgeClient` (same `BridgeClient<S, M>` shape).\n * Differences are confined to transport:\n *\n * - `hydrate()` / `dispatch(msg)` send via `chrome.tabs.sendMessage(tabId, …)`.\n * If the tab is gone, `chrome.tabs.sendMessage` rejects (same shape as\n * `chrome.runtime.sendMessage` with no receiver) — the promise propagates\n * the rejection. Callers decide whether to retry, drop, or surface; the\n * bridge has no opinion. Fire-and-forget posture matches `bridgeRuntime`.\n * - `subscribe(listener)` registers a `chrome.runtime.onMessage` listener\n * filtered by both `envelope.type === channel` AND `sender.tab?.id === tabId`,\n * so broadcasts from other tabs are ignored (no cross-contamination across\n * per-tab content-script runtimes).\n */\nexport function bridgeTabClient<S, M extends { type: string }>({\n tabId,\n channel,\n parseState,\n parseMsg,\n}: BridgeTabClientOpts<S, M>): BridgeClient<S, M> {\n return makeBridgeClient({\n channel,\n parseState,\n parseMsg,\n // SW → content transport: `chrome.runtime.sendMessage` does NOT reach a\n // tab's content script; `chrome.tabs.sendMessage(tabId, …)` does.\n send: (message) => chrome.tabs.sendMessage(tabId, message),\n // Every tab's content script broadcasts through the same SW\n // `chrome.runtime.onMessage`; filter to this tab so a client subscribed\n // to one tab never sees another's broadcasts.\n senderMatches: (sender) => sender.tab?.id === tabId,\n });\n}\n"]}
1
+ {"version":3,"sources":["../src/extension/bridge.ts"],"names":[],"mappings":"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","file":"chunk-CIDS43FH.js","sourcesContent":["/**\n * Runtime bridge — expose a `@demlik/tea` runtime running in one chrome context\n * (typically the background service worker) to consumers in other contexts\n * (sidepanel, popup, devtools panel) over `chrome.runtime.sendMessage` /\n * `chrome.runtime.onMessage`.\n *\n * Two halves, one channel name string.\n *\n * ┌──── background (host) ────┐ ┌──── react surface ────┐\n * │ bridgeRuntime(rt, {ch}) │ chrome.* │ useBackgroundRuntime │\n * │ ├ observe → broadcast │ ──────────▶ │ ├ hydrate (initial) │\n * │ └ onMessage(\":dispatch\") │ ◀────────── │ └ dispatch (msgs) │\n * │ └ onMessage(\":hydrate\") │ │ │\n * └────────────────────────────┘ └───────────────────────┘\n *\n * Wire-shape (single `channel` string, three messages):\n *\n * { type: channel, msg, state } // background → surfaces (broadcast)\n * { type: `${channel}:hydrate` } // surface → background (request)\n * { type: `${channel}:dispatch`, msg } // surface → background (input)\n *\n * The `:hydrate` reply is the current `runtime.getState()` snapshot —\n * lets a newly-opened surface render the right view BEFORE the next\n * transition broadcast arrives.\n *\n * The bridge is intentionally agnostic to `chrome.runtime.sendMessage`\n * failure modes (no receiver, sender gone, etc.) — every cross-context send\n * is fire-and-forget with `.catch(() => {})`. Surfaces that need\n * delivery guarantees should layer their own retry, not bake it here.\n */\n\nimport { describeError } from \"../describe-error\";\nimport { type HistoryTracker, historyTracker, type Runtime } from \"../index\";\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Wire types — internal. Consumers don't import these directly; they pick\n// the channel string and the bridge handles serialization.\n// ─────────────────────────────────────────────────────────────────────────────\n\ninterface BroadcastEnvelope<S, M> {\n type: string; // === `channel`\n msg: M | null;\n state: S;\n}\n\ninterface DispatchEnvelope<M> {\n type: string; // === `${channel}:dispatch`\n msg: M;\n}\n\ninterface HydrateRequest {\n type: string; // === `${channel}:hydrate`\n}\n\n/**\n * Wire shape of one entry in the hydrate `backlog`. Mirrors a single\n * broadcast envelope, but without the `type` discriminator (the parent\n * `HydrateResponse` already identified the channel).\n */\ninterface BacklogEntry<V, M> {\n msg: M | null;\n state: V;\n}\n\n/**\n * Single source for the two derived wire-type strings. The `:dispatch` /\n * `:hydrate` suffix convention lives here and nowhere else — `bridgeRuntime`\n * and both clients derive their type constants from this so the convention\n * can't drift across the three functions (or the wire-shape comments above).\n */\nfunction channelTypes(channel: string): {\n dispatchType: string;\n hydrateType: string;\n} {\n return {\n dispatchType: `${channel}:dispatch`,\n hydrateType: `${channel}:hydrate`,\n };\n}\n\ninterface HydrateResponse<S, M> {\n state: S;\n /**\n * The runtime's history (most recent transitions, oldest first) projected\n * through `serialize` if provided. Present when the host runtime was\n * started with `historySize > 0`; an empty array otherwise. The surface\n * replays each entry through registered listeners before firing the\n * current-state marker, so a late subscriber sees the recent msg log AND\n * the current state on connect — not just the current state.\n */\n backlog: BacklogEntry<S, M>[];\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Background side — bridgeRuntime(runtime, { channel, serialize? })\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport interface BridgeRuntimeOpts<S, M, V = S> {\n channel: string;\n /**\n * REQUIRED parse for inbound `msg` payloads on `:dispatch` envelopes.\n * Chrome runtime messages are an `unknown` boundary (Inv 8) — the same\n * JSON/chrome serialization seam the symmetric client side parses via\n * `BridgeClientOpts.parseMsg`. An untrusted surface can drive the host\n * reducer with an arbitrary `M`, so the host parses too — never casts. A\n * `msg` that parses to `null` is dropped: the dispatch never reaches the\n * reducer and the surface's `dispatch()` rejects with `{ ok: false }`.\n *\n * Hosts that trust the dispatch channel and treat the msg as opaque opt\n * out EXPLICITLY by passing the {@link passThroughMsg} helper — the unsound\n * cast is then a visible call-site decision, not a silent default.\n */\n parseMsg: (raw: unknown) => M | null;\n /**\n * Optional projection from the runtime's live state `S` to a JSON-safe\n * view `V` that goes over the wire (broadcast + hydrate). Required when\n * `S` carries non-serializable values (DOM refs, Map, Date, …).\n *\n * Identity passthrough by default — existing callers with POJO state pass\n * `serialize` unset and `V = S`.\n *\n * Wire shape with `serialize`:\n * { type: channel, msg, state: serialize(currentState) } // broadcast\n * { type: hydrate } → { state: serialize(currentState) } // hydrate\n *\n * Dispatch is NOT affected — it carries `M` end-to-end. Surfaces parse the\n * `V` they receive (they should pick a type parameter matching the\n * projection).\n */\n serialize?: (state: S) => V;\n /**\n * If > 0, the bridge composes a `historyTracker(runtime, size)` over the\n * runtime and returns the captured `(msg, state)` backlog in every\n * `:hydrate` reply. Late subscribers receive recent traffic on connect —\n * the inspector then sees prior tool calls / phase transitions instead\n * of just the current state snapshot.\n *\n * Defaults to 0 (no tracker, empty backlog). Composition stays opt-in:\n * the substrate is untouched, the dispatch loop is untouched, and the\n * bridge attaches the tracker only when this opt is set. Cleanup is\n * the bridge's responsibility — the disposer returned by `bridgeRuntime`\n * stops the tracker along with the observer.\n *\n * Memory cost: ~size × (msg + state) bytes, in-memory only, dies with\n * the bridge. Privacy: backlog entries flow to every `:hydrate` reply —\n * any surface that can hydrate sees them. Do not enable on bridges\n * carrying user secrets.\n */\n historySize?: number;\n}\n\n/**\n * Install the bridge for a runtime. Returns a cleanup function that removes\n * the observer and the chrome listener.\n *\n * - **Broadcast:** every (msg, state) transition emits\n * `{ type: channel, msg, state }` via `chrome.runtime.sendMessage`. The\n * send is fire-and-forget — when no surface is listening, chrome.runtime\n * rejects with \"Could not establish connection. Receiving end does not\n * exist.\" which we swallow. If `serialize` is provided, `state` carries\n * `serialize(state)` instead of the raw state.\n * - **Hydrate:** `{ type: \":hydrate\" }` is answered via `sendResponse` with\n * the current (optionally projected) `runtime.getState()`. If the runtime\n * is mid-boot (store load not yet resolved), `getState()` throws — we\n * swallow and reply with `state: null`; surfaces should treat null as\n * \"not ready yet\" and wait for the first broadcast.\n * - **Dispatch:** `{ type: \":dispatch\", msg }` is parsed through `parseMsg`\n * (Inv 8 — inbound msgs are an `unknown` boundary) and, if non-null, routes\n * to `runtime.dispatch`. Returns `true` from the listener so chrome keeps the\n * message channel open until dispatch's promise settles (we ack with\n * `{ ok: true }`). A msg that fails the parse is dropped — the reducer never\n * sees it and we reply `{ ok: false }`.\n */\nexport function bridgeRuntime<S, M extends { type: string }, V = S>(\n runtime: Runtime<S, M>,\n { channel, parseMsg, serialize, historySize }: BridgeRuntimeOpts<S, M, V>,\n): () => void {\n const { dispatchType, hydrateType } = channelTypes(channel);\n // Default to identity passthrough when no projection is provided. The\n // type parameter defaults `V = S` so callers with no projection see no\n // change in the wire-shape — the broadcast still carries `S`.\n const project: (state: S) => V =\n serialize ?? ((state: S): V => state as unknown as V);\n // Compose a history tracker only when the caller opted in. Substrate\n // (`@demlik/tea`'s `run()`) knows nothing about history — the tracker is a\n // pure composition over `runtime.observe(...)`, owned by the bridge.\n // `null` when unset means \"no backlog in :hydrate replies\"; the wire\n // shape stays uniform (backlog is always an array, possibly empty).\n const tracker: HistoryTracker<S, M> | null =\n historySize !== undefined && historySize > 0\n ? historyTracker(runtime, historySize)\n : null;\n\n // The broadcast wire still carries `msg: M | null` — surfaces depend on the\n // boot frame (`msg: null`) to hydrate from the bridge. Applied transitions now\n // arrive via `observe` with a total `msg`; the boot frame arrives via `onBoot`\n // (#47). One helper builds the envelope for both.\n const sendBroadcast = (msg: M | null, state: S): void => {\n const envelope: BroadcastEnvelope<V, M | null> = {\n type: channel,\n msg,\n state: project(state),\n };\n chrome.runtime.sendMessage(envelope).catch(() => {\n // No surface listening — drop silently. Costs are negligible at\n // typical audit-event rates; surfaces poll on hydrate on open.\n });\n };\n const unobserve = runtime.observe((msg, state) => {\n sendBroadcast(msg, state);\n });\n const unboot = runtime.onBoot((state) => {\n sendBroadcast(null, state);\n });\n\n const onMessage = (\n message: unknown,\n _sender: chrome.runtime.MessageSender,\n sendResponse: (response: unknown) => void,\n ): boolean | undefined => {\n if (!message || typeof message !== \"object\") return undefined;\n const obj = message as Record<string, unknown>;\n\n if (obj.type === hydrateType) {\n let state: V | null;\n try {\n state = project(runtime.getState());\n } catch {\n // Runtime still booting (store.load pending). Surfaces fall back to\n // waiting for the first broadcast.\n state = null;\n }\n // Read the tracker's snapshot and project each entry's state through\n // `serialize`. When `historySize` was unset (tracker === null) we\n // send `[]` — uniform wire shape means surfaces never branch on\n // \"did the bridge enable history\" — they always iterate.\n const backlog: BacklogEntry<V, M>[] = tracker\n ? tracker\n .snapshot()\n .map((entry) => ({ msg: entry.msg, state: project(entry.state) }))\n : [];\n const response: HydrateResponse<V | null, M> = { state, backlog };\n sendResponse(response);\n return false;\n }\n\n if (obj.type === dispatchType) {\n // Inv 8: the inbound msg crosses the same JSON/chrome boundary the\n // client side parses — parse here too, never cast. A null parse drops\n // the dispatch (the reducer never sees an arbitrary/malformed `M`) and\n // the surface's dispatch() rejects on the `{ ok: false }` reply.\n const msg = parseMsg(obj.msg);\n if (msg === null) {\n sendResponse({ ok: false, error: \"dispatch msg failed parse\" });\n return false;\n }\n runtime\n .dispatch(msg)\n .then(() => {\n sendResponse({ ok: true });\n })\n .catch((err: unknown) => {\n sendResponse({\n ok: false,\n error: describeError(err),\n });\n });\n return true; // keep channel open for async sendResponse\n }\n\n return undefined;\n };\n\n chrome.runtime.onMessage.addListener(onMessage);\n\n return () => {\n unobserve();\n unboot();\n chrome.runtime.onMessage.removeListener(onMessage);\n // Detach the history observer too — leaving it attached after the\n // bridge tears down would silently retain memory and keep observing\n // a runtime whose backlog can no longer reach any consumer.\n tracker?.stop();\n };\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Surface side — the bridge half that lives in a React surface. Exported as\n// a pure helper so the React adapter (or a test) wires the listeners and the\n// hydrate round-trip with the same wire format the host emits.\n//\n// Note: this file deliberately has NO React import. The React hook lives in\n// `react.ts` and consumes these helpers. Keeps the bridge testable in plain\n// vitest without happy-dom + React renderers.\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * The explicit opt-out of inbound msg parsing: an identity pass-through that\n * casts the raw `unknown` to `M`. Surfaces that gate only on state and treat\n * the broadcast msg as advisory pass this as `parseMsg` to acknowledge — at\n * the call site, in the types — that they are NOT parsing the msg boundary.\n *\n * This is the same behavior the old optional-`parseMsg` default had, but the\n * unsound cast now lives at one named, greppable, opt-in seam instead of being\n * the silent default for everyone. (Inv 8: the boundary parses; this helper is\n * the documented exception, chosen explicitly.)\n */\nexport function passThroughMsg<M>(raw: unknown): M | null {\n return raw as M;\n}\n\nexport interface BridgeClient<S, M> {\n /** Send the hydrate request; resolves with the initial snapshot (or null\n * if the host is still booting OR the response failed to parse). Also\n * primes `getSnapshot()` so React's `useSyncExternalStore` sees the\n * hydrated value on the next render. */\n hydrate(): Promise<S | null>;\n /** Send a dispatch envelope; resolves once the host's dispatch settles. */\n dispatch(msg: M): Promise<void>;\n /** Subscribe to broadcasts. Returns a cleanup function. Broadcasts whose\n * payload fails `parseState` or `parseMsg` are dropped silently — the\n * listener is not called. Each successful broadcast updates\n * `getSnapshot()`. */\n subscribe(listener: (msg: M | null, state: S) => void): () => void;\n /** Synchronous accessor for the latest known state, or `null` until the\n * first successful hydrate or broadcast lands. Required reference stability\n * for `useSyncExternalStore`: returns the SAME object reference when the\n * underlying state hasn't changed since the prior call. */\n getSnapshot(): S | null;\n}\n\nexport interface BridgeClientOpts<S, M> {\n channel: string;\n /**\n * REQUIRED parse for inbound `state` payloads (from `:hydrate` reply AND\n * every broadcast). Chrome runtime messages are an `unknown` boundary\n * (Inv 8) — the surface is the only party that knows the domain type `S`,\n * so it owns the boundary parse. Returning `null` drops the payload\n * silently (subscriber not called; hydrate resolves to `null`). The\n * parser MUST NOT throw — model rejection as `null`.\n */\n parseState: (raw: unknown) => S | null;\n /**\n * REQUIRED parse for inbound `msg` payloads on broadcasts. Chrome runtime\n * messages are an `unknown` boundary (Inv 8) just like the `state` payload,\n * so the msg crosses the same JSON serialization seam and must be parsed —\n * never cast. Returning `null` drops the broadcast silently.\n *\n * Surfaces that treat the msg union as advisory and only gate on state opt\n * out EXPLICITLY by passing the {@link passThroughMsg} helper — the opt-out\n * is now a visible call-site decision, not a hidden default that lets an\n * unparsed `unknown` reach listeners typed as `M`.\n */\n parseMsg: (raw: unknown) => M | null;\n}\n\n/**\n * The state+msg boundary parse (Inv 8) for one inbound envelope, in ONE\n * place. Chrome runtime messages are an `unknown` boundary: a failed\n * `parseState` drops the envelope (null state is indistinguishable from\n * \"host still booting\" and is not a useful signal), and a non-null `msg`\n * that fails `parseMsg` drops it too. `msg: null | undefined` on the wire is\n * the boot/current-state marker and passes through as `null`.\n *\n * Returns `null` to signal \"drop\"; callers translate that to `return`\n * (live `subscribe`) or `continue` (backlog replay). Living here keeps the\n * live-broadcast and backlog-replay paths from drifting apart.\n */\nfunction parseEnvelope<S, M extends { type: string }>(\n obj: Record<string, unknown>,\n parseState: (raw: unknown) => S | null,\n parseMsg: (raw: unknown) => M | null,\n): { msg: M | null; state: S } | null {\n const state = parseState(obj.state);\n if (state === null) return null;\n let msg: M | null;\n if (obj.msg === null || obj.msg === undefined) {\n msg = null;\n } else {\n msg = parseMsg(obj.msg);\n if (msg === null) return null;\n }\n return { msg, state };\n}\n\n/**\n * Shared backlog-replay helper used by both bridge clients. Iterates raw\n * entries from the wire, runs each through the same boundary-parse rules a\n * live broadcast would (via {@link parseEnvelope}), and fires the listeners\n * with the parsed `(msg, state)` pair. Updates the closure-held `latestState`\n * via `commitState` so post-replay `getSnapshot()` returns the most recent\n * backlog state if the current-state marker is absent or unparseable.\n *\n * Extracted so the two clients can't drift in how they parse backlog\n * payloads — both must stay symmetric with their live `subscribe` parse.\n */\nfunction replayBacklog<S, M extends { type: string }>(\n raw: unknown,\n parseState: (raw: unknown) => S | null,\n parseMsg: (raw: unknown) => M | null,\n listeners: Set<(msg: M | null, state: S) => void>,\n commitState: (next: S) => void,\n): void {\n if (!Array.isArray(raw)) return;\n for (const entryRaw of raw) {\n if (!entryRaw || typeof entryRaw !== \"object\") continue;\n const entry = entryRaw as Record<string, unknown>;\n const parsed = parseEnvelope(entry, parseState, parseMsg);\n if (parsed === null) continue;\n commitState(parsed.state);\n for (const l of listeners) l(parsed.msg, parsed.state);\n }\n}\n\n/**\n * Options for the shared client factory. On top of the public\n * `BridgeClient` contract (channel + the two boundary parsers), the factory\n * takes the two axes on which the runtime and tab clients actually differ:\n *\n * - `send` abstracts the request transport — `chrome.runtime.sendMessage`\n * for the runtime client, `chrome.tabs.sendMessage(tabId, …)` for the tab\n * client — used by both `hydrate()` and `dispatch()`.\n * - `senderMatches` filters inbound broadcasts by their `sender`. Defaults\n * to accept-all (runtime client); the tab client passes\n * `sender => sender.tab?.id === tabId` so a per-tab client only sees its\n * own tab's broadcasts.\n */\ninterface MakeBridgeClientOpts<S, M> {\n channel: string;\n parseState: (raw: unknown) => S | null;\n parseMsg: (raw: unknown) => M | null;\n send: (message: unknown) => Promise<unknown>;\n senderMatches?: (sender: chrome.runtime.MessageSender) => boolean;\n}\n\n/**\n * The single client implementation behind {@link bridgeClient} and\n * {@link bridgeTabClient}. Both public constructors are thin wrappers that\n * pick the transport (`send`) and, for the tab client, the sender filter.\n * Everything else — the `latestState`/`listeners` closures, the hydrate\n * round-trip + backlog replay, dispatch, the live-broadcast parse, and\n * `getSnapshot` reference stability — lives here so the two clients cannot\n * drift.\n */\nfunction makeBridgeClient<S, M extends { type: string }>({\n channel,\n parseState,\n parseMsg,\n send,\n senderMatches = () => true,\n}: MakeBridgeClientOpts<S, M>): BridgeClient<S, M> {\n const { dispatchType, hydrateType } = channelTypes(channel);\n // Closure-held latest snapshot. `useSyncExternalStore` reads this via\n // `getSnapshot`; the reference is stable across calls until a hydrate\n // reply or broadcast overwrites it. Identity matters: returning a new\n // object for the same logical state would loop React. `null` is the\n // pre-hydrate sentinel.\n let latestState: S | null = null;\n // Each active subscribe() registers a listener. Hydrate replies notify\n // every subscriber with `msg: null` (no host transition occurred — the\n // change source is the hydrate round-trip) so `useSyncExternalStore`\n // sees the snapshot update on first mount.\n const listeners = new Set<(msg: M | null, state: S) => void>();\n\n const client: BridgeClient<S, M> = {\n async hydrate(): Promise<S | null> {\n const req: HydrateRequest = { type: hydrateType };\n const res = (await send(req)) as\n | { state: unknown; backlog?: unknown }\n | undefined;\n // Replay the backlog FIRST — listeners observing for a msg log see\n // historical (msg, state) pairs in oldest-first order before the\n // current-state marker. Backlog entries that fail boundary parse are\n // dropped silently (same posture as live broadcasts). When the host\n // didn't enable history, `backlog` is `[]` and this loop is a no-op.\n replayBacklog(res?.backlog, parseState, parseMsg, listeners, (next) => {\n latestState = next;\n });\n const raw = res?.state;\n if (raw === undefined || raw === null) return null;\n const parsed = parseState(raw);\n if (parsed === null) return null;\n latestState = parsed;\n // Fire the current-state marker (msg = null) — distinct from any\n // backlog entry. Surfaces that show \"X just connected\" key off this.\n for (const l of listeners) l(null, parsed);\n return parsed;\n },\n\n async dispatch(msg: M): Promise<void> {\n const env: DispatchEnvelope<M> = { type: dispatchType, msg };\n await send(env);\n },\n\n subscribe(listener: (msg: M | null, state: S) => void): () => void {\n listeners.add(listener);\n const onMessage = (\n message: unknown,\n sender: chrome.runtime.MessageSender,\n ): void => {\n if (!senderMatches(sender)) return;\n if (!message || typeof message !== \"object\") return;\n const obj = message as Record<string, unknown>;\n if (obj.type !== channel) return;\n const parsed = parseEnvelope(obj, parseState, parseMsg);\n if (parsed === null) return;\n latestState = parsed.state;\n listener(parsed.msg, parsed.state);\n };\n chrome.runtime.onMessage.addListener(onMessage);\n return () => {\n chrome.runtime.onMessage.removeListener(onMessage);\n listeners.delete(listener);\n };\n },\n\n getSnapshot(): S | null {\n return latestState;\n },\n };\n\n return client;\n}\n\nexport function bridgeClient<S, M extends { type: string }>({\n channel,\n parseState,\n parseMsg,\n}: BridgeClientOpts<S, M>): BridgeClient<S, M> {\n return makeBridgeClient({\n channel,\n parseState,\n parseMsg,\n send: (message) => chrome.runtime.sendMessage(message),\n });\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// SW-side client of a content-script-hosted runtime.\n//\n// `bridgeClient` above sends via `chrome.runtime.sendMessage`, which lands on\n// the SW's `chrome.runtime.onMessage`. That's the surface-to-SW direction.\n//\n// For the SW-to-content direction we need:\n// - SENDS via `chrome.tabs.sendMessage(tabId, …)` — `chrome.runtime.sendMessage`\n// does NOT reach content scripts running in a tab.\n// - BROADCAST subscribers must filter on `sender.tab?.id === tabId`, since\n// every tab's content script broadcasts through the same SW\n// `chrome.runtime.onMessage`. Without the filter, a SW client subscribed\n// to \"tab 17\" would also see broadcasts from tabs 22, 31, ….\n//\n// Shape mirrors `BridgeClient<S, M>` so consumers can pick the constructor by\n// context and use the rest of the surface identically.\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport interface BridgeTabClientOpts<S, M> {\n tabId: number;\n channel: string;\n /**\n * REQUIRED parse for inbound `state` payloads. See `BridgeClientOpts.parseState`\n * — same contract, same rationale. Chrome runtime messages from a tab are\n * an `unknown` boundary; the SW client owns the parse.\n */\n parseState: (raw: unknown) => S | null;\n /**\n * REQUIRED parse for inbound `msg` payloads. See `BridgeClientOpts.parseMsg`\n * — same contract, same rationale. Opt out of msg parsing with\n * {@link passThroughMsg}.\n */\n parseMsg: (raw: unknown) => M | null;\n}\n\n/**\n * SW-side client of a `bridgeRuntime` hosted in a content script.\n *\n * Identical contract to `bridgeClient` (same `BridgeClient<S, M>` shape).\n * Differences are confined to transport:\n *\n * - `hydrate()` / `dispatch(msg)` send via `chrome.tabs.sendMessage(tabId, …)`.\n * If the tab is gone, `chrome.tabs.sendMessage` rejects (same shape as\n * `chrome.runtime.sendMessage` with no receiver) — the promise propagates\n * the rejection. Callers decide whether to retry, drop, or surface; the\n * bridge has no opinion. Fire-and-forget posture matches `bridgeRuntime`.\n * - `subscribe(listener)` registers a `chrome.runtime.onMessage` listener\n * filtered by both `envelope.type === channel` AND `sender.tab?.id === tabId`,\n * so broadcasts from other tabs are ignored (no cross-contamination across\n * per-tab content-script runtimes).\n */\nexport function bridgeTabClient<S, M extends { type: string }>({\n tabId,\n channel,\n parseState,\n parseMsg,\n}: BridgeTabClientOpts<S, M>): BridgeClient<S, M> {\n return makeBridgeClient({\n channel,\n parseState,\n parseMsg,\n // SW → content transport: `chrome.runtime.sendMessage` does NOT reach a\n // tab's content script; `chrome.tabs.sendMessage(tabId, …)` does.\n send: (message) => chrome.tabs.sendMessage(tabId, message),\n // Every tab's content script broadcasts through the same SW\n // `chrome.runtime.onMessage`; filter to this tab so a client subscribed\n // to one tab never sees another's broadcasts.\n senderMatches: (sender) => sender.tab?.id === tabId,\n });\n}\n"]}
@@ -176,6 +176,9 @@ var Cmd = {
176
176
  function subId(s) {
177
177
  return s;
178
178
  }
179
+ function depsInactive(deps) {
180
+ return deps === null || deps === void 0;
181
+ }
179
182
  function structuralHash(deps) {
180
183
  return stableStringify(deps);
181
184
  }
@@ -194,6 +197,14 @@ function stableStringify(value) {
194
197
  return `[${value.map(stableStringify).join(",")}]`;
195
198
  }
196
199
  if (t === "object") {
200
+ const proto = Object.getPrototypeOf(value);
201
+ if (proto !== Object.prototype && proto !== null) {
202
+ const ctor = value.constructor;
203
+ const label = ctor?.name ?? "object";
204
+ throw new Error(
205
+ `@demlik/tea: structuralHash received a non-plain object (${label}) in \`deps\`. A \`Date\`, \`Map\`, \`Set\`, \`Error\` or class instance carries no own enumerable properties, so every one of them would hash to the same id as \`{}\` \u2014 a silent collision, not a wrong-looking key. Project it to plain data first (e.g. \`startedAt.toISOString()\`, \`[...set].sort()\`) \u2014 see invariant 1.`
206
+ );
207
+ }
197
208
  const obj = value;
198
209
  const keys = Object.keys(obj).sort();
199
210
  return `{${keys.map((k) => `${JSON.stringify(k)}:${stableStringify(obj[k])}`).join(",")}}`;
@@ -216,6 +227,6 @@ function foldMsgs(machine, base, msgs) {
216
227
  return foldUpdates(machine, base, msgs).state;
217
228
  }
218
229
 
219
- export { Cmd, NoCellError, applyCell, applyCellChecked, detectUpdateForm, foldMsgs, foldUpdates, formOf, msgKeysOf, structuralHash, subId, without };
220
- //# sourceMappingURL=chunk-PBDYW2TU.js.map
221
- //# sourceMappingURL=chunk-PBDYW2TU.js.map
230
+ export { Cmd, NoCellError, applyCell, applyCellChecked, depsInactive, detectUpdateForm, foldMsgs, foldUpdates, formOf, msgKeysOf, structuralHash, subId, without };
231
+ //# sourceMappingURL=chunk-DBOJ2UC6.js.map
232
+ //# sourceMappingURL=chunk-DBOJ2UC6.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/pure/core.ts"],"names":["cell"],"mappings":"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","file":"chunk-DBOJ2UC6.js","sourcesContent":["/**\n * @demlik/tea — pure-core leaf (ADR 0006).\n *\n * This is the runtime-free pure core of the substrate. It holds the\n * client-prediction fold seam (`foldMsgs`), the form reader\n * (`formOf`/`detectUpdateForm`), and the pure type vocabulary (`Machine`,\n * `Reducer`, `Transitions`, `Cmd`, `Sub`, `Port`, …).\n *\n * **Dependency direction (the actual decoupling):** this module imports\n * NOTHING from the runtime — no `better-result`, no `run`/host/`Store`. The\n * runtime (`run`, the host, interpret, `Store`, subscribe — all in\n * `../index.ts`) imports *from* here; never the reverse. The\n * `@demlik/tea/pure` subpath re-exports this surface, and\n * `pure/import-graph.test.ts` is the regression fence asserting the pure\n * entrypoint's import graph never reaches `run`.\n */\n\n// === Dev-mode invariant enforcement ===\n//\n// Three runtime guards that catch TEA invariant violations during development.\n// Dead-code-eliminated in production by any bundler that replaces\n// `import.meta.env.DEV` (Vite) or `process.env.NODE_ENV` (webpack/esbuild).\n//\n// 1. deepFreeze(state) before passing to update — catches mutation\n// 2. Thenable check on return value — catches async update\n// 3. Walk cmd fields for function types — catches closures-as-data\n//\n// These enforce invariants 1 and 2 at runtime where the type system cannot.\nexport const __DEV__: boolean = (() => {\n try {\n const meta = import.meta as unknown as { env?: { DEV?: boolean } };\n if (meta.env?.DEV !== undefined) {\n return meta.env.DEV;\n }\n } catch {\n // import.meta not available (Node CJS, service workers)\n }\n try {\n return (\n typeof process !== \"undefined\" && process.env?.NODE_ENV !== \"production\"\n );\n } catch {\n return false;\n }\n})();\n\nexport function deepFreeze<T>(obj: T, seen?: WeakSet<object>): T {\n if (obj === null || typeof obj !== \"object\") return obj;\n const proto = Object.getPrototypeOf(obj);\n if (proto !== Object.prototype && proto !== Array.prototype && proto !== null)\n return obj;\n const visited = seen ?? new WeakSet();\n if (visited.has(obj as object)) return obj;\n visited.add(obj as object);\n Object.freeze(obj);\n for (const v of Object.values(obj as Record<string, unknown>))\n deepFreeze(v, visited);\n return obj;\n}\n\nexport function hasFunctionValues(obj: unknown): string | null {\n if (obj === null || typeof obj !== \"object\") return null;\n for (const [key, value] of Object.entries(obj as Record<string, unknown>)) {\n if (typeof value === \"function\") return key;\n }\n return null;\n}\n\nexport function assertPureResult(result: unknown, msgType: string): void {\n if (result && typeof (result as { then?: unknown }).then === \"function\") {\n throw new Error(\n `@demlik/tea: update cell \"${msgType}\" returned a Promise. ` +\n `update must be synchronous. Move async work to interpret.`,\n );\n }\n if (\n !Array.isArray(result) ||\n result.length < 2 ||\n !Array.isArray(result[1])\n ) {\n throw new Error(\n `@demlik/tea: update cell \"${msgType}\" returned a non-tuple. ` +\n `Expected [State, Cmd[]], got ${typeof result}.`,\n );\n }\n const cmds = result[1] as readonly { type: string }[];\n for (const cmd of cmds) {\n const fnField = hasFunctionValues(cmd);\n if (fnField !== null) {\n throw new Error(\n `@demlik/tea: Cmd \"${cmd.type}\" has function field \"${fnField}\". ` +\n `Cmds must be plain data, not closures.`,\n );\n }\n }\n}\n\n/**\n * Drop one `key` from a `Record` immutably — returns a new record with `key`\n * omitted, leaving the input untouched. The immutable-record-key-drop the L2\n * resilience bricks reach for whenever a settled/restarted call must forget its\n * per-key bookkeeping (a retry counter, a parked input). A plain\n * object-rest spread, lifted here so `resilient-call` and `authed-call` share\n * one definition instead of open-coding `const { [key]: _drop, ...rest } = rec`\n * at each site.\n */\nexport function without<V>(\n rec: Readonly<Record<string, V>>,\n key: string,\n): Readonly<Record<string, V>> {\n const { [key]: _dropped, ...rest } = rec;\n return rest;\n}\n\n// === Cmd: tagged-union, one-shot effect ===\nexport type Cmd<T extends string = string> = { readonly type: T };\n\n// === SyncReturn<S, C>: the compile-time reentrancy guard (ADR 0003 #5) ===\n//\n// The canon rule (`.patterns/tea-do/reentrancy.md`,\n// `.patterns/tea-do/reentrancy-deadlock.md`): a reducer must never\n// inline-`await` a re-entrant result. \"Non-reentrant by default\" ≡ \"the reducer\n// is pure AND synchronous\": a reducer that returns a `Promise` (because it is\n// `async`, or inline-`await`s) is the suspended-but-still-blocking turn Orleans\n// serializes against — it occupies the single-writer slot across a suspension\n// point and can be re-entered mid-flight → deadlock.\n//\n// So the guard is purely type-level: every reducer/transition cell returns\n// `SyncReturn<S, C>` — the synchronous result tuple, intersected with `{ then?:\n// never }` to make it *non-thenable*. A `Promise<readonly [S, readonly C[]]>`\n// is an object with a `then` method, so it fails to satisfy `then?: never`; an\n// `async (s, m) => [...]` (or one that inline-`await`s) is therefore\n// unrepresentable at compile time — `tsc` rejects it *at the cell*, not at a\n// runtime hang. A plain synchronous tuple `[next, cmds]` has no `then` property,\n// so the optional-absent `then?: never` is satisfied and existing pure reducers\n// compile unchanged. `SyncReturn<S, C>` is also assignable *to* `readonly [S,\n// readonly C[]]` (the intersection is narrower), so the runtime dispatch in\n// `run`/`replay` consumes a cell result exactly as before.\n//\n// This mirrors `retry-backoff`'s `Rng` brand (#63): the obligation lives in the\n// type at the construction boundary (\"parse, don't validate\"), not in a comment.\nexport type SyncReturn<S, C extends Cmd> = readonly [S, readonly C[]] & {\n // A thenable carries a callable `then`; forbidding it rejects every Promise.\n // Optional + `never` means \"absent on a sync tuple, impossible on a Promise\".\n readonly then?: never;\n};\n\n// === update form: Reducer vs Transitions, tagged once ===\n//\n// The substrate accepts two `update` shapes (see the `Machine.update` union and\n// the `defineMachine` overloads):\n//\n// - \"reducer\" — flat record keyed by Msg.type, every cell a function.\n// - \"transitions\" — 2D table keyed by State.type then Msg.type, every cell a\n// record of functions.\n//\n// Which one a given machine is must be known at runtime by `run`, `replay`, and\n// the `withX` wrappers (their dispatch / key-enumeration / reserved-namespace\n// scans differ per form). Rather than re-derive it structurally at every reader\n// — a `typeof update[firstKey] === \"function\"` heuristic that breaks the day a\n// reducer cell is itself an object-with-a-call — `defineMachine` computes the\n// form ONCE at construction and stamps it on the machine as a non-enumerable\n// `__form` (so it never serializes, never collides with a Msg.type key, never\n// shows up in `Object.keys(machine)`). Every reader calls `formOf(machine)`.\nexport type UpdateForm = \"reducer\" | \"transitions\";\n\n// The single structural heuristic, defined ONCE. Used only by `defineMachine`\n// (and `formOf`'s fallback for machines built without it, e.g. a plain object\n// literal annotated as `Machine`). A Reducer's first own value is a function; a\n// Transitions table's first own value is a record (of functions). An empty\n// record (`M` is `never`) can never dispatch a Msg, so the form is irrelevant —\n// \"reducer\" is returned arbitrarily.\nexport function detectUpdateForm(update: object): UpdateForm {\n const firstKey = Object.keys(update)[0];\n if (firstKey === undefined) return \"reducer\";\n const firstValue = (update as Record<string, unknown>)[firstKey];\n return typeof firstValue === \"function\" ? \"reducer\" : \"transitions\";\n}\n\n// The single reader every form-sensitive site goes through. Prefers the\n// `__form` tag stamped by `defineMachine` (authoritative — computed once at the\n// typed construction boundary); falls back to `detectUpdateForm` only for a\n// machine that never passed through `defineMachine`. No reader re-implements the\n// heuristic.\nexport function formOf(machine: {\n update: object;\n __form?: UpdateForm;\n}): UpdateForm {\n return machine.__form ?? detectUpdateForm(machine.update);\n}\n\n// === NoCellError: the named cell-lookup failure (#276) ===\n//\n// An unknown `msg.type` (wire data reaching dispatch) or a type-bypassed\n// missing cell used to surface as a bare `TypeError: ... is not a function`\n// deep inside dispatch — no msg.type, no state name, nothing actionable. The\n// guard lives in `applyCell` because it is the single dispatch primitive\n// every stepping site goes through (#275), so one guard covers `run`,\n// replay/foldMsgs, the PBT fold runner, and the withX wrappers.\nexport class NoCellError extends Error {\n override readonly name = \"NoCellError\";\n readonly _tag = \"NoCellError\" as const;\n constructor(\n public readonly msgType: string,\n public readonly stateName: string,\n ) {\n super(\n `@demlik/tea: no update cell for msg.type \"${msgType}\" in state ` +\n `\"${stateName}\" — the machine's update does not handle this Msg ` +\n `(an unknown wire msg.type, or a missing cell reached by bypassing ` +\n `the mapped types).`,\n );\n }\n}\n\n// Reducer-form State carries no mandatory discriminant; best-effort read of a\n// string `state.type` for the error, else a placeholder.\nfunction stateNameOf(state: unknown): string {\n if (typeof state === \"object\" && state !== null && \"type\" in state) {\n const t = (state as { type: unknown }).type;\n if (typeof t === \"string\") return t;\n }\n return \"(untagged state)\";\n}\n\n// === applyCell: THE single reducer-vs-transitions dispatch primitive ===\n//\n// Applies the one update cell selected by `(formOf(machine), state, msg)` and\n// returns its `[nextState, cmds]` verbatim. Every site that steps a machine —\n// `run`'s applyUpdate, `foldUpdates` (replay/foldMsgs), the PBT fold runner,\n// and the withX wrappers — dispatches through THIS function, so production and\n// the verification tools agree on the update form by construction (#275).\n// Pure and dev-check-free: `deepFreeze`/`assertPureResult` stay at the call\n// sites that want them. A missing cell throws `NoCellError` (#276), never a\n// bare TypeError.\nexport function applyCell<S, M extends { type: string }, C extends Cmd>(\n machine: { update: object; __form?: UpdateForm },\n state: S,\n msg: M,\n): readonly [S, readonly C[]] {\n type CellFn = (state: S, msg: M) => readonly [S, readonly C[]];\n if (formOf(machine) === \"reducer\") {\n const record = machine.update as Record<string, CellFn | undefined>;\n const cell = record[msg.type];\n if (typeof cell !== \"function\") {\n throw new NoCellError(msg.type, stateNameOf(state));\n }\n return cell(state, msg);\n }\n const table = machine.update as Record<\n string,\n Record<string, CellFn | undefined> | undefined\n >;\n // The ONE sanctioned `state as unknown as { type }` read: the Transitions\n // overload constrains S to `{ type: string }` at the `defineMachine`\n // boundary, but that constraint is erased on the runtime-facing `object`\n // here — every former per-site copy of this double-cast collapsed into\n // this line (#275).\n const stateKey = (state as unknown as { type: string }).type;\n const cell = table[stateKey]?.[msg.type];\n if (typeof cell !== \"function\") {\n throw new NoCellError(msg.type, String(stateKey));\n }\n return cell(state, msg);\n}\n\n// === applyCellChecked: `applyCell` wrapped in the DEV pre/post invariant pair ===\n//\n// The dev-mode discipline every fold site shares: `deepFreeze` the input `state`\n// so a reducer that mutates it in place trips synchronously, then\n// `assertPureResult` the returned `[state, cmds]` shape. Both guards compile out\n// of production (`__DEV__`). `run`'s dispatch loop and `foldUpdates` both step\n// through THIS wrapper so the invariant enforcement lives in exactly one place\n// and cannot drift between the runtime and the pure fold.\nexport function applyCellChecked<S, M extends { type: string }, C extends Cmd>(\n machine: { update: object; __form?: UpdateForm },\n state: S,\n msg: M,\n): readonly [S, readonly C[]] {\n if (__DEV__) deepFreeze(state);\n const result = applyCell<S, M, C>(machine, state, msg);\n if (__DEV__) assertPureResult(result, msg.type);\n return result;\n}\n\n// === msgKeysOf: recover the Msg.type set from either update form ===\n//\n// A Reducer's own keys ARE the Msg.type set. A Transitions table's keys are\n// state.type; its INNER keys are the Msg.type set (uniform across phases by\n// the mapped-type contract), so the first phase's inner keys are read. An\n// empty update (`M` is `never`) yields `[]`. Keyed on `formOf` — the withX\n// wrappers and the PBT `msgTypeKeys` all read through this one helper (#275).\nexport function msgKeysOf(machine: {\n update: object;\n __form?: UpdateForm;\n}): readonly string[] {\n const keys = Object.keys(machine.update);\n const firstKey = keys[0];\n if (firstKey === undefined) return [];\n if (formOf(machine) === \"reducer\") return keys;\n const firstValue = (machine.update as Record<string, unknown>)[firstKey];\n return Object.keys(firstValue as object);\n}\n\n// === Reducer<S, M, C>: record-of-handlers form of `update` ===\n//\n// Flat dispatch table keyed by `Msg.type`. Each cell is a pure transition for\n// a single Msg variant, narrowed via `Extract<M, { type: K }>`. The mapped type\n// is *load-bearing*: adding a Msg variant without a matching key in the\n// Reducer record is a compile error — no `absurd()` helper at call sites, no\n// silent fall-through, no default branch hiding impurity.\n//\n// Strengthens invariant 2 (pure transitions — the record form has no\n// fall-through default to hide impurity behind) and invariant 7 (identity is\n// explicit — the Msg variant set is load-bearing at the type level).\n//\n// `defineMachine` accepts the Reducer record form via overload. The runtime\n// dispatches via `update[msg.type](state, msg)`.\nexport type Reducer<S, M extends { type: string }, C extends Cmd> = {\n [K in M[\"type\"]]: (\n state: S,\n msg: Extract<M, { type: K }>,\n // `SyncReturn<S, C>` (not `readonly [S, readonly C[]]`) is the reentrancy\n // guard: a non-thenable return type, so an `async`/inline-`await`ing cell —\n // which returns `Promise<...>` — fails to compile at the cell. See the\n // `SyncReturn` doc above.\n ) => SyncReturn<S, C>;\n};\n\n// === Transitions<S, M, C>: table form of `update` for state-machine-shaped machines ===\n//\n// When `State` is itself a discriminated union (`State.type` is the active\n// phase), the table form makes every (state.type × msg.type) cell explicit at\n// the type level. Missing a cell — e.g. forgetting how `phase: \"settling\"`\n// handles `msg.type: \"tab_stepped\"` — fails to compile. No nested switch, no\n// fall-through, no `_ -> (state, [])` default branch hiding regressions.\n//\n// Each cell receives the narrowed `State` for its phase and the narrowed\n// `Msg` for its variant via two `Extract` lookups. Phantom-narrow at the\n// type level; pure data at runtime.\n//\n// `defineMachine` accepts a third `update` form for `Transitions<S, M, C>` —\n// the runtime dispatches via `update[state.type][msg.type](state, msg)`.\n//\n// Strengthens invariant 2 (table form has no fall-through default),\n// invariant 6 (runtime walks the table predictably, no hidden dispatch\n// fallback), and invariant 7 (both state.type and msg.type are load-bearing\n// at the type level).\nexport type Transitions<\n S extends { type: string },\n M extends { type: string },\n C extends Cmd,\n> = {\n [P in S[\"type\"]]: {\n [K in M[\"type\"]]: (\n state: Extract<S, { type: P }>,\n msg: Extract<M, { type: K }>,\n // Same reentrancy guard as `Reducer`: a transition cell that returns a\n // Promise (async / inline-await) is the suspended-blocking turn the canon\n // forbids, so its return is the non-thenable `SyncReturn<S, C>`.\n ) => SyncReturn<S, C>;\n };\n};\n\n// === Cmd: namespace for conditional Cmd emission ===\n//\n// Two recurring frictions in reducer cells:\n//\n// 1. \"Emit cmd X only when condition Y holds.\"\n// Hand-rolled: `cond ? [{ type: \"x\", ... }] : []`\n// With Cmd: `Cmd.when(cond, { type: \"x\", ... })`\n//\n// 2. \"Emit cmd X carrying a value that may be undefined; if undefined,\n// emit nothing.\"\n// Hand-rolled: `value !== undefined ? [{ type: \"x\", value }] : []`\n// With Cmd: `Cmd.whenDefined(value, (v) => ({ type: \"x\", value: v }))`\n//\n// Both forms return `readonly C[]` so they spread cleanly into the cmds\n// array returned by a Transitions cell:\n//\n// return [next, [\n// ...Cmd.when(state.tabId !== undefined, { type: \"detach_debugger\", tabId: state.tabId }),\n// ...Cmd.whenDefined(state.queueItemId, (id) => ({\n// type: \"queue:complete\", queueItemId: id, status: \"done\",\n// })),\n// ]];\n//\n// `Cmd` as a value namespace coexists with `Cmd<T>` as a type — TypeScript's\n// declaration merging puts them in different name spaces (type vs. value).\n// The reader sees `Cmd<...>` in type position and `Cmd.when(...)` in\n// expression position; no ambiguity at use sites.\n//\n// Why a namespace and not top-level `when` / `whenDefined`: `when` at module\n// scope reads as a generic conditional, but these helpers are specifically\n// for *Cmd emission* (return `readonly C[]`, the cmds-array contract of\n// every Transitions cell). The namespace pins that intent at the call site.\nexport const Cmd = {\n /**\n * The empty Cmd array. Typed `readonly never[]` so it's assignable to any\n * `readonly C[]` for any `C extends Cmd`. Use in `init` returns and\n * Transitions cells that emit zero effects:\n *\n * init: (loaded) => [loaded ?? initial, Cmd.none],\n * tick: (state) => [state, Cmd.none],\n *\n * Elm's `Cmd.none` analogue. Cultural signal alongside the runtime help:\n * `[state, Cmd.none]` reads as intent (\"this transition emits nothing\"),\n * `[state, []]` reads as \"empty array of what\".\n *\n * Frozen at runtime so a downstream consumer can't `.push()` into the\n * shared reference.\n */\n none: Object.freeze([]) as readonly never[],\n\n /**\n * Flat concat of cmd arrays. Use when a cell composes effects from\n * multiple conditional sources:\n *\n * return [next, Cmd.batch(\n * Cmd.whenDefined(state.queueItemId, (id) => ({ type: \"complete\", id })),\n * Cmd.when(state.windowId !== undefined, { type: \"close_window\", windowId }),\n * [{ type: \"detach_debugger\", tabId: state.tabId }],\n * )];\n *\n * `<const C>` keeps inline cmd literals' discriminants narrow (same\n * rationale as `when` / `whenDefined` above).\n */\n batch: <const C extends Cmd>(\n ...arrs: readonly (readonly C[])[]\n ): readonly C[] => {\n const out: C[] = [];\n for (const arr of arrs) out.push(...arr);\n return out;\n },\n\n /**\n * Emit `cmd` wrapped in a single-element array iff `cond` is true.\n * Otherwise return the empty array. Spreads cleanly into the cmds array\n * returned by a Transitions cell.\n *\n * `const C` (TypeScript 5.0+) keeps the inferred `type:` discriminator\n * literal narrow when called with an object literal — so `Cmd.when(b,\n * { type: \"x\", ... })` infers as `{ type: \"x\", ... }`, not `{ type:\n * string, ... }`, and stays assignable to a discriminated-union arm\n * without `as const` at the call site.\n */\n when: <const C extends Cmd>(cond: boolean, cmd: C): readonly C[] =>\n cond ? [cmd] : [],\n\n /**\n * If `value` is defined, call `build(value)` and emit the resulting cmd\n * wrapped in a single-element array. If `value` is `undefined`, return\n * the empty array. `value` is narrowed to `T` inside `build`.\n *\n * `const C` on the return type keeps the callback's object-literal\n * `type:` field narrow (see `when` above for the full rationale).\n */\n whenDefined: <T, const C extends Cmd>(\n value: T | undefined,\n build: (value: T) => C,\n ): readonly C[] => (value !== undefined ? [build(value)] : []),\n} as const;\n\n// === SubId: branded string for Sub identity ===\n//\n// Subs are reconciled by id on every transition (canon §2.5, invariant 7).\n// Before branding, `Sub.id` was a raw `string` — a typo at one call site and a\n// different typo at another would silently produce two distinct subs that\n// looked identical to a human reader. The brand makes id construction explicit\n// (`subId(\"...\")`) and catches accidental string-where-SubId at the type\n// level.\n//\n// The brand is structural: `string & { __brand: \"SubId\" }`. The `subId(s)`\n// constructor is the ONE permitted cast in the substrate — every other call\n// site must go through it.\nexport type SubId = string & { readonly __brand: \"SubId\" };\n\n/**\n * Construct a `SubId` from a string. Use this everywhere a Sub literal is\n * built — including ids derived from runtime data (`subId(\\`ws:${auditId}\\`)`).\n * The brand is structural, not nominal, so dynamic ids are permitted; the\n * constructor's job is to make the identity decision explicit at every call\n * site so the type system can catch accidental raw-string drift.\n *\n * Strengthens invariant 7 (identity is explicit).\n */\nexport function subId(s: string): SubId {\n return s as SubId;\n}\n\n// === Sub: tagged-union, continuous source of msgs; stable id used for diff/reconcile ===\nexport type Sub<T extends string = string> = {\n readonly id: SubId;\n readonly type: T;\n};\n\n// === Dispose: the cleanup a dep-keyed Sub's source returns ===\n//\n// A `source` opens a resource and returns the function that closes it. The\n// substrate's reconcile calls it when the Sub's `deps` go null (torn down) or\n// change (re-armed: old `Dispose` then a fresh `source`). Same contract as the\n// `() => void` cleanup every `subscribe[type]` handler returns — named so a\n// dep-keyed Sub's `source` reads as \"open → returns close\".\nexport type Dispose = () => void | Promise<void>;\n\n// === depsInactive: the dep-keyed gate, defined ONCE ===\n//\n// \"This Sub has no slice in this state\" is one fact read by two places — the\n// runtime's `reconcileDepSubs` and `replay`'s desired-set projection — so it\n// gets one definition rather than a `deps === null` written twice.\n//\n// It reads NULLISH, not `null`. `(s) => s.runId` over an optional field is the\n// natural projection an author writes, and it yields `undefined` on the states\n// that mean \"inactive\"; a `=== null` gate armed the Sub there and hashed the\n// slice to the string `\"undefined\"` — one shared key under which a resource\n// was acquired for a state that had none. The typed alternative (`deps: (s) =>\n// {} | null`) would reject that projection at compile time, but it also rejects\n// every battery's `(state) => TKey | null` for an unconstrained `TKey`, so the\n// gate is widened instead of the type narrowed.\nexport function depsInactive(deps: unknown): boolean {\n return deps === null || deps === undefined;\n}\n\n// === structuralHash: deterministic, order-independent id from a deps value ===\n//\n// The id of a dep-keyed Sub (invariant 7 — identity is explicit) is DERIVED\n// from the slice of state the Sub depends on, never hand-authored. So adding a\n// field to `deps` changes the id exactly when that field changes, and a\n// churning id — the named #1 Sub bug (a Sub that remounts every tick, so its\n// timer never fires) — becomes impossible by construction: the author cannot\n// build the id wrong because the author does not build it.\n//\n// Determinism is load-bearing (invariant 2 — the reconcile pass runs inside\n// the pure-transition path's reconcile step; the same `deps` must always hash\n// to the same id). So: NO `Date`, NO random, NO insertion-order dependence.\n// Object keys are sorted so `{ runId, phase }` and `{ phase, runId }` hash\n// identically — the canonical \"name the slice → stable key\" adaptation FoldKit\n// makes for TS (its `modelToDependencies`), which Elm gets free from its\n// structural compare.\n//\n// The supported `deps` shape is plain JSON-compatible data (string / number /\n// boolean / null / array / plain object). A function, class instance, symbol,\n// or bigint in `deps` is a programming error — `deps` names a state SLICE, and\n// state is a value (invariant 1); such a value would also break the\n// determinism this hash promises. The walk throws on all of them to surface\n// that loudly rather than silently producing an unstable — or, worse, a\n// COLLIDING — id.\n//\n// The non-plain OBJECT case is the one that bites: `Object.keys` reports no own\n// enumerable property on a `Date`, `Map`, `Set`, `Error`, or a typical class\n// instance, so a walk that trusts it renders every one of them — and `{}` —\n// as `\"{}\"`. That is not an unstable id, it is ONE id for every value, which\n// defeats the identity filter (a foreign run's message compares equal), pins a\n// dep-keyed Sub to its first slice forever, and makes a battery's handle table\n// return the previous key's handle. The guard is therefore on the PROTOTYPE\n// (`Object.prototype` or `null`), not on a list of known classes: a\n// user-defined `RunKey` is refused by the same rule as `Date`, and no future\n// exotic builtin slips through.\n//\n// Rendering `Date`/`Map`/`Set` structurally was the alternative. It was\n// rejected: it buys a second rule to remember for a value the author can\n// project in one call (`startedAt.toISOString()`), and `Map`/`Set` iteration is\n// INSERTION-ordered, so any faithful rendering is order-sensitive — exactly the\n// churn this hash exists to prevent. One rule, stated once: plain data only.\n//\n// It is also the ONE \"turn this key value into a stable string\" primitive the\n// `@demlik/tea/subs` batteries address their interpret-local handle tables\n// with, so a battery's key rendering and the kernel's Sub identity can never\n// drift into two hashes for one fact.\nexport function structuralHash(deps: unknown): string {\n return stableStringify(deps);\n}\n\nfunction stableStringify(value: unknown): string {\n if (value === null) return \"null\";\n const t = typeof value;\n if (t === \"string\") return JSON.stringify(value);\n if (t === \"number\" || t === \"boolean\") return String(value);\n if (t === \"undefined\") return \"undefined\";\n if (t === \"function\") {\n throw new Error(\n \"@demlik/tea: structuralHash received a function in `deps`. \" +\n \"A dep-keyed Sub's `deps` must be a plain state slice (string / number / \" +\n \"boolean / null / array / object), not a closure — see invariant 1.\",\n );\n }\n if (Array.isArray(value)) {\n return `[${value.map(stableStringify).join(\",\")}]`;\n }\n if (t === \"object\") {\n // Plain-object gate. `Object.keys` is only a faithful reading of a value\n // whose prototype is `Object.prototype` (or `null` — a bag built with\n // `Object.create(null)` is plain by every measure that matters here).\n // Anything else keeps its data somewhere `Object.keys` cannot see, and\n // would render as `\"{}\"` — one id for every such value.\n const proto = Object.getPrototypeOf(value) as object | null;\n if (proto !== Object.prototype && proto !== null) {\n const ctor = (value as { constructor?: { name?: string } }).constructor;\n const label = ctor?.name ?? \"object\";\n throw new Error(\n `@demlik/tea: structuralHash received a non-plain object (${label}) in \\`deps\\`. ` +\n \"A `Date`, `Map`, `Set`, `Error` or class instance carries no own enumerable \" +\n \"properties, so every one of them would hash to the same id as `{}` — a silent \" +\n \"collision, not a wrong-looking key. Project it to plain data first \" +\n \"(e.g. `startedAt.toISOString()`, `[...set].sort()`) — see invariant 1.\",\n );\n }\n const obj = value as Record<string, unknown>;\n const keys = Object.keys(obj).sort();\n return `{${keys.map((k) => `${JSON.stringify(k)}:${stableStringify(obj[k])}`).join(\",\")}}`;\n }\n // bigint / symbol — not JSON-representable; same class of error as function.\n throw new Error(\n `@demlik/tea: structuralHash received an unsupported \\`deps\\` value of type \"${t}\". ` +\n \"Use plain JSON-compatible data for a dep-keyed Sub's `deps`.\",\n );\n}\n\n// === DepKeyedSub<S, M, Ctx>: a Sub whose identity AND gate fall out of `deps` ===\n//\n// The author declares the slice of state the Sub depends on (`deps`) and how\n// to open the resource for that slice (`source`). The substrate derives BOTH:\n//\n// - the **id** = `structuralHash(deps(state))` — changes exactly when the\n// slice changes (re-arm), stable otherwise (no churn). The author never\n// writes `subId(...)` — the hand-written subId was the drift away from\n// Elm's structural identity, which this restores.\n// - the **gate** = `deps(state)` returning `null` ⇒ inactive in this state\n// (dispose if running); non-null ⇒ active. The substrate folds every\n// `DepKeyedSub` into its own active set, so the author deletes the central\n// `subscriptions(state)` aggregate — there is no list to forget to edit\n// when a new phase is added (a per-Sub gate travels with the Sub).\n//\n// This is the FoldKit `modelToDependencies` shape: a Sub names the state slice\n// it depends on, the kernel keys on that slice. The manual `subscriptions?:`\n// field stays as the documented escape hatch (see `Machine`).\n//\n// `source` returns a `Dispose`. On a `deps` change the substrate runs the old\n// `Dispose` then re-runs `source` with the new deps (re-arm); on `deps` going\n// null it runs `Dispose` (teardown). The same reconcile machinery the manual\n// `Sub` path uses (`reconcileSubs` in `../run.ts`) — no second loop.\n//\n// Strengthens invariant 4 (external lifecycle owned by the substrate — the\n// dep-keyed Sub is reconciled, never hand-driven) and invariant 7 (identity is\n// explicit — derived deterministically from `deps`, never an ad-hoc string).\nexport interface DepKeyedSub<S, M, Ctx = unknown> {\n /**\n * The slice of state this Sub depends on. `null` OR `undefined` ⇒ the Sub is\n * inactive in this state (the substrate disposes it if it was running) — see\n * `depsInactive`, so `(s) => s.optionalRunId` gates correctly. Otherwise the\n * Sub is active, keyed on `structuralHash(deps)`.\n *\n * Pure (invariant 2). Plain JSON-compatible data only (invariant 1) — the\n * structural hash walks it deterministically and THROWS on a `Date`, `Map`,\n * `Set`, `Error` or class instance rather than collapsing them all onto one\n * id. Project such a value first (`startedAt.toISOString()`).\n */\n readonly deps: (state: S) => unknown;\n /**\n * Open the resource for the current `state` (whose `deps` the kernel just\n * found non-null). Receives `dispatch` to fire follow-up Msgs (a timer's\n * fire, an inbound frame) and the machine's `ctx` (the host's transport\n * factory, storage handles). Returns the `Dispose` the substrate runs on\n * teardown / re-arm. Same role as a `subscribe[type]` handler's returned\n * cleanup.\n *\n * `source` takes `state` (not the deps slice) so the slice type `D` never\n * escapes the entry — each battery's `.depKeyed` closes over its own typed\n * deps internally (cast-free existential: the entry that produced the deps\n * is the entry that consumes them).\n */\n readonly source: (state: S, dispatch: (msg: M) => void, ctx: Ctx) => Dispose;\n}\n\n// === Identity<S, M>: declare the instance's identity once; the kernel drops\n// mis-addressed messages before they reach `update` ===\n//\n// Without this: the substrate dispatches EVERY message to `update`, so an\n// author who needs \"this message is for THIS run\" must repeat\n// `if (msg.runId !== state.runId) return [state, []]` in every reducer cell.\n// Forget one cell and a stale or foreign run's message is applied → silent\n// corruption. That per-cell check is a rung-4 runtime stand-in for invariant 7\n// (identity is explicit): the identity invariant the host already holds (one\n// instance per run — `idFromName(runId)`) is re-asserted, cell by cell, where\n// it can be forgotten.\n//\n// `Identity<S, M>` lifts that to ONE declaration the kernel enforces. The\n// machine names two projections:\n//\n// - `ofState(state)` → the identity THIS instance owns, derived from state.\n// - `ofMsg(msg)` → the identity a message is ADDRESSED to, or\n// `undefined` when the message carries no identity\n// (lifecycle messages like `boot`, or `start_audit`\n// before the run exists — those are never dropped).\n//\n// Per transition the kernel compares `ofMsg(msg)` to `ofState(state)` by\n// `structuralHash` (so the identity can be any plain value, same machinery the\n// dep-keyed Sub id uses). A message addressed to a DIFFERENT identity is\n// dropped BEFORE `update` runs — the reducer never sees it, so no cell needs a\n// guard. A message with `ofMsg === undefined` is identity-agnostic and always\n// reaches `update`.\n//\n// Two projections rather than a single `(state) => state.runId`: the kernel\n// needs BOTH the state's identity AND the message's to compare, and splitting\n// them keeps the comparison `K`-to-`K` with no `as` cast (a message union\n// carries its identity field on only SOME arms — `ofMsg` narrows per arm in\n// author-land, returning `undefined` on the arms that have none, instead of\n// the substrate reaching into `msg.runId` it cannot type).\n//\n// Opt-in (escape hatch): a machine that declares no `identity` skips the filter\n// entirely — every message reaches `update`, exactly as before.\n//\n// Strengthens invariant 7 (identity is explicit — declared once, derived\n// deterministically, kernel-enforced rather than re-checked per cell) and\n// invariant 6 (the runtime is small and inspectable — a mis-addressed message\n// is dropped at ONE observable point, not silently mishandled in N cells).\nexport interface Identity<S, M> {\n /** The identity THIS instance owns. Pure (invariant 2); plain value. */\n readonly ofState: (state: S) => unknown;\n /**\n * The identity a message is addressed to, or `undefined` when the message\n * carries no identity (lifecycle / pre-identity messages — never dropped).\n * Pure (invariant 2); plain value when defined.\n */\n readonly ofMsg: (msg: M) => unknown;\n}\n\n// === Port<T>: typed escape hatch for \"data leaving the runtime\" ===\n//\n// A Port is a named, typed channel that Cmd handlers can `emit` to via the\n// augmented `ctx.emit(port, value)`. Subscribers attach via\n// `runtime.subscribePort(port, listener)` and receive every emitted value\n// synchronously (same fanout discipline as `runtime.observe`).\n//\n// Why Ports are a substrate primitive, distinct from State and Observe:\n// - **State** is \"the world as the program sees it.\" Folding outgoing\n// announcements into State turns ephemeral signals into persisted facts —\n// the classic anti-pattern that motivated this primitive.\n// - **Observe** sees every transition `(msg, state)`. That's the right channel\n// for devtools and logging, but it forces every consumer to filter the\n// entire firehose for a single signal.\n// - **Ports** are typed and selective: one channel per concept, subscribers\n// only see what the handler chose to emit. Mirrors Elm's outgoing-port\n// semantics — a typed declaration with `port` keyword whose values flow\n// out of the program to the host.\n//\n// Port identity is **by reference** — the returned object IS the identity.\n// Name is metadata for debugging. To prevent the \"two modules definePort the\n// same name expecting to share a channel, silently get distinct ports\" class\n// of bug, `definePort` asserts the name has not been seen in this process —\n// the symmetric runtime check to `SubId` (canon §2.12, invariant 7). Each\n// definePort call must use a unique name; if two modules need the same port,\n// one module exports it and the other imports it.\nexport interface Port<T> {\n readonly __brand: \"port\";\n readonly name: string;\n // Phantom field — never assigned, never read at runtime. Carries `T` for\n // inference at `ctx.emit` and `subscribePort` call sites. Using `?` so\n // `definePort` can produce a Port without constructing a value of `T`.\n readonly __t?: T;\n}\n\n/**\n * Augmentation injected onto `ctx` inside Cmd handlers. Handlers receive\n * `ctx & PortEmitter` so they can call `ctx.emit(port, value)` synchronously.\n * Subscribers registered via `runtime.subscribePort(port, listener)` receive\n * the value immediately.\n *\n * Emitting to a port with no subscribers is a no-op (does not throw).\n */\nexport interface PortEmitter {\n emit<T>(port: Port<T>, value: T): void;\n}\n\n// === NoCtx: the context-free marker ===\n//\n// A handler, Sub, or machine that reads NOTHING from `ctx` says so by using\n// `NoCtx` for its Ctx slot. This is the DELIBERATE absence of context — not\n// the accidental `unknown` looseness that erodes a boundary (see issue #64).\n//\n// `unknown` at a Ctx seam is ambiguous: it can mean \"I intentionally need no\n// context\" OR \"I gave up tightening this type.\" `NoCtx` resolves that\n// ambiguity at the type level. A reader (and the next agent) sees `NoCtx` and\n// knows the context-free-ness is a choice the author made, not a hole.\n//\n// Shape: an empty readonly record. A `Ctx & PortEmitter` is-a `NoCtx`, so a\n// context-free handler still composes at a richer call site. It carries no\n// own fields, so the name's claim (\"reads nothing from ctx\") is what callers\n// read at the seam.\n//\n// Strengthens invariant 6 (no silent looseness — a context-free seam is named,\n// not inferred) and invariant 8 (the boundary is legible; `unknown` stays\n// reserved for genuine wire-edge erasure, not for \"didn't bother\").\nexport type NoCtx = Readonly<Record<never, never>>;\n\n/**\n * Spelled-out alias for `NoCtx`.\n *\n * @deprecated Use {@link NoCtx}. One Way to Do Each Thing — `NoCtx` is the\n * single non-deprecated name for the context-free-ctx marker (this alias has\n * no in-tree uses). Kept as a re-export for one minor per the deprecate-don't-\n * delete policy in `MAINTAINING.md`, then removed.\n */\nexport type ContextFree = NoCtx;\n\n// === Interpret<M, C, Ctx>: record-of-handlers form of `interpret` ===\n//\n// Flat dispatch table keyed by `Cmd.type`. Each cell receives the narrowed Cmd\n// and the runtime-augmented Ctx (`Ctx & PortEmitter`) and resolves to a\n// follow-up Msg or `void` (fire-and-forget). The mapped type makes a missing\n// handler a compile error — `defineMachine` cannot accept the dictionary until\n// every Cmd variant has one.\n//\n// **The injected `dispatch` — the typed Cmd→Msg edge.** A leaf handler that\n// resolves on its own returns its follow-up Msg (`Promise<M | void>`) and\n// IGNORES the third argument. A DETACHED handler (one that hands long-running\n// work to `ctx.waitUntil` and CANNOT return its terminal Msg inline — awaiting\n// it would deadlock the serial dispatch tail) fires its terminal Msg through\n// this injected `dispatch` instead. Today such a handler reaches for a\n// host-wired `ctx.dispatch` typed to the FULL Msg union — so a typo'd or wrong\n// terminal Msg from a detached site compiles silently (rung 5: the allowed set\n// lives only in a comment). `wrapDetached<C, Allowed>` (in `../runtime-types`)\n// narrows THIS injected `dispatch` to the Cmd's declared result-Msg set, so a\n// wrong terminal Msg fails to compile (rung 2).\n//\n// Additive: the third arg is OPTIONAL (`dispatch?: (msg: M) => void`), so a\n// handler declaring only `(cmd, ctx)` stays assignable, and a unit test that\n// invokes a handler directly with two args still typechecks. The kernel ALWAYS\n// passes the dispatch (see `runInterpret` in `../run.ts`); the optionality is\n// purely a backward-compatibility affordance on the TYPE, not a runtime \"maybe\n// absent\". A handler authored via `wrapDetached` receives a NARROWER view of\n// this dispatch (only its declared result-Msg set).\n//\n// Hoisted out of `Machine.interpret` so consumers can type a free-standing\n// handler dictionary with `Interpret<MyMsg, MyCmd, MyCtx>` instead of\n// re-declaring the mapped type at every effects module.\n//\n// Strengthens invariant 2 (the record form has no fall-through default to\n// hide impurity behind) and invariant 7 (identity is explicit — the Cmd\n// variant set is load-bearing at the type level).\nexport type Interpret<M extends { type: string }, C extends Cmd, Ctx> = {\n [K in C[\"type\"]]: (\n cmd: Extract<C, { type: K }>,\n ctx: Ctx & PortEmitter,\n dispatch?: (msg: M) => void,\n // biome-ignore lint/suspicious/noConfusingVoidType: an interpret handler returns a follow-up Msg or nothing; `void` permits no-return bodies that `M | undefined` would reject\n ) => Promise<M | void>;\n};\n\n// === InterpretDetached<C, Allowed, Ctx>: a detached interpret handler ===\n//\n// A handler that detaches long-running work (`ctx.waitUntil(...)`) and therefore\n// CANNOT return its terminal Msg inline (awaiting it would deadlock the serial\n// dispatch tail). It returns `Promise<void>` and fires its terminal Msg through\n// the kernel-injected `dispatch`, which is NARROWED to `Allowed` — the subset\n// of the Msg union this Cmd is permitted to produce. A wrong / typo'd terminal\n// Msg then fails to compile.\n//\n// `Allowed extends { type: string }` is the declared result-Msg set (e.g.\n// `GraphFinished | GraphFailed`). `dispatch: (msg: Allowed) => void` is the\n// narrowed edge. The handler still receives `ctx` (carrying `waitUntil`).\n//\n// Authored via `wrapDetached` (in `../runtime-types`), which adapts this shape\n// back into a plain `Interpret` cell so it drops into the existing `interpret`\n// dictionary with no kernel change at the call site.\nexport type InterpretDetached<\n C extends Cmd,\n Allowed extends { type: string },\n Ctx,\n> = (\n cmd: C,\n ctx: Ctx & PortEmitter,\n dispatch: (msg: Allowed) => void,\n) => Promise<void>;\n\n// === Subscribe<M, U, Ctx>: record-of-handlers form of `subscribe` ===\n//\n// Flat dispatch table keyed by `Sub.type`. Each cell receives the narrowed\n// Sub, the Ctx, and a `dispatch` to fire follow-up Msgs. Returns a cleanup\n// function the substrate calls when the Sub is reconciled out (state\n// transitioned away). The mapped type guarantees every Sub variant has a\n// handler at the type level; runtime dispatch is a single property lookup.\n//\n// The cleanup shares `Dispose`'s shape: a returned Promise is AWAITED by\n// `stop()` (bounded), so an async teardown a host relies on before evicting the\n// isolate actually completes. Mid-run reconcile does not await it — the\n// reconcile pass is synchronous by construction (invariant 2) — but the promise\n// is tracked from the moment it exists, so `stop()` catches it either way.\n//\n// Hoisted out of `Machine.subscribe` so consumers can type a free-standing\n// handler dictionary with `Subscribe<MyMsg, MySub, MyCtx>` instead of\n// re-declaring the mapped type at every subs module.\n//\n// Strengthens invariant 7 (identity is explicit — the Sub variant set is\n// load-bearing at the type level).\nexport type Subscribe<M extends { type: string }, U extends Sub, Ctx> = {\n [K in U[\"type\"]]: (\n sub: Extract<U, { type: K }>,\n ctx: Ctx,\n dispatch: (msg: M) => void,\n ) => Dispose;\n};\n\n// === Machine: pure data, host-agnostic ===\n//\n// `update` is stored as a union of the two record forms — the runtime branches\n// on shape at dispatch time. The public `defineMachine` overloads enforce one\n// shape per call so consumers never see the union themselves.\n//\n// - Reducer form: `Reducer<S, M, C>` — flat record keyed by Msg.type;\n// exhaustiveness enforced by the mapped type.\n// - Transitions form: `Transitions<S, M, C>` — 2D table keyed by\n// `state.type` then `msg.type`; only available when `S extends { type:\n// string }`. Conditional in the union so machines without a discriminated\n// State don't widen to include this branch.\n//\n// `M` is constrained to `{ type: string }` because both record forms require\n// a string discriminant.\n//\n// `interpret` is conditionally optional: when `C` is `Cmd<never>` the\n// interpret map is keyed by `never`, so the only valid value is `{}`. Forcing\n// every cmdless machine to write `interpret: {} as never` is ceremony for a\n// shape the type already pins. The tuple-wrap (`[C] extends [Cmd<never>]`)\n// disables distributive conditional so a real cmd union (e.g.\n// `Cmd<\"a\"> | Cmd<\"b\">`) doesn't degrade to optional just because one arm\n// happens to be `Cmd<never>`. Same trick the `update` field uses.\nexport type Machine<\n S,\n M extends { type: string },\n C extends Cmd,\n U extends Sub,\n Ctx,\n> = {\n /**\n * Boot the runtime. Called once by `run(...)` with whatever `Store.load()`\n * returned: `null` on fresh boot, the persisted state on rehydrate.\n *\n * **Contract:** when `loaded !== null`, init MUST return `[loaded, []]` —\n * no Cmds. Init's rehydrate branch is the migration / parse boundary, not\n * the boot-effect hook. See Invariant 2 in `.patterns/tea/tea-invariants.md`.\n *\n * Boot effects routes:\n * - Stateless infrastructure → host module top, outside TEA.\n * - State-conditional resume → a `boot` Msg the host dispatches once\n * after `run(...)` returns.\n *\n * Violations are caught at runtime by `replay` (which throws with a\n * pointer to the alternatives).\n */\n init: (loaded: S | null, ctx: Ctx) => readonly [S, readonly C[]];\n update:\n | Reducer<S, M, C>\n // Wrap in tuple to disable distributive conditional behavior. Without\n // `[S]`, TS distributes the conditional across `S`'s union members,\n // producing `Transitions<A,...> | Transitions<B,...>` instead of the\n // desired `Transitions<A|B,...>` — the table form needs a single\n // mapping over the full union of state types, not separate tables per\n // member.\n | ([S] extends [{ type: string }] ? Transitions<S, M, C> : never);\n /**\n * Dep-keyed Subs. Each declares the state slice it depends on; the substrate\n * derives the id (`structuralHash(deps)`) and the gate (`deps` non-null) and\n * folds them into the SAME reconcile loop `subscriptions` feeds. The author\n * never writes `subId(...)` and never lists Subs in a central\n * `subscriptions(state)` aggregate — a per-Sub gate travels with the Sub, so\n * a new phase can't silently forget to arm it.\n *\n * The deps slice type is erased to `unknown` per entry — each Sub's slice\n * differs, but the slice type never crosses this field (the kernel only needs\n * to hash it and pass `state` back to that same entry's `source`).\n *\n * Optional and independent of `subscriptions` / `subscribe`: a machine that\n * omits `subs` reconciles exactly as before.\n *\n * Strengthens invariant 4 (lifecycle owned by the substrate) and invariant 7\n * (identity derived, not hand-authored).\n */\n subs?: ReadonlyArray<DepKeyedSub<S, M, Ctx>>;\n /**\n * Instance-identity filter. Declares THIS instance's identity once; the\n * substrate drops any message addressed to a DIFFERENT identity before it\n * reaches `update`. Replaces the per-cell `if (msg.runId !== state.runId)`\n * guard entirely — the reducer never sees a foreign-instance message, so no\n * cell needs to check.\n *\n * Opt-in: a machine that omits `identity` skips the filter (every message\n * reaches `update`, exactly as before). A message whose `ofMsg` returns\n * `undefined` is identity-agnostic and always reaches `update` (lifecycle /\n * pre-identity messages).\n *\n * Strengthens invariant 7 (identity is explicit — declared once, enforced by\n * the substrate) and invariant 6 (mis-addressed messages dropped at one\n * observable point, not per cell).\n */\n identity?: Identity<S, M>;\n /**\n * Manual Sub aggregate — the documented escape hatch, KEPT alongside `subs`.\n * The machine that needs cross-Sub logic the per-Sub `deps` fold can't\n * express lists Subs here; the substrate reconciles them via `subscribe`.\n * Both paths feed ONE reconcile pass.\n */\n subscriptions?: (state: S) => readonly U[];\n subscribe?: Subscribe<M, U, Ctx>;\n /**\n * The update form (\"reducer\" | \"transitions\"), stamped non-enumerably by\n * `defineMachine` at construction (see `UpdateForm` / `formOf`). Optional in\n * the type so the structural `Machine` annotation form keeps accepting plain\n * object literals; readers go through `formOf`, which falls back to\n * `detectUpdateForm` when the tag is absent. Never written by hand.\n */\n readonly __form?: UpdateForm;\n} & ([C] extends [Cmd<never>]\n ? { interpret?: Interpret<M, C, Ctx> }\n : { interpret: Interpret<M, C, Ctx> }) &\n // `subscribe`/`subscriptions` are conditionally REQUIRED the same way\n // `interpret` is (#276): a machine declaring a real Sub union without a\n // subscribe map compiled and silently wired no subs (`reconcileSubs` skips\n // undefined handlers) — the exact silent-failure class the `interpret`\n // conditional prevents. The optional declarations above stay as U's\n // inference sites (a conditional type is not an inference site); this\n // intersection only adds requiredness when U is a real union. Same\n // tuple-wrap trick as `interpret` to disable distribution.\n ([U] extends [Sub<never>]\n ? unknown\n : {\n subscriptions: (state: S) => readonly U[];\n subscribe: Subscribe<M, U, Ctx>;\n });\n\n// === foldUpdates: the single internal fold `replay` and `foldMsgs` share ===\n//\n// Folds `machine.update` over `msgs` from `initialState`, dispatching each\n// Msg through `applyCell` — the same primitive `run` uses — so every fold site\n// agrees on the reducer-vs-transitions form by construction (no second copy of\n// the dispatch to drift). Returns the final state plus the Cmds the cells emitted\n// along the way; the caller keeps them (`replay`) or discards them (`foldMsgs`).\n//\n// Touches no `Store`, no `interpret` handler, and starts no subscription — it\n// only calls `update` cells. The two public folds differ only in how they\n// enter and what they return: `replay` enters via `init` and returns\n// `{ state, cmds, subs }`; `foldMsgs` enters from a base state and returns `S`.\nexport function foldUpdates<S, M extends { type: string }, C extends Cmd>(\n machine: { update: object; __form?: UpdateForm },\n initialState: S,\n msgs: readonly M[],\n): { state: S; cmds: C[] } {\n let state: S = initialState;\n const cmds: C[] = [];\n\n for (const msg of msgs) {\n const [next, emitted] = applyCellChecked<S, M, C>(machine, state, msg);\n state = next;\n cmds.push(...emitted);\n }\n\n return { state, cmds };\n}\n\n// === foldMsgs: runtime-free client-prediction fold seam (ADR 0006, #211) ===\n//\n// Folds `machine.update` over an ordered `Msg[]` starting from a caller-supplied\n// `base` state and returns the resulting state ONLY. This is the client-side\n// replay primitive a prediction loop needs (the Gambetta/Valve\n// authoritative-server reconcile step): re-simulate a queue of un-acked inputs\n// on top of an authoritative snapshot —\n// `foldMsgs(machine, snapshot, pendingInputs)`.\n//\n// Distinct from `replay` (the test idiom), by design (ADR 0006):\n// - Enters from a direct `base` parameter, NOT via `init`/`loaded` — so\n// reconciliation correctness never depends on the machine author's `init`\n// rehydrate discipline. Takes no `ctx` (the fold calls `update` only).\n// - Returns just the final `S` — not `{ state, cmds, subs }`. During prediction\n// the inputs' effects were already sent to the server; re-emitting their Cmds\n// on the client would double-fire, and `subs` are a runtime concern. Returning\n// only `S` makes \"replay fires no effects\" structural, not caller discipline.\n// A caller that wants the emitted Cmds for assertions uses `replay`.\n//\n// Invokes no `Store`, no `interpret` handler, and starts no subscription — it\n// shares `replay`'s `foldUpdates` fold, which only calls `update` cells, and\n// reads the reducer-vs-transitions form via the same `formOf(machine)` reader\n// `run`/`replay` use, so it agrees with them by construction.\n//\n// Per ADR 0006 the runtime-free *guarantee* lives on a dedicated\n// `@demlik/tea/pure` subpath whose module graph never reaches `run`; that\n// subpath export + import-graph guard are #213's scope. Here `foldMsgs` ships\n// as a reachable public API from root; #213 formalizes the boundary.\nexport function foldMsgs<\n S,\n M extends { type: string },\n C extends Cmd,\n U extends Sub,\n Ctx,\n>(machine: Machine<S, M, C, U, Ctx>, base: S, msgs: readonly M[]): S {\n return foldUpdates<S, M, C>(machine, base, msgs).state;\n}\n"]}
@@ -1,10 +1,10 @@
1
- import { createMonitoredRun } from './chunk-CAXMQJJ3.js';
2
- import { createLlmCall } from './chunk-VBUCHVCV.js';
3
- import { createResilientCall } from './chunk-IXBKFQH5.js';
1
+ import { createMonitoredRun } from './chunk-ZILCH3WS.js';
2
+ import { createLlmCall } from './chunk-T6HDAPMR.js';
3
+ import { createResilientCall } from './chunk-W2S4HK7J.js';
4
4
  import { MsgType } from './chunk-JELSZN25.js';
5
5
  import { createFanOut, initFanOut } from './chunk-D736QUGF.js';
6
- import { subscribeDeadline } from './chunk-RMX4EOQK.js';
7
- import { defineMachine } from './chunk-BP2R7TPP.js';
6
+ import { subscribeDeadline } from './chunk-ZT3MGQB7.js';
7
+ import { defineMachine } from './chunk-PBAYOJJO.js';
8
8
 
9
9
  // src/agent/schema.ts
10
10
  function schemaFromGuard(guard, name) {
@@ -515,5 +515,5 @@ function createAgent(config) {
515
515
  }
516
516
 
517
517
  export { COMPACTION_PURPOSE, agentBootMsg, agentEvents, agentTurnSchema, compactionSummarySchema, createAgent, isAgentTurn, isCompactionSummary, liftAgent, mergeInterpret, status };
518
- //# sourceMappingURL=chunk-WGGAMW7Y.js.map
519
- //# sourceMappingURL=chunk-WGGAMW7Y.js.map
518
+ //# sourceMappingURL=chunk-KEFMI24G.js.map
519
+ //# sourceMappingURL=chunk-KEFMI24G.js.map