@diagrammo/dgmo 0.2.27 → 0.2.28

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 (47) 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 +116 -108
  10. package/dist/index.cjs +543 -351
  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 +540 -350
  15. package/dist/index.js.map +1 -1
  16. package/docs/ai-integration.md +125 -0
  17. package/docs/language-reference.md +784 -0
  18. package/package.json +10 -3
  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/parser.ts +2 -10
  24. package/src/class/types.ts +1 -1
  25. package/src/cli.ts +130 -19
  26. package/src/d3.ts +1 -1
  27. package/src/dgmo-mermaid.ts +1 -1
  28. package/src/dgmo-router.ts +1 -1
  29. package/src/echarts.ts +33 -13
  30. package/src/er/parser.ts +34 -43
  31. package/src/er/types.ts +1 -1
  32. package/src/graph/flowchart-parser.ts +2 -25
  33. package/src/graph/types.ts +1 -1
  34. package/src/index.ts +5 -0
  35. package/src/initiative-status/parser.ts +36 -7
  36. package/src/initiative-status/types.ts +1 -1
  37. package/src/kanban/parser.ts +32 -53
  38. package/src/kanban/renderer.ts +9 -8
  39. package/src/kanban/types.ts +6 -14
  40. package/src/org/parser.ts +47 -87
  41. package/src/org/resolver.ts +11 -12
  42. package/src/sequence/parser.ts +97 -15
  43. package/src/sequence/renderer.ts +62 -69
  44. package/src/utils/arrows.ts +75 -0
  45. package/src/utils/inline-markdown.ts +75 -0
  46. package/src/utils/parsing.ts +67 -0
  47. 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;
