@diagrammo/dgmo 0.2.27 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/.claude/skills/dgmo-chart/SKILL.md +107 -0
  2. package/.claude/skills/dgmo-flowchart/SKILL.md +61 -0
  3. package/.claude/skills/dgmo-generate/SKILL.md +58 -0
  4. package/.claude/skills/dgmo-sequence/SKILL.md +83 -0
  5. package/.cursorrules +117 -0
  6. package/.github/copilot-instructions.md +117 -0
  7. package/.windsurfrules +117 -0
  8. package/README.md +10 -3
  9. package/dist/cli.cjs +366 -918
  10. package/dist/index.cjs +581 -396
  11. package/dist/index.cjs.map +1 -1
  12. package/dist/index.d.cts +39 -24
  13. package/dist/index.d.ts +39 -24
  14. package/dist/index.js +578 -395
  15. package/dist/index.js.map +1 -1
  16. package/docs/ai-integration.md +125 -0
  17. package/docs/language-reference.md +786 -0
  18. package/package.json +15 -8
  19. package/src/c4/parser.ts +90 -74
  20. package/src/c4/renderer.ts +13 -12
  21. package/src/c4/types.ts +6 -4
  22. package/src/chart.ts +3 -2
  23. package/src/class/layout.ts +17 -12
  24. package/src/class/parser.ts +22 -52
  25. package/src/class/renderer.ts +44 -46
  26. package/src/class/types.ts +1 -1
  27. package/src/cli.ts +130 -19
  28. package/src/d3.ts +1 -1
  29. package/src/dgmo-mermaid.ts +1 -1
  30. package/src/dgmo-router.ts +1 -1
  31. package/src/echarts.ts +33 -13
  32. package/src/er/parser.ts +34 -43
  33. package/src/er/types.ts +1 -1
  34. package/src/graph/flowchart-parser.ts +2 -25
  35. package/src/graph/types.ts +1 -1
  36. package/src/index.ts +5 -0
  37. package/src/initiative-status/parser.ts +36 -7
  38. package/src/initiative-status/types.ts +1 -1
  39. package/src/kanban/parser.ts +32 -53
  40. package/src/kanban/renderer.ts +9 -8
  41. package/src/kanban/types.ts +6 -14
  42. package/src/org/parser.ts +47 -87
  43. package/src/org/resolver.ts +11 -12
  44. package/src/sequence/parser.ts +97 -15
  45. package/src/sequence/renderer.ts +62 -69
  46. package/src/utils/arrows.ts +75 -0
  47. package/src/utils/inline-markdown.ts +75 -0
  48. package/src/utils/parsing.ts +67 -0
  49. package/src/utils/tag-groups.ts +76 -0
package/dist/index.cjs CHANGED
@@ -1549,6 +1549,96 @@ var init_participant_inference = __esm({
1549
1549
  }
1550
1550
  });
1551
1551
 
1552
+ // src/utils/arrows.ts
1553
+ function parseArrow(line7) {
1554
+ const patterns = [
1555
+ { re: BIDI_SYNC_LABELED_RE, async: false, bidirectional: true },
1556
+ { re: BIDI_ASYNC_LABELED_RE, async: true, bidirectional: true },
1557
+ { re: SYNC_LABELED_RE, async: false, bidirectional: false },
1558
+ { re: ASYNC_LABELED_RE, async: true, bidirectional: false }
1559
+ ];
1560
+ for (const { re, async: isAsync, bidirectional } of patterns) {
1561
+ const m = line7.match(re);
1562
+ if (!m) continue;
1563
+ const label = m[2].trim();
1564
+ if (!label) return null;
1565
+ for (const arrow of ARROW_CHARS) {
1566
+ if (label.includes(arrow)) {
1567
+ return {
1568
+ error: "Arrow characters (->, ~>) are not allowed inside labels"
1569
+ };
1570
+ }
1571
+ }
1572
+ return {
1573
+ from: m[1],
1574
+ to: m[3],
1575
+ label,
1576
+ async: isAsync,
1577
+ bidirectional
1578
+ };
1579
+ }
1580
+ return null;
1581
+ }
1582
+ var BIDI_SYNC_LABELED_RE, BIDI_ASYNC_LABELED_RE, SYNC_LABELED_RE, ASYNC_LABELED_RE, ARROW_CHARS;
1583
+ var init_arrows = __esm({
1584
+ "src/utils/arrows.ts"() {
1585
+ "use strict";
1586
+ BIDI_SYNC_LABELED_RE = /^(\S+)\s+<-(.+)->\s+(\S+)$/;
1587
+ BIDI_ASYNC_LABELED_RE = /^(\S+)\s+<~(.+)~>\s+(\S+)$/;
1588
+ SYNC_LABELED_RE = /^(\S+)\s+-(.+)->\s+(\S+)$/;
1589
+ ASYNC_LABELED_RE = /^(\S+)\s+~(.+)~>\s+(\S+)$/;
1590
+ ARROW_CHARS = ["->", "~>", "<->", "<~>"];
1591
+ }
1592
+ });
1593
+
1594
+ // src/utils/parsing.ts
1595
+ function measureIndent(line7) {
1596
+ let indent = 0;
1597
+ for (const ch of line7) {
1598
+ if (ch === " ") indent++;
1599
+ else if (ch === " ") indent += 4;
1600
+ else break;
1601
+ }
1602
+ return indent;
1603
+ }
1604
+ function extractColor(label, palette) {
1605
+ const m = label.match(COLOR_SUFFIX_RE);
1606
+ if (!m) return { label };
1607
+ const colorName = m[1].trim();
1608
+ return {
1609
+ label: label.substring(0, m.index).trim(),
1610
+ color: resolveColor(colorName, palette)
1611
+ };
1612
+ }
1613
+ function parsePipeMetadata(segments, aliasMap = /* @__PURE__ */ new Map()) {
1614
+ const metadata = {};
1615
+ for (let j = 1; j < segments.length; j++) {
1616
+ for (const part of segments[j].split(",")) {
1617
+ const trimmedPart = part.trim();
1618
+ if (!trimmedPart) continue;
1619
+ const colonIdx = trimmedPart.indexOf(":");
1620
+ if (colonIdx > 0) {
1621
+ const rawKey = trimmedPart.substring(0, colonIdx).trim().toLowerCase();
1622
+ const key = aliasMap.get(rawKey) ?? rawKey;
1623
+ const value = trimmedPart.substring(colonIdx + 1).trim();
1624
+ metadata[key] = value;
1625
+ }
1626
+ }
1627
+ }
1628
+ return metadata;
1629
+ }
1630
+ var COLOR_SUFFIX_RE, CHART_TYPE_RE, TITLE_RE, OPTION_RE;
1631
+ var init_parsing = __esm({
1632
+ "src/utils/parsing.ts"() {
1633
+ "use strict";
1634
+ init_colors();
1635
+ COLOR_SUFFIX_RE = /\(([^)]+)\)\s*$/;
1636
+ CHART_TYPE_RE = /^chart\s*:\s*(.+)/i;
1637
+ TITLE_RE = /^title\s*:\s*(.+)/i;
1638
+ OPTION_RE = /^([a-z][a-z0-9-]*)\s*:\s*(.+)$/i;
1639
+ }
1640
+ });
1641
+
1552
1642
  // src/sequence/parser.ts
1553
1643
  var parser_exports = {};
