@zombie-mermaid/ascii-renderer 2.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +22 -0
- package/dist/index.cjs +8 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +146 -0
- package/dist/index.d.ts +146 -0
- package/dist/index.js +5392 -0
- package/dist/index.js.map +1 -0
- package/package.json +36 -0
- package/src/__tests__/ascii-arrowhead-direction-1083.test.ts +51 -0
- package/src/__tests__/ascii-canvas-first-claim-wins-1093.test.ts +89 -0
- package/src/__tests__/ascii-canvas-size-offset-1093.test.ts +95 -0
- package/src/__tests__/ascii-canvas-write.test.ts +132 -0
- package/src/__tests__/ascii-chain-edge-overlap-1067.test.ts +100 -0
- package/src/__tests__/ascii-charset-border-junctions.test.ts +63 -0
- package/src/__tests__/ascii-cjk-width.test.ts +150 -0
- package/src/__tests__/ascii-class-box-occupancy.test.ts +462 -0
- package/src/__tests__/ascii-class-column-width-488-489.test.ts +639 -0
- package/src/__tests__/ascii-class-cross-level-jog-corruption.test.ts +187 -0
- package/src/__tests__/ascii-class-detour-label-routing-487.test.ts +114 -0
- package/src/__tests__/ascii-class-diagram-compartments.test.ts +70 -0
- package/src/__tests__/ascii-class-label-box-collision.test.ts +60 -0
- package/src/__tests__/ascii-class-label-row-collision-531.test.ts +135 -0
- package/src/__tests__/ascii-class-label-territory-row-awareness.test.ts +57 -0
- package/src/__tests__/ascii-class-padding.test.ts +104 -0
- package/src/__tests__/ascii-class-parent-alignment-971.test.ts +139 -0
- package/src/__tests__/ascii-class-parent-alignment-972.test.ts +208 -0
- package/src/__tests__/ascii-class-reciprocal-relationships-448.test.ts +169 -0
- package/src/__tests__/ascii-combining-mark-width.test.ts +70 -0
- package/src/__tests__/ascii-coords-overlay.test.ts +64 -0
- package/src/__tests__/ascii-decision-lr-box-start.test.ts +106 -0
- package/src/__tests__/ascii-display-width-unit.test.ts +154 -0
- package/src/__tests__/ascii-draw-arrows-coverage.test.ts +224 -0
- package/src/__tests__/ascii-draw-arrows-single-point-path.test.ts +105 -0
- package/src/__tests__/ascii-edge-bundling-rank-violation-454.test.ts +72 -0
- package/src/__tests__/ascii-edge-ending-glyphs.test.ts +206 -0
- package/src/__tests__/ascii-edge-label-diagonal-fallback-418.test.ts +95 -0
- package/src/__tests__/ascii-edge-routing-fixes.test.ts +178 -0
- package/src/__tests__/ascii-edge-routing-single-point-path.test.ts +103 -0
- package/src/__tests__/ascii-edge-style-consistency-1067.test.ts +89 -0
- package/src/__tests__/ascii-edge-styles.test.ts +149 -0
- package/src/__tests__/ascii-emoji-cluster-width.test.ts +101 -0
- package/src/__tests__/ascii-er-box-occupancy.test.ts +251 -0
- package/src/__tests__/ascii-er-cardinality.test.ts +40 -0
- package/src/__tests__/ascii-er-corner-glyphs.test.ts +249 -0
- package/src/__tests__/ascii-er-jog-stray-line.test.ts +111 -0
- package/src/__tests__/ascii-er-label-padding.test.ts +118 -0
- package/src/__tests__/ascii-er-padding.test.ts +114 -0
- package/src/__tests__/ascii-er-relationship-label-corruption-350.test.ts +458 -0
- package/src/__tests__/ascii-er-relationship-overwrite.test.ts +325 -0
- package/src/__tests__/ascii-er-stray-connectors.test.ts +344 -0
- package/src/__tests__/ascii-er-unrelated-stem-separation-411.test.ts +98 -0
- package/src/__tests__/ascii-er-vertical-one-marker.test.ts +110 -0
- package/src/__tests__/ascii-label-line-terminal-fallback.test.ts +168 -0
- package/src/__tests__/ascii-lane-search.test.ts +207 -0
- package/src/__tests__/ascii-multibox-cjk-width.test.ts +184 -0
- package/src/__tests__/ascii-multiline.test.ts +288 -0
- package/src/__tests__/ascii-padding-edge-cases.test.ts +154 -0
- package/src/__tests__/ascii-pathfinder-route-edge.test.ts +184 -0
- package/src/__tests__/ascii-sequence-alt-else-label.test.ts +236 -0
- package/src/__tests__/ascii-sequence-block-wall-clearance.test.ts +217 -0
- package/src/__tests__/ascii-sequence-box-group.test.ts +159 -0
- package/src/__tests__/ascii-sequence-cjk-width.test.ts +235 -0
- package/src/__tests__/ascii-sequence-create-destroy.test.ts +114 -0
- package/src/__tests__/ascii-sequence-form-invariants.test.ts +432 -0
- package/src/__tests__/ascii-sequence-mermaid-parity.test.ts +219 -0
- package/src/__tests__/ascii-sequence-notes.test.ts +61 -0
- package/src/__tests__/ascii-sequence-padding.test.ts +119 -0
- package/src/__tests__/ascii-sequence-self-arrow.test.ts +206 -0
- package/src/__tests__/ascii-shape-diamond.test.ts +38 -0
- package/src/__tests__/ascii-shape-rectangle.test.ts +258 -0
- package/src/__tests__/ascii-shape-rounded.test.ts +36 -0
- package/src/__tests__/ascii-shapes-circle.test.ts +41 -0
- package/src/__tests__/ascii-shapes-hexagon.test.ts +42 -0
- package/src/__tests__/ascii-shapes-special.test.ts +344 -0
- package/src/__tests__/ascii-shapes-stadium.test.ts +217 -0
- package/src/__tests__/ascii-shapes-state.test.ts +224 -0
- package/src/__tests__/ascii-state-bidirectional-label-swap-530.test.ts +130 -0
- package/src/__tests__/ascii-subgraph-direction-honored-445.test.ts +90 -0
- package/src/__tests__/ascii-subgraph-label-border-clip.test.ts +152 -0
- package/src/__tests__/ascii-subgraph-title-padding.test.ts +77 -0
- package/src/__tests__/ascii-territory-unit.test.ts +219 -0
- package/src/__tests__/ascii-validate.test.ts +220 -0
- package/src/__tests__/ascii.test.ts +325 -0
- package/src/__tests__/class-arrow-directions.test.ts +505 -0
- package/src/__tests__/draw-lines.test.ts +93 -0
- package/src/__tests__/edge-cell-styles.test.ts +278 -0
- package/src/__tests__/grid-occupancy.test.ts +240 -0
- package/src/__tests__/helpers/ascii-form.ts +142 -0
- package/src/__tests__/helpers/terminal-display-width.ts +74 -0
- package/src/__tests__/pathfinder.test.ts +239 -0
- package/src/__tests__/testdata/ascii/ampersand_lhs.txt +18 -0
- package/src/__tests__/testdata/ascii/ampersand_lhs_and_rhs.txt +18 -0
- package/src/__tests__/testdata/ascii/ampersand_rhs.txt +18 -0
- package/src/__tests__/testdata/ascii/ampersand_td_fanin.txt +18 -0
- package/src/__tests__/testdata/ascii/ampersand_td_fanout.txt +18 -0
- package/src/__tests__/testdata/ascii/ampersand_without_edge.txt +18 -0
- package/src/__tests__/testdata/ascii/back_reference_from_child.txt +10 -0
- package/src/__tests__/testdata/ascii/backlink_from_bottom.txt +22 -0
- package/src/__tests__/testdata/ascii/backlink_from_top.txt +22 -0
- package/src/__tests__/testdata/ascii/backlink_with_short_y_padding.txt +20 -0
- package/src/__tests__/testdata/ascii/cls_all_relationships.txt +19 -0
- package/src/__tests__/testdata/ascii/cls_annotation.txt +29 -0
- package/src/__tests__/testdata/ascii/cls_association.txt +14 -0
- package/src/__tests__/testdata/ascii/cls_basic.txt +15 -0
- package/src/__tests__/testdata/ascii/cls_dependency.txt +14 -0
- package/src/__tests__/testdata/ascii/cls_inheritance.txt +20 -0
- package/src/__tests__/testdata/ascii/cls_methods.txt +21 -0
- package/src/__tests__/testdata/ascii/comments.txt +23 -0
- package/src/__tests__/testdata/ascii/custom_padding.txt +10 -0
- package/src/__tests__/testdata/ascii/duplicate_labels.txt +19 -0
- package/src/__tests__/testdata/ascii/er_attributes.txt +21 -0
- package/src/__tests__/testdata/ascii/er_basic.txt +8 -0
- package/src/__tests__/testdata/ascii/er_identifying.txt +18 -0
- package/src/__tests__/testdata/ascii/flowchart_tb_simple.txt +29 -0
- package/src/__tests__/testdata/ascii/graph_bt_direction.txt +28 -0
- package/src/__tests__/testdata/ascii/graph_tb_direction.txt +26 -0
- package/src/__tests__/testdata/ascii/nested_subgraphs_with_labels.txt +36 -0
- package/src/__tests__/testdata/ascii/preserve_order_of_definition.txt +23 -0
- package/src/__tests__/testdata/ascii/self_reference.txt +10 -0
- package/src/__tests__/testdata/ascii/self_reference_with_edge.txt +10 -0
- package/src/__tests__/testdata/ascii/seq_basic.txt +17 -0
- package/src/__tests__/testdata/ascii/seq_multiple_messages.txt +25 -0
- package/src/__tests__/testdata/ascii/seq_self_message.txt +18 -0
- package/src/__tests__/testdata/ascii/single_node.txt +8 -0
- package/src/__tests__/testdata/ascii/single_node_longer_name.txt +8 -0
- package/src/__tests__/testdata/ascii/subgraph_complex_mixed.txt +38 -0
- package/src/__tests__/testdata/ascii/subgraph_complex_nested.txt +49 -0
- package/src/__tests__/testdata/ascii/subgraph_direction_override.txt +47 -0
- package/src/__tests__/testdata/ascii/subgraph_empty.txt +10 -0
- package/src/__tests__/testdata/ascii/subgraph_mixed_nodes.txt +20 -0
- package/src/__tests__/testdata/ascii/subgraph_mixed_nodes_td.txt +48 -0
- package/src/__tests__/testdata/ascii/subgraph_multiple_edges.txt +32 -0
- package/src/__tests__/testdata/ascii/subgraph_multiple_nodes.txt +16 -0
- package/src/__tests__/testdata/ascii/subgraph_nested.txt +24 -0
- package/src/__tests__/testdata/ascii/subgraph_nested_with_external.txt +30 -0
- package/src/__tests__/testdata/ascii/subgraph_node_outside_lr.txt +17 -0
- package/src/__tests__/testdata/ascii/subgraph_single_node.txt +16 -0
- package/src/__tests__/testdata/ascii/subgraph_td_direction.txt +26 -0
- package/src/__tests__/testdata/ascii/subgraph_td_multiple.txt +44 -0
- package/src/__tests__/testdata/ascii/subgraph_td_multiple_paddingy.txt +42 -0
- package/src/__tests__/testdata/ascii/subgraph_three_levels_nested.txt +32 -0
- package/src/__tests__/testdata/ascii/subgraph_three_separate.txt +24 -0
- package/src/__tests__/testdata/ascii/subgraph_two_separate.txt +20 -0
- package/src/__tests__/testdata/ascii/subgraph_with_labels.txt +20 -0
- package/src/__tests__/testdata/ascii/three_nodes.txt +9 -0
- package/src/__tests__/testdata/ascii/three_nodes_single_line.txt +8 -0
- package/src/__tests__/testdata/ascii/two_layer_single_graph.txt +19 -0
- package/src/__tests__/testdata/ascii/two_layer_single_graph_longer_names.txt +19 -0
- package/src/__tests__/testdata/ascii/two_nodes_linked.txt +8 -0
- package/src/__tests__/testdata/ascii/two_nodes_longer_names.txt +8 -0
- package/src/__tests__/testdata/ascii/two_root_nodes.txt +19 -0
- package/src/__tests__/testdata/ascii/two_root_nodes_longer_names.txt +19 -0
- package/src/__tests__/testdata/ascii/two_single_root_nodes.txt +19 -0
- package/src/__tests__/testdata/unicode/ampersand_lhs.txt +18 -0
- package/src/__tests__/testdata/unicode/ampersand_lhs_and_rhs.txt +18 -0
- package/src/__tests__/testdata/unicode/ampersand_rhs.txt +18 -0
- package/src/__tests__/testdata/unicode/ampersand_without_edge.txt +18 -0
- package/src/__tests__/testdata/unicode/back_reference_from_child.txt +10 -0
- package/src/__tests__/testdata/unicode/backlink_from_bottom.txt +22 -0
- package/src/__tests__/testdata/unicode/backlink_from_top.txt +22 -0
- package/src/__tests__/testdata/unicode/cls_all_relationships.txt +19 -0
- package/src/__tests__/testdata/unicode/cls_annotation.txt +29 -0
- package/src/__tests__/testdata/unicode/cls_association.txt +14 -0
- package/src/__tests__/testdata/unicode/cls_basic.txt +15 -0
- package/src/__tests__/testdata/unicode/cls_dependency.txt +14 -0
- package/src/__tests__/testdata/unicode/cls_inheritance.txt +20 -0
- package/src/__tests__/testdata/unicode/cls_methods.txt +21 -0
- package/src/__tests__/testdata/unicode/comments.txt +23 -0
- package/src/__tests__/testdata/unicode/duplicate_labels.txt +19 -0
- package/src/__tests__/testdata/unicode/er_attributes.txt +21 -0
- package/src/__tests__/testdata/unicode/er_basic.txt +8 -0
- package/src/__tests__/testdata/unicode/er_identifying.txt +18 -0
- package/src/__tests__/testdata/unicode/graph_bt_direction.txt +28 -0
- package/src/__tests__/testdata/unicode/preserve_order_of_definition.txt +23 -0
- package/src/__tests__/testdata/unicode/self_reference.txt +10 -0
- package/src/__tests__/testdata/unicode/self_reference_with_edge.txt +10 -0
- package/src/__tests__/testdata/unicode/seq_basic.txt +17 -0
- package/src/__tests__/testdata/unicode/seq_multiple_messages.txt +25 -0
- package/src/__tests__/testdata/unicode/seq_self_message.txt +18 -0
- package/src/__tests__/testdata/unicode/single_node.txt +8 -0
- package/src/__tests__/testdata/unicode/single_node_longer_name.txt +8 -0
- package/src/__tests__/testdata/unicode/three_nodes.txt +9 -0
- package/src/__tests__/testdata/unicode/three_nodes_single_line.txt +8 -0
- package/src/__tests__/testdata/unicode/two_layer_single_graph.txt +19 -0
- package/src/__tests__/testdata/unicode/two_layer_single_graph_longer_names.txt +19 -0
- package/src/__tests__/testdata/unicode/two_nodes_linked.txt +8 -0
- package/src/__tests__/testdata/unicode/two_nodes_longer_names.txt +8 -0
- package/src/__tests__/testdata/unicode/two_root_nodes.txt +19 -0
- package/src/__tests__/testdata/unicode/two_root_nodes_longer_names.txt +19 -0
- package/src/__tests__/testdata/unicode/two_single_root_nodes.txt +19 -0
- package/src/__tests__/xychart-ascii.test.ts +376 -0
- package/src/ansi.ts +490 -0
- package/src/canvas.ts +757 -0
- package/src/class-diagram.ts +2001 -0
- package/src/converter.ts +446 -0
- package/src/coords.ts +58 -0
- package/src/display-width.ts +151 -0
- package/src/draw-arrows.ts +593 -0
- package/src/draw-boxes.ts +267 -0
- package/src/draw-bundles.ts +611 -0
- package/src/draw-lines.ts +174 -0
- package/src/draw-subgraphs.ts +108 -0
- package/src/draw.ts +350 -0
- package/src/edge-bundling.ts +435 -0
- package/src/edge-cell-styles.ts +209 -0
- package/src/edge-routing.ts +1070 -0
- package/src/er-diagram.ts +1488 -0
- package/src/flowchart.ts +94 -0
- package/src/grid-occupancy.ts +234 -0
- package/src/grid.ts +1309 -0
- package/src/hyperlinks.ts +248 -0
- package/src/index.ts +163 -0
- package/src/lane-search.ts +68 -0
- package/src/multiline-utils.ts +82 -0
- package/src/pathfinder.ts +448 -0
- package/src/registry.ts +88 -0
- package/src/sequence.ts +1318 -0
- package/src/shapes/circle.ts +31 -0
- package/src/shapes/corners.ts +273 -0
- package/src/shapes/diamond.ts +31 -0
- package/src/shapes/hexagon.ts +35 -0
- package/src/shapes/index.ts +123 -0
- package/src/shapes/rectangle.ts +199 -0
- package/src/shapes/rounded.ts +31 -0
- package/src/shapes/special.ts +360 -0
- package/src/shapes/stadium.ts +122 -0
- package/src/shapes/state.ts +204 -0
- package/src/shapes/types.ts +78 -0
- package/src/territory.ts +136 -0
- package/src/types.ts +454 -0
- package/src/validate.ts +189 -0
- package/src/xychart.ts +1085 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../src/hyperlinks.ts","../src/ansi.ts","../src/display-width.ts","../src/canvas.ts","../src/xychart.ts","../src/types.ts","../src/grid-occupancy.ts","../src/pathfinder.ts","../src/edge-bundling.ts","../src/edge-cell-styles.ts","../src/multiline-utils.ts","../src/shapes/corners.ts","../src/shapes/rectangle.ts","../src/shapes/diamond.ts","../src/shapes/circle.ts","../src/shapes/state.ts","../src/shapes/rounded.ts","../src/shapes/stadium.ts","../src/shapes/hexagon.ts","../src/shapes/special.ts","../src/shapes/index.ts","../src/grid.ts","../src/edge-routing.ts","../src/draw-lines.ts","../src/draw-arrows.ts","../src/draw-bundles.ts","../src/draw-subgraphs.ts","../src/draw-boxes.ts","../src/draw.ts","../src/lane-search.ts","../src/er-diagram.ts","../src/sequence.ts","../src/territory.ts","../src/class-diagram.ts","../../../src/parser.ts","../src/converter.ts","../src/flowchart.ts","../src/registry.ts","../src/coords.ts","../src/index.ts"],"sourcesContent":["// ============================================================================\n// ASCII renderer — OSC 8 terminal hyperlinks (opt-in)\n//\n// Modern terminals (iTerm2, WezTerm, kitty, Windows Terminal, VTE-based\n// terminals such as GNOME Terminal) turn a run of text wrapped in an OSC 8\n// escape pair into a clickable link:\n//\n// ESC ] 8 ; ; https://example.com ESC \\ Docs ESC ] 8 ; ; ESC \\\n//\n// See https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda\n// for the de-facto spec. The escape sequences are zero-width, so they must\n// never take part in layout, column-width math, or box drawing — this module\n// records *which canvas cells* carry a link in a `LinkCanvas` (parallel to\n// `RoleCanvas`, the same column-major shape) at draw time, and the\n// canvas-to-string conversion (`canvasToString` / `colorizeLine`) inserts\n// the escape pairs around each run of linked cells as it emits a row.\n// Marking cells rather than searching rendered text for the label means a\n// label that repeats, or appears inside another label, still links only the\n// node that actually declared the `click`.\n//\n// This is what lets a `click A \"https://...\"` directive survive into\n// terminal output — see docs/decisions/no-script-interactivity.md for where\n// that sits in the interactivity tiers. Off by default\n// (`AsciiRenderOptions.hyperlinks`): not every terminal or pager handles\n// OSC 8 gracefully, and HTML color mode (the demo site's terminal mockup)\n// never emits it at all.\n// ============================================================================\n\nimport type { Canvas, DrawingCoord, AsciiGraph } from './types.ts'\nimport type { NodeInteraction } from '@zombie-mermaid/core'\nimport { safeHref } from '@zombie-mermaid/core'\n\n/**\n * [maxX, maxY] of a canvas — the same arithmetic as canvas.ts's\n * `getCanvasSize`, inlined so this module (imported by canvas.ts for\n * `joinWithLinks`) doesn't import canvas.ts back.\n */\nfunction canvasBounds(canvas: Canvas): [number, number] {\n return [canvas.length - 1, (canvas[0]?.length ?? 1) - 1]\n}\n\n/**\n * Link canvas — parallel to Canvas, records the href each cell is part of.\n * Same column-major structure: linkCanvas[x][y] gives the href at (x, y).\n * null means the cell is not part of any link.\n */\nexport type LinkCanvas = (string | null)[][]\n\n/** OSC 8 close sequence (an empty URI ends the current link). */\nexport const OSC8_CLOSE = '\\x1b]8;;\\x1b\\\\'\n\n/**\n * OSC 8 open sequence for `href`.\n *\n * The URI portion is limited to printable ASCII (0x21–0x7E) by the spec\n * linked above — anything else is percent-encoded here so a URL with a\n * non-ASCII path or a literal space can't break the sequence or leak a\n * control character into the terminal stream. `encodeURIComponent` on one\n * code point yields its UTF-8 percent-encoding; the `u` flag keeps a\n * surrogate pair together so it encodes as one code point rather than two\n * lone surrogates (which `encodeURIComponent` would throw on).\n */\nexport function osc8Open(href: string): string {\n const uri = href.replace(/[^\\x21-\\x7e]/gu, (ch) => encodeURIComponent(ch))\n return `\\x1b]8;;${uri}\\x1b\\\\`\n}\n\n/**\n * Matches one OSC 8 sequence — open or close, ST- or BEL-terminated. The\n * URI can never contain ESC or BEL (`osc8Open` percent-encodes anything\n * outside printable ASCII), so scanning to the first terminator is exact.\n */\nexport const OSC8_SEQUENCE = /\\x1b\\]8;[^\\x07\\x1b]*(?:\\x1b\\\\|\\x07)/g\n\n/** Remove every OSC 8 sequence, leaving the visible text (and any SGR color codes) intact. */\nexport function stripOsc8(text: string): string {\n return text.replace(OSC8_SEQUENCE, '')\n}\n\n// ============================================================================\n// Link canvas creation and marking\n// ============================================================================\n\n/**\n * Create a blank link canvas filled with nulls.\n * Dimensions are inclusive, matching `mkCanvas`: mkLinkCanvas(3, 2) covers\n * indices 0..3 by 0..2.\n */\nexport function mkLinkCanvas(x: number, y: number): LinkCanvas {\n const linkCanvas: LinkCanvas = []\n for (let i = 0; i <= x; i++) {\n const col: (string | null)[] = []\n for (let j = 0; j <= y; j++) {\n col.push(null)\n }\n linkCanvas.push(col)\n }\n return linkCanvas\n}\n\n/**\n * Bounds-checked write of an href to one cell. Out-of-range coordinates\n * are a silent no-op, the same contract as `write()` in canvas.ts — the\n * link canvas is created at the finished canvas's size, so a cell outside\n * it can't be printed anyway.\n */\nexport function setLink(\n linkCanvas: LinkCanvas,\n x: number,\n y: number,\n href: string,\n): void {\n const col = linkCanvas[x]\n if (col === undefined || y < 0 || y >= col.length) return\n col[y] = href\n}\n\n/**\n * Flip the link canvas vertically to match `flipCanvasVertically` (used for\n * BT direction). Mutates in place and returns it.\n */\nexport function flipLinkCanvasVertically(linkCanvas: LinkCanvas): LinkCanvas {\n for (const col of linkCanvas) {\n col.reverse()\n }\n return linkCanvas\n}\n\n/**\n * Mark the label text inside a drawn box as linked to `href`.\n *\n * `box` is a standalone box canvas (from `drawNode`/`drawMultiBox`) whose\n * border occupies its outermost row and column; `offset` is where its\n * top-left corner lands on the main canvas. For each interior row (or only\n * `rows.from..rows.to`, inclusive, when given — class diagrams pass just\n * the header's class-name rows), the linked span runs from the first to the\n * last non-space cell, so a multi-word label (\"Web Server\") is one link\n * rather than two, and each line of a multi-line label becomes its own\n * span. A row with no text is left unmarked.\n *\n * Working from the box's own geometry (rather than scanning the merged\n * canvas for the label's characters) is what keeps this exact for labels\n * that repeat across nodes, contain box-drawing-like punctuation, or hold\n * wide characters whose placeholder cell (`WIDE_CHAR_PLACEHOLDER`) is an\n * empty string rather than a space.\n */\nexport function markBoxLabelLinks(\n linkCanvas: LinkCanvas,\n box: Canvas,\n offset: DrawingCoord,\n href: string,\n rows?: { from: number; to: number },\n): void {\n const [maxX, maxY] = canvasBounds(box)\n const firstRow = Math.max(1, rows?.from ?? 1)\n const lastRow = Math.min(maxY - 1, rows?.to ?? maxY - 1)\n\n for (let y = firstRow; y <= lastRow; y++) {\n let first = -1\n let last = -1\n for (let x = 1; x <= maxX - 1; x++) {\n if (box[x]?.[y] !== ' ') {\n if (first === -1) first = x\n last = x\n }\n }\n if (first === -1) continue\n for (let x = first; x <= last; x++) {\n setLink(linkCanvas, x + offset.x, y + offset.y, href)\n }\n }\n}\n\n/**\n * Build the link canvas for a drawn flowchart/state graph: every node whose\n * `click` declared a safe href (see `safeHref` — `javascript:`/`data:` and\n * the like are dropped there, never here) gets its label cells marked. A\n * `call`/`callback`-only interaction has no href and marks nothing.\n *\n * Must run after `drawGraph` (so every node has its `drawing` and\n * `drawingCoord`) and before any BT flip of the canvas — flip this canvas\n * alongside it with `flipLinkCanvasVertically`.\n */\nexport function buildNodeLinkCanvas(\n graph: AsciiGraph,\n interactions: ReadonlyMap<string, NodeInteraction>,\n): LinkCanvas {\n const [maxX, maxY] = canvasBounds(graph.canvas)\n const linkCanvas = mkLinkCanvas(maxX, maxY)\n\n for (const node of graph.nodes) {\n if (!node.drawing || !node.drawingCoord) continue\n const href = safeHref(interactions.get(node.name)?.href)\n if (href === undefined) continue\n markBoxLabelLinks(linkCanvas, node.drawing, node.drawingCoord, href)\n }\n\n return linkCanvas\n}\n\n// ============================================================================\n// Emitting link runs while serializing a row\n// ============================================================================\n\n/**\n * Tracks the link state across one row's cells and hands back the escape\n * text to insert before each cell: a close when leaving a linked run, an\n * open when entering one (both when two different links touch), and\n * nothing while the state is unchanged. `finish()` closes a run still open\n * at the end of the row.\n *\n * The markers are pure insertions into the character stream — the SGR\n * color grouping in `colorizeLine` is left exactly as it would be without\n * links — so stripping every OSC 8 sequence from hyperlinked output yields\n * the non-hyperlinked output byte for byte.\n */\nexport class LinkRunTracker {\n private current: string | null = null\n\n /** Escape text to emit before a cell carrying `link` (null = no link). */\n advance(link: string | null): string {\n if (link === this.current) return ''\n let out = this.current !== null ? OSC8_CLOSE : ''\n if (link !== null) out += osc8Open(link)\n this.current = link\n return out\n }\n\n /** Escape text to emit after the row's last cell. */\n finish(): string {\n const out = this.current !== null ? OSC8_CLOSE : ''\n this.current = null\n return out\n }\n}\n\n/** Join a row's cells into plain (uncolored) text with OSC 8 pairs around each linked run. */\nexport function joinWithLinks(\n chars: readonly string[],\n links: readonly (string | null)[],\n): string {\n const tracker = new LinkRunTracker()\n let line = ''\n for (const [i, char] of chars.entries()) {\n line += tracker.advance(links[i] ?? null) + char\n }\n return line + tracker.finish()\n}\n","// ============================================================================\n// ASCII renderer — color utilities\n//\n// Provides color output for themed ASCII diagrams.\n// Supports ANSI terminal modes (16/256/truecolor) and HTML <span> tags\n// for browser rendering.\n// ============================================================================\n\nimport type { CharRole, AsciiTheme, ColorMode } from './types.ts'\nimport type { DiagramColors } from '@zombie-mermaid/core'\nimport {\n MIX,\n mixHexColors as mixColors,\n parseHexRgba,\n} from '@zombie-mermaid/core'\nimport { joinWithLinks, LinkRunTracker } from './hyperlinks.ts'\n\ndeclare const document: unknown\n\n// ============================================================================\n// Default theme — matches SVG theme colors for consistency\n// ============================================================================\n\n/**\n * Default ASCII theme derived from the SVG renderer's color palette.\n * Uses the same mixing ratios to maintain visual consistency.\n */\nexport const DEFAULT_ASCII_THEME: AsciiTheme = {\n fg: '#27272a', // zinc-800 — primary text\n border: '#a1a1aa', // zinc-400 — node borders (12% mix)\n line: '#71717a', // zinc-500 — edge lines (35% mix)\n arrow: '#52525b', // zinc-600 — arrowheads (60% mix)\n corner: '#71717a', // same as line\n junction: '#a1a1aa', // same as border\n}\n\n// ============================================================================\n// DiagramColors → AsciiTheme bridge\n//\n// Converts SVG DiagramColors into an AsciiTheme using the same MIX ratios\n// that the SVG renderer uses via CSS color-mix(). This ensures visual\n// consistency between SVG and ASCII output for any theme.\n// ============================================================================\n\n/**\n * Derive an AsciiTheme from SVG DiagramColors using the same mixing ratios.\n * Honors optional enrichment colors (line, accent, border) when present,\n * otherwise falls back to color-mix derivation — matching SVG behavior.\n */\nexport function diagramColorsToAsciiTheme(colors: DiagramColors): AsciiTheme {\n const line = colors.line ?? mixColors(colors.fg, colors.bg, MIX.line)\n const border =\n colors.border ?? mixColors(colors.fg, colors.bg, MIX.nodeStroke)\n return {\n fg: colors.fg,\n border,\n line,\n arrow: colors.accent ?? mixColors(colors.fg, colors.bg, MIX.arrow),\n accent: colors.accent,\n bg: colors.bg,\n corner: line,\n junction: border,\n }\n}\n\n// ============================================================================\n// Color mode detection\n// ============================================================================\n\n/**\n * Detect the best color mode for the current environment.\n *\n * Terminal detection order:\n * 1. COLORTERM=truecolor or COLORTERM=24bit → truecolor\n * 2. TERM contains \"256color\" → ansi256\n * 3. TERM is set and not \"dumb\" → ansi16\n *\n * Browser: returns 'html' (uses <span> tags with inline styles).\n * Unknown/piped: returns 'none'.\n */\nexport function detectColorMode(): ColorMode {\n // Check if we're in a Node.js-like environment with process object\n // Use globalThis to safely check for process without TypeScript errors\n const proc = (\n globalThis as {\n process?: {\n stdout?: { isTTY?: boolean }\n env?: Record<string, string | undefined>\n }\n }\n ).process\n\n if (proc) {\n // Check if stdout is a TTY (not piped/redirected)\n if (!proc.stdout?.isTTY) {\n return 'none'\n }\n\n const colorTerm = proc.env?.COLORTERM?.toLowerCase() ?? ''\n const term = proc.env?.TERM?.toLowerCase() ?? ''\n\n // True color support\n if (colorTerm === 'truecolor' || colorTerm === '24bit') {\n return 'truecolor'\n }\n\n // 256 color support\n if (term.includes('256color') || term.includes('256')) {\n return 'ansi256'\n }\n\n // Basic color support\n if (term && term !== 'dumb') {\n return 'ansi16'\n }\n\n return 'none'\n }\n\n // No process object → browser environment → use HTML color output\n if (typeof document !== 'undefined') {\n return 'html'\n }\n\n return 'none'\n}\n\n// ============================================================================\n// Hex color parsing\n// ============================================================================\n\n/**\n * Parse a hex color string to RGB values for ANSI escape generation.\n * Accepts 3/6-digit hex with or without a leading `#` (theme colors always\n * carry one; the bare form is kept for backward compatibility). Anything\n * unparseable falls back to black rather than emitting a NaN escape.\n */\nfunction parseHex(hex: string): { r: number; g: number; b: number } {\n const rgba = parseHexRgba(hex.startsWith('#') ? hex : `#${hex}`)\n return rgba ?? { r: 0, g: 0, b: 0 }\n}\n\n// ============================================================================\n// ANSI escape code generation\n// ============================================================================\n\n/** ANSI escape sequence prefix */\nconst ESC = '\\x1b['\n/** Reset all attributes */\nconst RESET = `${ESC}0m`\n\n/**\n * Generate ANSI foreground color escape sequence for 24-bit true color.\n * Format: ESC[38;2;R;G;Bm\n */\nfunction truecolorFg(hex: string): string {\n const { r, g, b } = parseHex(hex)\n return `${ESC}38;2;${r};${g};${b}m`\n}\n\n/**\n * Find the closest 256-color palette index for an RGB color.\n * The 256-color palette has:\n * - 0-15: Standard colors (duplicates of 16-color)\n * - 16-231: 6x6x6 color cube (216 colors)\n * - 232-255: Grayscale ramp (24 shades)\n */\nfunction rgbTo256(r: number, g: number, b: number): number {\n // Check if it's close to grayscale\n const avg = (r + g + b) / 3\n const maxDiff = Math.max(\n Math.abs(r - avg),\n Math.abs(g - avg),\n Math.abs(b - avg),\n )\n\n if (maxDiff < 10) {\n // Use grayscale ramp (232-255)\n // Each step is ~10.625 (256/24)\n const gray = Math.round((avg / 255) * 23)\n return 232 + Math.min(23, Math.max(0, gray))\n }\n\n // Use 6x6x6 color cube (16-231)\n // Each channel maps to 0-5: 0, 95, 135, 175, 215, 255\n const toIndex = (v: number): number => {\n if (v < 48) return 0\n if (v < 115) return 1\n return Math.min(5, Math.floor((v - 35) / 40))\n }\n\n const ri = toIndex(r)\n const gi = toIndex(g)\n const bi = toIndex(b)\n\n return 16 + 36 * ri + 6 * gi + bi\n}\n\n/**\n * Generate ANSI foreground color escape sequence for 256-color mode.\n * Format: ESC[38;5;Nm\n */\nfunction ansi256Fg(hex: string): string {\n const { r, g, b } = parseHex(hex)\n const index = rgbTo256(r, g, b)\n return `${ESC}38;5;${index}m`\n}\n\n/**\n * Map an RGB color to the closest 16-color ANSI code.\n * Returns the foreground color escape sequence.\n *\n * Standard 16 colors:\n * 0=black, 1=red, 2=green, 3=yellow, 4=blue, 5=magenta, 6=cyan, 7=white\n * 8-15 = bright versions\n */\nfunction ansi16Fg(hex: string): string {\n const { r, g, b } = parseHex(hex)\n const luma = 0.299 * r + 0.587 * g + 0.114 * b\n\n // Determine brightness (use bright colors for better visibility)\n const bright = luma > 100 ? 0 : 60 // 60 = bright variant offset\n\n // Determine base color based on dominant channel\n let code: number\n if (r > 180 && g < 100 && b < 100)\n code = 31 // red\n else if (g > 180 && r < 100 && b < 100)\n code = 32 // green\n else if (r > 150 && g > 150 && b < 100)\n code = 33 // yellow\n else if (b > 180 && r < 100 && g < 100)\n code = 34 // blue\n else if (r > 150 && b > 150 && g < 100)\n code = 35 // magenta\n else if (g > 150 && b > 150 && r < 100)\n code = 36 // cyan\n else if (luma > 200)\n code = 37 // white\n else if (luma < 50)\n code = 30 // black\n else code = 37 // default to white for grays\n\n return `${ESC}${code + bright}m`\n}\n\n// ============================================================================\n// HTML color output (for browser rendering)\n// ============================================================================\n\n/** Escape characters that would break HTML output. */\nfunction escapeHtml(text: string): string {\n return text.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>')\n}\n\n/** Wrap text in a <span> with an inline color style. */\nfunction htmlSpan(hex: string, text: string): string {\n return `<span style=\"color:${hex}\">${escapeHtml(text)}</span>`\n}\n\n// ============================================================================\n// Role → color mapping\n// ============================================================================\n\n/**\n * Get the color for a character role from the theme.\n */\nfunction getRoleColor(role: CharRole, theme: AsciiTheme): string {\n switch (role) {\n case 'text':\n return theme.fg\n case 'border':\n return theme.border\n case 'line':\n return theme.line\n case 'arrow':\n return theme.arrow\n case 'corner':\n return theme.corner ?? theme.line\n case 'junction':\n return theme.junction ?? theme.border\n default:\n return theme.fg\n }\n}\n\n/**\n * Generate the ANSI escape sequence for a role color.\n */\nexport function getAnsiColor(\n role: CharRole,\n theme: AsciiTheme,\n mode: ColorMode,\n): string {\n if (mode === 'none') return ''\n\n const hex = getRoleColor(role, theme)\n\n switch (mode) {\n case 'truecolor':\n return truecolorFg(hex)\n case 'ansi256':\n return ansi256Fg(hex)\n case 'ansi16':\n return ansi16Fg(hex)\n default:\n return ''\n }\n}\n\n/**\n * Get the ANSI reset sequence.\n */\nexport function getAnsiReset(mode: ColorMode): string {\n return mode === 'none' ? '' : RESET\n}\n\n/**\n * Wrap a character with ANSI color codes based on its role.\n */\nexport function colorizeChar(\n char: string,\n role: CharRole | null,\n theme: AsciiTheme,\n mode: ColorMode,\n): string {\n if (mode === 'none' || role === null || char === ' ') {\n return char\n }\n\n const colorCode = getAnsiColor(role, theme, mode)\n return `${colorCode}${char}${RESET}`\n}\n\n/**\n * Colorize an entire line efficiently by grouping consecutive same-role characters.\n * This reduces the number of escape sequences (ANSI) or span tags (HTML) in the output.\n *\n * `links` (one href-or-null per cell, see hyperlinks.ts) wraps each run of\n * linked cells in an OSC 8 hyperlink pair. The pair is inserted into the\n * character stream *without* disturbing the SGR grouping — an open/close\n * may land inside a color run rather than splitting it — so the output with\n * every OSC 8 sequence stripped is identical to the output without links.\n * Ignored in 'html' mode, which is rendered by a browser, not a terminal.\n */\nexport function colorizeLine(\n chars: string[],\n roles: (CharRole | null)[],\n theme: AsciiTheme,\n mode: ColorMode,\n links?: readonly (string | null)[],\n): string {\n if (mode === 'none') {\n return links ? joinWithLinks(chars, links) : chars.join('')\n }\n\n if (mode === 'html') {\n return colorizeLineHtml(chars, roles, theme)\n }\n\n const linkRuns = new LinkRunTracker()\n let result = ''\n let currentRole: CharRole | null = null\n let buffer = ''\n\n for (const [i, char] of chars.entries()) {\n const role = roles[i] ?? null\n // Zero-width; goes wherever this cell's character goes.\n const linkMarker = links ? linkRuns.advance(links[i] ?? null) : ''\n\n // Whitespace doesn't need coloring\n if (char === ' ') {\n // Flush any buffered characters (with or without color)\n if (buffer.length > 0) {\n if (currentRole !== null) {\n result += getAnsiColor(currentRole, theme, mode) + buffer + RESET\n } else {\n result += buffer\n }\n buffer = ''\n currentRole = null\n }\n result += linkMarker + char\n continue\n }\n\n // Same role as previous — accumulate\n if (role === currentRole) {\n buffer += linkMarker + char\n continue\n }\n\n // Role changed — flush buffer (with or without color) and start new\n if (buffer.length > 0) {\n if (currentRole !== null) {\n result += getAnsiColor(currentRole, theme, mode) + buffer + RESET\n } else {\n result += buffer\n }\n }\n buffer = linkMarker + char\n currentRole = role\n }\n\n // Flush remaining buffer\n if (buffer.length > 0 && currentRole !== null) {\n result += getAnsiColor(currentRole, theme, mode) + buffer + RESET\n } else if (buffer.length > 0) {\n result += buffer\n }\n\n return result + linkRuns.finish()\n}\n\n/**\n * HTML-specific line colorization.\n * Groups consecutive same-role characters into <span> tags with inline color styles.\n * Whitespace is emitted bare (no wrapping) to keep output compact.\n */\nfunction colorizeLineHtml(\n chars: string[],\n roles: (CharRole | null)[],\n theme: AsciiTheme,\n): string {\n let result = ''\n let currentRole: CharRole | null = null\n let buffer = ''\n\n const flush = () => {\n if (buffer.length === 0) return\n if (currentRole !== null) {\n result += htmlSpan(getRoleColor(currentRole, theme), buffer)\n } else {\n result += escapeHtml(buffer)\n }\n buffer = ''\n currentRole = null\n }\n\n for (const [i, char] of chars.entries()) {\n const role = roles[i] ?? null\n\n if (char === ' ') {\n flush()\n result += ' '\n continue\n }\n\n if (role === currentRole) {\n buffer += char\n continue\n }\n\n flush()\n buffer = char\n currentRole = role\n }\n\n flush()\n return result\n}\n\n/**\n * Colorize a text string with a direct hex color.\n * Used by renderers that need per-cell color control (e.g. multi-series xychart).\n * Handles all output modes: ANSI (16/256/truecolor) and HTML.\n */\nexport function colorizeText(\n text: string,\n hex: string,\n mode: ColorMode,\n): string {\n if (mode === 'none' || text.length === 0) return text\n if (mode === 'html') return htmlSpan(hex, text)\n let code: string\n switch (mode) {\n case 'truecolor':\n code = truecolorFg(hex)\n break\n case 'ansi256':\n code = ansi256Fg(hex)\n break\n case 'ansi16':\n code = ansi16Fg(hex)\n break\n default:\n return text\n }\n return `${code}${text}${RESET}`\n}\n","// ============================================================================\n// ASCII renderer — display width helpers\n//\n// The ASCII grid is column-major with one grid cell reserved per *grapheme\n// cluster* — the Unicode notion of a single user-perceived character, which\n// may span several JS code points. Measuring by code point instead breaks in\n// two opposite directions:\n//\n// - A combining mark (e.g. decomposed \"e\" + U+0301 COMBINING ACUTE ACCENT)\n// is its own code point but claims no terminal column of its own — it\n// attaches to the preceding base character. Counting it as a full column\n// overcounts (issue #205).\n// - A composed emoji sequence (ZWJ family emoji, flag via regional\n// indicators, skin-tone modifier) is several code points that render as\n// ONE glyph occupying at most 2 terminal columns. Counting each code\n// point separately overcounts even more badly (issue #214).\n//\n// Segmenting by grapheme cluster (`Intl.Segmenter`) and measuring each\n// cluster as a unit fixes both: a cluster is 2 columns if any code point\n// within it is \"wide\" (CJK etc, via the same `isWideChar` the SVG\n// text-measurement path uses), else 1 column — except a cluster made\n// entirely of combining marks with no base character, which is 0 columns\n// (the degenerate case the general Unicode categories Mn/Me describe;\n// ordinarily unreachable since a mark attaches to a preceding base within\n// the same cluster, but handled explicitly for a lone/leading mark).\n//\n// This module is the single source of truth for \"how many terminal columns\n// does this text occupy\" and \"how many grid cells does writing it need\"\n// across the ASCII renderer.\n// ============================================================================\n\nimport { isWideChar } from '@zombie-mermaid/core'\n\n/**\n * Placeholder written into the grid cell immediately following a wide\n * grapheme cluster. It reserves a grid column — keeping every subsequent\n * cell's x-index aligned with the same column in other rows (borders, other\n * labels, etc.) — but contributes no text when a canvas row is joined into\n * a string, because the wide glyph itself already renders across two\n * terminal columns.\n */\nexport const WIDE_CHAR_PLACEHOLDER = ''\n\n/** Unicode general categories Mn (Nonspacing_Mark) and Me (Enclosing_Mark). */\nconst COMBINING_MARK_REGEX = /\\p{Mn}|\\p{Me}/u\n\nfunction isCombiningMark(char: string): boolean {\n return COMBINING_MARK_REGEX.test(char)\n}\n\nconst graphemeSegmenter = new Intl.Segmenter(undefined, {\n granularity: 'grapheme',\n})\n\n/**\n * U+FE0F VARIATION SELECTOR-16 explicitly requests the emoji (wide)\n * presentation for the preceding base character, overriding whatever that\n * character's own default presentation is. `isWideChar` only ever sees one\n * isolated code point at a time (it's shared with the SVG text-measurement\n * path, which has no grapheme-cluster concept), so it has no way to look\n * ahead for a following VS16 — that requires cluster-level context, which\n * only exists here. A base character normally classified narrow (e.g. ▶\n * U+25B6, excluded from `isWideChar` as a Geometric Shapes glyph — see\n * `isGeometricShapesTextDefault` in text-metrics.ts) still renders as a\n * double-width emoji glyph in ▶️ once VS16 forces emoji presentation.\n */\nconst VARIATION_SELECTOR_16 = '\\u{FE0F}'\n\n/**\n * Split text into grapheme clusters — user-perceived characters. A cluster\n * may span multiple JS code points: a combining mark attaches to its base\n * character, and a ZWJ emoji sequence / flag / skin-tone modifier sequence\n * forms a single rendered glyph. Naive code-point iteration (`for...of`)\n * tears these apart, which is the root cause of both #205 and #214.\n */\nfunction graphemeClusters(text: string): string[] {\n const clusters: string[] = []\n for (const { segment } of graphemeSegmenter.segment(text)) {\n clusters.push(segment)\n }\n return clusters\n}\n\n/**\n * Number of terminal display columns a single grapheme cluster occupies.\n * Always 0, 1, or 2 for the ASCII grid (unlike the fractional SVG metrics in\n * `text-metrics.ts`, which model proportional-font rendering).\n *\n * - 2 if any code point in the cluster is \"wide\" (`isWideChar`) — covers\n * both plain CJK/fullwidth characters and composed emoji sequences, since\n * the sequence's base emoji code point is itself flagged wide.\n * - 0 if the cluster consists entirely of combining marks (Mn/Me) with no\n * base character — a degenerate case in practice, since a mark normally\n * attaches to a preceding base within the same cluster instead of forming\n * a standalone one.\n * - 1 otherwise, including a base character followed by zero-width\n * combining marks (e.g. decomposed \"é\"), since the marks contribute no\n * additional column within their cluster.\n *\n * A cluster containing VS16 (see `VARIATION_SELECTOR_16` above) is always\n * width 2, regardless of what `isWideChar` reports for its base character —\n * VS16 is an explicit, cluster-level request for emoji presentation that a\n * single isolated code point can't express.\n */\nexport function charDisplayWidth(cluster: string): number {\n if (cluster.includes(VARIATION_SELECTOR_16)) return 2\n let sawWide = false\n let sawNonMark = false\n for (const ch of cluster) {\n if (isWideChar(ch)) sawWide = true\n if (!isCombiningMark(ch)) sawNonMark = true\n }\n if (sawWide) return 2\n return sawNonMark ? 1 : 0\n}\n\n/**\n * Total terminal display width of a string, measured per grapheme cluster\n * (not per code point or UTF-16 code unit) so combining marks and composed\n * emoji sequences are each counted once, correctly.\n */\nexport function displayWidth(text: string): number {\n let width = 0\n for (const cluster of graphemeClusters(text)) {\n width += charDisplayWidth(cluster)\n }\n return width\n}\n\n/**\n * Split a string into grid cells for canvas writing.\n *\n * Each grapheme cluster produces one cell containing the full cluster text\n * (so a combining mark shares its base character's cell instead of\n * claiming one of its own). A wide cluster (display width 2) additionally\n * produces a trailing `WIDE_CHAR_PLACEHOLDER` cell. This keeps grid-cell\n * count in sync with display-column count so that box-width math (computed\n * via `displayWidth`) and the actual character-writing loop agree — a\n * zero-width cluster (a lone combining mark with no base) contributes no\n * cell at all, since it claims no column.\n */\nexport function toDisplayCells(text: string): string[] {\n const cells: string[] = []\n for (const cluster of graphemeClusters(text)) {\n const width = charDisplayWidth(cluster)\n if (width === 0) continue\n cells.push(cluster)\n if (width === 2) cells.push(WIDE_CHAR_PLACEHOLDER)\n }\n return cells\n}\n","// ============================================================================\n// ASCII renderer — 2D text canvas\n//\n// Ported from AlexanderGrooff/mermaid-ascii cmd/draw.go.\n// The canvas is a column-major 2D array of single-character strings.\n// canvas[x][y] gives the character at column x, row y.\n// ============================================================================\n\nimport type {\n Canvas,\n DrawingCoord,\n RoleCanvas,\n CharRole,\n AsciiTheme,\n ColorMode,\n} from './types.ts'\nimport { colorizeLine, DEFAULT_ASCII_THEME } from './ansi.ts'\nimport { toDisplayCells } from './display-width.ts'\nimport { joinWithLinks } from './hyperlinks.ts'\nimport type { LinkCanvas } from './hyperlinks.ts'\n\n/**\n * Create a blank canvas filled with spaces.\n * Dimensions are inclusive: mkCanvas(3, 2) creates a 4x3 grid (indices 0..3, 0..2).\n */\nexport function mkCanvas(x: number, y: number): Canvas {\n const canvas: Canvas = []\n for (let i = 0; i <= x; i++) {\n const col: string[] = []\n for (let j = 0; j <= y; j++) {\n col.push(' ')\n }\n canvas.push(col)\n }\n return canvas\n}\n\n/** Create a blank canvas with the same dimensions as the given canvas. */\nexport function copyCanvas(source: Canvas): Canvas {\n const [maxX, maxY] = getCanvasSize(source)\n return mkCanvas(maxX, maxY)\n}\n\n// ============================================================================\n// Role canvas creation and management\n// ============================================================================\n\n/**\n * Create a blank role canvas filled with nulls.\n * Same dimensions as mkCanvas — column-major, roleCanvas[x][y].\n */\nexport function mkRoleCanvas(x: number, y: number): RoleCanvas {\n const roleCanvas: RoleCanvas = []\n for (let i = 0; i <= x; i++) {\n const col: (CharRole | null)[] = []\n for (let j = 0; j <= y; j++) {\n col.push(null)\n }\n roleCanvas.push(col)\n }\n return roleCanvas\n}\n\n/** Create a blank role canvas with the same dimensions as the given role canvas. */\nexport function copyRoleCanvas(source: RoleCanvas): RoleCanvas {\n const maxX = source.length - 1\n const maxY = (source[0]?.length ?? 1) - 1\n return mkRoleCanvas(maxX, maxY)\n}\n\n/**\n * Grow the role canvas to fit at least (newX, newY), preserving existing roles.\n * Mutates the role canvas in place and returns it.\n */\nexport function increaseRoleCanvasSize(\n roleCanvas: RoleCanvas,\n newX: number,\n newY: number,\n): RoleCanvas {\n const currX = roleCanvas.length - 1\n const currY = (roleCanvas[0]?.length ?? 1) - 1\n const targetX = Math.max(newX, currX)\n const targetY = Math.max(newY, currY)\n const grown = mkRoleCanvas(targetX, targetY)\n for (let x = 0; x < grown.length; x++) {\n for (let y = 0; y < (grown[0]?.length ?? 0); y++) {\n if (x < roleCanvas.length && y < (roleCanvas[0]?.length ?? 0)) {\n grown[x]![y] = roleCanvas[x]![y]!\n }\n }\n }\n roleCanvas.length = 0\n roleCanvas.push(...grown)\n return roleCanvas\n}\n\n/**\n * Set a role at a specific coordinate.\n * Expands the role canvas if necessary.\n */\nexport function setRole(\n roleCanvas: RoleCanvas,\n x: number,\n y: number,\n role: CharRole,\n): void {\n if (x >= roleCanvas.length || y >= (roleCanvas[0]?.length ?? 0)) {\n increaseRoleCanvasSize(roleCanvas, x, y)\n }\n roleCanvas[x]![y] = role\n}\n\n/**\n * Merge role canvases — same logic as mergeCanvases but for roles.\n * Non-null roles in overlays overwrite null roles in base.\n */\nexport function mergeRoleCanvases(\n base: RoleCanvas,\n offset: DrawingCoord,\n ...overlays: RoleCanvas[]\n): RoleCanvas {\n let maxX = base.length - 1\n let maxY = (base[0]?.length ?? 1) - 1\n\n for (const overlay of overlays) {\n const oX = overlay.length - 1\n const oY = (overlay[0]?.length ?? 1) - 1\n maxX = Math.max(maxX, oX + offset.x)\n maxY = Math.max(maxY, oY + offset.y)\n }\n\n const merged = mkRoleCanvas(maxX, maxY)\n\n // Copy base\n for (let x = 0; x <= maxX; x++) {\n for (let y = 0; y <= maxY; y++) {\n if (x < base.length && y < (base[0]?.length ?? 0)) {\n merged[x]![y] = base[x]![y]!\n }\n }\n }\n\n // Apply overlays\n for (const overlay of overlays) {\n for (let x = 0; x < overlay.length; x++) {\n for (let y = 0; y < (overlay[0]?.length ?? 0); y++) {\n const role = overlay[x]?.[y]\n if (role !== null && role !== undefined) {\n const mx = x + offset.x\n const my = y + offset.y\n merged[mx]![my] = role\n }\n }\n }\n }\n\n return merged\n}\n\n/** Returns [maxX, maxY] — the highest valid indices in each dimension. */\nexport function getCanvasSize(canvas: Canvas): [number, number] {\n return [canvas.length - 1, (canvas[0]?.length ?? 1) - 1]\n}\n\n/**\n * Grow the canvas to fit at least (newX, newY), preserving existing content.\n * Mutates the canvas in place and returns it.\n */\nexport function increaseSize(\n canvas: Canvas,\n newX: number,\n newY: number,\n): Canvas {\n const [currX, currY] = getCanvasSize(canvas)\n const targetX = Math.max(newX, currX)\n const targetY = Math.max(newY, currY)\n const grown = mkCanvas(targetX, targetY)\n for (let x = 0; x < grown.length; x++) {\n for (let y = 0; y < (grown[0]?.length ?? 0); y++) {\n if (x < canvas.length && y < (canvas[0]?.length ?? 0)) {\n grown[x]![y] = canvas[x]![y]!\n }\n }\n }\n // Mutate in place: splice old contents and replace with grown\n canvas.length = 0\n canvas.push(...grown)\n return canvas\n}\n\n/**\n * Bounds-checked write to a single canvas cell.\n *\n * Sets the character at (x, y) if the coordinate falls within the canvas;\n * out-of-range coordinates are silently clipped (a no-op) instead of\n * mutating the canvas. This is the single write path drawing modules should\n * use instead of indexing `canvas[x]![y] = ch` directly, which had three\n * different out-of-range behaviors depending on axis — none of them a clean\n * bounds check:\n * - x out of range: threw, via the `canvas[x]!` non-null assertion.\n * - y too large (but x in range): did *not* throw — JS silently\n * ragged-extends that column's array. This was observable downstream:\n * `mergeCanvases`/`canvasToString` would read the extended slot back as\n * `undefined`, which `canvasToString` then concatenates into the output\n * as the literal string `\"undefined\"`.\n * - y negative (but x in range): silently set an own property keyed by\n * the negative index (e.g. `arr[-1]`) rather than a real array element;\n * rendering code never reads it back, so this was a silent no-op.\n * `write()` normalizes all three cases to the same silent no-op. That is a\n * behavior *change*, not a preservation of the prior status quo — callers\n * migrating to `write()` need their own bounds check if they relied on the\n * old per-axis behavior (see `drawSubgraphLabel` in `draw-subgraphs.ts`,\n * which needs a tighter, exclusive bound than `write()`'s own clip to avoid\n * overwriting a subgraph's border with an oversized title).\n *\n * When `roleTracking` is provided, the role is recorded via `setRole`\n * alongside the character write (used by callers, like the sequence-diagram\n * and xychart renderers, that track character roles inline rather than\n * deriving them from the finished canvas afterward). `role` and\n * `roleCanvas` are bundled into one parameter so a call can't pass one\n * without the other — passing just a role with nowhere to record it would\n * otherwise type-check while silently dropping the role, an error only\n * visible in colorized/ANSI output.\n */\nexport function write(\n canvas: Canvas,\n x: number,\n y: number,\n ch: string,\n roleTracking?: { role: CharRole; roleCanvas: RoleCanvas },\n): void {\n const [maxX, maxY] = getCanvasSize(canvas)\n if (x < 0 || x > maxX || y < 0 || y > maxY) return\n canvas[x]![y] = ch\n if (roleTracking) {\n setRole(roleTracking.roleCanvas, x, y, roleTracking.role)\n }\n}\n\n// ============================================================================\n// Junction merging — Unicode box-drawing character compositing\n// ============================================================================\n\n/** All Unicode box-drawing characters that participate in junction merging. */\nconst JUNCTION_CHARS = new Set([\n '─',\n '│',\n '┌',\n '┐',\n '└',\n '┘',\n '├',\n '┤',\n '┬',\n '┴',\n '┼',\n '╴',\n '╵',\n '╶',\n '╷',\n])\n\nexport function isJunctionChar(c: string): boolean {\n return JUNCTION_CHARS.has(c)\n}\n\n/** Check if a character is alphanumeric (part of a label). */\nfunction isAlphanumeric(c: string): boolean {\n return /^[a-zA-Z0-9]$/.test(c)\n}\n\n/**\n * When two junction characters overlap during canvas merging,\n * resolve them to the correct combined junction.\n * E.g., '─' overlapping '│' becomes '┼'.\n */\nconst JUNCTION_MAP: Record<string, Record<string, string>> = {\n '─': {\n '│': '┼',\n '┌': '┬',\n '┐': '┬',\n '└': '┴',\n '┘': '┴',\n '├': '┼',\n '┤': '┼',\n '┬': '┬',\n '┴': '┴',\n },\n '│': {\n '─': '┼',\n '┌': '├',\n '┐': '┤',\n '└': '├',\n '┘': '┤',\n '├': '├',\n '┤': '┤',\n '┬': '┼',\n '┴': '┼',\n },\n '┌': {\n '─': '┬',\n '│': '├',\n '┐': '┬',\n '└': '├',\n '┘': '┼',\n '├': '├',\n '┤': '┼',\n '┬': '┬',\n '┴': '┼',\n },\n '┐': {\n '─': '┬',\n '│': '┤',\n '┌': '┬',\n '└': '┼',\n '┘': '┤',\n '├': '┼',\n '┤': '┤',\n '┬': '┬',\n '┴': '┼',\n },\n '└': {\n '─': '┴',\n '│': '├',\n '┌': '├',\n '┐': '┼',\n '┘': '┴',\n '├': '├',\n '┤': '┼',\n '┬': '┼',\n '┴': '┴',\n },\n '┘': {\n '─': '┴',\n '│': '┤',\n '┌': '┼',\n '┐': '┤',\n '└': '┴',\n '├': '┼',\n '┤': '┤',\n '┬': '┼',\n '┴': '┴',\n },\n '├': {\n '─': '┼',\n '│': '├',\n '┌': '├',\n '┐': '┼',\n '└': '├',\n '┘': '┼',\n '┤': '┼',\n '┬': '┼',\n '┴': '┼',\n },\n '┤': {\n '─': '┼',\n '│': '┤',\n '┌': '┼',\n '┐': '┤',\n '└': '┼',\n '┘': '┤',\n '├': '┼',\n '┬': '┼',\n '┴': '┼',\n },\n '┬': {\n '─': '┬',\n '│': '┼',\n '┌': '┬',\n '┐': '┬',\n '└': '┼',\n '┘': '┼',\n '├': '┼',\n '┤': '┼',\n '┴': '┼',\n },\n '┴': {\n '─': '┴',\n '│': '┼',\n '┌': '┼',\n '┐': '┼',\n '└': '┴',\n '┘': '┴',\n '├': '┼',\n '┤': '┼',\n '┬': '┼',\n },\n}\n\nexport function mergeJunctions(c1: string, c2: string): string {\n return JUNCTION_MAP[c1]?.[c2] ?? c1\n}\n\n// ============================================================================\n// Canvas merging — composite multiple canvases with offset\n// ============================================================================\n\n/**\n * Blank out (replace with a space) any cell in a *later* canvas that a\n * *earlier* one in `canvases` already painted a non-space character into.\n * Canvases are otherwise untouched — same length/shape, same content at\n * every cell no earlier canvas has already claimed.\n *\n * `mergeCanvases` itself always lets the *last* non-space overlay win a\n * shared cell (see its own doc), which is the right default for most of\n * `draw.ts`'s layers (an arrowhead correctly overwrites the corner beneath\n * it, a later label wins a real collision, etc.) — but for the *line*\n * layer specifically, \"last edge processed wins\" is arbitrary and can pick\n * the wrong one: `edge-cell-styles.ts`'s module doc already documents\n * \"first claim wins\" as this renderer's intended rule for a cell two\n * differently-styled, unrelated edges both route through (that module\n * tracks exactly this to *detect* the conflict and trigger a reroute\n * around it — see grid.ts's `rerouteAroundStyleConflicts`), but drawing\n * itself never consulted that rule: every edge's line canvas painted in\n * `graph.edges` order and `mergeCanvases` let whichever one came *last*\n * silently overwrite an earlier edge's own, differently-styled glyph. Most\n * of the time rerouting already prevents the two from sharing a cell at\n * all, so this is a no-op; when it can't (e.g. a same-source fan-out with\n * 3+ distinct styles has no fully conflict-free route among the routing\n * candidates available — see #1067, \"All Edge Styles\"), applying \"first\n * claim wins\" here at the character level, immediately before the line\n * layer is merged, keeps whichever edge got there first rendered\n * consistently in its own style instead of visibly switching styles\n * mid-route where a later sibling's paint won by sheer draw order.\n *\n * Scoped to the line layer only (`draw.ts`'s `lineCanvases`) — corners,\n * arrowheads, box-start connectors and labels are composited in their own\n * later `mergeCanvases` passes with their existing (correct, layer-specific)\n * merge semantics, untouched by this function.\n *\n * A claimed cell only suppresses a *later* canvas's character when the two\n * wouldn't otherwise combine into something meaningful — i.e. when they\n * aren't both plain Unicode junction characters (`isJunctionChar`). Two\n * *different* junction characters at the same cell are usually a genuine\n * perpendicular crossing (one edge's `─`, another's `│`), which `drawLine`\n * relies on `mergeCanvases`'s own junction-merge logic to combine into `┼`\n * — blanket-suppressing by coordinate alone would silently turn that\n * crossing into whichever edge happened to draw first. Mixed-style\n * characters (dashed `┄`/`┆`, heavy `━`/`┃`) are never junction chars, so\n * they still fall through to plain first-claim suppression — the exact\n * #1067 \"All Edge Styles\" scenario this function exists for.\n */\nexport function firstClaimWins(canvases: readonly Canvas[]): Canvas[] {\n const claimed = new Map<string, string>()\n const result: Canvas[] = []\n for (const canvas of canvases) {\n const [maxX, maxY] = getCanvasSize(canvas)\n // `copyCanvas` (despite its name) returns a *blank* canvas of the same\n // size, not a clone of `canvas`'s content — see its own doc. Every cell\n // must be written explicitly below, not just the ones this function\n // blanks out.\n const out = copyCanvas(canvas)\n for (let x = 0; x <= maxX; x++) {\n for (let y = 0; y <= maxY; y++) {\n const c = canvas[x]?.[y]\n if (c === undefined || c === ' ') continue\n const key = `${x},${y}`\n const existing = claimed.get(key)\n if (existing !== undefined) {\n // A repeat of the *same* character (a collinear duplicate, e.g.\n // two edges both drawing '─' through a shared trunk cell) is\n // still a first-claim suppression, not a crossing — `mergeJunctions`\n // has no entry for a character merged with itself and would just\n // fall back to it, so letting it through would be a harmless but\n // pointless no-op; treating it as \"still claimed\" keeps the rule\n // simple and matches \"collinear overlap\" from the case that\n // actually needs a merge (two *different* junction characters).\n const isCrossing =\n existing !== c && isJunctionChar(existing) && isJunctionChar(c)\n if (!isCrossing) continue\n } else {\n claimed.set(key, c)\n }\n out[x]![y] = c\n }\n }\n result.push(out)\n }\n return result\n}\n\n/**\n * Merge overlay canvases onto a base canvas at the given offset.\n * Non-space characters in overlays overwrite the base.\n * When both characters are Unicode junction chars, they're merged intelligently.\n */\nexport function mergeCanvases(\n base: Canvas,\n offset: DrawingCoord,\n useAscii: boolean,\n ...overlays: Canvas[]\n): Canvas {\n let [maxX, maxY] = getCanvasSize(base)\n for (const overlay of overlays) {\n const [oX, oY] = getCanvasSize(overlay)\n maxX = Math.max(maxX, oX + offset.x)\n maxY = Math.max(maxY, oY + offset.y)\n }\n\n const merged = mkCanvas(maxX, maxY)\n\n // Copy base\n for (let x = 0; x <= maxX; x++) {\n for (let y = 0; y <= maxY; y++) {\n if (x < base.length && y < (base[0]?.length ?? 0)) {\n merged[x]![y] = base[x]![y]!\n }\n }\n }\n\n // Apply overlays\n for (const overlay of overlays) {\n for (let x = 0; x < overlay.length; x++) {\n for (let y = 0; y < (overlay[0]?.length ?? 0); y++) {\n const c = overlay[x]![y]!\n if (c !== ' ') {\n const mx = x + offset.x\n const my = y + offset.y\n const current = merged[mx]![my]!\n if (!useAscii && isJunctionChar(c) && isJunctionChar(current)) {\n merged[mx]![my] = mergeJunctions(current, c)\n } else if (isAlphanumeric(current) && isAlphanumeric(c)) {\n // Don't overwrite existing label text with new label text\n // This prevents label collisions (first label wins)\n } else {\n merged[mx]![my] = c\n }\n }\n }\n }\n }\n\n return merged\n}\n\n// ============================================================================\n// Canvas → string conversion\n// ============================================================================\n\n/** Options for converting canvas to string with optional coloring. */\nexport interface CanvasToStringOptions {\n /** Role canvas for applying colors. If not provided, output is plain text. */\n roleCanvas?: RoleCanvas\n /** Color mode for terminal output. Default: 'none' */\n colorMode?: ColorMode\n /** Theme colors for ASCII output. Uses default theme if not provided. */\n theme?: AsciiTheme\n /**\n * Link canvas (see hyperlinks.ts) — each run of cells carrying an href is\n * wrapped in an OSC 8 terminal-hyperlink escape pair. Ignored in 'html'\n * color mode, which is rendered by a browser, not a terminal.\n */\n linkCanvas?: LinkCanvas\n}\n\n/**\n * Convert the canvas to a multi-line string (row by row, left to right).\n * Optionally applies ANSI color codes based on character roles, and OSC 8\n * hyperlink escapes based on the link canvas.\n */\nexport function canvasToString(\n canvas: Canvas,\n options?: CanvasToStringOptions,\n): string {\n const [maxX, maxY] = getCanvasSize(canvas)\n const lines: string[] = []\n\n const roleCanvas = options?.roleCanvas\n const colorMode = options?.colorMode ?? 'none'\n const theme = options?.theme ?? DEFAULT_ASCII_THEME\n const linkCanvas = colorMode === 'html' ? undefined : options?.linkCanvas\n\n for (let y = 0; y <= maxY; y++) {\n if (colorMode === 'none' || !roleCanvas) {\n // Plain text output — no colors\n const chars: string[] = []\n for (let x = 0; x <= maxX; x++) {\n chars.push(canvas[x]![y]!)\n }\n if (linkCanvas) {\n const links = chars.map((_, x) => linkCanvas[x]?.[y] ?? null)\n lines.push(joinWithLinks(chars, links))\n } else {\n lines.push(chars.join(''))\n }\n } else {\n // Colored output — collect chars, roles, and links for this row\n const chars: string[] = []\n const roles: (CharRole | null)[] = []\n const links: (string | null)[] = []\n for (let x = 0; x <= maxX; x++) {\n chars.push(canvas[x]![y]!)\n roles.push(roleCanvas[x]?.[y] ?? null)\n links.push(linkCanvas?.[x]?.[y] ?? null)\n }\n lines.push(\n colorizeLine(\n chars,\n roles,\n theme,\n colorMode,\n linkCanvas ? links : undefined,\n ),\n )\n }\n }\n\n return lines.join('\\n')\n}\n\n// ============================================================================\n// Canvas vertical flip — used for BT (bottom-to-top) direction support.\n//\n// The ASCII renderer lays out graphs top-down (TD). For BT direction, we\n// flip the finished canvas vertically and remap directional characters so\n// arrows point upward and corners are mirrored correctly.\n// ============================================================================\n\n/**\n * Characters that change meaning when the Y-axis is flipped.\n * Symmetric characters (─, │, ├, ┤, ┼) are unchanged.\n */\nconst VERTICAL_FLIP_MAP: Record<string, string> = {\n // Unicode arrows\n '▲': '▼',\n '▼': '▲',\n // Diagonal arrowheads — U+2196–U+2199, not the filled triangles (◢◣◤◥),\n // since JetBrains Mono NL has no glyph for those at all. See\n // draw-arrows.ts's drawArrowHead and issue #1062.\n '↖': '↙',\n '↙': '↖',\n '↗': '↘',\n '↘': '↗',\n // ASCII arrows\n '^': 'v',\n v: '^',\n // Unicode corners\n '┌': '└',\n '└': '┌',\n '┐': '┘',\n '┘': '┐',\n // Unicode junctions (T-pieces flip vertically)\n '┬': '┴',\n '┴': '┬',\n // Box-start junctions (exit points from node boxes)\n '╵': '╷',\n '╷': '╵',\n}\n\n/**\n * Flip the canvas vertically (mirror across the horizontal center).\n * Reverses row order within each column and remaps directional characters\n * (arrows, corners, junctions) so they point the correct way after flip.\n *\n * Used to transform a TD-rendered canvas into BT output.\n * Mutates the canvas in place and returns it.\n */\nexport function flipCanvasVertically(canvas: Canvas): Canvas {\n // Reverse each column array (Y-axis flip in column-major layout)\n for (const col of canvas) {\n col.reverse()\n }\n\n // Remap directional characters that change meaning after vertical flip\n for (const col of canvas) {\n for (const [y, ch] of col.entries()) {\n const flipped = VERTICAL_FLIP_MAP[ch]\n if (flipped) col[y] = flipped\n }\n }\n\n return canvas\n}\n\n/**\n * Flip the role canvas vertically to match flipCanvasVertically.\n * Mutates the role canvas in place and returns it.\n */\nexport function flipRoleCanvasVertically(roleCanvas: RoleCanvas): RoleCanvas {\n for (const col of roleCanvas) {\n col.reverse()\n }\n return roleCanvas\n}\n\n/**\n * Draw text string onto the canvas starting at the given coordinate.\n * By default, preserves existing non-space characters (labels don't overwrite each other).\n * Set forceOverwrite=true to always overwrite (for box content).\n *\n * Wide characters (CJK/kana/hangul/fullwidth-form/emoji — see\n * `isWideChar`/`toDisplayCells` in `display-width.ts`) occupy two grid cells:\n * the glyph itself followed by a placeholder cell. This keeps grid-cell\n * count in sync with the two terminal columns the glyph actually renders as,\n * so text drawn here lines up with widths computed via `displayWidth`.\n */\nexport function drawText(\n canvas: Canvas,\n start: DrawingCoord,\n text: string,\n forceOverwrite = false,\n): void {\n const cells = toDisplayCells(text)\n increaseSize(canvas, start.x + cells.length, start.y)\n for (const [i, cell] of cells.entries()) {\n const x = start.x + i\n const current = canvas[x]![start.y]!\n // Only write if target is empty or we're forcing overwrite\n if (forceOverwrite || current === ' ') {\n canvas[x]![start.y] = cell\n }\n }\n}\n\n/**\n * Set the canvas size to fit all grid columns and rows.\n * Called after layout to ensure the canvas covers the full drawing area.\n *\n * `offsetX`/`offsetY` must be the same drawing-coordinate offset\n * `gridToDrawingCoord` adds to every point it computes (`graph.offsetX`/\n * `offsetY`, set by `offsetDrawingForSubgraphs`) — omitting them once left\n * the canvas exactly that much too narrow/short, silently clipping any\n * edge line whose drawing coordinate landed in the unreserved margin (see\n * `createMapping`'s call site for the full story).\n */\nexport function setCanvasSizeToGrid(\n canvas: Canvas,\n columnWidth: Map<number, number>,\n rowHeight: Map<number, number>,\n offsetX = 0,\n offsetY = 0,\n): void {\n let maxX = offsetX\n let maxY = offsetY\n for (const w of columnWidth.values()) maxX += w\n for (const h of rowHeight.values()) maxY += h\n increaseSize(canvas, maxX - 1, maxY - 1)\n}\n\n/**\n * Set the role canvas size to match the grid dimensions.\n * Should be called alongside setCanvasSizeToGrid, with the same\n * `offsetX`/`offsetY` — see that function's doc.\n */\nexport function setRoleCanvasSizeToGrid(\n roleCanvas: RoleCanvas,\n columnWidth: Map<number, number>,\n rowHeight: Map<number, number>,\n offsetX = 0,\n offsetY = 0,\n): void {\n let maxX = offsetX\n let maxY = offsetY\n for (const w of columnWidth.values()) maxX += w\n for (const h of rowHeight.values()) maxY += h\n increaseRoleCanvasSize(roleCanvas, maxX - 1, maxY - 1)\n}\n","// ============================================================================\n// ASCII renderer — XY Chart\n//\n// Renders xychart-beta diagrams to ASCII/Unicode text art.\n// Uses the parsed XYChart type directly (not PositionedXYChart) since\n// pixel coordinates don't map to character grids.\n//\n// Bar charts: █ (Unicode) or # (ASCII) block characters.\n// Line charts: continuous staircase routing with rounded corners (╭╮╰╯│─).\n//\n// Multi-series support: each series gets a distinct color from a palette.\n// ============================================================================\n\nimport {\n parseXYChart,\n getSeriesColor,\n CHART_ACCENT_FALLBACK,\n} from '@zombie-mermaid/mermaid-parser'\nimport type { XYChart } from '@zombie-mermaid/mermaid-parser'\nimport type {\n AsciiConfig,\n AsciiTheme,\n ColorMode,\n CharRole,\n Canvas,\n RoleCanvas,\n} from './types.ts'\nimport { colorizeText } from './ansi.ts'\nimport { mkCanvas, mkRoleCanvas, write } from './canvas.ts'\nimport { splitStatements } from '@zombie-mermaid/core'\n\n// ============================================================================\n// Constants\n// ============================================================================\n\nconst PLOT_WIDTH = 60\nconst PLOT_HEIGHT = 20\n\n// Unicode box-drawing characters\nconst UNI = {\n hLine: '─',\n vLine: '│',\n origin: '┼',\n yTick: '┤',\n xTick: '┬',\n bar: '█',\n grid: '·',\n cornerTL: '╭', // top-left: down+right\n cornerTR: '╮', // top-right: down+left\n cornerBL: '╰', // bottom-left: up+right\n cornerBR: '╯', // bottom-right: up+left\n} as const\n\n// ASCII fallback characters\nconst ASC = {\n hLine: '-',\n vLine: '|',\n origin: '+',\n yTick: '+',\n xTick: '+',\n bar: '#',\n grid: '.',\n cornerTL: '+',\n cornerTR: '+',\n cornerBL: '+',\n cornerBR: '+',\n} as const\n\n// ============================================================================\n// Multi-series color support\n// ============================================================================\n\n/** Per-cell hex color override canvas. Parallel to RoleCanvas. */\ntype HexCanvas = (string | null)[][]\n\n/** Generate an array of hex colors, one per series. */\nfunction getSeriesColors(total: number, theme: AsciiTheme): string[] {\n const accent = theme.accent ?? CHART_ACCENT_FALLBACK\n if (total <= 1) return [accent]\n return Array.from({ length: total }, (_, i) =>\n getSeriesColor(i, accent, theme.bg),\n )\n}\n\n/** Map a CharRole to its hex color from the theme (for canvasToString fallback). */\nfunction roleToHex(role: CharRole, theme: AsciiTheme): string {\n switch (role) {\n case 'text':\n return theme.fg\n case 'border':\n return theme.border\n case 'line':\n return theme.line\n case 'arrow':\n return theme.arrow\n case 'corner':\n return theme.corner ?? theme.line\n case 'junction':\n return theme.junction ?? theme.border\n default:\n return theme.fg\n }\n}\n\n// ============================================================================\n// Public API\n// ============================================================================\n\nexport function renderXYChartAscii(\n text: string,\n config: AsciiConfig,\n colorMode: ColorMode,\n theme: AsciiTheme,\n): string {\n const lines = splitStatements(text)\n const chart = parseXYChart(lines)\n const ch = config.useAscii ? ASC : UNI\n\n if (chart.horizontal) {\n return renderHorizontal(chart, ch, colorMode, theme)\n }\n return renderVertical(chart, ch, colorMode, theme)\n}\n\n// ============================================================================\n// Vertical chart layout + rendering\n// ============================================================================\n\nfunction renderVertical(\n chart: XYChart,\n ch: typeof UNI | typeof ASC,\n colorMode: ColorMode,\n theme: AsciiTheme,\n): string {\n const dataCount = getDataCount(chart)\n if (dataCount === 0) return ''\n\n // parseXYChart always derives a y-axis range (from data or a 0-100\n // fallback — see xychart/parser.ts) before returning, so this is never\n // actually undefined for parsed input. That guarantee lives in a\n // different module, so narrow it here explicitly instead of trusting it\n // silently across the boundary.\n const yRange = chart.yAxis.range\n if (!yRange) {\n /* v8 ignore next */\n throw new Error('XY chart: y-axis range was not set by the parser')\n }\n const yTicks = niceTickValues(yRange.min, yRange.max)\n const yLabels = yTicks.map((v) => formatTickValue(v))\n const yGutter = Math.max(...yLabels.map((l) => l.length)) + 1\n\n const plotW = Math.max(PLOT_WIDTH, dataCount * 6)\n const plotH = PLOT_HEIGHT\n const bandW = Math.floor(plotW / dataCount)\n const catLabels = getCategoryLabels(chart, dataCount)\n\n // Canvas dimensions\n const hasTitle = !!chart.title\n const hasXTitle = !!chart.xAxis.title\n const hasYTitle = !!chart.yAxis.title\n const hasLegend = chart.series.length > 1\n const titleRow = hasTitle ? 0 : -1\n const plotTop = (hasTitle ? 2 : 0) + (hasLegend ? 1 : 0)\n // Reserve a column for the rotated y-axis title (rendered top-to-bottom,\n // one character per row, mirroring how mermaid.js rotates it 90deg along\n // the left edge) plus a 1-column gap before the tick-value gutter.\n const yTitleWidth = hasYTitle ? 2 : 0\n const plotLeft = yTitleWidth + yGutter + 1 // +1 for axis character\n const totalW = plotLeft + bandW * dataCount + 2\n const xAxisRow = plotTop + plotH\n const xLabelRow = xAxisRow + 1\n const xTitleRow = hasXTitle ? xLabelRow + 1 : -1\n const totalH =\n xLabelRow + 1 + (hasXTitle ? 1 : 0) + (hasLegend && !hasTitle ? 0 : 0)\n\n // Create canvas\n const canvas = createCanvas(totalW, totalH)\n const roles = createRoleCanvas(totalW, totalH)\n const hexColors = createHexCanvas(totalW, totalH)\n\n // Series colors\n const seriesColors = getSeriesColors(chart.series.length, theme)\n\n // Scales\n const valueToRow = (v: number): number => {\n const t = (v - yRange.min) / (yRange.max - yRange.min || 1)\n return Math.round(t * (plotH - 1))\n }\n const bandCenter = (i: number): number =>\n plotLeft + Math.floor(bandW * (i + 0.5))\n\n // 1. Title\n if (chart.title && titleRow >= 0) {\n writeText(\n canvas,\n roles,\n titleRow,\n Math.floor(totalW / 2 - chart.title.length / 2),\n chart.title,\n 'text',\n )\n }\n\n // 2. Legend\n if (hasLegend) {\n const legendRow = hasTitle ? 1 : 0\n drawLegend(\n canvas,\n roles,\n hexColors,\n chart,\n legendRow,\n totalW,\n ch,\n seriesColors,\n )\n }\n\n // 3. Y-axis line + ticks + labels\n for (let row = 0; row < plotH; row++) {\n const displayRow = plotTop + (plotH - 1 - row)\n set(canvas, roles, displayRow, plotLeft - 1, ch.vLine, 'border')\n }\n // Origin\n set(canvas, roles, xAxisRow, plotLeft - 1, ch.origin, 'border')\n\n for (const tick of yTicks) {\n const row = valueToRow(tick)\n if (row < 0 || row >= plotH) continue\n const displayRow = plotTop + (plotH - 1 - row)\n const label = formatTickValue(tick)\n // Tick mark on axis\n set(\n canvas,\n roles,\n displayRow,\n plotLeft - 1,\n row === 0 ? ch.origin : ch.yTick,\n 'border',\n )\n // Label\n const labelStart = yTitleWidth + yGutter - label.length\n writeText(canvas, roles, displayRow, Math.max(0, labelStart), label, 'text')\n }\n\n // 4. X-axis line + ticks + labels\n for (let c = plotLeft; c < plotLeft + bandW * dataCount; c++) {\n set(canvas, roles, xAxisRow, c, ch.hLine, 'border')\n }\n for (let i = 0; i < dataCount; i++) {\n const cx = bandCenter(i)\n set(canvas, roles, xAxisRow, cx, ch.xTick, 'border')\n // Label below\n const label = catLabels[i]!\n const labelStart = cx - Math.floor(label.length / 2)\n writeText(canvas, roles, xLabelRow, Math.max(0, labelStart), label, 'text')\n }\n\n // 5. X-axis title\n if (chart.xAxis.title && xTitleRow >= 0) {\n const title = chart.xAxis.title\n writeText(\n canvas,\n roles,\n xTitleRow,\n Math.floor(totalW / 2 - title.length / 2),\n title,\n 'text',\n )\n }\n\n // 5b. Y-axis title — rendered rotated: one character per row, top-to-\n // bottom, in the leftmost column, mirroring how mermaid.js's SVG rotates\n // this label 90deg along the left edge of the chart. Vertically centered\n // within the plot's row range.\n if (chart.yAxis.title) {\n const title = chart.yAxis.title\n const startRow =\n plotTop + Math.max(0, Math.floor((plotH - title.length) / 2))\n for (let i = 0; i < title.length; i++) {\n const row = startRow + i\n if (row >= plotTop + plotH) break\n set(canvas, roles, row, 0, title[i]!, 'text')\n }\n }\n\n // 6. Grid lines (subtle horizontal dots at y-tick positions)\n for (const tick of yTicks) {\n const row = valueToRow(tick)\n if (row < 0 || row >= plotH) continue\n const displayRow = plotTop + (plotH - 1 - row)\n for (let c = plotLeft; c < plotLeft + bandW * dataCount; c++) {\n if (get(canvas, displayRow, c) === ' ') {\n set(canvas, roles, displayRow, c, ch.grid, 'line')\n }\n }\n }\n\n // 7. Bars — track global series index for per-series colors\n const barEntries: { data: number[]; globalIdx: number }[] = []\n for (let si = 0; si < chart.series.length; si++) {\n if (chart.series[si]!.type === 'bar')\n barEntries.push({ data: chart.series[si]!.data, globalIdx: si })\n }\n\n if (barEntries.length > 0) {\n // Multiple bar series in the same xychart-beta share one x-position and\n // width per category — mermaid.js overlays/occludes them rather than\n // laying them out as side-by-side grouped bars (confirmed against real\n // mermaid.js SVG output: bar-plot-N rects for every series in a category\n // carry identical x/width, differing only in y/height — see issue #452).\n // Draw order (series declaration order) is what produces the occlusion:\n // a later series drawn on top of an earlier, taller-or-shorter one.\n const usable = Math.max(1, bandW - 2)\n const singleBarW = Math.max(1, Math.min(usable, 8))\n const baseRow = valueToRow(Math.max(0, yRange.min))\n\n for (let bIdx = 0; bIdx < barEntries.length; bIdx++) {\n const entry = barEntries[bIdx]!\n const hexColor = seriesColors[entry.globalIdx]!\n for (let i = 0; i < entry.data.length; i++) {\n const cx = bandCenter(i)\n const bx = cx - Math.floor(singleBarW / 2)\n const valRow = valueToRow(entry.data[i]!)\n const fromRow = Math.min(baseRow, valRow)\n const toRow = Math.max(baseRow, valRow)\n\n for (let row = fromRow; row <= toRow; row++) {\n const displayRow = plotTop + (plotH - 1 - row)\n for (let c = bx; c < bx + singleBarW; c++) {\n set(\n canvas,\n roles,\n displayRow,\n c,\n ch.bar,\n 'arrow',\n hexColors,\n hexColor,\n )\n }\n }\n }\n }\n }\n\n // 8. Lines (staircase routing with rounded corners)\n const lineEntries: { data: number[]; globalIdx: number }[] = []\n for (let si = 0; si < chart.series.length; si++) {\n if (chart.series[si]!.type === 'line')\n lineEntries.push({ data: chart.series[si]!.data, globalIdx: si })\n }\n\n for (const entry of lineEntries) {\n if (entry.data.length === 0) continue\n const hexColor = seriesColors[entry.globalIdx]!\n drawStaircaseLine(\n canvas,\n roles,\n entry.data,\n bandCenter,\n valueToRow,\n plotTop,\n plotH,\n plotLeft,\n bandW * dataCount,\n ch,\n hexColors,\n hexColor,\n )\n }\n\n return canvasToString(canvas, roles, hexColors, colorMode, theme)\n}\n\n// ============================================================================\n// Horizontal chart layout + rendering\n// ============================================================================\n\nfunction renderHorizontal(\n chart: XYChart,\n ch: typeof UNI | typeof ASC,\n colorMode: ColorMode,\n theme: AsciiTheme,\n): string {\n const dataCount = getDataCount(chart)\n if (dataCount === 0) return ''\n\n // See the matching comment in renderVertical: the parser always derives a\n // y-axis range before returning, but that guarantee lives in a different\n // module, so it's narrowed here explicitly rather than trusted silently.\n const yRange = chart.yAxis.range\n if (!yRange) {\n /* v8 ignore next */\n throw new Error('XY chart: y-axis range was not set by the parser')\n }\n const valueTicks = niceTickValues(yRange.min, yRange.max)\n const catLabels = getCategoryLabels(chart, dataCount)\n const catGutter = Math.max(...catLabels.map((l) => l.length)) + 1\n\n const plotW = Math.max(PLOT_WIDTH, 40)\n const bandH = Math.max(2, Math.floor(PLOT_HEIGHT / dataCount))\n const plotH = bandH * dataCount\n\n const hasTitle = !!chart.title\n const hasYTitle = !!chart.yAxis.title\n const hasLegend = chart.series.length > 1\n const plotTop = (hasTitle ? 2 : 0) + (hasLegend ? 1 : 0)\n const plotLeft = catGutter + 1\n const totalW = plotLeft + plotW + 2\n const totalH = plotTop + plotH + 2 + (hasYTitle ? 1 : 0)\n const xAxisRow = plotTop + plotH\n\n const canvas = createCanvas(totalW, totalH)\n const roles = createRoleCanvas(totalW, totalH)\n const hexColors = createHexCanvas(totalW, totalH)\n\n // Series colors\n const seriesColors = getSeriesColors(chart.series.length, theme)\n\n // Value scale (horizontal)\n const valueToCol = (v: number): number => {\n const t = (v - yRange.min) / (yRange.max - yRange.min || 1)\n return plotLeft + Math.round(t * (plotW - 1))\n }\n const bandMid = (i: number): number => plotTop + Math.floor(bandH * (i + 0.5))\n\n // Title\n if (chart.title) {\n writeText(\n canvas,\n roles,\n 0,\n Math.floor(totalW / 2 - chart.title.length / 2),\n chart.title,\n 'text',\n )\n }\n\n // Legend\n if (hasLegend) {\n const legendRow = hasTitle ? 1 : 0\n drawLegend(\n canvas,\n roles,\n hexColors,\n chart,\n legendRow,\n totalW,\n ch,\n seriesColors,\n )\n }\n\n // Y-axis (category axis on left)\n for (let r = plotTop; r < plotTop + plotH; r++) {\n set(canvas, roles, r, plotLeft - 1, ch.vLine, 'border')\n }\n set(canvas, roles, xAxisRow, plotLeft - 1, ch.origin, 'border')\n\n for (let i = 0; i < dataCount; i++) {\n const my = bandMid(i)\n const label = catLabels[i]!\n const labelStart = catGutter - label.length\n writeText(canvas, roles, my, Math.max(0, labelStart), label, 'text')\n }\n\n // X-axis (value axis on bottom)\n for (let c = plotLeft; c < plotLeft + plotW; c++) {\n set(canvas, roles, xAxisRow, c, ch.hLine, 'border')\n }\n for (const tick of valueTicks) {\n const cx = valueToCol(tick)\n if (cx < plotLeft || cx >= plotLeft + plotW) continue\n set(canvas, roles, xAxisRow, cx, ch.xTick, 'border')\n const label = formatTickValue(tick)\n writeText(\n canvas,\n roles,\n xAxisRow + 1,\n cx - Math.floor(label.length / 2),\n label,\n 'text',\n )\n }\n\n // Y-axis title\n if (chart.yAxis.title) {\n const title = chart.yAxis.title\n writeText(\n canvas,\n roles,\n totalH - 1,\n Math.floor(totalW / 2 - title.length / 2),\n title,\n 'text',\n )\n }\n\n // Grid lines (vertical at value tick positions)\n for (const tick of valueTicks) {\n const cx = valueToCol(tick)\n if (cx < plotLeft || cx >= plotLeft + plotW) continue\n for (let r = plotTop; r < plotTop + plotH; r++) {\n if (get(canvas, r, cx) === ' ') {\n set(canvas, roles, r, cx, ch.grid, 'line')\n }\n }\n }\n\n // Bars (horizontal) — with per-series colors\n const barEntries: { data: number[]; globalIdx: number }[] = []\n for (let si = 0; si < chart.series.length; si++) {\n if (chart.series[si]!.type === 'bar')\n barEntries.push({ data: chart.series[si]!.data, globalIdx: si })\n }\n\n if (barEntries.length > 0) {\n // Same overlay/occlusion behavior as the vertical layout above (issue\n // #452): every bar series in a category shares one row rather than\n // being split into grouped sub-rows, so a later series' bar paints over\n // an earlier one.\n const singleBarH = 1\n const baseCol = valueToCol(Math.max(0, yRange.min))\n\n for (let bIdx = 0; bIdx < barEntries.length; bIdx++) {\n const entry = barEntries[bIdx]!\n const hexColor = seriesColors[entry.globalIdx]!\n for (let i = 0; i < entry.data.length; i++) {\n const by = bandMid(i)\n const valCol = valueToCol(entry.data[i]!)\n const fromCol = Math.min(baseCol, valCol)\n const toCol = Math.max(baseCol, valCol)\n\n for (let r = by; r < by + singleBarH; r++) {\n for (let c = fromCol; c <= toCol; c++) {\n set(canvas, roles, r, c, ch.bar, 'arrow', hexColors, hexColor)\n }\n }\n }\n }\n }\n\n // Lines (horizontal staircase: value on x, category on y) — with per-series colors\n const lineEntries: { data: number[]; globalIdx: number }[] = []\n for (let si = 0; si < chart.series.length; si++) {\n if (chart.series[si]!.type === 'line')\n lineEntries.push({ data: chart.series[si]!.data, globalIdx: si })\n }\n\n for (const entry of lineEntries) {\n if (entry.data.length === 0) continue\n const hexColor = seriesColors[entry.globalIdx]!\n drawHorizontalStaircaseLine(\n canvas,\n roles,\n entry.data,\n bandMid,\n valueToCol,\n plotTop,\n plotH,\n plotLeft,\n plotW,\n ch,\n hexColors,\n hexColor,\n )\n }\n\n return canvasToString(canvas, roles, hexColors, colorMode, theme)\n}\n\n// ============================================================================\n// Staircase line drawing — vertical charts\n//\n// Connects data points with flat segments (─) at each value's row,\n// vertical segments (│) between rows, and rounded corners (╭╮╰╯)\n// at transitions. The vertical step happens at the midpoint column\n// between adjacent data points.\n// ============================================================================\n\nfunction drawStaircaseLine(\n canvas: Canvas,\n roles: RoleCanvas,\n data: number[],\n bandCenter: (i: number) => number,\n valueToRow: (v: number) => number,\n plotTop: number,\n plotH: number,\n plotLeft: number,\n plotTotalW: number,\n ch: typeof UNI | typeof ASC,\n hexCanvas?: HexCanvas,\n hexColor?: string | null,\n): void {\n if (data.length === 0) return\n\n const points = data.map((v, i) => ({\n col: bandCenter(i),\n row: valueToRow(v),\n }))\n\n // Helper to draw on the canvas (row 0 = bottom, displayed inverted)\n const drawAt = (col: number, row: number, char: string) => {\n const displayRow = plotTop + (plotH - 1 - row)\n if (displayRow >= 0 && col >= plotLeft && col < plotLeft + plotTotalW) {\n set(canvas, roles, displayRow, col, char, 'arrow', hexCanvas, hexColor)\n }\n }\n\n // Single point: just draw a flat segment\n if (points.length === 1) {\n drawAt(points[0]!.col, points[0]!.row, ch.hLine)\n return\n }\n\n for (let i = 0; i < points.length - 1; i++) {\n const p1 = points[i]!\n const p2 = points[i + 1]!\n\n if (p1.row === p2.row) {\n // Flat: draw ─ across\n for (let c = p1.col; c <= p2.col; c++) {\n drawAt(c, p1.row, ch.hLine)\n }\n continue\n }\n\n const midCol = Math.round((p1.col + p2.col) / 2)\n const goingUp = p2.row > p1.row\n\n // 1. Flat at p1's row from p1.col to midCol-1\n for (let c = p1.col; c < midCol; c++) {\n drawAt(c, p1.row, ch.hLine)\n }\n\n // 2. Corner at (midCol, p1.row)\n // goingUp: ─ from LEFT, │ going UP → LEFT+TOP = ╯ (cornerBR)\n // goingDown: ─ from LEFT, │ going DOWN → LEFT+BOT = ╮ (cornerTR)\n if (goingUp) {\n drawAt(midCol, p1.row, ch.cornerBR) // ╯\n } else {\n drawAt(midCol, p1.row, ch.cornerTR) // ╮\n }\n\n // 3. Vertical from p1.row to p2.row (exclusive of endpoints)\n const minRow = Math.min(p1.row, p2.row)\n const maxRow = Math.max(p1.row, p2.row)\n for (let row = minRow + 1; row < maxRow; row++) {\n drawAt(midCol, row, ch.vLine)\n }\n\n // 4. Corner at (midCol, p2.row)\n // goingUp: │ from BOTTOM, ─ going RIGHT → BOT+RIGHT = ╭ (cornerTL)\n // goingDown: │ from TOP, ─ going RIGHT → TOP+RIGHT = ╰ (cornerBL)\n if (goingUp) {\n drawAt(midCol, p2.row, ch.cornerTL) // ╭\n } else {\n drawAt(midCol, p2.row, ch.cornerBL) // ╰\n }\n\n // 5. Flat at p2's row from midCol+1 to p2.col\n for (let c = midCol + 1; c <= p2.col; c++) {\n drawAt(c, p2.row, ch.hLine)\n }\n\n // Leading flat for first segment (before p1.col)\n if (i === 0) {\n const leadStart = Math.max(\n plotLeft,\n p1.col - Math.floor((p2.col - p1.col) / 4),\n )\n for (let c = leadStart; c < p1.col; c++) {\n drawAt(c, p1.row, ch.hLine)\n }\n }\n\n // Trailing flat for last segment (after p2.col)\n if (i === points.length - 2) {\n const trailEnd = Math.min(\n plotLeft + plotTotalW - 1,\n p2.col + Math.floor((p2.col - p1.col) / 4),\n )\n for (let c = p2.col + 1; c <= trailEnd; c++) {\n drawAt(c, p2.row, ch.hLine)\n }\n }\n }\n}\n\n// ============================================================================\n// Staircase line drawing — horizontal charts\n//\n// Same staircase approach but with axes swapped:\n// data values map to columns (horizontal position) and categories map to\n// rows (vertical position). Flat segments are vertical (│), transitions\n// are horizontal (─), with the same rounded corners.\n// ============================================================================\n\nfunction drawHorizontalStaircaseLine(\n canvas: Canvas,\n roles: RoleCanvas,\n data: number[],\n bandMid: (i: number) => number,\n valueToCol: (v: number) => number,\n plotTop: number,\n plotH: number,\n plotLeft: number,\n plotW: number,\n ch: typeof UNI | typeof ASC,\n hexCanvas?: HexCanvas,\n hexColor?: string | null,\n): void {\n if (data.length === 0) return\n\n const points = data.map((v, i) => ({\n row: bandMid(i),\n col: valueToCol(v),\n }))\n\n const drawAt = (row: number, col: number, char: string) => {\n if (\n row >= plotTop &&\n row < plotTop + plotH &&\n col >= plotLeft &&\n col < plotLeft + plotW\n ) {\n set(canvas, roles, row, col, char, 'arrow', hexCanvas, hexColor)\n }\n }\n\n if (points.length === 1) {\n drawAt(points[0]!.row, points[0]!.col, ch.vLine)\n return\n }\n\n for (let i = 0; i < points.length - 1; i++) {\n const p1 = points[i]!\n const p2 = points[i + 1]!\n\n if (p1.col === p2.col) {\n // Same value: draw │ down\n for (let r = p1.row; r <= p2.row; r++) {\n drawAt(r, p1.col, ch.vLine)\n }\n continue\n }\n\n const midRow = Math.round((p1.row + p2.row) / 2)\n const goingRight = p2.col > p1.col\n\n // 1. Vertical at p1's col from p1.row to midRow-1\n for (let r = p1.row; r < midRow; r++) {\n drawAt(r, p1.col, ch.vLine)\n }\n\n // 2. Corner at (midRow, p1.col)\n // goingRight: │ from TOP, ─ going RIGHT → TOP+RIGHT = ╰ (cornerBL)\n // goingLeft: │ from TOP, ─ going LEFT → TOP+LEFT = ╯ (cornerBR)\n if (goingRight) {\n drawAt(midRow, p1.col, ch.cornerBL) // ╰\n } else {\n drawAt(midRow, p1.col, ch.cornerBR) // ╯\n }\n\n // 3. Horizontal from p1.col to p2.col (exclusive)\n const minCol = Math.min(p1.col, p2.col)\n const maxCol = Math.max(p1.col, p2.col)\n for (let c = minCol + 1; c < maxCol; c++) {\n drawAt(midRow, c, ch.hLine)\n }\n\n // 4. Corner at (midRow, p2.col)\n // goingRight: ─ from LEFT, │ going DOWN → LEFT+BOT = ╮ (cornerTR)\n // goingLeft: ─ from RIGHT, │ going DOWN → RIGHT+BOT = ╭ (cornerTL)\n if (goingRight) {\n drawAt(midRow, p2.col, ch.cornerTR) // ╮\n } else {\n drawAt(midRow, p2.col, ch.cornerTL) // ╭\n }\n\n // 5. Vertical at p2's col from midRow+1 to p2.row\n for (let r = midRow + 1; r <= p2.row; r++) {\n drawAt(r, p2.col, ch.vLine)\n }\n }\n}\n\n// ============================================================================\n// Legend — shows series symbols with per-series colors\n// ============================================================================\n\nfunction drawLegend(\n canvas: Canvas,\n roles: RoleCanvas,\n hexCanvas: HexCanvas,\n chart: XYChart,\n row: number,\n totalW: number,\n ch: typeof UNI | typeof ASC,\n seriesColors: string[],\n): void {\n // Build legend items with global series indices\n type LegendItem = { symbol: string; label: string; globalIdx: number }\n const items: LegendItem[] = []\n let barIdx = 0,\n lineIdx = 0\n for (let si = 0; si < chart.series.length; si++) {\n const s = chart.series[si]!\n if (s.type === 'bar') {\n items.push({ symbol: ch.bar, label: `Bar ${barIdx + 1}`, globalIdx: si })\n barIdx++\n } else {\n items.push({\n symbol: ch.hLine,\n label: `Line ${lineIdx + 1}`,\n globalIdx: si,\n })\n lineIdx++\n }\n }\n\n // Calculate total legend width: \"symbol space label symbol space label ...\"\n let totalLen = 0\n for (let i = 0; i < items.length; i++) {\n if (i > 0) totalLen += 2 // gap between items\n totalLen += 1 + 1 + items[i]!.label.length // symbol + space + label\n }\n\n const startCol = Math.max(0, Math.floor(totalW / 2 - totalLen / 2))\n let col = startCol\n\n for (let i = 0; i < items.length; i++) {\n if (i > 0) col += 2 // gap\n const item = items[i]!\n // Symbol with series-specific color\n set(\n canvas,\n roles,\n row,\n col,\n item.symbol,\n 'arrow',\n hexCanvas,\n seriesColors[item.globalIdx],\n )\n col += 1\n // Space (already ' ' from canvas init)\n col += 1\n // Label text\n writeText(canvas, roles, row, col, item.label, 'text')\n col += item.label.length\n }\n}\n\n// ============================================================================\n// Canvas utilities\n// ============================================================================\n\n// createCanvas/createRoleCanvas delegate to the shared canvas.ts primitives\n// (mkCanvas/mkRoleCanvas), which take inclusive max-index arguments rather\n// than width/height — hence the `- 1`. createHexCanvas has no shared\n// equivalent: the per-cell hex color overlay is specific to xychart's\n// multi-series coloring and isn't part of the general Canvas/RoleCanvas\n// model in canvas.ts, so it keeps its own small implementation.\nfunction createCanvas(width: number, height: number): Canvas {\n return mkCanvas(width - 1, height - 1)\n}\n\nfunction createRoleCanvas(width: number, height: number): RoleCanvas {\n return mkRoleCanvas(width - 1, height - 1)\n}\n\nfunction createHexCanvas(width: number, height: number): HexCanvas {\n return Array.from({ length: width }, () =>\n Array.from<string | null>({ length: height }).fill(null),\n )\n}\n\nfunction set(\n canvas: Canvas,\n roles: RoleCanvas,\n row: number,\n col: number,\n char: string,\n role: CharRole,\n hexCanvas?: HexCanvas,\n hex?: string | null,\n): void {\n // Delegate the character+role write to the shared bounds-checked\n // primitive (same clipping semantics as before: out-of-range coordinates\n // are silently ignored). The hex-color overlay isn't known to canvas.ts,\n // so it's still set here directly, guarded by the same bounds check.\n const inBounds =\n col >= 0 &&\n col < canvas.length &&\n row >= 0 &&\n row < (canvas[0]?.length ?? 0)\n if (!inBounds) return\n write(canvas, col, row, char, { role, roleCanvas: roles })\n if (hexCanvas && hex) hexCanvas[col]![row] = hex\n}\n\nfunction get(canvas: Canvas, row: number, col: number): string {\n if (col >= 0 && col < canvas.length && row >= 0 && row < canvas[0]!.length) {\n return canvas[col]![row]!\n }\n return ' '\n}\n\nfunction writeText(\n canvas: Canvas,\n roles: RoleCanvas,\n row: number,\n startCol: number,\n text: string,\n role: CharRole,\n): void {\n for (let i = 0; i < text.length; i++) {\n set(canvas, roles, row, startCol + i, text[i]!, role)\n }\n}\n\n// ============================================================================\n// Canvas → string (with per-cell hex color support)\n// ============================================================================\n\nfunction canvasToString(\n canvas: Canvas,\n roles: RoleCanvas,\n hexCanvas: HexCanvas,\n colorMode: ColorMode,\n theme: AsciiTheme,\n): string {\n if (canvas.length === 0) return ''\n const height = canvas[0]!.length\n const width = canvas.length\n const lines: string[] = []\n\n for (let row = 0; row < height; row++) {\n const chars: string[] = []\n const rowRoles: (CharRole | null)[] = []\n const rowHex: (string | null)[] = []\n for (let col = 0; col < width; col++) {\n chars.push(canvas[col]![row]!)\n rowRoles.push(roles[col]![row]!)\n rowHex.push(hexCanvas[col]![row]!)\n }\n // Trim trailing spaces\n let end = chars.length - 1\n while (end >= 0 && chars[end] === ' ') end--\n if (end < 0) {\n lines.push('')\n } else {\n lines.push(\n colorizeRow(\n chars.slice(0, end + 1),\n rowRoles.slice(0, end + 1),\n rowHex.slice(0, end + 1),\n theme,\n colorMode,\n ),\n )\n }\n }\n\n // Trim trailing empty lines\n while (lines.length > 0 && lines[lines.length - 1] === '') {\n lines.pop()\n }\n\n return lines.join('\\n')\n}\n\n/**\n * Colorize a row of characters, using hex color overrides where available\n * and falling back to role-based theme colors otherwise.\n * Groups consecutive same-color characters for efficient escape sequences.\n */\nfunction colorizeRow(\n chars: string[],\n roles: (CharRole | null)[],\n hexOverrides: (string | null)[],\n theme: AsciiTheme,\n mode: ColorMode,\n): string {\n if (mode === 'none') return chars.join('')\n\n let result = ''\n let currentColor: string | null = null\n let buffer = ''\n\n for (let i = 0; i < chars.length; i++) {\n const char = chars[i]!\n\n if (char === ' ') {\n // Flush buffer before whitespace\n if (buffer.length > 0) {\n result += currentColor\n ? colorizeText(buffer, currentColor, mode)\n : buffer\n buffer = ''\n currentColor = null\n }\n result += ' '\n continue\n }\n\n // Effective color: hex override > role-based > null\n const hexOvr = hexOverrides[i] ?? null\n const roleVal = roles[i] ?? null\n const color = hexOvr ?? (roleVal ? roleToHex(roleVal, theme) : null)\n\n if (color === currentColor) {\n buffer += char\n } else {\n // Flush previous group\n if (buffer.length > 0) {\n result += currentColor\n ? colorizeText(buffer, currentColor, mode)\n : buffer\n }\n buffer = char\n currentColor = color\n }\n }\n\n // Flush remaining\n if (buffer.length > 0) {\n result += currentColor ? colorizeText(buffer, currentColor, mode) : buffer\n }\n\n return result\n}\n\n// ============================================================================\n// Helpers (chart-level)\n// ============================================================================\n\nfunction getDataCount(chart: XYChart): number {\n if (chart.xAxis.categories) return chart.xAxis.categories.length\n for (const s of chart.series) {\n if (s.data.length > 0) return s.data.length\n }\n return 0\n}\n\nfunction getCategoryLabels(chart: XYChart, count: number): string[] {\n if (chart.xAxis.categories) return chart.xAxis.categories\n if (chart.xAxis.range) {\n const { min, max } = chart.xAxis.range\n const step = count > 1 ? (max - min) / (count - 1) : 0\n return Array.from({ length: count }, (_, i) =>\n formatTickValue(min + step * i),\n )\n }\n return Array.from({ length: count }, (_, i) => String(i + 1))\n}\n\n/** Generate nice tick values for a numeric range. */\nfunction niceTickValues(min: number, max: number): number[] {\n const range = max - min\n if (range <= 0) return [min]\n\n const rawInterval = range / 6\n const magnitude = Math.pow(10, Math.floor(Math.log10(rawInterval)))\n const residual = rawInterval / magnitude\n let niceInterval: number\n if (residual <= 1.5) niceInterval = magnitude\n else if (residual <= 3) niceInterval = 2 * magnitude\n else if (residual <= 7) niceInterval = 5 * magnitude\n else niceInterval = 10 * magnitude\n\n const start = Math.ceil(min / niceInterval) * niceInterval\n const ticks: number[] = []\n for (let v = start; v <= max + niceInterval * 0.001; v += niceInterval) {\n ticks.push(Math.round(v * 1e10) / 1e10)\n }\n return ticks\n}\n\nfunction formatTickValue(v: number): string {\n if (Number.isInteger(v)) return String(v)\n return v.toFixed(Math.abs(v) < 10 ? 1 : 0)\n}\n","// ============================================================================\n// ASCII renderer — type definitions\n//\n// Ported from AlexanderGrooff/mermaid-ascii (Go).\n// These types model the grid-based coordinate system, 2D text canvas,\n// and graph structures used by the ASCII/Unicode renderer.\n// ============================================================================\n\nimport type { NodeShape } from '@zombie-mermaid/core'\nimport type { Grid } from './grid-occupancy.ts'\n\n// Re-export NodeShape for convenience\nexport type { NodeShape }\n\n/**\n * Shape type for ASCII rendering — maps parser shapes to ASCII renderers.\n * Most shapes from the parser are supported, with fallback to 'rectangle'.\n */\nexport type AsciiNodeShape = NodeShape\n\n/** Logical grid coordinate — nodes occupy 3x3 blocks on this grid. */\nexport interface GridCoord {\n x: number\n y: number\n}\n\n/**\n * A mutable, render-wide A* iteration budget shared across every getPath\n * call made while routing one graph's edges (see pathfinder.ts's getPath\n * and grid.ts's createMapping). Bounds total pathfinding work for the whole\n * render, independent of any single call's own per-call iteration cap.\n */\nexport interface PathBudget {\n remaining: number\n}\n\n/** Character-level coordinate on the 2D text canvas. */\nexport interface DrawingCoord {\n x: number\n y: number\n}\n\n/**\n * Direction constants model positions on a node's 3x3 grid block.\n * Each node occupies grid cells [x..x+2, y..y+2].\n * Directions are offsets into that block, used for edge attachment points.\n *\n * (0,0) UL (1,0) Up (2,0) UR\n * (0,1) Left (1,1) Mid (2,1) Right\n * (0,2) LL (1,2) Down (2,2) LR\n */\nexport interface Direction {\n readonly x: number\n readonly y: number\n}\n\nexport const Up: Direction = { x: 1, y: 0 }\nexport const Down: Direction = { x: 1, y: 2 }\nexport const Left: Direction = { x: 0, y: 1 }\nexport const Right: Direction = { x: 2, y: 1 }\nexport const UpperRight: Direction = { x: 2, y: 0 }\nexport const UpperLeft: Direction = { x: 0, y: 0 }\nexport const LowerRight: Direction = { x: 2, y: 2 }\nexport const LowerLeft: Direction = { x: 0, y: 2 }\nexport const Middle: Direction = { x: 1, y: 1 }\n\n/** All named directions for iteration. */\nexport const ALL_DIRECTIONS: readonly Direction[] = [\n Up,\n Down,\n Left,\n Right,\n UpperRight,\n UpperLeft,\n LowerRight,\n LowerLeft,\n Middle,\n]\n\n/** Compare directions by value (not reference). */\nexport function dirEquals(a: Direction, b: Direction): boolean {\n return a.x === b.x && a.y === b.y\n}\n\ndeclare const cardinalDirectionBrand: unique symbol\n\n/**\n * The 4 pure cardinal directions — Up/Down/Left/Right — the only Direction\n * values pathfinder.ts's routeEdge/tryDirectPath know how to turn into a\n * single L-shaped route (horizontal-first for Left/Right, vertical-first\n * for Up/Down). The other 5 Direction values (the four diagonals, plus\n * Middle) don't correspond to one routing axis. Direction can't\n * structurally distinguish these on its own — all 9 constants share the\n * same {x, y} shape — so this is a nominal brand: requireCardinalDirection\n * below is the only way to produce one, and it checks the actual value\n * rather than trusting a caller's claim.\n */\nexport type CardinalDirection = Direction & {\n readonly [cardinalDirectionBrand]: true\n}\n\nfunction isCardinalDirection(d: Direction): d is CardinalDirection {\n return (\n dirEquals(d, Up) ||\n dirEquals(d, Down) ||\n dirEquals(d, Left) ||\n dirEquals(d, Right)\n )\n}\n\n/**\n * Narrow a Direction into a CardinalDirection, throwing if it isn't one of\n * Up/Down/Left/Right. Mirrors requireGridCoord/requirePathBudget elsewhere\n * in this module family: the invariant that routeEdge only ever receives a\n * cardinal direction lives in caller logic spread across edge-routing.ts\n * and edge-bundling.ts, not in Direction's type, so it's checked explicitly\n * at the boundary rather than trusted silently across modules.\n */\nexport function requireCardinalDirection(d: Direction): CardinalDirection {\n if (!isCardinalDirection(d)) {\n throw new Error(\n `Expected a cardinal direction (Up/Down/Left/Right); got {x:${d.x}, y:${d.y}}`,\n )\n }\n return d\n}\n\n/**\n * 2D text canvas — column-major (canvas[x][y]).\n * Each cell holds a single character (or space).\n */\nexport type Canvas = string[][]\n\n/** A node in the ASCII graph, positioned on the grid. */\nexport interface AsciiNode {\n /** Unique identity key — the original node ID from the parser (e.g. \"A\", \"B\"). */\n name: string\n /** Human-readable label for rendering inside the box (e.g. \"Web Server\"). */\n displayLabel: string\n /** Node shape from the parser (e.g. \"rectangle\", \"diamond\", \"circle\"). */\n shape: AsciiNodeShape\n index: number\n gridCoord: GridCoord | null\n drawingCoord: DrawingCoord | null\n drawing: Canvas | null\n drawn: boolean\n styleClassName: string\n styleClass: AsciiStyleClass\n}\n\n/** Style class for colored node text (ported from Go's classDef). */\nexport interface AsciiStyleClass {\n name: string\n styles: Record<string, string>\n}\n\n/** Edge line style for ASCII rendering. */\nexport type AsciiEdgeStyle = 'solid' | 'dotted' | 'thick' | 'invisible'\n\n/** An edge in the ASCII graph, with a routed path. */\nexport interface AsciiEdge {\n from: AsciiNode\n to: AsciiNode\n text: string\n path: GridCoord[]\n labelLine: GridCoord[]\n startDir: Direction\n endDir: Direction\n /** Line style: solid (default), dotted (-.->) or thick (==>) */\n style: AsciiEdgeStyle\n /** Whether to render an arrowhead at the start (source end) of the edge */\n hasArrowStart: boolean\n /** Whether to render an arrowhead at the end (target end) of the edge */\n hasArrowEnd: boolean\n /**\n * Terminator shape at the source end when it's a circle/cross marker\n * (`o--`/`x--`) rather than a plain arrowhead. Undefined draws the\n * regular arrowhead glyph (per `hasArrowStart`). See issue #330.\n */\n startMarker?: 'circle' | 'cross'\n /** Terminator shape at the target end (`--o`/`--x`). See `startMarker`. */\n endMarker?: 'circle' | 'cross'\n /** Bundle this edge belongs to (if any). Set during bundling analysis. */\n bundle?: EdgeBundle\n /**\n * For bundled edges: path from source/target to the junction point.\n * The full visual path is: pathToJunction + bundle.sharedPath (for fan-in)\n * or bundle.sharedPath + pathToJunction (for fan-out).\n */\n pathToJunction?: GridCoord[]\n /**\n * Set when this edge connects the same *pair* of nodes as one or more\n * sibling edges — either in the same direction (`A -->|One| B` and\n * `A -->|Two| B`) or, for a pair laid out side by side, in opposite\n * directions (`A -->|req| B` and `B -->|res| A`). See\n * `parallelGroupKey` in edge-routing.ts for the exact rule. This is as\n * opposed to edges that merely share one endpoint, which `bundle` above\n * already handles via fan-in/fan-out junctions.\n *\n * `index` is this edge's 0-based position within the group (declaration\n * order); `total` is the group's size. `index === 0` keeps the ordinary\n * single-edge route (determinePath's ordinary preferred/alternative\n * search), so a graph with no parallel edges renders identically to\n * before this field existed. `index > 0` routes through an offset lane\n * instead — see determinePath in edge-routing.ts — so sibling edges never\n * compute the identical path (and therefore identically-positioned,\n * mutually-corrupting labels; see #329 and #629).\n *\n * `usedOffsets` is the *same* Set object, by reference, on every edge in\n * the group (assigned once in assignParallelEdgeLanes) — the lane-offset\n * search in buildParallelLanePath can land two different lane indices on\n * the same actual offset when both have to detour around the same\n * obstacle (see that function's doc), so each successful search records\n * its chosen offset here and skips any offset a sibling already claimed,\n * keeping every lane in the group on a genuinely distinct path.\n */\n parallelLane?: { index: number; total: number; usedOffsets: Set<number> }\n}\n\n/** A subgraph container with bounding box for rendering. */\nexport interface AsciiSubgraph {\n name: string\n nodes: AsciiNode[]\n parent: AsciiSubgraph | null\n children: AsciiSubgraph[]\n minX: number\n minY: number\n maxX: number\n maxY: number\n /** Optional direction override for layout within this subgraph (LR or TD). */\n direction?: 'LR' | 'TD'\n}\n\n// ============================================================================\n// Padding defaults\n//\n// Shared by every renderer that reads AsciiConfig's padding fields. Layouts\n// that predate `paddingX`/`paddingY`/`boxBorderPadding` becoming configurable\n// (sequence, class, ER — see issue #343) compute their own spacing as an\n// offset from these defaults rather than substituting the raw config value\n// directly, so that *not* passing a padding option still renders exactly as\n// it always has (no baseline/snapshot churn) while explicitly passing one\n// still visibly changes spacing. The flowchart/state grid layout (grid.ts)\n// predates this convention and uses `paddingX`/`paddingY` directly as its\n// column/row gap — don't \"fix\" that to go through these constants too, its\n// existing default already equals them, so behavior is identical.\n// ============================================================================\n\n/** `AsciiConfig.paddingX`'s default — see the block comment above. */\nexport const DEFAULT_PADDING_X = 5\n/** `AsciiConfig.paddingY`'s default — see the block comment above. */\nexport const DEFAULT_PADDING_Y = 5\n/** `AsciiConfig.boxBorderPadding`'s default — see the block comment above. */\nexport const DEFAULT_BOX_BORDER_PADDING = 1\n\n/**\n * Derive a diagram-local spacing constant from a padding option, offset from\n * that option's default so a diagram whose own historical constant differs\n * from the shared default (e.g. class diagrams' 4-column gap vs. the shared\n * default of 5) still renders unchanged when the caller passes no explicit\n * padding override. `-x`/`-y`/`-p` still visibly widen or tighten spacing\n * because they shift `configValue` away from `defaultValue`.\n *\n * Shared by sequence.ts/class-diagram.ts/er-diagram.ts so the three\n * renderers wired up for issue #343 apply padding the same way instead of\n * each re-deriving this arithmetic.\n *\n * @param floor - Minimum result, so a large negative padding can't collapse\n * the spacing to zero or negative (which would overlap adjacent elements).\n */\nexport function paddingOffset(\n configValue: number,\n defaultValue: number,\n base: number,\n floor: number,\n): number {\n return Math.max(floor, base + (configValue - defaultValue))\n}\n\n/** Configuration for ASCII rendering. */\nexport interface AsciiConfig {\n /** true = ASCII chars (+,-,|), false = Unicode box-drawing (┌,─,│). Default: false */\n useAscii: boolean\n /** Horizontal spacing between nodes. Default: 5 */\n paddingX: number\n /** Vertical spacing between nodes. Default: 5 */\n paddingY: number\n /** Padding inside node boxes. Default: 1 */\n boxBorderPadding: number\n /** Graph direction: \"LR\" or \"TD\". */\n graphDirection: 'LR' | 'TD'\n}\n\n/** Full ASCII graph state used during layout and rendering. */\nexport interface AsciiGraph {\n nodes: AsciiNode[]\n edges: AsciiEdge[]\n canvas: Canvas\n /** Role canvas — tracks the role of each character for colored output. */\n roleCanvas: RoleCanvas\n /** Grid occupancy map — tracks which \"x,y\" cells are reserved. */\n grid: Grid\n columnWidth: Map<number, number>\n rowHeight: Map<number, number>\n subgraphs: AsciiSubgraph[]\n config: AsciiConfig\n /** Offset applied to all drawing coords to accommodate subgraph borders. */\n offsetX: number\n offsetY: number\n /** Edge bundles for parallel link visualization. Set during bundling analysis. */\n bundles: EdgeBundle[]\n /**\n * Render-wide A* iteration budget shared across every getPath call made\n * while routing this graph's edges. Bounds total pathfinding work (and\n * therefore memory) for graphs with many edges, independent of any\n * single call's own iteration cap. Set at the start of createMapping's\n * edge-routing phase; undefined before then. See pathfinder.ts's\n * PathBudget for details.\n */\n pathBudget?: PathBudget\n}\n\n// ============================================================================\n// Coordinate helpers\n// ============================================================================\n\nexport function gridCoordEquals(a: GridCoord, b: GridCoord): boolean {\n return a.x === b.x && a.y === b.y\n}\n\nexport function drawingCoordEquals(a: DrawingCoord, b: DrawingCoord): boolean {\n return a.x === b.x && a.y === b.y\n}\n\n/** Apply a direction offset to a grid coordinate (move into the 3x3 block). */\nexport function gridCoordDirection(c: GridCoord, dir: Direction): GridCoord {\n return { x: c.x + dir.x, y: c.y + dir.y }\n}\n\n/** Key for storing GridCoord in a Map. */\nexport function gridKey(c: GridCoord): string {\n return `${c.x},${c.y}`\n}\n\n/**\n * Get a node's grid coordinate. Every consumer of a node's `gridCoord` after\n * layout (edge routing, edge bundling, drawing) runs strictly after\n * `createMapping` (grid.ts) has placed every node, so this is always defined\n * for a real graph — but that guarantee lives in layout's control flow, not\n * in `AsciiNode`'s type (`gridCoord: GridCoord | null`), so it's narrowed\n * here explicitly rather than trusted silently across the module boundary.\n *\n * Lives here (not in grid.ts, where it originated) because it's a pure\n * predicate over `AsciiNode` with zero dependency on grid/layout state —\n * keeping it here lets `draw-boxes.ts` and `draw-bundles.ts` reuse it\n * without pulling in all of grid.ts's transitive imports.\n */\nexport function requireGridCoord(node: AsciiNode): GridCoord {\n const gc = node.gridCoord\n if (gc === null) {\n /* v8 ignore next */\n throw new Error(\n `Node \"${node.name}\" has no gridCoord; grid layout must run before it is read`,\n )\n }\n return gc\n}\n\n/** Default empty style class. */\nexport const EMPTY_STYLE: AsciiStyleClass = { name: '', styles: {} }\n\n// ============================================================================\n// Character role types for colored output\n// ============================================================================\n\n/**\n * Role of a character in the ASCII diagram, used for theming.\n * Each role maps to a different color when colors are enabled.\n */\nexport type CharRole =\n | 'text' // Node labels, edge labels\n | 'border' // Node box borders, subgraph borders\n | 'line' // Edge lines (paths between nodes)\n | 'arrow' // Arrowheads (▲▼◄► or ^v<>)\n | 'corner' // Corner characters at path bends\n | 'junction' // Junction characters (┬┴├┤ where edges meet boxes)\n\n/**\n * Role canvas — parallel to Canvas, tracks the role of each character.\n * Same column-major structure: roleCanvas[x][y] gives the role at (x, y).\n * null means the character has no role (whitespace).\n */\nexport type RoleCanvas = (CharRole | null)[][]\n\n/**\n * Theme colors for ASCII output — hex color strings.\n * Derived from the SVG theme system for visual consistency.\n */\nexport interface AsciiTheme {\n /** Text color (node labels, edge labels) */\n fg: string\n /** Box border color (node borders, subgraph borders) */\n border: string\n /** Edge line color (paths between nodes) */\n line: string\n /** Arrowhead color (▲▼◄► or ^v<>) */\n arrow: string\n /** Theme accent color (optional, used by xycharts for series 0) */\n accent?: string\n /** Background color (optional, used by xycharts for dark-mode-aware shading) */\n bg?: string\n /** Corner character color (optional, defaults to line) */\n corner?: string\n /** Junction character color (optional, defaults to border) */\n junction?: string\n}\n\n/** Color mode for output. */\nexport type ColorMode =\n | 'none' // No colors (plain text)\n | 'ansi16' // 16-color ANSI (basic terminals)\n | 'ansi256' // 256-color ANSI (xterm)\n | 'truecolor' // 24-bit RGB (modern terminals)\n | 'html' // HTML <span> tags with inline color styles (browsers)\n\n// ============================================================================\n// Edge bundling types\n// ============================================================================\n\n/**\n * Edge bundle — groups edges that share a common source or target.\n * Used to visually merge parallel links before they reach the shared node.\n *\n * For fan-in (A & B --> C): multiple sources converge to one target.\n * For fan-out (A --> B & C): one source diverges to multiple targets.\n */\nexport interface EdgeBundle {\n /** Bundle type: fan-in = many→one, fan-out = one→many */\n type: 'fan-in' | 'fan-out'\n /** Edges in this bundle */\n edges: AsciiEdge[]\n /** The common node (target for fan-in, source for fan-out) */\n sharedNode: AsciiNode\n /** The non-shared nodes (sources for fan-in, targets for fan-out) */\n otherNodes: AsciiNode[]\n /** Junction point where edges merge/split — set during routing */\n junctionPoint: GridCoord | null\n /** Path from junction to shared node (drawn once for all edges) */\n sharedPath: GridCoord[]\n /** Direction when entering/exiting the junction */\n junctionDir: Direction\n /** Direction when entering/exiting the shared node */\n sharedNodeDir: Direction\n}\n","// ============================================================================\n// ASCII renderer — grid occupancy map\n//\n// An opaque set of \"x,y\" keys tracking which logical grid cells are\n// reserved. Before this module existed, `grid.ts`, `pathfinder.ts`, and\n// `edge-routing.ts` each poked at a raw `Map<string, AsciiNode>` directly\n// (`.has(gridKey(c))`, `.set(gridKey(c), node)`), so the \"x,y\" string-keying\n// scheme had no single owner. Centralizing it here means the keying scheme\n// only needs to be gotten right once, call sites read as grid operations\n// (`isFree`, `placeBlock`) rather than raw Map/Set calls, and — because\n// `Grid` stores its cells behind a real (`#`-private, not just\n// TypeScript-`private`) field — nothing outside this module can bypass\n// `gridKey` and the collision checks by reaching into the underlying\n// collection directly, not even via an `any` cast or plain-JS caller.\n// ============================================================================\n\nimport type { GridCoord } from './types.ts'\nimport { gridKey } from './types.ts'\n\n/**\n * Grid occupancy map — an opaque set of reserved \"x,y\" cells. Only tracks\n * *whether* a cell is reserved, not by whom: nothing in this renderer looks\n * up which node owns a given cell (layout code identifies a node's cells via\n * `node.gridCoord`, not via the grid), so there is no per-cell node value to\n * keep honest.\n *\n * `#cells` is a JavaScript private field, not merely a TypeScript `private`\n * annotation: the latter is erased at compile time and still reachable at\n * runtime via `(grid as any).cells` or plain bracket access, which would\n * let a caller add/delete/clear cells directly and bypass `gridKey` and the\n * collision checks in `placeBlock`. A `#`-private field has no such escape\n * hatch — it's enforced by the JS engine itself, so only the methods below\n * can ever touch the underlying `Set`.\n */\nexport class Grid {\n readonly #cells = new Set<string>()\n\n /** Whether a single cell is already reserved. */\n has(key: string): boolean {\n return this.#cells.has(key)\n }\n\n /** Reserve a single cell. Internal — callers go through `placeBlock`. */\n add(key: string): void {\n this.#cells.add(key)\n }\n\n /**\n * Release a single cell. For temporary reservations only (see\n * `rerouteAroundStyleConflicts` in grid.ts) — every other reservation in\n * this module (node blocks, in particular) is permanent for the life of\n * a render, so a real caller should rarely need this.\n */\n delete(key: string): void {\n this.#cells.delete(key)\n }\n\n /** Iterate the reserved cell keys. Read-only — see `cloneGrid`. */\n keys(): IterableIterator<string> {\n return this.#cells.keys()\n }\n}\n\n/** Create a fresh, empty grid occupancy map. */\nexport function createGrid(): Grid {\n return new Grid()\n}\n\n/**\n * Copy a grid's current reservations into a brand-new, independent `Grid`.\n *\n * Used to snapshot *node* occupancy before edge routing starts (grid.ts's\n * `createMapping`, right after all `placeBlock` calls and before the\n * edge-routing loop) so that later mutating `graph.grid` — specifically\n * `rerouteAroundStyleConflicts`'s temporary `add`/`delete` of a conflicting\n * cell — can't change what counts as \"node-owned\" for\n * `edge-cell-styles.ts`'s conflict check. Without this, a temporarily\n * blocked cell looks node-occupied to `findStyleConflict` for exactly as\n * long as it's reserved, which is precisely when a re-route needs to\n * re-check it — see the regression test this snapshot fixes in\n * ascii-edge-cross-style-overlap.test.ts.\n */\nexport function cloneGrid(grid: Grid): Grid {\n const clone = createGrid()\n for (const key of grid.keys()) clone.add(key)\n return clone\n}\n\n/** The N x N block of cells a node reserves: border, content, border. */\nexport const NODE_BLOCK_SIZE = 3\n\n/** Whether a single cell is already reserved. */\nexport function isOccupied(grid: Grid, coord: GridCoord): boolean {\n return grid.has(gridKey(coord))\n}\n\n/**\n * Whether a cell is free to route/place into: unoccupied and within the\n * non-negative quadrant.\n *\n * The bounds check is not an incidental side benefit of occupancy checking —\n * it is the *only* thing that bounds A*'s search space. `pathfinder.ts`'s\n * neighbor expansion generates `{x:-1,y:0}` / `{x:0,y:-1}` from `MOVE_DIRS`\n * on every iteration; without this check A* explores the infinite negative\n * quadrant on the first edge, burning the entire path budget. Do not remove\n * it as redundant.\n *\n * Note `isFree` is not the logical negation of `isOccupied`: for a negative\n * coordinate, both return `false`. `reserveSpotInGrid` (grid.ts) uses\n * `isOccupied`/`isBlockFree`; `pathfinder.ts` and `edge-routing.ts` use\n * `isFree`. Do not simplify `!isOccupied(...)` to `isFree(...)` (or vice\n * versa) — that silently changes behavior for negative/out-of-range\n * coordinates.\n */\nexport function isFree(grid: Grid, coord: GridCoord): boolean {\n if (coord.x < 0 || coord.y < 0) return false\n return !isOccupied(grid, coord)\n}\n\n/**\n * Whether every cell in a `size` x `size` block starting at `origin` is\n * free of existing occupants. Use before `placeBlock` to detect a collision\n * without triggering its throw.\n */\nexport function isBlockFree(\n grid: Grid,\n origin: GridCoord,\n size: number = NODE_BLOCK_SIZE,\n): boolean {\n for (let dx = 0; dx < size; dx++) {\n for (let dy = 0; dy < size; dy++) {\n if (isOccupied(grid, { x: origin.x + dx, y: origin.y + dy })) {\n return false\n }\n }\n }\n return true\n}\n\n/**\n * Reserve a `size` x `size` block of cells starting at `origin`. Throws if\n * any cell in the block is already occupied rather than silently\n * transferring ownership — callers that want to handle a collision (e.g. by\n * shifting to a different origin) must check `isBlockFree` first.\n *\n * Validates every cell in the block *before* reserving any of them: an\n * earlier version interleaved the occupancy check with the `add` inside a\n * single loop, so a collision on a later cell threw after earlier cells in\n * the same block had already been reserved — leaving a partial, corrupt\n * reservation behind even though the call as a whole failed. Checking the\n * whole block first means this function either reserves all of it or none\n * of it.\n */\nexport function placeBlock(\n grid: Grid,\n origin: GridCoord,\n size: number = NODE_BLOCK_SIZE,\n): void {\n if (!isBlockFree(grid, origin, size)) {\n throw new Error(\n `Grid block at (${origin.x},${origin.y}) is already occupied`,\n )\n }\n\n for (let dx = 0; dx < size; dx++) {\n for (let dy = 0; dy < size; dy++) {\n const coord: GridCoord = { x: origin.x + dx, y: origin.y + dy }\n grid.add(gridKey(coord))\n }\n }\n}\n\n/**\n * Every cell an edge's routed path passes through — including intermediate\n * cells on each straight segment, not just the corner waypoints `path`\n * itself contains (A* paths are simplified down to corners by `mergePath`).\n *\n * Segments are walked with integer Bresenham, not a naive \"step both axes\n * by their sign each iteration\": A*-routed segments are always axis-aligned\n * (see `MOVE_DIRS` in pathfinder.ts), but `determinePath`'s Case-4 direct\n * fallback (`edge.path = [prefFrom, prefTo]`, used when A* finds no route\n * at all) can produce an arbitrary, non-45°, non-axis-aligned segment. A\n * naive \"x += sign(dx); y += sign(dy)\" walk only ever reaches\n * `(to.x, to.y)` when `|dx| === |dy|` — for any other ratio, x and y drift\n * past each other's target on different iterations and the loop's\n * \"both must match\" exit condition is never satisfied, walking forever\n * (this happened in testing, on a plain 4-edge fan-out, until it blew a\n * `Set`'s max size). `MAX_WALK_STEPS` is a second, independent safety net in\n * case a future caller feeds in a segment absurdly far from the grid's\n * normal handful-of-columns/rows scale — reaching it throws rather than\n * returning a path quietly truncated partway through the segment, which\n * would under-report to any caller checking \"every cell this path touches\"\n * (`edge-cell-styles.ts`'s conflict detection, in particular): silently\n * dropping the second half of a segment doesn't fail loudly, it just makes\n * conflict detection blind to whatever's past the cutoff.\n */\nconst MAX_WALK_STEPS = 100_000\n\nexport function pathCells(path: readonly GridCoord[]): GridCoord[] {\n if (path.length === 0) return []\n const cells: GridCoord[] = [path[0]!]\n for (let i = 1; i < path.length; i++) {\n const from = path[i - 1]!\n const to = path[i]!\n let x = from.x\n let y = from.y\n const absDx = Math.abs(to.x - from.x)\n const absDy = Math.abs(to.y - from.y)\n const sx = from.x < to.x ? 1 : -1\n const sy = from.y < to.y ? 1 : -1\n let err = absDx - absDy\n let steps = 0\n while (x !== to.x || y !== to.y) {\n if (steps >= MAX_WALK_STEPS) {\n throw new Error(\n `pathCells: segment from (${from.x},${from.y}) to (${to.x},${to.y}) ` +\n `did not reach its endpoint within ${MAX_WALK_STEPS} steps`,\n )\n }\n const e2 = 2 * err\n if (e2 > -absDy) {\n err -= absDy\n x += sx\n }\n if (e2 < absDx) {\n err += absDx\n y += sy\n }\n cells.push({ x, y })\n steps++\n }\n }\n return cells\n}\n","// ============================================================================\n// ASCII renderer — A* pathfinding for edge routing\n//\n// Ported from AlexanderGrooff/mermaid-ascii cmd/arrow.go.\n// Uses A* search with a corner-penalizing heuristic to find clean\n// paths between nodes on the grid. Prefers straight lines over zigzags.\n// ============================================================================\n\nimport type {\n GridCoord,\n PathBudget,\n AsciiGraph,\n CardinalDirection,\n} from './types.ts'\nimport { gridKey, gridCoordEquals, dirEquals, Left, Right } from './types.ts'\nimport { isFree, type Grid } from './grid-occupancy.ts'\n\n// ============================================================================\n// Priority queue (min-heap) for A* open set\n// ============================================================================\n\ninterface PQItem {\n coord: GridCoord\n priority: number\n}\n\n/**\n * Simple min-heap priority queue.\n * For the grid sizes we handle (~100s of cells), this is more than fast enough.\n */\nclass MinHeap {\n private items: PQItem[] = []\n\n get length(): number {\n return this.items.length\n }\n\n push(item: PQItem): void {\n this.items.push(item)\n this.bubbleUp(this.items.length - 1)\n }\n\n pop(): PQItem | undefined {\n if (this.items.length === 0) return undefined\n const top = this.items[0]!\n const last = this.items.pop()!\n if (this.items.length > 0) {\n this.items[0] = last\n this.sinkDown(0)\n }\n return top\n }\n\n private bubbleUp(start: number): void {\n let i = start\n while (i > 0) {\n const parent = (i - 1) >> 1\n if (this.items[i]!.priority < this.items[parent]!.priority) {\n ;[this.items[i], this.items[parent]] = [\n this.items[parent]!,\n this.items[i]!,\n ]\n i = parent\n } else {\n break\n }\n }\n }\n\n private sinkDown(start: number): void {\n const n = this.items.length\n let i = start\n while (true) {\n let smallest = i\n const left = 2 * i + 1\n const right = 2 * i + 2\n if (\n left < n &&\n this.items[left]!.priority < this.items[smallest]!.priority\n ) {\n smallest = left\n }\n if (\n right < n &&\n this.items[right]!.priority < this.items[smallest]!.priority\n ) {\n smallest = right\n }\n if (smallest !== i) {\n ;[this.items[i], this.items[smallest]] = [\n this.items[smallest]!,\n this.items[i]!,\n ]\n i = smallest\n } else {\n break\n }\n }\n }\n}\n\n// ============================================================================\n// A* heuristic\n// ============================================================================\n\n/**\n * Manhattan distance with a +1 penalty when both dx and dy are non-zero.\n * This encourages the pathfinder to prefer straight lines and minimize corners.\n */\nexport function heuristic(a: GridCoord, b: GridCoord): number {\n const absX = Math.abs(a.x - b.x)\n const absY = Math.abs(a.y - b.y)\n if (absX === 0 || absY === 0) {\n return absX + absY\n }\n return absX + absY + 1\n}\n\n// ============================================================================\n// A* pathfinding\n// ============================================================================\n\n/** 4-directional movement (no diagonals in grid pathfinding). */\nconst MOVE_DIRS: GridCoord[] = [\n { x: 1, y: 0 },\n { x: -1, y: 0 },\n { x: 0, y: 1 },\n { x: 0, y: -1 },\n]\n\n/**\n * Maximum number of A* iterations before giving up on a *single* getPath\n * call. Prevents unbounded memory growth when the destination is\n * unreachable through free cells (the grid has no positive upper-bound\n * check).\n *\n * This alone is not sufficient to bound a whole render: a dense fan-in/out\n * graph can call getPath hundreds of times (once per edge, sometimes twice\n * — see determinePath's preferred + alternative attempts), and each call is\n * independently allowed to spend up to MAX_ITERATIONS work searching a grid\n * that's mostly occupied by other nodes on the same row. That per-call cap\n * bounds each search but not their sum, so total time/memory across a\n * render still scales with (edge count × MAX_ITERATIONS) and can exhaust\n * the heap well before any single call hits its own limit. See PathBudget\n * below for the render-wide bound that fixes this.\n */\nconst MAX_ITERATIONS = 50_000\n\n/**\n * A mutable, render-wide budget shared across every getPath call made while\n * laying out one graph (see grid.ts's createMapping, which creates one\n * fresh budget per render and threads it through edge-routing.ts and\n * edge-bundling.ts). Each A* iteration — in any call — decrements\n * `remaining`; once it hits zero, all subsequent getPath calls return null\n * immediately (falling back to the direct-path logic already used when A*\n * can't find a route at all), which hard-bounds total pathfinding work for\n * the whole render regardless of how many edges it has.\n *\n * (Type defined in types.ts alongside the other shared ASCII-renderer\n * types, to avoid a circular import between this module and types.ts.)\n */\n\n/**\n * Default render-wide iteration budget. Generous enough to fully route\n * ordinary diagrams (tens to low hundreds of edges) without ever being\n * hit, while still keeping total pathfinding work — and thus memory use —\n * bounded for pathological dense fan-in/out graphs with hundreds of edges.\n */\nexport const DEFAULT_PATH_BUDGET = 200_000\n\n/** Create a fresh render-wide path budget. */\nexport function createPathBudget(\n total: number = DEFAULT_PATH_BUDGET,\n): PathBudget {\n return { remaining: total }\n}\n\n/**\n * Find a path from `from` to `to` on the grid using A*.\n * Returns the path as an array of GridCoords, or null if no path exists.\n *\n * `budget`, if provided, is a render-wide iteration budget shared across\n * all getPath calls for the current layout — see PathBudget above. Once\n * exhausted, this (and every subsequent call sharing it) returns null\n * right away instead of searching.\n */\nexport function getPath(\n grid: Grid,\n from: GridCoord,\n to: GridCoord,\n budget?: PathBudget,\n): GridCoord[] | null {\n if (budget && budget.remaining <= 0) {\n return null\n }\n\n const pq = new MinHeap()\n pq.push({ coord: from, priority: 0 })\n\n const costSoFar = new Map<string, number>()\n costSoFar.set(gridKey(from), 0)\n\n const cameFrom = new Map<string, GridCoord | null>()\n cameFrom.set(gridKey(from), null)\n\n let iterations = 0\n while (pq.length > 0) {\n if (++iterations > MAX_ITERATIONS) {\n return null\n }\n if (budget) {\n if (budget.remaining <= 0) return null\n budget.remaining--\n }\n\n const current = pq.pop()!.coord\n\n if (gridCoordEquals(current, to)) {\n // Reconstruct path by walking backwards through cameFrom\n const path: GridCoord[] = []\n let c: GridCoord | null = current\n while (c !== null) {\n path.unshift(c)\n c = cameFrom.get(gridKey(c)) ?? null\n }\n return path\n }\n\n // Every coord ever pushed onto `pq` has its costSoFar entry set\n // immediately beforehand — either the initial `from` push above, or in\n // the neighbor-expansion loop below (`costSoFar.set` precedes\n // `pq.push`) — so a coord just popped from `pq` always has a recorded\n // cost. That invariant lives in this function's control flow, not in\n // the Map's type, so it's checked explicitly rather than trusted via\n // `!`.\n const currentCost = costSoFar.get(gridKey(current))\n if (currentCost === undefined) {\n /* v8 ignore next */\n throw new Error(\n `A* pathfinding: missing cost for visited cell ${gridKey(current)}`,\n )\n }\n\n for (const dir of MOVE_DIRS) {\n const next: GridCoord = { x: current.x + dir.x, y: current.y + dir.y }\n\n // Allow moving to the destination even if it's occupied (it's a node boundary)\n if (!isFree(grid, next) && !gridCoordEquals(next, to)) {\n continue\n }\n\n const newCost = currentCost + 1\n const nextKey = gridKey(next)\n const existingCost = costSoFar.get(nextKey)\n\n if (existingCost === undefined || newCost < existingCost) {\n costSoFar.set(nextKey, newCost)\n const priority = newCost + heuristic(next, to)\n pq.push({ coord: next, priority })\n cameFrom.set(nextKey, current)\n }\n }\n }\n\n return null // No path found\n}\n\n/**\n * Simplify a path by removing intermediate waypoints on straight segments.\n * E.g., [(0,0), (1,0), (2,0), (2,1)] becomes [(0,0), (2,0), (2,1)].\n * This reduces the number of line-drawing operations.\n */\nexport function mergePath(path: GridCoord[]): GridCoord[] {\n if (path.length <= 2) return path\n\n const toRemove = new Set<number>()\n let step0 = path[0]!\n let step1 = path[1]!\n\n for (let idx = 2; idx < path.length; idx++) {\n const step2 = path[idx]!\n const prevDx = step1.x - step0.x\n const prevDy = step1.y - step0.y\n const dx = step2.x - step1.x\n const dy = step2.y - step1.y\n\n // Same direction — the middle point is redundant\n if (prevDx === dx && prevDy === dy) {\n // In Go: indexToRemove = append(indexToRemove, idx+1) but idx is 0-based from path[2:]\n // which corresponds to index idx in the full path. Go uses idx+1 because idx iterates\n // from 0 in the [2:] slice, mapping to full-array index idx+1.\n // Actually re-checking Go code: the loop is `for idx, step2 := range path[2:]`\n // so idx=0 → path[2], and it removes idx+1 which is index 1 in the full array.\n // Wait, that doesn't look right. Let me re-read:\n // step0 = path[0], step1 = path[1]\n // for idx, step2 := range path[2:] { ... indexToRemove = append(indexToRemove, idx+1) ... }\n // When idx=0, step2=path[2], and it removes index 1 (step1 = path[1]) if directions match\n // So it removes the middle point (step1) which is at index idx+1 in the original array\n // when counting from the 2-ahead loop. Let me just track which middle indices to remove.\n toRemove.add(idx - 1) // Remove the middle point (step1's position)\n }\n\n step0 = step1\n step1 = step2\n }\n\n return path.filter((_, i) => !toRemove.has(i))\n}\n\n// ============================================================================\n// Direct (unobstructed L-shape) path — shared fast path for edge-routing.ts\n// and edge-bundling.ts\n// ============================================================================\n\n/**\n * Check every cell strictly after `from` up to (and optionally including)\n * `to` along a single axis-aligned run. `from` and `to` must share an x or\n * a y coordinate. `includeTo` should be false when `to` is the edge's\n * final destination point (which is expected to sit on/adjacent to the\n * target node's own border and so may legitimately be \"occupied\" in\n * graph.grid — mirroring the same allowance A*'s getPath makes for its\n * destination cell).\n */\nfunction isAxisRunFree(\n grid: Grid,\n from: GridCoord,\n to: GridCoord,\n includeTo: boolean,\n): boolean {\n if (from.x === to.x && from.y === to.y) return true\n const alongY = from.x === to.x\n if (!alongY && from.y !== to.y) return false // not axis-aligned\n\n const step = alongY ? (to.y > from.y ? 1 : -1) : to.x > from.x ? 1 : -1\n let pos = alongY ? from.y : from.x\n const end = alongY ? to.y : to.x\n\n for (pos += step; ; pos += step) {\n const isLast = pos === end\n const cell: GridCoord = alongY\n ? { x: from.x, y: pos }\n : { x: pos, y: from.y }\n if (!(isLast && !includeTo) && !isFree(grid, cell)) return false\n if (isLast) break\n }\n return true\n}\n\n/** Try one L-shaped corner order; null if any leg is obstructed. */\nfunction tryCornerPath(\n grid: Grid,\n from: GridCoord,\n to: GridCoord,\n horizontalFirst: boolean,\n): GridCoord[] | null {\n const corner: GridCoord = horizontalFirst\n ? { x: to.x, y: from.y }\n : { x: from.x, y: to.y }\n\n if (!isAxisRunFree(grid, from, corner, true)) return null\n if (!isAxisRunFree(grid, corner, to, false)) return null\n return [from, corner, to]\n}\n\n/**\n * Try a direct two-segment (L-shaped) route between `from` and `to`. `dir`\n * indicates the preferred corner order (horizontal-first for Left/Right,\n * vertical-first for Up/Down), but when that orientation is blocked the\n * *other* orientation is tried too before giving up.\n *\n * Trying both orientations (rather than trusting `dir` alone) matters\n * because `dir` is not always the true geometric departure direction from\n * `from`: edge-routing.ts's determinePath always passes it correctly, but\n * edge-bundling.ts's junction segments sometimes pass the *arrival* anchor\n * at `to` instead (the two only coincide when `from`/`to` happen to already\n * be axis-aligned, which is common but not guaranteed — see the PR #178\n * review that found the fast path dead at 3 of 4 bundled call sites because\n * of exactly this mix-up). Trying both orientations makes the result\n * correct regardless of which direction a caller actually knows, instead of\n * requiring every caller to recompute true geometric departure.\n *\n * Returns null when `from` and `to` are already axis-aligned (no corner\n * needed — A*'s result is already optimal there) or when both orientations\n * are blocked.\n *\n * A* always finds a *shortest* path, but when several equally-short routes\n * exist — e.g. a straight line and a zigzag with the same total step count\n * — which one it returns depends on priority-queue tie-breaking order, not\n * on which \"looks\" straighter. That's why sibling edges from the same\n * source can end up with visually inconsistent routing (one goes straight,\n * another zigzags) even though nothing actually blocks a straight route\n * for either. A direct L-shaped route is, by construction, exactly as\n * short as any route between two points can be, so whenever it's\n * unobstructed we prefer it outright over whatever A* found — this also\n * skips the A* search entirely for the common unobstructed case.\n */\nfunction tryDirectPath(\n graph: AsciiGraph,\n from: GridCoord,\n to: GridCoord,\n dir: CardinalDirection,\n): GridCoord[] | null {\n if (from.x === to.x || from.y === to.y) return null\n\n const preferHorizontalFirst = dirEquals(dir, Left) || dirEquals(dir, Right)\n return (\n tryCornerPath(graph.grid, from, to, preferHorizontalFirst) ??\n tryCornerPath(graph.grid, from, to, !preferHorizontalFirst)\n )\n}\n\n/**\n * Route a single directed edge segment from `from` to `to`: try an\n * unobstructed direct L-shaped route first (tryDirectPath, above), falling\n * back to A* search (getPath) when every direct orientation is blocked or\n * unavailable (from/to already axis-aligned). Returns the merged path, or\n * null when neither strategy finds a route at all (e.g. the destination\n * is unreachable through free cells).\n *\n * This is the single seam both edge-routing.ts (determinePath's\n * preferred/alternative candidates) and edge-bundling.ts\n * (routeBundledEdges' junction-based segments) route through, so regular\n * and bundled edges share the same fast-path-then-A* behavior instead of\n * each independently calling getPath.\n *\n * `graph.pathBudget` is required, not silently optional: it's the\n * render-wide bound against pathological/hostile diagrams (see PathBudget\n * above), and routeEdge is the shared entry point for two modules now — an\n * absent budget here would silently disable that bound for both instead of\n * failing loudly, the way requireGridCoord fails loudly for a missing grid\n * coordinate rather than treating it as some default.\n */\nexport function routeEdge(\n graph: AsciiGraph,\n from: GridCoord,\n to: GridCoord,\n dir: CardinalDirection,\n): GridCoord[] | null {\n if (!graph.pathBudget) {\n throw new Error(\n 'routeEdge requires graph.pathBudget to be set; call createPathBudget() (see grid.ts createMapping) before routing edges',\n )\n }\n const path =\n tryDirectPath(graph, from, to, dir) ??\n getPath(graph.grid, from, to, graph.pathBudget)\n return path ? mergePath(path) : null\n}\n","// ============================================================================\n// ASCII renderer — edge bundling for parallel links\n//\n// Analyzes edges to find parallel links (A & B --> C or A --> B & C) and\n// groups them into bundles. Bundled edges share a visual junction point\n// where they merge/split, creating cleaner diagrams.\n//\n// This module provides:\n// - analyzeEdgeBundles(): Finds and creates bundles from graph edges\n// - calculateJunctionPoint(): Computes optimal merge/split locations\n// - routeBundledEdges(): Routes edges through junction points\n// ============================================================================\n\nimport type {\n AsciiGraph,\n AsciiNode,\n AsciiEdge,\n EdgeBundle,\n GridCoord,\n CardinalDirection,\n} from './types.ts'\nimport {\n Up,\n Down,\n Left,\n Right,\n Middle,\n gridCoordDirection,\n requireCardinalDirection,\n} from './types.ts'\nimport { getNodeSubgraph, requireGridCoord } from './grid.ts'\nimport { routeEdge } from './pathfinder.ts'\n\n// ============================================================================\n// Bundle analysis\n// ============================================================================\n\n/**\n * Analyze graph edges and create bundles for parallel links.\n *\n * Groups edges by:\n * - Fan-in: Multiple edges sharing the same target (A & B --> C)\n * - Fan-out: Multiple edges sharing the same source (A --> B & C)\n *\n * Only creates bundles when:\n * - Graph direction is TD (top-down) - LR routing handles merging naturally\n * - 2+ edges share the endpoint\n * - All edges have the same style (solid/dotted/thick)\n * - None of the edges have labels (labels would overlap at junction)\n * - Edges are not self-loops\n *\n * @returns Array of bundles. Each edge can belong to at most one bundle.\n */\nexport function analyzeEdgeBundles(graph: AsciiGraph): EdgeBundle[] {\n // Only bundle in TD direction - LR routing handles merging naturally at corners\n if (graph.config.graphDirection !== 'TD') {\n return []\n }\n const bundles: EdgeBundle[] = []\n const bundledEdges = new Set<AsciiEdge>()\n\n // Group edges by target (fan-in candidates)\n const edgesByTarget = new Map<AsciiNode, AsciiEdge[]>()\n for (const edge of graph.edges) {\n // Skip self-loops\n if (edge.from === edge.to) continue\n\n const existing = edgesByTarget.get(edge.to) ?? []\n existing.push(edge)\n edgesByTarget.set(edge.to, existing)\n }\n\n // Create fan-in bundles\n for (const [target, edges] of edgesByTarget) {\n if (edges.length < 2) continue\n if (!canBundle(edges, graph)) continue\n\n // Check if all edges are already bundled\n if (edges.some((e) => bundledEdges.has(e))) continue\n\n const bundle: EdgeBundle = {\n type: 'fan-in',\n edges: [...edges],\n sharedNode: target,\n otherNodes: edges.map((e) => e.from),\n junctionPoint: null,\n sharedPath: [],\n junctionDir: Middle,\n sharedNodeDir: Middle,\n }\n\n // Mark edges as bundled\n for (const edge of edges) {\n edge.bundle = bundle\n bundledEdges.add(edge)\n }\n\n bundles.push(bundle)\n }\n\n // Group edges by source (fan-out candidates)\n const edgesBySource = new Map<AsciiNode, AsciiEdge[]>()\n for (const edge of graph.edges) {\n // Skip self-loops and already bundled edges\n if (edge.from === edge.to) continue\n if (bundledEdges.has(edge)) continue\n\n const existing = edgesBySource.get(edge.from) ?? []\n existing.push(edge)\n edgesBySource.set(edge.from, existing)\n }\n\n // Create fan-out bundles\n for (const [source, edges] of edgesBySource) {\n if (edges.length < 2) continue\n if (!canBundle(edges, graph)) continue\n\n const bundle: EdgeBundle = {\n type: 'fan-out',\n edges: [...edges],\n sharedNode: source,\n otherNodes: edges.map((e) => e.to),\n junctionPoint: null,\n sharedPath: [],\n junctionDir: Middle,\n sharedNodeDir: Middle,\n }\n\n // Mark edges as bundled\n for (const edge of edges) {\n edge.bundle = bundle\n bundledEdges.add(edge)\n }\n\n bundles.push(bundle)\n }\n\n return bundles\n}\n\n/**\n * Check if a group of edges can be bundled together.\n * Returns false if edges have different styles, any have labels,\n * or if the edges span subgraph boundaries (which creates complex routing).\n */\nfunction canBundle(edges: AsciiEdge[], graph: AsciiGraph): boolean {\n if (edges.length < 2) return false\n\n const firstStyle = edges[0]!.style\n const firstFromSg = getNodeSubgraph(graph, edges[0]!.from)\n const firstToSg = getNodeSubgraph(graph, edges[0]!.to)\n\n for (const edge of edges) {\n // Different styles can't be bundled (would look confusing)\n if (edge.style !== firstStyle) return false\n\n // Edges with labels can't be bundled (labels would overlap at junction)\n if (edge.text.length > 0) return false\n\n // Don't bundle if edges span different subgraph boundaries\n // (creates complex routing that doesn't look good)\n const fromSg = getNodeSubgraph(graph, edge.from)\n const toSg = getNodeSubgraph(graph, edge.to)\n if (fromSg !== firstFromSg || toSg !== firstToSg) return false\n\n // Don't bundle if source and target are in different subgraphs\n // (cross-boundary edges have special routing needs)\n if (fromSg !== toSg) return false\n }\n\n // A group formed by shared-target (fan-in) or shared-source (fan-out)\n // grouping can still contain two edges that ALSO share the other\n // endpoint — true parallel/multi-edges (e.g. two unlabeled `A --> B`\n // edges), not a genuine fan-in/fan-out. Folding those into one shared\n // trunk+junction would visually merge them into a single line (the same\n // \"second edge silently wins\" defect this bundling feature is supposed\n // to avoid — see #329), rather than the distinct offset lanes\n // assignParallelEdgeLanes (edge-routing.ts) already gives them. Detect it\n // generically: the group is fan-in (shared `.to`) or fan-out (shared\n // `.from`) by construction (see analyzeEdgeBundles' two call sites), so\n // checking whichever endpoint *isn't* uniformly shared for duplicates\n // catches both cases without the caller having to say which one it is.\n const sameTarget = edges.every((e) => e.to === edges[0]!.to)\n const otherEndpoints = sameTarget\n ? edges.map((e) => e.from)\n : edges.map((e) => e.to)\n if (new Set(otherEndpoints).size !== otherEndpoints.length) return false\n\n // The duplicate check above only catches an *ordered*-pair duplicate. A\n // reverse-direction sibling (`B --> A` alongside `A --> B`, #629) reaches\n // a fan-in/fan-out group with a *distinct* other endpoint and slips past\n // it, even though assignParallelEdgeLanes has already committed that pair\n // to two distinct lanes — and a lane is a fixed offset from the pair's\n // own center path, not from wherever a trunk would have moved its sibling\n // to. So no edge assigned to a lane group ever gets bundled.\n //\n // Defensive: currently unreachable, and deliberately kept anyway. A\n // reverse-direction lane is only assigned when the pair sits side by side\n // on one grid row (edge-routing.ts's sharesHorizontalChannel), which is\n // exactly what the rank-ordering check below already rejects — every\n // source must be strictly above the shared target (or every target\n // strictly below the shared source). Those two conditions happen to be\n // complementary today; nothing structural keeps them that way.\n /* v8 ignore next */\n if (edges.some((e) => e.parallelLane)) return false\n\n // Bundling assumes every source sits strictly before the shared target\n // along the graph-direction axis for fan-in (TD: above it; LR: left of\n // it), or every target sits strictly after the shared source for\n // fan-out — calculateJunctionPoint places the junction on that side of\n // the shared node and routeBundledEdges exits/enters it accordingly. The\n // grid layout can violate that: a node's rank gets pinned by whichever\n // incoming edge's source is placed first (placeReachableChildren in\n // grid.ts never revisits an already-placed node), so a \"diamond\" like\n // `Queue --> Worker` plus `Queue --> Retry --> Worker` can leave Worker\n // at the SAME rank as Retry instead of one rank after it (#454).\n // Bundling that pair would route Retry's edge through the same\n // trunk+arrowhead as Queue's, silently swallowing Retry's own arrowhead\n // into a line that looks like it belongs solely to Queue. Refuse to\n // bundle whenever any edge doesn't actually satisfy the \"before the\n // target\" / \"after the source\" assumption; routed independently instead,\n // it gets its own distinct, visible arrowhead into/out of the shared node.\n // `analyzeEdgeBundles` (this function's only caller) already gates on\n // `graphDirection === 'TD'` before ever calling `canBundle`, so the 'LR'\n // arm is unreachable today — kept for whenever LR bundling support\n // lands, matching this comment block's own \"TD: above it; LR: left of\n // it\" framing above.\n /* v8 ignore next */\n const axis: 'x' | 'y' = graph.config.graphDirection === 'LR' ? 'x' : 'y'\n const sharedNode = sameTarget ? edges[0]!.to : edges[0]!.from\n const sharedCoord = sharedNode.gridCoord\n // Grid layout always runs before bundling — every node reaching this\n // point has a gridCoord (see `requireGridCoord`'s own use lower in this\n // file for the same assumption). Defensive only; not reachable from any\n // real diagram.\n /* v8 ignore next */\n if (!sharedCoord) return false\n for (const edge of edges) {\n const otherNode = sameTarget ? edge.from : edge.to\n const otherCoord = otherNode.gridCoord\n /* v8 ignore next */\n if (!otherCoord) return false\n if (sameTarget) {\n // fan-in: source must be strictly before the shared target\n if (otherCoord[axis] >= sharedCoord[axis]) return false\n } else {\n // fan-out: target must be strictly after the shared source. The\n // fan-in violation above is reachable via a plain diamond (a shared\n // target reached both directly and via a detour — see #454's own\n // repro and this file's regression test) because rank assignment\n // can shortchange whichever of two *competing parents* gets visited\n // second. The mirror-image fan-out violation would need a target's\n // rank to land at or before its own source's — topologically only\n // possible with an actual cycle, not exercised by any sample in\n // this repo. Kept for symmetry with the fan-in case above (and\n // because a cycle isn't inherently invalid mermaid syntax) rather\n // than assumed impossible.\n /* v8 ignore next */\n if (otherCoord[axis] <= sharedCoord[axis]) return false\n }\n }\n\n return true\n}\n\n// ============================================================================\n// Junction point calculation\n// ============================================================================\n\n/**\n * Calculate the optimal junction point for a bundle.\n *\n * For fan-in (A & B --> C):\n * - Junction is placed between the sources and the target\n * - In TD: above the target, horizontally centered between sources\n * - In LR: left of the target, vertically centered between sources\n *\n * For fan-out (A --> B & C):\n * - Junction is placed between the source and the targets\n * - In TD: below the source, horizontally centered between targets\n * - In LR: right of the source, vertically centered between targets\n */\nexport function calculateJunctionPoint(\n graph: AsciiGraph,\n bundle: EdgeBundle,\n): GridCoord {\n const dir = graph.config.graphDirection\n const sharedCoord = requireGridCoord(bundle.sharedNode)\n\n if (bundle.type === 'fan-in') {\n // Junction is BEFORE the shared target\n // Calculate center of sources\n if (dir === 'TD') {\n // Junction above target, centered between sources\n // Place it one row above the target's entry point\n const junctionY = sharedCoord.y - 1\n // X is centered between sources, but clamped to shared node's X for alignment\n const junctionX = sharedCoord.x + 1 // Align with target's center\n\n return { x: junctionX, y: junctionY }\n } else {\n // LR: Junction left of target, centered between sources\n const junctionX = sharedCoord.x - 1\n const junctionY = sharedCoord.y + 1 // Align with target's center\n\n return { x: junctionX, y: junctionY }\n }\n } else {\n // fan-out: Junction is AFTER the shared source\n if (dir === 'TD') {\n // Junction below source, will then split to targets\n const junctionY = sharedCoord.y + 3 // Just below source's 3x3 block\n const junctionX = sharedCoord.x + 1 // Align with source's center\n\n return { x: junctionX, y: junctionY }\n } else {\n // LR: Junction right of source\n const junctionX = sharedCoord.x + 3\n const junctionY = sharedCoord.y + 1\n\n return { x: junctionX, y: junctionY }\n }\n }\n}\n\n// ============================================================================\n// Bundled edge routing\n// ============================================================================\n\n/**\n * Route all edges in a bundle through the junction point.\n *\n * For fan-in bundles:\n * 1. Route each source → junction (stored in edge.pathToJunction)\n * 2. Route junction → target (stored in bundle.sharedPath)\n *\n * For fan-out bundles:\n * 1. Route source → junction (stored in bundle.sharedPath)\n * 2. Route junction → each target (stored in edge.pathToJunction)\n */\nexport function routeBundledEdges(graph: AsciiGraph, bundle: EdgeBundle): void {\n const dir = graph.config.graphDirection\n\n // Calculate and store junction point\n bundle.junctionPoint = calculateJunctionPoint(graph, bundle)\n const junction = bundle.junctionPoint\n\n // Determine directions based on graph direction and bundle type\n if (bundle.type === 'fan-in') {\n // Sources converge to junction, then junction to target\n bundle.junctionDir = dir === 'TD' ? Up : Left\n bundle.sharedNodeDir = dir === 'TD' ? Down : Right\n\n // Route junction → target (shared path). Anchor offsets (top/left\n // center of target, bottom/right center of source below) are computed\n // via gridCoordDirection — the same per-direction offset edge-routing.ts\n // uses — instead of each bundling call reimplementing its own\n // arithmetic. routeEdge (pathfinder.ts, also used by edge-routing.ts)\n // tries an unobstructed direct path before falling back to A*. Note\n // that `targetDir` here is the *arrival* anchor at the target, not\n // necessarily the true departure direction from `junction` — routeEdge\n // handles that by trying both L-corner orientations internally (see\n // tryDirectPath in pathfinder.ts) rather than trusting this value as a\n // routing axis.\n const targetCoord = requireGridCoord(bundle.sharedNode)\n const targetDir: CardinalDirection = requireCardinalDirection(\n dir === 'TD' ? Up : Left,\n )\n const targetEntry = gridCoordDirection(targetCoord, targetDir)\n\n const sharedPath = routeEdge(graph, junction, targetEntry, targetDir)\n bundle.sharedPath = sharedPath ?? [junction, targetEntry]\n\n // Route each source → junction\n for (const edge of bundle.edges) {\n const sourceCoord = requireGridCoord(edge.from)\n const sourceDir: CardinalDirection = requireCardinalDirection(\n dir === 'TD' ? Down : Right,\n )\n const sourceExit = gridCoordDirection(sourceCoord, sourceDir)\n\n const pathToJunction = routeEdge(graph, sourceExit, junction, sourceDir)\n edge.pathToJunction = pathToJunction ?? [sourceExit, junction]\n\n // Set edge directions for proper drawing\n edge.startDir = sourceDir\n edge.endDir = targetDir\n\n // Build full path for grid size calculation: source → junction → target\n edge.path = [...edge.pathToJunction, ...bundle.sharedPath.slice(1)]\n }\n } else {\n // fan-out: Source to junction, then junction splits to targets\n bundle.junctionDir = dir === 'TD' ? Down : Right\n bundle.sharedNodeDir = dir === 'TD' ? Up : Left\n\n // Route source → junction (shared path)\n const sourceCoord = requireGridCoord(bundle.sharedNode)\n const sourceDir: CardinalDirection = requireCardinalDirection(\n dir === 'TD' ? Down : Right,\n )\n const sourceExit = gridCoordDirection(sourceCoord, sourceDir)\n\n const sharedPath = routeEdge(graph, sourceExit, junction, sourceDir)\n bundle.sharedPath = sharedPath ?? [sourceExit, junction]\n\n // Route junction → each target\n for (const edge of bundle.edges) {\n const targetCoord = requireGridCoord(edge.to)\n const targetDir: CardinalDirection = requireCardinalDirection(\n dir === 'TD' ? Up : Left,\n )\n const targetEntry = gridCoordDirection(targetCoord, targetDir)\n\n const pathToJunction = routeEdge(graph, junction, targetEntry, targetDir)\n edge.pathToJunction = pathToJunction ?? [junction, targetEntry]\n\n // Set edge directions\n edge.startDir = sourceDir\n edge.endDir = targetDir\n\n // Build full path for grid size calculation: source → junction → target\n edge.path = [...bundle.sharedPath, ...edge.pathToJunction.slice(1)]\n }\n }\n}\n\n/**\n * Process all bundles in a graph: calculate junction points and route edges.\n */\nexport function processBundles(graph: AsciiGraph): void {\n for (const bundle of graph.bundles) {\n routeBundledEdges(graph, bundle)\n }\n}\n","// ============================================================================\n// ASCII renderer — cross-style edge overlap detection\n//\n// `graph.grid` (grid-occupancy.ts) only tracks *node* occupancy, so two\n// unrelated edges (different source and target — no reason for\n// `analyzeEdgeBundles` to group them, and A* itself only avoids node cells)\n// can independently route through the same empty cell. When both edges have\n// the *same* line style that's harmless — it just looks like one clean\n// shared line, which `analyzeEdgeBundles`'s \"LR routing handles merging\n// naturally at corners\" comment and its own trunk-sharing tests rely on\n// intentionally. It's only a real defect when the styles *differ*: a solid\n// cell and a dotted cell can't both be drawn at the same grid position, so\n// one silently overwrites the other and the reader can't tell two distinct\n// connections are there at all.\n//\n// This module tracks, per *open, non-node* cell, which single style has\n// claimed it (first claim wins) so grid.ts can detect a genuine cross-style\n// collision after routing an edge and reroute just that edge around it —\n// see `rerouteAroundStyleConflicts` in grid.ts.\n//\n// Node-occupied cells are deliberately excluded from tracking: an edge's\n// path always includes its own source/target node's border cell (that's\n// how it connects to the box at all), so two edges sharing a node — one\n// incoming, one outgoing, as in a retry loop's `B -->|No| D; D -.-> A` —\n// legitimately share that exact port cell. The character actually drawn\n// there comes from the node's own box-drawing code, not either edge's line\n// style, so it was never a real conflict. Blocking it wouldn't even help:\n// an edge always returns to its own node's fixed attachment point\n// regardless of grid occupancy, so re-routing around a node-owned \"conflict\"\n// just finds the identical cell again on every retry.\n// ============================================================================\n\nimport type { AsciiEdge, AsciiEdgeStyle, GridCoord } from './types.ts'\nimport { gridKey } from './types.ts'\nimport { isOccupied, pathCells, type Grid } from './grid-occupancy.ts'\n\n/** Cells already claimed by a drawn edge, keyed by \"x,y\", storing which\n * single line style is drawn there. */\nexport type EdgeCellStyles = Map<string, AsciiEdgeStyle>\n\nexport function createEdgeCellStyles(): EdgeCellStyles {\n return new Map()\n}\n\n/**\n * The first open (non-node-occupied) cell in `path` already claimed by a\n * *different* style than `style`. Same-style overlap is not reported — see\n * module doc.\n */\nexport function findStyleConflict(\n grid: Grid,\n cellStyles: EdgeCellStyles,\n path: readonly GridCoord[],\n style: AsciiEdgeStyle,\n): GridCoord | null {\n for (const cell of pathCells(path)) {\n if (isOccupied(grid, cell)) continue\n const existing = cellStyles.get(gridKey(cell))\n if (existing !== undefined && existing !== style) return cell\n }\n return null\n}\n\n/**\n * Record every open (non-node-occupied) cell in `path` as claimed by\n * `style`. First claim per cell wins — a later same-style edge through the\n * same cell is a harmless no-op, and a later different-style edge is\n * caught by `findStyleConflict` before this ever runs for it.\n */\nexport function claimPathCells(\n grid: Grid,\n cellStyles: EdgeCellStyles,\n path: readonly GridCoord[],\n style: AsciiEdgeStyle,\n): void {\n for (const cell of pathCells(path)) {\n if (isOccupied(grid, cell)) continue\n const key = gridKey(cell)\n if (!cellStyles.has(key)) cellStyles.set(key, style)\n }\n}\n\n// ============================================================================\n// Chain-edge overlap detection (independent of style)\n// ============================================================================\n//\n// The cross-*style* conflict above only fires when two overlapping edges\n// draw different characters — a same-style overlap silently draws the same\n// glyph twice, which this module's own doc calls harmless, and it usually\n// is: `analyzeEdgeBundles`'s own fan-in/fan-out trunk sharing is same-style\n// by construction, and a \"complete bipartite\" crossing like `A & B --> C &\n// D` (see ascii.test.ts's `ampersand_lhs_and_rhs` golden file) *deliberately*\n// routes two edges that share neither endpoint (`A --> D` and `B --> C`)\n// through the same shared crossbar — that is the intended, faithful\n// rendering of that shape, not a defect.\n//\n// It stops being harmless in one specific shape a plain \"shares an\n// endpoint\" check can't distinguish from that crossing pattern: a *chain*\n// through a shared intermediate node, `A --> B` then `B --> C`, each routed\n// independently. When their corners happen to coincide, `A`'s incoming leg\n// and `C`'s outgoing leg trace the exact same column/row beyond the shared,\n// node-owned port cell and read as one continuous `A --> C` connector\n// (#1067, \"System Architecture\": `Mobile App --> API Gateway` and\n// `API Gateway --> User Service` sharing one on-screen row with no visual\n// break, even though there is no `Mobile App --> User Service` edge in the\n// source). Unlike the crossing pattern above, a chain's two edges have no\n// reason to share open-space cells at all beyond that one port — so this\n// check is scoped *only* to chain pairs (`isChainPair` below), not to every\n// pair of edges that merely shares neither endpoint; widening it to that\n// general case is exactly what broke the ampersand golden file during this\n// fix's own development.\n\n/** Cells already claimed by drawn edges, keyed by \"x,y\", storing every edge\n * (by reference) that drew there — independent of `EdgeCellStyles` above,\n * which tracks *style* rather than edge identity.\n *\n * Stores a `Set`, not a single edge: `graph.edges` order means an edge\n * unrelated to any chain can claim a cell before a real chain pair's own\n * edges do, and a single-owner map would let that unrelated edge's claim\n * hide the chain pair's overlap from `findUnrelatedOverlap` entirely. See\n * that function's own doc.\n */\nexport type EdgeCellOwners = Map<string, Set<AsciiEdge>>\n\nexport function createEdgeCellOwners(): EdgeCellOwners {\n return new Map()\n}\n\n/**\n * Whether `a` and `b` form a chain through a shared intermediate node: one\n * edge's target is the other's source. Deliberately narrower than \"shares\n * any endpoint\" — a fan-in/fan-out pair (shared `from` or shared `to`) is\n * excluded on purpose, since that is exactly the shape a legitimate shared\n * trunk (bundled or not — see module doc) already routes through common\n * cells for.\n */\nfunction isChainPair(a: AsciiEdge, b: AsciiEdge): boolean {\n return a.to === b.from || b.to === a.from\n}\n\n/**\n * Minimum number of open, non-node cells a chain pair must share before it\n * counts as a real conflict. A single shared cell is an ordinary crossing\n * (two independent lines passing through the same point, which still reads\n * as two lines) — it takes a *run* of shared cells, long enough to read as\n * one continuous connector, to actually mislead a reader. Chosen to be the\n * smallest value that catches a shared corner-to-corner leg (at least 2\n * collinear cells) while still letting a lone crossing through.\n */\nconst MIN_CHAIN_OVERLAP = 2\n\n/**\n * The first open (non-node-occupied) cell in `path` already claimed by a\n * different edge that forms a chain with `edge` (`isChainPair`) — but only\n * once *that specific chain partner's* total overlap with `edge` reaches\n * `MIN_CHAIN_OVERLAP` cells (see that constant's doc). Each distinct owner\n * of a cell is checked and counted independently — a cell can hold several\n * edges' claims (see `EdgeCellOwners`'s own doc), and an unrelated same-style\n * edge sharing a cell must not hide a real chain partner's overlap that also\n * claimed it. Returns `null` when no chain partner's overlap reaches the\n * threshold.\n */\nexport function findUnrelatedOverlap(\n grid: Grid,\n owners: EdgeCellOwners,\n path: readonly GridCoord[],\n edge: AsciiEdge,\n): GridCoord | null {\n const overlapCounts = new Map<AsciiEdge, number>()\n const firstConflicts = new Map<AsciiEdge, GridCoord>()\n for (const cell of pathCells(path)) {\n if (isOccupied(grid, cell)) continue\n const cellOwners = owners.get(gridKey(cell))\n if (cellOwners === undefined) continue\n for (const owner of cellOwners) {\n if (owner === edge) continue\n if (!isChainPair(owner, edge)) continue\n overlapCounts.set(owner, (overlapCounts.get(owner) ?? 0) + 1)\n if (!firstConflicts.has(owner)) firstConflicts.set(owner, cell)\n }\n }\n for (const [owner, count] of overlapCounts) {\n if (count >= MIN_CHAIN_OVERLAP) return firstConflicts.get(owner)!\n }\n return null\n}\n\n/**\n * Record every open (non-node-occupied) cell in `path` as claimed by\n * `edge`, alongside any edge(s) that already claimed it — see\n * `EdgeCellOwners`'s own doc for why a cell can have more than one owner.\n */\nexport function claimPathOwners(\n grid: Grid,\n owners: EdgeCellOwners,\n path: readonly GridCoord[],\n edge: AsciiEdge,\n): void {\n for (const cell of pathCells(path)) {\n if (isOccupied(grid, cell)) continue\n const key = gridKey(cell)\n let cellOwners = owners.get(key)\n if (cellOwners === undefined) {\n cellOwners = new Set()\n owners.set(key, cellOwners)\n }\n cellOwners.add(edge)\n }\n}\n","// ============================================================================\n// ASCII renderer — multi-line text utilities\n//\n// Shared utilities for handling multi-line labels (containing \\n from <br> tags)\n// in ASCII/Unicode rendering. Provides consistent text splitting, sizing, and\n// centered rendering across all diagram types.\n// ============================================================================\n\nimport type { Canvas } from './types.ts'\nimport { drawText } from './canvas.ts'\nimport { displayWidth } from './display-width.ts'\n\n/**\n * Split a label into lines.\n * Labels are already normalized by parsers (br tags → \\n).\n */\nexport function splitLines(label: string): string[] {\n return label.split('\\n')\n}\n\n/**\n * Get the maximum line width (in terminal display columns) for sizing\n * calculations. Used to determine column widths for multi-line labels.\n *\n * Uses `displayWidth` rather than `.length` so CJK/kana/hangul/fullwidth-form\n * and emoji characters — which render as two terminal columns each — are\n * accounted for correctly. Using code-unit `.length` here would produce\n * boxes too narrow for wide-character labels.\n */\nexport function maxLineWidth(label: string): number {\n const lines = splitLines(label)\n return Math.max(...lines.map((l) => displayWidth(l)), 0)\n}\n\n/**\n * Get the number of lines for height calculations.\n * Used to determine row heights for multi-line labels.\n */\nexport function lineCount(label: string): number {\n return splitLines(label).length\n}\n\n/**\n * Draw multi-line text centered at (cx, cy).\n * Expands vertically from the center point.\n * Each line is horizontally centered independently.\n */\nexport function drawMultilineTextCentered(\n canvas: Canvas,\n label: string,\n cx: number,\n cy: number,\n): void {\n const lines = splitLines(label)\n const totalHeight = lines.length\n // Center vertically: start y positions lines evenly around cy\n const startY = cy - Math.floor((totalHeight - 1) / 2)\n\n for (const [i, line] of lines.entries()) {\n // Center each line horizontally (in display columns, not code units)\n const startX = cx - Math.floor(displayWidth(line) / 2)\n // Force overwrite for node labels (they take priority)\n drawText(canvas, { x: startX, y: startY + i }, line, true)\n }\n}\n\n/**\n * Draw multi-line text left-aligned starting at (x, y).\n * Each subsequent line is placed one row below.\n */\nexport function drawMultilineTextLeft(\n canvas: Canvas,\n label: string,\n x: number,\n y: number,\n): void {\n const lines = splitLines(label)\n for (const [i, line] of lines.entries()) {\n // Force overwrite for node labels (they take priority)\n drawText(canvas, { x, y: y + i }, line, true)\n }\n}\n","// ============================================================================\n// Corner character lookup table for shape rendering\n// ============================================================================\n//\n// All shapes are rendered as rectangles with distinctive corner characters\n// to indicate shape type. This eliminates diagonal characters while keeping\n// shapes visually distinguishable.\n\nimport type { AsciiNodeShape } from '../types.ts'\n\n/**\n * Corner characters for a shape in both Unicode and ASCII modes.\n */\nexport interface CornerChars {\n /** Top-left corner */\n tl: string\n /** Top-right corner */\n tr: string\n /** Bottom-left corner */\n bl: string\n /** Bottom-right corner */\n br: string\n}\n\n/**\n * Shape corner configuration with both Unicode and ASCII variants.\n */\nexport interface ShapeCorners {\n unicode: CornerChars\n ascii: CornerChars\n}\n\n/**\n * Corner character lookup table for all shape types.\n *\n * Design principles:\n * - All shapes use orthogonal box structure (no diagonals)\n * - Corner characters indicate shape semantics\n * - ASCII fallbacks use available punctuation\n */\nexport const SHAPE_CORNERS: Record<AsciiNodeShape, ShapeCorners> = {\n // Standard rectangular shapes\n rectangle: {\n unicode: { tl: '┌', tr: '┐', bl: '└', br: '┘' },\n ascii: { tl: '+', tr: '+', bl: '+', br: '+' },\n },\n rounded: {\n unicode: { tl: '╭', tr: '╮', bl: '╰', br: '╯' },\n ascii: { tl: '.', tr: '.', bl: \"'\", br: \"'\" },\n },\n\n // Circular shapes - use circle markers at corners\n circle: {\n unicode: { tl: '◯', tr: '◯', bl: '◯', br: '◯' },\n ascii: { tl: 'o', tr: 'o', bl: 'o', br: 'o' },\n },\n doublecircle: {\n unicode: { tl: '◎', tr: '◎', bl: '◎', br: '◎' },\n ascii: { tl: '@', tr: '@', bl: '@', br: '@' },\n },\n\n // Diamond - decision nodes\n diamond: {\n unicode: { tl: '◇', tr: '◇', bl: '◇', br: '◇' },\n ascii: { tl: '<', tr: '>', bl: '<', br: '>' },\n },\n\n // Hexagon - process nodes (crop corners — monospace-safe, distinct from rectangle)\n hexagon: {\n unicode: { tl: '⌜', tr: '⌝', bl: '⌞', br: '⌟' },\n ascii: { tl: '*', tr: '*', bl: '*', br: '*' },\n },\n\n // Stadium/pill shape\n stadium: {\n unicode: { tl: '(', tr: ')', bl: '(', br: ')' },\n ascii: { tl: '(', tr: ')', bl: '(', br: ')' },\n },\n\n // Subroutine - double vertical bars\n subroutine: {\n unicode: { tl: '╟', tr: '╢', bl: '╟', br: '╢' },\n ascii: { tl: '|', tr: '|', bl: '|', br: '|' },\n },\n\n // Cylinder/database\n cylinder: {\n unicode: { tl: '╭', tr: '╮', bl: '╰', br: '╯' },\n ascii: { tl: '.', tr: '.', bl: \"'\", br: \"'\" },\n },\n\n // Asymmetric/flag - pointer on left side\n asymmetric: {\n unicode: { tl: '▷', tr: '┐', bl: '▷', br: '┘' },\n ascii: { tl: '>', tr: '+', bl: '>', br: '+' },\n },\n\n // Trapezoid - wider at bottom (top corners slope inward)\n trapezoid: {\n unicode: { tl: '/', tr: '\\\\', bl: '└', br: '┘' },\n ascii: { tl: '/', tr: '\\\\', bl: '+', br: '+' },\n },\n\n // Trapezoid-alt - wider at top (bottom corners slope inward)\n 'trapezoid-alt': {\n unicode: { tl: '┌', tr: '┐', bl: '\\\\', br: '/' },\n ascii: { tl: '+', tr: '+', bl: '\\\\', br: '/' },\n },\n\n // Parallelogram - leans right (both sides slope the same way)\n parallelogram: {\n unicode: { tl: '/', tr: '/', bl: '/', br: '/' },\n ascii: { tl: '/', tr: '/', bl: '/', br: '/' },\n },\n\n // Parallelogram-alt - leans left\n 'parallelogram-alt': {\n unicode: { tl: '\\\\', tr: '\\\\', bl: '\\\\', br: '\\\\' },\n ascii: { tl: '\\\\', tr: '\\\\', bl: '\\\\', br: '\\\\' },\n },\n\n // State diagram pseudostates (special handling, not corner-based)\n 'state-start': {\n unicode: { tl: '●', tr: '●', bl: '●', br: '●' },\n ascii: { tl: '*', tr: '*', bl: '*', br: '*' },\n },\n 'state-end': {\n unicode: { tl: '◉', tr: '◉', bl: '◉', br: '◉' },\n ascii: { tl: '@', tr: '@', bl: '@', br: '@' },\n },\n\n // --------------------------------------------------------------------\n // Expanded-syntax shapes (`A@{ shape: ... }`).\n //\n // The ASCII grid has no diagonals and one glyph per corner, so these are\n // distinguished by corner character alone. Shapes whose defining feature\n // is interior (a rule, a cross, a notch) or whose outline the grid cannot\n // express keep box corners and rely on their label — the alternative is a\n // misleading outline, and docs/diagrams.md records which shapes render\n // distinctly in ASCII.\n // --------------------------------------------------------------------\n\n // Document family — wavy bottom edge suggested by curve glyphs\n document: {\n unicode: { tl: '┌', tr: '┐', bl: '╰', br: '╮' },\n ascii: { tl: '+', tr: '+', bl: \"'\", br: '~' },\n },\n 'stacked-document': {\n unicode: { tl: '┌', tr: '╗', bl: '╰', br: '╮' },\n ascii: { tl: '+', tr: '#', bl: \"'\", br: '~' },\n },\n 'stacked-process': {\n unicode: { tl: '┌', tr: '╗', bl: '└', br: '╝' },\n ascii: { tl: '+', tr: '#', bl: '+', br: '#' },\n },\n\n // Card — notched top-left corner\n card: {\n unicode: { tl: '╱', tr: '┐', bl: '└', br: '┘' },\n ascii: { tl: '/', tr: '+', bl: '+', br: '+' },\n },\n\n // Interior-feature rectangles — box corners, feature not expressible\n 'lined-process': {\n unicode: { tl: '┌', tr: '┐', bl: '└', br: '┘' },\n ascii: { tl: '+', tr: '+', bl: '+', br: '+' },\n },\n 'divided-process': {\n unicode: { tl: '┌', tr: '┐', bl: '└', br: '┘' },\n ascii: { tl: '+', tr: '+', bl: '+', br: '+' },\n },\n 'window-pane': {\n unicode: { tl: '┌', tr: '┐', bl: '└', br: '┘' },\n ascii: { tl: '+', tr: '+', bl: '+', br: '+' },\n },\n\n // Triangles\n triangle: {\n unicode: { tl: '╱', tr: '╲', bl: '└', br: '┘' },\n ascii: { tl: '/', tr: '\\\\', bl: '+', br: '+' },\n },\n 'flipped-triangle': {\n unicode: { tl: '┌', tr: '┐', bl: '╲', br: '╱' },\n ascii: { tl: '+', tr: '+', bl: '\\\\', br: '/' },\n },\n\n // Circles\n 'filled-circle': {\n unicode: { tl: '●', tr: '●', bl: '●', br: '●' },\n ascii: { tl: '*', tr: '*', bl: '*', br: '*' },\n },\n 'crossed-circle': {\n unicode: { tl: '╳', tr: '╳', bl: '╳', br: '╳' },\n ascii: { tl: 'X', tr: 'X', bl: 'X', br: 'X' },\n },\n\n // Fork/join bar\n 'fork-join': {\n unicode: { tl: '━', tr: '━', bl: '━', br: '━' },\n ascii: { tl: '=', tr: '=', bl: '=', br: '=' },\n },\n\n // Notched pentagon — clipped top corners\n 'notched-pentagon': {\n unicode: { tl: '╱', tr: '╲', bl: '└', br: '┘' },\n ascii: { tl: '/', tr: '\\\\', bl: '+', br: '+' },\n },\n\n // Sloped rectangle (manual input) — sloped top edge\n 'sloped-rectangle': {\n unicode: { tl: '╱', tr: '┐', bl: '└', br: '┘' },\n ascii: { tl: '/', tr: '+', bl: '+', br: '+' },\n },\n\n // Flag / paper tape — wavy top and bottom\n flag: {\n unicode: { tl: '╭', tr: '╮', bl: '╰', br: '╯' },\n ascii: { tl: '~', tr: '~', bl: '~', br: '~' },\n },\n\n // Bow-tie rectangle (stored data) — concave sides\n 'bow-tie-rectangle': {\n unicode: { tl: '╲', tr: '╱', bl: '╱', br: '╲' },\n ascii: { tl: '\\\\', tr: '/', bl: '/', br: '\\\\' },\n },\n\n // Delay — one rounded end\n 'half-rounded-rectangle': {\n unicode: { tl: '┌', tr: '╮', bl: '└', br: '╯' },\n ascii: { tl: '+', tr: '.', bl: '+', br: \"'\" },\n },\n\n // Braces\n brace: {\n unicode: { tl: '╭', tr: '┐', bl: '╰', br: '┘' },\n ascii: { tl: '{', tr: '+', bl: '{', br: '+' },\n },\n 'brace-right': {\n unicode: { tl: '┌', tr: '╮', bl: '└', br: '╯' },\n ascii: { tl: '+', tr: '}', bl: '+', br: '}' },\n },\n braces: {\n unicode: { tl: '╭', tr: '╮', bl: '╰', br: '╯' },\n ascii: { tl: '{', tr: '}', bl: '{', br: '}' },\n },\n\n // Lightning bolt (communication link)\n bolt: {\n unicode: { tl: '╱', tr: '╲', bl: '╲', br: '╱' },\n ascii: { tl: '/', tr: '\\\\', bl: '\\\\', br: '/' },\n },\n\n // Bare text and anchor — no visible outline\n text: {\n unicode: { tl: ' ', tr: ' ', bl: ' ', br: ' ' },\n ascii: { tl: ' ', tr: ' ', bl: ' ', br: ' ' },\n },\n anchor: {\n unicode: { tl: ' ', tr: ' ', bl: ' ', br: ' ' },\n ascii: { tl: ' ', tr: ' ', bl: ' ', br: ' ' },\n },\n}\n\n/**\n * Get corner characters for a shape type.\n */\nexport function getCorners(\n shape: AsciiNodeShape,\n useAscii: boolean,\n): CornerChars {\n const corners = SHAPE_CORNERS[shape] ?? SHAPE_CORNERS.rectangle\n return useAscii ? corners.ascii : corners.unicode\n}\n","// ============================================================================\n// Rectangle shape renderer — standard box with corners\n// ============================================================================\n//\n// This module provides the base box rendering used by all rectangular shapes.\n// The renderBox() function accepts custom corner characters, allowing different\n// shapes to reuse the same rendering logic with different visual markers.\n\nimport type { Canvas, DrawingCoord, Direction } from '../types.ts'\nimport {\n Up,\n Down,\n Left,\n Right,\n UpperLeft,\n UpperRight,\n LowerLeft,\n LowerRight,\n} from '../types.ts'\nimport { mkCanvas } from '../canvas.ts'\nimport { splitLines } from '../multiline-utils.ts'\nimport { displayWidth, toDisplayCells } from '../display-width.ts'\nimport type {\n ShapeRenderer,\n ShapeDimensions,\n ShapeRenderOptions,\n} from './types.ts'\nimport { dirEquals } from '../edge-routing.ts'\nimport { type CornerChars, getCorners } from './corners.ts'\n\n// ============================================================================\n// Shared dimension calculation\n// ============================================================================\n\n/**\n * Calculate standard box dimensions for any rectangular shape.\n * Used by rectangle, circle, diamond, hexagon, etc.\n */\nexport function getBoxDimensions(\n label: string,\n options: ShapeRenderOptions,\n): ShapeDimensions {\n const lines = splitLines(label)\n const maxLineWidth = Math.max(...lines.map((l) => displayWidth(l)), 0)\n const lineCount = lines.length\n\n // Width: 2*padding + maxLineWidth + 2 border chars\n const innerWidth = 2 * options.padding + maxLineWidth\n const width = innerWidth + 2\n\n // Height: lineCount + 2*padding + 2 border chars\n // Ensure innerHeight is odd for symmetric vertical centering\n const rawInnerHeight = lineCount + 2 * options.padding\n const innerHeight =\n rawInnerHeight % 2 === 0 ? rawInnerHeight + 1 : rawInnerHeight\n const height = innerHeight + 2\n\n return {\n width,\n height,\n labelArea: {\n x: 1 + options.padding,\n y: 1 + options.padding,\n width: maxLineWidth,\n height: lineCount,\n },\n // Grid layout: [border=1, content, border=1]\n gridColumns: [1, innerWidth, 1],\n gridRows: [1, innerHeight, 1],\n }\n}\n\n// ============================================================================\n// Shared box rendering\n// ============================================================================\n\n/**\n * Render a box with custom corner characters.\n * This is the core rendering function used by all rectangular shapes.\n *\n * @param label - Text to display in the box\n * @param dimensions - Pre-calculated dimensions\n * @param corners - Corner characters (tl, tr, bl, br)\n * @param useAscii - Whether to use ASCII or Unicode for lines\n */\nexport function renderBox(\n label: string,\n dimensions: ShapeDimensions,\n corners: CornerChars,\n useAscii: boolean,\n): Canvas {\n const { width, height } = dimensions\n const canvas = mkCanvas(width - 1, height - 1)\n\n const from = { x: 0, y: 0 }\n const to = { x: width - 1, y: height - 1 }\n\n // Line characters\n const hLine = useAscii ? '-' : '─'\n const vLine = useAscii ? '|' : '│'\n\n // Draw horizontal lines (top and bottom)\n for (let x = from.x + 1; x < to.x; x++) {\n canvas[x]![from.y] = hLine\n canvas[x]![to.y] = hLine\n }\n\n // Draw vertical lines (left and right)\n for (let y = from.y + 1; y < to.y; y++) {\n canvas[from.x]![y] = vLine\n canvas[to.x]![y] = vLine\n }\n\n // Draw corners\n canvas[from.x]![from.y] = corners.tl\n canvas[to.x]![from.y] = corners.tr\n canvas[from.x]![to.y] = corners.bl\n canvas[to.x]![to.y] = corners.br\n\n // Center the multi-line label\n const lines = splitLines(label)\n const w = width - 1 // Match original grid-based width calculation\n const h = height - 1\n const centerY = Math.floor(h / 2)\n const startY = centerY - Math.floor((lines.length - 1) / 2)\n\n for (let i = 0; i < lines.length; i++) {\n const line = lines[i]!\n const textX = Math.floor(w / 2) - Math.ceil(displayWidth(line) / 2) + 1\n const cells = toDisplayCells(line)\n for (let j = 0; j < cells.length; j++) {\n const x = textX + j\n const y = startY + i\n if (x >= 0 && x < canvas.length && y >= 0 && y < canvas[0]!.length) {\n canvas[x]![y] = cells[j]!\n }\n }\n }\n\n return canvas\n}\n\n// ============================================================================\n// Shared attachment point calculation\n// ============================================================================\n\n/**\n * Calculate edge attachment point for rectangular shapes.\n * All box-based shapes use the same attachment logic.\n */\nexport function getBoxAttachmentPoint(\n dir: Direction,\n dimensions: ShapeDimensions,\n baseCoord: DrawingCoord,\n): DrawingCoord {\n const { width, height } = dimensions\n const centerX = baseCoord.x + Math.floor(width / 2)\n const centerY = baseCoord.y + Math.floor(height / 2)\n\n if (dirEquals(dir, Up)) return { x: centerX, y: baseCoord.y }\n if (dirEquals(dir, Down)) return { x: centerX, y: baseCoord.y + height - 1 }\n if (dirEquals(dir, Left)) return { x: baseCoord.x, y: centerY }\n if (dirEquals(dir, Right)) return { x: baseCoord.x + width - 1, y: centerY }\n if (dirEquals(dir, UpperLeft)) return { x: baseCoord.x, y: baseCoord.y }\n if (dirEquals(dir, UpperRight))\n return { x: baseCoord.x + width - 1, y: baseCoord.y }\n if (dirEquals(dir, LowerLeft))\n return { x: baseCoord.x, y: baseCoord.y + height - 1 }\n if (dirEquals(dir, LowerRight))\n return { x: baseCoord.x + width - 1, y: baseCoord.y + height - 1 }\n // Middle\n return { x: centerX, y: centerY }\n}\n\n// ============================================================================\n// Rectangle renderer\n// ============================================================================\n\n/**\n * Rectangle shape renderer — the default box shape.\n * Renders as:\n * ┌─────────┐\n * │ Label │\n * └─────────┘\n */\nexport const rectangleRenderer: ShapeRenderer = {\n getDimensions: getBoxDimensions,\n\n render(\n label: string,\n dimensions: ShapeDimensions,\n options: ShapeRenderOptions,\n ): Canvas {\n const corners = getCorners('rectangle', options.useAscii)\n return renderBox(label, dimensions, corners, options.useAscii)\n },\n\n getAttachmentPoint: getBoxAttachmentPoint,\n}\n","// ============================================================================\n// Diamond shape renderer — uses corner decorators instead of diagonals\n// ============================================================================\n\nimport type { ShapeRenderer } from './types.ts'\nimport {\n getBoxDimensions,\n renderBox,\n getBoxAttachmentPoint,\n} from './rectangle.ts'\nimport { getCorners } from './corners.ts'\n\n/**\n * Diamond shape renderer.\n * Uses diamond markers (◇) at corners to indicate decision node semantics.\n *\n * Renders as:\n * ◇─────────◇\n * │ Label │\n * ◇─────────◇\n */\nexport const diamondRenderer: ShapeRenderer = {\n getDimensions: getBoxDimensions,\n\n render(label, dimensions, options) {\n const corners = getCorners('diamond', options.useAscii)\n return renderBox(label, dimensions, corners, options.useAscii)\n },\n\n getAttachmentPoint: getBoxAttachmentPoint,\n}\n","// ============================================================================\n// Circle shape renderer — uses corner decorators instead of curves\n// ============================================================================\n\nimport type { ShapeRenderer } from './types.ts'\nimport {\n getBoxDimensions,\n renderBox,\n getBoxAttachmentPoint,\n} from './rectangle.ts'\nimport { getCorners } from './corners.ts'\n\n/**\n * Circle shape renderer.\n * Uses circle markers (◯) at corners to indicate circular shape semantics.\n *\n * Renders as:\n * ◯─────────◯\n * │ Label │\n * ◯─────────◯\n */\nexport const circleRenderer: ShapeRenderer = {\n getDimensions: getBoxDimensions,\n\n render(label, dimensions, options) {\n const corners = getCorners('circle', options.useAscii)\n return renderBox(label, dimensions, corners, options.useAscii)\n },\n\n getAttachmentPoint: getBoxAttachmentPoint,\n}\n","// ============================================================================\n// State pseudo-state renderers — UML start and end states\n// ============================================================================\n\nimport type { Canvas, DrawingCoord, Direction } from '../types.ts'\nimport { Up, Down, Left, Right } from '../types.ts'\nimport { mkCanvas } from '../canvas.ts'\nimport type {\n ShapeRenderer,\n ShapeDimensions,\n ShapeRenderOptions,\n} from './types.ts'\nimport { dirEquals } from '../edge-routing.ts'\n\n/**\n * State start pseudo-state renderer — filled circle in rounded box.\n * Renders as:\n * ╭───╮\n * │ ● │ (Unicode)\n * ╰───╯\n *\n * .---.\n * | * | (ASCII)\n * '---'\n *\n * This represents the UML initial pseudo-state.\n */\nexport const stateStartRenderer: ShapeRenderer = {\n getDimensions(_label: string, _options: ShapeRenderOptions): ShapeDimensions {\n // Start state is a 5x3 rounded box with centered symbol\n const width = 5\n const height = 3\n\n return {\n width,\n height,\n labelArea: { x: 2, y: 1, width: 1, height: 1 },\n gridColumns: [1, 3, 1],\n gridRows: [1, 1, 1],\n }\n },\n\n render(\n _label: string,\n dimensions: ShapeDimensions,\n options: ShapeRenderOptions,\n ): Canvas {\n const { width, height } = dimensions\n const canvas = mkCanvas(width - 1, height - 1)\n\n const centerX = Math.floor(width / 2) // = 2\n\n if (!options.useAscii) {\n // Unicode rounded box with filled circle: ╭───╮ │ ● │ ╰───╯\n canvas[0]![0] = '╭'\n canvas[1]![0] = '─'\n canvas[2]![0] = '─'\n canvas[3]![0] = '─'\n canvas[4]![0] = '╮'\n\n canvas[0]![1] = '│'\n canvas[centerX]![1] = '●'\n canvas[4]![1] = '│'\n\n canvas[0]![2] = '╰'\n canvas[1]![2] = '─'\n canvas[2]![2] = '─'\n canvas[3]![2] = '─'\n canvas[4]![2] = '╯'\n } else {\n // ASCII rounded box: .---. | * | '---'\n canvas[0]![0] = '.'\n canvas[1]![0] = '-'\n canvas[2]![0] = '-'\n canvas[3]![0] = '-'\n canvas[4]![0] = '.'\n\n canvas[0]![1] = '|'\n canvas[centerX]![1] = '*'\n canvas[4]![1] = '|'\n\n canvas[0]![2] = \"'\"\n canvas[1]![2] = '-'\n canvas[2]![2] = '-'\n canvas[3]![2] = '-'\n canvas[4]![2] = \"'\"\n }\n\n return canvas\n },\n\n getAttachmentPoint(\n dir: Direction,\n dimensions: ShapeDimensions,\n baseCoord: DrawingCoord,\n ): DrawingCoord {\n const { width, height } = dimensions\n const centerX = baseCoord.x + Math.floor(width / 2)\n const centerY = baseCoord.y + Math.floor(height / 2)\n\n if (dirEquals(dir, Up)) return { x: centerX, y: baseCoord.y }\n if (dirEquals(dir, Down)) return { x: centerX, y: baseCoord.y + height - 1 }\n if (dirEquals(dir, Left)) return { x: baseCoord.x, y: centerY }\n if (dirEquals(dir, Right)) return { x: baseCoord.x + width - 1, y: centerY }\n // All diagonals and middle point to center\n return { x: centerX, y: centerY }\n },\n}\n\n/**\n * State end pseudo-state renderer — bullseye in double-bordered box.\n * Renders as:\n * ╔═══╗\n * ║ ◎ ║ (Unicode)\n * ╚═══╝\n *\n * #===#\n * # * # (ASCII)\n * #===#\n *\n * This represents the UML final state. The double border distinguishes it\n * from the start state's single rounded border.\n */\nexport const stateEndRenderer: ShapeRenderer = {\n getDimensions(_label: string, _options: ShapeRenderOptions): ShapeDimensions {\n // End state is a 5x3 double-bordered box with centered symbol\n const width = 5\n const height = 3\n\n return {\n width,\n height,\n labelArea: { x: 2, y: 1, width: 1, height: 1 },\n gridColumns: [1, 3, 1],\n gridRows: [1, 1, 1],\n }\n },\n\n render(\n _label: string,\n dimensions: ShapeDimensions,\n options: ShapeRenderOptions,\n ): Canvas {\n const { width, height } = dimensions\n const canvas = mkCanvas(width - 1, height - 1)\n\n const centerX = Math.floor(width / 2) // = 2\n\n if (!options.useAscii) {\n // Unicode double-bordered box with bullseye: ╔═══╗ ║ ◎ ║ ╚═══╝\n canvas[0]![0] = '╔'\n canvas[1]![0] = '═'\n canvas[2]![0] = '═'\n canvas[3]![0] = '═'\n canvas[4]![0] = '╗'\n\n canvas[0]![1] = '║'\n canvas[centerX]![1] = '◎'\n canvas[4]![1] = '║'\n\n canvas[0]![2] = '╚'\n canvas[1]![2] = '═'\n canvas[2]![2] = '═'\n canvas[3]![2] = '═'\n canvas[4]![2] = '╝'\n } else {\n // ASCII double-bordered box: #===# # * # #===#\n canvas[0]![0] = '#'\n canvas[1]![0] = '='\n canvas[2]![0] = '='\n canvas[3]![0] = '='\n canvas[4]![0] = '#'\n\n canvas[0]![1] = '#'\n canvas[centerX]![1] = '*'\n canvas[4]![1] = '#'\n\n canvas[0]![2] = '#'\n canvas[1]![2] = '='\n canvas[2]![2] = '='\n canvas[3]![2] = '='\n canvas[4]![2] = '#'\n }\n\n return canvas\n },\n\n getAttachmentPoint(\n dir: Direction,\n dimensions: ShapeDimensions,\n baseCoord: DrawingCoord,\n ): DrawingCoord {\n const { width, height } = dimensions\n const centerX = baseCoord.x + Math.floor(width / 2)\n const centerY = baseCoord.y + Math.floor(height / 2)\n\n if (dirEquals(dir, Up)) return { x: centerX, y: baseCoord.y }\n if (dirEquals(dir, Down)) return { x: centerX, y: baseCoord.y + height - 1 }\n if (dirEquals(dir, Left)) return { x: baseCoord.x, y: centerY }\n if (dirEquals(dir, Right)) return { x: baseCoord.x + width - 1, y: centerY }\n // All diagonals and middle point to center\n return { x: centerX, y: centerY }\n },\n}\n","// ============================================================================\n// Rounded rectangle shape renderer — uses rounded corner decorators\n// ============================================================================\n\nimport type { ShapeRenderer } from './types.ts'\nimport {\n getBoxDimensions,\n renderBox,\n getBoxAttachmentPoint,\n} from './rectangle.ts'\nimport { getCorners } from './corners.ts'\n\n/**\n * Rounded rectangle shape renderer.\n * Uses rounded corner markers (╭╮╰╯) to indicate soft edges.\n *\n * Renders as:\n * ╭─────────╮\n * │ Label │\n * ╰─────────╯\n */\nexport const roundedRenderer: ShapeRenderer = {\n getDimensions: getBoxDimensions,\n\n render(label, dimensions, options) {\n const corners = getCorners('rounded', options.useAscii)\n return renderBox(label, dimensions, corners, options.useAscii)\n },\n\n getAttachmentPoint: getBoxAttachmentPoint,\n}\n","// ============================================================================\n// Stadium (pill) shape renderer — special parentheses-based rendering\n// ============================================================================\n//\n// Stadium has unique rendering: single-line is inline `(Label)`, multi-line\n// uses parentheses or rounded corners. This differs from other shapes that\n// use corner decorators with box lines.\n\nimport type { Canvas } from '../types.ts'\nimport { mkCanvas } from '../canvas.ts'\nimport { splitLines } from '../multiline-utils.ts'\nimport { displayWidth, toDisplayCells } from '../display-width.ts'\nimport type {\n ShapeRenderer,\n ShapeDimensions,\n ShapeRenderOptions,\n} from './types.ts'\nimport { getBoxAttachmentPoint } from './rectangle.ts'\n\n/**\n * Stadium (pill) shape renderer.\n *\n * Single-line: ( Label )\n *\n * Multi-line unicode:\n * ╭──────────╮\n * │ Label │\n * ╰──────────╯\n *\n * Multi-line ASCII:\n * (----------)\n * ( Label )\n * (----------)\n */\nexport const stadiumRenderer: ShapeRenderer = {\n getDimensions(label: string, options: ShapeRenderOptions): ShapeDimensions {\n const lines = splitLines(label)\n const maxLineWidth = Math.max(...lines.map((l) => displayWidth(l)), 0)\n const lineCount = lines.length\n\n const innerWidth = 2 * options.padding + maxLineWidth\n const width = innerWidth + 4 // Extra for rounded ends\n const innerHeight = lineCount + 2 * options.padding\n const height = Math.max(innerHeight + 2, 3)\n\n return {\n width,\n height,\n labelArea: {\n x: 2 + options.padding,\n y: 1 + options.padding,\n width: maxLineWidth,\n height: lineCount,\n },\n gridColumns: [2, innerWidth, 2],\n gridRows: [1, innerHeight, 1],\n }\n },\n\n render(\n label: string,\n dimensions: ShapeDimensions,\n options: ShapeRenderOptions,\n ): Canvas {\n const { width, height } = dimensions\n const canvas = mkCanvas(width - 1, height - 1)\n\n const centerY = Math.floor(height / 2)\n const hChar = options.useAscii ? '-' : '─'\n\n if (height === 3) {\n // Single row pill: ( Label )\n canvas[0]![centerY] = '('\n canvas[width - 1]![centerY] = ')'\n } else if (!options.useAscii) {\n // Multi-row stadium with rounded corners (unicode)\n canvas[0]![0] = '╭'\n for (let x = 1; x < width - 1; x++) canvas[x]![0] = hChar\n canvas[width - 1]![0] = '╮'\n\n for (let y = 1; y < height - 1; y++) {\n canvas[0]![y] = '│'\n canvas[width - 1]![y] = '│'\n }\n\n canvas[0]![height - 1] = '╰'\n for (let x = 1; x < width - 1; x++) canvas[x]![height - 1] = hChar\n canvas[width - 1]![height - 1] = '╯'\n } else {\n // Multi-row stadium ASCII — parentheses on all sides\n for (let y = 0; y < height; y++) {\n canvas[0]![y] = '('\n canvas[width - 1]![y] = ')'\n }\n for (let x = 1; x < width - 1; x++) {\n canvas[x]![0] = hChar\n canvas[x]![height - 1] = hChar\n }\n }\n\n // Center the label\n const lines = splitLines(label)\n const startY = centerY - Math.floor((lines.length - 1) / 2)\n\n for (let i = 0; i < lines.length; i++) {\n const line = lines[i]!\n const textX = Math.floor(width / 2) - Math.floor(displayWidth(line) / 2)\n const cells = toDisplayCells(line)\n for (let j = 0; j < cells.length; j++) {\n const x = textX + j\n const y = startY + i\n if (x > 0 && x < width - 1 && y >= 0 && y < height) {\n canvas[x]![y] = cells[j]!\n }\n }\n }\n\n return canvas\n },\n\n getAttachmentPoint: getBoxAttachmentPoint,\n}\n","// ============================================================================\n// Hexagon shape renderer — uses corner decorators instead of diagonals\n// ============================================================================\n\nimport type { ShapeRenderer } from './types.ts'\nimport {\n getBoxDimensions,\n renderBox,\n getBoxAttachmentPoint,\n} from './rectangle.ts'\nimport { getCorners } from './corners.ts'\n\n/**\n * Hexagon shape renderer.\n * Uses crop-corner markers (⌜⌝⌞⌟, see corners.ts's `SHAPE_CORNERS.hexagon`)\n * to indicate process node semantics — not the hexagon glyph ⬡ (U+2B21)\n * this comment used to claim: JetBrains Mono NL has no glyph for ⬡ at all,\n * and the renderer hasn't actually emitted it since corners.ts moved to\n * the monospace-safe crop-corner style. See issue #1062.\n *\n * Renders as:\n * ⌜─────────⌝\n * │ Label │\n * ⌞─────────⌟\n */\nexport const hexagonRenderer: ShapeRenderer = {\n getDimensions: getBoxDimensions,\n\n render(label, dimensions, options) {\n const corners = getCorners('hexagon', options.useAscii)\n return renderBox(label, dimensions, corners, options.useAscii)\n },\n\n getAttachmentPoint: getBoxAttachmentPoint,\n}\n","// ============================================================================\n// Special shape renderers — subroutine, doublecircle, cylinder, etc.\n// ============================================================================\n//\n// Some shapes have unique internal structure (subroutine, cylinder) and keep\n// custom rendering. Others use the corner decorator pattern for simplicity.\n\nimport type { Canvas } from '../types.ts'\nimport { mkCanvas } from '../canvas.ts'\nimport { splitLines } from '../multiline-utils.ts'\nimport { displayWidth, toDisplayCells } from '../display-width.ts'\nimport type {\n ShapeRenderer,\n ShapeDimensions,\n ShapeRenderOptions,\n} from './types.ts'\nimport {\n getBoxDimensions,\n renderBox,\n getBoxAttachmentPoint,\n} from './rectangle.ts'\nimport { getCorners } from './corners.ts'\n\n// ============================================================================\n// Subroutine — keeps custom double-border rendering\n// ============================================================================\n\n/**\n * Subroutine shape renderer — double-bordered rectangle.\n * Renders as:\n * ┌┬─────────┬┐\n * ││ Label ││\n * └┴─────────┴┘\n */\nexport const subroutineRenderer: ShapeRenderer = {\n getDimensions(label: string, options: ShapeRenderOptions): ShapeDimensions {\n const lines = splitLines(label)\n const maxLineWidth = Math.max(...lines.map((l) => displayWidth(l)), 0)\n const lineCount = lines.length\n\n const innerWidth = 2 * options.padding + maxLineWidth\n const width = innerWidth + 4 // Double borders on each side\n const innerHeight = lineCount + 2 * options.padding\n const height = innerHeight + 2\n\n return {\n width,\n height,\n labelArea: {\n x: 2 + options.padding,\n y: 1 + options.padding,\n width: maxLineWidth,\n height: lineCount,\n },\n gridColumns: [2, innerWidth, 2],\n gridRows: [1, innerHeight, 1],\n }\n },\n\n render(\n label: string,\n dimensions: ShapeDimensions,\n options: ShapeRenderOptions,\n ): Canvas {\n const { width, height } = dimensions\n const canvas = mkCanvas(width - 1, height - 1)\n\n const hChar = options.useAscii ? '-' : '─'\n const vChar = options.useAscii ? '|' : '│'\n\n // Top border\n canvas[0]![0] = options.useAscii ? '+' : '┌'\n canvas[1]![0] = options.useAscii ? '+' : '┬'\n for (let x = 2; x < width - 2; x++) canvas[x]![0] = hChar\n canvas[width - 2]![0] = options.useAscii ? '+' : '┬'\n canvas[width - 1]![0] = options.useAscii ? '+' : '┐'\n\n // Sides with double border\n for (let y = 1; y < height - 1; y++) {\n canvas[0]![y] = vChar\n canvas[1]![y] = vChar\n canvas[width - 2]![y] = vChar\n canvas[width - 1]![y] = vChar\n }\n\n // Bottom border\n canvas[0]![height - 1] = options.useAscii ? '+' : '└'\n canvas[1]![height - 1] = options.useAscii ? '+' : '┴'\n for (let x = 2; x < width - 2; x++) canvas[x]![height - 1] = hChar\n canvas[width - 2]![height - 1] = options.useAscii ? '+' : '┴'\n canvas[width - 1]![height - 1] = options.useAscii ? '+' : '┘'\n\n // Center the label\n const lines = splitLines(label)\n const centerY = Math.floor(height / 2)\n const startY = centerY - Math.floor((lines.length - 1) / 2)\n\n for (let i = 0; i < lines.length; i++) {\n const line = lines[i]!\n const textX = Math.floor(width / 2) - Math.floor(displayWidth(line) / 2)\n const cells = toDisplayCells(line)\n for (let j = 0; j < cells.length; j++) {\n const x = textX + j\n const y = startY + i\n if (x > 1 && x < width - 2 && y > 0 && y < height - 1) {\n canvas[x]![y] = cells[j]!\n }\n }\n }\n\n return canvas\n },\n\n getAttachmentPoint: getBoxAttachmentPoint,\n}\n\n// ============================================================================\n// Double circle — uses corner decorators\n// ============================================================================\n\n/**\n * Double circle shape renderer.\n * Uses double circle markers (◎) at corners.\n *\n * Renders as:\n * ◎─────────◎\n * │ Label │\n * ◎─────────◎\n */\nexport const doublecircleRenderer: ShapeRenderer = {\n getDimensions: getBoxDimensions,\n\n render(label, dimensions, options) {\n const corners = getCorners('doublecircle', options.useAscii)\n return renderBox(label, dimensions, corners, options.useAscii)\n },\n\n getAttachmentPoint: getBoxAttachmentPoint,\n}\n\n// ============================================================================\n// Cylinder — keeps custom rendering for database appearance\n// ============================================================================\n\n/**\n * Cylinder shape renderer — database symbol.\n * Renders as:\n * ╭─────╮\n * │─────│\n * │ DB │\n * │─────│\n * ╰─────╯\n */\nexport const cylinderRenderer: ShapeRenderer = {\n getDimensions(label: string, options: ShapeRenderOptions): ShapeDimensions {\n const lines = splitLines(label)\n const maxLineWidth = Math.max(...lines.map((l) => displayWidth(l)), 0)\n const lineCount = lines.length\n\n const innerWidth = 2 * options.padding + maxLineWidth\n const width = innerWidth + 2\n const innerHeight = lineCount + 2 * options.padding + 2 // Extra for curved top/bottom\n const height = innerHeight + 2\n\n return {\n width,\n height,\n labelArea: {\n x: 1 + options.padding,\n y: 2 + options.padding,\n width: maxLineWidth,\n height: lineCount,\n },\n gridColumns: [1, innerWidth, 1],\n gridRows: [2, innerHeight - 2, 2],\n }\n },\n\n render(\n label: string,\n dimensions: ShapeDimensions,\n options: ShapeRenderOptions,\n ): Canvas {\n const { width, height } = dimensions\n const canvas = mkCanvas(width - 1, height - 1)\n\n const hChar = options.useAscii ? '-' : '─'\n const vChar = options.useAscii ? '|' : '│'\n\n // Top ellipse\n canvas[0]![0] = options.useAscii ? '.' : '╭'\n for (let x = 1; x < width - 1; x++) canvas[x]![0] = hChar\n canvas[width - 1]![0] = options.useAscii ? '.' : '╮'\n\n // Second row - bottom of top ellipse\n canvas[0]![1] = vChar\n for (let x = 1; x < width - 1; x++) canvas[x]![1] = hChar\n canvas[width - 1]![1] = vChar\n\n // Middle section\n for (let y = 2; y < height - 2; y++) {\n canvas[0]![y] = vChar\n canvas[width - 1]![y] = vChar\n }\n\n // Second to last row - top of bottom ellipse\n canvas[0]![height - 2] = vChar\n for (let x = 1; x < width - 1; x++) canvas[x]![height - 2] = hChar\n canvas[width - 1]![height - 2] = vChar\n\n // Bottom ellipse\n canvas[0]![height - 1] = options.useAscii ? \"'\" : '╰'\n for (let x = 1; x < width - 1; x++) canvas[x]![height - 1] = hChar\n canvas[width - 1]![height - 1] = options.useAscii ? \"'\" : '╯'\n\n // Center the label\n const lines = splitLines(label)\n const centerY = Math.floor(height / 2)\n const startY = centerY - Math.floor((lines.length - 1) / 2)\n\n for (let i = 0; i < lines.length; i++) {\n const line = lines[i]!\n const textX = Math.floor(width / 2) - Math.floor(displayWidth(line) / 2)\n const cells = toDisplayCells(line)\n for (let j = 0; j < cells.length; j++) {\n const x = textX + j\n const y = startY + i\n if (x > 0 && x < width - 1 && y > 1 && y < height - 2) {\n canvas[x]![y] = cells[j]!\n }\n }\n }\n\n return canvas\n },\n\n getAttachmentPoint: getBoxAttachmentPoint,\n}\n\n// ============================================================================\n// Asymmetric (flag) — uses corner decorators\n// ============================================================================\n\n/**\n * Asymmetric (flag/banner) shape renderer.\n * Uses arrow markers (▷) on left corners.\n *\n * Renders as:\n * ▷─────────┐\n * │ Label │\n * ▷─────────┘\n */\nexport const asymmetricRenderer: ShapeRenderer = {\n getDimensions: getBoxDimensions,\n\n render(label, dimensions, options) {\n const corners = getCorners('asymmetric', options.useAscii)\n return renderBox(label, dimensions, corners, options.useAscii)\n },\n\n getAttachmentPoint: getBoxAttachmentPoint,\n}\n\n// ============================================================================\n// Trapezoid — uses corner decorators instead of diagonal sides\n// ============================================================================\n\n/**\n * Trapezoid shape renderer — wider at bottom.\n * Uses plain slash slope markers (/ \\, see corners.ts's\n * `SHAPE_CORNERS.trapezoid`) on top corners — not the ◸◹ corner-triangle\n * glyphs this comment used to claim: JetBrains Mono NL has no glyph for\n * those at all, and the renderer hasn't actually emitted them since\n * corners.ts moved to the diagonal-slash style. See issue #1062.\n *\n * Renders as:\n * /─────────\\\n * │ Label │\n * └─────────┘\n */\nexport const trapezoidRenderer: ShapeRenderer = {\n getDimensions: getBoxDimensions,\n\n render(label, dimensions, options) {\n const corners = getCorners('trapezoid', options.useAscii)\n return renderBox(label, dimensions, corners, options.useAscii)\n },\n\n getAttachmentPoint: getBoxAttachmentPoint,\n}\n\n// ============================================================================\n// Trapezoid-alt — uses corner decorators instead of diagonal sides\n// ============================================================================\n\n/**\n * Trapezoid-alt shape renderer — wider at top.\n * Uses plain slash slope markers (\\ /, see corners.ts's\n * `SHAPE_CORNERS['trapezoid-alt']`) on bottom corners — not the ◺◿\n * corner-triangle glyphs this comment used to claim: JetBrains Mono NL has\n * no glyph for those at all, and the renderer hasn't actually emitted them\n * since corners.ts moved to the diagonal-slash style. See issue #1062.\n *\n * Renders as:\n * ┌─────────┐\n * │ Label │\n * \\─────────/\n */\nexport const trapezoidAltRenderer: ShapeRenderer = {\n getDimensions: getBoxDimensions,\n\n render(label, dimensions, options) {\n const corners = getCorners('trapezoid-alt', options.useAscii)\n return renderBox(label, dimensions, corners, options.useAscii)\n },\n\n getAttachmentPoint: getBoxAttachmentPoint,\n}\n\n// ============================================================================\n// Parallelogram — both sides slope the same way\n// ============================================================================\n\n/**\n * Parallelogram shape renderer — leans right.\n *\n * Renders as:\n * /─────────/\n * │ Label │\n * /─────────/\n */\nexport const parallelogramRenderer: ShapeRenderer = {\n getDimensions: getBoxDimensions,\n\n render(label, dimensions, options) {\n const corners = getCorners('parallelogram', options.useAscii)\n return renderBox(label, dimensions, corners, options.useAscii)\n },\n\n getAttachmentPoint: getBoxAttachmentPoint,\n}\n\n/**\n * Parallelogram-alt shape renderer — leans left.\n *\n * Renders as:\n * \\─────────\\\n * │ Label │\n * \\─────────\\\n */\nexport const parallelogramAltRenderer: ShapeRenderer = {\n getDimensions: getBoxDimensions,\n\n render(label, dimensions, options) {\n const corners = getCorners('parallelogram-alt', options.useAscii)\n return renderBox(label, dimensions, corners, options.useAscii)\n },\n\n getAttachmentPoint: getBoxAttachmentPoint,\n}\n","// ============================================================================\n// Shape registry — pluggable ASCII shape renderers\n// ============================================================================\n\nimport type {\n AsciiNodeShape,\n Canvas,\n DrawingCoord,\n Direction,\n} from '../types.ts'\nimport type {\n ShapeRenderer,\n ShapeDimensions,\n ShapeRenderOptions,\n ShapeRegistry,\n} from './types.ts'\n\n// Import all shape renderers\nimport { rectangleRenderer } from './rectangle.ts'\nimport { diamondRenderer } from './diamond.ts'\nimport { circleRenderer } from './circle.ts'\nimport { stateStartRenderer, stateEndRenderer } from './state.ts'\nimport { roundedRenderer } from './rounded.ts'\nimport { stadiumRenderer } from './stadium.ts'\nimport { hexagonRenderer } from './hexagon.ts'\nimport {\n subroutineRenderer,\n doublecircleRenderer,\n cylinderRenderer,\n asymmetricRenderer,\n trapezoidRenderer,\n trapezoidAltRenderer,\n parallelogramRenderer,\n parallelogramAltRenderer,\n} from './special.ts'\n\n// Re-export types\nexport type {\n ShapeRenderer,\n ShapeDimensions,\n ShapeRenderOptions,\n ShapeRegistry,\n}\n\n/**\n * Global shape registry — maps shape types to their renderers.\n * Rectangle is the default fallback for unregistered shapes.\n */\nexport const shapeRegistry: ShapeRegistry = new Map<\n AsciiNodeShape,\n ShapeRenderer\n>([\n // Core shapes\n ['rectangle', rectangleRenderer],\n ['rounded', roundedRenderer],\n ['diamond', diamondRenderer],\n ['stadium', stadiumRenderer],\n ['circle', circleRenderer],\n\n // Batch 1 additions\n ['subroutine', subroutineRenderer],\n ['doublecircle', doublecircleRenderer],\n ['hexagon', hexagonRenderer],\n\n // Batch 2 additions\n ['cylinder', cylinderRenderer],\n ['asymmetric', asymmetricRenderer],\n ['trapezoid', trapezoidRenderer],\n ['trapezoid-alt', trapezoidAltRenderer],\n ['parallelogram', parallelogramRenderer],\n ['parallelogram-alt', parallelogramAltRenderer],\n\n // State diagram pseudo-states\n ['state-start', stateStartRenderer],\n ['state-end', stateEndRenderer],\n])\n\n/**\n * Get the renderer for a shape type, falling back to rectangle.\n */\nexport function getShapeRenderer(shape: AsciiNodeShape): ShapeRenderer {\n return shapeRegistry.get(shape) ?? rectangleRenderer\n}\n\n/**\n * Render a node shape to a canvas.\n * This is the main entry point for shape rendering.\n */\nexport function renderShape(\n shape: AsciiNodeShape,\n label: string,\n options: ShapeRenderOptions,\n): Canvas {\n const renderer = getShapeRenderer(shape)\n const dimensions = renderer.getDimensions(label, options)\n return renderer.render(label, dimensions, options)\n}\n\n/**\n * Get dimensions for a shape given a label.\n * Used during layout to determine node size.\n */\nexport function getShapeDimensions(\n shape: AsciiNodeShape,\n label: string,\n options: ShapeRenderOptions,\n): ShapeDimensions {\n const renderer = getShapeRenderer(shape)\n return renderer.getDimensions(label, options)\n}\n\n/**\n * Get edge attachment point for a shape.\n */\nexport function getShapeAttachmentPoint(\n shape: AsciiNodeShape,\n dir: Direction,\n dimensions: ShapeDimensions,\n baseCoord: DrawingCoord,\n): DrawingCoord {\n const renderer = getShapeRenderer(shape)\n return renderer.getAttachmentPoint(dir, dimensions, baseCoord)\n}\n","// ============================================================================\n// ASCII renderer — grid-based layout\n//\n// Ported from AlexanderGrooff/mermaid-ascii cmd/graph.go + cmd/mapping_node.go.\n// Places nodes on a logical grid, computes column/row sizes,\n// converts grid coordinates to character-level drawing coordinates,\n// and handles subgraph bounding boxes.\n// ============================================================================\n\nimport type {\n GridCoord,\n DrawingCoord,\n Direction,\n AsciiEdge,\n AsciiGraph,\n AsciiNode,\n AsciiSubgraph,\n} from './types.ts'\nimport { gridKey, requireGridCoord } from './types.ts'\nimport { setCanvasSizeToGrid, setRoleCanvasSizeToGrid } from './canvas.ts'\nimport {\n determinePath,\n determineLabelLine,\n assignParallelEdgeLanes,\n} from './edge-routing.ts'\nimport { analyzeEdgeBundles, processBundles } from './edge-bundling.ts'\nimport { createPathBudget } from './pathfinder.ts'\nimport {\n isBlockFree,\n placeBlock,\n cloneGrid,\n NODE_BLOCK_SIZE,\n type Grid,\n} from './grid-occupancy.ts'\nimport {\n createEdgeCellStyles,\n claimPathCells,\n findStyleConflict,\n createEdgeCellOwners,\n claimPathOwners,\n findUnrelatedOverlap,\n type EdgeCellStyles,\n type EdgeCellOwners,\n} from './edge-cell-styles.ts'\nimport { drawBox } from './draw.ts'\nimport { getShapeDimensions } from './shapes/index.ts'\nimport { splitLines } from './multiline-utils.ts'\nimport { displayWidth } from './display-width.ts'\n\n// `requireGridCoord` is defined in types.ts (a pure predicate over\n// AsciiNode with no grid-state dependency) and re-exported here so the two\n// existing call sites that import it from this module (edge-routing.ts,\n// edge-bundling.ts) don't need to change.\nexport { requireGridCoord }\n\n// ============================================================================\n// Grid coordinate → drawing coordinate conversion\n// ============================================================================\n\n/**\n * Convert a grid coordinate to a drawing (character) coordinate.\n * Sums column widths up to the target column, and row heights up to the target row,\n * then centers within the cell.\n */\nexport function gridToDrawingCoord(\n graph: AsciiGraph,\n c: GridCoord,\n dir?: Direction,\n): DrawingCoord {\n const target: GridCoord = dir ? { x: c.x + dir.x, y: c.y + dir.y } : c\n\n let x = 0\n for (let col = 0; col < target.x; col++) {\n x += graph.columnWidth.get(col) ?? 0\n }\n\n let y = 0\n for (let row = 0; row < target.y; row++) {\n y += graph.rowHeight.get(row) ?? 0\n }\n\n const colW = graph.columnWidth.get(target.x) ?? 0\n const rowH = graph.rowHeight.get(target.y) ?? 0\n return {\n x: x + Math.floor(colW / 2) + graph.offsetX,\n y: y + Math.floor(rowH / 2) + graph.offsetY,\n }\n}\n\n/** Convert a path of grid coords to drawing coords. */\nexport function lineToDrawing(\n graph: AsciiGraph,\n line: GridCoord[],\n): DrawingCoord[] {\n return line.map((c) => gridToDrawingCoord(graph, c))\n}\n\n// ============================================================================\n// Node placement on the grid\n// ============================================================================\n\n/**\n * Reserve a 3x3 block in the grid for a node.\n * If the requested position is occupied, recursively shift by 4 grid units\n * (in the perpendicular direction based on effective direction) until a free spot is found.\n *\n * @param effectiveDir - Optional direction override. If not provided, uses the node's\n * effective direction (subgraph direction if in a subgraph with override,\n * otherwise graph direction).\n */\nexport function reserveSpotInGrid(\n graph: AsciiGraph,\n node: AsciiNode,\n requested: GridCoord,\n effectiveDir?: 'LR' | 'TD',\n): GridCoord {\n // Determine direction for collision handling\n const dir = effectiveDir ?? getEffectiveDirection(graph, node)\n\n if (!isBlockFree(graph.grid, requested, NODE_BLOCK_SIZE)) {\n // Collision — shift perpendicular to main flow direction\n if (dir === 'LR') {\n return reserveSpotInGrid(\n graph,\n node,\n { x: requested.x, y: requested.y + 4 },\n dir,\n )\n } else {\n return reserveSpotInGrid(\n graph,\n node,\n { x: requested.x + 4, y: requested.y },\n dir,\n )\n }\n }\n\n placeBlock(graph.grid, requested, NODE_BLOCK_SIZE)\n\n node.gridCoord = requested\n return requested\n}\n\n// ============================================================================\n// Column width / row height computation\n// ============================================================================\n\n/**\n * Set column widths and row heights for a node's 3x3 grid block.\n * Each node occupies 3 columns (border, content, border) and 3 rows.\n * Uses shape-aware dimensions to properly size non-rectangular shapes.\n */\nexport function setColumnWidth(graph: AsciiGraph, node: AsciiNode): void {\n const gc = requireGridCoord(node)\n const padding = graph.config.boxBorderPadding\n\n // Get shape-aware dimensions\n const shapeDims = getShapeDimensions(node.shape, node.displayLabel, {\n useAscii: graph.config.useAscii,\n padding,\n })\n\n // Use shape-provided grid dimensions\n const colWidths = shapeDims.gridColumns\n const rowHeights = shapeDims.gridRows\n\n for (let idx = 0; idx < colWidths.length; idx++) {\n const xCoord = gc.x + idx\n const current = graph.columnWidth.get(xCoord) ?? 0\n graph.columnWidth.set(xCoord, Math.max(current, colWidths[idx]!))\n }\n\n for (let idx = 0; idx < rowHeights.length; idx++) {\n const yCoord = gc.y + idx\n const current = graph.rowHeight.get(yCoord) ?? 0\n graph.rowHeight.set(yCoord, Math.max(current, rowHeights[idx]!))\n }\n\n // Padding column/row before the node (spacing between nodes)\n if (gc.x > 0) {\n const current = graph.columnWidth.get(gc.x - 1) ?? 0\n graph.columnWidth.set(gc.x - 1, Math.max(current, graph.config.paddingX))\n }\n\n if (gc.y > 0) {\n let basePadding = graph.config.paddingY\n // Extra vertical padding for nodes with incoming edges from outside their subgraph\n if (hasIncomingEdgeFromOutsideSubgraph(graph, node)) {\n const subgraphOverhead = 4\n basePadding += subgraphOverhead\n }\n const current = graph.rowHeight.get(gc.y - 1) ?? 0\n graph.rowHeight.set(gc.y - 1, Math.max(current, basePadding))\n }\n}\n\n/** Ensure grid has width/height entries for all cells along an edge path. */\nexport function increaseGridSizeForPath(\n graph: AsciiGraph,\n path: GridCoord[],\n): void {\n for (const c of path) {\n if (!graph.columnWidth.has(c.x)) {\n graph.columnWidth.set(c.x, Math.floor(graph.config.paddingX / 2))\n }\n if (!graph.rowHeight.has(c.y)) {\n graph.rowHeight.set(c.y, Math.floor(graph.config.paddingY / 2))\n }\n }\n}\n\n/**\n * Cap on re-route attempts for a single edge's cross-style conflicts.\n *\n * `determinePath`'s A* attempts respect the blocked cells added below, but\n * its Case-4 direct-fallback (used when both A* attempts fail outright)\n * draws a straight line ignoring occupancy entirely — so a pathological\n * layout where every route is blocked could keep \"finding\" the same\n * conflict forever. This bounds that to a handful of tries; if a genuine\n * conflict survives them, the edge keeps its last-routed (still\n * overlapping) path rather than looping — the same \"graceful degradation\n * over a hard failure\" the render-wide `PathBudget` already applies to A*\n * itself.\n */\nconst MAX_STYLE_CONFLICT_REROUTES = 8\n\n/**\n * After `edge` has been routed, check whether its path crosses a cell\n * already claimed by a *different*-style edge, or by an unrelated edge's\n * run of cells long enough to read as one continuous connector (see\n * edge-cell-styles.ts's two conflict checks) and, if so, re-route it around\n * the conflicting cell(s).\n *\n * Works by temporarily adding the conflicting cell to `graph.grid` — the\n * same occupancy map A* already treats node cells as blocked through — so\n * `determinePath`'s A* search avoids it on the next attempt, then removing\n * that temporary block again once this edge is done (it must not\n * permanently block the cell for other, unrelated edges; only these two\n * conflict shapes are meant to be avoided, not all future overlap).\n *\n * `nodeOnlyGrid` — not `graph.grid` — is what gets passed to\n * `findStyleConflict`/`findUnrelatedOverlap`'s \"is this cell node-owned, and\n * therefore not a real conflict\" check. This must be a separate,\n * frozen-at-node-placement-time grid: `graph.grid` gets `add`ed to right\n * below for A*'s benefit, and if the *conflict check* used that same live\n * grid, a cell temporarily blocked on attempt 1 would look node-occupied on\n * attempt 2 — so if A*'s direct-fallback (which ignores occupancy) routes\n * right back through it, the loop would wrongly see \"no conflict\" and stop,\n * leaving the two edges still overlapping there. See\n * ascii-edge-cross-style-overlap.test.ts's regression test for this exact\n * scenario.\n */\nfunction rerouteAroundStyleConflicts(\n graph: AsciiGraph,\n edge: AsciiEdge,\n cellStyles: EdgeCellStyles,\n cellOwners: EdgeCellOwners,\n nodeOnlyGrid: Grid,\n): void {\n const temporarilyBlocked: GridCoord[] = []\n try {\n for (let i = 0; i < MAX_STYLE_CONFLICT_REROUTES; i++) {\n const conflict =\n findStyleConflict(nodeOnlyGrid, cellStyles, edge.path, edge.style) ??\n findUnrelatedOverlap(nodeOnlyGrid, cellOwners, edge.path, edge)\n if (!conflict) return\n graph.grid.add(gridKey(conflict))\n temporarilyBlocked.push(conflict)\n determinePath(graph, edge)\n }\n } finally {\n for (const cell of temporarilyBlocked) graph.grid.delete(gridKey(cell))\n }\n}\n\n// ============================================================================\n// Subgraph helpers\n// ============================================================================\n\nfunction isNodeInAnySubgraph(graph: AsciiGraph, node: AsciiNode): boolean {\n return graph.subgraphs.some((sg) => sg.nodes.includes(node))\n}\n\n/**\n * Get the innermost subgraph that directly contains this node.\n * Returns null if node is not in any subgraph.\n */\nexport function getNodeSubgraph(\n graph: AsciiGraph,\n node: AsciiNode,\n): AsciiSubgraph | null {\n // Find the innermost (most deeply nested) subgraph containing the node\n let innermost: AsciiSubgraph | null = null\n for (const sg of graph.subgraphs) {\n if (sg.nodes.includes(node)) {\n // Check if this subgraph is deeper (more nested) than current innermost\n if (!innermost || isAncestorOrSelf(innermost, sg)) {\n innermost = sg\n }\n }\n }\n return innermost\n}\n\n/** Check if `candidate` is the same as or an ancestor of `target`. */\nfunction isAncestorOrSelf(\n candidate: AsciiSubgraph,\n target: AsciiSubgraph,\n): boolean {\n let current: AsciiSubgraph | null = target\n while (current !== null) {\n if (current === candidate) return true\n current = current.parent\n }\n return false\n}\n\n/**\n * Get the outermost (top-level, unnested) subgraph ancestor for a node.\n * Returns null if the node isn't in any subgraph.\n *\n * Used to group nodes for bounding-box-disjointness purposes: sibling\n * subgraphs nested under different top-level subgraphs are unrelated and\n * must never be allowed to overlap (#90), while a subgraph's own nested\n * children are already folded into its box via calculateSubgraphBoundingBox.\n */\nfunction getTopLevelSubgraph(\n graph: AsciiGraph,\n node: AsciiNode,\n): AsciiSubgraph | null {\n const sg = getNodeSubgraph(graph, node)\n if (!sg) return null\n let top = sg\n while (top.parent) top = top.parent\n return top\n}\n\n/** Recursively collect every node belonging to a subgraph, including nodes in nested subgraphs. */\nfunction collectSubgraphMembers(sg: AsciiSubgraph): AsciiNode[] {\n const members = [...sg.nodes]\n for (const child of sg.children) {\n members.push(...collectSubgraphMembers(child))\n }\n return members\n}\n\n/** Whether `to` is reachable from `from` by following outgoing edges (BFS). */\nfunction isReachableViaEdges(\n graph: AsciiGraph,\n from: AsciiNode,\n to: AsciiNode,\n): boolean {\n if (from === to) return true\n const visited = new Set<AsciiNode>([from])\n const queue: AsciiNode[] = [from]\n while (queue.length > 0) {\n const current = queue.shift()!\n for (const child of getChildren(graph, current)) {\n if (child === to) return true\n if (!visited.has(child)) {\n visited.add(child)\n queue.push(child)\n }\n }\n }\n return false\n}\n\n/**\n * Get the effective direction for a node's layout.\n * Returns the subgraph's direction override if the node is in a subgraph with one,\n * otherwise returns the graph-level direction.\n */\nexport function getEffectiveDirection(\n graph: AsciiGraph,\n node: AsciiNode,\n): 'LR' | 'TD' {\n const sg = getNodeSubgraph(graph, node)\n if (sg?.direction) {\n return sg.direction\n }\n return graph.config.graphDirection\n}\n\n/**\n * Check if a node has an incoming edge from outside its subgraph\n * AND is the topmost such node in its subgraph.\n * Used to add extra vertical padding for subgraph borders.\n */\nfunction hasIncomingEdgeFromOutsideSubgraph(\n graph: AsciiGraph,\n node: AsciiNode,\n): boolean {\n const nodeSg = getNodeSubgraph(graph, node)\n if (!nodeSg) return false\n\n let hasExternalEdge = false\n for (const edge of graph.edges) {\n if (edge.to === node) {\n const sourceSg = getNodeSubgraph(graph, edge.from)\n if (sourceSg !== nodeSg) {\n hasExternalEdge = true\n break\n }\n }\n }\n\n if (!hasExternalEdge) return false\n\n // Only return true for the topmost node with an external incoming edge\n const nodeY = requireGridCoord(node).y\n for (const otherNode of nodeSg.nodes) {\n if (otherNode === node || !otherNode.gridCoord) continue\n let otherHasExternal = false\n for (const edge of graph.edges) {\n if (edge.to === otherNode) {\n const sourceSg = getNodeSubgraph(graph, edge.from)\n if (sourceSg !== nodeSg) {\n otherHasExternal = true\n break\n }\n }\n }\n if (otherHasExternal && otherNode.gridCoord.y < nodeY) {\n return false\n }\n }\n\n return true\n}\n\n// ============================================================================\n// Subgraph bounding boxes\n// ============================================================================\n\nfunction calculateSubgraphBoundingBox(\n graph: AsciiGraph,\n sg: AsciiSubgraph,\n): void {\n if (sg.nodes.length === 0) return\n\n let minX = 1_000_000\n let minY = 1_000_000\n let maxX = -1_000_000\n let maxY = -1_000_000\n\n // Include children's bounding boxes\n for (const child of sg.children) {\n calculateSubgraphBoundingBox(graph, child)\n if (child.nodes.length > 0) {\n minX = Math.min(minX, child.minX)\n minY = Math.min(minY, child.minY)\n maxX = Math.max(maxX, child.maxX)\n maxY = Math.max(maxY, child.maxY)\n }\n }\n\n // Include node positions\n for (const node of sg.nodes) {\n if (!node.drawingCoord || !node.drawing) continue\n const nodeMinX = node.drawingCoord.x\n const nodeMinY = node.drawingCoord.y\n const nodeMaxX = nodeMinX + node.drawing.length - 1\n const nodeMaxY = nodeMinY + node.drawing[0]!.length - 1\n minX = Math.min(minX, nodeMinX)\n minY = Math.min(minY, nodeMinY)\n maxX = Math.max(maxX, nodeMaxX)\n maxY = Math.max(maxY, nodeMaxY)\n }\n\n const subgraphPadding = 2\n const subgraphLabelSpace = 2\n sg.minX = minX - subgraphPadding\n sg.minY = minY - subgraphPadding - subgraphLabelSpace\n sg.maxX = maxX + subgraphPadding\n sg.maxY = maxY + subgraphPadding\n\n // Widen the box, if necessary, so the subgraph's own cluster label fits\n // without truncation. `drawSubgraphLabel` (draw-subgraphs.ts) writes the\n // label into the interior columns `1..width-1` (width = maxX - minX), so\n // the box needs `width >= labelWidth + 1` to avoid clipping the label\n // against its own right border. Widening symmetrically (splitting any\n // extra columns across both sides) keeps the child nodes visually\n // centered inside the enlarged box rather than skewing it to one side.\n const labelWidth = Math.max(\n 0,\n ...splitLines(sg.name).map((line) => displayWidth(line)),\n )\n const currentWidth = sg.maxX - sg.minX\n const requiredWidth = labelWidth + 1\n if (requiredWidth > currentWidth) {\n const extra = requiredWidth - currentWidth\n const extraLeft = Math.floor(extra / 2)\n const extraRight = extra - extraLeft\n sg.minX -= extraLeft\n sg.maxX += extraRight\n }\n}\n\n/** Ensure non-overlapping root subgraphs have minimum spacing. */\nfunction ensureSubgraphSpacing(graph: AsciiGraph): void {\n const minSpacing = 1\n const rootSubgraphs = graph.subgraphs.filter(\n (sg) => sg.parent === null && sg.nodes.length > 0,\n )\n\n for (let i = 0; i < rootSubgraphs.length; i++) {\n for (let j = i + 1; j < rootSubgraphs.length; j++) {\n const sg1 = rootSubgraphs[i]!\n const sg2 = rootSubgraphs[j]!\n\n // Horizontal overlap → adjust vertical\n if (sg1.minX < sg2.maxX && sg1.maxX > sg2.minX) {\n if (sg1.maxY >= sg2.minY - minSpacing && sg1.minY < sg2.minY) {\n sg2.minY = sg1.maxY + minSpacing + 1\n } else if (sg2.maxY >= sg1.minY - minSpacing && sg2.minY < sg1.minY) {\n sg1.minY = sg2.maxY + minSpacing + 1\n }\n }\n // Vertical overlap → adjust horizontal\n if (sg1.minY < sg2.maxY && sg1.maxY > sg2.minY) {\n if (sg1.maxX >= sg2.minX - minSpacing && sg1.minX < sg2.minX) {\n sg2.minX = sg1.maxX + minSpacing + 1\n } else if (sg2.maxX >= sg1.minX - minSpacing && sg2.minX < sg1.minX) {\n sg1.minX = sg2.maxX + minSpacing + 1\n }\n }\n }\n }\n}\n\nexport function calculateSubgraphBoundingBoxes(graph: AsciiGraph): void {\n for (const sg of graph.subgraphs) {\n calculateSubgraphBoundingBox(graph, sg)\n }\n ensureSubgraphSpacing(graph)\n}\n\n/**\n * Offset all drawing coordinates so subgraph borders don't go negative.\n * If any subgraph has negative min coordinates, shift everything positive.\n */\nexport function offsetDrawingForSubgraphs(graph: AsciiGraph): void {\n if (graph.subgraphs.length === 0) return\n\n let minX = 0\n let minY = 0\n for (const sg of graph.subgraphs) {\n minX = Math.min(minX, sg.minX)\n minY = Math.min(minY, sg.minY)\n }\n\n const offsetX = -minX\n const offsetY = -minY\n if (offsetX === 0 && offsetY === 0) return\n\n graph.offsetX = offsetX\n graph.offsetY = offsetY\n\n for (const sg of graph.subgraphs) {\n sg.minX += offsetX\n sg.minY += offsetY\n sg.maxX += offsetX\n sg.maxY += offsetY\n }\n\n for (const node of graph.nodes) {\n if (node.drawingCoord) {\n node.drawingCoord.x += offsetX\n node.drawingCoord.y += offsetY\n }\n }\n}\n\n/**\n * Group root nodes by which downstream target they feed into, preserving a\n * stable order: groups appear in the order their shared target was first\n * seen among `roots`, and a root with no children (or whose target no other\n * root shares) keeps its original relative position.\n *\n * Fixes fan-in root placement: without this, `createMapping` places roots\n * sequentially in whatever order they were discovered, so e.g. `A1, B1, A2,\n * B2` (all roots, A1/A2 feeding A and B1/B2 feeding B) land interleaved on\n * the grid instead of grouped as `A1, A2, B1, B2` — causing the two fan-in\n * bundles' trunk edges to share a row and visually cross.\n */\nfunction groupRootsByDownstreamTarget(\n graph: AsciiGraph,\n roots: AsciiNode[],\n): AsciiNode[] {\n const primaryTargetKey = (node: AsciiNode): string | null => {\n const children = getChildren(graph, node)\n return children.length > 0 ? children[0]!.name : null\n }\n\n // For each root, find the index (within `roots`) of the first root that\n // shares its primary target — that's this root's sort anchor.\n const firstIndexForTarget = new Map<string, number>()\n const anchorIndex: number[] = roots.map((node, i) => {\n const key = primaryTargetKey(node)\n if (key === null) return i // no downstream target: anchor to self\n const existing = firstIndexForTarget.get(key)\n if (existing !== undefined) return existing\n firstIndexForTarget.set(key, i)\n return i\n })\n\n return roots\n .map((node, i) => ({ node, i }))\n .sort((a, b) => anchorIndex[a.i]! - anchorIndex[b.i]! || a.i - b.i)\n .map(({ node }) => node)\n}\n\n/**\n * A node whose every incoming path loops back through itself (e.g. `A -->\n * B --> C --> A`, or a lone self-loop `A --> A`) is, correctly, never a\n * \"root\" — every node in the cycle has a real incoming edge. But the grid\n * layout still needs at least one seed node per such component to place\n * anything at all; with zero roots feeding it, that component would never\n * get a gridCoord and later crash (setColumnWidth calls `requireGridCoord`).\n *\n * The old order-dependent detection accidentally provided this seed — the\n * first node the scan reached \"looked like\" a root simply because it\n * hadn't been visited as a target *yet* — which is the exact bug fixed\n * above. This restores just the useful part of that behavior for genuine\n * cycles: for each weakly-connected component not reachable from any real\n * root, seed it with its first-declared (graph.nodes order) node.\n */\nfunction addPseudoRootsForUnreachableCycles(\n graph: AsciiGraph,\n roots: AsciiNode[],\n): AsciiNode[] {\n const reachable = new Set<string>()\n const floodFrom = (start: AsciiNode): void => {\n const queue: AsciiNode[] = [start]\n while (queue.length > 0) {\n const n = queue.shift()!\n if (reachable.has(n.name)) continue\n reachable.add(n.name)\n for (const child of getChildren(graph, n)) queue.push(child)\n }\n }\n for (const root of roots) floodFrom(root)\n\n const result = [...roots]\n for (const node of graph.nodes) {\n if (reachable.has(node.name)) continue\n result.push(node)\n floodFrom(node)\n }\n return result\n}\n\n/**\n * Find the node whose grid block originates at `coord`, if any.\n *\n * Only meaningful before edge routing starts: every reserved grid cell at\n * that point belongs to a placed node's block (edges haven't claimed any\n * cells yet), and every block's origin sits on the 4-unit lattice that\n * `reserveSpotInGrid` allocates from — so matching by exact origin equality\n * is safe; there's no partial-overlap case to worry about yet.\n */\nfunction findNodeAtGridOrigin(\n graph: AsciiGraph,\n coord: GridCoord,\n): AsciiNode | undefined {\n return graph.nodes.find(\n (n) =>\n n.gridCoord !== null &&\n n.gridCoord.x === coord.x &&\n n.gridCoord.y === coord.y,\n )\n}\n\n/**\n * Find a free grid slot adjacent to `anchor` along `axis`, without walking\n * through a node that belongs to a *different* top-level subgraph than\n * `ownTopSg`.\n *\n * A deferred subgraph root (see the module doc above createMapping) anchors\n * next to an already-placed sibling and slides along the shared axis until\n * it finds free space. Sliding blindly — the naive approach, via\n * `reserveSpotInGrid`'s generic collision handling — can walk straight\n * through an unrelated sibling subgraph's node that already occupies the\n * next slot over, landing the deferred node on the *far* side of that\n * foreign node. That foreign node then sits between the anchor and the\n * deferred node, so it falls inside this subgraph's bounding box and its own\n * frame/title is dropped (#301).\n *\n * Tries `preferredSign` first (the direction the old blind slide always\n * used), then the opposite sign. In each direction, stops — without\n * accepting the enclosure — the moment it would have to step past a node\n * belonging to a different top-level subgraph, and only continues past\n * nodes belonging to the *same* one. Returns null if both directions are\n * immediately foreign-blocked, so the caller can fall back to the old\n * (occasionally imperfect but non-looping) blind-slide behavior.\n */\nfunction findSubgraphAdjacentSlot(\n graph: AsciiGraph,\n anchor: GridCoord,\n ownTopSg: AsciiSubgraph,\n axis: 'x' | 'y',\n preferredSign: 1 | -1,\n): GridCoord | null {\n const other: 'x' | 'y' = axis === 'x' ? 'y' : 'x'\n\n const tryDirection = (sign: 1 | -1): GridCoord | null => {\n let offset = 4\n for (;;) {\n const axisVal = anchor[axis] + sign * offset\n if (axisVal < 0) return null\n const candidate: GridCoord =\n axis === 'x'\n ? { x: axisVal, y: anchor[other] }\n : { x: anchor[other], y: axisVal }\n if (isBlockFree(graph.grid, candidate, NODE_BLOCK_SIZE)) return candidate\n const occupant = findNodeAtGridOrigin(graph, candidate)\n if (!occupant || getTopLevelSubgraph(graph, occupant) !== ownTopSg) {\n return null // a foreign subgraph (or unexpected gap) blocks this side\n }\n offset += 4\n }\n }\n\n return (\n tryDirection(preferredSign) ?? tryDirection((preferredSign * -1) as 1 | -1)\n )\n}\n\n/**\n * Place any deferred nodes waiting on `rootNode`'s top-level subgraph\n * immediately adjacent to it, before returning control to the root-\n * placement loop — so an unrelated subgraph's root can never claim the slot\n * a deferred sibling needs first (#301). Removes the subgraph's entry from\n * `deferredByTopSg` once handled (a subgraph's deferred nodes attach to\n * whichever of its roots is placed *first*, not every one), and records each\n * placed node in `resolvedDeferred` so the later fallback pass (for deferred\n * nodes whose anchor turns out to be a non-root, only available after the\n * reachable-children traversal) skips them.\n */\nfunction placeDeferredSiblingsNextToRoot(\n graph: AsciiGraph,\n rootNode: AsciiNode,\n deferredByTopSg: Map<AsciiSubgraph, AsciiNode[]>,\n resolvedDeferred: Set<AsciiNode>,\n): void {\n const topSg = getTopLevelSubgraph(graph, rootNode)\n if (!topSg) return\n const waiting = deferredByTopSg.get(topSg)\n if (!waiting) return\n deferredByTopSg.delete(topSg)\n\n const anchor = requireGridCoord(rootNode)\n const axis: 'x' | 'y' =\n getEffectiveDirection(graph, rootNode) === 'LR' ? 'y' : 'x'\n\n for (const deferred of waiting) {\n const nodeDir = getEffectiveDirection(graph, deferred)\n const slot = findSubgraphAdjacentSlot(graph, anchor, topSg, axis, 1)\n reserveSpotInGrid(\n graph,\n graph.nodes[deferred.index]!,\n slot ?? anchor,\n nodeDir,\n )\n resolvedDeferred.add(deferred)\n }\n}\n\n/**\n * Place all currently-reachable, still-unplaced children of already-placed\n * nodes, level by level, mutating `highestPositionPerLevel` as it goes.\n * Multi-pass: iterates until no more progress can be made in a full pass\n * (handles non-topological node order, and simply leaves anything\n * unreachable from an already-placed node untouched).\n */\nfunction placeReachableChildren(\n graph: AsciiGraph,\n highestPositionPerLevel: number[],\n): void {\n let progressed = true\n while (progressed) {\n progressed = false\n\n // Visit already-placed nodes in cross-axis order (left-to-right for TD,\n // top-to-bottom for LR) rather than raw `graph.nodes` declaration\n // order. `highestPositionPerLevel[level]` hands out each level's next\n // free slot in visiting order, so a parent that's actually positioned\n // further along the cross axis must also be visited later — otherwise\n // its children claim an earlier (visually misaligned) slot than a\n // parent positioned before it, decoupling a child's column/row from its\n // own parent's. This matters once sibling placement order can diverge\n // from `graph.nodes` order — e.g. `compareBySiblingSubgraphOrder`\n // above, which places a later-declared sibling subgraph's root before\n // an earlier-declared one's (see issue #444).\n const crossAxisOf = (n: AsciiNode): number =>\n graph.config.graphDirection === 'LR'\n ? (n.gridCoord?.y ?? 0)\n : (n.gridCoord?.x ?? 0)\n const placedNodes = graph.nodes\n .filter((n) => n.gridCoord !== null)\n .sort((a, b) => crossAxisOf(a) - crossAxisOf(b))\n\n for (const node of placedNodes) {\n const gc = node.gridCoord\n if (gc === null) continue // unreachable: `placedNodes` is pre-filtered\n\n // Sort children so siblings that land in different subgraphs get\n // mermaid.js's reversed-declaration-order sibling placement (see\n // compareBySiblingSubgraphOrder) instead of raw edge-declaration\n // order — the sort is stable, so pairs with no subgraph-order\n // preference (the common case) keep their original relative order.\n const children = [...getChildren(graph, node)].sort((x, y) =>\n compareBySiblingSubgraphOrder(graph, x, y),\n )\n for (const child of children) {\n if (child.gridCoord !== null) continue // already placed\n\n // Determine direction for this edge (parent -> child)\n // Use subgraph direction only if both are in the same subgraph with override\n const parentSg = getNodeSubgraph(graph, node)\n const childSg = getNodeSubgraph(graph, child)\n const edgeDir =\n parentSg && parentSg === childSg && parentSg.direction\n ? parentSg.direction\n : graph.config.graphDirection\n\n const childLevel = edgeDir === 'LR' ? gc.x + 4 : gc.y + 4\n\n // Determine position based on direction context\n let highestPosition: number\n if (edgeDir !== graph.config.graphDirection) {\n // Cross-direction: use parent's perpendicular coordinate\n // This keeps children aligned with parent when direction changes\n highestPosition = edgeDir === 'LR' ? gc.y : gc.x\n } else {\n // Same direction: use level tracker\n highestPosition = highestPositionPerLevel[childLevel] ?? 0\n }\n\n const requested: GridCoord =\n edgeDir === 'LR'\n ? { x: childLevel, y: highestPosition }\n : { x: highestPosition, y: childLevel }\n reserveSpotInGrid(graph, graph.nodes[child.index]!, requested, edgeDir)\n\n // Only update level tracker for same-direction placements\n if (edgeDir === graph.config.graphDirection) {\n highestPositionPerLevel[childLevel] = highestPosition + 4\n }\n progressed = true\n }\n }\n }\n}\n\n// ============================================================================\n// Main layout orchestrator\n// ============================================================================\n\n/**\n * createMapping performs the full grid layout:\n * 1. Place root nodes on the grid\n * 2. Place child nodes level by level\n * 3. Compute column widths and row heights\n * 4. Run A* pathfinding for all edges\n * 5. Determine label placement\n * 6. Convert grid coords → drawing coords\n * 7. Generate node box drawings\n * 8. Calculate subgraph bounding boxes\n */\nexport function createMapping(graph: AsciiGraph): void {\n const dir = graph.config.graphDirection\n // A sparse array, not a fixed-size preallocation: level indices grow with\n // chain depth (each level adds 4 to the coordinate), and a long enough\n // chain would silently read past a fixed bound. Reads default missing\n // levels to 0 via `?? 0` below instead.\n const highestPositionPerLevel: number[] = []\n\n // Identify root nodes — nodes that are never the target of any edge.\n //\n // This must be order-independent: a single forward pass over graph.nodes\n // (in Map-insertion / first-mention order) incorrectly treats a node as a\n // root whenever it hasn't been seen as an edge target *yet* at the point\n // it's visited. That misclassifies nodes when a `child -> parent` edge\n // appears in the source *after* a `parent -> grandchild` edge (e.g. `A -->\n // C` is declared before `A1 --> A`), since `A` looks unvisited-as-target\n // when the loop reaches it.\n //\n // Two-pass fix: first collect every node that appears as the target of a\n // non-self-loop edge (a self-loop shouldn't disqualify a node from being a\n // root — see edge-bundling.ts's identical self-loop skip), then anything\n // never targeted is a genuine root, independent of source order.\n const targetedNames = new Set<string>()\n for (const edge of graph.edges) {\n if (edge.from === edge.to) continue // self-loop: doesn't count as \"targeted\"\n targetedNames.add(edge.to.name)\n }\n const targetBasedRoots: AsciiNode[] = graph.nodes.filter(\n (node) => !targetedNames.has(node.name),\n )\n\n // A weakly-connected component that's entirely a cycle (e.g. `A --> B -->\n // C --> A`) correctly has zero target-based roots — every node in it has\n // a real incoming edge — but the grid layout still needs one seed node\n // per component to place anything at all. See\n // addPseudoRootsForUnreachableCycles for why and how.\n const initialRoots = addPseudoRootsForUnreachableCycles(\n graph,\n targetBasedRoots,\n )\n\n // Filter out subgraph nodes that have incoming edges from external sources.\n // This handles the case where subgraph is declared before external nodes\n // (e.g., `subgraph s; A-->B; end; X-->A` - A shouldn't be a root, X should).\n const rootNodes = initialRoots.filter((node) => {\n const nodeSg = getNodeSubgraph(graph, node)\n if (!nodeSg) return true // external nodes: keep as roots\n\n // Check if this subgraph node has incoming edges from outside its subgraph\n for (const edge of graph.edges) {\n if (edge.to === node) {\n const sourceSg = getNodeSubgraph(graph, edge.from)\n if (sourceSg !== nodeSg) {\n return false // has external incoming edge → not a root\n }\n }\n }\n return true\n })\n\n // Defer root nodes that belong to a subgraph which has OTHER members that\n // are (a) not roots themselves and (b) not even reachable from this root\n // via its own edges — i.e. members whose placement is driven by some\n // completely unrelated part of the graph. Placing such a node at the\n // generic root level — shared with roots of unrelated sibling subgraphs —\n // can scatter its own subgraph's members across disjoint regions of the\n // grid, making that subgraph's bounding box balloon out to enclose\n // unrelated sibling content (#90). Instead, anchor these nodes next to\n // their already-placed subgraph siblings once the normal placement pass\n // below has run.\n //\n // A sibling that *is* reachable from this root (e.g. a subgraph root with\n // its own intra-subgraph child) is left alone: the normal traversal below\n // already positions it correctly relative to this root, so deferring would\n // be both unnecessary and wrong.\n const rootNodeSet = new Set(rootNodes)\n const deferredRoots: AsciiNode[] = []\n const placementRoots: AsciiNode[] = []\n for (const node of rootNodes) {\n const topSg = getTopLevelSubgraph(graph, node)\n const hasUnrelatedNonRootSibling =\n topSg !== null &&\n collectSubgraphMembers(topSg).some(\n (m) =>\n m !== node &&\n !rootNodeSet.has(m) &&\n !isReachableViaEdges(graph, node, m),\n )\n if (hasUnrelatedNonRootSibling) {\n deferredRoots.push(node)\n } else {\n placementRoots.push(node)\n }\n }\n\n // Group the non-deferred root nodes by which downstream target they feed\n // into, so a fan-in cluster (e.g. A1, A2 -> A) is placed contiguously\n // instead of interleaving with roots that feed a *different* target (e.g.\n // B1 -> B landing between A1 and A2). Without this, unrelated fan-in\n // bundles can end up on the same grid row and their trunk edges visually\n // cross.\n //\n // Stable: each root's sort key is the position of the *first* root that\n // shares its primary (first) downstream target, so groups appear in the\n // order their target was first seen, and a root with no children (or a\n // target no other root shares) keeps its original relative position.\n const groupedRootNodes = groupRootsByDownstreamTarget(graph, placementRoots)\n\n // Deferred nodes grouped by their top-level subgraph, so the placement\n // loops below can attach each subgraph's deferred members to whichever of\n // its roots gets placed first — before any *other* subgraph's root gets a\n // chance to claim the adjacent slot (#301). Entries are removed as they're\n // resolved; `resolvedDeferred` then lets the later fallback pass skip\n // anything already placed this way.\n const deferredByTopSg = new Map<AsciiSubgraph, AsciiNode[]>()\n for (const node of deferredRoots) {\n const topSg = getTopLevelSubgraph(graph, node)!\n const list = deferredByTopSg.get(topSg)\n if (list) list.push(node)\n else deferredByTopSg.set(topSg, [node])\n }\n const resolvedDeferred = new Set<AsciiNode>()\n\n // In LR mode with both external and subgraph roots, separate them\n // so subgraph roots are placed one level deeper\n let hasExternalRoots = false\n let hasSubgraphRootsWithEdges = false\n for (const node of groupedRootNodes) {\n if (isNodeInAnySubgraph(graph, node)) {\n if (getChildren(graph, node).length > 0) hasSubgraphRootsWithEdges = true\n } else {\n hasExternalRoots = true\n }\n }\n const shouldSeparate =\n dir === 'LR' && hasExternalRoots && hasSubgraphRootsWithEdges\n\n let externalRootNodes: AsciiNode[]\n let subgraphRootNodes: AsciiNode[] = []\n\n if (shouldSeparate) {\n externalRootNodes = groupedRootNodes.filter(\n (n) => !isNodeInAnySubgraph(graph, n),\n )\n subgraphRootNodes = groupedRootNodes.filter((n) =>\n isNodeInAnySubgraph(graph, n),\n )\n } else {\n externalRootNodes = groupedRootNodes\n }\n\n // Place external root nodes\n for (const node of externalRootNodes) {\n const requested: GridCoord =\n dir === 'LR'\n ? { x: 0, y: highestPositionPerLevel[0] ?? 0 }\n : { x: highestPositionPerLevel[0] ?? 0, y: 0 }\n reserveSpotInGrid(graph, graph.nodes[node.index]!, requested)\n highestPositionPerLevel[0] = (highestPositionPerLevel[0] ?? 0) + 4\n placeDeferredSiblingsNextToRoot(\n graph,\n node,\n deferredByTopSg,\n resolvedDeferred,\n )\n }\n\n // Place subgraph root nodes at level 4 (one level in from the edge)\n if (shouldSeparate && subgraphRootNodes.length > 0) {\n const subgraphLevel = 4\n for (const node of subgraphRootNodes) {\n const requested: GridCoord =\n dir === 'LR'\n ? { x: subgraphLevel, y: highestPositionPerLevel[subgraphLevel] ?? 0 }\n : { x: highestPositionPerLevel[subgraphLevel] ?? 0, y: subgraphLevel }\n reserveSpotInGrid(graph, graph.nodes[node.index]!, requested)\n highestPositionPerLevel[subgraphLevel] =\n (highestPositionPerLevel[subgraphLevel] ?? 0) + 4\n placeDeferredSiblingsNextToRoot(\n graph,\n node,\n deferredByTopSg,\n resolvedDeferred,\n )\n }\n }\n\n // Place child nodes level by level (reachable from the roots placed so far).\n placeReachableChildren(graph, highestPositionPerLevel)\n\n // Now place whatever deferred subgraph-orphan roots weren't already\n // resolved above (anchored to a root placed in this same subgraph) —\n // these are the ones whose anchor is itself a non-root, only placed by\n // the reachable-children traversal just above.\n for (const node of deferredRoots) {\n if (resolvedDeferred.has(node)) continue\n const topSg = getTopLevelSubgraph(graph, node)!\n const nodeDir = getEffectiveDirection(graph, node)\n // Type predicate narrows `gridCoord` to non-null on every element, so the\n // loop below can read `.gridCoord.x`/`.y` directly instead of trusting\n // that this filter and the access stay in sync via a bare `!`.\n const placedSiblings = collectSubgraphMembers(topSg).filter(\n (m): m is AsciiNode & { gridCoord: GridCoord } =>\n m !== node && m.gridCoord !== null,\n )\n\n let requested: GridCoord\n if (placedSiblings.length > 0) {\n // Anchor to whichever placed sibling sits at the shallowest level\n // (topmost row for TD, leftmost column for LR).\n let anchor = placedSiblings[0]!\n for (const sibling of placedSiblings) {\n const isShallower =\n nodeDir === 'LR'\n ? sibling.gridCoord.x < anchor.gridCoord.x\n : sibling.gridCoord.y < anchor.gridCoord.y\n if (isShallower) anchor = sibling\n }\n // Slide along the shared axis to find free space next to the anchor,\n // staying clear of unrelated sibling subgraphs (#301) rather than\n // reserveSpotInGrid's subgraph-agnostic blind slide. Falls back to the\n // anchor's own coordinate (triggering the old blind slide inside\n // reserveSpotInGrid below) only when both directions are immediately\n // foreign-blocked.\n const axis: 'x' | 'y' = nodeDir === 'LR' ? 'y' : 'x'\n requested = findSubgraphAdjacentSlot(\n graph,\n anchor.gridCoord,\n topSg,\n axis,\n 1,\n ) ?? { x: anchor.gridCoord.x, y: anchor.gridCoord.y }\n } else {\n // Defensive fallback — shouldn't normally happen, since we only defer\n // a node when it has a sibling that's guaranteed to be placed by the\n // traversal above. Fall back to ordinary root-level placement.\n requested =\n nodeDir === 'LR'\n ? { x: 0, y: highestPositionPerLevel[0] ?? 0 }\n : { x: highestPositionPerLevel[0] ?? 0, y: 0 }\n /* v8 ignore next */\n highestPositionPerLevel[0] = (highestPositionPerLevel[0] ?? 0) + 4\n }\n\n reserveSpotInGrid(graph, graph.nodes[node.index]!, requested, nodeDir)\n }\n\n // A deferred root may itself have children (edges) that couldn't be placed\n // above since it wasn't on the grid yet — give the traversal another pass.\n placeReachableChildren(graph, highestPositionPerLevel)\n\n // Compute column widths and row heights\n for (const node of graph.nodes) {\n setColumnWidth(graph, node)\n }\n\n // Fresh render-wide A* iteration budget for this layout pass. Shared by\n // every getPath call below (both bundled-edge routing and per-edge\n // determinePath), so total pathfinding work for the whole render is\n // hard-bounded regardless of edge count — a per-call iteration cap alone\n // isn't enough for dense fan-in/out graphs with hundreds of edges, since\n // each call is independently allowed to spend up to its own cap. See\n // pathfinder.ts's PathBudget for details.\n graph.pathBudget = createPathBudget()\n\n // Tag true parallel/multi-edges (same source AND target, e.g. two\n // separately-labeled A-->B edges) with a lane index before bundling\n // analysis runs, so determinePath below can route sibling edges past the\n // first through distinct offset lanes instead of all computing the\n // identical center path (see #329).\n assignParallelEdgeLanes(graph)\n\n // Analyze edges for bundling (parallel links like A & B --> C)\n // This groups edges that share sources or targets for cleaner visualization\n graph.bundles = analyzeEdgeBundles(graph)\n\n // Route bundled edges through junction points\n processBundles(graph)\n\n // Route non-bundled edges via A* and determine label positions.\n //\n // `cellStyles` tracks which line style has claimed each cell an edge's\n // path has passed through so far. After routing a non-bundled edge,\n // `rerouteAroundStyleConflicts` checks whether its path crosses a cell\n // already claimed by a *different*-style edge — e.g. a solid edge and a\n // dotted back-edge with no shared source or target, independently\n // finding the same empty column — and if so, re-routes just that edge\n // around the conflicting cell(s). Same-style overlap is left completely\n // untouched: it's how sibling/bundled edges are meant to share a trunk\n // (see edge-cell-styles.ts's module doc).\n //\n // `nodeOnlyGrid` snapshots `graph.grid` right here — after all node\n // placement and bundle routing (which doesn't itself reserve into\n // `graph.grid`; see routeBundledEdges), before any per-edge temporary\n // reroute reservations start mutating the live `graph.grid` below. See\n // `rerouteAroundStyleConflicts`'s doc for why the conflict check needs\n // this frozen copy instead of the live grid.\n const cellStyles = createEdgeCellStyles()\n const cellOwners = createEdgeCellOwners()\n const nodeOnlyGrid = cloneGrid(graph.grid)\n for (const edge of graph.edges) {\n // Skip edges that were already routed as part of a bundle\n if (edge.bundle && edge.path.length > 0) {\n increaseGridSizeForPath(graph, edge.path)\n claimPathCells(nodeOnlyGrid, cellStyles, edge.path, edge.style)\n claimPathOwners(nodeOnlyGrid, cellOwners, edge.path, edge)\n determineLabelLine(graph, edge)\n continue\n }\n\n determinePath(graph, edge)\n rerouteAroundStyleConflicts(\n graph,\n edge,\n cellStyles,\n cellOwners,\n nodeOnlyGrid,\n )\n increaseGridSizeForPath(graph, edge.path)\n claimPathCells(nodeOnlyGrid, cellStyles, edge.path, edge.style)\n claimPathOwners(nodeOnlyGrid, cellOwners, edge.path, edge)\n determineLabelLine(graph, edge)\n }\n\n // Convert grid coords → drawing coords and generate box drawings\n for (const node of graph.nodes) {\n node.drawingCoord = gridToDrawingCoord(graph, requireGridCoord(node))\n node.drawing = drawBox(node, graph)\n }\n\n // Compute subgraph bounding boxes and the resulting drawing offset\n // *before* sizing the canvas. `offsetDrawingForSubgraphs` can set\n // `graph.offsetX`/`offsetY` to a positive shift (needed whenever a\n // subgraph's own border padding would otherwise push its content\n // negative) — every `gridToDrawingCoord` call, including the ones\n // draw.ts makes later for edge lines, adds that offset to its result.\n // Sizing the canvas *before* this offset is known (the previous order)\n // left it too narrow/short by exactly `offsetX`/`offsetY`: node boxes\n // were retroactively shifted to the correct position (see below), but\n // nothing widened the canvas array to match, so any edge line whose\n // drawing coordinate landed in that unreserved margin was silently\n // dropped by `write()`'s out-of-bounds clip — invisible whenever\n // nothing happened to route that close to the diagram's far edge, but\n // a real, reproducible content loss once something did (see #1093,\n // the `Error --> Idle : retry` edge in the \"State: Composite States\"\n // sample, whose rerouted path was the first to reach it).\n calculateSubgraphBoundingBoxes(graph)\n offsetDrawingForSubgraphs(graph)\n\n // Set canvas size, now covering the offset computed above.\n setCanvasSizeToGrid(\n graph.canvas,\n graph.columnWidth,\n graph.rowHeight,\n graph.offsetX,\n graph.offsetY,\n )\n setRoleCanvasSizeToGrid(\n graph.roleCanvas,\n graph.columnWidth,\n graph.rowHeight,\n graph.offsetX,\n graph.offsetY,\n )\n}\n\n// ============================================================================\n// Graph traversal helpers\n// ============================================================================\n\n/** Get all edges originating from a node. */\nfunction getEdgesFromNode(\n graph: AsciiGraph,\n node: AsciiNode,\n): AsciiGraph['edges'] {\n return graph.edges.filter((e) => e.from.name === node.name)\n}\n\n/**\n * Outermost-to-innermost chain of subgraphs directly containing `node`\n * (empty if the node isn't in any subgraph).\n */\nfunction subgraphChain(graph: AsciiGraph, node: AsciiNode): AsciiSubgraph[] {\n const innermost = getNodeSubgraph(graph, node)\n const chain: AsciiSubgraph[] = []\n let current: AsciiSubgraph | null = innermost\n while (current !== null) {\n chain.unshift(current)\n current = current.parent\n }\n return chain\n}\n\n/**\n * Order two nodes the way real mermaid.js orders the sibling subgraphs they\n * (transitively) belong to, when they diverge into *different* subgraphs\n * under a shared subgraph ancestor (or both at the top level) — otherwise 0\n * (no preference; the caller's sort is stable, so original edge-declaration\n * order is preserved).\n *\n * Verified against mermaid@11.17.2's bundled flowDb.getData()\n * (node_modules/mermaid/dist/mermaid.min.js): it builds the layout node list\n * by iterating the parsed `subGraphs` array *backwards* to emit cluster\n * nodes, so sibling subgraphs end up in reversed declaration order in the\n * graph the layout engine actually sees — see the matching comment in\n * `../layout-engine/to-elk.ts`'s `mermaidToElk`, and issue #444. The ASCII\n * grid has no compound-node concept for the layout engine to order — node\n * position here is driven entirely by BFS descent from edges (see\n * `placeReachableChildren`) — so this comparator recovers the same visual\n * left-right order by reordering sibling children at the point they'd\n * otherwise be placed in raw edge-declaration order.\n */\nfunction compareBySiblingSubgraphOrder(\n graph: AsciiGraph,\n a: AsciiNode,\n b: AsciiNode,\n): number {\n const chainA = subgraphChain(graph, a)\n const chainB = subgraphChain(graph, b)\n let i = 0\n while (i < chainA.length && i < chainB.length && chainA[i] === chainB[i]) {\n i++\n }\n const sgA = chainA[i]\n const sgB = chainB[i]\n if (!sgA || !sgB || sgA === sgB) return 0\n // Reversed declaration order: later-declared sibling sorts first. Sibling\n // relative declaration order is recovered from each one's position in the\n // flat `graph.subgraphs` list, which preserves source order (see\n // `convertSubgraph`'s pre-order-DFS push in converter.ts).\n return graph.subgraphs.indexOf(sgB) - graph.subgraphs.indexOf(sgA)\n}\n\n/** Get all direct children of a node (targets of outgoing edges). */\nfunction getChildren(graph: AsciiGraph, node: AsciiNode): AsciiNode[] {\n return getEdgesFromNode(graph, node).map((e) => e.to)\n}\n","// ============================================================================\n// ASCII renderer — direction system and edge path determination\n//\n// Ported from AlexanderGrooff/mermaid-ascii cmd/direction.go + cmd/mapping_edge.go.\n// Handles direction constants, edge attachment point selection,\n// and dual-path comparison for optimal edge routing.\n// ============================================================================\n\nimport type {\n GridCoord,\n Direction,\n AsciiEdge,\n AsciiGraph,\n AsciiNode,\n} from './types.ts'\nimport {\n Up,\n Down,\n Left,\n Right,\n UpperRight,\n UpperLeft,\n LowerRight,\n LowerLeft,\n Middle,\n gridCoordDirection,\n dirEquals,\n requireCardinalDirection,\n} from './types.ts'\nimport { routeEdge, mergePath } from './pathfinder.ts'\nimport { getNodeSubgraph, requireGridCoord } from './grid.ts'\nimport { displayWidth } from './display-width.ts'\nimport { isOccupied, pathCells } from './grid-occupancy.ts'\n\n// Re-exported for existing consumers (draw-arrows.ts, draw-lines.ts,\n// draw-bundles.ts, shapes/*.ts) that import dirEquals from this module —\n// the implementation itself lives in types.ts now, alongside the other\n// coordinate-equality helpers (gridCoordEquals, drawingCoordEquals).\nexport { dirEquals }\n\n// ============================================================================\n// Direction utilities\n// ============================================================================\n\nexport function getOpposite(d: Direction): Direction {\n if (d === Up) return Down\n if (d === Down) return Up\n if (d === Left) return Right\n if (d === Right) return Left\n if (d === UpperRight) return LowerLeft\n if (d === UpperLeft) return LowerRight\n if (d === LowerRight) return UpperLeft\n if (d === LowerLeft) return UpperRight\n return Middle\n}\n\n/**\n * Determine 8-way direction from one coordinate to another.\n * Uses the coordinate difference to pick one of 8 cardinal/ordinal directions.\n */\nexport function determineDirection(\n from: { x: number; y: number },\n to: { x: number; y: number },\n): Direction {\n if (from.x === to.x) {\n return from.y < to.y ? Down : Up\n } else if (from.y === to.y) {\n return from.x < to.x ? Right : Left\n } else if (from.x < to.x) {\n return from.y < to.y ? LowerRight : UpperRight\n } else {\n return from.y < to.y ? LowerLeft : UpperLeft\n }\n}\n\n// ============================================================================\n// Start/end direction selection for edges\n// ============================================================================\n\n/** Self-reference routing (node points to itself). */\nfunction selfReferenceDirection(\n graphDirection: string,\n): [Direction, Direction, Direction, Direction] {\n if (graphDirection === 'LR') return [Right, Down, Down, Right]\n return [Down, Right, Right, Down]\n}\n\n/**\n * Determine preferred and alternative start/end directions for an edge.\n * Returns [preferredStart, preferredEnd, alternativeStart, alternativeEnd].\n *\n * The edge routing tries both pairs and picks the shorter path.\n * Direction selection depends on relative node positions and graph direction (LR vs TD).\n */\nexport function determineStartAndEndDir(\n edge: AsciiEdge,\n graphDirection: string,\n): [Direction, Direction, Direction, Direction] {\n if (edge.from === edge.to) return selfReferenceDirection(graphDirection)\n\n const d = determineDirection(\n requireGridCoord(edge.from),\n requireGridCoord(edge.to),\n )\n\n let preferredDir: Direction\n let preferredOppositeDir: Direction\n let alternativeDir: Direction\n let alternativeOppositeDir: Direction\n\n const isBackwards =\n graphDirection === 'LR'\n ? dirEquals(d, Left) || dirEquals(d, UpperLeft) || dirEquals(d, LowerLeft)\n : dirEquals(d, Up) || dirEquals(d, UpperLeft) || dirEquals(d, UpperRight)\n\n if (dirEquals(d, LowerRight)) {\n if (graphDirection === 'LR') {\n preferredDir = Down\n preferredOppositeDir = Left\n alternativeDir = Right\n alternativeOppositeDir = Up\n } else {\n preferredDir = Right\n preferredOppositeDir = Up\n alternativeDir = Down\n alternativeOppositeDir = Left\n }\n } else if (dirEquals(d, UpperRight)) {\n if (graphDirection === 'LR') {\n preferredDir = Up\n preferredOppositeDir = Left\n alternativeDir = Right\n alternativeOppositeDir = Down\n } else {\n preferredDir = Right\n preferredOppositeDir = Down\n alternativeDir = Up\n alternativeOppositeDir = Left\n }\n } else if (dirEquals(d, LowerLeft)) {\n if (graphDirection === 'LR') {\n preferredDir = Down\n preferredOppositeDir = Down\n alternativeDir = Left\n alternativeOppositeDir = Up\n } else {\n preferredDir = Left\n preferredOppositeDir = Up\n alternativeDir = Down\n alternativeOppositeDir = Right\n }\n } else if (dirEquals(d, UpperLeft)) {\n if (graphDirection === 'LR') {\n preferredDir = Down\n preferredOppositeDir = Down\n alternativeDir = Left\n alternativeOppositeDir = Down\n } else {\n preferredDir = Right\n preferredOppositeDir = Right\n alternativeDir = Up\n alternativeOppositeDir = Right\n }\n } else if (isBackwards) {\n if (graphDirection === 'LR' && dirEquals(d, Left)) {\n preferredDir = Down\n preferredOppositeDir = Down\n alternativeDir = Left\n alternativeOppositeDir = Right\n } else if (graphDirection === 'TD' && dirEquals(d, Up)) {\n preferredDir = Right\n preferredOppositeDir = Right\n alternativeDir = Up\n alternativeOppositeDir = Down\n } else {\n preferredDir = d\n preferredOppositeDir = getOpposite(d)\n alternativeDir = d\n alternativeOppositeDir = getOpposite(d)\n }\n } else {\n // Default: go in the natural direction\n preferredDir = d\n preferredOppositeDir = getOpposite(d)\n alternativeDir = d\n alternativeOppositeDir = getOpposite(d)\n }\n\n return [\n preferredDir,\n preferredOppositeDir,\n alternativeDir,\n alternativeOppositeDir,\n ]\n}\n\n// ============================================================================\n// Parallel edge (multi-edge) lane assignment\n// ============================================================================\n\n/**\n * Grid rows (for a horizontal departure) or columns (for a vertical\n * departure) between successive parallel-edge lanes. See\n * `buildParallelLanePath` below.\n */\nconst PARALLEL_LANE_STEP = 2\n\n/**\n * Bound on how far `buildParallelLanePath` will push a lane outward\n * (1 grid unit per attempt) to escape a cell occupied by some *other*\n * node's reserved block. Generous enough for any realistic diagram (a\n * stack of many dozens of sibling rows/columns), while still bounding the\n * search — see that function's doc for why an unbounded search isn't safe\n * to assume will terminate quickly on a pathological graph.\n */\nconst MAX_LANE_OFFSET_SEARCH = 200\n\n/** Whether `cell` falls inside `node`'s own reserved 3x3 block. */\nfunction isCellInNodeBlock(node: AsciiNode, cell: GridCoord): boolean {\n const gc = node.gridCoord\n if (!gc) return false\n return (\n cell.x >= gc.x && cell.x <= gc.x + 2 && cell.y >= gc.y && cell.y <= gc.y + 2\n )\n}\n\n/**\n * Every cell in `cells` strictly between its own first and last entry is\n * free of any node's reserved block — except a block listed in `ownNodes`,\n * which is allowed. The first/last cells are excluded outright, because\n * callers use this on lines/paths whose two endpoints are expected to\n * touch a node border (where an edge departs/arrives) or an already-\n * validated waypoint; only cells genuinely *passed through* matter here.\n *\n * Two callers, two different `ownNodes`:\n * - `determineLabelLine` calls this with no exemptions at all: label text\n * drawn across *any* node's border — including the edge's own source or\n * target — corrupts that node's box-drawing characters (see #329's\n * \"Second┬Arrow\" regression), so nothing is exempt there.\n * - `buildParallelLanePath` calls this with `[edge.from, edge.to]`\n * exempted: an edge's line is *expected* to graze its own node's other\n * border cells on the way to its actual attachment point — that's\n * exactly how the ├/┤/┬/┴ box connector glyphs get chosen elsewhere in\n * this renderer (see edge-cell-styles.ts's module doc for the same\n * \"an edge's own node border is a legitimate shared cell\" reasoning).\n * Only a *different*, unrelated node's block is a genuine collision\n * there.\n */\nfunction interiorCellsClearOfNodes(\n graph: AsciiGraph,\n cells: readonly GridCoord[],\n ownNodes: readonly AsciiNode[] = [],\n): boolean {\n for (let i = 1; i < cells.length - 1; i++) {\n const cell = cells[i]!\n if (!isOccupied(graph.grid, cell)) continue\n if (ownNodes.some((n) => isCellInNodeBlock(n, cell))) continue\n return false\n }\n return true\n}\n\n/**\n * Whether `edge`'s two nodes are laid out side by side on the same grid\n * row — the layout in which every edge between them, in either direction,\n * routes through one *horizontal* channel and so shares one label row.\n *\n * This is the discriminator for whether a reverse-direction sibling needs a\n * lane of its own; see `parallelGroupKey` below for why.\n *\n * Deliberately symmetric in `from`/`to` (it only compares the two grid\n * coordinates), so an edge and its reciprocal partner always agree on the\n * answer and therefore on their group key. A node with no grid coordinate\n * yet answers `false`, which just leaves that edge on the conservative\n * ordered-pair grouping.\n */\nfunction sharesHorizontalChannel(edge: AsciiEdge): boolean {\n const from = edge.from.gridCoord\n const to = edge.to.gridCoord\n if (!from || !to) return false\n return from.y === to.y && from.x !== to.x\n}\n\n/**\n * Grouping key for `assignParallelEdgeLanes` below: two edges get separate\n * lanes exactly when this returns the same key for both.\n *\n * The base case is the *ordered* pair (`A -->|One| B` twice, #329) — two\n * edges in the same direction always compute the identical center path,\n * whatever the layout.\n *\n * A *reverse*-direction sibling (`B --> A` alongside `A --> B`, #629) is a\n * different edge with its own natural route, so it collides in one specific\n * layout: when the two nodes sit side by side on the same grid row, both\n * edges route through the same horizontal channel, which puts both labels\n * on the same row and lets one edge's line overwrite the other's arrowhead.\n * That is the case this key folds together, by ordering the two node names.\n *\n * When the pair is stacked vertically instead, `drawTextOnLine`\n * (draw-arrows.ts) already de-collides the two labels by pulling each into\n * its own half of the shared vertical segment — a mechanism built for\n * exactly this shape (#530) that only applies to a vertical segment.\n * Grouping those two edges would route one of them through a lane and take\n * that mechanism out of play, so they stay in separate ordered groups.\n *\n * `JSON.stringify` over the name pair rather than a delimiter-joined\n * string: a node name may itself contain the delimiter, and `[\"A B\", \"C\"]`\n * must not collide with `[\"A\", \"B C\"]`.\n */\nfunction parallelGroupKey(edge: AsciiEdge): string {\n const from = edge.from.name\n const to = edge.to.name\n if (sharesHorizontalChannel(edge) && to < from) {\n return JSON.stringify([to, from])\n }\n return JSON.stringify([from, to])\n}\n\n/**\n * Group edges that connect the same pair of nodes — two edges in the same\n * direction (`A -->|One| B` and `A -->|Two| B`), or, when the two nodes are\n * laid out side by side, an edge and its reverse-direction partner\n * (`A -->|req| B` and `B -->|res| A`) — and tag each with its 0-based\n * position in the group plus the group's size, via `edge.parallelLane`.\n * `parallelGroupKey` above defines exactly which pairs group together, and\n * why a vertically-stacked reciprocal pair is deliberately left out.\n *\n * `determinePath` below reads this to route every edge past the first\n * through a distinct offset lane instead of the shared center path they\n * would otherwise all compute independently (see #329: identical paths\n * meant identically-positioned labels drawn on top of each other,\n * corrupting each other's text — and #629, the same defect between an edge\n * and its reverse-direction partner).\n *\n * Self-loops are excluded (`edge.from === edge.to`): they're routed and\n * drawn as a dedicated loop shape, not a lane-offset line between two\n * distinct nodes, so they're out of scope here.\n *\n * Must run before `analyzeEdgeBundles` (edge-bundling.ts): that module's\n * `canBundle` also refuses to fold a lane-assigned group into one shared\n * fan-in/fan-out trunk (see its own doc comment), but the two checks are\n * independent — this function is the one that actually assigns lanes for\n * `determinePath` to use.\n *\n * Must also run after node placement: `sharesHorizontalChannel` reads both\n * nodes' grid coordinates (grid.ts's call site satisfies this — it runs\n * this immediately before bundling analysis, well after layout).\n */\nexport function assignParallelEdgeLanes(graph: AsciiGraph): void {\n const groups = new Map<string, AsciiEdge[]>()\n for (const edge of graph.edges) {\n if (edge.from === edge.to) continue // self-loop: not in scope here\n const key = parallelGroupKey(edge)\n const existing = groups.get(key)\n if (existing) existing.push(edge)\n else groups.set(key, [edge])\n }\n\n for (const group of groups.values()) {\n if (group.length < 2) continue\n const usedOffsets = new Set<number>()\n for (let i = 0; i < group.length; i++) {\n group[i]!.parallelLane = { index: i, total: group.length, usedOffsets }\n }\n }\n}\n\n/**\n * Build an offset-lane path for a non-first edge in a parallel-edge group:\n * leave the source node at the same attachment point every sibling in the\n * group shares (consistent with how two edges are already allowed to share\n * a node's own border cell — see edge-cell-styles.ts's module doc), travel\n * an offset lane, then return to the target's attachment point. Two\n * candidate shapes are tried per offset, in order:\n *\n * - **\"wide\"**: descend/cross straight down `fromAttach`'s own column/row,\n * travel the *full* node-to-node span at the offset lane, then rise back\n * up `toAttach`'s own column/row. This is what `determineLabelLine`\n * wants — a segment already wide enough to host the label outright —\n * and is correct whenever nothing else occupies that column/row at the\n * chosen offset.\n * - **\"gutter\"**: like \"wide\", but the two short jogs near each endpoint\n * step one extra grid unit into the gutter column/row immediately\n * before/after that node — reserved by every node's own\n * `setColumnWidth` padding (`columnWidth.set(gc.x - 1, ...)` in\n * grid.ts) and never claimed by `placeBlock` for *any* node, at *any*\n * row/column, by construction of the grid-level spacing scheme (`x`/`y`\n * levels are always `NODE_BLOCK_SIZE` + 1 apart). \"wide\" fails exactly\n * when a second, independent `X --> Y` chain placed directly below\n * `A`/`B` (a completely ordinary \"second flow stacked under the first\"\n * layout) shares `A`'s/`B`'s exact border column, so \"wide\"'s straight\n * descent runs right through `X`'s/`Y`'s own reserved block — \"gutter\"\n * detours around exactly that, at the cost of a narrower (often single-\n * column) middle segment; still a legitimate label location, just via\n * determineLabelLine's node-avoidance fallback tier rather than its\n * width-based one. (Routing each jog through `routeEdge`/A* was tried\n * before landing on this: A* has no notion of \"this occupied cell is my\n * *own* node's, so it's fine to graze\", so it detoured *away* from the\n * source's/target's own border on every lane, producing a self-crossing\n * zigzag instead of a clean jog.)\n *\n * Each candidate is validated via `interiorCellsClearOfNodes` (the edge's\n * own two nodes exempted — grazing them is expected, exactly like the\n * `edge-cell-styles.ts` \"own node border is a legitimate shared cell\"\n * reasoning) before being accepted. If neither shape is clear at the\n * current offset, it's pushed out one more grid unit and both are retried\n * — bounded by `MAX_LANE_OFFSET_SEARCH` — mirroring `findNonNodeColumn`'s\n * \"search outward for a non-node column\" pattern above, generalized to a\n * whole path.\n *\n * The offset always grows in one direction (never negative) so it can\n * never land on a grid row/column that already exists above/left of the\n * node block — `gridToDrawingCoord`'s row/column summation only handles\n * coordinates from 0 upward, and `increaseGridSizeForPath` (grid.ts) is\n * what actually grows the canvas for a lane's newly-introduced row/column.\n *\n * `preferredDir`/`preferredOppositeDir` are whatever `determineStartAndEndDir`\n * computed for this edge (identical for every edge in the group, since they\n * share the same from/to nodes) — always one of the four cardinal\n * directions. A horizontal departure (Left/Right) offsets by row; a\n * vertical departure (Up/Down) offsets by column.\n *\n * If every offset within the search bound is still blocked for both shapes\n * (a genuinely pathological, densely-stacked graph), the last attempted\n * path is returned anyway rather than failing the whole render — the same\n * graceful-degradation-over-a-hard-failure choice `findNonNodeColumn` and\n * `determinePath`'s Case-4 direct fallback already make elsewhere in this\n * file.\n */\n/** A lane path plus the segment `determinePath` should use directly as\n * this edge's label line — see `buildParallelLanePath`'s doc for why this\n * is computed explicitly here rather than left to `determineLabelLine`'s\n * general (width-based) heuristic. */\ninterface ParallelLaneRoute {\n path: GridCoord[]\n labelSegment: [GridCoord, GridCoord]\n}\n\nfunction buildParallelLanePath(\n graph: AsciiGraph,\n edge: AsciiEdge,\n preferredDir: Direction,\n preferredOppositeDir: Direction,\n laneIndex: number,\n): ParallelLaneRoute {\n const fromAttach = gridCoordDirection(\n requireGridCoord(edge.from),\n preferredDir,\n )\n const toAttach = gridCoordDirection(\n requireGridCoord(edge.to),\n preferredOppositeDir,\n )\n const horizontalDeparture =\n dirEquals(preferredDir, Left) || dirEquals(preferredDir, Right)\n const ownNodes = [edge.from, edge.to]\n // Shared, by reference, across every edge in this parallel group (see\n // types.ts's parallelLane doc) — records every offset a sibling lane has\n // already committed to, so two lanes that each have to detour around the\n // same obstacle can't converge on the identical offset (and therefore\n // the identical path — the exact bug this whole mechanism exists to\n // prevent, just between two lanes instead of the original center path).\n const usedOffsets = edge.parallelLane!.usedOffsets\n\n // One grid unit further from each node, in the direction the edge\n // already departs/arrives — lands in the permanently node-free gutter\n // (see doc above) rather than the node's own border column/row.\n const fromStep = dirEquals(preferredDir, Right)\n ? 1\n : dirEquals(preferredDir, Left)\n ? -1\n : dirEquals(preferredDir, Down)\n ? 1\n : -1\n const toStep = dirEquals(preferredOppositeDir, Right)\n ? 1\n : dirEquals(preferredOppositeDir, Left)\n ? -1\n : dirEquals(preferredOppositeDir, Down)\n ? 1\n : -1\n const fromGutter = horizontalDeparture\n ? { x: fromAttach.x + fromStep, y: fromAttach.y }\n : { x: fromAttach.x, y: fromAttach.y + fromStep }\n const toGutter = horizontalDeparture\n ? { x: toAttach.x + toStep, y: toAttach.y }\n : { x: toAttach.x, y: toAttach.y + toStep }\n\n // Always-valid fallback (ignores occupancy entirely) in case every\n // offset attempt below fails outright — mirrors determinePath's own\n // Case-4 direct fallback: an edge must always end up with *some* path so\n // its arrowhead can still be drawn. Its label segment is the same\n // degenerate zero-length line determineLabelLine itself falls back to\n // for a path with no real segments — see this function's caller.\n let path: GridCoord[] = [fromAttach, toAttach]\n let labelSegment: [GridCoord, GridCoord] = [fromAttach, toAttach]\n\n const startingOffset = PARALLEL_LANE_STEP * laneIndex\n for (let step = 0; step < MAX_LANE_OFFSET_SEARCH; step++) {\n const offset = startingOffset + step\n if (usedOffsets.has(offset)) continue\n const laneMain = fromAttach.y + offset\n const laneCross = fromAttach.x + offset\n\n // Candidate A (\"wide\"): travel the offset lane across the *full*\n // node-to-node span (fromAttach.x..toAttach.x, or the vertical\n // equivalent). Its middle segment is always genuinely wide (it spans\n // two distinct nodes' attachment columns/rows), so it's used directly\n // as the label segment — no need to search for it afterward. Tried\n // first because it's clearly the better-looking result whenever safe.\n const wideLabelSegment: [GridCoord, GridCoord] = horizontalDeparture\n ? [\n { x: fromAttach.x, y: laneMain },\n { x: toAttach.x, y: laneMain },\n ]\n : [\n { x: laneCross, y: fromAttach.y },\n { x: laneCross, y: toAttach.y },\n ]\n const wideCandidate = mergePath([fromAttach, ...wideLabelSegment, toAttach])\n if (interiorCellsClearOfNodes(graph, pathCells(wideCandidate), ownNodes)) {\n usedOffsets.add(offset)\n return { path: wideCandidate, labelSegment: wideLabelSegment }\n }\n path = wideCandidate\n labelSegment = wideLabelSegment\n\n // Candidate B (\"gutter\"): candidate A travels along fromAttach's/\n // toAttach's own border column/row for the vertical/horizontal jog,\n // which can run straight through an unrelated node sharing that same\n // column/row (see this function's doc) — candidate A's occupancy\n // check just caught exactly that. Detour those two short jogs through\n // the permanently node-free gutter instead.\n //\n // `fromGutter`/`toGutter` coincide exactly whenever the two nodes sit\n // on directly adjacent grid levels (the ordinary case for \"two edges\n // between the same pair\") — there being only one gutter column/row\n // between them. When that happens the \"travel\" segment collapses to a\n // single point, which every sibling lane's gutter candidate would\n // share identically — reintroducing #329's own bug, just between two\n // *lanes* instead of the original center path (this was caught while\n // building this fix: see ascii-parallel-edges-329.test.ts's \"no lane\n // path passes through a cell owned by the unrelated X/Y chain\" test).\n // Use the vertical/horizontal \"spike\" descent instead in that case — a\n // zero-length point *at* this lane's own offset depth, not a segment\n // spanning from the attach row/column down to it: drawTextOnLine\n // centers text at a line's *midpoint*, so a segment running from (say)\n // row 1 down to row 7 centers its label at row 4, not row 7 — two\n // lanes at different depths (rows 5 and 7, say) can still produce the\n // *same* midpoint as some other lane's own row, corrupting labels all\n // over again (also caught by the same test named above). A point's\n // min/max/midpoint are all itself, so the label always lands exactly\n // at this lane's own unique depth — narrow (its column/row is widened\n // below like any single-segment label host), but unique per lane.\n const gutterTravelHasWidth = horizontalDeparture\n ? fromGutter.x !== toGutter.x\n : fromGutter.y !== toGutter.y\n const gutterLabelSegment: [GridCoord, GridCoord] = horizontalDeparture\n ? gutterTravelHasWidth\n ? [\n { x: fromGutter.x, y: laneMain },\n { x: toGutter.x, y: laneMain },\n ]\n : [\n { x: fromGutter.x, y: laneMain },\n { x: fromGutter.x, y: laneMain },\n ]\n : gutterTravelHasWidth\n ? [\n { x: laneCross, y: fromGutter.y },\n { x: laneCross, y: toGutter.y },\n ]\n : [\n { x: laneCross, y: fromGutter.y },\n { x: laneCross, y: fromGutter.y },\n ]\n const gutterCandidate = horizontalDeparture\n ? mergePath([\n fromAttach,\n fromGutter,\n { x: fromGutter.x, y: laneMain },\n { x: toGutter.x, y: laneMain },\n toGutter,\n toAttach,\n ])\n : mergePath([\n fromAttach,\n fromGutter,\n { x: laneCross, y: fromGutter.y },\n { x: laneCross, y: toGutter.y },\n toGutter,\n toAttach,\n ])\n if (\n interiorCellsClearOfNodes(graph, pathCells(gutterCandidate), ownNodes)\n ) {\n usedOffsets.add(offset)\n return { path: gutterCandidate, labelSegment: gutterLabelSegment }\n }\n // Defensive — shouldn't normally happen. The gutter candidate's\n // vertical/horizontal travel runs entirely through the permanently\n // node-free gutter column/row (see this function's doc: every node's\n // own column-width padding reserves it, and placeBlock never claims\n // it for any node), so gutterCandidate failing this check would mean\n // that structural guarantee didn't hold for this graph's layout.\n // Recorded anyway so the loop's very last iteration still has a\n // best-effort path/labelSegment to fall back to.\n /* v8 ignore next */\n path = gutterCandidate\n /* v8 ignore next */\n labelSegment = gutterLabelSegment\n }\n // Defensive — shouldn't normally happen, for the same reason as above:\n // reaching here means every offset up to MAX_LANE_OFFSET_SEARCH failed\n // both candidates, which requires the gutter's structural guarantee to\n // not hold. Mirrors determinePath's own Case-4 direct fallback and\n // findNonNodeColumn's \"every column occupied\" fallback elsewhere in this\n // file: graceful degradation over a hard failure, not a case expected to\n // be exercised by a realistic graph.\n /* v8 ignore next */\n return { path, labelSegment }\n}\n\n// ============================================================================\n// Edge path determination\n// ============================================================================\n\n/**\n * Determine the path for an edge by trying two candidate routes (preferred + alternative)\n * and picking the shorter one. Sets edge.path, edge.startDir, edge.endDir.\n *\n * When both A* paths fail (common for edges crossing subgraph boundaries), falls back\n * to a direct path using the start/end points. This ensures edges always have a path\n * for arrowhead rendering.\n *\n * Uses the effective direction for edge routing, respecting subgraph direction overrides\n * when both source and target are in the same subgraph.\n */\nexport function determinePath(graph: AsciiGraph, edge: AsciiEdge): void {\n // Determine effective direction for this edge\n // If both nodes are in the same subgraph with a direction override, use it\n // Otherwise, use the graph's direction (not source's effective direction)\n const sourceSg = getNodeSubgraph(graph, edge.from)\n const targetSg = getNodeSubgraph(graph, edge.to)\n const effectiveDir =\n sourceSg && sourceSg === targetSg && sourceSg.direction\n ? sourceSg.direction\n : graph.config.graphDirection\n\n const [\n preferredDir,\n preferredOppositeDir,\n alternativeDir,\n alternativeOppositeDir,\n ] = determineStartAndEndDir(edge, effectiveDir)\n\n // Edges after the first in a true-parallel (same source AND target) group\n // skip the normal preferred/alternative search entirely: that search\n // would just find the identical center path every sibling edge shares,\n // which is the root cause of #329. Route through an offset lane instead\n // so this edge's path — and its label — never overlaps a sibling's.\n //\n // The label line is set directly here (via applyLabelLine), not left for\n // determineLabelLine's later call to work out: buildParallelLanePath\n // already knows exactly which segment of the lane it just built is the\n // correct, sibling-distinct place for the label (see its own doc for why\n // determineLabelLine's general width-based heuristic can't reliably\n // re-derive that from the finished path alone). determineLabelLine\n // itself skips lane edges for exactly this reason — see its own guard.\n if (edge.parallelLane && edge.parallelLane.index > 0) {\n // A reverse-direction sibling (`B --> A` alongside `A --> B`, #629) runs\n // \"backwards\" relative to the graph direction, so determineStartAndEndDir\n // hands back a degenerate *same-side* pair — Down/Down in LR, Right/Right\n // in TD — describing a U-shaped detour that leaves and re-enters on the\n // same face. buildParallelLanePath's offset math can't express that\n // shape: it picks its offset axis from whether the *departure* is\n // horizontal, which for a Down/Down U-shape is exactly inverted (it\n // offsets by column when the lane actually travels horizontally and needs\n // a row offset), landing the lane on the nodes' own border rows. Use the\n // alternative (straight-through) pair for such an edge instead, making a\n // reverse-direction lane the mirror image of a forward one — the shape\n // #329's fix already produces for a same-direction sibling. A forward\n // edge's preferred and alternative pairs are identical (see\n // determineStartAndEndDir's default branch), so this never changes how an\n // existing same-direction group routes.\n const degenerateFace = dirEquals(preferredDir, preferredOppositeDir)\n const laneDir = degenerateFace ? alternativeDir : preferredDir\n const laneOppositeDir = degenerateFace\n ? alternativeOppositeDir\n : preferredOppositeDir\n edge.startDir = laneDir\n edge.endDir = laneOppositeDir\n const route = buildParallelLanePath(\n graph,\n edge,\n laneDir,\n laneOppositeDir,\n edge.parallelLane.index,\n )\n edge.path = route.path\n if (edge.text.length > 0) {\n applyLabelLine(graph, edge, route.labelSegment, displayWidth(edge.text))\n }\n return\n }\n\n // Try preferred path — routeEdge tries an unobstructed direct L-shape\n // before falling back to A* (see routeEdge / tryDirectPath in\n // pathfinder.ts). determineStartAndEndDir only ever produces one of the\n // four pure cardinal directions (see its implementation above), but that\n // invariant lives in this function's control flow, not in Direction's\n // type, so it's narrowed explicitly at the routeEdge boundary rather than\n // trusted silently across the module.\n const prefFrom = gridCoordDirection(requireGridCoord(edge.from), preferredDir)\n const prefTo = gridCoordDirection(\n requireGridCoord(edge.to),\n preferredOppositeDir,\n )\n const preferredPath = routeEdge(\n graph,\n prefFrom,\n prefTo,\n requireCardinalDirection(preferredDir),\n )\n\n // Try alternative path\n const altFrom = gridCoordDirection(\n requireGridCoord(edge.from),\n alternativeDir,\n )\n const altTo = gridCoordDirection(\n requireGridCoord(edge.to),\n alternativeOppositeDir,\n )\n const alternativePath = routeEdge(\n graph,\n altFrom,\n altTo,\n requireCardinalDirection(alternativeDir),\n )\n\n // Case 1: Both paths found — pick the shorter one (routeEdge already merged each)\n if (preferredPath !== null && alternativePath !== null) {\n if (preferredPath.length <= alternativePath.length) {\n edge.startDir = preferredDir\n edge.endDir = preferredOppositeDir\n edge.path = preferredPath\n } else {\n edge.startDir = alternativeDir\n edge.endDir = alternativeOppositeDir\n edge.path = alternativePath\n }\n return\n }\n\n // Case 2: Only preferred path found\n if (preferredPath !== null) {\n edge.startDir = preferredDir\n edge.endDir = preferredOppositeDir\n edge.path = preferredPath\n return\n }\n\n // Case 3: Only alternative path found\n if (alternativePath !== null) {\n edge.startDir = alternativeDir\n edge.endDir = alternativeOppositeDir\n edge.path = alternativePath\n return\n }\n\n // Case 4: Both paths failed — create a direct fallback path\n // This happens for edges crossing subgraph boundaries where A* can't find\n // a clear route. We create a direct path from source to target exit points\n // so arrowheads can still be rendered correctly.\n //\n // `[prefFrom, prefTo]` alone is a single non-axis-aligned (diagonal) grid\n // segment whenever the two points differ on both axes. draw-lines.ts's\n // drawLine never draws that as a straight diagonal — it draws it as an L,\n // horizontal-first (see expandDiagonalSegments' own doc, and #418/#1083,\n // which already had to special-case this same diagonal-vs-drawn-L gap for\n // label placement and arrowhead direction respectively). Everything else\n // that reasons about `edge.path` — in particular edge-cell-styles.ts's\n // cross-style overlap detection, which walks `edge.path` via `pathCells`\n // assuming it already matches the drawn geometry — implicitly assumes a\n // path is axis-aligned. Left as a raw diagonal pair, `pathCells` walks a\n // Bresenham diagonal that visits a completely different set of cells than\n // the L drawLine actually draws, so two Case-4 edges (or a Case-4 edge and\n // an ordinary routed one) whose *drawn* horizontal legs genuinely overlap\n // are never flagged as a conflict, and the later-drawn edge's style\n // silently overwrites the earlier one's on that shared row (#1067, \"All\n // Edge Styles\": the dotted edge's horizontal leg rendered as the thick\n // edge's heavy ━ instead of dashed). Expanding here — once, at the\n // source — keeps every downstream consumer (drawing, label placement,\n // conflict detection, corner glyphs) working from the same axis-aligned\n // path instead of each needing its own diagonal-awareness.\n edge.startDir = preferredDir\n edge.endDir = preferredOppositeDir\n edge.path = expandDiagonalSegments([prefFrom, prefTo])\n}\n\n/** Check whether grid column `x` falls inside any node's reserved 3-column block. */\nfunction isNodeOccupiedColumn(graph: AsciiGraph, x: number): boolean {\n for (const node of graph.nodes) {\n const gc = node.gridCoord\n if (gc && x >= gc.x && x <= gc.x + 2) return true\n }\n return false\n}\n\n/**\n * Find the column closest to `ideal` (within [minX, maxX]) that isn't part\n * of any node's reserved block, searching outward in both directions. Falls\n * back to `ideal` itself if every column in range is node-occupied (e.g. a\n * very short segment squeezed between two nodes) — there's no better option.\n */\nfunction findNonNodeColumn(\n graph: AsciiGraph,\n minX: number,\n maxX: number,\n ideal: number,\n): number {\n if (!isNodeOccupiedColumn(graph, ideal)) return ideal\n for (let d = 1; d <= maxX - minX; d++) {\n const right = ideal + d\n if (right <= maxX && !isNodeOccupiedColumn(graph, right)) return right\n const left = ideal - d\n if (left >= minX && !isNodeOccupiedColumn(graph, left)) return left\n }\n return ideal\n}\n\n/**\n * Find the best line segment in an edge's path to place a label on.\n * Prefers vertical segments for TD/BT graphs and horizontal for LR/RL to avoid\n * label collisions when multiple edges share initial segments.\n * Falls back to the widest segment if none are suitable.\n * Also increases the column width at the label position to fit the text.\n *\n * No-ops for an edge past the first in a parallel-edge group\n * (`edge.parallelLane.index > 0`): `determinePath` already called\n * `applyLabelLine` directly for it, with a segment `buildParallelLanePath`\n * chose explicitly rather than one this function's own width-based search\n * could reliably re-derive from the finished lane path alone (see that\n * function's doc) — re-running the search here would risk picking a\n * different, sibling-colliding segment instead.\n */\nexport function determineLabelLine(graph: AsciiGraph, edge: AsciiEdge): void {\n if (edge.text.length === 0) return\n if (edge.parallelLane && edge.parallelLane.index > 0) return\n\n const lenLabel = displayWidth(edge.text)\n\n // Every routed path is axis-aligned (A* is 4-directional and\n // tryDirectPath builds explicit L-shapes), with one exception:\n // determinePath's Case-4 direct fallback, `[prefFrom, prefTo]`, which is\n // a single *diagonal* segment. draw-lines.ts's drawLine never draws a\n // diagonal — it draws that segment as an L, horizontal-first (along\n // from.y to to.x, then down/up to to.y). A label centered on the\n // diagonal itself lands mid-way between the two legs, in open grid that\n // has nothing to do with this edge and is often right next to some\n // *other* edge's connector (#418, \"All Edge Styles\": the `thick` label\n // abutting the dotted edge's column). Expand any diagonal segment into\n // the same two legs drawLine will draw, so the label search below only\n // ever considers line segments that actually get drawn.\n const points = expandDiagonalSegments(edge.path)\n const pathLen = points.length\n\n // Collect all segments with their widths and orientation\n const segments: {\n line: [GridCoord, GridCoord]\n width: number\n index: number\n isVertical: boolean\n }[] = []\n\n for (let i = 1; i < pathLen; i++) {\n const p1 = points[i - 1]!\n const p2 = points[i]!\n const line: [GridCoord, GridCoord] = [p1, p2]\n const width = calculateLineWidth(graph, line)\n // A segment is vertical if X coords are same, horizontal if Y coords are same\n const isVertical = p1.x === p2.x\n segments.push({ line, width, index: i, isVertical })\n }\n\n // A segment whose *interior* (every cell strictly between its own two\n // endpoints — not the endpoints themselves, which are expected to touch\n // a node border or a path corner) passes through a cell owned by some\n // node's 3x3 block is a bad place for a label: the label text would be\n // drawn across that node's own box-drawing characters instead of open\n // grid. An ordinary A*/direct-routed path never does this (routing\n // itself avoids node-occupied cells), but edge-routing.ts's parallel\n // lane paths (buildParallelLanePath, for true multi-edges — see #329)\n // are constructed directly rather than pathfound, and their short jog\n // back into the target can otherwise look, by the width-only heuristic\n // below, like the best available segment even though it runs straight\n // across the target's own border row/column.\n const clearOfNodes = (line: [GridCoord, GridCoord]): boolean =>\n interiorCellsClearOfNodes(graph, pathCells(line))\n\n // A segment is \"terminal\" when one of its own two endpoints IS the\n // source's or target's box-border attachment point — i.e. it's the very\n // first or very last segment of the path. `clearOfNodes` above can't\n // catch a bad terminal segment on its own: it only inspects *interior*\n // cells (by design — a path's endpoints are expected to touch a node\n // border), so a short, node-adjacent segment with no interior cells at\n // all (e.g. the single-grid-step box-start connector straight into a\n // node) sails through as \"clear\" even though its own endpoint sits\n // exactly on that node's border column/row. Centering a label on such a\n // segment lands the text right where the border glyph (or box-start\n // connector) is drawn, silently erasing it (#450) — the label canvas is\n // merged on top of the node box canvas, so whichever writes last wins.\n // Excluding both terminal segments (not just the first, as before) from\n // the primary and \"later\" fallback tiers below keeps a label away from\n // either node's border, symmetric with how the first segment was already\n // excluded for being \"often shared between edges from the same source\n // node\".\n const isTerminalSegment = (s: (typeof segments)[number]): boolean =>\n s.index === 1 || s.index === segments.length\n\n // Find segments wide enough for the label, excluding terminal segments\n // (the first, often shared between edges from the same source node; and\n // the last, which is the box-start connector straight into the target —\n // see isTerminalSegment above).\n const suitableSegments = segments.filter(\n (s) => s.width >= lenLabel && !isTerminalSegment(s) && clearOfNodes(s.line),\n )\n\n let largestLine: [GridCoord, GridCoord]\n\n if (suitableSegments.length > 0) {\n // Prefer segments near the end of the path (closer to target)\n // This avoids the shared initial segments from source\n suitableSegments.sort((a, b) => b.index - a.index)\n largestLine = suitableSegments[0]!.line\n } else {\n // Fall back to any suitable segment, including a terminal one (the\n // first or last — see isTerminalSegment above): a very short path with\n // no non-terminal segment at all has nothing better to offer, and a\n // label landing on a terminal segment's *interior* is still preferable\n // to no label at all. (A terminal segment with literally zero interior\n // cells — the box-start-connector case #450 was filed against — can\n // still slip through here; that's an accepted last-resort trade-off,\n // not a regression, since this tier only runs once every non-terminal\n // segment has already been ruled out.)\n const fallbackSegments = segments.filter(\n (s) => s.width >= lenLabel && clearOfNodes(s.line),\n )\n if (fallbackSegments.length > 0) {\n fallbackSegments.sort((a, b) => b.index - a.index)\n largestLine = fallbackSegments[0]!.line\n } else {\n // No segment both wide enough and clear of nodes — prefer the\n // widest segment that's at least clear of nodes (its column can\n // still be widened below to fit the label). Still prefer a\n // non-terminal segment here too: a multi-segment lane path\n // (buildParallelLanePath) can have every one of its later segments\n // come up too narrow for this tier (e.g. a single-gutter-column\n // \"gutter\" candidate — see that function's doc), and without this,\n // the widest-clear-segment sort below would happily fall back to the\n // *first* segment purely because it's typically the widest (it's on\n // the shared trunk row every sibling edge from the same source\n // uses) — reintroducing the exact same-row label collision this\n // whole terminal-segment exclusion exists to prevent, just via this\n // tier instead of the ones above. Only fall back further — first to\n // *any* clear segment regardless of terminal-ness, then to every\n // segment — when nothing clear survives even that relaxation (a\n // single-segment edge with no room to exclude anything, or a segment\n // that runs through a node with no alternative at all).\n const clearLaterSegments = segments.filter(\n (s) => !isTerminalSegment(s) && clearOfNodes(s.line),\n )\n const clearSegments = segments.filter((s) => clearOfNodes(s.line))\n const pool =\n clearLaterSegments.length > 0\n ? clearLaterSegments\n : clearSegments.length > 0\n ? clearSegments\n : segments\n pool.sort((a, b) => b.width - a.width)\n if (pool.length > 0) {\n largestLine = pool[0]!.line\n } else {\n // No segments at all: edge.path has fewer than 2 points. This\n // happens when a routed edge's preferred from/to grid coordinates\n // coincide (e.g. closely-spaced/adjacent nodes), so getPath (see\n // pathfinder.ts) returns a single-point path. Treat it as a\n // degenerate zero-length line at that point instead of indexing\n // past the end of a 1-element (or empty) array.\n const only = edge.path[0] ?? { x: 0, y: 0 }\n largestLine = [only, only]\n }\n }\n }\n\n applyLabelLine(graph, edge, largestLine, lenLabel)\n}\n\n/**\n * Replace every diagonal step in `path` (consecutive points that differ in\n * both x and y) with the two axis-aligned legs draw-lines.ts's drawLine\n * actually draws for it: horizontal first, to the corner `{x: next.x,\n * y: prev.y}`, then vertical. Axis-aligned steps pass through unchanged, so\n * an ordinary routed path comes back identical. Only determinePath's Case-4\n * direct fallback produces a diagonal in practice — see determineLabelLine.\n */\nexport function expandDiagonalSegments(path: GridCoord[]): GridCoord[] {\n const out: GridCoord[] = []\n for (const point of path) {\n const prev = out[out.length - 1]\n if (prev && prev.x !== point.x && prev.y !== point.y) {\n out.push({ x: point.x, y: prev.y })\n }\n out.push(point)\n }\n return out\n}\n\n/**\n * Finish assigning a label to `line`: widen whichever column its midpoint\n * falls in enough to fit the label, then commit `edge.labelLine`. Shared\n * tail for `determineLabelLine`'s own segment-search heuristic above and\n * `buildParallelLanePath`'s explicit segment choice below — the latter\n * already knows exactly which segment is correct for its own lane (see\n * that function's doc for why the heuristic above can't reliably infer it\n * for a parallel-lane path), so it skips straight to this shared finish\n * instead of going through the search.\n *\n * The chosen column must not be one a node itself occupies (its 3-column\n * border/content/border block, reserved in reserveSpotInGrid): a shared\n * trunk segment often passes directly over/adjacent to another node's\n * reserved columns on its way elsewhere, and if the label's midpoint\n * happens to land there, widening it to fit the label inflates that\n * node's own border-column width — which then drags things anchored to\n * that column's *center* (like the box-start ├/┤/┬/┴ connector in\n * draw-arrows.ts, computed via gridToDrawingCoord) away from the node's\n * actual, fixed-width rendered border. Search outward from the ideal\n * midpoint for the nearest column in the segment that isn't node-owned.\n */\nfunction applyLabelLine(\n graph: AsciiGraph,\n edge: AsciiEdge,\n line: [GridCoord, GridCoord],\n lenLabel: number,\n): void {\n const minX = Math.min(line[0].x, line[1].x)\n const maxX = Math.max(line[0].x, line[1].x)\n const idealX = minX + Math.floor((maxX - minX) / 2)\n const middleX = findNonNodeColumn(graph, minX, maxX, idealX)\n\n const current = graph.columnWidth.get(middleX) ?? 0\n graph.columnWidth.set(middleX, Math.max(current, lenLabel + 2))\n\n edge.labelLine = [line[0], line[1]]\n}\n\n/** Calculate the total character width of a line segment by summing column widths. */\nfunction calculateLineWidth(\n graph: AsciiGraph,\n line: [GridCoord, GridCoord],\n): number {\n let total = 0\n const startX = Math.min(line[0].x, line[1].x)\n const endX = Math.max(line[0].x, line[1].x)\n for (let x = startX; x <= endX; x++) {\n total += graph.columnWidth.get(x) ?? 0\n }\n return total\n}\n","// ============================================================================\n// ASCII renderer — line drawing (8-directional lines on the canvas)\n//\n// Split out of draw.ts.\n// ============================================================================\n\nimport type { Canvas, DrawingCoord, AsciiEdgeStyle } from './types.ts'\nimport {\n Up,\n Down,\n Left,\n Right,\n UpperLeft,\n UpperRight,\n LowerLeft,\n LowerRight,\n} from './types.ts'\nimport { determineDirection, dirEquals } from './edge-routing.ts'\nimport { write } from './canvas.ts'\n\n/**\n * Line character sets for different edge styles.\n * Each style has horizontal, vertical, and diagonal characters for both\n * Unicode (box-drawing) and ASCII (basic punctuation) modes.\n *\n * Unicode dotted: ┄ (horizontal), ┆ (vertical) — U+2504, U+2506\n * Unicode thick: ━ (horizontal), ┃ (vertical) — U+2501, U+2503\n */\n/**\n * Line character sets for different edge styles.\n * Only horizontal and vertical characters - no diagonals.\n * All edges use orthogonal Manhattan routing (90° bends only).\n */\nconst LINE_CHARS = {\n solid: {\n h: { unicode: '─', ascii: '-' },\n v: { unicode: '│', ascii: '|' },\n },\n dotted: {\n h: { unicode: '┄', ascii: '.' },\n v: { unicode: '┆', ascii: ':' },\n },\n thick: {\n h: { unicode: '━', ascii: '=' },\n v: { unicode: '┃', ascii: '‖' },\n },\n /*\n * `A ~~~ B` — an invisible link. It routes and reserves grid cells like any\n * other edge (so the nodes it connects stay ranked apart), but every cell it\n * would occupy is written as a space, leaving no visible line.\n */\n invisible: {\n h: { unicode: ' ', ascii: ' ' },\n v: { unicode: ' ', ascii: ' ' },\n },\n} as const\n\n/**\n * Draw a line between two drawing coordinates using orthogonal Manhattan routing.\n * Returns the list of coordinates that were drawn on.\n * offsetFrom/offsetTo control how many cells to skip at the start/end.\n *\n * All lines use 90° bends only - no diagonal lines are produced.\n * For diagonal directions, uses horizontal-first routing (draws horizontal\n * segment, then vertical segment).\n */\nexport function drawLine(\n canvas: Canvas,\n from: DrawingCoord,\n to: DrawingCoord,\n offsetFrom: number,\n offsetTo: number,\n useAscii: boolean,\n style: AsciiEdgeStyle = 'solid',\n): DrawingCoord[] {\n const dir = determineDirection(from, to)\n const drawnCoords: DrawingCoord[] = []\n\n // Select character set based on style (horizontal and vertical only)\n const chars = LINE_CHARS[style]\n const hChar = useAscii ? chars.h.ascii : chars.h.unicode\n const vChar = useAscii ? chars.v.ascii : chars.v.unicode\n\n // Pure vertical directions\n if (dirEquals(dir, Up)) {\n for (let y = from.y - offsetFrom; y >= to.y - offsetTo; y--) {\n drawnCoords.push({ x: from.x, y })\n write(canvas, from.x, y, vChar)\n }\n } else if (dirEquals(dir, Down)) {\n for (let y = from.y + offsetFrom; y <= to.y + offsetTo; y++) {\n drawnCoords.push({ x: from.x, y })\n write(canvas, from.x, y, vChar)\n }\n }\n // Pure horizontal directions\n else if (dirEquals(dir, Left)) {\n for (let x = from.x - offsetFrom; x >= to.x - offsetTo; x--) {\n drawnCoords.push({ x, y: from.y })\n write(canvas, x, from.y, hChar)\n }\n } else if (dirEquals(dir, Right)) {\n for (let x = from.x + offsetFrom; x <= to.x + offsetTo; x++) {\n drawnCoords.push({ x, y: from.y })\n write(canvas, x, from.y, hChar)\n }\n }\n // Diagonal directions: use Manhattan routing (horizontal-first, then vertical)\n // UpperLeft: go left first, then up\n else if (dirEquals(dir, UpperLeft)) {\n // Horizontal segment: from.x -> to.x (going left)\n for (let x = from.x - offsetFrom; x >= to.x; x--) {\n drawnCoords.push({ x, y: from.y })\n write(canvas, x, from.y, hChar)\n }\n // Vertical segment: from.y -> to.y (going up)\n for (let y = from.y - 1; y >= to.y - offsetTo; y--) {\n drawnCoords.push({ x: to.x, y })\n write(canvas, to.x, y, vChar)\n }\n }\n // UpperRight: go right first, then up\n else if (dirEquals(dir, UpperRight)) {\n // Horizontal segment: from.x -> to.x (going right)\n for (let x = from.x + offsetFrom; x <= to.x; x++) {\n drawnCoords.push({ x, y: from.y })\n write(canvas, x, from.y, hChar)\n }\n // Vertical segment: from.y -> to.y (going up)\n for (let y = from.y - 1; y >= to.y - offsetTo; y--) {\n drawnCoords.push({ x: to.x, y })\n write(canvas, to.x, y, vChar)\n }\n }\n // LowerLeft: go left first, then down\n else if (dirEquals(dir, LowerLeft)) {\n // Horizontal segment: from.x -> to.x (going left)\n for (let x = from.x - offsetFrom; x >= to.x; x--) {\n drawnCoords.push({ x, y: from.y })\n write(canvas, x, from.y, hChar)\n }\n // Vertical segment: from.y -> to.y (going down)\n for (let y = from.y + 1; y <= to.y + offsetTo; y++) {\n drawnCoords.push({ x: to.x, y })\n write(canvas, to.x, y, vChar)\n }\n }\n // LowerRight: go right first, then down\n // Special case: if x difference is small (1), draw straight vertical at from.x\n // This keeps edges visually aligned with the source node\n else if (dirEquals(dir, LowerRight)) {\n const dx = to.x - from.x\n if (dx <= 1) {\n // Draw vertical line at from.x (source's x-coordinate)\n for (let y = from.y + offsetFrom; y <= to.y + offsetTo; y++) {\n drawnCoords.push({ x: from.x, y })\n write(canvas, from.x, y, vChar)\n }\n } else {\n // Horizontal segment: from.x -> to.x (going right)\n for (let x = from.x + offsetFrom; x <= to.x; x++) {\n drawnCoords.push({ x, y: from.y })\n write(canvas, x, from.y, hChar)\n }\n // Vertical segment: from.y -> to.y (going down)\n for (let y = from.y + 1; y <= to.y + offsetTo; y++) {\n drawnCoords.push({ x: to.x, y })\n write(canvas, to.x, y, vChar)\n }\n }\n }\n\n return drawnCoords\n}\n","// ============================================================================\n// ASCII renderer — arrow drawing (path, corners, arrowheads, box-start\n// junctions, labels)\n//\n// Split out of draw.ts.\n// ============================================================================\n\nimport type {\n Canvas,\n DrawingCoord,\n GridCoord,\n Direction,\n AsciiGraph,\n AsciiEdge,\n AsciiEdgeStyle,\n AsciiNode,\n} from './types.ts'\nimport {\n Up,\n Down,\n Left,\n Right,\n UpperLeft,\n UpperRight,\n LowerLeft,\n LowerRight,\n Middle,\n drawingCoordEquals,\n} from './types.ts'\nimport { copyCanvas, drawText, write } from './canvas.ts'\nimport { determineDirection, dirEquals } from './edge-routing.ts'\nimport { displayWidth } from './display-width.ts'\nimport { gridToDrawingCoord, lineToDrawing } from './grid.ts'\nimport { splitLines } from './multiline-utils.ts'\nimport { drawLine } from './draw-lines.ts'\n\n/**\n * Draw a complete arrow (edge) between two nodes.\n * Returns 6 separate canvases for layered compositing:\n * [path, boxStart, arrowHeadEnd, arrowHeadStart, corners, label]\n *\n * Supports bidirectional arrows via edge.hasArrowStart and edge.hasArrowEnd.\n */\nexport function drawArrow(\n graph: AsciiGraph,\n edge: AsciiEdge,\n): [Canvas, Canvas, Canvas, Canvas, Canvas, Canvas] {\n if (edge.path.length === 0) {\n const empty = copyCanvas(graph.canvas)\n return [empty, empty, empty, empty, empty, empty]\n }\n\n const labelCanvas = drawArrowLabel(graph, edge)\n const [pathCanvas, linesDrawn, lineDirs] = drawPath(\n graph,\n edge.path,\n edge.style,\n )\n\n // A routed path can collapse to zero drawn line segments when every grid\n // point maps to the same drawing coordinate — e.g. a routed edge whose\n // preferred from/to grid coordinates coincide for closely-spaced/adjacent\n // nodes (pathfinder.ts's getPath can legitimately return a single-point\n // path in that case; see #153). With no segment to anchor to, there's\n // nothing for a box-start connector or an end arrowhead to attach to, so\n // both are skipped instead of indexing into the empty linesDrawn/lineDirs\n // arrays.\n const hasSegments = linesDrawn.length > 0\n\n /*\n * An invisible link (`A ~~~ B`) routes and reserves space like any other\n * edge, but must leave no mark. drawPath already writes spaces for its\n * segments; the box connector and corner glyphs are drawn by separate\n * passes that don't consult the line character set, so they're suppressed\n * explicitly here. Arrowheads need no guard — `~~~` carries no `>`/`o`/`x`\n * marker, so hasArrowStart/hasArrowEnd are already false.\n */\n const invisible = edge.style === 'invisible'\n\n const boxStartCanvas =\n hasSegments && !invisible\n ? drawBoxStart(graph, edge.path, linesDrawn[0]!, edge.from)\n : copyCanvas(graph.canvas)\n\n // Draw end arrowhead only if hasArrowEnd is true (default behavior)\n let arrowHeadEndCanvas: Canvas\n if (edge.hasArrowEnd && hasSegments) {\n arrowHeadEndCanvas = drawArrowHead(\n graph,\n linesDrawn[linesDrawn.length - 1]!,\n lineDirs[lineDirs.length - 1]!,\n edge.endMarker,\n )\n } else {\n arrowHeadEndCanvas = copyCanvas(graph.canvas)\n }\n\n // Draw start arrowhead for bidirectional edges\n // The start arrowhead needs to be at the box connector position (one step back\n // from the first line point), pointing into the source node.\n let arrowHeadStartCanvas: Canvas\n if (edge.hasArrowStart && hasSegments) {\n const firstLine = linesDrawn[0]!\n const firstPoint = firstLine[0]!\n const startDir = reverseDirection(lineDirs[0]!)\n\n // Calculate the box connector position (one step back from first point)\n const arrowPos: DrawingCoord = { x: firstPoint.x, y: firstPoint.y }\n if (dirEquals(lineDirs[0]!, Right)) arrowPos.x = firstPoint.x - 1\n else if (dirEquals(lineDirs[0]!, Left)) arrowPos.x = firstPoint.x + 1\n else if (dirEquals(lineDirs[0]!, Down)) arrowPos.y = firstPoint.y - 1\n else if (dirEquals(lineDirs[0]!, Up)) arrowPos.y = firstPoint.y + 1\n\n // Create a synthetic line ending at the arrow position for drawArrowHead\n const syntheticLine: DrawingCoord[] = [firstPoint, arrowPos]\n arrowHeadStartCanvas = drawArrowHead(\n graph,\n syntheticLine,\n startDir,\n edge.startMarker,\n )\n } else {\n arrowHeadStartCanvas = copyCanvas(graph.canvas)\n }\n\n const cornersCanvas = invisible\n ? copyCanvas(graph.canvas)\n : drawCorners(graph, edge.path)\n\n return [\n pathCanvas,\n boxStartCanvas,\n arrowHeadEndCanvas,\n arrowHeadStartCanvas,\n cornersCanvas,\n labelCanvas,\n ]\n}\n\n/**\n * Reverse a direction (for bidirectional arrow start heads).\n */\nfunction reverseDirection(dir: Direction): Direction {\n if (dirEquals(dir, Up)) return Down\n if (dirEquals(dir, Down)) return Up\n if (dirEquals(dir, Left)) return Right\n if (dirEquals(dir, Right)) return Left\n if (dirEquals(dir, UpperLeft)) return LowerRight\n if (dirEquals(dir, UpperRight)) return LowerLeft\n if (dirEquals(dir, LowerLeft)) return UpperRight\n if (dirEquals(dir, LowerRight)) return UpperLeft\n return Middle\n}\n\n/**\n * Draw the path lines for an edge.\n * Returns the canvas, the coordinates drawn for each segment, and the direction of each segment.\n */\nfunction drawPath(\n graph: AsciiGraph,\n path: GridCoord[],\n style: AsciiEdgeStyle = 'solid',\n): [Canvas, DrawingCoord[][], Direction[]] {\n const canvas = copyCanvas(graph.canvas)\n // path is non-empty: drawArrow (drawPath's sole caller) already returns\n // early when edge.path.length === 0.\n let previousCoord = path[0]!\n const linesDrawn: DrawingCoord[][] = []\n const lineDirs: Direction[] = []\n\n for (let i = 1; i < path.length; i++) {\n const nextCoord = path[i]!\n const prevDC = gridToDrawingCoord(graph, previousCoord)\n const nextDC = gridToDrawingCoord(graph, nextCoord)\n\n if (drawingCoordEquals(prevDC, nextDC)) {\n previousCoord = nextCoord\n continue\n }\n\n const dir = determineDirection(previousCoord, nextCoord)\n const segment = drawLine(\n canvas,\n prevDC,\n nextDC,\n 1,\n -1,\n graph.config.useAscii,\n style,\n )\n if (segment.length === 0) segment.push(prevDC)\n linesDrawn.push(segment)\n lineDirs.push(dir)\n previousCoord = nextCoord\n }\n\n return [canvas, linesDrawn, lineDirs]\n}\n\n/** Vertical box-border characters a horizontal connector can genuinely merge with. */\nconst VERTICAL_BORDER_CHARS = new Set(['│', '┃', '║', '┆', '┊', '|', '‖'])\n/** Horizontal box-border characters a vertical connector can genuinely merge with. */\nconst HORIZONTAL_BORDER_CHARS = new Set(['─', '━', '═', '╌', '┄', '-', '='])\n\n/**\n * Draw the junction character where an edge exits the source node's box.\n * Unicode mode uses shape-specific T-junction glyphs; ASCII mode uses the\n * same universal '+' already used for corners in draw-boxes.ts.\n * Skips drawing for state pseudo-states which have their own visual borders.\n *\n * A tee/junction character (┬┴├┤) is only correct when it's actually merging\n * with a real perpendicular border line at that exact cell — e.g. a grid\n * column widened by a sibling edge's label can push the computed box-start\n * position away from the node's actual border, landing on a blank cell with\n * no vertical (or horizontal) line to justify a junction. In that case fall\n * back to a plain line character matching the edge's own direction, instead\n * of fabricating a stray tee (see issue #86).\n */\nfunction drawBoxStart(\n graph: AsciiGraph,\n path: GridCoord[],\n firstLine: DrawingCoord[],\n sourceNode: AsciiNode,\n): Canvas {\n const canvas = copyCanvas(graph.canvas)\n const useAscii = graph.config.useAscii\n\n // Skip box start connectors for state pseudo-states (they have their own bordered design)\n if (sourceNode.shape === 'state-start' || sourceNode.shape === 'state-end') {\n return canvas\n }\n\n // firstLine is guaranteed non-empty and path has >= 2 points: the sole\n // caller (drawArrow) only invokes drawBoxStart when drawPath produced at\n // least one line segment (see the `hasSegments` guard there), and\n // drawPath never pushes an empty segment into linesDrawn.\n const from = firstLine[0]!\n const dir = determineDirection(path[0]!, path[1]!)\n const junction = useAscii ? '+' : null\n\n // `canvas` (from copyCanvas) is a blank overlay layer — it never has\n // content of its own to check. Whether a genuine perpendicular border\n // line already occupies the target cell has to be read from\n // `graph.canvas`, which at this point in drawGraph already has node\n // boxes (and subgraph borders) merged onto it, before any edge layers.\n const existingOnBox = (x: number, y: number): string | undefined =>\n graph.canvas[x]?.[y]\n\n if (dirEquals(dir, Up)) {\n const x = from.x\n const y = from.y + 1\n const existing = existingOnBox(x, y)\n const hasBorder =\n existing !== undefined && HORIZONTAL_BORDER_CHARS.has(existing)\n write(canvas, x, y, hasBorder ? (junction ?? '┴') : useAscii ? '|' : '│')\n } else if (dirEquals(dir, Down)) {\n const x = from.x\n const y = from.y - 1\n const existing = existingOnBox(x, y)\n const hasBorder =\n existing !== undefined && HORIZONTAL_BORDER_CHARS.has(existing)\n write(canvas, x, y, hasBorder ? (junction ?? '┬') : useAscii ? '|' : '│')\n } else if (dirEquals(dir, Left) || dirEquals(dir, Right)) {\n // Anchor horizontal connectors to the source node's *own* rendered\n // border column, not to gridToDrawingCoord's grid-column-centered\n // position. That position is centered within the node's border grid\n // column using that column's *allocated* width — but a sibling edge's\n // label can land on that same column (its labelLine's chosen segment\n // just happens to pass through the node's border column on its way\n // elsewhere) and widen it well past the 1-character width the border\n // itself needs. Centering on the inflated width then drags the\n // connector away from the box's actual border character, which stays\n // put at a position that only depends on the box's own dimensions.\n //\n // Node dimensions are set before edge routing/drawing (see\n // createMapping in grid.ts), so drawingCoord/drawing should always be\n // present here — but that's a cross-module invariant the type checker\n // can't see, so it's validated explicitly rather than trusted silently.\n const dc = sourceNode.drawingCoord\n const drawing = sourceNode.drawing\n if (dc === null || drawing === null) {\n /* v8 ignore next */\n throw new Error(\n `drawBoxStart: node \"${sourceNode.name}\" has no drawingCoord/drawing assigned`,\n )\n }\n const boxWidth = drawing.length\n const x = dirEquals(dir, Left) ? dc.x : dc.x + boxWidth - 1\n const y = from.y\n const existing = existingOnBox(x, y)\n const hasBorder =\n existing !== undefined && VERTICAL_BORDER_CHARS.has(existing)\n write(\n canvas,\n x,\n y,\n hasBorder\n ? (junction ?? (dirEquals(dir, Left) ? '┤' : '├'))\n : useAscii\n ? '-'\n : '─',\n )\n }\n\n return canvas\n}\n\n/**\n * Fixed glyph for a `--o`/`--x` circle/cross terminator — direction-\n * independent, unlike the triangular arrowheads below, so callers don't\n * need to know which way the edge points to pick it. Returns undefined for\n * a plain arrowhead (no marker), so a caller can fall back to its own\n * directional glyph selection. Shared by drawArrowHead below (single-edge\n * arrowheads) and draw-bundles.ts (fan-in/fan-out bundled arrowheads) so\n * the glyph choice can't drift between the two. See issue #330.\n */\nexport function markerArrowChar(\n useAscii: boolean,\n marker: 'circle' | 'cross' | undefined,\n): string | undefined {\n if (marker === 'circle') return useAscii ? 'o' : '○'\n if (marker === 'cross') return useAscii ? 'x' : '✕'\n return undefined\n}\n\n/**\n * Maps a direction to its Unicode arrowhead glyph, or `undefined` for a\n * direction with no arrowhead (`Middle`, or any other value this switch\n * doesn't recognize) — callers decide the ultimate fallback.\n *\n * Uses triangular Unicode symbols (▲▼◄►) for the four orthogonal\n * directions and diagonal arrow symbols (↗↖↘↙) for the four diagonal ones.\n * The diagonal glyphs are plain Arrows-block characters (U+2196–U+2199),\n * not filled triangles like the orthogonal set — JetBrains Mono NL (this\n * site's self-hosted ASCII font, see scripts/build-mono-font-subset.ts)\n * has no glyph at all for the filled diagonal triangles (◢◣◤◥, U+25E2–\n * U+25E5) that would otherwise match the orthogonal style; U+2196–U+2199\n * are real, correctly-directional glyphs the font does have, which beats\n * falling back to an unpinned system font for just these four characters.\n * See issue #1062.\n *\n * Exported (alongside {@link asciiArrowChar}) so tests can exercise every\n * direction directly — the ELK/pathfinder layout this module actually\n * draws from rarely if ever produces some direction combinations (e.g.\n * `LowerLeft` only arises from determinePath's rare Case-4 diagonal\n * fallback, and only for a specific relative source/target position no\n * existing sample or hand-written diagram happens to trigger), which would\n * otherwise leave this mapping's correctness for those directions unverified.\n */\nexport function unicodeArrowChar(dir: Direction): string | undefined {\n if (dirEquals(dir, Up)) return '▲'\n if (dirEquals(dir, Down)) return '▼'\n if (dirEquals(dir, Left)) return '◄'\n if (dirEquals(dir, Right)) return '►'\n if (dirEquals(dir, UpperRight)) return '↗'\n if (dirEquals(dir, UpperLeft)) return '↖'\n if (dirEquals(dir, LowerRight)) return '↘'\n if (dirEquals(dir, LowerLeft)) return '↙'\n return undefined\n}\n\n/** ASCII-mode counterpart of {@link unicodeArrowChar} — orthogonal only, no diagonals. */\nexport function asciiArrowChar(dir: Direction): string | undefined {\n if (dirEquals(dir, Up)) return '^'\n if (dirEquals(dir, Down)) return 'v'\n if (dirEquals(dir, Left)) return '<'\n if (dirEquals(dir, Right)) return '>'\n return undefined\n}\n\n/**\n * Draw the arrowhead at the end of an edge path.\n *\n * `marker` overrides the directional glyph with a fixed circle/cross glyph\n * for `--o`/`--x` (flowchart) terminators — those are direction-\n * independent, so the direction computed below is only used to place it,\n * never to pick which glyph to draw. See issue #330.\n */\nfunction drawArrowHead(\n graph: AsciiGraph,\n lastLine: DrawingCoord[],\n fallbackDir: Direction,\n marker?: 'circle' | 'cross',\n): Canvas {\n const canvas = copyCanvas(graph.canvas)\n if (lastLine.length === 0) return canvas\n\n // Direction is derived from the *final step* into `lastPos` (its\n // immediate predecessor in `lastLine`), not from `lastLine`'s first\n // point. Those coincide for an ordinary straight segment, but\n // `determinePath`'s Case-4 diagonal fallback (edge-routing.ts) can hand\n // drawLine a single non-axis-aligned pair, which it then draws as an L\n // (horizontal run, then vertical run — see draw-lines.ts) folded into\n // one `lastLine` array. Using the first point there would span both\n // legs and read as diagonal even when the actual approach into the\n // arrowhead is a plain orthogonal step. See issue #1083.\n const lastPos = lastLine[lastLine.length - 1]!\n const from =\n lastLine.length >= 2 ? lastLine[lastLine.length - 2]! : lastLine[0]!\n let dir = determineDirection(from, lastPos)\n if (lastLine.length === 1 || dirEquals(dir, Middle)) dir = fallbackDir\n\n const markerChar = markerArrowChar(graph.config.useAscii, marker)\n let char: string\n if (markerChar !== undefined) {\n char = markerChar\n } else if (!graph.config.useAscii) {\n const resolved = unicodeArrowChar(dir)\n // `resolved` is only ever undefined when `dir` itself is `Middle` (or\n // an unrecognized Direction) — which, given the reassignment above,\n // only happens when `fallbackDir` is itself `Middle`. No current\n // caller passes that; every drawArrow/draw-bundles.ts call site\n // computes a real directional fallback. Kept as a safety net rather\n // than a hard assumption.\n /* v8 ignore else */\n if (resolved !== undefined) {\n char = resolved\n } else {\n char = unicodeArrowChar(fallbackDir) ?? '●'\n }\n } else {\n const resolved = asciiArrowChar(dir)\n // Same defensive-only fallback as the unicode-mode branch above,\n // mirrored for ASCII mode.\n /* v8 ignore else */\n if (resolved !== undefined) {\n char = resolved\n } else {\n char = asciiArrowChar(fallbackDir) ?? '*'\n }\n }\n\n write(canvas, lastPos.x, lastPos.y, char)\n return canvas\n}\n\n/**\n * Draw corner characters at path bends (where the direction changes).\n * Uses ┌┐└┘ in Unicode mode, + in ASCII mode.\n */\nfunction drawCorners(graph: AsciiGraph, path: GridCoord[]): Canvas {\n const canvas = copyCanvas(graph.canvas)\n\n for (let idx = 1; idx < path.length - 1; idx++) {\n const coord = path[idx]!\n const dc = gridToDrawingCoord(graph, coord)\n const prevDir = determineDirection(path[idx - 1]!, coord)\n const nextDir = determineDirection(coord, path[idx + 1]!)\n\n let corner: string\n if (!graph.config.useAscii) {\n if (\n (dirEquals(prevDir, Right) && dirEquals(nextDir, Down)) ||\n (dirEquals(prevDir, Up) && dirEquals(nextDir, Left))\n ) {\n corner = '┐'\n } else if (\n (dirEquals(prevDir, Right) && dirEquals(nextDir, Up)) ||\n (dirEquals(prevDir, Down) && dirEquals(nextDir, Left))\n ) {\n corner = '┘'\n } else if (\n (dirEquals(prevDir, Left) && dirEquals(nextDir, Down)) ||\n (dirEquals(prevDir, Up) && dirEquals(nextDir, Right))\n ) {\n corner = '┌'\n } else if (\n (dirEquals(prevDir, Left) && dirEquals(nextDir, Up)) ||\n (dirEquals(prevDir, Down) && dirEquals(nextDir, Right))\n ) {\n corner = '└'\n } else {\n corner = '+'\n }\n } else {\n corner = '+'\n }\n\n write(canvas, dc.x, dc.y, corner)\n }\n\n return canvas\n}\n\n/**\n * True when `edge` has a sibling edge connecting the exact same two nodes in\n * the opposite direction (`A --> B` alongside `B --> A`) — a \"reciprocal\n * pair\". Node identity is compared by reference: `converter.ts` resolves\n * every edge's `from`/`to` from the same shared node map, so the same\n * logical node is always the same object across edges.\n */\nfunction hasReciprocalPartner(graph: AsciiGraph, edge: AsciiEdge): boolean {\n return graph.edges.some(\n (other) =>\n other !== edge && other.from === edge.to && other.to === edge.from,\n )\n}\n\n/** Draw edge label text centered on the widest path segment. */\nfunction drawArrowLabel(graph: AsciiGraph, edge: AsciiEdge): Canvas {\n const canvas = copyCanvas(graph.canvas)\n if (edge.text.length === 0) return canvas\n\n const drawingLine = lineToDrawing(graph, edge.labelLine)\n\n // Determine if this is an upward edge (target is above source in the path)\n // This is used to offset labels on bidirectional edges to prevent overlap\n let isUpwardEdge: boolean | undefined\n if (edge.path.length >= 2) {\n const startY = edge.path[0]!.y\n const endY = edge.path[edge.path.length - 1]!.y\n // Edge goes up if end Y is less than start Y (smaller Y = higher on screen)\n if (endY < startY) {\n isUpwardEdge = true\n } else if (endY > startY) {\n isUpwardEdge = false\n }\n // If endY === startY, it's horizontal, leave isUpwardEdge undefined\n }\n\n // Only a genuine reciprocal pair (A-->B alongside B-->A) needs its label\n // pulled toward its own target instead of its own source — see #530 and\n // drawTextOnLine's doc comment below. A lone vertical edge keeps the\n // original \"precede the arrow, near the source\" placement.\n const pullTowardTarget = hasReciprocalPartner(graph, edge)\n\n drawTextOnLine(canvas, drawingLine, edge.text, isUpwardEdge, pullTowardTarget)\n return canvas\n}\n\n/**\n * Draw text centered on a line segment defined by two drawing coordinates.\n * Supports multi-line labels.\n *\n * When isUpwardEdge is provided, offsets the label vertically to prevent\n * overlapping with labels from edges going the opposite direction:\n * - Upward edges: label placed in lower portion of segment (near its own\n * source), unless `pullTowardTarget` is set — see below.\n * - Downward edges (isUpwardEdge=false): label placed in upper portion\n * (near its own source), unless `pullTowardTarget` is set.\n * - No direction (isUpwardEdge=undefined): label centered (default)\n *\n * `pullTowardTarget` inverts both of the above, pulling the label toward\n * its own arrowhead (the edge's target end) instead of its source. This\n * only makes a visible difference for a genuine reciprocal pair sharing one\n * vertical channel (`A --> B` alongside `B --> A`, both routed through the\n * same column): pulling each label toward its own *source* there pulls it\n * right next to the *other* edge's arrowhead instead, since in a two-node\n * cycle one edge's source is the other edge's target. #530 is exactly that\n * bug — a same-pair bidirectional edge's two labels rendered swapped\n * relative to the arrowheads they sit beside. A lone edge (no reciprocal\n * partner) keeps the original near-source placement so its label still\n * reads as \"preceding\" its own arrow rather than crowding the arrowhead.\n */\nfunction drawTextOnLine(\n canvas: Canvas,\n line: DrawingCoord[],\n label: string,\n isUpwardEdge?: boolean,\n pullTowardTarget = false,\n): void {\n if (line.length < 2) return\n const minX = Math.min(line[0]!.x, line[1]!.x)\n const maxX = Math.max(line[0]!.x, line[1]!.x)\n const minY = Math.min(line[0]!.y, line[1]!.y)\n const maxY = Math.max(line[0]!.y, line[1]!.y)\n const middleX = minX + Math.floor((maxX - minX) / 2)\n let middleY = minY + Math.floor((maxY - minY) / 2)\n\n // Offset label vertically to prevent overlap on bidirectional edges\n // For vertical segments (same X), shift based on edge direction\n if (isUpwardEdge !== undefined && minX === maxX) {\n const segmentHeight = maxY - minY\n const offset = Math.max(1, Math.floor(segmentHeight / 4))\n // XOR: pullTowardTarget flips which portion each direction lands in.\n const towardMinY = pullTowardTarget ? isUpwardEdge : !isUpwardEdge\n if (towardMinY) {\n middleY = middleY - offset\n } else {\n middleY = middleY + offset\n }\n }\n\n // Support multi-line labels\n const lines = splitLines(label)\n const startY = middleY - Math.floor((lines.length - 1) / 2)\n\n for (let i = 0; i < lines.length; i++) {\n const lineText = lines[i]!\n const startX = middleX - Math.floor(displayWidth(lineText) / 2)\n drawText(canvas, { x: startX, y: startY + i }, lineText)\n }\n}\n","// ============================================================================\n// ASCII renderer — bundled edge drawing (for parallel links, A & B --> C)\n//\n// Split out of draw.ts. Renders the fan-in/fan-out bundles computed by\n// ./edge-bundling.ts (junction points, shared trunks) onto the canvas.\n// ============================================================================\n\nimport type {\n Canvas,\n DrawingCoord,\n Direction,\n AsciiGraph,\n AsciiNode,\n AsciiEdge,\n EdgeBundle,\n} from './types.ts'\nimport {\n Up,\n Down,\n Left,\n Right,\n drawingCoordEquals,\n requireGridCoord,\n} from './types.ts'\nimport { copyCanvas, write } from './canvas.ts'\nimport { determineDirection, dirEquals } from './edge-routing.ts'\nimport { gridToDrawingCoord } from './grid.ts'\nimport { getShapeAttachmentPoint } from './shapes/index.ts'\nimport type { ShapeDimensions } from './shapes/index.ts'\nimport { drawLine } from './draw-lines.ts'\nimport { markerArrowChar } from './draw-arrows.ts'\n\n// ============================================================================\n// Node attachment point helper\n// ============================================================================\n\n/**\n * Get the drawing coordinate where an edge attaches to a node's border.\n * Uses grid-allocated dimensions so attachment points align with the actual\n * drawn box (which may be wider/taller than the intrinsic shape dimensions\n * when sharing a column/row with a larger node).\n */\nfunction getNodeAttachmentPoint(\n graph: AsciiGraph,\n node: AsciiNode,\n dir: Direction,\n): DrawingCoord {\n // Every node reaching bundled-edge drawing has already been placed by the\n // grid layout pass (see grid.ts's createMapping) — requireGridCoord\n // validates that rather than trusting it silently across the module\n // boundary, since the type checker can't see it.\n const gc = requireGridCoord(node)\n\n // Calculate actual drawn dimensions from grid (matching drawBoxWithGridDimensions)\n let w = 0\n for (let i = 0; i < 2; i++) {\n w += graph.columnWidth.get(gc.x + i) ?? 0\n }\n let h = 0\n for (let i = 0; i < 2; i++) {\n h += graph.rowHeight.get(gc.y + i) ?? 0\n }\n\n // Build dimensions matching the actual drawn box size\n const gridDimensions: ShapeDimensions = {\n width: w + 1,\n height: h + 1,\n labelArea: { x: 0, y: 0, width: 0, height: 0 },\n gridColumns: [0, 0, 0],\n gridRows: [0, 0, 0],\n }\n\n const baseCoord = node.drawingCoord\n if (baseCoord === null) {\n // Same invariant as gridCoord above: drawingCoord is derived from\n // gridCoord during layout, so it's set for every placed node.\n /* v8 ignore next */\n throw new Error(\n `Node \"${node.name}\" has no drawingCoord; grid layout must run before bundled-edge drawing`,\n )\n }\n return getShapeAttachmentPoint(node.shape, dir, gridDimensions, baseCoord)\n}\n\n/**\n * Draw a single edge's segment in a bundle (source → junction for fan-in,\n * junction → target for fan-out).\n *\n * Returns the same tuple format as drawArrow for consistency.\n */\nexport function drawBundledEdgeSegment(\n graph: AsciiGraph,\n edge: AsciiEdge,\n bundle: EdgeBundle,\n): [Canvas, Canvas, Canvas, Canvas, Canvas, Canvas] {\n const empty = copyCanvas(graph.canvas)\n\n // Captured as a local so its non-null, non-empty narrowing survives into\n // the closures below — narrowing a property access like\n // `edge.pathToJunction` does not persist inside a callback (the compiler\n // can't rule out the callback mutating it), but narrowing a local const\n // does.\n const pathToJunction = edge.pathToJunction\n if (!pathToJunction || pathToJunction.length === 0) {\n return [empty, empty, empty, empty, empty, empty]\n }\n\n // Draw the path segment (pathToJunction)\n const pathCanvas = copyCanvas(graph.canvas)\n const useAscii = graph.config.useAscii\n\n // Convert grid coords to drawing coords\n // For fan-in: first point is at source node border (use attachment point)\n // For fan-out: last point is at target node border (use attachment point)\n const drawingPath = pathToJunction.map((gc, idx) => {\n if (bundle.type === 'fan-in' && idx === 0) {\n // First point: use source node's actual border position\n return getNodeAttachmentPoint(graph, edge.from, edge.startDir)\n }\n if (bundle.type === 'fan-out' && idx === pathToJunction.length - 1) {\n // Last point: use target node's actual border position\n return getNodeAttachmentPoint(graph, edge.to, edge.endDir)\n }\n return gridToDrawingCoord(graph, gc)\n })\n\n // Draw line segments\n for (let i = 1; i < drawingPath.length; i++) {\n const from = drawingPath[i - 1]!\n const to = drawingPath[i]!\n if (!drawingCoordEquals(from, to)) {\n // Always skip both endpoints of every segment (offset 1, -1),\n // matching non-bundled drawPath behavior. This leaves endpoint\n // characters to corner/junction/boxStart canvases, preventing\n // line characters from corrupting them via mergeJunctions.\n drawLine(pathCanvas, from, to, 1, -1, useAscii, edge.style)\n }\n }\n\n // Draw corners at path bends\n const cornersCanvas = copyCanvas(graph.canvas)\n for (let idx = 1; idx < pathToJunction.length - 1; idx++) {\n const coord = pathToJunction[idx]!\n const dc = gridToDrawingCoord(graph, coord)\n const prevDir = determineDirection(pathToJunction[idx - 1]!, coord)\n const nextDir = determineDirection(coord, pathToJunction[idx + 1]!)\n\n let corner: string\n if (!useAscii) {\n if (\n (dirEquals(prevDir, Right) && dirEquals(nextDir, Down)) ||\n (dirEquals(prevDir, Up) && dirEquals(nextDir, Left))\n ) {\n corner = '┐'\n } else if (\n (dirEquals(prevDir, Right) && dirEquals(nextDir, Up)) ||\n (dirEquals(prevDir, Down) && dirEquals(nextDir, Left))\n ) {\n corner = '┘'\n } else if (\n (dirEquals(prevDir, Left) && dirEquals(nextDir, Down)) ||\n (dirEquals(prevDir, Up) && dirEquals(nextDir, Right))\n ) {\n corner = '┌'\n } else if (\n (dirEquals(prevDir, Left) && dirEquals(nextDir, Up)) ||\n (dirEquals(prevDir, Down) && dirEquals(nextDir, Right))\n ) {\n corner = '└'\n } else {\n corner = '+'\n }\n } else {\n corner = '+'\n }\n\n write(cornersCanvas, dc.x, dc.y, corner)\n }\n\n // Draw box start connector (for fan-in, from source node)\n // The connector is placed at the first point coordinate (box border position)\n // since we use offsets 1,-1 for drawLine, the line starts one step past this point\n const boxStartCanvas = copyCanvas(graph.canvas)\n if (bundle.type === 'fan-in' && pathToJunction.length >= 2) {\n const firstPoint = drawingPath[0]!\n const dir = determineDirection(pathToJunction[0]!, pathToJunction[1]!)\n\n const junction = useAscii ? '+' : null\n if (dirEquals(dir, Up))\n write(boxStartCanvas, firstPoint.x, firstPoint.y, junction ?? '┴')\n else if (dirEquals(dir, Down))\n write(boxStartCanvas, firstPoint.x, firstPoint.y, junction ?? '┬')\n else if (dirEquals(dir, Left))\n write(boxStartCanvas, firstPoint.x, firstPoint.y, junction ?? '┤')\n else if (dirEquals(dir, Right))\n write(boxStartCanvas, firstPoint.x, firstPoint.y, junction ?? '├')\n }\n\n // Label canvas (bundled edges typically don't have labels, but handle it)\n const labelCanvas = copyCanvas(graph.canvas)\n\n return [pathCanvas, boxStartCanvas, empty, empty, cornersCanvas, labelCanvas]\n}\n\n/**\n * Draw the shared path segment of a bundle (junction → target for fan-in,\n * source → junction for fan-out).\n */\nexport function drawBundleSharedPath(\n graph: AsciiGraph,\n bundle: EdgeBundle,\n): [Canvas, Canvas] {\n const pathCanvas = copyCanvas(graph.canvas)\n const cornersCanvas = copyCanvas(graph.canvas)\n\n if (bundle.sharedPath.length < 2) {\n return [pathCanvas, cornersCanvas]\n }\n\n const useAscii = graph.config.useAscii\n const style = bundle.edges[0]?.style ?? 'solid'\n const graphDir = graph.config.graphDirection\n\n // Convert grid coords to drawing coords\n // For fan-in: last point is at target node border\n // For fan-out: first point is at source node border\n const drawingPath = bundle.sharedPath.map((gc, idx) => {\n if (bundle.type === 'fan-in' && idx === bundle.sharedPath.length - 1) {\n // Last point: use target node's actual border position (entry from above/left)\n const entryDir = graphDir === 'TD' ? Up : Left\n return getNodeAttachmentPoint(graph, bundle.sharedNode, entryDir)\n }\n if (bundle.type === 'fan-out' && idx === 0) {\n // First point: use source node's actual border position (exit going down/right)\n const exitDir = graphDir === 'TD' ? Down : Right\n return getNodeAttachmentPoint(graph, bundle.sharedNode, exitDir)\n }\n return gridToDrawingCoord(graph, gc)\n })\n\n // Draw line segments with appropriate offsets\n for (let i = 1; i < drawingPath.length; i++) {\n const from = drawingPath[i - 1]!\n const to = drawingPath[i]!\n if (!drawingCoordEquals(from, to)) {\n // Always skip both endpoints (offset 1, -1), matching non-bundled drawPath.\n drawLine(pathCanvas, from, to, 1, -1, useAscii, style)\n }\n }\n\n // Draw corners at path bends\n for (let idx = 1; idx < bundle.sharedPath.length - 1; idx++) {\n const coord = bundle.sharedPath[idx]!\n const dc = gridToDrawingCoord(graph, coord)\n const prevDir = determineDirection(bundle.sharedPath[idx - 1]!, coord)\n const nextDir = determineDirection(coord, bundle.sharedPath[idx + 1]!)\n\n let corner: string\n if (!useAscii) {\n if (\n (dirEquals(prevDir, Right) && dirEquals(nextDir, Down)) ||\n (dirEquals(prevDir, Up) && dirEquals(nextDir, Left))\n ) {\n corner = '┐'\n } else if (\n (dirEquals(prevDir, Right) && dirEquals(nextDir, Up)) ||\n (dirEquals(prevDir, Down) && dirEquals(nextDir, Left))\n ) {\n corner = '┘'\n } else if (\n (dirEquals(prevDir, Left) && dirEquals(nextDir, Down)) ||\n (dirEquals(prevDir, Up) && dirEquals(nextDir, Right))\n ) {\n corner = '┌'\n } else if (\n (dirEquals(prevDir, Left) && dirEquals(nextDir, Up)) ||\n (dirEquals(prevDir, Down) && dirEquals(nextDir, Right))\n ) {\n corner = '└'\n } else {\n corner = '+'\n }\n } else {\n corner = '+'\n }\n\n write(cornersCanvas, dc.x, dc.y, corner)\n }\n\n return [pathCanvas, cornersCanvas]\n}\n\n/**\n * Draw the arrowhead for a fan-in bundle (single arrowhead at the shared target).\n */\nexport function drawBundleArrowhead(\n graph: AsciiGraph,\n bundle: EdgeBundle,\n): Canvas {\n const canvas = copyCanvas(graph.canvas)\n\n if (bundle.sharedPath.length < 2) return canvas\n\n // Get the last segment direction\n const lastIdx = bundle.sharedPath.length - 1\n const secondLast = bundle.sharedPath[lastIdx - 1]!\n const last = bundle.sharedPath[lastIdx]!\n const dir = determineDirection(secondLast, last)\n\n // Get drawing coord 1 char outside the target node's border (not on the border itself).\n // This matches non-bundled edges where drawPath uses offsetTo=-1 and the arrowhead\n // sits at the last drawn point (1 char before the border).\n const graphDir = graph.config.graphDirection\n const entryDir = graphDir === 'TD' ? Up : Left\n const dc = getNodeAttachmentPoint(graph, bundle.sharedNode, entryDir)\n // Offset 1 char away from the box border so arrowhead sits outside the box\n if (graphDir === 'TD') dc.y -= 1\n else dc.x -= 1\n\n // Draw arrowhead. A fan-in bundle merges several edges into one shared\n // trunk/junction, so there's only one arrowhead to draw for all of them —\n // it only gets a circle/cross glyph when every bundled edge agrees on the\n // same marker; a mix (e.g. one `-->`, one `--o`) falls back to the plain\n // directional arrowhead rather than guessing which edge's marker \"wins\".\n // See issue #330.\n const firstMarker = bundle.edges[0]?.endMarker\n const sharedMarker = bundle.edges.every((e) => e.endMarker === firstMarker)\n ? firstMarker\n : undefined\n const markerChar = markerArrowChar(graph.config.useAscii, sharedMarker)\n\n let char: string\n if (markerChar !== undefined) {\n char = markerChar\n } else if (!graph.config.useAscii) {\n if (dirEquals(dir, Up)) char = '▲'\n else if (dirEquals(dir, Down)) char = '▼'\n else if (dirEquals(dir, Left)) char = '◄'\n else if (dirEquals(dir, Right)) char = '►'\n else char = '▼' // default\n } else {\n if (dirEquals(dir, Up)) char = '^'\n else if (dirEquals(dir, Down)) char = 'v'\n else if (dirEquals(dir, Left)) char = '<'\n else if (dirEquals(dir, Right)) char = '>'\n else char = 'v' // default\n }\n\n write(canvas, dc.x, dc.y, char)\n return canvas\n}\n\n/**\n * Draw the arrowhead for a single edge in a fan-out bundle.\n */\nexport function drawBundledEdgeArrowhead(\n graph: AsciiGraph,\n edge: AsciiEdge,\n): Canvas {\n const canvas = copyCanvas(graph.canvas)\n\n if (!edge.pathToJunction || edge.pathToJunction.length < 2) return canvas\n\n // Get the last segment direction\n const lastIdx = edge.pathToJunction.length - 1\n const secondLast = edge.pathToJunction[lastIdx - 1]!\n const last = edge.pathToJunction[lastIdx]!\n const dir = determineDirection(secondLast, last)\n\n // Get drawing coord 1 char outside the target node's border\n const graphDir = graph.config.graphDirection\n const entryDir = graphDir === 'TD' ? Up : Left\n const dc = getNodeAttachmentPoint(graph, edge.to, entryDir)\n // Offset 1 char away from the box border so arrowhead sits outside the box\n if (graphDir === 'TD') dc.y -= 1\n else dc.x -= 1\n\n // Draw arrowhead — this is a single edge (unlike the fan-in case above),\n // so its own endMarker unambiguously picks the glyph. See issue #330.\n const markerChar = markerArrowChar(graph.config.useAscii, edge.endMarker)\n\n let char: string\n if (markerChar !== undefined) {\n char = markerChar\n } else if (!graph.config.useAscii) {\n if (dirEquals(dir, Up)) char = '▲'\n else if (dirEquals(dir, Down)) char = '▼'\n else if (dirEquals(dir, Left)) char = '◄'\n else if (dirEquals(dir, Right)) char = '►'\n else char = '▼' // default\n } else {\n if (dirEquals(dir, Up)) char = '^'\n else if (dirEquals(dir, Down)) char = 'v'\n else if (dirEquals(dir, Left)) char = '<'\n else if (dirEquals(dir, Right)) char = '>'\n else char = 'v' // default\n }\n\n write(canvas, dc.x, dc.y, char)\n return canvas\n}\n\n/** Reverse a cardinal direction (start arrowheads point back at the node they exit). */\nfunction reverseDirection(dir: Direction): Direction {\n if (dirEquals(dir, Up)) return Down\n if (dirEquals(dir, Down)) return Up\n if (dirEquals(dir, Left)) return Right\n if (dirEquals(dir, Right)) return Left\n return dir\n}\n\n/**\n * Draw the start-side arrowhead for a fan-out bundle (single arrowhead at\n * the shared source, mirroring drawBundleArrowhead's shared-target end).\n */\nexport function drawBundleArrowheadStart(\n graph: AsciiGraph,\n bundle: EdgeBundle,\n): Canvas {\n const canvas = copyCanvas(graph.canvas)\n\n if (bundle.sharedPath.length < 2) return canvas\n\n // Get the first segment direction (source -> next point) and reverse it:\n // the start arrowhead points back into the source, opposite the trunk's\n // direction of travel.\n const first = bundle.sharedPath[0]!\n const second = bundle.sharedPath[1]!\n const dir = reverseDirection(determineDirection(first, second))\n\n // Get drawing coord 1 char outside the source node's border (not on the\n // border itself), on the side the trunk exits from.\n const graphDir = graph.config.graphDirection\n const exitDir = graphDir === 'TD' ? Down : Right\n const dc = getNodeAttachmentPoint(graph, bundle.sharedNode, exitDir)\n // Offset 1 char away from the box border so arrowhead sits outside the box\n if (graphDir === 'TD') dc.y += 1\n else dc.x += 1\n\n // Draw arrowhead\n let char: string\n if (!graph.config.useAscii) {\n if (dirEquals(dir, Up)) char = '▲'\n else if (dirEquals(dir, Down)) char = '▼'\n else if (dirEquals(dir, Left)) char = '◄'\n else if (dirEquals(dir, Right)) char = '►'\n else char = '▲' // default\n } else {\n if (dirEquals(dir, Up)) char = '^'\n else if (dirEquals(dir, Down)) char = 'v'\n else if (dirEquals(dir, Left)) char = '<'\n else if (dirEquals(dir, Right)) char = '>'\n else char = '^' // default\n }\n\n write(canvas, dc.x, dc.y, char)\n return canvas\n}\n\n/**\n * Draw the start-side arrowhead for a single edge in a fan-in bundle\n * (mirroring drawBundledEdgeArrowhead's per-edge target end).\n */\nexport function drawBundledEdgeArrowheadStart(\n graph: AsciiGraph,\n edge: AsciiEdge,\n): Canvas {\n const canvas = copyCanvas(graph.canvas)\n\n if (!edge.pathToJunction || edge.pathToJunction.length < 2) return canvas\n\n // Get the first segment direction and reverse it: the start arrowhead\n // points back into the edge's own source, opposite its direction of travel.\n const first = edge.pathToJunction[0]!\n const second = edge.pathToJunction[1]!\n const dir = reverseDirection(determineDirection(first, second))\n\n // Get drawing coord 1 char outside the source node's border, on the side\n // this edge actually exits from.\n const dc = getNodeAttachmentPoint(graph, edge.from, edge.startDir)\n // Offset 1 char away from the box border so arrowhead sits outside the box\n if (dirEquals(edge.startDir, Up)) dc.y -= 1\n else if (dirEquals(edge.startDir, Down)) dc.y += 1\n else if (dirEquals(edge.startDir, Left)) dc.x -= 1\n else if (dirEquals(edge.startDir, Right)) dc.x += 1\n\n // Draw arrowhead\n let char: string\n if (!graph.config.useAscii) {\n if (dirEquals(dir, Up)) char = '▲'\n else if (dirEquals(dir, Down)) char = '▼'\n else if (dirEquals(dir, Left)) char = '◄'\n else if (dirEquals(dir, Right)) char = '►'\n else char = '▼' // default\n } else {\n if (dirEquals(dir, Up)) char = '^'\n else if (dirEquals(dir, Down)) char = 'v'\n else if (dirEquals(dir, Left)) char = '<'\n else if (dirEquals(dir, Right)) char = '>'\n else char = 'v' // default\n }\n\n write(canvas, dc.x, dc.y, char)\n return canvas\n}\n\n/**\n * Draw the junction character where bundled edges merge/split.\n *\n * Analyzes actual connecting directions to choose the correct character:\n * - ┼ (cross): lines from all 4 directions\n * - ┬ (T down): lines from left, right, and down\n * - ┴ (T up): lines from left, right, and up\n * - ├ (T right): lines from up, down, and right\n * - ┤ (T left): lines from up, down, and left\n */\nexport function drawJunctionCharacter(\n graph: AsciiGraph,\n bundle: EdgeBundle,\n): Canvas {\n const canvas = copyCanvas(graph.canvas)\n\n if (!bundle.junctionPoint) return canvas\n\n const dc = gridToDrawingCoord(graph, bundle.junctionPoint)\n const useAscii = graph.config.useAscii\n\n // Analyze what directions actually connect to the junction\n let hasUp = false\n let hasDown = false\n let hasLeft = false\n let hasRight = false\n\n // Check shared path direction (where the line continues to/from the shared node)\n if (bundle.sharedPath.length >= 2) {\n // For fan-in: shared path goes FROM junction TO target (index 0 is junction)\n // For fan-out: shared path goes FROM source TO junction (last index is junction)\n const junctionIdx =\n bundle.type === 'fan-in' ? 0 : bundle.sharedPath.length - 1\n const adjacentIdx =\n bundle.type === 'fan-in' ? 1 : bundle.sharedPath.length - 2\n const sharedDir = determineDirection(\n bundle.sharedPath[junctionIdx]!,\n bundle.sharedPath[adjacentIdx]!,\n )\n // This is the direction the shared path GOES from junction\n if (dirEquals(sharedDir, Down)) hasDown = true\n else if (dirEquals(sharedDir, Up)) hasUp = true\n else if (dirEquals(sharedDir, Right)) hasRight = true\n else if (dirEquals(sharedDir, Left)) hasLeft = true\n }\n\n // Check each edge's path direction at the junction\n for (const edge of bundle.edges) {\n if (edge.pathToJunction && edge.pathToJunction.length >= 2) {\n // For fan-in: pathToJunction goes FROM source TO junction (last is junction)\n // For fan-out: pathToJunction goes FROM junction TO target (first is junction)\n const junctionIdx =\n bundle.type === 'fan-in' ? edge.pathToJunction.length - 1 : 0\n const adjacentIdx =\n bundle.type === 'fan-in' ? edge.pathToJunction.length - 2 : 1\n\n const arrivalDir = determineDirection(\n edge.pathToJunction[adjacentIdx]!,\n edge.pathToJunction[junctionIdx]!,\n )\n // This is the direction the edge ARRIVES at junction from\n // e.g., if arrivalDir is Right, the line comes FROM the left\n if (dirEquals(arrivalDir, Down))\n hasUp = true // arrived going down = came from up\n else if (dirEquals(arrivalDir, Up)) hasDown = true\n else if (dirEquals(arrivalDir, Right)) hasLeft = true\n else if (dirEquals(arrivalDir, Left)) hasRight = true\n }\n }\n\n // Select character based on connected directions\n let char: string\n if (!useAscii) {\n if (hasUp && hasDown && hasLeft && hasRight) {\n char = '┼' // cross - all 4 directions\n } else if (hasDown && hasLeft && hasRight && !hasUp) {\n char = '┬' // T pointing down\n } else if (hasUp && hasLeft && hasRight && !hasDown) {\n char = '┴' // T pointing up\n } else if (hasUp && hasDown && hasRight && !hasLeft) {\n char = '├' // T pointing right\n } else if (hasUp && hasDown && hasLeft && !hasRight) {\n char = '┤' // T pointing left\n } else if (hasLeft && hasRight) {\n char = '─' // horizontal only\n } else if (hasUp && hasDown) {\n char = '│' // vertical only\n } else if (hasDown && hasRight) {\n char = '┌' // corner\n } else if (hasDown && hasLeft) {\n char = '┐'\n } else if (hasUp && hasRight) {\n char = '└'\n } else if (hasUp && hasLeft) {\n char = '┘'\n } else {\n char = '┼' // fallback\n }\n } else {\n char = '+'\n }\n\n write(canvas, dc.x, dc.y, char)\n return canvas\n}\n","// ============================================================================\n// ASCII renderer — subgraph drawing (border rectangle + label)\n//\n// Split out of draw.ts.\n// ============================================================================\n\nimport type {\n Canvas,\n DrawingCoord,\n AsciiGraph,\n AsciiSubgraph,\n} from './types.ts'\nimport { mkCanvas, write } from './canvas.ts'\nimport { splitLines } from './multiline-utils.ts'\nimport { displayWidth, toDisplayCells } from './display-width.ts'\n\n/** Draw a subgraph border rectangle. */\nexport function drawSubgraphBox(sg: AsciiSubgraph, graph: AsciiGraph): Canvas {\n const width = sg.maxX - sg.minX\n const height = sg.maxY - sg.minY\n if (width <= 0 || height <= 0) return mkCanvas(0, 0)\n\n const from: DrawingCoord = { x: 0, y: 0 }\n const to: DrawingCoord = { x: width, y: height }\n const canvas = mkCanvas(width, height)\n\n if (!graph.config.useAscii) {\n for (let x = from.x + 1; x < to.x; x++) write(canvas, x, from.y, '─')\n for (let x = from.x + 1; x < to.x; x++) write(canvas, x, to.y, '─')\n for (let y = from.y + 1; y < to.y; y++) write(canvas, from.x, y, '│')\n for (let y = from.y + 1; y < to.y; y++) write(canvas, to.x, y, '│')\n write(canvas, from.x, from.y, '┌')\n write(canvas, to.x, from.y, '┐')\n write(canvas, from.x, to.y, '└')\n write(canvas, to.x, to.y, '┘')\n } else {\n for (let x = from.x + 1; x < to.x; x++) write(canvas, x, from.y, '-')\n for (let x = from.x + 1; x < to.x; x++) write(canvas, x, to.y, '-')\n for (let y = from.y + 1; y < to.y; y++) write(canvas, from.x, y, '|')\n for (let y = from.y + 1; y < to.y; y++) write(canvas, to.x, y, '|')\n write(canvas, from.x, from.y, '+')\n write(canvas, to.x, from.y, '+')\n write(canvas, from.x, to.y, '+')\n write(canvas, to.x, to.y, '+')\n }\n\n return canvas\n}\n\n/**\n * A single cell of a subgraph title's own footprint — including cells whose\n * character is a literal space (e.g. the space inside \"US West Region\").\n * Callers use this to force those cells to win over whatever was already\n * drawn underneath (see the caller in `draw.ts` for why: `mergeCanvases`\n * treats overlay spaces as transparent, which lets a connector line drawn\n * earlier show through a label's internal spaces — issue #447).\n */\nexport interface SubgraphLabelCell {\n x: number\n y: number\n}\n\n/** Draw a subgraph label centered in its header area. Supports multi-line labels. */\n// `_graph` isn't read here but is kept to match the `(sg, graph)` signature\n// shared by the other `draw*` subgraph helpers in this file.\nexport function drawSubgraphLabel(\n sg: AsciiSubgraph,\n _graph: AsciiGraph,\n): [Canvas, DrawingCoord, SubgraphLabelCell[]] {\n const width = sg.maxX - sg.minX\n const height = sg.maxY - sg.minY\n if (width <= 0 || height <= 0) return [mkCanvas(0, 0), { x: 0, y: 0 }, []]\n\n const canvas = mkCanvas(width, height)\n const footprint: SubgraphLabelCell[] = []\n\n // Support multi-line subgraph labels\n const lines = splitLines(sg.name)\n\n // Start at row 1 inside subgraph, expand downward for multiple lines\n for (let i = 0; i < lines.length; i++) {\n const line = lines[i]!\n const labelY = 1 + i\n // Center the label within the interior columns (1..width-1, i.e.\n // excluding both border columns). When the interior width minus the\n // label width is odd, the leftover column can't be split evenly — bias\n // it to the right (keep >=1 space on the left) so the title never hugs\n // the left border. Using floor(width/2) - floor(label/2) here instead\n // biases the leftover to the left, which can zero out the left padding\n // entirely for even-length labels in an odd-width interior.\n let labelX = 1 + Math.ceil((width - 1 - displayWidth(line)) / 2)\n if (labelX < 1) labelX = 1\n\n // Unlike `write()`'s own bounds (inclusive of the canvas edge — correct\n // for the border-drawing calls in `drawSubgraphBox` above, which write\n // the border itself at x == width / y == height), the label must stay\n // strictly inside the border, so this loop enforces the tighter,\n // exclusive bound itself rather than relying on `write()`'s clip.\n const cells = toDisplayCells(line)\n for (let j = 0; j < cells.length; j++) {\n if (labelX + j >= width || labelY >= height) continue\n write(canvas, labelX + j, labelY, cells[j]!)\n footprint.push({ x: labelX + j, y: labelY })\n }\n }\n\n return [canvas, { x: sg.minX, y: sg.minY }, footprint]\n}\n","// ============================================================================\n// ASCII renderer — node/box drawing\n//\n// Split out of draw.ts. Renders individual node shapes and the\n// multi-section boxes used by class/ER diagram nodes.\n// ============================================================================\n\nimport type {\n Canvas,\n DrawingCoord,\n AsciiGraph,\n AsciiNode,\n CharRole,\n} from './types.ts'\nimport { requireGridCoord } from './types.ts'\nimport { mkCanvas, write } from './canvas.ts'\nimport { splitLines } from './multiline-utils.ts'\nimport { getCorners } from './shapes/corners.ts'\nimport { displayWidth, toDisplayCells } from './display-width.ts'\n\n// ============================================================================\n// Node drawing — renders a node using shape-aware rendering\n// ============================================================================\n\n/**\n * Draw a node using its shape type.\n * Returns a standalone canvas containing the rendered shape.\n *\n * For basic shapes (rectangle, rounded), uses grid-determined dimensions\n * to ensure consistent sizing across nodes in the same column.\n * For special shapes (diamond, circle, state pseudo-states, etc.),\n * uses shape-specific dimension calculation but centers the content\n * within the grid cell dimensions to ensure proper vertical alignment.\n */\nexport function drawNode(node: AsciiNode, graph: AsciiGraph): Canvas {\n // All shapes use grid-determined dimensions to fill their allocated space.\n // This ensures consistent sizing across nodes and eliminates gaps between\n // nodes and subgraph borders. All shapes are rectangles with distinctive\n // corner characters (defined in corners.ts) to indicate shape type.\n return drawBoxWithGridDimensions(node, graph)\n}\n\n/**\n * Draw a box shape using grid-determined dimensions.\n * This ensures consistent sizing when multiple nodes share a column,\n * and eliminates gaps between nodes and subgraph borders by filling\n * the entire allocated grid space.\n *\n * All shapes are rendered as rectangles with distinctive corner characters\n * (defined in corners.ts) to indicate shape type.\n */\nfunction drawBoxWithGridDimensions(node: AsciiNode, graph: AsciiGraph): Canvas {\n // Every node is guaranteed a gridCoord before drawing starts —\n // createMapping (grid.ts) seeds even nodes in otherwise-unreachable\n // cycles with a pseudo-root (see addPseudoRootsForUnreachableCycles)\n // specifically so every node gets placed. requireGridCoord validates that\n // invariant explicitly rather than trusting it silently across the module\n // boundary, since the type checker can't see it (`gridCoord: GridCoord |\n // null`).\n const gc = requireGridCoord(node)\n const useAscii = graph.config.useAscii\n\n // Width spans 2 columns (border + content) - matching original behavior\n let w = 0\n for (let i = 0; i < 2; i++) {\n w += graph.columnWidth.get(gc.x + i) ?? 0\n }\n // Height spans 2 rows (border + content)\n let h = 0\n for (let i = 0; i < 2; i++) {\n h += graph.rowHeight.get(gc.y + i) ?? 0\n }\n\n const from: DrawingCoord = { x: 0, y: 0 }\n const to: DrawingCoord = { x: w, y: h }\n const box = mkCanvas(Math.max(from.x, to.x), Math.max(from.y, to.y))\n\n // Get corner characters for this shape type\n const corners = getCorners(node.shape, useAscii)\n\n // State-end uses double border to differentiate from state-start\n const isDoubleBox = node.shape === 'state-end'\n const hChar = useAscii ? (isDoubleBox ? '=' : '-') : isDoubleBox ? '═' : '─'\n const vChar = useAscii ? (isDoubleBox ? '‖' : '|') : isDoubleBox ? '║' : '│'\n\n // Double-box corners (for state-end)\n const doubleCorners = useAscii\n ? { tl: '#', tr: '#', bl: '#', br: '#' }\n : { tl: '╔', tr: '╗', bl: '╚', br: '╝' }\n const effectiveCorners = isDoubleBox ? doubleCorners : corners\n\n // Draw box border with shape-specific corners\n for (let x = from.x + 1; x < to.x; x++) write(box, x, from.y, hChar)\n for (let x = from.x + 1; x < to.x; x++) write(box, x, to.y, hChar)\n for (let y = from.y + 1; y < to.y; y++) write(box, from.x, y, vChar)\n for (let y = from.y + 1; y < to.y; y++) write(box, to.x, y, vChar)\n write(box, from.x, from.y, effectiveCorners.tl)\n write(box, to.x, from.y, effectiveCorners.tr)\n write(box, from.x, to.y, effectiveCorners.bl)\n write(box, to.x, to.y, effectiveCorners.br)\n\n // Center the multi-line display label inside the box\n const label = node.displayLabel\n const lines = splitLines(label)\n const textCenterY = from.y + Math.floor(h / 2)\n const startY = textCenterY - Math.floor((lines.length - 1) / 2)\n\n for (let i = 0; i < lines.length; i++) {\n const line = lines[i]!\n const lineWidth = displayWidth(line)\n const textX = from.x + Math.floor(w / 2) - Math.ceil(lineWidth / 2) + 1\n const cells = toDisplayCells(line)\n for (let j = 0; j < cells.length; j++) {\n write(box, textX + j, startY + i, cells[j]!)\n }\n }\n\n return box\n}\n\n/**\n * Draw a node box with centered label text.\n * Returns a standalone canvas containing just the box.\n * Box size is determined by the grid column/row sizes for the node's position.\n */\nexport function drawBox(node: AsciiNode, graph: AsciiGraph): Canvas {\n return drawNode(node, graph)\n}\n\n// ============================================================================\n// Multi-section box drawing — for class and ER diagram nodes\n// ============================================================================\n\n/**\n * Classify a character from a multi-section box drawing as 'border' or 'text'.\n * Shared by the class and ER diagram ASCII renderers, which both copy a\n * `drawMultiBox` canvas onto their main canvas and need to tag each\n * non-blank cell with its role for colored output.\n */\nexport function classifyBoxChar(ch: string): CharRole {\n if (/^[┌┐└┘├┤┬┴┼│─╭╮╰╯+\\-|]$/.test(ch)) return 'border'\n return 'text'\n}\n\n/**\n * Outer dimensions, in grid cells, of the box `drawMultiBox` would draw for\n * `sections`.\n *\n * Class and ER diagrams both need these dimensions *before* drawing, to\n * reserve grid space for the box during layout. Deriving them here — rather\n * than each caller re-implementing the same arithmetic — keeps the reserved\n * space and the drawn box in lockstep. When the two disagree (as they did\n * while the callers measured `line.length` and this module measured display\n * width), wide-character boxes overlap their neighbours.\n */\nexport function measureMultiBox(\n sections: string[][],\n padding: number = 1,\n): { width: number; height: number } {\n // A caller-supplied padding (ultimately AsciiConfig.boxBorderPadding, which\n // reaches here unclamped from the public renderMermaidASCII options) could\n // be negative — clamp rather than let it produce a negative box width for\n // a short label (see issue #343's CodeRabbit review).\n const clampedPadding = Math.max(0, padding)\n // Width: widest line across all sections + 2*padding + 2 border chars.\n // displayWidth (not line.length) because CJK/fullwidth glyphs occupy two\n // terminal columns each, so a code-unit count under-measures them.\n let maxTextWidth = 0\n for (const section of sections) {\n for (const line of section) {\n maxTextWidth = Math.max(maxTextWidth, displayWidth(line))\n }\n }\n\n // Height: sum of all section line counts + dividers + 2 border rows\n let totalLines = 0\n for (const section of sections) {\n totalLines += Math.max(section.length, 1) // at least 1 row per section\n }\n\n return {\n width: maxTextWidth + 2 * clampedPadding + 2,\n height: totalLines + (sections.length - 1) + 2,\n }\n}\n\n/**\n * Draw a multi-section box with horizontal dividers between sections.\n * Used by class diagrams (header | attributes | methods) and ER diagrams (header | attributes).\n * Each section is an array of text lines to render left-aligned with padding.\n *\n * @param sections - Array of sections, each section is an array of text lines\n * @param useAscii - true for ASCII chars, false for Unicode box-drawing\n * @param padding - horizontal padding inside the box (default 1)\n * @returns A standalone Canvas containing the multi-section box\n */\nexport function drawMultiBox(\n sections: string[][],\n useAscii: boolean,\n padding: number = 1,\n): Canvas {\n // Clamp here too (not just inside measureMultiBox) since `padding` is also\n // used directly below for each line's startX — see the comment in\n // measureMultiBox for why a negative value can reach this function.\n const clampedPadding = Math.max(0, padding)\n const { width: boxWidth, height: boxHeight } = measureMultiBox(\n sections,\n clampedPadding,\n )\n\n // Box-drawing characters\n const hLine = useAscii ? '-' : '─'\n const vLine = useAscii ? '|' : '│'\n const tl = useAscii ? '+' : '┌'\n const tr = useAscii ? '+' : '┐'\n const bl = useAscii ? '+' : '└'\n const br = useAscii ? '+' : '┘'\n const divL = useAscii ? '+' : '├'\n const divR = useAscii ? '+' : '┤'\n\n const canvas = mkCanvas(boxWidth - 1, boxHeight - 1)\n\n // Top border\n write(canvas, 0, 0, tl)\n for (let x = 1; x < boxWidth - 1; x++) write(canvas, x, 0, hLine)\n write(canvas, boxWidth - 1, 0, tr)\n\n // Bottom border\n write(canvas, 0, boxHeight - 1, bl)\n for (let x = 1; x < boxWidth - 1; x++) write(canvas, x, boxHeight - 1, hLine)\n write(canvas, boxWidth - 1, boxHeight - 1, br)\n\n // Left and right borders (full height)\n for (let y = 1; y < boxHeight - 1; y++) {\n write(canvas, 0, y, vLine)\n write(canvas, boxWidth - 1, y, vLine)\n }\n\n // Render sections with dividers\n let row = 1 // current y position (starts after top border)\n for (let s = 0; s < sections.length; s++) {\n const section = sections[s]!\n const lines = section.length > 0 ? section : ['']\n\n // Draw section text lines. toDisplayCells (not line[i]) so a wide glyph\n // claims the two grid cells it renders across, and a surrogate pair is\n // never split across two cells.\n for (const line of lines) {\n const startX = 1 + clampedPadding\n const cells = toDisplayCells(line)\n for (let i = 0; i < cells.length; i++) {\n write(canvas, startX + i, row, cells[i]!)\n }\n row++\n }\n\n // Draw divider after each section except the last\n if (s < sections.length - 1) {\n write(canvas, 0, row, divL)\n for (let x = 1; x < boxWidth - 1; x++) write(canvas, x, row, hLine)\n write(canvas, boxWidth - 1, row, divR)\n row++\n }\n }\n\n return canvas\n}\n","// ============================================================================\n// ASCII renderer — drawing operations\n//\n// Ported from AlexanderGrooff/mermaid-ascii cmd/draw.go + cmd/arrow.go.\n// Contains the top-level draw orchestrator (drawGraph) that layers node,\n// edge, bundle, and subgraph canvases together, plus the character-role\n// tracking used for colored output.\n//\n// The individual drawing operations live in sibling modules, split out for\n// navigability:\n// - draw-boxes.ts node/box drawing, multi-section boxes\n// - draw-lines.ts 8-directional line drawing\n// - draw-arrows.ts arrow paths, corners, arrowheads, labels\n// - draw-bundles.ts bundled (fan-in/fan-out) edge drawing\n// - draw-subgraphs.ts subgraph border + label drawing\n// This file re-exports their public entry points so `./draw.ts` remains a\n// stable import path for the rest of the ASCII renderer.\n// ============================================================================\n\nimport type {\n Canvas,\n DrawingCoord,\n AsciiGraph,\n AsciiSubgraph,\n EdgeBundle,\n} from './types.ts'\nimport { mergeCanvases, firstClaimWins, write } from './canvas.ts'\nimport type { RoleCanvas, CharRole } from './types.ts'\nimport { setRole } from './canvas.ts'\nimport { drawArrow } from './draw-arrows.ts'\nimport {\n drawBundledEdgeSegment,\n drawBundleSharedPath,\n drawBundleArrowhead,\n drawBundleArrowheadStart,\n drawBundledEdgeArrowhead,\n drawBundledEdgeArrowheadStart,\n drawJunctionCharacter,\n} from './draw-bundles.ts'\nimport { drawSubgraphBox, drawSubgraphLabel } from './draw-subgraphs.ts'\n\nexport {\n drawNode,\n drawBox,\n drawMultiBox,\n measureMultiBox,\n classifyBoxChar,\n} from './draw-boxes.ts'\nexport { drawLine } from './draw-lines.ts'\nexport { drawArrow, drawSubgraphBox, drawSubgraphLabel }\n\n// ============================================================================\n// Top-level draw orchestrator\n// ============================================================================\n\n/** Sort subgraphs by nesting depth (shallowest first) for correct layered rendering. */\nfunction sortSubgraphsByDepth(subgraphs: AsciiSubgraph[]): AsciiSubgraph[] {\n function getDepth(sg: AsciiSubgraph): number {\n return sg.parent === null ? 0 : 1 + getDepth(sg.parent)\n }\n const sorted = [...subgraphs]\n sorted.sort((a, b) => getDepth(a) - getDepth(b))\n return sorted\n}\n\n// ============================================================================\n// Role tracking helpers for colored output\n// ============================================================================\n\n/**\n * Fill roles for all non-space characters in a canvas region.\n * Used after drawing a layer to record what role those characters have.\n */\nfunction fillRolesFromCanvas(\n roleCanvas: RoleCanvas,\n canvas: Canvas,\n offset: DrawingCoord,\n role: CharRole,\n): void {\n for (let x = 0; x < canvas.length; x++) {\n for (let y = 0; y < (canvas[0]?.length ?? 0); y++) {\n const char = canvas[x]?.[y]\n if (char && char !== ' ') {\n const rx = x + offset.x\n const ry = y + offset.y\n // Use setRole which auto-expands the role canvas if needed\n if (rx >= 0 && ry >= 0) {\n setRole(roleCanvas, rx, ry, role)\n }\n }\n }\n }\n}\n\n/**\n * Fill roles for multiple canvases with the same role.\n */\nfunction fillRolesFromCanvases(\n roleCanvas: RoleCanvas,\n canvases: Canvas[],\n offset: DrawingCoord,\n role: CharRole,\n): void {\n for (const canvas of canvases) {\n fillRolesFromCanvas(roleCanvas, canvas, offset, role)\n }\n}\n\n/**\n * Special handling for node boxes: border chars get 'border' role, text gets 'text' role.\n * Detects text by checking if character is alphanumeric or common punctuation.\n */\nfunction fillRolesForNodeBox(\n roleCanvas: RoleCanvas,\n canvas: Canvas,\n offset: DrawingCoord,\n): void {\n const isBorderChar = (c: string) => /^[┌┐└┘├┤┬┴┼│─╭╮╰╯+\\-|.':]$/.test(c)\n\n for (let x = 0; x < canvas.length; x++) {\n for (let y = 0; y < (canvas[0]?.length ?? 0); y++) {\n const char = canvas[x]?.[y]\n if (char && char !== ' ') {\n const rx = x + offset.x\n const ry = y + offset.y\n // Use setRole which auto-expands the role canvas if needed\n if (rx >= 0 && ry >= 0) {\n setRole(roleCanvas, rx, ry, isBorderChar(char) ? 'border' : 'text')\n }\n }\n }\n }\n}\n\n/**\n * Main draw function — renders the entire graph onto the canvas.\n * Drawing order matters for correct layering:\n * 1. Subgraph borders (bottom layer)\n * 2. Node boxes\n * 3. Edge paths (lines)\n * 4. Edge corners\n * 5. Arrowheads\n * 6. Box-start junctions\n * 7. Edge labels\n * 8. Subgraph labels (top layer)\n *\n * Also fills the roleCanvas with character roles for colored output.\n */\nexport function drawGraph(graph: AsciiGraph): Canvas {\n const useAscii = graph.config.useAscii\n const zero: DrawingCoord = { x: 0, y: 0 }\n\n // Draw subgraph borders\n const sortedSgs = sortSubgraphsByDepth(graph.subgraphs)\n for (const sg of sortedSgs) {\n const sgCanvas = drawSubgraphBox(sg, graph)\n const offset: DrawingCoord = { x: sg.minX, y: sg.minY }\n graph.canvas = mergeCanvases(graph.canvas, offset, useAscii, sgCanvas)\n // Subgraph borders get 'border' role\n fillRolesFromCanvas(graph.roleCanvas, sgCanvas, offset, 'border')\n }\n\n // Draw node boxes\n for (const node of graph.nodes) {\n if (!node.drawn && node.drawingCoord && node.drawing) {\n graph.canvas = mergeCanvases(\n graph.canvas,\n node.drawingCoord,\n useAscii,\n node.drawing,\n )\n // Node boxes: detect border vs text characters\n fillRolesForNodeBox(graph.roleCanvas, node.drawing, node.drawingCoord)\n node.drawn = true\n }\n }\n\n // Collect all edge drawing layers\n const lineCanvases: Canvas[] = []\n const cornerCanvases: Canvas[] = []\n const arrowHeadEndCanvases: Canvas[] = []\n const arrowHeadStartCanvases: Canvas[] = []\n const boxStartCanvases: Canvas[] = []\n const labelCanvases: Canvas[] = []\n const junctionCanvases: Canvas[] = []\n\n // Track which bundles have been processed (to draw shared paths only once)\n const processedBundles = new Set<EdgeBundle>()\n\n for (const edge of graph.edges) {\n // Handle bundled edges specially\n if (edge.bundle && edge.pathToJunction) {\n const bundle = edge.bundle\n\n // Draw this edge's individual path (source → junction for fan-in, junction → target for fan-out)\n const [pathC, boxStartC, , , cornersC, labelC] = drawBundledEdgeSegment(\n graph,\n edge,\n bundle,\n )\n lineCanvases.push(pathC)\n cornerCanvases.push(cornersC)\n boxStartCanvases.push(boxStartC)\n labelCanvases.push(labelC)\n\n // For fan-in bundles, draw a start arrowhead at each individual source\n if (bundle.type === 'fan-in' && edge.hasArrowStart) {\n const arrowHeadStartC = drawBundledEdgeArrowheadStart(graph, edge)\n arrowHeadStartCanvases.push(arrowHeadStartC)\n }\n\n // Draw the bundle's shared path and arrowhead only once\n if (!processedBundles.has(bundle)) {\n processedBundles.add(bundle)\n\n // Draw shared path (junction → target for fan-in, source → junction for fan-out)\n const [sharedPathC, sharedCornersC] = drawBundleSharedPath(\n graph,\n bundle,\n )\n lineCanvases.push(sharedPathC)\n cornerCanvases.push(sharedCornersC)\n\n // Draw arrowhead at target for fan-in (once for all edges in bundle)\n if (bundle.type === 'fan-in') {\n const arrowHeadC = drawBundleArrowhead(graph, bundle)\n arrowHeadEndCanvases.push(arrowHeadC)\n }\n\n // Draw start arrowhead at the shared source for fan-out (once for all\n // edges in the bundle — the trunk leaving the source is drawn once,\n // so it only gets a start arrowhead when every edge agrees on one).\n if (\n bundle.type === 'fan-out' &&\n bundle.edges.every((e) => e.hasArrowStart)\n ) {\n const arrowHeadStartC = drawBundleArrowheadStart(graph, bundle)\n arrowHeadStartCanvases.push(arrowHeadStartC)\n }\n\n // Draw junction character\n const junctionC = drawJunctionCharacter(graph, bundle)\n junctionCanvases.push(junctionC)\n }\n\n // For fan-out bundles, draw arrowhead at each target\n if (bundle.type === 'fan-out' && edge.hasArrowEnd) {\n const arrowHeadC = drawBundledEdgeArrowhead(graph, edge)\n arrowHeadEndCanvases.push(arrowHeadC)\n }\n } else {\n // Non-bundled edge: use standard drawing\n const [\n pathC,\n boxStartC,\n arrowHeadEndC,\n arrowHeadStartC,\n cornersC,\n labelC,\n ] = drawArrow(graph, edge)\n lineCanvases.push(pathC)\n cornerCanvases.push(cornersC)\n arrowHeadEndCanvases.push(arrowHeadEndC)\n arrowHeadStartCanvases.push(arrowHeadStartC)\n boxStartCanvases.push(boxStartC)\n labelCanvases.push(labelC)\n }\n }\n\n // Merge edge layers in order and track roles\n // Note: arrowHeadStart is merged AFTER boxStart so bidirectional arrows\n // properly overwrite the box connector at the source end\n //\n // `firstClaimWins` resolves any leftover cross-style overlap between two\n // edges' own line canvases (see its doc) before they're merged onto the\n // shared canvas — applied here, and not to the other edge layers below,\n // because only the line layer has this \"first claim wins\" rule; corners,\n // arrowheads, box-start connectors and labels keep mergeCanvases' normal\n // last-overlay-wins behavior.\n const resolvedLineCanvases = firstClaimWins(lineCanvases)\n graph.canvas = mergeCanvases(\n graph.canvas,\n zero,\n useAscii,\n ...resolvedLineCanvases,\n )\n fillRolesFromCanvases(graph.roleCanvas, resolvedLineCanvases, zero, 'line')\n\n graph.canvas = mergeCanvases(graph.canvas, zero, useAscii, ...cornerCanvases)\n fillRolesFromCanvases(graph.roleCanvas, cornerCanvases, zero, 'corner')\n\n graph.canvas = mergeCanvases(\n graph.canvas,\n zero,\n useAscii,\n ...junctionCanvases,\n )\n fillRolesFromCanvases(graph.roleCanvas, junctionCanvases, zero, 'junction')\n\n graph.canvas = mergeCanvases(\n graph.canvas,\n zero,\n useAscii,\n ...arrowHeadEndCanvases,\n )\n fillRolesFromCanvases(graph.roleCanvas, arrowHeadEndCanvases, zero, 'arrow')\n\n graph.canvas = mergeCanvases(\n graph.canvas,\n zero,\n useAscii,\n ...boxStartCanvases,\n )\n fillRolesFromCanvases(graph.roleCanvas, boxStartCanvases, zero, 'junction')\n\n graph.canvas = mergeCanvases(\n graph.canvas,\n zero,\n useAscii,\n ...arrowHeadStartCanvases,\n )\n fillRolesFromCanvases(graph.roleCanvas, arrowHeadStartCanvases, zero, 'arrow')\n\n graph.canvas = mergeCanvases(graph.canvas, zero, useAscii, ...labelCanvases)\n fillRolesFromCanvases(graph.roleCanvas, labelCanvases, zero, 'text')\n\n // Draw subgraph labels last (on top)\n for (const sg of graph.subgraphs) {\n if (sg.nodes.length === 0) continue\n const [labelCanvas, offset, footprint] = drawSubgraphLabel(sg, graph)\n graph.canvas = mergeCanvases(graph.canvas, offset, useAscii, labelCanvas)\n fillRolesFromCanvas(graph.roleCanvas, labelCanvas, offset, 'text')\n // mergeCanvases treats an overlay's space characters as transparent (so\n // sparse edge/arrow layers don't blank each other out) — but the title's\n // own footprint should win outright, including cells whose character is\n // a literal space (e.g. \"US West Region\"). Otherwise a connector line\n // already drawn at that cell shows through the label's internal space\n // (issue #447: rendered as \"US West│Region\"). Force those specific\n // cells, bypassing mergeCanvases's space-skip.\n for (const { x, y } of footprint) {\n const ch = labelCanvas[x]?.[y]\n if (ch === ' ') {\n write(graph.canvas, x + offset.x, y + offset.y, ch)\n setRole(graph.roleCanvas, x + offset.x, y + offset.y, 'text')\n }\n }\n }\n\n return graph.canvas\n}\n","// ============================================================================\n// ASCII renderers — shared \"nearest free lane\" search\n//\n// `er-diagram.ts` (`chooseFreeRow`) and `class-diagram.ts`\n// (`findClearColumn`) independently grew the same search: start at a\n// preferred row/column, and if that one is occupied, walk outward one step\n// at a time — forward first, then backward — until a candidate passes the\n// caller's occupancy check.\n//\n// Only the *search order* is shared here. The occupancy check itself stays\n// with each renderer and arrives as a callback, because the two renderers\n// answer \"is this lane usable?\" from genuinely different sources: ER scans\n// the rendered canvas itself, while class-diagram tests against the placed\n// box rectangles. Unifying that *storage* was scoped out as unsafe — see\n// `docs/decisions/ascii-occupancy-unification-532.md` (issue #532) — so this\n// module deliberately extracts the algorithm and nothing else.\n// ============================================================================\n\n/**\n * Caller-supplied occupancy check: whether the whole region a candidate\n * lane would occupy is usable. Called at most once per candidate, and only\n * for candidates already inside `[min, max]` (plus `preferred`, see below),\n * so an expensive check isn't run on lanes that would be rejected anyway.\n */\nexport type LaneFree = (candidate: number) => boolean\n\n/**\n * Find the nearest usable lane (a row or a column) to `preferred`, scanning\n * outward in alternating directions and stopping at the first candidate\n * `isFree` accepts.\n *\n * Search order: `preferred`, then `preferred + 1`, `preferred - 1`,\n * `preferred + 2`, `preferred - 2`, … — the forward direction is always\n * tried first at each distance, which is what both original call sites did\n * (ER scanned below-then-above; class-diagram right-then-left).\n *\n * `min`/`max` are *inclusive* bounds on the outward candidates only.\n * `preferred` itself is tested unconditionally, even when it falls outside\n * them: both call sites relied on that. ER's bounds are the open interval\n * between two entity borders, so a gap too narrow to hold any candidate row\n * still lets the plain midpoint through; class-diagram's `min` of 0 keeps\n * the search on-canvas while `preferred` is a box's own centre column.\n *\n * Returns `undefined` when no candidate in range is free — including when\n * `min > max` leaves no candidates at all. Each caller supplies its own\n * fallback for that case (they differ), rather than this function inventing\n * one.\n */\nexport function findFreeLane(\n preferred: number,\n min: number,\n max: number,\n isFree: LaneFree,\n): number | undefined {\n if (isFree(preferred)) return preferred\n\n // Far enough to reach whichever bound is further from `preferred`; beyond\n // that every remaining candidate is out of range in both directions.\n const maxOffset = Math.max(preferred - min, max - preferred)\n for (let offset = 1; offset <= maxOffset; offset++) {\n const forward = preferred + offset\n if (forward <= max && isFree(forward)) return forward\n const backward = preferred - offset\n if (backward >= min && isFree(backward)) return backward\n }\n\n return undefined\n}\n","// ============================================================================\n// ASCII renderer — ER diagrams\n//\n// Renders erDiagram text to ASCII/Unicode art.\n// Each entity is a 2-section box (header | attributes).\n// Relationships are drawn as lines with crow's foot notation at endpoints.\n//\n// Layout: entities are placed in a grid pattern (multiple rows if needed).\n// Relationship lines use Manhattan routing between entity boxes.\n// ============================================================================\n\nimport { parseErDiagram } from '@zombie-mermaid/mermaid-parser'\nimport type {\n ErDiagram,\n ErEntity,\n ErAttribute,\n Cardinality,\n} from '@zombie-mermaid/mermaid-parser'\nimport type {\n AsciiConfig,\n CharRole,\n AsciiTheme,\n ColorMode,\n Canvas,\n} from './types.ts'\nimport {\n mkCanvas,\n mkRoleCanvas,\n canvasToString,\n increaseSize,\n increaseRoleCanvasSize,\n write,\n} from './canvas.ts'\nimport { drawMultiBox, measureMultiBox, classifyBoxChar } from './draw.ts'\nimport { splitLines, maxLineWidth } from './multiline-utils.ts'\nimport { splitStatements } from '@zombie-mermaid/core'\nimport { toDisplayCells } from './display-width.ts'\nimport { findFreeLane } from './lane-search.ts'\nimport { DEFAULT_PADDING_X, DEFAULT_PADDING_Y, paddingOffset } from './types.ts'\n\n// ============================================================================\n// Entity box content\n// ============================================================================\n\n/** Format an attribute line: \"PK type name\" or \"FK type name\" etc. */\nfunction formatAttribute(attr: ErAttribute): string {\n const keyStr = attr.keys.length > 0 ? attr.keys.join(',') + ' ' : ' '\n return `${keyStr}${attr.type} ${attr.name}`\n}\n\n/** Build sections for an entity box: [header], [attributes] */\nfunction buildEntitySections(entity: ErEntity): string[][] {\n // Support multi-line entity names\n const header = splitLines(entity.label)\n const attrs = entity.attributes.map(formatAttribute)\n if (attrs.length === 0) return [header]\n return [header, attrs]\n}\n\n// ============================================================================\n// Crow's foot notation\n// ============================================================================\n\n/**\n * Returns the ASCII/Unicode characters for a crow's foot cardinality marker.\n * Markers are drawn adjacent to entity boxes at relationship endpoints.\n *\n * Standard ER notation:\n * one: ─┤├─ perpendicular line (exactly one)\n * zero-one: ─○┤─ circle + perpendicular (zero or one)\n * many: ─<>─ crow's foot (one or more)\n * zero-many: ─○<─ circle + crow's foot (zero or more)\n *\n * @param card - The cardinality type\n * @param useAscii - Use ASCII-only characters\n * @param isRight - True if this marker is on the right side of the relationship\n */\nfunction getCrowsFootChars(\n card: Cardinality,\n useAscii: boolean,\n isRight = false,\n vertical = false,\n): string {\n if (useAscii) {\n switch (card) {\n case 'one':\n // A bare '-' has no vertical extent, so it reads as a gap in the\n // vertical line rather than a tick crossing it (issue: PR #442's\n // fix made the marker visible but visually severed the line). '+'\n // carries both strokes, matching how '|' crosses the horizontal\n // line in the non-vertical case below.\n return vertical ? '+' : '|'\n case 'zero-one':\n return isRight ? 'o|' : '|o'\n case 'many':\n return isRight ? '<' : '>'\n case 'zero-many':\n return isRight ? 'o<' : '>o'\n }\n } else {\n // Use cleaner Unicode characters\n switch (card) {\n case 'one':\n // Same reasoning as the ASCII '+' above: '┼' keeps the vertical\n // stroke (line stays visually continuous) while adding the\n // horizontal stroke that marks the \"one\" cardinality.\n return vertical ? '┼' : '│'\n case 'zero-one':\n return isRight ? '○│' : '│○'\n case 'many':\n return isRight ? '╟' : '╢'\n case 'zero-many':\n return isRight ? '○╟' : '╢○'\n }\n }\n}\n\n// ============================================================================\n// Positioned entity\n// ============================================================================\n\ninterface PlacedEntity {\n entity: ErEntity\n sections: string[][]\n x: number\n y: number\n width: number\n height: number\n}\n\n// ============================================================================\n// Connected Component Detection\n// ============================================================================\n\n/**\n * Find connected components in the ER diagram using DFS.\n * Treats relationships as undirected edges for connectivity.\n *\n * Returns an array of entity ID sets, one per connected component.\n */\nfunction findConnectedComponents(diagram: ErDiagram): Set<string>[] {\n const visited = new Set<string>()\n const components: Set<string>[] = []\n\n // Build undirected adjacency list from relationships\n const neighbors = new Map<string, Set<string>>()\n for (const ent of diagram.entities) {\n neighbors.set(ent.id, new Set())\n }\n for (const rel of diagram.relationships) {\n neighbors.get(rel.entity1)?.add(rel.entity2)\n neighbors.get(rel.entity2)?.add(rel.entity1)\n }\n\n // DFS to find each component\n function dfs(startId: string, component: Set<string>): void {\n const stack = [startId]\n while (stack.length > 0) {\n const nodeId = stack.pop()!\n if (visited.has(nodeId)) continue\n\n visited.add(nodeId)\n component.add(nodeId)\n\n for (const neighbor of neighbors.get(nodeId) ?? []) {\n if (!visited.has(neighbor)) {\n stack.push(neighbor)\n }\n }\n }\n }\n\n // Find all components\n for (const ent of diagram.entities) {\n if (!visited.has(ent.id)) {\n const component = new Set<string>()\n dfs(ent.id, component)\n if (component.size > 0) {\n components.push(component)\n }\n }\n }\n\n return components\n}\n\n// ============================================================================\n// Layout and rendering\n// ============================================================================\n\n/**\n * Look up a per-entity value computed by renderErAscii's box-sizing pass.\n * Every entity in `diagram.entities` gets an entry in `entityBoxW`,\n * `entityBoxH`, and `entitySections` before layout runs, so this always\n * succeeds for a real diagram entity — but that guarantee is established by\n * a separate imperative loop the compiler can't connect back to this\n * lookup, so it's checked explicitly rather than trusted via `!`.\n */\nfunction mustGetEntityValue<T>(\n map: Map<string, T>,\n entityId: string,\n what: string,\n): T {\n const value = map.get(entityId)\n if (value === undefined) {\n /* v8 ignore next */\n throw new Error(\n `ER diagram layout: missing ${what} for entity \"${entityId}\"`,\n )\n }\n return value\n}\n\n/**\n * Check whether every cell in row `y` across [xStart, xEnd] is still blank\n * on `canvas`, ignoring column `skipX` (the relationship's own vertical\n * stem, which legitimately already occupies that column across the whole\n * gap before a jog row is chosen — see `chooseFreeRow`).\n *\n * A column past the canvas's current width isn't drawn yet — `canvas[x]` is\n * `undefined` there — but it isn't *occupied* either: relationship labels\n * routinely land past the initial bounds and grow the canvas on write (see\n * `increaseSize` at the label-drawing call sites below). Treating that as\n * \"not free\" made `chooseFreeRow` reject perfectly good candidate rows near\n * the canvas edge and fall back to the plain midpoint instead, undermining\n * the whole point of the search.\n */\n// Exported for direct unit testing (see check-diff-coverage.ts's own\n// exports for the precedent this repo already uses) — renderErAscii's fixed\n// vGap never produces a gap wide enough to exercise every branch of\n// chooseFreeRow's search order through the full render pipeline alone, so\n// the row-selection logic itself is tested directly against constructed\n// inputs instead.\nexport function isRowFree(\n canvas: Canvas,\n y: number,\n xStart: number,\n xEnd: number,\n skipX: number,\n): boolean {\n for (let x = xStart; x <= xEnd; x++) {\n if (x === skipX) continue\n const col = canvas[x]\n if (col === undefined) continue // not drawn yet — not occupied\n if (col[y] !== ' ' && col[y] !== undefined) return false\n }\n return true\n}\n\n/**\n * Choose a row for a vertical relationship's horizontal jog segment,\n * preferring the geometric midpoint between `startY` and `endY` but\n * scanning outward (alternating below/above) for the nearest row across\n * [xStart, xEnd] that isn't already occupied — by an entity box the naive\n * midpoint would otherwise cut through, or by another relationship's\n * already-drawn jog (see issue #351: every vertical relationship between\n * the same two component rows previously computed the *same* midpoint,\n * so their jogs and labels landed on one shared row and overwrote each\n * other). Candidates are restricted to the open interval (startY, endY) so\n * the chosen row never collides with the crow's-foot markers flush against\n * either entity border.\n *\n * Falls back to the plain midpoint — the only row every caller used before\n * this fix — when the gap is too small to have any candidate row at all, or\n * when every candidate in the gap is occupied (a dense diagram where\n * avoiding collisions entirely isn't possible with this renderer's\n * straight-jog routing).\n *\n * Exported for direct unit testing — see the comment on `isRowFree` above.\n */\nexport function chooseFreeRow(\n canvas: Canvas,\n startY: number,\n endY: number,\n xStart: number,\n xEnd: number,\n skipX: number,\n): number {\n const preferred = Math.floor((startY + endY) / 2)\n // Candidates are the open interval (startY, endY) — `findFreeLane`'s\n // bounds are inclusive, so they're the endpoints stepped one row inward.\n // `preferred` itself is exempt from those bounds by design (see\n // `findFreeLane`), which is what keeps the plain-midpoint fallback below\n // reachable for a gap too narrow to hold any candidate at all.\n return (\n findFreeLane(preferred, startY + 1, endY - 1, (row) =>\n isRowFree(canvas, row, xStart, xEnd, skipX),\n ) ?? preferred\n )\n}\n\n/**\n * Render a Mermaid ER diagram to ASCII/Unicode text.\n *\n * Pipeline: parse → build boxes → component-aware layout → draw boxes → draw relationships → string.\n */\nexport function renderErAscii(\n text: string,\n config: AsciiConfig,\n colorMode?: ColorMode,\n theme?: AsciiTheme,\n): string {\n const lines = splitStatements(text)\n const diagram = parseErDiagram(lines)\n\n if (diagram.entities.length === 0) return ''\n\n const useAscii = config.useAscii\n // See paddingOffset's doc comment (types.ts) for why these are an offset\n // from the padding defaults rather than the raw config values.\n //\n // hGap/vGap floors are higher than the generic \"don't collapse to\n // nothing\" minimum of 1: crow's-foot markers are up to 2 cells wide, so a\n // same-row relationship's two markers need room not to collide (see the\n // horizontal-marker section below). A vertical relationship needs at\n // least 2 rows so the upper and lower markers don't land on the same\n // row. (See issue #343's CodeRabbit review.)\n const hGap = paddingOffset(config.paddingX, DEFAULT_PADDING_X, 6, 6) // horizontal gap between entity boxes\n const vGap = paddingOffset(config.paddingY, DEFAULT_PADDING_Y, 4, 2) // vertical gap between rows (for relationship lines)\n // Vertical gap between disconnected components. Unlike vGap, this gap\n // never needs to fit a relationship line, a jog, or a label — disconnected\n // components have no edges between them by definition — so it only needs\n // enough room to read as a visual break between unrelated entities, not\n // the same routing headroom a same-component row wrap needs. A gap this\n // large previously left most of the output blank for diagrams with a few\n // small unrelated components (issue #351) — base lowered from 6 to 2\n // accordingly; still offset from config.paddingY like the others.\n const componentGap = paddingOffset(config.paddingY, DEFAULT_PADDING_Y, 2, 1)\n\n // Widest crow's-foot marker glyph across every cardinality, in the\n // current mode (ASCII/Unicode) — the same \"up to 2 cells wide\" fact the\n // hGap/vGap comment above states, but computed here instead of restated\n // as a raw literal, so anything sized off it (labelInset, labelX below)\n // automatically tracks a future change to the marker glyph set (issue\n // #415, following #384's precedent for hGap/vGap/componentGap).\n const maxMarkerWidth = Math.max(\n ...(['one', 'zero-one', 'many', 'zero-many'] as const).flatMap((card) => [\n getCrowsFootChars(card, useAscii, false).length,\n getCrowsFootChars(card, useAscii, true).length,\n ]),\n )\n\n // --- Build entity box dimensions ---\n const entitySections = new Map<string, string[][]>()\n const entityBoxW = new Map<string, number>()\n const entityBoxH = new Map<string, number>()\n const entityById = new Map<string, ErEntity>()\n\n for (const ent of diagram.entities) {\n entityById.set(ent.id, ent)\n const sections = buildEntitySections(ent)\n entitySections.set(ent.id, sections)\n\n // Reserve exactly what drawMultiBox will draw — measuring it here rather\n // than re-deriving the arithmetic keeps layout and drawing in lockstep for\n // wide-character (CJK/fullwidth) content.\n const { width: boxW, height: boxH } = measureMultiBox(\n sections,\n config.boxBorderPadding,\n )\n\n entityBoxW.set(ent.id, boxW)\n entityBoxH.set(ent.id, boxH)\n }\n\n // Widest relationship label between each unordered pair of entities.\n // Used to widen the horizontal gap between entities so labels aren't\n // truncated and keep at least 1 char of padding from both entity boxes\n // (see issue #67 — labels like \"ordered in\" were clamped to the fixed\n // 6-char gap and truncated to \"ordere\").\n const pairLabelWidth = new Map<string, number>()\n for (const rel of diagram.relationships) {\n if (!rel.label) continue\n const key = [rel.entity1, rel.entity2].sort().join('|')\n const w = maxLineWidth(rel.label)\n pairLabelWidth.set(key, Math.max(pairLabelWidth.get(key) ?? 0, w))\n }\n\n // --- Find connected components ---\n const components = findConnectedComponents(diagram)\n\n // --- Layout: place each component, then stack components vertically ---\n const placed = new Map<string, PlacedEntity>()\n let currentY = 0\n\n for (const component of components) {\n // Get entities in this component (preserve original order for consistency)\n const componentEntities = diagram.entities.filter((e) =>\n component.has(e.id),\n )\n\n // Layout entities within this component horizontally\n // Use sqrt-based row limit for larger components\n const maxPerRow = Math.max(\n 2,\n Math.ceil(Math.sqrt(componentEntities.length)),\n )\n\n let currentX = 0\n let maxRowH = 0\n let colCount = 0\n\n for (let idx = 0; idx < componentEntities.length; idx++) {\n const ent = componentEntities[idx]!\n const w = mustGetEntityValue(entityBoxW, ent.id, 'box width')\n const h = mustGetEntityValue(entityBoxH, ent.id, 'box height')\n\n if (colCount >= maxPerRow) {\n // Wrap to next row within this component\n currentY += maxRowH + vGap\n currentX = 0\n maxRowH = 0\n colCount = 0\n }\n\n placed.set(ent.id, {\n entity: ent,\n sections: mustGetEntityValue(entitySections, ent.id, 'sections'),\n x: currentX,\n y: currentY,\n width: w,\n height: h,\n })\n\n // Widen the gap to the next entity in this row so a connecting\n // relationship's label fits with 1 char of padding on each side\n // instead of being truncated or crammed against a box border.\n let gap = hGap\n const willWrapNext = colCount + 1 >= maxPerRow\n const nextEnt = componentEntities[idx + 1]\n if (!willWrapNext && nextEnt) {\n const key = [ent.id, nextEnt.id].sort().join('|')\n const labelW = pairLabelWidth.get(key) ?? 0\n if (labelW > 0) gap = Math.max(gap, labelW + 2)\n }\n\n currentX += w + gap\n maxRowH = Math.max(maxRowH, h)\n colCount++\n }\n\n // Move to next component row (add gap between components)\n currentY += maxRowH + componentGap\n }\n\n // --- Create canvas ---\n let totalW = 0\n let totalH = 0\n for (const p of placed.values()) {\n totalW = Math.max(totalW, p.x + p.width)\n totalH = Math.max(totalH, p.y + p.height)\n }\n totalW += 4\n totalH += 2\n\n const canvas = mkCanvas(totalW - 1, totalH - 1)\n const rc = mkRoleCanvas(totalW - 1, totalH - 1)\n\n /**\n * Set a character on the canvas and track its role.\n * Delegates bounds-checking to the shared `write()` primitive\n * (src/ascii/canvas.ts) instead of duplicating the guard here — see\n * issue #171.\n */\n function setC(x: number, y: number, ch: string, role: CharRole): void {\n write(canvas, x, y, ch, { role, roleCanvas: rc })\n }\n\n /**\n * True for a cell a relationship draw must not silently overwrite: an\n * entity's own box border, or 'text' (a label or an entity's own\n * header/attribute text). Relationships are drawn in declaration order, so\n * a later relationship's line, crow's-foot marker, or label can otherwise\n * land on the exact cell an earlier one (or a plain entity box) already\n * wrote, corrupting it — a stray line glyph mid-word, one label's\n * characters spliced into another's, or (for a same-row relationship whose\n * straight line runs across an unrelated entity sitting between its two\n * endpoints) that entity's border erased outright (issue #392). This\n * doesn't fix the underlying routing gap — the line still crosses straight\n * through the box, an out-of-scope defect tracked in #351/#390's \"known\n * limitation\" — it only stops that crossing from destroying content.\n */\n function isProtected(x: number, y: number): boolean {\n const role = rc[x]?.[y]\n return role === 'text' || role === 'border'\n }\n\n /**\n * True when every cell a label's line would occupy (after clamping to\n * [minX, maxX]) is free of protected content (see isProtected). Checked as\n * a whole line rather than character-by-character: a per-character skip on\n * a *label* write (unlike a line or marker) would let two overlapping\n * labels' letters splice together into a new word that isn't either\n * original label — e.g. \"has\" + the tail of \"tagged-with\" reading as\n * \"hasged-with\" — which is more misleading than either label winning\n * outright or neither appearing. A label either renders intact or is\n * skipped entirely.\n */\n function canPlaceLabelLine(\n cells: string[],\n startX: number,\n y: number,\n minX: number,\n maxX: number,\n ): boolean {\n for (let i = 0; i < cells.length; i++) {\n const x = startX + i\n if (x < minX || x > maxX) continue\n if (isProtected(x, y)) return false\n }\n return true\n }\n\n /**\n * Like canPlaceLabelLine, but also refuses a cell already holding a\n * crow's-foot marker ('arrow'), or reserved by an entity box (see\n * boxCells, defined below — referenced here by closure, since this\n * function isn't actually called until after boxCells exists). Used only\n * when searching for an alternate row for a vertical relationship's\n * label (see below): a label's own natural row is allowed to sit on top\n * of that same relationship's freshly-drawn line (expected — the label\n * always wins over the line beneath it), but a *different* row picked\n * specifically to dodge a collision shouldn't destroy a marker it\n * happens to land on instead. The boxCells check matters most for a\n * bypass-routed relationship (#350): a box's own blank interior padding\n * never gets a role written to it, so isProtected alone would treat it\n * as free even though it's inside another entity's box.\n */\n function canPlaceLabelLineAvoidingMarkers(\n cells: string[],\n startX: number,\n y: number,\n minX: number,\n maxX: number,\n ): boolean {\n for (let i = 0; i < cells.length; i++) {\n const x = startX + i\n if (x < minX || x > maxX) continue\n if (isProtected(x, y) || rc[x]?.[y] === 'arrow') return false\n if (boxCells.has(`${x},${y}`)) return false\n }\n return true\n }\n\n // --- Draw entity boxes ---\n for (const p of placed.values()) {\n const boxCanvas = drawMultiBox(\n p.sections,\n useAscii,\n config.boxBorderPadding,\n )\n for (let bx = 0; bx < boxCanvas.length; bx++) {\n for (let by = 0; by < boxCanvas[0]!.length; by++) {\n const ch = boxCanvas[bx]![by]!\n if (ch !== ' ') {\n const cx = p.x + bx\n const cy = p.y + by\n if (cx < totalW && cy < totalH) {\n setC(cx, cy, ch, classifyBoxChar(ch))\n }\n }\n }\n }\n }\n\n // --- Snapshot cells occupied by entity boxes ---\n // Taken once, right after boxes are drawn and before any relationship\n // line, crow's-foot marker, or label is drawn, so relationship rendering\n // can never silently overwrite a box border or attribute text (issue\n // #350). The obstruction-aware routing below (see `obstructionBottom` and\n // the vertical row-band clamp) avoids these cells in the common cases;\n // this snapshot is the last-resort guarantee for whatever routing doesn't\n // anticipate.\n const boxCells = new Set<string>()\n for (const p of placed.values()) {\n for (let by = 0; by < p.height; by++) {\n for (let bx = 0; bx < p.width; bx++) {\n boxCells.add(`${p.x + bx},${p.y + by}`)\n }\n }\n }\n\n /**\n * Like setC, but refuses to draw into a cell reserved by an entity box\n * (see boxCells above). Used for every relationship line, crow's-foot\n * marker, and label write below, so a mis-routed segment degrades to a\n * gap in the line rather than corrupting a box's border or attribute\n * text.\n */\n function setCGuarded(x: number, y: number, ch: string, role: CharRole): void {\n if (boxCells.has(`${x},${y}`)) return\n // Two relationship lines are allowed to cross (a normal, expected part\n // of ER layout), but a later relationship's line/marker must not punch\n // through an earlier relationship's already-placed label text — that's\n // the same silent-corruption shape as issue #350, just between two\n // relationships instead of a relationship and a box.\n if (role !== 'text' && rc[x]?.[y] === 'text') return\n setC(x, y, ch, role)\n }\n\n /**\n * Like setCGuarded, but for a horizontal line/dash fill: also backs off\n * one cell when either neighbor already holds 'text' (carried over from\n * the #392 fix's setRelHChar). isProtected/boxCells alone guard a\n * label's own cells, but a *different*, later-processed relationship's\n * horizontal jog — including the obstruction-detour and band-clamped\n * routing added for #350 — can still run its dashes right up against an\n * earlier one's label with zero visual gap (e.g. \"────authors────\").\n * Only used for horizontal fills (same-row connection line, detour\n * bottom, vertical connection's jog): a vertical '│' passing a label's\n * row doesn't read as visually cramped the same way, so it isn't padded.\n */\n function setCGuardedH(\n x: number,\n y: number,\n ch: string,\n role: CharRole,\n ): void {\n if (rc[x - 1]?.[y] === 'text' || rc[x + 1]?.[y] === 'text') return\n setCGuarded(x, y, ch, role)\n }\n\n /**\n * True when every cell in the rectangle [xStart, xEnd] x [yStart, yEnd] is\n * still blank — i.e. neither an entity box (see boxCells) nor a\n * previously-drawn relationship line/marker/label occupies it. Used to\n * pick a detour row for one relationship that doesn't land on top of\n * another relationship's already-drawn line or label (both routed through\n * the same row-gap band can otherwise silently overwrite each other).\n * Reads directly from `canvas` rather than boxCells, since it must also\n * see ink from relationships drawn earlier in this same loop.\n */\n function regionClear(\n xStart: number,\n xEnd: number,\n yStart: number,\n yEnd: number,\n ): boolean {\n for (let y = yStart; y <= yEnd; y++) {\n for (let x = xStart; x <= xEnd; x++) {\n const ch = canvas[x]?.[y]\n if (ch !== undefined && ch !== ' ') return false\n }\n }\n return true\n }\n\n /**\n * Vertical relationship \"lanes\" already drawn — one entry per straight\n * vertical run, added as each relationship's connector is finalized.\n * Checked before drawing each subsequent vertical connector so that two\n * *unrelated* relationships' stems never land on immediately-adjacent\n * (but not identical) columns purely by coincidence: with nothing in the\n * rendered output to distinguish them, that reads as a single connector\n * that inexplicably shifts sideways partway down, rather than as two\n * independent lines (issue #411). A relationship's own two segments\n * (either side of its own jog) are never checked against this registry\n * as they're computed — only against lanes registered by *earlier*\n * relationships in this same loop — since a single line legitimately\n * bending at its own jog is not the ambiguity this guards against.\n */\n const verticalLanes: { x: number; yStart: number; yEnd: number }[] = []\n\n /**\n * True when [yStart, yEnd] at column x sits one column from an existing\n * lane whose row range overlaps it. Every call site passes a range\n * derived from startY/endY (upper's bottom to lower's top, or a row\n * within that span), which the layout always establishes as\n * startY <= endY — a degenerate/inverted range never reaches here.\n */\n function verticalLaneConflict(\n x: number,\n yStart: number,\n yEnd: number,\n ): boolean {\n for (const lane of verticalLanes) {\n if (Math.abs(lane.x - x) !== 1) continue\n if (yStart <= lane.yEnd && yEnd >= lane.yStart) return true\n }\n return false\n }\n\n /**\n * Like verticalLaneConflict, but also rejects the *same* column as an\n * existing lane (diff 0), not just an adjacent one (diff 1). Used only\n * when searching for a via/bypass column (see the multiRowObstruction\n * branch below): landing the bypass on another relationship's own\n * column wouldn't just be visually adjacent, it would run straight\n * through — and setCGuarded doesn't protect a 'line' cell from being\n * overwritten by another 'line' write, so the two relationships' glyphs\n * (including corner turns) would silently blend into one path, which is\n * worse than the adjacent-column aliasing this fix targets in the first\n * place.\n */\n function viaColumnBlocked(x: number, yStart: number, yEnd: number): boolean {\n for (const lane of verticalLanes) {\n if (Math.abs(lane.x - x) > 1) continue\n if (yStart <= lane.yEnd && yEnd >= lane.yStart) return true\n }\n return false\n }\n\n function registerVerticalLane(x: number, yStart: number, yEnd: number): void {\n verticalLanes.push({ x, yStart, yEnd })\n }\n\n // --- Draw relationships ---\n const H = useAscii ? '-' : '─'\n const V = useAscii ? '|' : '│'\n const dashH = useAscii ? '.' : '╌'\n const dashV = useAscii ? ':' : '┊'\n\n /**\n * Character for the single cell where a routed relationship's path turns\n * — a vertical segment and a horizontal segment meeting at a right angle\n * (issue #414). `vertDir` is the direction the vertical segment extends\n * away from this corner cell ('up' toward smaller y, 'down' toward\n * larger y); `horizDir` is the direction the horizontal segment extends\n * away from this same cell. Collapses to '+' in ASCII mode, matching\n * getCrowsFootChars' own useAscii branching.\n */\n function getCornerChar(\n vertDir: 'up' | 'down',\n horizDir: 'left' | 'right',\n ): string {\n if (useAscii) return '+'\n if (vertDir === 'down') return horizDir === 'right' ? '┌' : '┐'\n return horizDir === 'right' ? '└' : '┘'\n }\n\n /**\n * Draw a corner glyph at a routed relationship's turn point, through the\n * same setCGuarded occupancy guard as every other relationship write (see\n * setCGuarded's doc comment above), so a corner can never overwrite an\n * entity box border or another relationship's already-placed label text\n * — the same corruption shape #391 fixed for plain line/marker writes.\n * Called after the plain line segments (and, in the multi-row-bypass\n * case, before the crow's-foot markers) are drawn, so the corner glyph\n * replaces whichever line/dash character would otherwise occupy that one\n * cell where the path actually changes direction.\n */\n function drawCorner(\n x: number,\n y: number,\n vertDir: 'up' | 'down',\n horizDir: 'left' | 'right',\n ): void {\n setCGuarded(x, y, getCornerChar(vertDir, horizDir), 'line')\n }\n\n /**\n * Attachment columns for vertical (different-row) relationships, keyed by\n * entity id, one bucket per edge the relationship attaches to. Without\n * this, every vertical relationship's marker independently defaults to\n * its entity's exact horizontal center — so when two or more\n * relationships converge on the *same* entity's top edge (or leave from\n * the same bottom edge) — e.g. USER→COMMENT and POST→COMMENT both ending\n * at COMMENT — their markers land on the identical cell and read as one\n * merged crow's-foot instead of two distinguishable relationships (issue\n * #453). Real mermaid.js avoids this by staggering each edge's\n * attachment point along the entity's border based on where the other\n * endpoint sits; `attachmentX` below mirrors that by spreading multiple\n * attachments left-to-right across the entity's own width, ordered by the\n * other entity's center X (so the visual left-to-right order of the\n * relationships matches the left-to-right order of their sources).\n * Entities with only one attachment on a given side are unaffected — they\n * still resolve to dead center, exactly as before this change.\n */\n interface VerticalAttachment {\n rel: (typeof diagram.relationships)[number]\n otherCenterX: number\n }\n const topAttachments = new Map<string, VerticalAttachment[]>()\n const bottomAttachments = new Map<string, VerticalAttachment[]>()\n for (const rel of diagram.relationships) {\n const e1 = placed.get(rel.entity1)\n const e2 = placed.get(rel.entity2)\n if (!e1 || !e2) continue\n const e1CY = e1.y + Math.floor(e1.height / 2)\n const e2CY = e2.y + Math.floor(e2.height / 2)\n const sameRow = Math.abs(e1CY - e2CY) < Math.max(e1.height, e2.height)\n if (sameRow) continue\n const [upperE, lowerE] = e1CY < e2CY ? [e1, e2] : [e2, e1]\n const upperCX = upperE.x + Math.floor(upperE.width / 2)\n const lowerCX = lowerE.x + Math.floor(lowerE.width / 2)\n let bottomList = bottomAttachments.get(upperE.entity.id)\n if (!bottomList) {\n bottomList = []\n bottomAttachments.set(upperE.entity.id, bottomList)\n }\n bottomList.push({ rel, otherCenterX: lowerCX })\n let topList = topAttachments.get(lowerE.entity.id)\n if (!topList) {\n topList = []\n topAttachments.set(lowerE.entity.id, topList)\n }\n topList.push({ rel, otherCenterX: upperCX })\n }\n\n /**\n * Resolve the attachment column for `rel` on `entity`'s given side. Falls\n * back to dead center when that side has zero or one attachment (the\n * common case, and the pre-#453 behavior). With two or more, spreads them\n * evenly across the entity's own width — inset by 1 cell from each edge\n * so a 2-cell-wide crow's-foot marker (e.g. \"zero-many\") doesn't sit\n * flush against a box corner — ordered by the other entity's center X.\n */\n function attachmentX(\n entity: PlacedEntity,\n rel: (typeof diagram.relationships)[number],\n attachments: Map<string, VerticalAttachment[]>,\n ): number {\n const list = attachments.get(entity.entity.id)\n const centerX = entity.x + Math.floor(entity.width / 2)\n if (!list || list.length <= 1) return centerX\n const sorted = [...list].sort((a, b) => a.otherCenterX - b.otherCenterX)\n const idx = sorted.findIndex((a) => a.rel === rel)\n if (idx === -1) return centerX\n const margin = Math.min(1, Math.floor((entity.width - 1) / 2))\n const usableWidth = Math.max(0, entity.width - 1 - margin * 2)\n const step = sorted.length > 1 ? usableWidth / (sorted.length - 1) : 0\n return entity.x + margin + Math.round(idx * step)\n }\n\n for (const rel of diagram.relationships) {\n const e1 = placed.get(rel.entity1)\n const e2 = placed.get(rel.entity2)\n if (!e1 || !e2) continue\n\n const lineH = rel.identifying ? H : dashH\n const lineV = rel.identifying ? V : dashV\n\n // Determine connection direction based on relative position.\n // Connect from right side of left entity to left side of right entity (horizontal),\n // or from bottom of upper entity to top of lower entity (vertical).\n const e1CX = e1.x + Math.floor(e1.width / 2)\n const e1CY = e1.y + Math.floor(e1.height / 2)\n const e2CX = e2.x + Math.floor(e2.width / 2)\n const e2CY = e2.y + Math.floor(e2.height / 2)\n\n // Check if entities are on the same row (horizontal connection)\n const sameRow = Math.abs(e1CY - e2CY) < Math.max(e1.height, e2.height)\n\n if (sameRow) {\n // Horizontal connection: right side of left entity → left side of right entity\n const [left, right] = e1CX < e2CX ? [e1, e2] : [e2, e1]\n const [leftCard, rightCard] =\n e1CX < e2CX\n ? [rel.cardinality1, rel.cardinality2]\n : [rel.cardinality2, rel.cardinality1]\n\n const startX = left.x + left.width\n const endX = right.x - 1\n const lineY = left.y + Math.floor(left.height / 2)\n\n // A straight line at lineY only stays clear of other boxes when left\n // and right are actually adjacent in the row. When some other entity\n // in the same row sits between them (e.g. ORDER↔SHIPMENT with\n // LINE_ITEM placed in between — issue #350), the direct path runs\n // straight through that entity's box. Detect that case by checking\n // for any other row-mate whose x-range overlaps the gap.\n let obstructionBottom: number | undefined\n for (const other of placed.values()) {\n if (other === left || other === right) continue\n if (other.y !== left.y) continue\n const overlapsGap = other.x < endX + 1 && other.x + other.width > startX\n if (overlapsGap) {\n obstructionBottom = Math.max(\n obstructionBottom ?? 0,\n other.y + other.height,\n )\n }\n }\n\n // Horizontal crow's-foot markers: flush against the border by default\n // (standard ER notation, and #390's original intent), except where a\n // marker's own border-adjacent glyph is character-identical to the\n // border glyph itself ('one'/'zero-one' use '│'/'|', the same as the\n // vertical border) — there, flush reads as a doubled border rather\n // than a marker touching one, so that side gets the same inset as\n // the label (issue #67). 'many'/'zero-many' markers ('╢'/'╟',\n // '○╢'/'○╟') never collide with the border glyph and stay flush,\n // matching #390's original intent for them.\n //\n // This was briefly a blanket inset for every horizontal marker\n // (matching the review comment at\n // https://github.com/dfadler/zombie-mermaid/issues/351#issuecomment-5497209031,\n // which found flush unreadable but tested against a raw text\n // comparison, not a render) — rendered in a real terminal, adjacent\n // monospace box-drawing glyphs each sit centered in their own cell\n // and don't visually fuse regardless of spacing, so a blanket inset\n // wasn't fixing a defect, it was just adding unrequested space to\n // markers that already read fine flush (e.g. `CUSTOMER }o--o{ ORDER`\n // — no collision on either side, no reason for either to move).\n // Narrowed to per-side glyph-identity detection instead, so only the\n // colliding side ever moves — see issue #413.\n const gapWidth = endX - startX + 1\n // Only worth insetting the label off the border when the gap could\n // fit the widest possible marker plus at least one cell of actual\n // label content (maxMarkerWidth + 1) — below that, insetting both\n // sides would just shrink an already-tight label region for no\n // readability benefit. Was a flat `>= 3` (issue #415); derived here\n // so it stays correct if maxMarkerWidth ever changes. The false\n // branch is unreachable today — hGap's own floor (paddingOffset's\n // hard-coded 6, independent of config.paddingX) keeps gapWidth well\n // above maxMarkerWidth + 1 for the current glyph set — but is kept\n // (not simplified to a bare `1`) so a future wider marker glyph set\n // is still handled correctly without revisiting this line.\n /* v8 ignore next */\n const labelInset = gapWidth >= maxMarkerWidth + 1 ? 1 : 0\n const leftChars = getCrowsFootChars(leftCard, useAscii, false)\n const rightChars = getCrowsFootChars(rightCard, useAscii, true)\n const leftInset = gapWidth >= 3 && leftChars[0] === V ? 1 : 0\n const rightInset =\n gapWidth >= 3 && rightChars[rightChars.length - 1] === V ? 1 : 0\n const markerStartX = startX + leftInset\n const markerEndX = endX - rightInset\n\n let labelBaseY: number\n\n if (obstructionBottom === undefined) {\n // Direct path: nothing sits between left and right in this row.\n for (let x = startX; x <= endX; x++) {\n setCGuardedH(x, lineY, lineH, 'line')\n }\n labelBaseY = lineY + 1\n\n // Two relationships between the exact same adjacent pair (a\n // parallel/multi edge) both compute this identical row — without\n // this search, the second relationship's label would silently\n // overwrite the first's, since setCGuarded only guards a\n // non-text write against existing text, not text-over-text.\n // Search downward, within the row-gap band, for a row where the\n // label is still on blank canvas.\n if (rel.label) {\n const labelMinX = startX + labelInset\n const labelMaxX = endX - labelInset\n const labelRows = splitLines(rel.label).length\n const maxLabelY = lineY + 1 + Math.max(vGap - 1, 1)\n while (\n labelBaseY < maxLabelY &&\n !regionClear(\n labelMinX,\n labelMaxX,\n labelBaseY,\n labelBaseY + labelRows - 1,\n )\n ) {\n labelBaseY++\n }\n }\n } else {\n // Detour beneath the obstructing entity, through the free row-gap\n // band (vGap) that layout already reserves below every row, then\n // back up to the right entity's edge. Nothing else is ever placed\n // in that band, so it's guaranteed clear regardless of how much\n // taller the obstruction is than left/right themselves.\n const rowBottom = Math.max(\n left.y + left.height,\n right.y + right.height,\n obstructionBottom,\n )\n // Reserve room for the label too (it goes one row below the detour\n // line), and search downward for a row where both the detour line\n // and the label are still on blank canvas — another relationship\n // detoured through the same row-gap band would otherwise land on\n // the exact same row and silently overwrite this one's label.\n const labelRows = rel.label ? splitLines(rel.label).length : 0\n let detourY = rowBottom + 1\n const maxDetourY = rowBottom + Math.max(vGap * 3, 4)\n while (\n detourY < maxDetourY &&\n !regionClear(startX, endX, detourY, detourY + labelRows)\n ) {\n detourY++\n }\n // Grow the canvas before drawing — a detour can reach further down\n // than the initial component-based sizing anticipated.\n increaseSize(canvas, endX + 1, detourY + labelRows + 1)\n increaseRoleCanvasSize(rc, endX + 1, detourY + labelRows + 1)\n for (let y = lineY; y <= detourY; y++) {\n setCGuarded(startX, y, lineV, 'line')\n setCGuarded(endX, y, lineV, 'line')\n }\n for (let x = startX; x <= endX; x++) {\n setCGuardedH(x, detourY, lineH, 'line')\n }\n // Mark the two points where the detour actually turns — straight\n // down from the row, then straight across, then straight back up —\n // so the turn reads as one continuous line rather than a line\n // ending flush against an unrelated mark (issue #414).\n if (startX !== endX) {\n drawCorner(startX, detourY, 'up', 'right')\n drawCorner(endX, detourY, 'up', 'left')\n }\n labelBaseY = detourY + 1\n }\n\n // Draw crow's foot markers at endpoints\n // Left marker (at left entity's right edge) - isRight=false\n for (let i = 0; i < leftChars.length; i++) {\n setCGuarded(markerStartX + i, lineY, leftChars[i]!, 'arrow')\n }\n\n // Right marker (at right entity's left edge) - isRight=true\n for (let i = 0; i < rightChars.length; i++) {\n setCGuarded(\n markerEndX - rightChars.length + 1 + i,\n lineY,\n rightChars[i]!,\n 'arrow',\n )\n }\n\n // Relationship label centered in the gap between the two entities,\n // below the line (or below the detour, when one was needed). Clamp\n // label to the padded gap region [startX + inset, endX - inset] so it\n // never touches a box border. Supports multi-line labels. The gap\n // itself is widened during layout (see pairLabelWidth) so the full\n // label always fits when the path is direct.\n if (rel.label) {\n const lines = splitLines(rel.label)\n const gapMid = Math.floor((startX + endX) / 2)\n const labelMinX = startX + labelInset\n const labelMaxX = endX - labelInset\n\n for (let lineIdx = 0; lineIdx < lines.length; lineIdx++) {\n const line = lines[lineIdx]!\n // Grid cells, not code units — see toDisplayCells.\n const cells = toDisplayCells(line)\n const labelStart = Math.max(\n labelMinX,\n gapMid - Math.floor(cells.length / 2),\n )\n const labelY = labelBaseY + lineIdx\n // Ensure canvas is tall enough\n increaseSize(\n canvas,\n Math.max(labelStart + cells.length, 1),\n Math.max(labelY + 1, 1),\n )\n increaseRoleCanvasSize(\n rc,\n Math.max(labelStart + cells.length, 1),\n Math.max(labelY + 1, 1),\n )\n if (\n !canPlaceLabelLine(cells, labelStart, labelY, labelMinX, labelMaxX)\n ) {\n continue\n }\n for (let i = 0; i < cells.length; i++) {\n const lx = labelStart + i\n if (lx >= labelMinX && lx <= labelMaxX) {\n setCGuarded(lx, labelY, cells[i]!, 'text')\n }\n }\n }\n }\n } else {\n // Vertical connection: bottom of upper entity → top of lower entity\n const [upper, lower] = e1CY < e2CY ? [e1, e2] : [e2, e1]\n const [upperCard, lowerCard] =\n e1CY < e2CY\n ? [rel.cardinality1, rel.cardinality2]\n : [rel.cardinality2, rel.cardinality1]\n\n const startY = upper.y + upper.height\n const endY = lower.y - 1\n // Dead center when this entity has only one relationship attaching\n // to this side; otherwise staggered so it doesn't collide with a\n // sibling relationship's marker on the same edge (issue #453).\n const lineX = attachmentX(upper, rel, bottomAttachments)\n const lowerCX = attachmentX(lower, rel, topAttachments)\n // Jog-row selection superseded by the multi-row-obstruction routing\n // below (issue #350's regionClear/pickBandY-based band search), which\n // subsumes this block's same-row-mate collision avoidance (issue\n // #351) as a special case. See chooseFreeRow's removal — no caller\n // remains.\n\n /** True when column x is free of every entity box across [yStart, yEnd]. */\n function columnClearOfBoxes(\n x: number,\n yStart: number,\n yEnd: number,\n ): boolean {\n for (let y = yStart; y <= yEnd; y++) {\n if (boxCells.has(`${x},${y}`)) return false\n }\n return true\n }\n\n // Crow's-foot markers sit flush against each entity border, matching\n // the horizontal-connection markers above — see issue #351. (No\n // separate label inset is needed here: the vertical label, below,\n // never clamped to an inset range in the first place.) Computed up\n // front (rather than only just before drawing the markers) because\n // the multi-row-obstruction routing below needs to jog *after* these\n // rows, not immediately at startY/endY — otherwise the marker ends up\n // sitting past where the line already turned away, disconnected from\n // it.\n const markerStartY = startY\n const markerEndY = endY\n\n // The naive vertical midpoint can still sit inside a row-mate of\n // `upper` that's taller than `upper` itself, so a horizontal jog (or\n // the label, placed near the same band) at that Y would run straight\n // through that entity's box (issue #350 — e.g. `dispatches`, where\n // SHIPMENT is shorter than its row-mate LINE_ITEM). Clamp to the\n // row-gap band that's actually free of every entity: below the\n // tallest box in upper's row, and above the top of lower's row.\n const upperRowBottom = Math.max(\n ...[...placed.values()]\n .filter((p) => p.y === upper.y)\n .map((p) => p.y + p.height),\n )\n const lowerRowTop = lower.y\n const bandTop = Math.max(startY, upperRowBottom + 1)\n const bandBottom = Math.min(endY, lowerRowTop - 1)\n\n /**\n * Pick a row within the free row-gap band for a horizontal run across\n * [xStart, xEnd] (plus `extraRows` below it, for a multi-line label).\n * Prefers a row that's still entirely blank — not already used by\n * another relationship's line or label routed through the same band\n * (issue #350) — falling back to the midpoint of the band (or of the\n * full startY..endY span, if the band is degenerate) when nothing is\n * fully clear.\n */\n function pickBandY(\n xStart: number,\n xEnd: number,\n extraRows: number,\n ): number {\n const fallback =\n bandTop <= bandBottom\n ? Math.floor((bandTop + bandBottom) / 2)\n : Math.floor((startY + endY) / 2)\n if (bandTop > bandBottom) return fallback\n for (let y = bandTop; y <= bandBottom; y++) {\n if (regionClear(xStart, xEnd, y, y + extraRows)) return y\n }\n return fallback\n }\n\n // Natural (unescalated) routing: a single straight column when the\n // two entities share a center column, otherwise a two-segment jog\n // through a row picked by pickBandY. Computed up front — even though\n // it ends up unused when multiRowObstruction below escalates to the\n // via-column bypass instead — because deciding *whether* to escalate\n // also depends on whether this natural routing's own columns would\n // land adjacent to another relationship's already-drawn vertical run\n // (see verticalLaneConflict, issue #411).\n const needsJog = lineX !== lowerCX\n const lx = Math.min(lineX, lowerCX)\n const rx = Math.max(lineX, lowerCX)\n const midY = needsJog ? pickBandY(lx, rx, 0) : endY\n const initialVertEnd = needsJog ? midY - 1 : endY\n\n // [bandTop, bandBottom] — not the wider [startY, endY] — is what the\n // single-row clamp above already guarantees is clear of a row-mate of\n // `upper` (or the top of `lower`'s own row). Checking the wider range\n // here would misfire on exactly that already-handled case (a row-mate\n // taller than `upper`, e.g. STUDENT next to a shorter TEACHER) and\n // route it through the free-column bypass unnecessarily. A genuine\n // multi-row obstruction — some entity in a row strictly between\n // `upper`'s and `lower`'s own rows (e.g. A→G below, where D sits in\n // the same column as A, two rows down) — still shows up here, since\n // bandTop/bandBottom span every row in between, not just one.\n //\n // Also escalates to the via-column bypass when the *natural* routing\n // computed above would put lineX's or lowerCX's own straight segment\n // column-adjacent to a different relationship's already-registered\n // vertical lane over an overlapping row range (issue #411) — bumping\n // this relationship onto its own distinct column keeps the two\n // readable as separate lines instead of one that appears to bend\n // sideways where they happen to sit closest.\n // The registered/checked range for each column includes the jog row\n // (midY) itself, not just the pure-vertical run up to it — the corner\n // glyph at (lineX, midY) or (lowerCX, midY) is where the bend\n // actually happens, and it's exactly as visually adjacent to a\n // neighboring column as the straight run above/below it.\n const multiRowObstruction =\n !columnClearOfBoxes(lineX, bandTop, bandBottom) ||\n !columnClearOfBoxes(lowerCX, bandTop, bandBottom) ||\n verticalLaneConflict(lineX, startY, needsJog ? midY : endY) ||\n (needsJog && verticalLaneConflict(lowerCX, midY, endY))\n\n // Where the visible line actually runs — lineX in the common case,\n // or the free bypass column found below when a third entity sits\n // directly in the way. The label (further down) anchors to whichever\n // one is real, instead of always assuming lineX.\n let routingX = lineX\n\n if (multiRowObstruction) {\n // Route around the intervening entity through a column that's\n // completely free of every box across the whole vertical span,\n // searched outward from lineX. Two short horizontal jogs — right\n // after leaving upper, right before reaching lower — connect\n // lineX/lowerCX to that column; the long middle run is a plain\n // vertical line, guaranteed not to touch any box.\n // Also skips a column that would collide with (viaColumnBlocked\n // rejects both identical and lane-adjacent columns) a different\n // relationship's already-registered vertical run (issue #411) — a\n // via column must be safe on both fronts, and reusing another\n // relationship's own column outright would silently blend the two\n // paths together (setCGuarded lets one 'line' write overwrite\n // another), which is worse than the adjacency this escalation is\n // meant to fix.\n const canvasWidth = canvas.length\n let viaX: number | undefined\n for (let offset = 0; offset <= canvasWidth; offset++) {\n const right = lineX + offset\n if (\n columnClearOfBoxes(right, startY, endY) &&\n !viaColumnBlocked(right, startY, endY)\n ) {\n viaX = right\n break\n }\n const left = lineX - offset\n if (\n offset > 0 &&\n columnClearOfBoxes(left, startY, endY) &&\n !viaColumnBlocked(left, startY, endY)\n ) {\n viaX = left\n break\n }\n }\n // No column anywhere on the canvas is fully clear (a very dense\n // diagram) — fall back to lineX. setCGuarded still guarantees no\n // box gets corrupted; the line just keeps the gap it already had.\n routingX = viaX ?? lineX\n\n // Stay on lineX/lowerCX through the marker rows (so each crow's\n // foot marker still sits on a connected line, same as the\n // non-obstructed case below), then jog over to routingX for the\n // long middle run.\n for (let y = startY; y <= markerStartY; y++) {\n setCGuarded(lineX, y, lineV, 'line')\n }\n for (\n let x = Math.min(lineX, routingX);\n x <= Math.max(lineX, routingX);\n x++\n ) {\n setCGuardedH(x, markerStartY, lineH, 'line')\n }\n for (let y = markerStartY; y <= markerEndY; y++) {\n setCGuarded(routingX, y, lineV, 'line')\n }\n for (\n let x = Math.min(routingX, lowerCX);\n x <= Math.max(routingX, lowerCX);\n x++\n ) {\n setCGuardedH(x, markerEndY, lineH, 'line')\n }\n for (let y = markerEndY; y <= endY; y++) {\n setCGuarded(lowerCX, y, lineV, 'line')\n }\n // Mark the bypass's four turns (issue #414). Drawn before the\n // crow's-foot markers below, so a marker centered on the same\n // column/row still wins where the two coincide — same precedence\n // as before this change, when the marker overwrote a plain line\n // character there instead of a corner.\n if (lineX !== routingX) {\n const horizDirAtLine = routingX > lineX ? 'right' : 'left'\n const horizDirAtRouting =\n horizDirAtLine === 'right' ? 'left' : 'right'\n drawCorner(lineX, markerStartY, 'up', horizDirAtLine)\n drawCorner(routingX, markerStartY, 'down', horizDirAtRouting)\n }\n if (routingX !== lowerCX) {\n const horizDirAtRouting2 = lowerCX > routingX ? 'right' : 'left'\n const horizDirAtLower =\n horizDirAtRouting2 === 'right' ? 'left' : 'right'\n drawCorner(routingX, markerEndY, 'up', horizDirAtRouting2)\n drawCorner(lowerCX, markerEndY, 'down', horizDirAtLower)\n }\n // Register the long middle run so a later relationship's own\n // vertical connector won't land adjacent to it either (#411). The\n // short lineX/lowerCX stubs at the marker rows are only 1 row deep\n // (markerStartY..markerStartY, markerEndY..markerEndY) — negligible\n // for this check, so they're not registered separately.\n registerVerticalLane(routingX, markerStartY, markerEndY)\n } else {\n // Vertical line. Column lineX stays within upper's own x-range,\n // which by layout construction never overlaps a row-mate's box, so\n // this straight run is safe regardless of how far it descends. When\n // a horizontal jog is needed below, though, the path redirects to\n // lowerCX at the jog row — so this initial fill must stop *before*\n // that row rather than always running the full height. Otherwise a\n // leftover remnant keeps drawing all the way to endY at the\n // original lineX, a stray parallel line beside the actual jogged\n // path that connects to nothing (issue #392).\n //\n // needsJog/lx/rx/midY/initialVertEnd were already computed above,\n // alongside the multiRowObstruction check itself (issue #411) —\n // reused here rather than recomputed.\n for (let y = startY; y <= initialVertEnd; y++) {\n setCGuarded(lineX, y, lineV, 'line')\n }\n // Registered range includes the jog row itself (midY) when jogging\n // — see the multiRowObstruction comment above for why.\n registerVerticalLane(lineX, startY, needsJog ? midY : initialVertEnd)\n\n // If horizontal offset needed, add a horizontal segment\n if (needsJog) {\n // Horizontal segment at midY\n for (let x = lx; x <= rx; x++) {\n setCGuardedH(x, midY, lineH, 'line')\n }\n // Vertical from midY to lower entity\n for (let y = midY + 1; y <= endY; y++) {\n setCGuarded(lowerCX, y, lineV, 'line')\n }\n registerVerticalLane(lowerCX, midY, endY)\n // Mark the jog's two turns (issue #414): the vertical run from\n // upper arrives from above and turns toward lowerCX; the\n // vertical run into lower departs downward, having turned away\n // from lineX.\n const horizDirAtLine = lowerCX > lineX ? 'right' : 'left'\n const horizDirAtLower = horizDirAtLine === 'right' ? 'left' : 'right'\n drawCorner(lineX, midY, 'up', horizDirAtLine)\n drawCorner(lowerCX, midY, 'down', horizDirAtLower)\n // The path now ends at lowerCX, not lineX — the lower marker and\n // label (below) must anchor there too, or they render visually\n // disconnected from the line that actually reaches them (#392).\n routingX = lowerCX\n }\n }\n\n // Crow's foot markers (vertical direction) — markerStartY/markerEndY\n // computed up front, above.\n // Upper marker (at upper entity's bottom edge) - treat as source side (isRight=false)\n const upperChars = getCrowsFootChars(upperCard, useAscii, false, true)\n for (let i = 0; i < upperChars.length; i++) {\n setCGuarded(\n lineX - Math.floor(upperChars.length / 2) + i,\n markerStartY,\n upperChars[i]!,\n 'arrow',\n )\n }\n\n // Lower marker (at lower entity's top edge) - treat as target side (isRight=true)\n const targetX = lineX !== lowerCX ? lowerCX : lineX\n const lowerChars = getCrowsFootChars(lowerCard, useAscii, true, true)\n for (let i = 0; i < lowerChars.length; i++) {\n setCGuarded(\n targetX - Math.floor(lowerChars.length / 2) + i,\n markerEndY,\n lowerChars[i]!,\n 'arrow',\n )\n }\n\n // Relationship label — placed to the right of the vertical line.\n // Anchored to routingX, not lineX/targetX: when the connection was\n // detoured around an obstructing entity (#350), or jogged over to\n // the lower entity's own column, the label needs to sit next to\n // wherever the line actually ends up, not next to the relationship's\n // original upper-entity column — otherwise it renders visually\n // disconnected, floating in the gap between the two. We expand the\n // canvas as needed since labels can extend beyond the initial\n // bounds. Supports multi-line labels.\n if (rel.label) {\n const lines = splitLines(rel.label)\n // How far a centered vertical marker's own rightmost glyph column\n // extends past its own center column — see upperChars/lowerChars\n // above, drawn via `center - Math.floor(chars.length / 2) + i`. For\n // every current 1- or 2-cell marker this is 0 (centering always\n // leans the extra cell left, never right), so labelX below reduces\n // to routingX + 2, matching the flat constant this replaces — but\n // it stays correct if a future marker's glyph or centering ever\n // extended past the line column (issue #415).\n const markerRightExtent = Math.max(\n upperChars.length - 1 - Math.floor(upperChars.length / 2),\n lowerChars.length - 1 - Math.floor(lowerChars.length / 2),\n )\n // 1 cell for the line column itself + 1 cell of breathing room\n // before the label starts. Was a flat `+ 2` (issue #415).\n const labelX = routingX + markerRightExtent + 2\n const cellsPerLine = lines.map((line) => toDisplayCells(line))\n const maxCells = Math.max(...cellsPerLine.map((cells) => cells.length))\n const lastLx = labelX + maxCells - 1\n const blockHeight = lines.length\n // Geometric midpoint, used only as a starting point for the\n // collision search below — the jog itself no longer shares this\n // value (chooseFreeRow was removed; multiRowObstruction routing\n // picks its own row independently), so this is a fresh, local\n // fallback rather than a value reused from the line/jog above.\n const midY = Math.floor((startY + endY) / 2)\n // the search for the label's block there rather than at a freshly\n // recomputed geometric midpoint — otherwise the label could still\n // drift onto a different row than its own horizontal segment.\n const naturalStartY = midY - Math.floor((blockHeight - 1) / 2)\n\n if (lastLx >= 0) {\n increaseSize(canvas, lastLx + 1, endY + 1)\n increaseRoleCanvasSize(rc, lastLx + 1, endY + 1)\n }\n\n // Multiple vertical relationships sharing the same upper/lower\n // entity \"rows\" in the grid layout end up with the identical\n // startY/endY — and so the identical natural midY — for their own\n // labels. Without ever considering another row, only the first of\n // them to draw would keep its label; the rest would find their\n // natural row already taken and drop out entirely, even though\n // there's room a row or two away. Try the natural row first, then\n // scan outward (alternating below/above) within the relationship's\n // own vertical run for the nearest row where the whole label fits\n // cleanly. \"Fits\" means clear of every entity box (see boxCells) —\n // not just text/border content already drawn — since a box's own\n // blank interior padding never gets a role written to it and would\n // otherwise look free; a bypass-routed relationship (routingX) can\n // legitimately run its label past a box that never touched\n // startY/endY's naive band (#350). It also means clear of markers,\n // not just text/borders (#392) — before giving up.\n let placedAtY: number | null = null\n for (\n let offset = 0;\n offset <= endY - startY && placedAtY === null;\n offset++\n ) {\n const candidates =\n offset === 0\n ? [naturalStartY]\n : [naturalStartY + offset, naturalStartY - offset]\n for (const candidateStart of candidates) {\n if (\n candidateStart < startY ||\n candidateStart + blockHeight - 1 > endY\n ) {\n continue\n }\n const fits = cellsPerLine.every((cells, lineIdx) =>\n canPlaceLabelLineAvoidingMarkers(\n cells,\n labelX,\n candidateStart + lineIdx,\n 0,\n lastLx,\n ),\n )\n if (fits) {\n placedAtY = candidateStart\n break\n }\n }\n }\n\n if (placedAtY !== null) {\n for (let lineIdx = 0; lineIdx < blockHeight; lineIdx++) {\n const cells = cellsPerLine[lineIdx]!\n const y = placedAtY + lineIdx\n for (let i = 0; i < cells.length; i++) {\n const lx = labelX + i\n if (lx >= 0) {\n setCGuarded(lx, y, cells[i]!, 'text')\n }\n }\n // A relationship's own jog can leave a line character\n // immediately beside where its own label starts —\n // setCGuardedH only stops a *different*, later write from\n // crowding an already-placed label; it can't retroactively\n // clean up a dash this same relationship left right there\n // moments earlier, before the label existed to protect\n // against it. Clear one cell of breathing room on each side\n // whenever a line glyph (from any relationship) is sitting\n // there.\n if (rc[labelX - 1]?.[y] === 'line') {\n setC(labelX - 1, y, ' ', 'line')\n }\n const afterX = labelX + cells.length\n if (rc[afterX]?.[y] === 'line') {\n setC(afterX, y, ' ', 'line')\n }\n }\n }\n }\n }\n }\n\n return canvasToString(canvas, { roleCanvas: rc, colorMode, theme })\n}\n","// ============================================================================\n// ASCII renderer — sequence diagrams\n//\n// Renders sequenceDiagram text to ASCII/Unicode art using a column-based layout.\n// Each actor occupies a column with a vertical lifeline; messages are horizontal\n// arrows between lifelines. Blocks (loop/alt/opt/par) wrap around message groups.\n//\n// Layout is fundamentally different from flowcharts — no grid or A* pathfinding.\n// Instead: actors → columns, messages → rows, all positioned linearly.\n// ============================================================================\n\nimport { parseSequenceDiagram } from '@zombie-mermaid/mermaid-parser'\nimport type { Block } from '@zombie-mermaid/mermaid-parser'\nimport type { AsciiConfig, CharRole, AsciiTheme, ColorMode } from './types.ts'\nimport {\n mkCanvas,\n mkRoleCanvas,\n canvasToString,\n getCanvasSize,\n increaseSize,\n increaseRoleCanvasSize,\n write,\n} from './canvas.ts'\nimport { splitLines, maxLineWidth, lineCount } from './multiline-utils.ts'\nimport { splitStatements } from '@zombie-mermaid/core'\nimport {\n displayWidth,\n toDisplayCells,\n WIDE_CHAR_PLACEHOLDER,\n} from './display-width.ts'\nimport type { Message } from '@zombie-mermaid/mermaid-parser'\nimport { DEFAULT_PADDING_X, DEFAULT_PADDING_Y, paddingOffset } from './types.ts'\n\n// Width of a self-message's loop glyphs (├──┐ / ◀──┘), excluding the label.\n// Shared between the drawing pass and the block-wall extent calculation so\n// self-arrows inside alt/loop/opt blocks don't get clipped by the wall.\nconst SELF_LOOP_WIDTH = 4\n\n// Small stick-figure glyph drawn above an `actor`-kind participant's label\n// (inside the same bordered box a `participant` gets), so mermaid's two\n// declaration keywords stay visually distinct in ASCII output the way real\n// mermaid.js's SVG renderer distinguishes them (a circle-person icon vs. a\n// plain box) — see issue #449. Plain ASCII already, so — unlike H/V/TL/etc.\n// above — there's no separate useAscii/unicode variant to pick between.\nconst ACTOR_GLYPH_LINES = ['O', '/|\\\\', '/ \\\\']\nconst ACTOR_GLYPH_WIDTH = 3\n\n// Horizontal clearance between a block's (loop/alt/opt/par/etc.) side wall\n// and the lifelines its own messages touch. Shared by every block type —\n// there is no per-type wall calculation, so this constant is the single\n// source of truth for that spacing (see BLOCK_WALL_MARGIN's use below for\n// why a *second*, independent use of it also guards against an untouched\n// lifeline).\nconst BLOCK_WALL_MARGIN = 4\n\n// Effective width of a self-message's loop, including room for an\n// autonumber badge drawn at the start of the top arm when active. The badge\n// digits replace the leading dashes rather than adding a second arrowhead,\n// so the loop only needs to widen by the badge's own digit count — one\n// extra column per digit — to keep the corner glyphs (┐/┘) intact.\n// Shared by the drawing pass, the canvas-width pass, and the block-wall\n// extent calculation so all three agree on how much room a numbered\n// self-message actually needs.\nfunction selfLoopWidth(msg: Message): number {\n return msg.seqNumber === undefined\n ? SELF_LOOP_WIDTH\n : SELF_LOOP_WIDTH + String(msg.seqNumber).length\n}\n\n/**\n * Render a Mermaid sequence diagram to ASCII/Unicode text.\n *\n * Pipeline: parse → layout (columns + rows) → draw onto canvas → string.\n */\nexport function renderSequenceAscii(\n text: string,\n config: AsciiConfig,\n colorMode?: ColorMode,\n theme?: AsciiTheme,\n): string {\n const lines = splitStatements(text)\n const diagram = parseSequenceDiagram(lines)\n\n if (diagram.actors.length === 0) return ''\n\n const useAscii = config.useAscii\n\n // Box-drawing characters\n const H = useAscii ? '-' : '─'\n const V = useAscii ? '|' : '│'\n const TL = useAscii ? '+' : '┌'\n const TR = useAscii ? '+' : '┐'\n const BL = useAscii ? '+' : '└'\n const BR = useAscii ? '+' : '┘'\n const JT = useAscii ? '+' : '┬' // top junction on lifeline\n const JB = useAscii ? '+' : '┴' // bottom junction on lifeline\n const JL = useAscii ? '+' : '├' // left junction\n const JR = useAscii ? '+' : '┤' // right junction\n\n // ---- LAYOUT: compute lifeline X positions ----\n\n const actorIdx = new Map<string, number>()\n diagram.actors.forEach((a, i) => actorIdx.set(a.id, i))\n\n /**\n * Look up an actor's column index. The parser's `ensureActor` (see\n * sequence/parser.ts) guarantees every message endpoint has a\n * corresponding actor entry, so this is always found for real parsed\n * input — but that guarantee lives in a different module/function, so\n * narrow it here explicitly rather than trusting it silently across the\n * boundary.\n */\n function actorIndexOf(id: string): number {\n const idx = actorIdx.get(id)\n if (idx === undefined) {\n /* v8 ignore next */\n throw new Error(`Sequence diagram: unknown actor \"${id}\"`)\n }\n return idx\n }\n\n /**\n * Widest line among a block's header (\"alt [label]\") and every divider\n * (\"[else label]\") — the minimum wall width the block's own text needs,\n * independent of how far its messages' lifelines happen to span.\n */\n function maxBlockLabelWidth(block: Block): number {\n const hdrLabel = block.label ? `${block.type} [${block.label}]` : block.type\n let width = maxLineWidth(hdrLabel)\n for (const divider of block.dividers) {\n if (divider.label) {\n width = Math.max(width, maxLineWidth(`[${divider.label}]`))\n }\n }\n return width\n }\n\n // Clamped: a negative boxBorderPadding would otherwise produce a negative\n // actor/note box width for a short label (see issue #343's CodeRabbit\n // review — the same class of bug fixed in draw-boxes.ts's\n // measureMultiBox/drawMultiBox for class/ER boxes).\n const boxPad = Math.max(0, config.boxBorderPadding)\n // `maxLineWidth`/`lineCount` account for the label only; add the\n // stick-figure glyph's own footprint (see ACTOR_GLYPH_LINES below) for\n // actor-kind participants so box sizing matches what drawActorBox draws.\n const actorContentWidth = (a: {\n label: string\n type: 'participant' | 'actor'\n }) =>\n Math.max(maxLineWidth(a.label), a.type === 'actor' ? ACTOR_GLYPH_WIDTH : 0)\n const actorContentHeight = (a: {\n label: string\n type: 'participant' | 'actor'\n }) => lineCount(a.label) + (a.type === 'actor' ? ACTOR_GLYPH_LINES.length : 0)\n // Use max line width for multi-line actor labels\n const actorBoxWidths = diagram.actors.map(\n (a) => actorContentWidth(a) + 2 * boxPad + 2,\n )\n const halfBox = actorBoxWidths.map((w) => Math.ceil(w / 2))\n // Calculate actor box heights based on number of lines in label\n const actorBoxHeights = diagram.actors.map((a) => actorContentHeight(a) + 2) // content lines + top/bottom border\n const actorBoxH = Math.max(...actorBoxHeights, 3) // Use max height for consistent lifeline positioning\n // Left border column of each actor's box — drawActorBox derives the same\n // `left` from the lifeline centre, so a creating arrow can stop just short\n // of it (see \"DRAW: messages\" below).\n const actorBoxLeft = (i: number, cx: number) =>\n cx - Math.floor(actorBoxWidths[i]! / 2)\n\n // ---- Participant lifecycle (create / destroy) ----\n //\n // A `create participant X` box is drawn centred on the row of the message\n // that creates it, in place of a header box; a `destroy X` lifeline stops\n // one row under the destroying arrow with a cross glyph, and gets no\n // footer box. Mermaid draws the created box centred on the message line\n // and ends the destroyed lifeline with a cross at that line; the ASCII\n // form keeps the arrowhead intact by putting the cross on the row below\n // — a cross *on* the arrow row would replace the arrowhead and read as a\n // lost message (`-x`) instead.\n //\n // Rows above the arrow taken by the created box, and rows below it. For\n // the default 3-row box that's one row each side of the arrow row.\n const createdBoxAbove = Math.floor((actorBoxH - 1) / 2)\n const createdBoxBelow = actorBoxH - 1 - createdBoxAbove\n // Message index → index of the actor it creates (recipient) / destroys.\n const createdByMsg = new Map<number, number>()\n const destroyedByMsg = new Map<number, number>()\n diagram.actors.forEach((a, i) => {\n if (a.createdAt !== undefined) createdByMsg.set(a.createdAt, i)\n if (a.destroyedAt !== undefined) destroyedByMsg.set(a.destroyedAt, i)\n })\n // Filled in by the vertical layout pass: actor index → top row of its\n // created box / row of its destroy cross.\n const createdBoxTop = new Map<number, number>()\n const destroyRow = new Map<number, number>()\n\n // ---- Participant groups (box … end) ----\n //\n // Each non-empty box becomes a labelled bracket around its members' header\n // boxes (and an unlabelled one around their footer boxes), one column\n // outside the outermost member's box on each side and one row above and\n // below the actor-box rows:\n //\n // ┌─ Label ───────────────────┐\n // │ ┌───────┐ ┌──────┐ │\n // │ │ Alice │ │ John │ │\n // │ └───┬───┘ └───┬──┘ │\n // └─────┼───────────────┼─────┘\n // │ │\n //\n // The bracket spans from the leftmost member to the rightmost, so a\n // participant declared between two members sits visually inside, as in\n // Mermaid. Colour is not representable here and is ignored. Running the\n // side walls the full height of the diagram (as Mermaid's background\n // rect does) would cut through every message crossing a group boundary,\n // so the group is shown at the header and footer only.\n const boxSpans = diagram.boxes\n .filter((b) => b.actorIds.length > 0)\n .map((b) => {\n const idxs = b.actorIds.map(actorIndexOf)\n return { label: b.label, lo: Math.min(...idxs), hi: Math.max(...idxs) }\n })\n const hasBoxes = boxSpans.length > 0\n // Actor index → index into boxSpans whose span contains it, or -1.\n const boxSpanOf = diagram.actors.map((_, i) =>\n boxSpans.findIndex((s) => s.lo <= i && i <= s.hi),\n )\n // Rows the header bracket adds above the actor boxes (its top border) —\n // the same count is added below them for its bottom border, and the\n // footer bracket mirrors both.\n const bracketRows = hasBoxes ? 1 : 0\n // Columns between a member box's border and the bracket wall.\n const BRACKET_GAP = 1\n // Minimum bracket width for a label: `┌─ Label ─┐` needs the corner, a\n // dash, a space, the label, a space, a dash, and the closing corner.\n const bracketLabelWidth = (label: string) =>\n label === '' ? 0 : maxLineWidth(label) + 6\n\n // Compute minimum gap between adjacent lifelines based on message labels.\n // For messages spanning multiple actors, distribute the required width across gaps.\n const adjMaxWidth: number[] = new Array(\n Math.max(diagram.actors.length - 1, 0),\n ).fill(0)\n\n for (const msg of diagram.messages) {\n const fi = actorIndexOf(msg.from)\n const ti = actorIndexOf(msg.to)\n if (fi === ti) continue // self-messages don't affect spacing\n const lo = Math.min(fi, ti)\n const hi = Math.max(fi, ti)\n // Required gap per span = (max line width + arrow decorations) / number of gaps\n const needed = maxLineWidth(msg.label) + 4\n const numGaps = hi - lo\n const perGap = Math.ceil(needed / numGaps)\n for (let g = lo; g < hi; g++) {\n adjMaxWidth[g] = Math.max(adjMaxWidth[g]!, perGap)\n }\n }\n\n // Width (including border + padding, matching the box drawn in the\n // vertical-layout pass below) of a note anchored to an actor. Computed up\n // front, before lifeline x-positions are finalized, so `left`/`right`\n // notes can participate in gap sizing the same way message labels already\n // do (issue #953 case C) instead of only being checked against a layout\n // that's already fixed.\n function noteBoxWidth(note: (typeof diagram.notes)[number]): number {\n const nLines = splitLines(note.text)\n return Math.max(...nLines.map((l) => displayWidth(l))) + 2 + 2 * boxPad\n }\n\n // Per-actor widest 'left'/'right' note anchored to it. A `left` note on\n // actor i draws into the gap to its left (between actor i-1 and i, or —\n // for the leftmost actor — the canvas's own left margin); a `right` note\n // draws into the gap to its right (between actor i and i+1). Both need to\n // be reserved as part of layout, not clamped after the fact (issue #953\n // cases A-C: notes previously only ever collided with whatever gap\n // message-label sizing happened to leave).\n const leftNoteWidth: number[] = new Array(diagram.actors.length).fill(0)\n const rightNoteWidth: number[] = new Array(diagram.actors.length).fill(0)\n // A single-actor `over` note draws centered on that actor's own lifeline\n // (see the note-x calculation below): `Math.floor(w / 2)` columns land to\n // its left, the rest to its right. Neither half was reserved during gap\n // sizing before this fix, so a note wider than the actor's own column\n // silently spilled into whichever neighbour was closer (issue #992) — the\n // same problem left/rightNoteWidth above already solved for `left`/\n // `right` notes, just for the two halves of a centered one instead. A\n // multi-actor `over` note (`Note over Alice,Bob`) is excluded on purpose:\n // spanning both actors' columns is its intended shape, not a collision.\n const overNoteHalfLeft: number[] = new Array(diagram.actors.length).fill(0)\n const overNoteHalfRight: number[] = new Array(diagram.actors.length).fill(0)\n for (const note of diagram.notes) {\n if (note.position === 'left' || note.position === 'right') {\n const aIdx = actorIndexOf(note.actorIds[0]!)\n const w = noteBoxWidth(note)\n if (note.position === 'left') {\n leftNoteWidth[aIdx] = Math.max(leftNoteWidth[aIdx]!, w)\n } else {\n rightNoteWidth[aIdx] = Math.max(rightNoteWidth[aIdx]!, w)\n }\n } else if (note.position === 'over' && note.actorIds.length === 1) {\n const aIdx = actorIndexOf(note.actorIds[0]!)\n const w = noteBoxWidth(note)\n const halfLeft = Math.floor(w / 2)\n const halfRight = w - halfLeft\n overNoteHalfLeft[aIdx] = Math.max(overNoteHalfLeft[aIdx]!, halfLeft)\n overNoteHalfRight[aIdx] = Math.max(overNoteHalfRight[aIdx]!, halfRight)\n }\n }\n\n // Compute lifeline x-positions (greedy left-to-right).\n // See paddingOffset's doc comment (types.ts) for why this is an offset\n // from the paddingX default rather than the raw config value.\n const minLifelineGap = paddingOffset(\n config.paddingX,\n DEFAULT_PADDING_X,\n 10,\n 4,\n )\n // A bracketed first participant needs room for the wall to its left; two\n // adjacent participants in different groups (or one grouped, one not)\n // need room for the wall(s) between their boxes, plus a blank column on\n // each side of every wall. Same-group neighbours have no wall between.\n //\n // A `left` note on the leftmost actor has no left neighbour to reserve a\n // gap against — it draws into the canvas's own left margin instead, so\n // that margin (the initial lifeline position itself) must be at least\n // wide enough for it: `nx = llX[0] - nWidth - 1 >= 0` (see the note-x\n // calculation below) requires `llX[0] >= nWidth + 1` (issue #953 case A —\n // previously `nx` was clamped to 0 instead, forcing the note onto the\n // lifeline it's supposed to sit beside).\n const llX: number[] = [\n Math.max(\n halfBox[0]! + (boxSpanOf[0] === -1 ? 0 : BRACKET_GAP + 1),\n leftNoteWidth[0]! + 1,\n // A single-actor `over` note on the leftmost actor has no left\n // neighbour either — same reasoning as the `left`-note case just\n // above, but for the note's own left half (`nx = llX[0] -\n // overNoteHalfLeft[0]` must stay >= 0; issue #992).\n overNoteHalfLeft[0]!,\n ),\n ]\n for (let i = 1; i < diagram.actors.length; i++) {\n const prevSpan = boxSpanOf[i - 1]!\n const thisSpan = boxSpanOf[i]!\n const walls =\n prevSpan === thisSpan\n ? 0\n : (prevSpan === -1 ? 0 : 1) + (thisSpan === -1 ? 0 : 1)\n // The base constraint already leaves two blank columns between boxes;\n // each wall needs one column of its own plus one more blank beside it.\n const wallExtra = walls === 0 ? 0 : walls * (BRACKET_GAP + 1) - 1\n // A `right` note on actor i-1, or a `left` note on actor i, draws into\n // this gap — reserve room for whichever is wider (issue #953 cases B/C)\n // so a wide note doesn't overflow into the neighbouring lifeline. The\n // `+2` mirrors adjMaxWidth's own margin below and matches the 1-column\n // clearance the note-x calculation already leaves on the lifeline side.\n // A single-actor `over` note on either actor reaches into this same gap\n // from its own side (its right half from i-1, its left half from i) —\n // issue #992, folded into the same max() rather than summed, matching\n // how the left/right pair above is already handled.\n const noteGapNeed =\n Math.max(\n rightNoteWidth[i - 1]!,\n leftNoteWidth[i]!,\n overNoteHalfRight[i - 1]!,\n overNoteHalfLeft[i]!,\n ) + 2\n const gap = Math.max(\n halfBox[i - 1]! + halfBox[i]! + 2 + wallExtra,\n adjMaxWidth[i - 1]! + 2,\n noteGapNeed,\n minLifelineGap,\n )\n llX[i] = llX[i - 1]! + gap\n }\n\n // Bracket wall columns, from live lifeline positions (later passes only\n // ever shift lifelines right, so these stay valid if read at draw time).\n const bracketLeft = (span: { lo: number }) =>\n actorBoxLeft(span.lo, llX[span.lo]!) - BRACKET_GAP - 1\n const bracketRight = (span: { label: string; lo: number; hi: number }) =>\n Math.max(\n actorBoxLeft(span.hi, llX[span.hi]!) +\n actorBoxWidths[span.hi]! -\n 1 +\n BRACKET_GAP +\n 1,\n bracketLeft(span) + bracketLabelWidth(span.label) - 1,\n )\n\n // A label wider than its members' span widens the bracket to the right;\n // push everything after the group out of the way, the same way the block-\n // label pass below does for loop/alt headers.\n for (const span of boxSpans) {\n if (span.hi + 1 >= diagram.actors.length) continue\n const nextLeft = actorBoxLeft(span.hi + 1, llX[span.hi + 1]!)\n const nextWall = boxSpanOf[span.hi + 1] === -1 ? 0 : BRACKET_GAP + 1\n const needed = bracketRight(span) + BRACKET_GAP + 1 + nextWall\n const shift = needed - nextLeft\n if (shift > 0) {\n const shifted = llX.slice(span.hi + 1).map((x) => x + shift)\n llX.splice(span.hi + 1, shifted.length, ...shifted)\n }\n }\n\n // A block's wall only needs to reach as far right as the lifelines its\n // own messages touch (see the identical minLX/maxLX calc in the block\n // drawing pass below) — but the header/divider LABEL can need more room\n // than that. If an uninvolved participant's lifeline already sits at or\n // past that label-driven width, drawing the wall there lands on a\n // different column than the lifeline (which was already placed) and\n // just leaves that lifeline's \"│\" sitting inside the block, right next\n // to the block's own wall — visually indistinguishable from the block\n // enclosing a participant it has nothing to do with (#387, on top of\n // #352's original fix). Push every lifeline after the block's rightmost\n // participant out of the way before anything downstream depends on\n // these positions.\n //\n // This runs independently of, and before, the draw-time pull-back added\n // for #353 (below, in the \"DRAW: blocks\" pass): that pull-back only ever\n // shrinks a block's *natural* (message-driven) extent away from an\n // untouched lifeline it would otherwise land on — it has no notion of\n // the label-driven width computed here. Without this pass shifting the\n // lifeline out of the way first, a long label would push bRight straight\n // back past whatever #353's pull-back had just cleared, re-enclosing it.\n // This pass's own estimate below (naturalRight/labelRight, using\n // BLOCK_WALL_MARGIN unclamped by any pull-back) is always >= whatever\n // the draw-time pass can ultimately produce, so it shifts at least far\n // enough — never more precisely, but never insufficiently either.\n for (const block of diagram.blocks) {\n let loIdx = -1\n let hiIdx = -1\n let minLX = Number.POSITIVE_INFINITY\n let maxLX = -1\n for (let m = block.startIndex; m <= block.endIndex; m++) {\n if (m >= diagram.messages.length) break\n const msg = diagram.messages[m]!\n const f = actorIndexOf(msg.from)\n const t = actorIndexOf(msg.to)\n loIdx = loIdx === -1 ? Math.min(f, t) : Math.min(loIdx, f, t)\n hiIdx = Math.max(hiIdx, f, t)\n minLX = Math.min(minLX, llX[Math.min(f, t)]!)\n maxLX = Math.max(maxLX, llX[Math.max(f, t)]!)\n if (f === t) {\n const selfRight =\n llX[f]! + SELF_LOOP_WIDTH + 2 + maxLineWidth(msg.label)\n maxLX = Math.max(maxLX, selfRight)\n }\n }\n // An empty block, or one whose rightmost participant is already the\n // last actor, has nothing after it that could be swallowed.\n if (hiIdx === -1 || hiIdx + 1 >= diagram.actors.length) continue\n\n const bLeft = Math.max(0, minLX - BLOCK_WALL_MARGIN)\n const naturalRight = maxLX + BLOCK_WALL_MARGIN\n const labelRight = bLeft + 1 + maxBlockLabelWidth(block)\n const bRight = Math.max(naturalRight, labelRight)\n\n const nextLL = llX[hiIdx + 1]!\n const shift = bRight + 2 - nextLL\n if (shift > 0) {\n // Shift every lifeline from hiIdx+1 onward by `shift`, in place —\n // llX's identity has to survive this pass (later blocks in this same\n // loop, and everything downstream, read positions back out of it) so\n // this can't rebuild the array under a new binding. Deriving the\n // shifted values with .map() and writing them back via splice (same\n // start/length, so it's a pure overwrite) avoids the index-reassignment\n // loop without changing what ends up in llX.\n const shifted = llX.slice(hiIdx + 1).map((x) => x + shift)\n llX.splice(hiIdx + 1, shifted.length, ...shifted)\n }\n }\n\n // ---- LAYOUT: compute vertical positions for messages ----\n\n // For each message index, track the y where its arrow is drawn.\n // Also track block start/end y positions and divider y positions.\n const msgArrowY: number[] = []\n const msgLabelY: number[] = []\n const blockStartY = new Map<number, number>()\n const blockEndY = new Map<number, number>()\n const divYMap = new Map<string, number>() // \"blockIdx:divIdx\" → y\n const notePositions: Array<{\n x: number\n y: number\n width: number\n height: number\n lines: string[]\n }> = []\n\n // Start right below the header boxes — and below the group bracket's\n // bottom border when there is one (its top border row sits above them).\n const headerBoxTop = bracketRows\n const headerBottom = headerBoxTop + actorBoxH // first row after the boxes\n let curY = headerBottom + bracketRows\n\n // rowGap: the blank rows around messages, notes, and blocks. See\n // paddingOffset's doc comment (types.ts) for why this is an offset from\n // the paddingY default rather than the raw config value. Floored at 0 (not\n // 1) since these are single-row gaps, not a whole box — collapsing a gap\n // to 0 rows is still a valid, readable layout, just a tight one.\n const rowGap = paddingOffset(config.paddingY, DEFAULT_PADDING_Y, 1, 0)\n\n // Below rowGap's own floor of 0, three specific gaps still need at least\n // one row: the row right after a divider (or the message row drawn there\n // would land on the divider's own row and overwrite it), the row right\n // after a block's closing border (same reasoning against whatever comes\n // next), and the row before the footer (the footer's top border is drawn\n // *before* messages/arrows in the draw pass — see \"DRAW: actor header +\n // footer boxes\" below — so a message landing on the same row as the\n // footer would draw its arrow through the footer's border). Everywhere\n // else (blank row before a message, gap before a note, blank row before a\n // block header) is genuinely optional spacing with no such collision risk,\n // so those keep using rowGap directly. See issue #343's CodeRabbit review.\n const minSeparatorGap = Math.max(rowGap, 1)\n\n // Pre-message notes: afterIndex === -1 — position before message loop\n for (const note of diagram.notes) {\n if (note.afterIndex !== -1) continue\n curY += rowGap // gap before note\n const nLines = splitLines(note.text)\n const nWidth = noteBoxWidth(note)\n const nHeight = nLines.length + 2\n\n const aIdx = actorIdx.get(note.actorIds[0]!) ?? 0\n let nx: number\n if (note.position === 'left') {\n nx = llX[aIdx]! - nWidth - 1\n } else if (note.position === 'right') {\n nx = llX[aIdx]! + 2\n } else {\n // 'over'\n if (note.actorIds.length >= 2) {\n const aIdx2 = actorIdx.get(note.actorIds[1]!) ?? aIdx\n nx = Math.floor((llX[aIdx]! + llX[aIdx2]!) / 2) - Math.floor(nWidth / 2)\n } else {\n nx = llX[aIdx]! - Math.floor(nWidth / 2)\n }\n }\n nx = Math.max(0, nx)\n\n notePositions.push({\n x: nx,\n y: curY,\n width: nWidth,\n height: nHeight,\n lines: nLines,\n })\n curY += nHeight\n }\n\n for (let m = 0; m < diagram.messages.length; m++) {\n // Block openings at this message\n for (let b = 0; b < diagram.blocks.length; b++) {\n if (diagram.blocks[b]!.startIndex === m) {\n curY += rowGap + 1 // blank rows + 1 fixed header row\n blockStartY.set(b, curY - 1)\n }\n }\n\n // Dividers at this message index\n for (let b = 0; b < diagram.blocks.length; b++) {\n for (let d = 0; d < diagram.blocks[b]!.dividers.length; d++) {\n if (diagram.blocks[b]!.dividers[d]!.index === m) {\n curY += rowGap\n divYMap.set(`${b}:${d}`, curY)\n curY += minSeparatorGap\n }\n }\n }\n\n curY += rowGap // blank row before message\n\n const msg = diagram.messages[m]!\n const isSelf = msg.from === msg.to\n\n // Calculate height needed for multi-line message labels\n const msgLineCount = lineCount(msg.label)\n\n // A self-message can't sensibly create its own recipient (the loop\n // glyphs would sit where the box goes), so that degenerate case keeps\n // the header box instead.\n const createdIdx = isSelf ? undefined : createdByMsg.get(m)\n const destroyedIdx = destroyedByMsg.get(m)\n\n if (isSelf) {\n // Self-message occupies 3+ rows: top-arm, label-col(s), bottom-arm\n msgLabelY[m] = curY + 1\n msgArrowY[m] = curY\n curY += 2 + msgLineCount // top-arm + label lines + bottom-arm\n } else {\n // Normal message: label row(s), then — for a creating message — the\n // rows of the created box above the arrow, then the arrow row, then\n // the created box's rows below it.\n const boxAbove = createdIdx === undefined ? 0 : createdBoxAbove\n const boxBelow = createdIdx === undefined ? 0 : createdBoxBelow\n msgLabelY[m] = curY\n msgArrowY[m] = curY + msgLineCount + boxAbove\n curY += msgLineCount + boxAbove + 1 + boxBelow\n if (createdIdx !== undefined) {\n createdBoxTop.set(createdIdx, msgArrowY[m]! - boxAbove)\n }\n }\n\n // One reserved row under the arrow (under the bottom arm, for a\n // self-message) for the destroy cross, so it never lands on whatever\n // comes next when rowGap is 0.\n if (destroyedIdx !== undefined) {\n destroyRow.set(destroyedIdx, curY)\n curY += 1\n }\n\n // Notes after this message\n for (let n = 0; n < diagram.notes.length; n++) {\n if (diagram.notes[n]!.afterIndex === m) {\n curY += rowGap\n const note = diagram.notes[n]!\n const nLines = splitLines(note.text)\n const nWidth = noteBoxWidth(note)\n const nHeight = nLines.length + 2\n\n // Determine x position based on note.position\n const aIdx = actorIdx.get(note.actorIds[0]!) ?? 0\n let nx: number\n if (note.position === 'left') {\n nx = llX[aIdx]! - nWidth - 1\n } else if (note.position === 'right') {\n nx = llX[aIdx]! + 2\n } else {\n // 'over' — center over actor(s)\n if (note.actorIds.length >= 2) {\n const aIdx2 = actorIdx.get(note.actorIds[1]!) ?? aIdx\n nx =\n Math.floor((llX[aIdx]! + llX[aIdx2]!) / 2) -\n Math.floor(nWidth / 2)\n } else {\n nx = llX[aIdx]! - Math.floor(nWidth / 2)\n }\n }\n nx = Math.max(0, nx)\n\n notePositions.push({\n x: nx,\n y: curY,\n width: nWidth,\n height: nHeight,\n lines: nLines,\n })\n curY += nHeight\n }\n }\n\n // Block closings after this message\n for (let b = 0; b < diagram.blocks.length; b++) {\n if (diagram.blocks[b]!.endIndex === m) {\n curY += rowGap\n blockEndY.set(b, curY)\n curY += minSeparatorGap\n }\n }\n }\n\n curY += minSeparatorGap // gap before footer (mandatory — see minSeparatorGap)\n // With brackets, footerY is the footer bracket's top border row and the\n // footer boxes start one row under it; otherwise it is the boxes' own top.\n const footerY = curY\n const footerBoxTop = footerY + bracketRows\n const totalH = footerBoxTop + actorBoxH + bracketRows\n\n // Total canvas width\n const lastLL = llX[llX.length - 1] ?? 0\n const lastHalf = halfBox[halfBox.length - 1] ?? 0\n let totalW = lastLL + lastHalf + 2\n for (const span of boxSpans) {\n totalW = Math.max(totalW, bracketRight(span) + 2)\n }\n\n // Ensure canvas is wide enough for self-message labels and notes\n for (let m = 0; m < diagram.messages.length; m++) {\n const msg = diagram.messages[m]!\n if (msg.from === msg.to) {\n const fi = actorIndexOf(msg.from)\n const selfRight =\n llX[fi]! + selfLoopWidth(msg) + 2 + 2 + maxLineWidth(msg.label)\n totalW = Math.max(totalW, selfRight + 1)\n }\n }\n for (const np of notePositions) {\n totalW = Math.max(totalW, np.x + np.width + 1)\n }\n\n const canvas = mkCanvas(totalW, totalH - 1)\n const rc = mkRoleCanvas(totalW, totalH - 1)\n\n /** Set a character on the canvas and track its role. */\n function setC(x: number, y: number, ch: string, role: CharRole): void {\n write(canvas, x, y, ch, { role, roleCanvas: rc })\n }\n\n /**\n * Write a line of text starting at grid cell (x, y), one grid cell per\n * terminal column rather than one grid cell per JS code point.\n *\n * A CJK/kana/hangul/fullwidth-form/emoji grapheme renders as TWO terminal\n * columns but is a single JS character — writing it into a single grid\n * cell (as a naive `for (let i = 0; i < line.length; i++)` loop does)\n * under-reserves a column for every wide character, so unrelated content\n * (box borders, adjacent lifelines) drawn later at a fixed grid index no\n * longer lines up with what a real terminal actually renders for this\n * row (issue #334). `toDisplayCells` (display-width.ts) splits `text`\n * into one entry per terminal column — a wide grapheme followed by an\n * empty placeholder entry — so writing one cell per entry keeps this\n * row's grid indices in step with its rendered terminal columns, the\n * same approach `drawText` (canvas.ts) already uses for flowchart/class/\n * ER diagram boxes.\n *\n * `exclusiveMaxX`, when given, additionally skips any cell at or past\n * that grid index — matching call sites that previously bounded their\n * own manual write loop with `x < totalW` (a one-column margin short of\n * `setC`'s own canvas-edge clipping). `setC` still clips to the actual\n * canvas bounds regardless, so omitting it just falls back to that.\n */\n function writeTextCells(\n x: number,\n y: number,\n text: string,\n role: CharRole,\n exclusiveMaxX?: number,\n ): void {\n const cells = toDisplayCells(text)\n for (let i = 0; i < cells.length; i++) {\n const cx = x + i\n if (exclusiveMaxX !== undefined && cx >= exclusiveMaxX) break\n // A wide grapheme's glyph cell is always immediately followed by its\n // placeholder cell (toDisplayCells' pairing). Writing the glyph\n // without room for that placeholder would leave its second terminal\n // column unreserved even though the glyph still renders across two\n // columns — reintroducing this file's own under-reservation bug\n // right at the clip boundary instead of over the whole string. Stop\n // one cell earlier instead of splitting the pair.\n const isWideGlyphStart = cells[i + 1] === WIDE_CHAR_PLACEHOLDER\n if (\n isWideGlyphStart &&\n exclusiveMaxX !== undefined &&\n cx + 1 >= exclusiveMaxX\n ) {\n break\n }\n setC(cx, y, cells[i]!, role)\n }\n }\n\n // ---- DRAW: helper to place a bordered actor box (supports multi-line labels) ----\n\n /**\n * Draws a bordered, centered actor box directly onto the shared sequence\n * canvas. This intentionally does NOT go through the shared `drawMultiBox`\n * primitive (src/ascii/draw-boxes.ts), for two concrete reasons:\n *\n * 1. Coordinate system: `drawMultiBox` returns a standalone canvas rooted\n * at (0, 0), meant to be measured and then copied onto a caller's\n * canvas (as class-diagram.ts and er-diagram.ts do). Actor boxes are\n * positioned by a lifeline's center x-coordinate (`cx`), and are drawn\n * twice per actor (header at y=0, footer at y=footerY) directly via the\n * closure-captured `setC`, alongside unrelated lifeline/junction\n * drawing that shares the same canvas and role-tracking.\n * 2. Sizing/alignment semantics differ, not just coordinates: `drawMultiBox`\n * sizes each section by raw `.length` and left-aligns its content with\n * fixed padding. Actor labels are centered and sized via\n * `maxLineWidth` (multiline-utils.ts), which is deliberately\n * display-width-aware (not `.length`) so wide CJK/emoji labels get a\n * correctly-sized box. Routing actor boxes through `drawMultiBox` as it\n * stands would silently narrow boxes for wide-character actor labels —\n * reintroducing the exact bug `maxLineWidth` exists to avoid — and\n * changing `drawMultiBox` itself to be display-width-aware and\n * center-aligned would alter class/ER diagram box sizing, which must\n * stay untouched.\n *\n * For an `actor`-kind participant, `ACTOR_GLYPH_LINES` (a small stick\n * figure) is prepended to the label lines, inside the same box — the\n * box's width/height precompute above (`actorContentWidth`/\n * `actorContentHeight`) already reserves room for it so this stays a\n * pure drawing step with no additional sizing math.\n *\n * `boxH` is always the diagram-wide `actorBoxH` (the tallest box among\n * *all* actors), not this actor's own content height — every box must\n * bottom out at the same row so the lifeline/junction drawn below it\n * (which unconditionally starts at row `actorBoxH`, see the \"DRAW:\n * lifelines\" section below) connects directly to the box's bottom\n * border instead of leaving a gap. A participant box shorter than an\n * adjacent actor's stick-figure box gets blank rows appended above its\n * bottom border to make up the difference.\n */\n function drawActorBox(\n cx: number,\n topY: number,\n label: string,\n actorType: 'participant' | 'actor',\n boxH: number,\n ): void {\n const glyphLines = actorType === 'actor' ? ACTOR_GLYPH_LINES : []\n const lines = [...glyphLines, ...splitLines(label)]\n const maxW = Math.max(\n maxLineWidth(label),\n glyphLines.length > 0 ? ACTOR_GLYPH_WIDTH : 0,\n )\n const w = maxW + 2 * boxPad + 2\n const h = boxH\n const contentRows = h - 2\n const left = cx - Math.floor(w / 2)\n\n // Top border\n setC(left, topY, TL, 'border')\n for (let x = 1; x < w - 1; x++) setC(left + x, topY, H, 'border')\n setC(left + w - 1, topY, TR, 'border')\n\n // Content rows (top-aligned; horizontally centered). Any rows beyond\n // `lines.length` (up to `contentRows`) stay blank padding — see the\n // `boxH` doc note above.\n for (let i = 0; i < contentRows; i++) {\n const row = topY + 1 + i\n setC(left, row, V, 'border')\n setC(left + w - 1, row, V, 'border')\n const line = lines[i]\n if (line === undefined) continue\n // Center this line within the box. Centering offset and cell-writing\n // both use display width (terminal columns), not `.length` (JS\n // code units) — a code-unit-based offset would under-center CJK\n // labels, and writing one grid cell per code unit (rather than per\n // terminal column) would under-reserve columns for wide glyphs,\n // both contributing to issue #334's border/content misalignment.\n const ls = left + 1 + boxPad + Math.floor((maxW - displayWidth(line)) / 2)\n writeTextCells(ls, row, line, 'text')\n }\n\n // Bottom border\n const bottomY = topY + h - 1\n setC(left, bottomY, BL, 'border')\n for (let x = 1; x < w - 1; x++) setC(left + x, bottomY, H, 'border')\n setC(left + w - 1, bottomY, BR, 'border')\n }\n\n // ---- DRAW: lifelines ----\n\n // A created participant's lifeline starts under its mid-diagram box; a\n // destroyed one's ends at its cross row (drawn in the messages pass, so\n // the cross wins over the `│` painted here).\n const lifelineTop = (i: number) => {\n const boxTop = createdBoxTop.get(i)\n return boxTop === undefined ? headerBottom : boxTop + actorBoxH\n }\n const lifelineBottom = (i: number) => destroyRow.get(i) ?? footerY\n\n for (let i = 0; i < diagram.actors.length; i++) {\n const x = llX[i]!\n for (let y = lifelineTop(i); y <= lifelineBottom(i); y++) {\n setC(x, y, V, 'line')\n }\n }\n\n // ---- DRAW: participant-group brackets (box … end) ----\n\n const CROSS = useAscii ? '+' : '┼'\n /**\n * One bracket: top border (with the label, if any), side walls down the\n * actor-box rows, bottom border. Lifelines that pass through a border row\n * get a crossing glyph; a created (header) or destroyed (footer) member's\n * lifeline doesn't reach that row, so its column keeps the plain border.\n */\n function drawBracket(\n span: { label: string; lo: number; hi: number },\n topRow: number,\n label: string,\n ): void {\n const left = bracketLeft(span)\n const right = bracketRight(span)\n const bottomRow = topRow + actorBoxH + 1\n setC(left, topRow, TL, 'border')\n setC(right, topRow, TR, 'border')\n setC(left, bottomRow, BL, 'border')\n setC(right, bottomRow, BR, 'border')\n for (let x = left + 1; x < right; x++) {\n setC(x, topRow, H, 'border')\n setC(x, bottomRow, H, 'border')\n }\n for (let y = topRow + 1; y < bottomRow; y++) {\n setC(left, y, V, 'border')\n setC(right, y, V, 'border')\n }\n if (label !== '') {\n // `┌─ Label ─…`: keep the first dash, then the label with a space on\n // each side. Multi-line labels take the first line only — the border\n // is a single row.\n writeTextCells(left + 2, topRow, ` ${splitLines(label)[0]!} `, 'text')\n }\n for (let i = span.lo; i <= span.hi; i++) {\n const x = llX[i]!\n if (lifelineTop(i) <= topRow && topRow <= lifelineBottom(i)) {\n setC(x, topRow, CROSS, 'junction')\n }\n if (lifelineTop(i) <= bottomRow && bottomRow <= lifelineBottom(i)) {\n setC(x, bottomRow, CROSS, 'junction')\n }\n }\n }\n for (const span of boxSpans) {\n drawBracket(span, 0, span.label)\n drawBracket(span, footerY, '')\n }\n\n // ---- DRAW: actor header + footer boxes (drawn over lifelines) ----\n\n for (let i = 0; i < diagram.actors.length; i++) {\n const actor = diagram.actors[i]!\n // Created: the box sits on its creating message's row, not in the\n // header. Destroyed: no footer box — the lifeline already ended.\n const headerTop = createdBoxTop.get(i) ?? headerBoxTop\n const destroyed = destroyRow.has(i)\n drawActorBox(llX[i]!, headerTop, actor.label, actor.type, actorBoxH)\n if (!destroyed) {\n drawActorBox(llX[i]!, footerBoxTop, actor.label, actor.type, actorBoxH)\n }\n\n // Lifeline junctions on box borders (Unicode only)\n if (!useAscii) {\n setC(llX[i]!, headerTop + actorBoxH - 1, JT, 'junction')\n if (!destroyed) setC(llX[i]!, footerBoxTop, JB, 'junction')\n }\n }\n\n // ---- DRAW: messages ----\n\n for (let m = 0; m < diagram.messages.length; m++) {\n const msg = diagram.messages[m]!\n const fi = actorIndexOf(msg.from)\n const ti = actorIndexOf(msg.to)\n const fromX = llX[fi]!\n const toX = llX[ti]!\n const isSelf = fi === ti\n const isDashed = msg.lineStyle === 'dashed'\n const isFilled = msg.arrowHead === 'filled'\n // \"lost message\" (-x/--x): a distinct cross terminator, not the plain\n // filled arrowhead it otherwise shares with ->>/-->>. Direction-\n // independent, unlike the arrow glyphs below — see issue #330.\n const isLost = msg.isLost === true\n const lostChar = useAscii ? 'x' : '✕'\n\n // Arrow line character (solid vs dashed)\n const lineChar = isDashed ? (useAscii ? '.' : '╌') : H\n\n if (isSelf) {\n // Self-message: 3-row loop to the right of the lifeline\n // ├──┐ (row 0 = msgArrowY)\n // │ │ Label (row 1)\n // │◄─┘ (row 2)\n //\n // The loop is only SELF_LOOP_WIDTH (4) columns wide by default, with no\n // spare room for a second arrowhead without corrupting the loop's\n // corner glyphs — so a bidirectional self-message still parses and\n // draws a single arrowhead only. An autonumbered self-message widens\n // the loop (see selfLoopWidth) so the badge digits fit at the start\n // of the top arm without touching the corner.\n const y0 = msgArrowY[m]!\n const loopW = selfLoopWidth(msg)\n const numStr =\n msg.seqNumber === undefined ? undefined : String(msg.seqNumber)\n // Split the label on <br/>-normalized newlines so multi-line self-arrow\n // labels get one row each instead of dumping a literal \\n mid-row.\n const msgLines = splitLines(msg.label)\n\n // Row 0: start junction + [autonumber badge] + horizontal + top-right\n // corner. The badge, when present, replaces the leading dashes rather\n // than sitting alongside them — mirroring how the normal-message\n // badge overwrites the start of its arrow line.\n setC(fromX, y0, JL, 'junction')\n let dashStart = fromX + 1\n if (numStr !== undefined) {\n for (let i = 0; i < numStr.length; i++)\n setC(fromX + 1 + i, y0, numStr[i]!, 'text')\n dashStart = fromX + 1 + numStr.length\n }\n for (let x = dashStart; x < fromX + loopW; x++)\n setC(x, y0, lineChar, 'line')\n setC(fromX + loopW, y0, useAscii ? '+' : '┐', 'corner')\n\n // Label rows: vertical on right side + one line of label text each\n const labelX = fromX + loopW + 2\n for (let lineIdx = 0; lineIdx < msgLines.length; lineIdx++) {\n const rowY = y0 + 1 + lineIdx\n setC(fromX + loopW, rowY, V, 'line')\n writeTextCells(labelX, rowY, msgLines[lineIdx]!, 'text', totalW)\n }\n\n // Bottom row: arrow-back + horizontal + bottom-right corner\n const bottomY = y0 + 1 + msgLines.length\n const arrowChar = isLost\n ? lostChar\n : isFilled\n ? useAscii\n ? '<'\n : '◀'\n : useAscii\n ? '<'\n : '◁'\n setC(fromX, bottomY, arrowChar, 'arrow')\n for (let x = fromX + 1; x < fromX + loopW; x++)\n setC(x, bottomY, lineChar, 'line')\n setC(fromX + loopW, bottomY, useAscii ? '+' : '┘', 'corner')\n } else {\n // Normal message: label on row above, arrow on row below\n const labelY = msgLabelY[m]!\n const arrowY = msgArrowY[m]!\n const leftToRight = fromX < toX\n\n // A creating message's arrow stops at the created box's near border\n // instead of the lifeline centre (the box occupies this row — see the\n // lifecycle comment near createdBoxAbove). `headX` is where the\n // arrowhead goes; the line runs from the sender up to it.\n const createsRecipient = createdByMsg.get(m) === ti\n let headX = toX\n if (createsRecipient) {\n const boxLeft = actorBoxLeft(ti, toX)\n const boxRight = boxLeft + actorBoxWidths[ti]! - 1\n headX = leftToRight ? boxLeft - 1 : boxRight + 1\n }\n\n // Draw label centered between the two lifelines (supports multi-line)\n const midX = Math.floor((fromX + toX) / 2)\n const msgLines = splitLines(msg.label)\n\n for (let lineIdx = 0; lineIdx < msgLines.length; lineIdx++) {\n const line = msgLines[lineIdx]!\n // Center on display width, not `.length` — see writeTextCells' doc\n // comment for why a code-unit-based offset under-centers CJK labels.\n const labelStart = midX - Math.floor(displayWidth(line) / 2)\n const y = labelY + lineIdx\n writeTextCells(labelStart, y, line, 'text', totalW)\n }\n\n // Draw arrow line\n if (leftToRight) {\n for (let x = fromX + 1; x < headX; x++)\n setC(x, arrowY, lineChar, 'line')\n // Arrowhead at destination\n const ah = isLost\n ? lostChar\n : isFilled\n ? useAscii\n ? '>'\n : '▶'\n : useAscii\n ? '>'\n : '▷'\n setC(headX, arrowY, ah, 'arrow')\n // Bidirectional (`<<->>` / `<<-->>`): mirror the arrowhead at the\n // departure end too. Both bidirectional tokens end in \">>\" (see\n // parser.ts), so isFilled is always true here — no open-head\n // variant to branch on, unlike the one-way `ah` glyph above.\n if (msg.bidirectional) {\n const ahStart = useAscii ? '<' : '◀'\n setC(fromX, arrowY, ahStart, 'arrow')\n }\n } else {\n for (let x = headX + 1; x < fromX; x++)\n setC(x, arrowY, lineChar, 'line')\n const ah = isLost\n ? lostChar\n : isFilled\n ? useAscii\n ? '<'\n : '◀'\n : useAscii\n ? '<'\n : '◁'\n setC(headX, arrowY, ah, 'arrow')\n if (msg.bidirectional) {\n const ahStart = useAscii ? '>' : '▶'\n setC(fromX, arrowY, ahStart, 'arrow')\n }\n }\n\n // autonumber badge: overwrite the start of the arrow line with the\n // sequence number, mirroring the small circled number the SVG\n // renderer draws over the start of the arrow (see renderer.ts's\n // renderSeqNumberBadge). Skipped if it wouldn't fit before the\n // opposite arrowhead — the minimum lifeline gap (10 cols) comfortably\n // fits typical 1-3 digit sequence numbers alongside short labels.\n if (msg.seqNumber !== undefined) {\n const numStr = String(msg.seqNumber)\n if (leftToRight) {\n const start = fromX + 1\n if (start + numStr.length < toX) {\n for (let i = 0; i < numStr.length; i++)\n setC(start + i, arrowY, numStr[i]!, 'text')\n }\n } else {\n const start = fromX - numStr.length\n if (start > headX) {\n for (let i = 0; i < numStr.length; i++)\n setC(start + i, arrowY, numStr[i]!, 'text')\n }\n }\n }\n }\n }\n\n // ---- DRAW: destroy crosses ----\n\n // Same glyph as a lost message's terminator, on the destroyed actor's\n // lifeline one row under the destroying arrow (see the lifecycle comment\n // near createdBoxAbove for why not on the arrow row itself).\n for (const [i, y] of destroyRow) {\n setC(llX[i]!, y, useAscii ? 'x' : '✕', 'arrow')\n }\n\n // ---- DRAW: blocks (loop, alt, opt, par, etc.) ----\n\n // Largest column index it's currently safe to write a block wall into.\n // Starts at the canvas's own right margin (mirrors the historical\n // `totalW - 1` clamp) and is pushed out via increaseSize/\n // increaseRoleCanvasSize below whenever a wall needs to grow past it.\n let blockCanvasMaxX = totalW - 1\n\n for (let b = 0; b < diagram.blocks.length; b++) {\n const block = diagram.blocks[b]!\n const topY = blockStartY.get(b)\n const botY = blockEndY.get(b)\n if (topY === undefined || botY === undefined) continue\n\n // Find the leftmost/rightmost lifelines involved in this block's messages\n let minLX = totalW\n let maxLX = 0\n for (let m = block.startIndex; m <= block.endIndex; m++) {\n if (m >= diagram.messages.length) break\n const msg = diagram.messages[m]!\n const f = actorIdx.get(msg.from) ?? 0\n const t = actorIdx.get(msg.to) ?? 0\n minLX = Math.min(minLX, llX[Math.min(f, t)]!)\n maxLX = Math.max(maxLX, llX[Math.max(f, t)]!)\n // Self-arrows draw their loop glyphs (├──┐ … ◀──┘) and label further\n // right than the lifeline itself — account for that extent too, or a\n // long self-arrow label gets clipped by / drawn outside the wall.\n if (f === t) {\n const selfRight =\n llX[f]! + selfLoopWidth(msg) + 2 + maxLineWidth(msg.label)\n maxLX = Math.max(maxLX, selfRight)\n }\n }\n\n let bLeft = minLX - BLOCK_WALL_MARGIN\n let bRight = maxLX + BLOCK_WALL_MARGIN\n\n // minLX/maxLX (and therefore bLeft/bRight) only account for lifelines\n // *this block's own messages* touch. That leaves a gap: the fixed\n // margin above can coincidentally place a wall exactly on — or past —\n // a different, untouched lifeline's column (#353).\n //\n // The fix is to PULL the wall back short of that lifeline, not push it\n // past. Verified against real mermaid.js's own SVG output for this\n // exact diagram (see scripts/lib/real-mermaid.ts, the same engine\n // behind GitHub's own mermaid preview): `loop`/`opt` enclose `Database`\n // there because their own messages touch it directly, but `alt` —\n // which never messages `Database` — stops well short of it (124px of\n // real clearance, not a few px of overshoot), even though `loop`/`opt`\n // in the same diagram extend ~11px *past* Database's lifeline to\n // enclose it. Real mermaid never widens a block's wall to enclose a\n // lifeline its own messages don't touch; it only ever clears one it\n // was already going to reach. Applies to every block type alike, both\n // walls.\n let nextRightLL = Number.POSITIVE_INFINITY\n let nextLeftLL = Number.NEGATIVE_INFINITY\n for (const x of llX) {\n if (x > maxLX && x < nextRightLL) nextRightLL = x\n if (x < minLX && x > nextLeftLL) nextLeftLL = x\n }\n // Never pull back past maxLX/minLX themselves — those already include\n // the self-arrow extent computed above, and an untouched lifeline\n // sitting close enough behind one can otherwise pull bRight below the\n // self-arrow's own label, which the later block-border draw then\n // overwrites (the label silently loses characters — CodeRabbit caught\n // this on this exact fix). Clearing the untouched lifeline yields to\n // not clipping this block's own content when the two can't both fit.\n if (bRight >= nextRightLL)\n bRight = Math.max(maxLX, nextRightLL - BLOCK_WALL_MARGIN)\n if (bLeft <= nextLeftLL)\n bLeft = Math.min(minLX, nextLeftLL + BLOCK_WALL_MARGIN)\n\n bLeft = Math.max(0, bLeft)\n if (bRight > blockCanvasMaxX) {\n increaseSize(canvas, bRight + 1, totalH - 1)\n increaseRoleCanvasSize(rc, bRight + 1, totalH - 1)\n blockCanvasMaxX = bRight\n }\n\n // Header (\"alt [label]\") and divider (\"[else label]\") text is drawn\n // starting at bLeft + 1 (see below), clipped to whatever bRight the\n // pull-back above produced. A wall sized purely from message spans\n // (even after that untouched-lifeline pull-back) has no relationship\n // to label length, so a long condition label was silently cut off\n // mid-word instead of the block widening to fit it (#352). Measure the\n // longest label among the header and every divider up front and widen\n // the wall — and the canvas itself, if the extra room isn't already\n // there — to fit it before any drawing happens.\n //\n // `bLeft` here MUST be the fully-resolved value above (post pull-back,\n // post clamp) — computing `neededRight` from an intermediate bLeft\n // would silently under-widen whenever minLX/the pull-back puts bLeft\n // below its margin (this repo's own #352 repro hits exactly that case:\n // `A` is actor 0, so minLX - BLOCK_WALL_MARGIN is negative and bLeft\n // only becomes 0 via the clamp above). Any participant this widening\n // would otherwise swallow was already pushed further right by the\n // lifeline-shifting layout pass earlier in this function, so growing\n // bRight here doesn't re-collide with whatever the pull-back above\n // just cleared.\n const hdrLabel = block.label ? `${block.type} [${block.label}]` : block.type\n const neededRight = bLeft + 1 + maxBlockLabelWidth(block)\n if (neededRight > bRight) {\n bRight = neededRight\n const [canvasMaxX] = getCanvasSize(canvas)\n if (bRight > canvasMaxX) {\n increaseSize(canvas, bRight, totalH - 1)\n increaseRoleCanvasSize(rc, bRight, totalH - 1)\n }\n totalW = Math.max(totalW, bRight + 1)\n blockCanvasMaxX = Math.max(blockCanvasMaxX, bRight)\n }\n\n // Top border with block type label\n setC(bLeft, topY, TL, 'border')\n for (let x = bLeft + 1; x < bRight; x++) setC(x, topY, H, 'border')\n setC(bRight, topY, TR, 'border')\n // Write block header label over the top border (supports multi-line)\n const hdrLines = splitLines(hdrLabel)\n\n for (\n let lineIdx = 0;\n lineIdx < hdrLines.length && topY + lineIdx < botY;\n lineIdx++\n ) {\n writeTextCells(\n bLeft + 1,\n topY + lineIdx,\n hdrLines[lineIdx]!,\n 'text',\n bRight,\n )\n }\n\n // Bottom border\n setC(bLeft, botY, BL, 'border')\n for (let x = bLeft + 1; x < bRight; x++) setC(x, botY, H, 'border')\n setC(bRight, botY, BR, 'border')\n\n // Side borders\n for (let y = topY + 1; y < botY; y++) {\n setC(bLeft, y, V, 'border')\n setC(bRight, y, V, 'border')\n }\n\n // Dividers\n for (let d = 0; d < block.dividers.length; d++) {\n const dY = divYMap.get(`${b}:${d}`)\n if (dY === undefined) continue\n const dashChar = isDashedH()\n setC(bLeft, dY, JL, 'junction')\n for (let x = bLeft + 1; x < bRight; x++) setC(x, dY, dashChar, 'line')\n setC(bRight, dY, JR, 'junction')\n // Divider label\n const dLabel = block.dividers[d]!.label\n if (dLabel) {\n const dStr = `[${dLabel}]`\n writeTextCells(bLeft + 1, dY, dStr, 'text', bRight)\n }\n }\n }\n\n // ---- DRAW: notes ----\n\n for (const np of notePositions) {\n // Ensure canvas is big enough\n increaseSize(canvas, np.x + np.width, np.y + np.height)\n increaseRoleCanvasSize(rc, np.x + np.width, np.y + np.height)\n // Top border\n setC(np.x, np.y, TL, 'border')\n for (let x = 1; x < np.width - 1; x++) setC(np.x + x, np.y, H, 'border')\n setC(np.x + np.width - 1, np.y, TR, 'border')\n // Content rows\n for (let l = 0; l < np.lines.length; l++) {\n const ly = np.y + 1 + l\n setC(np.x, ly, V, 'border')\n setC(np.x + np.width - 1, ly, V, 'border')\n // Blank the full interior (content + padding columns) first — the\n // note is drawn over lifelines that were already painted down every\n // row in this span, and writeTextCells below only touches the exact\n // cells the text occupies. Whenever a note's computed width happens\n // to put its own padding column on top of a lifeline's x position\n // (e.g. \"Note over A,B\" wide enough to reach B's lifeline), that\n // untouched padding column lets the stale lifeline character leak\n // through as a doubled border glyph right next to the note's own\n // border.\n for (let x = np.x + 1; x < np.x + np.width - 1; x++) {\n setC(x, ly, ' ', 'text')\n }\n writeTextCells(np.x + 1 + boxPad, ly, np.lines[l]!, 'text')\n }\n // Bottom border\n const by = np.y + np.height - 1\n setC(np.x, by, BL, 'border')\n for (let x = 1; x < np.width - 1; x++) setC(np.x + x, by, H, 'border')\n setC(np.x + np.width - 1, by, BR, 'border')\n }\n\n return canvasToString(canvas, { roleCanvas: rc, colorMode, theme })\n\n // ---- Helper: dashed horizontal character ----\n function isDashedH(): string {\n return useAscii ? '-' : '╌'\n }\n}\n","// ============================================================================\n// ASCII renderer — 1-D territory allocation\n//\n// Splits contested horizontal space between items (labels) whose row spans\n// overlap, giving each a mutually exclusive [left, right] column range\n// derived purely from geometry — never from draw order or from what an\n// earlier item happened to render as.\n//\n// Extracted from `class-diagram.ts`'s `territoryByRel` precompute (issue\n// #618). It stayed out of the shared `Grid` occupancy model (issue #532)\n// deliberately: this is a 1-D interval-scheduling problem over item\n// midpoints, not a 2-D occupancy map, so there is nothing to reserve and\n// nothing to look up per cell. Keeping it storage-agnostic — the caller\n// supplies geometry, this module supplies only the allocation — means a\n// second renderer can adopt it without first adopting a grid.\n// ============================================================================\n\n/**\n * One item's placement geometry, in whatever units the caller works in\n * (columns and rows, for the ASCII renderers).\n *\n * `start`/`end` describe the item's *natural* span — where it would sit if\n * nothing else competed for the space — and must already be clamped exactly\n * the way the caller's own draw pass clamps it. Passing an unclamped span\n * under-detects real collisions: an item pinned at the canvas edge draws at\n * column 0 but would be compared here as if it were still off-canvas at its\n * raw negative position, so the overlap with its neighbour is missed\n * entirely (issue #447).\n *\n * `rowStart`/`rowEnd` are inclusive and must likewise be the item's *final*\n * resolved rows, not a pre-fallback ideal: two items whose ideal rows don't\n * overlap can still resolve onto the same actual row, and a territory\n * computed from the ideal rows leaves them unsplit — so the later one's\n * draw silently overwrites the earlier one's (issue #531).\n */\nexport interface TerritoryGeometry {\n /** The item's preferred centre along the allocated axis. */\n idealMid: number\n /** Inclusive first column of the item's natural (uncontested) span. */\n start: number\n /** Inclusive last column of the item's natural (uncontested) span. */\n end: number\n /** Inclusive first row the item occupies. */\n rowStart: number\n /** Inclusive last row the item occupies. */\n rowEnd: number\n}\n\n/**\n * The columns an item may draw into, inclusive on both sides. An\n * unconstrained side is `-Infinity` / `Infinity` rather than a sentinel\n * number, so a caller can clamp or compare without special-casing.\n */\nexport interface Territory {\n left: number\n right: number\n}\n\n/** Whether two items' row spans actually intersect. */\nfunction rowsOverlap(a: TerritoryGeometry, b: TerritoryGeometry): boolean {\n return a.rowStart <= b.rowEnd && b.rowStart <= a.rowEnd\n}\n\n/**\n * Allocate each item a horizontal territory, splitting contested space\n * evenly at the midpoint between two competing items' `idealMid` values.\n *\n * Two items are competitors only when *both* their row spans intersect and\n * their natural spans overlap; anything else leaves both sides unconstrained\n * (`±Infinity`). Competitors are found by scanning outward in `idealMid`\n * order for the nearest row-overlapping item on each side — not by stopping\n * at the immediately adjacent one, since a non-colliding item can sit\n * between two that genuinely do collide (issue #531).\n *\n * Allocating up front, before anything is drawn, is what makes placement\n * independent of iteration order. A *reactive* approach — only avoiding\n * cells an earlier iteration already wrote — cascades: one item shifted into\n * view at the canvas edge eats its neighbour's rightful columns, that\n * neighbour then has nothing left to claim and vanishes, and everything\n * after it drifts based on the leftovers. With fixed, mutually exclusive\n * territories, a genuinely insufficient width truncates every competitor\n * consistently instead of destroying an arbitrary subset (issue #447).\n *\n * `geometry` is called exactly once per item, in `items` order. Items\n * tying on `idealMid` keep their `items` order (the sort is stable).\n *\n * @param items Items to allocate for, in any order.\n * @param geometry Extracts an item's placement geometry.\n * @returns Each item mapped to its allocated territory. Every item in\n * `items` is present; passing the same item reference twice yields one\n * entry (the last one allocated).\n */\nexport function allocateTerritory<T>(\n items: readonly T[],\n geometry: (item: T) => TerritoryGeometry,\n): Map<T, Territory> {\n const ordered = items\n .map((item) => ({ item, geom: geometry(item) }))\n .sort((a, b) => a.geom.idealMid - b.geom.idealMid)\n\n /**\n * The nearest entry in `dir` (-1 left, +1 right) from `idx`, by `idealMid`\n * order, whose rows actually overlap `ordered[idx]`'s — skipping over any\n * immediately-adjacent entries that don't.\n */\n const nearestRowOverlapping = (\n idx: number,\n dir: -1 | 1,\n ): TerritoryGeometry | undefined => {\n const g = ordered[idx]\n if (!g) return undefined\n for (let j = idx + dir; j >= 0 && j < ordered.length; j += dir) {\n const candidate = ordered[j]\n if (candidate && rowsOverlap(candidate.geom, g.geom))\n return candidate.geom\n }\n return undefined\n }\n\n const territories = new Map<T, Territory>()\n for (const [i, entry] of ordered.entries()) {\n const g = entry.geom\n const prev = nearestRowOverlapping(i, -1)\n const next = nearestRowOverlapping(i, 1)\n const left =\n prev && prev.end >= g.start\n ? Math.floor((prev.idealMid + g.idealMid) / 2) + 1\n : -Infinity\n const right =\n next && g.end >= next.start\n ? Math.floor((g.idealMid + next.idealMid) / 2)\n : Infinity\n territories.set(entry.item, { left, right })\n }\n return territories\n}\n","// ============================================================================\n// ASCII renderer — class diagrams\n//\n// Renders classDiagram text to ASCII/Unicode art.\n// Each class is a multi-compartment box (header | attributes | methods).\n// Relationships are drawn as lines between classes with UML markers.\n//\n// Layout: level-based top-down. \"From\" classes are placed above \"to\" classes\n// for all relationship types, matching ELK/mermaid.com behavior.\n// Relationship lines use simple Manhattan routing (vertical + horizontal).\n// ============================================================================\n\nimport {\n parseClassDiagram,\n formatClassMember,\n} from '@zombie-mermaid/mermaid-parser'\nimport type {\n ClassDiagram,\n ClassNode,\n ClassNote,\n RelationshipType,\n} from '@zombie-mermaid/mermaid-parser'\nimport type {\n AsciiConfig,\n Canvas,\n CharRole,\n AsciiTheme,\n ColorMode,\n} from './types.ts'\nimport {\n mkCanvas,\n mkRoleCanvas,\n canvasToString,\n increaseSize,\n increaseRoleCanvasSize,\n write,\n isJunctionChar,\n mergeJunctions,\n} from './canvas.ts'\nimport { drawMultiBox, measureMultiBox, classifyBoxChar } from './draw.ts'\nimport { allocateTerritory } from './territory.ts'\nimport type { Territory } from './territory.ts'\nimport { markBoxLabelLinks, mkLinkCanvas } from './hyperlinks.ts'\nimport type { LinkCanvas } from './hyperlinks.ts'\nimport { safeHref, splitStatements } from '@zombie-mermaid/core'\nimport { getCorners } from './shapes/corners.ts'\nimport { splitLines } from './multiline-utils.ts'\nimport { displayWidth, toDisplayCells } from './display-width.ts'\nimport { findFreeLane } from './lane-search.ts'\nimport { DEFAULT_PADDING_X, DEFAULT_PADDING_Y, paddingOffset } from './types.ts'\n\n/** Build the text sections for a class box: [header], [attributes], [methods] */\nfunction buildClassSections(cls: ClassNode): string[][] {\n // Header section: optional annotation + class name (may be multi-line)\n const header: string[] = []\n if (cls.annotation) header.push(`<<${cls.annotation}>>`)\n // Support multi-line class names\n const nameLines = splitLines(cls.label)\n header.push(...nameLines)\n\n // Attributes section\n const attrs = cls.attributes.map(formatClassMember)\n\n // Methods section\n const methods = cls.methods.map(formatClassMember)\n\n // Build sections from only the non-empty parts. Only attrs (2-section),\n // only methods (2-section), both (3-section), or neither (1-section, header only).\n const sections: string[][] = [header]\n if (attrs.length > 0) sections.push(attrs)\n if (methods.length > 0) sections.push(methods)\n return sections\n}\n\n// ============================================================================\n// Relationship marker characters\n// ============================================================================\n\ninterface RelMarker {\n /** Relationship type (determines marker shape) */\n type: RelationshipType\n /** Which end the marker is placed at */\n markerAt: 'from' | 'to'\n /** Whether the line is dashed */\n dashed: boolean\n}\n\n/**\n * Build the marker metadata for a relationship.\n * The actual marker character will be determined at placement time based on line direction.\n */\nfunction getRelMarker(\n type: RelationshipType,\n markerAt: 'from' | 'to',\n): RelMarker {\n const dashed = type === 'dependency' || type === 'realization'\n return { type, markerAt, dashed }\n}\n\n/**\n * Get the UML marker shape character for a relationship type.\n * For directional arrows (association/dependency), the direction parameter\n * specifies which way the arrow should point.\n */\nfunction getMarkerShape(\n type: RelationshipType,\n useAscii: boolean,\n direction?: 'up' | 'down' | 'left' | 'right',\n): string {\n switch (type) {\n case 'inheritance':\n case 'realization':\n // Hollow triangle - rotate based on line direction\n // Triangle points TOWARD the parent class\n if (direction === 'down') {\n // Line goes down (parent above, child below) - triangle points UP\n return useAscii ? '^' : '△'\n } else if (direction === 'up') {\n // Line goes up (parent below, child above) - triangle points DOWN\n return useAscii ? 'v' : '▽'\n } else if (direction === 'left') {\n // Line goes left - triangle points LEFT\n return useAscii ? '>' : '◁'\n } else {\n // Default: line goes right - triangle points RIGHT\n return useAscii ? '<' : '▷'\n }\n case 'composition':\n // Filled diamond - omnidirectional shape\n return useAscii ? '*' : '◆'\n case 'aggregation':\n // Hollow diamond - omnidirectional shape\n return useAscii ? 'o' : '◇'\n case 'association':\n case 'dependency':\n // Directional arrow - rotate based on line direction\n if (direction === 'down') {\n return useAscii ? 'v' : '▼'\n } else if (direction === 'up') {\n return useAscii ? '^' : '▲'\n } else if (direction === 'left') {\n return useAscii ? '<' : '◀'\n } else {\n // Default to right (or when direction not specified)\n return useAscii ? '>' : '▶'\n }\n }\n}\n\n/**\n * Clip a relationship label's padded display cells to fit within\n * [writableStart, writableEnd] — the room that's actually free on its row\n * before it would collide with a *different* relationship's label that a\n * previous iteration already drew there (see the call site in\n * `renderClassAscii` for how that room is computed).\n *\n * Deliberately clips in place rather than re-centering into the available\n * window: shifting a label's anchor to dodge a collision can push it\n * further right than its natural centered position, which then collides\n * with the *next* relationship's rightful space and cascades into\n * dropping labels entirely — this happened while fixing issue #447 (a\n * label pinned near the left canvas edge, itself already clamped rightward\n * by `Math.max(0, idealLabelStart)`, swallowed its neighbor's whole ideal\n * column). Truncated text gets an ellipsis on whichever side got clipped;\n * the label's own padding spaces are dropped first since they're the least\n * meaningful thing to lose.\n */\nfunction fitLabelToAvailableWidth(\n naturalStart: number,\n naturalCells: string[],\n text: string,\n writableStart: number,\n writableEnd: number,\n): { start: number; cells: string[] } {\n const naturalEnd = naturalStart + naturalCells.length - 1\n const clippedLeft = writableStart > naturalStart\n const clippedRight = writableEnd < naturalEnd\n if (!clippedLeft && !clippedRight) {\n return { start: naturalStart, cells: naturalCells }\n }\n\n // Only the side(s) that actually collided move inward — an unclipped\n // side keeps its natural bound. Using the raw `writableStart`/\n // `writableEnd` for *both* sides here (even the uncollided one) would\n // center the shrunk label inside the entire remaining canvas rather\n // than snug against its own natural extent, drifting it away from its\n // own relationship and into a completely different one's territory.\n const effectiveStart = clippedLeft ? writableStart : naturalStart\n const effectiveEnd = clippedRight ? writableEnd : naturalEnd\n const availableCols = Math.max(0, effectiveEnd - effectiveStart + 1)\n const ellipsisCount = (clippedLeft ? 1 : 0) + (clippedRight ? 1 : 0)\n\n if (availableCols === 0) return { start: effectiveStart, cells: [] }\n if (availableCols < ellipsisCount) {\n // Not even room for both ellipsis markers — show a single one rather\n // than nothing, so a squeezed-out label still leaves a visible trace.\n return { start: effectiveStart, cells: ['…'] }\n }\n\n const innerWidth = Math.max(0, availableCols - ellipsisCount)\n const textCells = toDisplayCells(text)\n const kept =\n textCells.length <= innerWidth\n ? textCells\n : clippedLeft && !clippedRight\n ? textCells.slice(textCells.length - innerWidth) // keep the tail\n : textCells.slice(0, innerWidth) // keep the head\n\n const cells = [\n ...(clippedLeft ? ['…'] : []),\n ...kept,\n ...(clippedRight ? ['…'] : []),\n ]\n // Center the shrunk label within the writable window so it still reads\n // near the relationship it belongs to, rather than hugging one edge.\n const start =\n effectiveStart + Math.max(0, Math.floor((availableCols - cells.length) / 2))\n return { start, cells }\n}\n\n/**\n * Display width of a relationship label as it is actually drawn: its widest\n * line plus one padding space on each side. Every pass that needs to know\n * how much horizontal room a label takes (column reservation, per-pair\n * column spacing, territory precomputation, drawing) must measure it the\n * same way, or the room reserved up front won't match what gets drawn.\n */\nfunction labelCellWidth(label: string): number {\n return Math.max(...splitLines(label).map((l) => displayWidth(l))) + 2\n}\n\n// ============================================================================\n// Layout and rendering\n// ============================================================================\n\n/** Positioned class node on the canvas */\ninterface PlacedClass {\n cls: ClassNode\n sections: string[][]\n x: number\n y: number\n width: number\n height: number\n}\n\n/** Per-render switches that aren't layout config (see `AsciiRenderOptions`). */\nexport interface ClassAsciiOptions {\n /** Wrap each `click`-linked class's name in an OSC 8 hyperlink pair. */\n hyperlinks?: boolean\n}\n\n/**\n * Render a Mermaid class diagram to ASCII/Unicode text.\n *\n * Pipeline: parse → build boxes → level-based layout → draw boxes → draw relationships → string.\n */\nexport function renderClassAscii(\n text: string,\n config: AsciiConfig,\n colorMode?: ColorMode,\n theme?: AsciiTheme,\n options: ClassAsciiOptions = {},\n): string {\n const lines = splitStatements(text)\n // Notes ride through the layout as pseudo-classes — see withNotePseudoClasses.\n const { diagram, notesById } = withNotePseudoClasses(parseClassDiagram(lines))\n\n if (diagram.classes.length === 0) return ''\n\n const useAscii = config.useAscii\n // See paddingOffset's doc comment (types.ts) for why these are an offset\n // from the padding defaults rather than the raw config values.\n const hGap = paddingOffset(config.paddingX, DEFAULT_PADDING_X, 4, 1) // horizontal gap between class boxes\n const vGap = paddingOffset(config.paddingY, DEFAULT_PADDING_Y, 3, 1) // vertical gap between levels (enough for relationship lines)\n\n // --- Build box dimensions for each class ---\n const classSections = new Map<string, string[][]>()\n const classBoxW = new Map<string, number>()\n const classBoxH = new Map<string, number>()\n\n for (const cls of diagram.classes) {\n const sections = buildClassSections(cls)\n classSections.set(cls.id, sections)\n\n // Reserve exactly what drawMultiBox will draw — measuring it here rather\n // than re-deriving the arithmetic keeps layout and drawing in lockstep for\n // wide-character (CJK/fullwidth) content.\n const { width: boxW, height: boxH } = measureMultiBox(\n sections,\n config.boxBorderPadding,\n )\n\n classBoxW.set(cls.id, boxW)\n classBoxH.set(cls.id, boxH)\n }\n\n // --- Assign levels: topological sort based on directed relationships ---\n // All relationship types place \"from\" above \"to\" in the layout, matching\n // ELK's layered algorithm and the official mermaid.com renderer behavior.\n // For \"Animal <|-- Dog\": from=\"Animal\", to=\"Dog\" → Animal above Dog.\n //\n // Every relationship type (including association and dependency) forces nodes\n // to different levels. Same-row routing for mixed diagrams causes collisions:\n // detour lines overlap with cross-level routing, and labels overwrite box borders.\n\n const classById = new Map<string, ClassNode>()\n for (const cls of diagram.classes) classById.set(cls.id, cls)\n\n const parents = new Map<string, Set<string>>() // child → set of parent IDs\n const children = new Map<string, Set<string>>() // parent → set of child IDs\n\n for (const rel of diagram.relationships) {\n // Level assignment always places \"from\" above \"to\", for every relationship\n // type — including inheritance and realization — matching real mermaid.js's\n // layout. This is independent of which end carries the UML marker glyph\n // (`rel.markerAt`, used only to orient the arrowhead when drawing the line\n // below); e.g. `Bird ..|> Flyable` (markerAt='to') places Bird above\n // Flyable even though the hollow-triangle marker touches Flyable. See\n // issue #446 — this used to special-case inheritance/realization to put\n // whichever end held the marker on top, which produced the correct order\n // for `<|--` (where marker happens to be at 'from') but reversed it for\n // `..|>` (where marker is at 'to').\n const parentId = rel.from\n const childId = rel.to\n\n const parentSet = parents.get(childId) ?? new Set<string>()\n parents.set(childId, parentSet)\n parentSet.add(parentId)\n const childSet = children.get(parentId) ?? new Set<string>()\n children.set(parentId, childSet)\n childSet.add(childId)\n }\n\n // BFS from roots (classes that have no parents) to assign levels.\n // Cap at classes.length - 1 to prevent infinite loops on cyclic graphs\n // (e.g. View --> Model and Model ..> View would otherwise push levels\n // upward forever). In a DAG the longest path has at most N-1 edges.\n const level = new Map<string, number>()\n const roots = diagram.classes.filter(\n (c) => !parents.has(c.id) || parents.get(c.id)!.size === 0,\n )\n const queue: string[] = roots.map((c) => c.id)\n for (const id of queue) level.set(id, 0)\n\n const levelCap = diagram.classes.length - 1\n let qi = 0\n while (qi < queue.length) {\n const id = queue[qi++]!\n const childSet = children.get(id)\n if (!childSet) continue\n for (const childId of childSet) {\n const newLevel = (level.get(id) ?? 0) + 1\n if (newLevel > levelCap) continue // cycle detected — skip to prevent infinite loop\n if (!level.has(childId) || level.get(childId)! < newLevel) {\n level.set(childId, newLevel)\n queue.push(childId)\n }\n }\n }\n\n // Assign remaining (unconnected) classes to level 0\n for (const cls of diagram.classes) {\n if (!level.has(cls.id)) level.set(cls.id, 0)\n }\n\n // A `note for X` note has no relationships, so it landed on level 0 above;\n // move it onto its class's row so the row placement below puts it right\n // beside that class.\n pinNoteLevels(notesById, level)\n\n // --- Position classes by level ---\n // Group classes by level\n const maxLevel = Math.max(...[...level.values()], 0)\n const levelGroups: string[][] = Array.from({ length: maxLevel + 1 }, () => [])\n for (const cls of diagram.classes) {\n levelGroups[level.get(cls.id)!]!.push(cls.id)\n }\n\n // When more than one relationship connects the same pair of classes —\n // most commonly a pair going in opposite directions, e.g. both\n // `View --> Model` and `Model ..> View` — every relationship's connection\n // point defaults to the exact same box-center column. Left alone, that\n // means their lines, arrowheads, and labels all land on the same cells:\n // whichever relationship draws last silently overwrites the other's, so\n // it appears to vanish from the ASCII output entirely (issue #448). Give\n // each relationship in such a group its own column, spread symmetrically\n // around the box center, so their routes never start from the same point.\n // Spacing is sized to the widest label in the group (not a fixed\n // constant) so long labels still clear each other horizontally.\n //\n // Computed before positions are assigned (it depends only on the\n // relationship list) because the column reservation below needs to know\n // how far each group fans out from its boxes' centers.\n const relColumnOffset = new Map<number, number>()\n /** Distance between a group's outermost lanes, keyed by member — see `anchorOffset`. */\n const relGroupSpread = new Map<number, number>()\n {\n const pairGroups = new Map<string, number[]>()\n diagram.relationships.forEach((rel, i) => {\n const pairKey = [rel.from, rel.to].sort().join('::')\n const group = pairGroups.get(pairKey) ?? []\n group.push(i)\n pairGroups.set(pairKey, group)\n })\n for (const group of pairGroups.values()) {\n if (group.length < 2) continue\n const n = group.length\n const widestLabel = Math.max(\n ...group.map((i) => {\n const label = diagram.relationships[i]!.label\n if (!label) return 3 // room for just a line/arrow\n return labelCellWidth(label)\n }),\n )\n const step = widestLabel + 1 // +1 for a visual gap between labels\n group.forEach((relIndex, pos) => {\n relColumnOffset.set(relIndex, Math.round((pos - (n - 1) / 2) * step))\n relGroupSpread.set(relIndex, (n - 1) * step)\n })\n }\n }\n\n // Fan relationships that converge on the same target (or diverge from the\n // same source) even when they come from/go to otherwise-unrelated\n // classes — e.g. `Teacher --> Course` and `Student --> Course`, two\n // distinct pairs that both terminate at Course. `pairGroups` above only\n // catches multiple relationships between the exact same two classes;\n // nothing previously separated these, so both anchored at Course's exact\n // center column. That collapsed their lines and arrowheads onto the same\n // cells, and — since a relationship's only horizontal jog can land on a\n // single shared row when the two source rows/box heights are equal —\n // let one relationship's label (drawn in a later pass, unaware of the\n // collision) blot out the entire visible portion of the *other*\n // relationship's connector, leaving it looking like it stops short of\n // its target instead of merely overlapping (issue #632).\n //\n // Scoped to the shared end only — the far end keeps its own center\n // column — since, unlike a duplicate pair, the *other* end of each\n // relationship is a different, unrelated class that needs no fanning of\n // its own. A relationship already offset by `pairGroups` above keeps\n // that symmetric (both-ends) offset unchanged; only relationships with\n // no pair-based offset get fanned here.\n //\n // Further scoped to relationships whose target sits exactly one level\n // below their source (`level.get(to) - level.get(from) === 1`) — a\n // direct, adjacent-level convergence/divergence like this one. A\n // relationship that instead skips a level (e.g. `A --> C` when `B` sits\n // between them at `A --> B --> C`) already needs the existing\n // box-collision detour routing below (`findClearColumn`) to route around\n // the intervening class; fanning its anchor here too would fight that\n // routing over the same geometry it depends on, corrupting it. Excluding\n // skip-level relationships from the group entirely — rather than just\n // from getting an offset — also keeps a same-level sibling's own offset\n // arithmetic (position-in-group, spread) based only on the other\n // relationships it could actually collide with.\n const relFromOffset = new Map<number, number>()\n const relFromSpread = new Map<number, number>()\n const relToOffset = new Map<number, number>()\n const relToSpread = new Map<number, number>()\n {\n const fanBySharedEndpoint = (\n endpointOf: (rel: (typeof diagram.relationships)[number]) => string,\n offsetOut: Map<number, number>,\n spreadOut: Map<number, number>,\n ): void => {\n const groups = new Map<string, number[]>()\n diagram.relationships.forEach((rel, i) => {\n if (relColumnOffset.has(i)) return // already fanned as a duplicate pair\n if (!classById.has(rel.from) || !classById.has(rel.to)) return\n const fromLevel = level.get(rel.from) ?? 0\n const toLevel = level.get(rel.to) ?? 0\n if (toLevel - fromLevel !== 1) return // skip-level — leave to detour routing\n const key = endpointOf(rel)\n const group = groups.get(key) ?? []\n group.push(i)\n groups.set(key, group)\n })\n for (const group of groups.values()) {\n if (group.length < 2) continue\n const n = group.length\n const widestLabel = Math.max(\n ...group.map((i) => {\n const label = diagram.relationships[i]!.label\n if (!label) return 3 // room for just a line/arrow\n return labelCellWidth(label)\n }),\n )\n const step = widestLabel + 1 // +1 for a visual gap between labels\n group.forEach((relIndex, pos) => {\n offsetOut.set(relIndex, Math.round((pos - (n - 1) / 2) * step))\n spreadOut.set(relIndex, (n - 1) * step)\n })\n }\n }\n fanBySharedEndpoint((rel) => rel.to, relToOffset, relToSpread)\n fanBySharedEndpoint((rel) => rel.from, relFromOffset, relFromSpread)\n }\n\n // --- Reserve column room for relationship labels and fanned-out groups ---\n // A class's horizontal slot used to be exactly its box's content width, so\n // a narrow box (a single-letter class with no members) whose relationship\n // carries a long label had nowhere to put that label: neighbouring labels\n // fought over the same cells and the territory pass below truncated them\n // all to `…` even though the diagram could simply have spread the columns\n // further apart (issue #488). The same shortfall broke multi-relationship\n // groups: the per-pair offsets above can fan out well past a narrow box's\n // edges, and clamping them back inside the box collapsed distinct\n // relationships onto one connection point (issue #489).\n //\n // So each class's column *slot* is its box plus whatever its\n // relationships overhang past the box's edges: for every relationship,\n // the label (or bare line) centered on that relationship's lane reaches\n // some distance left and right of the box's center column, and the slot\n // is padded by however much of that reach falls outside the box. The box\n // itself keeps its content width; only the gap around it grows, and only\n // on the side and by the amount a label or group actually needs — a\n // right-side overhang on the last class of a level, say, moves nothing.\n // Every relationship reserves at *both* of its endpoints so a vertical\n // pair (A above B) is padded identically and B stays directly under A.\n const columnReach = new Map<string, { left: number; right: number }>()\n for (const cls of diagram.classes) {\n columnReach.set(cls.id, { left: 0, right: 0 })\n }\n diagram.relationships.forEach((rel, relIndex) => {\n if (!classById.has(rel.from) || !classById.has(rel.to)) return\n // Mirror the draw pass: a label of `cellW` cells centered on its lane\n // starts `floor(cellW / 2)` cells left of the lane; a bare line/arrow\n // is a single cell on the lane itself. Each endpoint reserves room\n // using its own offset — a duplicate-pair member's offset is the same\n // at both ends, but a relationship fanned only at its shared target (or\n // source) — see `relFromOffset`/`relToOffset` above — must reserve\n // differently at each end, matching where its lane actually sits there.\n const cellW = rel.label ? labelCellWidth(rel.label) : 1\n const fromOffset =\n relColumnOffset.get(relIndex) ?? relFromOffset.get(relIndex) ?? 0\n const toOffset =\n relColumnOffset.get(relIndex) ?? relToOffset.get(relIndex) ?? 0\n const fromReach = columnReach.get(rel.from)!\n fromReach.left = Math.max(\n fromReach.left,\n Math.floor(cellW / 2) - fromOffset,\n )\n fromReach.right = Math.max(\n fromReach.right,\n fromOffset + cellW - 1 - Math.floor(cellW / 2),\n )\n const toReach = columnReach.get(rel.to)!\n toReach.left = Math.max(toReach.left, Math.floor(cellW / 2) - toOffset)\n toReach.right = Math.max(\n toReach.right,\n toOffset + cellW - 1 - Math.floor(cellW / 2),\n )\n })\n\n // Compute positions: each level is a row, classes in a row are spaced horizontally\n const placed = new Map<string, PlacedClass>()\n let currentY = 0\n // Right edge of the widest level's last slot — a slot can be wider than\n // its box, so the canvas has to cover the slot, not just the box.\n let maxSlotEnd = 0\n\n for (let lv = 0; lv <= maxLevel; lv++) {\n const group = levelGroups[lv]!\n if (group.length === 0) continue\n\n // --- #970's block-based x-assignment (issues #971, #972) ---\n // docs/decisions/ascii-class-diagram-x-coordinate-assignment-970.md.\n // Alignment blocks group classes at this level sharing the exact same\n // parent-id set, processed as a unit. A block's *qualifying* parents\n // are the subset strictly shallower than this level — a parent at the\n // same level (a rootless relationship cycle, whose members still\n // record each other as \"parents\" in `parents` even though nothing\n // above them ever resolves) never qualifies. A block with zero\n // qualifying parents has \"no resolvable parents\" and every member\n // keeps its plain left-to-right baseline position, computed\n // individually (not as a joint unit — #971's exact per-class\n // behavior, preserved verbatim for this shape, including when a\n // no-resolvable-parents block's members happen not to be declaration-\n // adjacent). A block with at least one qualifying parent computes a\n // real desired center: the mean of its qualifying parents' own,\n // already-placed box centers (a single qualifying parent's mean is\n // just that parent's center — the #964 repro exactly, #971's case; a\n // block of more than one class sharing that parent set spreads evenly\n // by centering the block's *total* width on that shared center, #972's\n // \"multiple children of one parent\" case; more than one qualifying\n // parent averages their centers, #972's \"converging edges\" case).\n // Either way, the block's desired *left* edge (its slot-space left,\n // matching `currentX`'s role below) is `desiredCenter -\n // floor(totalWidth / 2)` — floor chosen only for determinism. A single\n // left-to-right compaction pass then resolves any collision between\n // *any* two blocks' desired positions — universal across every block\n // shape, not just multi-parent ones, since two independently-aligned\n // singleton blocks can still want overlapping positions (#971) — by\n // only ever pushing a block right of its desired position, never left,\n // and never reordering blocks relative to each other.\n //\n // A class with no parents at all is always its own singleton block —\n // two unrelated roots must never be merged into one block just because\n // both happen to have an empty parent set; there is nothing shared to\n // align them around.\n const blockKeyOf = (id: string): string => {\n const pset = parents.get(id)\n return pset && pset.size > 0 ? [...pset].sort().join(',') : `root:${id}`\n }\n // Every member sharing a given block key, regardless of adjacency in\n // `group` — looked up once a block is actually confirmed to have a\n // shared center to align around (see the main loop below). Building\n // this eagerly for every id up front, and processing every block's\n // *entire* membership together the moment its first (in declaration\n // order) member is reached — even for a block with no resolvable\n // parents — used to silently reorder an unrelated class that was\n // declared between two same-parent-set siblings ahead of it, moving it\n // visually later than its own declaration position (caught in review\n // on #972). The main loop below only merges members into one placement\n // unit for a block that actually resolves to a shared center; a\n // no-resolvable-parents \"block\" is never merged, so it can't reorder\n // anything.\n const blockMembers = new Map<string, string[]>()\n for (const id of group) {\n const key = blockKeyOf(id)\n let members = blockMembers.get(key)\n if (!members) {\n members = []\n blockMembers.set(key, members)\n }\n members.push(id)\n }\n\n // Baseline: today's plain left-to-right slot position for every class\n // in this level, computed exactly as the pre-#971 algorithm did — the\n // fallback position for a no-resolvable-parents block's members, and\n // the starting point the compaction step below can only ever push\n // right of, never left.\n const baselineSlotLeft = new Map<string, number>()\n const slotWidthOf = new Map<string, number>()\n const leftPadOf = new Map<string, number>()\n {\n let x = 0\n for (const id of group) {\n const w = classBoxW.get(id)!\n // The box's center column sits `floor(w / 2)` cells from its left\n // edge (the same arithmetic `connectionColumns` uses), so anything\n // reaching further than that past the center overhangs the box.\n const reach = columnReach.get(id)!\n const leftPad = Math.max(0, reach.left - Math.floor(w / 2))\n const rightPad = Math.max(0, reach.right - (w - 1 - Math.floor(w / 2)))\n baselineSlotLeft.set(id, x)\n slotWidthOf.set(id, leftPad + w + rightPad)\n leftPadOf.set(id, leftPad)\n x += leftPad + w + rightPad + hGap\n }\n }\n\n let currentX = 0\n let maxH = 0\n\n /** Places one class's box at slot-left `slotLeft`, advancing `currentX`/`maxH`/`maxSlotEnd`. */\n const placeAt = (id: string, slotLeft: number): void => {\n const cls = classById.get(id)!\n const w = classBoxW.get(id)!\n const h = classBoxH.get(id)!\n const leftPad = leftPadOf.get(id)!\n const slotWidth = slotWidthOf.get(id)!\n placed.set(id, {\n cls,\n sections: classSections.get(id)!,\n // Shifted right only by the left overhang, so a box with nothing\n // reaching past its left edge lands exactly where its slot does.\n x: slotLeft + leftPad,\n y: currentY,\n width: w,\n height: h,\n })\n maxSlotEnd = Math.max(maxSlotEnd, slotLeft + slotWidth)\n currentX = slotLeft + slotWidth + hGap\n maxH = Math.max(maxH, h)\n }\n\n const placedThisLevel = new Set<string>()\n for (const id of group) {\n // Already placed as part of an earlier qualifying block's joint\n // placement below (never true for a no-resolvable-parents id, which\n // is always placed the moment the loop reaches it, in its own true\n // declaration-order turn).\n if (placedThisLevel.has(id)) continue\n\n const key = blockKeyOf(id)\n const pset = parents.get(id)\n // `level.get(pid)!`: every relationship endpoint is guaranteed a\n // `classById`/`level` entry (the parser's `ensureClass` auto-creates\n // an implicit class for any id used only as a relationship endpoint,\n // never just a dangling reference — see `classById.has(...)`'s own\n // guards elsewhere in this file, which protect a *different* case),\n // so this is never actually missing.\n const qualifying = pset\n ? [...pset].filter((pid) => level.get(pid)! < lv)\n : []\n\n if (qualifying.length === 0) {\n // No resolvable parents: this id has no shared center to align\n // around, so it is never merged into a joint block with the other\n // members `blockKeyOf` groups it with — only *this* id is placed\n // here, in its own true position in `group`'s declaration order,\n // exactly matching #971's per-class behavior for this shape. Any\n // other same-key member is placed independently, on its own later\n // turn through this same loop — merging them together here would\n // reorder whatever was declared between them ahead of its own\n // declaration position (issue caught in #972's review).\n const slotLeft = Math.max(baselineSlotLeft.get(id)!, currentX, 0)\n placeAt(id, slotLeft)\n placedThisLevel.add(id)\n continue\n }\n\n // A block with a real shared center to align around *is* placed as\n // a joint unit, gathering every member `blockKeyOf` groups with it\n // regardless of adjacency in `group` — the whole reason to group\n // same-parent-set siblings together in the first place.\n const members = blockMembers.get(key)!\n\n // `placed.get(...)!`: every id in `qualifying` has a strictly-\n // shallower level than `lv`, and this loop places every class of a\n // level before moving to the next, so a strictly-shallower class is\n // always already placed by the time this level is processed.\n // \"Center column\" is always a box's own rendered center, never the\n // reach-padded slot's — aligning to the slot center instead would\n // visually misalign the boxes whenever a relationship's label\n // overhang is asymmetric.\n const parentCenters = qualifying.map((pid) => {\n const p = placed.get(pid)!\n return p.x + Math.floor(p.width / 2)\n })\n const desiredCenter = Math.round(\n parentCenters.reduce((sum, c) => sum + c, 0) / parentCenters.length,\n )\n\n let totalWidth = 0\n for (let i = 0; i < members.length; i++) {\n totalWidth += slotWidthOf.get(members[i]!)!\n if (i < members.length - 1) totalWidth += hGap\n }\n // `totalWidth` is the *slot* span (every member's own reach padding\n // included at both ends), but the thing that must visually center on\n // `desiredCenter` is the *rendered box* span — from the first\n // member's own box left edge to the last member's own box right\n // edge, which excludes the outermost reach padding at each end (that\n // padding only ever separates a box from a *neighbor*; at the\n // block's own two ends there is no neighbor to separate from, so it\n // just becomes asymmetric dead space that would otherwise skew the\n // visible boxes off `desiredCenter` whenever the first member's\n // leftPad differs from the last member's rightPad — e.g. one child\n // has a long incoming label reserving room on its own left, the\n // other has none). Subtracting them back out here, once, is cheaper\n // than threading a separate \"visible width\" accumulator through the\n // loop above (caught in #972's review).\n const firstLeftPad = leftPadOf.get(members[0]!)!\n const lastMember = members[members.length - 1]!\n const lastRightPad =\n slotWidthOf.get(lastMember)! -\n leftPadOf.get(lastMember)! -\n classBoxW.get(lastMember)!\n const visibleWidth = totalWidth - firstLeftPad - lastRightPad\n const blockDesiredLeft =\n desiredCenter - firstLeftPad - Math.floor(visibleWidth / 2)\n const blockActualLeft = Math.max(blockDesiredLeft, currentX, 0)\n\n let memberX = blockActualLeft\n for (const memberId of members) {\n placeAt(memberId, memberX)\n placedThisLevel.add(memberId)\n memberX = currentX\n }\n }\n\n currentY += maxH + vGap\n }\n\n // --- Create canvas ---\n let totalW = maxSlotEnd\n let totalH = 0\n for (const p of placed.values()) {\n totalW = Math.max(totalW, p.x + p.width)\n totalH = Math.max(totalH, p.y + p.height)\n }\n\n // Extra space for relationship lines that may go below/beside\n totalW += 4\n totalH += 2\n\n const canvas = mkCanvas(totalW - 1, totalH - 1)\n const rc = mkRoleCanvas(totalW - 1, totalH - 1)\n\n /**\n * Set a character on the canvas and track its role.\n * Delegates bounds-checking to the shared `write()` primitive\n * (src/ascii/canvas.ts) instead of duplicating the guard here — see\n * issue #171.\n */\n function setC(x: number, y: number, ch: string, role: CharRole): void {\n write(canvas, x, y, ch, { role, roleCanvas: rc })\n }\n\n // Opt-in OSC 8 hyperlinks (see hyperlinks.ts): the link canvas is sized to\n // the main canvas, so a cell clipped by the `cx < totalW` guard below is\n // clipped here too.\n const linkCanvas: LinkCanvas | undefined = options.hyperlinks\n ? mkLinkCanvas(totalW - 1, totalH - 1)\n : undefined\n\n // --- Draw class boxes ---\n for (const p of placed.values()) {\n const boxCanvas = drawMultiBox(\n p.sections,\n useAscii,\n config.boxBorderPadding,\n )\n // Copy box onto main canvas at (p.x, p.y) with role tracking\n for (let bx = 0; bx < boxCanvas.length; bx++) {\n for (let by = 0; by < boxCanvas[0]!.length; by++) {\n const ch = boxCanvas[bx]![by]!\n if (ch !== ' ') {\n const cx = p.x + bx\n const cy = p.y + by\n if (cx < totalW && cy < totalH) {\n setC(cx, cy, ch, classifyBoxChar(ch))\n }\n }\n }\n }\n\n // A `click` href links the class-name line(s) of the header section —\n // box row 1 is the first header line, which is the `<<annotation>>`\n // when there is one, so the name starts on the row after it. The SVG\n // renderer wraps the whole class box in an <a>; in a terminal, the name\n // is the natural analog of \"the label\".\n if (linkCanvas) {\n const href = safeHref(diagram.interactions.get(p.cls.id)?.href)\n const header = p.sections[0]\n if (href !== undefined && header !== undefined) {\n const nameRowStart = 1 + (p.cls.annotation ? 1 : 0)\n markBoxLabelLinks(linkCanvas, boxCanvas, { x: p.x, y: p.y }, href, {\n from: nameRowStart,\n to: header.length,\n })\n }\n }\n }\n\n // --- Snapshot cells occupied by class boxes ---\n // Taken once, right after boxes are drawn and before any relationship\n // line, corner, marker, or label is drawn — the class-diagram analog of\n // the `boxCells`/`setCGuarded` guard er-diagram.ts gained for issue #350.\n // Two independent routing branches rely on it:\n // - The cross-level (\"target below\"/\"target above\") branches, whose\n // routing can jog a long way horizontally with no prior occupancy\n // check — e.g. connecting a level-1 child to a level-0 parent whose\n // column sits far from an intervening, taller same-level sibling's\n // box, which the jog then cuts straight through.\n // - The same-level branch's own detour (this file's final `else`), which\n // primarily avoids a same-row obstruction by computing its detour row\n // from this same box geometry (see `obstructionBottom` there); this\n // snapshot is the last-resort backstop for whatever that routing\n // doesn't anticipate.\n const boxCells = new Set<string>()\n for (const p of placed.values()) {\n for (let by = 0; by < p.height; by++) {\n for (let bx = 0; bx < p.width; bx++) {\n boxCells.add(`${p.x + bx},${p.y + by}`)\n }\n }\n }\n\n /**\n * Like setC, but refuses to draw into a cell already occupied by a class\n * box (see boxCells above). Used by both the cross-level relationship\n * branches (where a mis-routed jog can cut straight through an\n * unrelated, taller same-level box) and the same-level branch's own\n * detour (a last-resort backstop for whatever its obstruction-aware\n * routing doesn't anticipate — see `obstructionBottom` below), so either\n * kind of mis-routed segment degrades to a gap in the line rather than\n * corrupting a box's border or attribute/method text.\n */\n function setCGuarded(x: number, y: number, ch: string, role: CharRole): void {\n if (boxCells.has(`${x},${y}`)) return\n setC(x, y, ch, role)\n }\n\n /** Check if a point (x, y) is inside any class box */\n function isInsideBox(\n x: number,\n y: number,\n excludeIds?: Set<string>,\n ): boolean {\n for (const [id, p] of placed.entries()) {\n if (excludeIds?.has(id)) continue\n if (\n x >= p.x &&\n x <= p.x + p.width - 1 &&\n y >= p.y &&\n y <= p.y + p.height - 1\n ) {\n return true\n }\n }\n return false\n }\n\n /** Find a clear vertical column for routing that doesn't pass through any boxes */\n function findClearColumn(\n startX: number,\n y1: number,\n y2: number,\n excludeIds: Set<string>,\n ): number {\n const columnIsClear = (x: number): boolean => {\n for (let y = Math.min(y1, y2); y <= Math.max(y1, y2); y++) {\n if (isInsideBox(x, y, excludeIds)) return false\n }\n return true\n }\n\n // Scan outward from `startX`, right before left at each distance, never\n // off the left edge of the canvas. The right bound reaches far enough\n // past the widest box that a column clear of every box is always found\n // in practice — but the caller-side fallback below stays as a backstop\n // rather than being asserted away.\n return (\n findFreeLane(startX, 0, startX + totalW + 9, columnIsClear) ?? totalW + 2\n )\n }\n\n // --- Draw relationship lines ---\n const H = useAscii ? '-' : '─'\n const V = useAscii ? '|' : '│'\n const dashH = useAscii ? '.' : '╌'\n const dashV = useAscii ? ':' : '┊'\n\n /**\n * Offset from a box's center column to the *anchor* where one\n * relationship's line touches that box's border — always within the\n * box's own width, so a line visibly leaves from (or arrives at) the box\n * rather than from empty space beside it. This is the only place the\n * per-pair offset is reined in: the *lane* the line then runs along\n * keeps the full offset (see `connectionColumns`), which the column\n * reservation in the layout pass guarantees room for.\n *\n * A group whose lanes fan out wider than the box has its anchors spread\n * evenly across the box's full width (corner columns included) by\n * scaling the whole group down by one factor, so as many relationships\n * as the box has columns keep a distinct border cell — and so a distinct\n * arrowhead. Clamping each lane on its own instead collapsed every outer\n * lane onto the same edge column, which is exactly how #489's four\n * relationships ended up sharing two connection points. Only a group\n * with more members than the box has columns still has to share.\n */\n function anchorOffset(\n offset: number,\n spread: number,\n boxWidth: number,\n ): number {\n // Columns available on each side of the center — the center itself is\n // `floor(boxWidth / 2)` in from the left edge, matching `connectionColumns`.\n const left = Math.floor(boxWidth / 2)\n const right = boxWidth - 1 - left\n const scale = spread > left + right ? (left + right) / spread : 1\n return Math.max(-left, Math.min(right, Math.round(offset * scale)))\n }\n\n /**\n * Connection columns for one relationship, shifted by its per-pair offset.\n *\n * `fromCX`/`toCX` are the *lanes*: the columns the line's vertical run,\n * arrowhead-side jog, and label all sit on. They carry the full offset,\n * so relationships in a group never share a lane. Every pass that\n * positions something relative to a relationship's route — the\n * line-drawing loop, the label-territory precompute, and the\n * label-drawing pass — must go through this one helper and use these\n * lanes, so the label ends up on the same column the line actually runs\n * along. When only the line pass applied the offset (an earlier version\n * of this fix), a reciprocal pair's labels were still measured and drawn\n * against the shared box center: the territory pass saw two labels with\n * identical midpoints and split the column between them, truncating both\n * to a single mashed `rea……ies` while their lines sat on separate columns\n * (issue #448).\n *\n * `fromAnchorX`/`toAnchorX` are where the line meets each box border —\n * the lane pulled back inside the box (see `anchorOffset`). When a group\n * fans out wider than a narrow box (issue #489: four relationships\n * between two single-letter classes), a lane can sit well outside the\n * box; the line then jogs horizontally along the row adjacent to the box\n * from the anchor out to its lane (see `drawJog`) instead of the lanes\n * being collapsed onto the few columns the box has, which made distinct\n * relationships overwrite one another. Relationships may end up sharing\n * an anchor when a group outnumbers the box's columns — that's fine,\n * their jogs merge into a small trunk at the border — but never a lane.\n * Labels always use the lane, never the anchor: only the line's\n * box-border touchpoint needs pulling back inside the box.\n *\n * The offset applied at each end is resolved independently: a\n * relationship in a duplicate-pair group (`relColumnOffset`) uses that\n * same offset at both ends, as before, but one that only shares a single\n * endpoint with other relationships (`relFromOffset`/`relToOffset` — see\n * their doc comment above) is fanned at that shared end only, leaving its\n * other, unrelated end anchored on its own box center (issue #632).\n */\n function connectionColumns(\n relIndex: number,\n fromP: PlacedClass,\n toP: PlacedClass,\n ): {\n fromCX: number\n toCX: number\n fromAnchorX: number\n toAnchorX: number\n } {\n const fromOffset =\n relColumnOffset.get(relIndex) ?? relFromOffset.get(relIndex) ?? 0\n const toOffset =\n relColumnOffset.get(relIndex) ?? relToOffset.get(relIndex) ?? 0\n const fromSpread =\n relGroupSpread.get(relIndex) ?? relFromSpread.get(relIndex) ?? 0\n const toSpread =\n relGroupSpread.get(relIndex) ?? relToSpread.get(relIndex) ?? 0\n const fromCenter = fromP.x + Math.floor(fromP.width / 2)\n const toCenter = toP.x + Math.floor(toP.width / 2)\n return {\n fromCX: fromCenter + fromOffset,\n toCX: toCenter + toOffset,\n fromAnchorX:\n fromCenter + anchorOffset(fromOffset, fromSpread, fromP.width),\n toAnchorX: toCenter + anchorOffset(toOffset, toSpread, toP.width),\n }\n }\n\n /**\n * Write one cell of a jog, merging with whatever another relationship's\n * jog already drew there. Jogs of one group all run along the same row\n * next to the box, so they routinely overlap: two relationships sharing\n * an anchor stack their corners on one cell, and an outer relationship's\n * jog passes straight through an inner one's lane corner. Compositing\n * box-drawing glyphs (`─` over `┌` → `┬`, `┘` over `┘` → `┘`) turns those\n * overlaps into a readable trunk instead of the last write winning. A\n * marker glyph already placed by an earlier relationship is left alone —\n * an arrowhead at a shared anchor doubles as the junction.\n */\n function setJogCell(x: number, y: number, ch: string, role: CharRole): void {\n if (boxCells.has(`${x},${y}`)) return\n if (rc[x]?.[y] === 'arrow') return\n const existing = canvas[x]?.[y]\n if (\n !useAscii &&\n existing !== undefined &&\n isJunctionChar(existing) &&\n isJunctionChar(ch)\n ) {\n const merged = mergeJunctions(existing, ch)\n setC(x, y, merged, merged === ch ? role : 'junction')\n return\n }\n setC(x, y, ch, role)\n }\n\n /**\n * Draw the short horizontal jog that joins a relationship's anchor on a\n * box border to the lane its line runs along, on the row directly\n * adjacent to that border. `boxIsAbove` says which side of the row the\n * box sits on: the anchor's corner opens toward the box, the lane's\n * corner opens the other way, toward the vertical run. A relationship\n * whose lane already sits inside its box needs no jog at all, which keeps\n * every pre-existing (unfanned) layout byte-identical.\n */\n function drawJog(\n row: number,\n anchorX: number,\n laneX: number,\n lineH: string,\n boxIsAbove: boolean,\n ): void {\n if (anchorX === laneX) return\n const lx = Math.min(anchorX, laneX)\n const rx = Math.max(anchorX, laneX)\n // Interior cells only — the two ends are corners (or, in plain-ASCII\n // mode, the same line glyph), written separately so a second jog\n // sharing this anchor merges with the corner instead of flattening it.\n for (let x = lx + 1; x < rx; x++) {\n setJogCell(x, row, lineH, 'line')\n }\n if (useAscii) {\n setJogCell(anchorX, row, lineH, 'line')\n setJogCell(laneX, row, lineH, 'line')\n return\n }\n const laneIsRight = laneX > anchorX\n const anchorCorner = boxIsAbove\n ? laneIsRight\n ? '└'\n : '┘'\n : laneIsRight\n ? '┌'\n : '┐'\n const laneCorner = boxIsAbove\n ? laneIsRight\n ? '┐'\n : '┌'\n : laneIsRight\n ? '┘'\n : '└'\n setJogCell(anchorX, row, anchorCorner, 'corner')\n setJogCell(laneX, row, laneCorner, 'corner')\n }\n\n // A detoured relationship's actual routed path — the far column its\n // vertical trunk runs along, and the rows its horizontal jogs sit on —\n // keyed by relationship index. The line-drawing pass below is the only\n // place that decides whether (and where) a relationship detours, but the\n // label-territory precompute and label-drawing passes further down need\n // that same decision too, so they can anchor a detoured relationship's\n // label on its real path instead of the straight-line source/target\n // midpoint (issue #487). Populated only for the \"target below source,\n // needs a detour around an intermediate box\" case — the only routing\n // branch that currently detours at all.\n const detourRoutes = new Map<\n number,\n {\n routeX: number\n exitY: number\n entryY: number\n fromAnchorX: number\n toAnchorX: number\n clearSide: 'left' | 'right'\n }\n >()\n\n // Same-level relationships' obstruction-aware detour row (issue #953),\n // keyed by relationship index — `computeLabelAnchor`'s own same-level\n // branch reads this so a labeled same-level relationship's label anchors\n // on the same corrected row the connector itself routes through, instead\n // of recomputing the naive (obstruction-unaware) `Math.max(fromBY, toBY)\n // + 2` and landing back inside whatever box the connector fix was\n // routing around.\n const sameLevelDetourY = new Map<number, number>()\n\n diagram.relationships.forEach((rel, relIndex) => {\n const fromP = placed.get(rel.from)\n const toP = placed.get(rel.to)\n if (!fromP || !toP) return\n\n const marker = getRelMarker(rel.type, rel.markerAt)\n const lineH = marker.dashed ? dashH : H\n const lineV = marker.dashed ? dashV : V\n\n // Exclude source and target boxes from collision detection\n const excludeIds = new Set([rel.from, rel.to])\n\n // Connection points: the lanes the line runs along (`fromCX`/`toCX`)\n // and the anchors where it touches each box border — see\n // `connectionColumns` for why those can differ.\n const { fromCX, toCX, fromAnchorX, toAnchorX } = connectionColumns(\n relIndex,\n fromP,\n toP,\n )\n const fromBY = fromP.y + fromP.height - 1\n const toTY = toP.y\n\n // Route: Manhattan routing with collision avoidance\n // If target is below source: vertical down from source, horizontal if needed, vertical down to target\n // If same row: horizontal line with a small vertical detour above or below\n if (fromBY < toTY) {\n // Target is below source — routing with collision avoidance\n // Find a clear vertical column for the ENTIRE path from source to target\n const routeX = findClearColumn(fromCX, fromBY + 1, toTY - 1, excludeIds)\n const needsDetour = routeX !== fromCX\n\n // Expand canvas if needed to accommodate routing column\n if (routeX >= totalW) {\n increaseSize(canvas, routeX + 2, totalH)\n }\n\n if (needsDetour) {\n // COLLISION CASE: Route around intermediate boxes\n // Path: source anchor → horizontal to routeX → vertical to entry → horizontal to target anchor\n // The horizontals start/end at the *anchors* (on the box borders),\n // so a fanned-out lane needs no separate jog here — the detour's\n // own horizontal already runs along the jog row.\n\n const exitY = fromBY + 1\n const entryY = toTY - 1\n detourRoutes.set(relIndex, {\n routeX,\n exitY,\n entryY,\n fromAnchorX,\n toAnchorX,\n // `findClearColumn` starts its search at `fromCX` and only\n // returns a different column when that one collided with a box\n // over the route's full y-range — so whichever side it moved\n // toward is the side with clearance, and the box it moved away\n // from sits on the other side of `routeX`. The label-anchor pass\n // uses this to keep a detour label's whole width clear of that\n // box, not just the single column `routeX` itself.\n clearSide: routeX > fromCX ? 'right' : 'left',\n })\n\n // 1. Horizontal from source anchor to route column\n const lx1 = Math.min(fromAnchorX, routeX)\n const rx1 = Math.max(fromAnchorX, routeX)\n for (let x = lx1; x <= rx1; x++) {\n setCGuarded(x, exitY, lineH, 'line')\n }\n if (!useAscii && exitY < (canvas[0]?.length ?? 0)) {\n if (fromAnchorX < routeX) {\n setCGuarded(fromAnchorX, exitY, '└', 'corner')\n setCGuarded(routeX, exitY, '┐', 'corner')\n } else {\n setCGuarded(fromAnchorX, exitY, '┘', 'corner')\n setCGuarded(routeX, exitY, '┌', 'corner')\n }\n }\n\n // 2. Vertical at routeX from exit to entry\n for (let y = exitY + 1; y <= entryY; y++) {\n setCGuarded(routeX, y, lineV, 'line')\n }\n\n // 3. Horizontal from routeX to target anchor at entry\n if (routeX !== toAnchorX) {\n const lx2 = Math.min(routeX, toAnchorX)\n const rx2 = Math.max(routeX, toAnchorX)\n for (let x = lx2; x <= rx2; x++) {\n setCGuarded(x, entryY, lineH, 'line')\n }\n if (!useAscii && entryY < (canvas[0]?.length ?? 0)) {\n if (routeX < toAnchorX) {\n setCGuarded(routeX, entryY, '└', 'corner')\n setCGuarded(toAnchorX, entryY, '┐', 'corner')\n } else {\n setCGuarded(routeX, entryY, '┘', 'corner')\n setCGuarded(toAnchorX, entryY, '┌', 'corner')\n }\n }\n }\n\n // Markers for detour case\n if (marker.markerAt === 'to') {\n // Target sits below this point — the arrowhead must point down\n // into it. Hierarchical markers (inheritance/realization) rotate\n // opposite to directional ones (association/dependency): passing\n // 'up' yields a hierarchical marker's down-pointing glyph, while\n // 'down' yields a directional marker's down-pointing glyph. See\n // the matching compensation in the \"target is above source\"\n // branch below, which handles the mirrored case.\n const isHierarchical =\n marker.type === 'inheritance' || marker.type === 'realization'\n const markerChar = getMarkerShape(\n marker.type,\n useAscii,\n isHierarchical ? 'up' : 'down',\n )\n setCGuarded(toAnchorX, entryY, markerChar, 'arrow')\n }\n if (marker.markerAt === 'from') {\n const markerChar = getMarkerShape(marker.type, useAscii, 'down')\n setCGuarded(fromAnchorX, fromBY + 1, markerChar, 'arrow')\n }\n } else {\n // NO COLLISION CASE: Use original midpoint-based routing\n // Path: source anchor → (jog to lane) → vertical to midY → horizontal at midY → vertical → (jog to anchor) → target\n //\n // The jogs only exist when a lane sits outside its box; otherwise\n // anchor and lane coincide and the path is the plain three-segment\n // route. With the default vertical gap the from-jog row, midY, and\n // the to-jog row are the three rows between the boxes, so a fanned\n // group reads as a trunk under the source, labels on the lanes,\n // and a trunk gathering back into the target.\n\n const midY = fromBY + Math.floor((toTY - fromBY) / 2)\n const fromJogs = fromCX !== fromAnchorX\n const toJogs = toCX !== toAnchorX\n\n // 1. Jog from the source anchor out to the lane, then vertical from\n // the source bottom (or from below the jog) to midY\n drawJog(fromBY + 1, fromAnchorX, fromCX, lineH, true)\n for (let y = fromBY + (fromJogs ? 2 : 1); y <= midY; y++) {\n setCGuarded(fromCX, y, lineV, 'line')\n }\n\n // 2. Horizontal from fromCX to toCX at midY (if needed)\n //\n // This segment matters most for the boxCells guard: it can travel\n // a long way horizontally when the target's column sits far from\n // the source's (e.g. a level-1 child positioned under a completely\n // different level-0 sibling than its own parent). Nothing about\n // `findClearColumn` finding `fromCX` itself clear (the only check\n // that decided this is the no-collision branch) says the *row*\n // this horizontal run lands on is clear all the way over to\n // `toCX` too — a taller same-level sibling sitting between them,\n // extending below its own row's shortest box, is exactly the case\n // that check misses.\n if (fromCX !== toCX && midY < (canvas[0]?.length ?? 0)) {\n const lx = Math.min(fromCX, toCX)\n const rx = Math.max(fromCX, toCX)\n for (let x = lx; x <= rx; x++) {\n setCGuarded(x, midY, lineH, 'line')\n }\n if (!useAscii) {\n setCGuarded(fromCX, midY, fromCX < toCX ? '└' : '┘', 'corner')\n setCGuarded(toCX, midY, fromCX < toCX ? '┐' : '┌', 'corner')\n }\n }\n\n // 3. Vertical from midY to the target top (or to above the jog),\n // then jog from the lane back in to the target anchor\n for (let y = midY + 1; y < toTY - (toJogs ? 1 : 0); y++) {\n setCGuarded(toCX, y, lineV, 'line')\n }\n drawJog(toTY - 1, toAnchorX, toCX, lineH, false)\n\n // Markers for no-collision case\n if (marker.markerAt === 'to') {\n // Same rotation compensation as the detour case above — target is\n // below this point, so hierarchical markers need 'up' to point\n // down into it.\n const isHierarchical =\n marker.type === 'inheritance' || marker.type === 'realization'\n setCGuarded(\n toAnchorX,\n toTY - 1,\n getMarkerShape(\n marker.type,\n useAscii,\n isHierarchical ? 'up' : 'down',\n ),\n 'arrow',\n )\n }\n if (marker.markerAt === 'from') {\n setCGuarded(\n fromAnchorX,\n fromBY + 1,\n getMarkerShape(marker.type, useAscii, 'down'),\n 'arrow',\n )\n }\n }\n } else if (toP.y + toP.height - 1 < fromP.y) {\n // Target is ABOVE source — draw upward from source top to target bottom\n const fromTY = fromP.y\n const toBY = toP.y + toP.height - 1\n const midY = toBY + Math.floor((fromTY - toBY) / 2)\n const fromJogs = fromCX !== fromAnchorX\n const toJogs = toCX !== toAnchorX\n\n // Jog along the row above the source from its anchor out to the lane\n // (mirror of the downward case), then vertical up to midY\n drawJog(fromTY - 1, fromAnchorX, fromCX, lineH, false)\n for (let y = fromTY - (fromJogs ? 2 : 1); y >= midY; y--) {\n setCGuarded(fromCX, y, lineV, 'line')\n }\n\n // This branch has no collision-avoidance routing at all (unlike the\n // \"target below source\" branch above, which at least tries\n // `findClearColumn` first) — it always draws the straight\n // jog/vertical/horizontal/vertical/jog path. The boxCells guard is\n // this path's only protection against a horizontal run landing on\n // an unrelated box; see the equivalent midY comment in the \"target\n // below source\" branch above for why that's a real, not merely\n // theoretical, case.\n if (fromCX !== toCX) {\n const lx = Math.min(fromCX, toCX)\n const rx = Math.max(fromCX, toCX)\n for (let x = lx; x <= rx; x++) {\n setCGuarded(x, midY, lineH, 'line')\n }\n if (!useAscii && midY >= 0 && midY < totalH) {\n setCGuarded(fromCX, midY, fromCX < toCX ? '┌' : '┐', 'corner')\n setCGuarded(toCX, midY, fromCX < toCX ? '┘' : '└', 'corner')\n }\n }\n\n for (let y = midY - 1; y > toBY + (toJogs ? 1 : 0); y--) {\n setCGuarded(toCX, y, lineV, 'line')\n }\n drawJog(toBY + 1, toAnchorX, toCX, lineH, true)\n\n // Draw markers - arrows point in the direction of the vertical segment (upward)\n if (marker.markerAt === 'from') {\n const markerChar = getMarkerShape(marker.type, useAscii, 'up')\n const my = fromTY - 1\n for (let i = 0; i < markerChar.length; i++) {\n setCGuarded(\n fromAnchorX - Math.floor(markerChar.length / 2) + i,\n my,\n markerChar[i]!,\n 'arrow',\n )\n }\n }\n if (marker.markerAt === 'to') {\n const isHierarchical =\n marker.type === 'inheritance' || marker.type === 'realization'\n const markerDir = isHierarchical ? 'down' : 'up'\n const markerChar = getMarkerShape(marker.type, useAscii, markerDir)\n const my = toBY + 1\n for (let i = 0; i < markerChar.length; i++) {\n setCGuarded(\n toAnchorX - Math.floor(markerChar.length / 2) + i,\n my,\n markerChar[i]!,\n 'arrow',\n )\n }\n }\n } else {\n // Same level — draw horizontal line with a detour below both boxes.\n const toBY = toP.y + toP.height - 1\n const lx = Math.min(fromCX, toCX)\n const rx = Math.max(fromCX, toCX)\n\n // The naive detour row — max(fromBY, toBY) + 2 — is derived only from\n // this relationship's own two endpoints. A same-level layout puts\n // every class in the level on one shared row (see the placement loop\n // above), so a relationship cycle that can't be linearly leveled\n // (e.g. A->B->C->A) lands a third, unrelated class between `fromP`\n // and `toP` in that same row. When that in-between class is taller\n // than both endpoints (more attributes/methods), the naive detour row\n // sits *above* its bottom edge, and the horizontal segment below\n // (spanning [lx, rx], which crosses straight through that class's\n // x-range) cuts through its box instead of running beneath it.\n // Search for any such same-row obstruction and route the detour below\n // its bottom edge too — generalizing er-diagram.ts's obstructionBottom\n // search (issue #350) to class diagrams' same-level detour.\n let obstructionBottom = Math.max(fromBY, toBY)\n for (const [id, other] of placed.entries()) {\n if (id === rel.from || id === rel.to) continue\n if (other.y !== fromP.y) continue\n const overlapsGap = other.x < rx + 1 && other.x + other.width > lx\n if (overlapsGap) {\n obstructionBottom = Math.max(\n obstructionBottom,\n other.y + other.height - 1,\n )\n }\n }\n const detourY = obstructionBottom + 2\n sameLevelDetourY.set(relIndex, detourY)\n increaseSize(canvas, totalW, detourY + 1)\n increaseRoleCanvasSize(rc, totalW, detourY + 1)\n const fromJogs = fromCX !== fromAnchorX\n const toJogs = toCX !== toAnchorX\n\n // Jog out to the lane under the source, then vertical down from it\n drawJog(fromBY + 1, fromAnchorX, fromCX, lineH, true)\n for (let y = fromBY + (fromJogs ? 2 : 1); y <= detourY; y++) {\n setCGuarded(fromCX, y, lineV, 'line')\n }\n // Horizontal\n for (let x = lx; x <= rx; x++) {\n setCGuarded(x, detourY, lineH, 'line')\n }\n // Vertical up to target, then jog back in to its anchor\n for (let y = detourY - 1; y >= toBY + (toJogs ? 2 : 1); y--) {\n setCGuarded(toCX, y, lineV, 'line')\n }\n drawJog(toBY + 1, toAnchorX, toCX, lineH, true)\n\n // Draw markers - same-level routing uses vertical segments at both ends\n if (marker.markerAt === 'from') {\n const markerChar = getMarkerShape(marker.type, useAscii, 'down')\n const my = fromBY + 1\n for (let i = 0; i < markerChar.length; i++) {\n setCGuarded(\n fromAnchorX - Math.floor(markerChar.length / 2) + i,\n my,\n markerChar[i]!,\n 'arrow',\n )\n }\n }\n if (marker.markerAt === 'to') {\n // Target sits above this point (line detours below both boxes then\n // comes back up) — mirrors the \"target is above source\" branch's\n // compensation above.\n const isHierarchical =\n marker.type === 'inheritance' || marker.type === 'realization'\n const markerChar = getMarkerShape(\n marker.type,\n useAscii,\n isHierarchical ? 'down' : 'up',\n )\n const my = toP.y + toP.height\n for (let i = 0; i < markerChar.length; i++) {\n setCGuarded(\n toAnchorX - Math.floor(markerChar.length / 2) + i,\n my,\n markerChar[i]!,\n 'arrow',\n )\n }\n }\n }\n })\n\n /**\n * Where a relationship's label should ideally center — the column and row\n * the territory precompute and label-drawing passes below both anchor on.\n *\n * For the common case (no detour), this is the straight-line midpoint\n * between the source and target connection columns/rows, same as before.\n *\n * For a relationship whose line detours around an intermediate box (see\n * `detourRoutes` above), the label instead anchors on the *actual routed\n * path*: beside the midpoint of the detour's vertical trunk (the longest\n * segment in the common case), or — when the boxes are close enough\n * together that the trunk has no vertical room at all — the midpoint of\n * whichever horizontal jog is longer. Anchoring at the straight-line\n * midpoint ignored the detour entirely, so a detoured relationship's\n * label could land in the same column (and even the same row) as an\n * unrelated relationship's straight-line label, reading as though both\n * terminated at the same box (issue #487).\n *\n * `labelWidth` (the padded display width the caller is about to draw)\n * matters for the vertical-trunk case specifically: `findClearColumn`\n * only guarantees the single column `routeX` is clear of boxes over the\n * trunk's row range, not a whole label-width window centered on it. A\n * label centered directly on `routeX` routinely re-overlapped the very\n * box the trunk was routed around to avoid — visible as the label\n * appearing to collide with (or get shoved off) the box the trunk hugs.\n * `detour.clearSide` says which side of `routeX` is the side\n * `findClearColumn` actually found clear (see its call site), so the\n * label is anchored flush against the trunk on that side instead of\n * straddling it.\n */\n function computeLabelAnchor(\n relIndex: number,\n fromP: PlacedClass,\n toP: PlacedClass,\n labelWidth: number,\n ): { idealMidX: number; baseMidY: number } {\n const { fromCX, toCX } = connectionColumns(relIndex, fromP, toP)\n const fromBY = fromP.y + fromP.height - 1\n const toTY = toP.y\n\n if (fromBY < toTY) {\n const detour = detourRoutes.get(relIndex)\n if (detour) {\n const trunkTop = detour.exitY + 1\n const trunkBottom = detour.entryY\n if (trunkBottom >= trunkTop) {\n // Vertical trunk has room — it's the longest segment of the\n // route in the common case, so anchor there. The label sits\n // flush against the trunk on its clear side (see doc comment)\n // rather than centered on it, with a 1-column gap for legibility.\n const gap = 1\n const idealMidX =\n detour.clearSide === 'right'\n ? detour.routeX + gap + Math.floor(labelWidth / 2)\n : detour.routeX - gap - Math.ceil(labelWidth / 2)\n return {\n idealMidX,\n baseMidY: Math.floor((trunkTop + trunkBottom) / 2),\n }\n }\n // Boxes are close enough together that the trunk has no vertical\n // room (exit and entry jogs sit on the same or adjacent rows) —\n // anchor on whichever horizontal jog is longer instead.\n const exitWidth = Math.abs(detour.routeX - detour.fromAnchorX)\n const entryWidth = Math.abs(detour.toAnchorX - detour.routeX)\n return entryWidth >= exitWidth\n ? {\n idealMidX: Math.floor((detour.routeX + detour.toAnchorX) / 2),\n baseMidY: detour.entryY,\n }\n : {\n idealMidX: Math.floor((detour.fromAnchorX + detour.routeX) / 2),\n baseMidY: detour.exitY,\n }\n }\n return {\n idealMidX: Math.floor((fromCX + toCX) / 2),\n baseMidY: Math.floor((fromBY + 1 + toTY - 1) / 2),\n }\n }\n if (toP.y + toP.height - 1 < fromP.y) {\n const toBY = toP.y + toP.height - 1\n return {\n idealMidX: Math.floor((fromCX + toCX) / 2),\n baseMidY: Math.floor((toBY + 1 + fromP.y - 1) / 2),\n }\n }\n // Same level — anchor on the obstruction-aware detour row the connector\n // itself was routed through (see `sameLevelDetourY` above), not the\n // naive `Math.max(fromBY, toBY) + 2` recomputation, which ignores any\n // taller same-row box between `fromP`/`toP` and would place the label\n // right back inside it (issue #953).\n return {\n idealMidX: Math.floor((fromCX + toCX) / 2),\n // The `??` fallback is unreachable in practice: the main\n // relationship-drawing pass above runs for every relationship before\n // this label pass does, and it sets `sameLevelDetourY` for every\n // relIndex that takes this same \"same level\" branch — the exact\n // condition this function just evaluated to reach here. Kept only\n // because `Map.get` is typed `T | undefined`, not because a real\n // diagram can actually hit it.\n /* v8 ignore next */\n baseMidY:\n sameLevelDetourY.get(relIndex) ??\n Math.max(fromBY, toP.y + toP.height - 1) + 2,\n }\n }\n\n /**\n * Resolve a label's actual drawn row, starting from `computeLabelAnchor`'s\n * `baseMidY` and nudging it to the nearest box-free row in the from/to gap\n * when the ideal row would land inside an intervening box. Shared by the\n * territory precompute below and the draw pass further down so both agree\n * on the *same* final row for a given relationship — see the doc comment\n * on the territory precompute for why that agreement matters (issue #531).\n */\n function resolveLabelFinalY(\n fromP: PlacedClass,\n toP: PlacedClass,\n idealMidX: number,\n baseMidY: number,\n maxLabelWidth: number,\n lineCount: number,\n excludeIds: Set<string>,\n ): number {\n let labelY = baseMidY\n const halfHeight = Math.floor(lineCount / 2)\n const fromBY = fromP.y + fromP.height - 1\n const toTY = toP.y\n\n // Check if any label line would be inside a box\n let labelInBox = false\n for (let i = 0; i < lineCount; i++) {\n const y = labelY - halfHeight + i\n const idealLabelStart = idealMidX - Math.floor(maxLabelWidth / 2)\n const labelStart = Math.max(0, idealLabelStart)\n for (let x = labelStart; x < labelStart + maxLabelWidth; x++) {\n if (isInsideBox(x, y, excludeIds)) {\n labelInBox = true\n break\n }\n }\n if (labelInBox) break\n }\n\n // If label is inside a box, find the gap between boxes\n if (labelInBox) {\n const gapTop = fromBY + 1\n const gapBottom = toTY - 1\n\n // Place label in the middle of the gap, outside any intermediate box\n for (let y = gapTop; y <= gapBottom; y++) {\n let clearRow = true\n const idealLabelStart = idealMidX - Math.floor(maxLabelWidth / 2)\n const labelStart = Math.max(0, idealLabelStart)\n for (let x = labelStart; x < labelStart + maxLabelWidth; x++) {\n if (isInsideBox(x, y, excludeIds)) {\n clearRow = false\n break\n }\n }\n if (clearRow) {\n labelY = y\n break\n }\n }\n }\n\n return labelY\n }\n\n // --- Precompute each label's horizontal territory ---\n // A label's left/right bound is derived purely from connection-point\n // geometry — never from draw order or from what another relationship's\n // label happened to render as. Two labels whose natural (fully centered,\n // unclamped) spans would actually overlap split the contested space\n // evenly at the midpoint between their connection columns; labels that\n // don't naturally overlap are left unconstrained by each other.\n //\n // This matters because a *reactive* approach — only checking cells an\n // earlier iteration already drew text into — cascades: a label pinned\n // near the canvas's left edge (idealLabelStart < 0) used to be *shifted*\n // fully into view via `Math.max(0, idealLabelStart)` rather than\n // *clipped*, which silently ate into its neighbor's rightful column; that\n // neighbor then had nothing left to reactively claim and vanished\n // entirely, while labels further along drifted based on whatever was\n // left over. Precomputing fixed, mutually exclusive territories up front\n // — before any label is drawn — means every label's placement is\n // consistent regardless of relationship order, and a genuinely\n // insufficient column width (e.g. six same-row relationships spaced\n // closer together than their labels are wide) truncates *all* of them\n // consistently instead of destroying an arbitrary subset. See issue #447.\n //\n // rowStart/rowEnd are derived from `resolveLabelFinalY`'s result, *not*\n // `computeLabelAnchor`'s raw `baseMidY` — the same \"clear box gap\" runtime\n // fallback the draw pass applies can move a label to a row far from its\n // ideal one (e.g. a detoured relationship whose ideal row lands inside the\n // box it detoured around). Territories computed from the pre-fallback row\n // used to miss exactly the overlaps that fallback creates: two\n // relationships whose *ideal* rows didn't overlap could still both\n // resolve to the *same actual* row, and with no territory split between\n // them the later one's unconditional draw silently overwrote the earlier\n // one's label (issue #531). Resolving here, once, up front — and reusing\n // the result in the draw pass below via `finalLabelYByRel` — guarantees\n // territory and drawing always agree on the same row per relationship.\n //\n // The allocation itself — splitting contested columns at the midpoint\n // between two competing labels' idealMidX values — lives in\n // `allocateTerritory` (`territory.ts`), which is storage-agnostic and\n // knows nothing about relationships; this pass only supplies the geometry\n // (issue #618). Its doc comments carry the full rationale summarised\n // above.\n interface LabelGeometry {\n rel: (typeof diagram.relationships)[number]\n idealMidX: number\n naturalStart: number\n naturalEnd: number\n rowStart: number\n rowEnd: number\n }\n const finalLabelYByRel = new Map<\n (typeof diagram.relationships)[number],\n number\n >()\n const labelGeometry: LabelGeometry[] = []\n for (const [relIndex, rel] of diagram.relationships.entries()) {\n if (!rel.label) continue\n const fromP = placed.get(rel.from)\n const toP = placed.get(rel.to)\n if (!fromP || !toP) continue\n\n // Same anchor the draw pass below uses — needed so territory splitting\n // only ever kicks in between labels that could actually land on\n // overlapping rows. Two relationships can share a similar idealMidX\n // while being drawn many rows apart (e.g. one class's two separate\n // outgoing edges to two different targets at different heights) —\n // splitting their X territory in that case truncates both for no\n // reason, since they never actually collide.\n const lines = splitLines(rel.label)\n const halfHeight = Math.floor(lines.length / 2)\n\n const width = Math.max(...lines.map(displayWidth)) + 2 // +2 for padding\n const { idealMidX, baseMidY } = computeLabelAnchor(\n relIndex,\n fromP,\n toP,\n width,\n )\n const excludeIds = new Set([rel.from, rel.to])\n const labelY = resolveLabelFinalY(\n fromP,\n toP,\n idealMidX,\n baseMidY,\n width,\n lines.length,\n excludeIds,\n )\n finalLabelYByRel.set(rel, labelY)\n // Clamped the same way the draw loop below clamps it (never negative —\n // a label can't render left of the canvas edge) so this overlap check\n // reflects what will actually be drawn. Using the *unclamped* value\n // here would miss overlaps a left-edge label creates once it's shifted\n // into view: it would compute this label's territory as if it stayed\n // off-canvas at its raw, negative position instead of at column 0,\n // under-detecting a real collision with its neighbor (issue #447).\n const naturalStart = Math.max(0, idealMidX - Math.floor(width / 2))\n labelGeometry.push({\n rel,\n idealMidX,\n naturalStart,\n naturalEnd: naturalStart + width - 1,\n rowStart: labelY - halfHeight,\n rowEnd: labelY + halfHeight,\n })\n }\n const territoryByGeometry = allocateTerritory(labelGeometry, (g) => ({\n idealMid: g.idealMidX,\n start: g.naturalStart,\n end: g.naturalEnd,\n rowStart: g.rowStart,\n rowEnd: g.rowEnd,\n }))\n const territoryByRel = new Map<\n (typeof diagram.relationships)[number],\n Territory\n >()\n for (const g of labelGeometry) {\n const territory = territoryByGeometry.get(g)\n if (territory) territoryByRel.set(g.rel, territory)\n }\n\n // --- Draw relationship labels ---\n // Deliberately a separate pass over *all* relationships, run only after\n // every relationship's lines are drawn above. Labels used to be drawn\n // inline with each relationship's own lines, which let a *later*\n // relationship's connector line silently overwrite an *earlier*\n // relationship's already-drawn label whenever both routes crossed the\n // same row — e.g. two edges converging on one target box both jog through\n // the same midpoint row (issue #447, \"teaches\" truncated to \"tea\" and\n // merged into a box-drawing corner). Running all lines first means no\n // line write can ever land on top of label text again.\n for (const [relIndex, rel] of diagram.relationships.entries()) {\n const fromP = placed.get(rel.from)\n const toP = placed.get(rel.to)\n if (!fromP || !toP) continue\n if (!rel.label) continue\n\n // Draw relationship label at midpoint (supports multi-line)\n // Add padding around the label for readability\n {\n const lines = splitLines(rel.label)\n const maxLabelWidth = Math.max(...lines.map((l) => displayWidth(l))) + 2 // +2 for padding\n\n // Calculate ideal label position based on routing direction (and, for\n // a detoured relationship, its actual routed path — see\n // `computeLabelAnchor`).\n const { idealMidX } = computeLabelAnchor(\n relIndex,\n fromP,\n toP,\n maxLabelWidth,\n )\n\n // The row to draw on: resolved once, up front, by the territory\n // precompute above (via `resolveLabelFinalY`) — reused here rather\n // than re-resolved so this pass and the territory it reads from\n // (`territoryByRel`, just below) always agree on the same row for\n // this relationship. See the precompute's doc comment (issue #531).\n const labelY = finalLabelYByRel.get(rel)!\n const halfHeight = Math.floor(lines.length / 2)\n\n // Center lines vertically around labelY\n const startY = labelY - halfHeight\n\n const territory = territoryByRel.get(rel)!\n\n for (let lineIdx = 0; lineIdx < lines.length; lineIdx++) {\n const y = startY + lineIdx\n const text = lines[lineIdx]!\n\n // Grid cells, not code units: a wide glyph occupies two columns, so\n // measuring/writing by code unit would centre the label wrong and\n // overrun the space cleared for it.\n const naturalCells = toDisplayCells(` ${text} `) // space padding on both sides\n // Clamped to never go negative — matches the clamp the territory\n // precomputation above already applied when deciding whether this\n // label and its neighbors' natural spans overlap, so the two stay\n // consistent. `fitLabelToAvailableWidth` still clips in place\n // rather than shifting for whatever collisions the territory\n // bounds encode, including one this very clamp creates against a\n // left neighbor (see the territory precomputation's comment).\n const naturalStart = Math.max(\n 0,\n idealMidX - Math.floor(naturalCells.length / 2),\n )\n\n const { start: labelStart, cells } = fitLabelToAvailableWidth(\n naturalStart,\n naturalCells,\n text,\n Math.max(0, territory.left),\n territory.right,\n )\n\n // Ensure canvas is wide enough for the label\n const labelEnd = labelStart + cells.length\n if (labelEnd > 0 && y >= 0) {\n increaseSize(canvas, Math.max(labelEnd, 1), Math.max(y + 1, 1))\n increaseRoleCanvasSize(rc, Math.max(labelEnd, 1), Math.max(y + 1, 1))\n }\n // Clear the area first (overwrite line characters) then draw the padded label\n for (let i = 0; i < cells.length; i++) {\n const lx = labelStart + i\n if (lx >= 0 && y >= 0) {\n setC(lx, y, cells[i]!, 'text')\n }\n }\n }\n }\n }\n\n // Notes were drawn as plain boxes above; round their corners and join\n // each attached note to its class. Last, so a connector never lands on\n // top of a relationship line or label.\n decorateAsciiNotes(notesById, placed, canvas, useAscii, setC)\n\n return canvasToString(canvas, {\n roleCanvas: rc,\n colorMode,\n theme,\n linkCanvas,\n })\n}\n\n// ============================================================================\n// Notes — `note \"text\"` / `note for X \"text\"` (issue #420)\n//\n// A note is laid out as a pseudo-class: it gets a box like any class (a\n// single header-only section holding the note text, via the same\n// buildClassSections/drawMultiBox path), and is spliced into\n// `diagram.classes` immediately after the class it annotates so the row\n// placement puts it directly to that class's right. A free note, or one\n// whose class doesn't exist, is appended to the end and sits on the top row.\n// After all boxes and relationships are drawn, decorateAsciiNotes() swaps\n// the note's corners for rounded ones — the same rectangle-vs-rounded\n// distinction the flowchart ASCII renderer draws — and joins an attached\n// note to its class with a short dashed connector, the ASCII counterpart of\n// the dotted note→class link Mermaid draws. Class ids can't contain\n// whitespace, so a pseudo id with a space in it can never collide with one.\n// ============================================================================\n\n/**\n * Return a copy of `parsed` whose `classes` list also contains one\n * pseudo-class per note, positioned as described above, plus a map from each\n * pseudo id to its note.\n */\nfunction withNotePseudoClasses(parsed: ClassDiagram): {\n diagram: ClassDiagram\n notesById: Map<string, ClassNote>\n} {\n const notesById = new Map<string, ClassNote>()\n if (parsed.notes.length === 0) return { diagram: parsed, notesById }\n\n const pseudo = (index: number, note: ClassNote): ClassNode => {\n const id = `note ${index}`\n notesById.set(id, note)\n return { id, label: note.text, attributes: [], methods: [] }\n }\n\n const classIds = new Set(parsed.classes.map((c) => c.id))\n const classes: ClassNode[] = []\n for (const cls of parsed.classes) {\n classes.push(cls)\n for (const [i, note] of parsed.notes.entries()) {\n if (note.forClass === cls.id) classes.push(pseudo(i, note))\n }\n }\n for (const [i, note] of parsed.notes.entries()) {\n if (note.forClass === undefined || !classIds.has(note.forClass)) {\n classes.push(pseudo(i, note))\n }\n }\n\n return { diagram: { ...parsed, classes }, notesById }\n}\n\n/** Put each attached note on the same level as its class. */\nfunction pinNoteLevels(\n notesById: Map<string, ClassNote>,\n level: Map<string, number>,\n): void {\n for (const [id, note] of notesById) {\n if (note.forClass === undefined) continue\n const classLevel = level.get(note.forClass)\n if (classLevel !== undefined) level.set(id, classLevel)\n }\n}\n\n/**\n * Round each note box's corners and draw the dashed connector from an\n * attached note's class to the note. The connector only fills cells that\n * are still blank, so it can never break a relationship line that happens\n * to route through the gap.\n */\nfunction decorateAsciiNotes(\n notesById: Map<string, ClassNote>,\n placed: Map<string, PlacedClass>,\n canvas: Canvas,\n useAscii: boolean,\n setC: (x: number, y: number, ch: string, role: CharRole) => void,\n): void {\n if (notesById.size === 0) return\n const corners = getCorners('rounded', useAscii)\n const dash = useAscii ? '.' : '╌'\n\n for (const [id, note] of notesById) {\n const p = placed.get(id)\n if (!p) continue\n const right = p.x + p.width - 1\n const bottom = p.y + p.height - 1\n setC(p.x, p.y, corners.tl, 'border')\n setC(right, p.y, corners.tr, 'border')\n setC(p.x, bottom, corners.bl, 'border')\n setC(right, bottom, corners.br, 'border')\n\n if (note.forClass === undefined) continue\n const cls = placed.get(note.forClass)\n // By construction the note sits directly right of its class on the same\n // row; anything else means the layout changed under us — skip the line\n // rather than draw one through the middle of the diagram.\n if (!cls || cls.x + cls.width > p.x || cls.y !== p.y) continue\n const y = cls.y + 1\n for (let x = cls.x + cls.width; x < p.x; x++) {\n if (canvas[x]?.[y] === ' ') setC(x, y, dash, 'line')\n }\n }\n}\n","import type {\n MermaidGraph,\n MermaidNode,\n MermaidSubgraph,\n NodeShape,\n EdgeStyle,\n Statement,\n} from '@zombie-mermaid/core'\nimport {\n toDirection,\n normalizeBrTags,\n splitStatementsByLine,\n extractInitConfig,\n applyClickStatement as applyClickStatementShared,\n parseStyleProps,\n tryApplyClassDef,\n tryApplyClassAssignment,\n tryApplyStyleStatement,\n} from '@zombie-mermaid/core'\n\nimport {\n matchExpandedBlock,\n parseExpandedMeta,\n resolveShapeName,\n} from '@zombie-mermaid/mermaid-parser'\nimport type { ExpandedNodeMeta } from '@zombie-mermaid/mermaid-parser'\n/** Remove a single layer of matching wrapping quotes (`\"…\"` or `'…'`). */\nfunction stripWrappingQuotes(s: string): string {\n const t = s.trim()\n if (\n t.length >= 2 &&\n ((t[0] === '\"' && t[t.length - 1] === '\"') ||\n (t[0] === \"'\" && t[t.length - 1] === \"'\"))\n ) {\n return t.slice(1, -1)\n }\n return t\n}\n\n// ============================================================================\n// Mermaid parser — flowcharts and state diagrams\n//\n// Supports:\n// Flowcharts: graph TD / flowchart LR\n// State diagrams: stateDiagram-v2\n//\n// Line-by-line regex approach — the grammar is regular enough\n// that we don't need a grammar generator or full parser combinator.\n// ============================================================================\n\n// `toDirection` used to be defined here, with this file as its only caller.\n// #624 gave it a second caller — `packages/mermaid-parser/src/er/parser.ts`,\n// which cannot import this umbrella file without creating a cycle\n// (mermaid-parser -> umbrella -> mermaid-parser) — so it moved to\n// `packages/core/src/direction.ts` alongside `isDirection`, for the same\n// reason that one moved under #625. Re-exported here so existing importers\n// of `toDirection` from `./parser.ts` (this file's own tests included) keep\n// working unchanged.\nexport { toDirection } from '@zombie-mermaid/core'\n\n/**\n * All diagram-type headers this library recognizes, for the \"supported\n * headers\" list in the invalid-header error below. Kept in one place so a\n * newly-added diagram type doesn't get forgotten in the error message the\n * way sequence/class/ER/xychart were before issue #541.\n */\nconst SUPPORTED_HEADERS =\n '\"graph <dir>\"/\"flowchart <dir>\" (dir: TD, TB, LR, BT, RL), \"stateDiagram-v2\", \"sequenceDiagram\", \"classDiagram\", \"erDiagram\", \"xychart-beta\"'\n\n/**\n * Best-guess canonical header for a header line that looks like an attempt\n * at one of the *other* diagram types this library supports, but didn't\n * match `detectDiagramType`'s exact-match regex (a typo, extra trailing\n * text, wrong case pattern, etc.) and so fell through to this flowchart\n * parser as the default. Returns undefined when `header` doesn't look like\n * any of those — in which case the generic \"supported headers\" list in the\n * thrown error is the best we can do.\n *\n * This only recognizes the four *other* multi-word headers\n * (sequenceDiagram/classDiagram/erDiagram/xychart-beta/stateDiagram-v2);\n * \"graph\"/\"flowchart\" typos are handled by the direction-specific message\n * below instead, since those already matched the diagram-type keyword and\n * just have a bad or missing direction token.\n */\nfunction suggestedHeaderFor(header: string): string | undefined {\n const lower = header.trim().toLowerCase()\n if (/^sequence/.test(lower)) return 'sequenceDiagram'\n if (/^class/.test(lower)) return 'classDiagram'\n if (/^er/.test(lower)) return 'erDiagram'\n if (/^xychart/.test(lower)) return 'xychart-beta'\n if (/^state/.test(lower)) return 'stateDiagram-v2'\n return undefined\n}\n\n/**\n * Matches the start of a statement that continues the *previous* one's edge\n * chain rather than beginning a new one: Mermaid lets a vertex-chain\n * statement break across lines, with the link operator leading the next\n * line — e.g.\n *\n * start([Start])\n * ==> green([Change some code])\n * ==> finish([Finish])\n *\n * is one chained statement (`start ==> green ==> finish`), identical to\n * writing it on one line. `splitStatementsByLine` only knows about newlines\n * and `;` as separators, so without this it hands `parseFlowchart` three\n * unrelated-looking statements — the continuation lines start with a bare\n * arrow and no node group, so `parseEdgeLine` can't find a source node and\n * drops them entirely (see issue mermaid-js/mermaid#6049's repro, reported\n * against this parser).\n *\n * Covers every arrow opener `parseEdgeLine` itself recognizes (ARROW_REGEX /\n * TEXT_ARROW_REGEX below): a solid/thick run (`--`, `===`), a dotted run\n * (`-.`, `-.-`), a `~~~` run, each optionally preceded by a `<` marker and/or\n * an edge id (`e1@-->`).\n *\n * Deliberately excludes the `o`/`x` start markers ARROW_REGEX also accepts\n * (`o--o`, `x--x`): unlike `<`, both are also valid bare node ids, so a\n * first-body-line statement like `x-->Y` would otherwise be misread as an\n * `x`-marked continuation of the header and merged into it, corrupting the\n * header line (`flowchart TD x-->Y`) instead of parsing `x` as its own node.\n * A genuine marked-start continuation line is rare enough that losing it is\n * the safer tradeoff.\n */\nconst CONTINUATION_START_REGEX = /^(?:[\\w-]+@)?<?(?:-{2,}|={2,}|-\\.+-?|~{3,})/\n\n/**\n * Rejoin a continuation statement onto the one it continues.\n *\n * `splitStatementsByLine` groups statements by their originating physical\n * line, which is what makes this safe: only the *first* statement in a\n * group is eligible to merge backward into the previous group's last\n * statement (a genuine multi-line continuation, see\n * `CONTINUATION_START_REGEX`). A later statement in the same group got\n * there via an explicit `;` on that line (`A --> B; --> C`) — that boundary\n * must stay a boundary, or `--> C` would wrongly become part of the chain\n * instead of the source-less fragment it actually is.\n */\nfunction mergeContinuationLines(groups: Statement[][]): Statement[] {\n const merged: Statement[] = []\n for (const group of groups) {\n group.forEach((statement, index) => {\n if (\n index === 0 &&\n merged.length > 0 &&\n CONTINUATION_START_REGEX.test(statement.text)\n ) {\n // Keep the *first* line's number: a merged statement is reported at\n // the line its logical statement began, not the continuation line.\n const prev = merged[merged.length - 1]!\n merged[merged.length - 1] = {\n text: `${prev.text} ${statement.text}`,\n line: prev.line,\n }\n } else {\n merged.push(statement)\n }\n })\n }\n return merged\n}\n\n/**\n * Parse Mermaid text into a logical graph structure.\n * Auto-detects diagram type (flowchart or state diagram).\n * Throws on invalid/unsupported input.\n */\nexport function parseMermaid(text: string): MermaidGraph {\n /*\n * Init directives must be read from the raw lines: `%%{init: ...}%%` begins\n * with `%%`, so splitStatementsByLine — which treats `%%` as a comment and\n * truncates the line there — would otherwise discard it along with real\n * comments before it could be seen.\n */\n const initConfig = extractInitConfig(text.split('\\n').map((l) => l.trim()))\n\n const lines = mergeContinuationLines(splitStatementsByLine(text))\n\n if (lines.length === 0) {\n throw new Error('Empty mermaid diagram')\n }\n\n // Detect diagram type from header\n const headerStmt = lines[0]!\n\n // State diagram: \"stateDiagram-v2\" or \"stateDiagram\"\n const graph = /^stateDiagram(-v2)?\\s*$/i.test(headerStmt.text)\n ? parseStateDiagram(lines)\n : parseFlowchart(lines)\n\n graph.initConfig = initConfig\n return graph\n}\n\n// ============================================================================\n// Flowchart parser\n// ============================================================================\n\nfunction parseFlowchart(lines: Statement[]): MermaidGraph {\n // parseFlowchart is only ever invoked by parseMermaid, which has already\n // verified `lines` is non-empty — but that invariant isn't visible to the\n // type checker across the function boundary, so validate it here instead\n // of asserting past it.\n const headerStmt = lines[0]\n if (headerStmt === undefined) {\n /* v8 ignore next */\n throw new Error('parseFlowchart called with no lines')\n }\n const header = headerStmt.text\n\n const headerMatch = header.match(\n /^(?:graph|flowchart)\\s+(TD|TB|LR|BT|RL)\\s*$/i,\n )\n if (!headerMatch) {\n // A header that starts with \"graph\"/\"flowchart\" but has a bad or\n // missing direction gets a targeted message about the direction token\n // specifically — it already picked the right diagram type.\n const partialFlowchartMatch = header.match(\n /^(?:graph|flowchart)\\b\\s*(.*)$/i,\n )\n if (partialFlowchartMatch) {\n const rest = partialFlowchartMatch[1]!.trim()\n throw new Error(\n rest.length > 0\n ? `Line ${headerStmt.line}: Invalid direction \"${rest}\" in header \"${header}\". Expected one of: TD, TB, LR, BT, RL.`\n : `Line ${headerStmt.line}: Missing direction in header \"${header}\". Expected e.g. \"graph TD\" or \"flowchart LR\" — one of: TD, TB, LR, BT, RL.`,\n )\n }\n\n // Otherwise this header didn't match any recognized diagram-type\n // keyword at all (typo, extra text, or genuinely unsupported syntax) —\n // point at the closest known diagram type when the text resembles one.\n const suggestion = suggestedHeaderFor(header)\n const hint =\n suggestion && suggestion.toLowerCase() !== header.trim().toLowerCase()\n ? ` Did you mean \"${suggestion}\"?`\n : ''\n throw new Error(\n `Line ${headerStmt.line}: Invalid mermaid header: \"${header}\".${hint} Supported headers: ${SUPPORTED_HEADERS}.`,\n )\n }\n\n const direction = toDirection(headerMatch[1])\n\n const graph: MermaidGraph = {\n direction,\n nodes: new Map(),\n edges: [],\n subgraphs: [],\n classDefs: new Map(),\n classAssignments: new Map(),\n nodeStyles: new Map(),\n linkStyles: new Map(),\n interactions: new Map(),\n }\n\n // Subgraph stack for nested subgraphs.\n const subgraphStack: MermaidSubgraph[] = []\n\n for (let i = 1; i < lines.length; i++) {\n const stmt = lines[i]!\n const line = stmt.text\n\n // --- classDef / class assignment / style — shared with the class-diagram\n // parser, see packages/core/src/style-directives.ts ---\n if (tryApplyClassDef(line, graph)) continue\n if (tryApplyClassAssignment(line, graph)) continue\n if (tryApplyStyleStatement(line, graph)) continue\n\n // --- click interaction: `click A \"url\" \"tooltip\" _blank` / `click A call fn()` ---\n if (/^click\\s+/i.test(line)) {\n applyClickStatement(line, graph)\n continue\n }\n\n // --- edge metadata: `e1@{ animate: true }` — shared with parseStateDiagram ---\n if (tryApplyEdgeMetaLine(line, graph)) {\n continue\n }\n\n // --- linkStyle: `linkStyle 0 stroke:#f00` or `linkStyle default stroke:#f00` ---\n const linkStyleMatch = line.match(/^linkStyle\\s+(default|[\\d,\\s]+)\\s+(.+)$/)\n if (linkStyleMatch) {\n const target = linkStyleMatch[1]!.trim()\n const props = parseStyleProps(linkStyleMatch[2]!)\n if (target === 'default') {\n graph.linkStyles.set('default', {\n ...graph.linkStyles.get('default'),\n ...props,\n })\n } else {\n const indices = target.split(',').map((s) => parseInt(s.trim(), 10))\n for (const idx of indices) {\n if (!isNaN(idx)) {\n graph.linkStyles.set(idx, {\n ...graph.linkStyles.get(idx),\n ...props,\n })\n }\n }\n }\n continue\n }\n\n // --- direction override inside subgraph: `direction LR` ---\n const dirMatch = line.match(/^direction\\s+(TD|TB|LR|BT|RL)\\s*$/i)\n if (dirMatch && subgraphStack.length > 0) {\n subgraphStack[subgraphStack.length - 1]!.direction = toDirection(\n dirMatch[1],\n )\n continue\n }\n\n // --- subgraph start: `subgraph Label` or `subgraph id [Label]` ---\n const subgraphMatch = line.match(/^subgraph\\s+(.+)$/)\n if (subgraphMatch) {\n const rest = subgraphMatch[1]!.trim()\n // Check for `subgraph id [Label]` / `subgraph id [\"Label\"]` form. The id\n // may contain non-ASCII chars (e.g. CJK), so match any run of non-space,\n // non-`[` chars rather than ASCII [\\w-]; strip optional quotes wrapping\n // the title.\n const bracketMatch = rest.match(/^([^\\s[]+)\\s*\\[(.+)\\]$/)\n let id: string\n let label: string\n if (bracketMatch) {\n id = bracketMatch[1]!\n label = normalizeBrTags(stripWrappingQuotes(bracketMatch[2]!))\n } else {\n // `subgraph Label` (optionally quoted): the label doubles as the id,\n // slugified — but preserve unicode letters/numbers so a CJK-only title\n // doesn't collapse to an empty id (which broke nested layout).\n label = normalizeBrTags(stripWrappingQuotes(rest))\n id = label.replace(/\\s+/g, '_').replace(/[^\\p{L}\\p{N}_-]/gu, '')\n }\n const sg: MermaidSubgraph = { id, label, nodeIds: [], children: [] }\n subgraphStack.push(sg)\n continue\n }\n\n // --- subgraph end ---\n if (line === 'end') {\n const completed = subgraphStack.pop()\n if (completed) {\n if (subgraphStack.length > 0) {\n subgraphStack[subgraphStack.length - 1]!.children.push(completed)\n } else {\n graph.subgraphs.push(completed)\n }\n }\n continue\n }\n\n // --- Edge/node definitions ---\n parseEdgeLine(line, graph, subgraphStack)\n }\n\n return graph\n}\n\n// ============================================================================\n// State diagram parser\n//\n// Supported syntax:\n// stateDiagram-v2\n// s1 : Description\n// state \"Description\" as s1\n// s1 --> s2 : label\n// [*] --> s1 (start pseudostate)\n// s1 --> [*] (end pseudostate)\n// state CompositeState {\n// inner1 --> inner2\n// }\n// ============================================================================\n\nfunction parseStateDiagram(lines: Statement[]): MermaidGraph {\n const graph: MermaidGraph = {\n direction: 'TD',\n nodes: new Map(),\n edges: [],\n subgraphs: [],\n classDefs: new Map(),\n classAssignments: new Map(),\n nodeStyles: new Map(),\n linkStyles: new Map(),\n interactions: new Map(),\n }\n\n // Track composite state nesting (like subgraphs)\n const compositeStack: MermaidSubgraph[] = []\n // Track all composite state IDs to avoid creating duplicate nodes\n const compositeStateIds = new Set<string>()\n // Counter for unique [*] pseudostate IDs\n let startCount = 0\n let endCount = 0\n\n for (let i = 1; i < lines.length; i++) {\n const stmt = lines[i]!\n const line = stmt.text\n\n // --- direction override ---\n const dirMatch = line.match(/^direction\\s+(TD|TB|LR|BT|RL)\\s*$/i)\n if (dirMatch) {\n if (compositeStack.length > 0) {\n compositeStack[compositeStack.length - 1]!.direction = toDirection(\n dirMatch[1],\n )\n } else {\n graph.direction = toDirection(dirMatch[1])\n }\n continue\n }\n\n // --- linkStyle: `linkStyle 0 stroke:#f00` or `linkStyle default stroke:#f00` ---\n const linkStyleMatch = line.match(/^linkStyle\\s+(default|[\\d,\\s]+)\\s+(.+)$/)\n if (linkStyleMatch) {\n const target = linkStyleMatch[1]!.trim()\n const props = parseStyleProps(linkStyleMatch[2]!)\n if (target === 'default') {\n graph.linkStyles.set('default', {\n ...graph.linkStyles.get('default'),\n ...props,\n })\n } else {\n const indices = target.split(',').map((s) => parseInt(s.trim(), 10))\n for (const idx of indices) {\n if (!isNaN(idx)) {\n graph.linkStyles.set(idx, {\n ...graph.linkStyles.get(idx),\n ...props,\n })\n }\n }\n }\n continue\n }\n\n // --- edge metadata: `e1@{ animate: true }` — shared with parseFlowchart ---\n if (tryApplyEdgeMetaLine(line, graph)) {\n continue\n }\n\n // --- composite state start: `state CompositeState {` ---\n const compositeMatch = line.match(\n /^state\\s+(?:\"([^\"]+)\"\\s+as\\s+)?([\\w\\p{L}]+)\\s*\\{$/u,\n )\n if (compositeMatch) {\n const label = compositeMatch[1] ?? compositeMatch[2]!\n const id = compositeMatch[2]!\n const sg: MermaidSubgraph = { id, label, nodeIds: [], children: [] }\n compositeStack.push(sg)\n // Track this ID to avoid creating a duplicate node for the composite state\n compositeStateIds.add(id)\n // Remove any existing node that was created when parsing transitions before\n // this composite state definition (e.g., \"A --> Processing\" before \"state Processing {\")\n graph.nodes.delete(id)\n continue\n }\n\n // --- composite state end ---\n if (line === '}') {\n const completed = compositeStack.pop()\n if (completed) {\n if (compositeStack.length > 0) {\n compositeStack[compositeStack.length - 1]!.children.push(completed)\n } else {\n graph.subgraphs.push(completed)\n }\n }\n continue\n }\n\n // --- state alias: `state \"Description\" as s1` (without brace) ---\n const stateAliasMatch = line.match(\n /^state\\s+\"([^\"]+)\"\\s+as\\s+([\\w\\p{L}]+)\\s*$/u,\n )\n if (stateAliasMatch) {\n const label = normalizeBrTags(stateAliasMatch[1]!)\n const id = stateAliasMatch[2]!\n registerStateNode(graph, compositeStack, { id, label, shape: 'rounded' })\n continue\n }\n\n /*\n * --- transition: `s1 --> s2`, `s1 --> s2 : label`, `[*] --> s1`, or\n * with an edge id (Mermaid v11.10.0+): `s1 e1@--> s2` ---\n *\n * State-diagram transitions only ever use `-->` (unlike flowchart's\n * many arrow variants), so the edge id — same `id@` prefix syntax as\n * flowchart's `A e1@--> B` — is captured inline in this one regex\n * rather than needing flowchart's separate pre-arrow scan.\n */\n const transitionMatch = line.match(\n /^(\\[\\*\\]|[\\w\\p{L}-]+)\\s*(?:([\\w-]+)@)?-->\\s*(\\[\\*\\]|[\\w\\p{L}-]+)(?:\\s*:\\s*(.+))?$/u,\n )\n if (transitionMatch) {\n let sourceId = transitionMatch[1]!\n const edgeId = transitionMatch[2]\n let targetId = transitionMatch[3]!\n const rawTransitionLabel = transitionMatch[4]?.trim()\n const edgeLabel = rawTransitionLabel\n ? normalizeBrTags(rawTransitionLabel)\n : undefined\n\n // Handle [*] pseudostates — each occurrence gets a unique ID\n if (sourceId === '[*]') {\n startCount++\n sourceId = `_start${startCount > 1 ? startCount : ''}`\n registerStateNode(graph, compositeStack, {\n id: sourceId,\n label: '',\n shape: 'state-start',\n })\n } else if (!compositeStateIds.has(sourceId)) {\n // Only create a node if this isn't a composite state\n ensureStateNode(graph, compositeStack, sourceId)\n }\n\n if (targetId === '[*]') {\n endCount++\n targetId = `_end${endCount > 1 ? endCount : ''}`\n registerStateNode(graph, compositeStack, {\n id: targetId,\n label: '',\n shape: 'state-end',\n })\n } else if (!compositeStateIds.has(targetId)) {\n // Only create a node if this isn't a composite state\n ensureStateNode(graph, compositeStack, targetId)\n }\n\n graph.edges.push({\n source: sourceId,\n target: targetId,\n label: edgeLabel,\n style: 'solid',\n hasArrowStart: false,\n hasArrowEnd: true,\n ...(edgeId !== undefined ? { id: edgeId } : {}),\n })\n continue\n }\n\n // --- state description: `s1 : Description` ---\n const stateDescMatch = line.match(/^([\\w\\p{L}-]+)\\s*:\\s*(.+)$/u)\n if (stateDescMatch) {\n const id = stateDescMatch[1]!\n const label = normalizeBrTags(stateDescMatch[2]!.trim())\n registerStateNode(graph, compositeStack, { id, label, shape: 'rounded' })\n continue\n }\n }\n\n return graph\n}\n\n/** Register a state node and track in composite state if applicable */\nfunction registerStateNode(\n graph: MermaidGraph,\n compositeStack: MermaidSubgraph[],\n node: MermaidNode,\n): void {\n const isNew = !graph.nodes.has(node.id)\n if (isNew) {\n graph.nodes.set(node.id, node)\n }\n if (compositeStack.length > 0) {\n const current = compositeStack[compositeStack.length - 1]!\n if (!current.nodeIds.includes(node.id)) {\n current.nodeIds.push(node.id)\n }\n }\n}\n\n/** Ensure a state node exists with default rounded shape */\nfunction ensureStateNode(\n graph: MermaidGraph,\n compositeStack: MermaidSubgraph[],\n id: string,\n): void {\n if (!graph.nodes.has(id)) {\n registerStateNode(graph, compositeStack, {\n id,\n label: id,\n shape: 'rounded',\n })\n } else {\n // Track in composite if applicable\n if (compositeStack.length > 0) {\n const current = compositeStack[compositeStack.length - 1]!\n if (!current.nodeIds.includes(id)) {\n current.nodeIds.push(id)\n }\n }\n }\n}\n\n// ============================================================================\n// Flowchart edge line parser\n//\n// Handles chained edges like: A[Label] --> B(Label) -.-> C{Label}\n// Also handles & parallel links: A & B --> C & D\n// ============================================================================\n\n/**\n * Arrow regex — matches all arrow operators with optional labels.\n *\n * Supported operators:\n * --> --- solid arrow / solid line\n * -.-> -.- dotted arrow / dotted line\n * ==> === thick arrow / thick line\n * <--> <-.-> <==> bidirectional variants\n * --o --x circle-end / cross-end arrow (see issue #65)\n * o-- x-- circle-start / cross-start arrow\n * o--o x--x circle/cross at both ends\n *\n * The doubled `o--o`/`x--x` forms must be tried before the single-sided\n * `o--`/`x--` forms — regex alternation picks the first alternative that\n * matches, not the longest, so `o--` listed first would consume just the\n * start marker and strand the trailing `o` as a bogus token. Capturing the\n * markers as their own groups (rather than baking them into an alternation\n * of whole tokens) sidesteps that ordering problem entirely.\n *\n * The body is a variable-length run: Mermaid uses extra characters as a\n * layout-rank hint (`A ---- B` pushes B one rank further than `A --> B`).\n * A fixed alternation of `-->|---|==>` mis-tokenized anything longer,\n * stranding the surplus characters and corrupting the following token.\n * The run length is parsed so the edge survives; the rank hint it encodes\n * is not yet modelled by the layout engine.\n *\n * `~~~` is Mermaid's invisible link: it participates in layout but draws\n * nothing.\n *\n * Optional label: -->|label text|\n */\nconst ARROW_REGEX = /^(<|o|x)?(-{2,}|={2,}|-\\.+-|~{3,})(>|o|x)?(?:\\|([^|]*)\\|)?/\n\n/**\n * Link bodies that are only a link when a start or end marker accompanies\n * them.\n *\n * A bare `--` (or `==`) opens Mermaid's text-embedded label syntax —\n * `A -- Yes --> B` — which TEXT_ARROW_REGEX handles. Treating it as an\n * unmarked open link here would consume the opener and strand the label.\n * Mermaid itself requires three characters for an unmarked open link\n * (`A --- B`), so this only rejects what Mermaid also rejects.\n */\nconst AMBIGUOUS_UNMARKED_BODIES = new Set(['--', '=='])\n\n/**\n * Map a raw start/end marker character (`<`, `>`, `o`, `x`, or undefined) to\n * the distinct terminator shape it draws, if any.\n *\n * `<`/`>` are plain arrowheads — already handled by `hasArrowStart`/\n * `hasArrowEnd` — so they map to `undefined` here, same as no marker at all.\n * Only `o`/`x` need their own shape (circle/cross) recorded, so the ASCII\n * renderer can draw something other than the default arrowhead glyph — see\n * issue #330.\n */\nfunction markerKind(\n marker: string | undefined,\n): 'circle' | 'cross' | undefined {\n if (marker === 'o') return 'circle'\n if (marker === 'x') return 'cross'\n return undefined\n}\n\n/**\n * Text-embedded label regex — matches \"-- label -->\", \"-. label .->\", \"== label ==>\" syntax.\n * Tried as fallback when ARROW_REGEX doesn't match.\n *\n * The closing operator is a variable-length run, matching ARROW_REGEX. While\n * it was a fixed alternation, `A -- label ----> B` consumed only `---` from\n * `---->`, leaving `-> B`, which forms no node group — so the edge and its\n * target node were both dropped silently. Exactly the failure mode the\n * variable-length work exists to remove.\n *\n * Based on PR #36 by @liuxiaopai-ai (https://github.com/lukilabs/beautiful-mermaid/pull/36)\n */\nconst TEXT_ARROW_REGEX =\n /^(<|o|x)?(--|-\\.|==)\\s+(.+?)\\s+(-{2,}[>ox]|={2,}[>ox]|\\.+-[>ox]|-{3,}|={3,}|-\\.+-)/\n\n/**\n * Node shape patterns — ordered from most specific delimiters to least.\n * Multi-char delimiters must be tried before single-char to avoid false matches.\n *\n * The label-content group for each shape is quote-aware: it matches either a\n * complete `\"...\"` quoted span (any character, including this shape's own\n * closing delimiter chars, is fine inside quotes) or a single character that\n * isn't the start of the closing delimiter. This stops the scanner from\n * treating a `]`/`)`/`}` etc. *inside* a quoted label as the node's real\n * closing bracket (see issue #61) — without this, `A[\"test [] brackets\"]`\n * would stop at the first `]`, which lives inside the quoted string.\n */\n/**\n * A node shape pattern.\n *\n * `shape` is usually a fixed `NodeShape`. For the slash-bracket family it is\n * a function instead, because those four shapes are distinguished by which\n * delimiter *closes* them — which the regex has to capture rather than\n * hard-code. See SLASH_BRACKET note below.\n */\ninterface NodePattern {\n regex: RegExp\n shape: NodeShape | ((closingDelimiter: string) => NodeShape)\n}\n\nconst NODE_PATTERNS: NodePattern[] = [\n // Triple delimiters (must be first)\n {\n regex: /^([\\w\\p{L}-]+)\\(\\(\\(((?:\"[^\"]*\"|(?!\\)\\)\\)).)+)\\)\\)\\)/u,\n shape: 'doublecircle',\n }, // A(((text)))\n\n // Double delimiters with mixed brackets\n {\n regex: /^([\\w\\p{L}-]+)\\(\\[((?:\"[^\"]*\"|(?!\\]\\)).)+)\\]\\)/u,\n shape: 'stadium',\n }, // A([text])\n { regex: /^([\\w\\p{L}-]+)\\(\\(((?:\"[^\"]*\"|(?!\\)\\)).)+)\\)\\)/u, shape: 'circle' }, // A((text))\n {\n regex: /^([\\w\\p{L}-]+)\\[\\[((?:\"[^\"]*\"|(?!\\]\\]).)+)\\]\\]/u,\n shape: 'subroutine',\n }, // A[[text]]\n {\n regex: /^([\\w\\p{L}-]+)\\[\\(((?:\"[^\"]*\"|(?!\\)\\]).)+)\\)\\]/u,\n shape: 'cylinder',\n }, // A[(text)]\n\n /*\n * SLASH_BRACKET family — must come before plain [text].\n *\n * Four shapes share a `[` + slash opener and differ only in which\n * delimiter closes them:\n *\n * A[/text\\] trapezoid A[/text/] parallelogram\n * A[\\text/] trapezoid-alt A[\\text\\] parallelogram-alt\n *\n * They CANNOT be four separate patterns each negated against its own\n * closing pair. A pattern for `[/…\\]` whose content merely excludes `\\]`\n * will happily run past a `/]` to reach a `\\]` later on the same line, so\n *\n * A[/parallelogram/] --> B[\\alt\\]\n *\n * matched as a single trapezoid whose label was the entire statement —\n * silently swallowing the edge and the second node. Ordering the patterns\n * differently only moves which input breaks.\n *\n * Instead, one pattern per opener stops at whichever slash-close comes\n * first and captures it, and the captured delimiter selects the shape.\n */\n {\n regex: /^([\\w\\p{L}-]+)\\[\\/((?:\"[^\"]*\"|(?![\\\\/]\\]).)+)([\\\\/])\\]/u,\n shape: (close) => (close === '\\\\' ? 'trapezoid' : 'parallelogram'),\n }, // A[/text\\] or A[/text/]\n {\n regex: /^([\\w\\p{L}-]+)\\[\\\\((?:\"[^\"]*\"|(?![\\\\/]\\]).)+)([\\\\/])\\]/u,\n shape: (close) => (close === '/' ? 'trapezoid-alt' : 'parallelogram-alt'),\n }, // A[\\text/] or A[\\text\\]\n\n // Asymmetric flag shape\n { regex: /^([\\w\\p{L}-]+)>((?:\"[^\"]*\"|(?!\\]).)+)\\]/u, shape: 'asymmetric' }, // A>text]\n\n // Double curly braces (hexagon) — must come before single {text}\n {\n regex: /^([\\w\\p{L}-]+)\\{\\{((?:\"[^\"]*\"|(?!\\}\\}).)+)\\}\\}/u,\n shape: 'hexagon',\n }, // A{{text}}\n\n // Single-char delimiters (last — most common, least specific)\n { regex: /^([\\w\\p{L}-]+)\\[((?:\"[^\"]*\"|(?!\\]).)+)\\]/u, shape: 'rectangle' }, // A[text]\n { regex: /^([\\w\\p{L}-]+)\\(((?:\"[^\"]*\"|(?!\\)).)+)\\)/u, shape: 'rounded' }, // A(text)\n { regex: /^([\\w\\p{L}-]+)\\{((?:\"[^\"]*\"|(?!\\}).)+)\\}/u, shape: 'diamond' }, // A{text}\n]\n\n/**\n * Regex for a bare node reference (just an ID, no shape brackets).\n *\n * Only allows a hyphen when it's sandwiched between word characters\n * (`foo-bar`, `step-1-b`), never a bare/trailing/doubled hyphen. This keeps\n * legitimately hyphenated ids intact while stopping the id from swallowing\n * the leading dashes of an immediately-following arrow when there's no\n * whitespace before it — e.g. `A-->B` (see issue #61): the naive `[\\w-]+`\n * greedily consumed `A--`, leaving a bogus node and no edge. Arrow tokens\n * always start with `-`/`=`/`<` followed by another non-word character\n * (`-`, `.`, `=`, `>`), which this pattern never matches into, so it now\n * stops cleanly at `A` and lets the arrow regex take over.\n *\n * `\\p{L}` (any Unicode letter, alongside plain `\\w`) is included so a bare\n * non-ASCII name (`Lasaña`, `日本`) matches in full instead of truncating at\n * the first non-ASCII character and silently stranding the rest of the line\n * as unparsed text — see issue #328. This mirrors the state-diagram\n * transition regex below, which already allows `\\p{L}` for the same reason.\n */\nconst BARE_NODE_REGEX = /^([\\w\\p{L}]+(?:-[\\w\\p{L}]+)*)/u\n\n/**\n * Node id immediately followed by the expanded-syntax opener: `A@{`.\n *\n * Only the id is captured — the block itself needs depth- and quote-aware\n * scanning (a label may contain `}`), which a regex would do badly, so\n * `matchExpandedBlock` takes over from here.\n */\nconst EXPANDED_NODE_ID_REGEX = /^([\\w\\p{L}-]+)(?=@\\{)/u\n\n/**\n * Resolve the geometry for an `A@{ ... }` node.\n *\n * An `icon:` or `img:` node has no `shape:` of its own — its outline comes\n * from `form:` instead (Mermaid defaults to a square). An unrecognized shape\n * name falls back to a rectangle rather than throwing: Mermaid adds shape\n * names regularly, and rendering a plain box beats failing the whole diagram\n * over one unknown name.\n */\nfunction expandedNodeShape(meta: ExpandedNodeMeta): NodeShape {\n if (meta.shape) {\n return resolveShapeName(meta.shape) ?? 'rectangle'\n }\n\n if (meta.icon !== undefined || meta.img !== undefined) {\n switch (meta.form?.toLowerCase()) {\n case 'circle':\n return 'circle'\n case 'rounded':\n return 'rounded'\n default:\n return 'rectangle'\n }\n }\n\n return 'rectangle'\n}\n\n/**\n * Resolve the display label for an `A@{ ... }` node.\n *\n * Falls back to the node id when no `label:` is given, matching how the\n * bracket syntax treats a bare `A`. For an icon or image node with no label,\n * the icon/image reference itself is shown: this renderer draws neither\n * FontAwesome glyphs nor remote images, so showing the reference is more\n * useful than an empty box, and it keeps the node identifiable.\n */\nfunction expandedNodeLabel(id: string, meta: ExpandedNodeMeta): string {\n if (meta.label !== undefined && meta.label.length > 0) return meta.label\n if (meta.icon) return meta.icon\n if (meta.img) return meta.img\n return id\n}\n\n/**\n * Apply a `click` statement to the graph.\n *\n * Mermaid's forms:\n * click A \"https://example.com\"\n * click A \"https://example.com\" \"Tooltip\"\n * click A \"https://example.com\" _blank\n * click A href \"https://example.com\" \"Tooltip\" _blank\n * click A call myCallback()\n * click A callback \"Tooltip\"\n *\n * A callback is recorded but never invoked — this renderer emits static SVG\n * and does not execute script supplied by a diagram. An href, by contrast, is\n * genuinely actionable: the node is wrapped in an SVG <a>, which works in any\n * browser without script.\n *\n * The actual grammar and href-safety rules live in packages/core/src/click-directive.ts,\n * shared with the class diagram parser\n * (packages/mermaid-parser/src/class/parser.ts) — this is a thin wrapper\n * binding it to this parser's `graph.interactions` map.\n */\nfunction applyClickStatement(line: string, graph: MermaidGraph): void {\n applyClickStatementShared(line, graph.interactions)\n}\n\n/**\n * Id immediately followed by the expanded-syntax opener on a standalone\n * metadata line: `e1@{ ... }`.\n *\n * Shared by both parsers (`tryApplyEdgeMetaLine`) since the syntax and its\n * \"must already be a known edge id\" disambiguation rule are identical for\n * flowcharts and state diagrams — only how edge ids get declared differs\n * (`A e1@--> B` vs. `A e1@--> B` with a simpler arrow set).\n */\nconst EDGE_META_ID_REGEX = /^([\\w-]+)(?=@\\{)/\n\n/**\n * Try to parse and apply a standalone `e1@{ animate: true }` edge-metadata\n * line.\n *\n * Told apart from a node's `A@{ shape: ... }` by whether the id was already\n * declared as an edge id (`A e1@--> B`) — checked before node parsing so an\n * edge id is never registered as a stray node. Shared between\n * `parseFlowchart` and `parseStateDiagram`; returns `true` if the line was\n * consumed as edge metadata (caller should `continue` its loop).\n */\nfunction tryApplyEdgeMetaLine(line: string, graph: MermaidGraph): boolean {\n const edgeMetaMatch = line.match(EDGE_META_ID_REGEX)\n if (!edgeMetaMatch || !graph.edges.some((e) => e.id === edgeMetaMatch[1])) {\n return false\n }\n const block = matchExpandedBlock(line.slice(edgeMetaMatch[1]!.length))\n if (!block) return false\n applyEdgeMeta(graph, edgeMetaMatch[1]!, parseExpandedMeta(block.body))\n return true\n}\n\n/**\n * Apply an `e1@{ ... }` metadata block to the edge with that id.\n *\n * Only `animate` is acted on; Mermaid's other edge keys (`animation`,\n * `curve`) are recorded as no-ops rather than errors, matching how an\n * unknown node shape degrades.\n */\nfunction applyEdgeMeta(\n graph: MermaidGraph,\n edgeId: string,\n meta: Record<string, string | undefined>,\n): void {\n for (const edge of graph.edges) {\n if (edge.id !== edgeId) continue\n if (meta.animate !== undefined) {\n edge.animate = meta.animate.toLowerCase() !== 'false'\n }\n // Mermaid's `animation: fast|slow` is a speed hint on top of animate.\n if (meta.animation !== undefined) edge.animate = true\n }\n}\n\n/** Regex for ::: class shorthand suffix — matches :::className immediately after a node */\nconst CLASS_SHORTHAND_REGEX = /^:::([\\w][\\w-]*)/\n\n/**\n * Regex for ::: class shorthand appearing BEFORE the shape brackets,\n * e.g. A:::external[Label]. Captures the bare id and the class name so\n * the shorthand can be stripped out before shape-pattern matching runs.\n *\n * The id group allows `\\p{L}` (see BARE_NODE_REGEX above) so a non-ASCII\n * id keeps working with this shorthand instead of falling through to\n * BARE_NODE_REGEX with the `:::className` suffix left dangling as\n * unparsed text. The class-name group stays ASCII-only — Mermaid class\n * names follow CSS identifier conventions, not node-name conventions.\n */\nconst PRE_CLASS_SHORTHAND_REGEX = /^([\\w\\p{L}-]+):::([\\w][\\w-]*)/u\n\n/**\n * Parse a line that contains node definitions and edges.\n * Handles chaining: A --> B --> C produces edges A→B and B→C.\n * Handles parallel links: A & B --> C & D produces 4 edges.\n */\nfunction parseEdgeLine(\n line: string,\n graph: MermaidGraph,\n subgraphStack: MermaidSubgraph[],\n): void {\n let remaining = line.trim()\n\n // Parse the first node group (possibly with & separators)\n const firstGroup = consumeNodeGroup(remaining, graph, subgraphStack)\n if (!firstGroup || firstGroup.ids.length === 0) return\n\n remaining = firstGroup.remaining.trim()\n let prevGroupIds = firstGroup.ids\n\n // Parse arrow + node-group pairs until the line is exhausted\n while (remaining.length > 0) {\n let hasArrowStart: boolean\n let style: EdgeStyle\n let hasArrowEnd: boolean\n let edgeLabel: string | undefined\n let startMarkerKind: 'circle' | 'cross' | undefined\n let endMarkerKind: 'circle' | 'cross' | undefined\n\n /*\n * Optional edge id prefix: `A e1@--> B` (Mermaid v11.10.0+). Consumed\n * before the arrow so ARROW_REGEX still sees the link token at position\n * zero. `@` is not otherwise valid ahead of a link, so this cannot\n * shadow existing syntax.\n */\n let edgeId: string | undefined\n const edgeIdMatch = remaining.match(/^([\\w-]+)@(?=[-=<~ox])/)\n if (edgeIdMatch) {\n edgeId = edgeIdMatch[1]\n remaining = remaining.slice(edgeIdMatch[0].length)\n }\n\n const arrowMatch = remaining.match(ARROW_REGEX)\n const arrowBody = arrowMatch?.[2]\n const startMarker = arrowMatch?.[1]\n const endMarker = arrowMatch?.[3]\n\n if (\n arrowMatch &&\n arrowBody !== undefined &&\n // An unmarked `--`/`==` is the text-label opener, not a link.\n !(\n startMarker === undefined &&\n endMarker === undefined &&\n AMBIGUOUS_UNMARKED_BODIES.has(arrowBody)\n )\n ) {\n // `o`/`x` mark a circle/cross terminator (alongside `<` for a reversed\n // arrow) — `markerKind` records which, so the ASCII renderer can draw\n // a distinct glyph instead of the default arrowhead (see issue #330,\n // a follow-up to issue #65 which first stopped these from being\n // dropped entirely).\n hasArrowStart = startMarker !== undefined\n startMarkerKind = markerKind(startMarker)\n const rawEdgeLabel = arrowMatch[4]?.trim()\n edgeLabel = rawEdgeLabel ? normalizeBrTags(rawEdgeLabel) : undefined\n remaining = remaining.slice(arrowMatch[0].length).trim()\n style = arrowStyleFromBody(arrowBody)\n hasArrowEnd = endMarker !== undefined\n endMarkerKind = markerKind(endMarker)\n } else {\n // Fallback: text-embedded label syntax (-- Yes -->, -. Maybe .->, == Sure ==>)\n const textMatch = remaining.match(TEXT_ARROW_REGEX)\n if (!textMatch) break\n hasArrowStart = Boolean(textMatch[1])\n startMarkerKind = markerKind(textMatch[1])\n const rawLabel = textMatch[3]!.trim()\n edgeLabel = rawLabel ? normalizeBrTags(rawLabel) : undefined\n const openOp = textMatch[2]!\n const closeOp = textMatch[4]!\n remaining = remaining.slice(textMatch[0].length).trim()\n style = textArrowStyleFromOps(openOp, closeOp)\n // closeOp is a variable-length run (e.g. \"---o\", \"==x\", \"-.-.->\"); only\n // its final character can be a circle/cross marker. A circle/cross\n // terminator is its own kind of arrow ending — like `>` — so it counts\n // toward hasArrowEnd too; an unmarked run (`---`, `===`, `-.-`) is the\n // only case with no arrowhead of any kind at the close.\n endMarkerKind = markerKind(closeOp.slice(-1))\n hasArrowEnd = closeOp.endsWith('>') || endMarkerKind !== undefined\n }\n\n // Parse the next node group\n const nextGroup = consumeNodeGroup(remaining, graph, subgraphStack)\n if (!nextGroup || nextGroup.ids.length === 0) break\n\n remaining = nextGroup.remaining.trim()\n\n // Emit Cartesian product of edges: every source × every target\n for (const sourceId of prevGroupIds) {\n for (const targetId of nextGroup.ids) {\n graph.edges.push({\n source: sourceId,\n target: targetId,\n label: edgeLabel,\n style,\n hasArrowStart,\n hasArrowEnd,\n ...(startMarkerKind !== undefined\n ? { startMarker: startMarkerKind }\n : {}),\n ...(endMarkerKind !== undefined ? { endMarker: endMarkerKind } : {}),\n ...(edgeId !== undefined ? { id: edgeId } : {}),\n })\n }\n }\n\n prevGroupIds = nextGroup.ids\n }\n}\n\ninterface ConsumedNodeGroup {\n ids: string[]\n remaining: string\n}\n\n/**\n * Consume one or more nodes separated by `&`.\n * E.g. \"A & B & C --> ...\" returns ids: ['A', 'B', 'C']\n */\nfunction consumeNodeGroup(\n text: string,\n graph: MermaidGraph,\n subgraphStack: MermaidSubgraph[],\n): ConsumedNodeGroup | null {\n const first = consumeNode(text, graph, subgraphStack)\n if (!first) return null\n\n const ids = [first.id]\n let remaining = first.remaining.trim()\n\n // Check for & separators\n while (remaining.startsWith('&')) {\n remaining = remaining.slice(1).trim()\n const next = consumeNode(remaining, graph, subgraphStack)\n if (!next) break\n ids.push(next.id)\n remaining = next.remaining.trim()\n }\n\n return { ids, remaining }\n}\n\ninterface ConsumedNode {\n id: string\n remaining: string\n}\n\n/**\n * Try to consume a node definition from the start of `text`.\n * If the node has a shape+label (e.g. A[Text]), it's registered in the graph.\n * If it's a bare reference (e.g. A), we look it up or create a default.\n * Also handles ::: class shorthand suffix.\n */\nfunction consumeNode(\n text: string,\n graph: MermaidGraph,\n subgraphStack: MermaidSubgraph[],\n): ConsumedNode | null {\n let id: string | null = null\n let remaining: string = text\n\n // Check for ::: class shorthand appearing BEFORE the shape brackets\n // (e.g. A:::external[Label]). Strip it out so shape-pattern matching\n // below still sees the id directly adjacent to its brackets.\n let preClassName: string | undefined\n const preClassMatch = remaining.match(PRE_CLASS_SHORTHAND_REGEX)\n if (preClassMatch) {\n preClassName = preClassMatch[2]!\n remaining = preClassMatch[1]! + remaining.slice(preClassMatch[0].length)\n }\n\n /*\n * Expanded syntax: `A@{ shape: doc, label: \"Report\" }` (Mermaid v11.3.0+).\n *\n * Tried before the bracket patterns because the id is followed by `@{`,\n * which none of them match — without this the id would fall through to\n * BARE_NODE_REGEX and the whole metadata block would be stranded as\n * unparsed text.\n */\n const expandedIdMatch = remaining.match(EXPANDED_NODE_ID_REGEX)\n if (expandedIdMatch) {\n const expandedId = expandedIdMatch[1]!\n const block = matchExpandedBlock(remaining.slice(expandedId.length))\n if (block) {\n const meta = parseExpandedMeta(block.body)\n registerNode(graph, subgraphStack, {\n id: expandedId,\n label: normalizeBrTags(expandedNodeLabel(expandedId, meta)),\n shape: expandedNodeShape(meta),\n })\n id = expandedId\n remaining = remaining.slice(expandedId.length + block.length)\n }\n }\n\n // Try each node pattern (shape-qualified)\n if (id === null) {\n for (const { regex, shape } of NODE_PATTERNS) {\n const match = remaining.match(regex)\n if (match) {\n id = match[1]!\n const label = normalizeBrTags(match[2]!)\n // The slash-bracket family resolves its shape from the closing\n // delimiter it captured in group 3; every other pattern has a fixed\n // shape. See the SLASH_BRACKET note in NODE_PATTERNS.\n const resolvedShape =\n typeof shape === 'function' ? shape(match[3] ?? '') : shape\n registerNode(graph, subgraphStack, { id, label, shape: resolvedShape })\n remaining = remaining.slice(match[0].length)\n break\n }\n }\n }\n\n // Bare node reference — only register if node doesn't exist yet.\n // If it already exists, do NOT track it in the current subgraph;\n // nodes belong to the subgraph where they're first defined.\n if (id === null) {\n const bareMatch = remaining.match(BARE_NODE_REGEX)\n if (bareMatch) {\n id = bareMatch[1]!\n if (!graph.nodes.has(id)) {\n registerNode(graph, subgraphStack, {\n id,\n label: id,\n shape: 'rectangle',\n })\n }\n remaining = remaining.slice(bareMatch[0].length)\n }\n }\n\n if (id === null) return null\n\n if (preClassName) {\n graph.classAssignments.set(id, preClassName)\n }\n\n // Check for ::: class shorthand suffix immediately after the node\n const classMatch = remaining.match(CLASS_SHORTHAND_REGEX)\n if (classMatch) {\n graph.classAssignments.set(id, classMatch[1]!)\n remaining = remaining.slice(classMatch[0].length)\n }\n\n return { id, remaining }\n}\n\n/** Register a node in the graph and track it in the current subgraph */\nfunction registerNode(\n graph: MermaidGraph,\n subgraphStack: MermaidSubgraph[],\n node: MermaidNode,\n): void {\n const isNew = !graph.nodes.has(node.id)\n if (isNew) {\n graph.nodes.set(node.id, node)\n }\n trackInSubgraph(subgraphStack, node.id)\n}\n\n/** Add node ID to the innermost subgraph if we're inside one */\nfunction trackInSubgraph(\n subgraphStack: MermaidSubgraph[],\n nodeId: string,\n): void {\n if (subgraphStack.length > 0) {\n const current = subgraphStack[subgraphStack.length - 1]!\n if (!current.nodeIds.includes(nodeId)) {\n current.nodeIds.push(nodeId)\n }\n }\n}\n\n/**\n * Map a link body to its edge style, ignoring direction and run length.\n *\n * The body is the run between the optional start/end markers: `--`, `----`,\n * `==`, `-.-`, `-..-`, `~~~`, etc. Classification is by the characters used,\n * not the length, so every run length of a given style behaves identically.\n */\nfunction arrowStyleFromBody(body: string): EdgeStyle {\n if (body.startsWith('~')) return 'invisible'\n if (body.includes('.')) return 'dotted'\n if (body.startsWith('=')) return 'thick'\n return 'solid'\n}\n\n/** Map text-embedded arrow open/close operators to edge style */\nfunction textArrowStyleFromOps(openOp: string, closeOp: string): EdgeStyle {\n // Classify by the characters used, not by exact token, so every run length\n // of a given style resolves identically — the same rule arrowStyleFromBody\n // applies to the plain arrow forms.\n if (openOp.includes('.') || closeOp.includes('.')) return 'dotted'\n if (openOp.startsWith('=') || closeOp.startsWith('=')) return 'thick'\n return 'solid'\n}\n","// ============================================================================\n// ASCII renderer — MermaidGraph → AsciiGraph converter\n//\n// Bridges the existing TypeScript parser output to the ASCII renderer's\n// internal graph structure. This avoids maintaining a separate parser\n// for ASCII rendering — we reuse parseMermaid() and convert its output.\n// ============================================================================\n\nimport type { MermaidGraph, MermaidSubgraph } from '@zombie-mermaid/core'\nimport type {\n AsciiGraph,\n AsciiNode,\n AsciiEdge,\n AsciiSubgraph,\n AsciiConfig,\n} from './types.ts'\nimport { EMPTY_STYLE } from './types.ts'\nimport { mkCanvas, mkRoleCanvas } from './canvas.ts'\nimport { createGrid } from './grid-occupancy.ts'\nimport { stripFormattingTags } from '@zombie-mermaid/core'\n\n/**\n * Convert a parsed MermaidGraph into an AsciiGraph ready for grid layout.\n *\n * Key mappings:\n * - MermaidGraph.nodes (Map) → ordered AsciiNode[] preserving insertion order\n * - MermaidGraph.edges → AsciiEdge[] with resolved node references\n * - MermaidGraph.subgraphs → AsciiSubgraph[] with parent/child tree\n * - Node labels are used as display names (not raw IDs)\n */\nexport function convertToAsciiGraph(\n parsed: MermaidGraph,\n config: AsciiConfig,\n): AsciiGraph {\n // Collect subgraph ids (including nested) up front. When a flowchart edge\n // references a subgraph id directly (e.g. `ONE --> TWO` where ONE/TWO are\n // subgraph ids, not nodes — see issue #65), the parser has no way to know\n // that at parse time and registers a phantom node with that id instead.\n // The ELK/SVG path resolves this by never materializing a top-level node\n // for a subgraph id and letting the edge reference the compound subgraph\n // node directly (see `mermaidToElk` in to-elk.ts). The ASCII grid has no\n // equivalent \"compound node as edge endpoint\" concept, so instead we\n // suppress the phantom node here and, below, redirect any edge that\n // targets a subgraph id to a real member node at that subgraph's\n // boundary — see `resolveSubgraphEndpoint`.\n const subgraphIds = new Set<string>()\n const subgraphById = new Map<string, MermaidSubgraph>()\n for (const mSg of parsed.subgraphs) {\n indexSubgraph(mSg, subgraphIds, subgraphById)\n }\n\n // Build node list preserving Map insertion order\n const nodeMap = new Map<string, AsciiNode>()\n let index = 0\n\n for (const [id, mNode] of parsed.nodes) {\n // Skip phantom nodes that only exist because a subgraph id was\n // referenced as an edge endpoint — see comment above.\n if (subgraphIds.has(id)) continue\n\n const asciiNode: AsciiNode = {\n // Use the parser ID as the unique identity key to avoid collisions\n // when multiple nodes share the same label (e.g. A[Web Server], C[Web Server]).\n name: id,\n // The label is used for rendering inside the box. Inline formatting\n // tags (<b>, <i>, <em>, <strong>, ...) are meaningful to the SVG\n // renderer (rendered as styled tspans) but the ASCII renderer has no\n // equivalent concept, so they're stripped here rather than shown\n // literally — see issue #65.\n displayLabel: stripFormattingTags(mNode.label),\n // Preserve shape from parser for shape-aware rendering\n shape: mNode.shape,\n index,\n gridCoord: null,\n drawingCoord: null,\n drawing: null,\n drawn: false,\n styleClassName: '',\n styleClass: EMPTY_STYLE,\n }\n nodeMap.set(id, asciiNode)\n index++\n }\n\n const nodes = [...nodeMap.values()]\n\n // Build edges with resolved node references\n const edges: AsciiEdge[] = []\n for (const mEdge of parsed.edges) {\n let sourceId = mEdge.source\n let targetId = mEdge.target\n\n if (subgraphIds.has(sourceId)) {\n const mSg = subgraphById.get(sourceId)\n const resolved = mSg\n ? resolveSubgraphEndpoint(mSg, parsed, 'exit')\n : undefined\n if (resolved) sourceId = resolved\n }\n if (subgraphIds.has(targetId)) {\n const mSg = subgraphById.get(targetId)\n const resolved = mSg\n ? resolveSubgraphEndpoint(mSg, parsed, 'entry')\n : undefined\n if (resolved) targetId = resolved\n }\n\n const from = nodeMap.get(sourceId)\n const to = nodeMap.get(targetId)\n if (!from || !to) continue\n\n edges.push({\n from,\n to,\n text: mEdge.label ? stripFormattingTags(mEdge.label) : '',\n path: [],\n labelLine: [],\n startDir: { x: 0, y: 0 },\n endDir: { x: 0, y: 0 },\n style: mEdge.style,\n hasArrowStart: mEdge.hasArrowStart,\n hasArrowEnd: mEdge.hasArrowEnd,\n ...(mEdge.startMarker !== undefined\n ? { startMarker: mEdge.startMarker }\n : {}),\n ...(mEdge.endMarker !== undefined ? { endMarker: mEdge.endMarker } : {}),\n })\n }\n\n // Convert subgraphs recursively\n const subgraphs: AsciiSubgraph[] = []\n for (const mSg of parsed.subgraphs) {\n convertSubgraph(mSg, null, nodeMap, subgraphs, parsed)\n }\n\n // Deduplicate subgraph node membership to match Go parser behavior.\n // In Go, a node belongs only to the subgraph where it was FIRST DEFINED.\n // The TS parser adds referenced nodes to all subgraphs they appear in,\n // which causes incorrect bounding boxes when nodes span subgraph boundaries.\n deduplicateSubgraphNodes(parsed.subgraphs, subgraphs, nodeMap)\n\n /*\n * `classDef default` is Mermaid's implicit base style for every node, not\n * just for nodes that name it. Apply it to all nodes first so an explicit\n * assignment below can override it property by property.\n */\n const defaultDef = parsed.classDefs.get('default')\n if (defaultDef) {\n for (const node of nodeMap.values()) {\n node.styleClassName = 'default'\n node.styleClass = { name: 'default', styles: defaultDef }\n }\n }\n\n // Apply class definitions\n for (const [nodeId, className] of parsed.classAssignments) {\n const node = nodeMap.get(nodeId)\n const classDef = parsed.classDefs.get(className)\n if (node && classDef) {\n node.styleClassName = className\n node.styleClass = {\n name: className,\n styles: defaultDef ? { ...defaultDef, ...classDef } : classDef,\n }\n }\n }\n\n return {\n nodes,\n edges,\n canvas: mkCanvas(0, 0),\n roleCanvas: mkRoleCanvas(0, 0),\n grid: createGrid(),\n columnWidth: new Map(),\n rowHeight: new Map(),\n subgraphs,\n config,\n offsetX: 0,\n offsetY: 0,\n bundles: [], // Populated by analyzeEdgeBundles() during layout\n }\n}\n\n/**\n * Recursively index a subgraph (and its nested children) by id, for\n * resolving subgraph-id edge endpoints — see issue #65 and the comment in\n * `convertToAsciiGraph`.\n */\nfunction indexSubgraph(\n mSg: MermaidSubgraph,\n subgraphIds: Set<string>,\n subgraphById: Map<string, MermaidSubgraph>,\n): void {\n subgraphIds.add(mSg.id)\n subgraphById.set(mSg.id, mSg)\n for (const child of mSg.children) {\n indexSubgraph(child, subgraphIds, subgraphById)\n }\n}\n\n/** Recursively collect every node id that's a member of `mSg` or its nested children. */\nfunction collectAllMemberNodeIds(mSg: MermaidSubgraph, out: Set<string>): void {\n for (const id of mSg.nodeIds) out.add(id)\n for (const child of mSg.children) collectAllMemberNodeIds(child, out)\n}\n\n/**\n * Real mermaid.js ignores a subgraph's own `direction` override once any of\n * its member nodes (including nested descendants) has an edge to something\n * outside the subgraph — the subgraph then inherits the parent graph's\n * direction instead. Per the docs: \"If any of a subgraph's nodes are linked\n * to the outside, subgraph direction will be ignored. Instead the subgraph\n * will inherit the direction of the parent graph.\"\n * https://mermaid.js.org/syntax/flowchart.html\n *\n * Verified against real mermaid.js output for issue #445 (`graph TD` +\n * `direction LR` subgraph with `E --> A` / `D --> F` edges crossing the\n * boundary): all member nodes render at the same x-coordinate with\n * increasing y, i.e. stacked top-down per the outer `TD` direction, despite\n * the subgraph's own `direction LR`.\n */\nfunction subgraphDirectionIsHonored(\n mSg: MermaidSubgraph,\n parsed: MermaidGraph,\n): boolean {\n const memberIds = new Set<string>()\n collectAllMemberNodeIds(mSg, memberIds)\n if (memberIds.size === 0) return true\n\n const isInside = (id: string): boolean => memberIds.has(id) || id === mSg.id\n\n return !parsed.edges.some((e) => isInside(e.source) !== isInside(e.target))\n}\n\n/**\n * Pick a real member node to stand in for a subgraph when it's used as an\n * edge endpoint (e.g. `ONE --> TWO` where ONE/TWO are subgraph ids — issue\n * #65). The ASCII grid can only route edges between real nodes, so an edge\n * \"into\" the subgraph is anchored to the subgraph's entry (root) node — a\n * member with no incoming edge from another member of the same subgraph —\n * and an edge \"out of\" the subgraph is anchored to its exit (leaf) node —\n * a member with no outgoing edge to another member. That mirrors how the\n * edge would visually enter/exit the subgraph's frame, reusing the\n * existing (already-working) cross-subgraph edge-routing path instead of\n * inventing a new \"compound node\" concept in the grid layout.\n */\nfunction resolveSubgraphEndpoint(\n mSg: MermaidSubgraph,\n parsed: MermaidGraph,\n end: 'entry' | 'exit',\n): string | undefined {\n const memberIds = new Set<string>()\n collectAllMemberNodeIds(mSg, memberIds)\n if (memberIds.size === 0) return undefined\n\n // Preserve overall node declaration order for deterministic selection.\n const orderedIds = [...parsed.nodes.keys()].filter((id) => memberIds.has(id))\n if (orderedIds.length === 0) return undefined\n\n const candidates = orderedIds.filter((id) =>\n end === 'entry'\n ? !parsed.edges.some((e) => e.target === id && memberIds.has(e.source))\n : !parsed.edges.some((e) => e.source === id && memberIds.has(e.target)),\n )\n\n const pool = candidates.length > 0 ? candidates : orderedIds\n return end === 'entry' ? pool[0] : pool[pool.length - 1]\n}\n\n/**\n * Recursively convert a MermaidSubgraph to AsciiSubgraph.\n * Flattens the tree into the subgraphs array while maintaining parent/child references.\n * This matches the Go implementation where all subgraphs are in a flat list\n * but linked via parent/children pointers.\n */\nfunction convertSubgraph(\n mSg: MermaidSubgraph,\n parent: AsciiSubgraph | null,\n nodeMap: Map<string, AsciiNode>,\n allSubgraphs: AsciiSubgraph[],\n parsed: MermaidGraph,\n): AsciiSubgraph {\n // Normalize subgraph direction: BT→TD, RL→LR (same as root graph normalization).\n // A subgraph with an edge crossing its own boundary loses its direction\n // override in real mermaid.js (see subgraphDirectionIsHonored) — skip\n // normalizing it in that case so it falls through to the parent's\n // direction like everything else.\n let normalizedDirection: 'LR' | 'TD' | undefined\n if (mSg.direction && subgraphDirectionIsHonored(mSg, parsed)) {\n normalizedDirection =\n mSg.direction === 'LR' || mSg.direction === 'RL' ? 'LR' : 'TD'\n }\n\n const sg: AsciiSubgraph = {\n name: mSg.label,\n nodes: [],\n parent,\n children: [],\n minX: 0,\n minY: 0,\n maxX: 0,\n maxY: 0,\n direction: normalizedDirection,\n }\n\n // Resolve node references\n for (const nodeId of mSg.nodeIds) {\n const node = nodeMap.get(nodeId)\n if (node) sg.nodes.push(node)\n }\n\n allSubgraphs.push(sg)\n\n // Recurse into children\n for (const childMSg of mSg.children) {\n const child = convertSubgraph(childMSg, sg, nodeMap, allSubgraphs, parsed)\n sg.children.push(child)\n\n // Child nodes are also part of parent subgraphs (Go behavior).\n // The Go parser adds nodes to ALL subgraphs in the stack, so a nested\n // node belongs to both the inner and outer subgraph.\n for (const childNode of child.nodes) {\n if (!sg.nodes.includes(childNode)) {\n sg.nodes.push(childNode)\n }\n }\n }\n\n return sg\n}\n\n/**\n * Deduplicate subgraph node membership to match Go parser behavior.\n *\n * The Go parser only adds a node to the subgraph that was active when the node\n * was FIRST CREATED. If a node is later referenced inside a different subgraph,\n * it is NOT added to that subgraph. The TS parser is more permissive — it adds\n * referenced nodes to whichever subgraph they appear in.\n *\n * This function fixes the discrepancy by:\n * 1. Walking the edges to determine which nodes were first created inside each subgraph\n * 2. Removing nodes from subgraphs where they weren't first created\n */\nfunction deduplicateSubgraphNodes(\n mermaidSubgraphs: MermaidSubgraph[],\n asciiSubgraphs: AsciiSubgraph[],\n nodeMap: Map<string, AsciiNode>,\n): void {\n // Build a map from MermaidSubgraph to its corresponding AsciiSubgraph.\n // The ordering matches since we convert them in the same order.\n const sgMap = new Map<MermaidSubgraph, AsciiSubgraph>()\n buildSgMap(mermaidSubgraphs, asciiSubgraphs, sgMap)\n\n // Determine which subgraph each node was \"first defined\" in.\n // A node is first defined in the subgraph where it first appears as a NEW node\n // in the ordered edge/node list. We approximate this by checking the global\n // node insertion order against subgraph membership.\n const nodeOwner = new Map<string, AsciiSubgraph>() // nodeId → owning subgraph\n\n // Walk all mermaid subgraphs in document order. For each subgraph,\n // claim nodes that haven't been claimed yet by any previous subgraph.\n function claimNodes(mSg: MermaidSubgraph): void {\n const asciiSg = sgMap.get(mSg)\n if (!asciiSg) return\n\n // Recurse into children first (they appear before parent in the Go parser stack,\n // but nodes defined in children are added to parent too — this is handled by\n // the convertSubgraph function which propagates child nodes to parents).\n // For dedup, we process children first so their claims propagate up correctly.\n for (const child of mSg.children) {\n claimNodes(child)\n }\n\n // Claim unclaimed nodes in this subgraph\n for (const nodeId of mSg.nodeIds) {\n if (!nodeOwner.has(nodeId)) {\n nodeOwner.set(nodeId, asciiSg)\n }\n }\n }\n\n for (const mSg of mermaidSubgraphs) {\n claimNodes(mSg)\n }\n\n // Now remove nodes from subgraphs that don't own them.\n // A node should remain in: its owner subgraph + all ancestors of the owner.\n for (const asciiSg of asciiSubgraphs) {\n asciiSg.nodes = asciiSg.nodes.filter((node) => {\n // Find this node's ID in the nodeMap\n let nodeId: string | undefined\n for (const [id, n] of nodeMap) {\n if (n === node) {\n nodeId = id\n break\n }\n }\n if (!nodeId) return false\n\n const owner = nodeOwner.get(nodeId)\n if (!owner) return true // not in any subgraph claim — keep as-is\n\n // Keep the node if this subgraph is the owner or an ancestor of the owner\n return isAncestorOrSelf(asciiSg, owner)\n })\n }\n}\n\n/** Check if `candidate` is the same as or an ancestor of `target`. */\nfunction isAncestorOrSelf(\n candidate: AsciiSubgraph,\n target: AsciiSubgraph,\n): boolean {\n let current: AsciiSubgraph | null = target\n while (current !== null) {\n if (current === candidate) return true\n current = current.parent\n }\n return false\n}\n\n/** Build a mapping from MermaidSubgraph → AsciiSubgraph (matching by position). */\nfunction buildSgMap(\n mSgs: MermaidSubgraph[],\n aSgs: AsciiSubgraph[],\n result: Map<MermaidSubgraph, AsciiSubgraph>,\n): void {\n // The asciiSubgraphs array is flat (all subgraphs including nested ones),\n // while mermaidSubgraphs is hierarchical. We need to flatten the mermaid tree\n // in the same order the converter processes them (pre-order DFS).\n const flatMermaid: MermaidSubgraph[] = []\n function flatten(sgs: MermaidSubgraph[]): void {\n for (const sg of sgs) {\n flatMermaid.push(sg)\n flatten(sg.children)\n }\n }\n flatten(mSgs)\n\n for (let i = 0; i < flatMermaid.length && i < aSgs.length; i++) {\n const mSg = flatMermaid[i]\n const aSg = aSgs[i]\n if (!mSg || !aSg) continue\n result.set(mSg, aSg)\n }\n}\n","// ============================================================================\n// ASCII renderer — Flowchart / state diagram\n//\n// Renders flowcharts (graph TD / flowchart LR) and state diagrams\n// (stateDiagram-v2) to ASCII/Unicode box-drawing art via the shared\n// grid-based layout + A* pathfinding pipeline (converter -> grid -> draw).\n//\n// Extracted from the inline sequence that used to live directly in\n// `renderMermaidASCII`'s switch/fallback (src/ascii/index.ts) so flowchart\n// can be registered in `asciiRegistry` (src/ascii/registry.ts) alongside\n// every other diagram type — see\n// docs/decisions/diagram-type-registry-partial.md and issue #745. A pure,\n// behavior-preserving extraction: same functions, same call order, same\n// arguments.\n// ============================================================================\n\nimport { parseMermaid } from '../../../src/parser.ts'\nimport { withDirectionOverride } from '@zombie-mermaid/core'\nimport type { Direction } from '@zombie-mermaid/core'\nimport { convertToAsciiGraph } from './converter.ts'\nimport { createMapping } from './grid.ts'\nimport { drawGraph } from './draw.ts'\nimport {\n canvasToString,\n flipCanvasVertically,\n flipRoleCanvasVertically,\n} from './canvas.ts'\nimport { buildNodeLinkCanvas, flipLinkCanvasVertically } from './hyperlinks.ts'\nimport type { AsciiConfig, AsciiTheme, ColorMode } from './types.ts'\n\n/**\n * Flowchart-only ASCII extras: `direction` (override the parsed top-level\n * direction, same semantics as `RenderOptions.direction` for SVG — see\n * issue #276) and `hyperlinks` (OSC 8 terminal links). No other registered\n * ASCII type reads `direction`; `hyperlinks` is shared with `class` (see\n * `AsciiRenderExtras` in ./registry.ts).\n */\nexport interface FlowchartAsciiExtras {\n direction?: Direction\n hyperlinks?: boolean\n}\n\n/**\n * Render a flowchart or state diagram to ASCII/Unicode text art.\n *\n * Matches every other diagram type's ASCII entry-point shape\n * (`text, config, colorMode, theme, extras`) so it can slot into\n * `asciiRegistry` — see ./registry.ts.\n */\nexport function renderFlowchartAscii(\n text: string,\n config: AsciiConfig,\n colorMode: ColorMode,\n theme: AsciiTheme,\n extras: FlowchartAsciiExtras = {},\n): string {\n // `extras.direction` replaces the parsed top-level direction before\n // layout; see packages/core/src/direction-override.ts.\n const parsed = withDirectionOverride(parseMermaid(text), extras.direction)\n\n // Normalize direction for grid layout.\n // BT is laid out as TD then flipped vertically after drawing.\n // RL is treated as LR (full RL support not yet implemented).\n if (parsed.direction === 'LR' || parsed.direction === 'RL') {\n config.graphDirection = 'LR'\n } else {\n config.graphDirection = 'TD'\n }\n\n const graph = convertToAsciiGraph(parsed, config)\n createMapping(graph)\n drawGraph(graph)\n\n // Opt-in OSC 8 hyperlinks: mark each `click`-linked node's label cells\n // now, from the drawn node positions, before any flip below moves them.\n const linkCanvas = extras.hyperlinks\n ? buildNodeLinkCanvas(graph, parsed.interactions)\n : undefined\n\n // BT: flip the finished canvas vertically so the flow runs bottom→top.\n // The grid layout ran as TD; flipping + character remapping produces BT.\n if (parsed.direction === 'BT') {\n flipCanvasVertically(graph.canvas)\n flipRoleCanvasVertically(graph.roleCanvas)\n if (linkCanvas) flipLinkCanvasVertically(linkCanvas)\n }\n\n return canvasToString(graph.canvas, {\n roleCanvas: graph.roleCanvas,\n colorMode,\n theme,\n linkCanvas,\n })\n}\n","// ============================================================================\n// zombie-mermaid — ASCII half of the per-diagram-type registry (issue #533)\n//\n// `src/diagram-registry.ts` used to hold both halves of each registered\n// diagram type: `renderSvg` (plus `parse`/`layoutForSvg`) AND `renderAscii`.\n// That made the SVG-side module import `renderXYChartAscii`/`renderErAscii`\n// out of `src/ascii/`, while `src/ascii/index.ts` imported `diagramRegistry`\n// back out of `src/`. A cycle:\n//\n// src/ascii/index.ts -> src/diagram-registry.ts -> src/ascii/xychart.ts\n// -> src/ascii/er-diagram.ts\n//\n// Benign while both halves live in one package; fatal to the monorepo split\n// scoped in docs/decisions/monorepo-conversion-scoping.md (#416/#620), where\n// `src/ascii/**` becomes `@zombie-mermaid/ascii-renderer` and the SVG side\n// stays behind — a package-level import cycle, not just a module-level one.\n// It also had a measurable cost today: `dist/ascii.js` carried\n// `import \"elkjs/lib/elk.bundled.js\"` purely because the registry dragged\n// `src/er/layout.ts` -> `src/elk-instance.ts` into the ASCII entry's module\n// graph, which is precisely what the `./ascii` subpath export (#300) exists\n// to avoid.\n//\n// Splitting the table by renderer removes the back-edge: the ASCII side owns\n// its own dispatch here, and `src/diagram-registry.ts` keeps the SVG side and\n// no longer names anything under `src/ascii/`. Both directions are now\n// one-way, and the set of registered types stays a single decision per\n// renderer rather than a `switch` re-listed at each front door — the point of\n// #533. Adding a type to one renderer's table without the other is\n// expressible in principle, though in practice 'xychart'/'er'/'sequence'/\n// 'class'/'flowchart' now move together on both sides.\n// ============================================================================\n\nimport type { DiagramType, Direction } from '@zombie-mermaid/core'\nimport type { AsciiConfig, AsciiTheme, ColorMode } from './types.ts'\nimport { renderXYChartAscii } from './xychart.ts'\nimport { renderErAscii } from './er-diagram.ts'\nimport { renderSequenceAscii } from './sequence.ts'\nimport { renderClassAscii } from './class-diagram.ts'\nimport { renderFlowchartAscii } from './flowchart.ts'\n\n/**\n * Small, closed set of ASCII-only extras not every type needs — `class`\n * reads `hyperlinks` (see `ClassAsciiOptions` in\n * src/ascii/class-diagram.ts), `flowchart` reads both `direction` and\n * `hyperlinks` (see `FlowchartAsciiExtras` in src/ascii/flowchart.ts). Kept\n * as its own type rather than reusing `AsciiRenderOptions` from\n * src/ascii/index.ts so this module stays a leaf of the ASCII tree: index.ts\n * imports it, never the other way round.\n */\nexport interface AsciiRenderExtras {\n hyperlinks?: boolean\n direction?: Direction\n}\n\n/**\n * One registered diagram type's ASCII entry point. Every ASCII renderer\n * reruns its own parse + grid layout + draw from raw text — there is no\n * shared positioned model to hand it, unlike the SVG side's\n * `parse`/`layoutForSvg` split (see `DiagramModule` in\n * src/diagram-registry.ts for why a shared layout step would be fiction\n * here).\n */\nexport type AsciiRenderer = (\n text: string,\n config: AsciiConfig,\n colorMode: ColorMode,\n theme: AsciiTheme,\n extras: AsciiRenderExtras,\n) => string\n\n/**\n * Every diagram type `renderMermaidASCII` supports, dispatched through this\n * table — see that function in src/ascii/index.ts, which has no fallback\n * switch left now that 'flowchart' (the last holdout — see\n * docs/decisions/diagram-type-registry-partial.md) is registered here too.\n */\nexport const asciiRegistry: Record<DiagramType, AsciiRenderer> = {\n xychart: (text, config, colorMode, theme) =>\n renderXYChartAscii(text, config, colorMode, theme),\n er: (text, config, colorMode, theme) =>\n renderErAscii(text, config, colorMode, theme),\n sequence: (text, config, colorMode, theme) =>\n renderSequenceAscii(text, config, colorMode, theme),\n class: (text, config, colorMode, theme, extras) =>\n renderClassAscii(text, config, colorMode, theme, extras),\n flowchart: (text, config, colorMode, theme, extras) =>\n renderFlowchartAscii(text, config, colorMode, theme, extras),\n}\n","// ============================================================================\n// ASCII renderer — debug coordinate overlay (`--coords`)\n//\n// Annotates a finished, rendered ASCII/Unicode string with spreadsheet-style\n// row/column indices — numbered column headers across the top, row numbers\n// down the left side — to help debug layout spacing (padding, box sizes,\n// edge routing) without having to count characters by hand.\n//\n// This deliberately overlays the *character* grid of the final rendered\n// output, not the internal logical grid used by grid.ts (AsciiGraph's\n// node-placement lattice, where each node occupies a 3x3 block on a 4-unit\n// step). The internal grid has no 1:1 relationship with rendered character\n// columns/rows (column widths and row heights vary per node), so annotating\n// it directly wouldn't produce a marker a user looking at the printed\n// diagram could actually line up against. The character grid is what's\n// visible on screen, so its indices are what's useful to overlay.\n// ============================================================================\n\nimport { stripOsc8 } from './hyperlinks.ts'\n\n/**\n * Add a column-index ruler (two rows: tens digit, ones digit) above the\n * diagram and a row-index gutter to the left of each line.\n *\n * Purely a string transform over the already-rendered output — safe to\n * apply regardless of diagram type, color mode, or Unicode/ASCII mode.\n */\n// Matches SGR color escape sequences (`\\x1b[...m`) produced by ansi.ts's\n// ansi16/ansi256/truecolor modes. Stripped — along with any OSC 8\n// hyperlink sequences (hyperlinks.ts) — only for width MEASUREMENT below;\n// the original lines (with codes intact) are still what gets printed.\nconst ANSI_ESCAPE = /\\x1b\\[[0-9;]*m/g\n\nexport function addCoordsOverlay(rendered: string): string {\n const lines = rendered.split('\\n')\n const width = lines.reduce(\n (max, line) =>\n Math.max(max, stripOsc8(line.replace(ANSI_ESCAPE, '')).length),\n 0,\n )\n const rowGutterWidth = String(Math.max(0, lines.length - 1)).length\n\n const gutter = ' '.repeat(rowGutterWidth + 1)\n const tensRow =\n gutter +\n Array.from({ length: width }, (_, x) =>\n String(Math.floor(x / 10) % 10),\n ).join('')\n const onesRow =\n gutter + Array.from({ length: width }, (_, x) => String(x % 10)).join('')\n\n const bodyLines = lines.map((line, y) => {\n const rowLabel = String(y).padStart(rowGutterWidth, ' ')\n return `${rowLabel} ${line}`\n })\n\n return [tensRow, onesRow, ...bodyLines].join('\\n')\n}\n","// ============================================================================\n// zombie-mermaid — ASCII renderer public API\n//\n// Renders Mermaid diagrams to ASCII or Unicode box-drawing art.\n// No external dependencies — pure TypeScript.\n//\n// Supported diagram types:\n// - Flowcharts (graph TD / flowchart LR) — grid-based layout with A* pathfinding\n// - State diagrams (stateDiagram-v2) — same pipeline as flowcharts\n// - Sequence diagrams (sequenceDiagram) — column-based timeline layout\n// - Class diagrams (classDiagram) — level-based UML layout\n// - ER diagrams (erDiagram) — grid layout with crow's foot notation\n//\n// Usage:\n// import { renderMermaidASCII } from 'zombie-mermaid'\n// const ascii = renderMermaidASCII('graph LR\\n A --> B')\n// ============================================================================\n\nimport { detectDiagramType } from '@zombie-mermaid/core'\nimport type { Direction, DiagramType } from '@zombie-mermaid/core'\nimport { asciiRegistry } from './registry.ts'\nimport { addCoordsOverlay } from './coords.ts'\nimport {\n detectColorMode,\n DEFAULT_ASCII_THEME,\n diagramColorsToAsciiTheme,\n} from './ansi.ts'\nimport type { AsciiConfig, AsciiTheme, ColorMode } from './types.ts'\nimport {\n DEFAULT_PADDING_X,\n DEFAULT_PADDING_Y,\n DEFAULT_BOX_BORDER_PADDING,\n} from './types.ts'\n\n// Re-export types for external use\nexport type { AsciiTheme, ColorMode }\nexport { DEFAULT_ASCII_THEME, detectColorMode, diagramColorsToAsciiTheme }\n\nexport interface AsciiRenderOptions {\n /** true = ASCII chars (+,-,|,>), false = Unicode box-drawing (┌,─,│,►). Default: false */\n useAscii?: boolean\n /** Horizontal spacing between nodes. Default: 5 */\n paddingX?: number\n /** Vertical spacing between nodes. Default: 5 */\n paddingY?: number\n /** Padding inside node boxes. Default: 1 */\n boxBorderPadding?: number\n /**\n * Color mode for output.\n * - 'none': No colors (plain text)\n * - 'auto': Auto-detect (terminal ANSI capabilities, or HTML in browsers)\n * - 'ansi16': 16-color ANSI\n * - 'ansi256': 256-color xterm\n * - 'truecolor': 24-bit RGB\n * - 'html': HTML <span> tags with inline color styles (for browser rendering)\n * Default: 'auto'\n */\n colorMode?: ColorMode | 'auto'\n /** Theme colors for ASCII output. Uses default theme if not provided. */\n theme?: Partial<AsciiTheme>\n /**\n * Overlay spreadsheet-style row/column indices on the rendered output, to\n * help debug layout spacing. Off by default.\n */\n showCoords?: boolean\n /**\n * Force the diagram's layout direction, overriding the one its source\n * declares (a flowchart's `graph LR` header, or a state diagram's\n * top-level `direction LR` line). Applied after parsing and before\n * layout — the source text is never rewritten. Replaces only the\n * top-level direction: a nested subgraph's or composite state's own\n * `direction` line still applies on top of it, exactly as it does on top\n * of the diagram's own header.\n *\n * Flowchart and state diagrams only in ASCII output. The ASCII ER layout\n * has no direction concept (it already ignores a source `direction`\n * line), and sequence/class/XY-chart diagrams have none to override, so\n * all of those ignore this option (no error; output is identical with or\n * without it). Same semantics as `RenderOptions.direction` for SVG. See\n * issue #276.\n */\n direction?: Direction\n /**\n * Emit OSC 8 terminal hyperlinks: each node whose `click` directive\n * declared an http/https/mailto/relative href gets its label wrapped in\n * an `ESC ] 8 ; ; <url> ESC \\` … `ESC ] 8 ; ; ESC \\` pair, which\n * terminals that support OSC 8 (iTerm2, WezTerm, kitty, Windows\n * Terminal, VTE-based terminals) render as a clickable link. The\n * sequences are zero-width and never affect layout; `click ... call fn()`\n * bindings emit nothing. Off by default — not every terminal or pager\n * handles OSC 8 gracefully (`less` needs `-R`), and no capability\n * detection is attempted here; the caller decides. Ignored when\n * `colorMode` is 'html'.\n */\n hyperlinks?: boolean\n}\n\n/**\n * Render Mermaid diagram text to an ASCII/Unicode string.\n *\n * Synchronous — no async layout engine needed (unlike the SVG renderer).\n * Auto-detects diagram type from the header line and dispatches to\n * the appropriate renderer.\n *\n * @param text - Mermaid source text (any supported diagram type)\n * @param options - Rendering options\n * @returns Multi-line ASCII/Unicode string\n *\n * @example\n * ```ts\n * const result = renderMermaidASCII(`\n * graph LR\n * A --> B --> C\n * `, { useAscii: true })\n *\n * // Output:\n * // +---+ +---+ +---+\n * // | | | | | |\n * // | A |---->| B |---->| C |\n * // | | | | | |\n * // +---+ +---+ +---+\n * ```\n */\nexport function renderMermaidASCII(\n text: string,\n options: AsciiRenderOptions = {},\n): string {\n const config: AsciiConfig = {\n useAscii: options.useAscii ?? false,\n paddingX: options.paddingX ?? DEFAULT_PADDING_X,\n paddingY: options.paddingY ?? DEFAULT_PADDING_Y,\n boxBorderPadding: options.boxBorderPadding ?? DEFAULT_BOX_BORDER_PADDING,\n graphDirection: 'TD', // default; renderFlowchartAscii overrides this for flowcharts/state diagrams\n }\n\n // Resolve color mode ('auto' or unset → detect environment, otherwise use specified mode)\n const colorMode: ColorMode =\n options.colorMode === 'auto' || options.colorMode === undefined\n ? detectColorMode()\n : options.colorMode\n\n // Merge user theme with defaults\n const theme: AsciiTheme = { ...DEFAULT_ASCII_THEME, ...options.theme }\n\n const diagramType: DiagramType = detectDiagramType(text)\n\n // Registry dispatch (see src/ascii/registry.ts — issue #533 / #745):\n // every diagram type, including 'flowchart', is registered there and\n // dispatches to the exact same renderer calls each type's original\n // switch case (or, for 'flowchart', inline sequence) used to make.\n // `extras` carries every ASCII-only per-type option: `hyperlinks` (read\n // by 'class' and 'flowchart') and `direction` (read by 'flowchart'\n // only); both are ignored by every other registered type.\n const result = asciiRegistry[diagramType](text, config, colorMode, theme, {\n hyperlinks: options.hyperlinks ?? false,\n direction: options.direction,\n })\n\n return options.showCoords ? addCoordsOverlay(result) : result\n}\n\n/** @deprecated Use `renderMermaidASCII` */\nexport const renderMermaidAscii = renderMermaidASCII\n"],"mappings":"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"}
|