@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
@@ -24,6 +24,7 @@ import {
24
24
  measureIndent,
25
25
  parseFirstLine,
26
26
  parsePipeMetadata,
27
+ peelTrailingColorName,
27
28
  OPTION_NOCOLON_RE,
28
29
  tryParseSharedOption,
29
30
  } from '../utils/parsing';
@@ -82,10 +83,10 @@ const KNOWN_BOOLEANS = new Set<string>([
82
83
  ...Object.keys(VARIANT_LOCK_DIRECTIVES),
83
84
  ]);
84
85
 
85
- // Allow optional trailing `| key: value, ...` after the bracket,
86
- // e.g. `[Voyage] | color: blue` matches the modern dgmo idiom
87
- // (cycle / pyramid / ring / journey-map / boxes-and-lines).
88
- const PHASE_RE = /^\[(.+?)\]\s*(?:\|\s*(.+))?\s*$/;
86
+ // Allow optional trailing color shortcut and/or pipe metadata after the
87
+ // bracket: `[Voyage] blue | desc: …` (per §1.5 universal trailing-token
88
+ // rule + the modern cycle/pyramid/ring/journey-map/b&l idiom).
89
+ const PHASE_RE = /^\[(.+?)\](?:\s+(\S+))?(?:\s*\|\s*(.+))?\s*$/;
89
90
  const ROLE_ASSIGNMENT_RE = /^([^:]+):\s*(.*)$/;
90
91
 
91
92
  /**
@@ -177,7 +178,7 @@ export function parseRaci(
177
178
  result.diagnostics.push(makeDgmoError(line, message, 'error', code));
178
179
  };
179
180
 
180
- if (!content || !content.trim()) {
181
+ if (!content?.trim()) {
181
182
  return fail(0, 'No content provided');
182
183
  }
183
184
 
@@ -382,10 +383,10 @@ export function parseRaci(
382
383
  // Strip a possible trailing comma (user habit tolerance,
383
384
  // matches `collectIndentedValues`).
384
385
  const stripped = nextTrim.replace(/,\s*$/, '');
385
- // Optional pipe metadata: `Cap | color: blue` — matches
386
- // every modern chart-type's per-element styling form.
386
+ // Optional pipe metadata: `Cap | color: blue` — long form.
387
+ // Optional trailing-token shortcut: `Cap blue` — short form (§1.5).
387
388
  const segments = stripped.split('|').map((s) => s.trim());
388
- const roleLabel = segments[0] ?? '';
389
+ let roleLabel = segments[0] ?? '';
389
390
  let roleColor: string | undefined;
390
391
  if (segments.length > 1) {
391
392
  const meta = parsePipeMetadata(segments);
@@ -398,6 +399,20 @@ export function parseRaci(
398
399
  );
399
400
  }
400
401
  }
402
+ // Apply shortcut only when pipe metadata didn't already set color.
403
+ if (!roleColor) {
404
+ const { label: stripLabel, colorName: shortcutColor } =
405
+ peelTrailingColorName(roleLabel);
406
+ if (shortcutColor) {
407
+ roleColor = resolveColorWithDiagnostic(
408
+ shortcutColor,
409
+ j + 1,
410
+ result.diagnostics,
411
+ palette
412
+ );
413
+ roleLabel = stripLabel;
414
+ }
415
+ }
401
416
  if (roleLabel) getOrAddRole(roleLabel, j + 1, roleColor);
402
417
  }
403
418
  i = j - 1; // outer loop's i++ lands on the first non-block line
@@ -469,10 +484,13 @@ export function parseRaci(
469
484
  errorAt(lineNumber, 'Phase label is empty.');
470
485
  continue;
471
486
  }
472
- // Optional pipe metadata: `[Voyage] | color: blue`.
487
+ // PHASE_RE captures: 1=label, 2=optional trailing-token color, 3=pipe meta.
488
+ // Long pipe form (`[Voyage] | color: blue`) wins over the shortcut.
473
489
  let phaseColor: string | undefined;
474
- if (phaseMatch[2]) {
475
- const meta = parsePipeMetadata(['', phaseMatch[2]]);
490
+ const trailingToken = phaseMatch[2];
491
+ const pipeMeta = phaseMatch[3];
492
+ if (pipeMeta) {
493
+ const meta = parsePipeMetadata(['', pipeMeta]);
476
494
  if (meta['color']) {
477
495
  phaseColor = resolveColorWithDiagnostic(
478
496
  meta['color'],
@@ -482,6 +500,18 @@ export function parseRaci(
482
500
  );
483
501
  }
484
502
  }
503
+ if (!phaseColor && trailingToken) {
504
+ // Trailing token must be a recognized color word, or it's a parse error.
505
+ const { colorName } = peelTrailingColorName(`x ${trailingToken}`);
506
+ if (colorName) {
507
+ phaseColor = resolveColorWithDiagnostic(
508
+ colorName,
509
+ lineNumber,
510
+ result.diagnostics,
511
+ palette
512
+ );
513
+ }
514
+ }
485
515
  currentPhase = {
486
516
  id: normalizeName(display),
487
517
  displayName: display,
@@ -594,7 +624,7 @@ export function parseRaci(
594
624
  // detect by whether it was declared before `bodyStarted`.
595
625
  // We piggyback the `declaredLine` we recorded.
596
626
  const entry = roleStore.get(roleId);
597
- if (entry && entry.declaredLine === lineNumber) {
627
+ if (entry?.declaredLine === lineNumber) {
598
628
  const candidates = result.roleDisplayNames.filter(
599
629
  (n) => n !== entry.displayName
600
630
  );
@@ -602,7 +602,8 @@ export function renderRaci(
602
602
  parsed.roles.forEach((roleId, i) => {
603
603
  const cx = roleX(i) + COLUMN_INSET;
604
604
  const cw = roleColW - 2 * COLUMN_INSET;
605
- // Per-role color from `Cap(blue)` syntax. When the user provides
605
+ // Per-role color from `Cap blue` trailing-token (or `Cap | color: blue`)
606
+ // syntax. When the user provides
606
607
  // one, it wins; otherwise rotate through marker-safe accents so
607
608
  // each column has a subtle visual identity instead of every column
608
609
  // reading as the same neutral gray.
package/src/raci/types.ts CHANGED
@@ -67,9 +67,10 @@ export interface ParsedRaci {
67
67
  /** Display name for each role (parallel to `roles`). */
