libpetri 2.13.0 → 3.0.1

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 (48) hide show
  1. package/README.md +89 -130
  2. package/dist/chunk-6NH64RCU.js +1016 -0
  3. package/dist/chunk-6NH64RCU.js.map +1 -0
  4. package/dist/{chunk-7VJ5CYUU.js → chunk-JZIEWVAV.js} +905 -62
  5. package/dist/chunk-JZIEWVAV.js.map +1 -0
  6. package/dist/{chunk-JVI5HFRX.js → chunk-VCDOKWVU.js} +2 -2
  7. package/dist/{chunk-E3ZWB645.js → chunk-YVIPJ6KM.js} +1 -1
  8. package/dist/chunk-YVIPJ6KM.js.map +1 -0
  9. package/dist/debug/index.d.ts +2 -2
  10. package/dist/debug/index.js +2 -2
  11. package/dist/doclet/index.d.ts +12 -3
  12. package/dist/doclet/index.js +8 -4
  13. package/dist/doclet/index.js.map +1 -1
  14. package/dist/doclet/resources/petrinet-diagrams.css +21 -0
  15. package/dist/doclet/resources/petrinet-diagrams.js +3575 -3573
  16. package/dist/dot-exporter-3STXYK74.js +9 -0
  17. package/dist/{render-ZGZEZ5RK.js → elk-place-YVNQFGXI.js} +3 -258
  18. package/dist/elk-place-YVNQFGXI.js.map +1 -0
  19. package/dist/{event-store-DKTenPbC.d.ts → event-store-BFX_yJ8I.d.ts} +1 -1
  20. package/dist/export/index.d.ts +1 -1
  21. package/dist/export/index.js +2 -2
  22. package/dist/index.d.ts +91 -38
  23. package/dist/index.js +368 -294
  24. package/dist/index.js.map +1 -1
  25. package/dist/pan-zoom-Cp51IkDl.d.ts +33 -0
  26. package/dist/{petri-net-C3LSY-vm.d.ts → petri-net-WSScMyDL.d.ts} +43 -29
  27. package/dist/preprocess-FN3F75JR.js +193 -0
  28. package/dist/preprocess-FN3F75JR.js.map +1 -0
  29. package/dist/render-QOHGDWNE.js +78 -0
  30. package/dist/render-QOHGDWNE.js.map +1 -0
  31. package/dist/render-dom/index.d.ts +28 -29
  32. package/dist/render-dom/index.js +14 -21
  33. package/dist/render-dom/index.js.map +1 -1
  34. package/dist/verification/index.d.ts +273 -7
  35. package/dist/verification/index.js +7 -1
  36. package/dist/verification/index.js.map +1 -1
  37. package/dist/viewer/index.d.ts +24 -32
  38. package/dist/viewer/index.js +9 -1003
  39. package/dist/viewer/index.js.map +1 -1
  40. package/dist/viewer/viewer.css +21 -0
  41. package/dist/viewer/viewer.iife.js +3575 -3573
  42. package/package.json +2 -2
  43. package/dist/chunk-7VJ5CYUU.js.map +0 -1
  44. package/dist/chunk-E3ZWB645.js.map +0 -1
  45. package/dist/dot-exporter-SHBYMMJ3.js +0 -9
  46. package/dist/render-ZGZEZ5RK.js.map +0 -1
  47. /package/dist/{chunk-JVI5HFRX.js.map → chunk-VCDOKWVU.js.map} +0 -0
  48. /package/dist/{dot-exporter-SHBYMMJ3.js.map → dot-exporter-3STXYK74.js.map} +0 -0
@@ -0,0 +1,33 @@
1
+ import panzoom from 'panzoom';
2
+
3
+ /**
4
+ * Pan/zoom wrapper for the canonical libpetri viewer.
5
+ *
6
+ * Delegates to the `panzoom` library (no hand-rolled wheel handlers — we
7
+ * intentionally drop the IIFE/javadoc version's bespoke math in favour of
8
+ * a single battle-tested implementation). Defaults match the old debug-ui
9
+ * and dev-preview values so the canonical viewer feels identical to what
10
+ * users had before.
11
+ *
12
+ * @module viewer/pan-zoom
13
+ */
14
+
15
+ type PanzoomInstance = ReturnType<typeof panzoom>;
16
+ type PanzoomOptions = Parameters<typeof panzoom>[1];
17
+ /**
18
+ * Default panzoom configuration shared across all viewer surfaces.
19
+ *
20
+ * - `maxZoom: 1000` — effectively unlimited; lets users dive into ~150-node
21
+ * diagrams without hitting an artificial ceiling.
22
+ * - `minZoom: 0.02` — allows fitting very large nets in a small viewport.
23
+ * - `smoothScroll: false` — sharper interactive feel.
24
+ * - `zoomDoubleClickSpeed: 1` — single-click double-zoom toggle.
25
+ */
26
+ declare const DEFAULT_PANZOOM_OPTS: {
27
+ readonly smoothScroll: false;
28
+ readonly zoomDoubleClickSpeed: 1;
29
+ readonly maxZoom: 1000;
30
+ readonly minZoom: 0.02;
31
+ };
32
+
33
+ export { DEFAULT_PANZOOM_OPTS as D, type PanzoomInstance as P, type PanzoomOptions as a };
@@ -72,7 +72,6 @@ type Arc = ArcInput | ArcOutput | ArcInhibitor | ArcRead | ArcReset;
72
72
  interface ArcInput<T = any> {
73
73
  readonly type: 'input';
74
74
  readonly place: Place<T>;
75
- readonly guard?: (value: T) => boolean;
76
75
  }
