@diagrammo/dgmo 0.32.1 → 0.33.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 (57) hide show
  1. package/.cursorrules +7 -2
  2. package/.github/copilot-instructions.md +7 -2
  3. package/.windsurfrules +7 -2
  4. package/SKILL.md +7 -2
  5. package/dist/advanced.cjs +981 -170
  6. package/dist/advanced.d.cts +35 -1
  7. package/dist/advanced.d.ts +35 -1
  8. package/dist/advanced.js +980 -170
  9. package/dist/auto.cjs +915 -171
  10. package/dist/auto.js +93 -93
  11. package/dist/auto.mjs +915 -171
  12. package/dist/cli.cjs +136 -134
  13. package/dist/index.cjs +981 -170
  14. package/dist/index.d.cts +35 -1
  15. package/dist/index.d.ts +35 -1
  16. package/dist/index.js +980 -170
  17. package/docs/ai-integration.md +7 -2
  18. package/docs/language-reference.md +39 -8
  19. package/package.json +2 -2
  20. package/src/advanced.ts +6 -1
  21. package/src/boxes-and-lines/layout-grouped.ts +909 -0
  22. package/src/boxes-and-lines/layout-search.ts +18 -1
  23. package/src/boxes-and-lines/parser.ts +3 -2
  24. package/src/c4/layout.ts +2 -1
  25. package/src/c4/parser.ts +3 -2
  26. package/src/c4/renderer.ts +3 -2
  27. package/src/cli.ts +68 -0
  28. package/src/er/parser.ts +2 -1
  29. package/src/er/renderer.ts +2 -1
  30. package/src/gantt/parser.ts +3 -5
  31. package/src/gantt/renderer.ts +7 -6
  32. package/src/index.ts +6 -1
  33. package/src/infra/parser.ts +2 -1
  34. package/src/infra/renderer.ts +4 -3
  35. package/src/journey-map/parser.ts +27 -8
  36. package/src/journey-map/renderer.ts +26 -9
  37. package/src/kanban/parser.ts +4 -3
  38. package/src/kanban/renderer.ts +6 -5
  39. package/src/map/completion.ts +116 -0
  40. package/src/map/layout.ts +4 -3
  41. package/src/map/parser.ts +3 -2
  42. package/src/mindmap/parser.ts +4 -3
  43. package/src/mindmap/renderer.ts +2 -1
  44. package/src/org/layout.ts +3 -2
  45. package/src/org/parser.ts +3 -2
  46. package/src/org/renderer.ts +5 -4
  47. package/src/pert/parser.ts +3 -2
  48. package/src/sequence/renderer.ts +10 -12
  49. package/src/sequence/tag-resolution.ts +2 -1
  50. package/src/sitemap/layout.ts +3 -2
  51. package/src/sitemap/parser.ts +36 -2
  52. package/src/sitemap/renderer.ts +12 -6
  53. package/src/timeline/renderer.ts +4 -3
  54. package/src/utils/legend-d3.ts +4 -3
  55. package/src/utils/legend-svg.ts +3 -2
  56. package/src/utils/tag-groups.ts +28 -7
  57. package/src/visualizations/parse.ts +2 -1
@@ -39,6 +39,23 @@ export interface TagGroup {
39
39
  readonly lineNumber: number;
40
40
  }
41
41
 
42
+ /**
43
+ * DOM-safe key for a tag group — used wherever the group name becomes a
44
+ * `data-tag-*` attribute suffix, an entity metadata key, or an `active-tag`
45
+ * match target. For a single-identifier name (the only form the parser used to
46
+ * accept) this is exactly `name.toLowerCase()`, so swapping it in for existing
47
+ * diagrams is byte-identical. A quoted multi-word name (`tag "Trust Zone" as tz`)
48
+ * slugs to a hyphenated identifier (`trust-zone`) so it never produces an
49
+ * invalid DOM attribute name; the original `name` is kept for the legend label.
50
+ */
51
+ export function tagAttrKey(name: string): string {
52
+ return name
53
+ .trim()
54
+ .toLowerCase()
55
+ .replace(/[^a-z0-9_-]+/g, '-')
56
+ .replace(/^-+|-+$/g, '');
57
+ }
58
+
42
59
  /** Result of matching a tag block heading */