68
68
  roleDisplayNames: string[];
69
69
  /**
70
- * Optional per-role palette color from `Cap(blue)` suffix in the
71
- * roles block. Parallel to `roles`; entries default to `undefined`
72
- * (renderer falls back to the neutral column tint).
70
+ * Optional per-role palette color from the `Cap blue` trailing-token
71
+ * suffix in the roles block (or the long pipe form `Cap | color: blue`).
72
+ * Parallel to `roles`; entries default to `undefined` (renderer falls
73
+ * back to the neutral column tint).
73
74
  */
74
75
  roleColors: Array<string | undefined>;
75
76
  phases: RaciPhase[];
@@ -8,6 +8,7 @@ import {
8
8
  measureIndent,
9
9
  parseFirstLine,
10
10
  parsePipeMetadata,
11
+ peelTrailingColorName,
11
12
  tryParseSharedOption,
12
13
  PIPE_KEY_VALUE_PREFIX_RE,
13
14
  PIPE_LIKELY_STRUCTURED_TAIL_RE,
@@ -73,7 +74,7 @@ export function parseRing(content: string): ParsedRing {
73
74
  // ── First line: chart type declaration ──
74
75
  if (!headerParsed) {
75
76
  const firstLineResult = parseFirstLine(trimmed);
76
- if (firstLineResult && firstLineResult.chartType === 'ring') {
77
+ if (firstLineResult?.chartType === 'ring') {
77
78
  result.title = firstLineResult.title ?? '';
78
79
  result.titleLineNumber = lineNum;
79
80
  headerParsed = true;
@@ -175,6 +176,17 @@ export function parseRing(content: string): ParsedRing {
175
176
  continue;
176
177
  }
177
178
 
179
+ // Universal trailing-token shortcut: `Label color` equivalent to
180
+ // `Label | color: <name>` when color is not already set (§1.5).
181
+ if (!color) {
182
+ const { label: stripped, colorName: shortcutColor } =
183
+ peelTrailingColorName(label);
184
+ if (shortcutColor) {
185
+ color = shortcutColor;
186
+ label = stripped;
187
+ }
188
+ }
189
+
178
190
  currentLayer = {
179
191
  label,
180
192
  lineNumber: lineNum,
@@ -11,6 +11,7 @@ import {
11
11
  NAME_DIAGNOSTIC_CODES,
12
12
  nameMergedMessage,
13
13
  akaRemovedMessage,
14
+ participantTypeRemovedMessage,
14
15
  } from '../diagnostics';
15
16
  import { normalizeName, displayName } from '../utils/name-normalize';
16
17
  import { parseArrow, parseInArrowLabel } from '../utils/arrows';
@@ -60,29 +61,40 @@ const KNOWN_SEQ_BOOLEANS = new Set(['activations', 'solid-fill', 'no-title']);
60
61
 
61
62
  /**
62
63
  * Participant types that can be declared via "Name is a type" syntax.
64
+ *
65
+ * The 0.16.0 trim retained only the types whose shapes carry semantic
66
+ * weight at a glance: stick figure (actor), cylinder (database),
67
+ * dashed cylinder (cache), horizontal pipe (queue), plus the default
68
+ * rectangle. The legacy `service`/`frontend`/`networking`/`gateway`/
69
+ * `external` keywords are rejected at parse time via
70
+ * `E_PARTICIPANT_TYPE_REMOVED`.
63
71
  */
64
72
  export type ParticipantType =
65
73
  | 'default'
66
- | 'service'
67
74
  | 'database'
68
75
  | 'actor'
69
76
  | 'queue'
70
- | 'cache'
71
- | 'gateway'
72
- | 'external'
73
- | 'networking'
74
- | 'frontend';
77
+ | 'cache';
75
78
 
76
79
  const VALID_PARTICIPANT_TYPES: ReadonlySet<string> = new Set([
77
- 'service',
78
80
  'database',
79
81
  'actor',
80
82
  'queue',
81
83
  'cache',
84
+ ]);
85
+
86
+ /**
87
+ * Participant-type keywords that were removed in 0.16.0. Used to
88
+ * gate `is a X` declarations with a hard parse error rather than
89
+ * silent fall-through to default — aligns with the pre-1.0
90
+ * break-and-bump policy (see `E_AKA_REMOVED` precedent).
91
+ */
92
+ const REMOVED_PARTICIPANT_TYPES: ReadonlySet<string> = new Set([
93
+ 'service',
94
+ 'frontend',
95
+ 'networking',
82
96
  'gateway',
83
97
  'external',
84
- 'networking',
85
- 'frontend',
86
98
  ]);
87
99
 
88
100
  /**
@@ -205,9 +217,9 @@ export interface ParsedSequenceDgmo {
205
217
  error: string | null;
206
218
  }
207
219
 
208
- // "Name is a type" pattern — e.g. "Auth Server is a service"
220
+ // "Name is a type" pattern — e.g. "Auth Server is a database"
209
221
  // Participant names may contain spaces; [^:]+? stops at colons so that
210
- // note lines like "note right of A: this is a service" are not falsely matched.
222
+ // note lines like "note right of A: this is an actor" are not falsely matched.
211
223
  // Remainder after type is parsed separately for `position N` modifier.
212
224
  const IS_A_PATTERN = /^([^:]+?)\s+is\s+an?\s+(\w+)(?:\s+(.+))?$/i;
213
225
 
@@ -215,7 +227,10 @@ const IS_A_PATTERN = /^([^:]+?)\s+is\s+an?\s+(\w+)(?:\s+(.+))?$/i;
215
227
  const POSITION_ONLY_PATTERN = /^([^:]+?)\s+position\s+(-?\d+)$/i;
216
228
 
217
229
  // Colored participant declaration — e.g. "Tapin2(green)", "API(blue)"
218
- const COLORED_PARTICIPANT_PATTERN = /^(\S+?)\(([^)]+)\)\s*$/;
230
+ // Scoped to recognized 11-name palette colors only (§1.5) so legitimate
231
+ // `funcCall(arg)` lines don't trigger the legacy-color diagnostic.
232
+ const COLORED_PARTICIPANT_PATTERN =
233
+ /^(\S+?)\((red|orange|yellow|green|blue|purple|teal|cyan|gray|black|white)\)\s*$/;
219
234
 
220
235
  // Group heading pattern — "[Backend]", "[Backend] | t: Product"
221
236
  // Group 1: name (no ] or | inside brackets), Group 2: color in parens, Group 3: after-bracket text
@@ -477,7 +492,7 @@ export function parseSequenceDgmo(content: string): ParsedSequenceDgmo {
477
492
  result.diagnostics.push(makeDgmoError(line, message, 'warning'));
478
493
  };
479
494
 
480
- if (!content || !content.trim()) {
495
+ if (!content?.trim()) {
481
496
  return fail(0, 'Empty content');
482
497
  }
483
498
 
@@ -491,7 +506,7 @@ export function parseSequenceDgmo(content: string): ParsedSequenceDgmo {
491
506
  const fl = lines[fi].trim();
492
507
  if (!fl || fl.startsWith('//')) continue;
493
508
  const parsed = parseFirstLine(fl);
494
- if (parsed && parsed.chartType === 'sequence') {
509
+ if (parsed?.chartType === 'sequence') {
495
510
  hasExplicitChart = true;
496
511
  firstLineIndex = fi;
497
512
  if (parsed.title) {
@@ -678,7 +693,7 @@ export function parseSequenceDgmo(content: string): ParsedSequenceDgmo {
678
693
  if (groupColor) {
679
694
  pushWarning(
680
695
  lineNumber,
681
- `(${groupColor}) color syntax removed from sequence diagrams — use 'tag:' groups for coloring`
696
+ `'(${groupColor})' parens-color syntax removed from sequence diagrams — use 'tag:' groups for coloring`
682
697
  );
683
698
  }
684
699
  contentStarted = true;
@@ -765,7 +780,7 @@ export function parseSequenceDgmo(content: string): ParsedSequenceDgmo {
765
780
  continue;
766
781
  }
767
782
 
768
- // Tag group entries (indented Value(color) under tag heading)
783
+ // Tag group entries (indented Value color under tag heading)
769
784
  // First entry is the default unless another is marked `default`
770
785
  if (currentTagGroup && !contentStarted && measureIndent(raw) > 0) {
771
786
  const { text: cleanEntry, isDefault } = stripDefaultModifier(trimmed);
@@ -778,7 +793,7 @@ export function parseSequenceDgmo(content: string): ParsedSequenceDgmo {
778
793
  if (!color) {
779
794
  pushError(
780
795
  lineNumber,
781
- `Expected 'Value(color)' in tag group '${currentTagGroup.name}'`
796
+ `Expected 'Value color' in tag group '${currentTagGroup.name}'`
782
797
  );
783
798
  continue;
784
799
  }
@@ -807,11 +822,14 @@ export function parseSequenceDgmo(content: string): ParsedSequenceDgmo {
807
822
  blockStack.pop();
808
823
  }
809
824
  const labelRaw = sectionMatch[1].trim();
810
- const colorMatch = labelRaw.match(/^(.+?)\(([^)]+)\)$/);
825
+ // Scoped to recognized 11-name palette colors only (§1.5).
826
+ const colorMatch = labelRaw.match(
827
+ /^(.+?)\((red|orange|yellow|green|blue|purple|teal|cyan|gray|black|white)\)$/
828
+ );
811
829
  if (colorMatch) {
812
830
  pushWarning(
813
831
  lineNumber,
814
- `(${colorMatch[2].trim()}) color syntax removed from sequence diagrams — use 'tag:' groups for coloring`
832
+ `'(${colorMatch[2]})' parens-color syntax removed from sequence diagrams — use 'tag:' groups for coloring`
815
833
  );
816
834
  }
817
835
  contentStarted = true;
@@ -930,6 +948,23 @@ export function parseSequenceDgmo(content: string): ParsedSequenceDgmo {
930
948
  const typeStr = isAMatch[2].toLowerCase();
931
949
  let remainder = isAMatch[3]?.trim() || '';
932
950
 
951
+ // Reject the 5 removed-type keywords with a hard parse error
952
+ // (per Decision #2 / break-and-bump policy). Skip participant
953
+ // registration for the bad line — downstream message references
954
+ // will surface a "participant not declared" diagnostic, which
955
+ // is the intended behavior.
956
+ if (REMOVED_PARTICIPANT_TYPES.has(typeStr)) {
957
+ result.diagnostics.push(
958
+ makeDgmoError(
959
+ lineNumber,
960
+ participantTypeRemovedMessage(typeStr),
961
+ 'error',
962
+ NAME_DIAGNOSTIC_CODES.PARTICIPANT_TYPE_REMOVED
963
+ )
964
+ );
965
+ continue;
966
+ }
967
+
933
968
  const participantType: ParticipantType = VALID_PARTICIPANT_TYPES.has(
934
969
  typeStr
935
970
  )
@@ -951,7 +986,7 @@ export function parseSequenceDgmo(content: string): ParsedSequenceDgmo {
951
986
  // register it. Order on the line is `Name is a TYPE [position N] as <alias>`.
952
987
  // The leading `(.*?)\s*\b` allows the remainder to be just
953
988
  // `as <alias>` (empty prefix) — the canonical example writes
954
- // `Alice is a service as a` where the entire remainder is `as a`.
989
+ // `Alice is an actor as a` where the entire remainder is `as a`.
955
990
  const asInRemainder = remainder.match(
956
991
  /^(.*?)\s*\bas\s+([A-Za-z][A-Za-z0-9_]{0,11})\s*$/
957
992
  );
@@ -1016,8 +1051,8 @@ export function parseSequenceDgmo(content: string): ParsedSequenceDgmo {
1016
1051
  continue;
1017
1052
  }
1018
1053
 
1019
- // Colored participant declaration — "Name(color)" at any level
1020
- // Color syntax is deprecated emit warning and register without color
1054
+ // Legacy `Name(color)` participant declaration at any level (§1.5 hard
1055
+ // break). Scoped to the 11-name palette so `funcCall(arg)` doesn't trip.
1021
1056
  const { core: colorCore, meta: colorMeta } = splitPipe(trimmed, lineNumber);
1022
1057
  const coloredMatch = colorCore.match(COLORED_PARTICIPANT_PATTERN);
1023
1058
  if (coloredMatch && !ARROW_PATTERN.test(colorCore)) {
@@ -1025,7 +1060,7 @@ export function parseSequenceDgmo(content: string): ParsedSequenceDgmo {
1025
1060
  const color = coloredMatch[2].trim();
1026
1061
  pushError(
1027
1062
  lineNumber,
1028
- `'${id}(${color})' syntax is no longer supported — use 'tag:' groups for coloring`
1063
+ `'${id}(${color})' parens-color syntax is no longer supported — use 'tag:' groups for coloring`
1029
1064
  );
1030
1065
  contentStarted = true;
1031
1066
  const key = addParticipant(id, lineNumber, { metadata: colorMeta });
@@ -1310,6 +1345,29 @@ export function parseSequenceDgmo(content: string): ParsedSequenceDgmo {
1310
1345
  continue;
1311
1346
  }
1312
1347
 
1348
+ // 'elif <label>' → suggest 'else if <label>'
1349
+ const elifMatch = trimmed.match(/^elif\b\s*(.*)$/i);
1350
+ if (elifMatch) {
1351
+ const tailRaw = elifMatch[1].trim();
1352
+ const tail = tailRaw ? ' ' + tailRaw : '';
1353
+ pushError(
1354
+ lineNumber,
1355
+ `'elif' is not a keyword. Did you mean 'else if${tail}'?`
1356
+ );
1357
+ continue;
1358
+ }
1359
+
1360
+ // 'else <text>' (where text isn't 'if …') → suggest 'else if <text>'
1361
+ const elseLabelMatch = trimmed.match(/^else\s+(.+)$/i);
1362
+ if (elseLabelMatch) {
1363
+ const tail = elseLabelMatch[1].trim();
1364
+ pushError(
1365
+ lineNumber,
1366
+ `'else' does not take a label. Did you mean 'else if ${tail}'?`
1367
+ );
1368
+ continue;
1369
+ }
1370
+
1313
1371
  // ---- Note parsing (space-separated only) ----
1314
1372
  // Strategy:
1315
1373
  // 1. Try bare note: `note text` — position defaults, text is everything after `note`
@@ -3,9 +3,14 @@
3
3
  // ============================================================
4
4
  //
5
5
  // Data-driven rules table that infers participant type from name.
6
- // First match wins. Infrastructure overrides come before suffix
7
- // rules to prevent false positives (e.g. "Router" networking,
8
- // not actor despite the "-er" suffix).
6
+ // First match wins. Conflict overrides come before suffix rules
7
+ // to prevent false positives within the surviving 4-type taxonomy.
8
+ //
9
+ // Surviving types: actor, database, cache, queue (plus default).
10
+ // Removed in 0.16.0: service, frontend, networking, gateway, external.
11
+ // Names that previously inferred to a removed type fall through to
12
+ // default (sharp rectangle). Inference is intentionally conservative
13
+ // — narrow product/name lists, no broad suffix catch-alls.
9
14
  // ============================================================
10
15
 
11
16
  import type { ParticipantType } from './parser';
@@ -23,95 +28,17 @@ interface InferenceRule {
23
28
  *
24
29
  * Priority order:
25
30
  * 0. Conflict overrides (prevent misclassification by general patterns)
26
- * 1. Infrastructure overrides (prevent false actor matches)
27
- * 2. Networking patterns
28
- * 3. Database patterns
29
- * 4. Cache patterns
30
- * 5. Queue/Messaging patterns
31
- * 6. Actor patterns (suffix + exact)
32
- * 7. Frontend patterns
33
- * 8. Service patterns
34
- * 9. External patterns
31
+ * 1. Database patterns
32
+ * 2. Cache patterns
33
+ * 3. Queue/Messaging patterns
34
+ * 4. Actor patterns (suffix + exact)
35
35
  */
36
36
  const PARTICIPANT_RULES: readonly InferenceRule[] = [
37
37
  // ── 0. Conflict overrides ────────────────────────────────
38
38
  // Names that would incorrectly match general patterns in later groups
39
39
  { pattern: /^KeyDB$/i, type: 'cache' }, // not database (DB$ suffix)
40
- { pattern: /Webhook/i, type: 'external' }, // not frontend (Web contains)
41
- { pattern: /^Upstream$/i, type: 'external' }, // not queue (Stream$ suffix)
42
- { pattern: /^Downstream$/i, type: 'external' }, // not queue (Stream$ suffix)
43
-
44
- // ── 1. Infrastructure overrides ─────────────────────────
45
- // These names end in -er/-or but are NOT actors
46
- { pattern: /^.*Router$/i, type: 'networking' },
47
- { pattern: /^.*Scheduler$/i, type: 'service' },
48
- { pattern: /^.*Dispatcher$/i, type: 'service' },
49
- { pattern: /^.*Balancer$/i, type: 'networking' },
50
- { pattern: /^.*Controller$/i, type: 'service' },
51
- { pattern: /^.*Handler$/i, type: 'service' },
52
- { pattern: /^.*Processor$/i, type: 'service' },
53
- { pattern: /^.*Connector$/i, type: 'service' },
54
- { pattern: /^.*Adapter$/i, type: 'service' },
55
- { pattern: /^.*Provider$/i, type: 'service' },
56
- { pattern: /^.*Manager$/i, type: 'service' },
57
- { pattern: /^.*Orchestrator$/i, type: 'service' },
58
- { pattern: /^.*Monitor$/i, type: 'service' },
59
- { pattern: /^.*Resolver$/i, type: 'service' },
60
- { pattern: /^.*Logger$/i, type: 'service' },
61
- { pattern: /^.*Server$/i, type: 'service' },
62
- { pattern: /^.*Broker$/i, type: 'queue' },
63
- { pattern: /^.*Worker$/i, type: 'service' },
64
- { pattern: /^.*Consumer$/i, type: 'service' },
65
- { pattern: /^.*Producer$/i, type: 'service' },
66
- { pattern: /^.*Publisher$/i, type: 'service' },
67
- { pattern: /^.*Subscriber$/i, type: 'service' },
68
- { pattern: /^.*Listener$/i, type: 'service' },
69
- // New -er/-or suffixes that are services, not actors
70
- { pattern: /^.*Watcher$/i, type: 'service' },
71
- { pattern: /^.*Executor$/i, type: 'service' },
72
- { pattern: /^.*Aggregator$/i, type: 'service' },
73
- { pattern: /^.*Collector$/i, type: 'service' },
74
- { pattern: /^.*Transformer$/i, type: 'service' },
75
- { pattern: /^.*Validator$/i, type: 'service' },
76
- { pattern: /^.*Generator$/i, type: 'service' },
77
- { pattern: /^.*Indexer$/i, type: 'service' },
78
- { pattern: /^.*Crawler$/i, type: 'service' },
79
- { pattern: /^.*Scanner$/i, type: 'service' },
80
- { pattern: /^.*Parser$/i, type: 'service' },
81
- { pattern: /^.*Emitter$/i, type: 'service' },
82
- { pattern: /^.*Exporter$/i, type: 'service' },
83
- { pattern: /^.*Importer$/i, type: 'service' },
84
- { pattern: /^.*Loader$/i, type: 'service' },
85
- { pattern: /^.*Renderer$/i, type: 'service' },
86
- { pattern: /^.*Checker$/i, type: 'service' },
87
- { pattern: /^.*Inspector$/i, type: 'service' },
88
- { pattern: /^.*Encoder$/i, type: 'service' },
89
- { pattern: /^.*Decoder$/i, type: 'service' },
90
- { pattern: /^.*Notifier$/i, type: 'service' },
91
-
92
- // ── 2. Networking patterns ──────────────────────────────
93
- { pattern: /Gateway/i, type: 'networking' },
94
- { pattern: /GW$/i, type: 'networking' },
95
- { pattern: /Proxy/i, type: 'networking' },
96
- { pattern: /LB$/i, type: 'networking' },
97
- { pattern: /LoadBalancer/i, type: 'networking' },
98
- { pattern: /CDN/i, type: 'networking' },
99
- { pattern: /Firewall/i, type: 'networking' },
100
- { pattern: /WAF$/i, type: 'networking' },
101
- { pattern: /DNS/i, type: 'networking' },
102
- { pattern: /Ingress/i, type: 'networking' },
103
- // Named products & patterns
104
- { pattern: /Nginx/i, type: 'networking' },
105
- { pattern: /Traefik/i, type: 'networking' },
106
- { pattern: /Envoy/i, type: 'networking' },
107
- { pattern: /Istio/i, type: 'networking' },
108
- { pattern: /Kong/i, type: 'networking' },
109
- { pattern: /Akamai/i, type: 'networking' },
110
- { pattern: /Cloudflare/i, type: 'networking' },
111
- { pattern: /Mesh$/i, type: 'networking' },
112
- { pattern: /ServiceMesh/i, type: 'networking' },
113
40
 
114
- // ── 3. Database patterns ────────────────────────────────
41
+ // ── 1. Database patterns ────────────────────────────────
115
42
  { pattern: /DB$/i, type: 'database' },
116
43
  { pattern: /Database/i, type: 'database' },
117
44
  { pattern: /Datastore/i, type: 'database' },
@@ -146,17 +73,16 @@ const PARTICIPANT_RULES: readonly InferenceRule[] = [
146
73
  { pattern: /Presto/i, type: 'database' },
147
74
  { pattern: /Table$/i, type: 'database' },
148
75
 
149
- // ── 4. Cache patterns ──────────────────────────────────
76
+ // ── 2. Cache patterns ──────────────────────────────────
150
77
  { pattern: /Cache/i, type: 'cache' },
151
78
  { pattern: /Redis/i, type: 'cache' },
152
79
  { pattern: /Memcache/i, type: 'cache' },
153
- // CDN already matched by networking above
154
80
  // Named products
155
81
  { pattern: /Dragonfly/i, type: 'cache' },
156
82
  { pattern: /Hazelcast/i, type: 'cache' },
157
83
  { pattern: /Valkey/i, type: 'cache' },
158
84
 
159
- // ── 5. Queue/Messaging patterns ─────────────────────────
85
+ // ── 3. Queue/Messaging patterns ─────────────────────────
160
86
  { pattern: /Queue/i, type: 'queue' },
161
87
  { pattern: /MQ$/i, type: 'queue' },
162
88
  { pattern: /SQS/i, type: 'queue' },
@@ -169,6 +95,7 @@ const PARTICIPANT_RULES: readonly InferenceRule[] = [
169
95
  { pattern: /Stream$/i, type: 'queue' },
170
96
  { pattern: /SNS/i, type: 'queue' },
171
97
  { pattern: /PubSub/i, type: 'queue' },
98
+ { pattern: /Broker$/i, type: 'queue' },
172
99
  // Named products & patterns
173
100
  { pattern: /NATS/i, type: 'queue' },
174
101
  { pattern: /Pulsar/i, type: 'queue' },
@@ -180,7 +107,7 @@ const PARTICIPANT_RULES: readonly InferenceRule[] = [
180
107
  { pattern: /EventHub/i, type: 'queue' },
181
108
  { pattern: /Channel$/i, type: 'queue' },
182
109
 
183
- // ── 6. Actor patterns ──────────────────────────────────
110
+ // ── 4. Actor patterns ──────────────────────────────────
184
111
  // Exact matches first
185
112
  { pattern: /^Admin$/i, type: 'actor' },
186
113
  { pattern: /^User$/i, type: 'actor' },
@@ -199,101 +126,11 @@ const PARTICIPANT_RULES: readonly InferenceRule[] = [
199
126
  { pattern: /^Fan$/i, type: 'actor' },
200
127
  { pattern: /^Purchaser$/i, type: 'actor' },
201
128
  { pattern: /^Reviewer$/i, type: 'actor' },
202
- // Suffix rules (after infrastructure overrides filtered above)
129
+ // Suffix rules
203
130
  { pattern: /User$/i, type: 'actor' },
204
131
  { pattern: /Actor$/i, type: 'actor' },
205
132
  { pattern: /Analyst$/i, type: 'actor' },
206
133
  { pattern: /Staff$/i, type: 'actor' },
207
-
208
- // ── 7. Frontend patterns ────────────────────────────────
209
- { pattern: /App$/i, type: 'frontend' },
210
- { pattern: /Application/i, type: 'frontend' },
211
- { pattern: /Mobile/i, type: 'frontend' },
212
- { pattern: /iOS/i, type: 'frontend' },
213
- { pattern: /Android/i, type: 'frontend' },
214
- { pattern: /Web/i, type: 'frontend' },
215
- { pattern: /Browser/i, type: 'frontend' },
216
- { pattern: /Frontend/i, type: 'frontend' },
217
- { pattern: /UI$/i, type: 'frontend' },
218
- { pattern: /Dashboard/i, type: 'frontend' },
219
- { pattern: /CLI$/i, type: 'frontend' },
220
- { pattern: /Terminal/i, type: 'frontend' },
221
- // Frameworks & patterns
222
- { pattern: /React/i, type: 'frontend' },
223
- { pattern: /^Vue$/i, type: 'frontend' },
224
- { pattern: /Angular/i, type: 'frontend' },
225
- { pattern: /Svelte/i, type: 'frontend' },
226
- { pattern: /NextJS/i, type: 'frontend' },
227
- { pattern: /Nuxt/i, type: 'frontend' },
228
- { pattern: /Remix/i, type: 'frontend' },
229
- { pattern: /Electron/i, type: 'frontend' },
230
- { pattern: /Tauri/i, type: 'frontend' },
231
- { pattern: /Widget$/i, type: 'frontend' },
232
- { pattern: /Portal/i, type: 'frontend' },
233
- { pattern: /Console$/i, type: 'frontend' },
234
- { pattern: /^SPA$/i, type: 'frontend' },
235
- { pattern: /^PWA$/i, type: 'frontend' },
236
-
237
- // ── 8. Service patterns ─────────────────────────────────
238
- { pattern: /Service/i, type: 'service' },
239
- { pattern: /Svc$/i, type: 'service' },
240
- { pattern: /API$/i, type: 'service' },
241
- { pattern: /Lambda/i, type: 'service' },
242
- { pattern: /Function$/i, type: 'service' },
243
- { pattern: /Fn$/i, type: 'service' },
244
- { pattern: /Job$/i, type: 'service' },
245
- { pattern: /Cron/i, type: 'service' },
246
- { pattern: /Microservice/i, type: 'service' },
247
- // Auth
248
- { pattern: /^Auth$/i, type: 'service' },
249
- { pattern: /^AuthN$/i, type: 'service' },
250
- { pattern: /^AuthZ$/i, type: 'service' },
251
- { pattern: /^SSO$/i, type: 'service' },
252
- { pattern: /OAuth/i, type: 'service' },
253
- { pattern: /^OIDC$/i, type: 'service' },
254
- // SaaS
255
- { pattern: /Stripe/i, type: 'service' },
256
- { pattern: /Twilio/i, type: 'service' },
257
- { pattern: /SendGrid/i, type: 'service' },
258
- { pattern: /Mailgun/i, type: 'service' },
259
- // Cloud/infra
260
- { pattern: /^S3$/i, type: 'service' },
261
- { pattern: /^Blob$/i, type: 'service' },
262
- { pattern: /Vercel/i, type: 'service' },
263
- { pattern: /Netlify/i, type: 'service' },
264
- { pattern: /Heroku/i, type: 'service' },
265
- { pattern: /Docker/i, type: 'service' },
266
- { pattern: /Kubernetes/i, type: 'service' },
267
- { pattern: /K8s/i, type: 'service' },
268
- { pattern: /Terraform/i, type: 'service' },
269
- // Security
270
- { pattern: /Vault/i, type: 'service' },
271
- { pattern: /^HSM$/i, type: 'service' },
272
- { pattern: /KMS/i, type: 'service' },
273
- { pattern: /^IAM$/i, type: 'service' },
274
- // AI/ML
275
- { pattern: /^LLM$/i, type: 'service' },
276
- { pattern: /GPT/i, type: 'service' },
277
- { pattern: /^Claude$/i, type: 'service' },
278
- { pattern: /Embedding/i, type: 'service' },
279
- { pattern: /Inference/i, type: 'service' },
280
- // Suffixes & patterns
281
- { pattern: /Pipeline$/i, type: 'service' },
282
- { pattern: /Registry/i, type: 'service' },
283
- { pattern: /Engine$/i, type: 'service' },
284
- { pattern: /Daemon/i, type: 'service' },
285
-
286
- // ── 9. External patterns ────────────────────────────────
287
- { pattern: /External/i, type: 'external' },
288
- { pattern: /Ext$/i, type: 'external' },
289
- { pattern: /ThirdParty/i, type: 'external' },
290
- { pattern: /3P$/i, type: 'external' },
291
- { pattern: /Vendor/i, type: 'external' },
292
- // Named products & patterns
293
- { pattern: /Callback/i, type: 'external' },
294
- { pattern: /^AWS$/i, type: 'external' },
295
- { pattern: /^GCP$/i, type: 'external' },
296
- { pattern: /Azure/i, type: 'external' },
297
134
  ];
298
135
 
299
136
  /**