@diagrammo/dgmo 0.8.21 → 0.8.22

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 (93) hide show
  1. package/AGENTS.md +2 -1
  2. package/README.md +1 -0
  3. package/dist/cli.cjs +143 -93
  4. package/dist/editor.cjs +17 -3
  5. package/dist/editor.cjs.map +1 -1
  6. package/dist/editor.js +17 -3
  7. package/dist/editor.js.map +1 -1
  8. package/dist/highlight.cjs +12 -2
  9. package/dist/highlight.cjs.map +1 -1
  10. package/dist/highlight.js +12 -2
  11. package/dist/highlight.js.map +1 -1
  12. package/dist/index.cjs +19997 -14886
  13. package/dist/index.cjs.map +1 -1
  14. package/dist/index.d.cts +331 -8
  15. package/dist/index.d.ts +331 -8
  16. package/dist/index.js +19984 -14889
  17. package/dist/index.js.map +1 -1
  18. package/docs/guide/chart-sitemap.md +18 -1
  19. package/docs/guide/chart-tech-radar.md +219 -0
  20. package/docs/guide/registry.json +1 -0
  21. package/docs/language-reference.md +116 -6
  22. package/gallery/fixtures/boxes-and-lines.dgmo +10 -3
  23. package/gallery/fixtures/c4-full.dgmo +2 -2
  24. package/gallery/fixtures/cycle/ooda-loop.dgmo +25 -0
  25. package/gallery/fixtures/cycle/pdca-circle-nodes.dgmo +12 -0
  26. package/gallery/fixtures/cycle/pdca-minimal.dgmo +6 -0
  27. package/gallery/fixtures/cycle/sprint-cycle-span.dgmo +17 -0
  28. package/gallery/fixtures/gantt-full.dgmo +2 -2
  29. package/gallery/fixtures/gantt.dgmo +2 -2
  30. package/gallery/fixtures/infra-full.dgmo +2 -2
  31. package/gallery/fixtures/infra.dgmo +1 -1
  32. package/gallery/fixtures/sequence-tags-protocols.dgmo +2 -2
  33. package/gallery/fixtures/sequence-tags.dgmo +2 -2
  34. package/gallery/fixtures/tech-radar-dense.dgmo +77 -0
  35. package/gallery/fixtures/tech-radar.dgmo +36 -0
  36. package/gallery/fixtures/timeline.dgmo +1 -1
  37. package/package.json +1 -1
  38. package/src/boxes-and-lines/layout.ts +309 -33
  39. package/src/boxes-and-lines/parser.ts +86 -10
  40. package/src/boxes-and-lines/renderer.ts +250 -91
  41. package/src/boxes-and-lines/types.ts +1 -1
  42. package/src/c4/layout.ts +8 -8
  43. package/src/c4/parser.ts +35 -2
  44. package/src/c4/renderer.ts +19 -3
  45. package/src/c4/types.ts +1 -0
  46. package/src/chart.ts +14 -7
  47. package/src/completion.ts +227 -0
  48. package/src/cycle/layout.ts +732 -0
  49. package/src/cycle/parser.ts +352 -0
  50. package/src/cycle/renderer.ts +539 -0
  51. package/src/cycle/types.ts +77 -0
  52. package/src/d3.ts +87 -8
  53. package/src/dgmo-router.ts +9 -0
  54. package/src/echarts.ts +7 -4
  55. package/src/editor/dgmo.grammar +5 -1
  56. package/src/editor/dgmo.grammar.js +1 -1
  57. package/src/editor/keywords.ts +14 -0
  58. package/src/gantt/parser.ts +2 -8
  59. package/src/graph/flowchart-parser.ts +15 -21
  60. package/src/graph/state-parser.ts +5 -10
  61. package/src/index.ts +50 -0
  62. package/src/infra/layout.ts +218 -74
  63. package/src/infra/parser.ts +30 -6
  64. package/src/infra/renderer.ts +14 -8
  65. package/src/infra/types.ts +10 -3
  66. package/src/journey-map/layout.ts +386 -0
  67. package/src/journey-map/parser.ts +540 -0
  68. package/src/journey-map/renderer.ts +1456 -0
  69. package/src/journey-map/types.ts +47 -0
  70. package/src/kanban/parser.ts +3 -10
  71. package/src/kanban/renderer.ts +31 -15
  72. package/src/mindmap/parser.ts +12 -18
  73. package/src/mindmap/renderer.ts +14 -13
  74. package/src/mindmap/text-wrap.ts +22 -12
  75. package/src/mindmap/types.ts +2 -2
  76. package/src/org/parser.ts +2 -6
  77. package/src/sequence/renderer.ts +144 -38
  78. package/src/sharing.ts +1 -0
  79. package/src/sitemap/layout.ts +21 -6
  80. package/src/sitemap/parser.ts +26 -17
  81. package/src/sitemap/renderer.ts +34 -0
  82. package/src/sitemap/types.ts +1 -0
  83. package/src/tech-radar/index.ts +14 -0
  84. package/src/tech-radar/interactive.ts +1058 -0
  85. package/src/tech-radar/layout.ts +190 -0
  86. package/src/tech-radar/parser.ts +385 -0
  87. package/src/tech-radar/renderer.ts +1159 -0
  88. package/src/tech-radar/shared.ts +187 -0
  89. package/src/tech-radar/types.ts +81 -0
  90. package/src/utils/description-helpers.ts +33 -0
  91. package/src/utils/legend-layout.ts +3 -1
  92. package/src/utils/parsing.ts +46 -7
  93. package/src/utils/tag-groups.ts +46 -60
