@zombie-mermaid/mermaid-parser 3.2.0 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../src/architecture/parser.ts","../src/architecture/to-graph.ts","../src/c4/parser.ts","../src/c4/format.ts","../src/class/parser.ts","../src/class/format.ts","../src/er/parser.ts","../src/sequence/box-color.ts","../src/sequence/parser.ts","../src/sequence/activation-check.ts","../src/sequence/activation-fix.ts","../src/xychart/parser.ts","../src/xychart/colors.ts","../src/expanded-shapes.ts"],"sourcesContent":["import { normalizeBrTags, type Statement } from '@zombie-mermaid/core'\nimport type {\n ArchitectureDiagram,\n ArchitectureEdge,\n ArchitecturePort,\n} from './types.ts'\n\n// ============================================================================\n// Architecture diagram parser\n//\n// Parses Mermaid's `architecture-beta` diagram\n// (https://mermaid.ai/open-source/syntax/architecture.html):\n//\n// architecture-beta\n// group api(cloud)[API]\n// service db(database)[Database] in api\n// service server(server)[Server] in api\n// junction j1 in api\n// db:L -- R:server\n// server{group}:B --> T:client{group}\n//\n// Grammar:\n// group <id>[(icon)][[Title]] [in <parent>]\n// service <id>[(icon)][[Title]] [in <parent>]\n// junction <id> [in <parent>]\n// <id>[{group}]:<L|R|T|B> <--|-->|<-->|--> <L|R|T|B>:<id>[{group}]\n// align row|column <id> <id> ... (accepted, no effect: see to-graph)\n//\n// Unlike Mermaid's own parser, every malformed statement throws with its line\n// number rather than being dropped.\n// ============================================================================\n\nconst ID = String.raw`[\\w-]+`\nconst DECL_RE = new RegExp(\n String.raw`^(group|service)\\s+(${ID})\\s*(?:\\(([^)]*)\\))?\\s*(?:\\[([^\\]]*)\\])?(?:\\s+in\\s+(${ID}))?$`,\n)\nconst JUNCTION_RE = new RegExp(\n String.raw`^junction\\s+(${ID})(?:\\s+in\\s+(${ID}))?$`,\n)\nconst EDGE_RE = new RegExp(\n String.raw`^(${ID})(\\{group\\})?:([LRTB])\\s+(<)?--(>)?\\s+([LRTB]):(${ID})(\\{group\\})?$`,\n)\nconst ALIGN_RE = new RegExp(\n String.raw`^align\\s+(?:row|column)\\s+${ID}(?:\\s+${ID})+$`,\n)\n\ntype Kind = 'group' | 'service' | 'junction'\n\nfunction fail(stmt: Statement, msg: string): never {\n throw new Error(\n `Architecture diagram, line ${stmt.line}: ${msg} — \"${stmt.text}\"`,\n )\n}\n\n/** Strip the optional quotes around a custom icon name like `\"logos:aws\"`. */\nfunction cleanIcon(raw: string | undefined): string | undefined {\n const icon = raw?.trim().replace(/^\"(.*)\"$/, '$1')\n return icon || undefined\n}\n\n/**\n * Parse a Mermaid architecture diagram.\n * Expects the first statement to be the `architecture-beta` header.\n */\nexport function parseArchitecture(lines: Statement[]): ArchitectureDiagram {\n const diagram: ArchitectureDiagram = {\n groups: [],\n services: [],\n junctions: [],\n edges: [],\n }\n const kinds = new Map<string, Kind>()\n // `in` may name a group declared later, so parents and edge endpoints are\n // resolved after the loop.\n const parentRefs: { stmt: Statement; parent: string }[] = []\n const pendingEdges: { stmt: Statement; edge: ArchitectureEdge }[] = []\n\n const declare = (\n stmt: Statement,\n id: string,\n kind: Kind,\n parent: string | undefined,\n ): void => {\n if (kinds.has(id)) fail(stmt, `duplicate id \"${id}\"`)\n if (parent === id) fail(stmt, `\"${id}\" cannot be inside itself`)\n kinds.set(id, kind)\n if (parent) parentRefs.push({ stmt, parent })\n }\n\n for (let i = 1; i < lines.length; i++) {\n const stmt = lines[i]!\n const text = stmt.text\n\n const decl = text.match(DECL_RE)\n if (decl) {\n const kind = decl[1] as 'group' | 'service'\n const id = decl[2]!\n const title = decl[4]\n const parent = decl[5]\n declare(stmt, id, kind, parent)\n const entry = {\n id,\n icon: cleanIcon(decl[3]),\n title: title !== undefined ? normalizeBrTags(title.trim()) : id,\n parent,\n }\n if (kind === 'group') diagram.groups.push(entry)\n else diagram.services.push(entry)\n continue\n }\n\n const junction = text.match(JUNCTION_RE)\n if (junction) {\n const id = junction[1]!\n const parent = junction[2]\n declare(stmt, id, 'junction', parent)\n diagram.junctions.push({ id, parent })\n continue\n }\n\n const edge = text.match(EDGE_RE)\n if (edge) {\n pendingEdges.push({\n stmt,\n edge: {\n source: edge[1]!,\n sourceGroup: edge[2] !== undefined,\n sourcePort: edge[3] as ArchitecturePort,\n arrowStart: edge[4] !== undefined,\n arrowEnd: edge[5] !== undefined,\n targetPort: edge[6] as ArchitecturePort,\n target: edge[7]!,\n targetGroup: edge[8] !== undefined,\n },\n })\n continue\n }\n\n // Alignment constraints (Mermaid v11.16+) have no flowchart equivalent.\n if (ALIGN_RE.test(text)) continue\n\n fail(stmt, 'unrecognized statement')\n }\n\n for (const { stmt, parent } of parentRefs) {\n if (kinds.get(parent) !== 'group') {\n fail(stmt, `\"in ${parent}\" must name a declared group`)\n }\n }\n\n const parentOf = new Map<string, string>()\n for (const g of diagram.groups) if (g.parent) parentOf.set(g.id, g.parent)\n for (const g of diagram.groups) {\n const seen = new Set([g.id])\n for (let p = parentOf.get(g.id); p; p = parentOf.get(p)) {\n if (seen.has(p)) {\n throw new Error(\n `Architecture diagram: group \"${g.id}\" is nested inside itself`,\n )\n }\n seen.add(p)\n }\n }\n\n for (const { stmt, edge } of pendingEdges) {\n const ends: [string, boolean][] = [\n [edge.source, edge.sourceGroup],\n [edge.target, edge.targetGroup],\n ]\n for (const [id, viaGroup] of ends) {\n const kind = kinds.get(id)\n if (!kind) fail(stmt, `unknown id \"${id}\"`)\n if (kind === 'group') {\n fail(stmt, `edges connect services or junctions, not the group \"${id}\"`)\n }\n const parent = [...diagram.services, ...diagram.junctions].find(\n (n) => n.id === id,\n )?.parent\n if (viaGroup && !parent) {\n fail(stmt, `\"{group}\" needs \"${id}\" to be declared inside a group`)\n }\n }\n diagram.edges.push(edge)\n }\n\n return diagram\n}\n","import type {\n Direction,\n MermaidEdge,\n MermaidGraph,\n MermaidNode,\n MermaidSubgraph,\n NodeShape,\n} from '@zombie-mermaid/core'\nimport type {\n ArchitectureDiagram,\n ArchitectureEdge,\n ArchitecturePort,\n} from './types.ts'\n\n// ============================================================================\n// Architecture -> flowchart lowering\n//\n// `architecture-beta` is boxes in nested boxes joined by edges, which the\n// flowchart pipeline already lays out and draws (ELK for SVG, the grid router\n// for ASCII). Like the other lowered types it becomes a `MermaidGraph`:\n//\n// group -> subgraph (nested via `in`)\n// service -> node; the icon picks a shape\n// junction -> small filled circle, no label\n// edge -> edge; `<`/`>` become arrowheads\n// `{group}` end -> the edge attaches to the service's enclosing group\n//\n// What is not carried over: Mermaid places services on a grid from the edge\n// ports, so `a:R -- L:b` means \"b is right of a\". Here ports only choose the\n// flow direction (majority axis) and which way each edge points in the\n// layout; exact placement is the flowchart layout's. Icons are not drawn\n// (only the database/disk shapes differ) and `align` is ignored.\n// ============================================================================\n\n/** Built-in Mermaid icons that have a distinct flowchart shape. */\nconst ICON_SHAPE: Record<string, NodeShape> = {\n database: 'cylinder',\n disk: 'cylinder',\n cloud: 'stadium',\n internet: 'circle',\n}\n\nconst HORIZONTAL: ReadonlySet<ArchitecturePort> = new Set(['L', 'R'])\n\n/** Flow direction: vertical only when more edges leave via T/B than L/R. */\nfunction pickDirection(edges: readonly ArchitectureEdge[]): Direction {\n let horizontal = 0\n let vertical = 0\n for (const e of edges) {\n for (const p of [e.sourcePort, e.targetPort]) {\n if (HORIZONTAL.has(p)) horizontal++\n else vertical++\n }\n }\n return vertical > horizontal ? 'TB' : 'LR'\n}\n\n/**\n * Whether the edge, as written, runs against the layout flow. `a:L -- R:b`\n * puts `a` to the right of `b`, so in a left-to-right layout the edge has to\n * be emitted reversed for `a` to land on the right.\n */\nfunction runsBackwards(e: ArchitectureEdge, direction: Direction): boolean {\n const horizontal = direction === 'LR' || direction === 'RL'\n const [back, forward]: ArchitecturePort[] = horizontal\n ? ['L', 'R']\n : ['T', 'B']\n // Source port on the far side of the flow, or target port on the near side.\n if (e.sourcePort === forward || e.targetPort === back) return false\n return e.sourcePort === back || e.targetPort === forward\n}\n\nexport function architectureToGraph(\n diagram: ArchitectureDiagram,\n): MermaidGraph {\n const nodes = new Map<string, MermaidNode>()\n const parentOf = new Map<string, string | undefined>()\n\n for (const s of diagram.services) {\n nodes.set(s.id, {\n id: s.id,\n label: s.title,\n shape: (s.icon && ICON_SHAPE[s.icon]) || 'rectangle',\n })\n parentOf.set(s.id, s.parent)\n }\n for (const j of diagram.junctions) {\n nodes.set(j.id, { id: j.id, label: '', shape: 'filled-circle' })\n parentOf.set(j.id, j.parent)\n }\n\n // Subgraphs, nested. Children are attached in declaration order.\n const subgraphs = new Map<string, MermaidSubgraph>()\n for (const g of diagram.groups) {\n subgraphs.set(g.id, {\n id: g.id,\n label: g.title,\n nodeIds: [],\n children: [],\n })\n }\n const roots: MermaidSubgraph[] = []\n for (const g of diagram.groups) {\n const sg = subgraphs.get(g.id)!\n const parent = g.parent ? subgraphs.get(g.parent) : undefined\n if (parent) parent.children.push(sg)\n else roots.push(sg)\n }\n for (const [id, parent] of parentOf) {\n subgraphs.get(parent ?? '')?.nodeIds.push(id)\n }\n\n const direction = pickDirection(diagram.edges)\n const edges: MermaidEdge[] = diagram.edges.map((e) => {\n const end = (id: string, viaGroup: boolean): string =>\n (viaGroup ? parentOf.get(id) : undefined) ?? id\n const forward = !runsBackwards(e, direction)\n const [source, target] = forward\n ? [end(e.source, e.sourceGroup), end(e.target, e.targetGroup)]\n : [end(e.target, e.targetGroup), end(e.source, e.sourceGroup)]\n return {\n source,\n target,\n style: 'solid',\n hasArrowStart: forward ? e.arrowStart : e.arrowEnd,\n hasArrowEnd: forward ? e.arrowEnd : e.arrowStart,\n }\n })\n\n return {\n direction,\n nodes,\n edges,\n subgraphs: roots,\n classDefs: new Map(),\n classAssignments: new Map(),\n nodeStyles: new Map(),\n linkStyles: new Map(),\n interactions: new Map(),\n }\n}\n","import { normalizeBrTags, type Statement } from '@zombie-mermaid/core'\nimport type {\n C4Boundary,\n C4Diagram,\n C4Element,\n C4ElementKind,\n C4LayoutHint,\n C4ElementShape,\n C4Relationship,\n C4Variant,\n} from './types.ts'\n\n// ============================================================================\n// C4 diagram parser\n//\n// Supported syntax (Mermaid C4 macros):\n// Person / Person_Ext (alias, label, descr)\n// System[Db|Queue][_Ext] (alias, label, descr)\n// Container[Db|Queue][_Ext] (alias, label, techn, descr)\n// Component[Db|Queue][_Ext] (alias, label, techn, descr)\n// Boundary / Enterprise_Boundary / System_Boundary / Container_Boundary\n// (alias, label[, type]) { ... }\n// Deployment_Node / Node / Node_L / Node_R (alias, label, type, descr) { ... }\n// Rel / BiRel / Rel_U|Up|D|Down|L|Left|R|Right|Back (from, to, label, techn)\n// RelIndex (index, from, to, label, techn)\n// title <text>\n//\n// `Rel_U/D/L/R` record a placement hint (`C4Relationship.layout`) the\n// renderers use to steer layout. Accepted and ignored (styling / layout\n// config this renderer does not honor): UpdateElementStyle, UpdateRelStyle, UpdateLayoutConfig, LAYOUT_*,\n// SHOW_LEGEND, SHOW_FLOATING_LEGEND, AddElementTag, AddRelTag,\n// AddBoundaryTag, RoleTag. Named arguments (`$tags=\"x\"`, `$link=...`) are\n// dropped.\n// ============================================================================\n\nconst HEADERS: Record<string, C4Variant> = {\n c4context: 'context',\n c4container: 'container',\n c4component: 'component',\n c4dynamic: 'dynamic',\n c4deployment: 'deployment',\n}\n\nconst IGNORED_MACRO =\n /^(?:UpdateElementStyle|UpdateRelStyle|UpdateLayoutConfig|LAYOUT_[A-Z_]+|SHOW_LEGEND|SHOW_FLOATING_LEGEND|AddElementTag|AddRelTag|AddBoundaryTag|RoleTag)\\b/\n\nconst ELEMENT_NAME = /^(Person|System|Container|Component)(Db|Queue)?(_Ext)?$/\nconst BOUNDARY_NAME =\n /^(?:Boundary|Enterprise_Boundary|System_Boundary|Container_Boundary)$/\nconst NODE_NAME = /^(?:Deployment_Node|Node|Node_L|Node_R)$/\nconst REL_NAME =\n /^(Rel|BiRel|Rel_U|Rel_Up|Rel_D|Rel_Down|Rel_L|Rel_Left|Rel_R|Rel_Right|Rel_Back|RelIndex)$/\n\nconst BOUNDARY_TYPES: Record<string, string> = {\n Enterprise_Boundary: 'ENTERPRISE',\n System_Boundary: 'SYSTEM',\n Container_Boundary: 'CONTAINER',\n}\n\nconst REL_HINTS: Record<string, C4LayoutHint> = {\n Rel_U: 'up',\n Rel_Up: 'up',\n Rel_D: 'down',\n Rel_Down: 'down',\n Rel_L: 'left',\n Rel_Left: 'left',\n Rel_R: 'right',\n Rel_Right: 'right',\n}\n\nfunction unquote(a: string): string {\n const t = a.trim()\n const inner =\n t.length >= 2 && t.startsWith('\"') && t.endsWith('\"') ? t.slice(1, -1) : t\n return normalizeBrTags(inner)\n}\n\n/**\n * Split a macro's argument list on top-level commas. Double-quoted strings\n * may contain commas and parentheses; `$name=value` named arguments are\n * dropped. Returns null when a quote is unbalanced.\n */\nfunction splitArgs(inner: string): string[] | null {\n const raw: string[] = []\n let cur = ''\n let inQuote = false\n for (const ch of inner) {\n if (ch === '\"') inQuote = !inQuote\n if (ch === ',' && !inQuote) {\n raw.push(cur)\n cur = ''\n } else {\n cur += ch\n }\n }\n if (inQuote) return null\n raw.push(cur)\n return raw\n .map((a) => a.trim())\n .filter((a) => !a.startsWith('$'))\n .map(unquote)\n}\n\n/** `Name(args)` optionally followed by ` {`. */\nconst MACRO = /^([A-Za-z_][A-Za-z0-9_]*)\\s*\\((.*)\\)\\s*(\\{)?\\s*$/\n\nfunction fail(stmt: Statement, msg: string): never {\n throw new Error(`C4 diagram, line ${stmt.line}: ${msg} — \"${stmt.text}\"`)\n}\n\n/**\n * Parse a Mermaid C4 diagram. Expects the first statement to be a\n * `C4Context` / `C4Container` / `C4Component` / `C4Dynamic` /\n * `C4Deployment` header.\n */\nexport function parseC4Diagram(lines: Statement[]): C4Diagram {\n const variant = HEADERS[lines[0]?.text.trim().toLowerCase() ?? '']\n if (!variant) {\n throw new Error(\n `C4 diagram: expected a header of C4Context, C4Container, C4Component, C4Dynamic or C4Deployment, got \"${lines[0]?.text ?? ''}\"`,\n )\n }\n\n const diagram: C4Diagram = {\n variant,\n elements: [],\n boundaries: [],\n relationships: [],\n }\n const aliases = new Set<string>()\n const stack: C4Boundary[] = []\n /** Boundary declared on the previous statement without an inline `{`. */\n let pendingBoundary: C4Boundary | undefined\n\n const claim = (stmt: Statement, alias: string | undefined): string => {\n if (!alias) fail(stmt, 'missing alias (first argument)')\n if (aliases.has(alias)) fail(stmt, `duplicate alias \"${alias}\"`)\n aliases.add(alias)\n return alias\n }\n\n for (let i = 1; i < lines.length; i++) {\n const stmt = lines[i]!\n const line = stmt.text\n\n if (line === '}') {\n if (stack.length === 0) fail(stmt, 'unmatched \"}\"')\n stack.pop()\n continue\n }\n // Brace on its own line, directly after a boundary macro.\n if (line === '{') {\n if (!pendingBoundary) fail(stmt, 'unexpected \"{\"')\n stack.push(pendingBoundary)\n pendingBoundary = undefined\n continue\n }\n pendingBoundary = undefined\n\n const titleMatch = line.match(/^title(?:\\s+(.*))?$/i)\n if (titleMatch) {\n diagram.title = normalizeBrTags((titleMatch[1] ?? '').trim())\n continue\n }\n\n if (IGNORED_MACRO.test(line)) continue\n\n const m = line.match(MACRO)\n if (!m) fail(stmt, 'unrecognized statement')\n const name = m[1]!\n const opensBlock = m[3] === '{'\n const args = splitArgs(m[2]!)\n if (!args) fail(stmt, 'unbalanced quotes')\n\n const el = name.match(ELEMENT_NAME)\n if (el) {\n if (opensBlock) fail(stmt, `\"${name}\" cannot contain a block`)\n const kind = el[1]!.toLowerCase() as C4ElementKind\n const shape: C4ElementShape =\n el[2] === 'Db' ? 'db' : el[2] === 'Queue' ? 'queue' : 'default'\n const hasTech = kind === 'container' || kind === 'component'\n const alias = claim(stmt, args[0])\n const element: C4Element = {\n alias,\n kind,\n shape,\n external: el[3] !== undefined,\n label: args[1] || alias,\n }\n const technology = hasTech ? args[2] : undefined\n const description = hasTech ? args[3] : args[2]\n if (technology) element.technology = technology\n if (description) element.description = description\n diagram.elements.push(element)\n stack.at(-1)?.elementAliases.push(alias)\n continue\n }\n\n if (BOUNDARY_NAME.test(name) || NODE_NAME.test(name)) {\n const isNode = NODE_NAME.test(name)\n const alias = claim(stmt, args[0])\n const boundary: C4Boundary = {\n alias,\n label: args[1] || alias,\n elementAliases: [],\n children: [],\n }\n // Mermaid labels every frame with a type: the macro's own for the\n // typed boundaries, else the explicit argument, else a default.\n // (System_/Container_/Enterprise_Boundary take no type arg.)\n const typed = BOUNDARY_TYPES[name]\n if (typed) boundary.type = typed\n else if (args[2] && (isNode || name === 'Boundary'))\n boundary.type = args[2]\n else boundary.type = isNode ? 'node' : 'system'\n if (isNode && args[3]) boundary.description = args[3]\n const parent = stack.at(-1)\n if (parent) parent.children.push(boundary)\n else diagram.boundaries.push(boundary)\n if (opensBlock) stack.push(boundary)\n else pendingBoundary = boundary\n continue\n }\n\n const rel = name.match(REL_NAME)\n if (rel) {\n const indexed = name === 'RelIndex'\n const a = indexed ? args.slice(1) : args\n if (!a[0] || !a[1]) {\n fail(stmt, 'a relationship needs \"from\" and \"to\" aliases')\n }\n const r: C4Relationship = {\n from: a[0],\n to: a[1],\n label: a[2] ?? '',\n bidirectional: name === 'BiRel',\n }\n if (a[3]) r.technology = a[3]\n if (name === 'Rel_Back') r.reversed = true\n const hint = REL_HINTS[name]\n if (hint) r.layout = hint\n diagram.relationships.push(r)\n continue\n }\n\n fail(stmt, `unknown C4 macro \"${name}\"`)\n }\n\n if (stack.length > 0) {\n throw new Error(\n `C4 diagram: boundary \"${stack.at(-1)!.alias}\" is missing its closing \"}\"`,\n )\n }\n // Mermaid ignores RelIndex's index argument: in C4Dynamic every relationship\n // is numbered by its position in the source, and no other variant numbers.\n if (diagram.variant === 'dynamic') {\n diagram.relationships.forEach((r, i) => {\n r.index = String(i + 1)\n })\n }\n for (const r of diagram.relationships) {\n for (const end of [r.from, r.to]) {\n if (!aliases.has(end)) {\n throw new Error(\n `C4 diagram: relationship refers to undeclared alias \"${end}\"`,\n )\n }\n }\n }\n return diagram\n}\n","import type { C4Boundary, C4Element, C4Relationship } from './types.ts'\n\n// ============================================================================\n// C4 text and layout-hint helpers shared by the SVG and ASCII renderers, so\n// both draw the same words and honor the same `Rel_U/D/L/R` hints.\n// ============================================================================\n\n/** Greedy word wrap; a word longer than `width` stays on its own line. */\nexport function wrapC4Text(text: string, width: number): string[] {\n const out: string[] = []\n for (const paragraph of text.split('\\n')) {\n let cur = ''\n for (const word of paragraph.split(/\\s+/).filter(Boolean)) {\n if (cur && cur.length + 1 + word.length > width) {\n out.push(cur)\n cur = word\n } else {\n cur = cur ? `${cur} ${word}` : word\n }\n }\n out.push(cur)\n }\n return out\n}\n\nconst STEREOTYPES: Record<C4Element['kind'], string> = {\n person: 'Person',\n system: 'Software System',\n container: 'Container',\n component: 'Component',\n}\n\n/**\n * The bracketed type line under an element's name, as Mermaid draws it:\n * `[Person]`, `[Software System]`, `[Container: PostgreSQL]`. External,\n * database and queue variants share their base kind's line; the fill and the\n * shape tell them apart.\n */\nexport function c4TypeLine(el: C4Element): string {\n const tech =\n (el.kind === 'container' || el.kind === 'component') && el.technology\n ? `: ${el.technology}`\n : ''\n return `[${STEREOTYPES[el.kind]}${tech}]`\n}\n\n/** A boundary's type line (`[ENTERPRISE]`, `[Ubuntu]`), when it has a type. */\nexport function c4BoundaryTypeLine(b: C4Boundary): string | undefined {\n return b.type ? `[${b.type}]` : undefined\n}\n\n/**\n * The text lines of a relationship label: the label itself (prefixed with its\n * sequence number in a C4Dynamic diagram), then the technology in brackets on its\n * own line. Empty when the relationship has neither.\n */\nexport function c4RelLabelLines(rel: C4Relationship): string[] {\n const lines: string[] = []\n const head = [rel.index ? `${rel.index}:` : '', rel.label]\n .filter(Boolean)\n .join(' ')\n if (head) lines.push(head)\n if (rel.technology) lines.push(`[${rel.technology}]`)\n return lines\n}\n\n/**\n * How a relationship constrains placement. `source` is meant to sit before\n * `target` along `axis`: above it for `'vertical'`, left of it for\n * `'horizontal'`. `Rel_U`/`Rel_L` reverse the pair (the target sits above /\n * left of the source); plain `Rel` is a vertical hint, since C4 diagrams flow\n * top to bottom by default.\n */\nexport interface C4Placement {\n source: string\n target: string\n axis: 'vertical' | 'horizontal'\n /** True when the hint came from an explicit `Rel_U/D/L/R`. */\n explicit: boolean\n}\n\nexport function c4Placement(rel: C4Relationship): C4Placement {\n switch (rel.layout) {\n case 'up':\n return {\n source: rel.to,\n target: rel.from,\n axis: 'vertical',\n explicit: true,\n }\n case 'left':\n return {\n source: rel.to,\n target: rel.from,\n axis: 'horizontal',\n explicit: true,\n }\n case 'right':\n return {\n source: rel.from,\n target: rel.to,\n axis: 'horizontal',\n explicit: true,\n }\n case 'down':\n return {\n source: rel.from,\n target: rel.to,\n axis: 'vertical',\n explicit: true,\n }\n default:\n return {\n source: rel.from,\n target: rel.to,\n axis: 'vertical',\n explicit: false,\n }\n }\n}\n","import type {\n ClassDiagram,\n ClassNode,\n ClassRelationship,\n ClassMember,\n RelationshipType,\n ClassNamespace,\n} from './types.ts'\nimport {\n normalizeBrTags,\n applyClickStatement,\n splitClassShorthand,\n tryApplyClassAssignment,\n tryApplyClassDef,\n tryApplyCssClass,\n tryApplyStyleStatement,\n type Statement,\n} from '@zombie-mermaid/core'\n\n// ============================================================================\n// Class diagram parser\n//\n// Parses Mermaid classDiagram syntax into a ClassDiagram structure.\n//\n// Supported syntax:\n// class Animal { +String name; +eat() void }\n// class Shape { <<abstract>> }\n// Animal <|-- Dog (inheritance)\n// Car *-- Engine (composition)\n// Car o-- Wheel (aggregation)\n// A --> B (association)\n// A ..> B (dependency)\n// A ..|> B (realization)\n// A \"1\" --> \"*\" B : label (with cardinality + label)\n// Animal : +String name (inline attribute)\n// namespace MyNamespace { class A { } }\n// click Animal \"https://example.com\" \"Tooltip\" _blank (see docs/decisions/no-script-interactivity.md)\n// classDef name fill:#f96 / style Animal fill:#f96 / cssClass \"A,B\" name\n// class Animal:::name (style-class shorthand, also on relationship ends)\n// note \"text\" / note for Animal \"line1\\nline2\"\n// ============================================================================\n\n/**\n * The set of relationship-arrow tokens `parseRelationship` recognizes,\n * shared with the \"does this line even look like a relationship attempt\"\n * check below so the two can't drift apart. Order matters for regex\n * alternation (longer/more-specific tokens like `--|>` must be tried before\n * the bare `--` fallback).\n */\nconst RELATIONSHIP_ARROW_SOURCE =\n '<\\\\|--|<\\\\|\\\\.\\\\.|\\\\*--|o--|-->|--\\\\*|--o|--\\\\|>|\\\\.\\\\.>|\\\\.\\\\.\\\\|>|<--|<\\\\.\\\\.?|--'\n\n/**\n * Matches a line that *contains* a relationship-arrow token somewhere, even\n * if the overall shape (FROM/TO, cardinalities) doesn't parse. Used to\n * scope the \"malformed relationship\" error (issue #761) to lines that\n * already look like an attempt at this construct — mirroring the same\n * \"attempt, then validate\" pattern #722 used for the ER and xychart-beta\n * parsers, so a genuinely unrelated line (no arrow at all) still falls\n * through silently rather than throwing on totally unrecognized syntax.\n */\nconst RELATIONSHIP_ARROW_MARKER = new RegExp(RELATIONSHIP_ARROW_SOURCE)\n\n/**\n * Parse a Mermaid class diagram.\n * Expects the first line to be \"classDiagram\".\n */\n// Audited for issue #100 (non-null assertions): every `!` in this file is\n// either a bounds-checked loop-index array access (`lines[i]!` inside a\n// `for (let i = 1; i < lines.length; ...)` loop) or a regex-mandatory-\n// capture-group access after `.match()` (a group that isn't wrapped in an\n// optional `(?:...)?`, so it always participates when the overall match\n// succeeds). Both are the same idioms already accepted as justified\n// elsewhere in this codebase (see src/parser.ts, PR #158, and this\n// subsystem's earlier layout.ts/renderer.ts audit, PR #147, which fixed\n// the genuinely risky assertions but didn't reach this file) —\n// `noUncheckedIndexedAccess` can't see either guarantee, but removing the\n// `!` would only replace a proven-safe assertion with an unreachable guard.\n// Left as-is; no behavior change.\nexport function parseClassDiagram(lines: Statement[]): ClassDiagram {\n const diagram: ClassDiagram = {\n classes: [],\n relationships: [],\n namespaces: [],\n interactions: new Map(),\n classDefs: new Map(),\n classAssignments: new Map(),\n nodeStyles: new Map(),\n notes: [],\n }\n\n // Track classes by ID for deduplication\n const classMap = new Map<string, ClassNode>()\n // Track namespace nesting\n let currentNamespace: ClassNamespace | null = null\n // Track class body parsing\n let currentClass: ClassNode | null = null\n let braceDepth = 0\n // Line the currently-open class body started on, for the unclosed-body\n // error below (issue #761) — `currentClass`/`braceDepth` alone don't\n // carry position.\n let openClassLine: number | undefined\n\n for (let i = 1; i < lines.length; i++) {\n const stmt = lines[i]!\n const line = stmt.text\n\n // --- Inside a class body block ---\n if (currentClass && braceDepth > 0) {\n if (line === '}') {\n braceDepth--\n if (braceDepth === 0) {\n currentClass = null\n openClassLine = undefined\n }\n continue\n }\n\n // Check for annotation like <<interface>>\n const annotMatch = line.match(/^<<(\\w+)>>$/)\n if (annotMatch) {\n currentClass.annotation = annotMatch[1]!\n continue\n }\n\n // A line that starts with `<<` but doesn't match the full\n // `<<name>>` shape is an attempted annotation with broken syntax\n // (e.g. a missing closing `>>`) rather than a member — surface that\n // instead of silently mis-parsing it as a member named \"<<foo\" (#761).\n if (line.startsWith('<<')) {\n throw new Error(\n `Line ${stmt.line}: Malformed class annotation \"${line}\" — expected \"<<name>>\" (e.g. \"<<interface>>\").`,\n )\n }\n\n // Parse member: visibility, name, type, optional parens for method\n addMember(currentClass, line, stmt.line)\n continue\n }\n\n // --- click interaction: `click ClassName \"url\" \"tooltip\" _blank` / `click ClassName call fn()` ---\n if (/^click\\s+/i.test(line)) {\n applyClickStatement(line, diagram.interactions)\n continue\n }\n\n // --- Notes: `note \"text\"` / `note for ClassName \"text\"` ---\n // Mermaid's grammar takes a quoted string (`noteText: STR`) and its\n // renderer splits the text on `\\n` after JSON-parsing it (svgDraw.js\n // drawNote), so a literal `\\n` in the source is a line break;\n // normalizeBrTags handles that and `<br/>`, the form the rest of this\n // repo's labels use. The class need not be declared first (or at all).\n const noteMatch = line.match(/^note\\s+(?:for\\s+(\\S+)\\s+)?\"([^\"]*)\"\\s*$/)\n if (noteMatch) {\n const forClass = noteMatch[1]\n diagram.notes.push({\n text: normalizeBrTags(noteMatch[2]!),\n ...(forClass ? { forClass } : {}),\n })\n continue\n }\n\n // --- Styling: `classDef`, `style`, `cssClass \"A,B\" name`, `class A,B name` ---\n // Shared with the flowchart parser (packages/core/src/style-directives.ts). The\n // `class A,B name` assignment form is checked before the declaration\n // regexes below: a declaration never has a second bare-word token after\n // the class id (`class Animal`, `class Animal~T~`, `class Animal {`), so\n // the two can't collide. Mermaid's own class grammar only spells the\n // attachment as `cssClass \"A,B\" name` / `A:::name`; the flowchart-style\n // `class A,B name` is accepted here for parity with flowcharts.\n if (tryApplyClassDef(line, diagram)) continue\n if (tryApplyStyleStatement(line, diagram)) continue\n if (tryApplyCssClass(line, diagram)) continue\n if (tryApplyClassAssignment(line, diagram)) continue\n\n // --- Namespace block start ---\n const nsMatch = line.match(/^namespace\\s+(\\S+)\\s*\\{$/)\n if (nsMatch) {\n currentNamespace = { name: nsMatch[1]!, classIds: [] }\n continue\n }\n\n // --- Namespace end ---\n if (line === '}' && currentNamespace) {\n diagram.namespaces.push(currentNamespace)\n currentNamespace = null\n continue\n }\n\n // --- Class block start: `class ClassName {` or `class ClassName:::style {` ---\n const classBlockMatch = line.match(/^class\\s+(\\S+?)(?:\\s*~(\\w+)~)?\\s*\\{$/)\n if (classBlockMatch) {\n const id = declareClass(classBlockMatch[1]!, classBlockMatch[2])\n currentClass = classMap.get(id) ?? null\n braceDepth = 1\n openClassLine = stmt.line\n continue\n }\n\n // --- Standalone class declaration (no body): `class ClassName` / `class ClassName:::style` ---\n const classOnlyMatch = line.match(/^class\\s+(\\S+?)(?:\\s*~(\\w+)~)?\\s*$/)\n if (classOnlyMatch) {\n declareClass(classOnlyMatch[1]!, classOnlyMatch[2])\n continue\n }\n\n // --- Single-line body: `class ClassName { <<interface>> }` / `class ClassName:::style { -int size }` ---\n // One annotation or one member; `;`-separated members on a single line\n // are already split into separate statements upstream, so a multi-member\n // body can't reach here intact.\n const inlineBodyMatch = line.match(/^class\\s+(\\S+?)\\s*\\{\\s*(.*?)\\s*\\}$/)\n if (inlineBodyMatch) {\n const id = declareClass(inlineBodyMatch[1]!, undefined)\n const cls = classMap.get(id)\n const body = inlineBodyMatch[2]!\n const annotMatch = body.match(/^<<(\\w+)>>$/)\n if (cls && annotMatch) {\n cls.annotation = annotMatch[1]!\n } else if (cls && body.startsWith('<<')) {\n // Same broken-annotation-attempt case as the multi-line body\n // branch above, just on the single-line form (#761).\n throw new Error(\n `Line ${stmt.line}: Malformed class annotation \"${body}\" — expected \"<<name>>\" (e.g. \"<<interface>>\").`,\n )\n } else if (cls && body) {\n addMember(cls, body, stmt.line)\n }\n continue\n }\n\n // --- Inline attribute: `ClassName : +String name` ---\n const inlineAttrMatch = line.match(/^(\\S+?)\\s*:\\s*(.+)$/)\n if (inlineAttrMatch) {\n // Make sure this isn't a relationship line (those have arrows)\n const rest = inlineAttrMatch[2]!\n if (!rest.match(/<\\|--|--|\\*--|o--|-->|\\.\\.>|\\.\\.\\|>/)) {\n addMember(ensureClass(classMap, inlineAttrMatch[1]!), rest, stmt.line)\n continue\n }\n }\n\n // --- Relationship ---\n // Pattern: [FROM] [\"card\"] ARROW [\"card\"] [TO] [: label]\n // Arrows: <|--, *--, o--, -->, ..|>, ..>\n // Can also be reversed: --o, --*, --|>\n const rel = parseRelationship(line)\n if (rel) {\n // Ensure both classes exist. Either end may carry the `:::style`\n // shorthand (Mermaid's docs warn against combining it with a relation\n // statement, but it's better to strip it than to mint a class whose id\n // is the literal `Animal:::someclass`).\n rel.from = declareClass(rel.from, undefined, false)\n rel.to = declareClass(rel.to, undefined, false)\n diagram.relationships.push(rel)\n continue\n }\n\n // A line containing a relationship-arrow token that still didn't parse\n // above is a malformed/dangling relationship attempt — e.g. \"Animal\n // <|--\" with no target, or an unsupported arrow shape — rather than an\n // unrelated line that merely falls through. Scoped to lines that\n // already look like an attempt (same \"attempt, then validate\" pattern\n // #722 used for ER/xychart-beta) so genuinely unrecognized syntax with\n // no arrow at all keeps falling through silently (#761).\n if (RELATIONSHIP_ARROW_MARKER.test(line)) {\n throw new Error(\n `Line ${stmt.line}: Malformed class-diagram relationship \"${line}\". Expected \"FROM ARROW TO\" (optionally with cardinalities and a \": label\"), e.g. \"Animal <|-- Dog\" or 'A \"1\" --> \"*\" B : label'. ARROW must be one of <|--, <|.., *--, o--, -->, --*, --o, --|>, ..>, ..|>, <--, <.., or --.`,\n )\n }\n }\n\n if (currentClass !== null) {\n throw new Error(\n `Line ${openClassLine}: Unclosed class body for \"${currentClass.id}\" — expected a closing \"}\" before the diagram ends.`,\n )\n }\n\n diagram.classes = [...classMap.values()]\n return diagram\n\n /**\n * Register a class from a declaration token, splitting off any `:::style`\n * shorthand into `diagram.classAssignments`. Returns the bare id.\n *\n * @param generic - `~T~` generic parameter captured by the declaration regex\n * @param inNamespace - whether to record the class in the open namespace\n * (declarations do; relationship endpoints don't)\n */\n function declareClass(\n token: string,\n generic: string | undefined,\n inNamespace: boolean = true,\n ): string {\n const { id: rawId, className } = splitClassShorthand(token)\n let id = rawId\n let resolvedGeneric = generic\n if (!resolvedGeneric) {\n // The declaration regexes only split a `~T~` generic off the bare id\n // when nothing else trails it. When the `:::style` shorthand trails\n // the generic instead (`Foo~T~:::hot`), the generic gets swallowed\n // into the shorthand-stripped id here (`splitClassShorthand` returns\n // `Foo~T~`) — strip it off now so the class is declared as `Foo`\n // rather than the literal `Foo~T~` (#506).\n const genericMatch = rawId.match(/^(.+?)~(\\w+)~$/)\n if (genericMatch) {\n id = genericMatch[1]!\n resolvedGeneric = genericMatch[2]!\n }\n }\n const cls = ensureClass(classMap, id)\n if (resolvedGeneric) {\n cls.label = `${id}<${resolvedGeneric}>`\n }\n if (className) {\n diagram.classAssignments.set(id, className)\n }\n if (inNamespace && currentNamespace) {\n currentNamespace.classIds.push(id)\n }\n return id\n }\n}\n\n/** Parse one member line and file it under the class's attributes or methods. */\nfunction addMember(cls: ClassNode, line: string, lineNumber: number): void {\n const member = parseMember(line, lineNumber)\n if (!member) return\n if (member.isMethod) {\n cls.methods.push(member.member)\n } else {\n cls.attributes.push(member.member)\n }\n}\n\n/** Ensure a class exists in the map, creating a default if needed */\nfunction ensureClass(classMap: Map<string, ClassNode>, id: string): ClassNode {\n let cls = classMap.get(id)\n if (!cls) {\n cls = { id, label: id, attributes: [], methods: [] }\n classMap.set(id, cls)\n }\n return cls\n}\n\n/** Count occurrences of `needle` in `input`. */\nfunction countOccurrence(input: string, needle: string): number {\n return Math.max(0, input.split(needle).length - 1)\n}\n\n/**\n * Convert one comma-free (or already re-joined) segment's `~`-delimited\n * generics to angle brackets, pairing the outermost `~`s first so nested\n * generics like `List~List~T~~` become `List<List<T>>`. A segment with a\n * single `~` is left alone — there's nothing to pair it with — and an odd\n * count with a leading `~` keeps that leading one as-is (mermaid treats it\n * as literal text, not a delimiter). Mirrors mermaid's `processSet`.\n */\nfunction convertGenericSegment(input: string): string {\n const tildeCount = countOccurrence(input, '~')\n if (tildeCount <= 1) return input\n\n let text = input\n let keepLeadingTilde = false\n if (tildeCount % 2 !== 0 && text.startsWith('~')) {\n text = text.slice(1)\n keepLeadingTilde = true\n }\n\n const chars = [...text]\n let open = chars.indexOf('~')\n let close = chars.lastIndexOf('~')\n while (open !== -1 && close !== -1 && open !== close) {\n chars[open] = '<'\n chars[close] = '>'\n open = chars.indexOf('~')\n close = chars.lastIndexOf('~')\n }\n if (keepLeadingTilde) chars.unshift('~')\n return chars.join('')\n}\n\n/**\n * Convert mermaid's `~T~` generic syntax to the `<T>` form mermaid itself\n * renders — `List~Observer~` → `List<Observer>`, `Map~K,V~` → `Map<K,V>`,\n * `List~List~T~~` → `List<List<T>>`.\n *\n * Port of mermaid's `parseGenericTypes` (packages/mermaid/src/diagrams/\n * common/common.ts): the text is split on commas so a comma *inside* a\n * generic (`Map~K,V~`) can be re-joined with its neighbors — two adjacent\n * comma-separated pieces that each carry exactly one `~` are the two halves\n * of one generic — before each piece's `~` pairs are converted outermost-first.\n */\nexport function parseGenericTypes(input: string): string {\n const pieces = input.split(/(,)/)\n const output: string[] = []\n for (let i = 0; i < pieces.length; i++) {\n let piece = pieces[i]!\n if (piece === ',' && i > 0 && i + 1 < pieces.length) {\n const previous = pieces[i - 1]!\n const next = pieces[i + 1]!\n if (\n countOccurrence(previous, '~') === 1 &&\n countOccurrence(next, '~') === 1\n ) {\n piece = `${previous},${next}`\n i++\n output.pop()\n }\n }\n output.push(convertGenericSegment(piece))\n }\n return output.join('')\n}\n\n/** Parse a class member line (attribute or method) */\nfunction parseMember(\n line: string,\n lineNumber: number,\n): { member: ClassMember; isMethod: boolean } | null {\n const trimmed = line.trim().replace(/;$/, '')\n if (!trimmed) return null\n\n // Extract visibility prefix\n let visibility: ClassMember['visibility'] = ''\n let rest = trimmed\n const visibilityChar = rest[0]\n if (\n visibilityChar === '+' ||\n visibilityChar === '-' ||\n visibilityChar === '#' ||\n visibilityChar === '~'\n ) {\n visibility = visibilityChar\n rest = rest.slice(1).trim()\n }\n\n // An attempted method signature with an opening \"(\" but no closing \")\"\n // at all can't be a genuine attribute either (an attribute name/type\n // never legitimately contains a bare, unclosed paren) — surface it\n // instead of silently parsing \"eat(void\" as a member literally named\n // that (#761).\n if (rest.includes('(') && !rest.includes(')')) {\n throw new Error(\n `Line ${lineNumber}: Malformed class member \"${trimmed}\" — unclosed \"(\" in a method signature. Expected e.g. \"+eat() void\" or \"+eat(Food f) void\".`,\n )\n }\n\n // Mermaid renders `List~Observer~` as `List<Observer>` (its own\n // `parseGenericTypes`). It's applied per field below — name, params and\n // return type separately, the way mermaid's ClassMember does — rather\n // than to the whole line, since the comma re-join inside\n // parseGenericTypes would otherwise pair a `~` in the params with one in\n // the return type. Doing it after the visibility prefix is stripped also\n // keeps a package-visibility `~` from pairing with a generic's.\n\n // Check if it's a method (has parentheses)\n const methodMatch = rest.match(/^(.+?)\\(([^)]*)\\)(?:\\s*(.+))?$/)\n if (methodMatch) {\n const name = parseGenericTypes(methodMatch[1]!.trim())\n const rawParams = methodMatch[2]?.trim()\n const params = rawParams ? parseGenericTypes(rawParams) : undefined // Store the parameter string\n const rawType = methodMatch[3]?.trim()\n const type = rawType ? parseGenericTypes(rawType) : undefined\n // Check for static ($) or abstract (*) markers\n const isStatic = name.endsWith('$') || rest.includes('$')\n const isAbstract = name.endsWith('*') || rest.includes('*')\n return {\n member: {\n visibility,\n name: name.replace(/[$*]$/, ''),\n type: type || undefined,\n isStatic,\n isAbstract,\n isMethod: true,\n params,\n },\n isMethod: true,\n }\n }\n\n // It's an attribute: [Type] name or name Type\n // Common patterns: \"String name\", \"+int age\", \"name\"\n const parts = parseGenericTypes(rest).split(/\\s+/)\n let name: string\n let type: string | undefined\n\n if (parts.length >= 2) {\n // \"Type name\" pattern\n type = parts[0]\n name = parts.slice(1).join(' ')\n } else {\n name = parts[0] ?? rest\n }\n\n const isStatic = name.endsWith('$')\n const isAbstract = name.endsWith('*')\n\n return {\n member: {\n visibility,\n name: name.replace(/[$*]$/, ''),\n type: type || undefined,\n isStatic,\n isAbstract,\n isMethod: false,\n },\n isMethod: false,\n }\n}\n\n/** Parse a relationship line into a ClassRelationship */\nfunction parseRelationship(line: string): ClassRelationship | null {\n // Relationship regex — handles all arrow types with optional cardinality and labels\n // Pattern: FROM [\"card\"] ARROW [\"card\"] TO[:::style] [: label]\n // The `:::style` shorthand on TO is matched as its own group so the `:`\n // label separator that follows can't swallow it as a label of `::style`;\n // it's re-joined onto the id here and split off again by the caller.\n const match = line.match(\n new RegExp(\n `^(\\\\S+?)\\\\s+(?:\"([^\"]*?)\"\\\\s+)?(${RELATIONSHIP_ARROW_SOURCE})\\\\s+(?:\"([^\"]*?)\"\\\\s+)?(\\\\S+?)(?::::([\\\\w][\\\\w-]*))?(?:\\\\s*:\\\\s*(.+))?$`,\n ),\n )\n if (!match) return null\n\n const from = match[1]!\n const rawFromCardinality = match[2]\n const fromCardinality = rawFromCardinality\n ? normalizeBrTags(rawFromCardinality)\n : undefined\n const arrow = match[3]!.trim()\n const rawToCardinality = match[4]\n const toCardinality = rawToCardinality\n ? normalizeBrTags(rawToCardinality)\n : undefined\n const to = match[6] ? `${match[5]!}:::${match[6]}` : match[5]!\n const rawLabel = match[7]?.trim()\n const label = rawLabel ? normalizeBrTags(rawLabel) : undefined\n\n const parsed = parseArrow(arrow)\n if (!parsed) return null\n\n return {\n from,\n to,\n type: parsed.type,\n markerAt: parsed.markerAt,\n label,\n fromCardinality,\n toCardinality,\n }\n}\n\n/**\n * Map arrow syntax to relationship type and marker placement side.\n * Prefix markers (`<|--`, `*--`, `o--`) place the UML shape at the 'from' end.\n * Suffix markers (`..|>`, `-->`, `..>`, `--*`, `--o`) place it at the 'to' end.\n */\nfunction parseArrow(\n arrow: string,\n): { type: RelationshipType; markerAt: 'from' | 'to' } | null {\n // Trim whitespace that might be captured by the regex\n const a = arrow.trim()\n switch (a) {\n case '<|--':\n return { type: 'inheritance', markerAt: 'from' }\n case '--|>':\n return { type: 'inheritance', markerAt: 'to' }\n case '<|..':\n return { type: 'realization', markerAt: 'from' }\n case '..|>':\n return { type: 'realization', markerAt: 'to' }\n case '*--':\n return { type: 'composition', markerAt: 'from' }\n case '--*':\n return { type: 'composition', markerAt: 'to' }\n case 'o--':\n return { type: 'aggregation', markerAt: 'from' }\n case '--o':\n return { type: 'aggregation', markerAt: 'to' }\n case '-->':\n return { type: 'association', markerAt: 'to' }\n case '<--':\n return { type: 'association', markerAt: 'from' }\n case '..>':\n return { type: 'dependency', markerAt: 'to' }\n case '<..':\n return { type: 'dependency', markerAt: 'from' }\n case '--':\n return { type: 'association', markerAt: 'to' }\n default:\n return null\n }\n}\n","// ============================================================================\n// Class member formatting — shared between the SVG (layout.ts) and ASCII\n// (ascii/class-diagram.ts) renderers so method parentheses/params can't drift\n// between the two again. See issue #290.\n// ============================================================================\n\nimport type { ClassMember } from './types.ts'\n\n/** Format a class member as a display string: visibility + name(+params for methods) + optional type */\nexport function formatClassMember(m: ClassMember): string {\n const vis = m.visibility ? `${m.visibility} ` : ''\n const name = m.isMethod ? `${m.name}(${m.params || ''})` : m.name\n const type = m.type ? `: ${m.type}` : ''\n return `${vis}${name}${type}`\n}\n","import type {\n ErDiagram,\n ErEntity,\n ErAttribute,\n ErRelationship,\n Cardinality,\n} from './types.ts'\nimport {\n normalizeBrTags,\n toDirection,\n type Statement,\n} from '@zombie-mermaid/core'\n\n// ============================================================================\n// ER diagram parser\n//\n// Parses Mermaid erDiagram syntax into an ErDiagram structure.\n//\n// Supported syntax:\n// CUSTOMER ||--o{ ORDER : places\n// CUSTOMER {\n// string name PK\n// int age\n// string email UK \"user email\"\n// }\n//\n// Cardinality notation:\n// || || exactly one\n// |o o| zero or one\n// }| |{ one or more\n// }o o{ zero or more\n//\n// Line style:\n// -- identifying (solid line)\n// .. non-identifying (dashed line)\n// ============================================================================\n\n/**\n * Parse a Mermaid ER diagram.\n * Expects the first line to be \"erDiagram\".\n */\n// Audited for issue #100 (non-null assertions): every `!` in this file is\n// either a bounds-checked loop-index array access (`lines[i]!` inside a\n// `for (let i = 1; i < lines.length; ...)` loop) or a regex-mandatory-\n// capture-group access after `.match()` (a group that isn't wrapped in an\n// optional `(?:...)?`, so it always participates when the overall match\n// succeeds). Both are the same idioms already accepted as justified\n// elsewhere in this codebase (see src/parser.ts, PR #158, and this\n// subsystem's layout.ts/renderer.ts audit, PR #148, which fixed the\n// genuinely risky assertions there but didn't reach this file — PR #146\n// separately reviewed this file's `as` casts, which are a different\n// concern from these `!`s) — `noUncheckedIndexedAccess` can't see either\n// guarantee, but removing the `!` would only replace a proven-safe\n// assertion with an unreachable guard. Left as-is; no behavior change.\nexport function parseErDiagram(lines: Statement[]): ErDiagram {\n const diagram: ErDiagram = {\n entities: [],\n relationships: [],\n }\n\n // Track entities by ID for deduplication\n const entityMap = new Map<string, ErEntity>()\n // Track entity body parsing\n let currentEntity: ErEntity | null = null\n\n for (let i = 1; i < lines.length; i++) {\n const stmt = lines[i]!\n const line = stmt.text\n\n // --- Inside entity body ---\n if (currentEntity) {\n if (line === '}') {\n currentEntity = null\n continue\n }\n\n // Attribute line: type name [PK|FK|UK] [\"comment\"]\n const attr = parseAttribute(line)\n if (attr) {\n currentEntity.attributes.push(attr)\n }\n continue\n }\n\n // --- direction directive: `direction TB` / `direction LR` / etc. ---\n // ER diagrams have no subgraph nesting, so a single top-level direction\n // applies to the whole diagram (unlike flowcharts, where `direction` can\n // also appear per-subgraph).\n const dirMatch = line.match(/^direction\\s+(TD|TB|LR|BT|RL)\\s*$/i)\n if (dirMatch) {\n diagram.direction = toDirection(dirMatch[1]!)\n continue\n }\n\n // --- Entity block start: `ENTITY_NAME {`, `id[Alias Label] {`, or\n // `id[\"Quoted Alias\"] {` — optionally with the body (and even the\n // closing `}`) inline on the same line, e.g. `p[Person] { string name }`.\n const entityBlockMatch = line.match(/^(\\S+?)(?:\\[(.+)\\])?\\s*\\{(.*)$/)\n if (entityBlockMatch) {\n const id = entityBlockMatch[1]!\n const aliasRaw = entityBlockMatch[2]\n const alias =\n aliasRaw !== undefined\n ? normalizeBrTags(aliasRaw.trim().replace(/^[\"']|[\"']$/g, ''))\n : undefined\n const entity = ensureEntity(entityMap, id, alias)\n\n const trailing = entityBlockMatch[3]!.trim()\n const closesInline = trailing.endsWith('}')\n const body = closesInline ? trailing.slice(0, -1).trim() : trailing\n for (const part of body\n .split(';')\n .map((s) => s.trim())\n .filter(Boolean)) {\n const attr = parseAttribute(part)\n if (attr) entity.attributes.push(attr)\n }\n\n // Only keep the block open for subsequent attribute lines when this\n // line didn't already close it.\n if (!closesInline) {\n currentEntity = entity\n }\n continue\n }\n\n // --- Relationship: `ENTITY1 cardinality1--cardinality2 ENTITY2 : label` ---\n const rel = parseRelationshipLine(line, stmt.line)\n if (rel) {\n // Ensure both entities exist\n ensureEntity(entityMap, rel.entity1)\n ensureEntity(entityMap, rel.entity2)\n diagram.relationships.push(rel)\n continue\n }\n }\n\n diagram.entities = [...entityMap.values()]\n return diagram\n}\n\n/**\n * Ensure an entity exists in the map. When `alias` is provided (from an\n * `id[Alias]` entity-block header), it becomes the display label while the\n * map key — and every relationship reference — stays keyed off the raw `id`.\n */\nfunction ensureEntity(\n entityMap: Map<string, ErEntity>,\n id: string,\n alias?: string,\n): ErEntity {\n let entity = entityMap.get(id)\n if (!entity) {\n entity = { id, label: alias ?? id, attributes: [] }\n entityMap.set(id, entity)\n } else if (alias !== undefined) {\n entity.label = alias\n }\n return entity\n}\n\n/** Parse an attribute line inside an entity block */\nfunction parseAttribute(line: string): ErAttribute | null {\n // Format: type name [PK|FK|UK [...]] [\"comment\"]\n const match = line.match(/^(\\S+)\\s+(\\S+)(?:\\s+(.+))?$/)\n if (!match) return null\n\n const type = match[1]!\n const name = match[2]!\n const rest = match[3]?.trim() ?? ''\n\n // Extract key constraints (PK, FK, UK) and optional comment\n const keys: ErAttribute['keys'] = []\n let comment: string | undefined\n\n // Extract quoted comment first (supports <br> tags)\n const commentMatch = rest.match(/\"([^\"]*)\"/)\n if (commentMatch) {\n comment = normalizeBrTags(commentMatch[1]!)\n }\n\n // Extract key constraints\n const restWithoutComment = rest.replace(/\"[^\"]*\"/, '').trim()\n for (const part of restWithoutComment.split(/\\s+/)) {\n const upper = part.toUpperCase()\n if (upper === 'PK' || upper === 'FK' || upper === 'UK') {\n keys.push(upper)\n }\n }\n\n return { type, name, keys, comment }\n}\n\n/**\n * Parse a relationship line.\n *\n * Cardinality symbols on each side of the line style:\n * Left side (entity1): || |o }| }o\n * Line: -- (identifying) or .. (non-identifying)\n * Right side (entity2): || o| |{ o{\n *\n * Full pattern example: CUSTOMER ||--o{ ORDER : places\n */\nfunction parseRelationshipLine(\n line: string,\n lineNumber: number,\n): ErRelationship | null {\n // Loosely match a line shaped like a relationship attempt: two bare\n // tokens flanking a `--`/`..`-based marker, with an optional `: label`.\n // A line with no such marker at all doesn't look like a relationship\n // attempt and falls through silently (returns null), same as before —\n // that part of the parser's leniency is unchanged.\n //\n // Everything that *does* match this shape is validated below and either\n // returns a fully-parsed relationship or throws an actionable error.\n // Before this pass, an invalid cardinality token or a missing/empty\n // label silently dropped the *entire* line (both entities included)\n // with zero indication anything was wrong — see issue #541 (parser\n // error-message quality audit — this parser had zero throw sites before\n // this pass).\n const attempt = line.match(\n /^(\\S+)\\s+(\\S*(?:--|\\.\\.)\\S*)\\s+(\\S+)\\s*(?::\\s*(.*))?$/,\n )\n if (!attempt) return null\n\n const entity1 = attempt[1]!\n const cardinalityStr = attempt[2]!\n const entity2 = attempt[3]!\n const hasLabel = attempt[4] !== undefined\n const rawLabel = attempt[4]?.trim() ?? ''\n\n // Cardinality symbols on each side of the line style:\n // Left side (entity1): || |o }| }o\n // Line: -- (identifying) or .. (non-identifying)\n // Right side (entity2): || o| |{ o{\n // `shapeMatch` only confirms the token is built from crow's-foot\n // characters around a line-style marker — `parseLeftCardinality`/\n // `parseRightCardinality` still reject a shape match that isn't one of\n // the four valid two-character combinations per side (e.g. a lone \"|\").\n const shapeMatch = cardinalityStr.match(/^([|o}{]*)(--|\\.\\.?)([|o}{]*)$/)\n const cardinality1 = shapeMatch ? parseLeftCardinality(shapeMatch[1]!) : null\n const cardinality2 = shapeMatch ? parseRightCardinality(shapeMatch[3]!) : null\n\n if (!cardinality1 || !cardinality2) {\n throw new Error(\n `Line ${lineNumber}: Invalid ER relationship cardinality \"${cardinalityStr}\" in \"${line}\". ` +\n 'Left side must be one of ||, |o, }|, }o; right side must be one of ||, o|, |{, o{ (e.g. \"||--o{\").',\n )\n }\n\n if (!hasLabel || rawLabel.length === 0) {\n throw new Error(\n `Line ${lineNumber}: ER relationship \"${entity1} ${cardinalityStr} ${entity2}\" is missing a \": label\" — expected e.g. \"${entity1} ${cardinalityStr} ${entity2} : label\".`,\n )\n }\n\n // Strip surrounding quotes if present, then normalize br tags.\n const label = normalizeBrTags(rawLabel.replace(/^[\"']|[\"']$/g, ''))\n const identifying = shapeMatch![2] === '--'\n\n return { entity1, entity2, cardinality1, cardinality2, label, identifying }\n}\n\n/**\n * Parse a left-side (entity1) cardinality notation string.\n * The crow's-foot character sits nearer the entity, so left- and\n * right-side notations are mirror images of each other and must be\n * matched exactly rather than order-normalized (sorting `}o` and `o{`\n * to the same key conflates \"zero or more\" with malformed input).\n */\nfunction parseLeftCardinality(str: string): Cardinality | null {\n if (str === '||') return 'one'\n if (str === '|o') return 'zero-one'\n if (str === '}|') return 'many'\n if (str === '}o') return 'zero-many'\n return null\n}\n\n/** Parse a right-side (entity2) cardinality notation string. */\nfunction parseRightCardinality(str: string): Cardinality | null {\n if (str === '||') return 'one'\n if (str === 'o|') return 'zero-one'\n if (str === '|{') return 'many'\n if (str === 'o{') return 'zero-many'\n return null\n}\n","// ============================================================================\n// `box <color?> <label?>` header parsing\n//\n// Mermaid's sequenceDb.parseBoxData takes the first word of the header (or a\n// leading `rgb(...)`/`hsl(...)` call), asks the browser whether it is a valid\n// CSS colour, and — if it isn't — treats the whole header as the label. There\n// is no browser here, so \"is a colour\" is decided by a fixed list of CSS\n// named colours plus the hex / rgb() / hsl() function forms. That list is\n// also what keeps a diagram-supplied string safe to emit inside an SVG\n// attribute: anything not matching it never reaches the renderer as a colour.\n// ============================================================================\n\n/** The CSS Color Module Level 4 named colours (plus `transparent`, handled separately). */\nconst CSS_NAMED_COLORS = new Set(\n (\n 'aliceblue antiquewhite aqua aquamarine azure beige bisque black ' +\n 'blanchedalmond blue blueviolet brown burlywood cadetblue chartreuse ' +\n 'chocolate coral cornflowerblue cornsilk crimson cyan darkblue darkcyan ' +\n 'darkgoldenrod darkgray darkgreen darkgrey darkkhaki darkmagenta ' +\n 'darkolivegreen darkorange darkorchid darkred darksalmon darkseagreen ' +\n 'darkslateblue darkslategray darkslategrey darkturquoise darkviolet ' +\n 'deeppink deepskyblue dimgray dimgrey dodgerblue firebrick floralwhite ' +\n 'forestgreen fuchsia gainsboro ghostwhite gold goldenrod gray green ' +\n 'greenyellow grey honeydew hotpink indianred indigo ivory khaki lavender ' +\n 'lavenderblush lawngreen lemonchiffon lightblue lightcoral lightcyan ' +\n 'lightgoldenrodyellow lightgray lightgreen lightgrey lightpink ' +\n 'lightsalmon lightseagreen lightskyblue lightslategray lightslategrey ' +\n 'lightsteelblue lightyellow lime limegreen linen magenta maroon ' +\n 'mediumaquamarine mediumblue mediumorchid mediumpurple mediumseagreen ' +\n 'mediumslateblue mediumspringgreen mediumturquoise mediumvioletred ' +\n 'midnightblue mintcream mistyrose moccasin navajowhite navy oldlace olive ' +\n 'olivedrab orange orangered orchid palegoldenrod palegreen paleturquoise ' +\n 'palevioletred papayawhip peachpuff peru pink plum powderblue purple ' +\n 'rebeccapurple red rosybrown royalblue saddlebrown salmon sandybrown ' +\n 'seagreen seashell sienna silver skyblue slateblue slategray slategrey ' +\n 'snow springgreen steelblue tan teal thistle tomato turquoise violet ' +\n 'wheat white whitesmoke yellow yellowgreen'\n ).split(' '),\n)\n\n// `#rgb`, `#rgba`, `#rrggbb`, `#rrggbbaa`. Mermaid itself can't accept hex\n// here (upstream's lexer treats `#` as a comment); this parser has no such\n// comment syntax, so hex is accepted as a strict superset.\nconst HEX_COLOR_RE = /^#(?:[0-9a-f]{3,4}|[0-9a-f]{6}|[0-9a-f]{8})$/i\n// `rgb(33,66,99)`, `rgba(33,66,99,0.5)`, `hsl(10, 40%, 90%)`, `hsla(...)`,\n// including the modern space-separated / slash-alpha forms. The argument\n// character class is the safety boundary: digits, `.`, `%`, `,`, `/`, and\n// whitespace only.\nconst FUNCTION_COLOR_RE =\n /^(?:rgba?|hsla?)\\(\\s*[\\d.%]+(?:[\\s,/]+[\\d.%]+){2,3}\\s*\\)$/i\n\n/** Whether `value` is a colour this renderer will emit as-is (case-insensitive). */\nexport function isCssColor(value: string): boolean {\n return (\n CSS_NAMED_COLORS.has(value.toLowerCase()) ||\n HEX_COLOR_RE.test(value) ||\n FUNCTION_COLOR_RE.test(value)\n )\n}\n\n/**\n * Split the text after the `box` keyword into a colour and a label, per\n * Mermaid's `parseBoxData`: the leading word (or function call) is the\n * colour if it is one, otherwise the whole header is the label. An explicit\n * `transparent` is consumed as \"no colour\" so `box transparent Aqua` yields\n * a colourless box labelled `Aqua`, exactly as upstream documents.\n */\nexport function parseBoxHeader(header: string): {\n color?: string\n label: string\n} {\n const trimmed = header.trim()\n const match = trimmed.match(\n /^((?:rgba?|hsla?)\\s*\\([^)]*\\)|#[0-9a-fA-F]+|\\S+)?(.*)$/,\n )\n const candidate = match?.[1] ?? ''\n const rest = match?.[2]?.trim() ?? ''\n if (candidate.toLowerCase() === 'transparent') {\n return { label: rest }\n }\n if (candidate !== '' && isCssColor(candidate)) {\n return { color: candidate, label: rest }\n }\n return { label: trimmed }\n}\n","import type { SequenceDiagram, Message, Block, Actor } from './types.ts'\nimport { normalizeBrTags, type Statement } from '@zombie-mermaid/core'\nimport { parseBoxHeader } from './box-color.ts'\n\n/**\n * Which `box … end` group (index into `diagram.boxes`) is currently open,\n * and which box each participant already belongs to. Threaded through the\n * actor-creating helpers so a participant first mentioned inside a box —\n * by a declaration, a `create`, or (leniently) a message — joins it, the\n * way Mermaid's `addActor` assigns `currentBox`.\n */\ninterface BoxContext {\n open: number | undefined\n membership: Map<string, number>\n}\n\n/**\n * Narrow a regex-captured block keyword to `Block['type']`. The capturing\n * regex at the call site uses the same `(loop|alt|opt|par|critical|break|\n * rect)` alternation, so the input is always one of these seven values in\n * practice — but the match itself is typed as `string`.\n *\n * Exported for direct unit testing (see\n * src/__tests__/sequence-parser.test.ts) — not otherwise part of this\n * module's public parsing API.\n */\nexport function isBlockType(value: string): value is Block['type'] {\n return (\n value === 'loop' ||\n value === 'alt' ||\n value === 'opt' ||\n value === 'par' ||\n value === 'critical' ||\n value === 'break' ||\n value === 'rect'\n )\n}\n\n/**\n * Narrow a regex-captured block keyword to `Block['type']`, throwing if it\n * somehow isn't one of the seven recognized keywords (see `isBlockType`\n * above — unreachable via the guarding regex in practice, but this keeps\n * the failure explicit rather than silently mistyping the value).\n *\n * Exported for direct unit testing (see\n * src/__tests__/sequence-parser.test.ts) — the throw branch is unreachable\n * through the public `parseSequenceDiagram` API (the regex that captures\n * the value already restricts it to the seven valid keywords), so it can\n * only be exercised by calling this function directly.\n */\nexport function toBlockType(value: string): Block['type'] {\n if (!isBlockType(value)) {\n throw new Error(`Invalid block type: \"${value}\"`)\n }\n return value\n}\n\n// ============================================================================\n// Sequence diagram parser\n//\n// Parses Mermaid sequenceDiagram syntax into a SequenceDiagram structure.\n//\n// Supported syntax:\n// participant A as Alice\n// actor B as Bob\n// A->>B: Solid arrow\n// A-->>B: Dashed arrow\n// A-)B: Open arrow\n// A--)B: Dashed open arrow\n// A->>+B: Activate target\n// A-->>-B: Deactivate source\n// activate A / deactivate A (standalone form of the +/- shorthand)\n// create participant C [as Label] / create actor C [as Label]\n// (on the line before C's first message)\n// destroy C (on the line before C's last message)\n// box <color?> <label?> ... end (participant grouping; see box-color.ts)\n// A<<->>B: Bidirectional solid arrow\n// A<<-->>B: Bidirectional dashed arrow\n// autonumber / autonumber <start> <step> / autonumber off\n// loop Label ... end\n// alt Label ... else Label ... end\n// opt Label ... end\n// par Label ... and Label ... end\n// Note left of A: Text\n// Note right of A: Text\n// Note over A,B: Text\n// ============================================================================\n\n// Message-line arrow regexes, shared by the two-pass match in the \"Message\"\n// branch of the parsing loop below (see the comment there for why there are\n// two, and issue #341 for the mis-split bug this two-pass approach fixes).\n// `MESSAGE_LONG_ARROW_RE` only recognizes the \"long\" arrow forms — anything\n// ending in `>` (`->`, `-->`, `->>`, `-->>`) plus the bidirectional tokens\n// (`<<->>`, `<<-->>`) — which essentially never occur by accident inside an\n// unquoted actor name. `MESSAGE_ANY_ARROW_RE` is the original, full\n// alternation (also matching the short open/cross forms `-)`, `--)`, `-x`,\n// `--x`), used as a fallback when a line has no long arrow at all.\nconst MESSAGE_LONG_ARROW_RE =\n /^(.+?)\\s*(<<->>|<<-->>|--?>?>)\\s*([+-]?)(.+?)\\s*:\\s*(.+)$/\nconst MESSAGE_ANY_ARROW_RE =\n /^(.+?)\\s*(<<->>|<<-->>|--?>?>|--?[)x]|--?>>|--?>)\\s*([+-]?)(.+?)\\s*:\\s*(.+)$/\n\n/**\n * Parse a Mermaid sequence diagram.\n * Expects the first line to be \"sequenceDiagram\".\n */\n// Audited for issue #100 (non-null assertions): every `!` in this file is\n// one of three idioms already accepted as justified elsewhere in this\n// codebase — (1) a bounds-checked loop-index array access (`lines[i]!`\n// inside a `for (let i = 1; i < lines.length; ...)` loop), (2) a\n// regex-mandatory-capture-group access after `.match()` (a group not\n// wrapped in an optional `(?:...)?`, so it always participates when the\n// overall match succeeds), or (3) `blockStack[blockStack.length - 1]!` /\n// `blockStack.pop()!`, both guarded immediately above by an explicit\n// `blockStack.length > 0` check. See src/parser.ts (PR #158) and this\n// subsystem's layout.ts audit (PR #149), which fixed the one genuinely\n// risky assertion in the subsystem (a Map get/set race) but didn't reach\n// this file — PR #146 separately reviewed this file's `as` casts, a\n// different concern from these `!`s. `noUncheckedIndexedAccess` can't see\n// any of these guarantees, but removing the `!` would only replace a\n// proven-safe assertion with an unreachable guard. Left as-is; no\n// behavior change.\nexport function parseSequenceDiagram(lines: Statement[]): SequenceDiagram {\n const diagram: SequenceDiagram = {\n actors: [],\n messages: [],\n blocks: [],\n notes: [],\n activations: [],\n boxes: [],\n }\n\n const boxCtx: BoxContext = { open: undefined, membership: new Map() }\n // Block-stack depth at the moment the open box began. Mermaid's grammar\n // only allows participant declarations inside a box, so `end` there can\n // only mean the box — but this parser is lenient about other statements,\n // and a `loop` opened inside a box must still own the next `end`.\n let boxOpenedAtDepth = 0\n // Line the currently-open box started on, for the unclosed-box error at\n // EOF below (issue #762) — `boxCtx.open` alone doesn't carry position.\n let boxOpenLine: number | undefined\n\n // Track actor IDs to auto-create actors referenced in messages\n const actorIds = new Set<string>()\n // Track block nesting with a stack\n const blockStack: Array<{\n type: Block['type']\n label: string\n startIndex: number\n dividers: Block['dividers']\n line: number\n }> = []\n\n // `autonumber` state — a bare `autonumber` turns numbering on starting at 1\n // (step 1); `autonumber <start> <step>` sets both explicitly; `autonumber\n // off` turns it back off. Only messages consume a number — notes and\n // block/divider lines don't advance the counter.\n const autonumber = { enabled: false, next: 1, step: 1 }\n\n // `create participant X` / `destroy X` bind to the very next message —\n // Mermaid's sequenceDb enforces that the next message's recipient is the\n // created participant, and that the destroyed one is its sender or\n // recipient, throwing otherwise. Mirrored here (same error text) rather\n // than guessing which later message was meant. A directive with no\n // message after it at all is left as a plain declaration.\n let pendingCreate: string | undefined\n let pendingDestroy: string | undefined\n\n for (let i = 1; i < lines.length; i++) {\n const stmt = lines[i]!\n const line = stmt.text\n\n // --- box <color?> <label?> ---\n // Opens a participant group; closed by `end`. Boxes cannot nest\n // (Mermaid's grammar rejects it), so a second `box` while one is open\n // is an error rather than an implicit close.\n const boxMatch = line.match(/^box(?:\\s+(.*))?$/)\n if (boxMatch) {\n if (boxCtx.open !== undefined) {\n throw new Error(\n `Line ${stmt.line}: Sequence diagram: a box cannot be nested inside another box — close the open box with \"end\" first`,\n )\n }\n const { color, label } = parseBoxHeader(boxMatch[1] ?? '')\n const box: SequenceDiagram['boxes'][number] = {\n label: normalizeBrTags(label),\n actorIds: [],\n }\n if (color !== undefined) box.color = color\n diagram.boxes.push(box)\n boxCtx.open = diagram.boxes.length - 1\n boxOpenedAtDepth = blockStack.length\n boxOpenLine = stmt.line\n continue\n }\n\n // --- create participant / create actor ---\n // \"create participant C\" / \"create actor C as Label\" — the line before\n // C's first message. Same shape as a plain declaration, so the alias and\n // the participant/actor kind are kept, not dropped with the keyword.\n const createMatch = line.match(\n /^create\\s+(participant|actor)\\s+(\\S+?)(?:\\s+as\\s+(.+))?$/,\n )\n if (createMatch) {\n const type: 'participant' | 'actor' =\n createMatch[1] === 'actor' ? 'actor' : 'participant'\n const id = createMatch[2]!\n if (actorIds.has(id)) {\n // Mermaid's own wording (sequenceDb `createParticipant`).\n throw new Error(\n `Line ${stmt.line}: It is not possible to have actors with the same id, even if one is destroyed before the next is created. Use 'AS' aliases to simulate the behavior`,\n )\n }\n actorIds.add(id)\n diagram.actors.push({\n id,\n label: normalizeBrTags(createMatch[3]?.trim() ?? id),\n type,\n })\n joinOpenBox(diagram, boxCtx, id, stmt.line)\n pendingCreate = id\n continue\n }\n\n // --- destroy ---\n // \"destroy C\" — the line before C's last message.\n const destroyMatch = line.match(/^destroy\\s+(.+)$/)\n if (destroyMatch) {\n const id = destroyMatch[1]!.trim()\n ensureActor(diagram, actorIds, boxCtx, id, stmt.line)\n pendingDestroy = id\n continue\n }\n\n // --- autonumber directive ---\n const autonumberMatch = line.match(\n /^autonumber(?:\\s+(off|\\d+(?:\\.\\d{1,2})?)(?:\\s+(\\d+(?:\\.\\d{1,2})?))?)?$/,\n )\n if (autonumberMatch) {\n const start = autonumberMatch[1]\n const step = autonumberMatch[2]\n if (start === 'off') {\n autonumber.enabled = false\n } else {\n autonumber.enabled = true\n autonumber.next = start !== undefined ? Number(start) : 1\n autonumber.step = step !== undefined ? Number(step) : 1\n }\n continue\n }\n\n // --- Participant / Actor declaration ---\n // \"participant A as Alice\" or \"participant Alice\"\n // \"actor B as Bob\" or \"actor Bob\"\n const actorMatch = line.match(\n /^(participant|actor)\\s+(\\S+?)(?:\\s+as\\s+(.+))?$/,\n )\n if (actorMatch) {\n // Group 1 is constrained by the regex alternation to 'participant' | 'actor'\n const type: 'participant' | 'actor' =\n actorMatch[1] === 'actor' ? 'actor' : 'participant'\n const id = actorMatch[2]!\n const rawLabel = actorMatch[3]?.trim() ?? id\n const label = normalizeBrTags(rawLabel)\n if (!actorIds.has(id)) {\n actorIds.add(id)\n diagram.actors.push({ id, label, type })\n }\n // A re-declaration inside a box joins it (or errors if it already\n // belongs to a different one) — Mermaid's `addActor` rule.\n joinOpenBox(diagram, boxCtx, id, stmt.line)\n continue\n }\n\n // --- Note ---\n // \"Note left of A: text\" / \"Note right of A: text\" / \"Note over A,B: text\"\n const noteMatch = line.match(\n /^Note\\s+(left of|right of|over)\\s+([^:]+):\\s*(.+)$/i,\n )\n if (noteMatch) {\n const posStr = noteMatch[1]!.toLowerCase()\n const actorsStr = noteMatch[2]!.trim()\n const text = normalizeBrTags(noteMatch[3]!.trim())\n const noteActorIds = actorsStr.split(',').map((s) => s.trim())\n\n // Ensure actors exist\n for (const aid of noteActorIds) {\n ensureActor(diagram, actorIds, boxCtx, aid, stmt.line)\n }\n\n let position: 'left' | 'right' | 'over' = 'over'\n if (posStr === 'left of') position = 'left'\n else if (posStr === 'right of') position = 'right'\n\n diagram.notes.push({\n actorIds: noteActorIds,\n text,\n position,\n afterIndex: diagram.messages.length - 1,\n })\n continue\n }\n\n // --- Block start: loop, alt, opt, par, critical, break, rect ---\n const blockMatch = line.match(\n /^(loop|alt|opt|par|critical|break|rect)\\s*(.*)$/,\n )\n if (blockMatch) {\n const blockType = toBlockType(blockMatch[1]!)\n const rawBlockLabel = blockMatch[2]?.trim() ?? ''\n const label = normalizeBrTags(rawBlockLabel)\n blockStack.push({\n type: blockType,\n label,\n startIndex: diagram.messages.length,\n dividers: [],\n line: stmt.line,\n })\n continue\n }\n\n // --- Block divider: else, and ---\n const dividerMatch = line.match(/^(else|and)\\s*(.*)$/)\n if (dividerMatch && blockStack.length > 0) {\n const rawDividerLabel = dividerMatch[2]?.trim() ?? ''\n const label = normalizeBrTags(rawDividerLabel)\n blockStack[blockStack.length - 1]!.dividers.push({\n index: diagram.messages.length,\n label,\n })\n continue\n }\n\n // --- Box end ---\n // `end` closes the open box unless a block was opened *inside* it,\n // in which case the block (innermost) owns this `end`.\n if (\n line === 'end' &&\n boxCtx.open !== undefined &&\n blockStack.length === boxOpenedAtDepth\n ) {\n boxCtx.open = undefined\n boxOpenLine = undefined\n continue\n }\n\n // --- Block end ---\n if (line === 'end' && blockStack.length > 0) {\n const completed = blockStack.pop()!\n diagram.blocks.push({\n type: completed.type,\n label: completed.label,\n startIndex: completed.startIndex,\n endIndex: Math.max(diagram.messages.length - 1, completed.startIndex),\n dividers: completed.dividers,\n })\n continue\n }\n\n // --- Unmatched end ---\n // Neither branch above claimed this \"end\" — no box and no block is\n // currently open, so there's nothing for it to close. Previously\n // silently ignored (#762); mirrors the \"box cannot be nested\" throw\n // above in surfacing a structural mistake instead of dropping it.\n if (line === 'end') {\n throw new Error(\n `Line ${stmt.line}: Sequence diagram: \"end\" does not match any open block (\"loop\"/\"alt\"/\"opt\"/\"par\"/\"critical\"/\"break\"/\"rect\") or \"box\" — nothing is currently open to close.`,\n )\n }\n\n // --- Standalone activate / deactivate ---\n // \"activate A\" / \"deactivate A\". Mermaid's own grammar expands the `+`/`-`\n // arrow shorthand into exactly this (message, then an activeStart for the\n // recipient / activeEnd for the sender), so record the same event the\n // shorthand implies and let layout.ts feed both through one activation\n // stack. Checked before the message regex: neither keyword contains an\n // arrow token, but an actor literally named `activate` used as a message\n // *source* (`activate->>B: x`) still reaches the message branch, since\n // the `\\s+` here requires whitespace after the keyword.\n const activationMatch = line.match(/^(activate|deactivate)\\s+(.+)$/)\n if (activationMatch) {\n const actorId = activationMatch[2]!.trim()\n ensureActor(diagram, actorIds, boxCtx, actorId, stmt.line)\n diagram.activations.push({\n actorId,\n kind: activationMatch[1] === 'activate' ? 'start' : 'end',\n afterIndex: diagram.messages.length - 1,\n })\n continue\n }\n\n // --- Message ---\n // Patterns: A->>B, A-->>B, A-)B, A--)B, A<<->>B, A<<-->>B, with optional\n // +/- activation. Format: FROM ARROW TO: LABEL\n //\n // FROM/TO are matched with a lazy `.+?` (not `\\S+?`) so an undeclared\n // actor name can contain spaces or internal hyphens — e.g. `cron\n // job->>customer-notifier: hi` — mirroring real Mermaid's own sequence\n // grammar, whose unquoted ACTOR token excludes only the characters that\n // start an arrow/label (`/ \\ + ( ) < - > :`) rather than all whitespace.\n // Because the quantifier is lazy and anchored by the arrow/colon tokens\n // that follow, this still resolves to the same minimal split as before\n // for plain single-word names.\n //\n // Two-pass match (issue #341): the lazy FROM capture stops as soon as\n // *any* position looks like a valid arrow+TO+`:`+LABEL tail, which is a\n // false positive when an unquoted actor name happens to contain a short\n // open/cross arrow substring (`-)`, `--)`, `-x`, `--x` — matched by the\n // `--?[)x]` alternative) ahead of the *real* arrow later in the line —\n // e.g. `foo-x-bar->>baz: hi` mis-split at the embedded `-x` instead of\n // the real `->>`. Those two-char forms are rare and highly ambiguous\n // inside a bare identifier, whereas the \"long\" forms (anything ending\n // in `>`, plus the bidirectional tokens) essentially never occur by\n // accident, since they require a literal `>` character in an unquoted\n // name. So: try the long forms only first, and only fall back to the\n // full alternation (including the short forms) if the line has no long\n // arrow at all — which is what keeps a genuinely short-arrow message\n // like `A-)B: msg` working. This doesn't attempt to disambiguate every\n // theoretically possible collision (e.g. a literal `->` substring\n // embedded before a real `->>`) — see the issue's own \"Scope\" section,\n // which limits the fix to the `-x`/`-)`/`--x`/`--)` substrings.\n const msgMatch =\n line.match(MESSAGE_LONG_ARROW_RE) ?? line.match(MESSAGE_ANY_ARROW_RE)\n if (msgMatch) {\n // Mermaid's own unquoted actor-name grammar excludes `>` and `)` from\n // ever starting an identifier (see the FROM/TO comment above) — so a\n // TO capture starting with either one means the real arrow in the\n // source is longer than what the alternation above actually matched\n // (e.g. \"->>>\" only matches as \"->>\", leaving a stray \">\" to be\n // absorbed into TO as part of a garbled actor name) rather than a\n // genuinely valid, if unusual, message. Reject it with an actionable\n // error instead of silently minting that garbled actor — see the\n // `Alice->>>Bob: Hello` example in issue #762.\n const arrowToken = msgMatch[2]!\n const toFirstChar = msgMatch[4]![0]\n if (toFirstChar === '>' || toFirstChar === ')') {\n throw new Error(\n `Line ${stmt.line}: Malformed sequence-diagram arrow in \"${line}\" — \"${arrowToken}${toFirstChar}\" is not a recognized arrow. Expected one of: ->, -->, ->>, -->>, -x, --x, -), --), <<->>, <<-->>.`,\n )\n }\n pushMessage(\n diagram,\n actorIds,\n boxCtx,\n autonumber,\n msgMatch[1]!,\n msgMatch[2]!,\n msgMatch[3],\n msgMatch[4]!,\n msgMatch[5]!,\n stmt.line,\n )\n const msgIndex = diagram.messages.length - 1\n const msg = diagram.messages[msgIndex]!\n if (pendingCreate !== undefined) {\n if (msg.to !== pendingCreate) {\n throw new Error(\n `Line ${stmt.line}: The created participant ${pendingCreate} does not have an associated creating message after its declaration. Please check the sequence diagram.`,\n )\n }\n findActor(diagram, pendingCreate).createdAt = msgIndex\n pendingCreate = undefined\n }\n if (pendingDestroy !== undefined) {\n if (msg.from !== pendingDestroy && msg.to !== pendingDestroy) {\n throw new Error(\n `Line ${stmt.line}: The destroyed participant ${pendingDestroy} does not have an associated destroying message after its declaration. Please check the sequence diagram.`,\n )\n }\n findActor(diagram, pendingDestroy).destroyedAt = msgIndex\n pendingDestroy = undefined\n }\n continue\n }\n }\n\n // A block/box left open at EOF is a structural mistake, not something to\n // silently accept as \"closed by end of input\" — mirrors the class-diagram\n // parser's unclosed-body check (#761) for the same reason: previously\n // this was silently ignored (#762). Checked innermost-first (the block\n // stack) since an unclosed block is the more specific, more actionable\n // thing to report when both are open.\n if (blockStack.length > 0) {\n const unclosed = blockStack[blockStack.length - 1]!\n throw new Error(\n `Line ${unclosed.line}: Sequence diagram: unclosed \"${unclosed.type}\" block — expected a matching \"end\" before the diagram ends.`,\n )\n }\n if (boxCtx.open !== undefined) {\n const openBox = diagram.boxes[boxCtx.open]!\n throw new Error(\n `Line ${boxOpenLine}: Sequence diagram: unclosed \"box${openBox.label ? ` ${openBox.label}` : ''}\" — expected a matching \"end\" before the diagram ends.`,\n )\n }\n\n return diagram\n}\n\n/**\n * Look up an actor the parser itself already registered (every caller\n * passes an id that went through `ensureActor` or the create branch first,\n * so the miss branch is a parser-invariant violation, not user error).\n */\nfunction findActor(diagram: SequenceDiagram, id: string): Actor {\n const actor = diagram.actors.find((a) => a.id === id)\n if (actor === undefined) {\n /* v8 ignore next */\n throw new Error(`Sequence diagram: unknown actor \"${id}\"`)\n }\n return actor\n}\n\n/** Ensure an actor exists, creating a default participant if not */\nfunction ensureActor(\n diagram: SequenceDiagram,\n actorIds: Set<string>,\n boxCtx: BoxContext,\n id: string,\n lineNumber: number,\n): void {\n if (!actorIds.has(id)) {\n actorIds.add(id)\n diagram.actors.push({ id, label: id, type: 'participant' })\n }\n joinOpenBox(diagram, boxCtx, id, lineNumber)\n}\n\n/**\n * Put `id` into the currently open `box`, if any. A participant already in\n * a *different* box is an error (Mermaid's own wording); one already in\n * this box, or no box open, is a no-op.\n */\nfunction joinOpenBox(\n diagram: SequenceDiagram,\n boxCtx: BoxContext,\n id: string,\n lineNumber: number,\n): void {\n const open = boxCtx.open\n if (open === undefined) return\n const existing = boxCtx.membership.get(id)\n if (existing === open) return\n if (existing !== undefined) {\n const from = diagram.boxes[existing]!.label\n const to = diagram.boxes[open]!.label\n throw new Error(\n `Line ${lineNumber}: A same participant should only be defined in one Box: ${id} can't be in '${from}' and in '${to}' at the same time.`,\n )\n }\n boxCtx.membership.set(id, open)\n diagram.boxes[open]!.actorIds.push(id)\n}\n\n/**\n * Build a `Message` from a matched arrow-message line and push it onto the\n * diagram. Shared by both message regexes in `parseSequenceDiagram` above so\n * the arrow → line-style/arrow-head/bidirectional mapping and `autonumber`\n * bookkeeping can't drift out of sync between them.\n */\nfunction pushMessage(\n diagram: SequenceDiagram,\n actorIds: Set<string>,\n boxCtx: BoxContext,\n autonumber: { enabled: boolean; next: number; step: number },\n from: string,\n arrow: string,\n activationMark: string | undefined,\n to: string,\n rawLabel: string,\n lineNumber: number,\n): void {\n ensureActor(diagram, actorIds, boxCtx, from, lineNumber)\n ensureActor(diagram, actorIds, boxCtx, to, lineNumber)\n\n const bidirectional = arrow === '<<->>' || arrow === '<<-->>'\n const lineStyle = bidirectional\n ? arrow === '<<-->>'\n ? 'dashed'\n : 'solid'\n : arrow.startsWith('--')\n ? 'dashed'\n : 'solid'\n // \">>\" = filled arrow, \")\" or \">\" alone = open arrow, \"x\" = cross (treat as\n // filled). Both bidirectional tokens end in \">>\", so they fall out as filled.\n const arrowHead =\n arrow.includes('>>') || arrow.includes('x') ? 'filled' : 'open'\n // \"x\"/\"--x\" is Mermaid's \"lost message\" terminator — a cross, not a plain\n // filled arrowhead. `arrow.includes('x')` is unambiguous here: the only\n // arrow tokens containing \"x\" are \"-x\"/\"--x\" (see the message regex's\n // `--?[)x]` alternative above), never \"->>\"/\"-->>\" or the bidirectional\n // forms.\n const isLost = arrow.includes('x')\n\n const msg: Message = {\n from,\n to,\n label: normalizeBrTags(rawLabel.trim()),\n lineStyle,\n arrowHead,\n }\n if (isLost) msg.isLost = true\n if (bidirectional) msg.bidirectional = true\n if (activationMark === '+') msg.activate = true\n if (activationMark === '-') msg.deactivate = true\n\n if (autonumber.enabled) {\n msg.seqNumber = autonumber.next\n autonumber.next =\n Math.round((autonumber.next + autonumber.step) * 100) / 100\n }\n\n diagram.messages.push(msg)\n}\n","// ============================================================================\n// Sequence diagram — activation/deactivation balance check\n//\n// A mechanical, deterministic semantic check: every `activate X` (or the\n// `+` arrow shorthand) must be closed by a matching `deactivate X` (or the\n// `-` shorthand) before the diagram ends. This doesn't need an LLM judge —\n// it's the same activation-stack bookkeeping\n// packages/svg-renderer/src/sequence/layout.ts already runs to *draw*\n// activation bars (see its `activationStacks` map), reused here to *report*\n// imbalance instead of silently rendering around it.\n//\n// Motivation (issue #539, split from #536): research on LLM-generated\n// Mermaid sequence diagrams found they fail mostly on activation handling\n// and error/status tracking, not basic syntax — which existing validators\n// (syntax checkers, and agentic-mermaid's structural/geometric/lint\n// `verify` tool: dangling edges, label overflow, duplicate/unreachable\n// nodes) already cover well. Activation balance is a concrete, narrow,\n// mechanically-checkable instance of that gap.\n// ============================================================================\n\nimport type { SequenceDiagram, Message } from './types.ts'\n\nexport type ActivationIssueCode =\n 'DANGLING_ACTIVATION' | 'UNMATCHED_DEACTIVATION'\n\nexport interface ActivationIssue {\n code: ActivationIssueCode\n /** Actor whose activation is unbalanced. */\n actorId: string\n /** Human-readable explanation, including a source-position hint. */\n message: string\n}\n\nexport interface ActivationCheckResult {\n /** true when every activation is balanced (no issues found). */\n ok: boolean\n issues: ActivationIssue[]\n}\n\n/** One `activate`/`deactivate` event in source order, whichever form wrote it. */\ninterface Event {\n actorId: string\n kind: 'start' | 'end'\n afterIndex: number\n}\n\n/**\n * Describe where an event occurred, for a human-readable issue message.\n * Mirrors the `afterIndex` semantics of `SequenceDiagram.activations` (see\n * types.ts): -1 means \"before the first message\", otherwise the index of\n * the message this event follows.\n */\nfunction describePosition(\n diagram: SequenceDiagram,\n afterIndex: number,\n): string {\n if (afterIndex < 0) return 'before the first message'\n const msg: Message | undefined = diagram.messages[afterIndex]\n if (!msg) return 'at an unresolved position'\n const label = msg.label ? `: ${msg.label}` : ''\n return `after message ${afterIndex + 1} (\"${msg.from} -> ${msg.to}${label}\")`\n}\n\n/**\n * Check a parsed sequence diagram for activation/deactivation imbalance.\n *\n * Merges the inline `+`/`-` arrow shorthand (`Message.activate` /\n * `Message.deactivate`) with standalone `activate X` / `deactivate X`\n * statements (`SequenceDiagram.activations`) into one chronological event\n * stream per actor — the same merge `layout.ts` performs to position\n * activation bars — then walks a stack per actor:\n *\n * - A `deactivate` with nothing open on that actor's stack is reported as\n * `UNMATCHED_DEACTIVATION` (the renderer silently ignores this case —\n * see `endActivation` in layout.ts — this check surfaces it instead).\n * - Anything left on a stack once every message has been processed is\n * reported as `DANGLING_ACTIVATION` (the renderer draws these extending\n * to the bottom of the diagram, which usually isn't what the author\n * intended).\n */\nexport function checkActivationBalance(\n diagram: SequenceDiagram,\n): ActivationCheckResult {\n // Standalone activations, grouped by the message index they follow — same\n // grouping layout.ts builds for its `activationEventsByAfterIndex`.\n const standaloneByAfterIndex = new Map<number, typeof diagram.activations>()\n for (const event of diagram.activations) {\n const list = standaloneByAfterIndex.get(event.afterIndex) ?? []\n list.push(event)\n standaloneByAfterIndex.set(event.afterIndex, list)\n }\n\n const events: Event[] = []\n function pushStandalone(afterIndex: number): void {\n for (const event of standaloneByAfterIndex.get(afterIndex) ?? []) {\n events.push({ actorId: event.actorId, kind: event.kind, afterIndex })\n }\n }\n\n // Events before the first message open first, same as layout.ts.\n pushStandalone(-1)\n for (let i = 0; i < diagram.messages.length; i++) {\n const msg = diagram.messages[i]!\n if (msg.activate) {\n events.push({ actorId: msg.to, kind: 'start', afterIndex: i })\n }\n if (msg.deactivate) {\n events.push({ actorId: msg.from, kind: 'end', afterIndex: i })\n }\n pushStandalone(i)\n }\n\n // actorId -> stack of afterIndex values where an activation was opened.\n const stacks = new Map<string, number[]>()\n const issues: ActivationIssue[] = []\n\n for (const event of events) {\n const stack = stacks.get(event.actorId) ?? []\n stacks.set(event.actorId, stack)\n if (event.kind === 'start') {\n stack.push(event.afterIndex)\n continue\n }\n const openedAt = stack.pop()\n if (openedAt === undefined) {\n issues.push({\n code: 'UNMATCHED_DEACTIVATION',\n actorId: event.actorId,\n message:\n `\"deactivate ${event.actorId}\" ${describePosition(diagram, event.afterIndex)} ` +\n `has no matching \"activate ${event.actorId}\" open at that point.`,\n })\n }\n }\n\n for (const [actorId, stack] of stacks) {\n for (const openedAt of stack) {\n issues.push({\n code: 'DANGLING_ACTIVATION',\n actorId,\n message:\n `\"activate ${actorId}\" opened ${describePosition(diagram, openedAt)} ` +\n `is never closed with a matching \"deactivate ${actorId}\".`,\n })\n }\n }\n\n return { ok: issues.length === 0, issues }\n}\n","// ============================================================================\n// Sequence diagram — activation/deactivation auto-fix\n//\n// Extends activation-check.ts (issue #539) with the \"optionally proposes a\n// fix\" half of that issue's scope. Deliberately narrow: only\n// `DANGLING_ACTIVATION` (an `activate X` never closed) is auto-fixable —\n// the mechanical, unambiguous repair is to append a matching `deactivate X`\n// after the diagram's last statement, which is always syntactically valid\n// regardless of block nesting (Mermaid's `deactivate` isn't block-scoped).\n//\n// `UNMATCHED_DEACTIVATION` (a `deactivate X` with nothing open) is\n// deliberately NOT auto-fixed: fixing it means either deleting that\n// specific statement or inserting a preceding `activate X`, and this\n// package doesn't track source line/position per activation statement (see\n// SequenceDiagram.activations — only a message-index `afterIndex`, no raw\n// line offset), so a text-level edit could not target the right occurrence\n// with confidence when the same statement text repeats. Rather than guess\n// and risk mangling unrelated source, this case is reported back as a\n// remaining (unfixable) issue for a human — or a fuzzier LLM-based layer,\n// per the issue's own caveat about unvalidated frontier-model behavior — to\n// resolve.\n// ============================================================================\n\nimport { detectDiagramType, splitStatements } from '@zombie-mermaid/core'\nimport { parseSequenceDiagram } from './parser.ts'\nimport {\n checkActivationBalance,\n type ActivationIssue,\n} from './activation-check.ts'\n\nexport interface ActivationFixResult {\n /** true when the returned diagram has no remaining activation issues. */\n ok: boolean\n /** Original diagram, unchanged if there was nothing fixable. */\n originalDiagram: string\n /**\n * Diagram with a \"deactivate X\" statement appended for every\n * DANGLING_ACTIVATION issue found. Identical to `originalDiagram` when\n * there was nothing to fix.\n */\n fixedDiagram: string\n /** Human-readable description of each fix actually applied. */\n fixesApplied: string[]\n /**\n * Issues that could not be auto-fixed (currently always\n * UNMATCHED_DEACTIVATION — see module header). Empty when `ok` is true.\n */\n remainingIssues: ActivationIssue[]\n}\n\n/**\n * Check a Mermaid sequence diagram for activation/deactivation imbalance\n * and, where mechanically safe, return a corrected version.\n *\n * Throws the same way `parseSequenceDiagram`/`detectDiagramType` do on\n * invalid or non-sequence input — callers (e.g. the MCP tool handler) are\n * expected to catch and translate, matching `check-sequence-activations.ts`'s\n * own error handling.\n */\nexport function fixActivationBalance(\n sourceDiagram: string,\n): ActivationFixResult {\n const diagramType = detectDiagramType(sourceDiagram)\n if (diagramType !== 'sequence') {\n throw new Error(\n 'fixActivationBalance only supports sequence diagrams (source must ' +\n `start with \"sequenceDiagram\"); detected diagram type: \"${diagramType}\".`,\n )\n }\n\n const lines = splitStatements(sourceDiagram)\n const diagram = parseSequenceDiagram(lines)\n const result = checkActivationBalance(diagram)\n\n if (result.ok) {\n return {\n ok: true,\n originalDiagram: sourceDiagram,\n fixedDiagram: sourceDiagram,\n fixesApplied: [],\n remainingIssues: [],\n }\n }\n\n const dangling = result.issues.filter(\n (issue) => issue.code === 'DANGLING_ACTIVATION',\n )\n const remainingIssues = result.issues.filter(\n (issue) => issue.code !== 'DANGLING_ACTIVATION',\n )\n\n const appended = dangling\n .map((issue) => ` deactivate ${issue.actorId}`)\n .join('\\n')\n const fixedDiagram =\n dangling.length === 0\n ? sourceDiagram\n : `${sourceDiagram.replace(/\\s+$/, '')}\\n${appended}`\n\n const fixesApplied = dangling.map(\n (issue) =>\n `Appended \"deactivate ${issue.actorId}\" to close: ${issue.message}`,\n )\n\n return {\n ok: remainingIssues.length === 0,\n originalDiagram: sourceDiagram,\n fixedDiagram,\n fixesApplied,\n remainingIssues,\n }\n}\n","import type { XYChart, XYAxis, XYChartSeries } from './types.ts'\nimport type { Statement } from '@zombie-mermaid/core'\n\n// ============================================================================\n// XY Chart parser\n//\n// Parses Mermaid xychart-beta syntax into a typed XYChart structure.\n//\n// Supported directives:\n// xychart-beta [horizontal]\n// title \"Chart Title\"\n// x-axis [label1, label2, ...] — categorical\n// x-axis min --> max — numeric range\n// x-axis \"Axis Title\" [label1, ...] — with title\n// x-axis \"Axis Title\" min --> max — with title\n// y-axis (same patterns)\n// bar [val1, val2, ...]\n// line [val1, val2, ...]\n// ============================================================================\n\n/**\n * Parse a Mermaid xychart-beta diagram from preprocessed lines.\n * Lines should already be trimmed and comment-stripped.\n */\nexport function parseXYChart(lines: Statement[]): XYChart {\n const xAxis: XYAxis = {}\n const yAxis: XYAxis = {}\n const series: XYChartSeries[] = []\n let title: string | undefined\n let horizontal = false\n\n for (const stmt of lines) {\n const line = stmt.text\n // Header line — detect horizontal\n if (/^xychart(-beta)?\\b/i.test(line)) {\n if (/\\bhorizontal\\b/i.test(line)) horizontal = true\n continue\n }\n\n // Title\n const titleMatch = line.match(/^title\\s+\"([^\"]+)\"/)\n if (titleMatch) {\n title = titleMatch[1]\n continue\n }\n\n // x-axis with categories: x-axis \"Title\" [a, b, c] or x-axis [a, b, c]\n const xCatMatch = line.match(/^x-axis\\s+(?:\"([^\"]*)\"\\s*)?\\[([^\\]]+)\\]/)\n if (xCatMatch) {\n if (xCatMatch[1]) xAxis.title = xCatMatch[1]\n xAxis.categories = splitCategoryList(xCatMatch[2]!).map((s) =>\n unquote(s.trim()),\n )\n continue\n }\n\n // x-axis with range: x-axis \"Title\" min --> max or x-axis min --> max\n const xRangeMatch = line.match(\n /^x-axis\\s+(?:\"([^\"]*)\"\\s+)?(-?\\d+(?:\\.\\d+)?)\\s*-->\\s*(-?\\d+(?:\\.\\d+)?)/,\n )\n if (xRangeMatch) {\n if (xRangeMatch[1]) xAxis.title = xRangeMatch[1]\n xAxis.range = {\n min: parseFloat(xRangeMatch[2]!),\n max: parseFloat(xRangeMatch[3]!),\n }\n continue\n }\n\n // y-axis with range: y-axis \"Title\" min --> max or y-axis min --> max\n const yRangeMatch = line.match(\n /^y-axis\\s+(?:\"([^\"]*)\"\\s+)?(-?\\d+(?:\\.\\d+)?)\\s*-->\\s*(-?\\d+(?:\\.\\d+)?)/,\n )\n if (yRangeMatch) {\n if (yRangeMatch[1]) yAxis.title = yRangeMatch[1]\n yAxis.range = {\n min: parseFloat(yRangeMatch[2]!),\n max: parseFloat(yRangeMatch[3]!),\n }\n continue\n }\n\n // y-axis with just title (no range)\n const yTitleOnly = line.match(/^y-axis\\s+\"([^\"]+)\"\\s*$/)\n if (yTitleOnly) {\n yAxis.title = yTitleOnly[1]\n continue\n }\n\n // bar [...]\n const barMatch = line.match(/^bar\\s+\\[([^\\]]+)\\]/)\n if (barMatch) {\n series.push({\n type: 'bar',\n data: parseNumericArray(barMatch[1]!, 'bar', line, stmt.line),\n })\n continue\n }\n\n // line [...]\n const lineMatch = line.match(/^line\\s+\\[([^\\]]+)\\]/)\n if (lineMatch) {\n series.push({\n type: 'line',\n data: parseNumericArray(lineMatch[1]!, 'line', line, stmt.line),\n })\n continue\n }\n\n // None of the recognized directive shapes matched. A line that doesn't\n // start with any known xychart-beta keyword at all falls through\n // silently below, same as before — but a line that *does* start with\n // one of the five keyworded directives (x-axis/y-axis/bar/line/title)\n // and still failed every pattern above is almost certainly an attempt\n // at that directive with broken syntax (an unclosed bracket, a missing\n // quote, a malformed range). Previously this was silently dropped —\n // the diagram would render with that axis/series just missing and no\n // indication why. Surface an actionable error instead. See issue #541\n // (parser error-message quality audit — this parser had zero throw\n // sites before this pass).\n const keywordMatch = line.match(/^(x-axis|y-axis|bar|line|title)\\b/i)\n if (keywordMatch) {\n throw malformedDirectiveError(\n keywordMatch[1]!.toLowerCase(),\n line,\n stmt.line,\n )\n }\n }\n\n // Auto-derive y-axis range from data if not specified\n if (!yAxis.range && series.length > 0) {\n const allValues = series.flatMap((s) => s.data)\n let min = Math.min(...allValues)\n let max = Math.max(...allValues)\n const span = max - min || 1\n // Add 10% padding\n min = min - span * 0.1\n max = max + span * 0.1\n // Floor to 0 if all values are positive and min is close to 0\n if (min > 0 && min < span * 0.5) min = 0\n yAxis.range = { min, max }\n }\n\n // Fallback y-axis range\n if (!yAxis.range) {\n yAxis.range = { min: 0, max: 100 }\n }\n\n return { title, horizontal, xAxis, yAxis, series }\n}\n\n/**\n * Split a categorical x-axis bracket's comma-separated contents into raw\n * item strings, treating a comma *inside* a double-quoted item as literal\n * text rather than a delimiter. A naive `.split(',')` would break\n * `[\"Jan, Feb\", \"Mar\"]` into three pieces instead of two — quoting a value\n * that itself contains a comma is exactly the case quoting exists for. Each\n * returned raw item is still trimmed and unquoted by the caller.\n */\nfunction splitCategoryList(str: string): string[] {\n const items: string[] = []\n let current = ''\n let inQuotes = false\n for (const char of str) {\n if (char === '\"') {\n inQuotes = !inQuotes\n current += char\n } else if (char === ',' && !inQuotes) {\n items.push(current)\n current = ''\n } else {\n current += char\n }\n }\n items.push(current)\n return items\n}\n\n/**\n * Strip one matching pair of leading/trailing double quotes from a category\n * item, if present — mirroring how an axis *title* capture group already\n * unquotes via its regex. `x-axis [A, B, C]` items are only split and\n * trimmed, so a quoted item like `\"CLI output / logs\"` previously kept its\n * literal quote characters in the rendered label. See issue #1087.\n */\nfunction unquote(value: string): string {\n if (value.length >= 2 && value.startsWith('\"') && value.endsWith('\"')) {\n return value.slice(1, -1)\n }\n return value\n}\n\n/**\n * Parse a `bar`/`line` series' comma-separated bracket contents into\n * numbers, throwing on any element that isn't a valid number instead of\n * silently coercing it to `NaN` (which serializes as `null` and can\n * propagate into layout math with no indication anything was wrong — see\n * issue #541's audit finding on this exact parser).\n */\nfunction parseNumericArray(\n str: string,\n seriesType: 'bar' | 'line',\n line: string,\n lineNumber: number,\n): number[] {\n return str.split(',').map((raw, index) => {\n const trimmed = raw.trim()\n const value = parseFloat(trimmed)\n if (trimmed.length === 0 || Number.isNaN(value)) {\n throw new Error(\n `Line ${lineNumber}: Invalid numeric value ${JSON.stringify(trimmed)} at position ${\n index + 1\n } in \"${line}\". Every value in a ${seriesType} [...] list must be a number.`,\n )\n }\n return value\n })\n}\n\n/** Expected-syntax hint per xychart-beta keyword, for `malformedDirectiveError`. */\nconst XYCHART_DIRECTIVE_HELP: Record<string, string> = {\n 'x-axis':\n 'x-axis [A, B, C] (categories) or x-axis 0 --> 100 (numeric range), either optionally preceded by a quoted title',\n 'y-axis':\n 'y-axis 0 --> 100 (numeric range) or y-axis \"Title\" (title only), optionally preceded by a quoted title before a range',\n bar: 'bar [10, 20, 30] — a comma-separated numeric array in square brackets',\n line: 'line [10, 20, 30] — a comma-separated numeric array in square brackets',\n title: 'title \"Chart Title\" — a double-quoted string',\n}\n\n/**\n * Build the error for a line that starts with a recognized xychart-beta\n * keyword (x-axis/y-axis/bar/line/title) but doesn't match that keyword's\n * expected syntax in any of the forms this parser supports.\n */\nfunction malformedDirectiveError(\n keyword: string,\n line: string,\n lineNumber: number,\n): Error {\n const help = XYCHART_DIRECTIVE_HELP[keyword] ?? keyword\n return new Error(\n `Line ${lineNumber}: Malformed xychart-beta \"${keyword}\" directive: \"${line}\". Expected: ${help}.`,\n )\n}\n","// ============================================================================\n// XY Chart — shared color palette\n//\n// Generates monochromatic shades from the theme accent color.\n// Series 0 = accent (or blue fallback). Series 1+ are darker/lighter\n// shades of the same hue with subtle hue drift to stay in the same\n// color family (like navy ↔ cyan from blue).\n//\n// Used by both the SVG and ASCII renderers.\n// ============================================================================\n\n/** Default accent for charts when the theme doesn't provide one. */\nexport const CHART_ACCENT_FALLBACK = '#3b82f6' // blue-500\n\n// ---------------------------------------------------------------------------\n// HSL ↔ Hex conversion\n// ---------------------------------------------------------------------------\n\nfunction hexToHsl(hex: string): [number, number, number] {\n const h = hex.replace('#', '')\n const ri = parseInt(h.substring(0, 2), 16) / 255\n const gi = parseInt(h.substring(2, 4), 16) / 255\n const bi = parseInt(h.substring(4, 6), 16) / 255\n\n const max = Math.max(ri, gi, bi)\n const min = Math.min(ri, gi, bi)\n const l = (max + min) / 2\n\n if (max === min) return [0, 0, l * 100]\n\n const d = max - min\n const s = l > 0.5 ? d / (2 - max - min) : d / (max + min)\n\n let hue: number\n if (max === ri) hue = ((gi - bi) / d + (gi < bi ? 6 : 0)) / 6\n else if (max === gi) hue = ((bi - ri) / d + 2) / 6\n else hue = ((ri - gi) / d + 4) / 6\n\n return [hue * 360, s * 100, l * 100]\n}\n\nfunction hslToHex(h: number, s: number, l: number): string {\n const si = s / 100\n const li = l / 100\n\n const c = (1 - Math.abs(2 * li - 1)) * si\n const x = c * (1 - Math.abs(((h / 60) % 2) - 1))\n const m = li - c / 2\n\n let r: number, g: number, b: number\n if (h < 60) {\n r = c\n g = x\n b = 0\n } else if (h < 120) {\n r = x\n g = c\n b = 0\n } else if (h < 180) {\n r = 0\n g = c\n b = x\n } else if (h < 240) {\n r = 0\n g = x\n b = c\n } else if (h < 300) {\n r = x\n g = 0\n b = c\n } else {\n r = c\n g = 0\n b = x\n }\n\n const toHex = (v: number) =>\n Math.round((v + m) * 255)\n .toString(16)\n .padStart(2, '0')\n return `#${toHex(r)}${toHex(g)}${toHex(b)}`\n}\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\n/** Check whether a string is a valid 6-digit hex color (e.g. \"#3b82f6\"). */\nexport function isValidHex(color: string): boolean {\n return /^#[0-9a-fA-F]{6}$/.test(color)\n}\n\n/**\n * Detect whether a background color is dark (lightness < 50%).\n */\nexport function isDarkBackground(bgHex: string): boolean {\n return hexToHsl(bgHex)[2] < 50\n}\n\n/**\n * Get the hex color for a series index.\n * Index 0 returns the accent color as-is.\n * Index 1+ alternate between darker and lighter shades of the same hue\n * with subtle hue drift (±8-12° per tier) to stay in the same family.\n *\n * When `bgColor` is provided, shade direction adapts to the background:\n * - Light bg: odd = darker, even = lighter (default)\n * - Dark bg: odd = lighter, even = darker (so shades stay visible)\n */\nexport function getSeriesColor(\n index: number,\n accentColor: string,\n bgColor?: string,\n): string {\n if (index === 0) return accentColor\n // Fall back to defaults when inputs aren't valid hex (e.g. CSS variable refs like \"var(--accent)\")\n const safeAccent = isValidHex(accentColor)\n ? accentColor\n : CHART_ACCENT_FALLBACK\n const safeBg = bgColor && isValidHex(bgColor) ? bgColor : undefined\n const [h, s] = hexToHsl(safeAccent)\n const chartS = Math.max(55, Math.min(85, s))\n\n const tier = Math.ceil(index / 2)\n const oddIndex = index % 2 === 1\n\n // On dark backgrounds, flip: odd = lighter, even = darker\n const dark = safeBg && isDarkBackground(safeBg) ? !oddIndex : oddIndex\n const l = dark ? Math.max(25, 48 - tier * 13) : Math.min(78, 55 + tier * 11)\n\n // Subtle hue drift: darker shades shift slightly negative, lighter shift positive\n const hShift = (dark ? -8 : 12) * tier\n const newH = (((h + hShift) % 360) + 360) % 360\n\n return hslToHex(newH, chartS, l)\n}\n","// ============================================================================\n// zombie-mermaid — expanded node syntax A@{ shape: ..., label: ... }\n//\n// Mermaid v11.3.0 added a metadata form for node definitions:\n//\n// A@{ shape: rounded, label: \"Start here\" }\n// B@{ shape: doc }\n// C@{ icon: \"fa:bell\", form: \"circle\", label: \"Alert\" }\n// D@{ img: \"https://example.com/a.png\", label: \"Diagram\", w: 120, h: 80 }\n//\n// It exposes ~30 semantic shape names (many with several aliases) that the\n// classic bracket syntax cannot express. This module owns both halves of\n// supporting it: parsing the metadata block, and resolving a semantic name to\n// the geometry this renderer draws.\n// ============================================================================\n\nimport type { NodeShape } from '@zombie-mermaid/core'\n\n/**\n * Every documented Mermaid shape name (and alias) mapped to the geometry this\n * renderer draws for it.\n *\n * Mermaid's list is deliberately semantic — `database`, `manual-input`,\n * `paper-tape` — while a renderer only has so many distinct outlines. Names\n * that share an outline map to the same `NodeShape`; that is a rendering\n * choice, not a parse failure, and it is documented in docs/diagrams.md so\n * the collapse is discoverable rather than surprising.\n *\n * Keys are lowercase; lookup lowercases its input.\n */\nconst SHAPE_ALIASES: Record<string, NodeShape> = {\n // --- Rectangle family ---\n rect: 'rectangle',\n rectangle: 'rectangle',\n proc: 'rectangle',\n process: 'rectangle',\n 'normal-rect': 'rectangle',\n\n // --- Rounded ---\n rounded: 'rounded',\n event: 'rounded',\n 'rounded-rect': 'rounded',\n\n // --- Stadium / terminal ---\n stadium: 'stadium',\n pill: 'stadium',\n terminal: 'stadium',\n\n // --- Subroutine / framed rectangle ---\n subproc: 'subroutine',\n subprocess: 'subroutine',\n subroutine: 'subroutine',\n 'framed-rectangle': 'subroutine',\n 'fr-rect': 'subroutine',\n\n // --- Cylinder / database ---\n cyl: 'cylinder',\n cylinder: 'cylinder',\n db: 'cylinder',\n database: 'cylinder',\n 'h-cyl': 'cylinder',\n das: 'cylinder',\n 'horizontal-cylinder': 'cylinder',\n 'lin-cyl': 'cylinder',\n 'lined-cylinder': 'cylinder',\n disk: 'cylinder',\n\n // --- Circle ---\n circ: 'circle',\n circle: 'circle',\n start: 'circle',\n\n // --- Double circle ---\n 'dbl-circ': 'doublecircle',\n 'double-circle': 'doublecircle',\n stop: 'doublecircle',\n\n // --- Filled / crossed circles ---\n 'f-circ': 'filled-circle',\n 'filled-circle': 'filled-circle',\n junction: 'filled-circle',\n 'cross-circ': 'crossed-circle',\n 'crossed-circle': 'crossed-circle',\n summary: 'crossed-circle',\n\n // --- Diamond / decision ---\n diam: 'diamond',\n diamond: 'diamond',\n decision: 'diamond',\n question: 'diamond',\n\n // --- Hexagon / prepare ---\n hex: 'hexagon',\n hexagon: 'hexagon',\n prepare: 'hexagon',\n\n // --- Asymmetric / odd ---\n odd: 'asymmetric',\n 'rect-left-inv-arrow': 'asymmetric',\n\n // --- Parallelograms ---\n 'lean-r': 'parallelogram',\n 'lean-right': 'parallelogram',\n 'in-out': 'parallelogram',\n 'lean-l': 'parallelogram-alt',\n 'lean-left': 'parallelogram-alt',\n 'out-in': 'parallelogram-alt',\n\n // --- Trapezoids ---\n 'trap-b': 'trapezoid',\n 'trapezoid-bottom': 'trapezoid',\n priority: 'trapezoid',\n 'trap-t': 'trapezoid-alt',\n 'trapezoid-top': 'trapezoid-alt',\n manual: 'trapezoid-alt',\n 'curv-trap': 'trapezoid-alt',\n 'curved-trapezoid': 'trapezoid-alt',\n display: 'trapezoid-alt',\n\n // --- Document family ---\n doc: 'document',\n document: 'document',\n 'lin-doc': 'document',\n 'lined-document': 'document',\n 'tag-doc': 'document',\n 'tagged-document': 'document',\n docs: 'stacked-document',\n documents: 'stacked-document',\n 'st-doc': 'stacked-document',\n 'stacked-document': 'stacked-document',\n\n // --- Card / notched rectangle ---\n 'notch-rect': 'card',\n card: 'card',\n 'notched-rectangle': 'card',\n\n // --- Lined / divided / tagged rectangles ---\n 'lin-rect': 'lined-process',\n 'lined-rectangle': 'lined-process',\n 'lin-proc': 'lined-process',\n 'shaded-process': 'lined-process',\n 'div-rect': 'divided-process',\n 'divided-rectangle': 'divided-process',\n 'div-proc': 'divided-process',\n 'tag-rect': 'rectangle',\n 'tagged-rectangle': 'rectangle',\n 'tag-proc': 'rectangle',\n procs: 'stacked-process',\n processes: 'stacked-process',\n 'st-rect': 'stacked-process',\n 'stacked-rectangle': 'stacked-process',\n\n // --- Triangles ---\n tri: 'triangle',\n triangle: 'triangle',\n extract: 'triangle',\n 'flip-tri': 'flipped-triangle',\n 'flipped-triangle': 'flipped-triangle',\n 'manual-file': 'flipped-triangle',\n\n // --- Window pane / internal storage ---\n 'win-pane': 'window-pane',\n 'window-pane': 'window-pane',\n 'internal-storage': 'window-pane',\n\n // --- Fork / join ---\n fork: 'fork-join',\n join: 'fork-join',\n 'long-rect': 'fork-join',\n\n // --- Notched pentagon / loop limit ---\n 'notch-pent': 'notched-pentagon',\n 'loop-limit': 'notched-pentagon',\n 'notched-pentagon': 'notched-pentagon',\n\n // --- Sloped rectangle / manual input ---\n 'sl-rect': 'sloped-rectangle',\n 'sloped-rectangle': 'sloped-rectangle',\n 'manual-input': 'sloped-rectangle',\n\n // --- Flag / paper tape ---\n flag: 'flag',\n 'paper-tape': 'flag',\n\n // --- Bow-tie rectangle / stored data ---\n 'bow-rect': 'bow-tie-rectangle',\n 'bow-tie-rectangle': 'bow-tie-rectangle',\n 'stored-data': 'bow-tie-rectangle',\n\n // --- Delay / half-rounded rectangle ---\n delay: 'half-rounded-rectangle',\n 'half-rounded-rectangle': 'half-rounded-rectangle',\n\n // --- Braces / comment ---\n brace: 'brace',\n 'brace-l': 'brace',\n comment: 'brace',\n 'brace-r': 'brace-right',\n braces: 'braces',\n\n // --- Lightning bolt / communication link ---\n bolt: 'bolt',\n 'com-link': 'bolt',\n 'lightning-bolt': 'bolt',\n\n // --- Bare text, no outline ---\n text: 'text',\n\n // --- Anchor / hidden ---\n anchor: 'anchor',\n}\n\n/**\n * Resolve a Mermaid shape name to the geometry this renderer draws.\n * Returns `undefined` for an unrecognized name so the caller can decide\n * whether to fall back or report it.\n */\nexport function resolveShapeName(name: string): NodeShape | undefined {\n return SHAPE_ALIASES[name.trim().toLowerCase()]\n}\n\n/** Every shape name this renderer accepts, for documentation and tests. */\nexport function knownShapeNames(): string[] {\n return Object.keys(SHAPE_ALIASES).sort()\n}\n\n/** The metadata a `@{ ... }` block can carry. */\nexport interface ExpandedNodeMeta {\n shape?: string\n label?: string\n icon?: string\n img?: string\n /** Outline drawn around an icon or image: `square`, `circle`, `rounded`. */\n form?: string\n /** Explicit width/height for an image node. */\n w?: string\n h?: string\n /** Image fit mode Mermaid accepts alongside `img`. */\n constraint?: string\n /** Any other key seen, preserved rather than dropped. */\n [key: string]: string | undefined\n}\n\n/**\n * Parse the body of a `@{ ... }` block into key/value pairs.\n *\n * The body is a comma-separated list of `key: value`. Values may be quoted\n * with `\"` or `'`, and a quoted value may contain commas, colons, and braces\n * — which is why this is a scanner rather than a `split(',')`.\n *\n * Mermaid also accepts a bare value with no key as shorthand for the shape\n * (`A@{ rounded }`); that is handled by the caller, which sees an entry with\n * an empty key.\n */\nexport function parseExpandedMeta(body: string): ExpandedNodeMeta {\n const meta: ExpandedNodeMeta = {}\n\n for (const entry of splitTopLevel(body, ',')) {\n const trimmed = entry.trim()\n if (trimmed.length === 0) continue\n\n const colon = indexOfTopLevel(trimmed, ':')\n if (colon === -1) {\n // Bare value — Mermaid's shorthand for `shape: <value>`.\n meta.shape ??= stripQuotes(trimmed)\n continue\n }\n\n const key = trimmed.slice(0, colon).trim().toLowerCase()\n const value = stripQuotes(trimmed.slice(colon + 1).trim())\n if (key.length > 0) meta[key] = value\n }\n\n return meta\n}\n\n/** Split on `separator`, ignoring separators inside quotes. */\nfunction splitTopLevel(text: string, separator: string): string[] {\n const parts: string[] = []\n let current = ''\n let quote: string | null = null\n\n for (const ch of text) {\n if (quote !== null) {\n current += ch\n if (ch === quote) quote = null\n continue\n }\n if (ch === '\"' || ch === \"'\") {\n quote = ch\n current += ch\n continue\n }\n if (ch === separator) {\n parts.push(current)\n current = ''\n continue\n }\n current += ch\n }\n\n parts.push(current)\n return parts\n}\n\n/** Index of the first `needle` outside quotes, or -1. */\nfunction indexOfTopLevel(text: string, needle: string): number {\n let quote: string | null = null\n for (let i = 0; i < text.length; i++) {\n const ch = text[i]!\n if (quote !== null) {\n if (ch === quote) quote = null\n continue\n }\n if (ch === '\"' || ch === \"'\") {\n quote = ch\n continue\n }\n if (ch === needle) return i\n }\n return -1\n}\n\n/** Remove one layer of matching wrapping quotes. */\nfunction stripQuotes(value: string): string {\n if (\n value.length >= 2 &&\n ((value.startsWith('\"') && value.endsWith('\"')) ||\n (value.startsWith(\"'\") && value.endsWith(\"'\")))\n ) {\n return value.slice(1, -1)\n }\n return value\n}\n\n/**\n * Find the `@{ ... }` block at the start of `text`, returning its body and\n * total length.\n *\n * Brace matching is depth-aware and quote-aware, so a label containing `}`\n * (`A@{ label: \"a } b\" }`) does not terminate the block early. Returns\n * `undefined` if `text` does not open with `@{` or the block is unterminated.\n */\nexport function matchExpandedBlock(\n text: string,\n): { body: string; length: number } | undefined {\n if (!text.startsWith('@{')) return undefined\n\n let depth = 0\n let quote: string | null = null\n\n for (let i = 1; i < text.length; i++) {\n const ch = text[i]!\n\n if (quote !== null) {\n if (ch === quote) quote = null\n continue\n }\n if (ch === '\"' || ch === \"'\") {\n quote = ch\n continue\n }\n if (ch === '{') {\n depth++\n continue\n }\n if (ch === '}') {\n depth--\n if (depth === 0) {\n return { body: text.slice(2, i), length: i + 1 }\n }\n }\n }\n\n return undefined\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../src/architecture/parser.ts","../src/architecture/to-graph.ts","../src/c4/parser.ts","../src/c4/format.ts","../src/class/parser.ts","../src/class/format.ts","../src/er/parser.ts","../src/sequence/box-color.ts","../src/sequence/parser.ts","../src/sequence/activation-check.ts","../src/sequence/activation-fix.ts","../src/xychart/parser.ts","../src/xychart/colors.ts","../src/expanded-shapes.ts"],"sourcesContent":["import { normalizeBrTags, type Statement } from '@zombie-mermaid/core'\nimport type {\n ArchitectureDiagram,\n ArchitectureEdge,\n ArchitecturePort,\n} from './types.ts'\n\n// ============================================================================\n// Architecture diagram parser\n//\n// Parses Mermaid's `architecture-beta` diagram\n// (https://mermaid.ai/open-source/syntax/architecture.html):\n//\n// architecture-beta\n// group api(cloud)[API]\n// service db(database)[Database] in api\n// service server(server)[Server] in api\n// junction j1 in api\n// db:L -- R:server\n// server{group}:B --> T:client{group}\n//\n// Grammar:\n// group <id>[(icon)][[Title]] [in <parent>]\n// service <id>[(icon)][[Title]] [in <parent>]\n// junction <id> [in <parent>]\n// <id>[{group}]:<L|R|T|B> <--|-->|<-->|--> <L|R|T|B>:<id>[{group}]\n// align row|column <id> <id> ... (accepted, no effect: see to-graph)\n//\n// Unlike Mermaid's own parser, every malformed statement throws with its line\n// number rather than being dropped.\n// ============================================================================\n\nconst ID = String.raw`[\\w-]+`\nconst DECL_RE = new RegExp(\n String.raw`^(group|service)\\s+(${ID})\\s*(?:\\(([^)]*)\\))?\\s*(?:\\[([^\\]]*)\\])?(?:\\s+in\\s+(${ID}))?$`,\n)\nconst JUNCTION_RE = new RegExp(\n String.raw`^junction\\s+(${ID})(?:\\s+in\\s+(${ID}))?$`,\n)\nconst EDGE_RE = new RegExp(\n String.raw`^(${ID})(\\{group\\})?:([LRTB])\\s+(<)?--(>)?\\s+([LRTB]):(${ID})(\\{group\\})?$`,\n)\nconst ALIGN_RE = new RegExp(\n String.raw`^align\\s+(?:row|column)\\s+${ID}(?:\\s+${ID})+$`,\n)\n\ntype Kind = 'group' | 'service' | 'junction'\n\nfunction fail(stmt: Statement, msg: string): never {\n throw new Error(\n `Architecture diagram, line ${stmt.line}: ${msg} — \"${stmt.text}\"`,\n )\n}\n\n/** Strip the optional quotes around a custom icon name like `\"logos:aws\"`. */\nfunction cleanIcon(raw: string | undefined): string | undefined {\n const icon = raw?.trim().replace(/^\"(.*)\"$/, '$1')\n return icon || undefined\n}\n\n/**\n * Parse a Mermaid architecture diagram.\n * Expects the first statement to be the `architecture-beta` header.\n */\nexport function parseArchitecture(lines: Statement[]): ArchitectureDiagram {\n const diagram: ArchitectureDiagram = {\n groups: [],\n services: [],\n junctions: [],\n edges: [],\n }\n const kinds = new Map<string, Kind>()\n // `in` may name a group declared later, so parents and edge endpoints are\n // resolved after the loop.\n const parentRefs: { stmt: Statement; parent: string }[] = []\n const pendingEdges: { stmt: Statement; edge: ArchitectureEdge }[] = []\n\n const declare = (\n stmt: Statement,\n id: string,\n kind: Kind,\n parent: string | undefined,\n ): void => {\n if (kinds.has(id)) fail(stmt, `duplicate id \"${id}\"`)\n if (parent === id) fail(stmt, `\"${id}\" cannot be inside itself`)\n kinds.set(id, kind)\n if (parent) parentRefs.push({ stmt, parent })\n }\n\n for (let i = 1; i < lines.length; i++) {\n const stmt = lines[i]!\n const text = stmt.text\n\n const decl = text.match(DECL_RE)\n if (decl) {\n const kind = decl[1] as 'group' | 'service'\n const id = decl[2]!\n const title = decl[4]\n const parent = decl[5]\n declare(stmt, id, kind, parent)\n const entry = {\n id,\n icon: cleanIcon(decl[3]),\n title: title !== undefined ? normalizeBrTags(title.trim()) : id,\n parent,\n }\n if (kind === 'group') diagram.groups.push(entry)\n else diagram.services.push(entry)\n continue\n }\n\n const junction = text.match(JUNCTION_RE)\n if (junction) {\n const id = junction[1]!\n const parent = junction[2]\n declare(stmt, id, 'junction', parent)\n diagram.junctions.push({ id, parent })\n continue\n }\n\n const edge = text.match(EDGE_RE)\n if (edge) {\n pendingEdges.push({\n stmt,\n edge: {\n source: edge[1]!,\n sourceGroup: edge[2] !== undefined,\n sourcePort: edge[3] as ArchitecturePort,\n arrowStart: edge[4] !== undefined,\n arrowEnd: edge[5] !== undefined,\n targetPort: edge[6] as ArchitecturePort,\n target: edge[7]!,\n targetGroup: edge[8] !== undefined,\n },\n })\n continue\n }\n\n // Alignment constraints (Mermaid v11.16+) have no flowchart equivalent.\n if (ALIGN_RE.test(text)) continue\n\n fail(stmt, 'unrecognized statement')\n }\n\n for (const { stmt, parent } of parentRefs) {\n if (kinds.get(parent) !== 'group') {\n fail(stmt, `\"in ${parent}\" must name a declared group`)\n }\n }\n\n const parentOf = new Map<string, string>()\n for (const g of diagram.groups) if (g.parent) parentOf.set(g.id, g.parent)\n for (const g of diagram.groups) {\n const seen = new Set([g.id])\n for (let p = parentOf.get(g.id); p; p = parentOf.get(p)) {\n if (seen.has(p)) {\n throw new Error(\n `Architecture diagram: group \"${g.id}\" is nested inside itself`,\n )\n }\n seen.add(p)\n }\n }\n\n for (const { stmt, edge } of pendingEdges) {\n const ends: [string, boolean][] = [\n [edge.source, edge.sourceGroup],\n [edge.target, edge.targetGroup],\n ]\n for (const [id, viaGroup] of ends) {\n const kind = kinds.get(id)\n if (!kind) fail(stmt, `unknown id \"${id}\"`)\n if (kind === 'group') {\n fail(stmt, `edges connect services or junctions, not the group \"${id}\"`)\n }\n const parent = [...diagram.services, ...diagram.junctions].find(\n (n) => n.id === id,\n )?.parent\n if (viaGroup && !parent) {\n fail(stmt, `\"{group}\" needs \"${id}\" to be declared inside a group`)\n }\n }\n diagram.edges.push(edge)\n }\n\n return diagram\n}\n","import type {\n Direction,\n MermaidEdge,\n MermaidGraph,\n MermaidNode,\n MermaidSubgraph,\n NodeShape,\n} from '@zombie-mermaid/core'\nimport type {\n ArchitectureDiagram,\n ArchitectureEdge,\n ArchitecturePort,\n} from './types.ts'\n\n// ============================================================================\n// Architecture -> flowchart lowering\n//\n// `architecture-beta` is boxes in nested boxes joined by edges, which the\n// flowchart pipeline already lays out and draws (ELK for SVG, the grid router\n// for ASCII). Like the other lowered types it becomes a `MermaidGraph`:\n//\n// group -> subgraph (nested via `in`)\n// service -> node; the icon picks a shape\n// junction -> small filled circle, no label\n// edge -> edge; `<`/`>` become arrowheads\n// `{group}` end -> the edge attaches to the service's enclosing group\n//\n// What is not carried over: Mermaid places services on a grid from the edge\n// ports, so `a:R -- L:b` means \"b is right of a\". Here ports only choose the\n// flow direction (majority axis) and which way each edge points in the\n// layout; exact placement is the flowchart layout's. Icons are not drawn\n// (only the database/disk shapes differ) and `align` is ignored.\n// ============================================================================\n\n/** Built-in Mermaid icons that have a distinct flowchart shape. */\nconst ICON_SHAPE: Record<string, NodeShape> = {\n database: 'cylinder',\n disk: 'cylinder',\n cloud: 'stadium',\n internet: 'circle',\n}\n\nconst HORIZONTAL: ReadonlySet<ArchitecturePort> = new Set(['L', 'R'])\n\n/** Flow direction: vertical only when more edges leave via T/B than L/R. */\nfunction pickDirection(edges: readonly ArchitectureEdge[]): Direction {\n let horizontal = 0\n let vertical = 0\n for (const e of edges) {\n for (const p of [e.sourcePort, e.targetPort]) {\n if (HORIZONTAL.has(p)) horizontal++\n else vertical++\n }\n }\n return vertical > horizontal ? 'TB' : 'LR'\n}\n\n/**\n * Whether the edge, as written, runs against the layout flow. `a:L -- R:b`\n * puts `a` to the right of `b`, so in a left-to-right layout the edge has to\n * be emitted reversed for `a` to land on the right.\n */\nfunction runsBackwards(e: ArchitectureEdge, direction: Direction): boolean {\n const horizontal = direction === 'LR' || direction === 'RL'\n const [back, forward]: ArchitecturePort[] = horizontal\n ? ['L', 'R']\n : ['T', 'B']\n // Source port on the far side of the flow, or target port on the near side.\n if (e.sourcePort === forward || e.targetPort === back) return false\n return e.sourcePort === back || e.targetPort === forward\n}\n\nexport function architectureToGraph(\n diagram: ArchitectureDiagram,\n): MermaidGraph {\n const nodes = new Map<string, MermaidNode>()\n const parentOf = new Map<string, string | undefined>()\n\n for (const s of diagram.services) {\n nodes.set(s.id, {\n id: s.id,\n label: s.title,\n shape: (s.icon && ICON_SHAPE[s.icon]) || 'rectangle',\n })\n parentOf.set(s.id, s.parent)\n }\n for (const j of diagram.junctions) {\n nodes.set(j.id, { id: j.id, label: '', shape: 'filled-circle' })\n parentOf.set(j.id, j.parent)\n }\n\n // Subgraphs, nested. Children are attached in declaration order.\n const subgraphs = new Map<string, MermaidSubgraph>()\n for (const g of diagram.groups) {\n subgraphs.set(g.id, {\n id: g.id,\n label: g.title,\n nodeIds: [],\n children: [],\n })\n }\n const roots: MermaidSubgraph[] = []\n for (const g of diagram.groups) {\n const sg = subgraphs.get(g.id)!\n const parent = g.parent ? subgraphs.get(g.parent) : undefined\n if (parent) parent.children.push(sg)\n else roots.push(sg)\n }\n for (const [id, parent] of parentOf) {\n subgraphs.get(parent ?? '')?.nodeIds.push(id)\n }\n\n const direction = pickDirection(diagram.edges)\n const edges: MermaidEdge[] = diagram.edges.map((e) => {\n const end = (id: string, viaGroup: boolean): string =>\n (viaGroup ? parentOf.get(id) : undefined) ?? id\n const forward = !runsBackwards(e, direction)\n const [source, target] = forward\n ? [end(e.source, e.sourceGroup), end(e.target, e.targetGroup)]\n : [end(e.target, e.targetGroup), end(e.source, e.sourceGroup)]\n return {\n source,\n target,\n style: 'solid',\n hasArrowStart: forward ? e.arrowStart : e.arrowEnd,\n hasArrowEnd: forward ? e.arrowEnd : e.arrowStart,\n }\n })\n\n return {\n direction,\n nodes,\n edges,\n subgraphs: roots,\n classDefs: new Map(),\n classAssignments: new Map(),\n nodeStyles: new Map(),\n linkStyles: new Map(),\n interactions: new Map(),\n }\n}\n","import { normalizeBrTags, type Statement } from '@zombie-mermaid/core'\nimport type {\n C4Boundary,\n C4Diagram,\n C4Element,\n C4ElementKind,\n C4LayoutHint,\n C4ElementShape,\n C4Relationship,\n C4Variant,\n} from './types.ts'\n\n// ============================================================================\n// C4 diagram parser\n//\n// Supported syntax (Mermaid C4 macros):\n// Person / Person_Ext (alias, label, descr)\n// System[Db|Queue][_Ext] (alias, label, descr)\n// Container[Db|Queue][_Ext] (alias, label, techn, descr)\n// Component[Db|Queue][_Ext] (alias, label, techn, descr)\n// Boundary / Enterprise_Boundary / System_Boundary / Container_Boundary\n// (alias, label[, type]) { ... }\n// Deployment_Node / Node / Node_L / Node_R (alias, label, type, descr) { ... }\n// Rel / BiRel / Rel_U|Up|D|Down|L|Left|R|Right|Back (from, to, label, techn)\n// RelIndex (index, from, to, label, techn)\n// title <text>\n//\n// `Rel_U/D/L/R` record a placement hint (`C4Relationship.layout`) the\n// renderers use to steer layout. Accepted and ignored (styling / layout\n// config this renderer does not honor): UpdateElementStyle, UpdateRelStyle, UpdateLayoutConfig, LAYOUT_*,\n// SHOW_LEGEND, SHOW_FLOATING_LEGEND, AddElementTag, AddRelTag,\n// AddBoundaryTag, RoleTag. Named arguments (`$tags=\"x\"`, `$link=...`) are\n// dropped.\n// ============================================================================\n\nconst HEADERS: Record<string, C4Variant> = {\n c4context: 'context',\n c4container: 'container',\n c4component: 'component',\n c4dynamic: 'dynamic',\n c4deployment: 'deployment',\n}\n\nconst IGNORED_MACRO =\n /^(?:UpdateElementStyle|UpdateRelStyle|UpdateLayoutConfig|LAYOUT_[A-Z_]+|SHOW_LEGEND|SHOW_FLOATING_LEGEND|AddElementTag|AddRelTag|AddBoundaryTag|RoleTag)\\b/\n\nconst ELEMENT_NAME = /^(Person|System|Container|Component)(Db|Queue)?(_Ext)?$/\nconst BOUNDARY_NAME =\n /^(?:Boundary|Enterprise_Boundary|System_Boundary|Container_Boundary)$/\nconst NODE_NAME = /^(?:Deployment_Node|Node|Node_L|Node_R)$/\nconst REL_NAME =\n /^(Rel|BiRel|Rel_U|Rel_Up|Rel_D|Rel_Down|Rel_L|Rel_Left|Rel_R|Rel_Right|Rel_Back|RelIndex)$/\n\nconst BOUNDARY_TYPES: Record<string, string> = {\n Enterprise_Boundary: 'ENTERPRISE',\n System_Boundary: 'SYSTEM',\n Container_Boundary: 'CONTAINER',\n}\n\nconst REL_HINTS: Record<string, C4LayoutHint> = {\n Rel_U: 'up',\n Rel_Up: 'up',\n Rel_D: 'down',\n Rel_Down: 'down',\n Rel_L: 'left',\n Rel_Left: 'left',\n Rel_R: 'right',\n Rel_Right: 'right',\n}\n\nfunction unquote(a: string): string {\n const t = a.trim()\n const inner =\n t.length >= 2 && t.startsWith('\"') && t.endsWith('\"') ? t.slice(1, -1) : t\n return normalizeBrTags(inner)\n}\n\n/**\n * Split a macro's argument list on top-level commas. Double-quoted strings\n * may contain commas and parentheses; `$name=value` named arguments are\n * dropped. Returns null when a quote is unbalanced.\n */\nfunction splitArgs(inner: string): string[] | null {\n const raw: string[] = []\n let cur = ''\n let inQuote = false\n for (const ch of inner) {\n if (ch === '\"') inQuote = !inQuote\n if (ch === ',' && !inQuote) {\n raw.push(cur)\n cur = ''\n } else {\n cur += ch\n }\n }\n if (inQuote) return null\n raw.push(cur)\n return raw\n .map((a) => a.trim())\n .filter((a) => !a.startsWith('$'))\n .map(unquote)\n}\n\n/** `Name(args)` optionally followed by ` {`. */\nconst MACRO = /^([A-Za-z_][A-Za-z0-9_]*)\\s*\\((.*)\\)\\s*(\\{)?\\s*$/\n\nfunction fail(stmt: Statement, msg: string): never {\n throw new Error(`C4 diagram, line ${stmt.line}: ${msg} — \"${stmt.text}\"`)\n}\n\n/**\n * Parse a Mermaid C4 diagram. Expects the first statement to be a\n * `C4Context` / `C4Container` / `C4Component` / `C4Dynamic` /\n * `C4Deployment` header.\n */\nexport function parseC4Diagram(lines: Statement[]): C4Diagram {\n const variant = HEADERS[lines[0]?.text.trim().toLowerCase() ?? '']\n if (!variant) {\n throw new Error(\n `C4 diagram: expected a header of C4Context, C4Container, C4Component, C4Dynamic or C4Deployment, got \"${lines[0]?.text ?? ''}\"`,\n )\n }\n\n const diagram: C4Diagram = {\n variant,\n elements: [],\n boundaries: [],\n relationships: [],\n }\n const aliases = new Set<string>()\n const stack: C4Boundary[] = []\n /** Boundary declared on the previous statement without an inline `{`. */\n let pendingBoundary: C4Boundary | undefined\n\n const claim = (stmt: Statement, alias: string | undefined): string => {\n if (!alias) fail(stmt, 'missing alias (first argument)')\n if (aliases.has(alias)) fail(stmt, `duplicate alias \"${alias}\"`)\n aliases.add(alias)\n return alias\n }\n\n for (let i = 1; i < lines.length; i++) {\n const stmt = lines[i]!\n const line = stmt.text\n\n if (line === '}') {\n if (stack.length === 0) fail(stmt, 'unmatched \"}\"')\n stack.pop()\n continue\n }\n // Brace on its own line, directly after a boundary macro.\n if (line === '{') {\n if (!pendingBoundary) fail(stmt, 'unexpected \"{\"')\n stack.push(pendingBoundary)\n pendingBoundary = undefined\n continue\n }\n pendingBoundary = undefined\n\n const titleMatch = line.match(/^title(?:\\s+(.*))?$/i)\n if (titleMatch) {\n diagram.title = normalizeBrTags((titleMatch[1] ?? '').trim())\n continue\n }\n\n if (IGNORED_MACRO.test(line)) continue\n\n const m = line.match(MACRO)\n if (!m) fail(stmt, 'unrecognized statement')\n const name = m[1]!\n const opensBlock = m[3] === '{'\n const args = splitArgs(m[2]!)\n if (!args) fail(stmt, 'unbalanced quotes')\n\n const el = name.match(ELEMENT_NAME)\n if (el) {\n if (opensBlock) fail(stmt, `\"${name}\" cannot contain a block`)\n const kind = el[1]!.toLowerCase() as C4ElementKind\n const shape: C4ElementShape =\n el[2] === 'Db' ? 'db' : el[2] === 'Queue' ? 'queue' : 'default'\n const hasTech = kind === 'container' || kind === 'component'\n const alias = claim(stmt, args[0])\n const element: C4Element = {\n alias,\n kind,\n shape,\n external: el[3] !== undefined,\n label: args[1] || alias,\n }\n const technology = hasTech ? args[2] : undefined\n const description = hasTech ? args[3] : args[2]\n if (technology) element.technology = technology\n if (description) element.description = description\n diagram.elements.push(element)\n stack.at(-1)?.elementAliases.push(alias)\n continue\n }\n\n if (BOUNDARY_NAME.test(name) || NODE_NAME.test(name)) {\n const isNode = NODE_NAME.test(name)\n const alias = claim(stmt, args[0])\n const boundary: C4Boundary = {\n alias,\n label: args[1] || alias,\n elementAliases: [],\n children: [],\n }\n // Mermaid labels every frame with a type: the macro's own for the\n // typed boundaries, else the explicit argument, else a default.\n // (System_/Container_/Enterprise_Boundary take no type arg.)\n const typed = BOUNDARY_TYPES[name]\n if (typed) boundary.type = typed\n else if (args[2] && (isNode || name === 'Boundary'))\n boundary.type = args[2]\n else boundary.type = isNode ? 'node' : 'system'\n if (isNode && args[3]) boundary.description = args[3]\n const parent = stack.at(-1)\n if (parent) parent.children.push(boundary)\n else diagram.boundaries.push(boundary)\n if (opensBlock) stack.push(boundary)\n else pendingBoundary = boundary\n continue\n }\n\n const rel = name.match(REL_NAME)\n if (rel) {\n const indexed = name === 'RelIndex'\n const a = indexed ? args.slice(1) : args\n if (!a[0] || !a[1]) {\n fail(stmt, 'a relationship needs \"from\" and \"to\" aliases')\n }\n const r: C4Relationship = {\n from: a[0],\n to: a[1],\n label: a[2] ?? '',\n bidirectional: name === 'BiRel',\n }\n if (a[3]) r.technology = a[3]\n if (name === 'Rel_Back') r.reversed = true\n const hint = REL_HINTS[name]\n if (hint) r.layout = hint\n diagram.relationships.push(r)\n continue\n }\n\n fail(stmt, `unknown C4 macro \"${name}\"`)\n }\n\n if (stack.length > 0) {\n throw new Error(\n `C4 diagram: boundary \"${stack.at(-1)!.alias}\" is missing its closing \"}\"`,\n )\n }\n // Mermaid ignores RelIndex's index argument: in C4Dynamic every relationship\n // is numbered by its position in the source, and no other variant numbers.\n if (diagram.variant === 'dynamic') {\n diagram.relationships.forEach((r, i) => {\n r.index = String(i + 1)\n })\n }\n for (const r of diagram.relationships) {\n for (const end of [r.from, r.to]) {\n if (!aliases.has(end)) {\n throw new Error(\n `C4 diagram: relationship refers to undeclared alias \"${end}\"`,\n )\n }\n }\n }\n return diagram\n}\n","import type { C4Boundary, C4Element, C4Relationship } from './types.ts'\n\n// ============================================================================\n// C4 text and layout-hint helpers shared by the SVG and ASCII renderers, so\n// both draw the same words and honor the same `Rel_U/D/L/R` hints.\n// ============================================================================\n\n/** Greedy word wrap; a word longer than `width` stays on its own line. */\nexport function wrapC4Text(text: string, width: number): string[] {\n const out: string[] = []\n for (const paragraph of text.split('\\n')) {\n let cur = ''\n for (const word of paragraph.split(/\\s+/).filter(Boolean)) {\n if (cur && cur.length + 1 + word.length > width) {\n out.push(cur)\n cur = word\n } else {\n cur = cur ? `${cur} ${word}` : word\n }\n }\n out.push(cur)\n }\n return out\n}\n\nconst STEREOTYPES: Record<C4Element['kind'], string> = {\n person: 'Person',\n system: 'Software System',\n container: 'Container',\n component: 'Component',\n}\n\n/**\n * The bracketed type line under an element's name, as Mermaid draws it:\n * `[Person]`, `[Software System]`, `[Container: PostgreSQL]`. External,\n * database and queue variants share their base kind's line; the fill and the\n * shape tell them apart.\n */\nexport function c4TypeLine(el: C4Element): string {\n const tech =\n (el.kind === 'container' || el.kind === 'component') && el.technology\n ? `: ${el.technology}`\n : ''\n return `[${STEREOTYPES[el.kind]}${tech}]`\n}\n\n/** A boundary's type line (`[ENTERPRISE]`, `[Ubuntu]`), when it has a type. */\nexport function c4BoundaryTypeLine(b: C4Boundary): string | undefined {\n return b.type ? `[${b.type}]` : undefined\n}\n\n/**\n * The text lines of a relationship label: the label itself (prefixed with its\n * sequence number in a C4Dynamic diagram), then the technology in brackets on its\n * own line. Empty when the relationship has neither.\n */\nexport function c4RelLabelLines(rel: C4Relationship): string[] {\n const lines: string[] = []\n const head = [rel.index ? `${rel.index}:` : '', rel.label]\n .filter(Boolean)\n .join(' ')\n if (head) lines.push(head)\n if (rel.technology) lines.push(`[${rel.technology}]`)\n return lines\n}\n\n/**\n * How a relationship constrains placement. `source` is meant to sit before\n * `target` along `axis`: above it for `'vertical'`, left of it for\n * `'horizontal'`. `Rel_U`/`Rel_L` reverse the pair (the target sits above /\n * left of the source); plain `Rel` is a vertical hint, since C4 diagrams flow\n * top to bottom by default.\n */\nexport interface C4Placement {\n source: string\n target: string\n axis: 'vertical' | 'horizontal'\n /** True when the hint came from an explicit `Rel_U/D/L/R`. */\n explicit: boolean\n}\n\nexport function c4Placement(rel: C4Relationship): C4Placement {\n switch (rel.layout) {\n case 'up':\n return {\n source: rel.to,\n target: rel.from,\n axis: 'vertical',\n explicit: true,\n }\n case 'left':\n return {\n source: rel.to,\n target: rel.from,\n axis: 'horizontal',\n explicit: true,\n }\n case 'right':\n return {\n source: rel.from,\n target: rel.to,\n axis: 'horizontal',\n explicit: true,\n }\n case 'down':\n return {\n source: rel.from,\n target: rel.to,\n axis: 'vertical',\n explicit: true,\n }\n default:\n return {\n source: rel.from,\n target: rel.to,\n axis: 'vertical',\n explicit: false,\n }\n }\n}\n","import type {\n ClassDiagram,\n ClassNode,\n ClassRelationship,\n ClassMember,\n RelationshipType,\n ClassNamespace,\n} from './types.ts'\nimport {\n normalizeBrTags,\n applyClickStatement,\n splitClassShorthand,\n tryApplyClassAssignment,\n tryApplyClassDef,\n tryApplyCssClass,\n tryApplyStyleStatement,\n type Statement,\n} from '@zombie-mermaid/core'\n\n// ============================================================================\n// Class diagram parser\n//\n// Parses Mermaid classDiagram syntax into a ClassDiagram structure.\n//\n// Supported syntax:\n// class Animal { +String name; +eat() void }\n// class Shape { <<abstract>> }\n// Animal <|-- Dog (inheritance)\n// Car *-- Engine (composition)\n// Car o-- Wheel (aggregation)\n// A --> B (association)\n// A ..> B (dependency)\n// A ..|> B (realization)\n// A \"1\" --> \"*\" B : label (with cardinality + label)\n// Animal : +String name (inline attribute)\n// namespace MyNamespace { class A { } }\n// click Animal \"https://example.com\" \"Tooltip\" _blank (see docs/decisions/no-script-interactivity.md)\n// classDef name fill:#f96 / style Animal fill:#f96 / cssClass \"A,B\" name\n// class Animal:::name (style-class shorthand, also on relationship ends)\n// note \"text\" / note for Animal \"line1\\nline2\"\n// ============================================================================\n\n/**\n * The set of relationship-arrow tokens `parseRelationship` recognizes,\n * shared with the \"does this line even look like a relationship attempt\"\n * check below so the two can't drift apart. Order matters for regex\n * alternation (longer/more-specific tokens like `--|>` must be tried before\n * the bare `--` fallback).\n */\nconst RELATIONSHIP_ARROW_SOURCE =\n '<\\\\|--|<\\\\|\\\\.\\\\.|\\\\*--|o--|-->|--\\\\*|--o|--\\\\|>|\\\\.\\\\.>|\\\\.\\\\.\\\\|>|<--|<\\\\.\\\\.?|--'\n\n/**\n * Matches a line that *contains* a relationship-arrow token somewhere, even\n * if the overall shape (FROM/TO, cardinalities) doesn't parse. Used to\n * scope the \"malformed relationship\" error (issue #761) to lines that\n * already look like an attempt at this construct — mirroring the same\n * \"attempt, then validate\" pattern #722 used for the ER and xychart-beta\n * parsers, so a genuinely unrelated line (no arrow at all) still falls\n * through silently rather than throwing on totally unrecognized syntax.\n */\nconst RELATIONSHIP_ARROW_MARKER = new RegExp(RELATIONSHIP_ARROW_SOURCE)\n\n/**\n * Parse a Mermaid class diagram.\n * Expects the first line to be \"classDiagram\".\n */\n// Audited for issue #100 (non-null assertions): every `!` in this file is\n// either a bounds-checked loop-index array access (`lines[i]!` inside a\n// `for (let i = 1; i < lines.length; ...)` loop) or a regex-mandatory-\n// capture-group access after `.match()` (a group that isn't wrapped in an\n// optional `(?:...)?`, so it always participates when the overall match\n// succeeds). Both are the same idioms already accepted as justified\n// elsewhere in this codebase (see src/parser.ts, PR #158, and this\n// subsystem's earlier layout.ts/renderer.ts audit, PR #147, which fixed\n// the genuinely risky assertions but didn't reach this file) —\n// `noUncheckedIndexedAccess` can't see either guarantee, but removing the\n// `!` would only replace a proven-safe assertion with an unreachable guard.\n// Left as-is; no behavior change.\nexport function parseClassDiagram(lines: Statement[]): ClassDiagram {\n const diagram: ClassDiagram = {\n classes: [],\n relationships: [],\n namespaces: [],\n interactions: new Map(),\n classDefs: new Map(),\n classAssignments: new Map(),\n nodeStyles: new Map(),\n notes: [],\n }\n\n // Track classes by ID for deduplication\n const classMap = new Map<string, ClassNode>()\n // Track namespace nesting\n let currentNamespace: ClassNamespace | null = null\n // Track class body parsing\n let currentClass: ClassNode | null = null\n let braceDepth = 0\n // Line the currently-open class body started on, for the unclosed-body\n // error below (issue #761) — `currentClass`/`braceDepth` alone don't\n // carry position.\n let openClassLine: number | undefined\n\n for (let i = 1; i < lines.length; i++) {\n const stmt = lines[i]!\n const line = stmt.text\n\n // --- Inside a class body block ---\n if (currentClass && braceDepth > 0) {\n if (line === '}') {\n braceDepth--\n if (braceDepth === 0) {\n currentClass = null\n openClassLine = undefined\n }\n continue\n }\n\n // Check for annotation like <<interface>>\n const annotMatch = line.match(/^<<(\\w+)>>$/)\n if (annotMatch) {\n currentClass.annotation = annotMatch[1]!\n continue\n }\n\n // A line that starts with `<<` but doesn't match the full\n // `<<name>>` shape is an attempted annotation with broken syntax\n // (e.g. a missing closing `>>`) rather than a member — surface that\n // instead of silently mis-parsing it as a member named \"<<foo\" (#761).\n if (line.startsWith('<<')) {\n throw new Error(\n `Line ${stmt.line}: Malformed class annotation \"${line}\" — expected \"<<name>>\" (e.g. \"<<interface>>\").`,\n )\n }\n\n // Parse member: visibility, name, type, optional parens for method\n addMember(currentClass, line, stmt.line)\n continue\n }\n\n // --- click interaction: `click ClassName \"url\" \"tooltip\" _blank` / `click ClassName call fn()` ---\n if (/^click\\s+/i.test(line)) {\n applyClickStatement(line, diagram.interactions)\n continue\n }\n\n // --- Notes: `note \"text\"` / `note for ClassName \"text\"` ---\n // Mermaid's grammar takes a quoted string (`noteText: STR`) and its\n // renderer splits the text on `\\n` after JSON-parsing it (svgDraw.js\n // drawNote), so a literal `\\n` in the source is a line break;\n // normalizeBrTags handles that and `<br/>`, the form the rest of this\n // repo's labels use. The class need not be declared first (or at all).\n const noteMatch = line.match(/^note\\s+(?:for\\s+(\\S+)\\s+)?\"([^\"]*)\"\\s*$/)\n if (noteMatch) {\n const forClass = noteMatch[1]\n diagram.notes.push({\n text: normalizeBrTags(noteMatch[2]!),\n ...(forClass ? { forClass } : {}),\n })\n continue\n }\n\n // --- Styling: `classDef`, `style`, `cssClass \"A,B\" name`, `class A,B name` ---\n // Shared with the flowchart parser (packages/core/src/style-directives.ts). The\n // `class A,B name` assignment form is checked before the declaration\n // regexes below: a declaration never has a second bare-word token after\n // the class id (`class Animal`, `class Animal~T~`, `class Animal {`), so\n // the two can't collide. Mermaid's own class grammar only spells the\n // attachment as `cssClass \"A,B\" name` / `A:::name`; the flowchart-style\n // `class A,B name` is accepted here for parity with flowcharts.\n if (tryApplyClassDef(line, diagram)) continue\n if (tryApplyStyleStatement(line, diagram)) continue\n if (tryApplyCssClass(line, diagram)) continue\n if (tryApplyClassAssignment(line, diagram)) continue\n\n // --- Namespace block start ---\n const nsMatch = line.match(/^namespace\\s+(\\S+)\\s*\\{$/)\n if (nsMatch) {\n currentNamespace = { name: nsMatch[1]!, classIds: [] }\n continue\n }\n\n // --- Namespace end ---\n if (line === '}' && currentNamespace) {\n diagram.namespaces.push(currentNamespace)\n currentNamespace = null\n continue\n }\n\n // --- Class block start: `class ClassName {` or `class ClassName:::style {` ---\n const classBlockMatch = line.match(/^class\\s+(\\S+?)(?:\\s*~(\\w+)~)?\\s*\\{$/)\n if (classBlockMatch) {\n const id = declareClass(classBlockMatch[1]!, classBlockMatch[2])\n currentClass = classMap.get(id) ?? null\n braceDepth = 1\n openClassLine = stmt.line\n continue\n }\n\n // --- Standalone class declaration (no body): `class ClassName` / `class ClassName:::style` ---\n const classOnlyMatch = line.match(/^class\\s+(\\S+?)(?:\\s*~(\\w+)~)?\\s*$/)\n if (classOnlyMatch) {\n declareClass(classOnlyMatch[1]!, classOnlyMatch[2])\n continue\n }\n\n // --- Single-line body: `class ClassName { <<interface>> }` / `class ClassName:::style { -int size }` ---\n // One annotation or one member; `;`-separated members on a single line\n // are already split into separate statements upstream, so a multi-member\n // body can't reach here intact.\n const inlineBodyMatch = line.match(/^class\\s+(\\S+?)\\s*\\{\\s*(.*?)\\s*\\}$/)\n if (inlineBodyMatch) {\n const id = declareClass(inlineBodyMatch[1]!, undefined)\n const cls = classMap.get(id)\n const body = inlineBodyMatch[2]!\n const annotMatch = body.match(/^<<(\\w+)>>$/)\n if (cls && annotMatch) {\n cls.annotation = annotMatch[1]!\n } else if (cls && body.startsWith('<<')) {\n // Same broken-annotation-attempt case as the multi-line body\n // branch above, just on the single-line form (#761).\n throw new Error(\n `Line ${stmt.line}: Malformed class annotation \"${body}\" — expected \"<<name>>\" (e.g. \"<<interface>>\").`,\n )\n } else if (cls && body) {\n addMember(cls, body, stmt.line)\n }\n continue\n }\n\n // --- Inline attribute: `ClassName : +String name` ---\n const inlineAttrMatch = line.match(/^(\\S+?)\\s*:\\s*(.+)$/)\n if (inlineAttrMatch) {\n // Make sure this isn't a relationship line (those have arrows)\n const rest = inlineAttrMatch[2]!\n if (!rest.match(/<\\|--|--|\\*--|o--|-->|\\.\\.>|\\.\\.\\|>/)) {\n addMember(ensureClass(classMap, inlineAttrMatch[1]!), rest, stmt.line)\n continue\n }\n }\n\n // --- Relationship ---\n // Pattern: [FROM] [\"card\"] ARROW [\"card\"] [TO] [: label]\n // Arrows: <|--, *--, o--, -->, ..|>, ..>\n // Can also be reversed: --o, --*, --|>\n const rel = parseRelationship(line)\n if (rel) {\n // Ensure both classes exist. Either end may carry the `:::style`\n // shorthand (Mermaid's docs warn against combining it with a relation\n // statement, but it's better to strip it than to mint a class whose id\n // is the literal `Animal:::someclass`).\n rel.from = declareClass(rel.from, undefined, false)\n rel.to = declareClass(rel.to, undefined, false)\n diagram.relationships.push(rel)\n continue\n }\n\n // A line containing a relationship-arrow token that still didn't parse\n // above is a malformed/dangling relationship attempt — e.g. \"Animal\n // <|--\" with no target, or an unsupported arrow shape — rather than an\n // unrelated line that merely falls through. Scoped to lines that\n // already look like an attempt (same \"attempt, then validate\" pattern\n // #722 used for ER/xychart-beta) so genuinely unrecognized syntax with\n // no arrow at all keeps falling through silently (#761).\n if (RELATIONSHIP_ARROW_MARKER.test(line)) {\n throw new Error(\n `Line ${stmt.line}: Malformed class-diagram relationship \"${line}\". Expected \"FROM ARROW TO\" (optionally with cardinalities and a \": label\"), e.g. \"Animal <|-- Dog\" or 'A \"1\" --> \"*\" B : label'. ARROW must be one of <|--, <|.., *--, o--, -->, --*, --o, --|>, ..>, ..|>, <--, <.., or --.`,\n )\n }\n }\n\n if (currentClass !== null) {\n throw new Error(\n `Line ${openClassLine}: Unclosed class body for \"${currentClass.id}\" — expected a closing \"}\" before the diagram ends.`,\n )\n }\n\n diagram.classes = [...classMap.values()]\n return diagram\n\n /**\n * Register a class from a declaration token, splitting off any `:::style`\n * shorthand into `diagram.classAssignments`. Returns the bare id.\n *\n * @param generic - `~T~` generic parameter captured by the declaration regex\n * @param inNamespace - whether to record the class in the open namespace\n * (declarations do; relationship endpoints don't)\n */\n function declareClass(\n token: string,\n generic: string | undefined,\n inNamespace: boolean = true,\n ): string {\n const { id: rawId, className } = splitClassShorthand(token)\n let id = rawId\n let resolvedGeneric = generic\n if (!resolvedGeneric) {\n // The declaration regexes only split a `~T~` generic off the bare id\n // when nothing else trails it. When the `:::style` shorthand trails\n // the generic instead (`Foo~T~:::hot`), the generic gets swallowed\n // into the shorthand-stripped id here (`splitClassShorthand` returns\n // `Foo~T~`) — strip it off now so the class is declared as `Foo`\n // rather than the literal `Foo~T~` (#506).\n const genericMatch = rawId.match(/^(.+?)~(\\w+)~$/)\n if (genericMatch) {\n id = genericMatch[1]!\n resolvedGeneric = genericMatch[2]!\n }\n }\n const cls = ensureClass(classMap, id)\n if (resolvedGeneric) {\n cls.label = `${id}<${resolvedGeneric}>`\n }\n if (className) {\n diagram.classAssignments.set(id, className)\n }\n if (inNamespace && currentNamespace) {\n currentNamespace.classIds.push(id)\n }\n return id\n }\n}\n\n/** Parse one member line and file it under the class's attributes or methods. */\nfunction addMember(cls: ClassNode, line: string, lineNumber: number): void {\n const member = parseMember(line, lineNumber)\n if (!member) return\n if (member.isMethod) {\n cls.methods.push(member.member)\n } else {\n cls.attributes.push(member.member)\n }\n}\n\n/** Ensure a class exists in the map, creating a default if needed */\nfunction ensureClass(classMap: Map<string, ClassNode>, id: string): ClassNode {\n let cls = classMap.get(id)\n if (!cls) {\n cls = { id, label: id, attributes: [], methods: [] }\n classMap.set(id, cls)\n }\n return cls\n}\n\n/** Count occurrences of `needle` in `input`. */\nfunction countOccurrence(input: string, needle: string): number {\n return Math.max(0, input.split(needle).length - 1)\n}\n\n/**\n * Convert one comma-free (or already re-joined) segment's `~`-delimited\n * generics to angle brackets, pairing the outermost `~`s first so nested\n * generics like `List~List~T~~` become `List<List<T>>`. A segment with a\n * single `~` is left alone — there's nothing to pair it with — and an odd\n * count with a leading `~` keeps that leading one as-is (mermaid treats it\n * as literal text, not a delimiter). Mirrors mermaid's `processSet`.\n */\nfunction convertGenericSegment(input: string): string {\n const tildeCount = countOccurrence(input, '~')\n if (tildeCount <= 1) return input\n\n let text = input\n let keepLeadingTilde = false\n if (tildeCount % 2 !== 0 && text.startsWith('~')) {\n text = text.slice(1)\n keepLeadingTilde = true\n }\n\n const chars = [...text]\n let open = chars.indexOf('~')\n let close = chars.lastIndexOf('~')\n while (open !== -1 && close !== -1 && open !== close) {\n chars[open] = '<'\n chars[close] = '>'\n open = chars.indexOf('~')\n close = chars.lastIndexOf('~')\n }\n if (keepLeadingTilde) chars.unshift('~')\n return chars.join('')\n}\n\n/**\n * Convert mermaid's `~T~` generic syntax to the `<T>` form mermaid itself\n * renders — `List~Observer~` → `List<Observer>`, `Map~K,V~` → `Map<K,V>`,\n * `List~List~T~~` → `List<List<T>>`.\n *\n * Port of mermaid's `parseGenericTypes` (packages/mermaid/src/diagrams/\n * common/common.ts): the text is split on commas so a comma *inside* a\n * generic (`Map~K,V~`) can be re-joined with its neighbors — two adjacent\n * comma-separated pieces that each carry exactly one `~` are the two halves\n * of one generic — before each piece's `~` pairs are converted outermost-first.\n */\nexport function parseGenericTypes(input: string): string {\n const pieces = input.split(/(,)/)\n const output: string[] = []\n for (let i = 0; i < pieces.length; i++) {\n let piece = pieces[i]!\n if (piece === ',' && i > 0 && i + 1 < pieces.length) {\n const previous = pieces[i - 1]!\n const next = pieces[i + 1]!\n if (\n countOccurrence(previous, '~') === 1 &&\n countOccurrence(next, '~') === 1\n ) {\n piece = `${previous},${next}`\n i++\n output.pop()\n }\n }\n output.push(convertGenericSegment(piece))\n }\n return output.join('')\n}\n\n/** Parse a class member line (attribute or method) */\nfunction parseMember(\n line: string,\n lineNumber: number,\n): { member: ClassMember; isMethod: boolean } | null {\n const trimmed = line.trim().replace(/;$/, '')\n if (!trimmed) return null\n\n // Extract visibility prefix\n let visibility: ClassMember['visibility'] = ''\n let rest = trimmed\n const visibilityChar = rest[0]\n if (\n visibilityChar === '+' ||\n visibilityChar === '-' ||\n visibilityChar === '#' ||\n visibilityChar === '~'\n ) {\n visibility = visibilityChar\n rest = rest.slice(1).trim()\n }\n\n // An attempted method signature with an opening \"(\" but no closing \")\"\n // at all can't be a genuine attribute either (an attribute name/type\n // never legitimately contains a bare, unclosed paren) — surface it\n // instead of silently parsing \"eat(void\" as a member literally named\n // that (#761).\n if (rest.includes('(') && !rest.includes(')')) {\n throw new Error(\n `Line ${lineNumber}: Malformed class member \"${trimmed}\" — unclosed \"(\" in a method signature. Expected e.g. \"+eat() void\" or \"+eat(Food f) void\".`,\n )\n }\n\n // Mermaid renders `List~Observer~` as `List<Observer>` (its own\n // `parseGenericTypes`). It's applied per field below — name, params and\n // return type separately, the way mermaid's ClassMember does — rather\n // than to the whole line, since the comma re-join inside\n // parseGenericTypes would otherwise pair a `~` in the params with one in\n // the return type. Doing it after the visibility prefix is stripped also\n // keeps a package-visibility `~` from pairing with a generic's.\n\n // Check if it's a method (has parentheses)\n const methodMatch = rest.match(/^(.+?)\\(([^)]*)\\)(?:\\s*(.+))?$/)\n if (methodMatch) {\n const name = parseGenericTypes(methodMatch[1]!.trim())\n const rawParams = methodMatch[2]?.trim()\n const params = rawParams ? parseGenericTypes(rawParams) : undefined // Store the parameter string\n const rawType = methodMatch[3]?.trim()\n const type = rawType ? parseGenericTypes(rawType) : undefined\n // Check for static ($) or abstract (*) markers\n const isStatic = name.endsWith('$') || rest.includes('$')\n const isAbstract = name.endsWith('*') || rest.includes('*')\n return {\n member: {\n visibility,\n name: name.replace(/[$*]$/, ''),\n type: type || undefined,\n isStatic,\n isAbstract,\n isMethod: true,\n params,\n },\n isMethod: true,\n }\n }\n\n // It's an attribute. Mermaid renders attribute text verbatim (its\n // ClassMember keeps everything after the visibility marker as one id and\n // only splits methods into name/params/return type), so `String name`,\n // `name Type` and `name: Type` all display as written. Splitting two\n // tokens into Type + name guessed the order and flipped `-data Map` into\n // `- Map: data` (#1265).\n const name = parseGenericTypes(rest)\n const type: string | undefined = undefined\n\n const isStatic = name.endsWith('$')\n const isAbstract = name.endsWith('*')\n\n return {\n member: {\n visibility,\n name: name.replace(/[$*]$/, ''),\n type: type || undefined,\n isStatic,\n isAbstract,\n isMethod: false,\n },\n isMethod: false,\n }\n}\n\n/** Parse a relationship line into a ClassRelationship */\nfunction parseRelationship(line: string): ClassRelationship | null {\n // Relationship regex — handles all arrow types with optional cardinality and labels\n // Pattern: FROM [\"card\"] ARROW [\"card\"] TO[:::style] [: label]\n // The `:::style` shorthand on TO is matched as its own group so the `:`\n // label separator that follows can't swallow it as a label of `::style`;\n // it's re-joined onto the id here and split off again by the caller.\n const match = line.match(\n new RegExp(\n `^(\\\\S+?)\\\\s+(?:\"([^\"]*?)\"\\\\s+)?(${RELATIONSHIP_ARROW_SOURCE})\\\\s+(?:\"([^\"]*?)\"\\\\s+)?(\\\\S+?)(?::::([\\\\w][\\\\w-]*))?(?:\\\\s*:\\\\s*(.+))?$`,\n ),\n )\n if (!match) return null\n\n const from = match[1]!\n const rawFromCardinality = match[2]\n const fromCardinality = rawFromCardinality\n ? normalizeBrTags(rawFromCardinality)\n : undefined\n const arrow = match[3]!.trim()\n const rawToCardinality = match[4]\n const toCardinality = rawToCardinality\n ? normalizeBrTags(rawToCardinality)\n : undefined\n const to = match[6] ? `${match[5]!}:::${match[6]}` : match[5]!\n const rawLabel = match[7]?.trim()\n const label = rawLabel ? normalizeBrTags(rawLabel) : undefined\n\n const parsed = parseArrow(arrow)\n if (!parsed) return null\n\n return {\n from,\n to,\n type: parsed.type,\n markerAt: parsed.markerAt,\n label,\n fromCardinality,\n toCardinality,\n }\n}\n\n/**\n * Map arrow syntax to relationship type and marker placement side.\n * Prefix markers (`<|--`, `*--`, `o--`) place the UML shape at the 'from' end.\n * Suffix markers (`..|>`, `-->`, `..>`, `--*`, `--o`) place it at the 'to' end.\n */\nfunction parseArrow(\n arrow: string,\n): { type: RelationshipType; markerAt: 'from' | 'to' } | null {\n // Trim whitespace that might be captured by the regex\n const a = arrow.trim()\n switch (a) {\n case '<|--':\n return { type: 'inheritance', markerAt: 'from' }\n case '--|>':\n return { type: 'inheritance', markerAt: 'to' }\n case '<|..':\n return { type: 'realization', markerAt: 'from' }\n case '..|>':\n return { type: 'realization', markerAt: 'to' }\n case '*--':\n return { type: 'composition', markerAt: 'from' }\n case '--*':\n return { type: 'composition', markerAt: 'to' }\n case 'o--':\n return { type: 'aggregation', markerAt: 'from' }\n case '--o':\n return { type: 'aggregation', markerAt: 'to' }\n case '-->':\n return { type: 'association', markerAt: 'to' }\n case '<--':\n return { type: 'association', markerAt: 'from' }\n case '..>':\n return { type: 'dependency', markerAt: 'to' }\n case '<..':\n return { type: 'dependency', markerAt: 'from' }\n case '--':\n return { type: 'association', markerAt: 'to' }\n default:\n return null\n }\n}\n","// ============================================================================\n// Class member formatting — shared between the SVG (layout.ts) and ASCII\n// (ascii/class-diagram.ts) renderers so method parentheses/params can't drift\n// between the two again. See issue #290.\n// ============================================================================\n\nimport type { ClassMember } from './types.ts'\n\n/** Format a class member as a display string: visibility + name(+params for methods) + optional type */\nexport function formatClassMember(m: ClassMember): string {\n const vis = m.visibility ? `${m.visibility} ` : ''\n const name = m.isMethod ? `${m.name}(${m.params || ''})` : m.name\n const type = m.type ? `: ${m.type}` : ''\n return `${vis}${name}${type}`\n}\n","import type {\n ErDiagram,\n ErEntity,\n ErAttribute,\n ErRelationship,\n Cardinality,\n} from './types.ts'\nimport {\n normalizeBrTags,\n toDirection,\n type Statement,\n} from '@zombie-mermaid/core'\n\n// ============================================================================\n// ER diagram parser\n//\n// Parses Mermaid erDiagram syntax into an ErDiagram structure.\n//\n// Supported syntax:\n// CUSTOMER ||--o{ ORDER : places\n// CUSTOMER {\n// string name PK\n// int age\n// string email UK \"user email\"\n// }\n//\n// Cardinality notation:\n// || || exactly one\n// |o o| zero or one\n// }| |{ one or more\n// }o o{ zero or more\n//\n// Line style:\n// -- identifying (solid line)\n// .. non-identifying (dashed line)\n// ============================================================================\n\n/**\n * Parse a Mermaid ER diagram.\n * Expects the first line to be \"erDiagram\".\n */\n// Audited for issue #100 (non-null assertions): every `!` in this file is\n// either a bounds-checked loop-index array access (`lines[i]!` inside a\n// `for (let i = 1; i < lines.length; ...)` loop) or a regex-mandatory-\n// capture-group access after `.match()` (a group that isn't wrapped in an\n// optional `(?:...)?`, so it always participates when the overall match\n// succeeds). Both are the same idioms already accepted as justified\n// elsewhere in this codebase (see src/parser.ts, PR #158, and this\n// subsystem's layout.ts/renderer.ts audit, PR #148, which fixed the\n// genuinely risky assertions there but didn't reach this file — PR #146\n// separately reviewed this file's `as` casts, which are a different\n// concern from these `!`s) — `noUncheckedIndexedAccess` can't see either\n// guarantee, but removing the `!` would only replace a proven-safe\n// assertion with an unreachable guard. Left as-is; no behavior change.\nexport function parseErDiagram(lines: Statement[]): ErDiagram {\n const diagram: ErDiagram = {\n entities: [],\n relationships: [],\n }\n\n // Track entities by ID for deduplication\n const entityMap = new Map<string, ErEntity>()\n // Track entity body parsing\n let currentEntity: ErEntity | null = null\n\n for (let i = 1; i < lines.length; i++) {\n const stmt = lines[i]!\n const line = stmt.text\n\n // --- Inside entity body ---\n if (currentEntity) {\n if (line === '}') {\n currentEntity = null\n continue\n }\n\n // Attribute line: type name [PK|FK|UK] [\"comment\"]\n const attr = parseAttribute(line)\n if (attr) {\n currentEntity.attributes.push(attr)\n }\n continue\n }\n\n // --- direction directive: `direction TB` / `direction LR` / etc. ---\n // ER diagrams have no subgraph nesting, so a single top-level direction\n // applies to the whole diagram (unlike flowcharts, where `direction` can\n // also appear per-subgraph).\n const dirMatch = line.match(/^direction\\s+(TD|TB|LR|BT|RL)\\s*$/i)\n if (dirMatch) {\n diagram.direction = toDirection(dirMatch[1]!)\n continue\n }\n\n // --- Entity block start: `ENTITY_NAME {`, `id[Alias Label] {`, or\n // `id[\"Quoted Alias\"] {` — optionally with the body (and even the\n // closing `}`) inline on the same line, e.g. `p[Person] { string name }`.\n const entityBlockMatch = line.match(/^(\\S+?)(?:\\[(.+)\\])?\\s*\\{(.*)$/)\n if (entityBlockMatch) {\n const id = entityBlockMatch[1]!\n const aliasRaw = entityBlockMatch[2]\n const alias =\n aliasRaw !== undefined\n ? normalizeBrTags(aliasRaw.trim().replace(/^[\"']|[\"']$/g, ''))\n : undefined\n const entity = ensureEntity(entityMap, id, alias)\n\n const trailing = entityBlockMatch[3]!.trim()\n const closesInline = trailing.endsWith('}')\n const body = closesInline ? trailing.slice(0, -1).trim() : trailing\n for (const part of body\n .split(';')\n .map((s) => s.trim())\n .filter(Boolean)) {\n const attr = parseAttribute(part)\n if (attr) entity.attributes.push(attr)\n }\n\n // Only keep the block open for subsequent attribute lines when this\n // line didn't already close it.\n if (!closesInline) {\n currentEntity = entity\n }\n continue\n }\n\n // --- Relationship: `ENTITY1 cardinality1--cardinality2 ENTITY2 : label` ---\n const rel = parseRelationshipLine(line, stmt.line)\n if (rel) {\n // Ensure both entities exist\n ensureEntity(entityMap, rel.entity1)\n ensureEntity(entityMap, rel.entity2)\n diagram.relationships.push(rel)\n continue\n }\n }\n\n diagram.entities = [...entityMap.values()]\n return diagram\n}\n\n/**\n * Ensure an entity exists in the map. When `alias` is provided (from an\n * `id[Alias]` entity-block header), it becomes the display label while the\n * map key — and every relationship reference — stays keyed off the raw `id`.\n */\nfunction ensureEntity(\n entityMap: Map<string, ErEntity>,\n id: string,\n alias?: string,\n): ErEntity {\n let entity = entityMap.get(id)\n if (!entity) {\n entity = { id, label: alias ?? id, attributes: [] }\n entityMap.set(id, entity)\n } else if (alias !== undefined) {\n entity.label = alias\n }\n return entity\n}\n\n/** Parse an attribute line inside an entity block */\nfunction parseAttribute(line: string): ErAttribute | null {\n // Format: type name [PK|FK|UK [...]] [\"comment\"]\n const match = line.match(/^(\\S+)\\s+(\\S+)(?:\\s+(.+))?$/)\n if (!match) return null\n\n const type = match[1]!\n const name = match[2]!\n const rest = match[3]?.trim() ?? ''\n\n // Extract key constraints (PK, FK, UK) and optional comment\n const keys: ErAttribute['keys'] = []\n let comment: string | undefined\n\n // Extract quoted comment first (supports <br> tags)\n const commentMatch = rest.match(/\"([^\"]*)\"/)\n if (commentMatch) {\n comment = normalizeBrTags(commentMatch[1]!)\n }\n\n // Extract key constraints\n const restWithoutComment = rest.replace(/\"[^\"]*\"/, '').trim()\n for (const part of restWithoutComment.split(/\\s+/)) {\n const upper = part.toUpperCase()\n if (upper === 'PK' || upper === 'FK' || upper === 'UK') {\n keys.push(upper)\n }\n }\n\n return { type, name, keys, comment }\n}\n\n/**\n * Parse a relationship line.\n *\n * Cardinality symbols on each side of the line style:\n * Left side (entity1): || |o }| }o\n * Line: -- (identifying) or .. (non-identifying)\n * Right side (entity2): || o| |{ o{\n *\n * Full pattern example: CUSTOMER ||--o{ ORDER : places\n */\nfunction parseRelationshipLine(\n line: string,\n lineNumber: number,\n): ErRelationship | null {\n // Loosely match a line shaped like a relationship attempt: two bare\n // tokens flanking a `--`/`..`-based marker, with an optional `: label`.\n // A line with no such marker at all doesn't look like a relationship\n // attempt and falls through silently (returns null), same as before —\n // that part of the parser's leniency is unchanged.\n //\n // Everything that *does* match this shape is validated below and either\n // returns a fully-parsed relationship or throws an actionable error.\n // Before this pass, an invalid cardinality token or a missing/empty\n // label silently dropped the *entire* line (both entities included)\n // with zero indication anything was wrong — see issue #541 (parser\n // error-message quality audit — this parser had zero throw sites before\n // this pass).\n const attempt = line.match(\n /^(\\S+)\\s+(\\S*(?:--|\\.\\.)\\S*)\\s+(\\S+)\\s*(?::\\s*(.*))?$/,\n )\n if (!attempt) return null\n\n const entity1 = attempt[1]!\n const cardinalityStr = attempt[2]!\n const entity2 = attempt[3]!\n const hasLabel = attempt[4] !== undefined\n const rawLabel = attempt[4]?.trim() ?? ''\n\n // Cardinality symbols on each side of the line style:\n // Left side (entity1): || |o }| }o\n // Line: -- (identifying) or .. (non-identifying)\n // Right side (entity2): || o| |{ o{\n // `shapeMatch` only confirms the token is built from crow's-foot\n // characters around a line-style marker — `parseLeftCardinality`/\n // `parseRightCardinality` still reject a shape match that isn't one of\n // the four valid two-character combinations per side (e.g. a lone \"|\").\n const shapeMatch = cardinalityStr.match(/^([|o}{]*)(--|\\.\\.?)([|o}{]*)$/)\n const cardinality1 = shapeMatch ? parseLeftCardinality(shapeMatch[1]!) : null\n const cardinality2 = shapeMatch ? parseRightCardinality(shapeMatch[3]!) : null\n\n if (!cardinality1 || !cardinality2) {\n throw new Error(\n `Line ${lineNumber}: Invalid ER relationship cardinality \"${cardinalityStr}\" in \"${line}\". ` +\n 'Left side must be one of ||, |o, }|, }o; right side must be one of ||, o|, |{, o{ (e.g. \"||--o{\").',\n )\n }\n\n if (!hasLabel || rawLabel.length === 0) {\n throw new Error(\n `Line ${lineNumber}: ER relationship \"${entity1} ${cardinalityStr} ${entity2}\" is missing a \": label\" — expected e.g. \"${entity1} ${cardinalityStr} ${entity2} : label\".`,\n )\n }\n\n // Strip surrounding quotes if present, then normalize br tags.\n const label = normalizeBrTags(rawLabel.replace(/^[\"']|[\"']$/g, ''))\n const identifying = shapeMatch![2] === '--'\n\n return { entity1, entity2, cardinality1, cardinality2, label, identifying }\n}\n\n/**\n * Parse a left-side (entity1) cardinality notation string.\n * The crow's-foot character sits nearer the entity, so left- and\n * right-side notations are mirror images of each other and must be\n * matched exactly rather than order-normalized (sorting `}o` and `o{`\n * to the same key conflates \"zero or more\" with malformed input).\n */\nfunction parseLeftCardinality(str: string): Cardinality | null {\n if (str === '||') return 'one'\n if (str === '|o') return 'zero-one'\n if (str === '}|') return 'many'\n if (str === '}o') return 'zero-many'\n return null\n}\n\n/** Parse a right-side (entity2) cardinality notation string. */\nfunction parseRightCardinality(str: string): Cardinality | null {\n if (str === '||') return 'one'\n if (str === 'o|') return 'zero-one'\n if (str === '|{') return 'many'\n if (str === 'o{') return 'zero-many'\n return null\n}\n","// ============================================================================\n// `box <color?> <label?>` header parsing\n//\n// Mermaid's sequenceDb.parseBoxData takes the first word of the header (or a\n// leading `rgb(...)`/`hsl(...)` call), asks the browser whether it is a valid\n// CSS colour, and — if it isn't — treats the whole header as the label. There\n// is no browser here, so \"is a colour\" is decided by a fixed list of CSS\n// named colours plus the hex / rgb() / hsl() function forms. That list is\n// also what keeps a diagram-supplied string safe to emit inside an SVG\n// attribute: anything not matching it never reaches the renderer as a colour.\n// ============================================================================\n\n/** The CSS Color Module Level 4 named colours (plus `transparent`, handled separately). */\nconst CSS_NAMED_COLORS = new Set(\n (\n 'aliceblue antiquewhite aqua aquamarine azure beige bisque black ' +\n 'blanchedalmond blue blueviolet brown burlywood cadetblue chartreuse ' +\n 'chocolate coral cornflowerblue cornsilk crimson cyan darkblue darkcyan ' +\n 'darkgoldenrod darkgray darkgreen darkgrey darkkhaki darkmagenta ' +\n 'darkolivegreen darkorange darkorchid darkred darksalmon darkseagreen ' +\n 'darkslateblue darkslategray darkslategrey darkturquoise darkviolet ' +\n 'deeppink deepskyblue dimgray dimgrey dodgerblue firebrick floralwhite ' +\n 'forestgreen fuchsia gainsboro ghostwhite gold goldenrod gray green ' +\n 'greenyellow grey honeydew hotpink indianred indigo ivory khaki lavender ' +\n 'lavenderblush lawngreen lemonchiffon lightblue lightcoral lightcyan ' +\n 'lightgoldenrodyellow lightgray lightgreen lightgrey lightpink ' +\n 'lightsalmon lightseagreen lightskyblue lightslategray lightslategrey ' +\n 'lightsteelblue lightyellow lime limegreen linen magenta maroon ' +\n 'mediumaquamarine mediumblue mediumorchid mediumpurple mediumseagreen ' +\n 'mediumslateblue mediumspringgreen mediumturquoise mediumvioletred ' +\n 'midnightblue mintcream mistyrose moccasin navajowhite navy oldlace olive ' +\n 'olivedrab orange orangered orchid palegoldenrod palegreen paleturquoise ' +\n 'palevioletred papayawhip peachpuff peru pink plum powderblue purple ' +\n 'rebeccapurple red rosybrown royalblue saddlebrown salmon sandybrown ' +\n 'seagreen seashell sienna silver skyblue slateblue slategray slategrey ' +\n 'snow springgreen steelblue tan teal thistle tomato turquoise violet ' +\n 'wheat white whitesmoke yellow yellowgreen'\n ).split(' '),\n)\n\n// `#rgb`, `#rgba`, `#rrggbb`, `#rrggbbaa`. Mermaid itself can't accept hex\n// here (upstream's lexer treats `#` as a comment); this parser has no such\n// comment syntax, so hex is accepted as a strict superset.\nconst HEX_COLOR_RE = /^#(?:[0-9a-f]{3,4}|[0-9a-f]{6}|[0-9a-f]{8})$/i\n// `rgb(33,66,99)`, `rgba(33,66,99,0.5)`, `hsl(10, 40%, 90%)`, `hsla(...)`,\n// including the modern space-separated / slash-alpha forms. The argument\n// character class is the safety boundary: digits, `.`, `%`, `,`, `/`, and\n// whitespace only.\nconst FUNCTION_COLOR_RE =\n /^(?:rgba?|hsla?)\\(\\s*[\\d.%]+(?:[\\s,/]+[\\d.%]+){2,3}\\s*\\)$/i\n\n/** Whether `value` is a colour this renderer will emit as-is (case-insensitive). */\nexport function isCssColor(value: string): boolean {\n return (\n CSS_NAMED_COLORS.has(value.toLowerCase()) ||\n HEX_COLOR_RE.test(value) ||\n FUNCTION_COLOR_RE.test(value)\n )\n}\n\n/**\n * Split the text after the `box` keyword into a colour and a label, per\n * Mermaid's `parseBoxData`: the leading word (or function call) is the\n * colour if it is one, otherwise the whole header is the label. An explicit\n * `transparent` is consumed as \"no colour\" so `box transparent Aqua` yields\n * a colourless box labelled `Aqua`, exactly as upstream documents.\n */\nexport function parseBoxHeader(header: string): {\n color?: string\n label: string\n} {\n const trimmed = header.trim()\n const match = trimmed.match(\n /^((?:rgba?|hsla?)\\s*\\([^)]*\\)|#[0-9a-fA-F]+|\\S+)?(.*)$/,\n )\n const candidate = match?.[1] ?? ''\n const rest = match?.[2]?.trim() ?? ''\n if (candidate.toLowerCase() === 'transparent') {\n return { label: rest }\n }\n if (candidate !== '' && isCssColor(candidate)) {\n return { color: candidate, label: rest }\n }\n return { label: trimmed }\n}\n","import type { SequenceDiagram, Message, Block, Actor } from './types.ts'\nimport { normalizeBrTags, type Statement } from '@zombie-mermaid/core'\nimport { parseBoxHeader } from './box-color.ts'\n\n/**\n * Which `box … end` group (index into `diagram.boxes`) is currently open,\n * and which box each participant already belongs to. Threaded through the\n * actor-creating helpers so a participant first mentioned inside a box —\n * by a declaration, a `create`, or (leniently) a message — joins it, the\n * way Mermaid's `addActor` assigns `currentBox`.\n */\ninterface BoxContext {\n open: number | undefined\n membership: Map<string, number>\n}\n\n/**\n * Narrow a regex-captured block keyword to `Block['type']`. The capturing\n * regex at the call site uses the same `(loop|alt|opt|par|critical|break|\n * rect)` alternation, so the input is always one of these seven values in\n * practice — but the match itself is typed as `string`.\n *\n * Exported for direct unit testing (see\n * src/__tests__/sequence-parser.test.ts) — not otherwise part of this\n * module's public parsing API.\n */\nexport function isBlockType(value: string): value is Block['type'] {\n return (\n value === 'loop' ||\n value === 'alt' ||\n value === 'opt' ||\n value === 'par' ||\n value === 'critical' ||\n value === 'break' ||\n value === 'rect'\n )\n}\n\n/**\n * Narrow a regex-captured block keyword to `Block['type']`, throwing if it\n * somehow isn't one of the seven recognized keywords (see `isBlockType`\n * above — unreachable via the guarding regex in practice, but this keeps\n * the failure explicit rather than silently mistyping the value).\n *\n * Exported for direct unit testing (see\n * src/__tests__/sequence-parser.test.ts) — the throw branch is unreachable\n * through the public `parseSequenceDiagram` API (the regex that captures\n * the value already restricts it to the seven valid keywords), so it can\n * only be exercised by calling this function directly.\n */\nexport function toBlockType(value: string): Block['type'] {\n if (!isBlockType(value)) {\n throw new Error(`Invalid block type: \"${value}\"`)\n }\n return value\n}\n\n// ============================================================================\n// Sequence diagram parser\n//\n// Parses Mermaid sequenceDiagram syntax into a SequenceDiagram structure.\n//\n// Supported syntax:\n// participant A as Alice\n// actor B as Bob\n// A->>B: Solid arrow\n// A-->>B: Dashed arrow\n// A-)B: Open arrow\n// A--)B: Dashed open arrow\n// A->>+B: Activate target\n// A-->>-B: Deactivate source\n// activate A / deactivate A (standalone form of the +/- shorthand)\n// create participant C [as Label] / create actor C [as Label]\n// (on the line before C's first message)\n// destroy C (on the line before C's last message)\n// box <color?> <label?> ... end (participant grouping; see box-color.ts)\n// A<<->>B: Bidirectional solid arrow\n// A<<-->>B: Bidirectional dashed arrow\n// autonumber / autonumber <start> <step> / autonumber off\n// loop Label ... end\n// alt Label ... else Label ... end\n// opt Label ... end\n// par Label ... and Label ... end\n// Note left of A: Text\n// Note right of A: Text\n// Note over A,B: Text\n// ============================================================================\n\n// Message-line arrow regexes, shared by the two-pass match in the \"Message\"\n// branch of the parsing loop below (see the comment there for why there are\n// two, and issue #341 for the mis-split bug this two-pass approach fixes).\n// `MESSAGE_LONG_ARROW_RE` only recognizes the \"long\" arrow forms — anything\n// ending in `>` (`->`, `-->`, `->>`, `-->>`) plus the bidirectional tokens\n// (`<<->>`, `<<-->>`) — which essentially never occur by accident inside an\n// unquoted actor name. `MESSAGE_ANY_ARROW_RE` is the original, full\n// alternation (also matching the short open/cross forms `-)`, `--)`, `-x`,\n// `--x`), used as a fallback when a line has no long arrow at all.\nconst MESSAGE_LONG_ARROW_RE =\n /^(.+?)\\s*(<<->>|<<-->>|--?>?>)\\s*([+-]?)(.+?)\\s*:\\s*(.+)$/\nconst MESSAGE_ANY_ARROW_RE =\n /^(.+?)\\s*(<<->>|<<-->>|--?>?>|--?[)x]|--?>>|--?>)\\s*([+-]?)(.+?)\\s*:\\s*(.+)$/\n\n/**\n * Parse a Mermaid sequence diagram.\n * Expects the first line to be \"sequenceDiagram\".\n */\n// Audited for issue #100 (non-null assertions): every `!` in this file is\n// one of three idioms already accepted as justified elsewhere in this\n// codebase — (1) a bounds-checked loop-index array access (`lines[i]!`\n// inside a `for (let i = 1; i < lines.length; ...)` loop), (2) a\n// regex-mandatory-capture-group access after `.match()` (a group not\n// wrapped in an optional `(?:...)?`, so it always participates when the\n// overall match succeeds), or (3) `blockStack[blockStack.length - 1]!` /\n// `blockStack.pop()!`, both guarded immediately above by an explicit\n// `blockStack.length > 0` check. See src/parser.ts (PR #158) and this\n// subsystem's layout.ts audit (PR #149), which fixed the one genuinely\n// risky assertion in the subsystem (a Map get/set race) but didn't reach\n// this file — PR #146 separately reviewed this file's `as` casts, a\n// different concern from these `!`s. `noUncheckedIndexedAccess` can't see\n// any of these guarantees, but removing the `!` would only replace a\n// proven-safe assertion with an unreachable guard. Left as-is; no\n// behavior change.\nexport function parseSequenceDiagram(lines: Statement[]): SequenceDiagram {\n const diagram: SequenceDiagram = {\n actors: [],\n messages: [],\n blocks: [],\n notes: [],\n activations: [],\n boxes: [],\n }\n\n const boxCtx: BoxContext = { open: undefined, membership: new Map() }\n // Block-stack depth at the moment the open box began. Mermaid's grammar\n // only allows participant declarations inside a box, so `end` there can\n // only mean the box — but this parser is lenient about other statements,\n // and a `loop` opened inside a box must still own the next `end`.\n let boxOpenedAtDepth = 0\n // Line the currently-open box started on, for the unclosed-box error at\n // EOF below (issue #762) — `boxCtx.open` alone doesn't carry position.\n let boxOpenLine: number | undefined\n\n // Track actor IDs to auto-create actors referenced in messages\n const actorIds = new Set<string>()\n // Track block nesting with a stack\n const blockStack: Array<{\n type: Block['type']\n label: string\n startIndex: number\n dividers: Block['dividers']\n line: number\n }> = []\n\n // `autonumber` state — a bare `autonumber` turns numbering on starting at 1\n // (step 1); `autonumber <start> <step>` sets both explicitly; `autonumber\n // off` turns it back off. Only messages consume a number — notes and\n // block/divider lines don't advance the counter.\n const autonumber = { enabled: false, next: 1, step: 1 }\n\n // `create participant X` / `destroy X` bind to the very next message —\n // Mermaid's sequenceDb enforces that the next message's recipient is the\n // created participant, and that the destroyed one is its sender or\n // recipient, throwing otherwise. Mirrored here (same error text) rather\n // than guessing which later message was meant. A directive with no\n // message after it at all is left as a plain declaration.\n let pendingCreate: string | undefined\n let pendingDestroy: string | undefined\n\n for (let i = 1; i < lines.length; i++) {\n const stmt = lines[i]!\n const line = stmt.text\n\n // --- box <color?> <label?> ---\n // Opens a participant group; closed by `end`. Boxes cannot nest\n // (Mermaid's grammar rejects it), so a second `box` while one is open\n // is an error rather than an implicit close.\n const boxMatch = line.match(/^box(?:\\s+(.*))?$/)\n if (boxMatch) {\n if (boxCtx.open !== undefined) {\n throw new Error(\n `Line ${stmt.line}: Sequence diagram: a box cannot be nested inside another box — close the open box with \"end\" first`,\n )\n }\n const { color, label } = parseBoxHeader(boxMatch[1] ?? '')\n const box: SequenceDiagram['boxes'][number] = {\n label: normalizeBrTags(label),\n actorIds: [],\n }\n if (color !== undefined) box.color = color\n diagram.boxes.push(box)\n boxCtx.open = diagram.boxes.length - 1\n boxOpenedAtDepth = blockStack.length\n boxOpenLine = stmt.line\n continue\n }\n\n // --- create participant / create actor ---\n // \"create participant C\" / \"create actor C as Label\" — the line before\n // C's first message. Same shape as a plain declaration, so the alias and\n // the participant/actor kind are kept, not dropped with the keyword.\n const createMatch = line.match(\n /^create\\s+(participant|actor)\\s+(\\S+?)(?:\\s+as\\s+(.+))?$/,\n )\n if (createMatch) {\n const type: 'participant' | 'actor' =\n createMatch[1] === 'actor' ? 'actor' : 'participant'\n const id = createMatch[2]!\n if (actorIds.has(id)) {\n // Mermaid's own wording (sequenceDb `createParticipant`).\n throw new Error(\n `Line ${stmt.line}: It is not possible to have actors with the same id, even if one is destroyed before the next is created. Use 'AS' aliases to simulate the behavior`,\n )\n }\n actorIds.add(id)\n diagram.actors.push({\n id,\n label: normalizeBrTags(createMatch[3]?.trim() ?? id),\n type,\n })\n joinOpenBox(diagram, boxCtx, id, stmt.line)\n pendingCreate = id\n continue\n }\n\n // --- destroy ---\n // \"destroy C\" — the line before C's last message.\n const destroyMatch = line.match(/^destroy\\s+(.+)$/)\n if (destroyMatch) {\n const id = destroyMatch[1]!.trim()\n ensureActor(diagram, actorIds, boxCtx, id, stmt.line)\n pendingDestroy = id\n continue\n }\n\n // --- autonumber directive ---\n const autonumberMatch = line.match(\n /^autonumber(?:\\s+(off|\\d+(?:\\.\\d{1,2})?)(?:\\s+(\\d+(?:\\.\\d{1,2})?))?)?$/,\n )\n if (autonumberMatch) {\n const start = autonumberMatch[1]\n const step = autonumberMatch[2]\n if (start === 'off') {\n autonumber.enabled = false\n } else {\n autonumber.enabled = true\n autonumber.next = start !== undefined ? Number(start) : 1\n autonumber.step = step !== undefined ? Number(step) : 1\n }\n continue\n }\n\n // --- Participant / Actor declaration ---\n // \"participant A as Alice\" or \"participant Alice\"\n // \"actor B as Bob\" or \"actor Bob\"\n const actorMatch = line.match(\n /^(participant|actor)\\s+(\\S+?)(?:\\s+as\\s+(.+))?$/,\n )\n if (actorMatch) {\n // Group 1 is constrained by the regex alternation to 'participant' | 'actor'\n const type: 'participant' | 'actor' =\n actorMatch[1] === 'actor' ? 'actor' : 'participant'\n const id = actorMatch[2]!\n const rawLabel = actorMatch[3]?.trim() ?? id\n const label = normalizeBrTags(rawLabel)\n if (!actorIds.has(id)) {\n actorIds.add(id)\n diagram.actors.push({ id, label, type })\n }\n // A re-declaration inside a box joins it (or errors if it already\n // belongs to a different one) — Mermaid's `addActor` rule.\n joinOpenBox(diagram, boxCtx, id, stmt.line)\n continue\n }\n\n // --- Note ---\n // \"Note left of A: text\" / \"Note right of A: text\" / \"Note over A,B: text\"\n const noteMatch = line.match(\n /^Note\\s+(left of|right of|over)\\s+([^:]+):\\s*(.+)$/i,\n )\n if (noteMatch) {\n const posStr = noteMatch[1]!.toLowerCase()\n const actorsStr = noteMatch[2]!.trim()\n const text = normalizeBrTags(noteMatch[3]!.trim())\n const noteActorIds = actorsStr.split(',').map((s) => s.trim())\n\n // Ensure actors exist\n for (const aid of noteActorIds) {\n ensureActor(diagram, actorIds, boxCtx, aid, stmt.line)\n }\n\n let position: 'left' | 'right' | 'over' = 'over'\n if (posStr === 'left of') position = 'left'\n else if (posStr === 'right of') position = 'right'\n\n diagram.notes.push({\n actorIds: noteActorIds,\n text,\n position,\n afterIndex: diagram.messages.length - 1,\n })\n continue\n }\n\n // --- Block start: loop, alt, opt, par, critical, break, rect ---\n const blockMatch = line.match(\n /^(loop|alt|opt|par|critical|break|rect)\\s*(.*)$/,\n )\n if (blockMatch) {\n const blockType = toBlockType(blockMatch[1]!)\n const rawBlockLabel = blockMatch[2]?.trim() ?? ''\n const label = normalizeBrTags(rawBlockLabel)\n blockStack.push({\n type: blockType,\n label,\n startIndex: diagram.messages.length,\n dividers: [],\n line: stmt.line,\n })\n continue\n }\n\n // --- Block divider: else, and ---\n const dividerMatch = line.match(/^(else|and)\\s*(.*)$/)\n if (dividerMatch && blockStack.length > 0) {\n const rawDividerLabel = dividerMatch[2]?.trim() ?? ''\n const label = normalizeBrTags(rawDividerLabel)\n blockStack[blockStack.length - 1]!.dividers.push({\n index: diagram.messages.length,\n label,\n })\n continue\n }\n\n // --- Box end ---\n // `end` closes the open box unless a block was opened *inside* it,\n // in which case the block (innermost) owns this `end`.\n if (\n line === 'end' &&\n boxCtx.open !== undefined &&\n blockStack.length === boxOpenedAtDepth\n ) {\n boxCtx.open = undefined\n boxOpenLine = undefined\n continue\n }\n\n // --- Block end ---\n if (line === 'end' && blockStack.length > 0) {\n const completed = blockStack.pop()!\n diagram.blocks.push({\n type: completed.type,\n label: completed.label,\n startIndex: completed.startIndex,\n endIndex: Math.max(diagram.messages.length - 1, completed.startIndex),\n dividers: completed.dividers,\n })\n continue\n }\n\n // --- Unmatched end ---\n // Neither branch above claimed this \"end\" — no box and no block is\n // currently open, so there's nothing for it to close. Previously\n // silently ignored (#762); mirrors the \"box cannot be nested\" throw\n // above in surfacing a structural mistake instead of dropping it.\n if (line === 'end') {\n throw new Error(\n `Line ${stmt.line}: Sequence diagram: \"end\" does not match any open block (\"loop\"/\"alt\"/\"opt\"/\"par\"/\"critical\"/\"break\"/\"rect\") or \"box\" — nothing is currently open to close.`,\n )\n }\n\n // --- Standalone activate / deactivate ---\n // \"activate A\" / \"deactivate A\". Mermaid's own grammar expands the `+`/`-`\n // arrow shorthand into exactly this (message, then an activeStart for the\n // recipient / activeEnd for the sender), so record the same event the\n // shorthand implies and let layout.ts feed both through one activation\n // stack. Checked before the message regex: neither keyword contains an\n // arrow token, but an actor literally named `activate` used as a message\n // *source* (`activate->>B: x`) still reaches the message branch, since\n // the `\\s+` here requires whitespace after the keyword.\n const activationMatch = line.match(/^(activate|deactivate)\\s+(.+)$/)\n if (activationMatch) {\n const actorId = activationMatch[2]!.trim()\n ensureActor(diagram, actorIds, boxCtx, actorId, stmt.line)\n diagram.activations.push({\n actorId,\n kind: activationMatch[1] === 'activate' ? 'start' : 'end',\n afterIndex: diagram.messages.length - 1,\n })\n continue\n }\n\n // --- Message ---\n // Patterns: A->>B, A-->>B, A-)B, A--)B, A<<->>B, A<<-->>B, with optional\n // +/- activation. Format: FROM ARROW TO: LABEL\n //\n // FROM/TO are matched with a lazy `.+?` (not `\\S+?`) so an undeclared\n // actor name can contain spaces or internal hyphens — e.g. `cron\n // job->>customer-notifier: hi` — mirroring real Mermaid's own sequence\n // grammar, whose unquoted ACTOR token excludes only the characters that\n // start an arrow/label (`/ \\ + ( ) < - > :`) rather than all whitespace.\n // Because the quantifier is lazy and anchored by the arrow/colon tokens\n // that follow, this still resolves to the same minimal split as before\n // for plain single-word names.\n //\n // Two-pass match (issue #341): the lazy FROM capture stops as soon as\n // *any* position looks like a valid arrow+TO+`:`+LABEL tail, which is a\n // false positive when an unquoted actor name happens to contain a short\n // open/cross arrow substring (`-)`, `--)`, `-x`, `--x` — matched by the\n // `--?[)x]` alternative) ahead of the *real* arrow later in the line —\n // e.g. `foo-x-bar->>baz: hi` mis-split at the embedded `-x` instead of\n // the real `->>`. Those two-char forms are rare and highly ambiguous\n // inside a bare identifier, whereas the \"long\" forms (anything ending\n // in `>`, plus the bidirectional tokens) essentially never occur by\n // accident, since they require a literal `>` character in an unquoted\n // name. So: try the long forms only first, and only fall back to the\n // full alternation (including the short forms) if the line has no long\n // arrow at all — which is what keeps a genuinely short-arrow message\n // like `A-)B: msg` working. This doesn't attempt to disambiguate every\n // theoretically possible collision (e.g. a literal `->` substring\n // embedded before a real `->>`) — see the issue's own \"Scope\" section,\n // which limits the fix to the `-x`/`-)`/`--x`/`--)` substrings.\n const msgMatch =\n line.match(MESSAGE_LONG_ARROW_RE) ?? line.match(MESSAGE_ANY_ARROW_RE)\n if (msgMatch) {\n // Mermaid's own unquoted actor-name grammar excludes `>` and `)` from\n // ever starting an identifier (see the FROM/TO comment above) — so a\n // TO capture starting with either one means the real arrow in the\n // source is longer than what the alternation above actually matched\n // (e.g. \"->>>\" only matches as \"->>\", leaving a stray \">\" to be\n // absorbed into TO as part of a garbled actor name) rather than a\n // genuinely valid, if unusual, message. Reject it with an actionable\n // error instead of silently minting that garbled actor — see the\n // `Alice->>>Bob: Hello` example in issue #762.\n const arrowToken = msgMatch[2]!\n const toFirstChar = msgMatch[4]![0]\n if (toFirstChar === '>' || toFirstChar === ')') {\n throw new Error(\n `Line ${stmt.line}: Malformed sequence-diagram arrow in \"${line}\" — \"${arrowToken}${toFirstChar}\" is not a recognized arrow. Expected one of: ->, -->, ->>, -->>, -x, --x, -), --), <<->>, <<-->>.`,\n )\n }\n pushMessage(\n diagram,\n actorIds,\n boxCtx,\n autonumber,\n msgMatch[1]!,\n msgMatch[2]!,\n msgMatch[3],\n msgMatch[4]!,\n msgMatch[5]!,\n stmt.line,\n )\n const msgIndex = diagram.messages.length - 1\n const msg = diagram.messages[msgIndex]!\n if (pendingCreate !== undefined) {\n if (msg.to !== pendingCreate) {\n throw new Error(\n `Line ${stmt.line}: The created participant ${pendingCreate} does not have an associated creating message after its declaration. Please check the sequence diagram.`,\n )\n }\n findActor(diagram, pendingCreate).createdAt = msgIndex\n pendingCreate = undefined\n }\n if (pendingDestroy !== undefined) {\n if (msg.from !== pendingDestroy && msg.to !== pendingDestroy) {\n throw new Error(\n `Line ${stmt.line}: The destroyed participant ${pendingDestroy} does not have an associated destroying message after its declaration. Please check the sequence diagram.`,\n )\n }\n findActor(diagram, pendingDestroy).destroyedAt = msgIndex\n pendingDestroy = undefined\n }\n continue\n }\n }\n\n // A block/box left open at EOF is a structural mistake, not something to\n // silently accept as \"closed by end of input\" — mirrors the class-diagram\n // parser's unclosed-body check (#761) for the same reason: previously\n // this was silently ignored (#762). Checked innermost-first (the block\n // stack) since an unclosed block is the more specific, more actionable\n // thing to report when both are open.\n if (blockStack.length > 0) {\n const unclosed = blockStack[blockStack.length - 1]!\n throw new Error(\n `Line ${unclosed.line}: Sequence diagram: unclosed \"${unclosed.type}\" block — expected a matching \"end\" before the diagram ends.`,\n )\n }\n if (boxCtx.open !== undefined) {\n const openBox = diagram.boxes[boxCtx.open]!\n throw new Error(\n `Line ${boxOpenLine}: Sequence diagram: unclosed \"box${openBox.label ? ` ${openBox.label}` : ''}\" — expected a matching \"end\" before the diagram ends.`,\n )\n }\n\n return diagram\n}\n\n/**\n * Look up an actor the parser itself already registered (every caller\n * passes an id that went through `ensureActor` or the create branch first,\n * so the miss branch is a parser-invariant violation, not user error).\n */\nfunction findActor(diagram: SequenceDiagram, id: string): Actor {\n const actor = diagram.actors.find((a) => a.id === id)\n if (actor === undefined) {\n /* v8 ignore next */\n throw new Error(`Sequence diagram: unknown actor \"${id}\"`)\n }\n return actor\n}\n\n/** Ensure an actor exists, creating a default participant if not */\nfunction ensureActor(\n diagram: SequenceDiagram,\n actorIds: Set<string>,\n boxCtx: BoxContext,\n id: string,\n lineNumber: number,\n): void {\n if (!actorIds.has(id)) {\n actorIds.add(id)\n diagram.actors.push({ id, label: id, type: 'participant' })\n }\n joinOpenBox(diagram, boxCtx, id, lineNumber)\n}\n\n/**\n * Put `id` into the currently open `box`, if any. A participant already in\n * a *different* box is an error (Mermaid's own wording); one already in\n * this box, or no box open, is a no-op.\n */\nfunction joinOpenBox(\n diagram: SequenceDiagram,\n boxCtx: BoxContext,\n id: string,\n lineNumber: number,\n): void {\n const open = boxCtx.open\n if (open === undefined) return\n const existing = boxCtx.membership.get(id)\n if (existing === open) return\n if (existing !== undefined) {\n const from = diagram.boxes[existing]!.label\n const to = diagram.boxes[open]!.label\n throw new Error(\n `Line ${lineNumber}: A same participant should only be defined in one Box: ${id} can't be in '${from}' and in '${to}' at the same time.`,\n )\n }\n boxCtx.membership.set(id, open)\n diagram.boxes[open]!.actorIds.push(id)\n}\n\n/**\n * Build a `Message` from a matched arrow-message line and push it onto the\n * diagram. Shared by both message regexes in `parseSequenceDiagram` above so\n * the arrow → line-style/arrow-head/bidirectional mapping and `autonumber`\n * bookkeeping can't drift out of sync between them.\n */\nfunction pushMessage(\n diagram: SequenceDiagram,\n actorIds: Set<string>,\n boxCtx: BoxContext,\n autonumber: { enabled: boolean; next: number; step: number },\n from: string,\n arrow: string,\n activationMark: string | undefined,\n to: string,\n rawLabel: string,\n lineNumber: number,\n): void {\n ensureActor(diagram, actorIds, boxCtx, from, lineNumber)\n ensureActor(diagram, actorIds, boxCtx, to, lineNumber)\n\n const bidirectional = arrow === '<<->>' || arrow === '<<-->>'\n const lineStyle = bidirectional\n ? arrow === '<<-->>'\n ? 'dashed'\n : 'solid'\n : arrow.startsWith('--')\n ? 'dashed'\n : 'solid'\n // \">>\" = filled arrow, \")\" or \">\" alone = open arrow, \"x\" = cross (treat as\n // filled). Both bidirectional tokens end in \">>\", so they fall out as filled.\n const arrowHead =\n arrow.includes('>>') || arrow.includes('x') ? 'filled' : 'open'\n // \"x\"/\"--x\" is Mermaid's \"lost message\" terminator — a cross, not a plain\n // filled arrowhead. `arrow.includes('x')` is unambiguous here: the only\n // arrow tokens containing \"x\" are \"-x\"/\"--x\" (see the message regex's\n // `--?[)x]` alternative above), never \"->>\"/\"-->>\" or the bidirectional\n // forms.\n const isLost = arrow.includes('x')\n\n const msg: Message = {\n from,\n to,\n label: normalizeBrTags(rawLabel.trim()),\n lineStyle,\n arrowHead,\n }\n if (isLost) msg.isLost = true\n if (bidirectional) msg.bidirectional = true\n if (activationMark === '+') msg.activate = true\n if (activationMark === '-') msg.deactivate = true\n\n if (autonumber.enabled) {\n msg.seqNumber = autonumber.next\n autonumber.next =\n Math.round((autonumber.next + autonumber.step) * 100) / 100\n }\n\n diagram.messages.push(msg)\n}\n","// ============================================================================\n// Sequence diagram — activation/deactivation balance check\n//\n// A mechanical, deterministic semantic check: every `activate X` (or the\n// `+` arrow shorthand) must be closed by a matching `deactivate X` (or the\n// `-` shorthand) before the diagram ends. This doesn't need an LLM judge —\n// it's the same activation-stack bookkeeping\n// packages/svg-renderer/src/sequence/layout.ts already runs to *draw*\n// activation bars (see its `activationStacks` map), reused here to *report*\n// imbalance instead of silently rendering around it.\n//\n// Motivation (issue #539, split from #536): research on LLM-generated\n// Mermaid sequence diagrams found they fail mostly on activation handling\n// and error/status tracking, not basic syntax — which existing validators\n// (syntax checkers, and agentic-mermaid's structural/geometric/lint\n// `verify` tool: dangling edges, label overflow, duplicate/unreachable\n// nodes) already cover well. Activation balance is a concrete, narrow,\n// mechanically-checkable instance of that gap.\n// ============================================================================\n\nimport type { SequenceDiagram, Message } from './types.ts'\n\nexport type ActivationIssueCode =\n 'DANGLING_ACTIVATION' | 'UNMATCHED_DEACTIVATION'\n\nexport interface ActivationIssue {\n code: ActivationIssueCode\n /** Actor whose activation is unbalanced. */\n actorId: string\n /** Human-readable explanation, including a source-position hint. */\n message: string\n}\n\nexport interface ActivationCheckResult {\n /** true when every activation is balanced (no issues found). */\n ok: boolean\n issues: ActivationIssue[]\n}\n\n/** One `activate`/`deactivate` event in source order, whichever form wrote it. */\ninterface Event {\n actorId: string\n kind: 'start' | 'end'\n afterIndex: number\n}\n\n/**\n * Describe where an event occurred, for a human-readable issue message.\n * Mirrors the `afterIndex` semantics of `SequenceDiagram.activations` (see\n * types.ts): -1 means \"before the first message\", otherwise the index of\n * the message this event follows.\n */\nfunction describePosition(\n diagram: SequenceDiagram,\n afterIndex: number,\n): string {\n if (afterIndex < 0) return 'before the first message'\n const msg: Message | undefined = diagram.messages[afterIndex]\n if (!msg) return 'at an unresolved position'\n const label = msg.label ? `: ${msg.label}` : ''\n return `after message ${afterIndex + 1} (\"${msg.from} -> ${msg.to}${label}\")`\n}\n\n/**\n * Check a parsed sequence diagram for activation/deactivation imbalance.\n *\n * Merges the inline `+`/`-` arrow shorthand (`Message.activate` /\n * `Message.deactivate`) with standalone `activate X` / `deactivate X`\n * statements (`SequenceDiagram.activations`) into one chronological event\n * stream per actor — the same merge `layout.ts` performs to position\n * activation bars — then walks a stack per actor:\n *\n * - A `deactivate` with nothing open on that actor's stack is reported as\n * `UNMATCHED_DEACTIVATION` (the renderer silently ignores this case —\n * see `endActivation` in layout.ts — this check surfaces it instead).\n * - Anything left on a stack once every message has been processed is\n * reported as `DANGLING_ACTIVATION` (the renderer draws these extending\n * to the bottom of the diagram, which usually isn't what the author\n * intended).\n */\nexport function checkActivationBalance(\n diagram: SequenceDiagram,\n): ActivationCheckResult {\n // Standalone activations, grouped by the message index they follow — same\n // grouping layout.ts builds for its `activationEventsByAfterIndex`.\n const standaloneByAfterIndex = new Map<number, typeof diagram.activations>()\n for (const event of diagram.activations) {\n const list = standaloneByAfterIndex.get(event.afterIndex) ?? []\n list.push(event)\n standaloneByAfterIndex.set(event.afterIndex, list)\n }\n\n const events: Event[] = []\n function pushStandalone(afterIndex: number): void {\n for (const event of standaloneByAfterIndex.get(afterIndex) ?? []) {\n events.push({ actorId: event.actorId, kind: event.kind, afterIndex })\n }\n }\n\n // Events before the first message open first, same as layout.ts.\n pushStandalone(-1)\n for (let i = 0; i < diagram.messages.length; i++) {\n const msg = diagram.messages[i]!\n if (msg.activate) {\n events.push({ actorId: msg.to, kind: 'start', afterIndex: i })\n }\n if (msg.deactivate) {\n events.push({ actorId: msg.from, kind: 'end', afterIndex: i })\n }\n pushStandalone(i)\n }\n\n // actorId -> stack of afterIndex values where an activation was opened.\n const stacks = new Map<string, number[]>()\n const issues: ActivationIssue[] = []\n\n for (const event of events) {\n const stack = stacks.get(event.actorId) ?? []\n stacks.set(event.actorId, stack)\n if (event.kind === 'start') {\n stack.push(event.afterIndex)\n continue\n }\n const openedAt = stack.pop()\n if (openedAt === undefined) {\n issues.push({\n code: 'UNMATCHED_DEACTIVATION',\n actorId: event.actorId,\n message:\n `\"deactivate ${event.actorId}\" ${describePosition(diagram, event.afterIndex)} ` +\n `has no matching \"activate ${event.actorId}\" open at that point.`,\n })\n }\n }\n\n for (const [actorId, stack] of stacks) {\n for (const openedAt of stack) {\n issues.push({\n code: 'DANGLING_ACTIVATION',\n actorId,\n message:\n `\"activate ${actorId}\" opened ${describePosition(diagram, openedAt)} ` +\n `is never closed with a matching \"deactivate ${actorId}\".`,\n })\n }\n }\n\n return { ok: issues.length === 0, issues }\n}\n","// ============================================================================\n// Sequence diagram — activation/deactivation auto-fix\n//\n// Extends activation-check.ts (issue #539) with the \"optionally proposes a\n// fix\" half of that issue's scope. Deliberately narrow: only\n// `DANGLING_ACTIVATION` (an `activate X` never closed) is auto-fixable —\n// the mechanical, unambiguous repair is to append a matching `deactivate X`\n// after the diagram's last statement, which is always syntactically valid\n// regardless of block nesting (Mermaid's `deactivate` isn't block-scoped).\n//\n// `UNMATCHED_DEACTIVATION` (a `deactivate X` with nothing open) is\n// deliberately NOT auto-fixed: fixing it means either deleting that\n// specific statement or inserting a preceding `activate X`, and this\n// package doesn't track source line/position per activation statement (see\n// SequenceDiagram.activations — only a message-index `afterIndex`, no raw\n// line offset), so a text-level edit could not target the right occurrence\n// with confidence when the same statement text repeats. Rather than guess\n// and risk mangling unrelated source, this case is reported back as a\n// remaining (unfixable) issue for a human — or a fuzzier LLM-based layer,\n// per the issue's own caveat about unvalidated frontier-model behavior — to\n// resolve.\n// ============================================================================\n\nimport { detectDiagramType, splitStatements } from '@zombie-mermaid/core'\nimport { parseSequenceDiagram } from './parser.ts'\nimport {\n checkActivationBalance,\n type ActivationIssue,\n} from './activation-check.ts'\n\nexport interface ActivationFixResult {\n /** true when the returned diagram has no remaining activation issues. */\n ok: boolean\n /** Original diagram, unchanged if there was nothing fixable. */\n originalDiagram: string\n /**\n * Diagram with a \"deactivate X\" statement appended for every\n * DANGLING_ACTIVATION issue found. Identical to `originalDiagram` when\n * there was nothing to fix.\n */\n fixedDiagram: string\n /** Human-readable description of each fix actually applied. */\n fixesApplied: string[]\n /**\n * Issues that could not be auto-fixed (currently always\n * UNMATCHED_DEACTIVATION — see module header). Empty when `ok` is true.\n */\n remainingIssues: ActivationIssue[]\n}\n\n/**\n * Check a Mermaid sequence diagram for activation/deactivation imbalance\n * and, where mechanically safe, return a corrected version.\n *\n * Throws the same way `parseSequenceDiagram`/`detectDiagramType` do on\n * invalid or non-sequence input — callers (e.g. the MCP tool handler) are\n * expected to catch and translate, matching `check-sequence-activations.ts`'s\n * own error handling.\n */\nexport function fixActivationBalance(\n sourceDiagram: string,\n): ActivationFixResult {\n const diagramType = detectDiagramType(sourceDiagram)\n if (diagramType !== 'sequence') {\n throw new Error(\n 'fixActivationBalance only supports sequence diagrams (source must ' +\n `start with \"sequenceDiagram\"); detected diagram type: \"${diagramType}\".`,\n )\n }\n\n const lines = splitStatements(sourceDiagram)\n const diagram = parseSequenceDiagram(lines)\n const result = checkActivationBalance(diagram)\n\n if (result.ok) {\n return {\n ok: true,\n originalDiagram: sourceDiagram,\n fixedDiagram: sourceDiagram,\n fixesApplied: [],\n remainingIssues: [],\n }\n }\n\n const dangling = result.issues.filter(\n (issue) => issue.code === 'DANGLING_ACTIVATION',\n )\n const remainingIssues = result.issues.filter(\n (issue) => issue.code !== 'DANGLING_ACTIVATION',\n )\n\n const appended = dangling\n .map((issue) => ` deactivate ${issue.actorId}`)\n .join('\\n')\n const fixedDiagram =\n dangling.length === 0\n ? sourceDiagram\n : `${sourceDiagram.replace(/\\s+$/, '')}\\n${appended}`\n\n const fixesApplied = dangling.map(\n (issue) =>\n `Appended \"deactivate ${issue.actorId}\" to close: ${issue.message}`,\n )\n\n return {\n ok: remainingIssues.length === 0,\n originalDiagram: sourceDiagram,\n fixedDiagram,\n fixesApplied,\n remainingIssues,\n }\n}\n","import type { XYChart, XYAxis, XYChartSeries } from './types.ts'\nimport type { Statement } from '@zombie-mermaid/core'\n\n// ============================================================================\n// XY Chart parser\n//\n// Parses Mermaid xychart-beta syntax into a typed XYChart structure.\n//\n// Supported directives:\n// xychart-beta [horizontal]\n// title \"Chart Title\"\n// x-axis [label1, label2, ...] — categorical\n// x-axis min --> max — numeric range\n// x-axis \"Axis Title\" [label1, ...] — with title\n// x-axis \"Axis Title\" min --> max — with title\n// y-axis (same patterns)\n// bar [val1, val2, ...]\n// line [val1, val2, ...]\n// ============================================================================\n\n/**\n * Parse a Mermaid xychart-beta diagram from preprocessed lines.\n * Lines should already be trimmed and comment-stripped.\n */\nexport function parseXYChart(lines: Statement[]): XYChart {\n const xAxis: XYAxis = {}\n const yAxis: XYAxis = {}\n const series: XYChartSeries[] = []\n let title: string | undefined\n let horizontal = false\n\n for (const stmt of lines) {\n const line = stmt.text\n // Header line — detect horizontal\n if (/^xychart(-beta)?\\b/i.test(line)) {\n if (/\\bhorizontal\\b/i.test(line)) horizontal = true\n continue\n }\n\n // Title\n const titleMatch = line.match(/^title\\s+\"([^\"]+)\"/)\n if (titleMatch) {\n title = titleMatch[1]\n continue\n }\n\n // x-axis with categories: x-axis \"Title\" [a, b, c] or x-axis [a, b, c]\n const xCatMatch = line.match(/^x-axis\\s+(?:\"([^\"]*)\"\\s*)?\\[([^\\]]+)\\]/)\n if (xCatMatch) {\n if (xCatMatch[1]) xAxis.title = xCatMatch[1]\n xAxis.categories = splitCategoryList(xCatMatch[2]!).map((s) =>\n unquote(s.trim()),\n )\n continue\n }\n\n // x-axis with range: x-axis \"Title\" min --> max or x-axis min --> max\n const xRangeMatch = line.match(\n /^x-axis\\s+(?:\"([^\"]*)\"\\s+)?(-?\\d+(?:\\.\\d+)?)\\s*-->\\s*(-?\\d+(?:\\.\\d+)?)/,\n )\n if (xRangeMatch) {\n if (xRangeMatch[1]) xAxis.title = xRangeMatch[1]\n xAxis.range = {\n min: parseFloat(xRangeMatch[2]!),\n max: parseFloat(xRangeMatch[3]!),\n }\n continue\n }\n\n // y-axis with range: y-axis \"Title\" min --> max or y-axis min --> max\n const yRangeMatch = line.match(\n /^y-axis\\s+(?:\"([^\"]*)\"\\s+)?(-?\\d+(?:\\.\\d+)?)\\s*-->\\s*(-?\\d+(?:\\.\\d+)?)/,\n )\n if (yRangeMatch) {\n if (yRangeMatch[1]) yAxis.title = yRangeMatch[1]\n yAxis.range = {\n min: parseFloat(yRangeMatch[2]!),\n max: parseFloat(yRangeMatch[3]!),\n }\n continue\n }\n\n // y-axis with just title (no range)\n const yTitleOnly = line.match(/^y-axis\\s+\"([^\"]+)\"\\s*$/)\n if (yTitleOnly) {\n yAxis.title = yTitleOnly[1]\n continue\n }\n\n // bar [...]\n const barMatch = line.match(/^bar\\s+\\[([^\\]]+)\\]/)\n if (barMatch) {\n series.push({\n type: 'bar',\n data: parseNumericArray(barMatch[1]!, 'bar', line, stmt.line),\n })\n continue\n }\n\n // line [...]\n const lineMatch = line.match(/^line\\s+\\[([^\\]]+)\\]/)\n if (lineMatch) {\n series.push({\n type: 'line',\n data: parseNumericArray(lineMatch[1]!, 'line', line, stmt.line),\n })\n continue\n }\n\n // None of the recognized directive shapes matched. A line that doesn't\n // start with any known xychart-beta keyword at all falls through\n // silently below, same as before — but a line that *does* start with\n // one of the five keyworded directives (x-axis/y-axis/bar/line/title)\n // and still failed every pattern above is almost certainly an attempt\n // at that directive with broken syntax (an unclosed bracket, a missing\n // quote, a malformed range). Previously this was silently dropped —\n // the diagram would render with that axis/series just missing and no\n // indication why. Surface an actionable error instead. See issue #541\n // (parser error-message quality audit — this parser had zero throw\n // sites before this pass).\n const keywordMatch = line.match(/^(x-axis|y-axis|bar|line|title)\\b/i)\n if (keywordMatch) {\n throw malformedDirectiveError(\n keywordMatch[1]!.toLowerCase(),\n line,\n stmt.line,\n )\n }\n }\n\n // Auto-derive y-axis range from data if not specified\n if (!yAxis.range && series.length > 0) {\n const allValues = series.flatMap((s) => s.data)\n let min = Math.min(...allValues)\n let max = Math.max(...allValues)\n const span = max - min || 1\n // Add 10% padding\n min = min - span * 0.1\n max = max + span * 0.1\n // Floor to 0 if all values are positive and min is close to 0\n if (min > 0 && min < span * 0.5) min = 0\n yAxis.range = { min, max }\n }\n\n // Fallback y-axis range\n if (!yAxis.range) {\n yAxis.range = { min: 0, max: 100 }\n }\n\n return { title, horizontal, xAxis, yAxis, series }\n}\n\n/**\n * Split a categorical x-axis bracket's comma-separated contents into raw\n * item strings, treating a comma *inside* a double-quoted item as literal\n * text rather than a delimiter. A naive `.split(',')` would break\n * `[\"Jan, Feb\", \"Mar\"]` into three pieces instead of two — quoting a value\n * that itself contains a comma is exactly the case quoting exists for. Each\n * returned raw item is still trimmed and unquoted by the caller.\n */\nfunction splitCategoryList(str: string): string[] {\n const items: string[] = []\n let current = ''\n let inQuotes = false\n for (const char of str) {\n if (char === '\"') {\n inQuotes = !inQuotes\n current += char\n } else if (char === ',' && !inQuotes) {\n items.push(current)\n current = ''\n } else {\n current += char\n }\n }\n items.push(current)\n return items\n}\n\n/**\n * Strip one matching pair of leading/trailing double quotes from a category\n * item, if present — mirroring how an axis *title* capture group already\n * unquotes via its regex. `x-axis [A, B, C]` items are only split and\n * trimmed, so a quoted item like `\"CLI output / logs\"` previously kept its\n * literal quote characters in the rendered label. See issue #1087.\n */\nfunction unquote(value: string): string {\n if (value.length >= 2 && value.startsWith('\"') && value.endsWith('\"')) {\n return value.slice(1, -1)\n }\n return value\n}\n\n/**\n * Parse a `bar`/`line` series' comma-separated bracket contents into\n * numbers, throwing on any element that isn't a valid number instead of\n * silently coercing it to `NaN` (which serializes as `null` and can\n * propagate into layout math with no indication anything was wrong — see\n * issue #541's audit finding on this exact parser).\n */\nfunction parseNumericArray(\n str: string,\n seriesType: 'bar' | 'line',\n line: string,\n lineNumber: number,\n): number[] {\n return str.split(',').map((raw, index) => {\n const trimmed = raw.trim()\n const value = parseFloat(trimmed)\n if (trimmed.length === 0 || Number.isNaN(value)) {\n throw new Error(\n `Line ${lineNumber}: Invalid numeric value ${JSON.stringify(trimmed)} at position ${\n index + 1\n } in \"${line}\". Every value in a ${seriesType} [...] list must be a number.`,\n )\n }\n return value\n })\n}\n\n/** Expected-syntax hint per xychart-beta keyword, for `malformedDirectiveError`. */\nconst XYCHART_DIRECTIVE_HELP: Record<string, string> = {\n 'x-axis':\n 'x-axis [A, B, C] (categories) or x-axis 0 --> 100 (numeric range), either optionally preceded by a quoted title',\n 'y-axis':\n 'y-axis 0 --> 100 (numeric range) or y-axis \"Title\" (title only), optionally preceded by a quoted title before a range',\n bar: 'bar [10, 20, 30] — a comma-separated numeric array in square brackets',\n line: 'line [10, 20, 30] — a comma-separated numeric array in square brackets',\n title: 'title \"Chart Title\" — a double-quoted string',\n}\n\n/**\n * Build the error for a line that starts with a recognized xychart-beta\n * keyword (x-axis/y-axis/bar/line/title) but doesn't match that keyword's\n * expected syntax in any of the forms this parser supports.\n */\nfunction malformedDirectiveError(\n keyword: string,\n line: string,\n lineNumber: number,\n): Error {\n const help = XYCHART_DIRECTIVE_HELP[keyword] ?? keyword\n return new Error(\n `Line ${lineNumber}: Malformed xychart-beta \"${keyword}\" directive: \"${line}\". Expected: ${help}.`,\n )\n}\n","// ============================================================================\n// XY Chart — shared color palette\n//\n// Generates monochromatic shades from the theme accent color.\n// Series 0 = accent (or blue fallback). Series 1+ are darker/lighter\n// shades of the same hue with subtle hue drift to stay in the same\n// color family (like navy ↔ cyan from blue). Hue drifts toward navy for\n// darker shades and toward cyan for lighter ones, so neighbouring series\n// do not collapse into two near-identical blues (#1244).\n//\n// Used by both the SVG and ASCII renderers.\n// ============================================================================\n\n/** Default accent for charts when the theme doesn't provide one. */\nexport const CHART_ACCENT_FALLBACK = '#3b82f6' // blue-500\n\n// ---------------------------------------------------------------------------\n// HSL ↔ Hex conversion\n// ---------------------------------------------------------------------------\n\nfunction hexToHsl(hex: string): [number, number, number] {\n const h = hex.replace('#', '')\n const ri = parseInt(h.substring(0, 2), 16) / 255\n const gi = parseInt(h.substring(2, 4), 16) / 255\n const bi = parseInt(h.substring(4, 6), 16) / 255\n\n const max = Math.max(ri, gi, bi)\n const min = Math.min(ri, gi, bi)\n const l = (max + min) / 2\n\n if (max === min) return [0, 0, l * 100]\n\n const d = max - min\n const s = l > 0.5 ? d / (2 - max - min) : d / (max + min)\n\n let hue: number\n if (max === ri) hue = ((gi - bi) / d + (gi < bi ? 6 : 0)) / 6\n else if (max === gi) hue = ((bi - ri) / d + 2) / 6\n else hue = ((ri - gi) / d + 4) / 6\n\n return [hue * 360, s * 100, l * 100]\n}\n\nfunction hslToHex(h: number, s: number, l: number): string {\n const si = s / 100\n const li = l / 100\n\n const c = (1 - Math.abs(2 * li - 1)) * si\n const x = c * (1 - Math.abs(((h / 60) % 2) - 1))\n const m = li - c / 2\n\n let r: number, g: number, b: number\n if (h < 60) {\n r = c\n g = x\n b = 0\n } else if (h < 120) {\n r = x\n g = c\n b = 0\n } else if (h < 180) {\n r = 0\n g = c\n b = x\n } else if (h < 240) {\n r = 0\n g = x\n b = c\n } else if (h < 300) {\n r = x\n g = 0\n b = c\n } else {\n r = c\n g = 0\n b = x\n }\n\n const toHex = (v: number) =>\n Math.round((v + m) * 255)\n .toString(16)\n .padStart(2, '0')\n return `#${toHex(r)}${toHex(g)}${toHex(b)}`\n}\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\n/** Check whether a string is a valid 6-digit hex color (e.g. \"#3b82f6\"). */\nexport function isValidHex(color: string): boolean {\n return /^#[0-9a-fA-F]{6}$/.test(color)\n}\n\n/**\n * Detect whether a background color is dark (lightness < 50%).\n */\nexport function isDarkBackground(bgHex: string): boolean {\n return hexToHsl(bgHex)[2] < 50\n}\n\n/**\n * Get the hex color for a series index.\n * Index 0 returns the accent color as-is.\n * Index 1+ alternate between darker and lighter shades of the same hue\n * with subtle hue drift (-8° per tier for darker, -12° for lighter) to stay in the same family.\n *\n * When `bgColor` is provided, shade direction adapts to the background:\n * - Light bg: odd = darker, even = lighter (default)\n * - Dark bg: odd = lighter, even = darker (so shades stay visible)\n */\nexport function getSeriesColor(\n index: number,\n accentColor: string,\n bgColor?: string,\n): string {\n if (index === 0) return accentColor\n // Fall back to defaults when inputs aren't valid hex (e.g. CSS variable refs like \"var(--accent)\")\n const safeAccent = isValidHex(accentColor)\n ? accentColor\n : CHART_ACCENT_FALLBACK\n const safeBg = bgColor && isValidHex(bgColor) ? bgColor : undefined\n const [h, s] = hexToHsl(safeAccent)\n const chartS = Math.max(55, Math.min(85, s))\n\n const tier = Math.ceil(index / 2)\n const oddIndex = index % 2 === 1\n\n // On dark backgrounds, flip: odd = lighter, even = darker\n const dark = safeBg && isDarkBackground(safeBg) ? !oddIndex : oddIndex\n const l = dark ? Math.max(25, 48 - tier * 13) : Math.min(78, 55 + tier * 11)\n\n // Subtle hue drift: darker shades shift slightly negative (toward navy),\n // lighter shades shift negative too (toward sky/cyan). The lighter shade\n // used to drift positive (toward indigo), which landed series 2 within a\n // few ΔE of series 0 (#1244); drifting the other way keeps the three first\n // series distinct in hue as well as lightness.\n const hShift = (dark ? -8 : -12) * tier\n const newH = (((h + hShift) % 360) + 360) % 360\n\n return hslToHex(newH, chartS, l)\n}\n","// ============================================================================\n// zombie-mermaid — expanded node syntax A@{ shape: ..., label: ... }\n//\n// Mermaid v11.3.0 added a metadata form for node definitions:\n//\n// A@{ shape: rounded, label: \"Start here\" }\n// B@{ shape: doc }\n// C@{ icon: \"fa:bell\", form: \"circle\", label: \"Alert\" }\n// D@{ img: \"https://example.com/a.png\", label: \"Diagram\", w: 120, h: 80 }\n//\n// It exposes ~30 semantic shape names (many with several aliases) that the\n// classic bracket syntax cannot express. This module owns both halves of\n// supporting it: parsing the metadata block, and resolving a semantic name to\n// the geometry this renderer draws.\n// ============================================================================\n\nimport type { NodeShape } from '@zombie-mermaid/core'\n\n/**\n * Every documented Mermaid shape name (and alias) mapped to the geometry this\n * renderer draws for it.\n *\n * Mermaid's list is deliberately semantic — `database`, `manual-input`,\n * `paper-tape` — while a renderer only has so many distinct outlines. Names\n * that share an outline map to the same `NodeShape`; that is a rendering\n * choice, not a parse failure, and it is documented in docs/diagrams.md so\n * the collapse is discoverable rather than surprising.\n *\n * Keys are lowercase; lookup lowercases its input.\n */\nconst SHAPE_ALIASES: Record<string, NodeShape> = {\n // --- Rectangle family ---\n rect: 'rectangle',\n rectangle: 'rectangle',\n proc: 'rectangle',\n process: 'rectangle',\n 'normal-rect': 'rectangle',\n\n // --- Rounded ---\n rounded: 'rounded',\n event: 'rounded',\n 'rounded-rect': 'rounded',\n\n // --- Stadium / terminal ---\n stadium: 'stadium',\n pill: 'stadium',\n terminal: 'stadium',\n\n // --- Subroutine / framed rectangle ---\n subproc: 'subroutine',\n subprocess: 'subroutine',\n subroutine: 'subroutine',\n 'framed-rectangle': 'subroutine',\n 'fr-rect': 'subroutine',\n\n // --- Cylinder / database ---\n cyl: 'cylinder',\n cylinder: 'cylinder',\n db: 'cylinder',\n database: 'cylinder',\n 'h-cyl': 'cylinder',\n das: 'cylinder',\n 'horizontal-cylinder': 'cylinder',\n 'lin-cyl': 'cylinder',\n 'lined-cylinder': 'cylinder',\n disk: 'cylinder',\n\n // --- Circle ---\n circ: 'circle',\n circle: 'circle',\n start: 'circle',\n\n // --- Double circle ---\n 'dbl-circ': 'doublecircle',\n 'double-circle': 'doublecircle',\n stop: 'doublecircle',\n\n // --- Filled / crossed circles ---\n 'f-circ': 'filled-circle',\n 'filled-circle': 'filled-circle',\n junction: 'filled-circle',\n 'cross-circ': 'crossed-circle',\n 'crossed-circle': 'crossed-circle',\n summary: 'crossed-circle',\n\n // --- Diamond / decision ---\n diam: 'diamond',\n diamond: 'diamond',\n decision: 'diamond',\n question: 'diamond',\n\n // --- Hexagon / prepare ---\n hex: 'hexagon',\n hexagon: 'hexagon',\n prepare: 'hexagon',\n\n // --- Asymmetric / odd ---\n odd: 'asymmetric',\n 'rect-left-inv-arrow': 'asymmetric',\n\n // --- Parallelograms ---\n 'lean-r': 'parallelogram',\n 'lean-right': 'parallelogram',\n 'in-out': 'parallelogram',\n 'lean-l': 'parallelogram-alt',\n 'lean-left': 'parallelogram-alt',\n 'out-in': 'parallelogram-alt',\n\n // --- Trapezoids ---\n 'trap-b': 'trapezoid',\n 'trapezoid-bottom': 'trapezoid',\n priority: 'trapezoid',\n 'trap-t': 'trapezoid-alt',\n 'trapezoid-top': 'trapezoid-alt',\n manual: 'trapezoid-alt',\n 'curv-trap': 'trapezoid-alt',\n 'curved-trapezoid': 'trapezoid-alt',\n display: 'trapezoid-alt',\n\n // --- Document family ---\n doc: 'document',\n document: 'document',\n 'lin-doc': 'document',\n 'lined-document': 'document',\n 'tag-doc': 'document',\n 'tagged-document': 'document',\n docs: 'stacked-document',\n documents: 'stacked-document',\n 'st-doc': 'stacked-document',\n 'stacked-document': 'stacked-document',\n\n // --- Card / notched rectangle ---\n 'notch-rect': 'card',\n card: 'card',\n 'notched-rectangle': 'card',\n\n // --- Lined / divided / tagged rectangles ---\n 'lin-rect': 'lined-process',\n 'lined-rectangle': 'lined-process',\n 'lin-proc': 'lined-process',\n 'shaded-process': 'lined-process',\n 'div-rect': 'divided-process',\n 'divided-rectangle': 'divided-process',\n 'div-proc': 'divided-process',\n 'tag-rect': 'rectangle',\n 'tagged-rectangle': 'rectangle',\n 'tag-proc': 'rectangle',\n procs: 'stacked-process',\n processes: 'stacked-process',\n 'st-rect': 'stacked-process',\n 'stacked-rectangle': 'stacked-process',\n\n // --- Triangles ---\n tri: 'triangle',\n triangle: 'triangle',\n extract: 'triangle',\n 'flip-tri': 'flipped-triangle',\n 'flipped-triangle': 'flipped-triangle',\n 'manual-file': 'flipped-triangle',\n\n // --- Window pane / internal storage ---\n 'win-pane': 'window-pane',\n 'window-pane': 'window-pane',\n 'internal-storage': 'window-pane',\n\n // --- Fork / join ---\n fork: 'fork-join',\n join: 'fork-join',\n 'long-rect': 'fork-join',\n\n // --- Notched pentagon / loop limit ---\n 'notch-pent': 'notched-pentagon',\n 'loop-limit': 'notched-pentagon',\n 'notched-pentagon': 'notched-pentagon',\n\n // --- Sloped rectangle / manual input ---\n 'sl-rect': 'sloped-rectangle',\n 'sloped-rectangle': 'sloped-rectangle',\n 'manual-input': 'sloped-rectangle',\n\n // --- Flag / paper tape ---\n flag: 'flag',\n 'paper-tape': 'flag',\n\n // --- Bow-tie rectangle / stored data ---\n 'bow-rect': 'bow-tie-rectangle',\n 'bow-tie-rectangle': 'bow-tie-rectangle',\n 'stored-data': 'bow-tie-rectangle',\n\n // --- Delay / half-rounded rectangle ---\n delay: 'half-rounded-rectangle',\n 'half-rounded-rectangle': 'half-rounded-rectangle',\n\n // --- Braces / comment ---\n brace: 'brace',\n 'brace-l': 'brace',\n comment: 'brace',\n 'brace-r': 'brace-right',\n braces: 'braces',\n\n // --- Lightning bolt / communication link ---\n bolt: 'bolt',\n 'com-link': 'bolt',\n 'lightning-bolt': 'bolt',\n\n // --- Bare text, no outline ---\n text: 'text',\n\n // --- Anchor / hidden ---\n anchor: 'anchor',\n}\n\n/**\n * Resolve a Mermaid shape name to the geometry this renderer draws.\n * Returns `undefined` for an unrecognized name so the caller can decide\n * whether to fall back or report it.\n */\nexport function resolveShapeName(name: string): NodeShape | undefined {\n return SHAPE_ALIASES[name.trim().toLowerCase()]\n}\n\n/** Every shape name this renderer accepts, for documentation and tests. */\nexport function knownShapeNames(): string[] {\n return Object.keys(SHAPE_ALIASES).sort()\n}\n\n/** The metadata a `@{ ... }` block can carry. */\nexport interface ExpandedNodeMeta {\n shape?: string\n label?: string\n icon?: string\n img?: string\n /** Outline drawn around an icon or image: `square`, `circle`, `rounded`. */\n form?: string\n /** Explicit width/height for an image node. */\n w?: string\n h?: string\n /** Image fit mode Mermaid accepts alongside `img`. */\n constraint?: string\n /** Any other key seen, preserved rather than dropped. */\n [key: string]: string | undefined\n}\n\n/**\n * Parse the body of a `@{ ... }` block into key/value pairs.\n *\n * The body is a comma-separated list of `key: value`. Values may be quoted\n * with `\"` or `'`, and a quoted value may contain commas, colons, and braces\n * — which is why this is a scanner rather than a `split(',')`.\n *\n * Mermaid also accepts a bare value with no key as shorthand for the shape\n * (`A@{ rounded }`); that is handled by the caller, which sees an entry with\n * an empty key.\n */\nexport function parseExpandedMeta(body: string): ExpandedNodeMeta {\n const meta: ExpandedNodeMeta = {}\n\n for (const entry of splitTopLevel(body, ',')) {\n const trimmed = entry.trim()\n if (trimmed.length === 0) continue\n\n const colon = indexOfTopLevel(trimmed, ':')\n if (colon === -1) {\n // Bare value — Mermaid's shorthand for `shape: <value>`.\n meta.shape ??= stripQuotes(trimmed)\n continue\n }\n\n const key = trimmed.slice(0, colon).trim().toLowerCase()\n const value = stripQuotes(trimmed.slice(colon + 1).trim())\n if (key.length > 0) meta[key] = value\n }\n\n return meta\n}\n\n/** Split on `separator`, ignoring separators inside quotes. */\nfunction splitTopLevel(text: string, separator: string): string[] {\n const parts: string[] = []\n let current = ''\n let quote: string | null = null\n\n for (const ch of text) {\n if (quote !== null) {\n current += ch\n if (ch === quote) quote = null\n continue\n }\n if (ch === '\"' || ch === \"'\") {\n quote = ch\n current += ch\n continue\n }\n if (ch === separator) {\n parts.push(current)\n current = ''\n continue\n }\n current += ch\n }\n\n parts.push(current)\n return parts\n}\n\n/** Index of the first `needle` outside quotes, or -1. */\nfunction indexOfTopLevel(text: string, needle: string): number {\n let quote: string | null = null\n for (let i = 0; i < text.length; i++) {\n const ch = text[i]!\n if (quote !== null) {\n if (ch === quote) quote = null\n continue\n }\n if (ch === '\"' || ch === \"'\") {\n quote = ch\n continue\n }\n if (ch === needle) return i\n }\n return -1\n}\n\n/** Remove one layer of matching wrapping quotes. */\nfunction stripQuotes(value: string): string {\n if (\n value.length >= 2 &&\n ((value.startsWith('\"') && value.endsWith('\"')) ||\n (value.startsWith(\"'\") && value.endsWith(\"'\")))\n ) {\n return value.slice(1, -1)\n }\n return value\n}\n\n/**\n * Find the `@{ ... }` block at the start of `text`, returning its body and\n * total length.\n *\n * Brace matching is depth-aware and quote-aware, so a label containing `}`\n * (`A@{ label: \"a } b\" }`) does not terminate the block early. Returns\n * `undefined` if `text` does not open with `@{` or the block is unterminated.\n */\nexport function matchExpandedBlock(\n text: string,\n): { body: string; length: number } | undefined {\n if (!text.startsWith('@{')) return undefined\n\n let depth = 0\n let quote: string | null = null\n\n for (let i = 1; i < text.length; i++) {\n const ch = text[i]!\n\n if (quote !== null) {\n if (ch === quote) quote = null\n continue\n }\n if (ch === '\"' || ch === \"'\") {\n quote = ch\n continue\n }\n if (ch === '{') {\n depth++\n continue\n }\n if (ch === '}') {\n depth--\n if (depth === 0) {\n return { body: text.slice(2, i), length: i + 1 }\n }\n }\n }\n\n return undefined\n}\n"],"mappings":"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"}