@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.js CHANGED
@@ -1527,6 +1527,96 @@ var init_participant_inference = __esm({
1527
1527
  }
1528
1528
  });
1529
1529
 
1530
+ // src/utils/arrows.ts
1531
+ function parseArrow(line7) {
1532
+ const patterns = [
1533
+ { re: BIDI_SYNC_LABELED_RE, async: false, bidirectional: true },
1534
+ { re: BIDI_ASYNC_LABELED_RE, async: true, bidirectional: true },
1535
+ { re: SYNC_LABELED_RE, async: false, bidirectional: false },
1536
+ { re: ASYNC_LABELED_RE, async: true, bidirectional: false }
1537
+ ];
1538
+ for (const { re, async: isAsync, bidirectional } of patterns) {
1539
+ const m = line7.match(re);
1540
+ if (!m) continue;
1541
+ const label = m[2].trim();
1542
+ if (!label) return null;
1543
+ for (const arrow of ARROW_CHARS) {
1544
+ if (label.includes(arrow)) {
1545
+ return {
1546
+ error: "Arrow characters (->, ~>) are not allowed inside labels"
1547
+ };
1548
+ }
1549
+ }
1550
+ return {
1551
+ from: m[1],
1552
+ to: m[3],
1553
+ label,
1554
+ async: isAsync,
1555
+ bidirectional
1556
+ };
1557
+ }
1558
+ return null;
1559
+ }
1560
+ var BIDI_SYNC_LABELED_RE, BIDI_ASYNC_LABELED_RE, SYNC_LABELED_RE, ASYNC_LABELED_RE, ARROW_CHARS;
1561
+ var init_arrows = __esm({
1562
+ "src/utils/arrows.ts"() {
1563
+ "use strict";
1564
+ BIDI_SYNC_LABELED_RE = /^(\S+)\s+<-(.+)->\s+(\S+)$/;
1565
+ BIDI_ASYNC_LABELED_RE = /^(\S+)\s+<~(.+)~>\s+(\S+)$/;
1566
+ SYNC_LABELED_RE = /^(\S+)\s+-(.+)->\s+(\S+)$/;
1567
+ ASYNC_LABELED_RE = /^(\S+)\s+~(.+)~>\s+(\S+)$/;
1568
+ ARROW_CHARS = ["->", "~>", "<->", "<~>"];
1569
+ }
1570
+ });
1571
+
1572
+ // src/utils/parsing.ts
1573
+ function measureIndent(line7) {
1574
+ let indent = 0;
1575
+ for (const ch of line7) {
1576
+ if (ch === " ") indent++;
1577
+ else if (ch === " ") indent += 4;
1578
+ else break;
1579
+ }
1580
+ return indent;
1581
+ }
1582
+ function extractColor(label, palette) {
1583
+ const m = label.match(COLOR_SUFFIX_RE);
1584
+ if (!m) return { label };
1585
+ const colorName = m[1].trim();
1586
+ return {
1587
+ label: label.substring(0, m.index).trim(),
1588
+ color: resolveColor(colorName, palette)
1589
+ };
1590
+ }
1591
+ function parsePipeMetadata(segments, aliasMap = /* @__PURE__ */ new Map()) {
1592
+ const metadata = {};
1593
+ for (let j = 1; j < segments.length; j++) {
1594
+ for (const part of segments[j].split(",")) {
1595
+ const trimmedPart = part.trim();
1596
+ if (!trimmedPart) continue;
1597
+ const colonIdx = trimmedPart.indexOf(":");
1598
+ if (colonIdx > 0) {
1599
+ const rawKey = trimmedPart.substring(0, colonIdx).trim().toLowerCase();
1600
+ const key = aliasMap.get(rawKey) ?? rawKey;
1601
+ const value = trimmedPart.substring(colonIdx + 1).trim();
1602
+ metadata[key] = value;
1603
+ }
1604
+ }
1605
+ }
1606
+ return metadata;
1607
+ }
1608
+ var COLOR_SUFFIX_RE, CHART_TYPE_RE, TITLE_RE, OPTION_RE;
1609
+ var init_parsing = __esm({
1610
+ "src/utils/parsing.ts"() {
1611
+ "use strict";
1612
+ init_colors();
1613
+ COLOR_SUFFIX_RE = /\(([^)]+)\)\s*$/;
1614
+ CHART_TYPE_RE = /^chart\s*:\s*(.+)/i;
1615
+ TITLE_RE = /^title\s*:\s*(.+)/i;
1616
+ OPTION_RE = /^([a-z][a-z0-9-]*)\s*:\s*(.+)$/i;
1617
+ }
1618
+ });
1619
+
1530
1620
  // src/sequence/parser.ts
1531
1621
  var parser_exports = {};
