@diagrammo/dgmo 0.8.22 → 0.8.25

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 (90) hide show
  1. package/.claude/commands/dgmo.md +60 -72
  2. package/dist/cli.cjs +123 -116
  3. package/dist/editor.cjs +3 -2
  4. package/dist/editor.cjs.map +1 -1
  5. package/dist/editor.js +3 -2
  6. package/dist/editor.js.map +1 -1
  7. package/dist/highlight.cjs +3 -2
  8. package/dist/highlight.cjs.map +1 -1
  9. package/dist/highlight.js +3 -2
  10. package/dist/highlight.js.map +1 -1
  11. package/dist/index.cjs +1649 -442
  12. package/dist/index.cjs.map +1 -1
  13. package/dist/index.d.cts +196 -23
  14. package/dist/index.d.ts +196 -23
  15. package/dist/index.js +1631 -440
  16. package/dist/index.js.map +1 -1
  17. package/dist/internal.cjs +677 -0
  18. package/dist/internal.cjs.map +1 -0
  19. package/dist/internal.d.cts +267 -0
  20. package/dist/internal.d.ts +267 -0
  21. package/dist/internal.js +633 -0
  22. package/dist/internal.js.map +1 -0
  23. package/docs/guide/chart-area.md +17 -17
  24. package/docs/guide/chart-bar-stacked.md +12 -12
  25. package/docs/guide/chart-cycle.md +156 -0
  26. package/docs/guide/chart-doughnut.md +10 -10
  27. package/docs/guide/chart-funnel.md +9 -9
  28. package/docs/guide/chart-heatmap.md +10 -10
  29. package/docs/guide/chart-journey-map.md +179 -0
  30. package/docs/guide/chart-kanban.md +2 -0
  31. package/docs/guide/chart-line.md +19 -19
  32. package/docs/guide/chart-multi-line.md +16 -16
  33. package/docs/guide/chart-pie.md +11 -11
  34. package/docs/guide/chart-polar-area.md +10 -10
  35. package/docs/guide/chart-pyramid.md +111 -0
  36. package/docs/guide/chart-radar.md +9 -9
  37. package/docs/guide/chart-scatter.md +24 -27
  38. package/docs/guide/index.md +3 -3
  39. package/docs/guide/registry.json +5 -0
  40. package/docs/language-reference.md +108 -26
  41. package/fonts/Inter-Bold.ttf +0 -0
  42. package/fonts/Inter-Regular.ttf +0 -0
  43. package/fonts/LICENSE-Inter.txt +92 -0
  44. package/gallery/fixtures/bar-stacked.dgmo +12 -6
  45. package/gallery/fixtures/heatmap.dgmo +12 -6
  46. package/gallery/fixtures/multi-line.dgmo +11 -7
  47. package/gallery/fixtures/pyramid/dikw.dgmo +17 -0
  48. package/gallery/fixtures/pyramid/inverted-funnel.dgmo +16 -0
  49. package/gallery/fixtures/pyramid/minimal.dgmo +5 -0
  50. package/gallery/fixtures/quadrant.dgmo +8 -8
  51. package/gallery/fixtures/scatter.dgmo +12 -12
  52. package/package.json +14 -2
  53. package/src/boxes-and-lines/parser.ts +13 -2
  54. package/src/boxes-and-lines/renderer.ts +22 -13
  55. package/src/chart-type-scoring.ts +162 -0
  56. package/src/chart-types.ts +437 -0
  57. package/src/cli.ts +152 -101
  58. package/src/completion.ts +9 -48
  59. package/src/cycle/layout.ts +19 -28
  60. package/src/cycle/renderer.ts +59 -32
  61. package/src/cycle/types.ts +21 -0
  62. package/src/d3.ts +30 -3
  63. package/src/dgmo-router.ts +98 -73
  64. package/src/echarts.ts +1 -1
  65. package/src/editor/keywords.ts +3 -2
  66. package/src/fonts.ts +3 -2
  67. package/src/gantt/parser.ts +5 -1
  68. package/src/index.ts +37 -3
  69. package/src/infra/parser.ts +3 -3
  70. package/src/internal.ts +20 -0
  71. package/src/journey-map/layout.ts +7 -3
  72. package/src/journey-map/parser.ts +5 -1
  73. package/src/journey-map/renderer.ts +112 -47
  74. package/src/kanban/parser.ts +5 -1
  75. package/src/org/collapse.ts +82 -4
  76. package/src/org/parser.ts +1 -1
  77. package/src/org/renderer.ts +221 -4
  78. package/src/pyramid/parser.ts +172 -0
  79. package/src/pyramid/renderer.ts +684 -0
  80. package/src/pyramid/types.ts +28 -0
  81. package/src/render.ts +2 -8
  82. package/src/sequence/parser.ts +64 -22
  83. package/src/sequence/participant-inference.ts +0 -1
  84. package/src/sequence/renderer.ts +97 -265
  85. package/src/sharing.ts +0 -1
  86. package/src/sitemap/parser.ts +1 -1
  87. package/src/tech-radar/interactive.ts +54 -0
  88. package/src/utils/parsing.ts +1 -0
  89. package/src/utils/tag-groups.ts +35 -5
  90. package/src/wireframe/parser.ts +3 -1
