@demlik/tea 0.7.0 → 0.8.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 (45) hide show
  1. package/dist/chart/index.d.ts +72 -789
  2. package/dist/chart/index.js +4 -232
  3. package/dist/chart/index.js.map +1 -1
  4. package/dist/chart/inspect/index.d.ts +85 -0
  5. package/dist/chart/inspect/index.js +5 -0
  6. package/dist/chart/inspect/index.js.map +1 -0
  7. package/dist/chart/inspect/react.d.ts +112 -0
  8. package/dist/chart/inspect/react.js +11 -0
  9. package/dist/chart/inspect/react.js.map +1 -0
  10. package/dist/chart/inspect/styles.css +286 -0
  11. package/dist/chart/lane/index.d.ts +361 -0
  12. package/dist/chart/lane/index.js +9 -0
  13. package/dist/chart/lane/index.js.map +1 -0
  14. package/dist/chart/lane/react.d.ts +83 -0
  15. package/dist/chart/lane/react.js +559 -0
  16. package/dist/chart/lane/react.js.map +1 -0
  17. package/dist/chart/lane/styles.css +498 -0
  18. package/dist/chart/report/index.d.ts +116 -0
  19. package/dist/chart/report/index.js +32 -0
  20. package/dist/chart/report/index.js.map +1 -0
  21. package/dist/chunk-5KJKJOFC.js +758 -0
  22. package/dist/chunk-5KJKJOFC.js.map +1 -0
  23. package/dist/chunk-E6GEDSF4.js +307 -0
  24. package/dist/chunk-E6GEDSF4.js.map +1 -0
  25. package/dist/chunk-JER5O7PQ.js +416 -0
  26. package/dist/chunk-JER5O7PQ.js.map +1 -0
  27. package/dist/chunk-UOC2PL46.js +13 -0
  28. package/dist/chunk-UOC2PL46.js.map +1 -0
  29. package/dist/chunk-WRAA7GVZ.js +209 -0
  30. package/dist/chunk-WRAA7GVZ.js.map +1 -0
  31. package/dist/chunk-ZCL7SCBK.js +685 -0
  32. package/dist/chunk-ZCL7SCBK.js.map +1 -0
  33. package/dist/chunk-ZD6PNZBK.js +457 -0
  34. package/dist/chunk-ZD6PNZBK.js.map +1 -0
  35. package/dist/devtools/index.js +2 -207
  36. package/dist/devtools/index.js.map +1 -1
  37. package/dist/fold-Blbhru48.d.ts +435 -0
  38. package/dist/graph-CcjWZVrd.d.ts +923 -0
  39. package/dist/machine-viz/index.d.ts +18 -1
  40. package/dist/machine-viz/index.js +2 -8
  41. package/dist/machine-viz/index.js.map +1 -1
  42. package/dist/reducer-BqyoVyBG.d.ts +447 -0
  43. package/dist/run-BVeqJDBF.d.ts +713 -0
  44. package/dist/samples-DCt6nF8H.d.ts +59 -0
  45. package/package.json +24 -1
@@ -1,5 +1,22 @@
1
1
  import { C as Cmd, S as Sub, M as Machine } from '../core-Bjc0bS8j.js';
2
2
 
