libpetri 2.1.0 → 2.3.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.
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/viewer/pan-zoom.ts","../../src/viewer/cluster-overlay.ts","../../src/viewer/c0-annotations.ts","../../src/viewer/chrome/sidebar.ts","../../src/viewer/highlight.ts","../../src/viewer/replica-tagging.ts","../../src/viewer/visibility.ts","../../src/viewer/styles.ts","../../src/viewer/index.ts"],"sourcesContent":["/**\n * Pan/zoom wrapper for the canonical libpetri viewer.\n *\n * Delegates to the `panzoom` library (no hand-rolled wheel handlers — we\n * intentionally drop the IIFE/javadoc version's bespoke math in favour of\n * a single battle-tested implementation). Defaults match the old debug-ui\n * and dev-preview values so the canonical viewer feels identical to what\n * users had before.\n *\n * @module viewer/pan-zoom\n */\n\nimport panzoom from 'panzoom';\n\nexport type PanzoomInstance = ReturnType<typeof panzoom>;\nexport type PanzoomOptions = Parameters<typeof panzoom>[1];\n\n/**\n * Default panzoom configuration shared across all viewer surfaces.\n *\n * - `maxZoom: 1000` — effectively unlimited; lets users dive into ~150-node\n * diagrams without hitting an artificial ceiling.\n * - `minZoom: 0.02` — allows fitting very large nets in a small viewport.\n * - `smoothScroll: false` — sharper interactive feel.\n * - `zoomDoubleClickSpeed: 1` — single-click double-zoom toggle.\n */\nexport const DEFAULT_PANZOOM_OPTS = {\n smoothScroll: false,\n zoomDoubleClickSpeed: 1,\n maxZoom: 1000,\n minZoom: 0.02,\n} as const satisfies PanzoomOptions;\n\n/**\n * Attach panzoom to a freshly rendered SVG element. The caller is\n * responsible for disposing any previous instance (or pass it as\n * `previous` to dispose it here).\n */\nexport function attachPanzoom(\n svg: SVGSVGElement,\n options?: PanzoomOptions,\n previous?: PanzoomInstance | null,\n): PanzoomInstance {\n if (previous) {\n try {\n previous.dispose();\n } catch {\n // Swallow disposal errors — the previous instance may already be\n // detached or the panzoom internals may have raised on a missing\n // root node. Either way we just want a fresh instance.\n }\n }\n const opts = { ...DEFAULT_PANZOOM_OPTS, ...options };\n return panzoom(svg, opts);\n}\n","/**\n * Cluster overlay for the canonical libpetri viewer.\n *\n * Post-processes a rendered SVG to surface subnet structure visually:\n * 1. Discover `<g class=\"cluster\">` subgraphs, derive each prefix from the\n * contained `<title>` (Graphviz emits `cluster_<sanitizedPrefix>`).\n * 2. Tag every contained `<g class=\"node\">` with `data-instance=\"<prefix>\"`\n * so filtering can target it via attribute selectors.\n * 3. Assign each cluster a deterministic HSL colour (FNV-1a hash + golden\n * ratio hue increment) and paint its border.\n * 4. Provide collapse/expand and \"show only <prefix>\" filtering.\n *\n * Unlike the previous debug-ui cluster-overlay, this module is instance-\n * based: every `mount()` call gets its own `ClusterOverlay` so multiple\n * viewers on the same page don't share collapse/isolate state.\n *\n * @module viewer/cluster-overlay\n */\n\n/** A discovered cluster with its DOM group, node count, and palette colour. */\nexport interface ClusterDescriptor {\n readonly prefix: string;\n readonly group: SVGGElement;\n readonly nodeCount: number;\n readonly color: string;\n}\n\n/**\n * Augmentation slots stashed on the cluster `<g>` element across collapse\n * cycles. We don't subclass SVGElement; we tag arbitrary properties on the\n * DOM node, matching the doclet's `petrinet-diagrams.js` so the cross-surface\n * mental model is identical.\n *\n * `_petriHiddenSiblings` holds the sibling `<g class=\"node\">` and\n * `<g class=\"edge\">` elements (sibling to the cluster, not its DOM children\n * — Graphviz never nests nodes inside cluster groups) tagged as belonging\n * to this prefix at the moment of collapse. They retain their place in the\n * SVG tree; we hide them via the `petri-collapsed-inside` class so the\n * underlying layout is preserved for a cheap re-expand.\n */\ninterface ClusterAugment {\n _petriHiddenSiblings?: Element[];\n _petriCollapsedBadge?: SVGTextElement | null;\n}\ntype AugmentedGroup = SVGGElement & ClusterAugment;\n\n/**\n * Walk the SVG, find every `<g class=\"cluster\">`, derive the prefix from\n * each cluster's `<title>` (`cluster_<sanitized>`), and tag every cluster-\n * interior `<g class=\"node\">` with `data-instance=\"<prefix>\"`. Returns one\n * descriptor per cluster.\n *\n * Graphviz emits cluster nodes as SIBLINGS of the cluster `<g>`, not as\n * children — `<g class=\"cluster\">` only carries title, polygon border, and\n * the cluster's text label. To recover cluster membership we match each\n * node's `<title>` (the original graph id) against libpetri's prefixing\n * convention: interior places are emitted as `p_<prefix>_<name>`,\n * transitions as `t_<prefix>_<name>`, and junctions as `j_<prefix>_<name>`.\n *\n * Does not split nested-cluster prefixes (`outer_inner` could mean either\n * `outer/inner` or a flat `outer_inner` prefix — Graphviz sanitisation is\n * lossy). We treat every cluster as its own top-level entry, matching the\n * doclet's pragmatism.\n */\nexport function discoverClusters(svg: SVGSVGElement): Map<string, ClusterDescriptor> {\n const out = new Map<string, ClusterDescriptor>();\n const clusterGroups = svg.querySelectorAll<SVGGElement>('g.cluster');\n if (!clusterGroups.length) return out;\n\n const allNodes = Array.from(svg.querySelectorAll<SVGGElement>('g.node'));\n\n for (const g of Array.from(clusterGroups)) {\n const titleEl = directChild(g, 'title');\n if (!titleEl) continue;\n const raw = titleEl.textContent ?? '';\n if (!raw.startsWith('cluster_')) continue;\n const prefix = raw.slice('cluster_'.length);\n if (out.has(prefix)) continue;\n\n let nodeCount = 0;\n for (const node of allNodes) {\n // Don't re-tag a node already claimed by an inner (more specific)\n // cluster — outer clusters can still match by string-prefix at filter\n // time, but the data-instance is the leaf.\n if (node.getAttribute('data-instance')) continue;\n const nodeTitle = directChild(node, 'title');\n if (!nodeTitle) continue;\n const id = (nodeTitle.textContent ?? '').trim();\n if (nodeBelongsToPrefix(id, prefix)) {\n node.setAttribute('data-instance', prefix);\n nodeCount++;\n }\n }\n\n out.set(prefix, {\n prefix,\n group: g,\n nodeCount,\n color: colorForPrefix(prefix),\n });\n }\n return out;\n}\n\n/**\n * Does this node id belong to the cluster with the given prefix?\n *\n * libpetri's DOT renderer prefixes interior names: places as `p_<prefix>_…`,\n * transitions as `t_<prefix>_…`, junctions as `j_<prefix>_…`. Generic DOT\n * may also use slash-separated names (`<prefix>/…`). We accept both shapes\n * so non-libpetri DOT graphs and bare-Graphviz fixtures still match.\n */\nfunction nodeBelongsToPrefix(nodeId: string, prefix: string): boolean {\n return (\n nodeId === prefix ||\n nodeId.startsWith(`${prefix}/`) ||\n nodeId.startsWith(`p_${prefix}_`) ||\n nodeId.startsWith(`t_${prefix}_`) ||\n nodeId.startsWith(`j_${prefix}_`)\n );\n}\n\n/**\n * Deterministic HSL palette. FNV-1a-ish hash of the prefix, multiplied by\n * the golden-ratio fraction (0.61803...) to spread adjacent prefixes\n * visually. Saturation/lightness are fixed (60% / 45%) so the palette\n * stays readable on both the doc background and the dark debug UI.\n */\nexport function colorForPrefix(prefix: string): string {\n let h = 2166136261;\n for (let i = 0; i < prefix.length; i++) {\n h ^= prefix.charCodeAt(i);\n h = (h * 16777619) >>> 0;\n }\n let hue = Math.floor(((h / 4294967296) * 360 + (h % 360) * 0.61803) % 360);\n if (hue < 0) hue += 360;\n return `hsl(${hue}, 60%, 45%)`;\n}\n\n/** Paint the cluster border with the deterministic prefix colour. */\nexport function paintClusterBorders(clusters: Map<string, ClusterDescriptor>): void {\n for (const c of clusters.values()) {\n const border = directChild(c.group, 'polygon') ?? directChild(c.group, 'path');\n if (border) {\n border.setAttribute('stroke', c.color);\n // Slightly thicker than the old 2.0 — the user asked for clusters to\n // be visually obvious. Backed off from 3 to keep it readable at the\n // tiniest zoom.\n border.setAttribute('stroke-width', '2.2');\n }\n }\n}\n\n/**\n * Collapse / expand a cluster.\n *\n * Graphviz emits nodes and edges as siblings of the cluster `<g>`, not as\n * its DOM children. So \"collapsing\" cannot work by detaching the cluster's\n * children — that only removes title/border/label. Instead, we identify the\n * cluster-interior nodes (`data-instance=\"<prefix>\"`) and edges with both\n * endpoints inside the cluster, then add the `petri-collapsed-inside` class\n * so the canonical viewer.css hides them. Stash the list on the cluster\n * group so re-expand is a constant-time class removal.\n *\n * Edges that cross the cluster boundary (one endpoint inside, one outside)\n * are intentionally left visible — they show the cluster's external wiring\n * even when the interior is hidden.\n *\n * A small `<text>` badge with the actual interior node count is appended\n * inside the cluster `<g>` (which keeps its layout box).\n */\nexport function setClusterCollapsed(cluster: ClusterDescriptor, collapsed: boolean): void {\n const g = cluster.group as AugmentedGroup;\n const isCollapsed = g.classList.contains('cluster-collapsed');\n const root = g.ownerSVGElement;\n if (!root) return;\n\n if (collapsed && !isCollapsed) {\n g.classList.add('cluster-collapsed');\n const hidden: Element[] = [];\n\n // Hide interior nodes (sibling elements tagged with this prefix).\n root\n .querySelectorAll<SVGGElement>(`g.node[data-instance=\"${cssEscape(cluster.prefix)}\"]`)\n .forEach((node) => {\n node.classList.add('petri-collapsed-inside');\n hidden.push(node);\n });\n\n // Hide interior-interior edges. Edges' titles are `from->to`; we hide\n // an edge only when BOTH endpoints belong to this cluster.\n root.querySelectorAll<SVGGElement>('g.edge').forEach((edge) => {\n const titleEl = directChild(edge, 'title');\n if (!titleEl) return;\n const t = (titleEl.textContent ?? '').trim();\n const arrow = t.indexOf('->');\n if (arrow < 0) return;\n const from = t.slice(0, arrow);\n const to = t.slice(arrow + 2);\n if (nodeBelongsToPrefix(from, cluster.prefix) && nodeBelongsToPrefix(to, cluster.prefix)) {\n edge.classList.add('petri-collapsed-inside');\n hidden.push(edge);\n }\n });\n\n g._petriHiddenSiblings = hidden;\n\n if (!g._petriCollapsedBadge) {\n const label = directChild(g, 'text') as SVGTextElement | null;\n if (label) {\n const badge = document.createElementNS('http://www.w3.org/2000/svg', 'text');\n badge.setAttribute('x', label.getAttribute('x') ?? '0');\n const labelY = parseFloat(label.getAttribute('y') ?? '0');\n badge.setAttribute('y', String(labelY + 16));\n badge.setAttribute('text-anchor', label.getAttribute('text-anchor') ?? 'middle');\n badge.setAttribute('font-size', '11');\n badge.setAttribute('fill', '#9ca3af');\n badge.setAttribute('class', 'cluster-collapsed-badge');\n badge.textContent = `(${cluster.nodeCount} internal node${cluster.nodeCount === 1 ? '' : 's'})`;\n g.appendChild(badge);\n g._petriCollapsedBadge = badge;\n }\n }\n } else if (!collapsed && isCollapsed) {\n g.classList.remove('cluster-collapsed');\n if (g._petriHiddenSiblings) {\n g._petriHiddenSiblings.forEach((el) => el.classList.remove('petri-collapsed-inside'));\n g._petriHiddenSiblings = undefined;\n }\n if (g._petriCollapsedBadge && g._petriCollapsedBadge.parentNode === g) {\n g.removeChild(g._petriCollapsedBadge);\n g._petriCollapsedBadge = null;\n }\n }\n}\n\n/**\n * Toggle the \"show only <prefix>\" filter. Passing `null` (or the same\n * prefix already active) clears the filter. Mirrors the doclet's\n * `applyFilter`, including the edge-dimming heuristic that parses each\n * edge's `<title>` (`from->to`) and keeps an edge visible when either\n * endpoint matches the active prefix.\n */\nexport function applyFilter(svg: SVGSVGElement, prefix: string | null): void {\n if (prefix == null) {\n svg.removeAttribute('data-active-filter');\n svg.classList.remove('has-active-filter');\n svg.querySelectorAll('g.node, g.edge').forEach((el) => el.classList.remove('petri-dimmed'));\n return;\n }\n svg.setAttribute('data-active-filter', prefix);\n svg.classList.add('has-active-filter');\n svg.querySelectorAll('g.node').forEach((node) => {\n const di = node.getAttribute('data-instance');\n const match =\n !!di &&\n (di === prefix || di.indexOf(prefix + '_') === 0 || di.indexOf(prefix + '/') === 0);\n if (match) node.classList.remove('petri-dimmed');\n else node.classList.add('petri-dimmed');\n });\n svg.querySelectorAll('g.edge').forEach((edge) => {\n const titleEl = directChild(edge, 'title');\n if (!titleEl) return;\n const titleText = titleEl.textContent ?? '';\n const arrow = titleText.indexOf('->');\n if (arrow < 0) {\n edge.classList.remove('petri-dimmed');\n return;\n }\n const from = titleText.slice(0, arrow);\n const to = titleText.slice(arrow + 2);\n const matches = (name: string): boolean =>\n name.indexOf(prefix + '/') >= 0 || name.indexOf(prefix + '_') >= 0 || name === prefix;\n if (matches(from) || matches(to)) edge.classList.remove('petri-dimmed');\n else edge.classList.add('petri-dimmed');\n });\n}\n\n/**\n * Returns true when the place/transition node identified by `graphId` is\n * currently inside a collapsed cluster (i.e. detached from the live SVG).\n * Live consumers (e.g. highlight code in debug-ui) use this to skip lookups\n * that would otherwise hit a stale cache entry pointing at a detached node.\n */\nexport function isInsideCollapsedCluster(\n svg: SVGSVGElement | null,\n clusters: Map<string, ClusterDescriptor>,\n collapsedPrefixes: ReadonlySet<string>,\n graphId: string,\n): boolean {\n if (collapsedPrefixes.size === 0) return false;\n if (!svg) return false;\n for (const prefix of collapsedPrefixes) {\n if (!clusters.has(prefix)) continue;\n if (nodeBelongsToPrefix(graphId, prefix)) return true;\n }\n return false;\n}\n\n/** Minimal CSS.escape polyfill — happy-dom may not implement CSS globals. */\nfunction cssEscape(s: string): string {\n if (typeof CSS !== 'undefined' && typeof CSS.escape === 'function') return CSS.escape(s);\n return s.replace(/[^a-zA-Z0-9_-]/g, (c) => `\\\\${c}`);\n}\n\n/**\n * `:scope > <tag>` polyfill — happy-dom doesn't reliably resolve `:scope`\n * against namespaced SVG elements (returns null even when the child exists).\n * Walk `children` directly instead, matching by lower-cased tag name.\n */\nfunction directChild(parent: Element, tagName: string): Element | null {\n const lc = tagName.toLowerCase();\n for (const child of Array.from(parent.children)) {\n if (child.tagName.toLowerCase() === lc) return child;\n }\n return null;\n}\n","/**\n * Post-render SVG annotation for the C0 layout pipeline.\n *\n * Graphviz emits a clean SVG but without the `data-id`, `data-src`,\n * `data-dst`, `data-cluster`, `data-instance` attributes the click-all-\n * copies, highlight-through-junctions, and directed-reachability overlays\n * key off. {@link annotateSvgForC0} walks the rendered SVG once and adds\n * those attributes, plus `intra-cluster` / `cross-cluster` classes on\n * `<g class=\"edge\">` so the visibility layer can toggle edges as their\n * containing cluster's state changes.\n *\n * Mirrors the SVG-side conventions used by v3\n * (`run16_GOOD_v3_skip-junctions.mjs` overlay block, lines 876–905) but\n * derived from Graphviz's emitted DOM shape (`<g class=\"node\"><title>`,\n * `<g class=\"edge\"><title>src-&gt;dst</title>`) rather than the manual\n * emission v3 did.\n *\n * @module viewer/c0-annotations\n */\n\n/** Read the immediate `<title>` child's text content (Graphviz convention). */\nfunction ownTitle(g: Element): string {\n for (const child of Array.from(g.children)) {\n if (child.tagName === 'title') return child.textContent ?? '';\n }\n return '';\n}\n\n/**\n * Walk to the nearest ancestor `<g class=\"cluster\">` and return its\n * short name (with the leading `cluster_` stripped), or `''` if the node\n * lives at root (i.e. it's an orphan inside the synthetic orchestrator).\n *\n * Graphviz emits clusters as DOM **siblings** of their member nodes (not\n * parents), so `closest('g.cluster')` is null in our pipeline — this is\n * effectively a no-op fallback. Cluster membership is established up\n * front by {@link discoverClusters} via `<title>` prefix matching.\n */\nfunction enclosingClusterShortName(node: Element): string {\n const cluster = node.parentElement?.closest('g.cluster');\n if (!cluster) return '';\n return ownTitle(cluster).replace(/^cluster_/, '');\n}\n\n/**\n * Annotate a Graphviz-rendered SVG with the data attributes + edge classes\n * the C0 overlay layer relies on. Idempotent.\n *\n * Must run AFTER {@link discoverClusters}, which seeds `data-instance` via\n * title prefix matching (cluster → its member nodes by `t_${prefix}_…` /\n * `p_${prefix}_…` etc.). This function never clears `data-instance` —\n * Graphviz nests neither nodes inside clusters nor anything else, so DOM\n * ancestry is not a reliable source of cluster membership here.\n */\nexport function annotateSvgForC0(svg: SVGSVGElement): void {\n // Nodes — data-id (from title). Prefer the cluster tag discoverClusters\n // already wrote. Replicas (id ends `__rep__<shortName>`) override that\n // tag with their target cluster so the visibility layer can hide them\n // alongside the cluster they belong to — discoverClusters keys off the\n // libpetri prefix convention (`p_<short>_`) which replica ids don't match.\n const nodes = Array.from(svg.querySelectorAll<SVGGElement>('g.node'));\n for (const node of nodes) {\n const id = ownTitle(node);\n if (!id) continue;\n node.setAttribute('data-id', id);\n const replicaM = /__rep__([A-Za-z0-9_]+)$/.exec(id);\n if (replicaM) {\n node.setAttribute('data-instance', replicaM[1]!);\n } else if (!node.hasAttribute('data-instance')) {\n const inst = enclosingClusterShortName(node);\n if (inst) node.setAttribute('data-instance', inst);\n }\n }\n\n // Index nodes by data-id for fast cluster lookup on edge endpoints\n const nodeIndex = new Map<string, SVGGElement>();\n for (const n of nodes) {\n const id = n.getAttribute('data-id');\n if (id) nodeIndex.set(id, n);\n }\n\n // Edges — parse \"src->dst\" from title; tag with data-src/data-dst,\n // their cluster short names, and intra-/cross-cluster class.\n for (const edge of Array.from(svg.querySelectorAll<SVGGElement>('g.edge'))) {\n const title = ownTitle(edge);\n const arrow = title.indexOf('->');\n if (arrow <= 0) continue;\n const src = title.slice(0, arrow).trim();\n const dst = title.slice(arrow + 2).trim();\n edge.setAttribute('data-src', src);\n edge.setAttribute('data-dst', dst);\n\n const srcInst = nodeIndex.get(src)?.getAttribute('data-instance') ?? '';\n const dstInst = nodeIndex.get(dst)?.getAttribute('data-instance') ?? '';\n edge.setAttribute('data-src-cluster', srcInst);\n edge.setAttribute('data-dst-cluster', dstInst);\n\n edge.classList.remove('intra-cluster', 'cross-cluster');\n if (srcInst !== '' && srcInst === dstInst) {\n edge.classList.add('intra-cluster');\n edge.setAttribute('data-cluster', srcInst);\n } else {\n edge.classList.add('cross-cluster');\n edge.removeAttribute('data-cluster');\n }\n }\n}\n","/**\n * C0 cluster-chip sidebar. Replaces the previous top-left legend +\n * bottom filter strip with a single left-docked panel:\n *\n * - Cluster chips (click = toggle; shift-click = isolate to just this;\n * ctrl/cmd-click = add/remove from selection)\n * - \"show all\" / \"hide all\" buttons\n * - Shared-places toggle (orphan / cross-cluster places on/off)\n * - Highlight-mode toggle (click-node-to-highlight on/off)\n * - Inline legend explaining the ⇄ glyph\n *\n * Mounted from {@link import('../index.js').mount} when `chrome: true`.\n * Lives inside the viewer container as an `aside` so it travels with the\n * container into fullscreen.\n *\n * @module viewer/chrome/sidebar\n */\n\nimport type { ClusterDescriptor } from '../cluster-overlay.js';\nimport type { VisibilityState } from '../visibility.js';\n\nexport interface SidebarCallbacks {\n /**\n * Fired whenever the visibility state changes (chip click, show/hide all,\n * or orphan toggle). Caller forwards to `handle.setVisibility(state)`.\n */\n readonly onVisibilityChange: (state: VisibilityState) => void;\n /**\n * Fired when the user toggles highlight mode off, so the caller can\n * clear any active highlight via `handle.highlight(null)`.\n */\n readonly onHighlightModeChange: (enabled: boolean) => void;\n}\n\nexport interface SidebarHandle {\n /** The root `<aside>` element appended to the container. */\n readonly root: HTMLElement;\n /** Tear down listeners and remove the sidebar from the DOM. */\n dispose(): void;\n}\n\nconst SIDEBAR_CLASS = 'libpetri-sidebar';\nconst CHIP_CLASS = 'libpetri-sidebar-chip';\nconst CHIP_OFF_CLASS = 'is-off';\n\n/**\n * Build + mount the sidebar inside `container`. Returns a handle the\n * caller disposes on re-mount.\n */\nexport function mountSidebar(\n container: HTMLElement,\n clusters: ReadonlyMap<string, ClusterDescriptor>,\n callbacks: SidebarCallbacks,\n): SidebarHandle {\n const visibleClusters = new Set<string>();\n for (const prefix of clusters.keys()) visibleClusters.add(prefix);\n let includeSharedPlaces = true;\n let highlightMode = true;\n\n const emit = (): void => {\n callbacks.onVisibilityChange({\n visibleClusters: new Set(visibleClusters),\n includeSharedPlaces,\n });\n };\n\n const root = document.createElement('aside');\n root.className = SIDEBAR_CLASS;\n\n const title = document.createElement('h4');\n title.className = 'libpetri-sidebar-title';\n title.textContent = `Subnets (${clusters.size})`;\n root.appendChild(title);\n\n // Show all / hide all\n const actions = document.createElement('div');\n actions.className = 'libpetri-sidebar-actions';\n const showAll = document.createElement('button');\n showAll.type = 'button';\n showAll.textContent = 'show all';\n const hideAll = document.createElement('button');\n hideAll.type = 'button';\n hideAll.textContent = 'hide all';\n actions.appendChild(showAll);\n actions.appendChild(hideAll);\n root.appendChild(actions);\n\n // Shared-places toggle — hides every replica + original of a shared\n // place (anything tagged `petri-replica`) when unchecked.\n const sharedLabel = document.createElement('label');\n sharedLabel.className = 'libpetri-sidebar-checkbox';\n const sharedCb = document.createElement('input');\n sharedCb.type = 'checkbox';\n sharedCb.checked = includeSharedPlaces;\n sharedLabel.appendChild(sharedCb);\n sharedLabel.appendChild(document.createTextNode(' Shared places'));\n sharedCb.addEventListener('change', () => {\n includeSharedPlaces = sharedCb.checked;\n emit();\n });\n root.appendChild(sharedLabel);\n\n // Highlight-mode toggle\n const hlLabel = document.createElement('label');\n hlLabel.className = 'libpetri-sidebar-checkbox';\n const hlCb = document.createElement('input');\n hlCb.type = 'checkbox';\n hlCb.checked = highlightMode;\n hlLabel.appendChild(hlCb);\n hlLabel.appendChild(document.createTextNode(' Click node → highlight'));\n hlCb.addEventListener('change', () => {\n highlightMode = hlCb.checked;\n callbacks.onHighlightModeChange(highlightMode);\n });\n root.appendChild(hlLabel);\n\n // Hint\n const hint = document.createElement('div');\n hint.className = 'libpetri-sidebar-hint';\n hint.innerHTML =\n 'Click chip: toggle.<br>' +\n 'Shift-click: isolate.<br>' +\n 'Cmd/Ctrl-click: multi-select.<br>' +\n 'Click empty: clear highlight.';\n root.appendChild(hint);\n\n // Chips\n const chipList = document.createElement('ul');\n chipList.className = 'libpetri-sidebar-chips';\n const chipMap = new Map<string, HTMLLIElement>();\n for (const cluster of clusters.values()) {\n const li = document.createElement('li');\n li.className = CHIP_CLASS;\n li.style.borderLeftColor = cluster.color;\n li.dataset.prefix = cluster.prefix;\n const dot = document.createElement('span');\n dot.className = 'libpetri-sidebar-chip-dot';\n dot.style.background = cluster.color;\n const name = document.createElement('span');\n name.className = 'libpetri-sidebar-chip-name';\n name.textContent = cluster.prefix;\n const count = document.createElement('span');\n count.className = 'libpetri-sidebar-chip-count';\n count.textContent = String(cluster.nodeCount);\n li.appendChild(dot);\n li.appendChild(name);\n li.appendChild(count);\n li.addEventListener('click', (ev) => {\n const prefix = cluster.prefix;\n if (ev.shiftKey) {\n // Isolate to just this one — or restore \"show all\" if already isolated.\n const allOff = [...clusters.keys()].every(k =>\n k === prefix ? visibleClusters.has(k) : !visibleClusters.has(k),\n );\n visibleClusters.clear();\n if (allOff) for (const k of clusters.keys()) visibleClusters.add(k);\n else visibleClusters.add(prefix);\n } else if (ev.ctrlKey || ev.metaKey) {\n // Multi-select: ADD/remove. If currently \"show all\", start fresh.\n const anyHidden = [...clusters.keys()].some(k => !visibleClusters.has(k));\n if (!anyHidden) {\n visibleClusters.clear();\n visibleClusters.add(prefix);\n } else if (visibleClusters.has(prefix)) {\n visibleClusters.delete(prefix);\n } else {\n visibleClusters.add(prefix);\n }\n } else {\n if (visibleClusters.has(prefix)) visibleClusters.delete(prefix);\n else visibleClusters.add(prefix);\n }\n refreshChips();\n emit();\n });\n chipList.appendChild(li);\n chipMap.set(cluster.prefix, li);\n }\n root.appendChild(chipList);\n\n function refreshChips(): void {\n for (const [prefix, li] of chipMap) {\n li.classList.toggle(CHIP_OFF_CLASS, !visibleClusters.has(prefix));\n }\n }\n\n showAll.addEventListener('click', () => {\n visibleClusters.clear();\n for (const k of clusters.keys()) visibleClusters.add(k);\n includeSharedPlaces = true;\n sharedCb.checked = true;\n refreshChips();\n emit();\n });\n hideAll.addEventListener('click', () => {\n visibleClusters.clear();\n includeSharedPlaces = false;\n sharedCb.checked = false;\n refreshChips();\n emit();\n });\n\n container.appendChild(root);\n\n // Emit initial state so the caller's visibility starts in sync.\n emit();\n\n return {\n root,\n dispose(): void {\n if (root.parentNode === container) container.removeChild(root);\n },\n };\n}\n","/**\n * Click-to-highlight overlay for the C0 layout.\n *\n * Two operations:\n *\n * findAllCopies(svg, focusId) — given a node id, returns the set of every\n * DOM node representing the same logical place. For a replica it returns\n * the original + every sibling replica; for an original of a shared place\n * it returns itself + every replica; for an unshared node it returns just\n * `{focusId}`.\n *\n * applyHighlight(svg, focusId) — highlights the focus group, walks\n * junctions (`j_*` routing nodes) transparently to reveal the real\n * downstream place/transition neighbors, and fades everything else.\n * Pass `null` to clear.\n *\n * Ported from v3 `applyHighlight` / `findAllCopies` (lines 971–1047). All\n * SVG queries assume the DOM has been through {@link annotateSvgForC0}.\n *\n * @module viewer/highlight\n */\n\nconst HIGHLIGHT_CLASSES = ['is-highlight', 'is-neighbor', 'is-faded'] as const;\n\nfunction clearHighlightClasses(svg: SVGSVGElement): void {\n for (const cls of HIGHLIGHT_CLASSES) {\n for (const el of Array.from(svg.querySelectorAll<Element>('.' + cls))) {\n el.classList.remove(cls);\n }\n }\n}\n\n/**\n * Resolve every DOM node representing the same logical place as `focusId`.\n *\n * Replicas carry `data-replica-of=\"<semanticName>\"`. The original of a\n * shared place is itself tagged with `data-replica-of=\"<semanticName>\"` so\n * a single attribute query finds the whole family.\n */\nexport function findAllCopies(svg: SVGSVGElement, focusId: string): Set<string> {\n const focus = svg.querySelector<SVGGElement>(`g.node[data-id=\"${cssEscape(focusId)}\"]`);\n if (!focus) return new Set([focusId]);\n const copies = new Set<string>([focusId]);\n const origLabel = focus.getAttribute('data-replica-of')\n ?? (focusId.startsWith('p_') ? focusId.slice(2) : null);\n if (origLabel) {\n const orig = svg.querySelector<SVGGElement>(`g.node[data-id=\"${cssEscape('p_' + origLabel)}\"]`);\n if (orig) {\n const id = orig.getAttribute('data-id');\n if (id) copies.add(id);\n }\n for (const sibling of Array.from(\n svg.querySelectorAll<SVGGElement>(`g.node[data-replica-of=\"${cssEscape(origLabel)}\"]`),\n )) {\n const id = sibling.getAttribute('data-id');\n if (id) copies.add(id);\n }\n }\n return copies;\n}\n\n/**\n * Highlight `focusId` (and every DOM copy of the same logical place),\n * walking through junctions to reveal real downstream/upstream neighbors.\n *\n * Pass `null` to clear. Hidden nodes/edges are skipped so toggling a\n * cluster off doesn't bring them back into the path.\n */\nexport function applyHighlight(svg: SVGSVGElement, focusId: string | null): void {\n clearHighlightClasses(svg);\n if (focusId === null) return;\n\n const copies = findAllCopies(svg, focusId);\n for (const id of copies) {\n const n = svg.querySelector<SVGGElement>(`g.node[data-id=\"${cssEscape(id)}\"]`);\n if (n && !n.classList.contains('is-hidden')) n.classList.add('is-highlight');\n }\n\n // BFS through edges; junctions are transparent — the true neighbour is\n // the place/transition on the other side of the routing node.\n const neighbors = new Set<string>();\n const pathJunctions = new Set<string>();\n const pathEdges: SVGGElement[] = [];\n const queue: string[] = Array.from(copies);\n const visited = new Set<string>(copies);\n\n const allEdges = Array.from(svg.querySelectorAll<SVGGElement>('g.edge'));\n while (queue.length > 0) {\n const nodeId = queue.shift()!;\n for (const e of allEdges) {\n if (e.classList.contains('is-hidden')) continue;\n const s = e.getAttribute('data-src');\n const d = e.getAttribute('data-dst');\n if (s !== nodeId && d !== nodeId) continue;\n const otherId = s === nodeId ? d : s;\n if (!otherId) continue;\n pathEdges.push(e);\n if (visited.has(otherId)) continue;\n visited.add(otherId);\n if (otherId.startsWith('j_')) {\n pathJunctions.add(otherId);\n queue.push(otherId);\n } else {\n neighbors.add(otherId);\n }\n }\n }\n\n for (const e of pathEdges) e.classList.add('is-highlight');\n\n for (const e of allEdges) {\n if (e.classList.contains('is-hidden') || e.classList.contains('is-highlight')) continue;\n e.classList.add('is-faded');\n }\n\n for (const n of Array.from(svg.querySelectorAll<SVGGElement>('g.node'))) {\n if (n.classList.contains('is-hidden')) continue;\n const id = n.getAttribute('data-id') ?? '';\n if (copies.has(id)) continue;\n if (neighbors.has(id)) n.classList.add('is-neighbor');\n else if (pathJunctions.has(id)) continue;\n else n.classList.add('is-faded');\n }\n\n for (const c of Array.from(\n svg.querySelectorAll<SVGGElement>('g.cluster:not(.cluster-orchestrator)'),\n )) {\n if (c.classList.contains('is-hidden')) continue;\n c.classList.add('is-faded');\n }\n}\n\n/**\n * Escape a string for use inside a CSS attribute selector value.\n *\n * Built-in `CSS.escape` covers identifier characters; node ids contain\n * `_` and digits which `CSS.escape` accepts, plus the `__rep__` separator\n * which is also safe. We only need to escape the few syntactic chars\n * (`\"`, `\\`).\n */\nfunction cssEscape(value: string): string {\n return value.replace(/\\\\/g, '\\\\\\\\').replace(/\"/g, '\\\\\"');\n}\n","/**\n * Replica detection + DOM tagging for the C0 layout.\n *\n * `replicateShared` (in `layout/preprocess.ts`) emits clones of shared\n * places with ids of the form `${placeId}__rep__${shortCluster}`. After\n * Graphviz renders the pinned DOT, those replica clones appear as\n * `<g class=\"node\">` siblings whose inner `<title>` carries the replica\n * id. The original `placeId` also gets a sibling for every cluster it\n * touches and stays marked as a shared place.\n *\n * {@link tagReplicas} walks the rendered SVG once and adds:\n * - `class=\"petri-replica\"` on every replica node AND its original (so\n * `data-locations` / ⇄ glyph styling can target both).\n * - `data-replica-of=\"<semantic-place-name>\"` — the original place name\n * stripped of the `p_` prefix. Click-all-copies (Stage 4) looks up\n * every node with the same `data-replica-of` value.\n *\n * The ⇄ glyph + dashed border land in CSS (Stage 5); this module only\n * does the attribute tagging.\n *\n * @module viewer/replica-tagging\n */\n\nconst REPLICA_RE = /^(p_[A-Za-z0-9_]+)__rep__/;\n\n/**\n * Stable semantic id for a logical place — strips the `p_` prefix so the\n * value matches the `name` field on libpetri's Place model. Click-all-\n * copies in the overlay keys off this value, not the rendered DOM id.\n */\nfunction semanticName(placeId: string): string {\n return placeId.replace(/^p_/, '');\n}\n\n/**\n * Walk every `<g class=\"node\">` in `svg`, detect replicas and originals of\n * shared places, and tag them with `petri-replica` + `data-replica-of`.\n *\n * Idempotent: re-running on an already-tagged SVG is a no-op (the attrs\n * already match).\n */\nexport function tagReplicas(svg: SVGSVGElement): void {\n // First pass: collect every original whose id appears as the prefix of\n // at least one replica's title. We can't tag the originals until we\n // know which ones got copies.\n const originalsWithCopies = new Set<string>();\n const allNodes = Array.from(svg.querySelectorAll<SVGGElement>('g.node'));\n for (const g of allNodes) {\n const title = g.querySelector('title')?.textContent ?? '';\n const m = REPLICA_RE.exec(title);\n if (m) originalsWithCopies.add(m[1]!);\n }\n\n // Second pass: tag both replicas and their (matching) originals + append\n // the shared-place ⇄ glyph. SVG has no ::after pseudo-element so the\n // glyph is injected as a <text> child of the node's <g>.\n for (const g of allNodes) {\n const title = g.querySelector('title')?.textContent ?? '';\n const replicaMatch = REPLICA_RE.exec(title);\n let semantic: string | null = null;\n if (replicaMatch) {\n semantic = semanticName(replicaMatch[1]!);\n } else if (originalsWithCopies.has(title)) {\n semantic = semanticName(title);\n }\n if (semantic === null) continue;\n g.classList.add('petri-replica');\n g.setAttribute('data-replica-of', semantic);\n appendSharedGlyph(g);\n }\n}\n\nconst SVG_NS = 'http://www.w3.org/2000/svg';\nconst GLYPH_CLASS = 'libpetri-replica-glyph';\n\n/**\n * Inject a ⇄ marker adjacent to the upper-right of the node's place shape.\n * Positioned OUTSIDE the circle so it doesn't overlap the boundary stroke\n * or the inner fill (libpetri places are 25pt circles — anything drawn\n * inside is unreadable). Anchored top-left at `(cx + rx + 2, cy - ry + 2)`\n * with a 9pt sans-serif glyph; the styling is finished by the canonical\n * CSS via the `libpetri-replica-glyph` class.\n *\n * Idempotent.\n */\nfunction appendSharedGlyph(g: SVGGElement): void {\n if (g.querySelector(`text.${GLYPH_CLASS}`)) return;\n const shape = g.querySelector('ellipse, circle');\n if (!shape) return;\n let cx = 0, cy = 0, rx = 0, ry = 0;\n if (shape.tagName === 'ellipse') {\n cx = parseFloat(shape.getAttribute('cx') ?? '0');\n cy = parseFloat(shape.getAttribute('cy') ?? '0');\n rx = parseFloat(shape.getAttribute('rx') ?? '0');\n ry = parseFloat(shape.getAttribute('ry') ?? '0');\n } else {\n cx = parseFloat(shape.getAttribute('cx') ?? '0');\n cy = parseFloat(shape.getAttribute('cy') ?? '0');\n rx = parseFloat(shape.getAttribute('r') ?? '0');\n ry = rx;\n }\n const text = document.createElementNS(SVG_NS, 'text');\n text.setAttribute('class', GLYPH_CLASS);\n // Anchored at the upper-right OUTSIDE the circle (top-left of the glyph\n // sits at +2 / -2 from the bounding-box corner so it doesn't kiss the\n // stroke). text-anchor=start so the glyph extends to the right.\n text.setAttribute('x', (cx + rx + 2).toFixed(2));\n text.setAttribute('y', (cy - ry + 2).toFixed(2));\n text.setAttribute('text-anchor', 'start');\n text.setAttribute('font-size', '9');\n text.textContent = '⇄';\n g.appendChild(text);\n}\n","/**\n * Directed-reachability orphan visibility for the C0 layout.\n *\n * When only a subset of clusters is visible, orphan transitions (and\n * places) should appear iff they participate in a chain that ends at a\n * visible cluster — the chain direction matters. v3's `computeVisibleOrphans`\n * (lines 892–918) walks orphan-orphan edges backwards from \"seed upstream\"\n * orphans (those that feed INTO a visible cluster) and forwards from\n * \"seed downstream\" orphans (those a visible cluster feeds INTO). This\n * keeps `ResponseAwaited` hidden when only `productSearch` is visible\n * while exposing legitimate upstream chains like\n * `SearchProductsToolRequest → ForkSearchProductsToolRequest → … → productSearch`.\n *\n * @module viewer/visibility\n */\n\nimport { annotateSvgForC0 } from './c0-annotations.js';\n\n/**\n * Set of orphan ids whose visibility is justified by the current cluster\n * selection. Caller toggles `is-hidden` on the corresponding `<g class=\"node\">`s.\n */\nexport function computeVisibleOrphans(\n svg: SVGSVGElement,\n visibleClusters: ReadonlySet<string>,\n): Set<string> {\n if (visibleClusters.size === 0) return new Set();\n\n const orphanNodes = Array.from(\n svg.querySelectorAll<SVGGElement>('g.node:not([data-instance])'),\n );\n const orphanIds = new Set<string>();\n for (const n of orphanNodes) {\n const id = n.getAttribute('data-id');\n if (id) orphanIds.add(id);\n }\n\n const fwdAdj = new Map<string, Set<string>>();\n const bwdAdj = new Map<string, Set<string>>();\n const link = (m: Map<string, Set<string>>, k: string, v: string): void => {\n let s = m.get(k);\n if (!s) { s = new Set(); m.set(k, s); }\n s.add(v);\n };\n\n // Orphan-orphan edges feed the BFS walk\n for (const e of Array.from(svg.querySelectorAll<SVGGElement>('g.edge'))) {\n const s = e.getAttribute('data-src') ?? '';\n const d = e.getAttribute('data-dst') ?? '';\n if (orphanIds.has(s) && orphanIds.has(d)) {\n link(fwdAdj, s, d);\n link(bwdAdj, d, s);\n }\n }\n\n // Seed orphans = orphans on either end of a cross-cluster edge whose\n // other endpoint sits in a visible cluster.\n const seedUpstream = new Set<string>();\n const seedDownstream = new Set<string>();\n for (const e of Array.from(\n svg.querySelectorAll<SVGGElement>('g.edge.cross-cluster'),\n )) {\n const s = e.getAttribute('data-src') ?? '';\n const d = e.getAttribute('data-dst') ?? '';\n const sCl = e.getAttribute('data-src-cluster') ?? '';\n const dCl = e.getAttribute('data-dst-cluster') ?? '';\n if (orphanIds.has(s) && visibleClusters.has(dCl)) seedUpstream.add(s);\n if (orphanIds.has(d) && visibleClusters.has(sCl)) seedDownstream.add(d);\n }\n\n const visible = new Set<string>();\n const bfs = (seeds: Set<string>, adj: Map<string, Set<string>>): void => {\n const q: string[] = [];\n for (const s of seeds) { visible.add(s); q.push(s); }\n while (q.length) {\n const cur = q.shift()!;\n const next = adj.get(cur);\n if (!next) continue;\n for (const x of next) {\n if (!visible.has(x)) { visible.add(x); q.push(x); }\n }\n }\n };\n bfs(seedUpstream, bwdAdj);\n bfs(seedDownstream, fwdAdj);\n return visible;\n}\n\nexport interface VisibilityState {\n /** Cluster short names currently visible. */\n readonly visibleClusters: ReadonlySet<string>;\n /**\n * When false, hide every replica/shared place (`g.node.petri-replica`)\n * regardless of which cluster it belongs to. The corresponding sidebar\n * checkbox is labelled \"Shared places\".\n */\n readonly includeSharedPlaces: boolean;\n}\n\n/**\n * Apply visibility state to an annotated SVG. Idempotent — re-call to\n * reflect a new state.\n *\n * Graphviz emits cluster member nodes as DOM SIBLINGS of the cluster `<g>`,\n * not its children. Toggling `is-hidden` on the cluster `<g>` alone hides\n * only the cluster outline + label — the nodes, junctions, transitions and\n * intra-cluster edges inside stay visible. So this routine hides them\n * explicitly:\n *\n * 1. Cluster outlines via `g.cluster` ↔ `visibleClusters`.\n * 2. Cluster member nodes via `g.node[data-instance]` ↔ `visibleClusters`.\n * 3. Orphan nodes (no `data-instance`) via directed-reachability — when\n * no cluster is visible they all hide, otherwise only the chain\n * reaching a visible cluster shows.\n * 4. Replica + original shared places (`g.node.petri-replica`) hide as a\n * group when `includeSharedPlaces=false`.\n * 5. Edges follow their endpoints — hide if either is hidden.\n */\nexport function applyVisibility(svg: SVGSVGElement, state: VisibilityState): void {\n annotateSvgForC0(svg);\n\n const { visibleClusters, includeSharedPlaces } = state;\n\n // 1. Cluster outlines.\n for (const c of Array.from(\n svg.querySelectorAll<SVGGElement>('g.cluster:not(.cluster-orchestrator)'),\n )) {\n const titleEl = c.querySelector(':scope > title');\n const shortName = (titleEl?.textContent ?? '').replace(/^cluster_/, '');\n c.classList.toggle('is-hidden', !visibleClusters.has(shortName));\n }\n\n // 2. Cluster member nodes follow their cluster's visibility.\n for (const n of Array.from(svg.querySelectorAll<SVGGElement>('g.node[data-instance]'))) {\n const inst = n.getAttribute('data-instance') ?? '';\n n.classList.toggle('is-hidden', !visibleClusters.has(inst));\n }\n\n // 3. Orphan nodes (no data-instance) — orchestrator-level transitions\n // + unclustered places. Reachability emptys to ∅ when no cluster is\n // on, which hides every orphan — the \"Hide all\" case.\n const visOrphans = computeVisibleOrphans(svg, visibleClusters);\n for (const n of Array.from(\n svg.querySelectorAll<SVGGElement>('g.node:not([data-instance])'),\n )) {\n const id = n.getAttribute('data-id') ?? '';\n n.classList.toggle('is-hidden', !visOrphans.has(id));\n }\n\n // 4. Shared places (replicas + originals). The `petri-replica` class is\n // set by `replica-tagging.ts` on BOTH the replica clones and the\n // original whose cross-cluster shape spawned them — toggling them as a\n // group lets the user collapse the shared-place clutter in one click.\n if (!includeSharedPlaces) {\n for (const n of Array.from(svg.querySelectorAll<SVGGElement>('g.node.petri-replica'))) {\n n.classList.add('is-hidden');\n }\n }\n\n // 5. Edges follow their endpoints. Map lookup beats N*M querySelector.\n const nodeById = new Map<string, SVGGElement>();\n for (const n of Array.from(svg.querySelectorAll<SVGGElement>('g.node'))) {\n const id = n.getAttribute('data-id');\n if (id) nodeById.set(id, n);\n }\n for (const e of Array.from(svg.querySelectorAll<SVGGElement>('g.edge'))) {\n const s = e.getAttribute('data-src') ?? '';\n const d = e.getAttribute('data-dst') ?? '';\n const sHidden = nodeById.get(s)?.classList.contains('is-hidden') ?? false;\n const dHidden = nodeById.get(d)?.classList.contains('is-hidden') ?? false;\n e.classList.toggle('is-hidden', sHidden || dHidden);\n }\n}\n","/**\n * CSS custom properties exposed by the canonical viewer stylesheet.\n *\n * Each property has a default in `viewer.css`. Consumers can override any of\n * them at any scope (the viewer container, the document root, etc.) to\n * theme the viewer without touching the canonical CSS.\n *\n * Names are listed here as a reference + so the IIFE bundle can expose them\n * on `window.LibpetriViewer.cssVariables`. The list is informational — the\n * stylesheet is still the source of truth.\n *\n * @module viewer/styles\n */\n\n/** CSS custom property names that the canonical stylesheet honours. */\nexport const VIEWER_CSS_VARIABLES = [\n '--lpv-bg',\n '--lpv-header-bg',\n '--lpv-border',\n '--lpv-text',\n '--lpv-muted',\n '--lpv-cluster-bg',\n '--lpv-cluster-bg-collapsed',\n '--lpv-cluster-stroke-width',\n '--lpv-cluster-stroke-dash',\n '--lpv-cluster-fill-tint',\n '--lpv-dim-opacity',\n '--lpv-active-filter-outline',\n '--lpv-legend-bg',\n '--lpv-chip-bg',\n '--lpv-chip-active-bg',\n // C0 sidebar + replica + highlight (Stage 5)\n '--lpv-sidebar-bg',\n '--lpv-sidebar-border',\n '--lpv-sidebar-text',\n '--lpv-sidebar-muted',\n '--lpv-sidebar-chip-bg',\n '--lpv-sidebar-chip-hover-bg',\n '--lpv-sidebar-chip-off-opacity',\n '--lpv-shared-glyph-color',\n '--lpv-replica-fill',\n '--lpv-replica-stroke',\n '--lpv-highlight-stroke',\n '--lpv-highlight-glow',\n '--lpv-neighbor-stroke',\n '--lpv-faded-node-opacity',\n '--lpv-faded-edge-opacity',\n '--lpv-faded-cluster-opacity',\n] as const;\n\nexport type ViewerCssVariable = (typeof VIEWER_CSS_VARIABLES)[number];\n","/**\n * Canonical libpetri Petri-net diagram viewer.\n *\n * Single source of truth for DOT → SVG rendering + cluster overlay across\n * all four surfaces:\n * - debug-ui (live debugger)\n * - dev-preview (Vite app)\n * - Javadoc taglet (Java doclet)\n * - Rustdoc embed (libpetri-docgen)\n * - TypeDoc / typescript doclet\n *\n * Build outputs:\n * - ESM: `dist/viewer/index.js` — consumed by Vite/Webpack/Node bundlers.\n * - IIFE: `dist/viewer/viewer.iife.js` — self-contained, exposes\n * `window.LibpetriViewer = { mount, ... }`. Used by the doc taglets\n * that ship pre-rendered HTML pages without a bundler.\n * - CSS: `dist/viewer/viewer.css` — canonical stylesheet (copied from\n * `src/viewer/resources/viewer.css`).\n *\n * Edits to viewer behaviour MUST happen here. The three resource directories\n * under `java/`, `rust/`, and `typescript/src/doclet/` are **build outputs**\n * populated by `scripts/build-viewer.sh` and must not be hand-edited.\n *\n * @module viewer\n */\n\nimport {\n attachPanzoom,\n DEFAULT_PANZOOM_OPTS,\n type PanzoomInstance,\n type PanzoomOptions,\n} from './pan-zoom.js';\nimport {\n applyFilter as overlayApplyFilter,\n colorForPrefix,\n discoverClusters,\n isInsideCollapsedCluster as overlayIsInsideCollapsedCluster,\n paintClusterBorders,\n setClusterCollapsed,\n type ClusterDescriptor,\n} from './cluster-overlay.js';\nimport { annotateSvgForC0 } from './c0-annotations.js';\nimport { mountSidebar, type SidebarHandle } from './chrome/sidebar.js';\nimport { applyHighlight } from './highlight.js';\nimport { tagReplicas } from './replica-tagging.js';\nimport { applyVisibility, type VisibilityState } from './visibility.js';\nimport { VIEWER_CSS_VARIABLES } from './styles.js';\n\nexport {\n colorForPrefix,\n discoverClusters,\n VIEWER_CSS_VARIABLES,\n DEFAULT_PANZOOM_OPTS,\n};\nexport type { ClusterDescriptor, PanzoomInstance, PanzoomOptions };\n\n/** Handle returned by {@link mount}. */\nexport interface ViewerHandle {\n /** The rendered root `<svg>` element. */\n readonly svg: SVGSVGElement;\n /** The panzoom instance attached to the SVG. */\n readonly panzoom: PanzoomInstance;\n /** Map of cluster prefix → descriptor for the current SVG. */\n readonly clusters: ReadonlyMap<string, ClusterDescriptor>;\n /** Prefixes currently marked collapsed on this handle. */\n readonly collapsedPrefixes: ReadonlySet<string>;\n /** Currently active \"show only <prefix>\" filter, or null. */\n readonly activeFilter: string | null;\n /** Collapse a single cluster by prefix. No-op if unknown. */\n collapse(prefix: string): void;\n /** Expand a single cluster by prefix. No-op if unknown. */\n expand(prefix: string): void;\n /** Collapse every discovered cluster. */\n collapseAll(): void;\n /** Expand every collapsed cluster. */\n expandAll(): void;\n /** Apply the \"show only <prefix>\" filter; pass `null` to clear. */\n filter(prefix: string | null): void;\n /** Reset pan/zoom to the identity transform. */\n resetZoom(): void;\n /** Scale and translate so the full diagram fits the host viewport. */\n fit(): void;\n /** Reports whether `graphId` is inside a currently-collapsed cluster. */\n isInsideCollapsedCluster(graphId: string): boolean;\n /**\n * Highlight a node and every DOM copy of the same logical place; walk\n * junctions to surface real neighbors. Pass `null` to clear. C0-only.\n */\n highlight(nodeId: string | null): void;\n /** The node id currently highlighted, or `null`. */\n readonly highlightedNodeId: string | null;\n /**\n * Set the cluster-visibility state: which clusters render, and whether\n * directed-reachable orphans render. C0-only.\n */\n setVisibility(state: VisibilityState): void;\n /** Tear down: dispose panzoom and detach listeners. */\n dispose(): void;\n}\n\n/** Options accepted by {@link mount}. */\nexport interface MountOptions {\n /**\n * Previous handle to dispose before mounting. Pass the handle returned\n * by a prior `mount()` call so callers don't need to track it separately.\n * Cluster collapse / filter state from `previousHandle` is preserved\n * across the re-render so the user's intent survives a live re-draw.\n */\n readonly previousHandle?: ViewerHandle | null;\n /** Per-call panzoom overrides; merged on top of {@link DEFAULT_PANZOOM_OPTS}. */\n readonly panzoom?: PanzoomOptions;\n /** Fired after a cluster's collapsed state changes via the handle API. */\n readonly onClusterCollapse?: (prefix: string, collapsed: boolean) => void;\n /** Fired after `filter()` runs, with the new filter prefix or null. */\n readonly onClusterFilter?: (prefix: string | null) => void;\n /** When true, append a legend sidebar + filter chip strip inside `container`. */\n readonly chrome?: boolean;\n /**\n * Layout strategy. `'elk'` (default) runs the C0 pipeline — parse → fold\n * → replicate → ELK → Graphviz `neato` pin mode — and tags replica\n * nodes for the click-all-copies + ⇄ overlay. `'graphviz'` runs the\n * plain Graphviz `dot` engine and skips replica tagging. The result is\n * cached on the DOT hash; re-mounts on identical DOT skip the pipeline.\n */\n readonly layout?: 'elk' | 'graphviz';\n}\n\n/**\n * Render a DOT source, mount the resulting SVG into `container`, wire pan/zoom,\n * and surface cluster overlay controls.\n *\n * The container's existing children are removed before the new SVG is\n * appended (the same teardown behaviour `renderDotToContainer` used to provide).\n *\n * Pass `dotSource === null` to adopt an SVG already present as a direct child\n * of `container` (pre-rendered SVG path — e.g. the Java javadoc taglet emits\n * `dot -Tsvg` at build time). In that mode the viewer never imports the\n * runtime DOT renderer, which lets the static IIFE bundle drop `@viz-js/viz`\n * entirely.\n *\n * NOTE: when shipping a pre-rendered SVG (`dotSource === null`), do NOT also\n * pass `previousHandle` — the handle's `dispose()` runs before SVG detection\n * and would orphan the host. The Java taglet mounts once per page so this is\n * a non-issue in practice.\n */\nexport async function mount(\n dotSource: string | null,\n container: HTMLElement,\n opts: MountOptions = {},\n): Promise<ViewerHandle> {\n const previousHandle = opts.previousHandle ?? null;\n const preservedCollapsed = previousHandle\n ? new Set(previousHandle.collapsedPrefixes)\n : new Set<string>();\n const preservedFilter = previousHandle?.activeFilter ?? null;\n\n if (previousHandle) {\n previousHandle.dispose();\n }\n\n // Gate on dotSource (not on \"SVG present\") — debug-ui re-renders by\n // passing DOT with `previousHandle` and the leftover SVG must NOT be\n // misread as pre-rendered. Java passes literal `null` for the\n // pre-rendered path.\n let svg: SVGSVGElement;\n const existing = container.querySelector(':scope > svg');\n if (dotSource == null) {\n if (!(existing instanceof SVGSVGElement)) {\n throw new Error('mount: no DOT source and no pre-rendered SVG');\n }\n svg = existing;\n } else {\n // Dynamic import so the static IIFE entry can alias './render.js' to a\n // throwing stub and drop the @viz-js/viz + elkjs dependencies.\n const renderModule = await import('./render.js');\n const useElk = (opts.layout ?? 'elk') === 'elk';\n svg = useElk\n ? await renderModule.renderDotToSvgWithElkLayout(dotSource)\n : await renderModule.renderDotToSvg(dotSource);\n container.innerHTML = '';\n container.appendChild(svg);\n }\n // Tag the host so the canonical CSS (which scopes to .libpetri-viewer)\n // applies even when the consumer didn't wrap us in a .petrinet-diagram.\n container.classList.add('libpetri-viewer');\n\n const panzoomInstance = attachPanzoom(svg, opts.panzoom);\n const clusters = discoverClusters(svg);\n paintClusterBorders(clusters);\n const layoutMode = opts.layout ?? 'elk';\n if (layoutMode === 'elk') {\n tagReplicas(svg);\n annotateSvgForC0(svg);\n }\n\n const collapsedPrefixes = new Set<string>();\n let activeFilter: string | null = null;\n let highlightedNodeId: string | null = null;\n let disposed = false;\n\n // Chrome elements (created lazily; only when chrome:true is requested).\n // Re-rendering through the same handle does not duplicate them.\n let chromeRoot: HTMLElement | null = null;\n let sidebarHandle: SidebarHandle | null = null;\n let highlightModeEnabled = true;\n\n function ensureChrome(): void {\n if (!opts.chrome) return;\n if (chromeRoot && chromeRoot.parentNode === container) return;\n\n // Reset + Fullscreen controls anchor top-right; the C0 sidebar\n // (top-left) replaces the legacy legend + filter strip.\n chromeRoot = document.createElement('div');\n chromeRoot.className = 'libpetri-viewer-chrome';\n chromeRoot.style.position = 'absolute';\n chromeRoot.style.inset = '0';\n chromeRoot.style.pointerEvents = 'none';\n\n const controls = document.createElement('div');\n controls.className = 'diagram-controls';\n controls.style.pointerEvents = 'auto';\n const resetBtn = document.createElement('button');\n resetBtn.type = 'button';\n resetBtn.className = 'diagram-btn btn-reset';\n resetBtn.title = 'Reset view';\n resetBtn.textContent = 'Reset';\n resetBtn.addEventListener('click', () => handle.fit());\n const fsBtn = document.createElement('button');\n fsBtn.type = 'button';\n fsBtn.className = 'diagram-btn btn-fullscreen';\n fsBtn.title = 'Toggle fullscreen';\n fsBtn.textContent = 'Fullscreen';\n fsBtn.addEventListener('click', () => toggleFullscreen(container, fsBtn));\n controls.appendChild(resetBtn);\n controls.appendChild(fsBtn);\n chromeRoot.appendChild(controls);\n\n if (getComputedStyle(container).position === 'static') {\n container.style.position = 'relative';\n }\n container.appendChild(chromeRoot);\n\n // The cluster-chip sidebar is the C0 chrome — only mount when\n // there's a renderable layout and at least one cluster to show.\n if (layoutMode === 'elk' && clusters.size > 0) {\n sidebarHandle = mountSidebar(container, clusters, {\n onVisibilityChange: (state) => handle.setVisibility(state),\n onHighlightModeChange: (enabled) => {\n highlightModeEnabled = enabled;\n if (!enabled) handle.highlight(null);\n },\n });\n }\n }\n\n // In-page lightbox fullscreen: toggle a CSS class on the host and\n // re-fit. We deliberately don't invoke the native Fullscreen API —\n // users want the diagram to expand inside the page, not yank the\n // browser into OS-level fullscreen.\n function toggleFullscreen(host: HTMLElement, btn: HTMLButtonElement): void {\n const isFs = host.classList.toggle('diagram-fullscreen');\n btn.textContent = isFs ? 'Exit fullscreen' : 'Fullscreen';\n requestAnimationFrame(() => handle.fit());\n }\n\n const handle: ViewerHandle = {\n svg,\n panzoom: panzoomInstance,\n clusters,\n get collapsedPrefixes() {\n return collapsedPrefixes;\n },\n get activeFilter() {\n return activeFilter;\n },\n collapse(prefix: string): void {\n if (disposed) return;\n const cluster = clusters.get(prefix);\n if (!cluster) return;\n if (!collapsedPrefixes.has(prefix)) {\n setClusterCollapsed(cluster, true);\n collapsedPrefixes.add(prefix);\n opts.onClusterCollapse?.(prefix, true);\n }\n },\n expand(prefix: string): void {\n if (disposed) return;\n const cluster = clusters.get(prefix);\n if (!cluster) return;\n if (collapsedPrefixes.has(prefix)) {\n setClusterCollapsed(cluster, false);\n collapsedPrefixes.delete(prefix);\n opts.onClusterCollapse?.(prefix, false);\n }\n },\n collapseAll(): void {\n if (disposed) return;\n for (const cluster of clusters.values()) {\n if (!collapsedPrefixes.has(cluster.prefix)) {\n setClusterCollapsed(cluster, true);\n collapsedPrefixes.add(cluster.prefix);\n opts.onClusterCollapse?.(cluster.prefix, true);\n }\n }\n },\n expandAll(): void {\n if (disposed) return;\n for (const prefix of Array.from(collapsedPrefixes)) {\n const cluster = clusters.get(prefix);\n if (!cluster) continue;\n setClusterCollapsed(cluster, false);\n collapsedPrefixes.delete(prefix);\n opts.onClusterCollapse?.(prefix, false);\n }\n },\n filter(prefix: string | null): void {\n if (disposed) return;\n activeFilter = prefix;\n overlayApplyFilter(svg, prefix);\n opts.onClusterFilter?.(prefix);\n },\n resetZoom(): void {\n if (disposed) return;\n try {\n panzoomInstance.moveTo(0, 0);\n panzoomInstance.zoomAbs(0, 0, 1);\n } catch {\n // panzoom internals can throw if the element was detached; ignore.\n }\n },\n fit(): void {\n if (disposed) return;\n try {\n // With the host CSS-sized and the SVG forced to width/height: 100%\n // (see .petrinet-diagram-viewer rules in viewer.css), the browser\n // auto-fits viewBox content via preserveAspectRatio=\"xMidYMid meet\".\n // \"Fit\" is therefore the panzoom identity transform — no math needed.\n panzoomInstance.zoomAbs(0, 0, 1);\n panzoomInstance.moveTo(0, 0);\n } catch {\n // panzoom internals can throw if the element was detached; ignore.\n }\n },\n isInsideCollapsedCluster(graphId: string): boolean {\n return overlayIsInsideCollapsedCluster(svg, clusters, collapsedPrefixes, graphId);\n },\n get highlightedNodeId() {\n return highlightedNodeId;\n },\n highlight(nodeId: string | null): void {\n if (disposed) return;\n if (layoutMode !== 'elk') return;\n highlightedNodeId = nodeId;\n applyHighlight(svg, nodeId);\n },\n setVisibility(state: VisibilityState): void {\n if (disposed) return;\n if (layoutMode !== 'elk') return;\n applyVisibility(svg, state);\n },\n dispose(): void {\n if (disposed) return;\n disposed = true;\n try {\n panzoomInstance.dispose();\n } catch {\n // ignore\n }\n sidebarHandle?.dispose();\n sidebarHandle = null;\n if (chromeRoot && chromeRoot.parentNode === container) {\n container.removeChild(chromeRoot);\n }\n chromeRoot = null;\n },\n };\n\n // Re-apply preserved collapse/filter state from the previous handle so\n // a live re-render (e.g. on marking-snapshot) keeps the user's selection.\n for (const prefix of preservedCollapsed) {\n handle.collapse(prefix);\n }\n if (preservedFilter !== null) {\n handle.filter(preservedFilter);\n }\n\n // C0 click-to-highlight: clicking a node highlights it + every copy of\n // the same logical place. Clicking the SVG background (or the already-\n // highlighted node) clears. The sidebar's \"Click node → highlight\"\n // toggle gates the binding via `highlightModeEnabled`.\n if (layoutMode === 'elk') {\n svg.addEventListener('click', (ev) => {\n if (!highlightModeEnabled) return;\n const target = ev.target;\n if (!(target instanceof Element)) return;\n const nodeG = target.closest('g.node') as SVGGElement | null;\n if (!nodeG) {\n handle.highlight(null);\n return;\n }\n const id = nodeG.getAttribute('data-id');\n if (!id) return;\n handle.highlight(id === highlightedNodeId ? null : id);\n });\n }\n\n ensureChrome();\n\n // Default state: fit-to-host. Huge diagrams shouldn't mount at 1:1\n // (the user only sees the top-left corner). Deferred a frame so the\n // SVG has been laid out before getBBox + clientWidth/Height reads.\n requestAnimationFrame(() => handle.fit());\n\n return handle;\n}\n"],"mappings":"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","names":["cssEscape"]}
1
+ {"version":3,"sources":["../../src/viewer/pan-zoom.ts","../../src/viewer/cluster-overlay.ts","../../src/viewer/c0-annotations.ts","../../src/viewer/chrome/sidebar.ts","../../src/viewer/highlight.ts","../../src/viewer/replica-tagging.ts","../../src/viewer/visibility.ts","../../src/viewer/styles.ts","../../src/viewer/dot-flatten.ts","../../src/viewer/index.ts"],"sourcesContent":["/**\n * Pan/zoom wrapper for the canonical libpetri viewer.\n *\n * Delegates to the `panzoom` library (no hand-rolled wheel handlers — we\n * intentionally drop the IIFE/javadoc version's bespoke math in favour of\n * a single battle-tested implementation). Defaults match the old debug-ui\n * and dev-preview values so the canonical viewer feels identical to what\n * users had before.\n *\n * @module viewer/pan-zoom\n */\n\nimport panzoom from 'panzoom';\n\nexport type PanzoomInstance = ReturnType<typeof panzoom>;\nexport type PanzoomOptions = Parameters<typeof panzoom>[1];\n\n/**\n * Default panzoom configuration shared across all viewer surfaces.\n *\n * - `maxZoom: 1000` — effectively unlimited; lets users dive into ~150-node\n * diagrams without hitting an artificial ceiling.\n * - `minZoom: 0.02` — allows fitting very large nets in a small viewport.\n * - `smoothScroll: false` — sharper interactive feel.\n * - `zoomDoubleClickSpeed: 1` — single-click double-zoom toggle.\n */\nexport const DEFAULT_PANZOOM_OPTS = {\n smoothScroll: false,\n zoomDoubleClickSpeed: 1,\n maxZoom: 1000,\n minZoom: 0.02,\n} as const satisfies PanzoomOptions;\n\n/**\n * Attach panzoom to a freshly rendered SVG element. The caller is\n * responsible for disposing any previous instance (or pass it as\n * `previous` to dispose it here).\n */\nexport function attachPanzoom(\n svg: SVGSVGElement,\n options?: PanzoomOptions,\n previous?: PanzoomInstance | null,\n): PanzoomInstance {\n if (previous) {\n try {\n previous.dispose();\n } catch {\n // Swallow disposal errors — the previous instance may already be\n // detached or the panzoom internals may have raised on a missing\n // root node. Either way we just want a fresh instance.\n }\n }\n const opts = { ...DEFAULT_PANZOOM_OPTS, ...options };\n return panzoom(svg, opts);\n}\n","/**\n * Cluster overlay for the canonical libpetri viewer.\n *\n * Post-processes a rendered SVG to surface subnet structure visually:\n * 1. Discover `<g class=\"cluster\">` subgraphs, derive each prefix from the\n * contained `<title>` (Graphviz emits `cluster_<sanitizedPrefix>`).\n * 2. Tag every contained `<g class=\"node\">` with `data-instance=\"<prefix>\"`\n * so filtering can target it via attribute selectors.\n * 3. Assign each cluster a deterministic HSL colour (FNV-1a hash + golden\n * ratio hue increment) and paint its border.\n * 4. Provide collapse/expand and \"show only <prefix>\" filtering.\n *\n * Unlike the previous debug-ui cluster-overlay, this module is instance-\n * based: every `mount()` call gets its own `ClusterOverlay` so multiple\n * viewers on the same page don't share collapse/isolate state.\n *\n * @module viewer/cluster-overlay\n */\n\n/** A discovered cluster with its DOM group, node count, and palette colour. */\nexport interface ClusterDescriptor {\n readonly prefix: string;\n readonly group: SVGGElement;\n readonly nodeCount: number;\n readonly color: string;\n}\n\n/**\n * Augmentation slots stashed on the cluster `<g>` element across collapse\n * cycles. We don't subclass SVGElement; we tag arbitrary properties on the\n * DOM node, matching the doclet's `petrinet-diagrams.js` so the cross-surface\n * mental model is identical.\n *\n * `_petriHiddenSiblings` holds the sibling `<g class=\"node\">` and\n * `<g class=\"edge\">` elements (sibling to the cluster, not its DOM children\n * — Graphviz never nests nodes inside cluster groups) tagged as belonging\n * to this prefix at the moment of collapse. They retain their place in the\n * SVG tree; we hide them via the `petri-collapsed-inside` class so the\n * underlying layout is preserved for a cheap re-expand.\n */\ninterface ClusterAugment {\n _petriHiddenSiblings?: Element[];\n _petriCollapsedBadge?: SVGTextElement | null;\n}\ntype AugmentedGroup = SVGGElement & ClusterAugment;\n\n/**\n * Walk the SVG, find every `<g class=\"cluster\">`, derive the prefix from\n * each cluster's `<title>` (`cluster_<sanitized>`), and tag every cluster-\n * interior `<g class=\"node\">` with `data-instance=\"<prefix>\"`. Returns one\n * descriptor per cluster.\n *\n * Graphviz emits cluster nodes as SIBLINGS of the cluster `<g>`, not as\n * children — `<g class=\"cluster\">` only carries title, polygon border, and\n * the cluster's text label. To recover cluster membership we match each\n * node's `<title>` (the original graph id) against libpetri's prefixing\n * convention: interior places are emitted as `p_<prefix>_<name>`,\n * transitions as `t_<prefix>_<name>`, and junctions as `j_<prefix>_<name>`.\n *\n * Does not split nested-cluster prefixes (`outer_inner` could mean either\n * `outer/inner` or a flat `outer_inner` prefix — Graphviz sanitisation is\n * lossy). We treat every cluster as its own top-level entry, matching the\n * doclet's pragmatism.\n */\nexport function discoverClusters(svg: SVGSVGElement): Map<string, ClusterDescriptor> {\n const out = new Map<string, ClusterDescriptor>();\n const clusterGroups = svg.querySelectorAll<SVGGElement>('g.cluster');\n if (!clusterGroups.length) return out;\n\n const allNodes = Array.from(svg.querySelectorAll<SVGGElement>('g.node'));\n\n for (const g of Array.from(clusterGroups)) {\n const titleEl = directChild(g, 'title');\n if (!titleEl) continue;\n const raw = titleEl.textContent ?? '';\n if (!raw.startsWith('cluster_')) continue;\n const prefix = raw.slice('cluster_'.length);\n if (out.has(prefix)) continue;\n\n let nodeCount = 0;\n for (const node of allNodes) {\n // Don't re-tag a node already claimed by an inner (more specific)\n // cluster — outer clusters can still match by string-prefix at filter\n // time, but the data-instance is the leaf.\n if (node.getAttribute('data-instance')) continue;\n const nodeTitle = directChild(node, 'title');\n if (!nodeTitle) continue;\n const id = (nodeTitle.textContent ?? '').trim();\n if (nodeBelongsToPrefix(id, prefix)) {\n node.setAttribute('data-instance', prefix);\n nodeCount++;\n }\n }\n\n out.set(prefix, {\n prefix,\n group: g,\n nodeCount,\n color: colorForPrefix(prefix),\n });\n }\n return out;\n}\n\n/**\n * Does this node id belong to the cluster with the given prefix?\n *\n * libpetri's DOT renderer prefixes interior names: places as `p_<prefix>_…`,\n * transitions as `t_<prefix>_…`, junctions as `j_<prefix>_…`. Generic DOT\n * may also use slash-separated names (`<prefix>/…`). We accept both shapes\n * so non-libpetri DOT graphs and bare-Graphviz fixtures still match.\n */\nfunction nodeBelongsToPrefix(nodeId: string, prefix: string): boolean {\n return (\n nodeId === prefix ||\n nodeId.startsWith(`${prefix}/`) ||\n nodeId.startsWith(`p_${prefix}_`) ||\n nodeId.startsWith(`t_${prefix}_`) ||\n nodeId.startsWith(`j_${prefix}_`)\n );\n}\n\n/**\n * Deterministic HSL palette. FNV-1a-ish hash of the prefix, multiplied by\n * the golden-ratio fraction (0.61803...) to spread adjacent prefixes\n * visually. Saturation/lightness are fixed (60% / 45%) so the palette\n * stays readable on both the doc background and the dark debug UI.\n */\nexport function colorForPrefix(prefix: string): string {\n let h = 2166136261;\n for (let i = 0; i < prefix.length; i++) {\n h ^= prefix.charCodeAt(i);\n h = (h * 16777619) >>> 0;\n }\n let hue = Math.floor(((h / 4294967296) * 360 + (h % 360) * 0.61803) % 360);\n if (hue < 0) hue += 360;\n return `hsl(${hue}, 60%, 45%)`;\n}\n\n/** Paint the cluster border with the deterministic prefix colour. */\nexport function paintClusterBorders(clusters: Map<string, ClusterDescriptor>): void {\n for (const c of clusters.values()) {\n const border = directChild(c.group, 'polygon') ?? directChild(c.group, 'path');\n if (border) {\n border.setAttribute('stroke', c.color);\n // Slightly thicker than the old 2.0 — the user asked for clusters to\n // be visually obvious. Backed off from 3 to keep it readable at the\n // tiniest zoom.\n border.setAttribute('stroke-width', '2.2');\n }\n }\n}\n\n/**\n * Collapse / expand a cluster.\n *\n * Graphviz emits nodes and edges as siblings of the cluster `<g>`, not as\n * its DOM children. So \"collapsing\" cannot work by detaching the cluster's\n * children — that only removes title/border/label. Instead, we identify the\n * cluster-interior nodes (`data-instance=\"<prefix>\"`) and edges with both\n * endpoints inside the cluster, then add the `petri-collapsed-inside` class\n * so the canonical viewer.css hides them. Stash the list on the cluster\n * group so re-expand is a constant-time class removal.\n *\n * Edges that cross the cluster boundary (one endpoint inside, one outside)\n * are intentionally left visible — they show the cluster's external wiring\n * even when the interior is hidden.\n *\n * A small `<text>` badge with the actual interior node count is appended\n * inside the cluster `<g>` (which keeps its layout box).\n */\nexport function setClusterCollapsed(cluster: ClusterDescriptor, collapsed: boolean): void {\n const g = cluster.group as AugmentedGroup;\n const isCollapsed = g.classList.contains('cluster-collapsed');\n const root = g.ownerSVGElement;\n if (!root) return;\n\n if (collapsed && !isCollapsed) {\n g.classList.add('cluster-collapsed');\n const hidden: Element[] = [];\n\n // Hide interior nodes (sibling elements tagged with this prefix).\n root\n .querySelectorAll<SVGGElement>(`g.node[data-instance=\"${cssEscape(cluster.prefix)}\"]`)\n .forEach((node) => {\n node.classList.add('petri-collapsed-inside');\n hidden.push(node);\n });\n\n // Hide interior-interior edges. Edges' titles are `from->to`; we hide\n // an edge only when BOTH endpoints belong to this cluster.\n root.querySelectorAll<SVGGElement>('g.edge').forEach((edge) => {\n const titleEl = directChild(edge, 'title');\n if (!titleEl) return;\n const t = (titleEl.textContent ?? '').trim();\n const arrow = t.indexOf('->');\n if (arrow < 0) return;\n const from = t.slice(0, arrow);\n const to = t.slice(arrow + 2);\n if (nodeBelongsToPrefix(from, cluster.prefix) && nodeBelongsToPrefix(to, cluster.prefix)) {\n edge.classList.add('petri-collapsed-inside');\n hidden.push(edge);\n }\n });\n\n g._petriHiddenSiblings = hidden;\n\n if (!g._petriCollapsedBadge) {\n const label = directChild(g, 'text') as SVGTextElement | null;\n if (label) {\n const badge = document.createElementNS('http://www.w3.org/2000/svg', 'text');\n badge.setAttribute('x', label.getAttribute('x') ?? '0');\n const labelY = parseFloat(label.getAttribute('y') ?? '0');\n badge.setAttribute('y', String(labelY + 16));\n badge.setAttribute('text-anchor', label.getAttribute('text-anchor') ?? 'middle');\n badge.setAttribute('font-size', '11');\n badge.setAttribute('fill', '#9ca3af');\n badge.setAttribute('class', 'cluster-collapsed-badge');\n badge.textContent = `(${cluster.nodeCount} internal node${cluster.nodeCount === 1 ? '' : 's'})`;\n g.appendChild(badge);\n g._petriCollapsedBadge = badge;\n }\n }\n } else if (!collapsed && isCollapsed) {\n g.classList.remove('cluster-collapsed');\n if (g._petriHiddenSiblings) {\n g._petriHiddenSiblings.forEach((el) => el.classList.remove('petri-collapsed-inside'));\n g._petriHiddenSiblings = undefined;\n }\n if (g._petriCollapsedBadge && g._petriCollapsedBadge.parentNode === g) {\n g.removeChild(g._petriCollapsedBadge);\n g._petriCollapsedBadge = null;\n }\n }\n}\n\n/**\n * Toggle the \"show only <prefix>\" filter. Passing `null` (or the same\n * prefix already active) clears the filter. Mirrors the doclet's\n * `applyFilter`, including the edge-dimming heuristic that parses each\n * edge's `<title>` (`from->to`) and keeps an edge visible when either\n * endpoint matches the active prefix.\n */\nexport function applyFilter(svg: SVGSVGElement, prefix: string | null): void {\n if (prefix == null) {\n svg.removeAttribute('data-active-filter');\n svg.classList.remove('has-active-filter');\n svg.querySelectorAll('g.node, g.edge').forEach((el) => el.classList.remove('petri-dimmed'));\n return;\n }\n svg.setAttribute('data-active-filter', prefix);\n svg.classList.add('has-active-filter');\n svg.querySelectorAll('g.node').forEach((node) => {\n const di = node.getAttribute('data-instance');\n const match =\n !!di &&\n (di === prefix || di.indexOf(prefix + '_') === 0 || di.indexOf(prefix + '/') === 0);\n if (match) node.classList.remove('petri-dimmed');\n else node.classList.add('petri-dimmed');\n });\n svg.querySelectorAll('g.edge').forEach((edge) => {\n const titleEl = directChild(edge, 'title');\n if (!titleEl) return;\n const titleText = titleEl.textContent ?? '';\n const arrow = titleText.indexOf('->');\n if (arrow < 0) {\n edge.classList.remove('petri-dimmed');\n return;\n }\n const from = titleText.slice(0, arrow);\n const to = titleText.slice(arrow + 2);\n const matches = (name: string): boolean =>\n name.indexOf(prefix + '/') >= 0 || name.indexOf(prefix + '_') >= 0 || name === prefix;\n if (matches(from) || matches(to)) edge.classList.remove('petri-dimmed');\n else edge.classList.add('petri-dimmed');\n });\n}\n\n/**\n * Returns true when the place/transition node identified by `graphId` is\n * currently inside a collapsed cluster (i.e. detached from the live SVG).\n * Live consumers (e.g. highlight code in debug-ui) use this to skip lookups\n * that would otherwise hit a stale cache entry pointing at a detached node.\n */\nexport function isInsideCollapsedCluster(\n svg: SVGSVGElement | null,\n clusters: Map<string, ClusterDescriptor>,\n collapsedPrefixes: ReadonlySet<string>,\n graphId: string,\n): boolean {\n if (collapsedPrefixes.size === 0) return false;\n if (!svg) return false;\n for (const prefix of collapsedPrefixes) {\n if (!clusters.has(prefix)) continue;\n if (nodeBelongsToPrefix(graphId, prefix)) return true;\n }\n return false;\n}\n\n/** Minimal CSS.escape polyfill — happy-dom may not implement CSS globals. */\nfunction cssEscape(s: string): string {\n if (typeof CSS !== 'undefined' && typeof CSS.escape === 'function') return CSS.escape(s);\n return s.replace(/[^a-zA-Z0-9_-]/g, (c) => `\\\\${c}`);\n}\n\n/**\n * `:scope > <tag>` polyfill — happy-dom doesn't reliably resolve `:scope`\n * against namespaced SVG elements (returns null even when the child exists).\n * Walk `children` directly instead, matching by lower-cased tag name.\n */\nfunction directChild(parent: Element, tagName: string): Element | null {\n const lc = tagName.toLowerCase();\n for (const child of Array.from(parent.children)) {\n if (child.tagName.toLowerCase() === lc) return child;\n }\n return null;\n}\n","/**\n * Post-render SVG annotation for the C0 layout pipeline.\n *\n * Graphviz emits a clean SVG but without the `data-id`, `data-src`,\n * `data-dst`, `data-cluster`, `data-instance` attributes the click-all-\n * copies, highlight-through-junctions, and directed-reachability overlays\n * key off. {@link annotateSvgForC0} walks the rendered SVG once and adds\n * those attributes, plus `intra-cluster` / `cross-cluster` classes on\n * `<g class=\"edge\">` so the visibility layer can toggle edges as their\n * containing cluster's state changes.\n *\n * Mirrors the SVG-side conventions used by v3\n * (`run16_GOOD_v3_skip-junctions.mjs` overlay block, lines 876–905) but\n * derived from Graphviz's emitted DOM shape (`<g class=\"node\"><title>`,\n * `<g class=\"edge\"><title>src-&gt;dst</title>`) rather than the manual\n * emission v3 did.\n *\n * @module viewer/c0-annotations\n */\n\n/** Read the immediate `<title>` child's text content (Graphviz convention). */\nfunction ownTitle(g: Element): string {\n for (const child of Array.from(g.children)) {\n if (child.tagName === 'title') return child.textContent ?? '';\n }\n return '';\n}\n\n/**\n * Walk to the nearest ancestor `<g class=\"cluster\">` and return its\n * short name (with the leading `cluster_` stripped), or `''` if the node\n * lives at root (i.e. it's an orphan inside the synthetic orchestrator).\n *\n * Graphviz emits clusters as DOM **siblings** of their member nodes (not\n * parents), so `closest('g.cluster')` is null in our pipeline — this is\n * effectively a no-op fallback. Cluster membership is established up\n * front by {@link discoverClusters} via `<title>` prefix matching.\n */\nfunction enclosingClusterShortName(node: Element): string {\n const cluster = node.parentElement?.closest('g.cluster');\n if (!cluster) return '';\n return ownTitle(cluster).replace(/^cluster_/, '');\n}\n\n/**\n * Annotate a Graphviz-rendered SVG with the data attributes + edge classes\n * the C0 overlay layer relies on. Idempotent.\n *\n * Must run AFTER {@link discoverClusters}, which seeds `data-instance` via\n * title prefix matching (cluster → its member nodes by `t_${prefix}_…` /\n * `p_${prefix}_…` etc.). This function never clears `data-instance` —\n * Graphviz nests neither nodes inside clusters nor anything else, so DOM\n * ancestry is not a reliable source of cluster membership here.\n */\nexport function annotateSvgForC0(svg: SVGSVGElement): void {\n // Nodes — data-id (from title). Prefer the cluster tag discoverClusters\n // already wrote. Replicas (id ends `__rep__<shortName>`) override that\n // tag with their target cluster so the visibility layer can hide them\n // alongside the cluster they belong to — discoverClusters keys off the\n // libpetri prefix convention (`p_<short>_`) which replica ids don't match.\n const nodes = Array.from(svg.querySelectorAll<SVGGElement>('g.node'));\n for (const node of nodes) {\n const id = ownTitle(node);\n if (!id) continue;\n node.setAttribute('data-id', id);\n const replicaM = /__rep__([A-Za-z0-9_]+)$/.exec(id);\n if (replicaM) {\n node.setAttribute('data-instance', replicaM[1]!);\n } else if (!node.hasAttribute('data-instance')) {\n const inst = enclosingClusterShortName(node);\n if (inst) node.setAttribute('data-instance', inst);\n }\n }\n\n // Index nodes by data-id for fast cluster lookup on edge endpoints\n const nodeIndex = new Map<string, SVGGElement>();\n for (const n of nodes) {\n const id = n.getAttribute('data-id');\n if (id) nodeIndex.set(id, n);\n }\n\n // Edges — parse \"src->dst\" from title; tag with data-src/data-dst,\n // their cluster short names, and intra-/cross-cluster class.\n for (const edge of Array.from(svg.querySelectorAll<SVGGElement>('g.edge'))) {\n const title = ownTitle(edge);\n const arrow = title.indexOf('->');\n if (arrow <= 0) continue;\n const src = title.slice(0, arrow).trim();\n const dst = title.slice(arrow + 2).trim();\n edge.setAttribute('data-src', src);\n edge.setAttribute('data-dst', dst);\n\n const srcInst = nodeIndex.get(src)?.getAttribute('data-instance') ?? '';\n const dstInst = nodeIndex.get(dst)?.getAttribute('data-instance') ?? '';\n edge.setAttribute('data-src-cluster', srcInst);\n edge.setAttribute('data-dst-cluster', dstInst);\n\n edge.classList.remove('intra-cluster', 'cross-cluster');\n if (srcInst !== '' && srcInst === dstInst) {\n edge.classList.add('intra-cluster');\n edge.setAttribute('data-cluster', srcInst);\n } else {\n edge.classList.add('cross-cluster');\n edge.removeAttribute('data-cluster');\n }\n }\n}\n","/**\n * C0 cluster-chip sidebar. Replaces the previous top-left legend +\n * bottom filter strip with a single left-docked panel:\n *\n * - Cluster chips (click = toggle; shift-click = isolate to just this;\n * ctrl/cmd-click = add/remove from selection)\n * - \"show all\" / \"hide all\" buttons\n * - Shared-places toggle (orphan / cross-cluster places on/off)\n * - Highlight-mode toggle (click-node-to-highlight on/off)\n * - Inline legend explaining the ⇄ glyph\n *\n * Mounted from {@link import('../index.js').mount} when `chrome: true`.\n * Lives inside the viewer container as an `aside` so it travels with the\n * container into fullscreen.\n *\n * @module viewer/chrome/sidebar\n */\n\nimport type { ClusterDescriptor } from '../cluster-overlay.js';\nimport type { VisibilityState } from '../visibility.js';\n\nexport interface SidebarCallbacks {\n /**\n * Fired whenever the visibility state changes (chip click, show/hide all,\n * or orphan toggle). Caller forwards to `handle.setVisibility(state)`.\n */\n readonly onVisibilityChange: (state: VisibilityState) => void;\n /**\n * Fired when the user toggles highlight mode off, so the caller can\n * clear any active highlight via `handle.highlight(null)`.\n */\n readonly onHighlightModeChange: (enabled: boolean) => void;\n}\n\nexport interface SidebarHandle {\n /** The root `<aside>` element appended to the container. */\n readonly root: HTMLElement;\n /** Tear down listeners and remove the sidebar from the DOM. */\n dispose(): void;\n}\n\nconst SIDEBAR_CLASS = 'libpetri-sidebar';\nconst CHIP_CLASS = 'libpetri-sidebar-chip';\nconst CHIP_OFF_CLASS = 'is-off';\n\n/**\n * Build + mount the sidebar inside `container`. Returns a handle the\n * caller disposes on re-mount.\n */\nexport function mountSidebar(\n container: HTMLElement,\n clusters: ReadonlyMap<string, ClusterDescriptor>,\n callbacks: SidebarCallbacks,\n): SidebarHandle {\n const visibleClusters = new Set<string>();\n for (const prefix of clusters.keys()) visibleClusters.add(prefix);\n let includeSharedPlaces = true;\n let highlightMode = true;\n\n const emit = (): void => {\n callbacks.onVisibilityChange({\n visibleClusters: new Set(visibleClusters),\n includeSharedPlaces,\n });\n };\n\n const root = document.createElement('aside');\n root.className = SIDEBAR_CLASS;\n\n const title = document.createElement('h4');\n title.className = 'libpetri-sidebar-title';\n title.textContent = `Subnets (${clusters.size})`;\n root.appendChild(title);\n\n // Show all / hide all\n const actions = document.createElement('div');\n actions.className = 'libpetri-sidebar-actions';\n const showAll = document.createElement('button');\n showAll.type = 'button';\n showAll.textContent = 'show all';\n const hideAll = document.createElement('button');\n hideAll.type = 'button';\n hideAll.textContent = 'hide all';\n actions.appendChild(showAll);\n actions.appendChild(hideAll);\n root.appendChild(actions);\n\n // Shared-places toggle — hides every replica + original of a shared\n // place (anything tagged `petri-replica`) when unchecked.\n const sharedLabel = document.createElement('label');\n sharedLabel.className = 'libpetri-sidebar-checkbox';\n const sharedCb = document.createElement('input');\n sharedCb.type = 'checkbox';\n sharedCb.checked = includeSharedPlaces;\n sharedLabel.appendChild(sharedCb);\n sharedLabel.appendChild(document.createTextNode(' Shared places'));\n sharedCb.addEventListener('change', () => {\n includeSharedPlaces = sharedCb.checked;\n emit();\n });\n root.appendChild(sharedLabel);\n\n // Highlight-mode toggle\n const hlLabel = document.createElement('label');\n hlLabel.className = 'libpetri-sidebar-checkbox';\n const hlCb = document.createElement('input');\n hlCb.type = 'checkbox';\n hlCb.checked = highlightMode;\n hlLabel.appendChild(hlCb);\n hlLabel.appendChild(document.createTextNode(' Click node → highlight'));\n hlCb.addEventListener('change', () => {\n highlightMode = hlCb.checked;\n callbacks.onHighlightModeChange(highlightMode);\n });\n root.appendChild(hlLabel);\n\n // Hint\n const hint = document.createElement('div');\n hint.className = 'libpetri-sidebar-hint';\n hint.innerHTML =\n 'Click chip: toggle.<br>' +\n 'Shift-click: isolate.<br>' +\n 'Cmd/Ctrl-click: multi-select.<br>' +\n 'Click empty: clear highlight.';\n root.appendChild(hint);\n\n // Chips\n const chipList = document.createElement('ul');\n chipList.className = 'libpetri-sidebar-chips';\n const chipMap = new Map<string, HTMLLIElement>();\n for (const cluster of clusters.values()) {\n const li = document.createElement('li');\n li.className = CHIP_CLASS;\n li.style.borderLeftColor = cluster.color;\n li.dataset.prefix = cluster.prefix;\n const dot = document.createElement('span');\n dot.className = 'libpetri-sidebar-chip-dot';\n dot.style.background = cluster.color;\n const name = document.createElement('span');\n name.className = 'libpetri-sidebar-chip-name';\n name.textContent = cluster.prefix;\n const count = document.createElement('span');\n count.className = 'libpetri-sidebar-chip-count';\n count.textContent = String(cluster.nodeCount);\n li.appendChild(dot);\n li.appendChild(name);\n li.appendChild(count);\n li.addEventListener('click', (ev) => {\n const prefix = cluster.prefix;\n if (ev.shiftKey) {\n // Isolate to just this one — or restore \"show all\" if already isolated.\n const allOff = [...clusters.keys()].every(k =>\n k === prefix ? visibleClusters.has(k) : !visibleClusters.has(k),\n );\n visibleClusters.clear();\n if (allOff) for (const k of clusters.keys()) visibleClusters.add(k);\n else visibleClusters.add(prefix);\n } else if (ev.ctrlKey || ev.metaKey) {\n // Multi-select: ADD/remove. If currently \"show all\", start fresh.\n const anyHidden = [...clusters.keys()].some(k => !visibleClusters.has(k));\n if (!anyHidden) {\n visibleClusters.clear();\n visibleClusters.add(prefix);\n } else if (visibleClusters.has(prefix)) {\n visibleClusters.delete(prefix);\n } else {\n visibleClusters.add(prefix);\n }\n } else {\n if (visibleClusters.has(prefix)) visibleClusters.delete(prefix);\n else visibleClusters.add(prefix);\n }\n refreshChips();\n emit();\n });\n chipList.appendChild(li);\n chipMap.set(cluster.prefix, li);\n }\n root.appendChild(chipList);\n\n function refreshChips(): void {\n for (const [prefix, li] of chipMap) {\n li.classList.toggle(CHIP_OFF_CLASS, !visibleClusters.has(prefix));\n }\n }\n\n showAll.addEventListener('click', () => {\n visibleClusters.clear();\n for (const k of clusters.keys()) visibleClusters.add(k);\n includeSharedPlaces = true;\n sharedCb.checked = true;\n refreshChips();\n emit();\n });\n hideAll.addEventListener('click', () => {\n visibleClusters.clear();\n includeSharedPlaces = false;\n sharedCb.checked = false;\n refreshChips();\n emit();\n });\n\n container.appendChild(root);\n\n // Emit initial state so the caller's visibility starts in sync.\n emit();\n\n return {\n root,\n dispose(): void {\n if (root.parentNode === container) container.removeChild(root);\n },\n };\n}\n","/**\n * Click-to-highlight overlay for the C0 layout.\n *\n * Two operations:\n *\n * findAllCopies(svg, focusId) — given a node id, returns the set of every\n * DOM node representing the same logical place. For a replica it returns\n * the original + every sibling replica; for an original of a shared place\n * it returns itself + every replica; for an unshared node it returns just\n * `{focusId}`.\n *\n * applyHighlight(svg, focusId) — highlights the focus group, walks\n * junctions (`j_*` routing nodes) transparently to reveal the real\n * downstream place/transition neighbors, and fades everything else.\n * Pass `null` to clear.\n *\n * Ported from v3 `applyHighlight` / `findAllCopies` (lines 971–1047). All\n * SVG queries assume the DOM has been through {@link annotateSvgForC0}.\n *\n * @module viewer/highlight\n */\n\nconst HIGHLIGHT_CLASSES = ['is-highlight', 'is-neighbor', 'is-faded'] as const;\n\nfunction clearHighlightClasses(svg: SVGSVGElement): void {\n for (const cls of HIGHLIGHT_CLASSES) {\n for (const el of Array.from(svg.querySelectorAll<Element>('.' + cls))) {\n el.classList.remove(cls);\n }\n }\n}\n\n/**\n * Resolve every DOM node representing the same logical place as `focusId`.\n *\n * Replicas carry `data-replica-of=\"<semanticName>\"`. The original of a\n * shared place is itself tagged with `data-replica-of=\"<semanticName>\"` so\n * a single attribute query finds the whole family.\n */\nexport function findAllCopies(svg: SVGSVGElement, focusId: string): Set<string> {\n const focus = svg.querySelector<SVGGElement>(`g.node[data-id=\"${cssEscape(focusId)}\"]`);\n if (!focus) return new Set([focusId]);\n const copies = new Set<string>([focusId]);\n const origLabel = focus.getAttribute('data-replica-of')\n ?? (focusId.startsWith('p_') ? focusId.slice(2) : null);\n if (origLabel) {\n const orig = svg.querySelector<SVGGElement>(`g.node[data-id=\"${cssEscape('p_' + origLabel)}\"]`);\n if (orig) {\n const id = orig.getAttribute('data-id');\n if (id) copies.add(id);\n }\n for (const sibling of Array.from(\n svg.querySelectorAll<SVGGElement>(`g.node[data-replica-of=\"${cssEscape(origLabel)}\"]`),\n )) {\n const id = sibling.getAttribute('data-id');\n if (id) copies.add(id);\n }\n }\n return copies;\n}\n\n/**\n * Highlight `focusId` (and every DOM copy of the same logical place),\n * walking through junctions to reveal real downstream/upstream neighbors.\n *\n * Pass `null` to clear. Hidden nodes/edges are skipped so toggling a\n * cluster off doesn't bring them back into the path.\n */\nexport function applyHighlight(svg: SVGSVGElement, focusId: string | null): void {\n clearHighlightClasses(svg);\n if (focusId === null) return;\n\n const copies = findAllCopies(svg, focusId);\n for (const id of copies) {\n const n = svg.querySelector<SVGGElement>(`g.node[data-id=\"${cssEscape(id)}\"]`);\n if (n && !n.classList.contains('is-hidden')) n.classList.add('is-highlight');\n }\n\n // BFS through edges; junctions are transparent — the true neighbour is\n // the place/transition on the other side of the routing node.\n const neighbors = new Set<string>();\n const pathJunctions = new Set<string>();\n const pathEdges: SVGGElement[] = [];\n const queue: string[] = Array.from(copies);\n const visited = new Set<string>(copies);\n\n const allEdges = Array.from(svg.querySelectorAll<SVGGElement>('g.edge'));\n while (queue.length > 0) {\n const nodeId = queue.shift()!;\n for (const e of allEdges) {\n if (e.classList.contains('is-hidden')) continue;\n const s = e.getAttribute('data-src');\n const d = e.getAttribute('data-dst');\n if (s !== nodeId && d !== nodeId) continue;\n const otherId = s === nodeId ? d : s;\n if (!otherId) continue;\n pathEdges.push(e);\n if (visited.has(otherId)) continue;\n visited.add(otherId);\n if (otherId.startsWith('j_')) {\n pathJunctions.add(otherId);\n queue.push(otherId);\n } else {\n neighbors.add(otherId);\n }\n }\n }\n\n for (const e of pathEdges) e.classList.add('is-highlight');\n\n for (const e of allEdges) {\n if (e.classList.contains('is-hidden') || e.classList.contains('is-highlight')) continue;\n e.classList.add('is-faded');\n }\n\n for (const n of Array.from(svg.querySelectorAll<SVGGElement>('g.node'))) {\n if (n.classList.contains('is-hidden')) continue;\n const id = n.getAttribute('data-id') ?? '';\n if (copies.has(id)) continue;\n if (neighbors.has(id)) n.classList.add('is-neighbor');\n else if (pathJunctions.has(id)) continue;\n else n.classList.add('is-faded');\n }\n\n for (const c of Array.from(\n svg.querySelectorAll<SVGGElement>('g.cluster:not(.cluster-orchestrator)'),\n )) {\n if (c.classList.contains('is-hidden')) continue;\n c.classList.add('is-faded');\n }\n}\n\n/**\n * Escape a string for use inside a CSS attribute selector value.\n *\n * Built-in `CSS.escape` covers identifier characters; node ids contain\n * `_` and digits which `CSS.escape` accepts, plus the `__rep__` separator\n * which is also safe. We only need to escape the few syntactic chars\n * (`\"`, `\\`).\n */\nfunction cssEscape(value: string): string {\n return value.replace(/\\\\/g, '\\\\\\\\').replace(/\"/g, '\\\\\"');\n}\n","/**\n * Replica detection + DOM tagging for the C0 layout.\n *\n * `replicateShared` (in `layout/preprocess.ts`) emits clones of shared\n * places with ids of the form `${placeId}__rep__${shortCluster}`. After\n * Graphviz renders the pinned DOT, those replica clones appear as\n * `<g class=\"node\">` siblings whose inner `<title>` carries the replica\n * id. The original `placeId` also gets a sibling for every cluster it\n * touches and stays marked as a shared place.\n *\n * {@link tagReplicas} walks the rendered SVG once and adds:\n * - `class=\"petri-replica\"` on every replica node AND its original (so\n * `data-locations` / ⇄ glyph styling can target both).\n * - `data-replica-of=\"<semantic-place-name>\"` — the original place name\n * stripped of the `p_` prefix. Click-all-copies (Stage 4) looks up\n * every node with the same `data-replica-of` value.\n *\n * The ⇄ glyph + dashed border land in CSS (Stage 5); this module only\n * does the attribute tagging.\n *\n * @module viewer/replica-tagging\n */\n\nconst REPLICA_RE = /^(p_[A-Za-z0-9_]+)__rep__/;\n\n/**\n * Stable semantic id for a logical place — strips the `p_` prefix so the\n * value matches the `name` field on libpetri's Place model. Click-all-\n * copies in the overlay keys off this value, not the rendered DOM id.\n */\nfunction semanticName(placeId: string): string {\n return placeId.replace(/^p_/, '');\n}\n\n/**\n * Walk every `<g class=\"node\">` in `svg`, detect replicas and originals of\n * shared places, and tag them with `petri-replica` + `data-replica-of`.\n *\n * Idempotent: re-running on an already-tagged SVG is a no-op (the attrs\n * already match).\n */\nexport function tagReplicas(svg: SVGSVGElement): void {\n // First pass: collect every original whose id appears as the prefix of\n // at least one replica's title. We can't tag the originals until we\n // know which ones got copies.\n const originalsWithCopies = new Set<string>();\n const allNodes = Array.from(svg.querySelectorAll<SVGGElement>('g.node'));\n for (const g of allNodes) {\n const title = g.querySelector('title')?.textContent ?? '';\n const m = REPLICA_RE.exec(title);\n if (m) originalsWithCopies.add(m[1]!);\n }\n\n // Second pass: tag both replicas and their (matching) originals + append\n // the shared-place ⇄ glyph. SVG has no ::after pseudo-element so the\n // glyph is injected as a <text> child of the node's <g>.\n for (const g of allNodes) {\n const title = g.querySelector('title')?.textContent ?? '';\n const replicaMatch = REPLICA_RE.exec(title);\n let semantic: string | null = null;\n if (replicaMatch) {\n semantic = semanticName(replicaMatch[1]!);\n } else if (originalsWithCopies.has(title)) {\n semantic = semanticName(title);\n }\n if (semantic === null) continue;\n g.classList.add('petri-replica');\n g.setAttribute('data-replica-of', semantic);\n appendSharedGlyph(g);\n }\n}\n\nconst SVG_NS = 'http://www.w3.org/2000/svg';\nconst GLYPH_CLASS = 'libpetri-replica-glyph';\n\n/**\n * Inject a ⇄ marker adjacent to the upper-right of the node's place shape.\n * Positioned OUTSIDE the circle so it doesn't overlap the boundary stroke\n * or the inner fill (libpetri places are 25pt circles — anything drawn\n * inside is unreadable). Anchored top-left at `(cx + rx + 2, cy - ry + 2)`\n * with a 9pt sans-serif glyph; the styling is finished by the canonical\n * CSS via the `libpetri-replica-glyph` class.\n *\n * Idempotent.\n */\nfunction appendSharedGlyph(g: SVGGElement): void {\n if (g.querySelector(`text.${GLYPH_CLASS}`)) return;\n const shape = g.querySelector('ellipse, circle');\n if (!shape) return;\n let cx = 0, cy = 0, rx = 0, ry = 0;\n if (shape.tagName === 'ellipse') {\n cx = parseFloat(shape.getAttribute('cx') ?? '0');\n cy = parseFloat(shape.getAttribute('cy') ?? '0');\n rx = parseFloat(shape.getAttribute('rx') ?? '0');\n ry = parseFloat(shape.getAttribute('ry') ?? '0');\n } else {\n cx = parseFloat(shape.getAttribute('cx') ?? '0');\n cy = parseFloat(shape.getAttribute('cy') ?? '0');\n rx = parseFloat(shape.getAttribute('r') ?? '0');\n ry = rx;\n }\n const text = document.createElementNS(SVG_NS, 'text');\n text.setAttribute('class', GLYPH_CLASS);\n // Anchored at the upper-right OUTSIDE the circle (top-left of the glyph\n // sits at +2 / -2 from the bounding-box corner so it doesn't kiss the\n // stroke). text-anchor=start so the glyph extends to the right.\n text.setAttribute('x', (cx + rx + 2).toFixed(2));\n text.setAttribute('y', (cy - ry + 2).toFixed(2));\n text.setAttribute('text-anchor', 'start');\n text.setAttribute('font-size', '9');\n text.textContent = '⇄';\n g.appendChild(text);\n}\n","/**\n * Directed-reachability orphan visibility for the C0 layout.\n *\n * When only a subset of clusters is visible, orphan transitions (and\n * places) should appear iff they participate in a chain that ends at a\n * visible cluster — the chain direction matters. v3's `computeVisibleOrphans`\n * (lines 892–918) walks orphan-orphan edges backwards from \"seed upstream\"\n * orphans (those that feed INTO a visible cluster) and forwards from\n * \"seed downstream\" orphans (those a visible cluster feeds INTO). This\n * keeps `ResponseAwaited` hidden when only `productSearch` is visible\n * while exposing legitimate upstream chains like\n * `SearchProductsToolRequest → ForkSearchProductsToolRequest → … → productSearch`.\n *\n * @module viewer/visibility\n */\n\nimport { annotateSvgForC0 } from './c0-annotations.js';\n\n/**\n * Set of orphan ids whose visibility is justified by the current cluster\n * selection. Caller toggles `is-hidden` on the corresponding `<g class=\"node\">`s.\n */\nexport function computeVisibleOrphans(\n svg: SVGSVGElement,\n visibleClusters: ReadonlySet<string>,\n): Set<string> {\n if (visibleClusters.size === 0) return new Set();\n\n const orphanNodes = Array.from(\n svg.querySelectorAll<SVGGElement>('g.node:not([data-instance])'),\n );\n const orphanIds = new Set<string>();\n for (const n of orphanNodes) {\n const id = n.getAttribute('data-id');\n if (id) orphanIds.add(id);\n }\n\n const fwdAdj = new Map<string, Set<string>>();\n const bwdAdj = new Map<string, Set<string>>();\n const link = (m: Map<string, Set<string>>, k: string, v: string): void => {\n let s = m.get(k);\n if (!s) { s = new Set(); m.set(k, s); }\n s.add(v);\n };\n\n // Orphan-orphan edges feed the BFS walk\n for (const e of Array.from(svg.querySelectorAll<SVGGElement>('g.edge'))) {\n const s = e.getAttribute('data-src') ?? '';\n const d = e.getAttribute('data-dst') ?? '';\n if (orphanIds.has(s) && orphanIds.has(d)) {\n link(fwdAdj, s, d);\n link(bwdAdj, d, s);\n }\n }\n\n // Seed orphans = orphans on either end of a cross-cluster edge whose\n // other endpoint sits in a visible cluster.\n const seedUpstream = new Set<string>();\n const seedDownstream = new Set<string>();\n for (const e of Array.from(\n svg.querySelectorAll<SVGGElement>('g.edge.cross-cluster'),\n )) {\n const s = e.getAttribute('data-src') ?? '';\n const d = e.getAttribute('data-dst') ?? '';\n const sCl = e.getAttribute('data-src-cluster') ?? '';\n const dCl = e.getAttribute('data-dst-cluster') ?? '';\n if (orphanIds.has(s) && visibleClusters.has(dCl)) seedUpstream.add(s);\n if (orphanIds.has(d) && visibleClusters.has(sCl)) seedDownstream.add(d);\n }\n\n const visible = new Set<string>();\n const bfs = (seeds: Set<string>, adj: Map<string, Set<string>>): void => {\n const q: string[] = [];\n for (const s of seeds) { visible.add(s); q.push(s); }\n while (q.length) {\n const cur = q.shift()!;\n const next = adj.get(cur);\n if (!next) continue;\n for (const x of next) {\n if (!visible.has(x)) { visible.add(x); q.push(x); }\n }\n }\n };\n bfs(seedUpstream, bwdAdj);\n bfs(seedDownstream, fwdAdj);\n return visible;\n}\n\nexport interface VisibilityState {\n /** Cluster short names currently visible. */\n readonly visibleClusters: ReadonlySet<string>;\n /**\n * When false, hide every replica/shared place (`g.node.petri-replica`)\n * regardless of which cluster it belongs to. The corresponding sidebar\n * checkbox is labelled \"Shared places\".\n */\n readonly includeSharedPlaces: boolean;\n}\n\n/**\n * Apply visibility state to an annotated SVG. Idempotent — re-call to\n * reflect a new state.\n *\n * Graphviz emits cluster member nodes as DOM SIBLINGS of the cluster `<g>`,\n * not its children. Toggling `is-hidden` on the cluster `<g>` alone hides\n * only the cluster outline + label — the nodes, junctions, transitions and\n * intra-cluster edges inside stay visible. So this routine hides them\n * explicitly:\n *\n * 1. Cluster outlines via `g.cluster` ↔ `visibleClusters`.\n * 2. Cluster member nodes via `g.node[data-instance]` ↔ `visibleClusters`.\n * 3. Orphan nodes (no `data-instance`) via directed-reachability — when\n * no cluster is visible they all hide, otherwise only the chain\n * reaching a visible cluster shows.\n * 4. Replica + original shared places (`g.node.petri-replica`) hide as a\n * group when `includeSharedPlaces=false`.\n * 5. Edges follow their endpoints — hide if either is hidden.\n */\nexport function applyVisibility(svg: SVGSVGElement, state: VisibilityState): void {\n annotateSvgForC0(svg);\n\n const { visibleClusters, includeSharedPlaces } = state;\n\n // 1. Cluster outlines.\n for (const c of Array.from(\n svg.querySelectorAll<SVGGElement>('g.cluster:not(.cluster-orchestrator)'),\n )) {\n const titleEl = c.querySelector(':scope > title');\n const shortName = (titleEl?.textContent ?? '').replace(/^cluster_/, '');\n c.classList.toggle('is-hidden', !visibleClusters.has(shortName));\n }\n\n // 2. Cluster member nodes follow their cluster's visibility.\n for (const n of Array.from(svg.querySelectorAll<SVGGElement>('g.node[data-instance]'))) {\n const inst = n.getAttribute('data-instance') ?? '';\n n.classList.toggle('is-hidden', !visibleClusters.has(inst));\n }\n\n // 3. Orphan nodes (no data-instance) — orchestrator-level transitions\n // + unclustered places. Reachability emptys to ∅ when no cluster is\n // on, which hides every orphan — the \"Hide all\" case.\n const visOrphans = computeVisibleOrphans(svg, visibleClusters);\n for (const n of Array.from(\n svg.querySelectorAll<SVGGElement>('g.node:not([data-instance])'),\n )) {\n const id = n.getAttribute('data-id') ?? '';\n n.classList.toggle('is-hidden', !visOrphans.has(id));\n }\n\n // 4. Shared places (replicas + originals). The `petri-replica` class is\n // set by `replica-tagging.ts` on BOTH the replica clones and the\n // original whose cross-cluster shape spawned them — toggling them as a\n // group lets the user collapse the shared-place clutter in one click.\n if (!includeSharedPlaces) {\n for (const n of Array.from(svg.querySelectorAll<SVGGElement>('g.node.petri-replica'))) {\n n.classList.add('is-hidden');\n }\n }\n\n // 5. Edges follow their endpoints. Map lookup beats N*M querySelector.\n const nodeById = new Map<string, SVGGElement>();\n for (const n of Array.from(svg.querySelectorAll<SVGGElement>('g.node'))) {\n const id = n.getAttribute('data-id');\n if (id) nodeById.set(id, n);\n }\n for (const e of Array.from(svg.querySelectorAll<SVGGElement>('g.edge'))) {\n const s = e.getAttribute('data-src') ?? '';\n const d = e.getAttribute('data-dst') ?? '';\n const sHidden = nodeById.get(s)?.classList.contains('is-hidden') ?? false;\n const dHidden = nodeById.get(d)?.classList.contains('is-hidden') ?? false;\n e.classList.toggle('is-hidden', sHidden || dHidden);\n }\n}\n","/**\n * CSS custom properties exposed by the canonical viewer stylesheet.\n *\n * Each property has a default in `viewer.css`. Consumers can override any of\n * them at any scope (the viewer container, the document root, etc.) to\n * theme the viewer without touching the canonical CSS.\n *\n * Names are listed here as a reference + so the IIFE bundle can expose them\n * on `window.LibpetriViewer.cssVariables`. The list is informational — the\n * stylesheet is still the source of truth.\n *\n * @module viewer/styles\n */\n\n/** CSS custom property names that the canonical stylesheet honours. */\nexport const VIEWER_CSS_VARIABLES = [\n '--lpv-bg',\n '--lpv-header-bg',\n '--lpv-border',\n '--lpv-text',\n '--lpv-muted',\n '--lpv-cluster-bg',\n '--lpv-cluster-bg-collapsed',\n '--lpv-cluster-stroke-width',\n '--lpv-cluster-stroke-dash',\n '--lpv-cluster-fill-tint',\n '--lpv-dim-opacity',\n '--lpv-active-filter-outline',\n '--lpv-legend-bg',\n '--lpv-chip-bg',\n '--lpv-chip-active-bg',\n // C0 sidebar + replica + highlight (Stage 5)\n '--lpv-sidebar-bg',\n '--lpv-sidebar-border',\n '--lpv-sidebar-text',\n '--lpv-sidebar-muted',\n '--lpv-sidebar-chip-bg',\n '--lpv-sidebar-chip-hover-bg',\n '--lpv-sidebar-chip-off-opacity',\n '--lpv-shared-glyph-color',\n '--lpv-replica-fill',\n '--lpv-replica-stroke',\n '--lpv-highlight-stroke',\n '--lpv-highlight-glow',\n '--lpv-neighbor-stroke',\n '--lpv-faded-node-opacity',\n '--lpv-faded-edge-opacity',\n '--lpv-faded-cluster-opacity',\n] as const;\n\nexport type ViewerCssVariable = (typeof VIEWER_CSS_VARIABLES)[number];\n","/**\n * Strip `subgraph cluster_*` wrappers from a libpetri-generated DOT source,\n * producing an equivalent flat graph (no visual grouping). The returned\n * DOT contains every node and edge from the input — only the cluster\n * scope markers and their cluster-level attribute statements are dropped.\n *\n * Cross-cluster invisible edges that carry `ltail`/`lhead` attributes\n * targeting `cluster_*` have those attributes removed (the references\n * become dangling once clusters are gone; Graphviz warns about them).\n * The edges themselves are kept so layout rank hints survive the flatten.\n *\n * The transform assumes libpetri's byte-stable DOT shape (spec EXP-014):\n * - one statement per line, terminated by `;`\n * - `subgraph cluster_<id> {` and matching `}` each on their own line\n * - inside a cluster, cluster-level attributes are bare `key=value;`\n * lines (no `[`), node declarations carry `[`, edges carry `->`\n *\n * The function is idempotent: passing already-flat DOT through returns\n * a string equal to the input (modulo whitespace introduced by re-joining).\n *\n * @module viewer/dot-flatten\n */\n\nconst CLUSTER_OPEN = /^\\s*subgraph\\s+cluster_[A-Za-z0-9_]+\\s*\\{\\s*$/;\nconst ONLY_CLOSE_BRACE = /^\\s*\\}\\s*$/;\n\n/**\n * Strip cluster wrappers from a libpetri DOT source. Returns flat DOT\n * that renders the same nodes and edges without subgraph groupings.\n */\nexport function flattenClusters(dot: string): string {\n const lines = dot.split('\\n');\n const out: string[] = [];\n let clusterDepth = 0;\n\n for (const line of lines) {\n if (CLUSTER_OPEN.test(line)) {\n clusterDepth++;\n continue;\n }\n if (clusterDepth > 0) {\n if (ONLY_CLOSE_BRACE.test(line)) {\n clusterDepth--;\n continue;\n }\n // Inside a cluster: keep node decls (have `[`) and edges (have `->`).\n // Drop cluster-attribute statements (label=, style=, bgcolor=, ...).\n if (line.includes('[') || line.includes('->')) {\n out.push(stripClusterEdgeAttrs(line));\n }\n continue;\n }\n // Top level: cross-cluster edges live here; drop their cluster refs.\n if (line.includes('->') && (line.includes('ltail=') || line.includes('lhead='))) {\n out.push(stripClusterEdgeAttrs(line));\n continue;\n }\n out.push(line);\n }\n\n return out.join('\\n');\n}\n\nfunction stripClusterEdgeAttrs(line: string): string {\n // Handle each `ltail`/`lhead` whether it's the first attribute in the\n // bracket group or a later one. Quoted cluster names only — libpetri\n // emits them as `ltail=\"cluster_xxx\"`.\n return line\n .replace(/,\\s*ltail=\"cluster_[^\"]*\"/g, '')\n .replace(/,\\s*lhead=\"cluster_[^\"]*\"/g, '')\n .replace(/\\[\\s*ltail=\"cluster_[^\"]*\"\\s*,\\s*/g, '[')\n .replace(/\\[\\s*lhead=\"cluster_[^\"]*\"\\s*,\\s*/g, '[')\n .replace(/\\[\\s*ltail=\"cluster_[^\"]*\"\\s*\\]/g, '[]')\n .replace(/\\[\\s*lhead=\"cluster_[^\"]*\"\\s*\\]/g, '[]');\n}\n","/**\n * Canonical libpetri Petri-net diagram viewer.\n *\n * Single source of truth for DOT → SVG rendering + cluster overlay across\n * all four surfaces:\n * - debug-ui (live debugger)\n * - dev-preview (Vite app)\n * - Javadoc taglet (Java doclet)\n * - Rustdoc embed (libpetri-docgen)\n * - TypeDoc / typescript doclet\n *\n * Build outputs:\n * - ESM: `dist/viewer/index.js` — consumed by Vite/Webpack/Node bundlers.\n * - IIFE: `dist/viewer/viewer.iife.js` — self-contained, exposes\n * `window.LibpetriViewer = { mount, ... }`. Used by the doc taglets\n * that ship pre-rendered HTML pages without a bundler.\n * - CSS: `dist/viewer/viewer.css` — canonical stylesheet (copied from\n * `src/viewer/resources/viewer.css`).\n *\n * Edits to viewer behaviour MUST happen here. The three resource directories\n * under `java/`, `rust/`, and `typescript/src/doclet/` are **build outputs**\n * populated by `scripts/build-viewer.sh` and must not be hand-edited.\n *\n * @module viewer\n */\n\nimport {\n attachPanzoom,\n DEFAULT_PANZOOM_OPTS,\n type PanzoomInstance,\n type PanzoomOptions,\n} from './pan-zoom.js';\nimport {\n applyFilter as overlayApplyFilter,\n colorForPrefix,\n discoverClusters,\n isInsideCollapsedCluster as overlayIsInsideCollapsedCluster,\n paintClusterBorders,\n setClusterCollapsed,\n type ClusterDescriptor,\n} from './cluster-overlay.js';\nimport { annotateSvgForC0 } from './c0-annotations.js';\nimport { mountSidebar, type SidebarHandle } from './chrome/sidebar.js';\nimport { applyHighlight } from './highlight.js';\nimport { tagReplicas } from './replica-tagging.js';\nimport { applyVisibility, type VisibilityState } from './visibility.js';\nimport { VIEWER_CSS_VARIABLES } from './styles.js';\nimport { flattenClusters } from './dot-flatten.js';\n\nexport {\n colorForPrefix,\n discoverClusters,\n VIEWER_CSS_VARIABLES,\n DEFAULT_PANZOOM_OPTS,\n};\nexport type { ClusterDescriptor, PanzoomInstance, PanzoomOptions };\n\n/**\n * Subnet (cluster) visibility mode.\n *\n * - `'show'` — render the DOT as-is with `subgraph cluster_*` groupings\n * visible (the default; the post-subnets layout).\n * - `'hide'` — render a flattened variant that drops cluster wrappers\n * and the `ltail`/`lhead` cluster references on cross-cluster edges.\n * Same nodes and edges, no visual grouping.\n */\nexport type SubnetVisibility = 'show' | 'hide';\n\n/** Handle returned by {@link mount}. */\nexport interface ViewerHandle {\n /** The rendered root `<svg>` element. */\n readonly svg: SVGSVGElement;\n /** The panzoom instance attached to the SVG. */\n readonly panzoom: PanzoomInstance;\n /** Map of cluster prefix → descriptor for the current SVG. */\n readonly clusters: ReadonlyMap<string, ClusterDescriptor>;\n /** Prefixes currently marked collapsed on this handle. */\n readonly collapsedPrefixes: ReadonlySet<string>;\n /** Currently active \"show only <prefix>\" filter, or null. */\n readonly activeFilter: string | null;\n /** Current subnet visibility mode. */\n readonly subnets: SubnetVisibility;\n /**\n * Switch between the clustered (`'show'`) and flat (`'hide'`) views.\n * Triggers an internal re-mount via the canonical {@link mount} path —\n * the SVG is regenerated because Graphviz lays out cluster boundaries\n * during layout. Returns the new handle; the old one is disposed.\n *\n * After re-mount, the host container dispatches a bubbling\n * `libpetri-viewer:remount` CustomEvent whose `detail.handle` is the\n * fresh handle. Consumers that cache the handle (e.g. the debug-ui)\n * should listen on the container and update their reference.\n */\n setSubnets(mode: SubnetVisibility): Promise<ViewerHandle>;\n /** Flip {@link subnets} to the other mode and re-mount. See {@link setSubnets}. */\n toggleSubnets(): Promise<ViewerHandle>;\n /** Collapse a single cluster by prefix. No-op if unknown. */\n collapse(prefix: string): void;\n /** Expand a single cluster by prefix. No-op if unknown. */\n expand(prefix: string): void;\n /** Collapse every discovered cluster. */\n collapseAll(): void;\n /** Expand every collapsed cluster. */\n expandAll(): void;\n /** Apply the \"show only <prefix>\" filter; pass `null` to clear. */\n filter(prefix: string | null): void;\n /** Reset pan/zoom to the identity transform. */\n resetZoom(): void;\n /** Scale and translate so the full diagram fits the host viewport. */\n fit(): void;\n /** Reports whether `graphId` is inside a currently-collapsed cluster. */\n isInsideCollapsedCluster(graphId: string): boolean;\n /**\n * Highlight a node and every DOM copy of the same logical place; walk\n * junctions to surface real neighbors. Pass `null` to clear. C0-only.\n */\n highlight(nodeId: string | null): void;\n /** The node id currently highlighted, or `null`. */\n readonly highlightedNodeId: string | null;\n /**\n * Set the cluster-visibility state: which clusters render, and whether\n * directed-reachable orphans render. C0-only.\n */\n setVisibility(state: VisibilityState): void;\n /** Tear down: dispose panzoom and detach listeners. */\n dispose(): void;\n}\n\n/** Options accepted by {@link mount}. */\nexport interface MountOptions {\n /**\n * Previous handle to dispose before mounting. Pass the handle returned\n * by a prior `mount()` call so callers don't need to track it separately.\n * Cluster collapse / filter state from `previousHandle` is preserved\n * across the re-render so the user's intent survives a live re-draw.\n */\n readonly previousHandle?: ViewerHandle | null;\n /** Per-call panzoom overrides; merged on top of {@link DEFAULT_PANZOOM_OPTS}. */\n readonly panzoom?: PanzoomOptions;\n /** Fired after a cluster's collapsed state changes via the handle API. */\n readonly onClusterCollapse?: (prefix: string, collapsed: boolean) => void;\n /** Fired after `filter()` runs, with the new filter prefix or null. */\n readonly onClusterFilter?: (prefix: string | null) => void;\n /** When true, append a legend sidebar + filter chip strip inside `container`. */\n readonly chrome?: boolean;\n /**\n * Layout strategy. `'elk'` (default) runs the C0 pipeline — parse → fold\n * → replicate → ELK → Graphviz `neato` pin mode — and tags replica\n * nodes for the click-all-copies + ⇄ overlay. `'graphviz'` runs the\n * plain Graphviz `dot` engine and skips replica tagging. The result is\n * cached on the DOT hash; re-mounts on identical DOT skip the pipeline.\n */\n readonly layout?: 'elk' | 'graphviz';\n /**\n * Initial subnet visibility mode. Defaults to `'show'` (clustered view).\n * Pass `'hide'` to mount with the flat view from the start. The chrome\n * button toggles between the two at runtime; see {@link ViewerHandle.setSubnets}.\n *\n * If `previousHandle` is supplied and `subnets` is omitted, the previous\n * handle's mode is inherited so live re-renders preserve the user's choice.\n */\n readonly subnets?: SubnetVisibility;\n}\n\n/**\n * Render a DOT source, mount the resulting SVG into `container`, wire pan/zoom,\n * and surface cluster overlay controls.\n *\n * The container's existing children are removed before the new SVG is\n * appended.\n */\nexport async function mount(\n dotSource: string,\n container: HTMLElement,\n opts: MountOptions = {},\n): Promise<ViewerHandle> {\n const previousHandle = opts.previousHandle ?? null;\n const preservedCollapsed = previousHandle\n ? new Set(previousHandle.collapsedPrefixes)\n : new Set<string>();\n const preservedFilter = previousHandle?.activeFilter ?? null;\n // Subnet mode: explicit opt wins, else inherit from previousHandle, else default to 'show'.\n const subnetsMode: SubnetVisibility =\n opts.subnets ?? previousHandle?.subnets ?? 'show';\n\n if (previousHandle) {\n previousHandle.dispose();\n }\n\n const renderModule = await import('./render.js');\n const useElk = (opts.layout ?? 'elk') === 'elk';\n // When subnets are hidden, render a flattened variant. The original\n // DOT is kept around so toggleSubnets can re-mount with the clustered\n // view without callers having to supply DOT a second time.\n const renderedDot =\n subnetsMode === 'hide' ? flattenClusters(dotSource) : dotSource;\n const svg: SVGSVGElement = useElk\n ? await renderModule.renderDotToSvgWithElkLayout(renderedDot)\n : await renderModule.renderDotToSvg(renderedDot);\n container.innerHTML = '';\n container.appendChild(svg);\n // Tag the host so the canonical CSS (which scopes to .libpetri-viewer)\n // applies even when the consumer didn't wrap us in a .petrinet-diagram.\n container.classList.add('libpetri-viewer');\n\n const panzoomInstance = attachPanzoom(svg, opts.panzoom);\n const clusters = discoverClusters(svg);\n paintClusterBorders(clusters);\n const layoutMode = opts.layout ?? 'elk';\n if (layoutMode === 'elk') {\n tagReplicas(svg);\n annotateSvgForC0(svg);\n }\n\n const collapsedPrefixes = new Set<string>();\n let activeFilter: string | null = null;\n let highlightedNodeId: string | null = null;\n let disposed = false;\n\n // Chrome elements (created lazily; only when chrome:true is requested).\n // Re-rendering through the same handle does not duplicate them.\n let chromeRoot: HTMLElement | null = null;\n let sidebarHandle: SidebarHandle | null = null;\n let highlightModeEnabled = true;\n\n function ensureChrome(): void {\n if (!opts.chrome) return;\n if (chromeRoot && chromeRoot.parentNode === container) return;\n\n // Reset + Fullscreen controls anchor top-right; the C0 sidebar\n // (top-left) replaces the legacy legend + filter strip.\n chromeRoot = document.createElement('div');\n chromeRoot.className = 'libpetri-viewer-chrome';\n chromeRoot.style.position = 'absolute';\n chromeRoot.style.inset = '0';\n chromeRoot.style.pointerEvents = 'none';\n\n const controls = document.createElement('div');\n controls.className = 'diagram-controls';\n controls.style.pointerEvents = 'auto';\n const resetBtn = document.createElement('button');\n resetBtn.type = 'button';\n resetBtn.className = 'diagram-btn btn-reset';\n resetBtn.title = 'Reset view';\n resetBtn.textContent = 'Reset';\n resetBtn.addEventListener('click', () => handle.fit());\n const fsBtn = document.createElement('button');\n fsBtn.type = 'button';\n fsBtn.className = 'diagram-btn btn-fullscreen';\n fsBtn.title = 'Toggle fullscreen';\n fsBtn.textContent = 'Fullscreen';\n fsBtn.addEventListener('click', () => toggleFullscreen(container, fsBtn));\n // Subnet view toggle. Button label describes the action — what\n // clicking it will do, not the current state.\n const subnetsBtn = document.createElement('button');\n subnetsBtn.type = 'button';\n subnetsBtn.className = 'diagram-btn btn-subnets';\n subnetsBtn.title =\n subnetsMode === 'show' ? 'Hide subnet groupings' : 'Show subnet groupings';\n subnetsBtn.textContent = subnetsMode === 'show' ? 'Flat view' : 'Subnets view';\n subnetsBtn.addEventListener('click', () => {\n void handle.toggleSubnets();\n });\n controls.appendChild(subnetsBtn);\n controls.appendChild(resetBtn);\n controls.appendChild(fsBtn);\n chromeRoot.appendChild(controls);\n\n if (getComputedStyle(container).position === 'static') {\n container.style.position = 'relative';\n }\n container.appendChild(chromeRoot);\n\n // The cluster-chip sidebar is the C0 chrome — only mount when\n // there's a renderable layout and at least one cluster to show.\n // In flat (subnets: 'hide') mode the SVG has no clusters by\n // construction; the explicit gate keeps the intent obvious.\n if (layoutMode === 'elk' && subnetsMode === 'show' && clusters.size > 0) {\n sidebarHandle = mountSidebar(container, clusters, {\n onVisibilityChange: (state) => handle.setVisibility(state),\n onHighlightModeChange: (enabled) => {\n highlightModeEnabled = enabled;\n if (!enabled) handle.highlight(null);\n },\n });\n }\n }\n\n // In-page lightbox fullscreen: toggle a CSS class on the host and\n // re-fit. We deliberately don't invoke the native Fullscreen API —\n // users want the diagram to expand inside the page, not yank the\n // browser into OS-level fullscreen.\n function toggleFullscreen(host: HTMLElement, btn: HTMLButtonElement): void {\n const isFs = host.classList.toggle('diagram-fullscreen');\n btn.textContent = isFs ? 'Exit fullscreen' : 'Fullscreen';\n requestAnimationFrame(() => handle.fit());\n }\n\n const handle: ViewerHandle = {\n svg,\n panzoom: panzoomInstance,\n clusters,\n get collapsedPrefixes() {\n return collapsedPrefixes;\n },\n get activeFilter() {\n return activeFilter;\n },\n get subnets() {\n return subnetsMode;\n },\n setSubnets(mode: SubnetVisibility): Promise<ViewerHandle> {\n // Re-mount via the canonical path so all teardown + state preservation\n // (collapse set, filter) runs through the same code as external\n // re-renders. The original dotSource is reused.\n if (mode === subnetsMode) return Promise.resolve(handle);\n return mount(dotSource, container, {\n ...opts,\n previousHandle: handle,\n subnets: mode,\n }).then((next) => {\n container.dispatchEvent(\n new CustomEvent('libpetri-viewer:remount', {\n bubbles: true,\n detail: { handle: next, subnets: mode },\n }),\n );\n return next;\n });\n },\n toggleSubnets(): Promise<ViewerHandle> {\n return handle.setSubnets(subnetsMode === 'show' ? 'hide' : 'show');\n },\n collapse(prefix: string): void {\n if (disposed) return;\n const cluster = clusters.get(prefix);\n if (!cluster) return;\n if (!collapsedPrefixes.has(prefix)) {\n setClusterCollapsed(cluster, true);\n collapsedPrefixes.add(prefix);\n opts.onClusterCollapse?.(prefix, true);\n }\n },\n expand(prefix: string): void {\n if (disposed) return;\n const cluster = clusters.get(prefix);\n if (!cluster) return;\n if (collapsedPrefixes.has(prefix)) {\n setClusterCollapsed(cluster, false);\n collapsedPrefixes.delete(prefix);\n opts.onClusterCollapse?.(prefix, false);\n }\n },\n collapseAll(): void {\n if (disposed) return;\n for (const cluster of clusters.values()) {\n if (!collapsedPrefixes.has(cluster.prefix)) {\n setClusterCollapsed(cluster, true);\n collapsedPrefixes.add(cluster.prefix);\n opts.onClusterCollapse?.(cluster.prefix, true);\n }\n }\n },\n expandAll(): void {\n if (disposed) return;\n for (const prefix of Array.from(collapsedPrefixes)) {\n const cluster = clusters.get(prefix);\n if (!cluster) continue;\n setClusterCollapsed(cluster, false);\n collapsedPrefixes.delete(prefix);\n opts.onClusterCollapse?.(prefix, false);\n }\n },\n filter(prefix: string | null): void {\n if (disposed) return;\n activeFilter = prefix;\n overlayApplyFilter(svg, prefix);\n opts.onClusterFilter?.(prefix);\n },\n resetZoom(): void {\n if (disposed) return;\n try {\n panzoomInstance.moveTo(0, 0);\n panzoomInstance.zoomAbs(0, 0, 1);\n } catch {\n // panzoom internals can throw if the element was detached; ignore.\n }\n },\n fit(): void {\n if (disposed) return;\n try {\n // With the host CSS-sized and the SVG forced to width/height: 100%\n // (see .petrinet-diagram-viewer rules in viewer.css), the browser\n // auto-fits viewBox content via preserveAspectRatio=\"xMidYMid meet\".\n // \"Fit\" is therefore the panzoom identity transform — no math needed.\n panzoomInstance.zoomAbs(0, 0, 1);\n panzoomInstance.moveTo(0, 0);\n } catch {\n // panzoom internals can throw if the element was detached; ignore.\n }\n },\n isInsideCollapsedCluster(graphId: string): boolean {\n return overlayIsInsideCollapsedCluster(svg, clusters, collapsedPrefixes, graphId);\n },\n get highlightedNodeId() {\n return highlightedNodeId;\n },\n highlight(nodeId: string | null): void {\n if (disposed) return;\n if (layoutMode !== 'elk') return;\n highlightedNodeId = nodeId;\n applyHighlight(svg, nodeId);\n },\n setVisibility(state: VisibilityState): void {\n if (disposed) return;\n if (layoutMode !== 'elk') return;\n applyVisibility(svg, state);\n },\n dispose(): void {\n if (disposed) return;\n disposed = true;\n try {\n panzoomInstance.dispose();\n } catch {\n // ignore\n }\n sidebarHandle?.dispose();\n sidebarHandle = null;\n if (chromeRoot && chromeRoot.parentNode === container) {\n container.removeChild(chromeRoot);\n }\n chromeRoot = null;\n },\n };\n\n // Re-apply preserved collapse/filter state from the previous handle so\n // a live re-render (e.g. on marking-snapshot) keeps the user's selection.\n for (const prefix of preservedCollapsed) {\n handle.collapse(prefix);\n }\n if (preservedFilter !== null) {\n handle.filter(preservedFilter);\n }\n\n // C0 click-to-highlight: clicking a node highlights it + every copy of\n // the same logical place. Clicking the SVG background (or the already-\n // highlighted node) clears. The sidebar's \"Click node → highlight\"\n // toggle gates the binding via `highlightModeEnabled`.\n if (layoutMode === 'elk') {\n svg.addEventListener('click', (ev) => {\n if (!highlightModeEnabled) return;\n const target = ev.target;\n if (!(target instanceof Element)) return;\n const nodeG = target.closest('g.node') as SVGGElement | null;\n if (!nodeG) {\n handle.highlight(null);\n return;\n }\n const id = nodeG.getAttribute('data-id');\n if (!id) return;\n handle.highlight(id === highlightedNodeId ? null : id);\n });\n }\n\n ensureChrome();\n\n // Default state: fit-to-host. Huge diagrams shouldn't mount at 1:1\n // (the user only sees the top-left corner). Deferred a frame so the\n // SVG has been laid out before getBBox + clientWidth/Height reads.\n requestAnimationFrame(() => handle.fit());\n\n return handle;\n}\n"],"mappings":"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","names":["cssEscape"]}
@@ -136,6 +136,13 @@
136
136
  background: #e8e8e8;
137
137
  }
138
138
 
139
+ .petrinet-diagram .diagram-btn:disabled,
140
+ .libpetri-viewer .diagram-btn:disabled {
141
+ opacity: 0.5;
142
+ cursor: not-allowed;
143
+ background: white;
144
+ }
145
+
139
146
  .petrinet-diagram details {
140
147
  padding: 0 16px 16px;
141
148
  }