package/src/render.ts CHANGED
@@ -1,10 +1,6 @@
1
1
  import { renderForExport } from './d3';
2
2
  import { renderExtendedChartForExport } from './echarts';
3
- import {
4
- parseDgmoChartType,
5
- getRenderCategory,
6
- parseDgmo,
7
- } from './dgmo-router';
3
+ import { getRenderCategory, parseDgmo } from './dgmo-router';
8
4
  import type { DgmoError } from './diagnostics';
9
5
  import { getPalette } from './palettes/registry';
10
6
  import type { CompactViewState } from './sharing';
@@ -84,9 +80,7 @@ export async function render(
84
80
  const paletteColors =
85
81
  getPalette(paletteName)[theme === 'dark' ? 'dark' : 'light'];
86
82
 
87
- const { diagnostics } = parseDgmo(content);
88
-
89
- const chartType = parseDgmoChartType(content);
83
+ const { diagnostics, chartType } = parseDgmo(content);
90
84
  const category = chartType ? getRenderCategory(chartType) : null;
91
85
 
92
86
  // Build viewState from legendState (backwards compat) or use provided viewState
@@ -26,7 +26,7 @@ import {
26
26
  const KNOWN_SEQ_OPTIONS = new Set(['active-tag']);
27
27
 
28
28
  /** Known sequence-diagram boolean options (bare keyword or `no-` prefix). */
29
- const KNOWN_SEQ_BOOLEANS = new Set(['activations', 'collapse-notes']);
29
+ const KNOWN_SEQ_BOOLEANS = new Set(['activations']);
30
30
 
31
31
  /**
32
32
  * Participant types that can be declared via "Name is a type" syntax.
@@ -236,6 +236,8 @@ type NoteParseResult =
236
236
  function parseNoteLine(
237
237
  trimmed: string,
238
238
  participants: SequenceParticipant[],
239
+ participantIds: Set<string>,
240
+ sortedParticipantsCache: SequenceParticipant[],
239
241
  lastMsgFrom: string | null
240
242
  ): NoteParseResult {
241
243
  const lower = trimmed.toLowerCase();
@@ -256,7 +258,7 @@ function parseNoteLine(
256
258
  if (!lastMsgFrom) return { kind: 'skip' };
257
259
  participantId = lastMsgFrom;
258
260
  }
259
- if (participants.some((p) => p.id === participantId)) {
261
+ if (participantIds.has(participantId)) {
260
262
  return { kind: 'multi-head', position, participantId };
261
263
  }
262
264
  // Participant not found — fall through to bare-note handler for proper resolution
@@ -284,13 +286,17 @@ function parseNoteLine(
284
286
  if (!afterPos) {
285
287
  // Just `note left` or `note right` — multi-line head
286
288
  if (!lastMsgFrom) return { kind: 'skip' };
287
- if (!participants.some((p) => p.id === lastMsgFrom))
288
- return { kind: 'skip' };
289
+ if (!participantIds.has(lastMsgFrom)) return { kind: 'skip' };
289
290
  return { kind: 'multi-head', position, participantId: lastMsgFrom };
290
291
  }
291
292
 
292
293
  // Try to match a known participant at the start of afterPos
293
- const resolved = resolveParticipantAndText(afterPos, participants);
294
+ const resolved = resolveParticipantAndText(
295
+ afterPos,
296
+ participants,
297
+ participantIds,
298
+ sortedParticipantsCache
299
+ );
294
300
  if (resolved) {
295
301
  if (resolved.text) {
296
302
  return {
@@ -316,8 +322,7 @@ function parseNoteLine(
316
322
 
317
323
  // Without `of`, treat remaining text as note content on the last-msg sender
318
324
  if (!lastMsgFrom) return { kind: 'skip' };
319
- if (!participants.some((p) => p.id === lastMsgFrom))
320
- return { kind: 'skip' };
325
+ if (!participantIds.has(lastMsgFrom)) return { kind: 'skip' };
321
326
  return {
322
327
  kind: 'single',
323
328
  position,
@@ -328,8 +333,7 @@ function parseNoteLine(
328
333
 
329
334
  // Plain `note text` — default position, last msg sender
330
335
  if (!lastMsgFrom) return { kind: 'skip' };
331
- if (!participants.some((p) => p.id === lastMsgFrom))
332
- return { kind: 'skip' };
336
+ if (!participantIds.has(lastMsgFrom)) return { kind: 'skip' };
333
337
  return {
334
338
  kind: 'single',
335
339
  position: 'right',
@@ -348,7 +352,9 @@ function parseNoteLine(
348
352
  */
349
353
  function resolveParticipantAndText(
350
354
  input: string,
351
- participants: SequenceParticipant[]
355
+ participants: SequenceParticipant[],
356
+ participantIds: Set<string>,
357
+ sortedParticipantsCache: SequenceParticipant[]
352
358
  ): { participantId: string; text: string } | null {
353
359
  // Handle quoted participant: `"Auth Service" text`
354
360
  if (input.startsWith('"') || input.startsWith("'")) {
@@ -356,7 +362,7 @@ function resolveParticipantAndText(
356
362
  const endQuote = input.indexOf(quote, 1);
357
363
  if (endQuote > 0) {
358
364
  const name = input.substring(1, endQuote);
359
- if (participants.some((p) => p.id === name)) {
365
+ if (participantIds.has(name)) {
360
366
  const text = input.substring(endQuote + 1).trim();
361
367
  return { participantId: name, text };
362
368
  }
@@ -364,8 +370,8 @@ function resolveParticipantAndText(
364
370
  return null;
365
371
  }
366
372
 
367
- // Sort participants by name length (longest first) for greedy matching
368
- const sorted = [...participants].sort((a, b) => b.id.length - a.id.length);
373
+ // Use pre-sorted participants (longest first) for greedy matching
374
+ const sorted = sortedParticipantsCache;
369
375
  for (const p of sorted) {
370
376
  if (input.startsWith(p.id)) {
371
377
  const remaining = input.substring(p.id.length);
@@ -443,6 +449,25 @@ export function parseSequenceDgmo(content: string): ParsedSequenceDgmo {
443
449
  // Group parsing state — tracks the active [Group] heading
444
450
  let activeGroup: SequenceGroup | null = null;
445
451
 
452
+ // Fast lookup set for participant existence checks (mirrors result.participants)
453
+ const participantIds = new Set<string>();
454
+
455
+ // Cache sorted participants (longest ID first) for greedy name matching in notes.
456
+ // Invalidated whenever a new participant is added.
457
+ let sortedParticipantsCache: SequenceParticipant[] = [];
458
+ let sortedCacheDirty = true;
459
+
460
+ /** Get sorted participants, rebuilding cache only when dirty. */
461
+ const getSortedParticipants = (): SequenceParticipant[] => {
462
+ if (sortedCacheDirty) {
463
+ sortedParticipantsCache = [...result.participants].sort(
464
+ (a, b) => b.id.length - a.id.length
465
+ );
466
+ sortedCacheDirty = false;
467
+ }
468
+ return sortedParticipantsCache;
469
+ };
470
+
446
471
  // Track participant → group name for duplicate membership detection
447
472
  const participantGroupMap = new Map<string, string>();
448
473
 
@@ -774,7 +799,9 @@ export function parseSequenceDgmo(content: string): ParsedSequenceDgmo {
774
799
  const position = posMatch ? parseInt(posMatch[1], 10) : undefined;
775
800
 
776
801
  // Avoid duplicate participant declarations
777
- if (!result.participants.some((p) => p.id === id)) {
802
+ if (!participantIds.has(id)) {
803
+ participantIds.add(id);
804
+ sortedCacheDirty = true;
778
805
  result.participants.push({
779
806
  id,
780
807
  label: alias || id,
@@ -808,7 +835,9 @@ export function parseSequenceDgmo(content: string): ParsedSequenceDgmo {
808
835
  const id = posOnlyMatch[1];
809
836
  const position = parseInt(posOnlyMatch[2], 10);
810
837
 
811
- if (!result.participants.some((p) => p.id === id)) {
838
+ if (!participantIds.has(id)) {
839
+ participantIds.add(id);
840
+ sortedCacheDirty = true;
812
841
  result.participants.push({
813
842
  id,
814
843
  label: id,
@@ -846,7 +875,9 @@ export function parseSequenceDgmo(content: string): ParsedSequenceDgmo {
846
875
  `'${id}(${color})' syntax is no longer supported — use 'tag:' groups for coloring`
847
876
  );
848
877
  contentStarted = true;
849
- if (!result.participants.some((p) => p.id === id)) {
878
+ if (!participantIds.has(id)) {
879
+ participantIds.add(id);
880
+ sortedCacheDirty = true;
850
881
  result.participants.push({
851
882
  id,
852
883
  label: id,
@@ -882,7 +913,8 @@ export function parseSequenceDgmo(content: string): ParsedSequenceDgmo {
882
913
  ) {
883
914
  contentStarted = true;
884
915
  const id = bareCore;
885
- if (!result.participants.some((p) => p.id === id)) {
916
+ if (!participantIds.has(id)) {
917
+ participantIds.add(id);
886
918
  result.participants.push({
887
919
  id,
888
920
  label: id,
@@ -965,7 +997,9 @@ export function parseSequenceDgmo(content: string): ParsedSequenceDgmo {
965
997
  currentContainer().push(msg);
966
998
 
967
999
  // Auto-register participants
968
- if (!result.participants.some((p) => p.id === from)) {
1000
+ if (!participantIds.has(from)) {
1001
+ participantIds.add(from);
1002
+ sortedCacheDirty = true;
969
1003
  result.participants.push({
970
1004
  id: from,
971
1005
  label: from,
@@ -973,7 +1007,9 @@ export function parseSequenceDgmo(content: string): ParsedSequenceDgmo {
973
1007
  lineNumber,
974
1008
  });
975
1009
  }
976
- if (!result.participants.some((p) => p.id === to)) {
1010
+ if (!participantIds.has(to)) {
1011
+ participantIds.add(to);
1012
+ sortedCacheDirty = true;
977
1013
  result.participants.push({
978
1014
  id: to,
979
1015
  label: to,
@@ -1050,7 +1086,9 @@ export function parseSequenceDgmo(content: string): ParsedSequenceDgmo {
1050
1086
  result.messages.push(msg);
1051
1087
  currentContainer().push(msg);
1052
1088
 
1053
- if (!result.participants.some((p) => p.id === from)) {
1089
+ if (!participantIds.has(from)) {
1090
+ participantIds.add(from);
1091
+ sortedCacheDirty = true;
1054
1092
  result.participants.push({
1055
1093
  id: from,
1056
1094
  label: from,
@@ -1058,7 +1096,9 @@ export function parseSequenceDgmo(content: string): ParsedSequenceDgmo {
1058
1096
  lineNumber,
1059
1097
  });
1060
1098
  }
1061
- if (!result.participants.some((p) => p.id === to)) {
1099
+ if (!participantIds.has(to)) {
1100
+ participantIds.add(to);
1101
+ sortedCacheDirty = true;
1062
1102
  result.participants.push({
1063
1103
  id: to,
1064
1104
  label: to,
@@ -1182,6 +1222,8 @@ export function parseSequenceDgmo(content: string): ParsedSequenceDgmo {
1182
1222
  const noteParsed = parseNoteLine(
1183
1223
  trimmed,
1184
1224
  result.participants,
1225
+ participantIds,
1226
+ getSortedParticipants(),
1185
1227
  lastMsgFrom
1186
1228
  );
1187
1229
  if (noteParsed) {
@@ -1303,7 +1345,7 @@ export function parseSequenceDgmo(content: string): ParsedSequenceDgmo {
1303
1345
  entities.push({ metadata: g.metadata, lineNumber: g.lineNumber });
1304
1346
  }
1305
1347
  validateTagValues(entities, result.tagGroups, pushWarning, suggest);
1306
- validateTagGroupNames(result.tagGroups, pushWarning);
1348
+ validateTagGroupNames(result.tagGroups, pushWarning, pushError);
1307
1349
  }
1308
1350
 
1309
1351
  return result;
@@ -185,7 +185,6 @@ const PARTICIPANT_RULES: readonly InferenceRule[] = [
185
185
  { pattern: /^Admin$/i, type: 'actor' },
186
186
  { pattern: /^User$/i, type: 'actor' },
187
187
  { pattern: /^Customer$/i, type: 'actor' },
188
- { pattern: /^Client$/i, type: 'actor' },
189
188
  { pattern: /^Agent$/i, type: 'actor' },
190
189
  { pattern: /^Person$/i, type: 'actor' },
191
190
  { pattern: /^Buyer$/i, type: 'actor' },