package/src/d3.ts CHANGED
@@ -19,7 +19,9 @@ export type VisualizationType =
19
19
  | 'timeline'
20
20
  | 'venn'
21
21
  | 'quadrant'
22
- | 'sequence';
22
+ | 'sequence'
23
+ | 'tech-radar'
24
+ | 'cycle';
23
25
 
24
26
  interface D3DataItem {
25
27
  label: string;
@@ -184,6 +186,7 @@ import { makeDgmoError, formatDgmoError, suggest } from './diagnostics';
184
186
  import {
185
187
  collectIndentedValues,
186
188
  extractColor,
189
+ normalizeNumericToken,
187
190
  parseFirstLine,
188
191
  parsePipeMetadata,
189
192
  MULTIPLE_PIPE_ERROR,
@@ -632,7 +635,7 @@ export function parseVisualization(
632
635
  // Arc link line: source -> target(color) weight
633
636
  if (result.type === 'arc') {
634
637
  const linkMatch = line.match(
635
- /^(.+?)\s*->\s*(.+?)(?:\(([^)]+)\))?\s*(?:\s+(\d+(?:\.\d+)?))?$/
638
+ /^(.+?)\s*->\s*(.+?)(?:\(([^)]+)\))?\s*(?:\s+(-?[\d,_]+(?:\.[\d]+)?))?$/
636
639
  );
637
640
  if (linkMatch) {
638
641
  const source = linkMatch[1].trim();
@@ -648,7 +651,9 @@ export function parseVisualization(
648
651
  result.links.push({
649
652
  source,
650
653
  target,
651
- value: linkMatch[4] ? parseFloat(linkMatch[4]) : 1,
654
+ value: linkMatch[4]
655
+ ? parseFloat(normalizeNumericToken(linkMatch[4]) ?? linkMatch[4])
656
+ : 1,
652
657
  color: linkColor,
653
658
  lineNumber,
654
659
  });
@@ -1028,7 +1033,7 @@ export function parseVisualization(
1028
1033
 
1029
1034
  // Data points: Label x, y OR Label x y
1030
1035
  const pointMatch = line.match(
1031
- /^(.+?)\s+([0-9]*\.?[0-9]+)\s*[,\s]\s*([0-9]*\.?[0-9]+)\s*$/
1036
+ /^(.+?)\s+(-?[0-9][0-9,_]*(?:\.[0-9]+)?)\s*[,\s]\s*(-?[0-9][0-9,_]*(?:\.[0-9]+)?)\s*$/
1032
1037
  );
1033
1038
  if (pointMatch) {
1034
1039
  const label = pointMatch[1].trim();
@@ -1042,8 +1047,12 @@ export function parseVisualization(
1042
1047
  ) {
1043
1048
  result.quadrantPoints.push({
1044
1049
  label,
1045
- x: parseFloat(pointMatch[2]),
1046
- y: parseFloat(pointMatch[3]),
1050
+ x: parseFloat(
1051
+ normalizeNumericToken(pointMatch[2]) ?? pointMatch[2]
1052
+ ),
1053
+ y: parseFloat(
1054
+ normalizeNumericToken(pointMatch[3]) ?? pointMatch[3]
1055
+ ),
1047
1056
  lineNumber,
1048
1057
  });
1049
1058
  }
@@ -1201,7 +1210,8 @@ export function parseVisualization(
1201
1210
  // Scan from right, capped at P values
1202
1211
  let rightIdx = tokens.length - 1;
1203
1212
  while (rightIdx >= 0 && values.length < P) {
1204
- const raw = tokens[rightIdx].replace(/,/g, '');
1213
+ const raw =
1214
+ normalizeNumericToken(tokens[rightIdx]) ?? tokens[rightIdx];
1205
1215
  const num = parseFloat(raw);
1206
1216
  if (!isNaN(num) && /^-?\d/.test(raw)) {
1207
1217
  values.unshift(num);
@@ -1385,8 +1395,11 @@ export function parseVisualization(
1385
1395
  } else if (colonIndex === -1) {
1386
1396
  // Try "word weight" or "multi-word-label weight" space-separated format
1387
1397
  const lastSpace = line.lastIndexOf(' ');
1398
+ const rawWeight = lastSpace >= 0 ? line.substring(lastSpace + 1) : '';
1388
1399
  const maybeWeight =
1389
- lastSpace >= 0 ? parseFloat(line.substring(lastSpace + 1)) : NaN;
1400
+ lastSpace >= 0
1401
+ ? parseFloat(normalizeNumericToken(rawWeight) ?? rawWeight)
1402
+ : NaN;
1390
1403
  if (lastSpace >= 0 && !isNaN(maybeWeight) && maybeWeight > 0) {
1391
1404
  result.words.push({
1392
1405
  text: line.substring(0, lastSpace).trim(),
@@ -7041,6 +7054,72 @@ export async function renderForExport(
7041
7054
  return finalizeSvgExport(container, theme, effectivePalette);
7042
7055
  }
7043
7056
 
7057
+ if (detectedType === 'tech-radar') {
7058
+ const { parseTechRadar } = await import('./tech-radar/parser');
7059
+ const { renderTechRadarForExport } = await import('./tech-radar/renderer');
7060
+
7061
+ const effectivePalette = await resolveExportPalette(theme, palette);
7062
+ const radarParsed = parseTechRadar(content);
7063
+ if (radarParsed.error || radarParsed.quadrants.length === 0) return '';
7064
+
7065
+ const RADAR_EXPORT_W = 1600;
7066
+ const RADAR_EXPORT_H = 1200;
7067
+ const container = createExportContainer(RADAR_EXPORT_W, RADAR_EXPORT_H);
7068
+ renderTechRadarForExport(
7069
+ container,
7070
+ radarParsed,
7071
+ effectivePalette,
7072
+ theme === 'dark',
7073
+ { width: RADAR_EXPORT_W, height: RADAR_EXPORT_H },
7074
+ viewState
7075
+ );
7076
+ return finalizeSvgExport(container, theme, effectivePalette);
7077
+ }
7078
+
7079
+ if (detectedType === 'journey-map') {
7080
+ const { parseJourneyMap } = await import('./journey-map/parser');
7081
+ const { renderJourneyMap } = await import('./journey-map/renderer');
7082
+ const { layoutJourneyMap } = await import('./journey-map/layout');
7083
+
7084
+ const effectivePalette = await resolveExportPalette(theme, palette);
7085
+ const jmParsed = parseJourneyMap(content, effectivePalette);
7086
+ if (
7087
+ jmParsed.error ||
7088
+ (jmParsed.phases.length === 0 && jmParsed.steps.length === 0)
7089
+ )
7090
+ return '';
7091
+
7092
+ const jmLayout = layoutJourneyMap(jmParsed, effectivePalette);
7093
+ const container = createExportContainer(
7094
+ jmLayout.totalWidth,
7095
+ jmLayout.totalHeight
7096
+ );
7097
+ renderJourneyMap(container, jmParsed, effectivePalette, theme === 'dark', {
7098
+ exportDims: { width: jmLayout.totalWidth, height: jmLayout.totalHeight },
7099
+ });
7100
+ return finalizeSvgExport(container, theme, effectivePalette);
7101
+ }
7102
+
7103
+ if (detectedType === 'cycle') {
7104
+ const { parseCycle } = await import('./cycle/parser');
7105
+ const { renderCycleForExport } = await import('./cycle/renderer');
7106
+
7107
+ const effectivePalette = await resolveExportPalette(theme, palette);
7108
+ const cycleParsed = parseCycle(content);
7109
+ if (cycleParsed.error || cycleParsed.nodes.length === 0) return '';
7110
+
7111
+ const container = createExportContainer(EXPORT_WIDTH, EXPORT_HEIGHT);
7112
+ renderCycleForExport(
7113
+ container,
7114
+ cycleParsed,
7115
+ effectivePalette,
7116
+ theme === 'dark',
7117
+ { width: EXPORT_WIDTH, height: EXPORT_HEIGHT },
7118
+ viewState
7119
+ );
7120
+ return finalizeSvgExport(container, theme, effectivePalette);
7121
+ }
7122
+
7044
7123
  const parsed = parseVisualization(content, palette);
7045
7124
  // Allow sequence diagrams through even if parseVisualization errors —
7046
7125
  // sequence is parsed by its own dedicated parser (parseSequenceDgmo)
@@ -19,6 +19,9 @@ import { parseGantt } from './gantt/parser';
19
19
  import { parseBoxesAndLines } from './boxes-and-lines/parser';
20
20
  import { parseMindmap } from './mindmap/parser';
21
21
  import { parseWireframe } from './wireframe/parser';
22
+ import { parseTechRadar } from './tech-radar/parser';
23
+ import { parseCycle } from './cycle/parser';
24
+ import { parseJourneyMap } from './journey-map/parser';
22
25
  import { parseFirstLine } from './utils/parsing';
23
26
  import { makeDgmoError, suggest } from './diagnostics';
24
27
  import type { DgmoError } from './diagnostics';
@@ -137,6 +140,8 @@ const VISUALIZATION_TYPES = new Set([
137
140
  'timeline',
138
141
  'venn',
139
142
  'quadrant',
143
+ 'tech-radar',
144
+ 'cycle',
140
145
  ]);
141
146
  const DIAGRAM_TYPES = new Set([
142
147
  'sequence',
@@ -153,6 +158,7 @@ const DIAGRAM_TYPES = new Set([
153
158
  'boxes-and-lines',
154
159
  'mindmap',
155
160
  'wireframe',
161
+ 'journey-map',
156
162
  ]);
157
163
  const EXTENDED_CHART_TYPES = new Set([
158
164
  'scatter',
@@ -234,6 +240,9 @@ const PARSE_DISPATCH = new Map<
234
240
  ['boxes-and-lines', (c) => parseBoxesAndLines(c)],
235
241
  ['mindmap', (c) => parseMindmap(c)],
236
242
  ['wireframe', (c) => parseWireframe(c)],
243
+ ['tech-radar', (c) => parseTechRadar(c)],
244
+ ['cycle', (c) => parseCycle(c)],
245
+ ['journey-map', (c) => parseJourneyMap(c)],
237
246
  ]);
238
247
 
239
248
  /**
package/src/echarts.ts CHANGED
@@ -150,6 +150,7 @@ import {
150
150
  collectIndentedValues,
151
151
  extractColor,
152
152
  measureIndent,
153
+ normalizeNumericToken,
153
154
  parseFirstLine,
154
155
  parseSeriesNames,
155
156
  } from './utils/parsing';
@@ -359,10 +360,11 @@ export function parseExtendedChart(
359
360
 
360
361
  // Sankey/chord link syntax: Source -> Target Value (directed) or Source -- Target Value (undirected)
361
362
  const arrowMatch = trimmed.match(
362
- /^(.+?)\s*(->|--)\s*(.+?)\s+(\d+(?:\.\d+)?)\s*(?:\(([^)]+)\))?\s*$/
363
+ /^(.+?)\s*(->|--)\s*(.+?)\s+(-?[\d,_]+(?:\.[\d]+)?)\s*(?:\(([^)]+)\))?\s*$/
363
364
  );
364
365
  if (arrowMatch) {
365
- const [, rawSource, arrow, rawTarget, val, rawLinkColor] = arrowMatch;
366
+ const [, rawSource, arrow, rawTarget, rawVal, rawLinkColor] = arrowMatch;
367
+ const val = normalizeNumericToken(rawVal) ?? rawVal;
366
368
  const { label: source, color: sourceColor } = extractColor(
367
369
  rawSource.trim(),
368
370
  palette
@@ -409,7 +411,7 @@ export function parseExtendedChart(
409
411
  // Parse "TargetName value (linkColor)" or "TargetName(nodeColor) value (linkColor)"
410
412
  // Strip trailing (color) annotation before parseDataRowValues — it can't handle it
411
413
  const valColorMatch = trimmed.match(
412
- /(\d+(?:\.\d+)?)\s*\(([^)]+)\)\s*$/
414
+ /(-?[\d,_]+(?:\.[\d]+)?)\s*\(([^)]+)\)\s*$/
413
415
  );
414
416
  const strippedLine = valColorMatch
415
417
  ? trimmed.replace(/\s*\([^)]+\)\s*$/, '')
@@ -449,9 +451,10 @@ export function parseExtendedChart(
449
451
 
450
452
  // Bare label at indent 0 (or any indent without a value) = new source node
451
453
  const spaceIdx = trimmed.indexOf(' ');
454
+ const lastTok = trimmed.substring(trimmed.lastIndexOf(' ') + 1);
452
455
  const hasNumericSuffix =
453
456
  spaceIdx >= 0 &&
454
- !isNaN(parseFloat(trimmed.substring(trimmed.lastIndexOf(' ') + 1)));
457
+ !isNaN(parseFloat(normalizeNumericToken(lastTok) ?? lastTok));
455
458
  if (!hasNumericSuffix) {
456
459
  while (sankeyStack.length && sankeyStack.at(-1)!.indent >= indent) {
457
460
  sankeyStack.pop();
@@ -30,7 +30,11 @@ contentPart {
30
30
  Duration { $[0-9]+ ("." $[0-9]+)? ("min" | "bd" | "h" | "d" | "w" | "m" | "q" | "y" | "s") "?"? }
31
31
  DateLiteral { $[0-9] $[0-9] $[0-9] $[0-9] "-" $[0-9] $[0-9] ("-" $[0-9] $[0-9])? }
32
32
  Percentage { $[0-9]+ ("." $[0-9]+)? "%" }
33
- Number { $[0-9]+ ("." $[0-9]+)? }
33
+ Number {
34
+ $[0-9] $[0-9]? $[0-9]? ("," $[0-9] $[0-9] $[0-9])+ ("." $[0-9]+)? |
35
+ $[0-9]+ ("_" $[0-9]+)+ ("." $[0-9]+)? |
36
+ $[0-9]+ ("." $[0-9]+)?
37
+ }
34
38
 
35
39
  SectionMarker { "==" }
36
40
  Url { "http" "s"? "://" ![ \t\n|,)\]>]+ }
@@ -10,7 +10,7 @@ export const parser = LRParser.deserialize({
10
10
  maxTerm: 40,
11
11
  skippedNodes: [0],
12
12
  repeatNodeCount: 2,
13
- tokenData: ":T~RxOX#oXY#tYZ$PZp#opq#tqt#oux#oxy$Uyz$tz{${{|%S|}%Z}!O%b!O!P#o!P!Q%q!Q![&b![!]-Y!]!^#o!^!_-a!_!`-h!`!a-u!a!b-|!b!c#o!c!}.T!}#O1Z#O#P#o#P#Q1`#Q#R#o#R#S.T#S#T#o#T#[.T#[#]1g#]#o.T#o#p#o#p#q9g#q#r#o#r#s9n#s;'S#o;'S;=`9}<%lO#o~#tOq~~#yQv~XY#tpq#t~$UOw~~$]Qc~q~}!O$c#T#o$c~$fRyz$o}!O$c#T#o$c~$tOg~~${Od~q~~%SOn~q~~%ZOk~q~~%bOj~q~~%iPl~q~!`!a%l~%qOX~~%vPq~!P!Q%y~&OSV~OY%yZ;'S%y;'S;=`&[<%lO%y~&_P;=`<%l%y~&iZ^~q~uv'[!O!P'a!Q![)Q#U#V([#W#X(b#[#](b#a#b(o#e#f(b#g#h(b#k#l(b#m#n(b~'aO]~~'dP!Q!['g~'lY^~uv'[!Q!['g#U#V([#W#X(b#[#](b#a#b(o#e#f(b#g#h(b#k#l(b#m#n(b~(_P#W#X(b~(gPZ~!a!b(j~(oOZ~~(tQZ~!a!b(j#]#^(z~(}P#b#c(b~)VZ^~uv'[!O!P'a!Q![)x#U#V([#W#X(b#[#](b#a#b(o#e#f(b#g#h(b#k#l(b#m#n(b~)}Z^~uv'[!O!P'a!Q![*p#U#V([#W#X(b#[#](b#a#b(o#e#f(b#g#h(b#k#l(b#m#n(b~*u[^~uv'[}!O+k!O!P'a!Q![,b#U#V([#W#X(b#[#](b#a#b(o#e#f(b#g#h(b#k#l(b#m#n(b~+nP!Q![+q~+tP!Q![+w~+|P[~}!O,P~,SP!Q![,V~,YP!Q![,]~,bO[~~,gZ^~uv'[!O!P'a!Q![,b#U#V([#W#X(b#[#](b#a#b(o#e#f(b#g#h(b#k#l(b#m#n(b~-aOi~q~~-hOe~q~~-mPq~!_!`-p~-uO_~~-|Of~q~~.TOo~q~~.[_p~q~qr/Zst/Zvw/Zwx/Z{|/Z}!O0_!O!P/Z!P!Q/Z!Q![/Z!_!`/Z!a!b/Z!b!c/Z!c!}/Z#R#S/Z#T#o/Z~/`_p~qr/Zst/Zvw/Zwx/Z{|/Z}!O0_!O!P/Z!P!Q/Z!Q![/Z!_!`/Z!a!b/Z!b!c/Z!c!}/Z#R#S/Z#T#o/Z~0b]qr/Zst/Zvw/Zwx/Z{|/Z!O!P/Z!P!Q/Z!_!`/Z!a!b/Z!b!c/Z!c!}/Z#R#S/Z#T#o/Z~1`Oa~~1gOb~q~~1nap~q~qr/Zst/Zvw/Zwx/Z{|/Z}!O0_!O!P/Z!P!Q/Z!Q![/Z!_!`/Z!a!b/Z!b!c/Z!c!}/Z#R#S/Z#T#h/Z#h#i2s#i#o/Z~2xap~qr/Zst/Zvw/Zwx/Z{|/Z}!O0_!O!P/Z!P!Q/Z!Q![/Z!_!`/Z!a!b/Z!b!c/Z!c!}/Z#R#S/Z#T#h/Z#h#i3}#i#o/Z~4Sap~qr/Zst/Zvw/Zwx/Z{|/Z}!O0_!O!P/Z!P!Q/Z!Q![/Z!_!`/Z!a!b/Z!b!c/Z!c!}/Z#R#S/Z#T#d/Z#d#e5X#e#o/Z~5^bp~qr/Zst/Zvw/Zwx/Z{|/Z}!O0_!O!P/Z!P!Q/Z!Q![/Z![!]6f!_!`/Z!a!b/Z!b!c/Z!c!}/Z#R#S/Z#T#g/Z#g#h8`#h#o/Z~6iP!P!Q6l~6oP!P!Q6r~6uYOX7eZp7eqy7ez|7e}!`7e!a#P7e#Q#p7e#q;'S7e;'S;=`8Y<%lO7e~7jY`~OX7eZp7eqy7ez|7e}!`7e!a#P7e#Q#p7e#q;'S7e;'S;=`8Y<%lO7e~8]P;=`<%l7e~8e`p~qr/Zst/Zvw/Zwx/Z{|/Z}!O0_!O!P/Z!P!Q/Z!Q![/Z![!]6f!_!`/Z!a!b/Z!b!c/Z!c!}/Z#R#S/Z#T#o/Z~9nOh~q~~9uPm~q~!`!a9x~9}OY~~:QP;=`<%l#o",
13
+ tokenData: "<O~RxOX#oXY#tYZ$PZp#opq#tqt#oux#oxy$Uyz$tz{${{|%S|}%Z}!O%b!O!P#o!P!Q%q!Q![&b![!]/T!]!^#o!^!_/[!_!`/c!`!a/p!a!b/w!b!c#o!c!}0O!}#O3U#O#P#o#P#Q3Z#Q#R#o#R#S0O#S#T#o#T#[0O#[#]3b#]#o0O#o#p#o#p#q;b#q#r#o#r#s;i#s;'S#o;'S;=`;x<%lO#o~#tOq~~#yQv~XY#tpq#t~$UOw~~$]Qc~q~}!O$c#T#o$c~$fRyz$o}!O$c#T#o$c~$tOg~~${Od~q~~%SOn~q~~%ZOk~q~~%bOj~q~~%iPl~q~!`!a%l~%qOX~~%vPq~!P!Q%y~&OSV~OY%yZ;'S%y;'S;=`&[<%lO%y~&_P;=`<%l%y~&i]^~q~uv'b|}'g!O!P(d!Q![*T#R#S.o#U#V)_#W#X)e#[#])e#a#b)r#e#f)e#g#h)e#k#l)e#m#n)e~'gO]~~'jP!Q!['m~'pP!Q!['s~'vP!Q!['y~(OQ^~|}'g!O!P(U~(XP!Q![([~(aP^~!Q![([~(gP!Q![(j~(oY^~uv'b!Q![(j#U#V)_#W#X)e#[#])e#a#b)r#e#f)e#g#h)e#k#l)e#m#n)e~)bP#W#X)e~)jPZ~!a!b)m~)rOZ~~)wQZ~!a!b)m#]#^)}~*QP#b#c)e~*Y]^~uv'b|}'g!O!P(d!Q![+R#R#S.o#U#V)_#W#X)e#[#])e#a#b)r#e#f)e#g#h)e#k#l)e#m#n)e~+W]^~uv'b|}'g!O!P(d!Q![,P#R#S.o#U#V)_#W#X)e#[#])e#a#b)r#e#f)e#g#h)e#k#l)e#m#n)e~,U]^~uv'b}!O,}!O!P(d!Q![-t#R#S.o#U#V)_#W#X)e#[#])e#a#b)r#e#f)e#g#h)e#k#l)e#m#n)e~-QP!Q![-T~-WP!Q![-Z~-`P[~}!O-c~-fP!Q![-i~-lP!Q![-o~-tO[~~-y[^~uv'b!O!P(d!Q![-t#R#S.o#U#V)_#W#X)e#[#])e#a#b)r#e#f)e#g#h)e#k#l)e#m#n)e~.rP!Q![.u~.zR^~!O!P(U!Q![.u#R#S.o~/[Oi~q~~/cOe~q~~/hPq~!_!`/k~/pO_~~/wOf~q~~0OOo~q~~0V_p~q~qr1Ust1Uvw1Uwx1U{|1U}!O2Y!O!P1U!P!Q1U!Q![1U!_!`1U!a!b1U!b!c1U!c!}1U#R#S1U#T#o1U~1Z_p~qr1Ust1Uvw1Uwx1U{|1U}!O2Y!O!P1U!P!Q1U!Q![1U!_!`1U!a!b1U!b!c1U!c!}1U#R#S1U#T#o1U~2]]qr1Ust1Uvw1Uwx1U{|1U!O!P1U!P!Q1U!_!`1U!a!b1U!b!c1U!c!}1U#R#S1U#T#o1U~3ZOa~~3bOb~q~~3iap~q~qr1Ust1Uvw1Uwx1U{|1U}!O2Y!O!P1U!P!Q1U!Q![1U!_!`1U!a!b1U!b!c1U!c!}1U#R#S1U#T#h1U#h#i4n#i#o1U~4sap~qr1Ust1Uvw1Uwx1U{|1U}!O2Y!O!P1U!P!Q1U!Q![1U!_!`1U!a!b1U!b!c1U!c!}1U#R#S1U#T#h1U#h#i5x#i#o1U~5}ap~qr1Ust1Uvw1Uwx1U{|1U}!O2Y!O!P1U!P!Q1U!Q![1U!_!`1U!a!b1U!b!c1U!c!}1U#R#S1U#T#d1U#d#e7S#e#o1U~7Xbp~qr1Ust1Uvw1Uwx1U{|1U}!O2Y!O!P1U!P!Q1U!Q![1U![!]8a!_!`1U!a!b1U!b!c1U!c!}1U#R#S1U#T#g1U#g#h:Z#h#o1U~8dP!P!Q8g~8jP!P!Q8m~8pYOX9`Zp9`qy9`z|9`}!`9`!a#P9`#Q#p9`#q;'S9`;'S;=`:T<%lO9`~9eY`~OX9`Zp9`qy9`z|9`}!`9`!a#P9`#Q#p9`#q;'S9`;'S;=`:T<%lO9`~:WP;=`<%l9`~:``p~qr1Ust1Uvw1Uwx1U{|1U}!O2Y!O!P1U!P!Q1U!Q![1U![!]8a!_!`1U!a!b1U!b!c1U!c!}1U#R#S1U#T#o1U~;iOh~q~~;pPm~q~!`!a;s~;xOY~~;{P;=`<%l#o",
14
14
  tokenizers: [0],
15
15
  topRules: {"Document":[0,6]},
16
16
  specialized: [{term: 32, get: (value, stack) => (specializeKeyword(value, stack) << 1), external: specializeKeyword}],
@@ -14,6 +14,9 @@ export const CHART_TYPES = new Set([
14
14
  'gantt',
15
15
  'boxes-and-lines',
16
16
  'wireframe',
17
+ 'tech-radar',
18
+ 'mindmap',
19
+ 'journey-map',
17
20
  // Data chart types
18
21
  'bar',
19
22
  'line',
@@ -65,6 +68,10 @@ export const METADATA_KEYS = new Set([
65
68
  'top-left',
66
69
  'bottom-right',
67
70
  'bottom-left',
71
+ // Tech-radar pipe metadata
72
+ 'quadrant',
73
+ 'ring',
74
+ 'trend',
68
75
  ]);
69
76
 
70
77
  /** Tag declaration keyword. */
@@ -82,6 +89,8 @@ export const DIRECTIVE_KEYWORDS = new Set([
82
89
  'critical-path',
83
90
  'no-dependencies',
84
91
  'sort',
92
+ // Tech-radar
93
+ 'rings',
85
94
  // Tags
86
95
  'tags',
87
96
  'import',
@@ -194,6 +203,11 @@ export const STATUS_KEYWORDS = new Set([
194
203
  'in-progress',
195
204
  'backlog',
196
205
  'ready',
206
+ // Tech-radar trend values
207
+ 'new',
208
+ 'up',
209
+ 'down',
210
+ 'stable',
197
211
  ]);
198
212
 
199
213
  /** Modifier keywords — adjust declarations. */
@@ -744,7 +744,7 @@ export function parseGantt(
744
744
 
745
745
  // First segment could be empty (just `[Group]`) or have metadata
746
746
  let metadata: Record<string, string> = {};
747
- let color: string | null = null;
747
+ const color: string | null = null;
748
748
 
749
749
  const pipeWarn = () => warn(lineNumber, MULTIPLE_PIPE_ERROR);
750
750
  if (segments.length > 0 && segments[0].trim()) {
@@ -758,14 +758,8 @@ export function parseGantt(
758
758
  );
759
759
  }
760
760
 
761
- // Extract color from group name if present
762
- const nameExtracted = extractColor(groupMatch[1], palette);
763
- if (nameExtracted.color) {
764
- color = nameExtracted.color;
765
- }
766
-
767
761
  const group: GanttGroup = {
768
- name: nameExtracted.label,
762
+ name: groupMatch[1],
769
763
  color,
770
764
  metadata,
771
765
  lineNumber,
@@ -5,7 +5,6 @@ import { makeDgmoError, formatDgmoError, suggest } from '../diagnostics';
5
5
  import { parseInArrowLabel, matchColorParens } from '../utils/arrows';
6
6
  import {
7
7
  measureIndent,
8
- extractColor,
9
8
  inferArrowColor,
10
9
  parseFirstLine,
11
10
  OPTION_NOCOLON_RE,
@@ -32,55 +31,50 @@ interface NodeRef {
32
31
  * Try to parse a node reference from a text fragment.
33
32
  * Order matters: subroutine & document before process.
34
33
  */
35
- function parseNodeRef(text: string, palette?: PaletteColors): NodeRef | null {
34
+ function parseNodeRef(text: string): NodeRef | null {
36
35
  const t = text.trim();
37
36
  if (!t) return null;
38
37
 
39
38
  // Subroutine: [[Label]]
40
39
  let m = t.match(/^\[\[([^\]]+)\]\]$/);
41
40
  if (m) {
42
- const { label, color } = extractColor(m[1].trim(), palette);
43
- return {
44
- id: nodeId('subroutine', label),
45
- label,
46
- shape: 'subroutine',
47
- color,
48
- };
41
+ const label = m[1].trim();
42
+ return { id: nodeId('subroutine', label), label, shape: 'subroutine' };
49
43
  }
50
44
 
51
45
  // Document: [Label~]
52
46
  m = t.match(/^\[([^\]]+)~\]$/);
53
47
  if (m) {
54
- const { label, color } = extractColor(m[1].trim(), palette);
55
- return { id: nodeId('document', label), label, shape: 'document', color };
48
+ const label = m[1].trim();
49
+ return { id: nodeId('document', label), label, shape: 'document' };
56
50
  }
57
51
 
58
52
  // Process: [Label]
59
53
  m = t.match(/^\[([^\]]+)\]$/);
60
54
  if (m) {
61
- const { label, color } = extractColor(m[1].trim(), palette);
62
- return { id: nodeId('process', label), label, shape: 'process', color };
55
+ const label = m[1].trim();
56
+ return { id: nodeId('process', label), label, shape: 'process' };
63
57
  }
64
58
 
65
59
  // Terminal: (Label) — use .+ (greedy) so (Label(color)) matches outermost parens
66
60
  m = t.match(/^\((.+)\)$/);
67
61
  if (m) {
68
- const { label, color } = extractColor(m[1].trim(), palette);
69
- return { id: nodeId('terminal', label), label, shape: 'terminal', color };
62
+ const label = m[1].trim();
63
+ return { id: nodeId('terminal', label), label, shape: 'terminal' };
70
64
  }
71
65
 
72
66
  // Decision: <Label>
73
67
  m = t.match(/^<([^>]+)>$/);
74
68
  if (m) {
75
- const { label, color } = extractColor(m[1].trim(), palette);
76
- return { id: nodeId('decision', label), label, shape: 'decision', color };
69
+ const label = m[1].trim();
70
+ return { id: nodeId('decision', label), label, shape: 'decision' };
77
71
  }
78
72
 
79
73
  // I/O: /Label/
80
74
  m = t.match(/^\/([^/]+)\/$/);
81
75
  if (m) {
82
- const { label, color } = extractColor(m[1].trim(), palette);
83
- return { id: nodeId('io', label), label, shape: 'io', color };
76
+ const label = m[1].trim();
77
+ return { id: nodeId('io', label), label, shape: 'io' };
84
78
  }
85
79
 
86
80
  return null;
@@ -370,7 +364,7 @@ export function parseFlowchart(
370
364
 
371
365
  if (segments.length === 1) {
372
366
  // Single node reference, no arrows
373
- const ref = parseNodeRef(segments[0], palette);
367
+ const ref = parseNodeRef(segments[0]);
374
368
  if (ref) {
375
369
  const node = getOrCreateNode(ref, lineNumber);
376
370
  indentStack.push({ nodeId: node.id, indent });
@@ -398,7 +392,7 @@ export function parseFlowchart(
398
392
  }
399
393
 
400
394
  // This is a node text segment
401
- const ref = parseNodeRef(seg, palette);
395
+ const ref = parseNodeRef(seg);
402
396
  if (!ref) continue;
403
397
 
404
398
  const node = getOrCreateNode(ref, lineNumber);
@@ -5,7 +5,6 @@ import { makeDgmoError, formatDgmoError, suggest } from '../diagnostics';
5
5
  import { parseInArrowLabel, matchColorParens } from '../utils/arrows';
6
6
  import {
7
7
  measureIndent,
8
- extractColor,
9
8
  parseFirstLine,
10
9
  OPTION_NOCOLON_RE,
11
10
  ALL_CHART_TYPES,
@@ -173,10 +172,7 @@ interface NodeRef {
173
172
  color?: string;
174
173
  }
175
174
 
176
- function parseStateNodeRef(
177
- text: string,
178
- palette?: PaletteColors
179
- ): NodeRef | null {
175
+ function parseStateNodeRef(text: string): NodeRef | null {
180
176
  const t = text.trim();
181
177
  if (!t) return null;
182
178
 
@@ -189,14 +185,13 @@ function parseStateNodeRef(
189
185
  };
190
186
  }
191
187
 
192
- // State: bare text with optional (color) suffix
193
- const { label, color } = extractColor(t, palette);
188
+ // State: bare text
189
+ const label = t;
194
190
  if (!label) return null;
195
191
  return {
196
192
  id: `state:${label.toLowerCase().trim()}`,
197
193
  label,
198
194
  shape: 'state',
199
- color,
200
195
  };
201
196
  }
202
197
 
@@ -380,7 +375,7 @@ export function parseState(
380
375
 
381
376
  if (segments.length === 1) {
382
377
  // Single state reference, no arrows — this is the canonical definition
383
- const ref = parseStateNodeRef(segments[0], palette);
378
+ const ref = parseStateNodeRef(segments[0]);
384
379
  if (ref) {
385
380
  const node = getOrCreateNode(ref, lineNumber);
386
381
  // Standalone heading is the "definition" — update lineNumber so
@@ -409,7 +404,7 @@ export function parseState(
409
404
  continue;
410
405
  }
411
406
 
412
- const ref = parseStateNodeRef(seg, palette);
407
+ const ref = parseStateNodeRef(seg);
413
408
  if (!ref) continue;
414
409
 
415
410
  const node = getOrCreateNode(ref, lineNumber);
package/src/index.ts CHANGED
@@ -358,6 +358,55 @@ export { layoutWireframe } from './wireframe/layout';
358
358
  export type { WireframeLayout, WireframeLayoutNode } from './wireframe/layout';
359
359
  export { renderWireframe } from './wireframe/renderer';
360
360
 
361
+ export { parseTechRadar } from './tech-radar/parser';
362
+ export { computeRadarLayout, getRadarGeometry } from './tech-radar/layout';
363
+ export {
364
+ renderTechRadar,
365
+ renderTechRadarForExport,
366
+ } from './tech-radar/renderer';
367
+ export {
368
+ renderQuadrantFocus,
369
+ renderQuadrantFocusForExport,
370
+ } from './tech-radar/interactive';
371
+ export type {
372
+ ParsedTechRadar,
373
+ TechRadarRing,
374
+ TechRadarQuadrant,
375
+ TechRadarBlip,
376
+ TechRadarLayoutPoint,
377
+ QuadrantPosition,
378
+ BlipTrend,
379
+ } from './tech-radar/types';
380
+
381
+ export { parseCycle } from './cycle/parser';
382
+ export { computeCycleLayout } from './cycle/layout';
383
+ export { renderCycle, renderCycleForExport } from './cycle/renderer';
384
+ export type { CycleRenderOptions } from './cycle/renderer';
385
+ export type {
386
+ ParsedCycle,
387
+ CycleNode,
388
+ CycleEdge,
389
+ CycleLayoutNode,
390
+ CycleLayoutEdge,
391
+ CycleLayoutResult,
392
+ } from './cycle/types';
393
+
394
+ export { parseJourneyMap } from './journey-map/parser';
395
+ export { layoutJourneyMap } from './journey-map/layout';
396
+ export type { JourneyMapLayout } from './journey-map/layout';
397
+ export {
398
+ renderJourneyMap,
399
+ renderJourneyMapForExport,
400
+ } from './journey-map/renderer';
401
+ export type {
402
+ ParsedJourneyMap,
403
+ JourneyMapPhase,
404
+ JourneyMapStep,
405
+ JourneyMapPersona,
406
+ JourneyMapAnnotation,
407
+ } from './journey-map/types';
408
+ export type { JourneyMapInteractiveOptions } from './journey-map/renderer';
409
+
361
410
  export { resolveOrgImports } from './org/resolver';
362
411
  export type {
363
412
  ReadFileFn,
@@ -436,6 +485,7 @@ export {
436
485
  applyGroupOrdering,
437
486
  groupMessagesBySection,
438
487
  buildNoteMessageMap,
488
+ collectNoteLineNumbers,
439
489
  } from './sequence/renderer';
440
490
  export type {
441
491
  RenderStep,