@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.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;
@@ -2627,7 +2765,7 @@ function looksLikeClassDiagram(content) {
2627
2765
  const trimmed = line7.trim();
2628
2766
  if (!trimmed || trimmed.startsWith("//")) continue;
2629
2767
  if (/^(chart|title)\s*:/i.test(trimmed)) continue;
2630
- const indent = measureIndent3(line7);
2768
+ const indent = measureIndent(line7);
2631
2769
  if (indent === 0) {
2632
2770
  if (/^[A-Z][A-Za-z0-9_]*\s+\[(abstract|interface|enum)\]/i.test(trimmed)) {
2633
2771
  hasModifier = true;
@@ -2658,6 +2796,7 @@ var init_parser2 = __esm({
2658
2796
  "use strict";
2659
2797
  init_colors();
2660
2798
  init_diagnostics();
2799
+ init_parsing();
2661
2800
  CLASS_DECL_RE = /^([A-Z][A-Za-z0-9_]*)(?:\s+\[(abstract|interface|enum)\])?(?:\s+\(([^)]+)\))?\s*$/;
2662
2801
  REL_KEYWORD_RE = /^([A-Z][A-Za-z0-9_]*)\s+(extends|implements|contains|has|uses)\s+([A-Z][A-Za-z0-9_]*)(?:\s*:\s*(.+))?$/;
2663
2802
  REL_ARROW_RE = /^([A-Z][A-Za-z0-9_]*)\s+(--\|>|\.\.\|>|\*--|o--|\.\.\>|->)\s+([A-Z][A-Za-z0-9_]*)(?:\s*:\s*(.+))?$/;
@@ -2689,22 +2828,14 @@ __export(parser_exports3, {
2689
2828
  looksLikeERDiagram: () => looksLikeERDiagram,
2690
2829
  parseERDiagram: () => parseERDiagram
2691
2830
  });
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
2831
  function tableId(name) {
2702
2832
  return name.toLowerCase().trim();
2703
2833
  }
2704
2834
  function parseCardSide(token) {
2705
- return CARD_WORD[token.toLowerCase()] ?? null;
2835
+ if (token === "1" || token === "*" || token === "?") return token;
2836
+ return null;
2706
2837
  }
2707
- function parseRelationship(trimmed) {
2838
+ function parseRelationship(trimmed, lineNumber, pushError) {
2708
2839
  const sym = trimmed.match(REL_SYMBOLIC_RE);
2709
2840
  if (sym) {
2710
2841
  const fromCard = parseCardSide(sym[2]);
@@ -2721,17 +2852,13 @@ function parseRelationship(trimmed) {
2721
2852
  }
2722
2853
  const kw = trimmed.match(REL_KEYWORD_RE2);
2723
2854
  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
- }
2855
+ const fromSym = KEYWORD_TO_SYMBOL[kw[2].toLowerCase()] ?? kw[2];
2856
+ const toSym = KEYWORD_TO_SYMBOL[kw[3].toLowerCase()] ?? kw[3];
2857
+ pushError(
2858
+ lineNumber,
2859
+ `Use symbolic cardinality (1--*, ?--1, *--*) instead of "${kw[2]}-to-${kw[3]}". Example: ${kw[1]} ${fromSym}--${toSym} ${kw[4]}`
2860
+ );
2861
+ return null;
2735
2862
  }
2736
2863
  return null;
2737
2864
  }
@@ -2751,7 +2878,8 @@ function parseERDiagram(content, palette) {
2751
2878
  options: {},
2752
2879
  tables: [],
2753
2880
  relationships: [],
2754
- diagnostics: []
2881
+ diagnostics: [],
2882
+ error: null
2755
2883
  };
2756
2884
  const fail = (line7, message) => {
2757
2885
  const diag = makeDgmoError(line7, message);
@@ -2759,6 +2887,11 @@ function parseERDiagram(content, palette) {
2759
2887
  result.error = formatDgmoError(diag);
2760
2888
  return result;
2761
2889
  };
2890
+ const pushError = (line7, message) => {
2891
+ const diag = makeDgmoError(line7, message);
2892
+ result.diagnostics.push(diag);
2893
+ if (!result.error) result.error = formatDgmoError(diag);
2894
+ };
2762
2895
  const tableMap = /* @__PURE__ */ new Map();
2763
2896
  let currentTable = null;
2764
2897
  let contentStarted = false;
@@ -2780,7 +2913,7 @@ function parseERDiagram(content, palette) {
2780
2913
  const raw = lines[i];
2781
2914
  const trimmed = raw.trim();
2782
2915
  const lineNumber = i + 1;
2783
- const indent = measureIndent4(raw);
2916
+ const indent = measureIndent(raw);
2784
2917
  if (!trimmed) {
2785
2918
  if (indent === 0) currentTable = null;
2786
2919
  continue;
@@ -2829,7 +2962,7 @@ function parseERDiagram(content, palette) {
2829
2962
  }
2830
2963
  currentTable = null;
2831
2964
  contentStarted = true;
2832
- const rel = parseRelationship(trimmed);
2965
+ const rel = parseRelationship(trimmed, lineNumber, pushError);
2833
2966
  if (rel) {
2834
2967
  getOrCreateTable(rel.source, lineNumber);
2835
2968
  getOrCreateTable(rel.target, lineNumber);
@@ -2882,7 +3015,7 @@ function looksLikeERDiagram(content) {
2882
3015
  const trimmed = line7.trim();
2883
3016
  if (!trimmed || trimmed.startsWith("//")) continue;
2884
3017
  if (/^(chart|title|notation)\s*:/i.test(trimmed)) continue;
2885
- const indent = measureIndent4(line7);
3018
+ const indent = measureIndent(line7);
2886
3019
  if (indent > 0) {
2887
3020
  if (/\[(pk|fk)\]/i.test(trimmed)) {
2888
3021
  hasConstraint = true;
@@ -2891,7 +3024,7 @@ function looksLikeERDiagram(content) {
2891
3024
  if (TABLE_DECL_RE.test(trimmed)) {
2892
3025
  hasTableDecl = true;
2893
3026
  }
2894
- if (REL_SYMBOLIC_RE.test(trimmed) || REL_KEYWORD_RE2.test(trimmed)) {
3027
+ if (REL_SYMBOLIC_RE.test(trimmed)) {
2895
3028
  hasRelationship = true;
2896
3029
  }
2897
3030
  }
@@ -2900,12 +3033,13 @@ function looksLikeERDiagram(content) {
2900
3033
  if (hasRelationship && hasTableDecl && hasConstraint) return true;
2901
3034
  return false;
2902
3035
  }
2903
- var TABLE_DECL_RE, COLUMN_RE, CONSTRAINT_MAP, CARD_WORD, REL_SYMBOLIC_RE, REL_KEYWORD_RE2;
3036
+ var TABLE_DECL_RE, COLUMN_RE, CONSTRAINT_MAP, REL_SYMBOLIC_RE, REL_KEYWORD_RE2, KEYWORD_TO_SYMBOL;
2904
3037
  var init_parser3 = __esm({
2905
3038
  "src/er/parser.ts"() {
2906
3039
  "use strict";
2907
3040
  init_colors();
2908
3041
  init_diagnostics();
3042
+ init_parsing();
2909
3043
  TABLE_DECL_RE = /^([a-zA-Z_]\w*)(?:\s+\(([^)]+)\))?\s*$/;
2910
3044
  COLUMN_RE = /^(\w+)(?:\s*:\s*(\w[\w()]*(?:\s*\[\])?))?(?:\s+\[([^\]]+)\])?\s*$/;
2911
3045
  CONSTRAINT_MAP = {
@@ -2914,16 +3048,13 @@ var init_parser3 = __esm({
2914
3048
  unique: "unique",
2915
3049
  nullable: "nullable"
2916
3050
  };
2917
- CARD_WORD = {
3051
+ REL_SYMBOLIC_RE = /^([a-zA-Z_]\w*)\s+([1*?])\s*-{1,2}\s*([1*?])\s+([a-zA-Z_]\w*)(?:\s*:\s*(.+))?$/;
3052
+ REL_KEYWORD_RE2 = /^([a-zA-Z_]\w*)\s+(one|many|zero)[- ]to[- ](one|many|zero)\s+([a-zA-Z_]\w*)(?:\s*:\s*(.+))?$/i;
3053
+ KEYWORD_TO_SYMBOL = {
2918
3054
  one: "1",
2919
3055
  many: "*",
2920
- "1": "1",
2921
- "*": "*",
2922
- "?": "?",
2923
3056
  zero: "?"
2924
3057
  };
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
3058
  }
2928
3059
  });
2929
3060
 
@@ -2933,7 +3064,8 @@ function parseChart(content, palette) {
2933
3064
  const result = {
2934
3065
  type: "bar",
2935
3066
  data: [],
2936
- diagnostics: []
3067
+ diagnostics: [],
3068
+ error: null
2937
3069
  };
2938
3070
  const fail = (line7, message) => {
2939
3071
  const diag = makeDgmoError(line7, message);
@@ -2946,7 +3078,7 @@ function parseChart(content, palette) {
2946
3078
  const lineNumber = i + 1;
2947
3079
  if (!trimmed) continue;
2948
3080
  if (/^#{2,}\s+/.test(trimmed)) continue;
2949
- if (trimmed.startsWith("#") || trimmed.startsWith("//")) continue;
3081
+ if (trimmed.startsWith("//")) continue;
2950
3082
  const colonIndex = trimmed.indexOf(":");
2951
3083
  if (colonIndex === -1) continue;
2952
3084
  const key = trimmed.substring(0, colonIndex).trim().toLowerCase();
@@ -3105,7 +3237,8 @@ function parseEChart(content, palette) {
3105
3237
  const result = {
3106
3238
  type: "scatter",
3107
3239
  data: [],
3108
- diagnostics: []
3240
+ diagnostics: [],
3241
+ error: null
3109
3242
  };
3110
3243
  let currentCategory = "Default";
3111
3244
  for (let i = 0; i < lines.length; i++) {
@@ -3125,7 +3258,7 @@ function parseEChart(content, palette) {
3125
3258
  currentCategory = catName;
3126
3259
  continue;
3127
3260
  }
3128
- if (trimmed.startsWith("#") || trimmed.startsWith("//")) continue;
3261
+ if (trimmed.startsWith("//")) continue;
3129
3262
  const categoryMatch = trimmed.match(/^\[(.+)\]$/);
3130
3263
  if (categoryMatch) {
3131
3264
  currentCategory = categoryMatch[1].trim();
@@ -3754,7 +3887,7 @@ function buildScatterOption(parsed, palette, textColor, axisLineColor, gridOpaci
3754
3887
  }
3755
3888
  },
3756
3889
  grid: {
3757
- left: parsed.ylabel ? "5%" : "3%",
3890
+ left: parsed.ylabel ? "12%" : "3%",
3758
3891
  right: "4%",
3759
3892
  bottom: hasCategories ? "15%" : parsed.xlabel ? "10%" : "3%",
3760
3893
  top: parsed.title ? "15%" : "5%",
@@ -4026,17 +4159,26 @@ function resolveAxisLabels(parsed) {
4026
4159
  yLabel: parsed.ylabel ?? (isHorizontal ? void 0 : parsed.label)
4027
4160
  };
4028
4161
  }
4029
- function makeGridAxis(type, textColor, axisLineColor, splitLineColor, gridOpacity, label, data) {
4162
+ function makeGridAxis(type, textColor, axisLineColor, splitLineColor, gridOpacity, label, data, nameGapOverride) {
4163
+ const defaultGap = type === "value" ? 75 : 40;
4030
4164
  return {
4031
4165
  type,
4032
4166
  ...data && { data },
4033
4167
  axisLine: { lineStyle: { color: axisLineColor } },
4034
- axisLabel: { color: textColor, fontSize: 16, fontFamily: FONT_FAMILY },
4168
+ axisLabel: {
4169
+ color: textColor,
4170
+ fontSize: type === "category" && data ? data.length > 10 ? 11 : data.length > 5 ? 12 : 16 : 16,
4171
+ fontFamily: FONT_FAMILY,
4172
+ ...type === "category" && {
4173
+ interval: 0,
4174
+ formatter: (value) => value.replace(/([a-z])([A-Z])/g, "$1\n$2").replace(/ /g, "\n")
4175
+ }
4176
+ },
4035
4177
  splitLine: { lineStyle: { color: splitLineColor, opacity: gridOpacity } },
4036
4178
  ...label && {
4037
4179
  name: label,
4038
4180
  nameLocation: "middle",
4039
- nameGap: 40,
4181
+ nameGap: nameGapOverride ?? defaultGap,
4040
4182
  nameTextStyle: { color: textColor, fontSize: 18, fontFamily: FONT_FAMILY }
4041
4183
  }
4042
4184
  };
@@ -4091,7 +4233,8 @@ function buildBarOption(parsed, textColor, axisLineColor, splitLineColor, gridOp
4091
4233
  value: d.value,
4092
4234
  itemStyle: { color: d.color ?? colors[i % colors.length] }
4093
4235
  }));
4094
- const categoryAxis = makeGridAxis("category", textColor, axisLineColor, splitLineColor, gridOpacity, isHorizontal ? yLabel : xLabel, labels);
4236
+ const hCatGap = isHorizontal && yLabel ? Math.max(40, Math.max(...labels.map((l) => l.length)) * 8 + 16) : void 0;
4237
+ const categoryAxis = makeGridAxis("category", textColor, axisLineColor, splitLineColor, gridOpacity, isHorizontal ? yLabel : xLabel, labels, hCatGap);
4095
4238
  const valueAxis = makeGridAxis("value", textColor, axisLineColor, splitLineColor, gridOpacity, isHorizontal ? xLabel : yLabel);
4096
4239
  return {
4097
4240
  backgroundColor: "transparent",
@@ -4103,7 +4246,7 @@ function buildBarOption(parsed, textColor, axisLineColor, splitLineColor, gridOp
4103
4246
  axisPointer: { type: "shadow" }
4104
4247
  },
4105
4248
  grid: {
4106
- left: yLabel ? "5%" : "3%",
4249
+ left: yLabel ? "12%" : "3%",
4107
4250
  right: "4%",
4108
4251
  bottom: xLabel ? "10%" : "3%",
4109
4252
  top: parsed.title ? "15%" : "5%",
@@ -4138,7 +4281,7 @@ function buildLineOption(parsed, palette, textColor, axisLineColor, splitLineCol
4138
4281
  axisPointer: { type: "line" }
4139
4282
  },
4140
4283
  grid: {
4141
- left: yLabel ? "5%" : "3%",
4284
+ left: yLabel ? "12%" : "3%",
4142
4285
  right: "4%",
4143
4286
  bottom: xLabel ? "10%" : "3%",
4144
4287
  top: parsed.title ? "15%" : "5%",
@@ -4200,7 +4343,7 @@ function buildMultiLineOption(parsed, textColor, axisLineColor, splitLineColor,
4200
4343
  textStyle: { color: textColor }
4201
4344
  },
4202
4345
  grid: {
4203
- left: yLabel ? "5%" : "3%",
4346
+ left: yLabel ? "12%" : "3%",
4204
4347
  right: "4%",
4205
4348
  bottom: "15%",
4206
4349
  top: parsed.title ? "15%" : "5%",
@@ -4226,7 +4369,7 @@ function buildAreaOption(parsed, palette, textColor, axisLineColor, splitLineCol
4226
4369
  axisPointer: { type: "line" }
4227
4370
  },
4228
4371
  grid: {
4229
- left: yLabel ? "5%" : "3%",
4372
+ left: yLabel ? "12%" : "3%",
4230
4373
  right: "4%",
4231
4374
  bottom: xLabel ? "10%" : "3%",
4232
4375
  top: parsed.title ? "15%" : "5%",
@@ -4423,7 +4566,8 @@ function buildBarStackedOption(parsed, textColor, axisLineColor, splitLineColor,
4423
4566
  }
4424
4567
  };
4425
4568
  });
4426
- const categoryAxis = makeGridAxis("category", textColor, axisLineColor, splitLineColor, gridOpacity, isHorizontal ? yLabel : xLabel, labels);
4569
+ const hCatGap = isHorizontal && yLabel ? Math.max(40, Math.max(...labels.map((l) => l.length)) * 8 + 16) : void 0;
4570
+ const categoryAxis = makeGridAxis("category", textColor, axisLineColor, splitLineColor, gridOpacity, isHorizontal ? yLabel : xLabel, labels, hCatGap);
4427
4571
  const valueAxis = makeGridAxis("value", textColor, axisLineColor, splitLineColor, gridOpacity, isHorizontal ? xLabel : yLabel);
4428
4572
  return {
4429
4573
  backgroundColor: "transparent",
@@ -4440,7 +4584,7 @@ function buildBarStackedOption(parsed, textColor, axisLineColor, splitLineColor,
4440
4584
  textStyle: { color: textColor }
4441
4585
  },
4442
4586
  grid: {
4443
- left: yLabel ? "5%" : "3%",
4587
+ left: yLabel ? "12%" : "3%",
4444
4588
  right: "4%",
4445
4589
  bottom: "15%",
4446
4590
  top: parsed.title ? "15%" : "5%",
@@ -4518,35 +4662,51 @@ var init_echarts = __esm({
4518
4662
  }
4519
4663
  });
4520
4664
 
4665
+ // src/utils/tag-groups.ts
4666
+ function isTagBlockHeading(trimmed) {
4667
+ return TAG_BLOCK_RE.test(trimmed) || GROUP_HEADING_RE2.test(trimmed);
4668
+ }
4669
+ function matchTagBlockHeading(trimmed) {
4670
+ const tagMatch = trimmed.match(TAG_BLOCK_RE);
4671
+ if (tagMatch) {
4672
+ return {
4673
+ name: tagMatch[1].trim(),
4674
+ alias: tagMatch[2] || void 0,
4675
+ colorHint: tagMatch[3] || void 0,
4676
+ deprecated: false
4677
+ };
4678
+ }
4679
+ const groupMatch = trimmed.match(GROUP_HEADING_RE2);
4680
+ if (groupMatch) {
4681
+ return {
4682
+ name: groupMatch[1].trim(),
4683
+ alias: groupMatch[2] || void 0,
4684
+ colorHint: groupMatch[3] || void 0,
4685
+ deprecated: true
4686
+ };
4687
+ }
4688
+ return null;
4689
+ }
4690
+ var TAG_BLOCK_RE, GROUP_HEADING_RE2;
4691
+ var init_tag_groups = __esm({
4692
+ "src/utils/tag-groups.ts"() {
4693
+ "use strict";
4694
+ TAG_BLOCK_RE = /^tag:\s+(.+?)(?:\s+alias\s+(\w+))?(?:\s*\(([^)]+)\))?\s*$/i;
4695
+ GROUP_HEADING_RE2 = /^##\s+(.+?)(?:\s+alias\s+(\w+))?(?:\s*\(([^)]+)\))?\s*$/;
4696
+ }
4697
+ });
4698
+
4521
4699
  // src/org/parser.ts
4522
4700
  var parser_exports4 = {};
4523
4701
  __export(parser_exports4, {
4524
4702
  looksLikeOrg: () => looksLikeOrg,
4525
4703
  parseOrg: () => parseOrg
4526
4704
  });
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
4705
  function looksLikeOrg(content) {
4546
4706
  for (const line7 of content.split("\n")) {
4547
4707
  const trimmed = line7.trim();
4548
4708
  if (!trimmed || trimmed.startsWith("//")) continue;
4549
- if (GROUP_HEADING_RE2.test(trimmed)) return true;
4709
+ if (isTagBlockHeading(trimmed)) return true;
4550
4710
  }
4551
4711
  return false;
4552
4712
  }
@@ -4571,6 +4731,9 @@ function parseOrg(content, palette) {
4571
4731
  result.diagnostics.push(diag);
4572
4732
  if (!result.error) result.error = formatDgmoError(diag);
4573
4733
  };
4734
+ const pushWarning = (line7, message) => {
4735
+ result.diagnostics.push(makeDgmoError(line7, message, "warning"));
4736
+ };
4574
4737
  if (!content || !content.trim()) {
4575
4738
  return fail(0, "No content provided");
4576
4739
  }
@@ -4614,42 +4777,43 @@ function parseOrg(content, palette) {
4614
4777
  continue;
4615
4778
  }
4616
4779
  }
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) {
4780
+ const tagBlockMatch = matchTagBlockHeading(trimmed);
4781
+ if (tagBlockMatch) {
4629
4782
  if (contentStarted) {
4630
- pushError(lineNumber, "Tag groups (##) must appear before org content");
4783
+ pushError(lineNumber, "Tag groups must appear before org content");
4631
4784
  continue;
4632
4785
  }
4633
- const groupName = groupMatch[1].trim();
4634
- const alias = groupMatch[2] || void 0;
4786
+ if (tagBlockMatch.deprecated) {
4787
+ pushWarning(lineNumber, `'## ${tagBlockMatch.name}' is deprecated for tag groups \u2014 use 'tag: ${tagBlockMatch.name}' instead`);
4788
+ }
4635
4789
  currentTagGroup = {
4636
- name: groupName,
4637
- alias,
4790
+ name: tagBlockMatch.name,
4791
+ alias: tagBlockMatch.alias,
4638
4792
  entries: [],
4639
4793
  lineNumber
4640
4794
  };
4641
- if (alias) {
4642
- aliasMap.set(alias.toLowerCase(), groupName.toLowerCase());
4795
+ if (tagBlockMatch.alias) {
4796
+ aliasMap.set(tagBlockMatch.alias.toLowerCase(), tagBlockMatch.name.toLowerCase());
4643
4797
  }
4644
4798
  result.tagGroups.push(currentTagGroup);
4645
4799
  continue;
4646
4800
  }
4801
+ if (!contentStarted && !currentTagGroup && measureIndent(line7) === 0) {
4802
+ const optMatch = trimmed.match(OPTION_RE);
4803
+ if (optMatch) {
4804
+ const key = optMatch[1].trim().toLowerCase();
4805
+ if (key !== "chart" && key !== "title") {
4806
+ result.options[key] = optMatch[2].trim();
4807
+ continue;
4808
+ }
4809
+ }
4810
+ }
4647
4811
  if (currentTagGroup && !contentStarted) {
4648
- const indent2 = measureIndent5(line7);
4812
+ const indent2 = measureIndent(line7);
4649
4813
  if (indent2 > 0) {
4650
4814
  const isDefault = /\bdefault\s*$/.test(trimmed);
4651
4815
  const entryText = isDefault ? trimmed.replace(/\s+default\s*$/, "").trim() : trimmed;
4652
- const { label, color } = extractColor2(entryText, palette);
4816
+ const { label, color } = extractColor(entryText, palette);
4653
4817
  if (!color) {
4654
4818
  pushError(lineNumber, `Expected 'Value(color)' in tag group '${currentTagGroup.name}'`);
4655
4819
  continue;
@@ -4668,12 +4832,12 @@ function parseOrg(content, palette) {
4668
4832
  }
4669
4833
  contentStarted = true;
4670
4834
  currentTagGroup = null;
4671
- const indent = measureIndent5(line7);
4835
+ const indent = measureIndent(line7);
4672
4836
  const containerMatch = trimmed.match(CONTAINER_RE);
4673
4837
  const metadataMatch = trimmed.includes("|") ? null : trimmed.match(METADATA_RE);
4674
4838
  if (containerMatch) {
4675
4839
  const rawLabel = containerMatch[1].trim();
4676
- const { label, color } = extractColor2(rawLabel, palette);
4840
+ const { label, color } = extractColor(rawLabel, palette);
4677
4841
  containerCounter++;
4678
4842
  const node = {
4679
4843
  id: `container-${containerCounter}`,
@@ -4718,24 +4882,8 @@ function parseOrg(content, palette) {
4718
4882
  function parseNodeLabel(trimmed, _indent, lineNumber, palette, counter, aliasMap = /* @__PURE__ */ new Map()) {
4719
4883
  const segments = trimmed.split("|").map((s) => s.trim());
4720
4884
  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
- }
4885
+ const { label, color } = extractColor(rawLabel, palette);
4886
+ const metadata = parsePipeMetadata(segments, aliasMap);
4739
4887
  return {
4740
4888
  id: `node-${counter}`,
4741
4889
  label,
@@ -4773,19 +4921,15 @@ function findMetadataParent(indent, indentStack) {
4773
4921
  }
4774
4922
  return null;
4775
4923
  }
4776
- var COLOR_SUFFIX_RE2, GROUP_HEADING_RE2, CONTAINER_RE, METADATA_RE, CHART_TYPE_RE, TITLE_RE, OPTION_RE;
4924
+ var CONTAINER_RE, METADATA_RE;
4777
4925
  var init_parser4 = __esm({
4778
4926
  "src/org/parser.ts"() {
4779
4927
  "use strict";
4780
- init_colors();
4781
4928
  init_diagnostics();
4782
- COLOR_SUFFIX_RE2 = /\(([^)]+)\)\s*$/;
4783
- GROUP_HEADING_RE2 = /^##\s+(.+?)(?:\s+alias\s+(\w+))?(?:\s*\(([^)]+)\))?\s*$/;
4929
+ init_tag_groups();
4930
+ init_parsing();
4784
4931
  CONTAINER_RE = /^\[([^\]]+)\]$/;
4785
4932
  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
4933
  }
4790
4934
  });
4791
4935
 
@@ -4794,31 +4938,14 @@ var parser_exports5 = {};
4794
4938
  __export(parser_exports5, {
4795
4939
  parseKanban: () => parseKanban
4796
4940
  });
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
4941
  function parseKanban(content, palette) {
4816
4942
  const result = {
4817
4943
  type: "kanban",
4818
4944
  columns: [],
4819
4945
  tagGroups: [],
4820
4946
  options: {},
4821
- diagnostics: []
4947
+ diagnostics: [],
4948
+ error: null
4822
4949
  };
4823
4950
  const fail = (line7, message) => {
4824
4951
  const diag = makeDgmoError(line7, message);
@@ -4851,7 +4978,7 @@ function parseKanban(content, palette) {
4851
4978
  }
4852
4979
  if (trimmed.startsWith("//")) continue;
4853
4980
  if (!contentStarted && !currentTagGroup) {
4854
- const chartMatch = trimmed.match(CHART_TYPE_RE2);
4981
+ const chartMatch = trimmed.match(CHART_TYPE_RE);
4855
4982
  if (chartMatch) {
4856
4983
  const chartType = chartMatch[1].trim().toLowerCase();
4857
4984
  if (chartType !== "kanban") {
@@ -4875,16 +5002,35 @@ function parseKanban(content, palette) {
4875
5002
  }
4876
5003
  }
4877
5004
  if (!contentStarted && !currentTagGroup) {
4878
- const titleMatch = trimmed.match(TITLE_RE2);
5005
+ const titleMatch = trimmed.match(TITLE_RE);
4879
5006
  if (titleMatch) {
4880
5007
  result.title = titleMatch[1].trim();
4881
5008
  result.titleLineNumber = lineNumber;
4882
5009
  continue;
4883
5010
  }
4884
5011
  }
4885
- if (!contentStarted && !currentTagGroup && measureIndent6(line7) === 0) {
4886
- const optMatch = trimmed.match(OPTION_RE2);
4887
- if (optMatch && !trimmed.startsWith("##") && !COLUMN_RE2.test(trimmed)) {
5012
+ if (!contentStarted) {
5013
+ const tagBlockMatch = matchTagBlockHeading(trimmed);
5014
+ if (tagBlockMatch) {
5015
+ if (tagBlockMatch.deprecated) {
5016
+ warn(lineNumber, `'## ${tagBlockMatch.name}' is deprecated for tag groups \u2014 use 'tag: ${tagBlockMatch.name}' instead`);
5017
+ }
5018
+ currentTagGroup = {
5019
+ name: tagBlockMatch.name,
5020
+ alias: tagBlockMatch.alias,
5021
+ entries: [],
5022
+ lineNumber
5023
+ };
5024
+ if (tagBlockMatch.alias) {
5025
+ aliasMap.set(tagBlockMatch.alias.toLowerCase(), tagBlockMatch.name.toLowerCase());
5026
+ }
5027
+ result.tagGroups.push(currentTagGroup);
5028
+ continue;
5029
+ }
5030
+ }
5031
+ if (!contentStarted && !currentTagGroup && measureIndent(line7) === 0) {
5032
+ const optMatch = trimmed.match(OPTION_RE);
5033
+ if (optMatch && !COLUMN_RE2.test(trimmed)) {
4888
5034
  const key = optMatch[1].trim().toLowerCase();
4889
5035
  if (key !== "chart" && key !== "title") {
4890
5036
  result.options[key] = optMatch[2].trim();
@@ -4892,28 +5038,12 @@ function parseKanban(content, palette) {
4892
5038
  }
4893
5039
  }
4894
5040
  }
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
5041
  if (currentTagGroup && !contentStarted) {
4912
- const indent2 = measureIndent6(line7);
5042
+ const indent2 = measureIndent(line7);
4913
5043
  if (indent2 > 0) {
4914
5044
  const isDefault = /\bdefault\s*$/.test(trimmed);
4915
5045
  const entryText = isDefault ? trimmed.replace(/\s+default\s*$/, "").trim() : trimmed;
4916
- const { label, color } = extractColor3(entryText, palette);
5046
+ const { label, color } = extractColor(entryText, palette);
4917
5047
  if (!color) {
4918
5048
  warn(
4919
5049
  lineNumber,
@@ -4947,7 +5077,7 @@ function parseKanban(content, palette) {
4947
5077
  columnCounter++;
4948
5078
  const rawColName = columnMatch[1].trim();
4949
5079
  const wipStr = columnMatch[2];
4950
- const { label: colName, color: colColor } = extractColor3(
5080
+ const { label: colName, color: colColor } = extractColor(
4951
5081
  rawColName,
4952
5082
  palette
4953
5083
  );
@@ -4969,7 +5099,7 @@ function parseKanban(content, palette) {
4969
5099
  warn(lineNumber, "Card line found before any column");
4970
5100
  continue;
4971
5101
  }
4972
- const indent = measureIndent6(line7);
5102
+ const indent = measureIndent(line7);
4973
5103
  if (indent > 0 && currentCard) {
4974
5104
  currentCard.details.push(trimmed);
4975
5105
  currentCard.endLineNumber = lineNumber;
@@ -5034,7 +5164,7 @@ function parseCardLine(trimmed, lineNumber, counter, aliasMap, palette) {
5034
5164
  } else {
5035
5165
  rawTitle = trimmed;
5036
5166
  }
5037
- const { label: title, color } = extractColor3(rawTitle, palette);
5167
+ const { label: title, color } = extractColor(rawTitle, palette);
5038
5168
  const tags = {};
5039
5169
  if (tagsStr) {
5040
5170
  for (const part of tagsStr.split(",")) {
@@ -5057,18 +5187,14 @@ function parseCardLine(trimmed, lineNumber, counter, aliasMap, palette) {
5057
5187
  color
5058
5188
  };
5059
5189
  }
5060
- var CHART_TYPE_RE2, TITLE_RE2, OPTION_RE2, GROUP_HEADING_RE3, COLUMN_RE2, COLOR_SUFFIX_RE3;
5190
+ var COLUMN_RE2;
5061
5191
  var init_parser5 = __esm({
5062
5192
  "src/kanban/parser.ts"() {
5063
5193
  "use strict";
5064
- init_colors();
5065
5194
  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*$/;
5195
+ init_tag_groups();
5196
+ init_parsing();
5070
5197
  COLUMN_RE2 = /^==\s+(.+?)\s*(?:\[wip:\s*(\d+)\])?\s*==$/;
5071
- COLOR_SUFFIX_RE3 = /\(([^)]+)\)\s*$/;
5072
5198
  }
5073
5199
  });
5074
5200
 
@@ -5077,24 +5203,6 @@ var parser_exports6 = {};
5077
5203
  __export(parser_exports6, {
5078
5204
  parseC4: () => parseC4
5079
5205
  });
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
5206
  function participantTypeToC4Shape(pType) {
5099
5207
  switch (pType) {
5100
5208
  case "database":
@@ -5151,23 +5259,6 @@ function parseRelationshipBody(body) {
5151
5259
  }
5152
5260
  return { target, label: rest };
5153
5261
  }
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
5262
  function parseC4(content, palette) {
5172
5263
  const result = {
5173
5264
  title: null,
@@ -5213,7 +5304,7 @@ function parseC4(content, palette) {
5213
5304
  }
5214
5305
  if (trimmed.startsWith("//")) continue;
5215
5306
  if (!contentStarted) {
5216
- const chartMatch = trimmed.match(CHART_TYPE_RE3);
5307
+ const chartMatch = trimmed.match(CHART_TYPE_RE);
5217
5308
  if (chartMatch) {
5218
5309
  const chartType = chartMatch[1].trim().toLowerCase();
5219
5310
  if (chartType !== "c4") {
@@ -5227,49 +5318,50 @@ function parseC4(content, palette) {
5227
5318
  }
5228
5319
  }
5229
5320
  if (!contentStarted) {
5230
- const titleMatch = trimmed.match(TITLE_RE3);
5321
+ const titleMatch = trimmed.match(TITLE_RE);
5231
5322
  if (titleMatch) {
5232
5323
  result.title = titleMatch[1].trim();
5233
5324
  result.titleLineNumber = lineNumber;
5234
5325
  continue;
5235
5326
  }
5236
5327
  }
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) {
5328
+ const tagBlockMatch = matchTagBlockHeading(trimmed);
5329
+ if (tagBlockMatch) {
5249
5330
  if (contentStarted) {
5250
- pushError(lineNumber, "Tag groups (##) must appear before content");
5331
+ pushError(lineNumber, "Tag groups must appear before content");
5251
5332
  continue;
5252
5333
  }
5253
- const groupName = groupMatch[1].trim();
5254
- const alias = groupMatch[2] || void 0;
5334
+ if (tagBlockMatch.deprecated) {
5335
+ pushError(lineNumber, `'## ${tagBlockMatch.name}' is deprecated for tag groups \u2014 use 'tag: ${tagBlockMatch.name}' instead`, "warning");
5336
+ }
5255
5337
  currentTagGroup = {
5256
- name: groupName,
5257
- alias,
5338
+ name: tagBlockMatch.name,
5339
+ alias: tagBlockMatch.alias,
5258
5340
  entries: [],
5259
5341
  lineNumber
5260
5342
  };
5261
- if (alias) {
5262
- aliasMap.set(alias.toLowerCase(), groupName.toLowerCase());
5343
+ if (tagBlockMatch.alias) {
5344
+ aliasMap.set(tagBlockMatch.alias.toLowerCase(), tagBlockMatch.name.toLowerCase());
5263
5345
  }
5264
5346
  result.tagGroups.push(currentTagGroup);
5265
5347
  continue;
5266
5348
  }
5349
+ if (!contentStarted && !currentTagGroup && measureIndent(line7) === 0) {
5350
+ const optMatch = trimmed.match(OPTION_RE);
5351
+ if (optMatch) {
5352
+ const key = optMatch[1].trim().toLowerCase();
5353
+ if (key !== "chart" && key !== "title") {
5354
+ result.options[key] = optMatch[2].trim();
5355
+ continue;
5356
+ }
5357
+ }
5358
+ }
5267
5359
  if (currentTagGroup && !contentStarted) {
5268
- const indent2 = measureIndent7(line7);
5360
+ const indent2 = measureIndent(line7);
5269
5361
  if (indent2 > 0) {
5270
5362
  const isDefault = /\bdefault\s*$/.test(trimmed);
5271
5363
  const entryText = isDefault ? trimmed.replace(/\s+default\s*$/, "").trim() : trimmed;
5272
- const { label, color } = extractColor4(entryText, palette);
5364
+ const { label, color } = extractColor(entryText, palette);
5273
5365
  if (!color) {
5274
5366
  pushError(
5275
5367
  lineNumber,
@@ -5294,7 +5386,7 @@ function parseC4(content, palette) {
5294
5386
  if (!sawChartType) {
5295
5387
  return fail(lineNumber, 'Missing "chart: c4" header');
5296
5388
  }
5297
- const indent = measureIndent7(line7);
5389
+ const indent = measureIndent(line7);
5298
5390
  if (inDeployment) {
5299
5391
  while (deployStack.length > 0) {
5300
5392
  const top = deployStack[deployStack.length - 1];
@@ -5389,6 +5481,45 @@ function parseC4(content, palette) {
5389
5481
  }
5390
5482
  continue;
5391
5483
  }
5484
+ {
5485
+ const labeledPatterns = [
5486
+ { re: C4_LABELED_BIDI_SYNC_RE, arrowType: "bidirectional" },
5487
+ { re: C4_LABELED_BIDI_ASYNC_RE, arrowType: "bidirectional-async" },
5488
+ { re: C4_LABELED_SYNC_RE, arrowType: "sync" },
5489
+ { re: C4_LABELED_ASYNC_RE, arrowType: "async" }
5490
+ ];
5491
+ let labeledHandled = false;
5492
+ for (const { re, arrowType } of labeledPatterns) {
5493
+ const m = trimmed.match(re);
5494
+ if (!m) continue;
5495
+ const rawLabel = m[1].trim();
5496
+ const targetBody = m[2].trim();
5497
+ if (!rawLabel) break;
5498
+ let label = rawLabel;
5499
+ let technology;
5500
+ const techMatch = rawLabel.match(/\[([^\]]+)\]\s*$/);
5501
+ if (techMatch) {
5502
+ label = rawLabel.substring(0, techMatch.index).trim() || void 0;
5503
+ technology = techMatch[1].trim();
5504
+ }
5505
+ const rel = {
5506
+ target: targetBody,
5507
+ label,
5508
+ technology,
5509
+ arrowType,
5510
+ lineNumber
5511
+ };
5512
+ const parentEntry = findParentElement(indent, stack);
5513
+ if (parentEntry) {
5514
+ parentEntry.element.relationships.push(rel);
5515
+ } else {
5516
+ result.relationships.push(rel);
5517
+ }
5518
+ labeledHandled = true;
5519
+ break;
5520
+ }
5521
+ if (labeledHandled) continue;
5522
+ }
5392
5523
  const relMatch = trimmed.match(RELATIONSHIP_RE);
5393
5524
  if (relMatch) {
5394
5525
  const arrowType = parseArrowType(relMatch[1]);
@@ -5578,22 +5709,22 @@ function validateDeploymentRefs(result, knownNames, pushWarning) {
5578
5709
  }
5579
5710
  walkDeploy(result.deployment);
5580
5711
  }
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;
5712
+ 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
5713
  var init_parser6 = __esm({
5583
5714
  "src/c4/parser.ts"() {
5584
5715
  "use strict";
5585
- init_colors();
5586
5716
  init_diagnostics();
5717
+ init_tag_groups();
5587
5718
  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*$/;
5719
+ init_parsing();
5593
5720
  CONTAINER_RE2 = /^\[([^\]]+)\]$/;
5594
5721
  ELEMENT_RE = /^(person|system|container|component)\s+(.+)$/i;
5595
5722
  IS_A_RE = /\s+is\s+a(?:n)?\s+(\w+)\s*$/i;
5596
5723
  RELATIONSHIP_RE = /^(<?-?>|<?~?>)\s+(.+)$/;
5724
+ C4_LABELED_SYNC_RE = /^-(.+)->\s+(.+)$/;
5725
+ C4_LABELED_ASYNC_RE = /^~(.+)~>\s+(.+)$/;
5726
+ C4_LABELED_BIDI_SYNC_RE = /^<-(.+)->\s+(.+)$/;
5727
+ C4_LABELED_BIDI_ASYNC_RE = /^<~(.+)~>\s+(.+)$/;
5597
5728
  SECTION_HEADER_RE = /^(containers|components|deployment)\s*:\s*$/i;
5598
5729
  CONTAINER_REF_RE = /^container\s+(.+)$/i;
5599
5730
  METADATA_RE2 = /^([^:]+):\s*(.+)$/;
@@ -5654,13 +5785,13 @@ function looksLikeInitiativeStatus(content) {
5654
5785
  let hasIndentedArrow = false;
5655
5786
  for (const line7 of lines) {
5656
5787
  const trimmed = line7.trim();
5657
- if (!trimmed || trimmed.startsWith("#") || trimmed.startsWith("//")) continue;
5788
+ if (!trimmed || trimmed.startsWith("//")) continue;
5658
5789
  if (trimmed.match(/^chart\s*:/i)) continue;
5659
5790
  if (trimmed.match(/^title\s*:/i)) continue;
5660
5791
  if (trimmed.includes("->")) hasArrow = true;
5661
5792
  if (/\|\s*(done|wip|todo|na)\s*$/i.test(trimmed)) hasStatus = true;
5662
5793
  const isIndented = line7.length > 0 && line7 !== trimmed && /^\s/.test(line7);
5663
- if (isIndented && trimmed.startsWith("->")) hasIndentedArrow = true;
5794
+ if (isIndented && (trimmed.startsWith("->") || /^-[^>].*->/.test(trimmed))) hasIndentedArrow = true;
5664
5795
  if (hasArrow && hasStatus) return true;
5665
5796
  }
5666
5797
  return hasIndentedArrow;
@@ -5684,7 +5815,7 @@ function parseInitiativeStatus(content) {
5684
5815
  groups: [],
5685
5816
  options: {},
5686
5817
  diagnostics: [],
5687
- error: void 0
5818
+ error: null
5688
5819
  };
5689
5820
  const lines = content.split("\n");
5690
5821
  const nodeLabels = /* @__PURE__ */ new Set();
@@ -5694,7 +5825,7 @@ function parseInitiativeStatus(content) {
5694
5825
  const lineNum = i + 1;
5695
5826
  const raw = lines[i];
5696
5827
  const trimmed = raw.trim();
5697
- if (!trimmed || trimmed.startsWith("#") || trimmed.startsWith("//")) continue;
5828
+ if (!trimmed || trimmed.startsWith("//")) continue;
5698
5829
  const chartMatch = trimmed.match(/^chart\s*:\s*(.+)/i);
5699
5830
  if (chartMatch) {
5700
5831
  const chartType = chartMatch[1].trim().toLowerCase();
@@ -5727,7 +5858,7 @@ function parseInitiativeStatus(content) {
5727
5858
  }
5728
5859
  if (trimmed.includes("->")) {
5729
5860
  let edgeText = trimmed;
5730
- if (trimmed.startsWith("->")) {
5861
+ if (trimmed.startsWith("->") || /^-[^>].*->/.test(trimmed)) {
5731
5862
  if (!lastNodeLabel) {
5732
5863
  result.diagnostics.push(
5733
5864
  makeDgmoError(lineNum, "Indented edge has no preceding node to use as source", "warning")
@@ -5793,6 +5924,27 @@ function parseNodeLine(trimmed, lineNum, diagnostics) {
5793
5924
  return { label: trimmed, status: "na", shape: inferParticipantType(trimmed), lineNumber: lineNum };
5794
5925
  }
5795
5926
  function parseEdgeLine(trimmed, lineNum, diagnostics) {
5927
+ const labeledMatch = trimmed.match(/^(\S+)\s+-(.+)->\s+(.+)$/);
5928
+ if (labeledMatch) {
5929
+ const source2 = labeledMatch[1];
5930
+ const label2 = labeledMatch[2].trim();
5931
+ let targetRest = labeledMatch[3].trim();
5932
+ if (label2) {
5933
+ let status2 = "na";
5934
+ const lastPipe2 = targetRest.lastIndexOf("|");
5935
+ if (lastPipe2 >= 0) {
5936
+ const statusRaw = targetRest.slice(lastPipe2 + 1).trim();
5937
+ status2 = parseStatus(statusRaw, lineNum, diagnostics);
5938
+ targetRest = targetRest.slice(0, lastPipe2).trim();
5939
+ }
5940
+ const target2 = targetRest.trim();
5941
+ if (!target2) {
5942
+ diagnostics.push(makeDgmoError(lineNum, "Edge is missing target"));
5943
+ return null;
5944
+ }
5945
+ return { source: source2, target: target2, label: label2, status: status2, lineNumber: lineNum };
5946
+ }
5947
+ }
5796
5948
  const arrowIdx = trimmed.indexOf("->");
5797
5949
  if (arrowIdx < 0) return null;
5798
5950
  const source = trimmed.slice(0, arrowIdx).trim();
@@ -5847,7 +5999,7 @@ function parseDgmoChartType(content) {
5847
5999
  const lines = content.split("\n");
5848
6000
  for (const line7 of lines) {
5849
6001
  const trimmed = line7.trim();
5850
- if (!trimmed || trimmed.startsWith("#") || trimmed.startsWith("//"))
6002
+ if (!trimmed || trimmed.startsWith("//"))
5851
6003
  continue;
5852
6004
  const match = trimmed.match(/^chart\s*:\s*(.+)/i);
5853
6005
  if (match) return match[1].trim().toLowerCase();
@@ -7109,6 +7261,58 @@ var init_renderer = __esm({
7109
7261
  }
7110
7262
  });
7111
7263
 
7264
+ // src/utils/inline-markdown.ts
7265
+ function parseInlineMarkdown(text) {
7266
+ const spans = [];
7267
+ const regex = /\*\*(.+?)\*\*|__(.+?)__|\*(.+?)\*|_(.+?)_|`(.+?)`|\[(.+?)\]\((.+?)\)|(https?:\/\/[^\s)>\]]+|www\.[^\s)>\]]+)|([^*_`[]+?(?=https?:\/\/|www\.|$)|[^*_`[]+)/g;
7268
+ let match;
7269
+ while ((match = regex.exec(text)) !== null) {
7270
+ if (match[1]) spans.push({ text: match[1], bold: true });
7271
+ else if (match[2]) spans.push({ text: match[2], bold: true });
7272
+ else if (match[3]) spans.push({ text: match[3], italic: true });
7273
+ else if (match[4]) spans.push({ text: match[4], italic: true });
7274
+ else if (match[5]) spans.push({ text: match[5], code: true });
7275
+ else if (match[6]) spans.push({ text: match[6], href: match[7] });
7276
+ else if (match[8]) {
7277
+ const url = match[8];
7278
+ const href = url.startsWith("www.") ? `https://${url}` : url;
7279
+ spans.push({ text: url, href });
7280
+ } else if (match[9]) spans.push({ text: match[9] });
7281
+ }
7282
+ return spans;
7283
+ }
7284
+ function truncateBareUrl(url) {
7285
+ const stripped = url.replace(/^https?:\/\//, "").replace(/^www\./, "");
7286
+ if (stripped.length <= BARE_URL_MAX_DISPLAY) return stripped;
7287
+ return stripped.slice(0, BARE_URL_MAX_DISPLAY - 1) + "\u2026";
7288
+ }
7289
+ function renderInlineText(textEl, text, palette, fontSize) {
7290
+ const spans = parseInlineMarkdown(text);
7291
+ for (const span of spans) {
7292
+ if (span.href) {
7293
+ const isBareUrl = span.text === span.href || `https://${span.text}` === span.href;
7294
+ const display = isBareUrl ? truncateBareUrl(span.text) : span.text;
7295
+ const a = textEl.append("a").attr("href", span.href);
7296
+ a.append("tspan").text(display).attr("fill", palette.primary).style("text-decoration", "underline");
7297
+ } else {
7298
+ const tspan = textEl.append("tspan").text(span.text);
7299
+ if (span.bold) tspan.attr("font-weight", "bold");
7300
+ if (span.italic) tspan.attr("font-style", "italic");
7301
+ if (span.code) {
7302
+ tspan.attr("font-family", "monospace");
7303
+ if (fontSize) tspan.attr("font-size", fontSize - 1);
7304
+ }
7305
+ }
7306
+ }
7307
+ }
7308
+ var BARE_URL_MAX_DISPLAY;
7309
+ var init_inline_markdown = __esm({
7310
+ "src/utils/inline-markdown.ts"() {
7311
+ "use strict";
7312
+ BARE_URL_MAX_DISPLAY = 35;
7313
+ }
7314
+ });
7315
+
7112
7316
  // src/kanban/mutations.ts
7113
7317
  function computeCardMove(content, parsed, cardId, targetColumnId, targetIndex) {
7114
7318
  let sourceCard = null;
@@ -7431,7 +7635,8 @@ function renderKanban(container, parsed, palette, isDark, _onNavigateToLine, exp
7431
7635
  const cx = colLayout.x + cardLayout.x;
7432
7636
  const cy = colLayout.y + cardLayout.y;
7433
7637
  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);
7638
+ 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);
7639
+ renderInlineText(titleEl, card.title, palette, CARD_TITLE_FONT_SIZE);
7435
7640
  if (hasMeta) {
7436
7641
  const separatorY = cy + CARD_HEADER_HEIGHT;
7437
7642
  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 +7648,8 @@ function renderKanban(container, parsed, palette, isDark, _onNavigateToLine, exp
7443
7648
  metaY += CARD_META_LINE_HEIGHT;
7444
7649
  }
7445
7650
  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);
7651
+ 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);
7652
+ renderInlineText(detailEl, detail, palette, CARD_META_FONT_SIZE);
7447
7653
  metaY += CARD_META_LINE_HEIGHT;
7448
7654
  }
7449
7655
  }
@@ -7468,6 +7674,7 @@ var init_renderer2 = __esm({
7468
7674
  "src/kanban/renderer.ts"() {
7469
7675
  "use strict";
7470
7676
  init_fonts();
7677
+ init_inline_markdown();
7471
7678
  init_parser5();
7472
7679
  init_mutations();
7473
7680
  DIAGRAM_PADDING2 = 20;
@@ -10615,7 +10822,8 @@ function renderC4Context(container, parsed, layout, palette, isDark, onClickItem
10615
10822
  const contentWidth = w - CARD_H_PAD3 * 2;
10616
10823
  const lines = wrapText2(node.description, contentWidth, DESC_CHAR_WIDTH2);
10617
10824
  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);
10825
+ 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);
10826
+ renderInlineText(textEl, line7, palette, DESC_FONT_SIZE);
10619
10827
  yPos += DESC_LINE_HEIGHT2;
10620
10828
  }
10621
10829
  }
@@ -11101,7 +11309,8 @@ function renderC4Containers(container, parsed, layout, palette, isDark, onClickI
11101
11309
  const contentWidth = w - CARD_H_PAD3 * 2;
11102
11310
  const lines = wrapText2(node.description, contentWidth, DESC_CHAR_WIDTH2);
11103
11311
  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);
11312
+ 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);
11313
+ renderInlineText(textEl, line7, palette, DESC_FONT_SIZE);
11105
11314
  yPos += DESC_LINE_HEIGHT2;
11106
11315
  }
11107
11316
  }
@@ -11124,7 +11333,8 @@ function renderC4Containers(container, parsed, layout, palette, isDark, onClickI
11124
11333
  const contentWidth = w - CARD_H_PAD3 * 2;
11125
11334
  const lines = wrapText2(node.description, contentWidth, DESC_CHAR_WIDTH2);
11126
11335
  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);
11336
+ 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);
11337
+ renderInlineText(textEl, line7, palette, DESC_FONT_SIZE);
11128
11338
  yPos += DESC_LINE_HEIGHT2;
11129
11339
  }
11130
11340
  }
@@ -11243,6 +11453,7 @@ var init_renderer6 = __esm({
11243
11453
  "src/c4/renderer.ts"() {
11244
11454
  "use strict";
11245
11455
  init_fonts();
11456
+ init_inline_markdown();
11246
11457
  init_parser6();
11247
11458
  init_layout5();
11248
11459
  DIAGRAM_PADDING6 = 20;
@@ -11741,49 +11952,6 @@ __export(renderer_exports7, {
11741
11952
  truncateBareUrl: () => truncateBareUrl
11742
11953
  });
11743
11954
  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
11955
  function wrapTextLines(text, maxChars) {
11788
11956
  const rawLines = text.split("\n");
11789
11957
  const wrapped = [];
@@ -11964,8 +12132,12 @@ function buildRenderSequence(messages) {
11964
12132
  to: msg.to,
11965
12133
  label: msg.label,
11966
12134
  messageIndex: mi,
11967
- ...msg.async ? { async: true } : {}
12135
+ ...msg.async ? { async: true } : {},
12136
+ ...msg.bidirectional ? { bidirectional: true } : {}
11968
12137
  });
12138
+ if (msg.bidirectional) {
12139
+ continue;
12140
+ }
11969
12141
  if (msg.async) {
11970
12142
  continue;
11971
12143
  }
@@ -12457,6 +12629,14 @@ function renderSequenceDiagram(container, parsed, palette, isDark, _onNavigateTo
12457
12629
  "points",
12458
12630
  `0,0 ${ARROWHEAD_SIZE},${ARROWHEAD_SIZE / 2} 0,${ARROWHEAD_SIZE}`
12459
12631
  ).attr("fill", "none").attr("stroke", palette.text).attr("stroke-width", 1.2);
12632
+ 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(
12633
+ "points",
12634
+ `${ARROWHEAD_SIZE},0 0,${ARROWHEAD_SIZE / 2} ${ARROWHEAD_SIZE},${ARROWHEAD_SIZE}`
12635
+ ).attr("fill", palette.text);
12636
+ 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(
12637
+ "points",
12638
+ `${ARROWHEAD_SIZE},0 0,${ARROWHEAD_SIZE / 2} ${ARROWHEAD_SIZE},${ARROWHEAD_SIZE}`
12639
+ ).attr("fill", "none").attr("stroke", palette.text).attr("stroke-width", 1.2);
12460
12640
  if (title) {
12461
12641
  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
12642
  if (parsed.titleLineNumber) {
@@ -12737,10 +12917,17 @@ function renderSequenceDiagram(container, parsed, palette, isDark, _onNavigateTo
12737
12917
  const x1 = arrowEdgeX(step.from, i, goingRight ? "right" : "left");
12738
12918
  const x2 = arrowEdgeX(step.to, i, goingRight ? "left" : "right");
12739
12919
  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(
12920
+ const markerStartRef = step.bidirectional ? step.async ? "url(#seq-arrowhead-async-reverse)" : "url(#seq-arrowhead-reverse)" : null;
12921
+ 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
12922
  "data-line-number",
12742
12923
  String(messages[step.messageIndex].lineNumber)
12743
12924
  ).attr("data-msg-index", String(step.messageIndex)).attr("data-step-index", String(i));
12925
+ if (markerStartRef) {
12926
+ line7.attr("marker-start", markerStartRef);
12927
+ }
12928
+ if (step.bidirectional && step.async) {
12929
+ line7.attr("stroke-dasharray", "6 4");
12930
+ }
12744
12931
  if (step.label) {
12745
12932
  const midX = (x1 + x2) / 2;
12746
12933
  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 +13115,12 @@ function renderParticipant(svg, participant, cx, cy, palette, isDark) {
12928
13115
  isActor ? PARTICIPANT_BOX_HEIGHT + 14 : PARTICIPANT_BOX_HEIGHT / 2 + 5
12929
13116
  ).attr("text-anchor", "middle").attr("fill", palette.text).attr("font-size", 13).attr("font-weight", 500).text(participant.label);
12930
13117
  }
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;
13118
+ 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
13119
  var init_renderer7 = __esm({
12933
13120
  "src/sequence/renderer.ts"() {
12934
13121
  "use strict";
12935
13122
  init_colors();
13123
+ init_inline_markdown();
12936
13124
  init_fonts();
12937
13125
  init_parser();
12938
13126
  PARTICIPANT_GAP = 160;
@@ -12956,7 +13144,6 @@ var init_renderer7 = __esm({
12956
13144
  NOTE_CHARS_PER_LINE = Math.floor((NOTE_MAX_W - NOTE_PAD_H * 2 - NOTE_FOLD) / NOTE_CHAR_W);
12957
13145
  COLLAPSED_NOTE_H = 20;
12958
13146
  COLLAPSED_NOTE_W = 40;
12959
- BARE_URL_MAX_DISPLAY = 35;
12960
13147
  fill = (palette, isDark) => mix8(palette.primary, isDark ? palette.surface : palette.bg, isDark ? 15 : 30);
12961
13148
  stroke = (palette) => palette.textMuted;
12962
13149
  SW = 1.5;
@@ -13091,7 +13278,7 @@ function parseD3(content, palette) {
13091
13278
  }
13092
13279
  continue;
13093
13280
  }
13094
- if (line7.startsWith("#") || line7.startsWith("//")) {
13281
+ if (line7.startsWith("//")) {
13095
13282
  continue;
13096
13283
  }
13097
13284
  if (result.type === "arc") {
@@ -16265,7 +16452,7 @@ function parseQuadrant(content) {
16265
16452
  for (let i = 0; i < lines.length; i++) {
16266
16453
  const line7 = lines[i].trim();
16267
16454
  const lineNumber = i + 1;
16268
- if (!line7 || line7.startsWith("#") || line7.startsWith("//")) continue;
16455
+ if (!line7 || line7.startsWith("//")) continue;
16269
16456
  if (/^chart\s*:/i.test(line7)) continue;
16270
16457
  const titleMatch = line7.match(/^title\s*:\s*(.+)/i);
16271
16458
  if (titleMatch) {
@@ -16395,6 +16582,7 @@ init_renderer3();
16395
16582
  init_parser3();
16396
16583
  init_layout3();
16397
16584
  init_renderer4();
16585
+ init_inline_markdown();
16398
16586
  init_parser4();
16399
16587
  init_layout();
16400
16588
  init_renderer();
@@ -16411,12 +16599,12 @@ init_collapse();
16411
16599
 
16412
16600
  // src/org/resolver.ts
16413
16601
  init_diagnostics();
16602
+ init_tag_groups();
16414
16603
  var MAX_DEPTH = 10;
16415
16604
  var IMPORT_RE = /^(\s+)import:\s+(.+\.dgmo)\s*$/i;
16416
16605
  var TAGS_RE = /^tags:\s+(.+\.dgmo)\s*$/i;
16417
16606
  var HEADER_RE = /^(chart|title)\s*:/i;
16418
- var OPTION_RE4 = /^[a-z][a-z0-9-]*\s*:/i;
16419
- var GROUP_HEADING_RE5 = /^##\s+/;
16607
+ var OPTION_RE2 = /^[a-z][a-z0-9-]*\s*:/i;
16420
16608
  function dirname(filePath) {
16421
16609
  const last = filePath.lastIndexOf("/");
16422
16610
  return last > 0 ? filePath.substring(0, last) : "/";
@@ -16437,9 +16625,9 @@ function extractTagGroups(lines) {
16437
16625
  let current = null;
16438
16626
  for (const line7 of lines) {
16439
16627
  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();
16628
+ const headingMatch = matchTagBlockHeading(trimmed);
16629
+ if (headingMatch) {
16630
+ const name = headingMatch.name.toLowerCase();
16443
16631
  current = { name, lines: [line7] };
16444
16632
  blocks.push(current);
16445
16633
  } else if (current) {
@@ -16481,7 +16669,7 @@ function parseFileHeader(lines) {
16481
16669
  tagsDirective = tagsMatch[1].trim();
16482
16670
  continue;
16483
16671
  }
16484
- if (OPTION_RE4.test(trimmed) && !trimmed.startsWith("##") && !lines[i].match(/^\s/)) {
16672
+ if (OPTION_RE2.test(trimmed) && !isTagBlockHeading(trimmed) && !lines[i].match(/^\s/)) {
16485
16673
  const key = trimmed.split(":")[0].trim().toLowerCase();
16486
16674
  if (key !== "chart" && key !== "title" && !trimmed.includes("|")) {
16487
16675
  continue;
@@ -16511,7 +16699,7 @@ async function resolveFile(content, filePath, readFileFn, diagnostics, ancestorC
16511
16699
  headerLines.push(lines[i]);
16512
16700
  continue;
16513
16701
  }
16514
- if (GROUP_HEADING_RE5.test(trimmed)) continue;
16702
+ if (isTagBlockHeading(trimmed)) continue;
16515
16703
  if (lines[i] !== trimmed) continue;
16516
16704
  const tagsMatch = trimmed.match(TAGS_RE);
16517
16705
  if (tagsMatch) {
@@ -16543,7 +16731,7 @@ async function resolveFile(content, filePath, readFileFn, diagnostics, ancestorC
16543
16731
  const importMatch = line7.match(IMPORT_RE);
16544
16732
  if (!importMatch) {
16545
16733
  const trimmed = line7.trim();
16546
- if (GROUP_HEADING_RE5.test(trimmed) || inlineTagGroups.length > 0 && isTagGroupEntry(line7, bodyLines, i)) {
16734
+ if (isTagBlockHeading(trimmed) || inlineTagGroups.length > 0 && isTagGroupEntry(line7, bodyLines, i)) {
16547
16735
  continue;
16548
16736
  }
16549
16737
  resolvedBodyLines.push(line7);
@@ -16638,7 +16826,7 @@ function findBodyStart(lines) {
16638
16826
  if (inTagGroup) inTagGroup = false;
16639
16827
  continue;
16640
16828
  }
16641
- if (GROUP_HEADING_RE5.test(trimmed)) {
16829
+ if (isTagBlockHeading(trimmed)) {
16642
16830
  inTagGroup = true;
16643
16831
  continue;
16644
16832
  }
@@ -16650,7 +16838,7 @@ function findBodyStart(lines) {
16650
16838
  }
16651
16839
  if (HEADER_RE.test(trimmed)) continue;
16652
16840
  if (TAGS_RE.test(trimmed)) continue;
16653
- if (OPTION_RE4.test(trimmed) && !lines[i].match(/^\s/) && !trimmed.includes("|")) {
16841
+ if (OPTION_RE2.test(trimmed) && !isTagBlockHeading(trimmed) && !lines[i].match(/^\s/) && !trimmed.includes("|")) {
16654
16842
  const key = trimmed.split(":")[0].trim().toLowerCase();
16655
16843
  if (key !== "chart" && key !== "title") {
16656
16844
  continue;
@@ -16665,7 +16853,7 @@ function isTagGroupEntry(line7, allLines, index) {
16665
16853
  for (let i = index - 1; i >= 0; i--) {
16666
16854
  const prev = allLines[i].trim();
16667
16855
  if (prev === "" || prev.startsWith("//")) continue;
16668
- if (GROUP_HEADING_RE5.test(prev)) return true;
16856
+ if (isTagBlockHeading(prev)) return true;
16669
16857
  if (allLines[i].match(/^\s+/)) continue;
16670
16858
  return false;
16671
16859
  }
@@ -16822,6 +17010,7 @@ export {
16822
17010
  parseERDiagram,
16823
17011
  parseFlowchart,
16824
17012
  parseInitiativeStatus,
17013
+ parseInlineMarkdown,
16825
17014
  parseKanban,
16826
17015
  parseOrg,
16827
17016
  parseQuadrant,
@@ -16865,6 +17054,7 @@ export {
16865
17054
  shade,
16866
17055
  solarizedPalette,
16867
17056
  tint,
16868
- tokyoNightPalette
17057
+ tokyoNightPalette,
17058
+ truncateBareUrl
16869
17059
  };
16870
17060
  //# sourceMappingURL=index.js.map