@diagrammo/dgmo 0.15.1 → 0.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (122) hide show
  1. package/README.md +9 -9
  2. package/dist/advanced.cjs +612 -734
  3. package/dist/advanced.d.cts +42 -36
  4. package/dist/advanced.d.ts +42 -36
  5. package/dist/advanced.js +612 -733
  6. package/dist/auto.cjs +508 -620
  7. package/dist/auto.js +105 -105
  8. package/dist/auto.mjs +508 -620
  9. package/dist/cli.cjs +144 -144
  10. package/dist/editor.cjs +8 -9
  11. package/dist/editor.js +8 -9
  12. package/dist/highlight.cjs +8 -9
  13. package/dist/highlight.js +8 -9
  14. package/dist/index.cjs +497 -608
  15. package/dist/index.js +497 -608
  16. package/dist/internal.cjs +612 -734
  17. package/dist/internal.d.cts +42 -36
  18. package/dist/internal.d.ts +42 -36
  19. package/dist/internal.js +612 -733
  20. package/dist/pert.d.cts +2 -2
  21. package/dist/pert.d.ts +2 -2
  22. package/docs/language-reference.md +97 -84
  23. package/docs/migration-sequence-color-to-tags.md +1 -1
  24. package/gallery/fixtures/area.dgmo +3 -3
  25. package/gallery/fixtures/bar-stacked.dgmo +5 -5
  26. package/gallery/fixtures/boxes-and-lines.dgmo +2 -2
  27. package/gallery/fixtures/c4-full.dgmo +8 -8
  28. package/gallery/fixtures/class-full.dgmo +2 -2
  29. package/gallery/fixtures/doughnut.dgmo +6 -6
  30. package/gallery/fixtures/flowchart-colors.dgmo +3 -3
  31. package/gallery/fixtures/function.dgmo +3 -3
  32. package/gallery/fixtures/gantt-full.dgmo +9 -9
  33. package/gallery/fixtures/gantt.dgmo +7 -7
  34. package/gallery/fixtures/infra-full.dgmo +6 -6
  35. package/gallery/fixtures/infra.dgmo +2 -2
  36. package/gallery/fixtures/kanban.dgmo +9 -9
  37. package/gallery/fixtures/line.dgmo +2 -2
  38. package/gallery/fixtures/multi-line.dgmo +3 -3
  39. package/gallery/fixtures/org-full.dgmo +6 -6
  40. package/gallery/fixtures/quadrant.dgmo +2 -2
  41. package/gallery/fixtures/sankey.dgmo +9 -9
  42. package/gallery/fixtures/scatter.dgmo +3 -3
  43. package/gallery/fixtures/sequence-tags-protocols.dgmo +11 -11
  44. package/gallery/fixtures/sequence-tags.dgmo +10 -10
  45. package/gallery/fixtures/sequence.dgmo +4 -4
  46. package/gallery/fixtures/sitemap-full.dgmo +7 -7
  47. package/gallery/fixtures/slope.dgmo +5 -5
  48. package/gallery/fixtures/spr-eras.dgmo +9 -9
  49. package/gallery/fixtures/timeline.dgmo +3 -3
  50. package/gallery/fixtures/venn.dgmo +3 -3
  51. package/package.json +7 -3
  52. package/src/advanced.ts +0 -1
  53. package/src/auto/index.ts +2 -2
  54. package/src/boxes-and-lines/layout.ts +1 -2
  55. package/src/boxes-and-lines/renderer.ts +5 -1
  56. package/src/c4/parser.ts +2 -2
  57. package/src/c4/renderer.ts +15 -8
  58. package/src/chart.ts +18 -9
  59. package/src/class/parser.ts +8 -7
  60. package/src/class/renderer.ts +17 -6
  61. package/src/cli.ts +8 -8
  62. package/src/completion.ts +14 -17
  63. package/src/cycle/parser.ts +15 -1
  64. package/src/cycle/renderer.ts +6 -3
  65. package/src/d3.ts +88 -49
  66. package/src/diagnostics.ts +20 -0
  67. package/src/echarts.ts +28 -11
  68. package/src/editor/dgmo.grammar +1 -3
  69. package/src/editor/dgmo.grammar.d.ts +1 -1
  70. package/src/editor/dgmo.grammar.js +8 -8
  71. package/src/editor/dgmo.grammar.terms.js +11 -12
  72. package/src/editor/highlight-api.ts +0 -1
  73. package/src/editor/highlight.ts +0 -1
  74. package/src/er/parser.ts +19 -12
  75. package/src/er/renderer.ts +19 -7
  76. package/src/gantt/parser.ts +1 -1
  77. package/src/gantt/renderer.ts +7 -4
  78. package/src/graph/flowchart-parser.ts +18 -84
  79. package/src/graph/flowchart-renderer.ts +6 -8
  80. package/src/graph/layout.ts +0 -2
  81. package/src/graph/state-parser.ts +17 -62
  82. package/src/graph/state-renderer.ts +3 -8
  83. package/src/infra/parser.ts +21 -11
  84. package/src/infra/renderer.ts +8 -6
  85. package/src/journey-map/parser.ts +11 -4
  86. package/src/journey-map/renderer.ts +3 -1
  87. package/src/kanban/parser.ts +11 -7
  88. package/src/kanban/renderer.ts +3 -1
  89. package/src/mindmap/parser.ts +4 -5
  90. package/src/mindmap/renderer.ts +2 -1
  91. package/src/org/parser.ts +3 -3
  92. package/src/org/renderer.ts +4 -3
  93. package/src/pert/analyzer.ts +10 -10
  94. package/src/pert/layout.ts +1 -1
  95. package/src/pert/parser.ts +8 -8
  96. package/src/pert/renderer.ts +7 -2
  97. package/src/pert/types.ts +1 -1
  98. package/src/pyramid/parser.ts +13 -1
  99. package/src/raci/parser.ts +42 -12
  100. package/src/raci/renderer.ts +2 -1
  101. package/src/raci/types.ts +4 -3
  102. package/src/ring/parser.ts +13 -1
  103. package/src/sequence/parser.ts +81 -23
  104. package/src/sequence/participant-inference.ts +18 -181
  105. package/src/sequence/renderer.ts +48 -137
  106. package/src/sitemap/layout.ts +0 -2
  107. package/src/sitemap/parser.ts +12 -38
  108. package/src/sitemap/renderer.ts +13 -13
  109. package/src/sitemap/types.ts +0 -1
  110. package/src/tech-radar/parser.ts +2 -2
  111. package/src/tech-radar/renderer.ts +5 -3
  112. package/src/tech-radar/types.ts +2 -0
  113. package/src/utils/arrows.ts +3 -28
  114. package/src/utils/extract-alias.ts +1 -1
  115. package/src/utils/inline-markdown.ts +1 -1
  116. package/src/utils/legend-d3.ts +12 -6
  117. package/src/utils/legend-layout.ts +1 -1
  118. package/src/utils/legend-types.ts +1 -1
  119. package/src/utils/parsing.ts +64 -35
  120. package/src/utils/tag-groups.ts +98 -18
  121. package/src/utils/time-ticks.ts +1 -1
  122. package/src/wireframe/parser.ts +3 -3
