@diagrammo/dgmo 0.8.2 → 0.8.4

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 (120) hide show
  1. package/.claude/commands/dgmo-diagram-this.md +60 -0
  2. package/.claude/commands/dgmo-document-project.md +128 -0
  3. package/.claude/commands/dgmo.md +185 -50
  4. package/.cursorrules +32 -37
  5. package/.github/copilot-instructions.md +35 -44
  6. package/.windsurfrules +32 -37
  7. package/README.md +4 -4
  8. package/dist/cli.cjs +189 -194
  9. package/dist/editor.cjs +336 -0
  10. package/dist/editor.cjs.map +1 -0
  11. package/dist/editor.d.cts +27 -0
  12. package/dist/editor.d.ts +27 -0
  13. package/dist/editor.js +305 -0
  14. package/dist/editor.js.map +1 -0
  15. package/dist/index.cjs +3699 -1564
  16. package/dist/index.cjs.map +1 -1
  17. package/dist/index.d.cts +7 -6
  18. package/dist/index.d.ts +7 -6
  19. package/dist/index.js +3699 -1564
  20. package/dist/index.js.map +1 -1
  21. package/docs/language-reference.md +822 -1060
  22. package/gallery/fixtures/arc.dgmo +18 -0
  23. package/gallery/fixtures/area.dgmo +19 -0
  24. package/gallery/fixtures/bar-stacked.dgmo +10 -0
  25. package/gallery/fixtures/bar.dgmo +10 -0
  26. package/gallery/fixtures/c4-full.dgmo +52 -0
  27. package/gallery/fixtures/c4.dgmo +17 -0
  28. package/gallery/fixtures/chord.dgmo +12 -0
  29. package/gallery/fixtures/class-basic.dgmo +14 -0
  30. package/gallery/fixtures/class-full.dgmo +43 -0
  31. package/gallery/fixtures/doughnut.dgmo +8 -0
  32. package/gallery/fixtures/flowchart-basic.dgmo +3 -0
  33. package/gallery/fixtures/flowchart-colors.dgmo +5 -0
  34. package/gallery/fixtures/flowchart-complex.dgmo +17 -0
  35. package/gallery/fixtures/flowchart-decision.dgmo +5 -0
  36. package/gallery/fixtures/flowchart-full.dgmo +13 -0
  37. package/gallery/fixtures/flowchart-groups.dgmo +10 -0
  38. package/gallery/fixtures/flowchart-loop.dgmo +7 -0
  39. package/gallery/fixtures/flowchart-nested.dgmo +7 -0
  40. package/gallery/fixtures/flowchart-shapes.dgmo +5 -0
  41. package/gallery/fixtures/function.dgmo +8 -0
  42. package/gallery/fixtures/funnel.dgmo +7 -0
  43. package/gallery/fixtures/gantt-full.dgmo +49 -0
  44. package/gallery/fixtures/gantt.dgmo +42 -0
  45. package/gallery/fixtures/heatmap.dgmo +8 -0
  46. package/gallery/fixtures/infra-full.dgmo +78 -0
  47. package/gallery/fixtures/infra-overload.dgmo +25 -0
  48. package/gallery/fixtures/infra.dgmo +47 -0
  49. package/gallery/fixtures/initiative-status-full.dgmo +46 -0
  50. package/gallery/fixtures/initiative-status-phases.dgmo +29 -0
  51. package/gallery/fixtures/initiative-status.dgmo +9 -0
  52. package/gallery/fixtures/line.dgmo +19 -0
  53. package/gallery/fixtures/multi-line.dgmo +11 -0
  54. package/gallery/fixtures/org-basic.dgmo +16 -0
  55. package/gallery/fixtures/org-full.dgmo +69 -0
  56. package/gallery/fixtures/org-teams.dgmo +25 -0
  57. package/gallery/fixtures/pie.dgmo +9 -0
  58. package/gallery/fixtures/polar-area.dgmo +8 -0
  59. package/gallery/fixtures/quadrant.dgmo +18 -0
  60. package/gallery/fixtures/radar.dgmo +8 -0
  61. package/gallery/fixtures/sankey.dgmo +31 -0
  62. package/gallery/fixtures/scatter.dgmo +21 -0
  63. package/gallery/fixtures/sequence-tags-protocols.dgmo +45 -0
  64. package/gallery/fixtures/sequence-tags.dgmo +41 -0
  65. package/gallery/fixtures/sequence.dgmo +35 -0
  66. package/gallery/fixtures/sitemap-basic.dgmo +12 -0
  67. package/gallery/fixtures/sitemap-full.dgmo +156 -0
  68. package/gallery/fixtures/slope.dgmo +8 -0
  69. package/gallery/fixtures/spr-eras.dgmo +62 -0
  70. package/gallery/fixtures/state.dgmo +30 -0
  71. package/gallery/fixtures/timeline-intraday.dgmo +14 -0
  72. package/gallery/fixtures/timeline.dgmo +32 -0
  73. package/gallery/fixtures/venn.dgmo +10 -0
  74. package/gallery/fixtures/wordcloud.dgmo +24 -0
  75. package/package.json +51 -2
  76. package/src/c4/layout.ts +372 -90
  77. package/src/c4/parser.ts +113 -62
  78. package/src/chart.ts +149 -64
  79. package/src/class/parser.ts +84 -28
  80. package/src/class/renderer.ts +2 -2
  81. package/src/cli.ts +179 -77
  82. package/src/completion.ts +381 -182
  83. package/src/d3.ts +1026 -428
  84. package/src/dgmo-mermaid.ts +16 -13
  85. package/src/dgmo-router.ts +70 -24
  86. package/src/echarts.ts +682 -169
  87. package/src/editor/dgmo.grammar +69 -0
  88. package/src/editor/dgmo.grammar.d.ts +2 -0
  89. package/src/editor/dgmo.grammar.js +18 -0
  90. package/src/editor/dgmo.grammar.terms.d.ts +5 -0
  91. package/src/editor/dgmo.grammar.terms.js +35 -0
  92. package/src/editor/highlight.ts +36 -0
  93. package/src/editor/index.ts +28 -0
  94. package/src/editor/keywords.ts +220 -0
  95. package/src/editor/tokens.ts +30 -0
  96. package/src/er/parser.ts +55 -29
  97. package/src/er/renderer.ts +112 -53
  98. package/src/gantt/calculator.ts +91 -29
  99. package/src/gantt/parser.ts +291 -97
  100. package/src/gantt/renderer.ts +1120 -350
  101. package/src/graph/flowchart-parser.ts +48 -75
  102. package/src/graph/state-parser.ts +54 -27
  103. package/src/infra/parser.ts +161 -177
  104. package/src/infra/renderer.ts +723 -271
  105. package/src/infra/types.ts +0 -1
  106. package/src/initiative-status/parser.ts +144 -56
  107. package/src/kanban/parser.ts +27 -19
  108. package/src/org/layout.ts +111 -44
  109. package/src/org/parser.ts +71 -27
  110. package/src/org/resolver.ts +3 -3
  111. package/src/palettes/index.ts +3 -2
  112. package/src/render.ts +1 -2
  113. package/src/sequence/parser.ts +209 -100
  114. package/src/sitemap/parser.ts +73 -44
  115. package/src/utils/arrows.ts +2 -22
  116. package/src/utils/duration.ts +39 -21
  117. package/src/utils/legend-constants.ts +0 -2
  118. package/src/utils/parsing.ts +82 -72
  119. package/src/utils/tag-groups.ts +4 -41
  120. package/src/infra/serialize.ts +0 -67