1554
1644
  __export(parser_exports, {
@@ -1590,15 +1680,6 @@ function parseReturnLabel(rawLabel) {
1590
1680
  }
1591
1681
  return { label: rawLabel };
1592
1682
  }
1593
- function measureIndent(line7) {
1594
- let indent = 0;
1595
- for (const ch of line7) {
1596
- if (ch === " ") indent++;
1597
- else if (ch === " ") indent += 4;
1598
- else break;
1599
- }
1600
- return indent;
1601
- }
1602
1683
  function parseSequenceDgmo(content) {
1603
1684
  const result = {
1604
1685
  title: null,
@@ -1820,6 +1901,86 @@ function parseSequenceDgmo(content) {
1820
1901
  pushError(lineNumber, "Use ~> for async messages: A ~> B: message");
1821
1902
  continue;
1822
1903
  }
1904
+ const labeledArrow = parseArrow(trimmed);
1905
+ if (labeledArrow && "error" in labeledArrow) {
1906
+ pushError(lineNumber, labeledArrow.error);
1907
+ continue;
1908
+ }
1909
+ if (labeledArrow) {
1910
+ contentStarted = true;
1911
+ const { from, to, label, async: isAsync2, bidirectional } = labeledArrow;
1912
+ lastMsgFrom = from;
1913
+ const msg = {
1914
+ from,
1915
+ to,
1916
+ label,
1917
+ returnLabel: void 0,
1918
+ lineNumber,
1919
+ ...isAsync2 ? { async: true } : {},
1920
+ ...bidirectional ? { bidirectional: true } : {}
1921
+ };
1922
+ result.messages.push(msg);
1923
+ currentContainer().push(msg);
1924
+ if (!result.participants.some((p) => p.id === from)) {
1925
+ result.participants.push({
1926
+ id: from,
1927
+ label: from,
1928
+ type: inferParticipantType(from),
1929
+ lineNumber
1930
+ });
1931
+ }
1932
+ if (!result.participants.some((p) => p.id === to)) {
1933
+ result.participants.push({
1934
+ id: to,
1935
+ label: to,
1936
+ type: inferParticipantType(to),
1937
+ lineNumber
1938
+ });
1939
+ }
1940
+ continue;
1941
+ }
1942
+ const bidiSyncMatch = trimmed.match(
1943
+ /^(\S+)\s*<->\s*([^\s:]+)\s*(?::\s*(.+))?$/
1944
+ );
1945
+ const bidiAsyncMatch = trimmed.match(
1946
+ /^(\S+)\s*<~>\s*([^\s:]+)\s*(?::\s*(.+))?$/
1947
+ );
1948
+ const bidiMatch = bidiSyncMatch || bidiAsyncMatch;
1949
+ if (bidiMatch) {
1950
+ contentStarted = true;
1951
+ const from = bidiMatch[1];
1952
+ const to = bidiMatch[2];
1953
+ lastMsgFrom = from;
1954
+ const rawLabel = bidiMatch[3]?.trim() || "";
1955
+ const isBidiAsync = !!bidiAsyncMatch;
1956
+ const msg = {
1957
+ from,
1958
+ to,
1959
+ label: rawLabel,
1960
+ lineNumber,
1961
+ bidirectional: true,
1962
+ ...isBidiAsync ? { async: true } : {}
1963
+ };
1964
+ result.messages.push(msg);
1965
+ currentContainer().push(msg);
1966
+ if (!result.participants.some((p) => p.id === from)) {
1967
+ result.participants.push({
1968
+ id: from,
1969
+ label: from,
1970
+ type: inferParticipantType(from),
1971
+ lineNumber
1972
+ });
1973
+ }
1974
+ if (!result.participants.some((p) => p.id === to)) {
1975
+ result.participants.push({
1976
+ id: to,
1977
+ label: to,
1978
+ type: inferParticipantType(to),
1979
+ lineNumber
1980
+ });
1981
+ }
1982
+ continue;
1983
+ }
1823
1984
  let isAsync = false;
1824
1985
  const asyncArrowMatch = trimmed.match(
1825
1986
  /^(\S+)\s*~>\s*([^\s:]+)\s*(?::\s*(.+))?$/
@@ -2057,6 +2218,8 @@ var init_parser = __esm({
2057
2218
  "use strict";
2058
2219
  init_participant_inference();
2059
2220
  init_diagnostics();
2221
+ init_arrows();
2222
+ init_parsing();
2060
2223
  VALID_PARTICIPANT_TYPES = /* @__PURE__ */ new Set([
2061
2224
  "service",
2062
2225
  "database",
@@ -2072,7 +2235,7 @@ var init_parser = __esm({
2072
2235
  POSITION_ONLY_PATTERN = /^(\S+)\s+position\s+(-?\d+)$/i;
2073
2236
  GROUP_HEADING_PATTERN = /^##\s+(.+?)(?:\(([^)]+)\))?\s*$/;
2074
2237
  SECTION_PATTERN = /^==\s+(.+?)(?:\s*==)?\s*$/;
2075
- ARROW_PATTERN = /\S+\s*(?:->|~>)\s*\S+/;
2238
+ ARROW_PATTERN = /\S+\s*(?:<->|<~>|->|~>|-\S+->|~\S+~>|<-\S+->|<~\S+~>)\s*\S+/;
2076
2239
  ARROW_RETURN_PATTERN = /^(.+?)\s*<-\s*(.+)$/;
2077
2240
  UML_RETURN_PATTERN = /^(\w+\([^)]*\))\s*:\s*(.+)$/;
2078
2241
  NOTE_SINGLE = /^note(?:\s+(right|left)\s+of\s+(\S+))?\s*:\s*(.+)$/i;
@@ -2086,27 +2249,9 @@ __export(flowchart_parser_exports, {
2086
2249
  looksLikeFlowchart: () => looksLikeFlowchart,
2087
2250
  parseFlowchart: () => parseFlowchart
2088
2251
  });
2089
- function measureIndent2(line7) {
2090
- let indent = 0;
2091
- for (const ch of line7) {
2092
- if (ch === " ") indent++;
2093
- else if (ch === " ") indent += 4;
2094
- else break;
2095
- }
2096
- return indent;
2097
- }
2098
2252
  function nodeId(shape, label) {
2099
2253
  return `${shape}:${label.toLowerCase().trim()}`;
2100
2254
  }
2101
- function extractColor(label, palette) {
2102
- const m = label.match(COLOR_SUFFIX_RE);
2103
- if (!m) return { label };
2104
- const colorName = m[1].trim();
2105
- return {
2106
- label: label.substring(0, m.index).trim(),
2107
- color: resolveColor(colorName, palette)
2108
- };
2109
- }
2110
2255
  function parseNodeRef(text, palette) {
2111
2256
  const t = text.trim();
2112
2257
  if (!t) return null;
@@ -2221,7 +2366,8 @@ function parseFlowchart(content, palette) {
2221
2366
  nodes: [],
2222
2367
  edges: [],
2223
2368
  options: {},
2224
- diagnostics: []
2369
+ diagnostics: [],
2370
+ error: null
2225
2371
  };
2226
2372
  const fail = (line7, message) => {
2227
2373
  const diag = makeDgmoError(line7, message);
@@ -2323,7 +2469,7 @@ function parseFlowchart(content, palette) {
2323
2469
  const raw = lines[i];
2324
2470
  const trimmed = raw.trim();
2325
2471
  const lineNumber = i + 1;
2326
- const indent = measureIndent2(raw);
2472
+ const indent = measureIndent(raw);
2327
2473
  if (!trimmed) continue;
2328
2474
  if (trimmed.startsWith("//")) continue;
2329
2475
  const groupMatch = trimmed.match(GROUP_HEADING_RE);
@@ -2400,13 +2546,13 @@ function looksLikeFlowchart(content) {
2400
2546
  /->[ \t]*[\[(<\/]/.test(content);
2401
2547
  return shapeNearArrow;
2402
2548
  }
2403
- var COLOR_SUFFIX_RE, GROUP_HEADING_RE;
2549
+ var GROUP_HEADING_RE;
2404
2550
  var init_flowchart_parser = __esm({
2405
2551
  "src/graph/flowchart-parser.ts"() {
2406
2552
  "use strict";
2407
2553
  init_colors();
2408
2554
  init_diagnostics();
2409
- COLOR_SUFFIX_RE = /\(([^)]+)\)\s*$/;
2555
+ init_parsing();
2410
2556
  GROUP_HEADING_RE = /^##\s+(.+?)(?:\(([^)]+)\))?\s*$/;
2411
2557
  }
2412
2558
  });
@@ -2417,15 +2563,6 @@ __export(parser_exports2, {
2417
2563
  looksLikeClassDiagram: () => looksLikeClassDiagram,
2418
2564
  parseClassDiagram: () => parseClassDiagram
2419
2565
  });
2420
- function measureIndent3(line7) {
2421
- let indent = 0;
2422
- for (const ch of line7) {
2423
- if (ch === " ") indent++;
2424
- else if (ch === " ") indent += 4;
2425
- else break;
2426
- }
2427
- return indent;
2428
- }
2429
2566
  function classId(name) {
2430
2567
  return name.toLowerCase().trim();
2431
2568
  }
@@ -2500,7 +2637,8 @@ function parseClassDiagram(content, palette) {
2500
2637
  classes: [],
2501
2638
  relationships: [],
2502
2639
  options: {},
2503
- diagnostics: []
2640
+ diagnostics: [],
2641
+ error: null
2504
2642
  };
2505
2643
  const fail = (line7, message) => {
2506
2644
  const diag = makeDgmoError(line7, message);
@@ -2529,7 +2667,7 @@ function parseClassDiagram(content, palette) {
2529
2667
  const raw = lines[i];
2530
2668
  const trimmed = raw.trim();
2531
2669
  const lineNumber = i + 1;
2532
- const indent = measureIndent3(raw);
2670
+ const indent = measureIndent(raw);
2533
2671
  if (!trimmed) {
2534
2672
  if (indent === 0) currentClass = null;
2535
2673
  continue;
@@ -2572,23 +2710,6 @@ function parseClassDiagram(content, palette) {
2572
2710
  }
2573
2711
  currentClass = null;
2574
2712
  contentStarted = true;
2575
- const relKeyword = trimmed.match(REL_KEYWORD_RE);
2576
- if (relKeyword) {
2577
- const sourceName = relKeyword[1];
2578
- const keyword = relKeyword[2].toLowerCase();
2579
- const targetName = relKeyword[3];
2580
- const label = relKeyword[4]?.trim();
2581
- getOrCreateClass(sourceName, lineNumber);
2582
- getOrCreateClass(targetName, lineNumber);
2583
- result.relationships.push({
2584
- source: classId(sourceName),
2585
- target: classId(targetName),
2586
- type: KEYWORD_TO_TYPE[keyword],
2587
- ...label && { label },
2588
- lineNumber
2589
- });
2590
- continue;
2591
- }
2592
2713
  const relArrow = trimmed.match(REL_ARROW_RE);
2593
2714
  if (relArrow) {
2594
2715
  const sourceName = relArrow[1];
@@ -2609,13 +2730,24 @@ function parseClassDiagram(content, palette) {
2609
2730
  const classDecl = trimmed.match(CLASS_DECL_RE);
2610
2731
  if (classDecl) {
2611
2732
  const name = classDecl[1];
2612
- const modifier = classDecl[2];
2613
- const colorName = classDecl[3]?.trim();
2733
+ const relKeyword = classDecl[2];
2734
+ const parentName = classDecl[3];
2735
+ const modifier = classDecl[4];
2736
+ const colorName = classDecl[5]?.trim();
2614
2737
  const color = colorName ? resolveColor(colorName, palette) : void 0;
2615
2738
  const node = getOrCreateClass(name, lineNumber);
2616
2739
  if (modifier) node.modifier = modifier;
2617
2740
  if (color) node.color = color;
2618
2741
  node.lineNumber = lineNumber;
2742
+ if (relKeyword && parentName) {
2743
+ getOrCreateClass(parentName, lineNumber);
2744
+ result.relationships.push({
2745
+ source: classId(name),
2746
+ target: classId(parentName),
2747
+ type: relKeyword,
2748
+ lineNumber
2749
+ });
2750
+ }
2619
2751
  currentClass = node;
2620
2752
  continue;
2621
2753
  }
@@ -2649,14 +2781,15 @@ function looksLikeClassDiagram(content) {
2649
2781
  const trimmed = line7.trim();
2650
2782
  if (!trimmed || trimmed.startsWith("//")) continue;
2651
2783
  if (/^(chart|title)\s*:/i.test(trimmed)) continue;
2652
- const indent = measureIndent3(line7);
2784
+ const indent = measureIndent(line7);
2653
2785
  if (indent === 0) {
2654
2786
  if (/^[A-Z][A-Za-z0-9_]*\s+\[(abstract|interface|enum)\]/i.test(trimmed)) {
2655
2787
  hasModifier = true;
2656
2788
  hasClassDecl = true;
2657
2789
  }
2658
- if (REL_KEYWORD_RE.test(trimmed)) {
2790
+ if (/^[A-Z][A-Za-z0-9_]*\s+(extends|implements)\s+[A-Z]/.test(trimmed)) {
2659
2791
  hasRelationship = true;
2792
+ hasClassDecl = true;
2660
2793
  }
2661
2794
  if (REL_ARROW_RE.test(trimmed)) {
2662
2795
  hasRelationship = true;
@@ -2674,26 +2807,19 @@ function looksLikeClassDiagram(content) {
2674
2807
  if (hasRelationship && hasClassDecl && hasIndentedMember) return true;
2675
2808
  return false;
2676
2809
  }
2677
- var CLASS_DECL_RE, REL_KEYWORD_RE, REL_ARROW_RE, VISIBILITY_RE, STATIC_SUFFIX_RE, METHOD_RE, FIELD_RE, KEYWORD_TO_TYPE, ARROW_TO_TYPE;
2810
+ var CLASS_DECL_RE, REL_ARROW_RE, VISIBILITY_RE, STATIC_SUFFIX_RE, METHOD_RE, FIELD_RE, ARROW_TO_TYPE;
2678
2811
  var init_parser2 = __esm({
2679
2812
  "src/class/parser.ts"() {
2680
2813
  "use strict";
2681
2814
  init_colors();
2682
2815
  init_diagnostics();
2683
- CLASS_DECL_RE = /^([A-Z][A-Za-z0-9_]*)(?:\s+\[(abstract|interface|enum)\])?(?:\s+\(([^)]+)\))?\s*$/;
2684
- REL_KEYWORD_RE = /^([A-Z][A-Za-z0-9_]*)\s+(extends|implements|contains|has|uses)\s+([A-Z][A-Za-z0-9_]*)(?:\s*:\s*(.+))?$/;
2816
+ init_parsing();
2817
+ CLASS_DECL_RE = /^([A-Z][A-Za-z0-9_]*)(?:\s+(extends|implements)\s+([A-Z][A-Za-z0-9_]*))?(?:\s+\[(abstract|interface|enum)\])?(?:\s+\(([^)]+)\))?\s*$/;
2685
2818
  REL_ARROW_RE = /^([A-Z][A-Za-z0-9_]*)\s+(--\|>|\.\.\|>|\*--|o--|\.\.\>|->)\s+([A-Z][A-Za-z0-9_]*)(?:\s*:\s*(.+))?$/;
2686
2819
  VISIBILITY_RE = /^([+\-#])\s*/;
2687
2820
  STATIC_SUFFIX_RE = /\{static\}\s*$/;
2688
2821
  METHOD_RE = /^(.+?)\(([^)]*)\)(?:\s*:\s*(.+))?$/;
2689
2822
  FIELD_RE = /^(.+?)\s*:\s*(.+)$/;
2690
- KEYWORD_TO_TYPE = {
2691
- extends: "extends",
2692
- implements: "implements",
2693
- contains: "composes",
2694
- has: "aggregates",
2695
- uses: "depends"
2696
- };
2697
2823
  ARROW_TO_TYPE = {
2698
2824
  "--|>": "extends",
2699
2825
  "..|>": "implements",
@@ -2711,22 +2837,14 @@ __export(parser_exports3, {
2711
2837
  looksLikeERDiagram: () => looksLikeERDiagram,
2712
2838
  parseERDiagram: () => parseERDiagram
2713
2839
  });
2714
- function measureIndent4(line7) {
2715
- let indent = 0;
2716
- for (const ch of line7) {
2717
- if (ch === " ") indent++;
2718
- else if (ch === " ") indent += 4;
2719
- else break;
2720
- }
2721
- return indent;
2722
- }
2723
2840
  function tableId(name) {
2724
2841
  return name.toLowerCase().trim();
2725
2842
  }
2726
2843
  function parseCardSide(token) {
2727
- return CARD_WORD[token.toLowerCase()] ?? null;
2844
+ if (token === "1" || token === "*" || token === "?") return token;
2845
+ return null;
2728
2846
  }
2729
- function parseRelationship(trimmed) {
2847
+ function parseRelationship(trimmed, lineNumber, pushError) {
2730
2848
  const sym = trimmed.match(REL_SYMBOLIC_RE);
2731
2849
  if (sym) {
2732
2850
  const fromCard = parseCardSide(sym[2]);
@@ -2741,19 +2859,15 @@ function parseRelationship(trimmed) {
2741
2859
  };
2742
2860
  }
2743
2861
  }
2744
- const kw = trimmed.match(REL_KEYWORD_RE2);
2862
+ const kw = trimmed.match(REL_KEYWORD_RE);
2745
2863
  if (kw) {
2746
- const fromCard = parseCardSide(kw[2]);
2747
- const toCard = parseCardSide(kw[3]);
2748
- if (fromCard && toCard) {
2749
- return {
2750
- source: kw[1],
2751
- target: kw[4],
2752
- from: fromCard,
2753
- to: toCard,
2754
- label: kw[5]?.trim()
2755
- };
2756
- }
2864
+ const fromSym = KEYWORD_TO_SYMBOL[kw[2].toLowerCase()] ?? kw[2];
2865
+ const toSym = KEYWORD_TO_SYMBOL[kw[3].toLowerCase()] ?? kw[3];
2866
+ pushError(
2867
+ lineNumber,
2868
+ `Use symbolic cardinality (1--*, ?--1, *--*) instead of "${kw[2]}-to-${kw[3]}". Example: ${kw[1]} ${fromSym}--${toSym} ${kw[4]}`
2869
+ );
2870
+ return null;
2757
2871
  }
2758
2872
  return null;
2759
2873
  }
@@ -2773,7 +2887,8 @@ function parseERDiagram(content, palette) {
2773
2887
  options: {},
2774
2888
  tables: [],
2775
2889
  relationships: [],
2776
- diagnostics: []
2890
+ diagnostics: [],
2891
+ error: null
2777
2892
  };
2778
2893
  const fail = (line7, message) => {
2779
2894
  const diag = makeDgmoError(line7, message);
@@ -2781,6 +2896,11 @@ function parseERDiagram(content, palette) {
2781
2896
  result.error = formatDgmoError(diag);
2782
2897
  return result;
2783
2898
  };
2899
+ const pushError = (line7, message) => {
2900
+ const diag = makeDgmoError(line7, message);
2901
+ result.diagnostics.push(diag);
2902
+ if (!result.error) result.error = formatDgmoError(diag);
2903
+ };
2784
2904
  const tableMap = /* @__PURE__ */ new Map();
2785
2905
  let currentTable = null;
2786
2906
  let contentStarted = false;
@@ -2802,7 +2922,7 @@ function parseERDiagram(content, palette) {
2802
2922
  const raw = lines[i];
2803
2923
  const trimmed = raw.trim();
2804
2924
  const lineNumber = i + 1;
2805
- const indent = measureIndent4(raw);
2925
+ const indent = measureIndent(raw);
2806
2926
  if (!trimmed) {
2807
2927
  if (indent === 0) currentTable = null;
2808
2928
  continue;
@@ -2851,7 +2971,7 @@ function parseERDiagram(content, palette) {
2851
2971
  }
2852
2972
  currentTable = null;
2853
2973
  contentStarted = true;
2854
- const rel = parseRelationship(trimmed);
2974
+ const rel = parseRelationship(trimmed, lineNumber, pushError);
2855
2975
  if (rel) {
2856
2976
  getOrCreateTable(rel.source, lineNumber);
2857
2977
  getOrCreateTable(rel.target, lineNumber);
@@ -2904,7 +3024,7 @@ function looksLikeERDiagram(content) {
2904
3024
  const trimmed = line7.trim();
2905
3025
  if (!trimmed || trimmed.startsWith("//")) continue;
2906
3026
  if (/^(chart|title|notation)\s*:/i.test(trimmed)) continue;
2907
- const indent = measureIndent4(line7);
3027
+ const indent = measureIndent(line7);
2908
3028
  if (indent > 0) {
2909
3029
  if (/\[(pk|fk)\]/i.test(trimmed)) {
2910
3030
  hasConstraint = true;
@@ -2913,7 +3033,7 @@ function looksLikeERDiagram(content) {
2913
3033
  if (TABLE_DECL_RE.test(trimmed)) {
2914
3034
  hasTableDecl = true;
2915
3035
  }
2916
- if (REL_SYMBOLIC_RE.test(trimmed) || REL_KEYWORD_RE2.test(trimmed)) {
3036
+ if (REL_SYMBOLIC_RE.test(trimmed)) {
2917
3037
  hasRelationship = true;
2918
3038
  }
2919
3039
  }
@@ -2922,12 +3042,13 @@ function looksLikeERDiagram(content) {
2922
3042
  if (hasRelationship && hasTableDecl && hasConstraint) return true;
2923
3043
  return false;
2924
3044
  }
2925
- var TABLE_DECL_RE, COLUMN_RE, CONSTRAINT_MAP, CARD_WORD, REL_SYMBOLIC_RE, REL_KEYWORD_RE2;
3045
+ var TABLE_DECL_RE, COLUMN_RE, CONSTRAINT_MAP, REL_SYMBOLIC_RE, REL_KEYWORD_RE, KEYWORD_TO_SYMBOL;
2926
3046
  var init_parser3 = __esm({
2927
3047
  "src/er/parser.ts"() {
2928
3048
  "use strict";
2929
3049
  init_colors();
2930
3050
  init_diagnostics();
3051
+ init_parsing();
2931
3052
  TABLE_DECL_RE = /^([a-zA-Z_]\w*)(?:\s+\(([^)]+)\))?\s*$/;
2932
3053
  COLUMN_RE = /^(\w+)(?:\s*:\s*(\w[\w()]*(?:\s*\[\])?))?(?:\s+\[([^\]]+)\])?\s*$/;
2933
3054
  CONSTRAINT_MAP = {
@@ -2936,16 +3057,13 @@ var init_parser3 = __esm({
2936
3057
  unique: "unique",
2937
3058
  nullable: "nullable"
2938
3059
  };
2939
- CARD_WORD = {
3060
+ REL_SYMBOLIC_RE = /^([a-zA-Z_]\w*)\s+([1*?])\s*-{1,2}\s*([1*?])\s+([a-zA-Z_]\w*)(?:\s*:\s*(.+))?$/;
3061
+ REL_KEYWORD_RE = /^([a-zA-Z_]\w*)\s+(one|many|zero)[- ]to[- ](one|many|zero)\s+([a-zA-Z_]\w*)(?:\s*:\s*(.+))?$/i;
3062
+ KEYWORD_TO_SYMBOL = {
2940
3063
  one: "1",
2941
3064
  many: "*",
2942
- "1": "1",
2943
- "*": "*",
2944
- "?": "?",
2945
3065
  zero: "?"
2946
3066
  };
2947
- REL_SYMBOLIC_RE = /^([a-zA-Z_]\w*)\s+([1*?])\s*-{1,2}\s*([1*?])\s+([a-zA-Z_]\w*)(?:\s*:\s*(.+))?$/;
2948
- REL_KEYWORD_RE2 = /^([a-zA-Z_]\w*)\s+(one|many|zero|1|\*|\?)[- ]to[- ](one|many|zero|1|\*|\?)\s+([a-zA-Z_]\w*)(?:\s*:\s*(.+))?$/i;
2949
3067
  }
2950
3068
  });
2951
3069
 
@@ -2955,7 +3073,8 @@ function parseChart(content, palette) {
2955
3073
  const result = {
2956
3074
  type: "bar",
2957
3075
  data: [],
2958
- diagnostics: []
3076
+ diagnostics: [],
3077
+ error: null
2959
3078
  };
2960
3079
  const fail = (line7, message) => {
2961
3080
  const diag = makeDgmoError(line7, message);
@@ -2968,7 +3087,7 @@ function parseChart(content, palette) {
2968
3087
  const lineNumber = i + 1;
2969
3088
  if (!trimmed) continue;
2970
3089
  if (/^#{2,}\s+/.test(trimmed)) continue;
2971
- if (trimmed.startsWith("#") || trimmed.startsWith("//")) continue;
3090
+ if (trimmed.startsWith("//")) continue;
2972
3091
  const colonIndex = trimmed.indexOf(":");
2973
3092
  if (colonIndex === -1) continue;
2974
3093
  const key = trimmed.substring(0, colonIndex).trim().toLowerCase();
@@ -3126,7 +3245,8 @@ function parseEChart(content, palette) {
3126
3245
  const result = {
3127
3246
  type: "scatter",
3128
3247
  data: [],
3129
- diagnostics: []
3248
+ diagnostics: [],
3249
+ error: null
3130
3250
  };
3131
3251
  let currentCategory = "Default";
3132
3252
  for (let i = 0; i < lines.length; i++) {
@@ -3146,7 +3266,7 @@ function parseEChart(content, palette) {
3146
3266
  currentCategory = catName;
3147
3267
  continue;
3148
3268
  }
3149
- if (trimmed.startsWith("#") || trimmed.startsWith("//")) continue;
3269
+ if (trimmed.startsWith("//")) continue;
3150
3270
  const categoryMatch = trimmed.match(/^\[(.+)\]$/);
3151
3271
  if (categoryMatch) {
3152
3272
  currentCategory = categoryMatch[1].trim();
@@ -3775,7 +3895,7 @@ function buildScatterOption(parsed, palette, textColor, axisLineColor, gridOpaci
3775
3895
  }
3776
3896
  },
3777
3897
  grid: {
3778
- left: parsed.ylabel ? "5%" : "3%",
3898
+ left: parsed.ylabel ? "12%" : "3%",
3779
3899
  right: "4%",
3780
3900
  bottom: hasCategories ? "15%" : parsed.xlabel ? "10%" : "3%",
3781
3901
  top: parsed.title ? "15%" : "5%",
@@ -4047,17 +4167,26 @@ function resolveAxisLabels(parsed) {
4047
4167
  yLabel: parsed.ylabel ?? (isHorizontal ? void 0 : parsed.label)
4048
4168
  };
4049
4169
  }
4050
- function makeGridAxis(type, textColor, axisLineColor, splitLineColor, gridOpacity, label, data) {
4170
+ function makeGridAxis(type, textColor, axisLineColor, splitLineColor, gridOpacity, label, data, nameGapOverride) {
4171
+ const defaultGap = type === "value" ? 75 : 40;
4051
4172
  return {
4052
4173
  type,
4053
4174
  ...data && { data },
4054
4175
  axisLine: { lineStyle: { color: axisLineColor } },
4055
- axisLabel: { color: textColor, fontSize: 16, fontFamily: FONT_FAMILY },
4176
+ axisLabel: {
4177
+ color: textColor,
4178
+ fontSize: type === "category" && data ? data.length > 10 ? 11 : data.length > 5 ? 12 : 16 : 16,
4179
+ fontFamily: FONT_FAMILY,
4180
+ ...type === "category" && {
4181
+ interval: 0,
4182
+ formatter: (value) => value.replace(/([a-z])([A-Z])/g, "$1\n$2").replace(/ /g, "\n")
4183
+ }
4184
+ },
4056
4185
  splitLine: { lineStyle: { color: splitLineColor, opacity: gridOpacity } },
4057
4186
  ...label && {
4058
4187
  name: label,
4059
4188
  nameLocation: "middle",
4060
- nameGap: 40,
4189
+ nameGap: nameGapOverride ?? defaultGap,
4061
4190
  nameTextStyle: { color: textColor, fontSize: 18, fontFamily: FONT_FAMILY }
4062
4191
  }
4063
4192
  };
@@ -4112,7 +4241,8 @@ function buildBarOption(parsed, textColor, axisLineColor, splitLineColor, gridOp
4112
4241
  value: d.value,
4113
4242
  itemStyle: { color: d.color ?? colors[i % colors.length] }
4114
4243
  }));
4115
- const categoryAxis = makeGridAxis("category", textColor, axisLineColor, splitLineColor, gridOpacity, isHorizontal ? yLabel : xLabel, labels);
4244
+ const hCatGap = isHorizontal && yLabel ? Math.max(40, Math.max(...labels.map((l) => l.length)) * 8 + 16) : void 0;
4245
+ const categoryAxis = makeGridAxis("category", textColor, axisLineColor, splitLineColor, gridOpacity, isHorizontal ? yLabel : xLabel, labels, hCatGap);
4116
4246
  const valueAxis = makeGridAxis("value", textColor, axisLineColor, splitLineColor, gridOpacity, isHorizontal ? xLabel : yLabel);
4117
4247
  return {
4118
4248
  backgroundColor: "transparent",
@@ -4124,7 +4254,7 @@ function buildBarOption(parsed, textColor, axisLineColor, splitLineColor, gridOp
4124
4254
  axisPointer: { type: "shadow" }
4125
4255
  },
4126
4256
  grid: {
4127
- left: yLabel ? "5%" : "3%",
4257
+ left: yLabel ? "12%" : "3%",
4128
4258
  right: "4%",
4129
4259
  bottom: xLabel ? "10%" : "3%",
4130
4260
  top: parsed.title ? "15%" : "5%",
@@ -4159,7 +4289,7 @@ function buildLineOption(parsed, palette, textColor, axisLineColor, splitLineCol
4159
4289
  axisPointer: { type: "line" }
4160
4290
  },
4161
4291
  grid: {
4162
- left: yLabel ? "5%" : "3%",
4292
+ left: yLabel ? "12%" : "3%",
4163
4293
  right: "4%",
4164
4294
  bottom: xLabel ? "10%" : "3%",
4165
4295
  top: parsed.title ? "15%" : "5%",
@@ -4221,7 +4351,7 @@ function buildMultiLineOption(parsed, textColor, axisLineColor, splitLineColor,
4221
4351
  textStyle: { color: textColor }
4222
4352
  },
4223
4353
  grid: {
4224
- left: yLabel ? "5%" : "3%",
4354
+ left: yLabel ? "12%" : "3%",
4225
4355
  right: "4%",
4226
4356
  bottom: "15%",
4227
4357
  top: parsed.title ? "15%" : "5%",
@@ -4247,7 +4377,7 @@ function buildAreaOption(parsed, palette, textColor, axisLineColor, splitLineCol
4247
4377
  axisPointer: { type: "line" }
4248
4378
  },
4249
4379
  grid: {
4250
- left: yLabel ? "5%" : "3%",
4380
+ left: yLabel ? "12%" : "3%",
4251
4381
  right: "4%",
4252
4382
  bottom: xLabel ? "10%" : "3%",
4253
4383
  top: parsed.title ? "15%" : "5%",
@@ -4444,7 +4574,8 @@ function buildBarStackedOption(parsed, textColor, axisLineColor, splitLineColor,
4444
4574
  }
4445
4575
  };
4446
4576
  });
4447
- const categoryAxis = makeGridAxis("category", textColor, axisLineColor, splitLineColor, gridOpacity, isHorizontal ? yLabel : xLabel, labels);
4577
+ const hCatGap = isHorizontal && yLabel ? Math.max(40, Math.max(...labels.map((l) => l.length)) * 8 + 16) : void 0;
4578
+ const categoryAxis = makeGridAxis("category", textColor, axisLineColor, splitLineColor, gridOpacity, isHorizontal ? yLabel : xLabel, labels, hCatGap);
4448
4579
  const valueAxis = makeGridAxis("value", textColor, axisLineColor, splitLineColor, gridOpacity, isHorizontal ? xLabel : yLabel);
4449
4580
  return {
4450
4581
  backgroundColor: "transparent",
@@ -4461,7 +4592,7 @@ function buildBarStackedOption(parsed, textColor, axisLineColor, splitLineColor,
4461
4592
  textStyle: { color: textColor }
4462
4593
  },
4463
4594
  grid: {
4464
- left: yLabel ? "5%" : "3%",
4595
+ left: yLabel ? "12%" : "3%",
4465
4596
  right: "4%",
4466
4597
  bottom: "15%",
4467
4598
  top: parsed.title ? "15%" : "5%",
@@ -4540,35 +4671,51 @@ var init_echarts = __esm({
4540
4671
  }
4541
4672
  });
4542
4673
 
4674
+ // src/utils/tag-groups.ts
4675
+ function isTagBlockHeading(trimmed) {
4676
+ return TAG_BLOCK_RE.test(trimmed) || GROUP_HEADING_RE2.test(trimmed);
4677
+ }
4678
+ function matchTagBlockHeading(trimmed) {
4679
+ const tagMatch = trimmed.match(TAG_BLOCK_RE);
4680
+ if (tagMatch) {
4681
+ return {
4682
+ name: tagMatch[1].trim(),
4683
+ alias: tagMatch[2] || void 0,
4684
+ colorHint: tagMatch[3] || void 0,
4685
+ deprecated: false
4686
+ };
4687
+ }
4688
+ const groupMatch = trimmed.match(GROUP_HEADING_RE2);
4689
+ if (groupMatch) {
4690
+ return {
4691
+ name: groupMatch[1].trim(),
4692
+ alias: groupMatch[2] || void 0,
4693
+ colorHint: groupMatch[3] || void 0,
4694
+ deprecated: true
4695
+ };
4696
+ }
4697
+ return null;
4698
+ }
4699
+ var TAG_BLOCK_RE, GROUP_HEADING_RE2;
4700
+ var init_tag_groups = __esm({
4701
+ "src/utils/tag-groups.ts"() {
4702
+ "use strict";
4703
+ TAG_BLOCK_RE = /^tag:\s+(.+?)(?:\s+alias\s+(\w+))?(?:\s*\(([^)]+)\))?\s*$/i;
4704
+ GROUP_HEADING_RE2 = /^##\s+(.+?)(?:\s+alias\s+(\w+))?(?:\s*\(([^)]+)\))?\s*$/;
4705
+ }
4706
+ });
4707
+
4543
4708
  // src/org/parser.ts
4544
4709
  var parser_exports4 = {};
4545
4710
  __export(parser_exports4, {
4546
4711
  looksLikeOrg: () => looksLikeOrg,
4547
4712
  parseOrg: () => parseOrg
4548
4713
  });
4549
- function measureIndent5(line7) {
4550
- let indent = 0;
4551
- for (const ch of line7) {
4552
- if (ch === " ") indent++;
4553
- else if (ch === " ") indent += 4;
4554
- else break;
4555
- }
4556
- return indent;
4557
- }
4558
- function extractColor2(label, palette) {
4559
- const m = label.match(COLOR_SUFFIX_RE2);
4560
- if (!m) return { label };
4561
- const colorName = m[1].trim();
4562
- return {
4563
- label: label.substring(0, m.index).trim(),
4564
- color: resolveColor(colorName, palette)
4565
- };
4566
- }
4567
4714
  function looksLikeOrg(content) {
4568
4715
  for (const line7 of content.split("\n")) {
4569
4716
  const trimmed = line7.trim();
4570
4717
  if (!trimmed || trimmed.startsWith("//")) continue;
4571
- if (GROUP_HEADING_RE2.test(trimmed)) return true;
4718
+ if (isTagBlockHeading(trimmed)) return true;
4572
4719
  }
4573
4720
  return false;
4574
4721
  }
@@ -4593,6 +4740,9 @@ function parseOrg(content, palette) {
4593
4740
  result.diagnostics.push(diag);
4594
4741
  if (!result.error) result.error = formatDgmoError(diag);
4595
4742
  };
4743
+ const pushWarning = (line7, message) => {
4744
+ result.diagnostics.push(makeDgmoError(line7, message, "warning"));
4745
+ };
4596
4746
  if (!content || !content.trim()) {
4597
4747
  return fail(0, "No content provided");
4598
4748
  }
@@ -4636,42 +4786,43 @@ function parseOrg(content, palette) {
4636
4786
  continue;
4637
4787
  }
4638
4788
  }
4639
- if (!contentStarted && !currentTagGroup && measureIndent5(line7) === 0) {
4640
- const optMatch = trimmed.match(OPTION_RE);
4641
- if (optMatch && !trimmed.startsWith("##")) {
4642
- const key = optMatch[1].trim().toLowerCase();
4643
- if (key !== "chart" && key !== "title") {
4644
- result.options[key] = optMatch[2].trim();
4645
- continue;
4646
- }
4647
- }
4648
- }
4649
- const groupMatch = trimmed.match(GROUP_HEADING_RE2);
4650
- if (groupMatch) {
4789
+ const tagBlockMatch = matchTagBlockHeading(trimmed);
4790
+ if (tagBlockMatch) {
4651
4791
  if (contentStarted) {
4652
- pushError(lineNumber, "Tag groups (##) must appear before org content");
4792
+ pushError(lineNumber, "Tag groups must appear before org content");
4653
4793
  continue;
4654
4794
  }
4655
- const groupName = groupMatch[1].trim();
4656
- const alias = groupMatch[2] || void 0;
4795
+ if (tagBlockMatch.deprecated) {
4796
+ pushWarning(lineNumber, `'## ${tagBlockMatch.name}' is deprecated for tag groups \u2014 use 'tag: ${tagBlockMatch.name}' instead`);
4797
+ }
4657
4798
  currentTagGroup = {
4658
- name: groupName,
4659
- alias,
4799
+ name: tagBlockMatch.name,
4800
+ alias: tagBlockMatch.alias,
4660
4801
  entries: [],
4661
4802
  lineNumber
4662
4803
  };
4663
- if (alias) {
4664
- aliasMap.set(alias.toLowerCase(), groupName.toLowerCase());
4804
+ if (tagBlockMatch.alias) {
4805
+ aliasMap.set(tagBlockMatch.alias.toLowerCase(), tagBlockMatch.name.toLowerCase());
4665
4806
  }
4666
4807
  result.tagGroups.push(currentTagGroup);
4667
4808
  continue;
4668
4809
  }
4810
+ if (!contentStarted && !currentTagGroup && measureIndent(line7) === 0) {
4811
+ const optMatch = trimmed.match(OPTION_RE);
4812
+ if (optMatch) {
4813
+ const key = optMatch[1].trim().toLowerCase();
4814
+ if (key !== "chart" && key !== "title") {
4815
+ result.options[key] = optMatch[2].trim();
4816
+ continue;
4817
+ }
4818
+ }
4819
+ }
4669
4820
  if (currentTagGroup && !contentStarted) {
4670
- const indent2 = measureIndent5(line7);
4821
+ const indent2 = measureIndent(line7);
4671
4822
  if (indent2 > 0) {
4672
4823
  const isDefault = /\bdefault\s*$/.test(trimmed);
4673
4824
  const entryText = isDefault ? trimmed.replace(/\s+default\s*$/, "").trim() : trimmed;
4674
- const { label, color } = extractColor2(entryText, palette);
4825
+ const { label, color } = extractColor(entryText, palette);
4675
4826
  if (!color) {
4676
4827
  pushError(lineNumber, `Expected 'Value(color)' in tag group '${currentTagGroup.name}'`);
4677
4828
  continue;
@@ -4690,12 +4841,12 @@ function parseOrg(content, palette) {
4690
4841
  }
4691
4842
  contentStarted = true;
4692
4843
  currentTagGroup = null;
4693
- const indent = measureIndent5(line7);
4844
+ const indent = measureIndent(line7);
4694
4845
  const containerMatch = trimmed.match(CONTAINER_RE);
4695
4846
  const metadataMatch = trimmed.includes("|") ? null : trimmed.match(METADATA_RE);
4696
4847
  if (containerMatch) {
4697
4848
  const rawLabel = containerMatch[1].trim();
4698
- const { label, color } = extractColor2(rawLabel, palette);
4849
+ const { label, color } = extractColor(rawLabel, palette);
4699
4850
  containerCounter++;
4700
4851
  const node = {
4701
4852
  id: `container-${containerCounter}`,
@@ -4740,24 +4891,8 @@ function parseOrg(content, palette) {
4740
4891
  function parseNodeLabel(trimmed, _indent, lineNumber, palette, counter, aliasMap = /* @__PURE__ */ new Map()) {
4741
4892
  const segments = trimmed.split("|").map((s) => s.trim());
4742
4893
  let rawLabel = segments[0];
4743
- const { label, color } = extractColor2(rawLabel, palette);
4744
- const metadata = {};
4745
- const metaParts = [];
4746
- for (let j = 1; j < segments.length; j++) {
4747
- for (const part of segments[j].split(",")) {
4748
- const trimmedPart = part.trim();
4749
- if (trimmedPart) metaParts.push(trimmedPart);
4750
- }
4751
- }
4752
- for (const part of metaParts) {
4753
- const colonIdx = part.indexOf(":");
4754
- if (colonIdx > 0) {
4755
- const rawKey = part.substring(0, colonIdx).trim().toLowerCase();
4756
- const key = aliasMap.get(rawKey) ?? rawKey;
4757
- const value = part.substring(colonIdx + 1).trim();
4758
- metadata[key] = value;
4759
- }
4760
- }
4894
+ const { label, color } = extractColor(rawLabel, palette);
4895
+ const metadata = parsePipeMetadata(segments, aliasMap);
4761
4896
  return {
4762
4897
  id: `node-${counter}`,
4763
4898
  label,
@@ -4795,19 +4930,15 @@ function findMetadataParent(indent, indentStack) {
4795
4930
  }
4796
4931
  return null;
4797
4932
  }
4798
- var COLOR_SUFFIX_RE2, GROUP_HEADING_RE2, CONTAINER_RE, METADATA_RE, CHART_TYPE_RE, TITLE_RE, OPTION_RE;
4933
+ var CONTAINER_RE, METADATA_RE;
4799
4934
  var init_parser4 = __esm({
4800
4935
  "src/org/parser.ts"() {
4801
4936
  "use strict";
4802
- init_colors();
4803
4937
  init_diagnostics();
4804
- COLOR_SUFFIX_RE2 = /\(([^)]+)\)\s*$/;
4805
- GROUP_HEADING_RE2 = /^##\s+(.+?)(?:\s+alias\s+(\w+))?(?:\s*\(([^)]+)\))?\s*$/;
4938
+ init_tag_groups();
4939
+ init_parsing();
4806
4940
  CONTAINER_RE = /^\[([^\]]+)\]$/;
4807
4941
  METADATA_RE = /^([^:]+):\s*(.+)$/;
4808
- CHART_TYPE_RE = /^chart\s*:\s*(.+)/i;
4809
- TITLE_RE = /^title\s*:\s*(.+)/i;
4810
- OPTION_RE = /^([a-z][a-z0-9-]*)\s*:\s*(.+)$/i;
4811
4942
  }
4812
4943
  });
4813
4944
 
@@ -4816,31 +4947,14 @@ var parser_exports5 = {};
4816
4947
  __export(parser_exports5, {
4817
4948
  parseKanban: () => parseKanban
4818
4949
  });
4819
- function measureIndent6(line7) {
4820
- let indent = 0;
4821
- for (const ch of line7) {
4822
- if (ch === " ") indent++;
4823
- else if (ch === " ") indent += 4;
4824
- else break;
4825
- }
4826
- return indent;
4827
- }
4828
- function extractColor3(label, palette) {
4829
- const m = label.match(COLOR_SUFFIX_RE3);
4830
- if (!m) return { label };
4831
- const colorName = m[1].trim();
4832
- return {
4833
- label: label.substring(0, m.index).trim(),
4834
- color: resolveColor(colorName, palette)
4835
- };
4836
- }
4837
4950
  function parseKanban(content, palette) {
4838
4951
  const result = {
4839
4952
  type: "kanban",
4840
4953
  columns: [],
4841
4954
  tagGroups: [],
4842
4955
  options: {},
4843
- diagnostics: []
4956
+ diagnostics: [],
4957
+ error: null
4844
4958
  };
4845
4959
  const fail = (line7, message) => {
4846
4960
  const diag = makeDgmoError(line7, message);
@@ -4873,7 +4987,7 @@ function parseKanban(content, palette) {
4873
4987
  }
4874
4988
  if (trimmed.startsWith("//")) continue;
4875
4989
  if (!contentStarted && !currentTagGroup) {
4876
- const chartMatch = trimmed.match(CHART_TYPE_RE2);
4990
+ const chartMatch = trimmed.match(CHART_TYPE_RE);
4877
4991
  if (chartMatch) {
4878
4992
  const chartType = chartMatch[1].trim().toLowerCase();
4879
4993
  if (chartType !== "kanban") {
@@ -4897,16 +5011,35 @@ function parseKanban(content, palette) {
4897
5011
  }
4898
5012
  }
4899
5013
  if (!contentStarted && !currentTagGroup) {
4900
- const titleMatch = trimmed.match(TITLE_RE2);
5014
+ const titleMatch = trimmed.match(TITLE_RE);
4901
5015
  if (titleMatch) {
4902
5016
  result.title = titleMatch[1].trim();
4903
5017
  result.titleLineNumber = lineNumber;
4904
5018
  continue;
4905
5019
  }
4906
5020
  }
4907
- if (!contentStarted && !currentTagGroup && measureIndent6(line7) === 0) {
4908
- const optMatch = trimmed.match(OPTION_RE2);
4909
- if (optMatch && !trimmed.startsWith("##") && !COLUMN_RE2.test(trimmed)) {
5021
+ if (!contentStarted) {
5022
+ const tagBlockMatch = matchTagBlockHeading(trimmed);
5023
+ if (tagBlockMatch) {
5024
+ if (tagBlockMatch.deprecated) {
5025
+ warn(lineNumber, `'## ${tagBlockMatch.name}' is deprecated for tag groups \u2014 use 'tag: ${tagBlockMatch.name}' instead`);
5026
+ }
5027
+ currentTagGroup = {
5028
+ name: tagBlockMatch.name,
5029
+ alias: tagBlockMatch.alias,
5030
+ entries: [],
5031
+ lineNumber
5032
+ };
5033
+ if (tagBlockMatch.alias) {
5034
+ aliasMap.set(tagBlockMatch.alias.toLowerCase(), tagBlockMatch.name.toLowerCase());
5035
+ }
5036
+ result.tagGroups.push(currentTagGroup);
5037
+ continue;
5038
+ }
5039
+ }
5040
+ if (!contentStarted && !currentTagGroup && measureIndent(line7) === 0) {
5041
+ const optMatch = trimmed.match(OPTION_RE);
5042
+ if (optMatch && !COLUMN_RE2.test(trimmed)) {
4910
5043
  const key = optMatch[1].trim().toLowerCase();
4911
5044
  if (key !== "chart" && key !== "title") {
4912
5045
  result.options[key] = optMatch[2].trim();
@@ -4914,28 +5047,12 @@ function parseKanban(content, palette) {
4914
5047
  }
4915
5048
  }
4916
5049
  }
4917
- const groupMatch = trimmed.match(GROUP_HEADING_RE3);
4918
- if (groupMatch && !contentStarted) {
4919
- const groupName = groupMatch[1].trim();
4920
- const alias = groupMatch[2] || void 0;
4921
- currentTagGroup = {
4922
- name: groupName,
4923
- alias,
4924
- entries: [],
4925
- lineNumber
4926
- };
4927
- if (alias) {
4928
- aliasMap.set(alias.toLowerCase(), groupName.toLowerCase());
4929
- }
4930
- result.tagGroups.push(currentTagGroup);
4931
- continue;
4932
- }
4933
5050
  if (currentTagGroup && !contentStarted) {
4934
- const indent2 = measureIndent6(line7);
5051
+ const indent2 = measureIndent(line7);
4935
5052
  if (indent2 > 0) {
4936
5053
  const isDefault = /\bdefault\s*$/.test(trimmed);
4937
5054
  const entryText = isDefault ? trimmed.replace(/\s+default\s*$/, "").trim() : trimmed;
4938
- const { label, color } = extractColor3(entryText, palette);
5055
+ const { label, color } = extractColor(entryText, palette);
4939
5056
  if (!color) {
4940
5057
  warn(
4941
5058
  lineNumber,
@@ -4969,7 +5086,7 @@ function parseKanban(content, palette) {
4969
5086
  columnCounter++;
4970
5087
  const rawColName = columnMatch[1].trim();
4971
5088
  const wipStr = columnMatch[2];
4972
- const { label: colName, color: colColor } = extractColor3(
5089
+ const { label: colName, color: colColor } = extractColor(
4973
5090
  rawColName,
4974
5091
  palette
4975
5092
  );
@@ -4991,7 +5108,7 @@ function parseKanban(content, palette) {
4991
5108
  warn(lineNumber, "Card line found before any column");
4992
5109
  continue;
4993
5110
  }
4994
- const indent = measureIndent6(line7);
5111
+ const indent = measureIndent(line7);
4995
5112
  if (indent > 0 && currentCard) {
4996
5113
  currentCard.details.push(trimmed);
4997
5114
  currentCard.endLineNumber = lineNumber;
@@ -5056,7 +5173,7 @@ function parseCardLine(trimmed, lineNumber, counter, aliasMap, palette) {
5056
5173
  } else {
5057
5174
  rawTitle = trimmed;
5058
5175
  }
5059
- const { label: title, color } = extractColor3(rawTitle, palette);
5176
+ const { label: title, color } = extractColor(rawTitle, palette);
5060
5177
  const tags = {};
5061
5178
  if (tagsStr) {
5062
5179
  for (const part of tagsStr.split(",")) {
@@ -5079,18 +5196,14 @@ function parseCardLine(trimmed, lineNumber, counter, aliasMap, palette) {
5079
5196
  color
5080
5197
  };
5081
5198
  }
5082
- var CHART_TYPE_RE2, TITLE_RE2, OPTION_RE2, GROUP_HEADING_RE3, COLUMN_RE2, COLOR_SUFFIX_RE3;
5199
+ var COLUMN_RE2;
5083
5200
  var init_parser5 = __esm({
5084
5201
  "src/kanban/parser.ts"() {
5085
5202
  "use strict";
5086
- init_colors();
5087
5203
  init_diagnostics();
5088
- CHART_TYPE_RE2 = /^chart\s*:\s*(.+)/i;
5089
- TITLE_RE2 = /^title\s*:\s*(.+)/i;
5090
- OPTION_RE2 = /^([a-z][a-z0-9-]*)\s*:\s*(.+)$/i;
5091
- GROUP_HEADING_RE3 = /^##\s+(.+?)(?:\s+alias\s+(\w+))?(?:\s*\(([^)]+)\))?\s*$/;
5204
+ init_tag_groups();
5205
+ init_parsing();
5092
5206
  COLUMN_RE2 = /^==\s+(.+?)\s*(?:\[wip:\s*(\d+)\])?\s*==$/;
5093
- COLOR_SUFFIX_RE3 = /\(([^)]+)\)\s*$/;
5094
5207
  }
5095
5208
  });
5096
5209
 
@@ -5099,24 +5212,6 @@ var parser_exports6 = {};
5099
5212
  __export(parser_exports6, {
5100
5213
  parseC4: () => parseC4
5101
5214
  });
5102
- function measureIndent7(line7) {
5103
- let indent = 0;
5104
- for (const ch of line7) {
5105
- if (ch === " ") indent++;
5106
- else if (ch === " ") indent += 4;
5107
- else break;
5108
- }
5109
- return indent;
5110
- }
5111
- function extractColor4(label, palette) {
5112
- const m = label.match(COLOR_SUFFIX_RE4);
5113
- if (!m) return { label };
5114
- const colorName = m[1].trim();
5115
- return {
5116
- label: label.substring(0, m.index).trim(),
5117
- color: resolveColor(colorName, palette)
5118
- };
5119
- }
5120
5215
  function participantTypeToC4Shape(pType) {
5121
5216
  switch (pType) {
5122
5217
  case "database":
@@ -5173,23 +5268,6 @@ function parseRelationshipBody(body) {
5173
5268
  }
5174
5269
  return { target, label: rest };
5175
5270
  }
5176
- function parsePipeMetadata(segments, aliasMap) {
5177
- const metadata = {};
5178
- for (let j = 1; j < segments.length; j++) {
5179
- for (const part of segments[j].split(",")) {
5180
- const trimmedPart = part.trim();
5181
- if (!trimmedPart) continue;
5182
- const colonIdx = trimmedPart.indexOf(":");
5183
- if (colonIdx > 0) {
5184
- const rawKey = trimmedPart.substring(0, colonIdx).trim().toLowerCase();
5185
- const key = aliasMap.get(rawKey) ?? rawKey;
5186
- const value = trimmedPart.substring(colonIdx + 1).trim();
5187
- metadata[key] = value;
5188
- }
5189
- }
5190
- }
5191
- return metadata;
5192
- }
5193
5271
  function parseC4(content, palette) {
5194
5272
  const result = {
5195
5273
  title: null,
@@ -5235,7 +5313,7 @@ function parseC4(content, palette) {
5235
5313
  }
5236
5314
  if (trimmed.startsWith("//")) continue;
5237
5315
  if (!contentStarted) {
5238
- const chartMatch = trimmed.match(CHART_TYPE_RE3);
5316
+ const chartMatch = trimmed.match(CHART_TYPE_RE);
5239
5317
  if (chartMatch) {
5240
5318
  const chartType = chartMatch[1].trim().toLowerCase();
5241
5319
  if (chartType !== "c4") {
@@ -5249,49 +5327,50 @@ function parseC4(content, palette) {
5249
5327
  }
5250
5328
  }
5251
5329
  if (!contentStarted) {
5252
- const titleMatch = trimmed.match(TITLE_RE3);
5330
+ const titleMatch = trimmed.match(TITLE_RE);
5253
5331
  if (titleMatch) {
5254
5332
  result.title = titleMatch[1].trim();
5255
5333
  result.titleLineNumber = lineNumber;
5256
5334
  continue;
5257
5335
  }
5258
5336
  }
5259
- if (!contentStarted && !currentTagGroup && measureIndent7(line7) === 0) {
5260
- const optMatch = trimmed.match(OPTION_RE3);
5261
- if (optMatch && !trimmed.startsWith("##")) {
5262
- const key = optMatch[1].trim().toLowerCase();
5263
- if (key !== "chart" && key !== "title") {
5264
- result.options[key] = optMatch[2].trim();
5265
- continue;
5266
- }
5267
- }
5268
- }
5269
- const groupMatch = trimmed.match(GROUP_HEADING_RE4);
5270
- if (groupMatch) {
5337
+ const tagBlockMatch = matchTagBlockHeading(trimmed);
5338
+ if (tagBlockMatch) {
5271
5339
  if (contentStarted) {
5272
- pushError(lineNumber, "Tag groups (##) must appear before content");
5340
+ pushError(lineNumber, "Tag groups must appear before content");
5273
5341
  continue;
5274
5342
  }
5275
- const groupName = groupMatch[1].trim();
5276
- const alias = groupMatch[2] || void 0;
5343
+ if (tagBlockMatch.deprecated) {
5344
+ pushError(lineNumber, `'## ${tagBlockMatch.name}' is deprecated for tag groups \u2014 use 'tag: ${tagBlockMatch.name}' instead`, "warning");
5345
+ }
5277
5346
  currentTagGroup = {
5278
- name: groupName,
5279
- alias,
5347
+ name: tagBlockMatch.name,
5348
+ alias: tagBlockMatch.alias,
5280
5349
  entries: [],
5281
5350
  lineNumber
5282
5351
  };
5283
- if (alias) {
5284
- aliasMap.set(alias.toLowerCase(), groupName.toLowerCase());
5352
+ if (tagBlockMatch.alias) {
5353
+ aliasMap.set(tagBlockMatch.alias.toLowerCase(), tagBlockMatch.name.toLowerCase());
5285
5354
  }
5286
5355
  result.tagGroups.push(currentTagGroup);
5287
5356
  continue;
5288
5357
  }
5358
+ if (!contentStarted && !currentTagGroup && measureIndent(line7) === 0) {
5359
+ const optMatch = trimmed.match(OPTION_RE);
5360
+ if (optMatch) {
5361
+ const key = optMatch[1].trim().toLowerCase();
5362
+ if (key !== "chart" && key !== "title") {
5363
+ result.options[key] = optMatch[2].trim();
5364
+ continue;
5365
+ }
5366
+ }
5367
+ }
5289
5368
  if (currentTagGroup && !contentStarted) {
5290
- const indent2 = measureIndent7(line7);
5369
+ const indent2 = measureIndent(line7);
5291
5370
  if (indent2 > 0) {
5292
5371
  const isDefault = /\bdefault\s*$/.test(trimmed);
5293
5372
  const entryText = isDefault ? trimmed.replace(/\s+default\s*$/, "").trim() : trimmed;
5294
- const { label, color } = extractColor4(entryText, palette);
5373
+ const { label, color } = extractColor(entryText, palette);
5295
5374
  if (!color) {
5296
5375
  pushError(
5297
5376
  lineNumber,
@@ -5316,7 +5395,7 @@ function parseC4(content, palette) {
5316
5395
  if (!sawChartType) {
5317
5396
  return fail(lineNumber, 'Missing "chart: c4" header');
5318
5397
  }
5319
- const indent = measureIndent7(line7);
5398
+ const indent = measureIndent(line7);
5320
5399
  if (inDeployment) {
5321
5400
  while (deployStack.length > 0) {
5322
5401
  const top = deployStack[deployStack.length - 1];
@@ -5411,6 +5490,45 @@ function parseC4(content, palette) {
5411
5490
  }
5412
5491
  continue;
5413
5492
  }
5493
+ {
5494
+ const labeledPatterns = [
5495
+ { re: C4_LABELED_BIDI_SYNC_RE, arrowType: "bidirectional" },
5496
+ { re: C4_LABELED_BIDI_ASYNC_RE, arrowType: "bidirectional-async" },
5497
+ { re: C4_LABELED_SYNC_RE, arrowType: "sync" },
5498
+ { re: C4_LABELED_ASYNC_RE, arrowType: "async" }
5499
+ ];
5500
+ let labeledHandled = false;
5501
+ for (const { re, arrowType } of labeledPatterns) {
5502
+ const m = trimmed.match(re);
5503
+ if (!m) continue;
5504
+ const rawLabel = m[1].trim();
5505
+ const targetBody = m[2].trim();
5506
+ if (!rawLabel) break;
5507
+ let label = rawLabel;
5508
+ let technology;
5509
+ const techMatch = rawLabel.match(/\[([^\]]+)\]\s*$/);
5510
+ if (techMatch) {
5511
+ label = rawLabel.substring(0, techMatch.index).trim() || void 0;
5512
+ technology = techMatch[1].trim();
5513
+ }
5514
+ const rel = {
5515
+ target: targetBody,
5516
+ label,
5517
+ technology,
5518
+ arrowType,
5519
+ lineNumber
5520
+ };
5521
+ const parentEntry = findParentElement(indent, stack);
5522
+ if (parentEntry) {
5523
+ parentEntry.element.relationships.push(rel);
5524
+ } else {
5525
+ result.relationships.push(rel);
5526
+ }
5527
+ labeledHandled = true;
5528
+ break;
5529
+ }
5530
+ if (labeledHandled) continue;
5531
+ }
5414
5532
  const relMatch = trimmed.match(RELATIONSHIP_RE);
5415
5533
  if (relMatch) {
5416
5534
  const arrowType = parseArrowType(relMatch[1]);
@@ -5600,22 +5718,22 @@ function validateDeploymentRefs(result, knownNames, pushWarning) {
5600
5718
  }
5601
5719
  walkDeploy(result.deployment);
5602
5720
  }
5603
- var CHART_TYPE_RE3, TITLE_RE3, OPTION_RE3, GROUP_HEADING_RE4, COLOR_SUFFIX_RE4, CONTAINER_RE2, ELEMENT_RE, IS_A_RE, RELATIONSHIP_RE, SECTION_HEADER_RE, CONTAINER_REF_RE, METADATA_RE2, VALID_ELEMENT_TYPES, VALID_SHAPES, ALL_CHART_TYPES;
5721
+ var CONTAINER_RE2, ELEMENT_RE, IS_A_RE, RELATIONSHIP_RE, C4_LABELED_SYNC_RE, C4_LABELED_ASYNC_RE, C4_LABELED_BIDI_SYNC_RE, C4_LABELED_BIDI_ASYNC_RE, SECTION_HEADER_RE, CONTAINER_REF_RE, METADATA_RE2, VALID_ELEMENT_TYPES, VALID_SHAPES, ALL_CHART_TYPES;
5604
5722
  var init_parser6 = __esm({
5605
5723
  "src/c4/parser.ts"() {
5606
5724
  "use strict";
5607
- init_colors();
5608
5725
  init_diagnostics();
5726
+ init_tag_groups();
5609
5727
  init_participant_inference();
5610
- CHART_TYPE_RE3 = /^chart\s*:\s*(.+)/i;
5611
- TITLE_RE3 = /^title\s*:\s*(.+)/i;
5612
- OPTION_RE3 = /^([a-z][a-z0-9-]*)\s*:\s*(.+)$/i;
5613
- GROUP_HEADING_RE4 = /^##\s+(.+?)(?:\s+alias\s+(\w+))?(?:\s*\(([^)]+)\))?\s*$/;
5614
- COLOR_SUFFIX_RE4 = /\(([^)]+)\)\s*$/;
5728
+ init_parsing();
5615
5729
  CONTAINER_RE2 = /^\[([^\]]+)\]$/;
5616
5730
  ELEMENT_RE = /^(person|system|container|component)\s+(.+)$/i;
5617
5731
  IS_A_RE = /\s+is\s+a(?:n)?\s+(\w+)\s*$/i;
5618
5732
  RELATIONSHIP_RE = /^(<?-?>|<?~?>)\s+(.+)$/;
5733
+ C4_LABELED_SYNC_RE = /^-(.+)->\s+(.+)$/;
5734
+ C4_LABELED_ASYNC_RE = /^~(.+)~>\s+(.+)$/;
5735
+ C4_LABELED_BIDI_SYNC_RE = /^<-(.+)->\s+(.+)$/;
5736
+ C4_LABELED_BIDI_ASYNC_RE = /^<~(.+)~>\s+(.+)$/;
5619
5737
  SECTION_HEADER_RE = /^(containers|components|deployment)\s*:\s*$/i;
5620
5738
  CONTAINER_REF_RE = /^container\s+(.+)$/i;
5621
5739
  METADATA_RE2 = /^([^:]+):\s*(.+)$/;
@@ -5676,13 +5794,13 @@ function looksLikeInitiativeStatus(content) {
5676
5794
  let hasIndentedArrow = false;
5677
5795
  for (const line7 of lines) {
5678
5796
  const trimmed = line7.trim();
5679
- if (!trimmed || trimmed.startsWith("#") || trimmed.startsWith("//")) continue;
5797
+ if (!trimmed || trimmed.startsWith("//")) continue;
5680
5798
  if (trimmed.match(/^chart\s*:/i)) continue;
5681
5799
  if (trimmed.match(/^title\s*:/i)) continue;
5682
5800
  if (trimmed.includes("->")) hasArrow = true;
5683
5801
  if (/\|\s*(done|wip|todo|na)\s*$/i.test(trimmed)) hasStatus = true;
5684
5802
  const isIndented = line7.length > 0 && line7 !== trimmed && /^\s/.test(line7);
5685
- if (isIndented && trimmed.startsWith("->")) hasIndentedArrow = true;
5803
+ if (isIndented && (trimmed.startsWith("->") || /^-[^>].*->/.test(trimmed))) hasIndentedArrow = true;
5686
5804
  if (hasArrow && hasStatus) return true;
5687
5805
  }
5688
5806
  return hasIndentedArrow;
@@ -5706,7 +5824,7 @@ function parseInitiativeStatus(content) {
5706
5824
  groups: [],
5707
5825
  options: {},
5708
5826
  diagnostics: [],
5709
- error: void 0
5827
+ error: null
5710
5828
  };
5711
5829
  const lines = content.split("\n");
5712
5830
  const nodeLabels = /* @__PURE__ */ new Set();
@@ -5716,7 +5834,7 @@ function parseInitiativeStatus(content) {
5716
5834
  const lineNum = i + 1;
5717
5835
  const raw = lines[i];
5718
5836
  const trimmed = raw.trim();
5719
- if (!trimmed || trimmed.startsWith("#") || trimmed.startsWith("//")) continue;
5837
+ if (!trimmed || trimmed.startsWith("//")) continue;
5720
5838
  const chartMatch = trimmed.match(/^chart\s*:\s*(.+)/i);
5721
5839
  if (chartMatch) {
5722
5840
  const chartType = chartMatch[1].trim().toLowerCase();
@@ -5749,7 +5867,7 @@ function parseInitiativeStatus(content) {
5749
5867
  }
5750
5868
  if (trimmed.includes("->")) {
5751
5869
  let edgeText = trimmed;
5752
- if (trimmed.startsWith("->")) {
5870
+ if (trimmed.startsWith("->") || /^-[^>].*->/.test(trimmed)) {
5753
5871
  if (!lastNodeLabel) {
5754
5872
  result.diagnostics.push(
5755
5873
  makeDgmoError(lineNum, "Indented edge has no preceding node to use as source", "warning")
@@ -5815,6 +5933,27 @@ function parseNodeLine(trimmed, lineNum, diagnostics) {
5815
5933
  return { label: trimmed, status: "na", shape: inferParticipantType(trimmed), lineNumber: lineNum };
5816
5934
  }
5817
5935
  function parseEdgeLine(trimmed, lineNum, diagnostics) {
5936
+ const labeledMatch = trimmed.match(/^(\S+)\s+-(.+)->\s+(.+)$/);
5937
+ if (labeledMatch) {
5938
+ const source2 = labeledMatch[1];
5939
+ const label2 = labeledMatch[2].trim();
5940
+ let targetRest = labeledMatch[3].trim();
5941
+ if (label2) {
5942
+ let status2 = "na";
5943
+ const lastPipe2 = targetRest.lastIndexOf("|");
5944
+ if (lastPipe2 >= 0) {
5945
+ const statusRaw = targetRest.slice(lastPipe2 + 1).trim();
5946
+ status2 = parseStatus(statusRaw, lineNum, diagnostics);
5947
+ targetRest = targetRest.slice(0, lastPipe2).trim();
5948
+ }
5949
+ const target2 = targetRest.trim();
5950
+ if (!target2) {
5951
+ diagnostics.push(makeDgmoError(lineNum, "Edge is missing target"));
5952
+ return null;
5953
+ }
5954
+ return { source: source2, target: target2, label: label2, status: status2, lineNumber: lineNum };
5955
+ }
5956
+ }
5818
5957
  const arrowIdx = trimmed.indexOf("->");
5819
5958
  if (arrowIdx < 0) return null;
5820
5959
  const source = trimmed.slice(0, arrowIdx).trim();
@@ -5869,7 +6008,7 @@ function parseDgmoChartType(content) {
5869
6008
  const lines = content.split("\n");
5870
6009
  for (const line7 of lines) {
5871
6010
  const trimmed = line7.trim();
5872
- if (!trimmed || trimmed.startsWith("#") || trimmed.startsWith("//"))
6011
+ if (!trimmed || trimmed.startsWith("//"))
5873
6012
  continue;
5874
6013
  const match = trimmed.match(/^chart\s*:\s*(.+)/i);
5875
6014
  if (match) return match[1].trim().toLowerCase();
@@ -7131,6 +7270,58 @@ var init_renderer = __esm({
7131
7270
  }
7132
7271
  });
7133
7272
 
7273
+ // src/utils/inline-markdown.ts
7274
+ function parseInlineMarkdown(text) {
7275
+ const spans = [];
7276
+ const regex = /\*\*(.+?)\*\*|__(.+?)__|\*(.+?)\*|_(.+?)_|`(.+?)`|\[(.+?)\]\((.+?)\)|(https?:\/\/[^\s)>\]]+|www\.[^\s)>\]]+)|([^*_`[]+?(?=https?:\/\/|www\.|$)|[^*_`[]+)/g;
7277
+ let match;
7278
+ while ((match = regex.exec(text)) !== null) {
7279
+ if (match[1]) spans.push({ text: match[1], bold: true });
7280
+ else if (match[2]) spans.push({ text: match[2], bold: true });
7281
+ else if (match[3]) spans.push({ text: match[3], italic: true });
7282
+ else if (match[4]) spans.push({ text: match[4], italic: true });
7283
+ else if (match[5]) spans.push({ text: match[5], code: true });
7284
+ else if (match[6]) spans.push({ text: match[6], href: match[7] });
7285
+ else if (match[8]) {
7286
+ const url = match[8];
7287
+ const href = url.startsWith("www.") ? `https://${url}` : url;
7288
+ spans.push({ text: url, href });
7289
+ } else if (match[9]) spans.push({ text: match[9] });
7290
+ }
7291
+ return spans;
7292
+ }
7293
+ function truncateBareUrl(url) {
7294
+ const stripped = url.replace(/^https?:\/\//, "").replace(/^www\./, "");
7295
+ if (stripped.length <= BARE_URL_MAX_DISPLAY) return stripped;
7296
+ return stripped.slice(0, BARE_URL_MAX_DISPLAY - 1) + "\u2026";
7297
+ }
7298
+ function renderInlineText(textEl, text, palette, fontSize) {
7299
+ const spans = parseInlineMarkdown(text);
7300
+ for (const span of spans) {
7301
+ if (span.href) {
7302
+ const isBareUrl = span.text === span.href || `https://${span.text}` === span.href;
7303
+ const display = isBareUrl ? truncateBareUrl(span.text) : span.text;
7304
+ const a = textEl.append("a").attr("href", span.href);
7305
+ a.append("tspan").text(display).attr("fill", palette.primary).style("text-decoration", "underline");
7306
+ } else {
7307
+ const tspan = textEl.append("tspan").text(span.text);
7308
+ if (span.bold) tspan.attr("font-weight", "bold");
7309
+ if (span.italic) tspan.attr("font-style", "italic");
7310
+ if (span.code) {
7311
+ tspan.attr("font-family", "monospace");
7312
+ if (fontSize) tspan.attr("font-size", fontSize - 1);
7313
+ }
7314
+ }
7315
+ }
7316
+ }
7317
+ var BARE_URL_MAX_DISPLAY;
7318
+ var init_inline_markdown = __esm({
7319
+ "src/utils/inline-markdown.ts"() {
7320
+ "use strict";
7321
+ BARE_URL_MAX_DISPLAY = 35;
7322
+ }
7323
+ });
7324
+
7134
7325
  // src/kanban/mutations.ts
7135
7326
  function computeCardMove(content, parsed, cardId, targetColumnId, targetIndex) {
7136
7327
  let sourceCard = null;
@@ -7452,7 +7643,8 @@ function renderKanban(container, parsed, palette, isDark, _onNavigateToLine, exp
7452
7643
  const cx = colLayout.x + cardLayout.x;
7453
7644
  const cy = colLayout.y + cardLayout.y;
7454
7645
  cg.append("rect").attr("x", cx).attr("y", cy).attr("width", cardLayout.width).attr("height", cardLayout.height).attr("rx", CARD_RADIUS2).attr("fill", cardFill).attr("stroke", cardStroke).attr("stroke-width", CARD_STROKE_WIDTH);
7455
- cg.append("text").attr("x", cx + CARD_PADDING_X).attr("y", cy + CARD_PADDING_Y + CARD_TITLE_FONT_SIZE).attr("font-size", CARD_TITLE_FONT_SIZE).attr("font-weight", "500").attr("fill", palette.text).text(card.title);
7646
+ const titleEl = cg.append("text").attr("x", cx + CARD_PADDING_X).attr("y", cy + CARD_PADDING_Y + CARD_TITLE_FONT_SIZE).attr("font-size", CARD_TITLE_FONT_SIZE).attr("font-weight", "500").attr("fill", palette.text);
7647
+ renderInlineText(titleEl, card.title, palette, CARD_TITLE_FONT_SIZE);
7456
7648
  if (hasMeta) {
7457
7649
  const separatorY = cy + CARD_HEADER_HEIGHT;
7458
7650
  cg.append("line").attr("x1", cx).attr("y1", separatorY).attr("x2", cx + cardLayout.width).attr("y2", separatorY).attr("stroke", cardStroke).attr("stroke-opacity", 0.3).attr("stroke-width", 1);
@@ -7464,7 +7656,8 @@ function renderKanban(container, parsed, palette, isDark, _onNavigateToLine, exp
7464
7656
  metaY += CARD_META_LINE_HEIGHT;
7465
7657
  }
7466
7658
  for (const detail of card.details) {
7467
- cg.append("text").attr("x", cx + CARD_PADDING_X).attr("y", metaY).attr("font-size", CARD_META_FONT_SIZE).attr("fill", palette.textMuted).text(detail);
7659
+ const detailEl = cg.append("text").attr("x", cx + CARD_PADDING_X).attr("y", metaY).attr("font-size", CARD_META_FONT_SIZE).attr("fill", palette.textMuted);
7660
+ renderInlineText(detailEl, detail, palette, CARD_META_FONT_SIZE);
7468
7661
  metaY += CARD_META_LINE_HEIGHT;
7469
7662
  }
7470
7663
  }
@@ -7490,6 +7683,7 @@ var init_renderer2 = __esm({
7490
7683
  "use strict";
7491
7684
  d3Selection2 = __toESM(require("d3-selection"), 1);
7492
7685
  init_fonts();
7686
+ init_inline_markdown();
7493
7687
  init_parser5();
7494
7688
  init_mutations();
7495
7689
  DIAGRAM_PADDING2 = 20;
@@ -7546,22 +7740,30 @@ function computeNodeDimensions(node) {
7546
7740
  }
7547
7741
  const width = Math.max(MIN_WIDTH, maxTextLen * CHAR_WIDTH2 + PADDING_X);
7548
7742
  const headerHeight = HEADER_BASE + (node.modifier ? MODIFIER_BADGE : 0);
7549
- let fieldsHeight = 0;
7743
+ let fieldsHeight;
7550
7744
  if (isEnum) {
7551
7745
  const enumValues = node.members;
7552
7746
  if (enumValues.length > 0) {
7553
7747
  fieldsHeight = COMPARTMENT_PADDING_Y * 2 + enumValues.length * MEMBER_LINE_HEIGHT + SEPARATOR_HEIGHT;
7748
+ } else {
7749
+ fieldsHeight = SEPARATOR_HEIGHT + COMPARTMENT_PADDING_Y;
7554
7750
  }
7555
7751
  } else {
7556
7752
  if (fields.length > 0) {
7557
7753
  fieldsHeight = COMPARTMENT_PADDING_Y * 2 + fields.length * MEMBER_LINE_HEIGHT + SEPARATOR_HEIGHT;
7754
+ } else {
7755
+ fieldsHeight = SEPARATOR_HEIGHT + COMPARTMENT_PADDING_Y;
7558
7756
  }
7559
7757
  }
7560
7758
  let methodsHeight = 0;
7561
- if (!isEnum && methods.length > 0) {
7562
- methodsHeight = COMPARTMENT_PADDING_Y * 2 + methods.length * MEMBER_LINE_HEIGHT + SEPARATOR_HEIGHT;
7759
+ if (!isEnum) {
7760
+ if (methods.length > 0) {
7761
+ methodsHeight = COMPARTMENT_PADDING_Y * 2 + methods.length * MEMBER_LINE_HEIGHT + SEPARATOR_HEIGHT;
7762
+ } else {
7763
+ methodsHeight = SEPARATOR_HEIGHT + COMPARTMENT_PADDING_Y;
7764
+ }
7563
7765
  }
7564
- const height = headerHeight + fieldsHeight + methodsHeight + (fieldsHeight === 0 && methodsHeight === 0 ? 4 : 0);
7766
+ const height = headerHeight + fieldsHeight + methodsHeight;
7565
7767
  return { width, height, headerHeight, fieldsHeight, methodsHeight };
7566
7768
  }
7567
7769
  function layoutClassDiagram(parsed) {
@@ -7814,17 +8016,15 @@ function renderClassDiagram(container, parsed, layout, palette, isDark, onClickI
7814
8016
  const fields = node.members.filter((m) => !m.isMethod);
7815
8017
  const methods = node.members.filter((m) => m.isMethod);
7816
8018
  if (isEnum) {
7817
- if (node.members.length > 0) {
7818
- nodeG.append("line").attr("x1", -w / 2).attr("y1", yPos).attr("x2", w / 2).attr("y2", yPos).attr("stroke", stroke2).attr("stroke-width", 0.5).attr("stroke-opacity", 0.5);
7819
- let memberY = yPos + COMPARTMENT_PADDING_Y2;
7820
- for (const member of node.members) {
7821
- nodeG.append("text").attr("x", -w / 2 + MEMBER_PADDING_X).attr("y", memberY + MEMBER_LINE_HEIGHT2 / 2).attr("dominant-baseline", "central").attr("fill", palette.text).attr("font-size", MEMBER_FONT_SIZE).text(member.name);
7822
- memberY += MEMBER_LINE_HEIGHT2;
7823
- }
8019
+ nodeG.append("line").attr("x1", -w / 2).attr("y1", yPos).attr("x2", w / 2).attr("y2", yPos).attr("stroke", stroke2).attr("stroke-width", 0.5).attr("stroke-opacity", 0.5);
8020
+ let memberY = yPos + COMPARTMENT_PADDING_Y2;
8021
+ for (const member of node.members) {
8022
+ nodeG.append("text").attr("x", -w / 2 + MEMBER_PADDING_X).attr("y", memberY + MEMBER_LINE_HEIGHT2 / 2).attr("dominant-baseline", "central").attr("fill", palette.text).attr("font-size", MEMBER_FONT_SIZE).text(member.name);
8023
+ memberY += MEMBER_LINE_HEIGHT2;
7824
8024
  }
7825
8025
  } else {
8026
+ nodeG.append("line").attr("x1", -w / 2).attr("y1", yPos).attr("x2", w / 2).attr("y2", yPos).attr("stroke", stroke2).attr("stroke-width", 0.5).attr("stroke-opacity", 0.5);
7826
8027
  if (fields.length > 0) {
7827
- nodeG.append("line").attr("x1", -w / 2).attr("y1", yPos).attr("x2", w / 2).attr("y2", yPos).attr("stroke", stroke2).attr("stroke-width", 0.5).attr("stroke-opacity", 0.5);
7828
8028
  let memberY = yPos + COMPARTMENT_PADDING_Y2;
7829
8029
  for (const field of fields) {
7830
8030
  const vis = visibilitySymbol(field.visibility);
@@ -7837,10 +8037,10 @@ function renderClassDiagram(container, parsed, layout, palette, isDark, onClickI
7837
8037
  textEl.text(text);
7838
8038
  memberY += MEMBER_LINE_HEIGHT2;
7839
8039
  }
7840
- yPos += node.fieldsHeight;
7841
8040
  }
8041
+ yPos += node.fieldsHeight;
8042
+ nodeG.append("line").attr("x1", -w / 2).attr("y1", yPos).attr("x2", w / 2).attr("y2", yPos).attr("stroke", stroke2).attr("stroke-width", 0.5).attr("stroke-opacity", 0.5);
7842
8043
  if (methods.length > 0) {
7843
- nodeG.append("line").attr("x1", -w / 2).attr("y1", yPos).attr("x2", w / 2).attr("y2", yPos).attr("stroke", stroke2).attr("stroke-width", 0.5).attr("stroke-opacity", 0.5);
7844
8044
  let memberY = yPos + COMPARTMENT_PADDING_Y2;
7845
8045
  for (const method of methods) {
7846
8046
  const vis = visibilitySymbol(method.visibility);
@@ -10635,7 +10835,8 @@ function renderC4Context(container, parsed, layout, palette, isDark, onClickItem
10635
10835
  const contentWidth = w - CARD_H_PAD3 * 2;
10636
10836
  const lines = wrapText2(node.description, contentWidth, DESC_CHAR_WIDTH2);
10637
10837
  for (const line7 of lines) {
10638
- nodeG.append("text").attr("x", 0).attr("y", yPos + DESC_FONT_SIZE / 2).attr("text-anchor", "middle").attr("dominant-baseline", "central").attr("fill", palette.textMuted).attr("font-size", DESC_FONT_SIZE).text(line7);
10838
+ const textEl = nodeG.append("text").attr("x", 0).attr("y", yPos + DESC_FONT_SIZE / 2).attr("text-anchor", "middle").attr("dominant-baseline", "central").attr("fill", palette.textMuted).attr("font-size", DESC_FONT_SIZE);
10839
+ renderInlineText(textEl, line7, palette, DESC_FONT_SIZE);
10639
10840
  yPos += DESC_LINE_HEIGHT2;
10640
10841
  }
10641
10842
  }
@@ -11121,7 +11322,8 @@ function renderC4Containers(container, parsed, layout, palette, isDark, onClickI
11121
11322
  const contentWidth = w - CARD_H_PAD3 * 2;
11122
11323
  const lines = wrapText2(node.description, contentWidth, DESC_CHAR_WIDTH2);
11123
11324
  for (const line7 of lines) {
11124
- nodeG.append("text").attr("x", 0).attr("y", yPos + DESC_FONT_SIZE / 2).attr("text-anchor", "middle").attr("dominant-baseline", "central").attr("fill", palette.textMuted).attr("font-size", DESC_FONT_SIZE).text(line7);
11325
+ const textEl = nodeG.append("text").attr("x", 0).attr("y", yPos + DESC_FONT_SIZE / 2).attr("text-anchor", "middle").attr("dominant-baseline", "central").attr("fill", palette.textMuted).attr("font-size", DESC_FONT_SIZE);
11326
+ renderInlineText(textEl, line7, palette, DESC_FONT_SIZE);
11125
11327
  yPos += DESC_LINE_HEIGHT2;
11126
11328
  }
11127
11329
  }
@@ -11144,7 +11346,8 @@ function renderC4Containers(container, parsed, layout, palette, isDark, onClickI
11144
11346
  const contentWidth = w - CARD_H_PAD3 * 2;
11145
11347
  const lines = wrapText2(node.description, contentWidth, DESC_CHAR_WIDTH2);
11146
11348
  for (const line7 of lines) {
11147
- nodeG.append("text").attr("x", 0).attr("y", yPos + DESC_FONT_SIZE / 2).attr("text-anchor", "middle").attr("dominant-baseline", "central").attr("fill", palette.textMuted).attr("font-size", DESC_FONT_SIZE).text(line7);
11349
+ const textEl = nodeG.append("text").attr("x", 0).attr("y", yPos + DESC_FONT_SIZE / 2).attr("text-anchor", "middle").attr("dominant-baseline", "central").attr("fill", palette.textMuted).attr("font-size", DESC_FONT_SIZE);
11350
+ renderInlineText(textEl, line7, palette, DESC_FONT_SIZE);
11148
11351
  yPos += DESC_LINE_HEIGHT2;
11149
11352
  }
11150
11353
  }
@@ -11265,6 +11468,7 @@ var init_renderer6 = __esm({
11265
11468
  d3Selection6 = __toESM(require("d3-selection"), 1);
11266
11469
  d3Shape4 = __toESM(require("d3-shape"), 1);
11267
11470
  init_fonts();
11471
+ init_inline_markdown();
11268
11472
  init_parser6();
11269
11473
  init_layout5();
11270
11474
  DIAGRAM_PADDING6 = 20;
@@ -11762,49 +11966,6 @@ __export(renderer_exports7, {
11762
11966
  renderSequenceDiagram: () => renderSequenceDiagram,
11763
11967
  truncateBareUrl: () => truncateBareUrl
11764
11968
  });
11765
- function parseInlineMarkdown(text) {
11766
- const spans = [];
11767
- const regex = /\*\*(.+?)\*\*|__(.+?)__|\*(.+?)\*|_(.+?)_|`(.+?)`|\[(.+?)\]\((.+?)\)|(https?:\/\/[^\s)>\]]+|www\.[^\s)>\]]+)|([^*_`[]+?(?=https?:\/\/|www\.|$)|[^*_`[]+)/g;
11768
- let match;
11769
- while ((match = regex.exec(text)) !== null) {
11770
- if (match[1]) spans.push({ text: match[1], bold: true });
11771
- else if (match[2]) spans.push({ text: match[2], bold: true });
11772
- else if (match[3]) spans.push({ text: match[3], italic: true });
11773
- else if (match[4]) spans.push({ text: match[4], italic: true });
11774
- else if (match[5]) spans.push({ text: match[5], code: true });
11775
- else if (match[6]) spans.push({ text: match[6], href: match[7] });
11776
- else if (match[8]) {
11777
- const url = match[8];
11778
- const href = url.startsWith("www.") ? `https://${url}` : url;
11779
- spans.push({ text: url, href });
11780
- } else if (match[9]) spans.push({ text: match[9] });
11781
- }
11782
- return spans;
11783
- }
11784
- function truncateBareUrl(url) {
11785
- const stripped = url.replace(/^https?:\/\//, "").replace(/^www\./, "");
11786
- if (stripped.length <= BARE_URL_MAX_DISPLAY) return stripped;
11787
- return stripped.slice(0, BARE_URL_MAX_DISPLAY - 1) + "\u2026";
11788
- }
11789
- function renderInlineText(textEl, text, palette, fontSize) {
11790
- const spans = parseInlineMarkdown(text);
11791
- for (const span of spans) {
11792
- if (span.href) {
11793
- const isBareUrl = span.text === span.href || `https://${span.text}` === span.href;
11794
- const display = isBareUrl ? truncateBareUrl(span.text) : span.text;
11795
- const a = textEl.append("a").attr("href", span.href);
11796
- a.append("tspan").text(display).attr("fill", palette.primary).style("text-decoration", "underline");
11797
- } else {
11798
- const tspan = textEl.append("tspan").text(span.text);
11799
- if (span.bold) tspan.attr("font-weight", "bold");
11800
- if (span.italic) tspan.attr("font-style", "italic");
11801
- if (span.code) {
11802
- tspan.attr("font-family", "monospace");
11803
- if (fontSize) tspan.attr("font-size", fontSize - 1);
11804
- }
11805
- }
11806
- }
11807
- }
11808
11969
  function wrapTextLines(text, maxChars) {
11809
11970
  const rawLines = text.split("\n");
11810
11971
  const wrapped = [];
@@ -11985,8 +12146,12 @@ function buildRenderSequence(messages) {
11985
12146
  to: msg.to,
11986
12147
  label: msg.label,
11987
12148
  messageIndex: mi,
11988
- ...msg.async ? { async: true } : {}
12149
+ ...msg.async ? { async: true } : {},
12150
+ ...msg.bidirectional ? { bidirectional: true } : {}
11989
12151
  });
12152
+ if (msg.bidirectional) {
12153
+ continue;
12154
+ }
11990
12155
  if (msg.async) {
11991
12156
  continue;
11992
12157
  }
@@ -12478,6 +12643,14 @@ function renderSequenceDiagram(container, parsed, palette, isDark, _onNavigateTo
12478
12643
  "points",
12479
12644
  `0,0 ${ARROWHEAD_SIZE},${ARROWHEAD_SIZE / 2} 0,${ARROWHEAD_SIZE}`
12480
12645
  ).attr("fill", "none").attr("stroke", palette.text).attr("stroke-width", 1.2);
12646
+ defs.append("marker").attr("id", "seq-arrowhead-reverse").attr("viewBox", `0 0 ${ARROWHEAD_SIZE} ${ARROWHEAD_SIZE}`).attr("refX", 0).attr("refY", ARROWHEAD_SIZE / 2).attr("markerWidth", ARROWHEAD_SIZE).attr("markerHeight", ARROWHEAD_SIZE).attr("orient", "auto").append("polygon").attr(
12647
+ "points",
12648
+ `${ARROWHEAD_SIZE},0 0,${ARROWHEAD_SIZE / 2} ${ARROWHEAD_SIZE},${ARROWHEAD_SIZE}`
12649
+ ).attr("fill", palette.text);
12650
+ defs.append("marker").attr("id", "seq-arrowhead-async-reverse").attr("viewBox", `0 0 ${ARROWHEAD_SIZE} ${ARROWHEAD_SIZE}`).attr("refX", 0).attr("refY", ARROWHEAD_SIZE / 2).attr("markerWidth", ARROWHEAD_SIZE).attr("markerHeight", ARROWHEAD_SIZE).attr("orient", "auto").append("polyline").attr(
12651
+ "points",
12652
+ `${ARROWHEAD_SIZE},0 0,${ARROWHEAD_SIZE / 2} ${ARROWHEAD_SIZE},${ARROWHEAD_SIZE}`
12653
+ ).attr("fill", "none").attr("stroke", palette.text).attr("stroke-width", 1.2);
12481
12654
  if (title) {
12482
12655
  const titleEl = svg.append("text").attr("class", "chart-title").attr("x", svgWidth / 2).attr("y", 30).attr("text-anchor", "middle").attr("fill", palette.text).attr("font-size", 20).attr("font-weight", "bold").text(title);
12483
12656
  if (parsed.titleLineNumber) {
@@ -12758,10 +12931,17 @@ function renderSequenceDiagram(container, parsed, palette, isDark, _onNavigateTo
12758
12931
  const x1 = arrowEdgeX(step.from, i, goingRight ? "right" : "left");
12759
12932
  const x2 = arrowEdgeX(step.to, i, goingRight ? "left" : "right");
12760
12933
  const markerRef = step.async ? "url(#seq-arrowhead-async)" : "url(#seq-arrowhead)";
12761
- svg.append("line").attr("x1", x1).attr("y1", y).attr("x2", x2).attr("y2", y).attr("stroke", palette.text).attr("stroke-width", 1.2).attr("marker-end", markerRef).attr("class", "message-arrow").attr(
12934
+ const markerStartRef = step.bidirectional ? step.async ? "url(#seq-arrowhead-async-reverse)" : "url(#seq-arrowhead-reverse)" : null;
12935
+ const line7 = svg.append("line").attr("x1", x1).attr("y1", y).attr("x2", x2).attr("y2", y).attr("stroke", palette.text).attr("stroke-width", 1.2).attr("marker-end", markerRef).attr("class", "message-arrow").attr(
12762
12936
  "data-line-number",
12763
12937
  String(messages[step.messageIndex].lineNumber)
12764
12938
  ).attr("data-msg-index", String(step.messageIndex)).attr("data-step-index", String(i));
12939
+ if (markerStartRef) {
12940
+ line7.attr("marker-start", markerStartRef);
12941
+ }
12942
+ if (step.bidirectional && step.async) {
12943
+ line7.attr("stroke-dasharray", "6 4");
12944
+ }
12765
12945
  if (step.label) {
12766
12946
  const midX = (x1 + x2) / 2;
12767
12947
  const labelEl = svg.append("text").attr("x", midX).attr("y", y - 8).attr("text-anchor", "middle").attr("fill", palette.text).attr("font-size", 12).attr("class", "message-label").attr(
@@ -12949,12 +13129,13 @@ function renderParticipant(svg, participant, cx, cy, palette, isDark) {
12949
13129
  isActor ? PARTICIPANT_BOX_HEIGHT + 14 : PARTICIPANT_BOX_HEIGHT / 2 + 5
12950
13130
  ).attr("text-anchor", "middle").attr("fill", palette.text).attr("font-size", 13).attr("font-weight", 500).text(participant.label);
12951
13131
  }
12952
- var d3Selection8, PARTICIPANT_GAP, PARTICIPANT_BOX_WIDTH, PARTICIPANT_BOX_HEIGHT, TOP_MARGIN, TITLE_HEIGHT4, PARTICIPANT_Y_OFFSET, SERVICE_BORDER_RADIUS, MESSAGE_START_OFFSET, LIFELINE_TAIL, ARROWHEAD_SIZE, NOTE_MAX_W, NOTE_FOLD, NOTE_PAD_H, NOTE_PAD_V, NOTE_FONT_SIZE, NOTE_LINE_H, NOTE_GAP, NOTE_CHAR_W, NOTE_CHARS_PER_LINE, COLLAPSED_NOTE_H, COLLAPSED_NOTE_W, BARE_URL_MAX_DISPLAY, fill, stroke, SW, W, H;
13132
+ var d3Selection8, PARTICIPANT_GAP, PARTICIPANT_BOX_WIDTH, PARTICIPANT_BOX_HEIGHT, TOP_MARGIN, TITLE_HEIGHT4, PARTICIPANT_Y_OFFSET, SERVICE_BORDER_RADIUS, MESSAGE_START_OFFSET, LIFELINE_TAIL, ARROWHEAD_SIZE, NOTE_MAX_W, NOTE_FOLD, NOTE_PAD_H, NOTE_PAD_V, NOTE_FONT_SIZE, NOTE_LINE_H, NOTE_GAP, NOTE_CHAR_W, NOTE_CHARS_PER_LINE, COLLAPSED_NOTE_H, COLLAPSED_NOTE_W, fill, stroke, SW, W, H;
12953
13133
  var init_renderer7 = __esm({
12954
13134
  "src/sequence/renderer.ts"() {
12955
13135
  "use strict";
12956
13136
  d3Selection8 = __toESM(require("d3-selection"), 1);
12957
13137
  init_colors();
13138
+ init_inline_markdown();
12958
13139
  init_fonts();
12959
13140
  init_parser();
12960
13141
  PARTICIPANT_GAP = 160;
@@ -12978,7 +13159,6 @@ var init_renderer7 = __esm({
12978
13159
  NOTE_CHARS_PER_LINE = Math.floor((NOTE_MAX_W - NOTE_PAD_H * 2 - NOTE_FOLD) / NOTE_CHAR_W);
12979
13160
  COLLAPSED_NOTE_H = 20;
12980
13161
  COLLAPSED_NOTE_W = 40;
12981
- BARE_URL_MAX_DISPLAY = 35;
12982
13162
  fill = (palette, isDark) => mix8(palette.primary, isDark ? palette.surface : palette.bg, isDark ? 15 : 30);
12983
13163
  stroke = (palette) => palette.textMuted;
12984
13164
  SW = 1.5;
@@ -13108,7 +13288,7 @@ function parseD3(content, palette) {
13108
13288
  }
13109
13289
  continue;
13110
13290
  }
13111
- if (line7.startsWith("#") || line7.startsWith("//")) {
13291
+ if (line7.startsWith("//")) {
13112
13292
  continue;
13113
13293
  }
13114
13294
  if (result.type === "arc") {
@@ -16281,6 +16461,7 @@ __export(index_exports, {
16281
16461
  parseERDiagram: () => parseERDiagram,
16282
16462
  parseFlowchart: () => parseFlowchart,
16283
16463
  parseInitiativeStatus: () => parseInitiativeStatus,
16464
+ parseInlineMarkdown: () => parseInlineMarkdown,
16284
16465
  parseKanban: () => parseKanban,
16285
16466
  parseOrg: () => parseOrg,
16286
16467
  parseQuadrant: () => parseQuadrant,
@@ -16324,7 +16505,8 @@ __export(index_exports, {
16324
16505
  shade: () => shade,
16325
16506
  solarizedPalette: () => solarizedPalette,
16326
16507
  tint: () => tint,
16327
- tokyoNightPalette: () => tokyoNightPalette
16508
+ tokyoNightPalette: () => tokyoNightPalette,
16509
+ truncateBareUrl: () => truncateBareUrl
16328
16510
  });
16329
16511
  module.exports = __toCommonJS(index_exports);
16330
16512
  init_diagnostics();
@@ -16405,7 +16587,7 @@ function parseQuadrant(content) {
16405
16587
  for (let i = 0; i < lines.length; i++) {
16406
16588
  const line7 = lines[i].trim();
16407
16589
  const lineNumber = i + 1;
16408
- if (!line7 || line7.startsWith("#") || line7.startsWith("//")) continue;
16590
+ if (!line7 || line7.startsWith("//")) continue;
16409
16591
  if (/^chart\s*:/i.test(line7)) continue;
16410
16592
  const titleMatch = line7.match(/^title\s*:\s*(.+)/i);
16411
16593
  if (titleMatch) {
@@ -16535,6 +16717,7 @@ init_renderer3();
16535
16717
  init_parser3();
16536
16718
  init_layout3();
16537
16719
  init_renderer4();
16720
+ init_inline_markdown();
16538
16721
  init_parser4();
16539
16722
  init_layout();
16540
16723
  init_renderer();
@@ -16551,12 +16734,12 @@ init_collapse();
16551
16734
 
16552
16735
  // src/org/resolver.ts
16553
16736
  init_diagnostics();
16737
+ init_tag_groups();
16554
16738
  var MAX_DEPTH = 10;
16555
16739
  var IMPORT_RE = /^(\s+)import:\s+(.+\.dgmo)\s*$/i;
16556
16740
  var TAGS_RE = /^tags:\s+(.+\.dgmo)\s*$/i;
16557
16741
  var HEADER_RE = /^(chart|title)\s*:/i;
16558
- var OPTION_RE4 = /^[a-z][a-z0-9-]*\s*:/i;
16559
- var GROUP_HEADING_RE5 = /^##\s+/;
16742
+ var OPTION_RE2 = /^[a-z][a-z0-9-]*\s*:/i;
16560
16743
  function dirname(filePath) {
16561
16744
  const last = filePath.lastIndexOf("/");
16562
16745
  return last > 0 ? filePath.substring(0, last) : "/";
@@ -16577,9 +16760,9 @@ function extractTagGroups(lines) {
16577
16760
  let current = null;
16578
16761
  for (const line7 of lines) {
16579
16762
  const trimmed = line7.trim();
16580
- if (GROUP_HEADING_RE5.test(trimmed)) {
16581
- const nameMatch = trimmed.match(/^##\s+(.+?)(?:\s+alias\s+\w+)?(?:\s*\([^)]+\))?\s*$/);
16582
- const name = nameMatch ? nameMatch[1].trim().toLowerCase() : trimmed.substring(3).trim().toLowerCase();
16763
+ const headingMatch = matchTagBlockHeading(trimmed);
16764
+ if (headingMatch) {
16765
+ const name = headingMatch.name.toLowerCase();
16583
16766
  current = { name, lines: [line7] };
16584
16767
  blocks.push(current);
16585
16768
  } else if (current) {
@@ -16621,7 +16804,7 @@ function parseFileHeader(lines) {
16621
16804
  tagsDirective = tagsMatch[1].trim();
16622
16805
  continue;
16623
16806
  }
16624
- if (OPTION_RE4.test(trimmed) && !trimmed.startsWith("##") && !lines[i].match(/^\s/)) {
16807
+ if (OPTION_RE2.test(trimmed) && !isTagBlockHeading(trimmed) && !lines[i].match(/^\s/)) {
16625
16808
  const key = trimmed.split(":")[0].trim().toLowerCase();
16626
16809
  if (key !== "chart" && key !== "title" && !trimmed.includes("|")) {
16627
16810
  continue;
@@ -16651,7 +16834,7 @@ async function resolveFile(content, filePath, readFileFn, diagnostics, ancestorC
16651
16834
  headerLines.push(lines[i]);
16652
16835
  continue;
16653
16836
  }
16654
- if (GROUP_HEADING_RE5.test(trimmed)) continue;
16837
+ if (isTagBlockHeading(trimmed)) continue;
16655
16838
  if (lines[i] !== trimmed) continue;
16656
16839
  const tagsMatch = trimmed.match(TAGS_RE);
16657
16840
  if (tagsMatch) {
@@ -16683,7 +16866,7 @@ async function resolveFile(content, filePath, readFileFn, diagnostics, ancestorC
16683
16866
  const importMatch = line7.match(IMPORT_RE);
16684
16867
  if (!importMatch) {
16685
16868
  const trimmed = line7.trim();
16686
- if (GROUP_HEADING_RE5.test(trimmed) || inlineTagGroups.length > 0 && isTagGroupEntry(line7, bodyLines, i)) {
16869
+ if (isTagBlockHeading(trimmed) || inlineTagGroups.length > 0 && isTagGroupEntry(line7, bodyLines, i)) {
16687
16870
  continue;
16688
16871
  }
16689
16872
  resolvedBodyLines.push(line7);
@@ -16778,7 +16961,7 @@ function findBodyStart(lines) {
16778
16961
  if (inTagGroup) inTagGroup = false;
16779
16962
  continue;
16780
16963
  }
16781
- if (GROUP_HEADING_RE5.test(trimmed)) {
16964
+ if (isTagBlockHeading(trimmed)) {
16782
16965
  inTagGroup = true;
16783
16966
  continue;
16784
16967
  }
@@ -16790,7 +16973,7 @@ function findBodyStart(lines) {
16790
16973
  }
16791
16974
  if (HEADER_RE.test(trimmed)) continue;
16792
16975
  if (TAGS_RE.test(trimmed)) continue;
16793
- if (OPTION_RE4.test(trimmed) && !lines[i].match(/^\s/) && !trimmed.includes("|")) {
16976
+ if (OPTION_RE2.test(trimmed) && !isTagBlockHeading(trimmed) && !lines[i].match(/^\s/) && !trimmed.includes("|")) {
16794
16977
  const key = trimmed.split(":")[0].trim().toLowerCase();
16795
16978
  if (key !== "chart" && key !== "title") {
16796
16979
  continue;
@@ -16805,7 +16988,7 @@ function isTagGroupEntry(line7, allLines, index) {
16805
16988
  for (let i = index - 1; i >= 0; i--) {
16806
16989
  const prev = allLines[i].trim();
16807
16990
  if (prev === "" || prev.startsWith("//")) continue;
16808
- if (GROUP_HEADING_RE5.test(prev)) return true;
16991
+ if (isTagBlockHeading(prev)) return true;
16809
16992
  if (allLines[i].match(/^\s+/)) continue;
16810
16993
  return false;
16811
16994
  }
@@ -16960,6 +17143,7 @@ init_branding();
16960
17143
  parseERDiagram,
16961
17144
  parseFlowchart,
16962
17145
  parseInitiativeStatus,
17146
+ parseInlineMarkdown,
16963
17147
  parseKanban,
16964
17148
  parseOrg,
16965
17149
  parseQuadrant,
@@ -17003,6 +17187,7 @@ init_branding();
17003
17187
  shade,
17004
17188
  solarizedPalette,
17005
17189
  tint,
17006
- tokyoNightPalette
17190
+ tokyoNightPalette,
17191
+ truncateBareUrl
17007
17192
  });
17008
17193
  //# sourceMappingURL=index.cjs.map