77
76
  interface ArcOutput<T = any> {
78
77
  readonly type: 'output';
@@ -91,7 +90,7 @@ interface ArcReset<T = any> {
91
90
  readonly place: Place<T>;
92
91
  }
93
92
  /** Input arc: consumes token from place when transition fires. */
94
- declare function inputArc<T>(place: Place<T>, guard?: (value: T) => boolean): ArcInput<T>;
93
+ declare function inputArc<T>(place: Place<T>): ArcInput<T>;
95
94
  /** Output arc: produces token to place when transition fires. */
96
95
  declare function outputArc<T>(place: Place<T>): ArcOutput<T>;
97
96
  /** Inhibitor arc: blocks transition if place has tokens. */
@@ -102,15 +101,14 @@ declare function readArc<T>(place: Place<T>): ArcRead<T>;
102
101
  declare function resetArc<T>(place: Place<T>): ArcReset<T>;
103
102
  /** Returns the place this arc connects to. */
104
103
  declare function arcPlace(arc: Arc): Place<any>;
105
- /** Checks if an input arc has a guard predicate. */
106
- declare function hasGuard(arc: ArcInput): boolean;
107
- /** Checks if a token value matches an input arc's guard. */
108
- declare function matchesGuard<T>(arc: ArcInput<T>, value: T): boolean;
109
104
 
110
105
  /**
111
- * Input specification with cardinality and optional guard predicate.
112
- * CPN-compliant: cardinality determines how many tokens to consume,
113
- * guard filters which tokens are eligible.
106
+ * Input specification with cardinality. Purely structural (IO-006): cardinality
107
+ * determines how many tokens to consume; there is no per-token predicate.
108
+ *
109
+ * Conditional token selection is modeled with multiple conflicting transitions
110
+ * and XOR-on-input semantics rather than a predicate coupled to the enablement
111
+ * check.
114
112
  *
115
113
  * Inputs are always AND-joined (all must be satisfied to enable transition).
116
114
  * XOR on inputs is modeled via multiple transitions (conflict).
@@ -119,33 +117,29 @@ type In = InOne | InExactly | InAll | InAtLeast;
119
117
  interface InOne<T = any> {
120
118
  readonly type: 'one';
121
119
  readonly place: Place<T>;
122
- readonly guard?: (value: T) => boolean;
123
120
  }
124
121
  interface InExactly<T = any> {
125
122
  readonly type: 'exactly';
126
123
  readonly place: Place<T>;
127
124
  readonly count: number;
128
- readonly guard?: (value: T) => boolean;
129
125
  }
130
126
  interface InAll<T = any> {
131
127
  readonly type: 'all';
132
128
  readonly place: Place<T>;
133
- readonly guard?: (value: T) => boolean;
134
129
  }
135
130
  interface InAtLeast<T = any> {
136
131
  readonly type: 'at-least';
137
132
  readonly place: Place<T>;
138
133
  readonly minimum: number;
139
- readonly guard?: (value: T) => boolean;
140
134
  }
141
- /** Consume exactly 1 token (standard CPN semantics). Optional guard filters eligible tokens. */
142
- declare function one<T>(place: Place<T>, guard?: (value: T) => boolean): InOne<T>;
143
- /** Consume exactly N tokens (batching). Optional guard filters eligible tokens. */
144
- declare function exactly<T>(count: number, place: Place<T>, guard?: (value: T) => boolean): InExactly<T>;
145
- /** Consume all available tokens (must be 1+). Optional guard filters eligible tokens. */
146
- declare function all<T>(place: Place<T>, guard?: (value: T) => boolean): InAll<T>;
147
- /** Wait for N+ tokens, consume all when enabled. Optional guard filters eligible tokens. */
148
- declare function atLeast<T>(minimum: number, place: Place<T>, guard?: (value: T) => boolean): InAtLeast<T>;
135
+ /** Consume exactly 1 token (standard CPN semantics). */
136
+ declare function one<T>(place: Place<T>): InOne<T>;
137
+ /** Consume exactly N tokens (batching). */
138
+ declare function exactly<T>(count: number, place: Place<T>): InExactly<T>;
139
+ /** Consume all available tokens (must be 1+). */
140
+ declare function all<T>(place: Place<T>): InAll<T>;
141
+ /** Wait for N+ tokens, consume all when enabled. */
142
+ declare function atLeast<T>(minimum: number, place: Place<T>): InAtLeast<T>;
149
143
  /** Returns the minimum number of tokens required to enable. */
150
144
  declare function requiredCount(spec: In): number;
151
145
  /**
@@ -191,11 +185,11 @@ declare function nameId(value: string): NameId;
191
185
  * On firing, exactly those name-matched tokens are consumed (spec NU-020).
192
186
  *
193
187
  * This is the single *decidable* predicate — equality of opaque names — and is
194
- * deliberately NOT a general guard: it correlates the name dimension *across*
195
- * places (composition-structural, like cardinality) rather than evaluating an
196
- * arbitrary boolean per token. When both a unary input `guard` and a match are
197
- * present the guard filters first, then the name correlation runs over the
198
- * survivors (NU-021).
188
+ * deliberately NOT a general input predicate: it correlates the name dimension
189
+ * *across* places (composition-structural, like cardinality) rather than
190
+ * evaluating an arbitrary boolean per token. Input specifications themselves
191
+ * remain purely structural (IO-006); name correlation is the only per-token
192
+ * filter the enablement check ever applies (NU-021).
199
193
  */
200
194
 
201
195
  /**
@@ -228,7 +222,7 @@ declare function matchKey<T>(place: Place<T>, key: KeyFn<T>): MatchKey<T>;
228
222
  * ))
229
223
  *
230
224
  * @throws if fewer than two inputs are correlated (a match over a single place
231
- * is just a guard).
225
+ * correlates nothing).
232
226
  */
233
227
  declare function matchSpec(...keys: MatchKey[]): MatchSpec;
234
228
  /** Returns the name projection for `placeName`, or `undefined` if not correlated. */
@@ -988,6 +982,25 @@ interface SmtVerificationResult {
988
982
  readonly discoveredInvariants: readonly string[];
989
983
  readonly counterexampleTrace: readonly MarkingState[];
990
984
  readonly counterexampleTransitions: readonly string[];
985
+ /**
986
+ * Outcome of the abstract counterexample replay, as a TRI-STATE. `null` means
987
+ * "the replay did not apply"; the two booleans both mean it ran.
988
+ *
989
+ * - `true` — an abstract firing chain from M₀ to a property-violating state
990
+ * was re-executed TS-side; `counterexampleTrace` is that chain in FIRING
991
+ * (replay) order and the verdict is `violated`.
992
+ * - `false` — the replay ran without confirming the trace. Either it could not
993
+ * settle the question (nothing decoded from the Z3 derivation, M₀ absent
994
+ * from the decoded set, or a node/segment budget hit), in which case the
995
+ * `violated` verdict rests on Spacer's SAT answer alone; or the search
996
+ * completed and found NO chain, in which case the verdict was downgraded to
997
+ * `unknown`. The report distinguishes the two ("UNCONFIRMED" vs "FAILED").
998
+ * - `null` — replay did not apply: non-violated verdict, replay disabled via
999
+ * `counterexampleReplay(false)`, the coloured ν-encoding / Route B (whose
1000
+ * state shapes are outside the flat replayer's scope), or a structural
1001
+ * proof.
1002
+ */
1003
+ readonly counterexampleConfirmed: boolean | null;
991
1004
  readonly elapsedMs: number;
992
1005
  readonly statistics: SmtStatistics;
993
1006
  }
@@ -1746,7 +1759,8 @@ declare class PetriNetBuilder {
1746
1759
  * Map<string, Place<unknown>> convention — TypeScript Place identity is
1747
1760
  * name-based per `runtime/compiled-net.ts`).
1748
1761
  * 3. Walk every transition through {@link applyFusion} to rewrite arc place
1749
- * references.
1762
+ * references; input arcs colliding on a canonical place merge per
1763
+ * [MOD-021] (additive where summable, rejected otherwise).
1750
1764
  * 4. Re-derive the place set from the rewritten transitions plus any
1751
1765
  * caller-declared standalone places, dropping non-canonical members.
1752
1766
  * Caller-declared standalone places that happen to be non-canonical
@@ -1782,4 +1796,4 @@ declare class PetriNetBuilder {
1782
1796
  private buildWithFusion;
1783
1797
  }
1784
1798
 
1785
- export { type VerificationHarness as $, type Arc as A, type Port as B, type Channel as C, type PortDirection as D, type EnvironmentPlace as E, FusionSet as F, SubnetDefBuilder as G, type SubnetInstance as H, type In as I, type Timing as J, type KeyFn as K, type LogFn as L, MAX_DURATION_MS as M, type NameId as N, type Out as O, PetriNet as P, type TimingDeadline as Q, type TimingDelayed as R, SubnetDef as S, type Token as T, type TimingExact as U, type TimingImmediate as V, type TimingWindow as W, TokenInput as X, TokenOutput as Y, type TransitionAction as Z, TransitionBuilder as _, type Place as a, type SmtStatistics as a$, type VerificationResult as a0, all as a1, allPlaces as a2, and as a3, andPlaces as a4, arcPlace as a5, atLeast as a6, consumptionCount as a7, deadline as a8, delayed as a9, produce as aA, readArc as aB, requiredCount as aC, resetArc as aD, timeout as aE, timeoutPlace as aF, tokenAt as aG, tokenOf as aH, transform as aI, transformAsync as aJ, transformFrom as aK, unitToken as aL, window as aM, withTimeout as aN, xor as aO, xorPlaces as aP, MarkingState as aQ, type PInvariant as aR, MarkingStateBuilder as aS, type SmtProperty as aT, type SmtVerificationResult as aU, type BranchPlaceBound as aV, type DeadlockFree as aW, type JoinedOrDeadLettered as aX, type MutualExclusion as aY, type PlaceBound as aZ, type Proven as a_, earliest as aa, enumerateBranches as ab, environmentPlace as ac, exact as ad, exactly as ae, fork as af, forwardInput as ag, hasDeadline as ah, hasGuard as ai, immediate as aj, inhibitorArc as ak, inputArc as al, isPassthrough as am, isUnit as an, keyForPlace as ao, latest as ap, matchCorrelates as aq, matchKey as ar, matchSpec as as, matchesGuard as at, nameId as au, one as av, outPlace as aw, outputArc as ax, passthrough as ay, place as az, Transition as b, type TokenSupplier as b0, type Unknown as b1, type Unreachable as b2, type Verdict as b3, type Violated as b4, branchPlaceBound as b5, deadlockFree as b6, isProven as b7, isViolated as b8, joinedOrDeadLettered as b9, mutualExclusion as ba, pInvariant as bb, pInvariantToString as bc, placeBound as bd, propertyDescription as be, unreachable as bf, TransitionContext as c, type ArcInhibitor as d, type ArcInput as e, type ArcOutput as f, type ArcRead as g, type ArcReset as h, ComposeBindings as i, FusionSetBuilder as j, type InAll as k, type InAtLeast as l, type InExactly as m, type InOne as n, Instance as o, Interface as p, InterfaceBuilder as q, type MatchKey as r, type MatchSpec as s, type OutAnd as t, type OutForwardInput as u, type OutPlace as v, type OutTimeout as w, type OutXor as x, type OutputEntry as y, PetriNetBuilder as z };
1799
+ export { type VerificationHarness as $, type Arc as A, type Port as B, type Channel as C, type PortDirection as D, type EnvironmentPlace as E, FusionSet as F, SubnetDefBuilder as G, type SubnetInstance as H, type In as I, type Timing as J, type KeyFn as K, type LogFn as L, MAX_DURATION_MS as M, type NameId as N, type Out as O, PetriNet as P, type TimingDeadline as Q, type TimingDelayed as R, SubnetDef as S, type Token as T, type TimingExact as U, type TimingImmediate as V, type TimingWindow as W, TokenInput as X, TokenOutput as Y, type TransitionAction as Z, TransitionBuilder as _, type Place as a, type Unknown as a$, type VerificationResult as a0, all as a1, allPlaces as a2, and as a3, andPlaces as a4, arcPlace as a5, atLeast as a6, consumptionCount as a7, deadline as a8, delayed as a9, requiredCount as aA, resetArc as aB, timeout as aC, timeoutPlace as aD, tokenAt as aE, tokenOf as aF, transform as aG, transformAsync as aH, transformFrom as aI, unitToken as aJ, window as aK, withTimeout as aL, xor as aM, xorPlaces as aN, MarkingState as aO, type PInvariant as aP, type SmtProperty as aQ, MarkingStateBuilder as aR, type SmtVerificationResult as aS, type BranchPlaceBound as aT, type DeadlockFree as aU, type JoinedOrDeadLettered as aV, type MutualExclusion as aW, type PlaceBound as aX, type Proven as aY, type SmtStatistics as aZ, type TokenSupplier as a_, earliest as aa, enumerateBranches as ab, environmentPlace as ac, exact as ad, exactly as ae, fork as af, forwardInput as ag, hasDeadline as ah, immediate as ai, inhibitorArc as aj, inputArc as ak, isPassthrough as al, isUnit as am, keyForPlace as an, latest as ao, matchCorrelates as ap, matchKey as aq, matchSpec as ar, nameId as as, one as at, outPlace as au, outputArc as av, passthrough as aw, place as ax, produce as ay, readArc as az, Transition as b, type Unreachable as b0, type Verdict as b1, type Violated as b2, branchPlaceBound as b3, deadlockFree as b4, isProven as b5, isViolated as b6, joinedOrDeadLettered as b7, mutualExclusion as b8, pInvariant as b9, pInvariantToString as ba, placeBound as bb, propertyDescription as bc, unreachable as bd, TransitionContext as c, type ArcInhibitor as d, type ArcInput as e, type ArcOutput as f, type ArcRead as g, type ArcReset as h, ComposeBindings as i, FusionSetBuilder as j, type InAll as k, type InAtLeast as l, type InExactly as m, type InOne as n, Instance as o, Interface as p, InterfaceBuilder as q, type MatchKey as r, type MatchSpec as s, type OutAnd as t, type OutForwardInput as u, type OutPlace as v, type OutTimeout as w, type OutXor as x, type OutputEntry as y, PetriNetBuilder as z };
@@ -0,0 +1,193 @@
1
+ // src/viewer/layout/preprocess.ts
2
+ function classifyKind(id) {
3
+ if (id.startsWith("p_")) return "place";
4
+ if (id.startsWith("t_")) return "transition";
5
+ return "junction";
6
+ }
7
+ function parseAttrs(body) {
8
+ const attrs = {};
9
+ for (const m of body.matchAll(/(\w+)=("(?:[^"]|\\.)*"|\S+?)(?=[\s,]|$)/g)) {
10
+ const [, key, raw] = m;
11
+ const v = raw.startsWith('"') && raw.endsWith('"') ? raw.slice(1, -1) : raw;
12
+ attrs[key] = v;
13
+ }
14
+ return attrs;
15
+ }
16
+ function parseLibpetriDot(dot) {
17
+ const nodes = /* @__PURE__ */ new Map();
18
+ for (const m of dot.matchAll(/^\s*([pjt]_[A-Za-z0-9_]+)\s*\[(.*)\];\s*$/gm)) {
19
+ const [, id, body] = m;
20
+ nodes.set(id, { id, kind: classifyKind(id), attrs: parseAttrs(body) });
21
+ }
22
+ const clusters = /* @__PURE__ */ new Map();
23
+ const nodeToCluster = /* @__PURE__ */ new Map();
24
+ for (const cm of dot.matchAll(/subgraph (cluster_\w+)\s*\{([^]*?)^\s*\}/gm)) {
25
+ const [, clusterId, body] = cm;
26
+ const memberIds = [...body.matchAll(/^\s*([pjt]_[A-Za-z0-9_]+)\s*\[/gm)].map((m) => m[1]);
27
+ clusters.set(clusterId, {
28
+ id: clusterId,
29
+ shortName: clusterId.replace(/^cluster_/, ""),
30
+ nodes: memberIds
31
+ });
32
+ for (const n of memberIds) nodeToCluster.set(n, clusterId);
33
+ }
34
+ const stripped = dot.replace(/subgraph cluster_\w+\s*\{[^]*?^\s*\}/gm, "");
35
+ for (const m of stripped.matchAll(/^\s*([pjt]_[A-Za-z0-9_]+)\s*\[/gm)) {
36
+ const id = m[1];
37
+ if (!nodes.has(id)) {
38
+ nodes.set(id, { id, kind: classifyKind(id), attrs: {} });
39
+ }
40
+ }
41
+ const orphans = [...nodes.keys()].filter((n) => !nodeToCluster.has(n));
42
+ const edges = [];
43
+ for (const m of dot.matchAll(/([pjt]_[A-Za-z0-9_]+)\s*->\s*([pjt]_[A-Za-z0-9_]+)\s*(\[[^\]]*\])?/g)) {
44
+ const [, src, dst, rawAttrs] = m;
45
+ const attrs = rawAttrs ?? "";
46
+ if (attrs.includes("lhead=") || attrs.includes("ltail=")) continue;
47
+ let arc = "normal";
48
+ if (attrs.includes('label="reset"') || attrs.includes("#fd7e14")) arc = "reset";
49
+ else if (attrs.includes('arrowhead="odot"') || attrs.includes("#dc3545")) arc = "inhibitor";
50
+ else if (attrs.includes('label="read"')) arc = "read";
51
+ const stripped2 = attrs.startsWith("[") && attrs.endsWith("]") ? attrs.slice(1, -1) : "";
52
+ edges.push({ src, dst, arc, rawAttrs: stripped2 });
53
+ }
54
+ return { nodes, clusters, nodeToCluster, orphans, edges };
55
+ }
56
+ function foldOrphans(graph, threshold = 0.7) {
57
+ if (threshold <= 0) return graph;
58
+ const nodeToCluster = new Map(graph.nodeToCluster);
59
+ const clusterMembers = /* @__PURE__ */ new Map();
60
+ for (const [id, c] of graph.clusters) clusterMembers.set(id, [...c.nodes]);
61
+ const tally = /* @__PURE__ */ new Map();
62
+ const bump = (orphan, other) => {
63
+ const oc = nodeToCluster.get(other);
64
+ if (!oc) return;
65
+ let t = tally.get(orphan);
66
+ if (!t) {
67
+ t = /* @__PURE__ */ new Map();
68
+ tally.set(orphan, t);
69
+ }
70
+ t.set(oc, (t.get(oc) ?? 0) + 1);
71
+ };
72
+ for (const e of graph.edges) {
73
+ const sIsOrphan = !nodeToCluster.has(e.src);
74
+ const dIsOrphan = !nodeToCluster.has(e.dst);
75
+ if (sIsOrphan && !dIsOrphan) bump(e.src, e.dst);
76
+ if (dIsOrphan && !sIsOrphan) bump(e.dst, e.src);
77
+ }
78
+ for (const id of graph.orphans) {
79
+ const t = tally.get(id);
80
+ if (!t) continue;
81
+ let total = 0;
82
+ let bestCluster = null;
83
+ let bestCount = 0;
84
+ for (const [c, n] of t) {
85
+ total += n;
86
+ if (n > bestCount) {
87
+ bestCount = n;
88
+ bestCluster = c;
89
+ }
90
+ }
91
+ if (bestCluster && total > 0 && bestCount / total >= threshold) {
92
+ clusterMembers.get(bestCluster).push(id);
93
+ nodeToCluster.set(id, bestCluster);
94
+ }
95
+ }
96
+ const clusters = /* @__PURE__ */ new Map();
97
+ for (const [id, c] of graph.clusters) {
98
+ clusters.set(id, { ...c, nodes: clusterMembers.get(id) });
99
+ }
100
+ const orphans = [...graph.nodes.keys()].filter((n) => !nodeToCluster.has(n));
101
+ return { ...graph, clusters, nodeToCluster, orphans };
102
+ }
103
+ function replicateShared(graph, opts = {}) {
104
+ const max = opts.max ?? Infinity;
105
+ const replicateTransitions = opts.replicateTransitions ?? false;
106
+ const nodes = new Map(graph.nodes);
107
+ const nodeToCluster = new Map(graph.nodeToCluster);
108
+ const clusterMembers = /* @__PURE__ */ new Map();
109
+ for (const [id, c] of graph.clusters) clusterMembers.set(id, [...c.nodes]);
110
+ const edges = graph.edges.map((e) => ({ ...e }));
111
+ const foreignByNode = /* @__PURE__ */ new Map();
112
+ const noteForeign = (nodeId, otherId) => {
113
+ if (!nodeId.startsWith("p_") && !(replicateTransitions && nodeId.startsWith("t_"))) return;
114
+ const home = nodeToCluster.get(nodeId);
115
+ const otherCluster = nodeToCluster.get(otherId);
116
+ if (!otherCluster) return;
117
+ if (otherCluster === home) return;
118
+ let s = foreignByNode.get(nodeId);
119
+ if (!s) {
120
+ s = /* @__PURE__ */ new Set();
121
+ foreignByNode.set(nodeId, s);
122
+ }
123
+ s.add(otherCluster);
124
+ };
125
+ for (const e of edges) {
126
+ noteForeign(e.src, e.dst);
127
+ noteForeign(e.dst, e.src);
128
+ }
129
+ const replicaMap = /* @__PURE__ */ new Map();
130
+ let replicatedPlaces = 0;
131
+ let totalCopies = 0;
132
+ for (const [placeId, foreigns] of foreignByNode) {
133
+ if (foreigns.size === 0) continue;
134
+ if (foreigns.size > max) continue;
135
+ const perCluster = /* @__PURE__ */ new Map();
136
+ const orig = nodes.get(placeId);
137
+ if (!orig) continue;
138
+ for (const clusterId of foreigns) {
139
+ const shortName = clusterId.replace(/^cluster_/, "");
140
+ const repId = `${placeId}__rep__${shortName}`;
141
+ perCluster.set(clusterId, repId);
142
+ clusterMembers.get(clusterId).push(repId);
143
+ nodeToCluster.set(repId, clusterId);
144
+ nodes.set(repId, {
145
+ id: repId,
146
+ kind: orig.kind,
147
+ attrs: { ...orig.attrs },
148
+ replica: true,
149
+ replicaOf: placeId
150
+ });
151
+ totalCopies++;
152
+ }
153
+ replicaMap.set(placeId, perCluster);
154
+ replicatedPlaces++;
155
+ nodes.set(placeId, { ...orig, replica: true, replicaOf: placeId });
156
+ }
157
+ for (const e of edges) {
158
+ const sMap = replicaMap.get(e.src);
159
+ const dMap = replicaMap.get(e.dst);
160
+ if (sMap) {
161
+ const dCl = nodeToCluster.get(e.dst);
162
+ if (dCl && sMap.has(dCl)) e.src = sMap.get(dCl);
163
+ }
164
+ if (dMap) {
165
+ const sCl = nodeToCluster.get(e.src);
166
+ if (sCl && dMap.has(sCl)) e.dst = dMap.get(sCl);
167
+ }
168
+ }
169
+ for (const [origId] of replicaMap) {
170
+ if (nodeToCluster.has(origId)) continue;
171
+ const stillHasEdge = edges.some((e) => e.src === origId || e.dst === origId);
172
+ if (!stillHasEdge) nodes.delete(origId);
173
+ }
174
+ const clusters = /* @__PURE__ */ new Map();
175
+ for (const [id, c] of graph.clusters) {
176
+ clusters.set(id, { ...c, nodes: clusterMembers.get(id) });
177
+ }
178
+ const orphans = [...nodes.keys()].filter((n) => !nodeToCluster.has(n));
179
+ return {
180
+ nodes,
181
+ clusters,
182
+ nodeToCluster,
183
+ orphans,
184
+ edges,
185
+ replicateStats: { replicatedPlaces, totalCopies }
186
+ };
187
+ }
188
+ export {
189
+ foldOrphans,
190
+ parseLibpetriDot,
191
+ replicateShared
192
+ };
193
+ //# sourceMappingURL=preprocess-FN3F75JR.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/viewer/layout/preprocess.ts"],"sourcesContent":["/**\n * Graph pre-processing for the C0-replicate-default layout pipeline.\n *\n * Three pure-ish transforms operate on a {@link GraphModel} parsed from a\n * libpetri-exported DOT string:\n *\n * parseLibpetriDot(dot) → GraphModel\n * foldOrphans(graph, threshold) → GraphModel (REASSIGN_ORPHANS)\n * replicateShared(graph, opts) → GraphModel (REPLICATE_SHARED)\n *\n * The pipeline turns a flat cluster soup into a cluster-internal one by\n * (a) folding orphan nodes whose edges concentrate in one cluster into that\n * cluster, and (b) cloning shared places into every foreign cluster they\n * touch so cross-cluster spaghetti disappears. The resulting GraphModel is\n * what ELK lays out (Stage 2) and Graphviz then renders in pin-mode.\n *\n * Algorithm lineage: ported from\n * `/home/db/otto-repos/nucleus_marvin/.scratch/javadoc-layout-exp/CHECKPOINTS/run16_GOOD_v3_skip-junctions.mjs`\n * lines 14–220. The arc-type detection, replica naming\n * (`${placeId}__rep__${clusterShortName}`), and the `__replica` /\n * `replicaOf` markers are load-bearing — downstream stages key off them.\n *\n * @module viewer/layout/preprocess\n */\n\nexport type ArcType = 'normal' | 'reset' | 'inhibitor' | 'read';\nexport type NodeKind = 'place' | 'transition' | 'junction';\n\nexport interface ParsedNode {\n readonly id: string;\n readonly kind: NodeKind;\n readonly attrs: Record<string, string>;\n /** True iff this node was emitted by replicateShared. */\n readonly replica?: boolean;\n /** For a replica, the id of the original logical place. */\n readonly replicaOf?: string;\n}\n\nexport interface ParsedEdge {\n readonly src: string;\n readonly dst: string;\n readonly arc: ArcType;\n /**\n * Raw attribute body from the libpetri DOT (between the `[…]` brackets,\n * brackets stripped). Carries style/penwidth/label that we want to\n * round-trip verbatim so the rendered output matches `DotExporter`'s\n * native look.\n */\n readonly rawAttrs: string;\n}\n\nexport interface ParsedCluster {\n readonly id: string; // \"cluster_productSearch\"\n readonly shortName: string; // \"productSearch\"\n readonly nodes: readonly string[];\n}\n\nexport interface GraphModel {\n readonly nodes: ReadonlyMap<string, ParsedNode>;\n readonly clusters: ReadonlyMap<string, ParsedCluster>;\n readonly nodeToCluster: ReadonlyMap<string, string>;\n /** Node ids not in any cluster (rendered at the orchestrator root). */\n readonly orphans: readonly string[];\n readonly edges: readonly ParsedEdge[];\n}\n\nfunction classifyKind(id: string): NodeKind {\n if (id.startsWith('p_')) return 'place';\n if (id.startsWith('t_')) return 'transition';\n return 'junction';\n}\n\n/**\n * Recover key=value attributes from inside a DOT `[...]` block.\n *\n * Mirrors v3 line 29. The regex is intentionally permissive about quoted\n * values containing escaped quotes; it does not validate DOT syntax.\n */\nfunction parseAttrs(body: string): Record<string, string> {\n const attrs: Record<string, string> = {};\n for (const m of body.matchAll(/(\\w+)=(\"(?:[^\"]|\\\\.)*\"|\\S+?)(?=[\\s,]|$)/g)) {\n const [, key, raw] = m as unknown as [string, string, string];\n const v = raw.startsWith('\"') && raw.endsWith('\"') ? raw.slice(1, -1) : raw;\n attrs[key] = v;\n }\n return attrs;\n}\n\n/**\n * Parse a libpetri-exported DOT string into a {@link GraphModel}.\n *\n * Tolerates the current exporter shape (see\n * `java/src/main/java/org/libpetri/export/StyleConstants.java`):\n * - inhibitor arcs use `arrowhead=\"odot\"` + color `#dc3545`\n * - reset arcs use `label=\"reset\"` + color `#fd7e14`\n * - read arcs use `label=\"read\"` + color `#6c757d`\n *\n * Mirrors v3 lines 24–103.\n */\nexport function parseLibpetriDot(dot: string): GraphModel {\n const nodes = new Map<string, ParsedNode>();\n // Greedy `.*` (no /s flag) consumes the full attrs body up to the line's\n // trailing `];`. The v3 reference used `[^\\]]*` which silently dropped\n // transitions whose label contained `[…]` text (e.g., `\"[0, ∞]ms\"`); we\n // can't tolerate that here because the typed model wants every cluster\n // member to have a Node entry.\n for (const m of dot.matchAll(/^\\s*([pjt]_[A-Za-z0-9_]+)\\s*\\[(.*)\\];\\s*$/gm)) {\n const [, id, body] = m as unknown as [string, string, string];\n nodes.set(id, { id, kind: classifyKind(id), attrs: parseAttrs(body) });\n }\n\n const clusters = new Map<string, ParsedCluster>();\n const nodeToCluster = new Map<string, string>();\n for (const cm of dot.matchAll(/subgraph (cluster_\\w+)\\s*\\{([^]*?)^\\s*\\}/gm)) {\n const [, clusterId, body] = cm as unknown as [string, string, string];\n const memberIds = [...body.matchAll(/^\\s*([pjt]_[A-Za-z0-9_]+)\\s*\\[/gm)]\n .map(m => (m as unknown as [string, string])[1]);\n clusters.set(clusterId, {\n id: clusterId,\n shortName: clusterId.replace(/^cluster_/, ''),\n nodes: memberIds,\n });\n for (const n of memberIds) nodeToCluster.set(n, clusterId);\n }\n\n // Catch nodes declared at root (orphans) — strip cluster bodies, then scan\n const stripped = dot.replace(/subgraph cluster_\\w+\\s*\\{[^]*?^\\s*\\}/gm, '');\n for (const m of stripped.matchAll(/^\\s*([pjt]_[A-Za-z0-9_]+)\\s*\\[/gm)) {\n const id = (m as unknown as [string, string])[1];\n if (!nodes.has(id)) {\n // Root-declared node without full attrs (rare); seed with empty attrs.\n nodes.set(id, { id, kind: classifyKind(id), attrs: {} });\n }\n }\n\n const orphans = [...nodes.keys()].filter(n => !nodeToCluster.has(n));\n\n const edges: ParsedEdge[] = [];\n for (const m of dot.matchAll(/([pjt]_[A-Za-z0-9_]+)\\s*->\\s*([pjt]_[A-Za-z0-9_]+)\\s*(\\[[^\\]]*\\])?/g)) {\n const [, src, dst, rawAttrs] = m as unknown as [string, string, string, string | undefined];\n const attrs = rawAttrs ?? '';\n // Cluster-anchor edges (lhead/ltail) are exporter-internal routing hints,\n // not real arcs in the Petri net — skip per v3 line 97.\n if (attrs.includes('lhead=') || attrs.includes('ltail=')) continue;\n let arc: ArcType = 'normal';\n if (attrs.includes('label=\"reset\"') || attrs.includes('#fd7e14')) arc = 'reset';\n else if (attrs.includes('arrowhead=\"odot\"') || attrs.includes('#dc3545')) arc = 'inhibitor';\n else if (attrs.includes('label=\"read\"')) arc = 'read';\n // Strip the `[…]` brackets so writeBack can splice the body into a new\n // attr list. Empty if libpetri emitted a bare `src -> dst;`.\n const stripped = attrs.startsWith('[') && attrs.endsWith(']')\n ? attrs.slice(1, -1)\n : '';\n edges.push({ src, dst, arc, rawAttrs: stripped });\n }\n\n return { nodes, clusters, nodeToCluster, orphans, edges };\n}\n\n/**\n * Adopt orphan nodes into the cluster their edges most prefer.\n *\n * For each orphan, tally how many of its edges touch each cluster. If the\n * dominant cluster gets at least `threshold` of the orphan's cluster-touching\n * edges, the orphan is reassigned to that cluster.\n *\n * Mirrors v3 lines 105–136. Default threshold 0.7 matches REASSIGN_ORPHANS.\n */\nexport function foldOrphans(graph: GraphModel, threshold = 0.7): GraphModel {\n if (threshold <= 0) return graph;\n\n const nodeToCluster = new Map(graph.nodeToCluster);\n const clusterMembers = new Map<string, string[]>();\n for (const [id, c] of graph.clusters) clusterMembers.set(id, [...c.nodes]);\n\n const tally = new Map<string, Map<string, number>>();\n const bump = (orphan: string, other: string): void => {\n const oc = nodeToCluster.get(other);\n if (!oc) return;\n let t = tally.get(orphan);\n if (!t) { t = new Map(); tally.set(orphan, t); }\n t.set(oc, (t.get(oc) ?? 0) + 1);\n };\n\n for (const e of graph.edges) {\n const sIsOrphan = !nodeToCluster.has(e.src);\n const dIsOrphan = !nodeToCluster.has(e.dst);\n if (sIsOrphan && !dIsOrphan) bump(e.src, e.dst);\n if (dIsOrphan && !sIsOrphan) bump(e.dst, e.src);\n }\n\n for (const id of graph.orphans) {\n const t = tally.get(id);\n if (!t) continue;\n let total = 0;\n let bestCluster: string | null = null;\n let bestCount = 0;\n for (const [c, n] of t) {\n total += n;\n if (n > bestCount) { bestCount = n; bestCluster = c; }\n }\n if (bestCluster && total > 0 && bestCount / total >= threshold) {\n clusterMembers.get(bestCluster)!.push(id);\n nodeToCluster.set(id, bestCluster);\n }\n }\n\n const clusters = new Map<string, ParsedCluster>();\n for (const [id, c] of graph.clusters) {\n clusters.set(id, { ...c, nodes: clusterMembers.get(id)! });\n }\n const orphans = [...graph.nodes.keys()].filter(n => !nodeToCluster.has(n));\n return { ...graph, clusters, nodeToCluster, orphans };\n}\n\nexport interface ReplicateOptions {\n /**\n * Skip places that touch more than this many foreign clusters. Per the\n * plan's \"no cap\" decision, default is `Infinity`. Set to a finite value\n * to suppress replication of hub places that would otherwise produce many\n * copies.\n */\n readonly max?: number;\n /** If true, replicate transitions as well as places. Default false. */\n readonly replicateTransitions?: boolean;\n}\n\nexport interface ReplicateStats {\n readonly replicatedPlaces: number;\n readonly totalCopies: number;\n}\n\n/**\n * For each place P, replicate it into every foreign cluster touched by its\n * edges, and redirect those cross-cluster edges to the local copy.\n *\n * Replica id convention is load-bearing: `${placeId}__rep__${shortClusterName}`.\n * Downstream stages (mount-time tagging, overlay highlight, click-all-copies)\n * key off this naming and the `replica: true` / `replicaOf` markers.\n *\n * If P has a home cluster (folded earlier), the original stays as the primary\n * copy and is also marked as a replica so the ⇄ glyph renders consistently.\n * If P is an orphan with all its edges redirected, the original is dropped.\n *\n * Mirrors v3 lines 138–221.\n */\nexport function replicateShared(\n graph: GraphModel,\n opts: ReplicateOptions = {},\n): GraphModel & { readonly replicateStats: ReplicateStats } {\n const max = opts.max ?? Infinity;\n const replicateTransitions = opts.replicateTransitions ?? false;\n\n const nodes = new Map(graph.nodes);\n const nodeToCluster = new Map(graph.nodeToCluster);\n const clusterMembers = new Map<string, string[]>();\n for (const [id, c] of graph.clusters) clusterMembers.set(id, [...c.nodes]);\n const edges = graph.edges.map(e => ({ ...e }));\n\n // 1. Compute foreign-cluster set per replicable node.\n const foreignByNode = new Map<string, Set<string>>();\n const noteForeign = (nodeId: string, otherId: string): void => {\n if (!nodeId.startsWith('p_') && !(replicateTransitions && nodeId.startsWith('t_'))) return;\n const home = nodeToCluster.get(nodeId);\n const otherCluster = nodeToCluster.get(otherId);\n if (!otherCluster) return;\n if (otherCluster === home) return;\n let s = foreignByNode.get(nodeId);\n if (!s) { s = new Set(); foreignByNode.set(nodeId, s); }\n s.add(otherCluster);\n };\n for (const e of edges) {\n noteForeign(e.src, e.dst);\n noteForeign(e.dst, e.src);\n }\n\n // 2. Emit replicas for nodes within the cap.\n const replicaMap = new Map<string, Map<string, string>>(); // orig → cluster → replicaId\n let replicatedPlaces = 0;\n let totalCopies = 0;\n for (const [placeId, foreigns] of foreignByNode) {\n if (foreigns.size === 0) continue;\n if (foreigns.size > max) continue;\n const perCluster = new Map<string, string>();\n const orig = nodes.get(placeId);\n if (!orig) continue;\n for (const clusterId of foreigns) {\n const shortName = clusterId.replace(/^cluster_/, '');\n const repId = `${placeId}__rep__${shortName}`;\n perCluster.set(clusterId, repId);\n clusterMembers.get(clusterId)!.push(repId);\n nodeToCluster.set(repId, clusterId);\n nodes.set(repId, {\n id: repId,\n kind: orig.kind,\n attrs: { ...orig.attrs },\n replica: true,\n replicaOf: placeId,\n });\n totalCopies++;\n }\n replicaMap.set(placeId, perCluster);\n replicatedPlaces++;\n // Mark the original as a shared place so the ⇄ glyph renders uniformly.\n nodes.set(placeId, { ...orig, replica: true, replicaOf: placeId });\n }\n\n // 3. Redirect cross-cluster edges to the local replica on each end.\n for (const e of edges) {\n const sMap = replicaMap.get(e.src);\n const dMap = replicaMap.get(e.dst);\n if (sMap) {\n const dCl = nodeToCluster.get(e.dst);\n if (dCl && sMap.has(dCl)) e.src = sMap.get(dCl)!;\n }\n if (dMap) {\n const sCl = nodeToCluster.get(e.src);\n if (sCl && dMap.has(sCl)) e.dst = dMap.get(sCl)!;\n }\n }\n\n // 4. Drop orphan originals whose edges all got redirected away.\n for (const [origId] of replicaMap) {\n if (nodeToCluster.has(origId)) continue; // folded — keep as primary\n const stillHasEdge = edges.some(e => e.src === origId || e.dst === origId);\n if (!stillHasEdge) nodes.delete(origId);\n }\n\n const clusters = new Map<string, ParsedCluster>();\n for (const [id, c] of graph.clusters) {\n clusters.set(id, { ...c, nodes: clusterMembers.get(id)! });\n }\n const orphans = [...nodes.keys()].filter(n => !nodeToCluster.has(n));\n\n return {\n nodes,\n clusters,\n nodeToCluster,\n orphans,\n edges,\n replicateStats: { replicatedPlaces, totalCopies },\n };\n}\n"],"mappings":"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","names":["stripped"]}
@@ -0,0 +1,78 @@
1
+ // src/viewer/render.ts
2
+ import { instance as vizInstance } from "@viz-js/viz";
3
+ var vizPromise = null;
4
+ function getViz() {
5
+ if (!vizPromise) vizPromise = vizInstance();
6
+ return vizPromise;
7
+ }
8
+ async function renderDotToSvg(dotSource) {
9
+ const viz = await getViz();
10
+ return viz.renderSVGElement(dotSource, { engine: "dot" });
11
+ }
12
+ function fnv1a(input) {
13
+ let h = 2166136261;
14
+ for (let i = 0; i < input.length; i++) {
15
+ h ^= input.charCodeAt(i);
16
+ h = Math.imul(h, 16777619);
17
+ }
18
+ return (h >>> 0).toString(16);
19
+ }
20
+ var PINNED_DOT_CACHE_CAP = 16;
21
+ var pinnedDotCache = /* @__PURE__ */ new Map();
22
+ function getCachedPinnedDot(key) {
23
+ const hit = pinnedDotCache.get(key);
24
+ if (hit !== void 0) {
25
+ pinnedDotCache.delete(key);
26
+ pinnedDotCache.set(key, hit);
27
+ }
28
+ return hit;
29
+ }
30
+ function setCachedPinnedDot(key, value) {
31
+ pinnedDotCache.set(key, value);
32
+ while (pinnedDotCache.size > PINNED_DOT_CACHE_CAP) {
33
+ const oldest = pinnedDotCache.keys().next().value;
34
+ if (oldest === void 0) break;
35
+ pinnedDotCache.delete(oldest);
36
+ }
37
+ }
38
+ function _clearElkLayoutCache() {
39
+ pinnedDotCache.clear();
40
+ }
41
+ async function renderDotToSvgWithElkLayout(dotSource, cfg) {
42
+ const key = fnv1a(dotSource + "\0" + JSON.stringify(cfg ?? {}));
43
+ let pinnedDot = getCachedPinnedDot(key);
44
+ if (pinnedDot === void 0) {
45
+ const { foldOrphans, parseLibpetriDot, replicateShared } = await import("./preprocess-FN3F75JR.js");
46
+ const { elkLayout, writeBack } = await import("./elk-place-YVNQFGXI.js").catch(
47
+ (cause) => {
48
+ throw new Error(
49
+ "the viewer's default 'elk' layout requires the optional peer dependency 'elkjs'. Install it, or pass { layout: 'graphviz' } to mount() for stock Graphviz layout with spline edges.",
50
+ { cause }
51
+ );
52
+ }
53
+ );
54
+ const graph = replicateShared(
55
+ foldOrphans(parseLibpetriDot(dotSource), 0.7),
56
+ { max: Infinity }
57
+ );
58
+ const layout = await elkLayout(graph, cfg);
59
+ pinnedDot = writeBack(graph, layout);
60
+ setCachedPinnedDot(key, pinnedDot);
61
+ }
62
+ const viz = await getViz();
63
+ return viz.renderSVGElement(pinnedDot, {
64
+ // nop2 draws the pinned node positions AND our ELK-computed orthogonal
65
+ // edge routes verbatim. See writeBack/edgePosSpline: we route edges
66
+ // ourselves rather than let Graphviz's ortho router run, which crashes
67
+ // the wasm on large nets.
68
+ engine: "nop2",
69
+ yInvert: true
70
+ });
71
+ }
72
+ export {
73
+ _clearElkLayoutCache,
74
+ getViz,
75
+ renderDotToSvg,
76
+ renderDotToSvgWithElkLayout
77
+ };
78
+ //# sourceMappingURL=render-QOHGDWNE.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/viewer/render.ts"],"sourcesContent":["/**\n * DOT → SVG rendering for the canonical libpetri viewer.\n *\n * Two render paths:\n *\n * renderDotToSvg(dot) — plain Graphviz `dot` engine.\n * renderDotToSvgWithElkLayout(dot) — C0 pipeline: parse → fold →\n * replicate → ELK → writeBack →\n * Graphviz `nop2` (pinned nodes AND\n * edge routes). Cached by DOT hash.\n *\n * The ELK path is the default for {@link mount}; the plain-Graphviz path\n * remains as a fallback for callers that want stock layout (or for\n * environments where elkjs isn't installed).\n *\n * The ELK stage is pulled in by dynamic import rather than at module load, so\n * the plain-Graphviz path genuinely works without `elkjs` present. Under a\n * static import the whole module failed to load and `layout: 'graphviz'` was\n * an escape hatch that could never be reached.\n *\n * @module viewer/render\n */\n\nimport { instance as vizInstance } from '@viz-js/viz';\nimport type { ElkLayoutConfig } from './layout/elk-place.js';\n\ntype Viz = Awaited<ReturnType<typeof vizInstance>>;\n\nlet vizPromise: Promise<Viz> | null = null;\n\n/** Memoize the viz.js instance so the wasm loads only once per page. */\nexport function getViz(): Promise<Viz> {\n if (!vizPromise) vizPromise = vizInstance();\n return vizPromise;\n}\n\n/**\n * Render a DOT source string to an SVGSVGElement using the plain `dot`\n * engine. Deterministic across runs; libpetri exporters emit byte-stable\n * DOT per spec EXP-014 so the same DOT yields the same SVG.\n */\nexport async function renderDotToSvg(dotSource: string): Promise<SVGSVGElement> {\n const viz = await getViz();\n return viz.renderSVGElement(dotSource, { engine: 'dot' });\n}\n\n// ---- C0 / ELK pin-mode cache ---------------------------------------------\n\n/** FNV-1a 32-bit hash. Fast enough that a SHA isn't worth the import. */\nfunction fnv1a(input: string): string {\n let h = 0x811c9dc5;\n for (let i = 0; i < input.length; i++) {\n h ^= input.charCodeAt(i);\n h = Math.imul(h, 0x01000193);\n }\n return (h >>> 0).toString(16);\n}\n\nconst PINNED_DOT_CACHE_CAP = 16;\nconst pinnedDotCache = new Map<string, string>();\n\nfunction getCachedPinnedDot(key: string): string | undefined {\n const hit = pinnedDotCache.get(key);\n if (hit !== undefined) {\n // LRU touch: re-insert moves to most-recent position in Map ordering.\n pinnedDotCache.delete(key);\n pinnedDotCache.set(key, hit);\n }\n return hit;\n}\n\nfunction setCachedPinnedDot(key: string, value: string): void {\n pinnedDotCache.set(key, value);\n while (pinnedDotCache.size > PINNED_DOT_CACHE_CAP) {\n const oldest = pinnedDotCache.keys().next().value;\n if (oldest === undefined) break;\n pinnedDotCache.delete(oldest);\n }\n}\n\n/** Test helper — clears the pinned-DOT LRU cache between mounts. */\nexport function _clearElkLayoutCache(): void {\n pinnedDotCache.clear();\n}\n\n/**\n * Run the C0 layout pipeline against a libpetri DOT source and return the\n * rendered SVG. The expensive steps (preprocess + ELK layout + DOT rewrite)\n * are cached keyed on the input DOT's hash plus the layout config, so\n * re-mounts on the same net structure and config skip everything except the\n * Graphviz pin-mode draw.\n *\n * Per-tick marking updates do NOT call this — they toggle classes on the\n * already-mounted SVG. ELK only runs when the net structure changes.\n *\n * `cfg` tunes the ELK stage — see {@link ElkLayoutConfig}.\n */\nexport async function renderDotToSvgWithElkLayout(\n dotSource: string,\n cfg?: ElkLayoutConfig,\n): Promise<SVGSVGElement> {\n const key = fnv1a(dotSource + '\\0' + JSON.stringify(cfg ?? {}));\n let pinnedDot = getCachedPinnedDot(key);\n if (pinnedDot === undefined) {\n const { foldOrphans, parseLibpetriDot, replicateShared } =\n await import('./layout/preprocess.js');\n // `elkjs` is an optional peer dependency, in line with the viewer's other\n // browser-only peers. Name it explicitly here: the raw resolution failure\n // mentions a deep path inside elkjs and reads as a libpetri bug.\n const { elkLayout, writeBack } = await import('./layout/elk-place.js').catch(\n (cause: unknown) => {\n throw new Error(\n \"the viewer's default 'elk' layout requires the optional peer \"\n + \"dependency 'elkjs'. Install it, or pass { layout: 'graphviz' } \"\n + 'to mount() for stock Graphviz layout with spline edges.',\n { cause },\n );\n },\n );\n const graph = replicateShared(\n foldOrphans(parseLibpetriDot(dotSource), 0.7),\n { max: Infinity },\n );\n const layout = await elkLayout(graph, cfg);\n pinnedDot = writeBack(graph, layout);\n setCachedPinnedDot(key, pinnedDot);\n }\n const viz = await getViz();\n return viz.renderSVGElement(pinnedDot, {\n // nop2 draws the pinned node positions AND our ELK-computed orthogonal\n // edge routes verbatim. See writeBack/edgePosSpline: we route edges\n // ourselves rather than let Graphviz's ortho router run, which crashes\n // the wasm on large nets.\n engine: 'nop2',\n yInvert: true,\n });\n}\n"],"mappings":";AAuBA,SAAS,YAAY,mBAAmB;AAKxC,IAAI,aAAkC;AAG/B,SAAS,SAAuB;AACrC,MAAI,CAAC,WAAY,cAAa,YAAY;AAC1C,SAAO;AACT;AAOA,eAAsB,eAAe,WAA2C;AAC9E,QAAM,MAAM,MAAM,OAAO;AACzB,SAAO,IAAI,iBAAiB,WAAW,EAAE,QAAQ,MAAM,CAAC;AAC1D;AAKA,SAAS,MAAM,OAAuB;AACpC,MAAI,IAAI;AACR,WAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;AACrC,SAAK,MAAM,WAAW,CAAC;AACvB,QAAI,KAAK,KAAK,GAAG,QAAU;AAAA,EAC7B;AACA,UAAQ,MAAM,GAAG,SAAS,EAAE;AAC9B;AAEA,IAAM,uBAAuB;AAC7B,IAAM,iBAAiB,oBAAI,IAAoB;AAE/C,SAAS,mBAAmB,KAAiC;AAC3D,QAAM,MAAM,eAAe,IAAI,GAAG;AAClC,MAAI,QAAQ,QAAW;AAErB,mBAAe,OAAO,GAAG;AACzB,mBAAe,IAAI,KAAK,GAAG;AAAA,EAC7B;AACA,SAAO;AACT;AAEA,SAAS,mBAAmB,KAAa,OAAqB;AAC5D,iBAAe,IAAI,KAAK,KAAK;AAC7B,SAAO,eAAe,OAAO,sBAAsB;AACjD,UAAM,SAAS,eAAe,KAAK,EAAE,KAAK,EAAE;AAC5C,QAAI,WAAW,OAAW;AAC1B,mBAAe,OAAO,MAAM;AAAA,EAC9B;AACF;AAGO,SAAS,uBAA6B;AAC3C,iBAAe,MAAM;AACvB;AAcA,eAAsB,4BACpB,WACA,KACwB;AACxB,QAAM,MAAM,MAAM,YAAY,OAAO,KAAK,UAAU,OAAO,CAAC,CAAC,CAAC;AAC9D,MAAI,YAAY,mBAAmB,GAAG;AACtC,MAAI,cAAc,QAAW;AAC3B,UAAM,EAAE,aAAa,kBAAkB,gBAAgB,IACrD,MAAM,OAAO,0BAAwB;AAIvC,UAAM,EAAE,WAAW,UAAU,IAAI,MAAM,OAAO,yBAAuB,EAAE;AAAA,MACrE,CAAC,UAAmB;AAClB,cAAM,IAAI;AAAA,UACR;AAAA,UAGA,EAAE,MAAM;AAAA,QACV;AAAA,MACF;AAAA,IACF;AACA,UAAM,QAAQ;AAAA,MACZ,YAAY,iBAAiB,SAAS,GAAG,GAAG;AAAA,MAC5C,EAAE,KAAK,SAAS;AAAA,IAClB;AACA,UAAM,SAAS,MAAM,UAAU,OAAO,GAAG;AACzC,gBAAY,UAAU,OAAO,MAAM;AACnC,uBAAmB,KAAK,SAAS;AAAA,EACnC;AACA,QAAM,MAAM,MAAM,OAAO;AACzB,SAAO,IAAI,iBAAiB,WAAW;AAAA;AAAA;AAAA;AAAA;AAAA,IAKrC,QAAQ;AAAA,IACR,SAAS;AAAA,EACX,CAAC;AACH;","names":[]}
@@ -1,20 +1,33 @@
1
- import panzoom from 'panzoom';
1
+ import { P as PanzoomInstance, a as PanzoomOptions } from '../pan-zoom-Cp51IkDl.js';
2
+ export { D as DEFAULT_PANZOOM_OPTS } from '../pan-zoom-Cp51IkDl.js';
3
+ import 'panzoom';
2
4
 
3
5
  /**
4
- * DOM rendering helper for libpetri DOT diagrams.
6
+ * Compatibility wrapper over the canonical viewer.
5
7
  *
6
- * Single source of truth for the viz.js + panzoom pipeline used by both
7
- * debug-ui (live sessions) and the internal dev-preview Vite app
8
- * (sample.dot iteration loop). Doc generators (Javadoc taglet, Rustdoc
9
- * build.rs, TypeDoc plugin) take a different path they shell out to the
10
- * Graphviz `dot` binary at build time and do not consume this module.
8
+ * This module predates `libpetri/viewer` and used to be the shared viz.js +
9
+ * panzoom pipeline behind debug-ui and the dev-preview app. Both moved to the
10
+ * canonical viewer at `274cb41`, and the doc generators (Javadoc taglet,
11
+ * `libpetri-docgen`, TypeDoc plugin) never shelled out to a `dot` binary
12
+ * again either: they embed the viewer IIFE and mount client-side.
11
13
  *
12
- * Browser-only: requires `@viz-js/viz` and `panzoom` as peer dependencies.
14
+ * What was left behind was a public export that still pinned Graphviz
15
+ * `engine: 'dot'`, so anything reaching for it got stock spline layout with
16
+ * diagonal edges while every first-party surface rendered ELK-placed nodes
17
+ * with orthogonal routes. `renderDotToContainer` now delegates to
18
+ * {@link mount}, so all render paths agree.
19
+ *
20
+ * Prefer importing `libpetri/viewer` directly in new code: `mount()` returns a
21
+ * {@link ViewerHandle} with cluster collapse, subnet toggling, filtering and
22
+ * highlight control, of which this wrapper surfaces only the SVG and the
23
+ * panzoom instance.
24
+ *
25
+ * Browser-only. Requires `@viz-js/viz`, `panzoom`, and `elkjs` (the viewer's
26
+ * default layout) as peer dependencies.
13
27
  *
14
28
  * @module render-dom
15
29
  */
16
30
 
17
- type PanzoomInstance = ReturnType<typeof panzoom>;
18
31
  interface RenderDotOptions {
19
32
  /**
20
33
  * Existing panzoom instance to dispose before initializing a new one.
@@ -27,33 +40,19 @@ interface RenderDotOptions {
27
40
  * so partial overrides keep the unspecified defaults (e.g. passing only
28
41
  * `smoothScroll: true` retains `maxZoom: 1000` and `minZoom: 0.02`).
29
42
  */
30
- readonly panzoom?: Parameters<typeof panzoom>[1];
43
+ readonly panzoom?: PanzoomOptions;
31
44
  }
32
45
  interface RenderDotResult {
33
46
  readonly svg: SVGSVGElement;
34
47
  readonly panzoom: PanzoomInstance;
35
48
  }
36
- /**
37
- * Default panzoom configuration shared by debug-ui and the dev preview.
38
- *
39
- * - `maxZoom: 1000` — effectively unlimited; lets users dive into ~150-node
40
- * diagrams without hitting an artificial ceiling.
41
- * - `minZoom: 0.02` — allows fitting very large nets in a small viewport.
42
- * - `smoothScroll: false` — sharper interactive feel.
43
- * - `zoomDoubleClickSpeed: 1` — single-click double-zoom toggle.
44
- */
45
- declare const DEFAULT_PANZOOM_OPTS: {
46
- readonly smoothScroll: false;
47
- readonly zoomDoubleClickSpeed: 1;
48
- readonly maxZoom: 1000;
49
- readonly minZoom: 0.02;
50
- };
51
49
  /**
52
50
  * Render a DOT string into a container element and wrap it with panzoom.
53
51
  *
54
- * Engine is pinned to `'dot'` so layout is deterministic across reloads;
55
- * libpetri mappers emit byte-stable DOT (per spec EXP-014) and the `dot`
56
- * engine produces identical SVG for identical input.
52
+ * Layout is the viewer default: ELK node placement plus ELK-computed
53
+ * orthogonal edge routes, drawn by Graphviz `nop2`. Deterministic across
54
+ * reloads, since libpetri mappers emit byte-stable DOT (spec EXP-014) and
55
+ * neither stage introduces randomness.
57
56
  *
58
57
  * The container's existing children are removed before the new SVG is
59
58
  * appended.
@@ -64,4 +63,4 @@ declare const DEFAULT_PANZOOM_OPTS: {
64
63
  */
65
64
  declare function renderDotToContainer(dotSource: string, container: HTMLElement, opts?: RenderDotOptions): Promise<RenderDotResult>;
66
65
 
67
- export { DEFAULT_PANZOOM_OPTS, type PanzoomInstance, type RenderDotOptions, type RenderDotResult, renderDotToContainer };
66
+ export { PanzoomInstance, PanzoomOptions, type RenderDotOptions, type RenderDotResult, renderDotToContainer };