3
+ /**
4
+ * Sanitize a discriminant string into a Mermaid-safe node identifier.
5
+ *
6
+ * Any run of non-identifier characters becomes `_`; a leading digit is
7
+ * prefixed so the id is always a valid Mermaid token; the empty string gets a
8
+ * stable placeholder. Deterministic and idempotent — `safeId(safeId(x))` is
9
+ * `safeId(x)`, so a diagram may sanitize the same name at several sites.
10
+ */
11
+ declare function safeId(raw: string): string;
12
+ /**
13
+ * Sanitize text used as a Mermaid edge/transition label (the part after `:`).
14
+ *
15
+ * Strips the characters that terminate or confuse a label: newlines, the `:`
16
+ * that separates label from target, and Mermaid's `"`/`;` delimiters.
17
+ */
18
+ declare function safeLabel(raw: string): string;
19
+
3
20
  /**
4
21
  * @demlik/tea/machine-viz — turn a `Machine` into a Mermaid diagram string.
5
22
  *
@@ -75,4 +92,4 @@ declare function toMermaid<S, M extends {
75
92
  type: string;
76
93
  }, C extends Cmd, U extends Sub, Ctx>(machine: Machine<S, M, C, U, Ctx>, opts?: MachineVizOptions<NoInfer<S>, NoInfer<M>>): string;
77
94
 
78
- export { type MachineVizOptions, toMermaid };
95
+ export { type MachineVizOptions, safeId, safeLabel, toMermaid };
@@ -1,15 +1,9 @@
1
+ import { safeId, safeLabel } from '../chunk-UOC2PL46.js';
2
+ export { safeId, safeLabel } from '../chunk-UOC2PL46.js';
1
3
  import '../chunk-5EHLEUTL.js';
2
4
  import { formOf } from '../chunk-B5VXOFMZ.js';
3
5
 
4
6
  // src/machine-viz/index.ts
5
- function safeId(raw) {
6
- const cleaned = raw.replace(/[^A-Za-z0-9_]/g, "_");
7
- if (cleaned === "") return "_empty";
8
- return /^[0-9]/.test(cleaned) ? `s_${cleaned}` : cleaned;
9
- }
10
- function safeLabel(raw) {
11
- return raw.replace(/["\n\r;:]/g, " ").trim();
12
- }
13
7
  function toMermaid(machine, opts = {}) {
14
8
  const direction = opts.direction ?? "TB";
15
9
  const stateSamples = opts.samples?.states ?? {};
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/machine-viz/index.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\n * @demlik/tea/machine-viz — turn a `Machine` into a Mermaid diagram string.\n *\n * PURE. No deps, no AST parsing, no file I/O. The function reads only the\n * `Machine` value it is handed plus optional sample states/msgs/ctx.\n *\n * The trick: TEA update cells are PURE (invariant 2 — they return\n * `[nextState, cmds]` as data, no effects). So we can SAFELY EXECUTE a cell to\n * discover its target state — exactly what `replay` does. That gives us REAL\n * edges (`red --> green : go`) without parsing source. When no sample is\n * provided for a `(state, msg)` pair we cannot resolve the target, so we fall\n * back to a STRUCTURAL self-edge annotated with `?` — visually distinct from a\n * resolved edge and documented in the diagram's legend.\n *\n * Form detection is `formOf(machine)` — the same reader `run`/`replay` use,\n * honoring the authoritative `__form` tag `defineMachine` stamps (falling back\n * to the structural heuristic for machines built without it; #275).\n *\n * What we MUST NOT do (these are the effectful paths, off-limits here):\n * - call `interpret[*]` handlers (perform I/O)\n * - call `subscribe[*]` handlers (install listeners)\n * - call `run` / the runtime\n * We MAY call `init`, `update[*]` cells, and `subscriptions(state)` — all pure\n * by the invariants (same set `replay` is allowed to touch).\n */\n\nimport type { Cmd, Machine, Sub } from \"../index\";\nimport { formOf } from \"../index\";\n\n// === Public options ===\n\n/**\n * Options controlling diagram emission.\n *\n * `samples` is the lever that turns STRUCTURAL edges into RESOLVED edges. For a\n * Transitions machine, providing a sample state for `stateType` AND a sample\n * msg for `msgType` lets us execute `update[stateType][msgType](state, msg)` and\n * read the real target `next.type`. Sample maps are keyed by the discriminant\n * string (`state.type` / `msg.type`).\n *\n * `samples.ctx` is the argument to `init(null, ctx)` — provide it to draw the\n * `[*] -->` initial-state edge. Omitted ⇒ no init edge (annotated as such),\n * because `init`'s signature requires a ctx and we will not invent one.\n */\nexport interface MachineVizOptions<S, M> {\n samples?: {\n /** Sample state per `state.type` — unlocks RESOLVED edges + sub annotations. */\n states?: Partial<Record<string, S>>;\n /** Sample msg per `msg.type` — unlocks RESOLVED edges. */\n msgs?: Partial<Record<string, M>>;\n /** Ctx for `init(null, ctx)` — unlocks the `[*] -->` initial-state edge. */\n ctx?: unknown;\n };\n /** Mermaid layout direction. Default `\"TB\"` (top-to-bottom). */\n direction?: \"TB\" | \"LR\";\n /** Optional diagram title, emitted as a Mermaid front-matter `title:` block. */\n title?: string;\n}\n\n// === Mermaid label escaping ===\n//\n// Mermaid state ids must be identifier-safe; arbitrary `type` strings (which\n// CAN contain `:`, spaces, `-`, etc.) are not. We sanitize the id used as the\n// node identifier and carry the human-readable original as a quoted label via\n// `stateId : \"Original\"` when they differ. Edge transition labels (after the\n// `:`) are sanitized to avoid breaking the arrow syntax.\n\n/** Sanitize a discriminant string into a Mermaid-safe node identifier. */\nfunction safeId(raw: string): string {\n // Replace any run of non-identifier chars with `_`; prefix a leading digit so\n // the id is always a valid Mermaid token. Empty ⇒ a stable placeholder.\n const cleaned = raw.replace(/[^A-Za-z0-9_]/g, \"_\");\n if (cleaned === \"\") return \"_empty\";\n return /^[0-9]/.test(cleaned) ? `s_${cleaned}` : cleaned;\n}\n\n/** Sanitize text used as a Mermaid edge/transition label (after the `:`). */\nfunction safeLabel(raw: string): string {\n // Strip the chars that terminate or confuse a transition label: newlines,\n // the `:` that separates label from target, and Mermaid's `\"`/`;` delimiters.\n return raw.replace(/[\"\\n\\r;:]/g, \" \").trim();\n}\n\n// === Edge model ===\n\ntype Edge =\n // Resolved: we executed the cell with samples and read the real target.\n | { kind: \"resolved\"; from: string; to: string; msg: string }\n // Structural: no samples for the pair → target unknown, drawn as a self-loop.\n | { kind: \"structural\"; from: string; msg: string };\n\n// === toMermaid ===\n\n/**\n * Render a `Machine` as a Mermaid `stateDiagram-v2` string.\n *\n * - **Transitions form** (`update` is a record-of-records): nodes are the\n * `state.type` keys; each `(stateType × msgType)` cell becomes an edge. With\n * samples for both, the edge is RESOLVED by executing the (pure) cell. Without\n * samples it is STRUCTURAL — a `from --> from : msg?` self-loop (the `?`\n * suffix marks \"target unresolved\"). A legend note documents the distinction.\n * The `[*] -->` init edge is drawn only when `samples.ctx` is provided.\n *\n * - **Reducer form** (`update` values are functions): there is no static phase\n * graph (state is not a discriminated union over phases). We emit a single\n * node listing the handled `msg.type`s and an honest note saying so. No edges\n * are invented.\n *\n * Output is deterministic for the same input (keys iterated in insertion order,\n * no clocks, no randomness).\n */\nexport function toMermaid<\n S,\n M extends { type: string },\n C extends Cmd,\n U extends Sub,\n Ctx,\n>(\n machine: Machine<S, M, C, U, Ctx>,\n // `NoInfer` pins S/M to the `machine` argument so sample object literals in\n // `opts` are CHECKED against the machine's State/Msg unions rather than\n // driving inference (which would widen `{ type: \"open\" }` to `{ type: string }`\n // and collide with the machine's discriminated union).\n opts: MachineVizOptions<NoInfer<S>, NoInfer<M>> = {},\n): string {\n const direction = opts.direction ?? \"TB\";\n const stateSamples = opts.samples?.states ?? {};\n const msgSamples = opts.samples?.msgs ?? {};\n const hasCtx = opts.samples !== undefined && \"ctx\" in opts.samples;\n\n const mode = formOf(machine);\n const lines: string[] = [];\n\n // Front matter — Mermaid reads a YAML `title:` block before the diagram.\n if (opts.title !== undefined) {\n lines.push(\"---\", `title: ${opts.title}`, \"---\");\n }\n lines.push(\"stateDiagram-v2\");\n lines.push(` direction ${direction}`);\n\n if (mode === \"reducer\") {\n emitReducer(machine, lines);\n } else {\n emitTransitions(machine, lines, {\n stateSamples,\n msgSamples,\n hasCtx,\n ctx: opts.samples?.ctx,\n });\n }\n\n // Subscriptions annotation — pure call to `subscriptions(sampleState)` per\n // sampled state. Skipped entirely when no state samples are provided (we\n // refuse to invent a state to feed it).\n emitSubscriptions(machine, lines, stateSamples);\n\n return lines.join(\"\\n\");\n}\n\n// === Reducer-form emission ===\n\n/**\n * Reducer form has no phase graph. Emit one node listing the handled msg types\n * plus an honest note. The msg set is the `update` record's keys (the Reducer\n * mapped type guarantees one cell per Msg variant).\n */\nfunction emitReducer(machine: { update: unknown }, lines: string[]): void {\n const msgTypes = Object.keys(machine.update as object);\n lines.push(\" state ReducerMachine {\");\n if (msgTypes.length === 0) {\n lines.push(\" [*] --> NoMsgs\");\n lines.push(' NoMsgs : \"(no msg variants — M is never)\"');\n } else {\n for (const m of msgTypes) {\n lines.push(` ${safeId(m)} : ${safeLabel(m)}`);\n }\n }\n lines.push(\" }\");\n // The note IS the design honesty: no edges, because reducer-form state is not\n // a discriminated union over phases — there is no static target to draw.\n lines.push(\" note right of ReducerMachine\");\n lines.push(\" Reducer-form machine: flat dispatch keyed by msg.type.\");\n lines.push(\n \" No phase graph — state is not a discriminated union over phases.\",\n );\n lines.push(` Handled msg types: ${msgTypes.length}`);\n lines.push(\" end note\");\n}\n\n// === Transitions-form emission ===\n\ninterface TransitionsCtx<S, M> {\n stateSamples: Partial<Record<string, S>>;\n msgSamples: Partial<Record<string, M>>;\n hasCtx: boolean;\n ctx: unknown;\n}\n\n/**\n * Transitions form: walk the `update[stateType][msgType]` table. Execute the\n * (pure) cell when both samples exist → RESOLVED edge; otherwise → STRUCTURAL\n * self-loop. Draw the `[*] -->` init edge only when a ctx sample is given.\n */\nfunction emitTransitions<\n S,\n M extends { type: string },\n C extends Cmd,\n U extends Sub,\n Ctx,\n>(\n machine: Machine<S, M, C, U, Ctx>,\n lines: string[],\n ctx: TransitionsCtx<S, M>,\n): void {\n type CellFn = (state: S, msg: M) => readonly [S, readonly C[]];\n type Table = Record<string, Record<string, CellFn>>;\n const table = machine.update as unknown as Table;\n const stateTypes = Object.keys(table);\n\n // Declare every node up front so even unreachable phases render.\n for (const st of stateTypes) {\n lines.push(` ${safeId(st)}`);\n }\n\n // Init edge — only resolvable with a ctx sample. `init(null, ctx)` is pure\n // (invariant 2: fresh-boot branch returns a starting state). Read `next.type`.\n if (ctx.hasCtx) {\n const [initState] = machine.init(null, ctx.ctx as Ctx);\n const initType = (initState as unknown as { type: string }).type;\n lines.push(` [*] --> ${safeId(initType)}`);\n } else {\n lines.push(\n \" %% init edge omitted: no samples.ctx provided to run init(null, ctx)\",\n );\n }\n\n let anyStructural = false;\n for (const st of stateTypes) {\n const row = table[st];\n if (row === undefined) continue;\n const msgTypes = Object.keys(row);\n for (const mt of msgTypes) {\n const edge = resolveEdge(row, st, mt, ctx);\n if (edge.kind === \"resolved\") {\n lines.push(\n ` ${safeId(edge.from)} --> ${safeId(edge.to)} : ${safeLabel(edge.msg)}`,\n );\n } else {\n anyStructural = true;\n // Structural self-loop with a `?` suffix — target unresolved. Visually\n // distinct from a resolved edge (self-loop + trailing `?`).\n lines.push(\n ` ${safeId(edge.from)} --> ${safeId(edge.from)} : ${safeLabel(edge.msg)}?`,\n );\n }\n }\n }\n\n // Legend — document the resolved-vs-structural distinction in the output.\n lines.push(` note right of ${safeId(stateTypes[0] ?? \"_empty\")}`);\n lines.push(\" Transitions-form machine.\");\n lines.push(\" Edge a --> b : msg = RESOLVED (cell executed with samples).\");\n if (anyStructural) {\n lines.push(\n \" Self-loop a --> a : msg? = STRUCTURAL (no sample; target unresolved).\",\n );\n }\n lines.push(\" end note\");\n}\n\n/**\n * Resolve a single `(stateType, msgType)` cell into an Edge. With both a sample\n * state and a sample msg, EXECUTE the pure cell to read the real target. Any\n * missing sample → structural. A thrown cell (e.g. a sample shaped wrong for\n * the cell) degrades to structural rather than crashing the whole diagram —\n * the diagram is a best-effort visualization, not a correctness gate.\n */\nfunction resolveEdge<S, M extends { type: string }, C extends Cmd>(\n row: Record<string, (state: S, msg: M) => readonly [S, readonly C[]]>,\n stateType: string,\n msgType: string,\n ctx: {\n stateSamples: Partial<Record<string, S>>;\n msgSamples: Partial<Record<string, M>>;\n },\n): Edge {\n const sampleState = ctx.stateSamples[stateType];\n const sampleMsg = ctx.msgSamples[msgType];\n const cell = row[msgType];\n if (\n sampleState === undefined ||\n sampleMsg === undefined ||\n cell === undefined\n ) {\n return { kind: \"structural\", from: stateType, msg: msgType };\n }\n try {\n const [next] = cell(sampleState, sampleMsg);\n const to = (next as unknown as { type: string }).type;\n return { kind: \"resolved\", from: stateType, to, msg: msgType };\n } catch {\n // Mismatched sample → cannot resolve. Fall back to structural.\n return { kind: \"structural\", from: stateType, msg: msgType };\n }\n}\n\n// === Subscriptions annotation ===\n\n/**\n * For each sampled state, call the (pure) `subscriptions(state)` description\n * function and annotate which Sub types are active in that state as a Mermaid\n * note. We call `subscriptions` (the description) but NEVER `subscribe[*]` (the\n * effectful installer) — same boundary `replay` honors. Skipped when no state\n * samples are provided or the machine declares no `subscriptions`.\n */\nfunction emitSubscriptions<\n S,\n M extends { type: string },\n C extends Cmd,\n U extends Sub,\n Ctx,\n>(\n machine: Machine<S, M, C, U, Ctx>,\n lines: string[],\n stateSamples: Partial<Record<string, S>>,\n): void {\n if (machine.subscriptions === undefined) return;\n const stateTypes = Object.keys(stateSamples);\n if (stateTypes.length === 0) return;\n\n for (const st of stateTypes) {\n const sample = stateSamples[st];\n if (sample === undefined) continue;\n const subs = machine.subscriptions(sample);\n const subTypes = subs.map((s) => s.type);\n const body =\n subTypes.length === 0\n ? \"(no active subs)\"\n : `subs: ${subTypes.map(safeLabel).join(\", \")}`;\n lines.push(` note left of ${safeId(st)}`);\n lines.push(` ${body}`);\n lines.push(\" end note\");\n }\n}\n"]}
1
+ {"version":3,"sources":["../../src/machine-viz/index.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\n * @demlik/tea/machine-viz — turn a `Machine` into a Mermaid diagram string.\n *\n * PURE. No deps, no AST parsing, no file I/O. The function reads only the\n * `Machine` value it is handed plus optional sample states/msgs/ctx.\n *\n * The trick: TEA update cells are PURE (invariant 2 — they return\n * `[nextState, cmds]` as data, no effects). So we can SAFELY EXECUTE a cell to\n * discover its target state — exactly what `replay` does. That gives us REAL\n * edges (`red --> green : go`) without parsing source. When no sample is\n * provided for a `(state, msg)` pair we cannot resolve the target, so we fall\n * back to a STRUCTURAL self-edge annotated with `?` — visually distinct from a\n * resolved edge and documented in the diagram's legend.\n *\n * Form detection is `formOf(machine)` — the same reader `run`/`replay` use,\n * honoring the authoritative `__form` tag `defineMachine` stamps (falling back\n * to the structural heuristic for machines built without it; #275).\n *\n * What we MUST NOT do (these are the effectful paths, off-limits here):\n * - call `interpret[*]` handlers (perform I/O)\n * - call `subscribe[*]` handlers (install listeners)\n * - call `run` / the runtime\n * We MAY call `init`, `update[*]` cells, and `subscriptions(state)` — all pure\n * by the invariants (same set `replay` is allowed to touch).\n */\n\nimport type { Cmd, Machine, Sub } from \"../index\";\nimport { formOf } from \"../index\";\nimport { safeId, safeLabel } from \"./mermaid-id\";\n\n// The sanitizers are SHARED with `chart`'s `chartMermaid` — one copy, in a\n// dependency-free module, re-exported here because both drawings are public\n// and a consumer post-processing either one needs the same escaping.\nexport { safeId, safeLabel } from \"./mermaid-id\";\n\n// === Public options ===\n\n/**\n * Options controlling diagram emission.\n *\n * `samples` is the lever that turns STRUCTURAL edges into RESOLVED edges. For a\n * Transitions machine, providing a sample state for `stateType` AND a sample\n * msg for `msgType` lets us execute `update[stateType][msgType](state, msg)` and\n * read the real target `next.type`. Sample maps are keyed by the discriminant\n * string (`state.type` / `msg.type`).\n *\n * `samples.ctx` is the argument to `init(null, ctx)` — provide it to draw the\n * `[*] -->` initial-state edge. Omitted ⇒ no init edge (annotated as such),\n * because `init`'s signature requires a ctx and we will not invent one.\n */\nexport interface MachineVizOptions<S, M> {\n samples?: {\n /** Sample state per `state.type` — unlocks RESOLVED edges + sub annotations. */\n states?: Partial<Record<string, S>>;\n /** Sample msg per `msg.type` — unlocks RESOLVED edges. */\n msgs?: Partial<Record<string, M>>;\n /** Ctx for `init(null, ctx)` — unlocks the `[*] -->` initial-state edge. */\n ctx?: unknown;\n };\n /** Mermaid layout direction. Default `\"TB\"` (top-to-bottom). */\n direction?: \"TB\" | \"LR\";\n /** Optional diagram title, emitted as a Mermaid front-matter `title:` block. */\n title?: string;\n}\n\n// === Edge model ===\n\ntype Edge =\n // Resolved: we executed the cell with samples and read the real target.\n | { kind: \"resolved\"; from: string; to: string; msg: string }\n // Structural: no samples for the pair → target unknown, drawn as a self-loop.\n | { kind: \"structural\"; from: string; msg: string };\n\n// === toMermaid ===\n\n/**\n * Render a `Machine` as a Mermaid `stateDiagram-v2` string.\n *\n * - **Transitions form** (`update` is a record-of-records): nodes are the\n * `state.type` keys; each `(stateType × msgType)` cell becomes an edge. With\n * samples for both, the edge is RESOLVED by executing the (pure) cell. Without\n * samples it is STRUCTURAL — a `from --> from : msg?` self-loop (the `?`\n * suffix marks \"target unresolved\"). A legend note documents the distinction.\n * The `[*] -->` init edge is drawn only when `samples.ctx` is provided.\n *\n * - **Reducer form** (`update` values are functions): there is no static phase\n * graph (state is not a discriminated union over phases). We emit a single\n * node listing the handled `msg.type`s and an honest note saying so. No edges\n * are invented.\n *\n * Output is deterministic for the same input (keys iterated in insertion order,\n * no clocks, no randomness).\n */\nexport function toMermaid<\n S,\n M extends { type: string },\n C extends Cmd,\n U extends Sub,\n Ctx,\n>(\n machine: Machine<S, M, C, U, Ctx>,\n // `NoInfer` pins S/M to the `machine` argument so sample object literals in\n // `opts` are CHECKED against the machine's State/Msg unions rather than\n // driving inference (which would widen `{ type: \"open\" }` to `{ type: string }`\n // and collide with the machine's discriminated union).\n opts: MachineVizOptions<NoInfer<S>, NoInfer<M>> = {},\n): string {\n const direction = opts.direction ?? \"TB\";\n const stateSamples = opts.samples?.states ?? {};\n const msgSamples = opts.samples?.msgs ?? {};\n const hasCtx = opts.samples !== undefined && \"ctx\" in opts.samples;\n\n const mode = formOf(machine);\n const lines: string[] = [];\n\n // Front matter — Mermaid reads a YAML `title:` block before the diagram.\n if (opts.title !== undefined) {\n lines.push(\"---\", `title: ${opts.title}`, \"---\");\n }\n lines.push(\"stateDiagram-v2\");\n lines.push(` direction ${direction}`);\n\n if (mode === \"reducer\") {\n emitReducer(machine, lines);\n } else {\n emitTransitions(machine, lines, {\n stateSamples,\n msgSamples,\n hasCtx,\n ctx: opts.samples?.ctx,\n });\n }\n\n // Subscriptions annotation — pure call to `subscriptions(sampleState)` per\n // sampled state. Skipped entirely when no state samples are provided (we\n // refuse to invent a state to feed it).\n emitSubscriptions(machine, lines, stateSamples);\n\n return lines.join(\"\\n\");\n}\n\n// === Reducer-form emission ===\n\n/**\n * Reducer form has no phase graph. Emit one node listing the handled msg types\n * plus an honest note. The msg set is the `update` record's keys (the Reducer\n * mapped type guarantees one cell per Msg variant).\n */\nfunction emitReducer(machine: { update: unknown }, lines: string[]): void {\n const msgTypes = Object.keys(machine.update as object);\n lines.push(\" state ReducerMachine {\");\n if (msgTypes.length === 0) {\n lines.push(\" [*] --> NoMsgs\");\n lines.push(' NoMsgs : \"(no msg variants — M is never)\"');\n } else {\n for (const m of msgTypes) {\n lines.push(` ${safeId(m)} : ${safeLabel(m)}`);\n }\n }\n lines.push(\" }\");\n // The note IS the design honesty: no edges, because reducer-form state is not\n // a discriminated union over phases — there is no static target to draw.\n lines.push(\" note right of ReducerMachine\");\n lines.push(\" Reducer-form machine: flat dispatch keyed by msg.type.\");\n lines.push(\n \" No phase graph — state is not a discriminated union over phases.\",\n );\n lines.push(` Handled msg types: ${msgTypes.length}`);\n lines.push(\" end note\");\n}\n\n// === Transitions-form emission ===\n\ninterface TransitionsCtx<S, M> {\n stateSamples: Partial<Record<string, S>>;\n msgSamples: Partial<Record<string, M>>;\n hasCtx: boolean;\n ctx: unknown;\n}\n\n/**\n * Transitions form: walk the `update[stateType][msgType]` table. Execute the\n * (pure) cell when both samples exist → RESOLVED edge; otherwise → STRUCTURAL\n * self-loop. Draw the `[*] -->` init edge only when a ctx sample is given.\n */\nfunction emitTransitions<\n S,\n M extends { type: string },\n C extends Cmd,\n U extends Sub,\n Ctx,\n>(\n machine: Machine<S, M, C, U, Ctx>,\n lines: string[],\n ctx: TransitionsCtx<S, M>,\n): void {\n type CellFn = (state: S, msg: M) => readonly [S, readonly C[]];\n type Table = Record<string, Record<string, CellFn>>;\n const table = machine.update as unknown as Table;\n const stateTypes = Object.keys(table);\n\n // Declare every node up front so even unreachable phases render.\n for (const st of stateTypes) {\n lines.push(` ${safeId(st)}`);\n }\n\n // Init edge — only resolvable with a ctx sample. `init(null, ctx)` is pure\n // (invariant 2: fresh-boot branch returns a starting state). Read `next.type`.\n if (ctx.hasCtx) {\n const [initState] = machine.init(null, ctx.ctx as Ctx);\n const initType = (initState as unknown as { type: string }).type;\n lines.push(` [*] --> ${safeId(initType)}`);\n } else {\n lines.push(\n \" %% init edge omitted: no samples.ctx provided to run init(null, ctx)\",\n );\n }\n\n let anyStructural = false;\n for (const st of stateTypes) {\n const row = table[st];\n if (row === undefined) continue;\n const msgTypes = Object.keys(row);\n for (const mt of msgTypes) {\n const edge = resolveEdge(row, st, mt, ctx);\n if (edge.kind === \"resolved\") {\n lines.push(\n ` ${safeId(edge.from)} --> ${safeId(edge.to)} : ${safeLabel(edge.msg)}`,\n );\n } else {\n anyStructural = true;\n // Structural self-loop with a `?` suffix — target unresolved. Visually\n // distinct from a resolved edge (self-loop + trailing `?`).\n lines.push(\n ` ${safeId(edge.from)} --> ${safeId(edge.from)} : ${safeLabel(edge.msg)}?`,\n );\n }\n }\n }\n\n // Legend — document the resolved-vs-structural distinction in the output.\n lines.push(` note right of ${safeId(stateTypes[0] ?? \"_empty\")}`);\n lines.push(\" Transitions-form machine.\");\n lines.push(\" Edge a --> b : msg = RESOLVED (cell executed with samples).\");\n if (anyStructural) {\n lines.push(\n \" Self-loop a --> a : msg? = STRUCTURAL (no sample; target unresolved).\",\n );\n }\n lines.push(\" end note\");\n}\n\n/**\n * Resolve a single `(stateType, msgType)` cell into an Edge. With both a sample\n * state and a sample msg, EXECUTE the pure cell to read the real target. Any\n * missing sample → structural. A thrown cell (e.g. a sample shaped wrong for\n * the cell) degrades to structural rather than crashing the whole diagram —\n * the diagram is a best-effort visualization, not a correctness gate.\n */\nfunction resolveEdge<S, M extends { type: string }, C extends Cmd>(\n row: Record<string, (state: S, msg: M) => readonly [S, readonly C[]]>,\n stateType: string,\n msgType: string,\n ctx: {\n stateSamples: Partial<Record<string, S>>;\n msgSamples: Partial<Record<string, M>>;\n },\n): Edge {\n const sampleState = ctx.stateSamples[stateType];\n const sampleMsg = ctx.msgSamples[msgType];\n const cell = row[msgType];\n if (\n sampleState === undefined ||\n sampleMsg === undefined ||\n cell === undefined\n ) {\n return { kind: \"structural\", from: stateType, msg: msgType };\n }\n try {\n const [next] = cell(sampleState, sampleMsg);\n const to = (next as unknown as { type: string }).type;\n return { kind: \"resolved\", from: stateType, to, msg: msgType };\n } catch {\n // Mismatched sample → cannot resolve. Fall back to structural.\n return { kind: \"structural\", from: stateType, msg: msgType };\n }\n}\n\n// === Subscriptions annotation ===\n\n/**\n * For each sampled state, call the (pure) `subscriptions(state)` description\n * function and annotate which Sub types are active in that state as a Mermaid\n * note. We call `subscriptions` (the description) but NEVER `subscribe[*]` (the\n * effectful installer) — same boundary `replay` honors. Skipped when no state\n * samples are provided or the machine declares no `subscriptions`.\n */\nfunction emitSubscriptions<\n S,\n M extends { type: string },\n C extends Cmd,\n U extends Sub,\n Ctx,\n>(\n machine: Machine<S, M, C, U, Ctx>,\n lines: string[],\n stateSamples: Partial<Record<string, S>>,\n): void {\n if (machine.subscriptions === undefined) return;\n const stateTypes = Object.keys(stateSamples);\n if (stateTypes.length === 0) return;\n\n for (const st of stateTypes) {\n const sample = stateSamples[st];\n if (sample === undefined) continue;\n const subs = machine.subscriptions(sample);\n const subTypes = subs.map((s) => s.type);\n const body =\n subTypes.length === 0\n ? \"(no active subs)\"\n : `subs: ${subTypes.map(safeLabel).join(\", \")}`;\n lines.push(` note left of ${safeId(st)}`);\n lines.push(` ${body}`);\n lines.push(\" end note\");\n }\n}\n"]}
@@ -0,0 +1,447 @@
1
+ import { k as EventOrigin, C as Chart, R as ReducerChart } from './graph-CcjWZVrd.js';
2
+ import { R as RtSamples } from './samples-DCt6nF8H.js';
3
+
4
+ /**
5
+ * WHY a (state, event) pair is refused. The chart's three refusal mechanisms,
6
+ * each carrying the evidence that produced it, so a UI can say *"`WIP` is not
7
+ * addressed to `blocked` — its scope is `edges`"* rather than "disabled".
8
+ *
9
+ * `undeclared` is the fourth case the type layer forbids: a pair that is live,
10
+ * unhandled and unrefused. It cannot occur on a chart that compiled through
11
+ * `defineChart`, and it is reported rather than swallowed because reaching it
12
+ * means the types were bypassed with a cast — which is precisely when a
13
+ * debugger should say so instead of drawing a well-behaved button.
14
+ */
15
+ type RefusalReason = {
16
+ readonly kind: "end";
17
+ /** Terminal state: it accepts nothing, by declaration. */
18
+ readonly state: string;
19
+ } | {
20
+ readonly kind: "out-of-scope";
21
+ /** The event's declared `scope`, flattened to its members. */
22
+ readonly scope: readonly string[];
23
+ /** The phase this state lives in — the one that is not in `scope`. */
24
+ readonly phase: string;
25
+ } | {
26
+ /**
27
+ * `scope: "edges"` — the event is live exactly where an edge declares it,
28
+ * and this state declares none.
29
+ *
30
+ * SPLIT OUT OF `out-of-scope` because the phase has nothing to do with it.
31
+ * `"edges"` is not a phase name, so the phase test could never pass and
32
+ * the refusal came back naming a phase the reader then went looking for:
33
+ * *"`DONE` is not addressed to phase `working` (scope: edges)"* — three
34
+ * pieces of vocabulary for one fact, and the fact is not in there. The
35
+ * default scope is `edges`, so this was most refusals on most charts.
36
+ */
37
+ readonly kind: "no-edge";
38
+ readonly state: string;
39
+ } | {
40
+ readonly kind: "ignored";
41
+ /** The event is live here and this state refuses it BY NAME. */
42
+ readonly state: string;
43
+ } | {
44
+ readonly kind: "undeclared";
45
+ };
46
+ /** A one-line, human-readable rendering of a {@link RefusalReason}. */
47
+ declare function explainRefusal(event: string, reason: RefusalReason): string;
48
+ /** What kind of transition an edge declares. */
49
+ type EdgeKind = "plain" | "guarded" | "cell" | "resume";
50
+ /** One declared edge — a `(state, event)` pair the chart routes. */
51
+ interface ChartEdge {
52
+ readonly from: string;
53
+ readonly event: string;
54
+ /** `"${from}.${event}"` — the site key guards/cmds/cells are indexed by. */
55
+ readonly at: string;
56
+ readonly kind: EdgeKind;
57
+ /**
58
+ * Every state this edge can land on, in declaration order. A guarded edge
59
+ * contributes both arms; a cell edge contributes its whole declared `to`.
60
+ * For a resume edge this is the fallback alone — the real target is
61
+ * `state.was`, which is a property of the LIVE state, not of the chart.
62
+ */
63
+ readonly targets: readonly string[];
64
+ /** The guard's name, on a guarded edge. */
65
+ readonly guard?: string;
66
+ /** The target taken when the guard HOLDS. */
67
+ readonly then?: string;
68
+ /** The target taken when the guard FAILS. */
69
+ readonly otherwise?: string;
70
+ /** The cell's name, on a `{ to, cell }` edge. */
71
+ readonly cell?: string;
72
+ /** The declared fallback, on a `resume` edge. */
73
+ readonly fallback?: string;
74
+ /** Cmds fired when the edge fires (the guard's `then` arm, if guarded). */
75
+ readonly cmds: readonly string[];
76
+ /** Cmds fired when the guard FAILS. Empty unless the edge is guarded. */
77
+ readonly otherwiseCmds: readonly string[];
78
+ }
79
+ /** A (state, event) pair the chart explicitly refuses, and why. */
80
+ interface ChartRefusal {
81
+ readonly from: string;
82
+ readonly event: string;
83
+ readonly reason: RefusalReason;
84
+ }
85
+ /** One state, with the facts the chart declares about it. */
86
+ interface ChartStateInfo {
87
+ readonly name: string;
88
+ /** The phase it was declared under. */
89
+ readonly phase: string;
90
+ /** `initial: true` — the entry state. Exactly one per machine-bearing chart. */
91
+ readonly initial: boolean;
92
+ /**
93
+ * Terminal by DECLARATION, not by "has no outgoing edges" — `true` for both
94
+ * polarities, because both accept nothing and both owe nothing.
95
+ */
96
+ readonly end: boolean;
97
+ /**
98
+ * WHICH ending. `true` a success final, `"error"` the failure terminal,
99
+ * `false` not a final at all.
100
+ *
101
+ * `end` answers "does this accept anything"; this answers "did the machine
102
+ * win or lose". A lane needs both and they are not the same question —
103
+ * `shipped` and `frozen` are equally terminal and are not the same outcome.
104
+ */
105
+ readonly endPolarity: false | true | "error";
106
+ /** Carries a `resume` edge out of it, so the compiler injects `was` here. */
107
+ readonly parking: boolean;
108
+ }
109
+ /** One event of the alphabet. */
110
+ interface ChartEventInfo {
111
+ readonly name: string;
112
+ /** The declared `scope`, flattened: `"edges"`, `"all"`, or phase names. */
113
+ readonly scope: readonly string[];
114
+ /** `foreign: true` — the name belongs to someone else and stays bare. */
115
+ readonly foreign: boolean;
116
+ /**
117
+ * WHERE THIS EVENT COMES FROM — `from`, read back off the chart.
118
+ *
119
+ * `undefined` means the chart does not say, which is a fact of its own and
120
+ * never a default: a UI that groups its button row by sender must render the
121
+ * undeclared ones as undeclared rather than filing them under a guess.
122
+ */
123
+ readonly from?: EventOrigin;
124
+ /**
125
+ * Whether the event DECLARED a payload (`data: ty<…>()`).
126
+ *
127
+ * `ty<T>()` is `{}` at runtime, so `T` is unreadable — but the `data` KEY is
128
+ * right there in the literal, so "does this event carry a payload at all" IS
129
+ * a runtime fact. It is what decides whether a sample is required.
130
+ */
131
+ readonly hasPayload: boolean;
132
+ }
133
+ /**
134
+ * A whole chart, as data a UI can build itself from. Nothing here is supplied
135
+ * by the caller; every field is read off the one chart value.
136
+ */
137
+ interface ChartDescription {
138
+ /** The phases, in declaration order, each with its member states. */
139
+ readonly phases: readonly {
140
+ readonly name: string;
141
+ readonly states: readonly string[];
142
+ }[];
143
+ readonly states: readonly ChartStateInfo[];
144
+ /** The state marked `initial: true`, or `undefined` on a chart with none. */
145
+ readonly initial: string | undefined;
146
+ readonly events: readonly ChartEventInfo[];
147
+ /** Every cmd DECLARED in the `cmds` section — the effect alphabet. */
148
+ readonly cmds: readonly string[];
149
+ /** Every guard name any edge references. */
150
+ readonly guards: readonly string[];
151
+ /** Every cell name any `{ to, cell }` edge references. */
152
+ readonly cells: readonly string[];
153
+ readonly edges: readonly ChartEdge[];
154
+ readonly refusals: readonly ChartRefusal[];
155
+ }
156
+ /**
157
+ * A read edge MINUS the state it leaves — everything about an edge that is a
158
+ * fact of the edge itself rather than of the grid it sits in.
159
+ *
160
+ * The reducer form has no state dimension (`src/chart/inspect/reducer.ts`), so
161
+ * its edges are exactly this: same kinds, same targets, same guard/cell/cmd
162
+ * fields, with `from` unrepresentable rather than blank.
163
+ */
164
+ type EdgeCore = Omit<ChartEdge, "from">;
165
+ /**
166
+ * Read a chart into its {@link ChartDescription}.
167
+ *
168
+ * Pure, total, and allocation-only — it never executes a guard, a cell or a
169
+ * payload builder, so it is safe on a chart whose parts do not exist yet.
170
+ */
171
+ declare function describeChart<const C extends Chart<C>>(chart: C): ChartDescription;
172
+ /** The declared edge out of `state` on `event`, or `undefined`. */
173
+ declare function edgeAt(desc: ChartDescription, state: string, event: string): ChartEdge | undefined;
174
+ /** Why `state` refuses `event`, or `undefined` when it does not. */
175
+ declare function refusalAtState(desc: ChartDescription, state: string, event: string): RefusalReason | undefined;
176
+ /** The record for `state`, or `undefined` when the chart has no such state. */
177
+ declare function stateInfo(desc: ChartDescription, state: string): ChartStateInfo | undefined;
178
+
179
+ /** Any guard, however narrowly its use sites typed it. */
180
+ type AnyGuard = (...args: never[]) => boolean;
181
+ /** Any cell, in either the single-body or the per-site form. */
182
+ type AnyCell = ((...args: never[]) => readonly [{
183
+ readonly type: string;
184
+ }, unknown]) | Readonly<Record<string, unknown>>;
185
+ /**
186
+ * The code parts a preview may consult — the SAME bag `compile` receives, so
187
+ * an author passes the one they already built rather than a second copy.
188
+ * Every field is optional: the preview degrades where a part is absent.
189
+ */
190
+ interface InspectParts {
191
+ readonly guards?: Readonly<Record<string, AnyGuard | undefined>>;
192
+ readonly cells?: Readonly<Record<string, AnyCell | undefined>>;
193
+ }
194
+ /** Everything a live preview may consult, beyond the state itself. */
195
+ interface InspectOptions {
196
+ /** The parts bag `compile` was given. Guards/cells are read from it. */
197
+ readonly parts?: InspectParts;
198
+ /** Sample payload per event — see `./samples`. */
199
+ readonly samples?: RtSamples;
200
+ }
201
+ /** Why a guard could not be evaluated. Each is a fact, not a guess. */
202
+ type GuardUnknown = "no-guard-bag" | "no-implementation" | "no-sample" | "threw";
203
+ /**
204
+ * Which branch a guarded edge would take right now.
205
+ *
206
+ * `then` / `else` are ANSWERS — the guard ran, on this state, with this msg.
207
+ * `unknown` is the honest non-answer, carrying the reason and (for a throwing
208
+ * guard) the message, so a UI shows "cannot evaluate: no sample for FAIL"
209
+ * rather than a confidently wrong arrow.
210
+ */
211
+ type GuardPreview = {
212
+ readonly guard: string;
213
+ readonly branch: "then" | "else";
214
+ /** The state that branch lands on. */
215
+ readonly target: string;
216
+ /** The cmds that branch fires. */
217
+ readonly cmds: readonly string[];
218
+ } | {
219
+ readonly guard: string;
220
+ readonly branch: "unknown";
221
+ readonly why: GuardUnknown;
222
+ /** The thrown error's message, when `why` is `"threw"`. */
223
+ readonly error?: string;
224
+ };
225
+ /** How a single target was determined, when one could be. */
226
+ type ResolvedBy = "declared" | "guard" | "resume" | "cell";
227
+ /**
228
+ * What would happen if `event` were dispatched at the current state.
229
+ *
230
+ * A refusal is FIRST-CLASS: it carries the reason and a rendered explanation,
231
+ * so a UI draws it as visibly refused rather than omitting the control. That
232
+ * is the totality property paying rent — the chart knows the difference
233
+ * between "not routed here", "deliberately dropped here" and "this state is
234
+ * over", and a debugger that hides all three behind a missing button throws
235
+ * away the one thing this substrate knows and others do not.
236
+ */
237
+ interface EventPreview {
238
+ readonly event: string;
239
+ readonly status: "legal" | "refused";
240
+ /**
241
+ * Who would send this — the event's declared `from`, carried through rather
242
+ * than recomputed, so a button row can be grouped by sender with the same
243
+ * answer the report's "waiting on" line gives. `undefined` when the chart
244
+ * does not say.
245
+ */
246
+ readonly from?: EventOrigin;
247
+ /** Present iff `status` is `"refused"`. */
248
+ readonly reason?: RefusalReason;
249
+ /** A one-line rendering of `reason`. Present iff refused. */
250
+ readonly why?: string;
251
+ /** The declared edge. Present iff legal. */
252
+ readonly edge?: ChartEdge;
253
+ /** Present iff the edge is guarded. */
254
+ readonly guard?: GuardPreview;
255
+ /**
256
+ * The cmds that WOULD fire — read off the edge's declaration. For a guarded
257
+ * edge whose branch resolved, the cmds of THAT arm; for one that did not, the
258
+ * `then` arm's (the declared happy path) — `guard.branch === "unknown"` is
259
+ * what says so.
260
+ *
261
+ * This is the BEFORE question, and a `{ to, cell }` edge answers it with an
262
+ * empty list BY DECLARATION: the cell builds its cmds inside its body, so the
263
+ * chart has none to declare. For what actually fired, ask the after question
264
+ * — `captureCmds` in `./captured`, which reads the emitted cmds off the
265
+ * machine's own output.
266
+ */
267
+ readonly cmds: readonly string[];
268
+ /** Every state the edge declares it can reach. */
269
+ readonly targets: readonly string[];
270
+ /** The single target, when one is determined. */
271
+ readonly resolved?: string;
272
+ /** How {@link EventPreview.resolved} was determined. */
273
+ readonly resolvedBy?: ResolvedBy;
274
+ /**
275
+ * The msg this preview used — the sample plus the chart's `type`. `undefined`
276
+ * when the event declares a payload and no sample was supplied, which is
277
+ * also why a guard or cell may have come back `unknown`.
278
+ */
279
+ readonly msg?: {
280
+ readonly type: string;
281
+ };
282
+ }
283
+ /**
284
+ * Preview one event against a live state.
285
+ *
286
+ * @param desc the chart's {@link ChartDescription}.
287
+ * @param state the live state — anything with a `type` (and `was`, on a
288
+ * parking state, which is what makes a resume target resolvable).
289
+ */
290
+ declare function previewEvent<S extends {
291
+ readonly type: string;
292
+ }>(desc: ChartDescription, state: S, event: string, opts?: InspectOptions): EventPreview;
293
+ /**
294
+ * Preview EVERY event of the alphabet against a live state, in the chart's
295
+ * declaration order.
296
+ *
297
+ * This is the button row: one entry per declared event, each either legal
298
+ * (with its branch, targets and cmds) or refused (with its reason). Nothing is
299
+ * filtered out — a refused event is a control the UI draws, not one it lacks.
300
+ */
301
+ declare function inspectState<S extends {
302
+ readonly type: string;
303
+ }>(desc: ChartDescription, state: S, opts?: InspectOptions): readonly EventPreview[];
304
+ /**
305
+ * {@link inspectState}, plus the two roll-ups a caller almost always wants
306
+ * next: the legal event names and the refused ones.
307
+ *
308
+ * The whole verdict list is still there — the split is a convenience, not a
309
+ * filter, and `events` remains the surface a UI renders so a refusal keeps its
310
+ * reason.
311
+ */
312
+ declare function inspectStateSummary<S extends {
313
+ readonly type: string;
314
+ }>(desc: ChartDescription, state: S, opts?: InspectOptions): {
315
+ readonly state: S;
316
+ readonly events: readonly EventPreview[];
317
+ readonly legal: readonly string[];
318
+ readonly refused: readonly string[];
319
+ };
320
+
321
+ /**
322
+ * A QUESTION THIS FORM CANNOT ANSWER, carried in the slot where the grid form
323
+ * carries the answer.
324
+ *
325
+ * `answerable: false` is the whole type: there is no `true` member, because a
326
+ * question this form CAN answer is answered with the answer. So `desc.refusals`
327
+ * on a reducer description is not an empty list a UI silently renders — it is a
328
+ * value whose only shape says why the list is not there, and code written
329
+ * against the grid form's array fails to compile against it.
330
+ */
331
+ interface Unanswerable<Q extends string> {
332
+ readonly answerable: false;
333
+ /** Which question — the grid-form field this stands in for. */
334
+ readonly question: Q;
335
+ /** One line, ready to render. */
336
+ readonly why: string;
337
+ }
338
+ /** One edge of a reducer chart — an edge with no state to leave from. */
339
+ type ReducerEdge = EdgeCore;
340
+ /** One event of a reducer chart's alphabet. `scope` has no counterpart here. */
341
+ interface ReducerEventInfo {
342
+ readonly name: string;
343
+ /** `foreign: true` — the name belongs to someone else and stays bare. */
344
+ readonly foreign: boolean;
345
+ /** WHO SENDS THIS. `undefined` means the chart does not say — never a guess. */
346
+ readonly from?: EventOrigin;
347
+ /** Whether the event declared a payload (`data: ty<…>()`). */
348
+ readonly hasPayload: boolean;
349
+ /** `false` iff the chart declares this event and routes it nowhere. */
350
+ readonly routed: boolean;
351
+ }
352
+ /**
353
+ * A reducer-form chart, as data a UI can build itself from — the same contract
354
+ * as {@link import("./describe").ChartDescription}, over the facts this form
355
+ * actually has.
356
+ */
357
+ interface ReducerChartDescription {
358
+ /** The declared state names, in declaration order. Flat: there are no groups. */
359
+ readonly states: readonly string[];
360
+ /** The entry state — the chart's `initial` field, not a marked node. */
361
+ readonly initial: string;
362
+ readonly events: readonly ReducerEventInfo[];
363
+ /** Every cmd DECLARED in the `cmds` section — the effect alphabet. */
364
+ readonly cmds: readonly string[];
365
+ readonly guards: readonly string[];
366
+ readonly cells: readonly string[];
367
+ /** One edge per routed event, in the alphabet's declaration order. */
368
+ readonly edges: readonly ReducerEdge[];
369
+ /**
370
+ * TOTALITY, read back off the value: `true` iff every declared event owes and
371
+ * has an edge. Through the typed door this is always `true` — `on` is a
372
+ * required mapped type — so a `false` here means the types were bypassed with
373
+ * a cast, exactly as `refusals`' `undeclared` kind does in the grid form.
374
+ */
375
+ readonly total: boolean;
376
+ /** The declared events with no edge. Empty through the typed door. */
377
+ readonly missing: readonly string[];
378
+ /** NOT AVAILABLE — see {@link Unanswerable}. */
379
+ readonly phases: Unanswerable<"phases">;
380
+ /** NOT AVAILABLE — see {@link Unanswerable}. */
381
+ readonly refusals: Unanswerable<"refusals">;
382
+ /** NOT AVAILABLE — see {@link Unanswerable}. */
383
+ readonly scope: Unanswerable<"scope">;
384
+ }
385
+ /**
386
+ * Read a reducer-form chart into its {@link ReducerChartDescription}.
387
+ *
388
+ * Pure, total and allocation-only, exactly as `describeChart` is: it never runs
389
+ * a guard, a cell or a payload builder, so it is safe on a chart whose parts do
390
+ * not exist yet.
391
+ */
392
+ declare function describeReducerChart<const C extends ReducerChart<C>>(chart: C): ReducerChartDescription;
393
+ /** The edge that routes `event`, or `undefined` when the chart routes it nowhere. */
394
+ declare function reducerEdgeAt(desc: ReducerChartDescription, event: string): ReducerEdge | undefined;
395
+ /**
396
+ * What would happen if `event` were dispatched at the current state.
397
+ *
398
+ * There is NO `status` field, and its absence is the honest part: this form
399
+ * cannot refuse an event — `on` is total over the alphabet and there is no
400
+ * state dimension to refuse from — so a "legal" tag would be a distinction
401
+ * without a second case. Everything a grid preview can still say here it says:
402
+ * the edge, the guard's branch, the declared fan-out, the target a cell picks.
403
+ *
404
+ * The cmds are the DECLARED ones — the before question. For what actually
405
+ * fired, see `captureCmds` in `./captured`.
406
+ */
407
+ interface ReducerEventPreview {
408
+ readonly event: string;
409
+ /** The event's declared `from`, carried through rather than recomputed. */
410
+ readonly from?: EventOrigin;
411
+ /** The declared edge. `undefined` only when the chart routes the event nowhere. */
412
+ readonly edge?: ReducerEdge;
413
+ /** Present iff the edge is guarded. */
414
+ readonly guard?: GuardPreview;
415
+ /** The cmds the edge DECLARES for the arm that resolved. */
416
+ readonly cmds: readonly string[];
417
+ /** Every state the edge declares it can reach. */
418
+ readonly targets: readonly string[];
419
+ /** The single target, when one is determined. */
420
+ readonly resolved?: string;
421
+ /** How {@link ReducerEventPreview.resolved} was determined. */
422
+ readonly resolvedBy?: ResolvedBy;
423
+ /** The msg this preview used, or `undefined` when no sample was supplied. */
424
+ readonly msg?: {
425
+ readonly type: string;
426
+ };
427
+ }
428
+ /**
429
+ * Preview one event of a reducer chart against a live state.
430
+ *
431
+ * The guard and cell previews are the SAME functions the grid form uses — a
432
+ * guard is pure and a cell is pure in both forms, and the only difference is
433
+ * that `at` is the bare event name.
434
+ */
435
+ declare function previewReducerEvent<S extends {
436
+ readonly type: string;
437
+ }>(desc: ReducerChartDescription, state: S, event: string, opts?: InspectOptions): ReducerEventPreview;
438
+ /**
439
+ * Preview EVERY event of a reducer chart's alphabet against a live state, in
440
+ * declaration order. The button row — and here every control is live, because
441
+ * this form has nothing with which to refuse one.
442
+ */
443
+ declare function inspectReducerState<S extends {
444
+ readonly type: string;
445
+ }>(desc: ReducerChartDescription, state: S, opts?: InspectOptions): readonly ReducerEventPreview[];
446
+
447
+ export { type AnyCell as A, type ChartDescription as C, type EdgeCore as E, type GuardPreview as G, type InspectOptions as I, type ReducerChartDescription as R, type Unanswerable as U, type AnyGuard as a, type ChartEdge as b, type ChartEventInfo as c, type ChartRefusal as d, type ChartStateInfo as e, type EdgeKind as f, type EventPreview as g, type GuardUnknown as h, type InspectParts as i, type ReducerEdge as j, type ReducerEventInfo as k, type ReducerEventPreview as l, type RefusalReason as m, type ResolvedBy as n, describeChart as o, describeReducerChart as p, edgeAt as q, explainRefusal as r, inspectReducerState as s, inspectState as t, inspectStateSummary as u, previewEvent as v, previewReducerEvent as w, reducerEdgeAt as x, refusalAtState as y, stateInfo as z };