@diagrammo/dgmo 0.8.20 → 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 (110) hide show
  1. package/AGENTS.md +2 -1
  2. package/README.md +1 -0
  3. package/dist/cli.cjs +142 -90
  4. package/dist/editor.cjs +30 -4
  5. package/dist/editor.cjs.map +1 -1
  6. package/dist/editor.js +30 -4
  7. package/dist/editor.js.map +1 -1
  8. package/dist/highlight.cjs +25 -3
  9. package/dist/highlight.cjs.map +1 -1
  10. package/dist/highlight.js +25 -3
  11. package/dist/highlight.js.map +1 -1
  12. package/dist/index.cjs +21201 -12886
  13. package/dist/index.cjs.map +1 -1
  14. package/dist/index.d.cts +646 -89
  15. package/dist/index.d.ts +646 -89
  16. package/dist/index.js +21178 -12889
  17. package/dist/index.js.map +1 -1
  18. package/docs/guide/chart-mindmap.md +198 -0
  19. package/docs/guide/chart-sequence.md +23 -1
  20. package/docs/guide/chart-sitemap.md +18 -1
  21. package/docs/guide/chart-tech-radar.md +219 -0
  22. package/docs/guide/chart-wireframe.md +100 -0
  23. package/docs/guide/index.md +8 -0
  24. package/docs/guide/registry.json +1 -0
  25. package/docs/language-reference.md +249 -4
  26. package/gallery/fixtures/boxes-and-lines.dgmo +10 -3
  27. package/gallery/fixtures/c4-full.dgmo +2 -2
  28. package/gallery/fixtures/cycle/ooda-loop.dgmo +25 -0
  29. package/gallery/fixtures/cycle/pdca-circle-nodes.dgmo +12 -0
  30. package/gallery/fixtures/cycle/pdca-minimal.dgmo +6 -0
  31. package/gallery/fixtures/cycle/sprint-cycle-span.dgmo +17 -0
  32. package/gallery/fixtures/gantt-full.dgmo +2 -2
  33. package/gallery/fixtures/gantt.dgmo +2 -2
  34. package/gallery/fixtures/infra-full.dgmo +2 -2
  35. package/gallery/fixtures/infra.dgmo +1 -1
  36. package/gallery/fixtures/sequence-tags-protocols.dgmo +2 -2
  37. package/gallery/fixtures/sequence-tags.dgmo +2 -2
  38. package/gallery/fixtures/tech-radar-dense.dgmo +77 -0
  39. package/gallery/fixtures/tech-radar.dgmo +36 -0
  40. package/gallery/fixtures/timeline.dgmo +1 -1
  41. package/package.json +1 -1
  42. package/src/boxes-and-lines/collapse.ts +21 -3
  43. package/src/boxes-and-lines/layout.ts +360 -42
  44. package/src/boxes-and-lines/parser.ts +94 -11
  45. package/src/boxes-and-lines/renderer.ts +371 -114
  46. package/src/boxes-and-lines/types.ts +2 -1
  47. package/src/c4/layout.ts +8 -8
  48. package/src/c4/parser.ts +35 -2
  49. package/src/c4/renderer.ts +19 -3
  50. package/src/c4/types.ts +1 -0
  51. package/src/chart.ts +14 -7
  52. package/src/completion.ts +253 -0
  53. package/src/cycle/layout.ts +732 -0
  54. package/src/cycle/parser.ts +352 -0
  55. package/src/cycle/renderer.ts +539 -0
  56. package/src/cycle/types.ts +77 -0
  57. package/src/d3.ts +240 -40
  58. package/src/dgmo-router.ts +15 -0
  59. package/src/echarts.ts +7 -4
  60. package/src/editor/dgmo.grammar +5 -1
  61. package/src/editor/dgmo.grammar.js +1 -1
  62. package/src/editor/keywords.ts +26 -0
  63. package/src/gantt/parser.ts +2 -8
  64. package/src/graph/flowchart-parser.ts +15 -21
  65. package/src/graph/layout.ts +73 -9
  66. package/src/graph/state-collapse.ts +78 -0
  67. package/src/graph/state-parser.ts +5 -10
  68. package/src/graph/state-renderer.ts +139 -34
  69. package/src/index.ts +78 -0
  70. package/src/infra/layout.ts +218 -74
  71. package/src/infra/parser.ts +30 -6
  72. package/src/infra/renderer.ts +14 -8
  73. package/src/infra/types.ts +10 -3
  74. package/src/journey-map/layout.ts +386 -0
  75. package/src/journey-map/parser.ts +540 -0
  76. package/src/journey-map/renderer.ts +1456 -0
  77. package/src/journey-map/types.ts +47 -0
  78. package/src/kanban/parser.ts +3 -10
  79. package/src/kanban/renderer.ts +325 -63
  80. package/src/mindmap/collapse.ts +88 -0
  81. package/src/mindmap/layout.ts +605 -0
  82. package/src/mindmap/parser.ts +373 -0
  83. package/src/mindmap/renderer.ts +544 -0
  84. package/src/mindmap/text-wrap.ts +217 -0
  85. package/src/mindmap/types.ts +55 -0
  86. package/src/org/parser.ts +2 -6
  87. package/src/render.ts +18 -21
  88. package/src/sequence/renderer.ts +273 -56
  89. package/src/sharing.ts +3 -0
  90. package/src/sitemap/layout.ts +56 -18
  91. package/src/sitemap/parser.ts +26 -17
  92. package/src/sitemap/renderer.ts +34 -0
  93. package/src/sitemap/types.ts +1 -0
  94. package/src/tech-radar/index.ts +14 -0
  95. package/src/tech-radar/interactive.ts +1058 -0
  96. package/src/tech-radar/layout.ts +190 -0
  97. package/src/tech-radar/parser.ts +385 -0
  98. package/src/tech-radar/renderer.ts +1159 -0
  99. package/src/tech-radar/shared.ts +187 -0
  100. package/src/tech-radar/types.ts +81 -0
  101. package/src/utils/description-helpers.ts +33 -0
  102. package/src/utils/export-container.ts +3 -2
  103. package/src/utils/legend-d3.ts +1 -0
  104. package/src/utils/legend-layout.ts +5 -3
  105. package/src/utils/parsing.ts +48 -7
  106. package/src/utils/tag-groups.ts +46 -60
  107. package/src/wireframe/layout.ts +460 -0
  108. package/src/wireframe/parser.ts +956 -0
  109. package/src/wireframe/renderer.ts +1293 -0
  110. package/src/wireframe/types.ts +110 -0
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(),
@@ -6492,12 +6505,7 @@ export async function renderForExport(
6492
6505
  content: string,
6493
6506
  theme: 'light' | 'dark' | 'transparent',
6494
6507
  palette?: PaletteColors,
6495
- orgExportState?: {
6496
- collapsedNodes?: Set<string>;
6497
- activeTagGroup?: string | null;
6498
- hiddenAttributes?: Set<string>;
6499
- swimlaneTagGroup?: string | null;
6500
- },
6508
+ viewState?: import('./sharing').CompactViewState,
6501
6509
  options?: {
6502
6510
  c4Level?: 'context' | 'containers' | 'components' | 'deployment';
6503
6511
  c4System?: string;
@@ -6521,14 +6529,14 @@ export async function renderForExport(
6521
6529
  const orgParsed = parseOrg(content, effectivePalette);
6522
6530
  if (orgParsed.error) return '';
6523
6531
 
6524
- // Apply interactive collapse state when provided
6525
- const collapsedNodes = orgExportState?.collapsedNodes;
6532
+ // Apply interactive collapse state when provided (read from unified viewState)
6533
+ const collapsedNodes = viewState?.cg ? new Set(viewState.cg) : undefined;
6526
6534
  const activeTagGroup = resolveActiveTagGroup(
6527
6535
  orgParsed.tagGroups,
6528
6536
  orgParsed.options['active-tag'],
6529
- orgExportState?.activeTagGroup ?? options?.tagGroup
6537
+ viewState?.tag ?? options?.tagGroup
6530
6538
  );
6531
- const hiddenAttributes = orgExportState?.hiddenAttributes;
6539
+ const hiddenAttributes = viewState?.ha ? new Set(viewState.ha) : undefined;
6532
6540
 
6533
6541
  const { parsed: effectiveParsed, hiddenCounts } =
6534
6542
  collapsedNodes && collapsedNodes.size > 0
@@ -6575,14 +6583,14 @@ export async function renderForExport(
6575
6583
  const sitemapParsed = parseSitemap(content, effectivePalette);
6576
6584
  if (sitemapParsed.error || sitemapParsed.roots.length === 0) return '';
6577
6585
 
6578
- // Apply interactive collapse state when provided
6579
- const collapsedNodes = orgExportState?.collapsedNodes;
6586
+ // Apply interactive collapse state when provided (read from unified viewState)
6587
+ const collapsedNodes = viewState?.cg ? new Set(viewState.cg) : undefined;
6580
6588
  const activeTagGroup = resolveActiveTagGroup(
6581
6589
  sitemapParsed.tagGroups,
6582
6590
  sitemapParsed.options['active-tag'],
6583
- orgExportState?.activeTagGroup ?? options?.tagGroup
6591
+ viewState?.tag ?? options?.tagGroup
6584
6592
  );
6585
- const hiddenAttributes = orgExportState?.hiddenAttributes;
6593
+ const hiddenAttributes = viewState?.ha ? new Set(viewState.ha) : undefined;
6586
6594
 
6587
6595
  const { parsed: effectiveParsed, hiddenCounts } =
6588
6596
  collapsedNodes && collapsedNodes.size > 0
@@ -6635,8 +6643,12 @@ export async function renderForExport(
6635
6643
  activeTagGroup: resolveActiveTagGroup(
6636
6644
  kanbanParsed.tagGroups,
6637
6645
  kanbanParsed.options['active-tag'],
6638
- options?.tagGroup
6646
+ viewState?.tag ?? options?.tagGroup
6639
6647
  ),
6648
+ currentSwimlaneGroup: viewState?.swim ?? null,
6649
+ collapsedLanes: viewState?.cl ? new Set(viewState.cl) : undefined,
6650
+ collapsedColumns: viewState?.cc ? new Set(viewState.cc) : undefined,
6651
+ compactMeta: viewState?.cm,
6640
6652
  });
6641
6653
  return finalizeSvgExport(container, theme, effectivePalette);
6642
6654
  }
@@ -6696,22 +6708,32 @@ export async function renderForExport(
6696
6708
  resolveActiveTagGroup(
6697
6709
  erParsed.tagGroups,
6698
6710
  erParsed.options['active-tag'],
6699
- options?.tagGroup
6700
- )
6711
+ viewState?.tag ?? options?.tagGroup
6712
+ ),
6713
+ viewState?.sem
6701
6714
  );
6702
6715
  return finalizeSvgExport(container, theme, effectivePalette);
6703
6716
  }