@@ -4,22 +4,12 @@ import { makeDgmoError, formatDgmoError, suggest } from '../diagnostics';
4
4
  import {
5
5
  measureIndent,
6
6
  extractColor,
7
- normalizeDirection,
8
7
  inferArrowColor,
9
8
  parseFirstLine,
10
9
  OPTION_NOCOLON_RE,
11
- GROUP_HASH_RE,
12
- DOUBLE_HASH_RE,
13
10
  ALL_CHART_TYPES,
14
11
  } from '../utils/parsing';
15
- import type {
16
- ParsedGraph,
17
- GraphNode,
18
- GraphEdge,
19
- GraphGroup,
20
- GraphShape,
21
- GraphDirection,
22
- } from './types';
12
+ import type { ParsedGraph, GraphNode, GraphEdge, GraphShape } from './types';
23
13
 
24
14
  // ============================================================
25
15
  // Helpers
@@ -40,10 +30,7 @@ interface NodeRef {
40
30
  * Try to parse a node reference from a text fragment.
41
31
  * Order matters: subroutine & document before process.
42
32
  */
43
- function parseNodeRef(
44
- text: string,
45
- palette?: PaletteColors
46
- ): NodeRef | null {
33
+ function parseNodeRef(text: string, palette?: PaletteColors): NodeRef | null {
47
34
  const t = text.trim();
48
35
  if (!t) return null;
49
36
 
@@ -51,7 +38,12 @@ function parseNodeRef(
51
38
  let m = t.match(/^\[\[([^\]]+)\]\]$/);
52
39
  if (m) {
53
40
  const { label, color } = extractColor(m[1].trim(), palette);
54
- return { id: nodeId('subroutine', label), label, shape: 'subroutine', color };
41
+ return {
42
+ id: nodeId('subroutine', label),
43
+ label,
44
+ shape: 'subroutine',
45
+ color,
46
+ };
55
47
  }
56
48
 
57
49
  // Document: [Label~]
@@ -103,7 +95,12 @@ function splitArrows(line: string): string[] {
103
95
  const segments: string[] = [];
104
96
  let lastIndex = 0;
105
97
  // Simpler approach: find all `->` positions, then determine if there's a label prefix
106
- const arrowPositions: { start: number; end: number; label?: string; color?: string }[] = [];
98
+ const arrowPositions: {
99
+ start: number;
100
+ end: number;
101
+ label?: string;
102
+ color?: string;
103
+ }[] = [];
107
104
 
108
105
  // Find all -> occurrences
109
106
  let searchFrom = 0;
@@ -128,10 +125,14 @@ function splitArrows(line: string): string[] {
128
125
  scanBack--;
129
126
  }
130
127
  // Check if this `-` could be the start of the arrow
131
- if (line[scanBack] === '-' && (scanBack === 0 || /\s/.test(line[scanBack - 1]))) {
128
+ if (
129
+ line[scanBack] === '-' &&
130
+ (scanBack === 0 || /\s/.test(line[scanBack - 1]))
131
+ ) {
132
132
  // Content between opening `-` and `->` (strip trailing `-` that is part of `->`)
133
133
  let arrowContent = line.substring(scanBack + 1, idx);
134
- if (arrowContent.endsWith('-')) arrowContent = arrowContent.slice(0, -1);
134
+ if (arrowContent.endsWith('-'))
135
+ arrowContent = arrowContent.slice(0, -1);
135
136
  // Parse label and color from arrow content
136
137
  const colorMatch = arrowContent.match(/\(([^)]+)\)\s*$/);
137
138
  if (colorMatch) {
@@ -163,7 +164,8 @@ function splitArrows(line: string): string[] {
163
164
  }
164
165
  // Arrow marker
165
166
  let arrowToken = '->';
166
- if (arrow.label && arrow.color) arrowToken = `-${arrow.label}(${arrow.color})->`;
167
+ if (arrow.label && arrow.color)
168
+ arrowToken = `-${arrow.label}(${arrow.color})->`;
167
169
  else if (arrow.label) arrowToken = `-${arrow.label}->`;
168
170
  else if (arrow.color) arrowToken = `-(${arrow.color})->`;
169
171
  segments.push(arrowToken);
@@ -194,7 +196,9 @@ function parseArrowToken(token: string, palette?: PaletteColors): ArrowInfo {
194
196
  const m = token.match(/^-(.+?)(?:\(([^)]+)\))?->$/);
195
197
  if (m) {
196
198
  const label = m[1]?.trim() || undefined;
197
- let color = m[2] ? resolveColor(m[2].trim(), palette) ?? undefined : undefined;
199
+ let color = m[2]
200
+ ? (resolveColor(m[2].trim(), palette) ?? undefined)
201
+ : undefined;
198
202
  if (label && !color) {
199
203
  color = inferArrowColor(label);
200
204
  }
@@ -238,11 +242,6 @@ export function parseFlowchart(
238
242
  let contentStarted = false;
239
243
  let firstLineParsed = false;
240
244
 
241
- // Group support
242
- let currentGroup: GraphGroup | null = null;
243
- let groupIndent = -1;
244
- const groups: GraphGroup[] = [];
245
-
246
245
  function getOrCreateNode(ref: NodeRef, lineNumber: number): GraphNode {
247
246
  const existing = nodeMap.get(ref.id);
248
247
  if (existing) return existing;
@@ -253,15 +252,10 @@ export function parseFlowchart(
253
252
  shape: ref.shape,
254
253
  lineNumber,
255
254
  ...(ref.color && { color: ref.color }),
256
- ...(currentGroup && { group: currentGroup.id }),
257
255
  };
258
256
  nodeMap.set(ref.id, node);
259
257
  result.nodes.push(node);
260
258
 
261
- if (currentGroup && !currentGroup.nodeIds.includes(ref.id)) {
262
- currentGroup.nodeIds.push(ref.id);
263
- }
264
-
265
259
  return node;
266
260
  }
267
261
 
@@ -418,45 +412,19 @@ export function parseFlowchart(
418
412
  }
419
413
  }
420
414
 
421
- // ## group headings — emit helpful error
422
- if (DOUBLE_HASH_RE.test(trimmed)) {
423
- result.diagnostics.push(
424
- makeDgmoError(lineNumber, 'Use `#` for groups \u2014 nesting is done with indentation.', 'error')
425
- );
426
- continue;
427
- }
428
-
429
- // # GroupName — alternate group notation
430
- const hashGroupMatch = trimmed.match(GROUP_HASH_RE);
431
- if (hashGroupMatch) {
432
- const { label, color } = extractColor(hashGroupMatch[1].trim(), palette);
433
- currentGroup = {
434
- id: `group:${label.toLowerCase()}`,
435
- label,
436
- nodeIds: [],
437
- lineNumber,
438
- ...(color && { color }),
439
- };
440
- groupIndent = indent;
441
- groups.push(currentGroup);
442
- continue;
443
- }
444
-
445
415
  // Options (space-separated, before content)
446
416
  if (!contentStarted) {
417
+ // Bare boolean: direction-lr
418
+ if (/^direction-lr$/i.test(trimmed)) {
419
+ result.direction = 'LR';
420
+ continue;
421
+ }
422
+
447
423
  const optMatch = trimmed.match(OPTION_NOCOLON_RE);
448
424
  if (optMatch && !trimmed.includes('->')) {
449
425
  const key = optMatch[1].toLowerCase();
450
426
  const value = optMatch[2].trim();
451
427
 
452
- if (key === 'direction' || key === 'orientation') {
453
- const dir = normalizeDirection(value);
454
- if (dir) {
455
- result.direction = dir;
456
- }
457
- continue;
458
- }
459
-
460
428
  // Boolean: no-color = color off
461
429
  if (key === 'no-color') {
462
430
  result.options['color'] = 'off';
@@ -469,21 +437,16 @@ export function parseFlowchart(
469
437
  }
470
438
  }
471
439
 
472
- // Close current group when indent returns to or below the group level
473
- if (currentGroup && indent <= groupIndent) {
474
- currentGroup = null;
475
- groupIndent = -1;
476
- }
477
-
478
440
  // Content line (nodes and edges)
479
441
  processContentLine(trimmed, lineNumber, indent);
480
442
  }
481
443
 
482
- if (groups.length > 0) result.groups = groups;
483
-
484
444
  // Validation: no nodes found
485
445
  if (result.nodes.length === 0 && !result.error) {
486
- const diag = makeDgmoError(1, 'No nodes found. Add flowchart content with shape syntax like [Process] or (Start).');
446
+ const diag = makeDgmoError(
447
+ 1,
448
+ 'No nodes found. Add flowchart content with shape syntax like [Process] or (Start).'
449
+ );
487
450
  result.diagnostics.push(diag);
488
451
  result.error = formatDgmoError(diag);
489
452
  }
@@ -497,7 +460,13 @@ export function parseFlowchart(
497
460
  }
498
461
  for (const node of result.nodes) {
499
462
  if (!connectedIds.has(node.id)) {
500
- result.diagnostics.push(makeDgmoError(node.lineNumber, `Node "${node.label}" is not connected to any other node`, 'warning'));
463
+ result.diagnostics.push(
464
+ makeDgmoError(
465
+ node.lineNumber,
466
+ `Node "${node.label}" is not connected to any other node`,
467
+ 'warning'
468
+ )
469
+ );
501
470
  }
502
471
  }
503
472
  }
@@ -533,7 +502,7 @@ export function looksLikeFlowchart(content: string): boolean {
533
502
  // Look for patterns like `[X] ->` or `-> [X]` or `(X) ->` etc.
534
503
  const shapeNearArrow =
535
504
  /[\])][ \t]*-.*->/.test(content) || // shape ] or ) followed by arrow
536
- /->[ \t]*[\[(<\/]/.test(content); // arrow followed by shape opener
505
+ /->[ \t]*[[(</]/.test(content); // arrow followed by shape opener
537
506
 
538
507
  return shapeNearArrow;
539
508
  }
@@ -557,7 +526,11 @@ export function extractSymbols(docText: string): DiagramSymbols {
557
526
  for (const rawLine of docText.split('\n')) {
558
527
  const line = rawLine.trim();
559
528
  // Skip old-style colon metadata and new-style space-separated options
560
- if (inMetadata && (/^[a-z-]+\s*:/i.test(line) || /^[a-z-]+\s+\S/i.test(line))) continue;
529
+ if (
530
+ inMetadata &&
531
+ (/^[a-z-]+\s*:/i.test(line) || /^[a-z-]+\s+\S/i.test(line))
532
+ )
533
+ continue;
561
534
  inMetadata = false;
562
535
  if (line.length === 0 || /^\s/.test(rawLine)) continue;
563
536
  const m = NODE_ID_RE.exec(line);
@@ -4,17 +4,11 @@ import { makeDgmoError, formatDgmoError, suggest } from '../diagnostics';
4
4
  import {
5
5
  measureIndent,
6
6
  extractColor,
7
- normalizeDirection,
8
7
  parseFirstLine,
9
8
  OPTION_NOCOLON_RE,
10
9
  ALL_CHART_TYPES,
11
10
  } from '../utils/parsing';
12
- import type {
13
- ParsedGraph,
14
- GraphNode,
15
- GraphGroup,
16
- GraphDirection,
17
- } from './types';
11
+ import type { ParsedGraph, GraphNode, GraphGroup } from './types';
18
12
 
19
13
  // ============================================================
20
14
  // Constants
@@ -37,7 +31,12 @@ const GROUP_BRACKET_RE = /^\[([^\]]+)\](?:\(([^)]+)\))?\s*$/;
37
31
  */
38
32
  function splitArrows(line: string): string[] {
39
33
  const segments: string[] = [];
40
- const arrowPositions: { start: number; end: number; label?: string; color?: string }[] = [];
34
+ const arrowPositions: {
35
+ start: number;
36
+ end: number;
37
+ label?: string;
38
+ color?: string;
39
+ }[] = [];
41
40
 
42
41
  let searchFrom = 0;
43
42
  while (searchFrom < line.length) {
@@ -53,9 +52,13 @@ function splitArrows(line: string): string[] {
53
52
  while (scanBack > 0 && line[scanBack] !== '-') {
54
53
  scanBack--;
55
54
  }
56
- if (line[scanBack] === '-' && (scanBack === 0 || /\s/.test(line[scanBack - 1]))) {
55
+ if (
56
+ line[scanBack] === '-' &&
57
+ (scanBack === 0 || /\s/.test(line[scanBack - 1]))
58
+ ) {
57
59
  let arrowContent = line.substring(scanBack + 1, idx);
58
- if (arrowContent.endsWith('-')) arrowContent = arrowContent.slice(0, -1);
60
+ if (arrowContent.endsWith('-'))
61
+ arrowContent = arrowContent.slice(0, -1);
59
62
  const colorMatch = arrowContent.match(/\(([^)]+)\)\s*$/);
60
63
  if (colorMatch) {
61
64
  color = colorMatch[1].trim();
@@ -82,7 +85,8 @@ function splitArrows(line: string): string[] {
82
85
  if (beforeText || i === 0) segments.push(beforeText);
83
86
 
84
87
  let arrowToken = '->';
85
- if (arrow.label && arrow.color) arrowToken = `-${arrow.label}(${arrow.color})->`;
88
+ if (arrow.label && arrow.color)
89
+ arrowToken = `-${arrow.label}(${arrow.color})->`;
86
90
  else if (arrow.label) arrowToken = `-${arrow.label}->`;
87
91
  else if (arrow.color) arrowToken = `-(${arrow.color})->`;
88
92
  segments.push(arrowToken);
@@ -102,11 +106,14 @@ interface ArrowInfo {
102
106
  function parseArrowToken(token: string, palette?: PaletteColors): ArrowInfo {
103
107
  if (token === '->') return {};
104
108
  const colorOnly = token.match(/^-\(([^)]+)\)->$/);
105
- if (colorOnly) return { color: resolveColor(colorOnly[1].trim(), palette) ?? undefined };
109
+ if (colorOnly)
110
+ return { color: resolveColor(colorOnly[1].trim(), palette) ?? undefined };
106
111
  const m = token.match(/^-(.+?)(?:\(([^)]+)\))?->$/);
107
112
  if (m) {
108
113
  const label = m[1]?.trim() || undefined;
109
- const color = m[2] ? resolveColor(m[2].trim(), palette) ?? undefined : undefined;
114
+ const color = m[2]
115
+ ? (resolveColor(m[2].trim(), palette) ?? undefined)
116
+ : undefined;
110
117
  return { label, color };
111
118
  }
112
119
  return {};
@@ -123,13 +130,20 @@ interface NodeRef {
123
130
  color?: string;
124
131
  }
125
132
 
126
- function parseStateNodeRef(text: string, palette?: PaletteColors): NodeRef | null {
133
+ function parseStateNodeRef(
134
+ text: string,
135
+ palette?: PaletteColors
136
+ ): NodeRef | null {
127
137
  const t = text.trim();
128
138
  if (!t) return null;
129
139
 
130
140
  // Pseudostate: [*]
131
141
  if (t === '[*]') {
132
- return { id: PSEUDOSTATE_ID, label: PSEUDOSTATE_LABEL, shape: 'pseudostate' };
142
+ return {
143
+ id: PSEUDOSTATE_ID,
144
+ label: PSEUDOSTATE_LABEL,
145
+ shape: 'pseudostate',
146
+ };
133
147
  }
134
148
 
135
149
  // State: bare text with optional (color) suffix
@@ -154,7 +168,7 @@ export function parseState(
154
168
  const lines = content.split('\n');
155
169
  const result: ParsedGraph = {
156
170
  type: 'state',
157
- direction: 'TB',
171
+ direction: 'LR',
158
172
  nodes: [],
159
173
  edges: [],
160
174
  options: {},
@@ -268,19 +282,17 @@ export function parseState(
268
282
 
269
283
  // Options (space-separated, before content)
270
284
  if (!contentStarted) {
285
+ // Bare boolean: direction-tb
286
+ if (/^direction-tb$/i.test(trimmed)) {
287
+ result.direction = 'TB';
288
+ continue;
289
+ }
290
+
271
291
  const optMatch = trimmed.match(OPTION_NOCOLON_RE);
272
292
  if (optMatch && !trimmed.includes('->')) {
273
293
  const key = optMatch[1].toLowerCase();
274
294
  const value = optMatch[2].trim();
275
295
 
276
- if (key === 'direction' || key === 'orientation') {
277
- const dir = normalizeDirection(value);
278
- if (dir) {
279
- result.direction = dir;
280
- }
281
- continue;
282
- }
283
-
284
296
  // Boolean: no-color = color off
285
297
  if (key === 'no-color') {
286
298
  result.options['color'] = 'off';
@@ -353,7 +365,13 @@ export function parseState(
353
365
  // Use explicit source if available, else implicit from indent
354
366
  const sourceId = lastNodeId ?? implicitSourceId;
355
367
  if (sourceId) {
356
- addEdge(sourceId, node.id, lineNumber, pendingArrow.label, pendingArrow.color);
368
+ addEdge(
369
+ sourceId,
370
+ node.id,
371
+ lineNumber,
372
+ pendingArrow.label,
373
+ pendingArrow.color
374
+ );
357
375
  }
358
376
  pendingArrow = null;
359
377
  }
@@ -370,7 +388,10 @@ export function parseState(
370
388
 
371
389
  // Validation: no nodes found
372
390
  if (result.nodes.length === 0 && !result.error) {
373
- const diag = makeDgmoError(1, 'No states found. Add state transitions like: Idle -> Active');
391
+ const diag = makeDgmoError(
392
+ 1,
393
+ 'No states found. Add state transitions like: Idle -> Active'
394
+ );
374
395
  result.diagnostics.push(diag);
375
396
  result.error = formatDgmoError(diag);
376
397
  }
@@ -384,7 +405,13 @@ export function parseState(
384
405
  }
385
406
  for (const node of result.nodes) {
386
407
  if (!connectedIds.has(node.id)) {
387
- result.diagnostics.push(makeDgmoError(node.lineNumber, `State "${node.label}" is not connected to any other state`, 'warning'));
408
+ result.diagnostics.push(
409
+ makeDgmoError(
410
+ node.lineNumber,
411
+ `State "${node.label}" is not connected to any other state`,
412
+ 'warning'
413
+ )
414
+ );
388
415
  }
389
416
  }
390
417
  }