@@ -2649,7 +2787,7 @@ function looksLikeClassDiagram(content) {
2649
2787
  const trimmed = line7.trim();
2650
2788
  if (!trimmed || trimmed.startsWith("//")) continue;
2651
2789
  if (/^(chart|title)\s*:/i.test(trimmed)) continue;
2652
- const indent = measureIndent3(line7);
2790
+ const indent = measureIndent(line7);
2653
2791
  if (indent === 0) {
2654
2792
  if (/^[A-Z][A-Za-z0-9_]*\s+\[(abstract|interface|enum)\]/i.test(trimmed)) {
2655
2793
  hasModifier = true;
@@ -2680,6 +2818,7 @@ var init_parser2 = __esm({
2680
2818
  "use strict";
2681
2819
  init_colors();
2682
2820
  init_diagnostics();
2821
+ init_parsing();
2683
2822
  CLASS_DECL_RE = /^([A-Z][A-Za-z0-9_]*)(?:\s+\[(abstract|interface|enum)\])?(?:\s+\(([^)]+)\))?\s*$/;
2684
2823
  REL_KEYWORD_RE = /^([A-Z][A-Za-z0-9_]*)\s+(extends|implements|contains|has|uses)\s+([A-Z][A-Za-z0-9_]*)(?:\s*:\s*(.+))?$/;
2685
2824
  REL_ARROW_RE = /^([A-Z][A-Za-z0-9_]*)\s+(--\|>|\.\.\|>|\*--|o--|\.\.\>|->)\s+([A-Z][A-Za-z0-9_]*)(?:\s*:\s*(.+))?$/;
@@ -2711,22 +2850,14 @@ __export(parser_exports3, {
2711
2850
  looksLikeERDiagram: () => looksLikeERDiagram,
2712
2851
  parseERDiagram: () => parseERDiagram
2713
2852
  });
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
2853
  function tableId(name) {
2724
2854
  return name.toLowerCase().trim();
2725
2855
  }
2726
2856
  function parseCardSide(token) {
2727
- return CARD_WORD[token.toLowerCase()] ?? null;
2857
+ if (token === "1" || token === "*" || token === "?") return token;
2858
+ return null;
2728
2859
  }
2729
- function parseRelationship(trimmed) {
2860
+ function parseRelationship(trimmed, lineNumber, pushError) {
2730
2861
  const sym = trimmed.match(REL_SYMBOLIC_RE);
2731
2862
  if (sym) {
2732
2863
  const fromCard = parseCardSide(sym[2]);
@@ -2743,17 +2874,13 @@ function parseRelationship(trimmed) {
2743
2874
  }
2744
2875
  const kw = trimmed.match(REL_KEYWORD_RE2);
2745
2876
  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
- }
2877
+ const fromSym = KEYWORD_TO_SYMBOL[kw[2].toLowerCase()] ?? kw[2];
2878
+ const toSym = KEYWORD_TO_SYMBOL[kw[3].toLowerCase()] ?? kw[3];
2879
+ pushError(
2880
+ lineNumber,
2881
+ `Use symbolic cardinality (1--*, ?--1, *--*) instead of "${kw[2]}-to-${kw[3]}". Example: ${kw[1]} ${fromSym}--${toSym} ${kw[4]}`
2882
+ );
2883
+ return null;
2757
2884
  }
2758
2885
  return null;
2759
2886
  }
@@ -2773,7 +2900,8 @@ function parseERDiagram(content, palette) {
2773
2900
  options: {},
2774
2901
  tables: [],
2775
2902
  relationships: [],
2776
- diagnostics: []
2903
+ diagnostics: [],
2904
+ error: null
2777
2905
  };
2778
2906
  const fail = (line7, message) => {
2779
2907
  const diag = makeDgmoError(line7, message);
@@ -2781,6 +2909,11 @@ function parseERDiagram(content, palette) {
2781
2909
  result.error = formatDgmoError(diag);
2782
2910
  return result;
2783
2911
  };
2912
+ const pushError = (line7, message) => {
2913
+ const diag = makeDgmoError(line7, message);
2914
+ result.diagnostics.push(diag);
2915
+ if (!result.error) result.error = formatDgmoError(diag);
2916
+ };
2784
2917
  const tableMap = /* @__PURE__ */ new Map();
2785
2918
  let currentTable = null;
2786
2919
  let contentStarted = false;
@@ -2802,7 +2935,7 @@ function parseERDiagram(content, palette) {
2802
2935
  const raw = lines[i];
2803
2936
  const trimmed = raw.trim();
2804
2937
  const lineNumber = i + 1;
2805
- const indent = measureIndent4(raw);
2938
+ const indent = measureIndent(raw);
2806
2939
  if (!trimmed) {
2807
2940
  if (indent === 0) currentTable = null;
2808
2941
  continue;
@@ -2851,7 +2984,7 @@ function parseERDiagram(content, palette) {
2851
2984
  }
2852
2985
  currentTable = null;
2853
2986
  contentStarted = true;
2854
- const rel = parseRelationship(trimmed);
2987
+ const rel = parseRelationship(trimmed, lineNumber, pushError);
2855
2988
  if (rel) {
2856
2989
  getOrCreateTable(rel.source, lineNumber);
2857
2990
  getOrCreateTable(rel.target, lineNumber);
@@ -2904,7 +3037,7 @@ function looksLikeERDiagram(content) {
2904
3037
  const trimmed = line7.trim();
2905
3038
  if (!trimmed || trimmed.startsWith("//")) continue;
2906
3039
  if (/^(chart|title|notation)\s*:/i.test(trimmed)) continue;
2907
- const indent = measureIndent4(line7);
3040
+ const indent = measureIndent(line7);
2908
3041
  if (indent > 0) {
2909
3042
  if (/\[(pk|fk)\]/i.test(trimmed)) {
2910
3043
  hasConstraint = true;
@@ -2913,7 +3046,7 @@ function looksLikeERDiagram(content) {
2913
3046
  if (TABLE_DECL_RE.test(trimmed)) {
2914
3047
  hasTableDecl = true;
2915
3048
  }
2916
- if (REL_SYMBOLIC_RE.test(trimmed) || REL_KEYWORD_RE2.test(trimmed)) {
3049
+ if (REL_SYMBOLIC_RE.test(trimmed)) {
2917
3050
  hasRelationship = true;
2918
3051
  }
2919
3052
  }
@@ -2922,12 +3055,13 @@ function looksLikeERDiagram(content) {
2922
3055
  if (hasRelationship && hasTableDecl && hasConstraint) return true;
2923
3056
  return false;
2924
3057
  }
2925
- var TABLE_DECL_RE, COLUMN_RE, CONSTRAINT_MAP, CARD_WORD, REL_SYMBOLIC_RE, REL_KEYWORD_RE2;
3058
+ var TABLE_DECL_RE, COLUMN_RE, CONSTRAINT_MAP, REL_SYMBOLIC_RE, REL_KEYWORD_RE2, KEYWORD_TO_SYMBOL;
2926
3059
  var init_parser3 = __esm({
2927
3060
  "src/er/parser.ts"() {
2928
3061
  "use strict";
2929
3062
  init_colors();
2930
3063
  init_diagnostics();
3064
+ init_parsing();
2931
3065
  TABLE_DECL_RE = /^([a-zA-Z_]\w*)(?:\s+\(([^)]+)\))?\s*$/;
2932
3066
  COLUMN_RE = /^(\w+)(?:\s*:\s*(\w[\w()]*(?:\s*\[\])?))?(?:\s+\[([^\]]+)\])?\s*$/;
2933
3067
  CONSTRAINT_MAP = {
@@ -2936,16 +3070,13 @@ var init_parser3 = __esm({
2936
3070
  unique: "unique",
2937
3071
  nullable: "nullable"
2938
3072
  };
2939
- CARD_WORD = {
3073
+ REL_SYMBOLIC_RE = /^([a-zA-Z_]\w*)\s+([1*?])\s*-{1,2}\s*([1*?])\s+([a-zA-Z_]\w*)(?:\s*:\s*(.+))?$/;
3074
+ REL_KEYWORD_RE2 = /^([a-zA-Z_]\w*)\s+(one|many|zero)[- ]to[- ](one|many|zero)\s+([a-zA-Z_]\w*)(?:\s*:\s*(.+))?$/i;
3075
+ KEYWORD_TO_SYMBOL = {
2940
3076
  one: "1",
2941
3077
  many: "*",
2942
- "1": "1",
2943
- "*": "*",
2944
- "?": "?",
2945
3078
  zero: "?"
2946
3079
  };
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
3080
  }
2950
3081
  });
2951
3082
 
@@ -2955,7 +3086,8 @@ function parseChart(content, palette) {
2955
3086
  const result = {
2956
3087
  type: "bar",
2957
3088
  data: [],
2958
- diagnostics: []
3089
+ diagnostics: [],
3090
+ error: null
2959
3091
  };
2960
3092
  const fail = (line7, message) => {
2961
3093
  const diag = makeDgmoError(line7, message);
@@ -2968,7 +3100,7 @@ function parseChart(content, palette) {
2968
3100
  const lineNumber = i + 1;
2969
3101
  if (!trimmed) continue;
2970
3102
  if (/^#{2,}\s+/.test(trimmed)) continue;
2971
- if (trimmed.startsWith("#") || trimmed.startsWith("//")) continue;
3103
+ if (trimmed.startsWith("//")) continue;
2972
3104
  const colonIndex = trimmed.indexOf(":");
2973
3105
  if (colonIndex === -1) continue;
2974
3106
  const key = trimmed.substring(0, colonIndex).trim().toLowerCase();
@@ -3126,7 +3258,8 @@ function parseEChart(content, palette) {
3126
3258
  const result = {
3127
3259
  type: "scatter",
3128
3260
  data: [],
3129
- diagnostics: []
3261
+ diagnostics: [],
3262
+ error: null
3130
3263
  };
3131
3264
  let currentCategory = "Default";
3132
3265
  for (let i = 0; i < lines.length; i++) {
@@ -3146,7 +3279,7 @@ function parseEChart(content, palette) {
3146
3279
  currentCategory = catName;
3147
3280
  continue;
3148
3281
  }
3149
- if (trimmed.startsWith("#") || trimmed.startsWith("//")) continue;
3282
+ if (trimmed.startsWith("//")) continue;
3150
3283
  const categoryMatch = trimmed.match(/^\[(.+)\]$/);
3151
3284
  if (categoryMatch) {
3152
3285
  currentCategory = categoryMatch[1].trim();
@@ -3775,7 +3908,7 @@ function buildScatterOption(parsed, palette, textColor, axisLineColor, gridOpaci
3775
3908
  }
3776
3909
  },
3777
3910
  grid: {
3778
- left: parsed.ylabel ? "5%" : "3%",
3911
+ left: parsed.ylabel ? "12%" : "3%",
3779
3912
  right: "4%",
3780
3913
  bottom: hasCategories ? "15%" : parsed.xlabel ? "10%" : "3%",
3781
3914
  top: parsed.title ? "15%" : "5%",
@@ -4047,17 +4180,26 @@ function resolveAxisLabels(parsed) {
4047
4180
  yLabel: parsed.ylabel ?? (isHorizontal ? void 0 : parsed.label)
4048
4181
  };
4049
4182
  }
4050
- function makeGridAxis(type, textColor, axisLineColor, splitLineColor, gridOpacity, label, data) {
4183
+ function makeGridAxis(type, textColor, axisLineColor, splitLineColor, gridOpacity, label, data, nameGapOverride) {
4184
+ const defaultGap = type === "value" ? 75 : 40;
4051
4185
  return {
4052
4186
  type,
4053
4187
  ...data && { data },
4054
4188
  axisLine: { lineStyle: { color: axisLineColor } },
4055
- axisLabel: { color: textColor, fontSize: 16, fontFamily: FONT_FAMILY },
4189
+ axisLabel: {
4190
+ color: textColor,
4191
+ fontSize: type === "category" && data ? data.length > 10 ? 11 : data.length > 5 ? 12 : 16 : 16,
4192
+ fontFamily: FONT_FAMILY,
4193
+ ...type === "category" && {
4194
+ interval: 0,
4195
+ formatter: (value) => value.replace(/([a-z])([A-Z])/g, "$1\n$2").replace(/ /g, "\n")
4196
+ }
4197
+ },
4056
4198
  splitLine: { lineStyle: { color: splitLineColor, opacity: gridOpacity } },
4057
4199
  ...label && {
4058
4200
  name: label,
4059
4201
  nameLocation: "middle",
4060
- nameGap: 40,
4202
+ nameGap: nameGapOverride ?? defaultGap,
4061
4203
  nameTextStyle: { color: textColor, fontSize: 18, fontFamily: FONT_FAMILY }
4062
4204
  }
4063
4205
  };
@@ -4112,7 +4254,8 @@ function buildBarOption(parsed, textColor, axisLineColor, splitLineColor, gridOp
4112
4254
  value: d.value,
4113
4255
  itemStyle: { color: d.color ?? colors[i % colors.length] }
4114
4256
  }));
4115
- const categoryAxis = makeGridAxis("category", textColor, axisLineColor, splitLineColor, gridOpacity, isHorizontal ? yLabel : xLabel, labels);
4257
+ const hCatGap = isHorizontal && yLabel ? Math.max(40, Math.max(...labels.map((l) => l.length)) * 8 + 16) : void 0;
4258
+ const categoryAxis = makeGridAxis("category", textColor, axisLineColor, splitLineColor, gridOpacity, isHorizontal ? yLabel : xLabel, labels, hCatGap);
4116
4259
  const valueAxis = makeGridAxis("value", textColor, axisLineColor, splitLineColor, gridOpacity, isHorizontal ? xLabel : yLabel);
4117
4260
  return {
4118
4261
  backgroundColor: "transparent",
@@ -4124,7 +4267,7 @@ function buildBarOption(parsed, textColor, axisLineColor, splitLineColor, gridOp
4124
4267
  axisPointer: { type: "shadow" }
4125
4268
  },
4126
4269
  grid: {
4127
- left: yLabel ? "5%" : "3%",
4270
+ left: yLabel ? "12%" : "3%",
4128
4271
  right: "4%",
4129
4272
  bottom: xLabel ? "10%" : "3%",
4130
4273
  top: parsed.title ? "15%" : "5%",
@@ -4159,7 +4302,7 @@ function buildLineOption(parsed, palette, textColor, axisLineColor, splitLineCol
4159
4302
  axisPointer: { type: "line" }
4160
4303
  },
4161
4304
  grid: {
4162
- left: yLabel ? "5%" : "3%",
4305
+ left: yLabel ? "12%" : "3%",
4163
4306
  right: "4%",
4164
4307
  bottom: xLabel ? "10%" : "3%",
4165
4308
  top: parsed.title ? "15%" : "5%",
@@ -4221,7 +4364,7 @@ function buildMultiLineOption(parsed, textColor, axisLineColor, splitLineColor,
4221
4364
  textStyle: { color: textColor }
4222
4365
  },
4223
4366
  grid: {
4224
- left: yLabel ? "5%" : "3%",
4367
+ left: yLabel ? "12%" : "3%",
4225
4368
  right: "4%",
4226
4369
  bottom: "15%",
4227
4370
  top: parsed.title ? "15%" : "5%",
@@ -4247,7 +4390,7 @@ function buildAreaOption(parsed, palette, textColor, axisLineColor, splitLineCol
4247
4390
  axisPointer: { type: "line" }
4248
4391
  },
4249
4392
  grid: {
4250
- left: yLabel ? "5%" : "3%",
4393
+ left: yLabel ? "12%" : "3%",
4251
4394
  right: "4%",
4252
4395
  bottom: xLabel ? "10%" : "3%",
4253
4396
  top: parsed.title ? "15%" : "5%",
@@ -4444,7 +4587,8 @@ function buildBarStackedOption(parsed, textColor, axisLineColor, splitLineColor,
4444
4587
  }
4445
4588
  };
4446
4589
  });
4447
- const categoryAxis = makeGridAxis("category", textColor, axisLineColor, splitLineColor, gridOpacity, isHorizontal ? yLabel : xLabel, labels);
4590
+ const hCatGap = isHorizontal && yLabel ? Math.max(40, Math.max(...labels.map((l) => l.length)) * 8 + 16) : void 0;
4591
+ const categoryAxis = makeGridAxis("category", textColor, axisLineColor, splitLineColor, gridOpacity, isHorizontal ? yLabel : xLabel, labels, hCatGap);
4448
4592
  const valueAxis = makeGridAxis("value", textColor, axisLineColor, splitLineColor, gridOpacity, isHorizontal ? xLabel : yLabel);
4449
4593
  return {
4450
4594
  backgroundColor: "transparent",
@@ -4461,7 +4605,7 @@ function buildBarStackedOption(parsed, textColor, axisLineColor, splitLineColor,
4461
4605
  textStyle: { color: textColor }
4462
4606
  },
4463
4607
  grid: {
4464
- left: yLabel ? "5%" : "3%",
4608
+ left: yLabel ? "12%" : "3%",
4465
4609
  right: "4%",
4466
4610
  bottom: "15%",
4467
4611
  top: parsed.title ? "15%" : "5%",
@@ -4540,35 +4684,51 @@ var init_echarts = __esm({
4540
4684
  }
4541
4685
  });
4542
4686
 
4687
+ // src/utils/tag-groups.ts
4688
+ function isTagBlockHeading(trimmed) {
4689
+ return TAG_BLOCK_RE.test(trimmed) || GROUP_HEADING_RE2.test(trimmed);
4690
+ }
4691
+ function matchTagBlockHeading(trimmed) {
4692
+ const tagMatch = trimmed.match(TAG_BLOCK_RE);
4693
+ if (tagMatch) {
4694
+ return {
4695
+ name: tagMatch[1].trim(),
4696
+ alias: tagMatch[2] || void 0,
4697
+ colorHint: tagMatch[3] || void 0,
4698
+ deprecated: false
4699
+ };
4700
+ }
4701
+ const groupMatch = trimmed.match(GROUP_HEADING_RE2);
4702
+ if (groupMatch) {
4703
+ return {
4704
+ name: groupMatch[1].trim(),
4705
+ alias: groupMatch[2] || void 0,
4706
+ colorHint: groupMatch[3] || void 0,
4707
+ deprecated: true
4708
+ };
4709
+ }
4710
+ return null;
4711
+ }
4712
+ var TAG_BLOCK_RE, GROUP_HEADING_RE2;
4713
+ var init_tag_groups = __esm({
4714
+ "src/utils/tag-groups.ts"() {
4715
+ "use strict";
4716
+ TAG_BLOCK_RE = /^tag:\s+(.+?)(?:\s+alias\s+(\w+))?(?:\s*\(([^)]+)\))?\s*$/i;
4717
+ GROUP_HEADING_RE2 = /^##\s+(.+?)(?:\s+alias\s+(\w+))?(?:\s*\(([^)]+)\))?\s*$/;
4718
+ }
4719
+ });
4720
+
4543
4721
  // src/org/parser.ts
4544
4722
  var parser_exports4 = {};
4545
4723
  __export(parser_exports4, {
4546
4724
  looksLikeOrg: () => looksLikeOrg,
4547
4725
  parseOrg: () => parseOrg
4548
4726
  });
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
4727
  function looksLikeOrg(content) {
4568
4728
  for (const line7 of content.split("\n")) {
4569
4729
  const trimmed = line7.trim();
4570
4730
  if (!trimmed || trimmed.startsWith("//")) continue;
4571
- if (GROUP_HEADING_RE2.test(trimmed)) return true;
4731
+ if (isTagBlockHeading(trimmed)) return true;
4572
4732
  }
4573
4733
  return false;
4574
4734
  }
@@ -4593,6 +4753,9 @@ function parseOrg(content, palette) {
4593
4753
  result.diagnostics.push(diag);
4594
4754
  if (!result.error) result.error = formatDgmoError(diag);
4595
4755
  };
4756
+ const pushWarning = (line7, message) => {
4757
+ result.diagnostics.push(makeDgmoError(line7, message, "warning"));
4758
+ };
4596
4759
  if (!content || !content.trim()) {
4597
4760
  return fail(0, "No content provided");
4598
4761
  }
@@ -4636,42 +4799,43 @@ function parseOrg(content, palette) {
4636
4799
  continue;
4637
4800
  }
4638
4801
  }
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) {
4802
+ const tagBlockMatch = matchTagBlockHeading(trimmed);
4803
+ if (tagBlockMatch) {
4651
4804
  if (contentStarted) {
4652
- pushError(lineNumber, "Tag groups (##) must appear before org content");
4805
+ pushError(lineNumber, "Tag groups must appear before org content");
4653
4806
  continue;
4654
4807
  }
4655
- const groupName = groupMatch[1].trim();
4656
- const alias = groupMatch[2] || void 0;
4808
+ if (tagBlockMatch.deprecated) {
4809
+ pushWarning(lineNumber, `'## ${tagBlockMatch.name}' is deprecated for tag groups \u2014 use 'tag: ${tagBlockMatch.name}' instead`);
4810
+ }
4657
4811
  currentTagGroup = {
4658
- name: groupName,
4659
- alias,
4812
+ name: tagBlockMatch.name,
4813
+ alias: tagBlockMatch.alias,
4660
4814
  entries: [],
4661
4815
  lineNumber
4662
4816
  };
4663
- if (alias) {
4664
- aliasMap.set(alias.toLowerCase(), groupName.toLowerCase());
4817
+ if (tagBlockMatch.alias) {
4818
+ aliasMap.set(tagBlockMatch.alias.toLowerCase(), tagBlockMatch.name.toLowerCase());
4665
4819
  }
4666
4820
  result.tagGroups.push(currentTagGroup);
4667
4821
  continue;
4668
4822
  }
4823
+ if (!contentStarted && !currentTagGroup && measureIndent(line7) === 0) {
4824
+ const optMatch = trimmed.match(OPTION_RE);
4825
+ if (optMatch) {
4826
+ const key = optMatch[1].trim().toLowerCase();
4827
+ if (key !== "chart" && key !== "title") {
4828
+ result.options[key] = optMatch[2].trim();
4829
+ continue;
4830
+ }
4831
+ }
4832
+ }
4669
4833
  if (currentTagGroup && !contentStarted) {
4670
- const indent2 = measureIndent5(line7);
4834
+ const indent2 = measureIndent(line7);
4671
4835
  if (indent2 > 0) {
4672
4836
  const isDefault = /\bdefault\s*$/.test(trimmed);
4673
4837
  const entryText = isDefault ? trimmed.replace(/\s+default\s*$/, "").trim() : trimmed;
4674
- const { label, color } = extractColor2(entryText, palette);
4838
+ const { label, color } = extractColor(entryText, palette);
4675
4839
  if (!color) {
4676
4840
  pushError(lineNumber, `Expected 'Value(color)' in tag group '${currentTagGroup.name}'`);
4677
4841
  continue;
@@ -4690,12 +4854,12 @@ function parseOrg(content, palette) {
4690
4854
  }
4691
4855
  contentStarted = true;
4692
4856
  currentTagGroup = null;
4693
- const indent = measureIndent5(line7);
4857
+ const indent = measureIndent(line7);
4694
4858
  const containerMatch = trimmed.match(CONTAINER_RE);
4695
4859
  const metadataMatch = trimmed.includes("|") ? null : trimmed.match(METADATA_RE);
4696
4860
  if (containerMatch) {
4697
4861
  const rawLabel = containerMatch[1].trim();
4698
- const { label, color } = extractColor2(rawLabel, palette);
4862
+ const { label, color } = extractColor(rawLabel, palette);
4699
4863
  containerCounter++;
4700
4864
  const node = {
4701
4865
  id: `container-${containerCounter}`,
@@ -4740,24 +4904,8 @@ function parseOrg(content, palette) {
4740
4904
  function parseNodeLabel(trimmed, _indent, lineNumber, palette, counter, aliasMap = /* @__PURE__ */ new Map()) {
4741
4905
  const segments = trimmed.split("|").map((s) => s.trim());
4742
4906
  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
- }
4907
+ const { label, color } = extractColor(rawLabel, palette);
4908
+ const metadata = parsePipeMetadata(segments, aliasMap);
4761
4909
  return {
4762
4910
  id: `node-${counter}`,
4763
4911
  label,
@@ -4795,19 +4943,15 @@ function findMetadataParent(indent, indentStack) {
4795
4943
  }
4796
4944
  return null;
4797
4945
  }
4798
- var COLOR_SUFFIX_RE2, GROUP_HEADING_RE2, CONTAINER_RE, METADATA_RE, CHART_TYPE_RE, TITLE_RE, OPTION_RE;
4946
+ var CONTAINER_RE, METADATA_RE;
4799
4947
  var init_parser4 = __esm({
4800
4948
  "src/org/parser.ts"() {
4801
4949
  "use strict";
4802
- init_colors();
4803
4950
  init_diagnostics();
4804
- COLOR_SUFFIX_RE2 = /\(([^)]+)\)\s*$/;
4805
- GROUP_HEADING_RE2 = /^##\s+(.+?)(?:\s+alias\s+(\w+))?(?:\s*\(([^)]+)\))?\s*$/;
4951
+ init_tag_groups();
4952
+ init_parsing();
4806
4953
  CONTAINER_RE = /^\[([^\]]+)\]$/;
4807
4954
  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
4955
  }
4812
4956
  });
4813
4957
 
@@ -4816,31 +4960,14 @@ var parser_exports5 = {};
4816
4960
  __export(parser_exports5, {
4817
4961
  parseKanban: () => parseKanban
4818
4962
  });
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
4963
  function parseKanban(content, palette) {
4838
4964
  const result = {
4839
4965
  type: "kanban",
4840
4966
  columns: [],
4841
4967
  tagGroups: [],
4842
4968
  options: {},
4843
- diagnostics: []
4969
+ diagnostics: [],
4970
+ error: null
4844
4971
  };
4845
4972
  const fail = (line7, message) => {
4846
4973
  const diag = makeDgmoError(line7, message);
@@ -4873,7 +5000,7 @@ function parseKanban(content, palette) {
4873
5000
  }
4874
5001
  if (trimmed.startsWith("//")) continue;
4875
5002
  if (!contentStarted && !currentTagGroup) {
4876
- const chartMatch = trimmed.match(CHART_TYPE_RE2);
5003
+ const chartMatch = trimmed.match(CHART_TYPE_RE);
4877
5004
  if (chartMatch) {
4878
5005
  const chartType = chartMatch[1].trim().toLowerCase();
4879
5006
  if (chartType !== "kanban") {
@@ -4897,16 +5024,35 @@ function parseKanban(content, palette) {
4897
5024
  }
4898
5025
  }
4899
5026
  if (!contentStarted && !currentTagGroup) {
4900
- const titleMatch = trimmed.match(TITLE_RE2);
5027
+ const titleMatch = trimmed.match(TITLE_RE);
4901
5028
  if (titleMatch) {
4902
5029
  result.title = titleMatch[1].trim();
4903
5030
  result.titleLineNumber = lineNumber;
4904
5031
  continue;
4905
5032
  }
4906
5033
  }
4907
- if (!contentStarted && !currentTagGroup && measureIndent6(line7) === 0) {
4908
- const optMatch = trimmed.match(OPTION_RE2);
4909
- if (optMatch && !trimmed.startsWith("##") && !COLUMN_RE2.test(trimmed)) {
5034
+ if (!contentStarted) {
5035
+ const tagBlockMatch = matchTagBlockHeading(trimmed);
5036
+ if (tagBlockMatch) {
5037
+ if (tagBlockMatch.deprecated) {
5038
+ warn(lineNumber, `'## ${tagBlockMatch.name}' is deprecated for tag groups \u2014 use 'tag: ${tagBlockMatch.name}' instead`);
5039
+ }
5040
+ currentTagGroup = {
5041
+ name: tagBlockMatch.name,
5042
+ alias: tagBlockMatch.alias,
5043
+ entries: [],
5044
+ lineNumber
5045
+ };
5046
+ if (tagBlockMatch.alias) {
5047
+ aliasMap.set(tagBlockMatch.alias.toLowerCase(), tagBlockMatch.name.toLowerCase());
5048
+ }
5049
+ result.tagGroups.push(currentTagGroup);
5050
+ continue;
5051
+ }
5052
+ }
5053
+ if (!contentStarted && !currentTagGroup && measureIndent(line7) === 0) {
5054
+ const optMatch = trimmed.match(OPTION_RE);
5055
+ if (optMatch && !COLUMN_RE2.test(trimmed)) {
4910
5056
  const key = optMatch[1].trim().toLowerCase();
4911
5057
  if (key !== "chart" && key !== "title") {
4912
5058
  result.options[key] = optMatch[2].trim();
@@ -4914,28 +5060,12 @@ function parseKanban(content, palette) {
4914
5060
  }
4915
5061
  }
4916
5062
  }
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
5063
  if (currentTagGroup && !contentStarted) {
4934
- const indent2 = measureIndent6(line7);
5064
+ const indent2 = measureIndent(line7);
4935
5065
  if (indent2 > 0) {
4936
5066
  const isDefault = /\bdefault\s*$/.test(trimmed);
4937
5067
  const entryText = isDefault ? trimmed.replace(/\s+default\s*$/, "").trim() : trimmed;
4938
- const { label, color } = extractColor3(entryText, palette);
5068
+ const { label, color } = extractColor(entryText, palette);
4939
5069
  if (!color) {
4940
5070
  warn(
4941
5071
  lineNumber,
@@ -4969,7 +5099,7 @@ function parseKanban(content, palette) {
4969
5099
  columnCounter++;
4970
5100
  const rawColName = columnMatch[1].trim();
4971
5101
  const wipStr = columnMatch[2];
4972
- const { label: colName, color: colColor } = extractColor3(
5102
+ const { label: colName, color: colColor } = extractColor(
4973
5103
  rawColName,
4974
5104
  palette
4975
5105
  );
@@ -4991,7 +5121,7 @@ function parseKanban(content, palette) {
4991
5121
  warn(lineNumber, "Card line found before any column");
4992
5122
  continue;
4993
5123
  }
4994
- const indent = measureIndent6(line7);
5124
+ const indent = measureIndent(line7);
4995
5125
  if (indent > 0 && currentCard) {
4996
5126
  currentCard.details.push(trimmed);
4997
5127
  currentCard.endLineNumber = lineNumber;
@@ -5056,7 +5186,7 @@ function parseCardLine(trimmed, lineNumber, counter, aliasMap, palette) {
5056
5186
  } else {
5057
5187
  rawTitle = trimmed;
5058
5188
  }
5059
- const { label: title, color } = extractColor3(rawTitle, palette);
5189
+ const { label: title, color } = extractColor(rawTitle, palette);
5060
5190
  const tags = {};
5061
5191
  if (tagsStr) {
5062
5192
  for (const part of tagsStr.split(",")) {
@@ -5079,18 +5209,14 @@ function parseCardLine(trimmed, lineNumber, counter, aliasMap, palette) {
5079
5209
  color
5080
5210
  };
5081
5211
  }
5082
- var CHART_TYPE_RE2, TITLE_RE2, OPTION_RE2, GROUP_HEADING_RE3, COLUMN_RE2, COLOR_SUFFIX_RE3;
5212
+ var COLUMN_RE2;
5083
5213
  var init_parser5 = __esm({
5084
5214
  "src/kanban/parser.ts"() {
5085
5215
  "use strict";
5086
- init_colors();
5087
5216
  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*$/;
5217
+ init_tag_groups();
5218
+ init_parsing();
5092
5219
  COLUMN_RE2 = /^==\s+(.+?)\s*(?:\[wip:\s*(\d+)\])?\s*==$/;
5093
- COLOR_SUFFIX_RE3 = /\(([^)]+)\)\s*$/;
5094
5220
  }
5095
5221
  });
5096
5222
 
@@ -5099,24 +5225,6 @@ var parser_exports6 = {};
5099
5225
  __export(parser_exports6, {
5100
5226
  parseC4: () => parseC4
5101
5227
  });
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
5228
  function participantTypeToC4Shape(pType) {
5121
5229
  switch (pType) {
5122
5230
  case "database":
@@ -5173,23 +5281,6 @@ function parseRelationshipBody(body) {
5173
5281
  }
5174
5282
  return { target, label: rest };
5175
5283
  }
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
5284
  function parseC4(content, palette) {
5194
5285
  const result = {
5195
5286
  title: null,
@@ -5235,7 +5326,7 @@ function parseC4(content, palette) {
5235
5326
  }
5236
5327
  if (trimmed.startsWith("//")) continue;
5237
5328
  if (!contentStarted) {
5238
- const chartMatch = trimmed.match(CHART_TYPE_RE3);
5329
+ const chartMatch = trimmed.match(CHART_TYPE_RE);
5239
5330
  if (chartMatch) {
5240
5331
  const chartType = chartMatch[1].trim().toLowerCase();
5241
5332
  if (chartType !== "c4") {
@@ -5249,49 +5340,50 @@ function parseC4(content, palette) {
5249
5340
  }
5250
5341
  }
5251
5342
  if (!contentStarted) {
5252
- const titleMatch = trimmed.match(TITLE_RE3);
5343
+ const titleMatch = trimmed.match(TITLE_RE);
5253
5344
  if (titleMatch) {
5254
5345
  result.title = titleMatch[1].trim();
5255
5346
  result.titleLineNumber = lineNumber;
5256
5347
  continue;
5257
5348
  }
5258
5349
  }
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) {
5350
+ const tagBlockMatch = matchTagBlockHeading(trimmed);
5351
+ if (tagBlockMatch) {
5271
5352
  if (contentStarted) {
5272
- pushError(lineNumber, "Tag groups (##) must appear before content");
5353
+ pushError(lineNumber, "Tag groups must appear before content");
5273
5354
  continue;
5274
5355
  }
5275
- const groupName = groupMatch[1].trim();
5276
- const alias = groupMatch[2] || void 0;
5356
+ if (tagBlockMatch.deprecated) {
5357
+ pushError(lineNumber, `'## ${tagBlockMatch.name}' is deprecated for tag groups \u2014 use 'tag: ${tagBlockMatch.name}' instead`, "warning");
5358
+ }
5277
5359
  currentTagGroup = {
5278
- name: groupName,
5279
- alias,
5360
+ name: tagBlockMatch.name,
5361
+ alias: tagBlockMatch.alias,
5280
5362
  entries: [],
5281
5363
  lineNumber
5282
5364
  };
5283
- if (alias) {
5284
- aliasMap.set(alias.toLowerCase(), groupName.toLowerCase());
5365
+ if (tagBlockMatch.alias) {
5366
+ aliasMap.set(tagBlockMatch.alias.toLowerCase(), tagBlockMatch.name.toLowerCase());
5285
5367
  }
5286
5368
  result.tagGroups.push(currentTagGroup);
5287
5369
  continue;
5288
5370
  }
5371
+ if (!contentStarted && !currentTagGroup && measureIndent(line7) === 0) {
5372
+ const optMatch = trimmed.match(OPTION_RE);
5373
+ if (optMatch) {
5374
+ const key = optMatch[1].trim().toLowerCase();
5375
+ if (key !== "chart" && key !== "title") {
5376
+ result.options[key] = optMatch[2].trim();
5377
+ continue;
5378
+ }
5379
+ }
5380
+ }
5289
5381
  if (currentTagGroup && !contentStarted) {
5290
- const indent2 = measureIndent7(line7);
5382
+ const indent2 = measureIndent(line7);
5291
5383
  if (indent2 > 0) {
5292
5384
  const isDefault = /\bdefault\s*$/.test(trimmed);
5293
5385
  const entryText = isDefault ? trimmed.replace(/\s+default\s*$/, "").trim() : trimmed;
5294
- const { label, color } = extractColor4(entryText, palette);
5386
+ const { label, color } = extractColor(entryText, palette);
5295
5387
  if (!color) {
5296
5388
  pushError(
5297
5389
  lineNumber,
@@ -5316,7 +5408,7 @@ function parseC4(content, palette) {
5316
5408
  if (!sawChartType) {
5317
5409
  return fail(lineNumber, 'Missing "chart: c4" header');
5318
5410
  }
5319
- const indent = measureIndent7(line7);
5411
+ const indent = measureIndent(line7);
5320
5412
  if (inDeployment) {
5321
5413
  while (deployStack.length > 0) {
5322
5414
  const top = deployStack[deployStack.length - 1];
@@ -5411,6 +5503,45 @@ function parseC4(content, palette) {
5411
5503
  }
5412
5504
  continue;
5413
5505
  }
5506
+ {
5507
+ const labeledPatterns = [
5508
+ { re: C4_LABELED_BIDI_SYNC_RE, arrowType: "bidirectional" },
5509
+ { re: C4_LABELED_BIDI_ASYNC_RE, arrowType: "bidirectional-async" },
5510
+ { re: C4_LABELED_SYNC_RE, arrowType: "sync" },
5511
+ { re: C4_LABELED_ASYNC_RE, arrowType: "async" }
5512
+ ];
5513
+ let labeledHandled = false;
5514
+ for (const { re, arrowType } of labeledPatterns) {
5515
+ const m = trimmed.match(re);
5516
+ if (!m) continue;
5517
+ const rawLabel = m[1].trim();
5518
+ const targetBody = m[2].trim();
5519
+ if (!rawLabel) break;
5520
+ let label = rawLabel;
5521
+ let technology;
5522
+ const techMatch = rawLabel.match(/\[([^\]]+)\]\s*$/);
5523
+ if (techMatch) {
5524
+ label = rawLabel.substring(0, techMatch.index).trim() || void 0;
5525
+ technology = techMatch[1].trim();
5526
+ }
5527
+ const rel = {
5528
+ target: targetBody,
5529
+ label,
5530
+ technology,
5531
+ arrowType,
5532
+ lineNumber
5533
+ };
5534
+ const parentEntry = findParentElement(indent, stack);
5535
+ if (parentEntry) {
5536
+ parentEntry.element.relationships.push(rel);
5537
+ } else {
5538
+ result.relationships.push(rel);
5539
+ }
5540
+ labeledHandled = true;
5541
+ break;
5542
+ }
5543
+ if (labeledHandled) continue;
5544
+ }
5414
5545
  const relMatch = trimmed.match(RELATIONSHIP_RE);
5415
5546
  if (relMatch) {
5416
5547
  const arrowType = parseArrowType(relMatch[1]);
@@ -5600,22 +5731,22 @@ function validateDeploymentRefs(result, knownNames, pushWarning) {
5600
5731
  }
5601
5732
  walkDeploy(result.deployment);
5602
5733
  }
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;
5734
+ 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
5735
  var init_parser6 = __esm({
5605
5736
  "src/c4/parser.ts"() {
5606
5737
  "use strict";
5607
- init_colors();
5608
5738
  init_diagnostics();
5739
+ init_tag_groups();
5609
5740
  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*$/;
5741
+ init_parsing();
5615
5742
  CONTAINER_RE2 = /^\[([^\]]+)\]$/;
5616
5743
  ELEMENT_RE = /^(person|system|container|component)\s+(.+)$/i;
5617
5744
  IS_A_RE = /\s+is\s+a(?:n)?\s+(\w+)\s*$/i;
5618
5745
  RELATIONSHIP_RE = /^(<?-?>|<?~?>)\s+(.+)$/;
5746
+ C4_LABELED_SYNC_RE = /^-(.+)->\s+(.+)$/;
5747
+ C4_LABELED_ASYNC_RE = /^~(.+)~>\s+(.+)$/;
5748
+ C4_LABELED_BIDI_SYNC_RE = /^<-(.+)->\s+(.+)$/;
5749
+ C4_LABELED_BIDI_ASYNC_RE = /^<~(.+)~>\s+(.+)$/;
5619
5750
  SECTION_HEADER_RE = /^(containers|components|deployment)\s*:\s*$/i;
5620
5751
  CONTAINER_REF_RE = /^container\s+(.+)$/i;
5621
5752
  METADATA_RE2 = /^([^:]+):\s*(.+)$/;
@@ -5676,13 +5807,13 @@ function looksLikeInitiativeStatus(content) {
5676
5807
  let hasIndentedArrow = false;
5677
5808
  for (const line7 of lines) {
5678
5809
  const trimmed = line7.trim();
5679
- if (!trimmed || trimmed.startsWith("#") || trimmed.startsWith("//")) continue;
5810
+ if (!trimmed || trimmed.startsWith("//")) continue;
5680
5811
  if (trimmed.match(/^chart\s*:/i)) continue;
5681
5812
  if (trimmed.match(/^title\s*:/i)) continue;
5682
5813
  if (trimmed.includes("->")) hasArrow = true;
5683
5814
  if (/\|\s*(done|wip|todo|na)\s*$/i.test(trimmed)) hasStatus = true;
5684
5815
  const isIndented = line7.length > 0 && line7 !== trimmed && /^\s/.test(line7);
5685
- if (isIndented && trimmed.startsWith("->")) hasIndentedArrow = true;
5816
+ if (isIndented && (trimmed.startsWith("->") || /^-[^>].*->/.test(trimmed))) hasIndentedArrow = true;
5686
5817
  if (hasArrow && hasStatus) return true;
5687
5818
  }
5688
5819
  return hasIndentedArrow;
@@ -5706,7 +5837,7 @@ function parseInitiativeStatus(content) {
5706
5837
  groups: [],
5707
5838
  options: {},
5708
5839
  diagnostics: [],
5709
- error: void 0
5840
+ error: null
5710
5841
  };
5711
5842
  const lines = content.split("\n");
5712
5843
  const nodeLabels = /* @__PURE__ */ new Set();
@@ -5716,7 +5847,7 @@ function parseInitiativeStatus(content) {
5716
5847
  const lineNum = i + 1;
5717
5848
  const raw = lines[i];
5718
5849
  const trimmed = raw.trim();
5719
- if (!trimmed || trimmed.startsWith("#") || trimmed.startsWith("//")) continue;
5850
+ if (!trimmed || trimmed.startsWith("//")) continue;
5720
5851
  const chartMatch = trimmed.match(/^chart\s*:\s*(.+)/i);
5721
5852
  if (chartMatch) {
5722
5853
  const chartType = chartMatch[1].trim().toLowerCase();
@@ -5749,7 +5880,7 @@ function parseInitiativeStatus(content) {
5749
5880
  }
5750
5881
  if (trimmed.includes("->")) {
5751
5882
  let edgeText = trimmed;
5752
- if (trimmed.startsWith("->")) {
5883
+ if (trimmed.startsWith("->") || /^-[^>].*->/.test(trimmed)) {
5753
5884
  if (!lastNodeLabel) {
5754
5885
  result.diagnostics.push(
5755
5886
  makeDgmoError(lineNum, "Indented edge has no preceding node to use as source", "warning")
@@ -5815,6 +5946,27 @@ function parseNodeLine(trimmed, lineNum, diagnostics) {
5815
5946
  return { label: trimmed, status: "na", shape: inferParticipantType(trimmed), lineNumber: lineNum };
5816
5947
  }
5817
5948
  function parseEdgeLine(trimmed, lineNum, diagnostics) {
5949
+ const labeledMatch = trimmed.match(/^(\S+)\s+-(.+)->\s+(.+)$/);
5950
+ if (labeledMatch) {
5951
+ const source2 = labeledMatch[1];
5952
+ const label2 = labeledMatch[2].trim();
5953
+ let targetRest = labeledMatch[3].trim();
5954
+ if (label2) {
5955
+ let status2 = "na";
5956
+ const lastPipe2 = targetRest.lastIndexOf("|");
5957
+ if (lastPipe2 >= 0) {
5958
+ const statusRaw = targetRest.slice(lastPipe2 + 1).trim();
5959
+ status2 = parseStatus(statusRaw, lineNum, diagnostics);
5960
+ targetRest = targetRest.slice(0, lastPipe2).trim();
5961
+ }
5962
+ const target2 = targetRest.trim();
5963
+ if (!target2) {
5964
+ diagnostics.push(makeDgmoError(lineNum, "Edge is missing target"));
5965
+ return null;
5966
+ }
5967
+ return { source: source2, target: target2, label: label2, status: status2, lineNumber: lineNum };
5968
+ }
5969
+ }
5818
5970
  const arrowIdx = trimmed.indexOf("->");
5819
5971
  if (arrowIdx < 0) return null;
5820
5972
  const source = trimmed.slice(0, arrowIdx).trim();
@@ -5869,7 +6021,7 @@ function parseDgmoChartType(content) {
5869
6021
  const lines = content.split("\n");
5870
6022
  for (const line7 of lines) {
5871
6023
  const trimmed = line7.trim();
5872
- if (!trimmed || trimmed.startsWith("#") || trimmed.startsWith("//"))
6024
+ if (!trimmed || trimmed.startsWith("//"))
5873
6025
  continue;
5874
6026
  const match = trimmed.match(/^chart\s*:\s*(.+)/i);
5875
6027
  if (match) return match[1].trim().toLowerCase();
@@ -7131,6 +7283,58 @@ var init_renderer = __esm({
7131
7283
  }
7132
7284
  });
7133
7285
 
7286
+ // src/utils/inline-markdown.ts
7287
+ function parseInlineMarkdown(text) {
7288
+ const spans = [];
7289
+ const regex = /\*\*(.+?)\*\*|__(.+?)__|\*(.+?)\*|_(.+?)_|`(.+?)`|\[(.+?)\]\((.+?)\)|(https?:\/\/[^\s)>\]]+|www\.[^\s)>\]]+)|([^*_`[]+?(?=https?:\/\/|www\.|$)|[^*_`[]+)/g;
7290
+ let match;
7291
+ while ((match = regex.exec(text)) !== null) {
7292
+ if (match[1]) spans.push({ text: match[1], bold: true });
7293
+ else if (match[2]) spans.push({ text: match[2], bold: true });
7294
+ else if (match[3]) spans.push({ text: match[3], italic: true });
7295
+ else if (match[4]) spans.push({ text: match[4], italic: true });
7296
+ else if (match[5]) spans.push({ text: match[5], code: true });
7297
+ else if (match[6]) spans.push({ text: match[6], href: match[7] });
7298
+ else if (match[8]) {
7299
+ const url = match[8];
7300
+ const href = url.startsWith("www.") ? `https://${url}` : url;
7301
+ spans.push({ text: url, href });
7302
+ } else if (match[9]) spans.push({ text: match[9] });
7303
+ }
7304
+ return spans;
7305
+ }
7306
+ function truncateBareUrl(url) {
7307
+ const stripped = url.replace(/^https?:\/\//, "").replace(/^www\./, "");
7308
+ if (stripped.length <= BARE_URL_MAX_DISPLAY) return stripped;
7309
+ return stripped.slice(0, BARE_URL_MAX_DISPLAY - 1) + "\u2026";
7310
+ }
7311
+ function renderInlineText(textEl, text, palette, fontSize) {
7312
+ const spans = parseInlineMarkdown(text);
7313
+ for (const span of spans) {
7314
+ if (span.href) {
7315
+ const isBareUrl = span.text === span.href || `https://${span.text}` === span.href;
7316
+ const display = isBareUrl ? truncateBareUrl(span.text) : span.text;
7317
+ const a = textEl.append("a").attr("href", span.href);
7318
+ a.append("tspan").text(display).attr("fill", palette.primary).style("text-decoration", "underline");
7319
+ } else {
7320
+ const tspan = textEl.append("tspan").text(span.text);
7321
+ if (span.bold) tspan.attr("font-weight", "bold");
7322
+ if (span.italic) tspan.attr("font-style", "italic");
7323
+ if (span.code) {
7324
+ tspan.attr("font-family", "monospace");
7325
+ if (fontSize) tspan.attr("font-size", fontSize - 1);
7326
+ }
7327
+ }
7328
+ }
7329
+ }
7330
+ var BARE_URL_MAX_DISPLAY;
7331
+ var init_inline_markdown = __esm({
7332
+ "src/utils/inline-markdown.ts"() {
7333
+ "use strict";
7334
+ BARE_URL_MAX_DISPLAY = 35;
7335
+ }
7336
+ });
7337
+
7134
7338
  // src/kanban/mutations.ts
7135
7339
  function computeCardMove(content, parsed, cardId, targetColumnId, targetIndex) {
7136
7340
  let sourceCard = null;
@@ -7452,7 +7656,8 @@ function renderKanban(container, parsed, palette, isDark, _onNavigateToLine, exp
7452
7656
  const cx = colLayout.x + cardLayout.x;
7453
7657
  const cy = colLayout.y + cardLayout.y;
7454
7658
  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);
7659
+ 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);
7660
+ renderInlineText(titleEl, card.title, palette, CARD_TITLE_FONT_SIZE);
7456
7661
  if (hasMeta) {
7457
7662
  const separatorY = cy + CARD_HEADER_HEIGHT;
7458
7663
  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 +7669,8 @@ function renderKanban(container, parsed, palette, isDark, _onNavigateToLine, exp
7464
7669
  metaY += CARD_META_LINE_HEIGHT;
7465
7670
  }
7466
7671
  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);
7672
+ 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);
7673
+ renderInlineText(detailEl, detail, palette, CARD_META_FONT_SIZE);
7468
7674
  metaY += CARD_META_LINE_HEIGHT;
7469
7675
  }
7470
7676
  }
@@ -7490,6 +7696,7 @@ var init_renderer2 = __esm({
7490
7696
  "use strict";
7491
7697
  d3Selection2 = __toESM(require("d3-selection"), 1);
7492
7698
  init_fonts();
7699
+ init_inline_markdown();
7493
7700
  init_parser5();
7494
7701
  init_mutations();
7495
7702
  DIAGRAM_PADDING2 = 20;
@@ -10635,7 +10842,8 @@ function renderC4Context(container, parsed, layout, palette, isDark, onClickItem
10635
10842
  const contentWidth = w - CARD_H_PAD3 * 2;
10636
10843
  const lines = wrapText2(node.description, contentWidth, DESC_CHAR_WIDTH2);
10637
10844
  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);
10845
+ 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);
10846
+ renderInlineText(textEl, line7, palette, DESC_FONT_SIZE);
10639
10847
  yPos += DESC_LINE_HEIGHT2;
10640
10848
  }
10641
10849
  }
@@ -11121,7 +11329,8 @@ function renderC4Containers(container, parsed, layout, palette, isDark, onClickI
11121
11329
  const contentWidth = w - CARD_H_PAD3 * 2;
11122
11330
  const lines = wrapText2(node.description, contentWidth, DESC_CHAR_WIDTH2);
11123
11331
  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);
11332
+ 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);
11333
+ renderInlineText(textEl, line7, palette, DESC_FONT_SIZE);
11125
11334
  yPos += DESC_LINE_HEIGHT2;
11126
11335
  }
11127
11336
  }
@@ -11144,7 +11353,8 @@ function renderC4Containers(container, parsed, layout, palette, isDark, onClickI
11144
11353
  const contentWidth = w - CARD_H_PAD3 * 2;
11145
11354
  const lines = wrapText2(node.description, contentWidth, DESC_CHAR_WIDTH2);
11146
11355
  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);
11356
+ 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);
11357
+ renderInlineText(textEl, line7, palette, DESC_FONT_SIZE);
11148
11358
  yPos += DESC_LINE_HEIGHT2;
11149
11359
  }
11150
11360
  }
@@ -11265,6 +11475,7 @@ var init_renderer6 = __esm({
11265
11475
  d3Selection6 = __toESM(require("d3-selection"), 1);
11266
11476
  d3Shape4 = __toESM(require("d3-shape"), 1);
11267
11477
  init_fonts();
11478
+ init_inline_markdown();
11268
11479
  init_parser6();
11269
11480
  init_layout5();
11270
11481
  DIAGRAM_PADDING6 = 20;
@@ -11762,49 +11973,6 @@ __export(renderer_exports7, {
11762
11973
  renderSequenceDiagram: () => renderSequenceDiagram,
11763
11974
  truncateBareUrl: () => truncateBareUrl
11764
11975
  });
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
11976
  function wrapTextLines(text, maxChars) {
11809
11977
  const rawLines = text.split("\n");
11810
11978
  const wrapped = [];
@@ -11985,8 +12153,12 @@ function buildRenderSequence(messages) {
11985
12153
  to: msg.to,
11986
12154
  label: msg.label,
11987
12155
  messageIndex: mi,
11988
- ...msg.async ? { async: true } : {}
12156
+ ...msg.async ? { async: true } : {},
12157
+ ...msg.bidirectional ? { bidirectional: true } : {}
11989
12158
  });
12159
+ if (msg.bidirectional) {
12160
+ continue;
12161
+ }
11990
12162
  if (msg.async) {
11991
12163
  continue;
11992
12164
  }
@@ -12478,6 +12650,14 @@ function renderSequenceDiagram(container, parsed, palette, isDark, _onNavigateTo
12478
12650
  "points",
12479
12651
  `0,0 ${ARROWHEAD_SIZE},${ARROWHEAD_SIZE / 2} 0,${ARROWHEAD_SIZE}`
12480
12652
  ).attr("fill", "none").attr("stroke", palette.text).attr("stroke-width", 1.2);
12653
+ 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(
12654
+ "points",
12655
+ `${ARROWHEAD_SIZE},0 0,${ARROWHEAD_SIZE / 2} ${ARROWHEAD_SIZE},${ARROWHEAD_SIZE}`
12656
+ ).attr("fill", palette.text);
12657
+ 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(
12658
+ "points",
12659
+ `${ARROWHEAD_SIZE},0 0,${ARROWHEAD_SIZE / 2} ${ARROWHEAD_SIZE},${ARROWHEAD_SIZE}`
12660
+ ).attr("fill", "none").attr("stroke", palette.text).attr("stroke-width", 1.2);
12481
12661
  if (title) {
12482
12662
  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
12663
  if (parsed.titleLineNumber) {
@@ -12758,10 +12938,17 @@ function renderSequenceDiagram(container, parsed, palette, isDark, _onNavigateTo
12758
12938
  const x1 = arrowEdgeX(step.from, i, goingRight ? "right" : "left");
12759
12939
  const x2 = arrowEdgeX(step.to, i, goingRight ? "left" : "right");
12760
12940
  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(
12941
+ const markerStartRef = step.bidirectional ? step.async ? "url(#seq-arrowhead-async-reverse)" : "url(#seq-arrowhead-reverse)" : null;
12942
+ 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
12943
  "data-line-number",
12763
12944
  String(messages[step.messageIndex].lineNumber)
12764
12945
  ).attr("data-msg-index", String(step.messageIndex)).attr("data-step-index", String(i));
12946
+ if (markerStartRef) {
12947
+ line7.attr("marker-start", markerStartRef);
12948
+ }
12949
+ if (step.bidirectional && step.async) {
12950
+ line7.attr("stroke-dasharray", "6 4");
12951
+ }
12765
12952
  if (step.label) {
12766
12953
  const midX = (x1 + x2) / 2;
12767
12954
  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 +13136,13 @@ function renderParticipant(svg, participant, cx, cy, palette, isDark) {
12949
13136
  isActor ? PARTICIPANT_BOX_HEIGHT + 14 : PARTICIPANT_BOX_HEIGHT / 2 + 5
12950
13137
  ).attr("text-anchor", "middle").attr("fill", palette.text).attr("font-size", 13).attr("font-weight", 500).text(participant.label);
12951
13138
  }
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;
13139
+ 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
13140
  var init_renderer7 = __esm({
12954
13141
  "src/sequence/renderer.ts"() {
12955
13142
  "use strict";
12956
13143
  d3Selection8 = __toESM(require("d3-selection"), 1);
12957
13144
  init_colors();
13145
+ init_inline_markdown();
12958
13146
  init_fonts();
12959
13147
  init_parser();
12960
13148
  PARTICIPANT_GAP = 160;
@@ -12978,7 +13166,6 @@ var init_renderer7 = __esm({
12978
13166
  NOTE_CHARS_PER_LINE = Math.floor((NOTE_MAX_W - NOTE_PAD_H * 2 - NOTE_FOLD) / NOTE_CHAR_W);
12979
13167
  COLLAPSED_NOTE_H = 20;
12980
13168
  COLLAPSED_NOTE_W = 40;
12981
- BARE_URL_MAX_DISPLAY = 35;
12982
13169
  fill = (palette, isDark) => mix8(palette.primary, isDark ? palette.surface : palette.bg, isDark ? 15 : 30);
12983
13170
  stroke = (palette) => palette.textMuted;
12984
13171
  SW = 1.5;
@@ -13108,7 +13295,7 @@ function parseD3(content, palette) {
13108
13295
  }
13109
13296
  continue;
13110
13297
  }
13111
- if (line7.startsWith("#") || line7.startsWith("//")) {
13298
+ if (line7.startsWith("//")) {
13112
13299
  continue;
13113
13300
  }
13114
13301
  if (result.type === "arc") {
@@ -16281,6 +16468,7 @@ __export(index_exports, {
16281
16468
  parseERDiagram: () => parseERDiagram,
16282
16469
  parseFlowchart: () => parseFlowchart,
16283
16470
  parseInitiativeStatus: () => parseInitiativeStatus,
16471
+ parseInlineMarkdown: () => parseInlineMarkdown,
16284
16472
  parseKanban: () => parseKanban,
16285
16473
  parseOrg: () => parseOrg,
16286
16474
  parseQuadrant: () => parseQuadrant,
@@ -16324,7 +16512,8 @@ __export(index_exports, {
16324
16512
  shade: () => shade,
16325
16513
  solarizedPalette: () => solarizedPalette,
16326
16514
  tint: () => tint,
16327
- tokyoNightPalette: () => tokyoNightPalette
16515
+ tokyoNightPalette: () => tokyoNightPalette,
16516
+ truncateBareUrl: () => truncateBareUrl
16328
16517
  });
16329
16518
  module.exports = __toCommonJS(index_exports);
16330
16519
  init_diagnostics();
@@ -16405,7 +16594,7 @@ function parseQuadrant(content) {
16405
16594
  for (let i = 0; i < lines.length; i++) {
16406
16595
  const line7 = lines[i].trim();
16407
16596
  const lineNumber = i + 1;
16408
- if (!line7 || line7.startsWith("#") || line7.startsWith("//")) continue;
16597
+ if (!line7 || line7.startsWith("//")) continue;
16409
16598
  if (/^chart\s*:/i.test(line7)) continue;
16410
16599
  const titleMatch = line7.match(/^title\s*:\s*(.+)/i);
16411
16600
  if (titleMatch) {
@@ -16535,6 +16724,7 @@ init_renderer3();
16535
16724
  init_parser3();
16536
16725
  init_layout3();
16537
16726
  init_renderer4();
16727
+ init_inline_markdown();
16538
16728
  init_parser4();
16539
16729
  init_layout();
16540
16730
  init_renderer();
@@ -16551,12 +16741,12 @@ init_collapse();
16551
16741
 
16552
16742
  // src/org/resolver.ts
16553
16743
  init_diagnostics();
16744
+ init_tag_groups();
16554
16745
  var MAX_DEPTH = 10;
16555
16746
  var IMPORT_RE = /^(\s+)import:\s+(.+\.dgmo)\s*$/i;
16556
16747
  var TAGS_RE = /^tags:\s+(.+\.dgmo)\s*$/i;
16557
16748
  var HEADER_RE = /^(chart|title)\s*:/i;
16558
- var OPTION_RE4 = /^[a-z][a-z0-9-]*\s*:/i;
16559
- var GROUP_HEADING_RE5 = /^##\s+/;
16749
+ var OPTION_RE2 = /^[a-z][a-z0-9-]*\s*:/i;
16560
16750
  function dirname(filePath) {
16561
16751
  const last = filePath.lastIndexOf("/");
16562
16752
  return last > 0 ? filePath.substring(0, last) : "/";
@@ -16577,9 +16767,9 @@ function extractTagGroups(lines) {
16577
16767
  let current = null;
16578
16768
  for (const line7 of lines) {
16579
16769
  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();
16770
+ const headingMatch = matchTagBlockHeading(trimmed);
16771
+ if (headingMatch) {
16772
+ const name = headingMatch.name.toLowerCase();
16583
16773
  current = { name, lines: [line7] };
16584
16774
  blocks.push(current);
16585
16775
  } else if (current) {
@@ -16621,7 +16811,7 @@ function parseFileHeader(lines) {
16621
16811
  tagsDirective = tagsMatch[1].trim();
16622
16812
  continue;
16623
16813
  }
16624
- if (OPTION_RE4.test(trimmed) && !trimmed.startsWith("##") && !lines[i].match(/^\s/)) {
16814
+ if (OPTION_RE2.test(trimmed) && !isTagBlockHeading(trimmed) && !lines[i].match(/^\s/)) {
16625
16815
  const key = trimmed.split(":")[0].trim().toLowerCase();
16626
16816
  if (key !== "chart" && key !== "title" && !trimmed.includes("|")) {
16627
16817
  continue;
@@ -16651,7 +16841,7 @@ async function resolveFile(content, filePath, readFileFn, diagnostics, ancestorC
16651
16841
  headerLines.push(lines[i]);
16652
16842
  continue;
16653
16843
  }
16654
- if (GROUP_HEADING_RE5.test(trimmed)) continue;
16844
+ if (isTagBlockHeading(trimmed)) continue;
16655
16845
  if (lines[i] !== trimmed) continue;
16656
16846
  const tagsMatch = trimmed.match(TAGS_RE);
16657
16847
  if (tagsMatch) {
@@ -16683,7 +16873,7 @@ async function resolveFile(content, filePath, readFileFn, diagnostics, ancestorC
16683
16873
  const importMatch = line7.match(IMPORT_RE);
16684
16874
  if (!importMatch) {
16685
16875
  const trimmed = line7.trim();
16686
- if (GROUP_HEADING_RE5.test(trimmed) || inlineTagGroups.length > 0 && isTagGroupEntry(line7, bodyLines, i)) {
16876
+ if (isTagBlockHeading(trimmed) || inlineTagGroups.length > 0 && isTagGroupEntry(line7, bodyLines, i)) {
16687
16877
  continue;
16688
16878
  }
16689
16879
  resolvedBodyLines.push(line7);
@@ -16778,7 +16968,7 @@ function findBodyStart(lines) {
16778
16968
  if (inTagGroup) inTagGroup = false;
16779
16969
  continue;
16780
16970
  }
16781
- if (GROUP_HEADING_RE5.test(trimmed)) {
16971
+ if (isTagBlockHeading(trimmed)) {
16782
16972
  inTagGroup = true;
16783
16973
  continue;
16784
16974
  }
@@ -16790,7 +16980,7 @@ function findBodyStart(lines) {
16790
16980
  }
16791
16981
  if (HEADER_RE.test(trimmed)) continue;
16792
16982
  if (TAGS_RE.test(trimmed)) continue;
16793
- if (OPTION_RE4.test(trimmed) && !lines[i].match(/^\s/) && !trimmed.includes("|")) {
16983
+ if (OPTION_RE2.test(trimmed) && !isTagBlockHeading(trimmed) && !lines[i].match(/^\s/) && !trimmed.includes("|")) {
16794
16984
  const key = trimmed.split(":")[0].trim().toLowerCase();
16795
16985
  if (key !== "chart" && key !== "title") {
16796
16986
  continue;
@@ -16805,7 +16995,7 @@ function isTagGroupEntry(line7, allLines, index) {
16805
16995
  for (let i = index - 1; i >= 0; i--) {
16806
16996
  const prev = allLines[i].trim();
16807
16997
  if (prev === "" || prev.startsWith("//")) continue;
16808
- if (GROUP_HEADING_RE5.test(prev)) return true;
16998
+ if (isTagBlockHeading(prev)) return true;
16809
16999
  if (allLines[i].match(/^\s+/)) continue;
16810
17000
  return false;
16811
17001
  }
@@ -16960,6 +17150,7 @@ init_branding();
16960
17150
  parseERDiagram,
16961
17151
  parseFlowchart,
16962
17152
  parseInitiativeStatus,
17153
+ parseInlineMarkdown,
16963
17154
  parseKanban,
16964
17155
  parseOrg,
16965
17156
  parseQuadrant,
@@ -17003,6 +17194,7 @@ init_branding();
17003
17194
  shade,
17004
17195
  solarizedPalette,
17005
17196
  tint,
17006
- tokyoNightPalette
17197
+ tokyoNightPalette,
17198
+ truncateBareUrl
17007
17199
  });
17008
17200
  //# sourceMappingURL=index.cjs.map