6704
6717
 
6705
6718
  if (detectedType === 'boxes-and-lines') {
6706
6719
  const { parseBoxesAndLines } = await import('./boxes-and-lines/parser');
6707
- const { layoutBoxesAndLines } = await import('./boxes-and-lines/layout');
6708
- const { renderBoxesAndLinesForExport } =
6709
- await import('./boxes-and-lines/renderer');
6710
-
6711
6720
  const effectivePalette = await resolveExportPalette(theme, palette);
6712
6721
  const blParsed = parseBoxesAndLines(content);
6713
6722
  if (blParsed.error || blParsed.nodes.length === 0) return '';
6714
6723
 
6724
+ // Convert viewState.htv (Record<string, string[]>) to Map<string, Set<string>>
6725
+ let blHiddenTagValues: Map<string, Set<string>> | undefined;
6726
+ if (viewState?.htv) {
6727
+ blHiddenTagValues = new Map();
6728
+ for (const [k, v] of Object.entries(viewState.htv)) {
6729
+ blHiddenTagValues.set(k, new Set(v));
6730
+ }
6731
+ }
6732
+
6733
+ const { layoutBoxesAndLines } = await import('./boxes-and-lines/layout');
6734
+ const { renderBoxesAndLinesForExport } =
6735
+ await import('./boxes-and-lines/renderer');
6736
+
6715
6737
  const blLayout = layoutBoxesAndLines(blParsed);
6716
6738
  const PADDING = 20;
6717
6739
  const titleOffset = blParsed.title ? 40 : 0;
@@ -6727,12 +6749,106 @@ export async function renderForExport(
6727
6749
  theme === 'dark',
6728
6750
  {
6729
6751
  exportDims: { width: exportWidth, height: exportHeight },
6730
- activeTagGroup: options?.tagGroup,
6752
+ activeTagGroup: viewState?.tag ?? options?.tagGroup,
6753
+ hiddenTagValues: blHiddenTagValues,
6731
6754
  }
6732
6755
  );
6733
6756
  return finalizeSvgExport(container, theme, effectivePalette);
6734
6757
  }
