@mantaq/viz 0.1.0 → 0.2.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/README.md +15 -14
- package/dist/{embed-BRQxeM4W.d.mts → embed-CISoaFIK.d.mts} +7 -2
- package/dist/{embed-BtlbyzmZ.mjs → embed-QX7vRXoL.mjs} +282 -30
- package/dist/index.d.mts +2 -2
- package/dist/index.mjs +1 -1
- package/dist/page.d.mts +1 -1
- package/dist/page.mjs +1 -1
- package/dist/{routing-BNJN5Oiu.mjs → routing-xXYo05xv.mjs} +79 -5
- package/dist/{validate-3D5LrtlE.d.mts → validate-C_EXBtem.d.mts} +43 -2
- package/dist/viewer.d.mts +1 -1
- package/dist/viewer.mjs +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -20,20 +20,21 @@ Take a step with a panel button, by clicking a pulsing edge, or by clicking a st
|
|
|
20
20
|
|
|
21
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
22
|
|
|
23
|
-
## Schema (
|
|
24
|
-
|
|
25
|
-
`VizDocument { schemaVersion:
|
|
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
|
-
| `
|
|
23
|
+
## Schema (v8)
|
|
24
|
+
|
|
25
|
+
`VizDocument { schemaVersion: 8, 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 }`), `doc` (leading JSDoc of the `state(...)` declaration, `null` = undocumented or AST not run) |
|
|
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), `doc` (leading JSDoc of the `event(...)` declaration, `null` = none) |
|
|
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
|
+
| `edge.doc` | leading JSDoc of the `m.on` / `m.onAny` call behind the edge; `null` = undocumented, no handler, or AST not run |
|
|
37
|
+
| `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
|
|
|
38
39
|
Bump `VIZ_SCHEMA_VERSION` on any breaking change. The validator accepts only the current version.
|
|
39
40
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { r as VizIssue, u as VizDocument } from "./validate-
|
|
1
|
+
import { d as VizDriver, r as VizIssue, u as VizDocument } from "./validate-C_EXBtem.mjs";
|
|
2
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
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
4
|
interface ViewerOptions {
|
|
@@ -6,8 +6,13 @@ interface ViewerOptions {
|
|
|
6
6
|
heading?: "h1" | "h2" | "h3";
|
|
7
7
|
/** Machine to show first; defaults to the first one. */
|
|
8
8
|
machine?: string;
|
|
9
|
-
/** Encoded steps to open on (from `?step=`), in the shown machine. */
|
|
9
|
+
/** Encoded steps to open on (from `?step=`), in the shown machine. With a `driver`, live steps replayed through it. */
|
|
10
10
|
steps?: ReadonlyArray<string>;
|
|
11
|
+
/**
|
|
12
|
+
* Runs the machine for real: steps go to the driver and the cursor follows its state.
|
|
13
|
+
* The driver runs one machine, so the machine picker is hidden.
|
|
14
|
+
*/
|
|
15
|
+
driver?: VizDriver;
|
|
11
16
|
/** Called after every walk or machine change, e.g. to keep the URL shareable. */
|
|
12
17
|
onNavigate?: (where: {
|
|
13
18
|
machine: string;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as stateAfter,
|
|
1
|
+
import { a as encodeStep, c as stateAfter, g as scopeOf, h as isAnyState, i as encodeLiveStep, l as unhandledSends, m as coveredByAny, n as decodeLiveSteps, o as movesFrom, r as decodePath, s as payloadSamples, t as routingWarnings, u as parseViz } from "./routing-xXYo05xv.mjs";
|
|
2
2
|
import { Either } from "@mantaq/utils";
|
|
3
3
|
import { Graph, layout } from "@dagrejs/dagre";
|
|
4
4
|
// src/viewer/dom.ts
|
|
@@ -641,6 +641,7 @@ function edgePanel(doc, edge, machine) {
|
|
|
641
641
|
["source", isAnyState(edge.from) ? "any state (onAny)" : edge.from],
|
|
642
642
|
["event", edge.kind === "initial" ? "(initial)" : `${edge.event}${internal ? " (internal)" : ""}`],
|
|
643
643
|
["target", targetText$1(edge)],
|
|
644
|
+
["doc", edge.doc ?? "none"],
|
|
644
645
|
["kind", edge.kind],
|
|
645
646
|
["origin", ORIGIN_LABELS[edge.origin]],
|
|
646
647
|
["emits", joined(edge.emits)],
|
|
@@ -665,6 +666,7 @@ function statePanel(doc, state, context) {
|
|
|
665
666
|
"data-selected-state": state.id
|
|
666
667
|
}, [html(doc, "h2", {}, [`State ${state.id}`]), list(doc, [
|
|
667
668
|
["id", state.id],
|
|
669
|
+
["doc", state.doc ?? "none"],
|
|
668
670
|
["region", state.region ?? "(root)"],
|
|
669
671
|
["flags", joined(flags)],
|
|
670
672
|
["effects on entry", joined(state.effects.map((effect) => effect.sends === null ? effect.name : `${effect.name} (sends ${effect.sends.join(", ") || "nothing"})`))],
|
|
@@ -719,6 +721,12 @@ function button(doc, label, context) {
|
|
|
719
721
|
element.addEventListener("click", context.onClick);
|
|
720
722
|
return element;
|
|
721
723
|
}
|
|
724
|
+
function docNote(doc, note) {
|
|
725
|
+
return note.text === null ? null : html(doc, "p", {
|
|
726
|
+
class: "doc",
|
|
727
|
+
"data-doc": note.scope
|
|
728
|
+
}, [note.text]);
|
|
729
|
+
}
|
|
722
730
|
function targetText(move, target, current) {
|
|
723
731
|
return target === current && move.edge.kind === "stay" ? "stays" : `→ ${target}`;
|
|
724
732
|
}
|
|
@@ -728,9 +736,13 @@ function moveItem(doc, move, context) {
|
|
|
728
736
|
if (move.targets.length === 0) return html(doc, "li", { "data-move": edge.id }, [
|
|
729
737
|
code(doc, edge.event),
|
|
730
738
|
via,
|
|
731
|
-
html(doc, "span", { class: "muted" }, [" → ? target depends on payload or context"])
|
|
739
|
+
html(doc, "span", { class: "muted" }, [" → ? target depends on payload or context"]),
|
|
740
|
+
docNote(doc, {
|
|
741
|
+
text: edge.doc,
|
|
742
|
+
scope: "edge"
|
|
743
|
+
})
|
|
732
744
|
]);
|
|
733
|
-
return html(doc, "li", { "data-move": edge.id }, move.targets.map((target) => button(doc, [
|
|
745
|
+
return html(doc, "li", { "data-move": edge.id }, [...move.targets.map((target) => button(doc, [
|
|
734
746
|
code(doc, edge.event),
|
|
735
747
|
` ${targetText(move, target, context.current)}`,
|
|
736
748
|
...via ? [via] : []
|
|
@@ -743,7 +755,10 @@ function moveItem(doc, move, context) {
|
|
|
743
755
|
"data-take": edge.id,
|
|
744
756
|
"data-target": target
|
|
745
757
|
}
|
|
746
|
-
}))
|
|
758
|
+
})), docNote(doc, {
|
|
759
|
+
text: edge.doc,
|
|
760
|
+
scope: "edge"
|
|
761
|
+
})]);
|
|
747
762
|
}
|
|
748
763
|
function effectsSection(doc, machine, current) {
|
|
749
764
|
const effects = machine.states.find((state) => state.id === current)?.effects ?? [];
|
|
@@ -805,39 +820,206 @@ function pathSection(doc, context) {
|
|
|
805
820
|
attrs: { "data-rewind": "0" }
|
|
806
821
|
})]), ...items])]);
|
|
807
822
|
}
|
|
823
|
+
function describeStep(step) {
|
|
824
|
+
if (step.kind === "advance") return `advance ${step.ms} ms`;
|
|
825
|
+
return step.payload === null ? step.event : `${step.event} ${JSON.stringify(step.payload)}`;
|
|
826
|
+
}
|
|
827
|
+
/** The clock starts at 0 and only `advance` steps move it. */
|
|
828
|
+
function elapsed(steps) {
|
|
829
|
+
return steps.reduce((sum, step) => sum + (step.kind === "advance" ? step.ms : 0), 0);
|
|
830
|
+
}
|
|
831
|
+
function timersSection(doc, context) {
|
|
832
|
+
const now = elapsed(context.state.steps);
|
|
833
|
+
const input = html(doc, "input", {
|
|
834
|
+
type: "number",
|
|
835
|
+
min: "0",
|
|
836
|
+
value: "100",
|
|
837
|
+
"aria-label": "milliseconds",
|
|
838
|
+
"data-advance-ms": ""
|
|
839
|
+
});
|
|
840
|
+
const problem = html(doc, "p", {
|
|
841
|
+
class: "explore-warning",
|
|
842
|
+
role: "alert",
|
|
843
|
+
hidden: ""
|
|
844
|
+
}, ["Enter a number of milliseconds, 0 or more."]);
|
|
845
|
+
const advance = () => {
|
|
846
|
+
const ms = Number(input.value);
|
|
847
|
+
const valid = input.value.trim() !== "" && Number.isFinite(ms) && ms >= 0;
|
|
848
|
+
problem.toggleAttribute("hidden", valid);
|
|
849
|
+
if (valid) context.send({
|
|
850
|
+
kind: "advance",
|
|
851
|
+
ms
|
|
852
|
+
});
|
|
853
|
+
};
|
|
854
|
+
return html(doc, "div", { class: "explore-time" }, [
|
|
855
|
+
html(doc, "h3", {}, ["Time"]),
|
|
856
|
+
html(doc, "ul", { "data-group": "timers" }, context.state.timers.map((timer) => {
|
|
857
|
+
const remaining = Math.max(0, timer.deadline - now);
|
|
858
|
+
return html(doc, "li", { "data-timer": timer.id }, [button(doc, [`advance ${remaining} ms → ${timer.eventName ?? timer.id}`], {
|
|
859
|
+
onClick: () => context.send({
|
|
860
|
+
kind: "advance",
|
|
861
|
+
ms: remaining
|
|
862
|
+
}),
|
|
863
|
+
attrs: { "data-advance-timer": timer.id }
|
|
864
|
+
})]);
|
|
865
|
+
})),
|
|
866
|
+
context.state.timers.length === 0 ? html(doc, "p", {
|
|
867
|
+
class: "muted",
|
|
868
|
+
"data-no-timers": ""
|
|
869
|
+
}, ["No timers pending."]) : null,
|
|
870
|
+
html(doc, "div", {}, [
|
|
871
|
+
input,
|
|
872
|
+
" ms ",
|
|
873
|
+
button(doc, ["advance"], {
|
|
874
|
+
onClick: advance,
|
|
875
|
+
attrs: { "data-advance": "" }
|
|
876
|
+
})
|
|
877
|
+
]),
|
|
878
|
+
problem
|
|
879
|
+
]);
|
|
880
|
+
}
|
|
881
|
+
function liveMovesSection(doc, machine, context) {
|
|
882
|
+
const current = context.state.state;
|
|
883
|
+
if (context.final) return html(doc, "p", {
|
|
884
|
+
class: "explore-end explore-done",
|
|
885
|
+
role: "status"
|
|
886
|
+
}, [html(doc, "strong", {}, ["Done."]), ` ${current} is final: the actor has stopped.`]);
|
|
887
|
+
const handled = new Set(movesFrom(machine, current).map((move) => move.edge.event));
|
|
888
|
+
const inputs = machine.events.filter((event) => !event.internal).map((event) => event.id);
|
|
889
|
+
const events = [.../* @__PURE__ */ new Set([...handled, ...inputs])].sort();
|
|
890
|
+
const json = html(doc, "textarea", {
|
|
891
|
+
rows: "2",
|
|
892
|
+
placeholder: "payload as JSON (empty: the sample, or none)",
|
|
893
|
+
"aria-label": "payload JSON",
|
|
894
|
+
"data-payload-json": ""
|
|
895
|
+
});
|
|
896
|
+
const problem = html(doc, "p", {
|
|
897
|
+
class: "explore-warning",
|
|
898
|
+
role: "alert",
|
|
899
|
+
hidden: ""
|
|
900
|
+
}, ["Payload is not valid JSON."]);
|
|
901
|
+
const payloadFor = (sample) => {
|
|
902
|
+
try {
|
|
903
|
+
if (json.value.trim() !== "") return { value: JSON.parse(json.value) };
|
|
904
|
+
return { value: sample === null || sample.value === "" ? null : JSON.parse(sample.value) };
|
|
905
|
+
} catch {
|
|
906
|
+
return null;
|
|
907
|
+
}
|
|
908
|
+
};
|
|
909
|
+
return html(doc, "div", { class: "explore-moves" }, [
|
|
910
|
+
html(doc, "h3", {}, ["Send"]),
|
|
911
|
+
json,
|
|
912
|
+
problem,
|
|
913
|
+
html(doc, "ul", { "data-group": "live" }, events.map((event) => {
|
|
914
|
+
const samples = payloadSamples(machine, event);
|
|
915
|
+
const sample = samples.length === 0 ? null : html(doc, "select", {
|
|
916
|
+
"aria-label": `${event} payload sample`,
|
|
917
|
+
"data-sample": event
|
|
918
|
+
}, [html(doc, "option", { value: "" }, ["no payload"]), ...samples.map((each) => html(doc, "option", { value: JSON.stringify(each) }, [JSON.stringify(each)]))]);
|
|
919
|
+
return html(doc, "li", { "data-send-item": event }, [
|
|
920
|
+
button(doc, [code(doc, event)], {
|
|
921
|
+
onClick: () => {
|
|
922
|
+
const payload = payloadFor(sample);
|
|
923
|
+
problem.toggleAttribute("hidden", payload !== null);
|
|
924
|
+
if (payload !== null) context.send({
|
|
925
|
+
kind: "event",
|
|
926
|
+
event,
|
|
927
|
+
payload: payload.value
|
|
928
|
+
});
|
|
929
|
+
},
|
|
930
|
+
attrs: { "data-send": event }
|
|
931
|
+
}),
|
|
932
|
+
sample,
|
|
933
|
+
handled.has(event) ? null : html(doc, "span", {
|
|
934
|
+
class: "muted",
|
|
935
|
+
"data-ignored": ""
|
|
936
|
+
}, [` ignored in ${current}`])
|
|
937
|
+
]);
|
|
938
|
+
}))
|
|
939
|
+
]);
|
|
940
|
+
}
|
|
941
|
+
function outcomeText(last) {
|
|
942
|
+
if (last.outcome === "transition") return ` ${last.from} → ${last.to}`;
|
|
943
|
+
if (last.outcome === "stay") return ` handled in ${last.from}: stays`;
|
|
944
|
+
return ` ignored in ${last.from}: no handler`;
|
|
945
|
+
}
|
|
946
|
+
function liveResultSection(doc, state) {
|
|
947
|
+
const { last } = state;
|
|
948
|
+
return html(doc, "div", { class: "explore-live" }, [last === null ? html(doc, "p", { class: "muted" }, ["Nothing has run yet."]) : html(doc, "div", {
|
|
949
|
+
class: "explore-outcome",
|
|
950
|
+
"data-outcome": last.outcome
|
|
951
|
+
}, [
|
|
952
|
+
html(doc, "p", {}, [code(doc, last.event), outcomeText(last)]),
|
|
953
|
+
last.effects.length === 0 ? null : html(doc, "p", { "data-live-effects": "" }, ["Effects run: ", ...last.effects.flatMap((name, index) => [index === 0 ? "" : ", ", code(doc, name)])]),
|
|
954
|
+
last.error === null ? null : html(doc, "p", {
|
|
955
|
+
class: "explore-warning",
|
|
956
|
+
role: "alert",
|
|
957
|
+
"data-live-error": ""
|
|
958
|
+
}, [`Handler threw: ${last.error}`])
|
|
959
|
+
]), html(doc, "p", { "data-live-context": "" }, ["Context: ", code(doc, JSON.stringify(state.context))])]);
|
|
960
|
+
}
|
|
961
|
+
function livePathSection(doc, context, state) {
|
|
962
|
+
return html(doc, "div", { class: "explore-path" }, [html(doc, "h3", {}, ["Path"]), html(doc, "ol", {}, [html(doc, "li", { "aria-current": state.steps.length === 0 ? "step" : null }, [button(doc, ["start"], {
|
|
963
|
+
onClick: () => context.rewind(0),
|
|
964
|
+
attrs: { "data-rewind": "0" }
|
|
965
|
+
})]), ...state.steps.map((step, index) => html(doc, "li", { "aria-current": index === state.steps.length - 1 ? "step" : null }, [button(doc, [describeStep(step)], {
|
|
966
|
+
onClick: () => context.rewind(index + 1),
|
|
967
|
+
attrs: { "data-rewind": String(index + 1) }
|
|
968
|
+
})]))])]);
|
|
969
|
+
}
|
|
970
|
+
function nowSection(doc, machine, context) {
|
|
971
|
+
const { current, final } = context;
|
|
972
|
+
return [html(doc, "p", { class: "explore-now" }, [
|
|
973
|
+
"In ",
|
|
974
|
+
html(doc, "strong", {}, [current]),
|
|
975
|
+
final ? " (final)" : ""
|
|
976
|
+
]), docNote(doc, {
|
|
977
|
+
text: machine.states.find((state) => state.id === current)?.doc ?? null,
|
|
978
|
+
scope: "state"
|
|
979
|
+
})];
|
|
980
|
+
}
|
|
808
981
|
/** Side-panel section: where the walk is, what can happen next, and how it got here. */
|
|
809
982
|
function explorePanel(doc, context) {
|
|
810
|
-
const { machine, path } = context;
|
|
811
|
-
const current = stateAfter(machine, path) ?? "";
|
|
983
|
+
const { machine, path, live } = context;
|
|
984
|
+
const current = live?.state.state ?? stateAfter(machine, path) ?? "";
|
|
812
985
|
const final = machine.states.find((state) => state.id === current)?.final === true;
|
|
813
|
-
const
|
|
986
|
+
const length = live?.state.steps.length ?? path.length;
|
|
814
987
|
return html(doc, "section", {
|
|
815
988
|
class: "explore",
|
|
816
989
|
"data-current": current,
|
|
817
990
|
"data-done": final ? "" : null
|
|
818
991
|
}, [
|
|
819
992
|
html(doc, "h2", {}, ["Explore"]),
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
993
|
+
...nowSection(doc, machine, {
|
|
994
|
+
current,
|
|
995
|
+
final
|
|
996
|
+
}),
|
|
997
|
+
...live ? [
|
|
998
|
+
liveResultSection(doc, live.state),
|
|
999
|
+
liveMovesSection(doc, machine, {
|
|
1000
|
+
state: live.state,
|
|
1001
|
+
send: live.send,
|
|
1002
|
+
final
|
|
1003
|
+
}),
|
|
1004
|
+
final ? null : timersSection(doc, {
|
|
1005
|
+
state: live.state,
|
|
1006
|
+
send: live.send
|
|
1007
|
+
})
|
|
1008
|
+
] : [effectsSection(doc, machine, current), movesSection(doc, movesFrom(machine, current), {
|
|
827
1009
|
current,
|
|
828
1010
|
final,
|
|
829
1011
|
take: context.take
|
|
830
|
-
}),
|
|
1012
|
+
})],
|
|
831
1013
|
html(doc, "div", { class: "explore-controls" }, [button(doc, ["Back"], {
|
|
832
|
-
onClick: () => context.rewind(
|
|
833
|
-
disabled:
|
|
1014
|
+
onClick: () => context.rewind(length - 1),
|
|
1015
|
+
disabled: length === 0,
|
|
834
1016
|
attrs: { "data-explore-action": "back" }
|
|
835
1017
|
}), button(doc, ["Reset"], {
|
|
836
1018
|
onClick: () => context.rewind(0),
|
|
837
|
-
disabled:
|
|
1019
|
+
disabled: length === 0,
|
|
838
1020
|
attrs: { "data-explore-action": "reset" }
|
|
839
1021
|
})]),
|
|
840
|
-
pathSection(doc, context)
|
|
1022
|
+
live ? livePathSection(doc, context, live.state) : pathSection(doc, context)
|
|
841
1023
|
]);
|
|
842
1024
|
}
|
|
843
1025
|
/**
|
|
@@ -845,7 +1027,7 @@ function explorePanel(doc, context) {
|
|
|
845
1027
|
* a next-step edge with one target, or a state exactly one next step reaches.
|
|
846
1028
|
*/
|
|
847
1029
|
function clickStep(machine, context) {
|
|
848
|
-
const current = stateAfter(machine, context.path);
|
|
1030
|
+
const current = context.live ?? stateAfter(machine, context.path);
|
|
849
1031
|
if (current === null) return null;
|
|
850
1032
|
const { selection } = context;
|
|
851
1033
|
const steps = movesFrom(machine, current).flatMap((move) => move.targets.map((to) => ({
|
|
@@ -858,7 +1040,7 @@ function clickStep(machine, context) {
|
|
|
858
1040
|
/** Mark the walk on the drawn graph: current state, path taken, possible next steps. */
|
|
859
1041
|
function markExplore(root, context) {
|
|
860
1042
|
const { machine, path } = context;
|
|
861
|
-
const current = stateAfter(machine, path);
|
|
1043
|
+
const current = context.live ?? stateAfter(machine, path);
|
|
862
1044
|
const moves = current === null ? [] : movesFrom(machine, current);
|
|
863
1045
|
const next = new Set(moves.filter((move) => move.targets.length > 0).map((move) => move.edge.id));
|
|
864
1046
|
const reachable = new Set(moves.flatMap((move) => move.targets).filter((to) => to !== current));
|
|
@@ -877,6 +1059,7 @@ function markExplore(root, context) {
|
|
|
877
1059
|
}
|
|
878
1060
|
// src/viewer/viewer.ts
|
|
879
1061
|
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.";
|
|
1062
|
+
const LIVE_BANNER = "Live: the real machine runs. Send an input and the cursor follows its actual state; guards and effects run.";
|
|
880
1063
|
const FUZZ_NOTE = "Green edges were observed while fuzzing but not declared. Fuzzing finds behaviour; it never proves an edge is impossible.";
|
|
881
1064
|
function effectiveMode(view, machine) {
|
|
882
1065
|
const modes = availableModes(machine);
|
|
@@ -933,8 +1116,11 @@ function mountViewer(root, viz, options = {}) {
|
|
|
933
1116
|
machineId: first?.id ?? "",
|
|
934
1117
|
selection: null,
|
|
935
1118
|
coverage: null,
|
|
936
|
-
path: first === void 0 ? [] : decodePath(first, options.steps ?? [])
|
|
1119
|
+
path: first === void 0 || options.driver !== void 0 ? [] : decodePath(first, options.steps ?? []),
|
|
1120
|
+
live: null
|
|
937
1121
|
};
|
|
1122
|
+
const { driver } = options;
|
|
1123
|
+
if (driver !== void 0) view.live = replay(driver, decodeLiveSteps(options.steps ?? []));
|
|
938
1124
|
let back = () => void 0;
|
|
939
1125
|
root.addEventListener("keydown", (event) => {
|
|
940
1126
|
if (!("key" in event) || event.target instanceof HTMLSelectElement) return;
|
|
@@ -959,42 +1145,71 @@ function mountViewer(root, viz, options = {}) {
|
|
|
959
1145
|
machine,
|
|
960
1146
|
path: view.path,
|
|
961
1147
|
take,
|
|
962
|
-
rewind
|
|
1148
|
+
rewind,
|
|
1149
|
+
...view.live === null ? {} : { live: {
|
|
1150
|
+
state: view.live,
|
|
1151
|
+
send
|
|
1152
|
+
} }
|
|
963
1153
|
}), renderPanel(doc, machine, {
|
|
964
1154
|
selection: view.selection,
|
|
965
1155
|
select
|
|
966
1156
|
}));
|
|
967
1157
|
markExplore(root, {
|
|
968
1158
|
machine,
|
|
969
|
-
path: view.path
|
|
1159
|
+
path: view.path,
|
|
1160
|
+
...liveState(view)
|
|
970
1161
|
});
|
|
971
1162
|
markSelection(root, view.selection);
|
|
972
1163
|
};
|
|
973
1164
|
const walked = () => {
|
|
1165
|
+
const steps = view.live === null ? view.path.map((step) => encodeStep(machine, step)) : view.live.steps.map(encodeLiveStep);
|
|
974
1166
|
options.onNavigate?.({
|
|
975
1167
|
machine: machine.id,
|
|
976
|
-
steps
|
|
1168
|
+
steps
|
|
977
1169
|
});
|
|
978
1170
|
refresh();
|
|
979
1171
|
};
|
|
1172
|
+
const send = (step) => {
|
|
1173
|
+
if (driver === void 0) return;
|
|
1174
|
+
view.live = stepSafely(driver, {
|
|
1175
|
+
before: view.live,
|
|
1176
|
+
step
|
|
1177
|
+
});
|
|
1178
|
+
walked();
|
|
1179
|
+
};
|
|
980
1180
|
const take = (step) => {
|
|
1181
|
+
if (driver !== void 0) {
|
|
1182
|
+
const event = machine.edges.find((edge) => edge.id === step.edge)?.event;
|
|
1183
|
+
if (event !== void 0) send({
|
|
1184
|
+
kind: "event",
|
|
1185
|
+
event,
|
|
1186
|
+
payload: null
|
|
1187
|
+
});
|
|
1188
|
+
return;
|
|
1189
|
+
}
|
|
981
1190
|
view.path = [...view.path, step];
|
|
982
1191
|
walked();
|
|
983
1192
|
};
|
|
984
1193
|
const rewind = (length) => {
|
|
1194
|
+
if (driver !== void 0 && view.live !== null) {
|
|
1195
|
+
view.live = replay(driver, view.live.steps.slice(0, Math.max(0, length)));
|
|
1196
|
+
walked();
|
|
1197
|
+
return;
|
|
1198
|
+
}
|
|
985
1199
|
view.path = view.path.slice(0, Math.max(0, length));
|
|
986
1200
|
walked();
|
|
987
1201
|
};
|
|
988
1202
|
const select = (selection) => {
|
|
989
1203
|
const step = clickStep(machine, {
|
|
990
1204
|
path: view.path,
|
|
991
|
-
selection
|
|
1205
|
+
selection,
|
|
1206
|
+
...liveState(view)
|
|
992
1207
|
});
|
|
993
1208
|
view.selection = selection;
|
|
994
1209
|
if (step === null) refresh();
|
|
995
1210
|
else take(step);
|
|
996
1211
|
};
|
|
997
|
-
back = () => rewind(view.path.length - 1);
|
|
1212
|
+
back = () => rewind((view.live?.steps.length ?? view.path.length) - 1);
|
|
998
1213
|
root.append(headerOf(doc, {
|
|
999
1214
|
viz,
|
|
1000
1215
|
machine,
|
|
@@ -1002,11 +1217,12 @@ function mountViewer(root, viz, options = {}) {
|
|
|
1002
1217
|
heading: options.heading ?? "h1",
|
|
1003
1218
|
mode,
|
|
1004
1219
|
render,
|
|
1005
|
-
onNavigate: options.onNavigate
|
|
1220
|
+
onNavigate: options.onNavigate,
|
|
1221
|
+
live: driver !== void 0
|
|
1006
1222
|
}), html(doc, "p", {
|
|
1007
1223
|
class: "banner",
|
|
1008
1224
|
role: "note"
|
|
1009
|
-
}, [banner(machine)]), legend(doc, {
|
|
1225
|
+
}, [driver === void 0 ? banner(machine) : LIVE_BANNER]), legend(doc, {
|
|
1010
1226
|
mode,
|
|
1011
1227
|
machine
|
|
1012
1228
|
}), html(doc, "div", { class: "viz-main" }, [html(doc, "div", { class: "canvas" }, [renderGraph(doc, machine, {
|
|
@@ -1017,11 +1233,32 @@ function mountViewer(root, viz, options = {}) {
|
|
|
1017
1233
|
}
|
|
1018
1234
|
render();
|
|
1019
1235
|
}
|
|
1236
|
+
/** A driver that throws leaves the state where it was and shows the message as a handler error. */
|
|
1237
|
+
function stepSafely(driver, context) {
|
|
1238
|
+
const { before, step } = context;
|
|
1239
|
+
try {
|
|
1240
|
+
return driver.step(step);
|
|
1241
|
+
} catch (error) {
|
|
1242
|
+
if (before === null) throw error;
|
|
1243
|
+
return {
|
|
1244
|
+
...before,
|
|
1245
|
+
last: {
|
|
1246
|
+
from: before.state,
|
|
1247
|
+
event: step.kind === "event" ? step.event : step.kind,
|
|
1248
|
+
to: before.state,
|
|
1249
|
+
outcome: "stay",
|
|
1250
|
+
effects: [],
|
|
1251
|
+
context: before.context,
|
|
1252
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1253
|
+
}
|
|
1254
|
+
};
|
|
1255
|
+
}
|
|
1256
|
+
}
|
|
1020
1257
|
function headerOf(doc, context) {
|
|
1021
1258
|
const { machine, view, render } = context;
|
|
1022
1259
|
return html(doc, "header", { class: "viz-header" }, [
|
|
1023
1260
|
html(doc, context.heading, {}, [`Viz: ${machine.id}`]),
|
|
1024
|
-
machineSelect(doc, context.viz, {
|
|
1261
|
+
context.live ? null : machineSelect(doc, context.viz, {
|
|
1025
1262
|
view,
|
|
1026
1263
|
onChange: (id) => {
|
|
1027
1264
|
view.machineId = id;
|
|
@@ -1051,6 +1288,21 @@ function markSelection(root, selection) {
|
|
|
1051
1288
|
const attribute = selection?.kind === "edge" ? "edgeId" : "nodeId";
|
|
1052
1289
|
for (const element of root.querySelectorAll(".graph g")) element.classList.toggle("selected", selection !== null && element.dataset[attribute] === selection.id);
|
|
1053
1290
|
}
|
|
1291
|
+
function liveState(view) {
|
|
1292
|
+
return view.live === null ? {} : { live: view.live.state };
|
|
1293
|
+
}
|
|
1294
|
+
/** Back is reset plus replay: the driver keeps no history. Stops at the first step the driver throws on. */
|
|
1295
|
+
function replay(driver, steps) {
|
|
1296
|
+
let live = driver.reset();
|
|
1297
|
+
for (const step of steps) {
|
|
1298
|
+
live = stepSafely(driver, {
|
|
1299
|
+
before: live,
|
|
1300
|
+
step
|
|
1301
|
+
});
|
|
1302
|
+
if (live.last?.error) return live;
|
|
1303
|
+
}
|
|
1304
|
+
return live;
|
|
1305
|
+
}
|
|
1054
1306
|
// src/viewer/embed.ts
|
|
1055
1307
|
/**
|
|
1056
1308
|
* Browser entry for embedding the viewer in another page (e.g. the docs site).
|
package/dist/index.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { C as
|
|
1
|
+
import { A as coveredByAny, C as VizLiveState, D as VizMachine, E as VizLocation, M as scopeOf, O as VizState, S as VizLiveOutcome, T as VizLiveTimer, _ as VizExplanation, a as VIZ_SCHEMA_VERSION, b as VizFuzzSequence, c as VizComparison, d as VizDriver, f as VizEdge, g as VizEvent, h as VizEffect, i as ANY_STATE, j as isAnyState, k as VizStep, l as VizCoverage, m as VizEdgeOrigin, n as validateViz, o as VizBranch, p as VizEdgeKind, r as VizIssue, s as VizBranchTarget, t as parseViz, u as VizDocument, v as VizFuzz, w as VizLiveStep, x as VizJson, y as VizFuzzError } from "./validate-C_EXBtem.mjs";
|
|
2
2
|
/** Byte-stable JSON: sorted object keys, two-space indent, trailing newline. */
|
|
3
3
|
export declare function serializeViz(document: VizDocument): string;
|
|
4
4
|
/**
|
|
@@ -79,4 +79,4 @@ export declare function livePageHtml(parts: {
|
|
|
79
79
|
/** Stylesheet text, inlined: the built entry carries no CSS of its own. */
|
|
80
80
|
css: string;
|
|
81
81
|
}): string;
|
|
82
|
-
export { ANY_STATE, type ExploreStep, type Move, type MoveGroup, type RoutingWarning, VIZ_SCHEMA_VERSION, VizBranch, VizBranchTarget, VizComparison, VizCoverage, VizDocument, VizEdge, VizEdgeKind, VizEdgeOrigin, VizEffect, VizEvent, VizExplanation, VizFuzz, VizFuzzError, VizFuzzSequence, type VizIssue, VizJson, VizLocation, VizMachine, VizState, VizStep, coveredByAny, isAnyState, parseViz, scopeOf, validateViz };
|
|
82
|
+
export { ANY_STATE, type ExploreStep, type Move, type MoveGroup, type RoutingWarning, VIZ_SCHEMA_VERSION, VizBranch, VizBranchTarget, VizComparison, VizCoverage, VizDocument, VizDriver, VizEdge, VizEdgeKind, VizEdgeOrigin, VizEffect, VizEvent, VizExplanation, VizFuzz, VizFuzzError, VizFuzzSequence, type VizIssue, VizJson, VizLiveOutcome, VizLiveState, VizLiveStep, VizLiveTimer, VizLocation, VizMachine, VizState, VizStep, coveredByAny, isAnyState, parseViz, scopeOf, validateViz };
|
package/dist/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as
|
|
1
|
+
import { a as encodeStep, c as stateAfter, d as validateViz, f as ANY_STATE, g as scopeOf, h as isAnyState, l as unhandledSends, m as coveredByAny, o as movesFrom, p as VIZ_SCHEMA_VERSION, r as decodePath, t as routingWarnings, u as parseViz } from "./routing-xXYo05xv.mjs";
|
|
2
2
|
import { i as standaloneHtml, n as UPDATE_EVENT, r as livePageHtml, t as DOCUMENT_ELEMENT_ID } from "./html-BfvuzJs6.mjs";
|
|
3
3
|
// src/serialize.ts
|
|
4
4
|
function sortKeys(value) {
|
package/dist/page.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as ViewerOptions } from "./embed-
|
|
1
|
+
import { a as ViewerOptions } from "./embed-CISoaFIK.mjs";
|
|
2
2
|
/** Options that open the walk named in the address bar and keep it there. */
|
|
3
3
|
export declare function linkOptions(): ViewerOptions;
|
|
4
4
|
/** Mount the document embedded in the page itself. */
|
package/dist/page.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import "./html-BfvuzJs6.mjs";
|
|
2
|
-
import { n as showIssues, t as mountVizText } from "./embed-
|
|
2
|
+
import { n as showIssues, t as mountVizText } from "./embed-QX7vRXoL.mjs";
|
|
3
3
|
// src/viewer/page.ts
|
|
4
4
|
/**
|
|
5
5
|
* Whole-page entries: a self-contained HTML file (`mountEmbedded`) and a live
|
|
@@ -4,7 +4,7 @@ import { Either } from "@mantaq/utils";
|
|
|
4
4
|
* Viz document schema. Bump `VIZ_SCHEMA_VERSION` on any breaking change.
|
|
5
5
|
* Everything not observable at runtime is `null`, never guessed.
|
|
6
6
|
*/
|
|
7
|
-
const VIZ_SCHEMA_VERSION =
|
|
7
|
+
const VIZ_SCHEMA_VERSION = 8;
|
|
8
8
|
/**
|
|
9
9
|
* Edge source of an `onAny` registration: any non-final state of the machine,
|
|
10
10
|
* or `region.*` inside a region. Never listed in `states`.
|
|
@@ -485,6 +485,11 @@ function readState(reader, record, path) {
|
|
|
485
485
|
record,
|
|
486
486
|
key: "tested",
|
|
487
487
|
path
|
|
488
|
+
}),
|
|
489
|
+
doc: readNullableString(reader, {
|
|
490
|
+
record,
|
|
491
|
+
key: "doc",
|
|
492
|
+
path
|
|
488
493
|
})
|
|
489
494
|
};
|
|
490
495
|
}
|
|
@@ -504,6 +509,11 @@ function readEvent(reader, record, path) {
|
|
|
504
509
|
key: "internal",
|
|
505
510
|
path
|
|
506
511
|
}),
|
|
512
|
+
doc: readNullableString(reader, {
|
|
513
|
+
record,
|
|
514
|
+
key: "doc",
|
|
515
|
+
path
|
|
516
|
+
}),
|
|
507
517
|
payloadType: null
|
|
508
518
|
};
|
|
509
519
|
}
|
|
@@ -553,6 +563,11 @@ function readEdge(reader, record, path) {
|
|
|
553
563
|
record,
|
|
554
564
|
path
|
|
555
565
|
}),
|
|
566
|
+
doc: readNullableString(reader, {
|
|
567
|
+
record,
|
|
568
|
+
key: "doc",
|
|
569
|
+
path
|
|
570
|
+
}),
|
|
556
571
|
coverage: readCoverage(reader, {
|
|
557
572
|
record,
|
|
558
573
|
key: "coverage",
|
|
@@ -635,9 +650,9 @@ function validateViz(value) {
|
|
|
635
650
|
path: "$",
|
|
636
651
|
message: "expected object"
|
|
637
652
|
}]);
|
|
638
|
-
if (value["schemaVersion"] !==
|
|
653
|
+
if (value["schemaVersion"] !== 8) return Either.left([{
|
|
639
654
|
path: "$.schemaVersion",
|
|
640
|
-
message: `expected
|
|
655
|
+
message: `expected 8, got ${JSON.stringify(value["schemaVersion"])}`
|
|
641
656
|
}]);
|
|
642
657
|
const machines = readList(reader, {
|
|
643
658
|
record: value,
|
|
@@ -646,7 +661,7 @@ function validateViz(value) {
|
|
|
646
661
|
}, (item, at) => readMachine(reader, item, at));
|
|
647
662
|
if (reader.issues.length > 0) return Either.left(reader.issues);
|
|
648
663
|
const document = {
|
|
649
|
-
schemaVersion:
|
|
664
|
+
schemaVersion: 8,
|
|
650
665
|
machines
|
|
651
666
|
};
|
|
652
667
|
return Either.right(document);
|
|
@@ -743,6 +758,65 @@ function decodePath(machine, encoded) {
|
|
|
743
758
|
}
|
|
744
759
|
return path;
|
|
745
760
|
}
|
|
761
|
+
/**
|
|
762
|
+
* A live step as text for a URL: `@800` advances the clock 800 ms, `go` sends
|
|
763
|
+
* `go` with no payload, `pay={"amount":5}` sends `pay` with a JSON payload.
|
|
764
|
+
*/
|
|
765
|
+
function encodeLiveStep(step) {
|
|
766
|
+
if (step.kind === "advance") return `@${step.ms}`;
|
|
767
|
+
return step.payload === null ? step.event : `${step.event}=${JSON.stringify(step.payload)}`;
|
|
768
|
+
}
|
|
769
|
+
function parseJson(text) {
|
|
770
|
+
try {
|
|
771
|
+
return { value: JSON.parse(text) };
|
|
772
|
+
} catch {
|
|
773
|
+
return null;
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
/** Inverse of `encodeLiveStep`; null when the text is not a step. */
|
|
777
|
+
function decodeLiveStep(text) {
|
|
778
|
+
if (text.startsWith("@")) {
|
|
779
|
+
const ms = Number(text.slice(1));
|
|
780
|
+
return text.length > 1 && Number.isFinite(ms) && ms >= 0 ? {
|
|
781
|
+
kind: "advance",
|
|
782
|
+
ms
|
|
783
|
+
} : null;
|
|
784
|
+
}
|
|
785
|
+
const cut = text.indexOf("=");
|
|
786
|
+
const event = cut === -1 ? text : text.slice(0, cut);
|
|
787
|
+
if (event === "") return null;
|
|
788
|
+
if (cut === -1) return {
|
|
789
|
+
kind: "event",
|
|
790
|
+
event,
|
|
791
|
+
payload: null
|
|
792
|
+
};
|
|
793
|
+
const payload = parseJson(text.slice(cut + 1));
|
|
794
|
+
return payload === null ? null : {
|
|
795
|
+
kind: "event",
|
|
796
|
+
event,
|
|
797
|
+
payload: payload.value
|
|
798
|
+
};
|
|
799
|
+
}
|
|
800
|
+
/** Steps from a shared link, up to the first one that does not parse. */
|
|
801
|
+
function decodeLiveSteps(encoded) {
|
|
802
|
+
const steps = [];
|
|
803
|
+
for (const text of encoded) {
|
|
804
|
+
const step = decodeLiveStep(text);
|
|
805
|
+
if (step === null) break;
|
|
806
|
+
steps.push(step);
|
|
807
|
+
}
|
|
808
|
+
return steps;
|
|
809
|
+
}
|
|
810
|
+
/** Distinct payloads the fuzzer sent with `event`, in first-seen order. */
|
|
811
|
+
function payloadSamples(machine, event) {
|
|
812
|
+
const steps = [...machine.fuzz?.corpus.flatMap((sequence) => sequence.steps) ?? [], ...machine.fuzz?.errors.flatMap((error) => error.steps) ?? []];
|
|
813
|
+
const seen = /* @__PURE__ */ new Map();
|
|
814
|
+
for (const step of steps) {
|
|
815
|
+
if (step.kind !== "event" || step.event !== event || step.payload === null) continue;
|
|
816
|
+
seen.set(JSON.stringify(step.payload), step.payload);
|
|
817
|
+
}
|
|
818
|
+
return [...seen.values()];
|
|
819
|
+
}
|
|
746
820
|
// src/routing.ts
|
|
747
821
|
/**
|
|
748
822
|
* `onAny` handlers that pick their target from the current state. Drawn from
|
|
@@ -756,4 +830,4 @@ function routingWarnings(machine) {
|
|
|
756
830
|
message: `onAny(${event}) reads actor state to pick its target, so the diagram cannot tell which state leads where. Register m.on per state instead.`
|
|
757
831
|
}));
|
|
758
832
|
}
|
|
759
|
-
export {
|
|
833
|
+
export { encodeStep as a, stateAfter as c, validateViz as d, ANY_STATE as f, scopeOf as g, isAnyState as h, encodeLiveStep as i, unhandledSends as l, coveredByAny as m, decodeLiveSteps as n, movesFrom as o, VIZ_SCHEMA_VERSION as p, decodePath as r, payloadSamples as s, routingWarnings as t, parseViz as u };
|
|
@@ -3,7 +3,7 @@ import { Either } from "@mantaq/utils";
|
|
|
3
3
|
* Viz document schema. Bump `VIZ_SCHEMA_VERSION` on any breaking change.
|
|
4
4
|
* Everything not observable at runtime is `null`, never guessed.
|
|
5
5
|
*/
|
|
6
|
-
declare const VIZ_SCHEMA_VERSION =
|
|
6
|
+
declare const VIZ_SCHEMA_VERSION = 8;
|
|
7
7
|
/**
|
|
8
8
|
* Edge source of an `onAny` registration: any non-final state of the machine,
|
|
9
9
|
* or `region.*` inside a region. Never listed in `states`.
|
|
@@ -37,10 +37,14 @@ interface VizState {
|
|
|
37
37
|
coverage: VizCoverage | null;
|
|
38
38
|
/** Visits by the supplied test sequences. null = no tests supplied. */
|
|
39
39
|
tested: VizCoverage | null;
|
|
40
|
+
/** Leading JSDoc of the `state(...)` declaration. null = undocumented or AST not run. */
|
|
41
|
+
doc: string | null;
|
|
40
42
|
}
|
|
41
43
|
interface VizEvent {
|
|
42
44
|
id: string;
|
|
43
45
|
internal: boolean;
|
|
46
|
+
/** Leading JSDoc of the `event(...)` declaration. null = undocumented or AST not run. */
|
|
47
|
+
doc: string | null;
|
|
44
48
|
/** Payload types are erased at runtime; always null. */
|
|
45
49
|
payloadType: null;
|
|
46
50
|
}
|
|
@@ -123,6 +127,8 @@ interface VizEdge {
|
|
|
123
127
|
contexts: string[];
|
|
124
128
|
/** null = no handler behind this edge (initial, effect) or analysis not run. */
|
|
125
129
|
explanation: VizExplanation | null;
|
|
130
|
+
/** Leading JSDoc of the `m.on` / `m.onAny` call. null = undocumented, no handler, or AST not run. */
|
|
131
|
+
doc: string | null;
|
|
126
132
|
/** Times taken in the supplied run (history or fuzz corpus replay). null = no run. */
|
|
127
133
|
coverage: VizCoverage | null;
|
|
128
134
|
/** Times taken by the supplied test sequences. null = no tests supplied. */
|
|
@@ -151,6 +157,41 @@ type VizStep = {
|
|
|
151
157
|
kind: "advance";
|
|
152
158
|
ms: number;
|
|
153
159
|
};
|
|
160
|
+
/** What one step did: the actor moved, handled it without moving, or had no handler. */
|
|
161
|
+
type VizLiveOutcome = "transition" | "stay" | "ignored";
|
|
162
|
+
interface VizLiveStep {
|
|
163
|
+
from: string;
|
|
164
|
+
event: string;
|
|
165
|
+
to: string;
|
|
166
|
+
outcome: VizLiveOutcome;
|
|
167
|
+
/** Names of the effects the step ran. */
|
|
168
|
+
effects: string[];
|
|
169
|
+
context: VizJson;
|
|
170
|
+
/** Message of a handler that threw; null when none did. */
|
|
171
|
+
error: string | null;
|
|
172
|
+
}
|
|
173
|
+
interface VizLiveTimer {
|
|
174
|
+
id: string;
|
|
175
|
+
ms: number;
|
|
176
|
+
deadline: number;
|
|
177
|
+
eventName: string | null;
|
|
178
|
+
}
|
|
179
|
+
/** Where a running actor is, after the steps in `steps`. */
|
|
180
|
+
interface VizLiveState {
|
|
181
|
+
state: string;
|
|
182
|
+
steps: VizStep[];
|
|
183
|
+
last: VizLiveStep | null;
|
|
184
|
+
timers: VizLiveTimer[];
|
|
185
|
+
context: VizJson;
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* A running actor behind the viewer. JSON in, JSON out, so viz needs no core:
|
|
189
|
+
* `@mantaq/inspect` implements it on the step function the fuzzer uses.
|
|
190
|
+
*/
|
|
191
|
+
interface VizDriver {
|
|
192
|
+
reset(): VizLiveState;
|
|
193
|
+
step(step: VizStep): VizLiveState;
|
|
194
|
+
}
|
|
154
195
|
interface VizFuzzSequence {
|
|
155
196
|
/** Shrunk replayable sequence, starting from a fresh actor. */
|
|
156
197
|
steps: VizStep[];
|
|
@@ -186,4 +227,4 @@ interface VizIssue {
|
|
|
186
227
|
declare function validateViz(value: unknown): Either<VizIssue[], VizDocument>;
|
|
187
228
|
/** Parse JSON text and validate it. JSON syntax errors become an issue at `$`. */
|
|
188
229
|
declare function parseViz(text: string): Either<VizIssue[], VizDocument>;
|
|
189
|
-
export {
|
|
230
|
+
export { coveredByAny as A, VizLiveState as C, VizMachine as D, VizLocation as E, scopeOf as M, VizState as O, VizLiveOutcome as S, VizLiveTimer as T, VizExplanation as _, VIZ_SCHEMA_VERSION as a, VizFuzzSequence as b, VizComparison as c, VizDriver as d, VizEdge as f, VizEvent as g, VizEffect as h, ANY_STATE as i, isAnyState as j, VizStep as k, VizCoverage as l, VizEdgeOrigin as m, validateViz as n, VizBranch as o, VizEdgeKind as p, VizIssue as r, VizBranchTarget as s, parseViz as t, VizDocument as u, VizFuzz as v, VizLiveStep as w, VizJson as x, VizFuzzError as y };
|
package/dist/viewer.d.mts
CHANGED
|
@@ -1,2 +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-
|
|
1
|
+
import { a as ViewerOptions, i as STRUCTURE_BANNER, n as showIssues, o as mountViewer, r as FUZZ_NOTE, t as mountVizText } from "./embed-CISoaFIK.mjs";
|
|
2
2
|
export { FUZZ_NOTE, STRUCTURE_BANNER, type ViewerOptions, mountViewer, mountVizText, showIssues };
|
package/dist/viewer.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { r as FUZZ_NOTE, i as STRUCTURE_BANNER, a as mountViewer, t as mountVizText, n as showIssues } from "./embed-
|
|
1
|
+
export { r as FUZZ_NOTE, i as STRUCTURE_BANNER, a as mountViewer, t as mountVizText, n as showIssues } from "./embed-QX7vRXoL.mjs";
|