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
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  matchCorrelates
3
- } from "./chunk-E3ZWB645.js";
3
+ } from "./chunk-YVIPJ6KM.js";
4
4
  import {
5
5
  earliest,
6
6
  hasDeadline,
@@ -714,4 +714,4 @@ export {
714
714
  renderDot,
715
715
  dotExport
716
716
  };
717
- //# sourceMappingURL=chunk-JVI5HFRX.js.map
717
+ //# sourceMappingURL=chunk-VCDOKWVU.js.map
@@ -24,4 +24,4 @@ export {
24
24
  keyForPlace,
25
25
  matchCorrelates
26
26
  };
27
- //# sourceMappingURL=chunk-E3ZWB645.js.map
27
+ //# sourceMappingURL=chunk-YVIPJ6KM.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/core/match-spec.ts"],"sourcesContent":["/**\n * ν-net join correlation ([[MatchSpec]]).\n *\n * A {@link MatchSpec} declares that a subset of a transition's **input** places\n * must be correlated by **name equality**: the transition is enabled only when\n * there exists a single {@link NameId} `n` such that every correlated input\n * supplies (at least) its required token count whose projected name equals `n`.\n * On firing, exactly those name-matched tokens are consumed (spec NU-020).\n *\n * This is the single *decidable* predicate — equality of opaque names — and is\n * deliberately NOT a general input predicate: it correlates the name dimension\n * *across* places (composition-structural, like cardinality) rather than\n * evaluating an arbitrary boolean per token. Input specifications themselves\n * remain purely structural (IO-006); name correlation is the only per-token\n * filter the enablement check ever applies (NU-021).\n */\nimport type { Place } from './place.js';\nimport type { NameId } from './name.js';\n\n/**\n * A name projection: maps a token's value to its {@link NameId}. A projection\n * that yields no name (returns `null`/`undefined` at runtime) is treated as\n * \"no name\" — that token never correlates — mirroring the Java/Rust `KeyFn`\n * contract; the binding selector handles a nullish result defensively.\n */\nexport type KeyFn<T = any> = (value: T) => NameId;\n\n/** One correlated input: the place plus its name projection. */\nexport interface MatchKey<T = any> {\n readonly place: Place<T>;\n readonly key: KeyFn<T>;\n}\n\n/** Correlated fork/join match specification (ν-net join side). */\nexport interface MatchSpec {\n readonly keys: readonly MatchKey[];\n}\n\n/** Builds one correlated input for a {@link MatchSpec}. */\nexport function matchKey<T>(place: Place<T>, key: KeyFn<T>): MatchKey<T> {\n return { place, key };\n}\n\n/**\n * Builds a {@link MatchSpec} from two or more correlated inputs.\n *\n * @example\n * Transition.builder('join')\n * .inputs(one(branchA), one(branchB))\n * .match(matchSpec(\n * matchKey(branchA, (m: Msg) => nameId(m.correlationId)),\n * matchKey(branchB, (m: Msg) => nameId(m.correlationId)),\n * ))\n *\n * @throws if fewer than two inputs are correlated (a match over a single place\n * correlates nothing).\n */\nexport function matchSpec(...keys: MatchKey[]): MatchSpec {\n if (keys.length < 2) {\n throw new Error(`MatchSpec must correlate at least 2 input places, got ${keys.length}`);\n }\n return { keys };\n}\n\n/** Returns the name projection for `placeName`, or `undefined` if not correlated. */\nexport function keyForPlace(spec: MatchSpec, placeName: string): KeyFn | undefined {\n for (const k of spec.keys) {\n if (k.place.name === placeName) return k.key;\n }\n return undefined;\n}\n\n/** True when `placeName` is one of the correlated inputs. */\nexport function matchCorrelates(spec: MatchSpec, placeName: string): boolean {\n return spec.keys.some(k => k.place.name === placeName);\n}\n"],"mappings":";AAuCO,SAAS,SAAY,OAAiB,KAA4B;AACvE,SAAO,EAAE,OAAO,IAAI;AACtB;AAgBO,SAAS,aAAa,MAA6B;AACxD,MAAI,KAAK,SAAS,GAAG;AACnB,UAAM,IAAI,MAAM,yDAAyD,KAAK,MAAM,EAAE;AAAA,EACxF;AACA,SAAO,EAAE,KAAK;AAChB;AAGO,SAAS,YAAY,MAAiB,WAAsC;AACjF,aAAW,KAAK,KAAK,MAAM;AACzB,QAAI,EAAE,MAAM,SAAS,UAAW,QAAO,EAAE;AAAA,EAC3C;AACA,SAAO;AACT;AAGO,SAAS,gBAAgB,MAAiB,WAA4B;AAC3E,SAAO,KAAK,KAAK,KAAK,OAAK,EAAE,MAAM,SAAS,SAAS;AACvD;","names":[]}
@@ -1,6 +1,6 @@
1
1
  import { Writable } from 'node:stream';
2
- import { P as PetriNet, b as Transition, T as Token } from '../petri-net-C3LSY-vm.js';
3
- import { E as EventStore, N as NetEvent } from '../event-store-DKTenPbC.js';
2
+ import { P as PetriNet, b as Transition, T as Token } from '../petri-net-WSScMyDL.js';
3
+ import { E as EventStore, N as NetEvent } from '../event-store-BFX_yJ8I.js';
4
4
 
5
5
  /**
6
6
  * Commands sent from debug UI client to server via WebSocket.
@@ -6,8 +6,8 @@ import {
6
6
  instancePrefixOf,
7
7
  parentOf,
8
8
  sanitize
9
- } from "../chunk-JVI5HFRX.js";
10
- import "../chunk-E3ZWB645.js";
9
+ } from "../chunk-VCDOKWVU.js";
10
+ import "../chunk-YVIPJ6KM.js";
11
11
  import "../chunk-ATT7U5H5.js";
12
12
 
13
13
  // src/debug/debug-command.ts
@@ -1,5 +1,5 @@
1
1
  import { Application } from 'typedoc';
2
- import { P as PetriNet, S as SubnetDef, o as Instance } from '../petri-net-C3LSY-vm.js';
2
+ import { P as PetriNet, S as SubnetDef, o as Instance } from '../petri-net-WSScMyDL.js';
3
3
 
4
4
  /**
5
5
  * TypeDoc plugin for `@petrinet` and `@subnet` tags — auto-generates
@@ -76,11 +76,16 @@ declare function renderSvg(title: string | null | undefined, dotSource: string):
76
76
  declare function renderSubnetSvg(title: string | null | undefined, headerHtml: string, dotSource: string): string;
77
77
 
78
78
  /**
79
- * SVG renderer using `@viz-js/viz` (Graphviz WASM).
79
+ * Stock Graphviz SVG rendering using `@viz-js/viz` (Graphviz WASM).
80
80
  *
81
81
  * Pure WASM — zero external dependencies. No system `dot` binary required.
82
82
  *
83
- * Mirrors: Java's `PetriNetTaglet.dotToSvg()` (which uses `dot -Tsvg` subprocess)
83
+ * **Not** what the TypeDoc plugin emits. The plugin embeds the viewer bundle
84
+ * and mounts client-side (see `diagram-renderer.ts`), which gives ELK node
85
+ * placement and orthogonal edge routes. This module is the plain `dot` engine:
86
+ * Graphviz does its own layout and routes edges as diagonal splines. It stays
87
+ * exported for callers that want a server-side SVG string and are content with
88
+ * stock layout; for the canonical look, use `mount()` from `libpetri/viewer`.
84
89
  *
85
90
  * @module doclet/svg-renderer
86
91
  */
@@ -90,6 +95,10 @@ declare function renderSubnetSvg(title: string | null | undefined, headerHtml: s
90
95
  * Strips the XML prolog/DOCTYPE and explicit width/height attributes so the SVG
91
96
  * scales via viewBox + CSS instead of overriding with fixed pt sizes.
92
97
  *
98
+ * Layout is stock Graphviz `dot`, so edges are diagonal splines. For the
99
+ * orthogonal routing the doc generators and debug-ui produce, mount the
100
+ * canonical viewer instead (`mount()` from `libpetri/viewer`).
101
+ *
93
102
  * @param dot - the DOT source string
94
103
  * @returns SVG markup string
95
104
  * @throws if `@viz-js/viz` is not installed or DOT parsing fails
@@ -3,8 +3,8 @@ import {
3
3
  dotExport,
4
4
  nodeStyle,
5
5
  sanitize
6
- } from "../chunk-JVI5HFRX.js";
7
- import "../chunk-E3ZWB645.js";
6
+ } from "../chunk-VCDOKWVU.js";
7
+ import "../chunk-YVIPJ6KM.js";
8
8
  import "../chunk-ATT7U5H5.js";
9
9
 
10
10
  // src/doclet/petri-net-plugin.ts
@@ -130,6 +130,10 @@ function renderSvg(title, dotSource) {
130
130
  function renderSubnetSvg(title, headerHtml, dotSource) {
131
131
  return renderInternal(title ?? null, headerHtml, dotSource);
132
132
  }
133
+ function mountScript(containerId) {
134
+ const id = JSON.stringify(containerId);
135
+ return `(function(){function fail(el,e){console.error('[libpetri] viewer mount failed',e);var p=document.createElement('p');p.className='libpetri-diagram-error';p.textContent='Diagram render failed: '+(e&&e.message?e.message:e);el.textContent='';el.appendChild(p);}var tries=0;function mount(){var el=document.getElementById(${id});if(!el)return;if(el.dataset.libpetriMounted)return;if(!window.LibpetriViewer||typeof window.LibpetriViewer.mount!=='function'){if(++tries>100){return fail(el,new Error('viewer bundle did not load'));}return setTimeout(mount,30);}el.dataset.libpetriMounted='1';el.dataset.libpetriViewer=window.LibpetriViewer.VERSION||'unknown';try{Promise.resolve(window.LibpetriViewer.mount(el.dataset.dot,el,{chrome:true})).catch(function(e){fail(el,e);});}catch(e){fail(el,e);}}if(document.readyState==='loading'){document.addEventListener('DOMContentLoaded',mount);}else{mount();}})();`;
136
+ }
133
137
  var diagramIdCounter = 0;
134
138
  function renderInternal(title, headerHtml, dotSource) {
135
139
  const titleHtml = title !== null ? `<h4>${escapeHtml(title)}</h4>
@@ -150,7 +154,7 @@ ${titleHtml}${headerBlock}<div class="diagram-container" id="${containerId}" dat
150
154
  <pre><code>${escapeHtml(dotSource)}</code></pre>
151
155
  </details>
152
156
  </div>
153
- <script>(function(){function mount(){var el=document.getElementById(${JSON.stringify(containerId)});if(!el)return;if(el.dataset.libpetriMounted)return;if(!window.LibpetriViewer||typeof window.LibpetriViewer.mount!=='function'){return setTimeout(mount,30);}el.dataset.libpetriMounted='1';try{window.LibpetriViewer.mount(el.dataset.dot,el,{chrome:true});}catch(e){console.error('[libpetri] mount failed',e);}}if(document.readyState==='loading'){document.addEventListener('DOMContentLoaded',mount);}else{mount();}})();</script>`;
157
+ <script>${mountScript(containerId)}</script>`;
154
158
  }
155
159
 
156
160
  // src/doclet/subnet-header.ts
@@ -447,7 +451,7 @@ function escapeLabel(s) {
447
451
 
448
452
  // src/doclet/petri-net-plugin.ts
449
453
  async function getDotExport() {
450
- const mod = await import("../dot-exporter-SHBYMMJ3.js");
454
+ const mod = await import("../dot-exporter-3STXYK74.js");
451
455
  return mod.dotExport;
452
456
  }
453
457
  var TAG_PETRINET = "@petrinet";
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/doclet/petri-net-plugin.ts","../../src/doclet/net-resolver.ts","../../src/doclet/diagram-renderer.ts","../../src/doclet/subnet-header.ts","../../src/doclet/subnet-dot-export.ts","../../src/doclet/svg-renderer.ts"],"sourcesContent":["/**\n * TypeDoc plugin for `@petrinet` and `@subnet` tags — auto-generates\n * interactive SVG diagrams from PetriNet / SubnetDef / Instance definitions\n * and embeds them in TypeDoc output.\n *\n * Mirrors: `org.libpetri.doclet.PetriNetTaglet` + `SubnetTaglet`\n *\n * Plugin lifecycle (TypeDoc hooks):\n * 1. `bootstrapEnd` → register `@petrinet` and `@subnet` as known block tags.\n * 2. `preRenderAsyncJobs` → walk reflections, resolve references, generate\n * SVGs, cache HTML.\n * 3. `comment.beforeTags` → inject cached HTML via `JSX.Raw`, skip default\n * rendering for the recognised tags.\n *\n * @module doclet/petri-net-plugin\n */\n\nimport {\n type Application,\n type CommentTag,\n type DeclarationReflection,\n type ProjectReflection,\n type Reflection,\n JSX,\n} from 'typedoc';\nimport { resolveNet, type ResolvedNet } from './net-resolver.js';\nimport { renderSvg, renderSubnetSvg, escapeHtml } from './diagram-renderer.js';\nimport { forSubnetDef, forInstance } from './subnet-header.js';\nimport { fullBody as subnetFullBody, interfaceOnly as subnetInterfaceOnly } from './subnet-dot-export.js';\n\n// Lazy import to avoid requiring libpetri/export at module level.\nasync function getDotExport(): Promise<typeof import('../export/dot-exporter.js').dotExport> {\n const mod = await import('../export/dot-exporter.js');\n return mod.dotExport;\n}\n\n/** Block tag for full body diagrams — resolves PetriNet / SubnetDef / Instance. */\nconst TAG_PETRINET = '@petrinet' as `@${string}`;\n/** Block tag for interface-only subnet diagrams. */\nconst TAG_SUBNET = '@subnet' as `@${string}`;\n\n/** Cache: reflection ID → rendered HTML string */\nconst htmlCache = new Map<number, string>();\n\n/**\n * Loads the petri-net plugin into the TypeDoc application.\n *\n * @param app - the TypeDoc Application instance\n */\nexport function load(app: Application): void {\n // 1. Register both tags as known block tags at bootstrap time.\n app.on('bootstrapEnd', () => {\n const blockTags = app.options.getValue('blockTags') as string[];\n const additions: string[] = [];\n if (!blockTags.includes(TAG_PETRINET)) additions.push(TAG_PETRINET);\n if (!blockTags.includes(TAG_SUBNET)) additions.push(TAG_SUBNET);\n if (additions.length > 0) {\n app.options.setValue('blockTags', [...blockTags, ...additions]);\n }\n });\n\n // 2. Resolve references and generate SVG during pre-render async phase.\n app.renderer.preRenderAsyncJobs.push(async (output) => {\n htmlCache.clear();\n await processProject(app, output.project);\n });\n\n // 3. Inject cached HTML into comment output, suppress default tag rendering.\n app.renderer.hooks.on('comment.beforeTags', (_context, comment, reflection) => {\n const cached = htmlCache.get(reflection.id);\n if (!cached) return JSX.createElement(JSX.Raw, { html: '' });\n\n // Mark our tags as skip so TypeDoc doesn't render them as plain text.\n for (const tag of comment.blockTags) {\n if (tag.tag === TAG_PETRINET || tag.tag === TAG_SUBNET) {\n tag.skipRendering = true;\n }\n }\n\n return JSX.createElement(JSX.Raw, { html: cached });\n });\n}\n\n/**\n * Walks all reflections in the project and processes `@petrinet` / `@subnet`\n * tags. Per-reflection HTML accumulates in {@link htmlCache} so multiple tags\n * on one symbol render in declaration order.\n */\nasync function processProject(app: Application, project: ProjectReflection): Promise<void> {\n const reflections = Object.values(project.reflections);\n\n for (const reflection of reflections) {\n const comment = reflection.comment;\n if (!comment) continue;\n\n for (const tag of comment.blockTags) {\n const isPetrinet = tag.tag === TAG_PETRINET;\n const isSubnet = tag.tag === TAG_SUBNET;\n if (!isPetrinet && !isSubnet) continue;\n\n const reference = tagContent(tag);\n try {\n const html = isSubnet\n ? await generateInterfaceOnly(reference, reflection)\n : await generateDiagram(reference, reflection);\n const existing = htmlCache.get(reflection.id) ?? '';\n htmlCache.set(reflection.id, existing + html);\n } catch (e) {\n const msg = e instanceof Error ? e.message : String(e);\n const tagName = isSubnet ? '@subnet' : '@petrinet';\n app.logger.warn(`${tagName} error for ${reflection.getFriendlyFullName()}: ${msg}`);\n const html = errorHtml(`Error generating diagram for '${reference}': ${msg}`);\n const existing = htmlCache.get(reflection.id) ?? '';\n htmlCache.set(reflection.id, existing + html);\n }\n }\n }\n}\n\n/**\n * Generates a diagram HTML string for a single `@petrinet` reference. Resolves\n * the reference to one of {@link ResolvedNet} variants and dispatches to the\n * matching renderer.\n */\nasync function generateDiagram(\n reference: string,\n reflection: Reflection,\n): Promise<string> {\n const trimmed = reference.trim();\n if (!trimmed) {\n return errorHtml(`Empty @petrinet reference on ${reflection.getFriendlyFullName()}`);\n }\n\n const sourceFile = getSourceFilePath(reflection);\n if (!sourceFile) {\n return errorHtml(`Cannot determine source file for ${reflection.getFriendlyFullName()}`);\n }\n\n const resolved = await resolveNet(trimmed, sourceFile);\n if (!resolved) {\n return errorHtml(`Cannot resolve PetriNet/SubnetDef/Instance: ${trimmed}`);\n }\n\n return await renderResolved(resolved, /* interfaceOnly */ false);\n}\n\n/**\n * Generates an interface-only diagram for a `@subnet` reference. When the\n * resolved reference is not a {@link import('../core/subnet-def.js').SubnetDef},\n * falls back to the regular diagram so the tag still produces useful output.\n */\nasync function generateInterfaceOnly(\n reference: string,\n reflection: Reflection,\n): Promise<string> {\n const trimmed = reference.trim();\n if (!trimmed) {\n return errorHtml(`Empty @subnet reference on ${reflection.getFriendlyFullName()}`);\n }\n\n const sourceFile = getSourceFilePath(reflection);\n if (!sourceFile) {\n return errorHtml(`Cannot determine source file for ${reflection.getFriendlyFullName()}`);\n }\n\n const resolved = await resolveNet(trimmed, sourceFile);\n if (!resolved) {\n return errorHtml(`Cannot resolve SubnetDef: ${trimmed}`);\n }\n\n return await renderResolved(resolved, /* interfaceOnly */ true);\n}\n\n/**\n * Dispatches a {@link ResolvedNet} to the matching renderer. The\n * `interfaceOnly` flag is honoured only when the resolved kind is `subnet` —\n * for `net` and `instance` it is ignored (the body is the whole point of\n * those references).\n */\nasync function renderResolved(\n resolved: ResolvedNet,\n interfaceOnly: boolean,\n): Promise<string> {\n switch (resolved.kind) {\n case 'net': {\n const dotExport = await getDotExport();\n const dot = dotExport(resolved.value);\n return renderDiagramHtml(resolved.title, dot, /* header */ null);\n }\n case 'subnet': {\n const dot = interfaceOnly\n ? subnetInterfaceOnly(resolved.value)\n : subnetFullBody(resolved.value);\n const header = forSubnetDef(resolved.value, interfaceOnly);\n const title = resolved.value.name + (interfaceOnly ? ' (interface)' : '');\n return renderDiagramHtml(title, dot, header);\n }\n case 'instance': {\n const dotExport = await getDotExport();\n const dot = dotExport(resolved.value.renamedBody);\n const header = forInstance(resolved.value);\n return renderDiagramHtml(resolved.title, dot, header);\n }\n }\n}\n\n/**\n * Emits the diagram HTML. The canonical viewer renders DOT to SVG\n * client-side via Graphviz-WASM, so no server-side SVG rendering happens\n * here. Routes through the subnet-aware renderer when `header` is non-null,\n * the plain renderer otherwise.\n */\nfunction renderDiagramHtml(\n title: string,\n dot: string,\n header: string | null,\n): string {\n if (header !== null) {\n return renderSubnetSvg(title, header, dot);\n }\n return renderSvg(title, dot);\n}\n\n/**\n * Extracts text content from a CommentTag.\n */\nfunction tagContent(tag: CommentTag): string {\n return tag.content\n .map((part) => part.text)\n .join('')\n .trim();\n}\n\n/**\n * Gets the source file path from a reflection. Walks parents because\n * comment-bearing reflections sometimes lack their own `sources` (e.g.\n * inherited members).\n */\nfunction getSourceFilePath(reflection: Reflection): string | null {\n const decl = reflection as DeclarationReflection;\n if (decl.sources && decl.sources.length > 0) {\n return decl.sources[0]!.fullFileName;\n }\n if (reflection.parent) {\n return getSourceFilePath(reflection.parent);\n }\n return null;\n}\n\n/**\n * Renders an error message as styled HTML.\n */\nfunction errorHtml(message: string): string {\n return `<div class=\"petrinet-error\" style=\"color: #dc3545; border: 1px solid #dc3545; padding: 10px; border-radius: 4px;\">\n<strong>@petrinet Error:</strong> ${escapeHtml(message)}\n</div>`;\n}\n","/**\n * Dynamic import-based resolver for `@petrinet` / `@subnet` references.\n *\n * Resolves three kinds of static exports per Java's `PetriNetTaglet` design:\n * 1. `PetriNet` — full body diagram (existing behaviour).\n * 2. `SubnetDef<?>` — body diagram with interface ports highlighted, plus a\n * subnet header with port/channel badges.\n * 3. `Instance<?>` — renamed body diagram (already prefix-clustered by the\n * DOT exporter), plus an instance header with prefix, def name, and\n * params summary.\n *\n * TypeScript cannot use reflection like Java — modules must be importable at\n * doc time (typically from `dist/` after `npm run build`).\n *\n * Tag format:\n * ```\n * @petrinet ./path/to/module#exportName — access a static export\n * @petrinet ./path/to/module#functionName() — call a function returning one\n * @petrinet #localExport — resolve from same file\n * ```\n *\n * Mirrors: `org.libpetri.doclet.PetriNetTaglet.resolveReference`\n *\n * @module doclet/net-resolver\n */\n\nimport { resolve, dirname } from 'node:path';\nimport { pathToFileURL } from 'node:url';\nimport type { PetriNet } from '../core/petri-net.js';\nimport type { SubnetDef } from '../core/subnet-def.js';\nimport type { Instance } from '../core/instance.js';\n\n/**\n * Discriminated-union result from {@link resolveNet}. Each variant carries\n * the resolved value plus a display title.\n */\nexport type ResolvedNet =\n | { readonly kind: 'net'; readonly value: PetriNet; readonly title: string }\n | { readonly kind: 'subnet'; readonly value: SubnetDef<unknown>; readonly title: string }\n | { readonly kind: 'instance'; readonly value: Instance<unknown>; readonly title: string };\n\n/**\n * Parses a `@petrinet` / `@subnet` reference and resolves it via dynamic\n * import. Returns `null` when the reference cannot be parsed or imported, or\n * when the resolved value is none of the three supported kinds.\n *\n * @param reference - the tag content, e.g. `./definition#buildDebugNet()`\n * @param sourceFilePath - absolute path to the source file containing the tag\n */\nexport async function resolveNet(\n reference: string,\n sourceFilePath: string,\n): Promise<ResolvedNet | null> {\n const trimmed = reference.trim().split(/\\s+/)[0] ?? '';\n if (!trimmed) return null;\n\n const hashIndex = trimmed.indexOf('#');\n if (hashIndex === -1) return null;\n\n const modulePath = trimmed.substring(0, hashIndex);\n const exportRef = trimmed.substring(hashIndex + 1);\n if (!exportRef) return null;\n\n const isCall = exportRef.endsWith('()');\n const exportName = isCall ? exportRef.slice(0, -2) : exportRef;\n\n // Resolve module path relative to the source file.\n let absolutePath: string;\n if (modulePath) {\n absolutePath = resolve(dirname(sourceFilePath), modulePath);\n } else {\n // `#localExport` — resolve from same file's compiled output.\n absolutePath = sourceFilePath;\n }\n\n // Try `.js` extension for compiled output, then `.ts` for ts-node scenarios.\n const candidates = [\n absolutePath,\n absolutePath + '.js',\n absolutePath + '.ts',\n absolutePath.replace(/\\.ts$/, '.js'),\n ];\n\n let mod: Record<string, unknown> | undefined;\n for (const candidate of candidates) {\n try {\n mod = await import(pathToFileURL(candidate).href) as Record<string, unknown>;\n break;\n } catch {\n // Try next candidate.\n }\n }\n\n if (!mod) return null;\n\n const exported = mod[exportName];\n if (exported == null) return null;\n\n let value: unknown;\n if (isCall) {\n if (typeof exported !== 'function') return null;\n const result = (exported as (...args: unknown[]) => unknown)();\n // Support functions that return `{ net: PetriNet, ... }` for legacy\n // callers; otherwise pass the result through directly.\n if (\n result !== null && typeof result === 'object' && 'net' in result &&\n isPetriNetLike((result as { net: unknown }).net)\n ) {\n value = (result as { net: unknown }).net;\n } else {\n value = result;\n }\n } else {\n value = exported;\n }\n\n return classify(value);\n}\n\n/**\n * Wraps the resolved value into a {@link ResolvedNet} variant by structural\n * shape — checks {@link Instance} before {@link SubnetDef} before\n * {@link PetriNet} so the most specific match wins (Instance has both `def`\n * and `renamedBody`; SubnetDef has `iface` and `body`; PetriNet has only\n * `transitions` + `name`).\n *\n * Returns `null` when the value matches none of the supported shapes.\n */\nfunction classify(value: unknown): ResolvedNet | null {\n if (value === null || typeof value !== 'object') return null;\n\n if (isInstanceLike(value)) {\n const inst = value as Instance<unknown>;\n return { kind: 'instance', value: inst, title: `${inst.def.name} :: ${inst.prefix}` };\n }\n if (isSubnetDefLike(value)) {\n const def = value as SubnetDef<unknown>;\n return { kind: 'subnet', value: def, title: def.name };\n }\n if (isPetriNetLike(value)) {\n const net = value as PetriNet;\n return { kind: 'net', value: net, title: net.name };\n }\n return null;\n}\n\n/**\n * Structural test for {@link PetriNet}: has a string `name` and a `transitions`\n * Set. Avoids `instanceof` because the imported module may have re-bound the\n * class identity (TypeDoc's bundled output vs `dist/` import path).\n */\nfunction isPetriNetLike(value: unknown): value is PetriNet {\n if (value === null || typeof value !== 'object') return false;\n const candidate = value as { name?: unknown; transitions?: unknown };\n return typeof candidate.name === 'string' && candidate.transitions instanceof Set;\n}\n\n/**\n * Structural test for {@link SubnetDef}: has a string `name`, a {@link\n * PetriNet}-shaped `body`, and an `iface` carrying `ports` + `channels` maps.\n */\nfunction isSubnetDefLike(value: unknown): value is SubnetDef<unknown> {\n if (value === null || typeof value !== 'object') return false;\n const candidate = value as { name?: unknown; body?: unknown; iface?: unknown };\n if (typeof candidate.name !== 'string') return false;\n if (!isPetriNetLike(candidate.body)) return false;\n const iface = candidate.iface as { ports?: unknown; channels?: unknown } | undefined;\n return iface !== undefined && iface !== null\n && iface.ports instanceof Map && iface.channels instanceof Map;\n}\n\n/**\n * Structural test for {@link Instance}: has a string `prefix`, a {@link\n * SubnetDef}-shaped `def`, and a {@link PetriNet}-shaped `renamedBody`.\n */\nfunction isInstanceLike(value: unknown): value is Instance<unknown> {\n if (value === null || typeof value !== 'object') return false;\n const candidate = value as { prefix?: unknown; def?: unknown; renamedBody?: unknown };\n return typeof candidate.prefix === 'string'\n && isSubnetDefLike(candidate.def)\n && isPetriNetLike(candidate.renamedBody);\n}\n","/**\n * Shared HTML renderer for Petri Net diagrams in TypeDoc.\n *\n * Generates consistent HTML markup that mounts the canonical\n * {@link https://libpetri.org | libpetri} viewer client-side. The viewer IIFE\n * bundle (Graphviz WASM inlined, offline-safe) is inlined into the generated\n * HTML, making the plugin fully self-contained with no external file\n * dependencies.\n *\n * Each diagram container carries its DOT source as a `data-dot` attribute. An\n * init snippet reads the attribute and calls\n * `window.LibpetriViewer.mount(dot, container, { chrome: true })` to render the\n * SVG and wire pan/zoom + cluster legend/filter chrome.\n *\n * Mirrors: `org.libpetri.doclet.DiagramRenderer`\n *\n * @module doclet/diagram-renderer\n */\n\nimport { readFileSync } from 'node:fs';\nimport { fileURLToPath } from 'node:url';\nimport { dirname, join } from 'node:path';\n\nconst __dirname = dirname(fileURLToPath(import.meta.url));\n\nfunction loadResource(filename: string): string {\n return readFileSync(join(__dirname, 'resources', filename), 'utf-8');\n}\n\nlet inlineCss: string | undefined;\nlet inlineJs: string | undefined;\n\nfunction css(): string {\n return (inlineCss ??= loadResource('petrinet-diagrams.css'));\n}\n\nfunction js(): string {\n return (inlineJs ??= loadResource('petrinet-diagrams.js'));\n}\n\n/**\n * Escapes HTML special characters for safe embedding as text content.\n */\nexport function escapeHtml(text: string): string {\n return text\n .replace(/&/g, '&amp;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;')\n .replace(/\"/g, '&quot;');\n}\n\n/**\n * Escapes a string for safe inclusion in an HTML attribute value enclosed in\n * double quotes. Identical to {@link escapeHtml} plus single-quote escaping\n * (defence-in-depth even though we always emit double-quoted attributes).\n */\nfunction escapeAttr(text: string): string {\n return text\n .replace(/&/g, '&amp;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;')\n .replace(/\"/g, '&quot;')\n .replace(/'/g, '&#39;');\n}\n\n/**\n * Renders a diagram from a DOT source string. The viewer renders the SVG\n * client-side via Graphviz-WASM; no server-side SVG is emitted on the\n * embedded path.\n *\n * Inlines CSS and the viewer IIFE from bundled resources. The IIFE is guarded\n * by an idempotency check (`window._libpetriViewerInit`) so it only executes\n * once per page even when multiple `@petrinet` tags appear.\n *\n * @param title - optional title (null/undefined for no title)\n * @param dotSource - the DOT source code; embedded as `data-dot` and shown in\n * the collapsible \"View DOT Source\" block\n * @returns HTML markup with diagram controls\n */\nexport function renderSvg(\n title: string | null | undefined,\n dotSource: string,\n): string {\n return renderInternal(title ?? null, /* headerHtml */ null, dotSource);\n}\n\n/**\n * Renders a subnet- or instance-aware diagram. Identical to {@link renderSvg}\n * apart from an extra {@code headerHtml} block (the port/channel/params\n * badge bar) prepended above the diagram, and the CSS class\n * {@code subnet-diagram} on the wrapper.\n *\n * @param title diagram title (e.g. \"BoundedBuffer :: b1\")\n * @param headerHtml the HTML produced by {@link import('./subnet-header.js')}\n * @param dotSource the DOT source code\n * @returns HTML markup\n */\nexport function renderSubnetSvg(\n title: string | null | undefined,\n headerHtml: string,\n dotSource: string,\n): string {\n return renderInternal(title ?? null, headerHtml, dotSource);\n}\n\nlet diagramIdCounter = 0;\n\nfunction renderInternal(\n title: string | null,\n headerHtml: string | null,\n dotSource: string,\n): string {\n const titleHtml = title !== null ? `<h4>${escapeHtml(title)}</h4>\\n` : '';\n const summaryText = title !== null ? 'View DOT Source' : 'View Source';\n const diagramClass = headerHtml !== null ? 'petrinet-diagram subnet-diagram' : 'petrinet-diagram';\n const headerBlock = headerHtml !== null ? headerHtml : '';\n const dotAttr = escapeAttr(dotSource);\n const containerId = `libpetri-diagram-${++diagramIdCounter}`;\n\n return `<style>${css()}</style>\n<script>if(!window._libpetriViewerInit){window._libpetriViewerInit=true;\n${js()}\n}</script>\n<div class=\"${diagramClass}\">\n${titleHtml}${headerBlock}<div class=\"diagram-container\" id=\"${containerId}\" data-libpetri-diagram=\"true\" data-dot=\"${dotAttr}\"></div>\n<details>\n<summary>${summaryText}</summary>\n<pre><code>${escapeHtml(dotSource)}</code></pre>\n</details>\n</div>\n<script>(function(){function mount(){var el=document.getElementById(${JSON.stringify(containerId)});if(!el)return;if(el.dataset.libpetriMounted)return;if(!window.LibpetriViewer||typeof window.LibpetriViewer.mount!=='function'){return setTimeout(mount,30);}el.dataset.libpetriMounted='1';try{window.LibpetriViewer.mount(el.dataset.dot,el,{chrome:true});}catch(e){console.error('[libpetri] mount failed',e);}}if(document.readyState==='loading'){document.addEventListener('DOMContentLoaded',mount);}else{mount();}})();</script>`;\n}\n","/**\n * HTML-fragment generator for the small \"subnet header\" badge bar that the\n * {@link import('./diagram-renderer.js')} layers above subnet diagrams.\n *\n * For a {@link import('../core/subnet-def.js').SubnetDef} the header lists\n * the subnet name, the parameter type (best-effort — TypeScript erases\n * generics at runtime so the slot is shown as a placeholder), and one badge\n * per port and channel — colour-coded by direction\n * (`interface-port-input` / `-output` / `-inout` CSS classes, plus\n * `interface-channel` for sync channels).\n *\n * For an {@link import('../core/instance.js').Instance} the header lists the\n * instance prefix, the originating definition's name, and the {@link\n * import('../core/instance.js').Instance#params} summary.\n *\n * Mirrors: `org.libpetri.doclet.SubnetHeader`\n *\n * @module doclet/subnet-header\n */\n\nimport type { Instance } from '../core/instance.js';\nimport type { Interface, Port } from '../core/interface.js';\nimport type { SubnetDef } from '../core/subnet-def.js';\nimport { escapeHtml } from './diagram-renderer.js';\n\n/**\n * Builds the header HTML for a {@link SubnetDef} field.\n *\n * @param def the subnet definition\n * @param interfaceOnly when `true`, the header carries the \"(interface only)\"\n * badge so the reader knows the body is intentionally\n * omitted\n */\nexport function forSubnetDef(def: SubnetDef<unknown>, interfaceOnly: boolean): string {\n const sb: string[] = [];\n sb.push(`<div class=\"subnet-header\">\\n`);\n sb.push(` <div class=\"subnet-header-row\">\\n`);\n sb.push(` <span class=\"subnet-header-label\">subnet</span>\\n`);\n sb.push(` <span class=\"subnet-header-name\">${escapeHtml(def.name)}</span>\\n`);\n // TypeScript erases generics at runtime, so the parameter-type slot is\n // populated with a placeholder (\"?\"). Java emits the simple class name —\n // the structural HTML is otherwise byte-equivalent.\n sb.push(` <span class=\"subnet-header-param\">&lt;?&gt;</span>\\n`);\n if (interfaceOnly) {\n sb.push(` <span class=\"subnet-header-badge subnet-header-badge-interface\">interface only</span>\\n`);\n }\n sb.push(` </div>\\n`);\n sb.push(renderPortChannelBadges(def.iface));\n sb.push(`</div>\\n`);\n return sb.join('');\n}\n\n/**\n * Builds the header HTML for an {@link Instance} field.\n */\nexport function forInstance(instance: Instance<unknown>): string {\n const def = instance.def;\n const sb: string[] = [];\n sb.push(`<div class=\"subnet-header\">\\n`);\n sb.push(` <div class=\"subnet-header-row\">\\n`);\n sb.push(` <span class=\"subnet-header-label\">instance</span>\\n`);\n sb.push(` <span class=\"subnet-header-name\">${escapeHtml(instance.prefix)}</span>\\n`);\n sb.push(` <span class=\"subnet-header-of\">of</span>\\n`);\n sb.push(` <span class=\"subnet-header-name\">${escapeHtml(def.name)}</span>\\n`);\n const params = instance.params;\n if (params !== null && params !== undefined) {\n sb.push(` <span class=\"subnet-header-params\">params=${escapeHtml(String(params))}</span>\\n`);\n }\n sb.push(` </div>\\n`);\n sb.push(renderPortChannelBadges(def.iface));\n sb.push(`</div>\\n`);\n return sb.join('');\n}\n\n/**\n * Renders the per-port and per-channel badge row. Each badge carries a CSS\n * class identifying its kind so the stylesheet can colour-code by direction.\n */\nfunction renderPortChannelBadges(iface: Interface): string {\n if (iface.ports.size === 0 && iface.channels.size === 0) {\n return '';\n }\n const sb: string[] = [];\n sb.push(` <div class=\"subnet-header-badges\">\\n`);\n for (const port of iface.ports.values()) {\n const kind = portKind(port);\n sb.push(` <span class=\"interface-port-badge interface-port-badge-${kind}\">`);\n sb.push(`<span class=\"badge-direction\">${kind}</span>`);\n sb.push(`<span class=\"badge-name\">${escapeHtml(port.name)}</span>`);\n // TypeScript erases generics so we can't print the runtime token type;\n // omit the type-name span rather than emit a bogus value.\n sb.push(`</span>\\n`);\n }\n for (const channel of iface.channels.values()) {\n sb.push(` <span class=\"interface-channel-badge\">`);\n sb.push(`<span class=\"badge-direction\">channel</span>`);\n sb.push(`<span class=\"badge-name\">${escapeHtml(channel.name)}</span>`);\n sb.push(`</span>\\n`);\n }\n sb.push(` </div>\\n`);\n return sb.join('');\n}\n\nfunction portKind(port: Port<unknown>): 'input' | 'output' | 'inout' {\n return port.direction;\n}\n","/**\n * DOT generators for the subnet-aware diagrams the {@link\n * import('./petri-net-plugin.js')} plugin draws.\n *\n * Two flavours:\n * - {@link fullBody} — the subnet's complete body, but with its interface-port\n * places restyled with the `interface-port` category from\n * `petri-net-styles.json` so they read as boundary elements at a glance. A\n * small `subgraph cluster_iface_*` with label `\"interface\"` surrounds the\n * port places and channel transitions to advertise the subnet boundary\n * visually.\n * - {@link interfaceOnly} — a tiny diagram of just the port places, the\n * channel transitions, and a single stub node indicating the body is\n * omitted. Used by the `@subnet` block tag.\n *\n * This module stays in the doclet directory (rather than living in\n * `export/`) because it is a doclet-presentation concern — the production\n * export pipeline never adds the `interface` cluster.\n *\n * Mirrors: `org.libpetri.doclet.SubnetDotExport`\n *\n * @module doclet/subnet-dot-export\n */\n\nimport type { SubnetDef } from '../core/subnet-def.js';\nimport type { Interface, Port, Channel } from '../core/interface.js';\nimport { dotExport } from '../export/dot-exporter.js';\nimport { sanitize } from '../export/petri-net-mapper.js';\nimport { nodeStyle, FONT } from '../export/styles.js';\n\n/**\n * Renders the full body of a {@link SubnetDef} with the interface ports\n * restyled. The output is a post-processed version of {@link dotExport}: the\n * port-place node attribute lines are rewritten so the post-processor swaps in\n * the {@code interface-port} category, channel-transition lines swap in\n * {@code sync-channel}, and an extra {@code subgraph cluster_iface_<name>}\n * block is appended that lists the port nodes by id — overriding their\n * cluster membership without re-emitting them (DOT semantics: a node\n * referenced inside a subgraph block by id alone is treated as a member of\n * that cluster).\n */\nexport function fullBody(def: SubnetDef<unknown>): string {\n let dot = dotExport(def.body);\n const portIds = portNodeIds(def.iface);\n const channelIds = channelNodeIds(def.iface);\n\n // Restyle: rewrite the matched node lines so the post-processor swaps in\n // the interface-port and sync-channel categories. The renderer emits each\n // node as one line ending with `];` so a per-id rewrite is sufficient and\n // deterministic.\n for (const portId of portIds) {\n dot = restyleNode(dot, portId, nodeStyle('interface-port'));\n }\n for (const chId of channelIds) {\n dot = restyleNode(dot, chId, nodeStyle('sync-channel'));\n }\n\n // Wrap the port and channel ids in a header cluster so the doclet's viewer\n // can render the boundary distinctly. Done by injecting an extra subgraph\n // block before the closing `}` of the digraph.\n if (portIds.size > 0 || channelIds.size > 0) {\n let iface = '';\n iface += ` subgraph cluster_iface_${sanitize(def.name)} {\\n`;\n iface += ` label=\"interface\";\\n`;\n iface += ` style=\"rounded,dashed\";\\n`;\n iface += ` bgcolor=\"#F0F6FF\";\\n`;\n iface += ` penwidth=2;\\n`;\n for (const id of portIds) {\n iface += ` ${id};\\n`;\n }\n for (const id of channelIds) {\n iface += ` ${id};\\n`;\n }\n iface += ` }\\n`;\n\n const lastBrace = dot.lastIndexOf('}');\n if (lastBrace > 0) {\n dot = dot.substring(0, lastBrace) + iface + dot.substring(lastBrace);\n }\n }\n return dot;\n}\n\n/**\n * Renders an interface-only diagram: just the port places + channel\n * transitions + a single stub node labelled \"(internals omitted)\" so the\n * reader knows the body has not been drawn.\n */\nexport function interfaceOnly(def: SubnetDef<unknown>): string {\n const sb: string[] = [];\n sb.push(`digraph ${sanitize(def.name)} {\\n`);\n sb.push(` rankdir=LR;\\n`);\n sb.push(` nodesep=0.5;\\n`);\n sb.push(` ranksep=0.6;\\n`);\n sb.push(` fontname=\"${FONT.family}\";\\n`);\n sb.push(` node [fontname=\"${FONT.family}\", fontsize=12];\\n`);\n sb.push(` edge [fontname=\"${FONT.family}\", fontsize=10];\\n`);\n sb.push('\\n');\n\n const stubId = 'stub_' + sanitize(def.name);\n\n // Group ports by direction: inputs on the left, outputs on the right,\n // inouts above. This keeps the interface diagram visually structured even\n // when the body internals are absent.\n const inputs: Port<unknown>[] = [];\n const outputs: Port<unknown>[] = [];\n const inouts: Port<unknown>[] = [];\n for (const port of def.iface.ports.values()) {\n switch (port.direction) {\n case 'input': inputs.push(port); break;\n case 'output': outputs.push(port); break;\n case 'inout': inouts.push(port); break;\n }\n }\n\n // Emit port nodes.\n for (const port of inputs) emitPortNode(sb, port, 'input');\n for (const port of outputs) emitPortNode(sb, port, 'output');\n for (const port of inouts) emitPortNode(sb, port, 'inout');\n\n // Emit channel nodes.\n const syncStyle = nodeStyle('sync-channel');\n for (const ch of def.iface.channels.values()) {\n sb.push(` ${channelNodeId(ch)} [`);\n sb.push(`label=\"⇄ ${escapeLabel(ch.name)}\", `);\n sb.push(`shape=${syncStyle.shape}, `);\n sb.push(`style=\"filled\", `);\n sb.push(`fillcolor=\"${syncStyle.fill}\", `);\n sb.push(`color=\"${syncStyle.stroke}\", `);\n sb.push(`penwidth=${trimNumber(syncStyle.penwidth)}`);\n sb.push(`];\\n`);\n }\n\n // Emit stub node.\n sb.push(` ${stubId} [`);\n sb.push(`label=\"${escapeLabel(def.name)}\\\\n(internals omitted)\", `);\n sb.push(`shape=box, style=\"filled,dashed,rounded\", `);\n sb.push(`fillcolor=\"#FFFFFF\", color=\"#666666\", penwidth=1, `);\n sb.push(`fontsize=11`);\n sb.push(`];\\n`);\n sb.push('\\n');\n\n // Connect inputs -> stub -> outputs; inouts and channels link both ways.\n for (const port of inputs) {\n sb.push(` ${portNodeId(port)} -> ${stubId} [color=\"#1d4ed8\", arrowhead=normal];\\n`);\n }\n for (const port of outputs) {\n sb.push(` ${stubId} -> ${portNodeId(port)} [color=\"#1d4ed8\", arrowhead=normal];\\n`);\n }\n for (const port of inouts) {\n sb.push(` ${portNodeId(port)} -> ${stubId} [color=\"#1d4ed8\", arrowhead=normalnormal, dir=both];\\n`);\n }\n for (const ch of def.iface.channels.values()) {\n sb.push(` ${channelNodeId(ch)} -> ${stubId} [color=\"#6d28d9\", arrowhead=normal, style=dashed];\\n`);\n }\n\n sb.push(`}\\n`);\n return sb.join('');\n}\n\n// ============================================================\n// Helpers\n// ============================================================\n\nfunction emitPortNode(sb: string[], port: Port<unknown>, direction: string): void {\n const style = nodeStyle('interface-port');\n // Token type isn't carried at runtime in TypeScript (generics erased), so\n // the xlabel is the direction marker only — Java additionally appends the\n // port's tokenType simple name. The structural content is otherwise\n // identical.\n sb.push(` ${portNodeId(port)} [`);\n sb.push(`label=\"${escapeLabel(port.name)}\", `);\n sb.push(`xlabel=\"${escapeLabel(direction)}\", `);\n sb.push(`shape=${style.shape}, `);\n sb.push(`style=\"filled,${style.style ?? 'solid'}\", `);\n sb.push(`fillcolor=\"${style.fill}\", `);\n sb.push(`color=\"${style.stroke}\", `);\n sb.push(`penwidth=${trimNumber(style.penwidth)}`);\n if (style.width !== undefined) {\n sb.push(`, width=${trimNumber(style.width)}, fixedsize=true`);\n }\n sb.push(`];\\n`);\n}\n\nfunction portNodeId(port: Port<unknown>): string {\n return 'p_' + sanitize(port.place.name);\n}\n\nfunction channelNodeId(ch: Channel): string {\n return 't_' + sanitize(ch.transition.name);\n}\n\nfunction portNodeIds(iface: Interface): Set<string> {\n const ids = new Set<string>();\n for (const port of iface.ports.values()) ids.add(portNodeId(port));\n return ids;\n}\n\nfunction channelNodeIds(iface: Interface): Set<string> {\n const ids = new Set<string>();\n for (const ch of iface.channels.values()) ids.add(channelNodeId(ch));\n return ids;\n}\n\n/**\n * Rewrites the {@code fillcolor=}, {@code color=}, {@code style=}, and\n * {@code penwidth=} attributes of the line declaring the given node id so\n * the supplied style takes precedence. Operates as a plain text rewrite:\n * the {@link import('../export/dot-renderer.js').renderDot} output formats\n * each node line deterministically, so a per-attribute regex is reliable.\n */\nfunction restyleNode(\n dot: string,\n nodeId: string,\n style: ReturnType<typeof nodeStyle>,\n): string {\n // The node line begins with: <id> [\n // and ends with: ];\n // Find the line, then surgically swap fillcolor / color / style / penwidth.\n const marker = nodeId + ' [';\n let start = dot.indexOf('\\n ' + marker);\n if (start < 0) start = dot.indexOf(' ' + marker);\n if (start < 0) return dot;\n // Skip the leading newline if present so we operate on the line proper.\n if (dot.charAt(start) === '\\n') start += 1;\n const lineEnd = dot.indexOf('];', start);\n if (lineEnd < 0) return dot;\n let line = dot.substring(start, lineEnd + 2);\n\n // Order matters: keep `fillcolor` before `color` so the substring search\n // for `color=\"` doesn't accidentally hit `fillcolor=\"`.\n line = replaceAttr(line, 'fillcolor', style.fill);\n line = replaceAttr(line, 'color', style.stroke);\n line = replaceAttr(line, 'penwidth', trimNumber(style.penwidth));\n if (style.style !== undefined) {\n // Compose with `filled` because the renderer already includes that.\n line = replaceAttr(line, 'style', `\"filled,${style.style}\"`);\n }\n\n return dot.substring(0, start) + line + dot.substring(lineEnd + 2);\n}\n\n/**\n * Replaces the value of a {@code key=value} pair within a single DOT\n * attribute list. Quotes the value when the existing form was quoted, leaves\n * the value unquoted when the existing form was unquoted (numeric).\n */\nfunction replaceAttr(line: string, key: string, value: string): string {\n // Match key=\"...\" (quoted) — the key must be preceded by a non-word char\n // (start, comma, or space) so `color=` doesn't match inside `fillcolor=`.\n const quotedPat = key + '=\"';\n const qStart = findAttrStart(line, quotedPat);\n if (qStart >= 0) {\n const valStart = qStart + quotedPat.length;\n const valEnd = line.indexOf('\"', valStart);\n if (valEnd < 0) return line;\n let v = value;\n if (v.startsWith('\"') && v.endsWith('\"') && v.length >= 2) {\n v = v.substring(1, v.length - 1);\n }\n return line.substring(0, valStart) + v + line.substring(valEnd);\n }\n // Match key=NN (numeric, ends at ',' or ']').\n const unquoted = key + '=';\n const uStart = findAttrStart(line, unquoted);\n if (uStart < 0) return line;\n const valStart = uStart + unquoted.length;\n let valEnd = valStart;\n while (valEnd < line.length) {\n const c = line.charAt(valEnd);\n if (c === ',' || c === ']') break;\n valEnd++;\n }\n return line.substring(0, valStart) + value + line.substring(valEnd);\n}\n\n/**\n * Finds the start of a `key=` attribute pattern in `line`, ensuring the\n * match is not a suffix of another attribute name (e.g. `color=` must not\n * match inside `fillcolor=`). Returns -1 when no boundary-respecting match\n * exists.\n */\nfunction findAttrStart(line: string, pattern: string): number {\n let from = 0;\n while (from < line.length) {\n const idx = line.indexOf(pattern, from);\n if (idx < 0) return -1;\n if (idx === 0) return idx;\n const prev = line.charAt(idx - 1);\n // Word-boundary check: previous char must not be alphanumeric/underscore.\n if (!/[A-Za-z0-9_]/.test(prev)) return idx;\n from = idx + 1;\n }\n return -1;\n}\n\nfunction trimNumber(d: number): string {\n if (Number.isInteger(d)) return String(d);\n return String(d);\n}\n\nfunction escapeLabel(s: string): string {\n return s.replace(/\\\\/g, '\\\\\\\\').replace(/\"/g, '\\\\\"');\n}\n","/**\n * SVG renderer using `@viz-js/viz` (Graphviz WASM).\n *\n * Pure WASM — zero external dependencies. No system `dot` binary required.\n *\n * Mirrors: Java's `PetriNetTaglet.dotToSvg()` (which uses `dot -Tsvg` subprocess)\n *\n * @module doclet/svg-renderer\n */\n\n/**\n * Renders a DOT string to SVG using the `@viz-js/viz` WASM engine.\n *\n * Strips the XML prolog/DOCTYPE and explicit width/height attributes so the SVG\n * scales via viewBox + CSS instead of overriding with fixed pt sizes.\n *\n * @param dot - the DOT source string\n * @returns SVG markup string\n * @throws if `@viz-js/viz` is not installed or DOT parsing fails\n */\nexport async function dotToSvg(dot: string): Promise<string> {\n // Dynamic import — @viz-js/viz is an optional peer dependency\n const { instance } = await import('@viz-js/viz');\n const viz = await instance();\n let svg = viz.renderString(dot, { format: 'svg', engine: 'dot' });\n\n // Strip XML prolog and DOCTYPE — invalid inside HTML5\n const svgStart = svg.indexOf('<svg');\n if (svgStart > 0) svg = svg.substring(svgStart);\n\n // Strip explicit width/height attributes (e.g. \"1942pt\") so the SVG\n // scales via viewBox + CSS instead of overriding with fixed pt sizes\n svg = svg.replace(/\\s+(?:width|height)=\"[^\"]*\"/g, '');\n\n return svg;\n}\n"],"mappings":"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","names":["dirname","interfaceOnly","valStart","valEnd","interfaceOnly","dotExport"]}
1
+ {"version":3,"sources":["../../src/doclet/petri-net-plugin.ts","../../src/doclet/net-resolver.ts","../../src/doclet/diagram-renderer.ts","../../src/doclet/subnet-header.ts","../../src/doclet/subnet-dot-export.ts","../../src/doclet/svg-renderer.ts"],"sourcesContent":["/**\n * TypeDoc plugin for `@petrinet` and `@subnet` tags — auto-generates\n * interactive SVG diagrams from PetriNet / SubnetDef / Instance definitions\n * and embeds them in TypeDoc output.\n *\n * Mirrors: `org.libpetri.doclet.PetriNetTaglet` + `SubnetTaglet`\n *\n * Plugin lifecycle (TypeDoc hooks):\n * 1. `bootstrapEnd` → register `@petrinet` and `@subnet` as known block tags.\n * 2. `preRenderAsyncJobs` → walk reflections, resolve references, generate\n * SVGs, cache HTML.\n * 3. `comment.beforeTags` → inject cached HTML via `JSX.Raw`, skip default\n * rendering for the recognised tags.\n *\n * @module doclet/petri-net-plugin\n */\n\nimport {\n type Application,\n type CommentTag,\n type DeclarationReflection,\n type ProjectReflection,\n type Reflection,\n JSX,\n} from 'typedoc';\nimport { resolveNet, type ResolvedNet } from './net-resolver.js';\nimport { renderSvg, renderSubnetSvg, escapeHtml } from './diagram-renderer.js';\nimport { forSubnetDef, forInstance } from './subnet-header.js';\nimport { fullBody as subnetFullBody, interfaceOnly as subnetInterfaceOnly } from './subnet-dot-export.js';\n\n// Lazy import to avoid requiring libpetri/export at module level.\nasync function getDotExport(): Promise<typeof import('../export/dot-exporter.js').dotExport> {\n const mod = await import('../export/dot-exporter.js');\n return mod.dotExport;\n}\n\n/** Block tag for full body diagrams — resolves PetriNet / SubnetDef / Instance. */\nconst TAG_PETRINET = '@petrinet' as `@${string}`;\n/** Block tag for interface-only subnet diagrams. */\nconst TAG_SUBNET = '@subnet' as `@${string}`;\n\n/** Cache: reflection ID → rendered HTML string */\nconst htmlCache = new Map<number, string>();\n\n/**\n * Loads the petri-net plugin into the TypeDoc application.\n *\n * @param app - the TypeDoc Application instance\n */\nexport function load(app: Application): void {\n // 1. Register both tags as known block tags at bootstrap time.\n app.on('bootstrapEnd', () => {\n const blockTags = app.options.getValue('blockTags') as string[];\n const additions: string[] = [];\n if (!blockTags.includes(TAG_PETRINET)) additions.push(TAG_PETRINET);\n if (!blockTags.includes(TAG_SUBNET)) additions.push(TAG_SUBNET);\n if (additions.length > 0) {\n app.options.setValue('blockTags', [...blockTags, ...additions]);\n }\n });\n\n // 2. Resolve references and generate SVG during pre-render async phase.\n app.renderer.preRenderAsyncJobs.push(async (output) => {\n htmlCache.clear();\n await processProject(app, output.project);\n });\n\n // 3. Inject cached HTML into comment output, suppress default tag rendering.\n app.renderer.hooks.on('comment.beforeTags', (_context, comment, reflection) => {\n const cached = htmlCache.get(reflection.id);\n if (!cached) return JSX.createElement(JSX.Raw, { html: '' });\n\n // Mark our tags as skip so TypeDoc doesn't render them as plain text.\n for (const tag of comment.blockTags) {\n if (tag.tag === TAG_PETRINET || tag.tag === TAG_SUBNET) {\n tag.skipRendering = true;\n }\n }\n\n return JSX.createElement(JSX.Raw, { html: cached });\n });\n}\n\n/**\n * Walks all reflections in the project and processes `@petrinet` / `@subnet`\n * tags. Per-reflection HTML accumulates in {@link htmlCache} so multiple tags\n * on one symbol render in declaration order.\n */\nasync function processProject(app: Application, project: ProjectReflection): Promise<void> {\n const reflections = Object.values(project.reflections);\n\n for (const reflection of reflections) {\n const comment = reflection.comment;\n if (!comment) continue;\n\n for (const tag of comment.blockTags) {\n const isPetrinet = tag.tag === TAG_PETRINET;\n const isSubnet = tag.tag === TAG_SUBNET;\n if (!isPetrinet && !isSubnet) continue;\n\n const reference = tagContent(tag);\n try {\n const html = isSubnet\n ? await generateInterfaceOnly(reference, reflection)\n : await generateDiagram(reference, reflection);\n const existing = htmlCache.get(reflection.id) ?? '';\n htmlCache.set(reflection.id, existing + html);\n } catch (e) {\n const msg = e instanceof Error ? e.message : String(e);\n const tagName = isSubnet ? '@subnet' : '@petrinet';\n app.logger.warn(`${tagName} error for ${reflection.getFriendlyFullName()}: ${msg}`);\n const html = errorHtml(`Error generating diagram for '${reference}': ${msg}`);\n const existing = htmlCache.get(reflection.id) ?? '';\n htmlCache.set(reflection.id, existing + html);\n }\n }\n }\n}\n\n/**\n * Generates a diagram HTML string for a single `@petrinet` reference. Resolves\n * the reference to one of {@link ResolvedNet} variants and dispatches to the\n * matching renderer.\n */\nasync function generateDiagram(\n reference: string,\n reflection: Reflection,\n): Promise<string> {\n const trimmed = reference.trim();\n if (!trimmed) {\n return errorHtml(`Empty @petrinet reference on ${reflection.getFriendlyFullName()}`);\n }\n\n const sourceFile = getSourceFilePath(reflection);\n if (!sourceFile) {\n return errorHtml(`Cannot determine source file for ${reflection.getFriendlyFullName()}`);\n }\n\n const resolved = await resolveNet(trimmed, sourceFile);\n if (!resolved) {\n return errorHtml(`Cannot resolve PetriNet/SubnetDef/Instance: ${trimmed}`);\n }\n\n return await renderResolved(resolved, /* interfaceOnly */ false);\n}\n\n/**\n * Generates an interface-only diagram for a `@subnet` reference. When the\n * resolved reference is not a {@link import('../core/subnet-def.js').SubnetDef},\n * falls back to the regular diagram so the tag still produces useful output.\n */\nasync function generateInterfaceOnly(\n reference: string,\n reflection: Reflection,\n): Promise<string> {\n const trimmed = reference.trim();\n if (!trimmed) {\n return errorHtml(`Empty @subnet reference on ${reflection.getFriendlyFullName()}`);\n }\n\n const sourceFile = getSourceFilePath(reflection);\n if (!sourceFile) {\n return errorHtml(`Cannot determine source file for ${reflection.getFriendlyFullName()}`);\n }\n\n const resolved = await resolveNet(trimmed, sourceFile);\n if (!resolved) {\n return errorHtml(`Cannot resolve SubnetDef: ${trimmed}`);\n }\n\n return await renderResolved(resolved, /* interfaceOnly */ true);\n}\n\n/**\n * Dispatches a {@link ResolvedNet} to the matching renderer. The\n * `interfaceOnly` flag is honoured only when the resolved kind is `subnet` —\n * for `net` and `instance` it is ignored (the body is the whole point of\n * those references).\n */\nasync function renderResolved(\n resolved: ResolvedNet,\n interfaceOnly: boolean,\n): Promise<string> {\n switch (resolved.kind) {\n case 'net': {\n const dotExport = await getDotExport();\n const dot = dotExport(resolved.value);\n return renderDiagramHtml(resolved.title, dot, /* header */ null);\n }\n case 'subnet': {\n const dot = interfaceOnly\n ? subnetInterfaceOnly(resolved.value)\n : subnetFullBody(resolved.value);\n const header = forSubnetDef(resolved.value, interfaceOnly);\n const title = resolved.value.name + (interfaceOnly ? ' (interface)' : '');\n return renderDiagramHtml(title, dot, header);\n }\n case 'instance': {\n const dotExport = await getDotExport();\n const dot = dotExport(resolved.value.renamedBody);\n const header = forInstance(resolved.value);\n return renderDiagramHtml(resolved.title, dot, header);\n }\n }\n}\n\n/**\n * Emits the diagram HTML. The canonical viewer renders DOT to SVG\n * client-side via Graphviz-WASM, so no server-side SVG rendering happens\n * here. Routes through the subnet-aware renderer when `header` is non-null,\n * the plain renderer otherwise.\n */\nfunction renderDiagramHtml(\n title: string,\n dot: string,\n header: string | null,\n): string {\n if (header !== null) {\n return renderSubnetSvg(title, header, dot);\n }\n return renderSvg(title, dot);\n}\n\n/**\n * Extracts text content from a CommentTag.\n */\nfunction tagContent(tag: CommentTag): string {\n return tag.content\n .map((part) => part.text)\n .join('')\n .trim();\n}\n\n/**\n * Gets the source file path from a reflection. Walks parents because\n * comment-bearing reflections sometimes lack their own `sources` (e.g.\n * inherited members).\n */\nfunction getSourceFilePath(reflection: Reflection): string | null {\n const decl = reflection as DeclarationReflection;\n if (decl.sources && decl.sources.length > 0) {\n return decl.sources[0]!.fullFileName;\n }\n if (reflection.parent) {\n return getSourceFilePath(reflection.parent);\n }\n return null;\n}\n\n/**\n * Renders an error message as styled HTML.\n */\nfunction errorHtml(message: string): string {\n return `<div class=\"petrinet-error\" style=\"color: #dc3545; border: 1px solid #dc3545; padding: 10px; border-radius: 4px;\">\n<strong>@petrinet Error:</strong> ${escapeHtml(message)}\n</div>`;\n}\n","/**\n * Dynamic import-based resolver for `@petrinet` / `@subnet` references.\n *\n * Resolves three kinds of static exports per Java's `PetriNetTaglet` design:\n * 1. `PetriNet` — full body diagram (existing behaviour).\n * 2. `SubnetDef<?>` — body diagram with interface ports highlighted, plus a\n * subnet header with port/channel badges.\n * 3. `Instance<?>` — renamed body diagram (already prefix-clustered by the\n * DOT exporter), plus an instance header with prefix, def name, and\n * params summary.\n *\n * TypeScript cannot use reflection like Java — modules must be importable at\n * doc time (typically from `dist/` after `npm run build`).\n *\n * Tag format:\n * ```\n * @petrinet ./path/to/module#exportName — access a static export\n * @petrinet ./path/to/module#functionName() — call a function returning one\n * @petrinet #localExport — resolve from same file\n * ```\n *\n * Mirrors: `org.libpetri.doclet.PetriNetTaglet.resolveReference`\n *\n * @module doclet/net-resolver\n */\n\nimport { resolve, dirname } from 'node:path';\nimport { pathToFileURL } from 'node:url';\nimport type { PetriNet } from '../core/petri-net.js';\nimport type { SubnetDef } from '../core/subnet-def.js';\nimport type { Instance } from '../core/instance.js';\n\n/**\n * Discriminated-union result from {@link resolveNet}. Each variant carries\n * the resolved value plus a display title.\n */\nexport type ResolvedNet =\n | { readonly kind: 'net'; readonly value: PetriNet; readonly title: string }\n | { readonly kind: 'subnet'; readonly value: SubnetDef<unknown>; readonly title: string }\n | { readonly kind: 'instance'; readonly value: Instance<unknown>; readonly title: string };\n\n/**\n * Parses a `@petrinet` / `@subnet` reference and resolves it via dynamic\n * import. Returns `null` when the reference cannot be parsed or imported, or\n * when the resolved value is none of the three supported kinds.\n *\n * @param reference - the tag content, e.g. `./definition#buildDebugNet()`\n * @param sourceFilePath - absolute path to the source file containing the tag\n */\nexport async function resolveNet(\n reference: string,\n sourceFilePath: string,\n): Promise<ResolvedNet | null> {\n const trimmed = reference.trim().split(/\\s+/)[0] ?? '';\n if (!trimmed) return null;\n\n const hashIndex = trimmed.indexOf('#');\n if (hashIndex === -1) return null;\n\n const modulePath = trimmed.substring(0, hashIndex);\n const exportRef = trimmed.substring(hashIndex + 1);\n if (!exportRef) return null;\n\n const isCall = exportRef.endsWith('()');\n const exportName = isCall ? exportRef.slice(0, -2) : exportRef;\n\n // Resolve module path relative to the source file.\n let absolutePath: string;\n if (modulePath) {\n absolutePath = resolve(dirname(sourceFilePath), modulePath);\n } else {\n // `#localExport` — resolve from same file's compiled output.\n absolutePath = sourceFilePath;\n }\n\n // Try `.js` extension for compiled output, then `.ts` for ts-node scenarios.\n const candidates = [\n absolutePath,\n absolutePath + '.js',\n absolutePath + '.ts',\n absolutePath.replace(/\\.ts$/, '.js'),\n ];\n\n let mod: Record<string, unknown> | undefined;\n for (const candidate of candidates) {\n try {\n mod = await import(pathToFileURL(candidate).href) as Record<string, unknown>;\n break;\n } catch {\n // Try next candidate.\n }\n }\n\n if (!mod) return null;\n\n const exported = mod[exportName];\n if (exported == null) return null;\n\n let value: unknown;\n if (isCall) {\n if (typeof exported !== 'function') return null;\n const result = (exported as (...args: unknown[]) => unknown)();\n // Support functions that return `{ net: PetriNet, ... }` for legacy\n // callers; otherwise pass the result through directly.\n if (\n result !== null && typeof result === 'object' && 'net' in result &&\n isPetriNetLike((result as { net: unknown }).net)\n ) {\n value = (result as { net: unknown }).net;\n } else {\n value = result;\n }\n } else {\n value = exported;\n }\n\n return classify(value);\n}\n\n/**\n * Wraps the resolved value into a {@link ResolvedNet} variant by structural\n * shape — checks {@link Instance} before {@link SubnetDef} before\n * {@link PetriNet} so the most specific match wins (Instance has both `def`\n * and `renamedBody`; SubnetDef has `iface` and `body`; PetriNet has only\n * `transitions` + `name`).\n *\n * Returns `null` when the value matches none of the supported shapes.\n */\nfunction classify(value: unknown): ResolvedNet | null {\n if (value === null || typeof value !== 'object') return null;\n\n if (isInstanceLike(value)) {\n const inst = value as Instance<unknown>;\n return { kind: 'instance', value: inst, title: `${inst.def.name} :: ${inst.prefix}` };\n }\n if (isSubnetDefLike(value)) {\n const def = value as SubnetDef<unknown>;\n return { kind: 'subnet', value: def, title: def.name };\n }\n if (isPetriNetLike(value)) {\n const net = value as PetriNet;\n return { kind: 'net', value: net, title: net.name };\n }\n return null;\n}\n\n/**\n * Structural test for {@link PetriNet}: has a string `name` and a `transitions`\n * Set. Avoids `instanceof` because the imported module may have re-bound the\n * class identity (TypeDoc's bundled output vs `dist/` import path).\n */\nfunction isPetriNetLike(value: unknown): value is PetriNet {\n if (value === null || typeof value !== 'object') return false;\n const candidate = value as { name?: unknown; transitions?: unknown };\n return typeof candidate.name === 'string' && candidate.transitions instanceof Set;\n}\n\n/**\n * Structural test for {@link SubnetDef}: has a string `name`, a {@link\n * PetriNet}-shaped `body`, and an `iface` carrying `ports` + `channels` maps.\n */\nfunction isSubnetDefLike(value: unknown): value is SubnetDef<unknown> {\n if (value === null || typeof value !== 'object') return false;\n const candidate = value as { name?: unknown; body?: unknown; iface?: unknown };\n if (typeof candidate.name !== 'string') return false;\n if (!isPetriNetLike(candidate.body)) return false;\n const iface = candidate.iface as { ports?: unknown; channels?: unknown } | undefined;\n return iface !== undefined && iface !== null\n && iface.ports instanceof Map && iface.channels instanceof Map;\n}\n\n/**\n * Structural test for {@link Instance}: has a string `prefix`, a {@link\n * SubnetDef}-shaped `def`, and a {@link PetriNet}-shaped `renamedBody`.\n */\nfunction isInstanceLike(value: unknown): value is Instance<unknown> {\n if (value === null || typeof value !== 'object') return false;\n const candidate = value as { prefix?: unknown; def?: unknown; renamedBody?: unknown };\n return typeof candidate.prefix === 'string'\n && isSubnetDefLike(candidate.def)\n && isPetriNetLike(candidate.renamedBody);\n}\n","/**\n * Shared HTML renderer for Petri Net diagrams in TypeDoc.\n *\n * Generates consistent HTML markup that mounts the canonical\n * {@link https://libpetri.org | libpetri} viewer client-side. The viewer IIFE\n * bundle (Graphviz WASM inlined, offline-safe) is inlined into the generated\n * HTML, making the plugin fully self-contained with no external file\n * dependencies.\n *\n * Each diagram container carries its DOT source as a `data-dot` attribute. An\n * init snippet reads the attribute and calls\n * `window.LibpetriViewer.mount(dot, container, { chrome: true })` to render the\n * SVG and wire pan/zoom + cluster legend/filter chrome.\n *\n * Mirrors: `org.libpetri.doclet.DiagramRenderer`\n *\n * @module doclet/diagram-renderer\n */\n\nimport { readFileSync } from 'node:fs';\nimport { fileURLToPath } from 'node:url';\nimport { dirname, join } from 'node:path';\n\nconst __dirname = dirname(fileURLToPath(import.meta.url));\n\nfunction loadResource(filename: string): string {\n return readFileSync(join(__dirname, 'resources', filename), 'utf-8');\n}\n\nlet inlineCss: string | undefined;\nlet inlineJs: string | undefined;\n\nfunction css(): string {\n return (inlineCss ??= loadResource('petrinet-diagrams.css'));\n}\n\nfunction js(): string {\n return (inlineJs ??= loadResource('petrinet-diagrams.js'));\n}\n\n/**\n * Escapes HTML special characters for safe embedding as text content.\n */\nexport function escapeHtml(text: string): string {\n return text\n .replace(/&/g, '&amp;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;')\n .replace(/\"/g, '&quot;');\n}\n\n/**\n * Escapes a string for safe inclusion in an HTML attribute value enclosed in\n * double quotes. Identical to {@link escapeHtml} plus single-quote escaping\n * (defence-in-depth even though we always emit double-quoted attributes).\n */\nfunction escapeAttr(text: string): string {\n return text\n .replace(/&/g, '&amp;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;')\n .replace(/\"/g, '&quot;')\n .replace(/'/g, '&#39;');\n}\n\n/**\n * Renders a diagram from a DOT source string. The viewer renders the SVG\n * client-side via Graphviz-WASM; no server-side SVG is emitted on the\n * embedded path.\n *\n * Inlines CSS and the viewer IIFE from bundled resources. The IIFE is guarded\n * by an idempotency check (`window._libpetriViewerInit`) so it only executes\n * once per page even when multiple `@petrinet` tags appear.\n *\n * @param title - optional title (null/undefined for no title)\n * @param dotSource - the DOT source code; embedded as `data-dot` and shown in\n * the collapsible \"View DOT Source\" block\n * @returns HTML markup with diagram controls\n */\nexport function renderSvg(\n title: string | null | undefined,\n dotSource: string,\n): string {\n return renderInternal(title ?? null, /* headerHtml */ null, dotSource);\n}\n\n/**\n * Renders a subnet- or instance-aware diagram. Identical to {@link renderSvg}\n * apart from an extra {@code headerHtml} block (the port/channel/params\n * badge bar) prepended above the diagram, and the CSS class\n * {@code subnet-diagram} on the wrapper.\n *\n * @param title diagram title (e.g. \"BoundedBuffer :: b1\")\n * @param headerHtml the HTML produced by {@link import('./subnet-header.js')}\n * @param dotSource the DOT source code\n * @returns HTML markup\n */\nexport function renderSubnetSvg(\n title: string | null | undefined,\n headerHtml: string,\n dotSource: string,\n): string {\n return renderInternal(title ?? null, headerHtml, dotSource);\n}\n\n/**\n * The client-side init snippet for one diagram container.\n *\n * Byte-for-byte the same JavaScript the Java taglet and the Rust docgen emit\n * (`DiagramRenderer.java`, `diagram_renderer.rs`) — the three generators must\n * stay in lockstep so a diagram looks identical whichever port produced the\n * page. Three things it has to get right:\n *\n * - `data-libpetri-viewer` records the bundle version, so a page rendered by\n * an old viewer can be identified without comparing pixels.\n * - `mount()` is async, so its rejection needs a `.catch`. A bare `try/catch`\n * around the call only sees synchronous throws and leaves a failed render as\n * an empty box plus an unhandled rejection.\n * - the poll for the bundle is bounded. An unbounded retry on a bundle that\n * never loads is indistinguishable from a diagram that simply has no edges.\n */\nfunction mountScript(containerId: string): string {\n const id = JSON.stringify(containerId);\n return `(function(){function fail(el,e){console.error('[libpetri] viewer mount failed',e);var p=document.createElement('p');p.className='libpetri-diagram-error';p.textContent='Diagram render failed: '+(e&&e.message?e.message:e);el.textContent='';el.appendChild(p);}var tries=0;function mount(){var el=document.getElementById(${id});if(!el)return;if(el.dataset.libpetriMounted)return;if(!window.LibpetriViewer||typeof window.LibpetriViewer.mount!=='function'){if(++tries>100){return fail(el,new Error('viewer bundle did not load'));}return setTimeout(mount,30);}el.dataset.libpetriMounted='1';el.dataset.libpetriViewer=window.LibpetriViewer.VERSION||'unknown';try{Promise.resolve(window.LibpetriViewer.mount(el.dataset.dot,el,{chrome:true})).catch(function(e){fail(el,e);});}catch(e){fail(el,e);}}if(document.readyState==='loading'){document.addEventListener('DOMContentLoaded',mount);}else{mount();}})();`;\n}\n\nlet diagramIdCounter = 0;\n\nfunction renderInternal(\n title: string | null,\n headerHtml: string | null,\n dotSource: string,\n): string {\n const titleHtml = title !== null ? `<h4>${escapeHtml(title)}</h4>\\n` : '';\n const summaryText = title !== null ? 'View DOT Source' : 'View Source';\n const diagramClass = headerHtml !== null ? 'petrinet-diagram subnet-diagram' : 'petrinet-diagram';\n const headerBlock = headerHtml !== null ? headerHtml : '';\n const dotAttr = escapeAttr(dotSource);\n const containerId = `libpetri-diagram-${++diagramIdCounter}`;\n\n return `<style>${css()}</style>\n<script>if(!window._libpetriViewerInit){window._libpetriViewerInit=true;\n${js()}\n}</script>\n<div class=\"${diagramClass}\">\n${titleHtml}${headerBlock}<div class=\"diagram-container\" id=\"${containerId}\" data-libpetri-diagram=\"true\" data-dot=\"${dotAttr}\"></div>\n<details>\n<summary>${summaryText}</summary>\n<pre><code>${escapeHtml(dotSource)}</code></pre>\n</details>\n</div>\n<script>${mountScript(containerId)}</script>`;\n}\n","/**\n * HTML-fragment generator for the small \"subnet header\" badge bar that the\n * {@link import('./diagram-renderer.js')} layers above subnet diagrams.\n *\n * For a {@link import('../core/subnet-def.js').SubnetDef} the header lists\n * the subnet name, the parameter type (best-effort — TypeScript erases\n * generics at runtime so the slot is shown as a placeholder), and one badge\n * per port and channel — colour-coded by direction\n * (`interface-port-input` / `-output` / `-inout` CSS classes, plus\n * `interface-channel` for sync channels).\n *\n * For an {@link import('../core/instance.js').Instance} the header lists the\n * instance prefix, the originating definition's name, and the {@link\n * import('../core/instance.js').Instance#params} summary.\n *\n * Mirrors: `org.libpetri.doclet.SubnetHeader`\n *\n * @module doclet/subnet-header\n */\n\nimport type { Instance } from '../core/instance.js';\nimport type { Interface, Port } from '../core/interface.js';\nimport type { SubnetDef } from '../core/subnet-def.js';\nimport { escapeHtml } from './diagram-renderer.js';\n\n/**\n * Builds the header HTML for a {@link SubnetDef} field.\n *\n * @param def the subnet definition\n * @param interfaceOnly when `true`, the header carries the \"(interface only)\"\n * badge so the reader knows the body is intentionally\n * omitted\n */\nexport function forSubnetDef(def: SubnetDef<unknown>, interfaceOnly: boolean): string {\n const sb: string[] = [];\n sb.push(`<div class=\"subnet-header\">\\n`);\n sb.push(` <div class=\"subnet-header-row\">\\n`);\n sb.push(` <span class=\"subnet-header-label\">subnet</span>\\n`);\n sb.push(` <span class=\"subnet-header-name\">${escapeHtml(def.name)}</span>\\n`);\n // TypeScript erases generics at runtime, so the parameter-type slot is\n // populated with a placeholder (\"?\"). Java emits the simple class name —\n // the structural HTML is otherwise byte-equivalent.\n sb.push(` <span class=\"subnet-header-param\">&lt;?&gt;</span>\\n`);\n if (interfaceOnly) {\n sb.push(` <span class=\"subnet-header-badge subnet-header-badge-interface\">interface only</span>\\n`);\n }\n sb.push(` </div>\\n`);\n sb.push(renderPortChannelBadges(def.iface));\n sb.push(`</div>\\n`);\n return sb.join('');\n}\n\n/**\n * Builds the header HTML for an {@link Instance} field.\n */\nexport function forInstance(instance: Instance<unknown>): string {\n const def = instance.def;\n const sb: string[] = [];\n sb.push(`<div class=\"subnet-header\">\\n`);\n sb.push(` <div class=\"subnet-header-row\">\\n`);\n sb.push(` <span class=\"subnet-header-label\">instance</span>\\n`);\n sb.push(` <span class=\"subnet-header-name\">${escapeHtml(instance.prefix)}</span>\\n`);\n sb.push(` <span class=\"subnet-header-of\">of</span>\\n`);\n sb.push(` <span class=\"subnet-header-name\">${escapeHtml(def.name)}</span>\\n`);\n const params = instance.params;\n if (params !== null && params !== undefined) {\n sb.push(` <span class=\"subnet-header-params\">params=${escapeHtml(String(params))}</span>\\n`);\n }\n sb.push(` </div>\\n`);\n sb.push(renderPortChannelBadges(def.iface));\n sb.push(`</div>\\n`);\n return sb.join('');\n}\n\n/**\n * Renders the per-port and per-channel badge row. Each badge carries a CSS\n * class identifying its kind so the stylesheet can colour-code by direction.\n */\nfunction renderPortChannelBadges(iface: Interface): string {\n if (iface.ports.size === 0 && iface.channels.size === 0) {\n return '';\n }\n const sb: string[] = [];\n sb.push(` <div class=\"subnet-header-badges\">\\n`);\n for (const port of iface.ports.values()) {\n const kind = portKind(port);\n sb.push(` <span class=\"interface-port-badge interface-port-badge-${kind}\">`);\n sb.push(`<span class=\"badge-direction\">${kind}</span>`);\n sb.push(`<span class=\"badge-name\">${escapeHtml(port.name)}</span>`);\n // TypeScript erases generics so we can't print the runtime token type;\n // omit the type-name span rather than emit a bogus value.\n sb.push(`</span>\\n`);\n }\n for (const channel of iface.channels.values()) {\n sb.push(` <span class=\"interface-channel-badge\">`);\n sb.push(`<span class=\"badge-direction\">channel</span>`);\n sb.push(`<span class=\"badge-name\">${escapeHtml(channel.name)}</span>`);\n sb.push(`</span>\\n`);\n }\n sb.push(` </div>\\n`);\n return sb.join('');\n}\n\nfunction portKind(port: Port<unknown>): 'input' | 'output' | 'inout' {\n return port.direction;\n}\n","/**\n * DOT generators for the subnet-aware diagrams the {@link\n * import('./petri-net-plugin.js')} plugin draws.\n *\n * Two flavours:\n * - {@link fullBody} — the subnet's complete body, but with its interface-port\n * places restyled with the `interface-port` category from\n * `petri-net-styles.json` so they read as boundary elements at a glance. A\n * small `subgraph cluster_iface_*` with label `\"interface\"` surrounds the\n * port places and channel transitions to advertise the subnet boundary\n * visually.\n * - {@link interfaceOnly} — a tiny diagram of just the port places, the\n * channel transitions, and a single stub node indicating the body is\n * omitted. Used by the `@subnet` block tag.\n *\n * This module stays in the doclet directory (rather than living in\n * `export/`) because it is a doclet-presentation concern — the production\n * export pipeline never adds the `interface` cluster.\n *\n * Mirrors: `org.libpetri.doclet.SubnetDotExport`\n *\n * @module doclet/subnet-dot-export\n */\n\nimport type { SubnetDef } from '../core/subnet-def.js';\nimport type { Interface, Port, Channel } from '../core/interface.js';\nimport { dotExport } from '../export/dot-exporter.js';\nimport { sanitize } from '../export/petri-net-mapper.js';\nimport { nodeStyle, FONT } from '../export/styles.js';\n\n/**\n * Renders the full body of a {@link SubnetDef} with the interface ports\n * restyled. The output is a post-processed version of {@link dotExport}: the\n * port-place node attribute lines are rewritten so the post-processor swaps in\n * the {@code interface-port} category, channel-transition lines swap in\n * {@code sync-channel}, and an extra {@code subgraph cluster_iface_<name>}\n * block is appended that lists the port nodes by id — overriding their\n * cluster membership without re-emitting them (DOT semantics: a node\n * referenced inside a subgraph block by id alone is treated as a member of\n * that cluster).\n */\nexport function fullBody(def: SubnetDef<unknown>): string {\n let dot = dotExport(def.body);\n const portIds = portNodeIds(def.iface);\n const channelIds = channelNodeIds(def.iface);\n\n // Restyle: rewrite the matched node lines so the post-processor swaps in\n // the interface-port and sync-channel categories. The renderer emits each\n // node as one line ending with `];` so a per-id rewrite is sufficient and\n // deterministic.\n for (const portId of portIds) {\n dot = restyleNode(dot, portId, nodeStyle('interface-port'));\n }\n for (const chId of channelIds) {\n dot = restyleNode(dot, chId, nodeStyle('sync-channel'));\n }\n\n // Wrap the port and channel ids in a header cluster so the doclet's viewer\n // can render the boundary distinctly. Done by injecting an extra subgraph\n // block before the closing `}` of the digraph.\n if (portIds.size > 0 || channelIds.size > 0) {\n let iface = '';\n iface += ` subgraph cluster_iface_${sanitize(def.name)} {\\n`;\n iface += ` label=\"interface\";\\n`;\n iface += ` style=\"rounded,dashed\";\\n`;\n iface += ` bgcolor=\"#F0F6FF\";\\n`;\n iface += ` penwidth=2;\\n`;\n for (const id of portIds) {\n iface += ` ${id};\\n`;\n }\n for (const id of channelIds) {\n iface += ` ${id};\\n`;\n }\n iface += ` }\\n`;\n\n const lastBrace = dot.lastIndexOf('}');\n if (lastBrace > 0) {\n dot = dot.substring(0, lastBrace) + iface + dot.substring(lastBrace);\n }\n }\n return dot;\n}\n\n/**\n * Renders an interface-only diagram: just the port places + channel\n * transitions + a single stub node labelled \"(internals omitted)\" so the\n * reader knows the body has not been drawn.\n */\nexport function interfaceOnly(def: SubnetDef<unknown>): string {\n const sb: string[] = [];\n sb.push(`digraph ${sanitize(def.name)} {\\n`);\n sb.push(` rankdir=LR;\\n`);\n sb.push(` nodesep=0.5;\\n`);\n sb.push(` ranksep=0.6;\\n`);\n sb.push(` fontname=\"${FONT.family}\";\\n`);\n sb.push(` node [fontname=\"${FONT.family}\", fontsize=12];\\n`);\n sb.push(` edge [fontname=\"${FONT.family}\", fontsize=10];\\n`);\n sb.push('\\n');\n\n const stubId = 'stub_' + sanitize(def.name);\n\n // Group ports by direction: inputs on the left, outputs on the right,\n // inouts above. This keeps the interface diagram visually structured even\n // when the body internals are absent.\n const inputs: Port<unknown>[] = [];\n const outputs: Port<unknown>[] = [];\n const inouts: Port<unknown>[] = [];\n for (const port of def.iface.ports.values()) {\n switch (port.direction) {\n case 'input': inputs.push(port); break;\n case 'output': outputs.push(port); break;\n case 'inout': inouts.push(port); break;\n }\n }\n\n // Emit port nodes.\n for (const port of inputs) emitPortNode(sb, port, 'input');\n for (const port of outputs) emitPortNode(sb, port, 'output');\n for (const port of inouts) emitPortNode(sb, port, 'inout');\n\n // Emit channel nodes.\n const syncStyle = nodeStyle('sync-channel');\n for (const ch of def.iface.channels.values()) {\n sb.push(` ${channelNodeId(ch)} [`);\n sb.push(`label=\"⇄ ${escapeLabel(ch.name)}\", `);\n sb.push(`shape=${syncStyle.shape}, `);\n sb.push(`style=\"filled\", `);\n sb.push(`fillcolor=\"${syncStyle.fill}\", `);\n sb.push(`color=\"${syncStyle.stroke}\", `);\n sb.push(`penwidth=${trimNumber(syncStyle.penwidth)}`);\n sb.push(`];\\n`);\n }\n\n // Emit stub node.\n sb.push(` ${stubId} [`);\n sb.push(`label=\"${escapeLabel(def.name)}\\\\n(internals omitted)\", `);\n sb.push(`shape=box, style=\"filled,dashed,rounded\", `);\n sb.push(`fillcolor=\"#FFFFFF\", color=\"#666666\", penwidth=1, `);\n sb.push(`fontsize=11`);\n sb.push(`];\\n`);\n sb.push('\\n');\n\n // Connect inputs -> stub -> outputs; inouts and channels link both ways.\n for (const port of inputs) {\n sb.push(` ${portNodeId(port)} -> ${stubId} [color=\"#1d4ed8\", arrowhead=normal];\\n`);\n }\n for (const port of outputs) {\n sb.push(` ${stubId} -> ${portNodeId(port)} [color=\"#1d4ed8\", arrowhead=normal];\\n`);\n }\n for (const port of inouts) {\n sb.push(` ${portNodeId(port)} -> ${stubId} [color=\"#1d4ed8\", arrowhead=normalnormal, dir=both];\\n`);\n }\n for (const ch of def.iface.channels.values()) {\n sb.push(` ${channelNodeId(ch)} -> ${stubId} [color=\"#6d28d9\", arrowhead=normal, style=dashed];\\n`);\n }\n\n sb.push(`}\\n`);\n return sb.join('');\n}\n\n// ============================================================\n// Helpers\n// ============================================================\n\nfunction emitPortNode(sb: string[], port: Port<unknown>, direction: string): void {\n const style = nodeStyle('interface-port');\n // Token type isn't carried at runtime in TypeScript (generics erased), so\n // the xlabel is the direction marker only — Java additionally appends the\n // port's tokenType simple name. The structural content is otherwise\n // identical.\n sb.push(` ${portNodeId(port)} [`);\n sb.push(`label=\"${escapeLabel(port.name)}\", `);\n sb.push(`xlabel=\"${escapeLabel(direction)}\", `);\n sb.push(`shape=${style.shape}, `);\n sb.push(`style=\"filled,${style.style ?? 'solid'}\", `);\n sb.push(`fillcolor=\"${style.fill}\", `);\n sb.push(`color=\"${style.stroke}\", `);\n sb.push(`penwidth=${trimNumber(style.penwidth)}`);\n if (style.width !== undefined) {\n sb.push(`, width=${trimNumber(style.width)}, fixedsize=true`);\n }\n sb.push(`];\\n`);\n}\n\nfunction portNodeId(port: Port<unknown>): string {\n return 'p_' + sanitize(port.place.name);\n}\n\nfunction channelNodeId(ch: Channel): string {\n return 't_' + sanitize(ch.transition.name);\n}\n\nfunction portNodeIds(iface: Interface): Set<string> {\n const ids = new Set<string>();\n for (const port of iface.ports.values()) ids.add(portNodeId(port));\n return ids;\n}\n\nfunction channelNodeIds(iface: Interface): Set<string> {\n const ids = new Set<string>();\n for (const ch of iface.channels.values()) ids.add(channelNodeId(ch));\n return ids;\n}\n\n/**\n * Rewrites the {@code fillcolor=}, {@code color=}, {@code style=}, and\n * {@code penwidth=} attributes of the line declaring the given node id so\n * the supplied style takes precedence. Operates as a plain text rewrite:\n * the {@link import('../export/dot-renderer.js').renderDot} output formats\n * each node line deterministically, so a per-attribute regex is reliable.\n */\nfunction restyleNode(\n dot: string,\n nodeId: string,\n style: ReturnType<typeof nodeStyle>,\n): string {\n // The node line begins with: <id> [\n // and ends with: ];\n // Find the line, then surgically swap fillcolor / color / style / penwidth.\n const marker = nodeId + ' [';\n let start = dot.indexOf('\\n ' + marker);\n if (start < 0) start = dot.indexOf(' ' + marker);\n if (start < 0) return dot;\n // Skip the leading newline if present so we operate on the line proper.\n if (dot.charAt(start) === '\\n') start += 1;\n const lineEnd = dot.indexOf('];', start);\n if (lineEnd < 0) return dot;\n let line = dot.substring(start, lineEnd + 2);\n\n // Order matters: keep `fillcolor` before `color` so the substring search\n // for `color=\"` doesn't accidentally hit `fillcolor=\"`.\n line = replaceAttr(line, 'fillcolor', style.fill);\n line = replaceAttr(line, 'color', style.stroke);\n line = replaceAttr(line, 'penwidth', trimNumber(style.penwidth));\n if (style.style !== undefined) {\n // Compose with `filled` because the renderer already includes that.\n line = replaceAttr(line, 'style', `\"filled,${style.style}\"`);\n }\n\n return dot.substring(0, start) + line + dot.substring(lineEnd + 2);\n}\n\n/**\n * Replaces the value of a {@code key=value} pair within a single DOT\n * attribute list. Quotes the value when the existing form was quoted, leaves\n * the value unquoted when the existing form was unquoted (numeric).\n */\nfunction replaceAttr(line: string, key: string, value: string): string {\n // Match key=\"...\" (quoted) — the key must be preceded by a non-word char\n // (start, comma, or space) so `color=` doesn't match inside `fillcolor=`.\n const quotedPat = key + '=\"';\n const qStart = findAttrStart(line, quotedPat);\n if (qStart >= 0) {\n const valStart = qStart + quotedPat.length;\n const valEnd = line.indexOf('\"', valStart);\n if (valEnd < 0) return line;\n let v = value;\n if (v.startsWith('\"') && v.endsWith('\"') && v.length >= 2) {\n v = v.substring(1, v.length - 1);\n }\n return line.substring(0, valStart) + v + line.substring(valEnd);\n }\n // Match key=NN (numeric, ends at ',' or ']').\n const unquoted = key + '=';\n const uStart = findAttrStart(line, unquoted);\n if (uStart < 0) return line;\n const valStart = uStart + unquoted.length;\n let valEnd = valStart;\n while (valEnd < line.length) {\n const c = line.charAt(valEnd);\n if (c === ',' || c === ']') break;\n valEnd++;\n }\n return line.substring(0, valStart) + value + line.substring(valEnd);\n}\n\n/**\n * Finds the start of a `key=` attribute pattern in `line`, ensuring the\n * match is not a suffix of another attribute name (e.g. `color=` must not\n * match inside `fillcolor=`). Returns -1 when no boundary-respecting match\n * exists.\n */\nfunction findAttrStart(line: string, pattern: string): number {\n let from = 0;\n while (from < line.length) {\n const idx = line.indexOf(pattern, from);\n if (idx < 0) return -1;\n if (idx === 0) return idx;\n const prev = line.charAt(idx - 1);\n // Word-boundary check: previous char must not be alphanumeric/underscore.\n if (!/[A-Za-z0-9_]/.test(prev)) return idx;\n from = idx + 1;\n }\n return -1;\n}\n\nfunction trimNumber(d: number): string {\n if (Number.isInteger(d)) return String(d);\n return String(d);\n}\n\nfunction escapeLabel(s: string): string {\n return s.replace(/\\\\/g, '\\\\\\\\').replace(/\"/g, '\\\\\"');\n}\n","/**\n * Stock Graphviz SVG rendering using `@viz-js/viz` (Graphviz WASM).\n *\n * Pure WASM — zero external dependencies. No system `dot` binary required.\n *\n * **Not** what the TypeDoc plugin emits. The plugin embeds the viewer bundle\n * and mounts client-side (see `diagram-renderer.ts`), which gives ELK node\n * placement and orthogonal edge routes. This module is the plain `dot` engine:\n * Graphviz does its own layout and routes edges as diagonal splines. It stays\n * exported for callers that want a server-side SVG string and are content with\n * stock layout; for the canonical look, use `mount()` from `libpetri/viewer`.\n *\n * @module doclet/svg-renderer\n */\n\n/**\n * Renders a DOT string to SVG using the `@viz-js/viz` WASM engine.\n *\n * Strips the XML prolog/DOCTYPE and explicit width/height attributes so the SVG\n * scales via viewBox + CSS instead of overriding with fixed pt sizes.\n *\n * Layout is stock Graphviz `dot`, so edges are diagonal splines. For the\n * orthogonal routing the doc generators and debug-ui produce, mount the\n * canonical viewer instead (`mount()` from `libpetri/viewer`).\n *\n * @param dot - the DOT source string\n * @returns SVG markup string\n * @throws if `@viz-js/viz` is not installed or DOT parsing fails\n */\nexport async function dotToSvg(dot: string): Promise<string> {\n // Dynamic import — @viz-js/viz is an optional peer dependency\n const { instance } = await import('@viz-js/viz');\n const viz = await instance();\n let svg = viz.renderString(dot, { format: 'svg', engine: 'dot' });\n\n // Strip XML prolog and DOCTYPE — invalid inside HTML5\n const svgStart = svg.indexOf('<svg');\n if (svgStart > 0) svg = svg.substring(svgStart);\n\n // Strip explicit width/height attributes (e.g. \"1942pt\") so the SVG\n // scales via viewBox + CSS instead of overriding with fixed pt sizes\n svg = svg.replace(/\\s+(?:width|height)=\"[^\"]*\"/g, '');\n\n return svg;\n}\n"],"mappings":"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","names":["dirname","interfaceOnly","valStart","valEnd","interfaceOnly","dotExport"]}
@@ -21,6 +21,9 @@
21
21
  --lpv-border: #ddd;
22
22
  --lpv-text: #111827;
23
23
  --lpv-muted: #6b7280;
24
+ --lpv-error-bg: #fef2f2;
25
+ --lpv-error-border: #fecaca;
26
+ --lpv-error-text: #991b1b;
24
27
 
25
28
  /* Cluster visuals — user feedback: clusters must be visually obvious.
26
29
  Bumped the cluster background from a near-white tint to a saturated
@@ -752,9 +755,27 @@ g.node.petri-replica > text.libpetri-replica-glyph {
752
755
  --lpv-sidebar-chip-hover-bg: #312e81;
753
756
  --lpv-replica-fill: #312e81;
754
757
  --lpv-replica-stroke: #a78bfa;
758
+ --lpv-error-bg: #3f1d1d;
759
+ --lpv-error-border: #7f1d1d;
760
+ --lpv-error-text: #fecaca;
755
761
  --lpv-shared-glyph-color: #a78bfa;
756
762
  }
757
763
 
764
+ /* Painted by the doc-generator init snippets when `mount()` rejects or the
765
+ viewer bundle never loads. Without it a failed render is an empty box, which
766
+ reads as "this net has no diagram" rather than "rendering broke". */
767
+ .libpetri-diagram-error {
768
+ margin: 0;
769
+ padding: 12px 14px;
770
+ border: 1px solid var(--lpv-error-border);
771
+ border-radius: 4px;
772
+ background: var(--lpv-error-bg);
773
+ color: var(--lpv-error-text);
774
+ font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
775
+ font-size: 13px;
776
+ line-height: 1.5;
777
+ }
778
+
758
779
  [data-lpv-theme="dark"] .libpetri-sidebar .libpetri-sidebar-actions button {
759
780
  background: #111827;
760
781
  color: var(--lpv-sidebar-text);