@mantaq/viz 0.0.0 → 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.
@@ -0,0 +1 @@
1
+ export {}
@@ -0,0 +1,6 @@
1
+ import { mountEmbedded } from "./page.mjs";
2
+ // src/viewer/standalone.ts
3
+ /** Entry of the self-contained HTML page `mantaq-inspect` writes. */
4
+ const root = document.querySelector("#app");
5
+ if (root) mountEmbedded(root);
6
+ export {};
package/dist/style.css ADDED
@@ -0,0 +1,404 @@
1
+ .viz {
2
+ --fuzz: #138a4a;
3
+ --bg: #fbfbfa;
4
+ --fg: #1d1d1f;
5
+ --muted: #6b6b70;
6
+ --line: #4a4a52;
7
+ --state-bg: #fff;
8
+ --state-border: #3b3b44;
9
+ --region-bg: #f1f1ef;
10
+ --accent: #2463eb;
11
+ --unknown: #a35c00;
12
+ --banner-bg: #fff6e0;
13
+ --banner-fg: #5a3e00;
14
+ color-scheme: light;
15
+ background: var(--bg);
16
+ color: var(--fg);
17
+ }
18
+
19
+ @media (prefers-color-scheme: dark) {
20
+ .viz {
21
+ --bg: #141416;
22
+ --fg: #ececf0;
23
+ --muted: #9a9aa3;
24
+ --line: #b4b4bf;
25
+ --state-bg: #1f1f23;
26
+ --state-border: #c9c9d2;
27
+ --region-bg: #1a1a1e;
28
+ --accent: #7aa7ff;
29
+ --unknown: #f0a742;
30
+ --fuzz: #4cc98a;
31
+ --banner-bg: #2e2510;
32
+ --banner-fg: #f3d58c;
33
+ color-scheme: dark;
34
+ }
35
+ }
36
+
37
+ .viz .viz-header {
38
+ flex-wrap: wrap;
39
+ align-items: center;
40
+ gap: 16px;
41
+ display: flex;
42
+ }
43
+
44
+ .viz .viz-header :is(h1, h2, h3) {
45
+ margin: 0;
46
+ font-size: 18px;
47
+ }
48
+
49
+ .viz .banner {
50
+ background: var(--banner-bg);
51
+ color: var(--banner-fg);
52
+ border-radius: 6px;
53
+ padding: 8px 12px;
54
+ }
55
+
56
+ .viz .legend {
57
+ color: var(--muted);
58
+ flex-wrap: wrap;
59
+ gap: 4px 18px;
60
+ padding: 0;
61
+ font-size: 12px;
62
+ list-style: none;
63
+ display: flex;
64
+ }
65
+
66
+ .viz .viz-main {
67
+ grid-template-columns: minmax(0, 1fr) 320px;
68
+ gap: 16px;
69
+ display: grid;
70
+ }
71
+
72
+ @media (width <= 760px) {
73
+ .viz .viz-main {
74
+ grid-template-columns: minmax(0, 1fr);
75
+ }
76
+ }
77
+
78
+ .viz .canvas {
79
+ overflow: auto;
80
+ }
81
+
82
+ .viz .panel {
83
+ border-left: 1px solid var(--region-bg);
84
+ padding-left: 16px;
85
+ }
86
+
87
+ .viz .panel dl {
88
+ grid-template-columns: auto 1fr;
89
+ gap: 4px 12px;
90
+ display: grid;
91
+ }
92
+
93
+ .viz .panel dt {
94
+ color: var(--muted);
95
+ }
96
+
97
+ .viz .panel dd {
98
+ margin: 0;
99
+ }
100
+
101
+ .viz .panel ul {
102
+ margin: 0;
103
+ padding-left: 16px;
104
+ }
105
+
106
+ .viz .link {
107
+ color: var(--accent);
108
+ cursor: pointer;
109
+ }
110
+
111
+ .viz .graph {
112
+ width: 100%;
113
+ height: auto;
114
+ }
115
+
116
+ .viz .graph text {
117
+ fill: var(--fg);
118
+ paint-order: stroke;
119
+ stroke: var(--bg);
120
+ stroke-width: 4px;
121
+ stroke-linejoin: round;
122
+ font-size: 12px;
123
+ }
124
+
125
+ .viz .graph .arrow-head {
126
+ fill: var(--line);
127
+ }
128
+
129
+ .viz .region rect {
130
+ fill: var(--region-bg);
131
+ stroke: var(--muted);
132
+ stroke-dasharray: 2 3;
133
+ }
134
+
135
+ .viz .region text {
136
+ fill: var(--muted);
137
+ }
138
+
139
+ .viz .state .frame {
140
+ fill: var(--state-bg);
141
+ stroke: var(--state-border);
142
+ stroke-width: 1.5px;
143
+ }
144
+
145
+ .viz .state.final .inner {
146
+ fill: none;
147
+ }
148
+
149
+ .viz .state, .viz .edge {
150
+ cursor: pointer;
151
+ outline: none;
152
+ }
153
+
154
+ .viz .edge path {
155
+ fill: none;
156
+ stroke: var(--line);
157
+ stroke-width: 1.5px;
158
+ }
159
+
160
+ .viz .edge.internal path {
161
+ stroke-dasharray: 1 3;
162
+ stroke-linecap: round;
163
+ }
164
+
165
+ .viz .edge.internal text {
166
+ font-style: italic;
167
+ }
168
+
169
+ .viz .edge.effect {
170
+ opacity: .45;
171
+ }
172
+
173
+ .viz .edge.undetermined path {
174
+ stroke: var(--unknown);
175
+ stroke-dasharray: 6 4;
176
+ }
177
+
178
+ .viz .edge.fuzz path {
179
+ stroke: var(--fuzz);
180
+ }
181
+
182
+ .viz .edge.fuzz text {
183
+ fill: var(--fuzz);
184
+ }
185
+
186
+ .viz .edge.stay path {
187
+ stroke-dasharray: 1 3;
188
+ stroke-linecap: round;
189
+ }
190
+
191
+ .viz .steps {
192
+ margin: 0;
193
+ padding-left: 20px;
194
+ font-family: ui-monospace, monospace;
195
+ font-size: 12px;
196
+ }
197
+
198
+ .viz .unknown-node circle {
199
+ fill: var(--state-bg);
200
+ stroke: var(--unknown);
201
+ stroke-dasharray: 4 3;
202
+ }
203
+
204
+ .viz .unknown-node text {
205
+ fill: var(--unknown);
206
+ font-weight: 700;
207
+ }
208
+
209
+ .viz .any-node rect {
210
+ fill: none;
211
+ stroke: var(--state-border);
212
+ stroke-width: 1.5px;
213
+ stroke-dasharray: 2 3;
214
+ }
215
+
216
+ .viz .any-node text {
217
+ fill: var(--muted);
218
+ font-style: italic;
219
+ }
220
+
221
+ .viz .entry circle {
222
+ fill: var(--line);
223
+ }
224
+
225
+ .viz .entry path {
226
+ stroke: var(--line);
227
+ stroke-width: 1.5px;
228
+ }
229
+
230
+ .viz.coverage-on .unreached {
231
+ opacity: .35;
232
+ }
233
+
234
+ .viz.coverage-on .state.unreached .frame, .viz.coverage-on .edge.unreached path {
235
+ stroke-dasharray: 3 3;
236
+ }
237
+
238
+ .viz .highlight path, .viz .highlight .frame, .viz .highlight circle, .viz .selected path, .viz .selected .frame {
239
+ stroke: var(--accent);
240
+ stroke-width: 2.5px;
241
+ }
242
+
243
+ .viz .state:focus-visible .frame, .viz .edge:focus-visible path {
244
+ stroke: var(--accent);
245
+ stroke-width: 3px;
246
+ }
247
+
248
+ .viz .load-error {
249
+ color: var(--unknown);
250
+ }
251
+
252
+ .viz .muted {
253
+ color: var(--muted);
254
+ }
255
+
256
+ .viz .explanation p {
257
+ margin: 2px 0;
258
+ }
259
+
260
+ .viz .explanation pre {
261
+ background: var(--region-bg);
262
+ border-radius: 4px;
263
+ padding: 6px;
264
+ font-size: 12px;
265
+ overflow-x: auto;
266
+ }
267
+
268
+ .viz .branches {
269
+ padding-left: 16px;
270
+ }
271
+
272
+ .viz .branch[data-branch-status="not in graph"] .branch-status {
273
+ color: var(--unknown);
274
+ font-weight: 600;
275
+ }
276
+
277
+ .viz .branch[data-branch-status="this edge"] .branch-status {
278
+ color: var(--accent);
279
+ }
280
+
281
+ .viz .prediction-note, .viz .routing-note {
282
+ background: var(--banner-bg);
283
+ color: var(--banner-fg);
284
+ border-radius: 4px;
285
+ padding: 6px 8px;
286
+ }
287
+
288
+ .viz .taken path, .viz .current .frame {
289
+ stroke: var(--accent);
290
+ stroke-width: 2.5px;
291
+ }
292
+
293
+ .viz .current .frame {
294
+ stroke-width: 3.5px;
295
+ }
296
+
297
+ .viz .next path {
298
+ stroke: var(--accent);
299
+ stroke-width: 3px;
300
+ stroke-dasharray: 8 5;
301
+ animation: .8s linear infinite viz-march;
302
+ }
303
+
304
+ .viz .next text {
305
+ fill: var(--accent);
306
+ font-weight: 700;
307
+ }
308
+
309
+ .viz .edge.effect.running {
310
+ opacity: 1;
311
+ }
312
+
313
+ .viz .running path {
314
+ stroke: var(--accent);
315
+ stroke-width: 2.5px;
316
+ }
317
+
318
+ .viz .running text {
319
+ fill: var(--accent);
320
+ font-weight: 700;
321
+ }
322
+
323
+ @keyframes viz-march {
324
+ to {
325
+ stroke-dashoffset: -13px;
326
+ }
327
+ }
328
+
329
+ @media (prefers-reduced-motion: reduce) {
330
+ .viz .next path {
331
+ animation: none;
332
+ }
333
+ }
334
+
335
+ .viz .explore {
336
+ background: var(--region-bg);
337
+ border-radius: 6px;
338
+ margin-bottom: 12px;
339
+ padding: 6px 10px;
340
+ }
341
+
342
+ .viz .explore h3 {
343
+ color: var(--muted);
344
+ margin: 10px 0 4px;
345
+ font-size: 13px;
346
+ }
347
+
348
+ .viz .explore ul, .viz .explore ol {
349
+ margin: 0;
350
+ padding-left: 18px;
351
+ }
352
+
353
+ .viz .explore li {
354
+ margin: 3px 0;
355
+ }
356
+
357
+ .viz .explore li > button + button {
358
+ margin-left: 6px;
359
+ }
360
+
361
+ .viz .explore button {
362
+ cursor: pointer;
363
+ }
364
+
365
+ .viz .explore-path li[aria-current="step"] button {
366
+ font-weight: 700;
367
+ }
368
+
369
+ .viz .explore-controls {
370
+ gap: 6px;
371
+ margin: 8px 0;
372
+ display: flex;
373
+ }
374
+
375
+ .viz .explore .unhandled code {
376
+ color: var(--unknown);
377
+ text-decoration: line-through;
378
+ }
379
+
380
+ .viz .explore-done {
381
+ border-left: 3px solid var(--accent);
382
+ padding: 4px 8px;
383
+ }
384
+
385
+ .viz .explore-warning {
386
+ background: var(--banner-bg);
387
+ color: var(--banner-fg);
388
+ border-radius: 4px;
389
+ padding: 4px 8px;
390
+ }
391
+
392
+ .viz .reachable .frame {
393
+ stroke: var(--accent);
394
+ stroke-dasharray: 4 3;
395
+ }
396
+ body {
397
+ margin: 0;
398
+ padding: 16px;
399
+ font: 14px / 1.4 system-ui, sans-serif;
400
+ }
401
+
402
+ .toolbar {
403
+ margin-bottom: 12px;
404
+ }
@@ -0,0 +1,189 @@
1
+ import { Either } from "@mantaq/utils";
2
+ /**
3
+ * Viz document schema. Bump `VIZ_SCHEMA_VERSION` on any breaking change.
4
+ * Everything not observable at runtime is `null`, never guessed.
5
+ */
6
+ declare const VIZ_SCHEMA_VERSION = 7;
7
+ /**
8
+ * Edge source of an `onAny` registration: any non-final state of the machine,
9
+ * or `region.*` inside a region. Never listed in `states`.
10
+ */
11
+ declare const ANY_STATE = "*";
12
+ /** Region prefix of a state id (`""` at the root). */
13
+ declare function scopeOf(id: string): string;
14
+ declare function isAnyState(id: string): boolean;
15
+ /** True when `stateId` is one of the states an `anyId` source stands for. */
16
+ declare function coveredByAny(anyId: string, stateId: string): boolean;
17
+ interface VizCoverage {
18
+ hits: number;
19
+ }
20
+ /** An effect a state runs on entry. Effects never transition; the events they send do. */
21
+ interface VizEffect {
22
+ name: string;
23
+ /** Event ids from the effect's `sends` declaration. null = not declared. */
24
+ sends: string[] | null;
25
+ }
26
+ interface VizState {
27
+ /** Path-prefixed id, e.g. `payment` or `region1.childA`. */
28
+ id: string;
29
+ label: string;
30
+ /** Region path this state belongs to, or null for the root machine. */
31
+ region: string | null;
32
+ initial: boolean;
33
+ final: boolean;
34
+ /** Effects this state runs on entry, in registration order. */
35
+ effects: VizEffect[];
36
+ /** Visits in the supplied run (history or fuzz corpus replay). null = no run. */
37
+ coverage: VizCoverage | null;
38
+ /** Visits by the supplied test sequences. null = no tests supplied. */
39
+ tested: VizCoverage | null;
40
+ }
41
+ interface VizEvent {
42
+ id: string;
43
+ internal: boolean;
44
+ /** Payload types are erased at runtime; always null. */
45
+ payloadType: null;
46
+ }
47
+ /**
48
+ * `stay`: the event was handled but the state did not change. Declared by a
49
+ * registration without `to`, or observed while fuzzing.
50
+ */
51
+ type VizEdgeKind = "initial" | "transition" | "effect" | "stay";
52
+ /**
53
+ * `declared`: from the builder's `to` list, so the type checker vouches for it.
54
+ * `probe`: an actor without declarations, found by synthetic traversal.
55
+ * `fuzz`: observed only while fuzzing.
56
+ */
57
+ type VizEdgeOrigin = "declared" | "probe" | "fuzz";
58
+ interface VizLocation {
59
+ /** POSIX path relative to the analysis root. */
60
+ file: string;
61
+ /** 1-based. */
62
+ line: number;
63
+ /** 1-based. */
64
+ column: number;
65
+ }
66
+ /** Where a handler return path goes. `stay` = no `state` in the result. */
67
+ type VizBranchTarget = {
68
+ kind: "state";
69
+ state: string;
70
+ } | {
71
+ kind: "stay";
72
+ } | {
73
+ kind: "unresolved";
74
+ text: string;
75
+ };
76
+ interface VizBranch {
77
+ /** Source text of each condition on the path, outermost first; `!(...)` = else side. */
78
+ conditions: string[];
79
+ target: VizBranchTarget;
80
+ }
81
+ /** `payload.amount > 50000` style comparison against a literal, for search guidance. */
82
+ interface VizComparison {
83
+ source: "payload" | "context";
84
+ /** Dotted path below payload / context, e.g. `result.reservationId`. */
85
+ path: string;
86
+ operator: "<" | "<=" | ">" | ">=" | "===" | "!==" | "==" | "!=";
87
+ literal: number | string | boolean;
88
+ }
89
+ /**
90
+ * Static reading of the handler behind an edge (TypeScript compiler API).
91
+ * Annotates runtime edges; never creates them.
92
+ */
93
+ type VizExplanation = {
94
+ status: "analyzed";
95
+ location: VizLocation;
96
+ registration: "on" | "onAny";
97
+ branches: VizBranch[];
98
+ payloadReads: string[];
99
+ contextReads: string[];
100
+ contextWrites: string[];
101
+ /** Reads `<actor>.state`: target depends on live state, which probing cannot see. */
102
+ readsActorState: boolean;
103
+ comparisons: VizComparison[];
104
+ literals: Array<number | string>;
105
+ } | {
106
+ status: "not_analyzed";
107
+ location: VizLocation | null;
108
+ reason: string;
109
+ /** First lines of the source, when there is any. */
110
+ snippet: string | null;
111
+ };
112
+ interface VizEdge {
113
+ id: string;
114
+ from: string;
115
+ /** Event id; empty string for the initial edge. */
116
+ event: string;
117
+ /** null = handler target depends on payload or context (undetermined). */
118
+ to: string | null;
119
+ kind: VizEdgeKind;
120
+ origin: VizEdgeOrigin;
121
+ emits: string[];
122
+ /** Sample-context names under which this edge was observed. */
123
+ contexts: string[];
124
+ /** null = no handler behind this edge (initial, effect) or analysis not run. */
125
+ explanation: VizExplanation | null;
126
+ /** Times taken in the supplied run (history or fuzz corpus replay). null = no run. */
127
+ coverage: VizCoverage | null;
128
+ /** Times taken by the supplied test sequences. null = no tests supplied. */
129
+ tested: VizCoverage | null;
130
+ }
131
+ interface VizMachine {
132
+ id: string;
133
+ initial: string | null;
134
+ /** Region paths, sorted. Empty for flat machines. */
135
+ regions: string[];
136
+ states: VizState[];
137
+ events: VizEvent[];
138
+ edges: VizEdge[];
139
+ /** null = not fuzzed. */
140
+ fuzz: VizFuzz | null;
141
+ }
142
+ type VizJson = null | boolean | number | string | VizJson[] | {
143
+ [key: string]: VizJson;
144
+ };
145
+ /** One input to a machine: an event (`send` for inputs, `inject` for internal) or a clock advance. */
146
+ type VizStep = {
147
+ kind: "event";
148
+ event: string;
149
+ payload: VizJson;
150
+ } | {
151
+ kind: "advance";
152
+ ms: number;
153
+ };
154
+ interface VizFuzzSequence {
155
+ /** Shrunk replayable sequence, starting from a fresh actor. */
156
+ steps: VizStep[];
157
+ /** Coverage features this sequence was kept for, e.g. `edge:a>go>b`. */
158
+ discovered: string[];
159
+ }
160
+ /** A handler that threw: the actor entered its error state. */
161
+ interface VizFuzzError {
162
+ from: string;
163
+ event: string;
164
+ message: string;
165
+ steps: VizStep[];
166
+ }
167
+ interface VizFuzz {
168
+ seed: number;
169
+ iterations: number;
170
+ corpus: VizFuzzSequence[];
171
+ errors: VizFuzzError[];
172
+ }
173
+ interface VizDocument {
174
+ schemaVersion: typeof VIZ_SCHEMA_VERSION;
175
+ machines: VizMachine[];
176
+ }
177
+ /** One problem found while reading a document, with a JSON-path-like location. */
178
+ interface VizIssue {
179
+ path: string;
180
+ message: string;
181
+ }
182
+ /**
183
+ * Read an untrusted value (parsed JSON) as an viz document. Returns every
184
+ * issue found, not just the first, so a broken file can be fixed in one pass.
185
+ */
186
+ declare function validateViz(value: unknown): Either<VizIssue[], VizDocument>;
187
+ /** Parse JSON text and validate it. JSON syntax errors become an issue at `$`. */
188
+ declare function parseViz(text: string): Either<VizIssue[], VizDocument>;
189
+ export { VizState as C, scopeOf as D, isAnyState as E, VizMachine as S, coveredByAny as T, VizFuzz as _, VIZ_SCHEMA_VERSION as a, VizJson as b, VizComparison as c, VizEdge as d, VizEdgeKind as f, VizExplanation as g, VizEvent as h, ANY_STATE as i, VizCoverage as l, VizEffect as m, validateViz as n, VizBranch as o, VizEdgeOrigin as p, VizIssue as r, VizBranchTarget as s, parseViz as t, VizDocument as u, VizFuzzError as v, VizStep as w, VizLocation as x, VizFuzzSequence as y };
@@ -0,0 +1,2 @@
1
+ import { a as ViewerOptions, i as STRUCTURE_BANNER, n as showIssues, o as mountViewer, r as FUZZ_NOTE, t as mountVizText } from "./embed-BRQxeM4W.mjs";
2
+ export { FUZZ_NOTE, STRUCTURE_BANNER, type ViewerOptions, mountViewer, mountVizText, showIssues };
@@ -0,0 +1 @@
1
+ export { r as FUZZ_NOTE, i as STRUCTURE_BANNER, a as mountViewer, t as mountVizText, n as showIssues } from "./embed-BtlbyzmZ.mjs";
package/package.json CHANGED
@@ -1,7 +1,46 @@
1
1
  {
2
2
  "name": "@mantaq/viz",
3
- "version": "0.0.0",
4
- "description": "Placeholder to enable npm trusted publishing.",
5
- "repository": { "type": "git", "url": "git+https://github.com/AndersCan/mantaq.git" },
6
- "license": "MIT"
7
- }
3
+ "version": "0.1.0",
4
+ "description": "Draw and walk a Mantaq machine in the browser, from a versioned, deterministic JSON document.",
5
+ "license": "MIT",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "https://github.com/AndersCan/mantaq",
9
+ "directory": "packages/viz"
10
+ },
11
+ "files": [
12
+ "dist",
13
+ "src/viewer/viewer.css",
14
+ "src/viewer/page.css"
15
+ ],
16
+ "type": "module",
17
+ "exports": {
18
+ ".": "./dist/index.mjs",
19
+ "./viewer": "./dist/viewer.mjs",
20
+ "./viewer.css": "./src/viewer/viewer.css",
21
+ "./page.css": "./src/viewer/page.css",
22
+ "./page": "./dist/page.mjs",
23
+ "./standalone": "./dist/standalone.mjs",
24
+ "./live": "./dist/live.mjs",
25
+ "./package.json": "./package.json"
26
+ },
27
+ "publishConfig": {
28
+ "access": "public"
29
+ },
30
+ "dependencies": {
31
+ "@dagrejs/dagre": "^3.1.2",
32
+ "@mantaq/utils": "0.0.4"
33
+ },
34
+ "devDependencies": {
35
+ "@typescript/native-preview": "7.0.0-dev.20260707.2",
36
+ "happy-dom": "^21.0.4",
37
+ "vite": "npm:@voidzero-dev/vite-plus-core@1.4.0",
38
+ "vite-plus": "1.4.0"
39
+ },
40
+ "scripts": {
41
+ "dev": "vp dev",
42
+ "build": "vp pack",
43
+ "test": "vp test",
44
+ "check": "vp check"
45
+ }
46
+ }
@@ -0,0 +1,12 @@
1
+ /* Standalone viewer page chrome (index.html). Embeds bring their own. */
2
+ body {
3
+ margin: 0;
4
+ padding: 16px;
5
+ font:
6
+ 14px/1.4 system-ui,
7
+ sans-serif;
8
+ }
9
+
10
+ .toolbar {
11
+ margin-bottom: 12px;
12
+ }