6735
6758
 
6759
+ if (detectedType === 'mindmap') {
6760
+ const { parseMindmap } = await import('./mindmap/parser');
6761
+ const { layoutMindmap } = await import('./mindmap/layout');
6762
+ const { collapseMindmapTree } = await import('./mindmap/collapse');
6763
+ const { renderMindmap } = await import('./mindmap/renderer');
6764
+
6765
+ const isDark = theme === 'dark';
6766
+ const effectivePalette = await resolveExportPalette(theme, palette);
6767
+
6768
+ const mmParsed = parseMindmap(content, effectivePalette);
6769
+ if (mmParsed.error) return '';
6770
+
6771
+ const collapsedNodes = viewState?.cg ? new Set(viewState.cg) : undefined;
6772
+ const activeTagGroup = resolveActiveTagGroup(
6773
+ mmParsed.tagGroups,
6774
+ mmParsed.options['active-tag'],
6775
+ viewState?.tag ?? options?.tagGroup
6776
+ );
6777
+ const hideDescriptions =
6778
+ mmParsed.options['hide-descriptions'] === 'true' ||
6779
+ viewState?.hd === true;
6780
+
6781
+ const { roots: effectiveRoots, hiddenCounts } =
6782
+ collapsedNodes && collapsedNodes.size > 0
6783
+ ? collapseMindmapTree(mmParsed.roots, collapsedNodes)
6784
+ : { roots: mmParsed.roots, hiddenCounts: new Map<string, number>() };
6785
+
6786
+ const effectiveParsed = { ...mmParsed, roots: effectiveRoots };
6787
+
6788
+ const mmLayout = layoutMindmap(effectiveParsed, effectivePalette, {
6789
+ interactive: false,
6790
+ hiddenCounts: hiddenCounts.size > 0 ? hiddenCounts : undefined,
6791
+ activeTagGroup,
6792
+ hideDescriptions,
6793
+ });
6794
+
6795
+ const PADDING = 20;
6796
+ const titleOffset = effectiveParsed.title ? 30 : 0;
6797
+ const exportWidth = mmLayout.width + PADDING * 2;
6798
+ const exportHeight = mmLayout.height + PADDING * 2 + titleOffset;
6799
+ const container = createExportContainer(exportWidth, exportHeight);
6800
+
6801
+ const colorByDepth = viewState?.cbd === true;
6802
+
6803
+ renderMindmap(
6804
+ container,
6805
+ effectiveParsed,
6806
+ mmLayout,
6807
+ effectivePalette,
6808
+ isDark,
6809
+ undefined,
6810
+ { width: exportWidth, height: exportHeight },
6811
+ undefined,
6812
+ hideDescriptions,
6813
+ colorByDepth ? null : activeTagGroup,
6814
+ colorByDepth ? { colorByDepth: true } : undefined
6815
+ );
6816
+ return finalizeSvgExport(container, theme, effectivePalette);
6817
+ }
6818
+
6819
+ if (detectedType === 'wireframe') {
6820
+ const { parseWireframe } = await import('./wireframe/parser');
6821
+ const { layoutWireframe } = await import('./wireframe/layout');
6822
+ const { renderWireframe } = await import('./wireframe/renderer');
6823
+
6824
+ const effectivePalette = await resolveExportPalette(theme, palette);
6825
+ const wireframeParsed = parseWireframe(content);
6826
+ if (
6827
+ wireframeParsed.error ||
6828
+ (wireframeParsed.roots.length === 0 &&
6829
+ wireframeParsed.modals.length === 0)
6830
+ )
6831
+ return '';
6832
+
6833
+ const wireframeLayout = layoutWireframe(wireframeParsed);
6834
+
6835
+ const exportWidth = wireframeLayout.width;
6836
+ const exportHeight = wireframeLayout.height;
6837
+ const container = createExportContainer(exportWidth, exportHeight);
6838
+
6839
+ renderWireframe(
6840
+ container,
6841
+ wireframeParsed,
6842
+ wireframeLayout,
6843
+ effectivePalette,
6844
+ theme === 'dark',
6845
+ undefined,
6846
+ { width: exportWidth, height: exportHeight },
6847
+ theme
6848
+ );
6849
+ return finalizeSvgExport(container, theme, effectivePalette);
6850
+ }
6851
+
6736
6852
  if (detectedType === 'c4') {
6737
6853
  const { parseC4 } = await import('./c4/parser');
6738
6854
  const {
@@ -6748,10 +6864,18 @@ export async function renderForExport(
6748
6864
  const c4Parsed = parseC4(content, effectivePalette);
6749
6865
  if (c4Parsed.error || c4Parsed.elements.length === 0) return '';
6750
6866
 
6751
- // Container/component-level rendering
6752
- const c4Level = options?.c4Level ?? 'context';
6753
- const c4System = options?.c4System;
6754
- const c4Container = options?.c4Container;
6867
+ // Container/component-level rendering (viewState fallback for share links)
6868
+ const c4Level =
6869
+ options?.c4Level ??
6870
+ (viewState?.c4l as
6871
+ | 'context'
6872
+ | 'containers'
6873
+ | 'components'
6874
+ | 'deployment'
6875
+ | undefined) ??
6876
+ 'context';
6877
+ const c4System = options?.c4System ?? viewState?.c4s;
6878
+ const c4Container = options?.c4Container ?? viewState?.c4c;
6755
6879
 
6756
6880
  const c4Layout =
6757
6881
  c4Level === 'deployment'
@@ -6788,7 +6912,7 @@ export async function renderForExport(
6788
6912
  resolveActiveTagGroup(
6789
6913
  c4Parsed.tagGroups,
6790
6914
  c4Parsed.options['active-tag'],
6791
- options?.tagGroup
6915
+ viewState?.tag ?? options?.tagGroup
6792
6916
  )
6793
6917
  );
6794
6918
  return finalizeSvgExport(container, theme, effectivePalette);
@@ -6834,7 +6958,7 @@ export async function renderForExport(
6834
6958
  const activeTagGroup = resolveActiveTagGroup(
6835
6959
  infraParsed.tagGroups,
6836
6960
  infraParsed.options['active-tag'],
6837
- options?.tagGroup
6961
+ viewState?.tag ?? options?.tagGroup
6838
6962
  );
6839
6963
 
6840
6964
  const titleOffset = infraParsed.title ? 40 : 0;
@@ -6860,7 +6984,8 @@ export async function renderForExport(
6860
6984
  false,
6861
6985
  null,
6862
6986
  null,
6863
- true
6987
+ true,
6988
+ viewState?.cg ? new Set(viewState.cg) : null
6864
6989
  );
6865
6990
  // Restore explicit pixel dimensions for resvg (renderer uses 100%/viewBox for app scaling)
6866
6991
  const infraSvg = container.querySelector('svg');
@@ -6890,7 +7015,16 @@ export async function renderForExport(
6890
7015
  resolved,
6891
7016
  effectivePalette,
6892
7017
  theme === 'dark',
6893
- undefined,
7018
+ {
7019
+ collapsedGroups: viewState?.cg ? new Set(viewState.cg) : undefined,
7020
+ currentSwimlaneGroup: viewState?.swim ?? undefined,
7021
+ collapsedLanes: viewState?.cl ? new Set(viewState.cl) : undefined,
7022
+ currentActiveGroup: resolveActiveTagGroup(
7023
+ resolved.tagGroups,
7024
+ resolved.options.activeTag ?? undefined,
7025
+ viewState?.tag ?? options?.tagGroup
7026
+ ),
7027
+ },
6894
7028
  { width: EXPORT_W, height: EXPORT_H }
6895
7029
  );
6896
7030
  return finalizeSvgExport(container, theme, effectivePalette);
@@ -6920,6 +7054,72 @@ export async function renderForExport(
6920
7054
  return finalizeSvgExport(container, theme, effectivePalette);
6921
7055
  }
6922
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
+
6923
7123
  const parsed = parseVisualization(content, palette);
6924
7124
  // Allow sequence diagrams through even if parseVisualization errors —
6925
7125
  // sequence is parsed by its own dedicated parser (parseSequenceDgmo)
@@ -6991,9 +7191,9 @@ export async function renderForExport(
6991
7191
  resolveActiveTagGroup(
6992
7192
  parsed.timelineTagGroups,
6993
7193
  undefined,
6994
- orgExportState?.activeTagGroup ?? options?.tagGroup
7194
+ viewState?.tag ?? options?.tagGroup
6995
7195
  ),
6996
- orgExportState?.swimlaneTagGroup
7196
+ viewState?.swim
6997
7197
  );
6998
7198
  } else if (parsed.type === 'venn') {
6999
7199
  renderVenn(container, parsed, effectivePalette, isDark, undefined, dims);
@@ -17,6 +17,11 @@ import { looksLikeSitemap, parseSitemap } from './sitemap/parser';
17
17
  import { parseInfra } from './infra/parser';
18
18
  import { parseGantt } from './gantt/parser';
19
19
  import { parseBoxesAndLines } from './boxes-and-lines/parser';
20
+ import { parseMindmap } from './mindmap/parser';
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';
20
25
  import { parseFirstLine } from './utils/parsing';
21
26
  import { makeDgmoError, suggest } from './diagnostics';
22
27
  import type { DgmoError } from './diagnostics';
@@ -135,6 +140,8 @@ const VISUALIZATION_TYPES = new Set([
135
140
  'timeline',
136
141
  'venn',
137
142
  'quadrant',
143
+ 'tech-radar',
144
+ 'cycle',
138
145
  ]);
139
146
  const DIAGRAM_TYPES = new Set([
140
147
  'sequence',
@@ -149,6 +156,9 @@ const DIAGRAM_TYPES = new Set([
149
156
  'infra',
150
157
  'gantt',
151
158
  'boxes-and-lines',
159
+ 'mindmap',
160
+ 'wireframe',
161
+ 'journey-map',
152
162
  ]);
153
163
  const EXTENDED_CHART_TYPES = new Set([
154
164
  'scatter',
@@ -228,6 +238,11 @@ const PARSE_DISPATCH = new Map<
228
238
  ['infra', (c) => parseInfra(c)],
229
239
  ['gantt', (c) => parseGantt(c)],
230
240
  ['boxes-and-lines', (c) => parseBoxesAndLines(c)],
241
+ ['mindmap', (c) => parseMindmap(c)],
242
+ ['wireframe', (c) => parseWireframe(c)],
243
+ ['tech-radar', (c) => parseTechRadar(c)],
244
+ ['cycle', (c) => parseCycle(c)],
245
+ ['journey-map', (c) => parseJourneyMap(c)],
231
246
  ]);
232
247
 
233
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}],
@@ -13,6 +13,10 @@ export const CHART_TYPES = new Set([
13
13
  'infra',
14
14
  'gantt',
15
15
  'boxes-and-lines',
16
+ 'wireframe',
17
+ 'tech-radar',
18
+ 'mindmap',
19
+ 'journey-map',
16
20
  // Data chart types
17
21
  'bar',
18
22
  'line',
@@ -64,6 +68,10 @@ export const METADATA_KEYS = new Set([
64
68
  'top-left',
65
69
  'bottom-right',
66
70
  'bottom-left',
71
+ // Tech-radar pipe metadata
72
+ 'quadrant',
73
+ 'ring',
74
+ 'trend',
67
75
  ]);
68
76
 
69
77
  /** Tag declaration keyword. */
@@ -81,6 +89,8 @@ export const DIRECTIVE_KEYWORDS = new Set([
81
89
  'critical-path',
82
90
  'no-dependencies',
83
91
  'sort',
92
+ // Tech-radar
93
+ 'rings',
84
94
  // Tags
85
95
  'tags',
86
96
  'import',
@@ -170,6 +180,17 @@ export const CONTROL_KEYWORDS = new Set([
170
180
  'loop',
171
181
  'parallel',
172
182
  'note',
183
+ // Wireframe elements
184
+ 'nav',
185
+ 'tabs',
186
+ 'table',
187
+ 'image',
188
+ 'modal',
189
+ 'skeleton',
190
+ 'alert',
191
+ 'progress',
192
+ 'chart',
193
+ 'mobile',
173
194
  ]);
174
195
 
175
196
  /** Status keywords — kanban. */
@@ -182,6 +203,11 @@ export const STATUS_KEYWORDS = new Set([
182
203
  'in-progress',
183
204
  'backlog',
184
205
  'ready',
206
+ // Tech-radar trend values
207
+ 'new',
208
+ 'up',
209
+ 'down',
210
+ 'stable',
185
211
  ]);
186
212
 
187
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,