43
60
  interface TagBlockMatch {
44
61
  name: string;
@@ -450,12 +467,12 @@ export function resolveTagColor(
450
467
  if (!activeGroupName) return undefined;
451
468
 
452
469
  const group = tagGroups.find(
453
- (g) => g.name.toLowerCase() === activeGroupName.toLowerCase()
470
+ (g) => tagAttrKey(g.name) === tagAttrKey(activeGroupName)
454
471
  );
455
472
  if (!group) return undefined;
456
473
 
457
474
  const metaValue =
458
- metadata[group.name.toLowerCase()] ??
475
+ metadata[tagAttrKey(group.name)] ??
459
476
  (isContainer ? undefined : group.defaultValue);
460
477
  if (!metaValue) return '#999999';
461
478
 
@@ -489,7 +506,7 @@ export function validateTagValues(
489
506
  if (tagGroups.length === 0) return;
490
507
 
491
508
  const groupMap = new Map<string, TagGroup>();
492
- for (const g of tagGroups) groupMap.set(g.name.toLowerCase(), g);
509
+ for (const g of tagGroups) groupMap.set(tagAttrKey(g.name), g);
493
510
 
494
511
  for (const entity of entities) {
495
512
  for (const [key, value] of Object.entries(entity.metadata)) {
@@ -557,10 +574,14 @@ export function validateTagGroupNames(
557
574
  `'none' is a reserved keyword and cannot be used as a tag group name`
558
575
  );
559
576
  }
560
- if (!VALID_TAG_IDENT_RE.test(group.name)) {
577
+ // A single-identifier name is valid as-is; a multi-word name is valid when
578
+ // quoted (`tag "Trust Zone" as tz`) — it slugs to a DOM-safe key while the
579
+ // original text stays the legend label. Reject only names whose slug is
580
+ // not a usable identifier (e.g. all-punctuation, or leading digit).
581
+ if (!VALID_TAG_IDENT_RE.test(tagAttrKey(group.name))) {
561
582
  report(
562
583
  group.lineNumber,
563
- `Tag group name "${group.name}" contains invalid characters — use a single identifier (letters, digits, underscore, hyphen)`
584
+ `Tag group name "${group.name}" can't form a valid key — use letters, digits, underscore, or hyphen, and quote a multi-word name (e.g. tag "Trust Zone" as tz)`
564
585
  );
565
586
  }
566
587
  if (group.alias != null && !VALID_TAG_IDENT_RE.test(group.alias)) {
@@ -591,7 +612,7 @@ export function validateTagGroupNames(
591
612
  export function cascadeTagMetadata<
592
613
  T extends { metadata: Record<string, string>; children: readonly T[] },
593
614
  >(roots: readonly T[], tagGroups: ReadonlyArray<{ name: string }>): void {
594
- const keys = tagGroups.map((g) => g.name.toLowerCase());
615
+ const keys = tagGroups.map((g) => tagAttrKey(g.name));
595
616
  if (keys.length === 0) return;
596
617
 
597
618
  const walk = (node: T, inherited: Record<string, string>): void => {
@@ -629,7 +650,7 @@ export function injectDefaultTagMetadata(
629
650
  for (const group of tagGroups) {
630
651
  if (group.defaultValue) {
631
652
  defaults.push({
632
- key: group.name.toLowerCase(),
653
+ key: tagAttrKey(group.name),
633
654
  value: group.defaultValue,
634
655
  });
635
656
  }
@@ -36,6 +36,7 @@ import {
36
36
  stripDefaultModifier,
37
37
  validateTagGroupNames,
38
38
  validateTagValues,
39
+ tagAttrKey,
39
40
  } from '../utils/tag-groups';
40
41
  import type { TagGroup } from '../utils/tag-groups';
41
42
  import type { Writable } from '../utils/brand';
@@ -1364,7 +1365,7 @@ function parseVisualizationFull(
1364
1365
  );
1365
1366
  for (const group of result.timelineTagGroups) {
1366
1367
  if (!group.defaultValue) continue;
1367
- const key = group.name.toLowerCase();
1368
+ const key = tagAttrKey(group.name);
1368
1369
  for (const event of result.timelineEvents) {
1369
1370
  if (!event.metadata[key]) {
1370
1371
  event.metadata[key] = group.defaultValue;