package/src/chart.ts CHANGED
@@ -63,7 +63,7 @@ export interface ParsedChart {
63
63
  // Colors
64
64
  // ============================================================
65
65
 
66
- import { resolveColorWithDiagnostic } from './colors';
66
+ import { resolveColorWithDiagnostic, RECOGNIZED_COLOR_NAMES } from './colors';
67
67
  import type { PaletteColors } from './palettes';
68
68
  import { makeDgmoError, formatDgmoError, suggest } from './diagnostics';
69
69
  import {
@@ -212,21 +212,30 @@ export function parseChart(
212
212
  // Fall through — first line might be a data row or option
213
213
  }
214
214
 
215
- // Era line: era Day 1 -> Day 3 Rough Seas (blue) — colon-free
216
- const eraMatch = trimmed.match(
217
- /^era\s+(.+?)\s*->\s*(.+?)(?:\s*\(([^)]+)\))?\s*$/
218
- );
215
+ // Era line 1.5 trailing-token):
216
+ // `era Day 1 -> Day 3 Rough Seas` (no color)
217
+ // `era Day 1 -> Day 3 Rough Seas blue` (trailing color word)
218
+ // Color (if any) is the last whitespace-delimited token of the label.
219
+ const eraMatch = trimmed.match(/^era\s+(.+?)\s*->\s*(.+?)\s*$/);
219
220
  if (eraMatch) {
220
- // Store start and raw afterArrow — resolved against data labels after parsing
221
221
  const afterArrow = eraMatch[2].trim();
222
222
  const spaceIdx = afterArrow.indexOf(' ');
223
223
  if (spaceIdx >= 0) {
224
+ // Peel trailing-token color off the after-arrow label region.
225
+ const lastSpaceIdx = afterArrow.lastIndexOf(' ');
226
+ const trailing = afterArrow.substring(lastSpaceIdx + 1);
227
+ const hasColor = RECOGNIZED_COLOR_NAMES.includes(
228
+ trailing as (typeof RECOGNIZED_COLOR_NAMES)[number]
229
+ );
230
+ const labelPart = hasColor
231
+ ? afterArrow.substring(0, lastSpaceIdx).trimEnd()
232
+ : afterArrow;
224
233
  rawEras.push({
225
234
  start: eraMatch[1].trim(),
226
- afterArrow,
227
- color: eraMatch[3]
235
+ afterArrow: labelPart,
236
+ color: hasColor
228
237
  ? (resolveColorWithDiagnostic(
229
- eraMatch[3].trim(),
238
+ trailing,
230
239
  lineNumber,
231
240
  result.diagnostics,
232
241
  palette
@@ -35,10 +35,11 @@ function classId(name: string): string {
35
35
  // Regex patterns
36
36
  // ============================================================
37
37
 
38
- // Class declaration: [modifier] ClassName [extends Parent] [implements Interface] (color)
39
- // Multi-word names allowed (`Customer Service`); quote with `"name"` if name
40
- // contains reserved chars. ClassName must start with uppercase to keep the
41
- // convention. Captures (positional):
38
+ // Class declaration: [modifier] ClassName [extends Parent] [implements Interface] [color] [as alias]
39
+ // Color is the universal §1.5 trailing-token form (a bare lowercase palette
40
+ // color word after structural slots). Multi-word names allowed
41
+ // (`Customer Service`); quote with `"name"` if name contains reserved chars.
42
+ // ClassName must start with uppercase. Captures (positional):
42
43
  // 1: modifier (abstract|interface|enum) | undefined
43
44
  // 2: quotedClassName | undefined
44
45
  // 3: bareClassName | undefined
@@ -47,10 +48,10 @@ function classId(name: string): string {
47
48
  // 6: quotedImplements | undefined
48
49
  // 7: bareImplements | undefined
49
50
  // 8: legacy bracket modifier | undefined
50
- // 9: color | undefined
51
+ // 9: color (trailing token; recognized palette word) | undefined
51
52
  // 10: alias literal (TD-18) | undefined
52
53
  const CLASS_DECL_RE =
53
- /^(?:(abstract|interface|enum)\s+)?(?:"([^"]+)"|([A-Z][^":]*?))(?:\s+extends\s+(?:"([^"]+)"|([A-Z][^":]*?)))?(?:\s+implements\s+(?:"([^"]+)"|([A-Z][^":]*?)))?(?:\s+\[(abstract|interface|enum)\])?(?:\s+\(([^)]+)\))?(?:\s+as\s+([A-Za-z][A-Za-z0-9_]{0,11}))?\s*$/;
54
+ /^(?:(abstract|interface|enum)\s+)?(?:"([^"]+)"|([A-Z][^":]*?))(?:\s+extends\s+(?:"([^"]+)"|([A-Z][^":]*?)))?(?:\s+implements\s+(?:"([^"]+)"|([A-Z][^":]*?)))?(?:\s+\[(abstract|interface|enum)\])?(?:\s+(red|orange|yellow|green|blue|purple|teal|cyan|gray|black|white))?(?:\s+as\s+([A-Za-z][A-Za-z0-9_]{0,11}))?\s*$/;
54
55
 
