@mantaq/viz 0.0.0-stage → 0.1.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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Anders Can
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,45 @@
1
- # Temporary Holding Version
1
+ # @mantaq/viz
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Draws a Mantaq machine in the browser and lets you walk it. Input is a viz document: versioned, deterministic JSON. `@mantaq/inspect` makes one from your machine; this package only reads it, and runs nothing.
4
+
5
+ - `@mantaq/viz`: the document. Types, `parseViz` / `validateViz` (untrusted JSON → `Either<VizIssue[], VizDocument>`, every issue with its path), `serializeViz`, and the walk (`movesFrom`, `decodePath`, …). No DOM.
6
+ - `@mantaq/viz/viewer`: `mountViewer(root, document)` / `mountVizText(root, { source, text })` to embed it in a page, plus `@mantaq/viz/viewer.css`.
7
+ - `@mantaq/viz/standalone`, `@mantaq/viz/live`: whole-page entries used by `mantaq-inspect` (one HTML file) and the Vite plugin (live page).
8
+
9
+ Dev page with the bundled examples: `vp run viz:export`, then `cd packages/viz && vp dev`. It loads `examples/examples.json`, `?src=<url>`, or a picked file.
10
+
11
+ ## Walking the graph
12
+
13
+ The viewer keeps a cursor on one state, starting at the initial one. The panel lists what can happen next, in three groups:
14
+
15
+ - **Send an input**: edges on input events.
16
+ - **Effect reports back**: internal events that an effect of the current state declares in `sends` (`m.effect(state, { name, sends: [...], fn })`). Entering `submitting` runs `chargeCard`, which sends `paymentOk | paymentFail | paymentTimeout`.
17
+ - **Other internal events**: internal events that no effect of the state declares.
18
+
19
+ Take a step with a panel button, by clicking a pulsing edge, or by clicking a state that exactly one next step reaches. An undetermined edge (`to: null`) offers each target its AST explanation names. Without one, it cannot be taken. A final state ends the walk. **Back** (or ←), **Reset** and the path list undo steps. The standalone page keeps the walk in the URL: `?step=` holds one edge id per step, plus `~target` for an undetermined edge.
20
+
21
+ Nothing runs. The walk follows declarations, so guards are not evaluated, and a path may not happen at runtime. The logic is in `src/moves.ts`: `movesFrom`, `unhandledSends`, `decodePath`.
22
+
23
+ ## Schema (v7)
24
+
25
+ `VizDocument { schemaVersion: 7, machines: VizMachine[] }`, keys sorted, two-space indent. Types in `src/schema.ts`.
26
+
27
+ | field | meaning |
28
+ | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
29
+ | `machine.states[]` | `id` (region-prefixed `region.state`), `region`, `initial`, `final`, `effects` (`[{ name, sends }]` run on entry, registration order; `sends`: declared event ids, `null` = not declared), `coverage` (`null` = no run, else `{ hits }`) |
30
+ | `edge.from` | a state id, `__initial__`, or `*` (`region.*` in a region): an `onAny` registration, meaning any non-final state. `*` is never in `states`; a `stay` from `*` has `to: "*"` |
31
+ | `machine.events[]` | `id`, `internal`, `payloadType` (always `null`: erased at runtime) |
32
+ | `edge.kind` | `initial` · `transition` · `effect` (state runs effects on entry) · `stay` (handled, no state change: a registration without `to`, or observed while fuzzing) |
33
+ | `edge.to` | `null` = **undetermined**: only for actors that carry no `to` declarations |
34
+ | `edge.origin` | `declared` (builder `to`, vouched for by the type checker) · `probe` (synthetic traversal, undeclared actors) · `fuzz` (observed only while fuzzing) |
35
+ | `edge.explanation` | `null` (no handler: initial/effect, or AST not run) · `{ status: "analyzed", location, registration, branches, payloadReads, contextReads, contextWrites, readsActorState, comparisons, literals }` · `{ status: "not_analyzed", location, reason, snippet }` |
36
+ | `machine.fuzz` | `null` or `{ seed, iterations, corpus: [{ steps, discovered }], errors: [{ from, event, message, steps }] }`; every sequence is shrunk and replayable from a fresh actor |
37
+
38
+ Bump `VIZ_SCHEMA_VERSION` on any breaking change. The validator accepts only the current version.
39
+
40
+ ## Coverage: two sources, two questions
41
+
42
+ The viewer's "Coverage from" picker dims elements with zero hits. What dimmed means depends on the source:
43
+
44
+ - **example tests** (`tested`): no test exercises this. It is the classic "what is untested" view, and the default when tests are supplied. Supply the event sequences your tests send (`inspect({ tests })`).
45
+ - **fuzz runs** (`coverage`): no fuzz run produced this. Fuzzing reaches almost everything, so little is dimmed. What does stay dimmed is usually a **declared but unreached edge**: the handler lists the target in `to`, but its guards may never allow it. Saga's `compensatingRefund -REFUND_DONE-> failed` is an example. The edge panel calls these out. Fuzzing is not proof, so check the handler.
@@ -0,0 +1,32 @@
1
+ import { r as VizIssue, u as VizDocument } from "./validate-3D5LrtlE.mjs";
2
+ declare const STRUCTURE_BANNER = "Click a pulsing edge, or a state it leads to, to walk the machine. Edges come from each handler's declared `to` list and effects' `sends`; guards are not evaluated, so a path here may not happen at runtime.";
3
+ declare const FUZZ_NOTE = "Green edges were observed while fuzzing but not declared. Fuzzing finds behaviour; it never proves an edge is impossible.";
4
+ interface ViewerOptions {
5
+ /** Title element. Embedded viewers use `h2`/`h3` to fit the host page's outline. */
6
+ heading?: "h1" | "h2" | "h3";
7
+ /** Machine to show first; defaults to the first one. */
8
+ machine?: string;
9
+ /** Encoded steps to open on (from `?step=`), in the shown machine. */
10
+ steps?: ReadonlyArray<string>;
11
+ /** Called after every walk or machine change, e.g. to keep the URL shareable. */
12
+ onNavigate?: (where: {
13
+ machine: string;
14
+ steps: string[];
15
+ }) => void;
16
+ }
17
+ /**
18
+ * Mount a viewer for an viz document into `root`. Walking the graph moves a
19
+ * cursor over the drawn edges; no actor runs. The document is the only input.
20
+ */
21
+ declare function mountViewer(root: HTMLElement, viz: VizDocument, options?: ViewerOptions): void;
22
+ /** Replace `root` with a list of reasons `source` cannot be shown. */
23
+ declare function showIssues(root: HTMLElement, context: {
24
+ source: string;
25
+ issues: VizIssue[];
26
+ }): void;
27
+ /** Validate viz JSON text and mount it, or list what is wrong with it. */
28
+ declare function mountVizText(root: HTMLElement, context: {
29
+ source: string;
30
+ text: string;
31
+ }, options?: ViewerOptions): void;
32
+ export { ViewerOptions as a, STRUCTURE_BANNER as i, showIssues as n, mountViewer as o, FUZZ_NOTE as r, mountVizText as t };