1532
1622
  __export(parser_exports, {
@@ -1568,15 +1658,6 @@ function parseReturnLabel(rawLabel) {
1568
1658
  }
1569
1659
  return { label: rawLabel };
1570
1660
  }
1571
- function measureIndent(line7) {
1572
- let indent = 0;
1573
- for (const ch of line7) {
1574
- if (ch === " ") indent++;
1575
- else if (ch === " ") indent += 4;
1576
- else break;
1577
- }
1578
- return indent;
1579
- }
1580
1661
  function parseSequenceDgmo(content) {
1581
1662
  const result = {
1582
1663
  title: null,
@@ -1798,6 +1879,86 @@ function parseSequenceDgmo(content) {
1798
1879
  pushError(lineNumber, "Use ~> for async messages: A ~> B: message");
1799
1880
  continue;
1800
1881
  }
1882
+ const labeledArrow = parseArrow(trimmed);
1883
+ if (labeledArrow && "error" in labeledArrow) {
1884
+ pushError(lineNumber, labeledArrow.error);
1885
+ continue;
1886
+ }
1887
+ if (labeledArrow) {
1888
+ contentStarted = true;
1889
+ const { from, to, label, async: isAsync2, bidirectional } = labeledArrow;
1890
+ lastMsgFrom = from;
1891
+ const msg = {
1892
+ from,
1893
+ to,
1894
+ label,
1895
+ returnLabel: void 0,
1896
+ lineNumber,
1897
+ ...isAsync2 ? { async: true } : {},
1898
+ ...bidirectional ? { bidirectional: true } : {}
1899
+ };
1900
+ result.messages.push(msg);
1901
+ currentContainer().push(msg);
1902
+ if (!result.participants.some((p) => p.id === from)) {
1903
+ result.participants.push({
1904
+ id: from,
1905
+ label: from,
1906
+ type: inferParticipantType(from),
1907
+ lineNumber
1908
+ });
1909
+ }
1910
+ if (!result.participants.some((p) => p.id === to)) {
1911
+ result.participants.push({
1912
+ id: to,
1913
+ label: to,
1914
+ type: inferParticipantType(to),
1915
+ lineNumber
1916
+ });
1917
+ }
1918
+ continue;
1919
+ }
1920
+ const bidiSyncMatch = trimmed.match(
1921
+ /^(\S+)\s*<->\s*([^\s:]+)\s*(?::\s*(.+))?$/
1922
+ );
1923
+ const bidiAsyncMatch = trimmed.match(
1924
+ /^(\S+)\s*<~>\s*([^\s:]+)\s*(?::\s*(.+))?$/
1925
+ );
1926
+ const bidiMatch = bidiSyncMatch || bidiAsyncMatch;
1927
+ if (bidiMatch) {
1928
+ contentStarted = true;
1929
+ const from = bidiMatch[1];
1930
+ const to = bidiMatch[2];
1931
+ lastMsgFrom = from;
1932
+ const rawLabel = bidiMatch[3]?.trim() || "";
1933
+ const isBidiAsync = !!bidiAsyncMatch;
1934
+ const msg = {
1935
+ from,
1936
+ to,
1937
+ label: rawLabel,
1938
+ lineNumber,
1939
+ bidirectional: true,
1940
+ ...isBidiAsync ? { async: true } : {}
1941
+ };
1942
+ result.messages.push(msg);
1943
+ currentContainer().push(msg);
1944
+ if (!result.participants.some((p) => p.id === from)) {
1945
+ result.participants.push({
1946
+ id: from,
1947
+ label: from,
1948
+ type: inferParticipantType(from),
1949
+ lineNumber
1950
+ });
1951
+ }
1952
+ if (!result.participants.some((p) => p.id === to)) {
1953
+ result.participants.push({
1954
+ id: to,
1955
+ label: to,
1956
+ type: inferParticipantType(to),
1957
+ lineNumber
1958
+ });
1959
+ }
1960
+ continue;
1961
+ }
1801
1962
  let isAsync = false;
1802
1963
  const asyncArrowMatch = trimmed.match(
1803
1964
  /^(\S+)\s*~>\s*([^\s:]+)\s*(?::\s*(.+))?$/
@@ -2035,6 +2196,8 @@ var init_parser = __esm({
2035
2196
  "use strict";
2036
2197
  init_participant_inference();
2037
2198
  init_diagnostics();
2199
+ init_arrows();
2200
+ init_parsing();
2038
2201
  VALID_PARTICIPANT_TYPES = /* @__PURE__ */ new Set([
2039
2202
  "service",
2040
2203
  "database",
@@ -2050,7 +2213,7 @@ var init_parser = __esm({
2050
2213
  POSITION_ONLY_PATTERN = /^(\S+)\s+position\s+(-?\d+)$/i;
2051
2214
  GROUP_HEADING_PATTERN = /^##\s+(.+?)(?:\(([^)]+)\))?\s*$/;
2052
2215
  SECTION_PATTERN = /^==\s+(.+?)(?:\s*==)?\s*$/;
2053
- ARROW_PATTERN = /\S+\s*(?:->|~>)\s*\S+/;
2216
+ ARROW_PATTERN = /\S+\s*(?:<->|<~>|->|~>|-\S+->|~\S+~>|<-\S+->|<~\S+~>)\s*\S+/;
2054
2217
  ARROW_RETURN_PATTERN = /^(.+?)\s*<-\s*(.+)$/;
2055
2218
  UML_RETURN_PATTERN = /^(\w+\([^)]*\))\s*:\s*(.+)$/;
2056
2219
  NOTE_SINGLE = /^note(?:\s+(right|left)\s+of\s+(\S+))?\s*:\s*(.+)$/i;
@@ -2064,27 +2227,9 @@ __export(flowchart_parser_exports, {
2064
2227
  looksLikeFlowchart: () => looksLikeFlowchart,
2065
2228
  parseFlowchart: () => parseFlowchart
2066
2229
  });
2067
- function measureIndent2(line7) {
2068
- let indent = 0;
2069
- for (const ch of line7) {
2070
- if (ch === " ") indent++;
2071
- else if (ch === " ") indent += 4;
2072
- else break;
2073
- }
2074
- return indent;
2075
- }
2076
2230
  function nodeId(shape, label) {
2077
2231
  return `${shape}:${label.toLowerCase().trim()}`;
2078
2232
  }
2079
- function extractColor(label, palette) {
2080
- const m = label.match(COLOR_SUFFIX_RE);
2081
- if (!m) return { label };
2082
- const colorName = m[1].trim();
2083
- return {
2084
- label: label.substring(0, m.index).trim(),
2085
- color: resolveColor(colorName, palette)
2086
- };
2087
- }
2088
2233
  function parseNodeRef(text, palette) {
2089
2234
  const t = text.trim();
2090
2235
  if (!t) return null;
@@ -2199,7 +2344,8 @@ function parseFlowchart(content, palette) {
2199
2344
  nodes: [],
2200
2345
  edges: [],
2201
2346
  options: {},
2202
- diagnostics: []
2347
+ diagnostics: [],
2348
+ error: null
2203
2349
  };
2204
2350
  const fail = (line7, message) => {
2205
2351
  const diag = makeDgmoError(line7, message);
@@ -2301,7 +2447,7 @@ function parseFlowchart(content, palette) {
2301
2447
  const raw = lines[i];
2302
2448
  const trimmed = raw.trim();
2303
2449
  const lineNumber = i + 1;
2304
- const indent = measureIndent2(raw);
2450
+ const indent = measureIndent(raw);
2305
2451
  if (!trimmed) continue;
2306
2452
  if (trimmed.startsWith("//")) continue;
2307
2453
  const groupMatch = trimmed.match(GROUP_HEADING_RE);
@@ -2378,13 +2524,13 @@ function looksLikeFlowchart(content) {
2378
2524
  /->[ \t]*[\[(<\/]/.test(content);
2379
2525
  return shapeNearArrow;
2380
2526
  }
2381
- var COLOR_SUFFIX_RE, GROUP_HEADING_RE;
2527
+ var GROUP_HEADING_RE;
2382
2528
  var init_flowchart_parser = __esm({
2383
2529
  "src/graph/flowchart-parser.ts"() {
2384
2530
  "use strict";
2385
2531
  init_colors();
2386
2532
  init_diagnostics();
2387
- COLOR_SUFFIX_RE = /\(([^)]+)\)\s*$/;
2533
+ init_parsing();
2388
2534
  GROUP_HEADING_RE = /^##\s+(.+?)(?:\(([^)]+)\))?\s*$/;
2389
2535
  }
2390
2536
  });
@@ -2395,15 +2541,6 @@ __export(parser_exports2, {
2395
2541
  looksLikeClassDiagram: () => looksLikeClassDiagram,
2396
2542
  parseClassDiagram: () => parseClassDiagram
2397
2543
  });
2398
- function measureIndent3(line7) {
2399
- let indent = 0;
2400
- for (const ch of line7) {
2401
- if (ch === " ") indent++;
2402
- else if (ch === " ") indent += 4;
2403
- else break;
2404
- }
2405
- return indent;
2406
- }
2407
2544
  function classId(name) {
2408
2545
  return name.toLowerCase().trim();
2409
2546
  }
@@ -2478,7 +2615,8 @@ function parseClassDiagram(content, palette) {
2478
2615
  classes: [],
2479
2616
  relationships: [],
2480
2617
  options: {},
2481
- diagnostics: []
2618
+ diagnostics: [],
2619
+ error: null
2482
2620
  };
2483
2621
  const fail = (line7, message) => {
2484
2622
  const diag = makeDgmoError(line7, message);
@@ -2507,7 +2645,7 @@ function parseClassDiagram(content, palette) {
2507
2645
  const raw = lines[i];
2508
2646
  const trimmed = raw.trim();
2509
2647
  const lineNumber = i + 1;
2510
- const indent = measureIndent3(raw);
2648
+ const indent = measureIndent(raw);
2511
2649
  if (!trimmed) {
2512
2650
  if (indent === 0) currentClass = null;
2513
2651
  continue;
@@ -2550,23 +2688,6 @@ function parseClassDiagram(content, palette) {
2550
2688
  }
2551
2689
  currentClass = null;
2552
2690
  contentStarted = true;
2553
- const relKeyword = trimmed.match(REL_KEYWORD_RE);
2554
- if (relKeyword) {
2555
- const sourceName = relKeyword[1];
2556
- const keyword = relKeyword[2].toLowerCase();
2557
- const targetName = relKeyword[3];
2558
- const label = relKeyword[4]?.trim();
2559
- getOrCreateClass(sourceName, lineNumber);
2560
- getOrCreateClass(targetName, lineNumber);
2561
- result.relationships.push({
2562
- source: classId(sourceName),
2563
- target: classId(targetName),
2564
- type: KEYWORD_TO_TYPE[keyword],
2565
- ...label && { label },
2566
- lineNumber
2567
- });
2568
- continue;
2569
- }
2570
2691
  const relArrow = trimmed.match(REL_ARROW_RE);
2571
2692
  if (relArrow) {
2572
2693
  const sourceName = relArrow[1];
@@ -2587,13 +2708,24 @@ function parseClassDiagram(content, palette) {
2587
2708
  const classDecl = trimmed.match(CLASS_DECL_RE);
2588
2709
  if (classDecl) {
2589
2710
  const name = classDecl[1];
2590
- const modifier = classDecl[2];
2591
- const colorName = classDecl[3]?.trim();
2711
+ const relKeyword = classDecl[2];
2712
+ const parentName = classDecl[3];
2713
+ const modifier = classDecl[4];
2714
+ const colorName = classDecl[5]?.trim();
2592
2715
  const color = colorName ? resolveColor(colorName, palette) : void 0;
2593
2716
  const node = getOrCreateClass(name, lineNumber);
2594
2717
  if (modifier) node.modifier = modifier;
2595
2718
  if (color) node.color = color;
2596
2719
  node.lineNumber = lineNumber;
2720
+ if (relKeyword && parentName) {
2721
+ getOrCreateClass(parentName, lineNumber);
2722
+ result.relationships.push({
2723
+ source: classId(name),
2724
+ target: classId(parentName),
2725
+ type: relKeyword,
2726
+ lineNumber
2727
+ });
2728
+ }
2597
2729
  currentClass = node;
2598
2730
  continue;
2599
2731
  }
@@ -2627,14 +2759,15 @@ function looksLikeClassDiagram(content) {
2627
2759
  const trimmed = line7.trim();
2628
2760
  if (!trimmed || trimmed.startsWith("//")) continue;
2629
2761
  if (/^(chart|title)\s*:/i.test(trimmed)) continue;
2630
- const indent = measureIndent3(line7);
2762
+ const indent = measureIndent(line7);
2631
2763
  if (indent === 0) {
2632
2764
  if (/^[A-Z][A-Za-z0-9_]*\s+\[(abstract|interface|enum)\]/i.test(trimmed)) {
2633
2765
  hasModifier = true;
2634
2766
  hasClassDecl = true;
2635
2767
  }
2636
- if (REL_KEYWORD_RE.test(trimmed)) {
2768
+ if (/^[A-Z][A-Za-z0-9_]*\s+(extends|implements)\s+[A-Z]/.test(trimmed)) {
2637
2769
  hasRelationship = true;
2770
+ hasClassDecl = true;
2638
2771
  }
2639
2772
  if (REL_ARROW_RE.test(trimmed)) {
2640
2773
  hasRelationship = true;
@@ -2652,26 +2785,19 @@ function looksLikeClassDiagram(content) {
2652
2785
  if (hasRelationship && hasClassDecl && hasIndentedMember) return true;
2653
2786
  return false;
2654
2787
  }
2655
- 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;
2788
+ var CLASS_DECL_RE, REL_ARROW_RE, VISIBILITY_RE, STATIC_SUFFIX_RE, METHOD_RE, FIELD_RE, ARROW_TO_TYPE;
2656
2789
  var init_parser2 = __esm({
2657
2790
  "src/class/parser.ts"() {
2658
2791
  "use strict";
2659
2792
  init_colors();
2660
2793
  init_diagnostics();
2661
- CLASS_DECL_RE = /^([A-Z][A-Za-z0-9_]*)(?:\s+\[(abstract|interface|enum)\])?(?:\s+\(([^)]+)\))?\s*$/;
2662
- REL_KEYWORD_RE = /^([A-Z][A-Za-z0-9_]*)\s+(extends|implements|contains|has|uses)\s+([A-Z][A-Za-z0-9_]*)(?:\s*:\s*(.+))?$/;
2794
+ init_parsing();
2795
+ 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*$/;
2663
2796
  REL_ARROW_RE = /^([A-Z][A-Za-z0-9_]*)\s+(--\|>|\.\.\|>|\*--|o--|\.\.\>|->)\s+([A-Z][A-Za-z0-9_]*)(?:\s*:\s*(.+))?$/;
2664
2797
  VISIBILITY_RE = /^([+\-#])\s*/;
2665
2798
  STATIC_SUFFIX_RE = /\{static\}\s*$/;
2666
2799
  METHOD_RE = /^(.+?)\(([^)]*)\)(?:\s*:\s*(.+))?$/;
2667
2800
  FIELD_RE = /^(.+?)\s*:\s*(.+)$/;
2668
- KEYWORD_TO_TYPE = {
2669
- extends: "extends",
2670
- implements: "implements",
2671
- contains: "composes",
2672
- has: "aggregates",
2673
- uses: "depends"
2674
- };
2675
2801
  ARROW_TO_TYPE = {
2676
2802
  "--|>": "extends",
2677
2803
  "..|>": "implements",
@@ -2689,22 +2815,14 @@ __export(parser_exports3, {
2689
2815
  looksLikeERDiagram: () => looksLikeERDiagram,
2690
2816
  parseERDiagram: () => parseERDiagram
2691
2817
  });
2692
- function measureIndent4(line7) {
2693
- let indent = 0;
2694
- for (const ch of line7) {
2695
- if (ch === " ") indent++;
2696
- else if (ch === " ") indent += 4;
2697
- else break;
2698
- }
2699
- return indent;
2700
- }
2701
2818
  function tableId(name) {
2702
2819
  return name.toLowerCase().trim();
2703
2820
  }
2704
2821
  function parseCardSide(token) {
2705
- return CARD_WORD[token.toLowerCase()] ?? null;
2822
+ if (token === "1" || token === "*" || token === "?") return token;
2823
+ return null;
2706
2824
  }
2707
- function parseRelationship(trimmed) {
2825
+ function parseRelationship(trimmed, lineNumber, pushError) {
2708
2826
  const sym = trimmed.match(REL_SYMBOLIC_RE);
2709
2827
  if (sym) {
2710
2828
  const fromCard = parseCardSide(sym[2]);
@@ -2719,19 +2837,15 @@ function parseRelationship(trimmed) {
2719
2837
  };
2720
2838
  }
2721
2839
  }
2722
- const kw = trimmed.match(REL_KEYWORD_RE2);
2840
+ const kw = trimmed.match(REL_KEYWORD_RE);
2723
2841
  if (kw) {
2724
- const fromCard = parseCardSide(kw[2]);
2725
- const toCard = parseCardSide(kw[3]);
2726
- if (fromCard && toCard) {
2727
- return {
2728
- source: kw[1],
2729
- target: kw[4],
2730
- from: fromCard,
2731
- to: toCard,
2732
- label: kw[5]?.trim()
2733
- };
2734
- }
2842
+ const fromSym = KEYWORD_TO_SYMBOL[kw[2].toLowerCase()] ?? kw[2];
2843
+ const toSym = KEYWORD_TO_SYMBOL[kw[3].toLowerCase()] ?? kw[3];
2844
+ pushError(
2845
+ lineNumber,
2846
+ `Use symbolic cardinality (1--*, ?--1, *--*) instead of "${kw[2]}-to-${kw[3]}". Example: ${kw[1]} ${fromSym}--${toSym} ${kw[4]}`
2847
+ );
2848
+ return null;
2735
2849
  }
2736
2850
  return null;
2737
2851
  }
@@ -2751,7 +2865,8 @@ function parseERDiagram(content, palette) {
2751
2865
  options: {},
2752
2866
  tables: [],
2753
2867
  relationships: [],
2754
- diagnostics: []
2868
+ diagnostics: [],
2869
+ error: null
2755
2870
  };
2756
2871
  const fail = (line7, message) => {
2757
2872
  const diag = makeDgmoError(line7, message);
@@ -2759,6 +2874,11 @@ function parseERDiagram(content, palette) {
2759
2874
  result.error = formatDgmoError(diag);
2760
2875
  return result;
2761
2876
  };
2877
+ const pushError = (line7, message) => {
2878
+ const diag = makeDgmoError(line7, message);
2879
+ result.diagnostics.push(diag);
2880
+ if (!result.error) result.error = formatDgmoError(diag);
2881
+ };
2762
2882
  const tableMap = /* @__PURE__ */ new Map();
2763
2883
  let currentTable = null;
2764
2884
  let contentStarted = false;
@@ -2780,7 +2900,7 @@ function parseERDiagram(content, palette) {
2780
2900
  const raw = lines[i];
2781
2901
  const trimmed = raw.trim();
2782
2902
  const lineNumber = i + 1;
2783
- const indent = measureIndent4(raw);
2903
+ const indent = measureIndent(raw);
2784
2904
  if (!trimmed) {
2785
2905
  if (indent === 0) currentTable = null;
2786
2906
  continue;
@@ -2829,7 +2949,7 @@ function parseERDiagram(content, palette) {
2829
2949
  }
2830
2950
  currentTable = null;
2831
2951
  contentStarted = true;
2832
- const rel = parseRelationship(trimmed);
2952
+ const rel = parseRelationship(trimmed, lineNumber, pushError);
2833
2953
  if (rel) {
2834
2954
  getOrCreateTable(rel.source, lineNumber);
2835
2955
  getOrCreateTable(rel.target, lineNumber);
@@ -2882,7 +3002,7 @@ function looksLikeERDiagram(content) {
2882
3002
  const trimmed = line7.trim();
2883
3003
  if (!trimmed || trimmed.startsWith("//")) continue;
2884
3004
  if (/^(chart|title|notation)\s*:/i.test(trimmed)) continue;
2885
- const indent = measureIndent4(line7);
3005
+ const indent = measureIndent(line7);
2886
3006
  if (indent > 0) {
2887
3007
  if (/\[(pk|fk)\]/i.test(trimmed)) {
2888
3008
  hasConstraint = true;
@@ -2891,7 +3011,7 @@ function looksLikeERDiagram(content) {
2891
3011
  if (TABLE_DECL_RE.test(trimmed)) {
2892
3012
  hasTableDecl = true;
2893
3013
  }
2894
- if (REL_SYMBOLIC_RE.test(trimmed) || REL_KEYWORD_RE2.test(trimmed)) {
3014
+ if (REL_SYMBOLIC_RE.test(trimmed)) {
2895
3015
  hasRelationship = true;
2896
3016
  }
2897
3017
  }
@@ -2900,12 +3020,13 @@ function looksLikeERDiagram(content) {
2900
3020
  if (hasRelationship && hasTableDecl && hasConstraint) return true;
2901
3021
  return false;
2902
3022
  }
2903
- var TABLE_DECL_RE, COLUMN_RE, CONSTRAINT_MAP, CARD_WORD, REL_SYMBOLIC_RE, REL_KEYWORD_RE2;
3023
+ var TABLE_DECL_RE, COLUMN_RE, CONSTRAINT_MAP, REL_SYMBOLIC_RE, REL_KEYWORD_RE, KEYWORD_TO_SYMBOL;
2904
3024
  var init_parser3 = __esm({
2905
3025
  "src/er/parser.ts"() {
2906
3026
  "use strict";
2907
3027
  init_colors();
2908
3028
  init_diagnostics();
3029
+ init_parsing();
2909
3030
  TABLE_DECL_RE = /^([a-zA-Z_]\w*)(?:\s+\(([^)]+)\))?\s*$/;
2910
3031
  COLUMN_RE = /^(\w+)(?:\s*:\s*(\w[\w()]*(?:\s*\[\])?))?(?:\s+\[([^\]]+)\])?\s*$/;
2911
3032
  CONSTRAINT_MAP = {
@@ -2914,16 +3035,13 @@ var init_parser3 = __esm({
2914
3035
  unique: "unique",
2915
3036
  nullable: "nullable"
2916
3037
  };
2917
- CARD_WORD = {
3038
+ REL_SYMBOLIC_RE = /^([a-zA-Z_]\w*)\s+([1*?])\s*-{1,2}\s*([1*?])\s+([a-zA-Z_]\w*)(?:\s*:\s*(.+))?$/;
3039
+ REL_KEYWORD_RE = /^([a-zA-Z_]\w*)\s+(one|many|zero)[- ]to[- ](one|many|zero)\s+([a-zA-Z_]\w*)(?:\s*:\s*(.+))?$/i;
3040
+ KEYWORD_TO_SYMBOL = {
2918
3041
  one: "1",
2919
3042
  many: "*",
2920
- "1": "1",
2921
- "*": "*",
2922
- "?": "?",
2923
3043
  zero: "?"
2924
3044
  };
2925
- REL_SYMBOLIC_RE = /^([a-zA-Z_]\w*)\s+([1*?])\s*-{1,2}\s*([1*?])\s+([a-zA-Z_]\w*)(?:\s*:\s*(.+))?$/;
2926
- 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;
2927
3045
  }
2928
3046
  });
2929
3047
 
@@ -2933,7 +3051,8 @@ function parseChart(content, palette) {
2933
3051
  const result = {
2934
3052
  type: "bar",
2935
3053
  data: [],
2936
- diagnostics: []
3054
+ diagnostics: [],
3055
+ error: null
2937
3056
  };
2938
3057
  const fail = (line7, message) => {
2939
3058
  const diag = makeDgmoError(line7, message);
@@ -2946,7 +3065,7 @@ function parseChart(content, palette) {
2946
3065
  const lineNumber = i + 1;
2947
3066
  if (!trimmed) continue;
2948
3067
  if (/^#{2,}\s+/.test(trimmed)) continue;
2949
- if (trimmed.startsWith("#") || trimmed.startsWith("//")) continue;
3068
+ if (trimmed.startsWith("//")) continue;
2950
3069
  const colonIndex = trimmed.indexOf(":");
2951
3070
  if (colonIndex === -1) continue;
2952
3071
  const key = trimmed.substring(0, colonIndex).trim().toLowerCase();
@@ -3105,7 +3224,8 @@ function parseEChart(content, palette) {
3105
3224
  const result = {
3106
3225
  type: "scatter",
3107
3226
  data: [],
3108
- diagnostics: []
3227
+ diagnostics: [],
3228
+ error: null
3109
3229
  };
3110
3230
  let currentCategory = "Default";
3111
3231
  for (let i = 0; i < lines.length; i++) {
@@ -3125,7 +3245,7 @@ function parseEChart(content, palette) {
3125
3245
  currentCategory = catName;
3126
3246
  continue;
3127
3247
  }
3128
- if (trimmed.startsWith("#") || trimmed.startsWith("//")) continue;
3248
+ if (trimmed.startsWith("//")) continue;
3129
3249
  const categoryMatch = trimmed.match(/^\[(.+)\]$/);
3130
3250
  if (categoryMatch) {
3131
3251
  currentCategory = categoryMatch[1].trim();
@@ -3754,7 +3874,7 @@ function buildScatterOption(parsed, palette, textColor, axisLineColor, gridOpaci
3754
3874
  }
3755
3875
  },
3756
3876
  grid: {
3757
- left: parsed.ylabel ? "5%" : "3%",
3877
+ left: parsed.ylabel ? "12%" : "3%",
3758
3878
  right: "4%",
3759
3879
  bottom: hasCategories ? "15%" : parsed.xlabel ? "10%" : "3%",
3760
3880
  top: parsed.title ? "15%" : "5%",
@@ -4026,17 +4146,26 @@ function resolveAxisLabels(parsed) {
4026
4146
  yLabel: parsed.ylabel ?? (isHorizontal ? void 0 : parsed.label)
4027
4147
  };
4028
4148
  }
4029
- function makeGridAxis(type, textColor, axisLineColor, splitLineColor, gridOpacity, label, data) {
4149
+ function makeGridAxis(type, textColor, axisLineColor, splitLineColor, gridOpacity, label, data, nameGapOverride) {
4150
+ const defaultGap = type === "value" ? 75 : 40;
4030
4151
  return {
4031
4152
  type,
4032
4153
  ...data && { data },
4033
4154
  axisLine: { lineStyle: { color: axisLineColor } },
4034
- axisLabel: { color: textColor, fontSize: 16, fontFamily: FONT_FAMILY },
4155
+ axisLabel: {
4156
+ color: textColor,
4157
+ fontSize: type === "category" && data ? data.length > 10 ? 11 : data.length > 5 ? 12 : 16 : 16,
4158
+ fontFamily: FONT_FAMILY,
4159
+ ...type === "category" && {
4160
+ interval: 0,
4161
+ formatter: (value) => value.replace(/([a-z])([A-Z])/g, "$1\n$2").replace(/ /g, "\n")
4162
+ }
4163
+ },
4035
4164
  splitLine: { lineStyle: { color: splitLineColor, opacity: gridOpacity } },
4036
4165
  ...label && {
4037
4166
  name: label,
4038
4167
  nameLocation: "middle",
4039
- nameGap: 40,
4168
+ nameGap: nameGapOverride ?? defaultGap,
4040
4169
  nameTextStyle: { color: textColor, fontSize: 18, fontFamily: FONT_FAMILY }
4041
4170
  }
4042
4171
  };
@@ -4091,7 +4220,8 @@ function buildBarOption(parsed, textColor, axisLineColor, splitLineColor, gridOp
4091
4220
  value: d.value,
4092
4221
  itemStyle: { color: d.color ?? colors[i % colors.length] }
4093
4222
  }));
4094
- const categoryAxis = makeGridAxis("category", textColor, axisLineColor, splitLineColor, gridOpacity, isHorizontal ? yLabel : xLabel, labels);
4223
+ const hCatGap = isHorizontal && yLabel ? Math.max(40, Math.max(...labels.map((l) => l.length)) * 8 + 16) : void 0;
4224
+ const categoryAxis = makeGridAxis("category", textColor, axisLineColor, splitLineColor, gridOpacity, isHorizontal ? yLabel : xLabel, labels, hCatGap);
4095
4225
  const valueAxis = makeGridAxis("value", textColor, axisLineColor, splitLineColor, gridOpacity, isHorizontal ? xLabel : yLabel);
4096
4226
  return {
4097
4227
  backgroundColor: "transparent",
@@ -4103,7 +4233,7 @@ function buildBarOption(parsed, textColor, axisLineColor, splitLineColor, gridOp
4103
4233
  axisPointer: { type: "shadow" }
4104
4234
  },
4105
4235
  grid: {
4106
- left: yLabel ? "5%" : "3%",
4236
+ left: yLabel ? "12%" : "3%",
4107
4237
  right: "4%",
4108
4238
  bottom: xLabel ? "10%" : "3%",
4109
4239
  top: parsed.title ? "15%" : "5%",
@@ -4138,7 +4268,7 @@ function buildLineOption(parsed, palette, textColor, axisLineColor, splitLineCol
4138
4268
  axisPointer: { type: "line" }
4139
4269
  },
4140
4270
  grid: {
4141
- left: yLabel ? "5%" : "3%",
4271
+ left: yLabel ? "12%" : "3%",
4142
4272
  right: "4%",
4143
4273
  bottom: xLabel ? "10%" : "3%",
4144
4274
  top: parsed.title ? "15%" : "5%",
@@ -4200,7 +4330,7 @@ function buildMultiLineOption(parsed, textColor, axisLineColor, splitLineColor,
4200
4330
  textStyle: { color: textColor }
4201
4331
  },
4202
4332
  grid: {
4203
- left: yLabel ? "5%" : "3%",
4333
+ left: yLabel ? "12%" : "3%",
4204
4334
  right: "4%",
4205
4335
  bottom: "15%",
4206
4336
  top: parsed.title ? "15%" : "5%",
@@ -4226,7 +4356,7 @@ function buildAreaOption(parsed, palette, textColor, axisLineColor, splitLineCol
4226
4356
  axisPointer: { type: "line" }
4227
4357
  },
4228
4358
  grid: {
4229
- left: yLabel ? "5%" : "3%",
4359
+ left: yLabel ? "12%" : "3%",
4230
4360
  right: "4%",
4231
4361
  bottom: xLabel ? "10%" : "3%",
4232
4362
  top: parsed.title ? "15%" : "5%",
@@ -4423,7 +4553,8 @@ function buildBarStackedOption(parsed, textColor, axisLineColor, splitLineColor,
4423
4553
  }
4424
4554
  };
4425
4555
  });
4426
- const categoryAxis = makeGridAxis("category", textColor, axisLineColor, splitLineColor, gridOpacity, isHorizontal ? yLabel : xLabel, labels);
4556
+ const hCatGap = isHorizontal && yLabel ? Math.max(40, Math.max(...labels.map((l) => l.length)) * 8 + 16) : void 0;
4557
+ const categoryAxis = makeGridAxis("category", textColor, axisLineColor, splitLineColor, gridOpacity, isHorizontal ? yLabel : xLabel, labels, hCatGap);
4427
4558
  const valueAxis = makeGridAxis("value", textColor, axisLineColor, splitLineColor, gridOpacity, isHorizontal ? xLabel : yLabel);
4428
4559
  return {
4429
4560
  backgroundColor: "transparent",
@@ -4440,7 +4571,7 @@ function buildBarStackedOption(parsed, textColor, axisLineColor, splitLineColor,
4440
4571
  textStyle: { color: textColor }
4441
4572
  },
4442
4573
  grid: {
4443
- left: yLabel ? "5%" : "3%",
4574
+ left: yLabel ? "12%" : "3%",
4444
4575
  right: "4%",
4445
4576
  bottom: "15%",
4446
4577
  top: parsed.title ? "15%" : "5%",
@@ -4518,35 +4649,51 @@ var init_echarts = __esm({
4518
4649
  }
4519
4650
  });
4520
4651
 
4652
+ // src/utils/tag-groups.ts
4653
+ function isTagBlockHeading(trimmed) {
4654
+ return TAG_BLOCK_RE.test(trimmed) || GROUP_HEADING_RE2.test(trimmed);
4655
+ }
4656
+ function matchTagBlockHeading(trimmed) {
4657
+ const tagMatch = trimmed.match(TAG_BLOCK_RE);
4658
+ if (tagMatch) {
4659
+ return {
4660
+ name: tagMatch[1].trim(),
4661
+ alias: tagMatch[2] || void 0,
4662
+ colorHint: tagMatch[3] || void 0,
4663
+ deprecated: false
4664
+ };
4665
+ }
4666
+ const groupMatch = trimmed.match(GROUP_HEADING_RE2);
4667
+ if (groupMatch) {
4668
+ return {
4669
+ name: groupMatch[1].trim(),
4670
+ alias: groupMatch[2] || void 0,
4671
+ colorHint: groupMatch[3] || void 0,
4672
+ deprecated: true
4673
+ };
4674
+ }
4675
+ return null;
4676
+ }
4677
+ var TAG_BLOCK_RE, GROUP_HEADING_RE2;
4678
+ var init_tag_groups = __esm({
4679
+ "src/utils/tag-groups.ts"() {
4680
+ "use strict";
4681
+ TAG_BLOCK_RE = /^tag:\s+(.+?)(?:\s+alias\s+(\w+))?(?:\s*\(([^)]+)\))?\s*$/i;
4682
+ GROUP_HEADING_RE2 = /^##\s+(.+?)(?:\s+alias\s+(\w+))?(?:\s*\(([^)]+)\))?\s*$/;
4683
+ }
4684
+ });
4685
+
4521
4686
  // src/org/parser.ts
4522
4687
  var parser_exports4 = {};
4523
4688
  __export(parser_exports4, {
4524
4689
  looksLikeOrg: () => looksLikeOrg,
4525
4690
  parseOrg: () => parseOrg
4526
4691
  });
4527
- function measureIndent5(line7) {
4528
- let indent = 0;
4529
- for (const ch of line7) {
4530
- if (ch === " ") indent++;
4531
- else if (ch === " ") indent += 4;
4532
- else break;
4533
- }
4534
- return indent;
4535
- }
4536
- function extractColor2(label, palette) {
4537
- const m = label.match(COLOR_SUFFIX_RE2);
4538
- if (!m) return { label };
4539
- const colorName = m[1].trim();
4540
- return {
4541
- label: label.substring(0, m.index).trim(),
4542
- color: resolveColor(colorName, palette)
4543
- };
4544
- }
4545
4692
  function looksLikeOrg(content) {
4546
4693
  for (const line7 of content.split("\n")) {
4547
4694
  const trimmed = line7.trim();
4548
4695
  if (!trimmed || trimmed.startsWith("//")) continue;
4549
- if (GROUP_HEADING_RE2.test(trimmed)) return true;
4696
+ if (isTagBlockHeading(trimmed)) return true;
4550
4697
  }
4551
4698
  return false;
4552
4699
  }
@@ -4571,6 +4718,9 @@ function parseOrg(content, palette) {
4571
4718
  result.diagnostics.push(diag);
4572
4719
  if (!result.error) result.error = formatDgmoError(diag);
4573
4720
  };
4721
+ const pushWarning = (line7, message) => {
4722
+ result.diagnostics.push(makeDgmoError(line7, message, "warning"));
4723
+ };
4574
4724
  if (!content || !content.trim()) {
4575
4725
  return fail(0, "No content provided");
4576
4726
  }
@@ -4614,42 +4764,43 @@ function parseOrg(content, palette) {
4614
4764
  continue;
4615
4765
  }
4616
4766
  }
4617
- if (!contentStarted && !currentTagGroup && measureIndent5(line7) === 0) {
4618
- const optMatch = trimmed.match(OPTION_RE);
4619
- if (optMatch && !trimmed.startsWith("##")) {
4620
- const key = optMatch[1].trim().toLowerCase();
4621
- if (key !== "chart" && key !== "title") {
4622
- result.options[key] = optMatch[2].trim();
4623
- continue;
4624
- }
4625
- }
4626
- }
4627
- const groupMatch = trimmed.match(GROUP_HEADING_RE2);
4628
- if (groupMatch) {
4767
+ const tagBlockMatch = matchTagBlockHeading(trimmed);
4768
+ if (tagBlockMatch) {
4629
4769
  if (contentStarted) {
4630
- pushError(lineNumber, "Tag groups (##) must appear before org content");
4770
+ pushError(lineNumber, "Tag groups must appear before org content");
4631
4771
  continue;
4632
4772
  }
4633
- const groupName = groupMatch[1].trim();
4634
- const alias = groupMatch[2] || void 0;
4773
+ if (tagBlockMatch.deprecated) {
4774
+ pushWarning(lineNumber, `'## ${tagBlockMatch.name}' is deprecated for tag groups \u2014 use 'tag: ${tagBlockMatch.name}' instead`);
4775
+ }
4635
4776
  currentTagGroup = {
4636
- name: groupName,
4637
- alias,
4777
+ name: tagBlockMatch.name,
4778
+ alias: tagBlockMatch.alias,
4638
4779
  entries: [],
4639
4780
  lineNumber
4640
4781
  };
4641
- if (alias) {
4642
- aliasMap.set(alias.toLowerCase(), groupName.toLowerCase());
4782
+ if (tagBlockMatch.alias) {
4783
+ aliasMap.set(tagBlockMatch.alias.toLowerCase(), tagBlockMatch.name.toLowerCase());
4643
4784
  }
4644
4785
  result.tagGroups.push(currentTagGroup);
4645
4786
  continue;
4646
4787
  }
4788
+ if (!contentStarted && !currentTagGroup && measureIndent(line7) === 0) {
4789
+ const optMatch = trimmed.match(OPTION_RE);
4790
+ if (optMatch) {
4791
+ const key = optMatch[1].trim().toLowerCase();
4792
+ if (key !== "chart" && key !== "title") {
4793
+ result.options[key] = optMatch[2].trim();
4794
+ continue;
4795
+ }
4796
+ }
4797
+ }
4647
4798
  if (currentTagGroup && !contentStarted) {
4648
- const indent2 = measureIndent5(line7);
4799
+ const indent2 = measureIndent(line7);
4649
4800
  if (indent2 > 0) {
4650
4801
  const isDefault = /\bdefault\s*$/.test(trimmed);
4651
4802
  const entryText = isDefault ? trimmed.replace(/\s+default\s*$/, "").trim() : trimmed;
4652
- const { label, color } = extractColor2(entryText, palette);
4803
+ const { label, color } = extractColor(entryText, palette);
4653
4804
  if (!color) {
4654
4805
  pushError(lineNumber, `Expected 'Value(color)' in tag group '${currentTagGroup.name}'`);
4655
4806
  continue;
@@ -4668,12 +4819,12 @@ function parseOrg(content, palette) {
4668
4819
  }
4669
4820
  contentStarted = true;
4670
4821
  currentTagGroup = null;
4671
- const indent = measureIndent5(line7);
4822
+ const indent = measureIndent(line7);
4672
4823
  const containerMatch = trimmed.match(CONTAINER_RE);
4673
4824
  const metadataMatch = trimmed.includes("|") ? null : trimmed.match(METADATA_RE);
4674
4825
  if (containerMatch) {
4675
4826
  const rawLabel = containerMatch[1].trim();
4676
- const { label, color } = extractColor2(rawLabel, palette);
4827
+ const { label, color } = extractColor(rawLabel, palette);
4677
4828
  containerCounter++;
4678
4829
  const node = {
4679
4830
  id: `container-${containerCounter}`,
@@ -4718,24 +4869,8 @@ function parseOrg(content, palette) {
4718
4869
  function parseNodeLabel(trimmed, _indent, lineNumber, palette, counter, aliasMap = /* @__PURE__ */ new Map()) {
4719
4870
  const segments = trimmed.split("|").map((s) => s.trim());
4720
4871
  let rawLabel = segments[0];
4721
- const { label, color } = extractColor2(rawLabel, palette);
4722
- const metadata = {};
4723
- const metaParts = [];
4724
- for (let j = 1; j < segments.length; j++) {
4725
- for (const part of segments[j].split(",")) {
4726
- const trimmedPart = part.trim();
4727
- if (trimmedPart) metaParts.push(trimmedPart);
4728
- }
4729
- }
4730
- for (const part of metaParts) {
4731
- const colonIdx = part.indexOf(":");
4732
- if (colonIdx > 0) {
4733
- const rawKey = part.substring(0, colonIdx).trim().toLowerCase();
4734
- const key = aliasMap.get(rawKey) ?? rawKey;
4735
- const value = part.substring(colonIdx + 1).trim();
4736
- metadata[key] = value;
4737
- }
4738
- }
4872
+ const { label, color } = extractColor(rawLabel, palette);
4873
+ const metadata = parsePipeMetadata(segments, aliasMap);
4739
4874
  return {
4740
4875
  id: `node-${counter}`,
4741
4876
  label,
@@ -4773,19 +4908,15 @@ function findMetadataParent(indent, indentStack) {
4773
4908
  }
4774
4909
  return null;
4775
4910
  }
4776
- var COLOR_SUFFIX_RE2, GROUP_HEADING_RE2, CONTAINER_RE, METADATA_RE, CHART_TYPE_RE, TITLE_RE, OPTION_RE;
4911
+ var CONTAINER_RE, METADATA_RE;
4777
4912
  var init_parser4 = __esm({
4778
4913
  "src/org/parser.ts"() {
4779
4914
  "use strict";
4780
- init_colors();
4781
4915
  init_diagnostics();
4782
- COLOR_SUFFIX_RE2 = /\(([^)]+)\)\s*$/;
4783
- GROUP_HEADING_RE2 = /^##\s+(.+?)(?:\s+alias\s+(\w+))?(?:\s*\(([^)]+)\))?\s*$/;
4916
+ init_tag_groups();
4917
+ init_parsing();
4784
4918
  CONTAINER_RE = /^\[([^\]]+)\]$/;
4785
4919
  METADATA_RE = /^([^:]+):\s*(.+)$/;
4786
- CHART_TYPE_RE = /^chart\s*:\s*(.+)/i;
4787
- TITLE_RE = /^title\s*:\s*(.+)/i;
4788
- OPTION_RE = /^([a-z][a-z0-9-]*)\s*:\s*(.+)$/i;
4789
4920
  }
4790
4921
  });
4791
4922
 
@@ -4794,31 +4925,14 @@ var parser_exports5 = {};
4794
4925
  __export(parser_exports5, {
4795
4926
  parseKanban: () => parseKanban
4796
4927
  });
4797
- function measureIndent6(line7) {
4798
- let indent = 0;
4799
- for (const ch of line7) {
4800
- if (ch === " ") indent++;
4801
- else if (ch === " ") indent += 4;
4802
- else break;
4803
- }
4804
- return indent;
4805
- }
4806
- function extractColor3(label, palette) {
4807
- const m = label.match(COLOR_SUFFIX_RE3);
4808
- if (!m) return { label };
4809
- const colorName = m[1].trim();
4810
- return {
4811
- label: label.substring(0, m.index).trim(),
4812
- color: resolveColor(colorName, palette)
4813
- };
4814
- }
4815
4928
  function parseKanban(content, palette) {
4816
4929
  const result = {
4817
4930
  type: "kanban",
4818
4931
  columns: [],
4819
4932
  tagGroups: [],
4820
4933
  options: {},
4821
- diagnostics: []
4934
+ diagnostics: [],
4935
+ error: null
4822
4936
  };
4823
4937
  const fail = (line7, message) => {
4824
4938
  const diag = makeDgmoError(line7, message);
@@ -4851,7 +4965,7 @@ function parseKanban(content, palette) {
4851
4965
  }
4852
4966
  if (trimmed.startsWith("//")) continue;
4853
4967
  if (!contentStarted && !currentTagGroup) {
4854
- const chartMatch = trimmed.match(CHART_TYPE_RE2);
4968
+ const chartMatch = trimmed.match(CHART_TYPE_RE);
4855
4969
  if (chartMatch) {
4856
4970
  const chartType = chartMatch[1].trim().toLowerCase();
4857
4971
  if (chartType !== "kanban") {
@@ -4875,16 +4989,35 @@ function parseKanban(content, palette) {
4875
4989
  }
4876
4990
  }
4877
4991
  if (!contentStarted && !currentTagGroup) {
4878
- const titleMatch = trimmed.match(TITLE_RE2);
4992
+ const titleMatch = trimmed.match(TITLE_RE);
4879
4993
  if (titleMatch) {
4880
4994
  result.title = titleMatch[1].trim();
4881
4995
  result.titleLineNumber = lineNumber;
4882
4996
  continue;
4883
4997
  }
4884
4998
  }
4885
- if (!contentStarted && !currentTagGroup && measureIndent6(line7) === 0) {
4886
- const optMatch = trimmed.match(OPTION_RE2);
4887
- if (optMatch && !trimmed.startsWith("##") && !COLUMN_RE2.test(trimmed)) {
4999
+ if (!contentStarted) {
5000
+ const tagBlockMatch = matchTagBlockHeading(trimmed);
5001
+ if (tagBlockMatch) {
5002
+ if (tagBlockMatch.deprecated) {
5003
+ warn(lineNumber, `'## ${tagBlockMatch.name}' is deprecated for tag groups \u2014 use 'tag: ${tagBlockMatch.name}' instead`);
5004
+ }
5005
+ currentTagGroup = {
5006
+ name: tagBlockMatch.name,
5007
+ alias: tagBlockMatch.alias,
5008
+ entries: [],
5009
+ lineNumber
5010
+ };
5011
+ if (tagBlockMatch.alias) {
5012
+ aliasMap.set(tagBlockMatch.alias.toLowerCase(), tagBlockMatch.name.toLowerCase());
5013
+ }
5014
+ result.tagGroups.push(currentTagGroup);
5015
+ continue;
5016
+ }
5017
+ }
5018
+ if (!contentStarted && !currentTagGroup && measureIndent(line7) === 0) {
5019
+ const optMatch = trimmed.match(OPTION_RE);
5020
+ if (optMatch && !COLUMN_RE2.test(trimmed)) {
4888
5021
  const key = optMatch[1].trim().toLowerCase();
4889
5022
  if (key !== "chart" && key !== "title") {
4890
5023
  result.options[key] = optMatch[2].trim();
@@ -4892,28 +5025,12 @@ function parseKanban(content, palette) {
4892
5025
  }
4893
5026
  }
4894
5027
  }
4895
- const groupMatch = trimmed.match(GROUP_HEADING_RE3);
4896
- if (groupMatch && !contentStarted) {
4897
- const groupName = groupMatch[1].trim();
4898
- const alias = groupMatch[2] || void 0;
4899
- currentTagGroup = {
4900
- name: groupName,
4901
- alias,
4902
- entries: [],
4903
- lineNumber
4904
- };
4905
- if (alias) {
4906
- aliasMap.set(alias.toLowerCase(), groupName.toLowerCase());
4907
- }
4908
- result.tagGroups.push(currentTagGroup);
4909
- continue;
4910
- }
4911
5028
  if (currentTagGroup && !contentStarted) {
4912
- const indent2 = measureIndent6(line7);
5029
+ const indent2 = measureIndent(line7);
4913
5030
  if (indent2 > 0) {
4914
5031
  const isDefault = /\bdefault\s*$/.test(trimmed);
4915
5032
  const entryText = isDefault ? trimmed.replace(/\s+default\s*$/, "").trim() : trimmed;
4916
- const { label, color } = extractColor3(entryText, palette);
5033
+ const { label, color } = extractColor(entryText, palette);
4917
5034
  if (!color) {
4918
5035
  warn(
4919
5036
  lineNumber,
@@ -4947,7 +5064,7 @@ function parseKanban(content, palette) {
4947
5064
  columnCounter++;
4948
5065
  const rawColName = columnMatch[1].trim();
4949
5066
  const wipStr = columnMatch[2];
4950
- const { label: colName, color: colColor } = extractColor3(
5067
+ const { label: colName, color: colColor } = extractColor(
4951
5068
  rawColName,
4952
5069
  palette
4953
5070
  );
@@ -4969,7 +5086,7 @@ function parseKanban(content, palette) {
4969
5086
  warn(lineNumber, "Card line found before any column");
4970
5087
  continue;
4971
5088
  }
4972
- const indent = measureIndent6(line7);
5089
+ const indent = measureIndent(line7);
4973
5090
  if (indent > 0 && currentCard) {
4974
5091
  currentCard.details.push(trimmed);
4975
5092
  currentCard.endLineNumber = lineNumber;
@@ -5034,7 +5151,7 @@ function parseCardLine(trimmed, lineNumber, counter, aliasMap, palette) {
5034
5151
  } else {
5035
5152
  rawTitle = trimmed;
5036
5153
  }
5037
- const { label: title, color } = extractColor3(rawTitle, palette);
5154
+ const { label: title, color } = extractColor(rawTitle, palette);
5038
5155
  const tags = {};
5039
5156
  if (tagsStr) {
5040
5157
  for (const part of tagsStr.split(",")) {
@@ -5057,18 +5174,14 @@ function parseCardLine(trimmed, lineNumber, counter, aliasMap, palette) {
5057
5174
  color
5058
5175
  };
5059
5176
  }
5060
- var CHART_TYPE_RE2, TITLE_RE2, OPTION_RE2, GROUP_HEADING_RE3, COLUMN_RE2, COLOR_SUFFIX_RE3;
5177
+ var COLUMN_RE2;
5061
5178
  var init_parser5 = __esm({
5062
5179
  "src/kanban/parser.ts"() {
5063
5180
  "use strict";
5064
- init_colors();
5065
5181
  init_diagnostics();
5066
- CHART_TYPE_RE2 = /^chart\s*:\s*(.+)/i;
5067
- TITLE_RE2 = /^title\s*:\s*(.+)/i;
5068
- OPTION_RE2 = /^([a-z][a-z0-9-]*)\s*:\s*(.+)$/i;
5069
- GROUP_HEADING_RE3 = /^##\s+(.+?)(?:\s+alias\s+(\w+))?(?:\s*\(([^)]+)\))?\s*$/;
5182
+ init_tag_groups();
5183
+ init_parsing();
5070
5184
  COLUMN_RE2 = /^==\s+(.+?)\s*(?:\[wip:\s*(\d+)\])?\s*==$/;
5071
- COLOR_SUFFIX_RE3 = /\(([^)]+)\)\s*$/;
5072
5185
  }
5073
5186
  });
5074
5187
 
@@ -5077,24 +5190,6 @@ var parser_exports6 = {};
5077
5190
  __export(parser_exports6, {
5078
5191
  parseC4: () => parseC4
5079
5192
  });
5080
- function measureIndent7(line7) {
5081
- let indent = 0;
5082
- for (const ch of line7) {
5083
- if (ch === " ") indent++;
5084
- else if (ch === " ") indent += 4;
5085
- else break;
5086
- }
5087
- return indent;
5088
- }
5089
- function extractColor4(label, palette) {
5090
- const m = label.match(COLOR_SUFFIX_RE4);
5091
- if (!m) return { label };
5092
- const colorName = m[1].trim();
5093
- return {
5094
- label: label.substring(0, m.index).trim(),
5095
- color: resolveColor(colorName, palette)
5096
- };
5097
- }
5098
5193
  function participantTypeToC4Shape(pType) {
5099
5194
  switch (pType) {
5100
5195
  case "database":
@@ -5151,23 +5246,6 @@ function parseRelationshipBody(body) {
5151
5246
  }
5152
5247
  return { target, label: rest };
5153
5248
  }
5154
- function parsePipeMetadata(segments, aliasMap) {
5155
- const metadata = {};
5156
- for (let j = 1; j < segments.length; j++) {
5157
- for (const part of segments[j].split(",")) {
5158
- const trimmedPart = part.trim();
5159
- if (!trimmedPart) continue;
5160
- const colonIdx = trimmedPart.indexOf(":");
5161
- if (colonIdx > 0) {
5162
- const rawKey = trimmedPart.substring(0, colonIdx).trim().toLowerCase();
5163
- const key = aliasMap.get(rawKey) ?? rawKey;
5164
- const value = trimmedPart.substring(colonIdx + 1).trim();
5165
- metadata[key] = value;
5166
- }
5167
- }
5168
- }
5169
- return metadata;
5170
- }
5171
5249
  function parseC4(content, palette) {
5172
5250
  const result = {
5173
5251
  title: null,
@@ -5213,7 +5291,7 @@ function parseC4(content, palette) {
5213
5291
  }
5214
5292
  if (trimmed.startsWith("//")) continue;
5215
5293
  if (!contentStarted) {
5216
- const chartMatch = trimmed.match(CHART_TYPE_RE3);
5294
+ const chartMatch = trimmed.match(CHART_TYPE_RE);
5217
5295
  if (chartMatch) {
5218
5296
  const chartType = chartMatch[1].trim().toLowerCase();
5219
5297
  if (chartType !== "c4") {
@@ -5227,49 +5305,50 @@ function parseC4(content, palette) {
5227
5305
  }
5228
5306
  }
5229
5307
  if (!contentStarted) {
5230
- const titleMatch = trimmed.match(TITLE_RE3);
5308
+ const titleMatch = trimmed.match(TITLE_RE);
5231
5309
  if (titleMatch) {
5232
5310
  result.title = titleMatch[1].trim();
5233
5311
  result.titleLineNumber = lineNumber;
5234
5312
  continue;
5235
5313
  }
5236
5314
  }
5237
- if (!contentStarted && !currentTagGroup && measureIndent7(line7) === 0) {
5238
- const optMatch = trimmed.match(OPTION_RE3);
5239
- if (optMatch && !trimmed.startsWith("##")) {
5240
- const key = optMatch[1].trim().toLowerCase();
5241
- if (key !== "chart" && key !== "title") {
5242
- result.options[key] = optMatch[2].trim();
5243
- continue;
5244
- }
5245
- }
5246
- }
5247
- const groupMatch = trimmed.match(GROUP_HEADING_RE4);
5248
- if (groupMatch) {
5315
+ const tagBlockMatch = matchTagBlockHeading(trimmed);
5316
+ if (tagBlockMatch) {
5249
5317
  if (contentStarted) {
5250
- pushError(lineNumber, "Tag groups (##) must appear before content");
5318
+ pushError(lineNumber, "Tag groups must appear before content");
5251
5319
  continue;
5252
5320
  }
5253
- const groupName = groupMatch[1].trim();
5254
- const alias = groupMatch[2] || void 0;
5321
+ if (tagBlockMatch.deprecated) {
5322
+ pushError(lineNumber, `'## ${tagBlockMatch.name}' is deprecated for tag groups \u2014 use 'tag: ${tagBlockMatch.name}' instead`, "warning");
5323
+ }
5255
5324
  currentTagGroup = {
5256
- name: groupName,
5257
- alias,
5325
+ name: tagBlockMatch.name,
5326
+ alias: tagBlockMatch.alias,
5258
5327
  entries: [],
5259
5328
  lineNumber
5260
5329
  };
5261
- if (alias) {
5262
- aliasMap.set(alias.toLowerCase(), groupName.toLowerCase());
5330
+ if (tagBlockMatch.alias) {
5331
+ aliasMap.set(tagBlockMatch.alias.toLowerCase(), tagBlockMatch.name.toLowerCase());
5263
5332
  }
5264
5333
  result.tagGroups.push(currentTagGroup);
5265
5334
  continue;
5266
5335
  }
5336
+ if (!contentStarted && !currentTagGroup && measureIndent(line7) === 0) {
5337
+ const optMatch = trimmed.match(OPTION_RE);
5338
+ if (optMatch) {
5339
+ const key = optMatch[1].trim().toLowerCase();
5340
+ if (key !== "chart" && key !== "title") {
5341
+ result.options[key] = optMatch[2].trim();
5342
+ continue;
5343
+ }
5344
+ }
5345
+ }
5267
5346
  if (currentTagGroup && !contentStarted) {
5268
- const indent2 = measureIndent7(line7);
5347
+ const indent2 = measureIndent(line7);
5269
5348
  if (indent2 > 0) {
5270
5349
  const isDefault = /\bdefault\s*$/.test(trimmed);
5271
5350
  const entryText = isDefault ? trimmed.replace(/\s+default\s*$/, "").trim() : trimmed;
5272
- const { label, color } = extractColor4(entryText, palette);
5351
+ const { label, color } = extractColor(entryText, palette);
5273
5352
  if (!color) {
5274
5353
  pushError(
5275
5354
  lineNumber,
@@ -5294,7 +5373,7 @@ function parseC4(content, palette) {
5294
5373
  if (!sawChartType) {
5295
5374
  return fail(lineNumber, 'Missing "chart: c4" header');
5296
5375
  }
5297
- const indent = measureIndent7(line7);
5376
+ const indent = measureIndent(line7);
5298
5377
  if (inDeployment) {
5299
5378
  while (deployStack.length > 0) {
5300
5379
  const top = deployStack[deployStack.length - 1];
@@ -5389,6 +5468,45 @@ function parseC4(content, palette) {
5389
5468
  }
5390
5469
  continue;
5391
5470
  }
5471
+ {
5472
+ const labeledPatterns = [
5473
+ { re: C4_LABELED_BIDI_SYNC_RE, arrowType: "bidirectional" },
5474
+ { re: C4_LABELED_BIDI_ASYNC_RE, arrowType: "bidirectional-async" },
5475
+ { re: C4_LABELED_SYNC_RE, arrowType: "sync" },
5476
+ { re: C4_LABELED_ASYNC_RE, arrowType: "async" }
5477
+ ];
5478
+ let labeledHandled = false;
5479
+ for (const { re, arrowType } of labeledPatterns) {
5480
+ const m = trimmed.match(re);
5481
+ if (!m) continue;
5482
+ const rawLabel = m[1].trim();
5483
+ const targetBody = m[2].trim();
5484
+ if (!rawLabel) break;
5485
+ let label = rawLabel;
5486
+ let technology;
5487
+ const techMatch = rawLabel.match(/\[([^\]]+)\]\s*$/);
5488
+ if (techMatch) {
5489
+ label = rawLabel.substring(0, techMatch.index).trim() || void 0;
5490
+ technology = techMatch[1].trim();
5491
+ }
5492
+ const rel = {
5493
+ target: targetBody,
5494
+ label,
5495
+ technology,
5496
+ arrowType,
5497
+ lineNumber
5498
+ };
5499
+ const parentEntry = findParentElement(indent, stack);
5500
+ if (parentEntry) {
5501
+ parentEntry.element.relationships.push(rel);
5502
+ } else {
5503
+ result.relationships.push(rel);
5504
+ }
5505
+ labeledHandled = true;
5506
+ break;
5507
+ }
5508
+ if (labeledHandled) continue;
5509
+ }
5392
5510
  const relMatch = trimmed.match(RELATIONSHIP_RE);
5393
5511
  if (relMatch) {
5394
5512
  const arrowType = parseArrowType(relMatch[1]);
@@ -5578,22 +5696,22 @@ function validateDeploymentRefs(result, knownNames, pushWarning) {
5578
5696
  }
5579
5697
  walkDeploy(result.deployment);
5580
5698
  }
5581
- 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;
5699
+ 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;
5582
5700
  var init_parser6 = __esm({
5583
5701
  "src/c4/parser.ts"() {
5584
5702
  "use strict";
5585
- init_colors();
5586
5703
  init_diagnostics();
5704
+ init_tag_groups();
5587
5705
  init_participant_inference();
5588
- CHART_TYPE_RE3 = /^chart\s*:\s*(.+)/i;
5589
- TITLE_RE3 = /^title\s*:\s*(.+)/i;
5590
- OPTION_RE3 = /^([a-z][a-z0-9-]*)\s*:\s*(.+)$/i;
5591
- GROUP_HEADING_RE4 = /^##\s+(.+?)(?:\s+alias\s+(\w+))?(?:\s*\(([^)]+)\))?\s*$/;
5592
- COLOR_SUFFIX_RE4 = /\(([^)]+)\)\s*$/;
5706
+ init_parsing();
5593
5707
  CONTAINER_RE2 = /^\[([^\]]+)\]$/;
5594
5708
  ELEMENT_RE = /^(person|system|container|component)\s+(.+)$/i;
5595
5709
  IS_A_RE = /\s+is\s+a(?:n)?\s+(\w+)\s*$/i;
5596
5710
  RELATIONSHIP_RE = /^(<?-?>|<?~?>)\s+(.+)$/;
5711
+ C4_LABELED_SYNC_RE = /^-(.+)->\s+(.+)$/;
5712
+ C4_LABELED_ASYNC_RE = /^~(.+)~>\s+(.+)$/;
5713
+ C4_LABELED_BIDI_SYNC_RE = /^<-(.+)->\s+(.+)$/;
5714
+ C4_LABELED_BIDI_ASYNC_RE = /^<~(.+)~>\s+(.+)$/;
5597
5715
  SECTION_HEADER_RE = /^(containers|components|deployment)\s*:\s*$/i;
5598
5716
  CONTAINER_REF_RE = /^container\s+(.+)$/i;
5599
5717
  METADATA_RE2 = /^([^:]+):\s*(.+)$/;
@@ -5654,13 +5772,13 @@ function looksLikeInitiativeStatus(content) {
5654
5772
  let hasIndentedArrow = false;
5655
5773
  for (const line7 of lines) {
5656
5774
  const trimmed = line7.trim();
5657
- if (!trimmed || trimmed.startsWith("#") || trimmed.startsWith("//")) continue;
5775
+ if (!trimmed || trimmed.startsWith("//")) continue;
5658
5776
  if (trimmed.match(/^chart\s*:/i)) continue;
5659
5777
  if (trimmed.match(/^title\s*:/i)) continue;
5660
5778
  if (trimmed.includes("->")) hasArrow = true;
5661
5779
  if (/\|\s*(done|wip|todo|na)\s*$/i.test(trimmed)) hasStatus = true;
5662
5780
  const isIndented = line7.length > 0 && line7 !== trimmed && /^\s/.test(line7);
5663
- if (isIndented && trimmed.startsWith("->")) hasIndentedArrow = true;
5781
+ if (isIndented && (trimmed.startsWith("->") || /^-[^>].*->/.test(trimmed))) hasIndentedArrow = true;
5664
5782
  if (hasArrow && hasStatus) return true;
5665
5783
  }
5666
5784
  return hasIndentedArrow;
@@ -5684,7 +5802,7 @@ function parseInitiativeStatus(content) {
5684
5802
  groups: [],
5685
5803
  options: {},
5686
5804
  diagnostics: [],
5687
- error: void 0
5805
+ error: null
5688
5806
  };
5689
5807
  const lines = content.split("\n");
5690
5808
  const nodeLabels = /* @__PURE__ */ new Set();
@@ -5694,7 +5812,7 @@ function parseInitiativeStatus(content) {
5694
5812
  const lineNum = i + 1;
5695
5813
  const raw = lines[i];
5696
5814
  const trimmed = raw.trim();
5697
- if (!trimmed || trimmed.startsWith("#") || trimmed.startsWith("//")) continue;
5815
+ if (!trimmed || trimmed.startsWith("//")) continue;
5698
5816
  const chartMatch = trimmed.match(/^chart\s*:\s*(.+)/i);
5699
5817
  if (chartMatch) {
5700
5818
  const chartType = chartMatch[1].trim().toLowerCase();
@@ -5727,7 +5845,7 @@ function parseInitiativeStatus(content) {
5727
5845
  }
5728
5846
  if (trimmed.includes("->")) {
5729
5847
  let edgeText = trimmed;
5730
- if (trimmed.startsWith("->")) {
5848
+ if (trimmed.startsWith("->") || /^-[^>].*->/.test(trimmed)) {
5731
5849
  if (!lastNodeLabel) {
5732
5850
  result.diagnostics.push(
5733
5851
  makeDgmoError(lineNum, "Indented edge has no preceding node to use as source", "warning")
@@ -5793,6 +5911,27 @@ function parseNodeLine(trimmed, lineNum, diagnostics) {
5793
5911
  return { label: trimmed, status: "na", shape: inferParticipantType(trimmed), lineNumber: lineNum };
5794
5912
  }
5795
5913
  function parseEdgeLine(trimmed, lineNum, diagnostics) {
5914
+ const labeledMatch = trimmed.match(/^(\S+)\s+-(.+)->\s+(.+)$/);
5915
+ if (labeledMatch) {
5916
+ const source2 = labeledMatch[1];
5917
+ const label2 = labeledMatch[2].trim();
5918
+ let targetRest = labeledMatch[3].trim();
5919
+ if (label2) {
5920
+ let status2 = "na";
5921
+ const lastPipe2 = targetRest.lastIndexOf("|");
5922
+ if (lastPipe2 >= 0) {
5923
+ const statusRaw = targetRest.slice(lastPipe2 + 1).trim();
5924
+ status2 = parseStatus(statusRaw, lineNum, diagnostics);
5925
+ targetRest = targetRest.slice(0, lastPipe2).trim();
5926
+ }
5927
+ const target2 = targetRest.trim();
5928
+ if (!target2) {
5929
+ diagnostics.push(makeDgmoError(lineNum, "Edge is missing target"));
5930
+ return null;
5931
+ }
5932
+ return { source: source2, target: target2, label: label2, status: status2, lineNumber: lineNum };
5933
+ }
5934
+ }
5796
5935
  const arrowIdx = trimmed.indexOf("->");
5797
5936
  if (arrowIdx < 0) return null;
5798
5937
  const source = trimmed.slice(0, arrowIdx).trim();
@@ -5847,7 +5986,7 @@ function parseDgmoChartType(content) {
5847
5986
  const lines = content.split("\n");
5848
5987
  for (const line7 of lines) {
5849
5988
  const trimmed = line7.trim();
5850
- if (!trimmed || trimmed.startsWith("#") || trimmed.startsWith("//"))
5989
+ if (!trimmed || trimmed.startsWith("//"))
5851
5990
  continue;
5852
5991
  const match = trimmed.match(/^chart\s*:\s*(.+)/i);
5853
5992
  if (match) return match[1].trim().toLowerCase();
@@ -7109,6 +7248,58 @@ var init_renderer = __esm({
7109
7248
  }
7110
7249
  });
7111
7250
 
7251
+ // src/utils/inline-markdown.ts
7252
+ function parseInlineMarkdown(text) {
7253
+ const spans = [];
7254
+ const regex = /\*\*(.+?)\*\*|__(.+?)__|\*(.+?)\*|_(.+?)_|`(.+?)`|\[(.+?)\]\((.+?)\)|(https?:\/\/[^\s)>\]]+|www\.[^\s)>\]]+)|([^*_`[]+?(?=https?:\/\/|www\.|$)|[^*_`[]+)/g;
7255
+ let match;
7256
+ while ((match = regex.exec(text)) !== null) {
7257
+ if (match[1]) spans.push({ text: match[1], bold: true });
7258
+ else if (match[2]) spans.push({ text: match[2], bold: true });
7259
+ else if (match[3]) spans.push({ text: match[3], italic: true });
7260
+ else if (match[4]) spans.push({ text: match[4], italic: true });
7261
+ else if (match[5]) spans.push({ text: match[5], code: true });
7262
+ else if (match[6]) spans.push({ text: match[6], href: match[7] });
7263
+ else if (match[8]) {
7264
+ const url = match[8];
7265
+ const href = url.startsWith("www.") ? `https://${url}` : url;
7266
+ spans.push({ text: url, href });
7267
+ } else if (match[9]) spans.push({ text: match[9] });
7268
+ }
7269
+ return spans;
7270
+ }
7271
+ function truncateBareUrl(url) {
7272
+ const stripped = url.replace(/^https?:\/\//, "").replace(/^www\./, "");
7273
+ if (stripped.length <= BARE_URL_MAX_DISPLAY) return stripped;
7274
+ return stripped.slice(0, BARE_URL_MAX_DISPLAY - 1) + "\u2026";
7275
+ }
7276
+ function renderInlineText(textEl, text, palette, fontSize) {
7277
+ const spans = parseInlineMarkdown(text);
7278
+ for (const span of spans) {
7279
+ if (span.href) {
7280
+ const isBareUrl = span.text === span.href || `https://${span.text}` === span.href;
7281
+ const display = isBareUrl ? truncateBareUrl(span.text) : span.text;
7282
+ const a = textEl.append("a").attr("href", span.href);
7283
+ a.append("tspan").text(display).attr("fill", palette.primary).style("text-decoration", "underline");
7284
+ } else {
7285
+ const tspan = textEl.append("tspan").text(span.text);
7286
+ if (span.bold) tspan.attr("font-weight", "bold");
7287
+ if (span.italic) tspan.attr("font-style", "italic");
7288
+ if (span.code) {
7289
+ tspan.attr("font-family", "monospace");
7290
+ if (fontSize) tspan.attr("font-size", fontSize - 1);
7291
+ }
7292
+ }
7293
+ }
7294
+ }
7295
+ var BARE_URL_MAX_DISPLAY;
7296
+ var init_inline_markdown = __esm({
7297
+ "src/utils/inline-markdown.ts"() {
7298
+ "use strict";
7299
+ BARE_URL_MAX_DISPLAY = 35;
7300
+ }
7301
+ });
7302
+
7112
7303
  // src/kanban/mutations.ts
7113
7304
  function computeCardMove(content, parsed, cardId, targetColumnId, targetIndex) {
7114
7305
  let sourceCard = null;
@@ -7431,7 +7622,8 @@ function renderKanban(container, parsed, palette, isDark, _onNavigateToLine, exp
7431
7622
  const cx = colLayout.x + cardLayout.x;
7432
7623
  const cy = colLayout.y + cardLayout.y;
7433
7624
  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);
7434
- 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);
7625
+ 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);
7626
+ renderInlineText(titleEl, card.title, palette, CARD_TITLE_FONT_SIZE);
7435
7627
  if (hasMeta) {
7436
7628
  const separatorY = cy + CARD_HEADER_HEIGHT;
7437
7629
  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);
@@ -7443,7 +7635,8 @@ function renderKanban(container, parsed, palette, isDark, _onNavigateToLine, exp
7443
7635
  metaY += CARD_META_LINE_HEIGHT;
7444
7636
  }
7445
7637
  for (const detail of card.details) {
7446
- 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);
7638
+ 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);
7639
+ renderInlineText(detailEl, detail, palette, CARD_META_FONT_SIZE);
7447
7640
  metaY += CARD_META_LINE_HEIGHT;
7448
7641
  }
7449
7642
  }
@@ -7468,6 +7661,7 @@ var init_renderer2 = __esm({
7468
7661
  "src/kanban/renderer.ts"() {
7469
7662
  "use strict";
7470
7663
  init_fonts();
7664
+ init_inline_markdown();
7471
7665
  init_parser5();
7472
7666
  init_mutations();
7473
7667
  DIAGRAM_PADDING2 = 20;
@@ -7525,22 +7719,30 @@ function computeNodeDimensions(node) {
7525
7719
  }
7526
7720
  const width = Math.max(MIN_WIDTH, maxTextLen * CHAR_WIDTH2 + PADDING_X);
7527
7721
  const headerHeight = HEADER_BASE + (node.modifier ? MODIFIER_BADGE : 0);
7528
- let fieldsHeight = 0;
7722
+ let fieldsHeight;
7529
7723
  if (isEnum) {
7530
7724
  const enumValues = node.members;
7531
7725
  if (enumValues.length > 0) {
7532
7726
  fieldsHeight = COMPARTMENT_PADDING_Y * 2 + enumValues.length * MEMBER_LINE_HEIGHT + SEPARATOR_HEIGHT;
7727
+ } else {
7728
+ fieldsHeight = SEPARATOR_HEIGHT + COMPARTMENT_PADDING_Y;
7533
7729
  }
7534
7730
  } else {
7535
7731
  if (fields.length > 0) {
7536
7732
  fieldsHeight = COMPARTMENT_PADDING_Y * 2 + fields.length * MEMBER_LINE_HEIGHT + SEPARATOR_HEIGHT;
7733
+ } else {
7734
+ fieldsHeight = SEPARATOR_HEIGHT + COMPARTMENT_PADDING_Y;
7537
7735
  }
7538
7736
  }
7539
7737
  let methodsHeight = 0;
7540
- if (!isEnum && methods.length > 0) {
7541
- methodsHeight = COMPARTMENT_PADDING_Y * 2 + methods.length * MEMBER_LINE_HEIGHT + SEPARATOR_HEIGHT;
7738
+ if (!isEnum) {
7739
+ if (methods.length > 0) {
7740
+ methodsHeight = COMPARTMENT_PADDING_Y * 2 + methods.length * MEMBER_LINE_HEIGHT + SEPARATOR_HEIGHT;
7741
+ } else {
7742
+ methodsHeight = SEPARATOR_HEIGHT + COMPARTMENT_PADDING_Y;
7743
+ }
7542
7744
  }
7543
- const height = headerHeight + fieldsHeight + methodsHeight + (fieldsHeight === 0 && methodsHeight === 0 ? 4 : 0);
7745
+ const height = headerHeight + fieldsHeight + methodsHeight;
7544
7746
  return { width, height, headerHeight, fieldsHeight, methodsHeight };
7545
7747
  }
7546
7748
  function layoutClassDiagram(parsed) {
@@ -7794,17 +7996,15 @@ function renderClassDiagram(container, parsed, layout, palette, isDark, onClickI
7794
7996
  const fields = node.members.filter((m) => !m.isMethod);
7795
7997
  const methods = node.members.filter((m) => m.isMethod);
7796
7998
  if (isEnum) {
7797
- if (node.members.length > 0) {
7798
- 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);
7799
- let memberY = yPos + COMPARTMENT_PADDING_Y2;
7800
- for (const member of node.members) {
7801
- 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);
7802
- memberY += MEMBER_LINE_HEIGHT2;
7803
- }
7999
+ 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);
8000
+ let memberY = yPos + COMPARTMENT_PADDING_Y2;
8001
+ for (const member of node.members) {
8002
+ 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);
8003
+ memberY += MEMBER_LINE_HEIGHT2;
7804
8004
  }
7805
8005
  } else {
8006
+ 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);
7806
8007
  if (fields.length > 0) {
7807
- 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);
7808
8008
  let memberY = yPos + COMPARTMENT_PADDING_Y2;
7809
8009
  for (const field of fields) {
7810
8010
  const vis = visibilitySymbol(field.visibility);
@@ -7817,10 +8017,10 @@ function renderClassDiagram(container, parsed, layout, palette, isDark, onClickI
7817
8017
  textEl.text(text);
7818
8018
  memberY += MEMBER_LINE_HEIGHT2;
7819
8019
  }
7820
- yPos += node.fieldsHeight;
7821
8020
  }
8021
+ yPos += node.fieldsHeight;
8022
+ 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);
7822
8023
  if (methods.length > 0) {
7823
- 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);
7824
8024
  let memberY = yPos + COMPARTMENT_PADDING_Y2;
7825
8025
  for (const method of methods) {
7826
8026
  const vis = visibilitySymbol(method.visibility);
@@ -10615,7 +10815,8 @@ function renderC4Context(container, parsed, layout, palette, isDark, onClickItem
10615
10815
  const contentWidth = w - CARD_H_PAD3 * 2;
10616
10816
  const lines = wrapText2(node.description, contentWidth, DESC_CHAR_WIDTH2);
10617
10817
  for (const line7 of lines) {
10618
- 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);
10818
+ 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);
10819
+ renderInlineText(textEl, line7, palette, DESC_FONT_SIZE);
10619
10820
  yPos += DESC_LINE_HEIGHT2;
10620
10821
  }
10621
10822
  }
@@ -11101,7 +11302,8 @@ function renderC4Containers(container, parsed, layout, palette, isDark, onClickI
11101
11302
  const contentWidth = w - CARD_H_PAD3 * 2;
11102
11303
  const lines = wrapText2(node.description, contentWidth, DESC_CHAR_WIDTH2);
11103
11304
  for (const line7 of lines) {
11104
- 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);
11305
+ 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);
11306
+ renderInlineText(textEl, line7, palette, DESC_FONT_SIZE);
11105
11307
  yPos += DESC_LINE_HEIGHT2;
11106
11308
  }
11107
11309
  }
@@ -11124,7 +11326,8 @@ function renderC4Containers(container, parsed, layout, palette, isDark, onClickI
11124
11326
  const contentWidth = w - CARD_H_PAD3 * 2;
11125
11327
  const lines = wrapText2(node.description, contentWidth, DESC_CHAR_WIDTH2);
11126
11328
  for (const line7 of lines) {
11127
- 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);
11329
+ 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);
11330
+ renderInlineText(textEl, line7, palette, DESC_FONT_SIZE);
11128
11331
  yPos += DESC_LINE_HEIGHT2;
11129
11332
  }
11130
11333
  }
@@ -11243,6 +11446,7 @@ var init_renderer6 = __esm({
11243
11446
  "src/c4/renderer.ts"() {
11244
11447
  "use strict";
11245
11448
  init_fonts();
11449
+ init_inline_markdown();
11246
11450
  init_parser6();
11247
11451
  init_layout5();
11248
11452
  DIAGRAM_PADDING6 = 20;
@@ -11741,49 +11945,6 @@ __export(renderer_exports7, {
11741
11945
  truncateBareUrl: () => truncateBareUrl
11742
11946
  });
11743
11947
  import * as d3Selection8 from "d3-selection";
11744
- function parseInlineMarkdown(text) {
11745
- const spans = [];
11746
- const regex = /\*\*(.+?)\*\*|__(.+?)__|\*(.+?)\*|_(.+?)_|`(.+?)`|\[(.+?)\]\((.+?)\)|(https?:\/\/[^\s)>\]]+|www\.[^\s)>\]]+)|([^*_`[]+?(?=https?:\/\/|www\.|$)|[^*_`[]+)/g;
11747
- let match;
11748
- while ((match = regex.exec(text)) !== null) {
11749
- if (match[1]) spans.push({ text: match[1], bold: true });
11750
- else if (match[2]) spans.push({ text: match[2], bold: true });
11751
- else if (match[3]) spans.push({ text: match[3], italic: true });
11752
- else if (match[4]) spans.push({ text: match[4], italic: true });
11753
- else if (match[5]) spans.push({ text: match[5], code: true });
11754
- else if (match[6]) spans.push({ text: match[6], href: match[7] });
11755
- else if (match[8]) {
11756
- const url = match[8];
11757
- const href = url.startsWith("www.") ? `https://${url}` : url;
11758
- spans.push({ text: url, href });
11759
- } else if (match[9]) spans.push({ text: match[9] });
11760
- }
11761
- return spans;
11762
- }
11763
- function truncateBareUrl(url) {
11764
- const stripped = url.replace(/^https?:\/\//, "").replace(/^www\./, "");
11765
- if (stripped.length <= BARE_URL_MAX_DISPLAY) return stripped;
11766
- return stripped.slice(0, BARE_URL_MAX_DISPLAY - 1) + "\u2026";
11767
- }
11768
- function renderInlineText(textEl, text, palette, fontSize) {
11769
- const spans = parseInlineMarkdown(text);
11770
- for (const span of spans) {
11771
- if (span.href) {
11772
- const isBareUrl = span.text === span.href || `https://${span.text}` === span.href;
11773
- const display = isBareUrl ? truncateBareUrl(span.text) : span.text;
11774
- const a = textEl.append("a").attr("href", span.href);
11775
- a.append("tspan").text(display).attr("fill", palette.primary).style("text-decoration", "underline");
11776
- } else {
11777
- const tspan = textEl.append("tspan").text(span.text);
11778
- if (span.bold) tspan.attr("font-weight", "bold");
11779
- if (span.italic) tspan.attr("font-style", "italic");
11780
- if (span.code) {
11781
- tspan.attr("font-family", "monospace");
11782
- if (fontSize) tspan.attr("font-size", fontSize - 1);
11783
- }
11784
- }
11785
- }
11786
- }
11787
11948
  function wrapTextLines(text, maxChars) {
11788
11949
  const rawLines = text.split("\n");
11789
11950
  const wrapped = [];
@@ -11964,8 +12125,12 @@ function buildRenderSequence(messages) {
11964
12125
  to: msg.to,
11965
12126
  label: msg.label,
11966
12127
  messageIndex: mi,
11967
- ...msg.async ? { async: true } : {}
12128
+ ...msg.async ? { async: true } : {},
12129
+ ...msg.bidirectional ? { bidirectional: true } : {}
11968
12130
  });
12131
+ if (msg.bidirectional) {
12132
+ continue;
12133
+ }
11969
12134
  if (msg.async) {
11970
12135
  continue;
11971
12136
  }
@@ -12457,6 +12622,14 @@ function renderSequenceDiagram(container, parsed, palette, isDark, _onNavigateTo
12457
12622
  "points",
12458
12623
  `0,0 ${ARROWHEAD_SIZE},${ARROWHEAD_SIZE / 2} 0,${ARROWHEAD_SIZE}`
12459
12624
  ).attr("fill", "none").attr("stroke", palette.text).attr("stroke-width", 1.2);
12625
+ 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(
12626
+ "points",
12627
+ `${ARROWHEAD_SIZE},0 0,${ARROWHEAD_SIZE / 2} ${ARROWHEAD_SIZE},${ARROWHEAD_SIZE}`
12628
+ ).attr("fill", palette.text);
12629
+ 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(
12630
+ "points",
12631
+ `${ARROWHEAD_SIZE},0 0,${ARROWHEAD_SIZE / 2} ${ARROWHEAD_SIZE},${ARROWHEAD_SIZE}`
12632
+ ).attr("fill", "none").attr("stroke", palette.text).attr("stroke-width", 1.2);
12460
12633
  if (title) {
12461
12634
  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);
12462
12635
  if (parsed.titleLineNumber) {
@@ -12737,10 +12910,17 @@ function renderSequenceDiagram(container, parsed, palette, isDark, _onNavigateTo
12737
12910
  const x1 = arrowEdgeX(step.from, i, goingRight ? "right" : "left");
12738
12911
  const x2 = arrowEdgeX(step.to, i, goingRight ? "left" : "right");
12739
12912
  const markerRef = step.async ? "url(#seq-arrowhead-async)" : "url(#seq-arrowhead)";
12740
- 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(
12913
+ const markerStartRef = step.bidirectional ? step.async ? "url(#seq-arrowhead-async-reverse)" : "url(#seq-arrowhead-reverse)" : null;
12914
+ 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(
12741
12915
  "data-line-number",
12742
12916
  String(messages[step.messageIndex].lineNumber)
12743
12917
  ).attr("data-msg-index", String(step.messageIndex)).attr("data-step-index", String(i));
12918
+ if (markerStartRef) {
12919
+ line7.attr("marker-start", markerStartRef);
12920
+ }
12921
+ if (step.bidirectional && step.async) {
12922
+ line7.attr("stroke-dasharray", "6 4");
12923
+ }
12744
12924
  if (step.label) {
12745
12925
  const midX = (x1 + x2) / 2;
12746
12926
  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(
@@ -12928,11 +13108,12 @@ function renderParticipant(svg, participant, cx, cy, palette, isDark) {
12928
13108
  isActor ? PARTICIPANT_BOX_HEIGHT + 14 : PARTICIPANT_BOX_HEIGHT / 2 + 5
12929
13109
  ).attr("text-anchor", "middle").attr("fill", palette.text).attr("font-size", 13).attr("font-weight", 500).text(participant.label);
12930
13110
  }
12931
- var 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;
13111
+ var 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;
12932
13112
  var init_renderer7 = __esm({
12933
13113
  "src/sequence/renderer.ts"() {
12934
13114
  "use strict";
12935
13115
  init_colors();
13116
+ init_inline_markdown();
12936
13117
  init_fonts();
12937
13118
  init_parser();
12938
13119
  PARTICIPANT_GAP = 160;
@@ -12956,7 +13137,6 @@ var init_renderer7 = __esm({
12956
13137
  NOTE_CHARS_PER_LINE = Math.floor((NOTE_MAX_W - NOTE_PAD_H * 2 - NOTE_FOLD) / NOTE_CHAR_W);
12957
13138
  COLLAPSED_NOTE_H = 20;
12958
13139
  COLLAPSED_NOTE_W = 40;
12959
- BARE_URL_MAX_DISPLAY = 35;
12960
13140
  fill = (palette, isDark) => mix8(palette.primary, isDark ? palette.surface : palette.bg, isDark ? 15 : 30);
12961
13141
  stroke = (palette) => palette.textMuted;
12962
13142
  SW = 1.5;
@@ -13091,7 +13271,7 @@ function parseD3(content, palette) {
13091
13271
  }
13092
13272
  continue;
13093
13273
  }
13094
- if (line7.startsWith("#") || line7.startsWith("//")) {
13274
+ if (line7.startsWith("//")) {
13095
13275
  continue;
13096
13276
  }
13097
13277
  if (result.type === "arc") {
@@ -16265,7 +16445,7 @@ function parseQuadrant(content) {
16265
16445
  for (let i = 0; i < lines.length; i++) {
16266
16446
  const line7 = lines[i].trim();
16267
16447
  const lineNumber = i + 1;
16268
- if (!line7 || line7.startsWith("#") || line7.startsWith("//")) continue;
16448
+ if (!line7 || line7.startsWith("//")) continue;
16269
16449
  if (/^chart\s*:/i.test(line7)) continue;
16270
16450
  const titleMatch = line7.match(/^title\s*:\s*(.+)/i);
16271
16451
  if (titleMatch) {
@@ -16395,6 +16575,7 @@ init_renderer3();
16395
16575
  init_parser3();
16396
16576
  init_layout3();
16397
16577
  init_renderer4();
16578
+ init_inline_markdown();
16398
16579
  init_parser4();
16399
16580
  init_layout();
16400
16581
  init_renderer();
@@ -16411,12 +16592,12 @@ init_collapse();
16411
16592
 
16412
16593
  // src/org/resolver.ts
16413
16594
  init_diagnostics();
16595
+ init_tag_groups();
16414
16596
  var MAX_DEPTH = 10;
16415
16597
  var IMPORT_RE = /^(\s+)import:\s+(.+\.dgmo)\s*$/i;
16416
16598
  var TAGS_RE = /^tags:\s+(.+\.dgmo)\s*$/i;
16417
16599
  var HEADER_RE = /^(chart|title)\s*:/i;
16418
- var OPTION_RE4 = /^[a-z][a-z0-9-]*\s*:/i;
16419
- var GROUP_HEADING_RE5 = /^##\s+/;
16600
+ var OPTION_RE2 = /^[a-z][a-z0-9-]*\s*:/i;
16420
16601
  function dirname(filePath) {
16421
16602
  const last = filePath.lastIndexOf("/");
16422
16603
  return last > 0 ? filePath.substring(0, last) : "/";
@@ -16437,9 +16618,9 @@ function extractTagGroups(lines) {
16437
16618
  let current = null;
16438
16619
  for (const line7 of lines) {
16439
16620
  const trimmed = line7.trim();
16440
- if (GROUP_HEADING_RE5.test(trimmed)) {
16441
- const nameMatch = trimmed.match(/^##\s+(.+?)(?:\s+alias\s+\w+)?(?:\s*\([^)]+\))?\s*$/);
16442
- const name = nameMatch ? nameMatch[1].trim().toLowerCase() : trimmed.substring(3).trim().toLowerCase();
16621
+ const headingMatch = matchTagBlockHeading(trimmed);
16622
+ if (headingMatch) {
16623
+ const name = headingMatch.name.toLowerCase();
16443
16624
  current = { name, lines: [line7] };
16444
16625
  blocks.push(current);
16445
16626
  } else if (current) {
@@ -16481,7 +16662,7 @@ function parseFileHeader(lines) {
16481
16662
  tagsDirective = tagsMatch[1].trim();
16482
16663
  continue;
16483
16664
  }
16484
- if (OPTION_RE4.test(trimmed) && !trimmed.startsWith("##") && !lines[i].match(/^\s/)) {
16665
+ if (OPTION_RE2.test(trimmed) && !isTagBlockHeading(trimmed) && !lines[i].match(/^\s/)) {
16485
16666
  const key = trimmed.split(":")[0].trim().toLowerCase();
16486
16667
  if (key !== "chart" && key !== "title" && !trimmed.includes("|")) {
16487
16668
  continue;
@@ -16511,7 +16692,7 @@ async function resolveFile(content, filePath, readFileFn, diagnostics, ancestorC
16511
16692
  headerLines.push(lines[i]);
16512
16693
  continue;
16513
16694
  }
16514
- if (GROUP_HEADING_RE5.test(trimmed)) continue;
16695
+ if (isTagBlockHeading(trimmed)) continue;
16515
16696
  if (lines[i] !== trimmed) continue;
16516
16697
  const tagsMatch = trimmed.match(TAGS_RE);
16517
16698
  if (tagsMatch) {
@@ -16543,7 +16724,7 @@ async function resolveFile(content, filePath, readFileFn, diagnostics, ancestorC
16543
16724
  const importMatch = line7.match(IMPORT_RE);
16544
16725
  if (!importMatch) {
16545
16726
  const trimmed = line7.trim();
16546
- if (GROUP_HEADING_RE5.test(trimmed) || inlineTagGroups.length > 0 && isTagGroupEntry(line7, bodyLines, i)) {
16727
+ if (isTagBlockHeading(trimmed) || inlineTagGroups.length > 0 && isTagGroupEntry(line7, bodyLines, i)) {
16547
16728
  continue;
16548
16729
  }
16549
16730
  resolvedBodyLines.push(line7);
@@ -16638,7 +16819,7 @@ function findBodyStart(lines) {
16638
16819
  if (inTagGroup) inTagGroup = false;
16639
16820
  continue;
16640
16821
  }
16641
- if (GROUP_HEADING_RE5.test(trimmed)) {
16822
+ if (isTagBlockHeading(trimmed)) {
16642
16823
  inTagGroup = true;
16643
16824
  continue;
16644
16825
  }
@@ -16650,7 +16831,7 @@ function findBodyStart(lines) {
16650
16831
  }
16651
16832
  if (HEADER_RE.test(trimmed)) continue;
16652
16833
  if (TAGS_RE.test(trimmed)) continue;
16653
- if (OPTION_RE4.test(trimmed) && !lines[i].match(/^\s/) && !trimmed.includes("|")) {
16834
+ if (OPTION_RE2.test(trimmed) && !isTagBlockHeading(trimmed) && !lines[i].match(/^\s/) && !trimmed.includes("|")) {
16654
16835
  const key = trimmed.split(":")[0].trim().toLowerCase();
16655
16836
  if (key !== "chart" && key !== "title") {
16656
16837
  continue;
@@ -16665,7 +16846,7 @@ function isTagGroupEntry(line7, allLines, index) {
16665
16846
  for (let i = index - 1; i >= 0; i--) {
16666
16847
  const prev = allLines[i].trim();
16667
16848
  if (prev === "" || prev.startsWith("//")) continue;
16668
- if (GROUP_HEADING_RE5.test(prev)) return true;
16849
+ if (isTagBlockHeading(prev)) return true;
16669
16850
  if (allLines[i].match(/^\s+/)) continue;
16670
16851
  return false;
16671
16852
  }
@@ -16822,6 +17003,7 @@ export {
16822
17003
  parseERDiagram,
16823
17004
  parseFlowchart,
16824
17005
  parseInitiativeStatus,
17006
+ parseInlineMarkdown,
16825
17007
  parseKanban,
16826
17008
  parseOrg,
16827
17009
  parseQuadrant,
@@ -16865,6 +17047,7 @@ export {
16865
17047
  shade,
16866
17048
  solarizedPalette,
16867
17049
  tint,
16868
- tokyoNightPalette
17050
+ tokyoNightPalette,
17051
+ truncateBareUrl
16869
17052
  };
16870
17053
  //# sourceMappingURL=index.js.map