55
56
  // Relationship — arrow syntax (indented under source class).
56
57
  // Arrows: --|> ..|> *-- o-- ..> ->
@@ -258,7 +259,7 @@ export function parseClassDiagram(
258
259
  // First line: bare chart type + optional title (new syntax)
259
260
  if (!contentStarted && indent === 0 && i === 0) {
260
261
  const firstLine = parseFirstLine(trimmed);
261
- if (firstLine && firstLine.chartType === 'class') {
262
+ if (firstLine?.chartType === 'class') {
262
263
  if (firstLine.title) {
263
264
  result.title = firstLine.title;
264
265
  result.titleLineNumber = lineNumber;
@@ -192,7 +192,8 @@ export function renderClassDiagram(
192
192
  isDark: boolean,
193
193
  onClickItem?: (lineNumber: number) => void,
194
194
  exportDims?: { width?: number; height?: number },
195
- legendActive?: boolean | null
195
+ legendActive?: boolean | null,
196
+ exportMode?: boolean
196
197
  ): void {
197
198
  d3Selection.select(container).selectAll(':not([data-d3-tooltip])').remove();
198
199
 
@@ -376,7 +377,7 @@ export function renderClassDiagram(
376
377
  const legendConfig: LegendConfig = {
377
378
  groups: legendGroups,
378
379
  position: { placement: 'top-center', titleRelation: 'below-title' },
379
- mode: 'fixed',
380
+ mode: exportMode ? 'export' : 'preview',
380
381
  };
381
382
  const legendState: LegendState = {
382
383
  activeGroup: isLegendExpanded ? LEGEND_GROUP_NAME : null,
@@ -697,10 +698,20 @@ export function renderClassDiagramForExport(
697
698
  legendReserve;
698
699
 
699
700
  return runInExportContainer(exportWidth, exportHeight, (container) => {
700
- renderClassDiagram(container, parsed, layout, palette, isDark, undefined, {
701
- width: exportWidth,
702
- height: exportHeight,
703
- });
701
+ renderClassDiagram(
702
+ container,
703
+ parsed,
704
+ layout,
705
+ palette,
706
+ isDark,
707
+ undefined,
708
+ {
709
+ width: exportWidth,
710
+ height: exportHeight,
711
+ },
712
+ true, // legendActive for export
713
+ true // exportMode
714
+ );
704
715
  return extractExportSvg(container, theme);
705
716
  });
706
717
  }
package/src/cli.ts CHANGED
@@ -179,8 +179,8 @@ palette: catppuccin // override palette
179
179
  // This is a comment (only // syntax — not #)
180
180
  \`\`\`
181
181
 
182
- Inline colors on most elements: append \`(colorname)\` — e.g. \`North(red): 850\`, \`[Process(blue)]\`.
183
- Named colors: \`red\`, \`orange\`, \`yellow\`, \`green\`, \`blue\`, \`purple\`, \`teal\`, \`cyan\`, \`gray\`.
182
+ Inline colors on most elements: append the color name as the trailing token — e.g. \`North red 850\`, \`[Process] blue\`. To use a color word as a literal label, capitalize it (\`Red\` stays as the word Red).
183
+ Named colors: \`red\`, \`orange\`, \`yellow\`, \`green\`, \`blue\`, \`purple\`, \`teal\`, \`cyan\`, \`gray\`, \`black\`, \`white\`.
184
184
 
185
185
  ### sequence (most commonly used)
186
186
 
@@ -190,8 +190,8 @@ title: Auth Flow
190
190
 
191
191
  // Participants auto-inferred, or declare explicitly:
192
192
  User is an actor
193
- API is a service
194
193
  DB is a database
194
+ Cache is a cache
195
195
 
196
196
  User -Login-> API
197
197
  API -Find user-> DB
@@ -234,7 +234,7 @@ North: 850
234
234
  South: 620
235
235
 
236
236
  // line (multi-series)
237
- series: Sales(red), Costs(blue)
237
+ series: Sales red, Costs blue
238
238
  Q1: 100, 50
239
239
  Q2: 120, 55
240
240
 
@@ -295,7 +295,7 @@ API
295
295
  async A -> B: msg ❌ use A ~msg~> B
296
296
  A <- B ❌ left-pointing arrows removed — use B -> A
297
297
  parallel else ❌ not supported — use separate parallel blocks
298
- == Foo(#ff0000) == ❌ hex colors not supported — use named colors: == Foo(red) ==
298
+ == Foo #ff0000 == ❌ hex colors not supported — use named colors: == Foo red ==
299
299
  A -routes to /api-> B ❌ -> inside a label is ambiguous — rephrase the label
300
300
  end ❌ not needed — indentation closes blocks in sequence diagrams
301
301
  \`\`\`
@@ -427,8 +427,8 @@ bar, line, multi-line, area, pie, doughnut, radar, polar-area, bar-stacked, scat
427
427
 
428
428
  - First line: chart type keyword (e.g. \`sequence\`, \`flowchart\`, \`bar\`), optionally followed by a title (\`bar Revenue\`)
429
429
  - \`// comment\` — only \`//\` comments (not \`#\`)
430
- - \`(colorname)\` — inline colors on data series, tag values, kanban columns: \`Label(red) 100\`
431
- - \`series A(red), B(blue)\` — multi-series with colors
430
+ - Trailing color name — inline colors on data series, tag values, kanban columns: \`Label red 100\`
431
+ - \`series A red, B blue\` — multi-series with colors
432
432
 
433
433
  ## Rendering via CLI
434
434
 
@@ -1157,7 +1157,7 @@ async function main(): Promise<void> {
1157
1157
  }
1158
1158
 
1159
1159
  const existingDgmo = config.mcpServers?.['dgmo'];
1160
- if (existingDgmo && existingDgmo.command === 'dgmo-mcp') {
1160
+ if (existingDgmo?.command === 'dgmo-mcp') {
1161
1161
  console.log(`✓ dgmo MCP server already configured in ${configPath}`);
1162
1162
  } else {
1163
1163
  if (existingDgmo) {
package/src/completion.ts CHANGED
@@ -17,6 +17,11 @@ import { extractSymbols as extractInfraSymbols } from './infra/parser';
17
17
  import { extractSymbols as extractClassSymbols } from './class/parser';
18
18
  import { extractPertSymbols } from './pert/parser';
19
19
  import { parseFirstLine, ALL_CHART_TYPES } from './utils/parsing';
20
+ import { RECOGNIZED_COLOR_NAMES } from './colors';
21
+
22
+ const RECOGNIZED_COLOR_SET: ReadonlySet<string> = new Set(
23
+ RECOGNIZED_COLOR_NAMES
24
+ );
20
25
  // Read chart-type descriptions directly from the source-of-truth data
21
26
  // module instead of via dgmo-router.ts. dgmo-router imports every
22
27
  // parser, and the parsers (Class/ER/Infra/Pert/Flowchart) type-only
@@ -590,20 +595,7 @@ export const CHART_TYPES: ReadonlyArray<{ name: string; description: string }> =
590
595
  * C4_IS_A_RE).
591
596
  */
592
597
  export const ENTITY_TYPES = new Map<string, string[]>([
593
- [
594
- 'sequence',
595
- [
596
- 'service',
597
- 'database',
598
- 'actor',
599
- 'queue',
600
- 'cache',
601
- 'gateway',
602
- 'external',
603
- 'networking',
604
- 'frontend',
605
- ],
606
- ],
598
+ ['sequence', ['actor', 'database', 'queue', 'cache']],
607
599
  [
608
600
  'c4',
609
601
  ['person', 'system', 'container', 'component', 'external', 'database'],
@@ -968,10 +960,15 @@ export function extractTagDeclarations(docText: string): Map<string, string[]> {
968
960
  (raw[0] === ' ' || raw[0] === '\t')
969
961
  ) {
970
962
  if (trimmed && !trimmed.startsWith('//')) {
971
- // Strip color annotation: Frontend(blue) → Frontend
972
- const colorIdx = trimmed.indexOf('(');
963
+ // Strip trailing-token color (§1.5): `Frontend blue``Frontend`.
964
+ // Whitespace-split; if the last token is a recognized color word,
965
+ // drop it; otherwise the whole trimmed string is the value.
966
+ const lastSpaceIdx = trimmed.lastIndexOf(' ');
973
967
  const value =
974
- colorIdx > 0 ? trimmed.substring(0, colorIdx).trim() : trimmed;
968
+ lastSpaceIdx > 0 &&
969
+ RECOGNIZED_COLOR_SET.has(trimmed.substring(lastSpaceIdx + 1))
970
+ ? trimmed.substring(0, lastSpaceIdx).trim()
971
+ : trimmed;
975
972
  if (value) currentValues.push(value);
976
973
  }
977
974
  continue;
@@ -7,6 +7,7 @@ import {
7
7
  measureIndent,
8
8
  parseFirstLine,
9
9
  parsePipeMetadata,
10
+ peelTrailingColorName,
10
11
  tryParseSharedOption,
11
12
  } from '../utils/parsing';
12
13
  import type { ParsedCycle, CycleNode, CycleEdge } from './types';
@@ -102,7 +103,7 @@ export function parseCycle(content: string): ParsedCycle {
102
103
  // ── First line: chart type declaration ──
103
104
  if (!headerParsed) {
104
105
  const firstLineResult = parseFirstLine(trimmed);
105
- if (firstLineResult && firstLineResult.chartType === 'cycle') {
106
+ if (firstLineResult?.chartType === 'cycle') {
106
107
  result.title = firstLineResult.title ?? '';
107
108
  result.titleLineNumber = lineNum;
108
109
  headerParsed = true;
@@ -149,6 +150,7 @@ export function parseCycle(content: string): ParsedCycle {
149
150
  }
150
151
 
151
152
  // Parse node: Label | color: blue, span: 3, description: text
153
+ // OR shortcut form (color only): `Label color` (universal §1.5)
152
154
  const pipeIdx = trimmed.indexOf('|');
153
155
  let label: string;
154
156
  let metadata: Record<string, string> = {};
@@ -166,6 +168,18 @@ export function parseCycle(content: string): ParsedCycle {
166
168
  continue;
167
169
  }
168
170
 
171
+ // Universal trailing-token shortcut: if no `| color:` was set explicitly
172
+ // and the label ends in a recognized color word, treat that word as the
173
+ // color and strip it from the label.
174
+ if (!metadata['color']) {
175
+ const { label: stripped, colorName: shortcutColor } =
176
+ peelTrailingColorName(label);
177
+ if (shortcutColor) {
178
+ metadata['color'] = shortcutColor;
179
+ label = stripped;
180
+ }
181
+ }
182
+
169
183
  // Extract known keys from metadata
170
184
  const color = metadata['color'];
171
185
  const spanStr = metadata['span'];
@@ -46,6 +46,7 @@ export interface CycleRenderOptions {
46
46
  controlsExpanded?: boolean;
47
47
  onToggleDescriptions?: (active: boolean) => void;
48
48
  onToggleControlsExpand?: () => void;
49
+ exportMode?: boolean;
49
50
  }
50
51
 
51
52
  /**
@@ -144,7 +145,7 @@ export function renderCycle(
144
145
  const legendConfig: LegendConfig = {
145
146
  groups: [],
146
147
  position: { placement: 'top-center', titleRelation: 'below-title' },
147
- mode: 'fixed',
148
+ mode: renderOptions?.exportMode ? 'export' : 'preview',
148
149
  controlsGroup,
149
150
  };
150
151
  const legendState: LegendState = {
@@ -519,7 +520,8 @@ export function renderCycleForExport(
519
520
  palette: PaletteColors,
520
521
  isDark: boolean,
521
522
  exportDims?: D3ExportDimensions,
522
- viewState?: CompactViewState
523
+ viewState?: CompactViewState,
524
+ exportMode?: boolean
523
525
  ): void {
524
526
  renderCycle(
525
527
  container,
@@ -528,7 +530,8 @@ export function renderCycleForExport(
528
530
  isDark,
529
531
  undefined,
530
532
  exportDims,
531
- viewState
533
+ viewState,
534
+ { exportMode }
532
535
  );
533
536
  }
534
537