@diagrammo/dgmo 0.32.0 → 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 +55 -24
  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
package/dist/advanced.js CHANGED
@@ -1253,6 +1253,9 @@ var init_parsing = __esm({
1253
1253
  });
1254
1254
 
1255
1255
  // src/utils/tag-groups.ts
1256
+ function tagAttrKey(name) {
1257
+ return name.trim().toLowerCase().replace(/[^a-z0-9_-]+/g, "-").replace(/^-+|-+$/g, "");
1258
+ }
1256
1259
  function stripDefaultModifier(text) {
1257
1260
  if (/\bdefault\s*$/.test(text)) {
1258
1261
  return { text: text.replace(/\s+default\s*$/, "").trim(), isDefault: true };
@@ -1398,17 +1401,17 @@ function parseTagDeclaration(line11) {
1398
1401
  function resolveTagColor(metadata, tagGroups, activeGroupName, isContainer) {
1399
1402
  if (!activeGroupName) return void 0;
1400
1403
  const group = tagGroups.find(
1401
- (g) => g.name.toLowerCase() === activeGroupName.toLowerCase()
1404
+ (g) => tagAttrKey(g.name) === tagAttrKey(activeGroupName)
1402
1405
  );
1403
1406
  if (!group) return void 0;
1404
- const metaValue = metadata[group.name.toLowerCase()] ?? (isContainer ? void 0 : group.defaultValue);
1407
+ const metaValue = metadata[tagAttrKey(group.name)] ?? (isContainer ? void 0 : group.defaultValue);
1405
1408
  if (!metaValue) return "#999999";
1406
1409
  return group.entries.find((e) => e.value.toLowerCase() === metaValue.toLowerCase())?.color ?? "#999999";
1407
1410
  }
1408
1411
  function validateTagValues(entities, tagGroups, pushWarning, suggestFn) {
1409
1412
  if (tagGroups.length === 0) return;
1410
1413
  const groupMap = /* @__PURE__ */ new Map();
1411
- for (const g of tagGroups) groupMap.set(g.name.toLowerCase(), g);
1414
+ for (const g of tagGroups) groupMap.set(tagAttrKey(g.name), g);
1412
1415
  for (const entity of entities) {
1413
1416
  for (const [key, value] of Object.entries(entity.metadata)) {
1414
1417
  const group = groupMap.get(key);
@@ -1445,10 +1448,10 @@ function validateTagGroupNames(tagGroups, pushWarning, pushError) {
1445
1448
  `'none' is a reserved keyword and cannot be used as a tag group name`
1446
1449
  );
1447
1450
  }
1448
- if (!VALID_TAG_IDENT_RE.test(group.name)) {
1451
+ if (!VALID_TAG_IDENT_RE.test(tagAttrKey(group.name))) {
1449
1452
  report(
1450
1453
  group.lineNumber,
1451
- `Tag group name "${group.name}" contains invalid characters \u2014 use a single identifier (letters, digits, underscore, hyphen)`
1454
+ `Tag group name "${group.name}" can't form a valid key \u2014 use letters, digits, underscore, or hyphen, and quote a multi-word name (e.g. tag "Trust Zone" as tz)`
1452
1455
  );
1453
1456
  }
1454
1457
  if (group.alias != null && !VALID_TAG_IDENT_RE.test(group.alias)) {
@@ -1460,7 +1463,7 @@ function validateTagGroupNames(tagGroups, pushWarning, pushError) {
1460
1463
  }
1461
1464
  }
1462
1465
  function cascadeTagMetadata(roots, tagGroups) {
1463
- const keys = tagGroups.map((g) => g.name.toLowerCase());
1466
+ const keys = tagGroups.map((g) => tagAttrKey(g.name));
1464
1467
  if (keys.length === 0) return;
1465
1468
  const walk = (node, inherited) => {
1466
1469
  const childInherited = { ...inherited };
@@ -1481,7 +1484,7 @@ function injectDefaultTagMetadata(entities, tagGroups, skip) {
1481
1484
  for (const group of tagGroups) {
1482
1485
  if (group.defaultValue) {
1483
1486
  defaults.push({
1484
- key: group.name.toLowerCase(),
1487
+ key: tagAttrKey(group.name),
1485
1488
  value: group.defaultValue
1486
1489
  });
1487
1490
  }
@@ -2872,7 +2875,7 @@ function parseVisualizationFull(content, palette) {
2872
2875
  );
2873
2876
  for (const group of result.timelineTagGroups) {
2874
2877
  if (!group.defaultValue) continue;
2875
- const key = group.name.toLowerCase();
2878
+ const key = tagAttrKey(group.name);
2876
2879
  for (const event of result.timelineEvents) {
2877
2880
  if (!event.metadata[key]) {
2878
2881
  event.metadata[key] = group.defaultValue;
@@ -5268,7 +5271,7 @@ function renderLegendD3(container, config, state, palette, isDark, callbacks, co
5268
5271
  };
5269
5272
  }
5270
5273
  function renderCapsule(parent, capsule, palette, groupBg, pillBorder, isDark, callbacks) {
5271
- const g = parent.append("g").attr("transform", `translate(${capsule.x},${capsule.y})`).attr("data-legend-group", capsule.groupName.toLowerCase()).style("cursor", "pointer");
5274
+ const g = parent.append("g").attr("transform", `translate(${capsule.x},${capsule.y})`).attr("data-legend-group", tagAttrKey(capsule.groupName)).style("cursor", "pointer");
5272
5275
  g.append("rect").attr("width", capsule.width).attr("height", capsule.height).attr("rx", LEGEND_HEIGHT / 2).attr("fill", groupBg);
5273
5276
  const pill = capsule.pill;
5274
5277
  g.append("rect").attr("x", pill.x).attr("y", pill.y).attr("width", pill.width).attr("height", pill.height).attr("rx", pill.height / 2).attr("fill", palette.bg);
@@ -5276,7 +5279,7 @@ function renderCapsule(parent, capsule, palette, groupBg, pillBorder, isDark, ca
5276
5279
  g.append("text").attr("x", pill.x + pill.width / 2).attr("y", LEGEND_HEIGHT / 2).attr("text-anchor", "middle").call(centerText).attr("font-size", LEGEND_PILL_FONT_SIZE).attr("font-weight", 500).attr("fill", palette.text).attr("pointer-events", "none").attr("font-family", FONT_FAMILY).text(capsule.groupName);
5277
5280
  if (capsule.gradient) {
5278
5281
  const gr = capsule.gradient;
5279
- const gradId = `dgmo-legend-ramp-${capsule.groupName.toLowerCase().replace(/[^a-z0-9]+/g, "-")}`;
5282
+ const gradId = `dgmo-legend-ramp-${tagAttrKey(capsule.groupName).replace(/[^a-z0-9]+/g, "-")}`;
5280
5283
  const def = g.append("defs").append("linearGradient").attr("id", gradId);
5281
5284
  for (const stop of valueRampStops(gr.low, gr.high, { isDark })) {
5282
5285
  def.append("stop").attr("offset", `${stop.offset * 100}%`).attr("stop-color", stop.color);
@@ -5306,7 +5309,7 @@ function renderCapsule(parent, capsule, palette, groupBg, pillBorder, isDark, ca
5306
5309
  }
5307
5310
  }
5308
5311
  function renderPill(parent, pill, palette, groupBg, callbacks) {
5309
- const g = parent.append("g").attr("transform", `translate(${pill.x},${pill.y})`).attr("data-legend-group", pill.groupName.toLowerCase()).style("cursor", "pointer");
5312
+ const g = parent.append("g").attr("transform", `translate(${pill.x},${pill.y})`).attr("data-legend-group", tagAttrKey(pill.groupName)).style("cursor", "pointer");
5310
5313
  g.append("rect").attr("width", pill.width).attr("height", pill.height).attr("rx", pill.height / 2).attr("fill", groupBg);
5311
5314
  g.append("text").attr("x", pill.width / 2).attr("y", pill.height / 2).attr("text-anchor", "middle").call(centerText).attr("font-size", LEGEND_PILL_FONT_SIZE).attr("font-weight", 500).attr("fill", palette.textMuted).attr("pointer-events", "none").attr("font-family", FONT_FAMILY).text(pill.groupName);
5312
5315
  if (callbacks?.onGroupToggle) {
@@ -5412,6 +5415,7 @@ var LEGEND_TEXT_DY;
5412
5415
  var init_legend_d3 = __esm({
5413
5416
  "src/utils/legend-d3.ts"() {
5414
5417
  "use strict";
5418
+ init_tag_groups();
5415
5419
  init_legend_constants();
5416
5420
  init_legend_layout();
5417
5421
  init_color_utils();
@@ -9333,7 +9337,7 @@ function parseERDiagram(content, palette) {
9333
9337
  );
9334
9338
  for (const group of result.tagGroups) {
9335
9339
  if (!group.defaultValue) continue;
9336
- const key = group.name.toLowerCase();
9340
+ const key = tagAttrKey(group.name);
9337
9341
  for (const table of result.tables) {
9338
9342
  if (!table.metadata[key]) {
9339
9343
  const mutableMeta = tableMetadataMap.get(table.id);
@@ -9563,12 +9567,12 @@ function parseOrg(content, palette) {
9563
9567
  if (tagBlockMatch.alias) {
9564
9568
  metaAliasMap.set(
9565
9569
  normalizeName(tagBlockMatch.alias),
9566
- tagBlockMatch.name.toLowerCase()
9570
+ tagAttrKey(tagBlockMatch.name)
9567
9571
  );
9568
9572
  }
9569
9573
  metaAliasMap.set(
9570
9574
  normalizeName(tagBlockMatch.name),
9571
- tagBlockMatch.name.toLowerCase()
9575
+ tagAttrKey(tagBlockMatch.name)
9572
9576
  );
9573
9577
  result.tagGroups.push(currentTagGroup);
9574
9578
  continue;
@@ -9954,12 +9958,12 @@ function parseSitemap(content, palette) {
9954
9958
  if (tagBlockMatch.alias) {
9955
9959
  metaAliasMap.set(
9956
9960
  normalizeName(tagBlockMatch.alias),
9957
- tagBlockMatch.name.toLowerCase()
9961
+ tagAttrKey(tagBlockMatch.name)
9958
9962
  );
9959
9963
  }
9960
9964
  metaAliasMap.set(
9961
9965
  normalizeName(tagBlockMatch.name),
9962
- tagBlockMatch.name.toLowerCase()
9966
+ tagAttrKey(tagBlockMatch.name)
9963
9967
  );
9964
9968
  result.tagGroups.push(newTagGroup);
9965
9969
  continue;
@@ -10129,6 +10133,14 @@ function parseSitemap(content, palette) {
10129
10133
  continue;
10130
10134
  }
10131
10135
  }
10136
+ const enclosingContainer = nestedPageInsideContainer(indent, indentStack);
10137
+ if (enclosingContainer) {
10138
+ pushError(
10139
+ lineNumber,
10140
+ `Pages inside container '[${enclosingContainer}]' cannot have indented sub-pages. Containers hold a flat list of pages.`
10141
+ );
10142
+ continue;
10143
+ }
10132
10144
  const node = parseNodeLabel2(
10133
10145
  trimmed,
10134
10146
  lineNumber,
@@ -10276,6 +10288,16 @@ function attachNode2(node, indent, indentStack, result) {
10276
10288
  }
10277
10289
  indentStack.push({ node, indent });
10278
10290
  }
10291
+ function nestedPageInsideContainer(indent, indentStack) {
10292
+ let idx = indentStack.length - 1;
10293
+ while (idx >= 0 && indentStack[idx].indent >= indent) idx--;
10294
+ if (idx < 0) return null;
10295
+ if (indentStack[idx].node.isContainer) return null;
10296
+ for (let k = idx - 1; k >= 0; k--) {
10297
+ if (indentStack[k].node.isContainer) return indentStack[k].node.label;
10298
+ }
10299
+ return null;
10300
+ }
10279
10301
  function findParentNode(indent, indentStack) {
10280
10302
  for (let i = indentStack.length - 1; i >= 0; i--) {
10281
10303
  if (indentStack[i].indent < indent) {
@@ -10859,12 +10881,12 @@ function parsePert(content, parseOpts = {}) {
10859
10881
  if (tagBlockMatch.alias) {
10860
10882
  metaAliasMap.set(
10861
10883
  tagBlockMatch.alias.toLowerCase(),
10862
- tagBlockMatch.name.toLowerCase()
10884
+ tagAttrKey(tagBlockMatch.name)
10863
10885
  );
10864
10886
  }
10865
10887
  metaAliasMap.set(
10866
10888
  normalizeName(tagBlockMatch.name),
10867
- tagBlockMatch.name.toLowerCase()
10889
+ tagAttrKey(tagBlockMatch.name)
10868
10890
  );
10869
10891
  tagGroups.push(currentTagGroup);
10870
10892
  if (tagBlockMatch.inlineValues) {
@@ -12329,10 +12351,10 @@ function emitCapsule(capsule, palette, groupBg, pillBorder) {
12329
12351
  `<g data-legend-entry="${esc(entry.value.toLowerCase())}" data-series-name="${esc(entry.value)}" style="cursor:pointer"><circle cx="${entry.dotCx}" cy="${entry.dotCy}" r="${LEGEND_DOT_R}" fill="${esc(entry.color)}"/><text x="${entry.textX}" y="${entry.dotCy + LEGEND_ENTRY_FONT_SIZE / 2 - 1}" font-size="${LEGEND_ENTRY_FONT_SIZE}" fill="${esc(palette.textMuted)}" font-family="${esc(FONT_FAMILY)}">${esc(label)}</text></g>`
12330
12352
  );
12331
12353
  }
12332
- return `<g transform="translate(${capsule.x},${capsule.y})" data-legend-group="${esc(capsule.groupName.toLowerCase())}" style="cursor:pointer">${inner.join("")}</g>`;
12354
+ return `<g transform="translate(${capsule.x},${capsule.y})" data-legend-group="${esc(tagAttrKey(capsule.groupName))}" style="cursor:pointer">${inner.join("")}</g>`;
12333
12355
  }
12334
12356
  function emitPill(pill, palette, groupBg) {
12335
- return `<g transform="translate(${pill.x},${pill.y})" data-legend-group="${esc(pill.groupName.toLowerCase())}" style="cursor:pointer"><rect width="${pill.width}" height="${pill.height}" rx="${pill.height / 2}" fill="${esc(groupBg)}"/><text x="${pill.width / 2}" y="${pill.height / 2 + LEGEND_PILL_FONT_SIZE / 2 - 2}" font-size="${LEGEND_PILL_FONT_SIZE}" font-weight="500" fill="${esc(palette.textMuted)}" text-anchor="middle" font-family="${esc(FONT_FAMILY)}">${esc(pill.groupName)}</text></g>`;
12357
+ return `<g transform="translate(${pill.x},${pill.y})" data-legend-group="${esc(tagAttrKey(pill.groupName))}" style="cursor:pointer"><rect width="${pill.width}" height="${pill.height}" rx="${pill.height / 2}" fill="${esc(groupBg)}"/><text x="${pill.width / 2}" y="${pill.height / 2 + LEGEND_PILL_FONT_SIZE / 2 - 2}" font-size="${LEGEND_PILL_FONT_SIZE}" font-weight="500" fill="${esc(palette.textMuted)}" text-anchor="middle" font-family="${esc(FONT_FAMILY)}">${esc(pill.groupName)}</text></g>`;
12336
12358
  }
12337
12359
  function renderLegendSvg(groups, options) {
12338
12360
  if (groups.length === 0) return { svg: "", height: 0, width: 0 };
@@ -12393,6 +12415,7 @@ function renderLegendSvgFromConfig(config, state, palette, containerWidth) {
12393
12415
  var init_legend_svg = __esm({
12394
12416
  "src/utils/legend-svg.ts"() {
12395
12417
  "use strict";
12418
+ init_tag_groups();
12396
12419
  init_legend_constants();
12397
12420
  init_legend_layout();
12398
12421
  init_color_utils();
@@ -15101,12 +15124,12 @@ function parseKanban(content, palette) {
15101
15124
  if (tagBlockMatch.alias) {
15102
15125
  metaAliasMap.set(
15103
15126
  normalizeName(tagBlockMatch.alias),
15104
- tagBlockMatch.name.toLowerCase()
15127
+ tagAttrKey(tagBlockMatch.name)
15105
15128
  );
15106
15129
  }
15107
15130
  metaAliasMap.set(
15108
15131
  normalizeName(tagBlockMatch.name),
15109
- tagBlockMatch.name.toLowerCase()
15132
+ tagAttrKey(tagBlockMatch.name)
15110
15133
  );
15111
15134
  result.tagGroups.push(currentTagGroup);
15112
15135
  continue;
@@ -15295,7 +15318,7 @@ function parseKanban(content, palette) {
15295
15318
  }
15296
15319
  for (const group of result.tagGroups) {
15297
15320
  const values = new Set(group.entries.map((e) => e.value.toLowerCase()));
15298
- tagValueSets.set(group.name.toLowerCase(), values);
15321
+ tagValueSets.set(tagAttrKey(group.name), values);
15299
15322
  }
15300
15323
  for (const col of result.columns) {
15301
15324
  if (col.wipLimit != null && col.cards.length > col.wipLimit) {
@@ -15557,12 +15580,12 @@ function parseC4(content, palette) {
15557
15580
  if (tagBlockMatch.alias) {
15558
15581
  metaAliasMap.set(
15559
15582
  normalizeName(tagBlockMatch.alias),
15560
- tagBlockMatch.name.toLowerCase()
15583
+ tagAttrKey(tagBlockMatch.name)
15561
15584
  );
15562
15585
  }
15563
15586
  metaAliasMap.set(
15564
15587
  normalizeName(tagBlockMatch.name),
15565
- tagBlockMatch.name.toLowerCase()
15588
+ tagAttrKey(tagBlockMatch.name)
15566
15589
  );
15567
15590
  result.tagGroups.push(currentTagGroup);
15568
15591
  continue;
@@ -17245,7 +17268,7 @@ function parseInfra(content) {
17245
17268
  }
17246
17269
  for (const tg of result.tagGroups) {
17247
17270
  if (!tg.defaultValue) continue;
17248
- const key = (tg.alias ?? tg.name).toLowerCase();
17271
+ const key = tagAttrKey(tg.alias ?? tg.name);
17249
17272
  for (const node of result.nodes) {
17250
17273
  if (node.isEdge) continue;
17251
17274
  if (!(key in node.tags)) {
@@ -17382,6 +17405,7 @@ var CONNECTION_RE, SIMPLE_CONNECTION_RE, ASYNC_CONNECTION_RE, ASYNC_SIMPLE_CONNE
17382
17405
  var init_parser15 = __esm({
17383
17406
  "src/infra/parser.ts"() {
17384
17407
  "use strict";
17408
+ init_tag_groups();
17385
17409
  init_diagnostics();
17386
17410
  init_description_helpers();
17387
17411
  init_arrows();
@@ -17927,13 +17951,10 @@ function parseGantt(content, palette) {
17927
17951
  if (tagMatch.alias) {
17928
17952
  metaAliasMap.set(
17929
17953
  normalizeName(tagMatch.alias),
17930
- tagMatch.name.toLowerCase()
17954
+ tagAttrKey(tagMatch.name)
17931
17955
  );
17932
17956
  }
17933
- metaAliasMap.set(
17934
- normalizeName(tagMatch.name),
17935
- tagMatch.name.toLowerCase()
17936
- );
17957
+ metaAliasMap.set(normalizeName(tagMatch.name), tagAttrKey(tagMatch.name));
17937
17958
  continue;
17938
17959
  }
17939
17960
  const topWorkweekMatch = line11.match(WORKWEEK_RE);
@@ -18859,7 +18880,7 @@ function parseMap(content, palette) {
18859
18880
  const aliasMap = /* @__PURE__ */ new Map();
18860
18881
  const open = { tag: null, route: null, poi: null };
18861
18882
  const registry = () => withTagAliases(MAP_REGISTRY, new Set(aliasMap.keys()));
18862
- const tagGroupNames = () => new Set(tagGroups.map((g) => g.name.toLowerCase()));
18883
+ const tagGroupNames = () => new Set(tagGroups.map((g) => tagAttrKey(g.name)));
18863
18884
  const closeBlocks = () => {
18864
18885
  open.tag = null;
18865
18886
  open.route = null;
@@ -19043,7 +19064,7 @@ function parseMap(content, palette) {
19043
19064
  entries: [],
19044
19065
  lineNumber: line11
19045
19066
  };
19046
- if (m.alias) aliasMap.set(m.alias.toLowerCase(), m.name.toLowerCase());
19067
+ if (m.alias) aliasMap.set(m.alias.toLowerCase(), tagAttrKey(m.name));
19047
19068
  tagGroups.push(group);
19048
19069
  open.tag = group;
19049
19070
  if (m.inlineValues?.length) {
@@ -19576,12 +19597,12 @@ function parseBoxesAndLines(content, palette) {
19576
19597
  if (tagBlockMatch.alias) {
19577
19598
  metaAliasMap.set(
19578
19599
  normalizeName(tagBlockMatch.alias),
19579
- tagBlockMatch.name.toLowerCase()
19600
+ tagAttrKey(tagBlockMatch.name)
19580
19601
  );
19581
19602
  }
19582
19603
  metaAliasMap.set(
19583
19604
  normalizeName(tagBlockMatch.name),
19584
- tagBlockMatch.name.toLowerCase()
19605
+ tagAttrKey(tagBlockMatch.name)
19585
19606
  );
19586
19607
  if (tagBlockMatch.inlineValues) {
19587
19608
  for (const rawVal of tagBlockMatch.inlineValues) {
@@ -20322,12 +20343,12 @@ function parseMindmap(content, palette) {
20322
20343
  if (tagBlockMatch.alias) {
20323
20344
  aliasMap.set(
20324
20345
  tagBlockMatch.alias.toLowerCase(),
20325
- tagBlockMatch.name.toLowerCase()
20346
+ tagAttrKey(tagBlockMatch.name)
20326
20347
  );
20327
20348
  }
20328
20349
  aliasMap.set(
20329
- tagBlockMatch.name.toLowerCase(),
20330
- tagBlockMatch.name.toLowerCase()
20350
+ tagAttrKey(tagBlockMatch.name),
20351
+ tagAttrKey(tagBlockMatch.name)
20331
20352
  );
20332
20353
  result.tagGroups.push(currentTagGroup);
20333
20354
  continue;
@@ -22036,7 +22057,18 @@ function parseJourneyMap(content, palette) {
22036
22057
  palette
22037
22058
  ) ?? void 0;
22038
22059
  } else {
22039
- personaName = afterKeyword;
22060
+ const { label, colorName } = peelTrailingColorName(afterKeyword);
22061
+ if (colorName) {
22062
+ personaName = label;
22063
+ personaColor = resolveColorWithDiagnostic(
22064
+ colorName,
22065
+ lineNumber,
22066
+ result.diagnostics,
22067
+ palette
22068
+ ) ?? void 0;
22069
+ } else {
22070
+ personaName = afterKeyword;
22071
+ }
22040
22072
  }
22041
22073
  }
22042
22074
  if (!personaName) {
@@ -22079,12 +22111,12 @@ function parseJourneyMap(content, palette) {
22079
22111
  if (tagBlockMatch.alias) {
22080
22112
  aliasMap.set(
22081
22113
  tagBlockMatch.alias.toLowerCase(),
22082
- tagBlockMatch.name.toLowerCase()
22114
+ tagAttrKey(tagBlockMatch.name)
22083
22115
  );
22084
22116
  }
22085
22117
  aliasMap.set(
22086
- tagBlockMatch.name.toLowerCase(),
22087
- tagBlockMatch.name.toLowerCase()
22118
+ tagAttrKey(tagBlockMatch.name),
22119
+ tagAttrKey(tagBlockMatch.name)
22088
22120
  );
22089
22121
  result.tagGroups.push(currentTagGroup);
22090
22122
  continue;
@@ -22228,7 +22260,7 @@ function parseJourneyMap(content, palette) {
22228
22260
  }
22229
22261
  for (const group of result.tagGroups) {
22230
22262
  const values = new Set(group.entries.map((e) => e.value.toLowerCase()));
22231
- tagValueSets.set(group.name.toLowerCase(), values);
22263
+ tagValueSets.set(tagAttrKey(group.name), values);
22232
22264
  }
22233
22265
  const allSteps = hasPhases ? result.phases.flatMap((p) => p.steps) : result.steps;
22234
22266
  for (const step of allSteps) {
@@ -24215,7 +24247,7 @@ function computeLegendGroups(tagGroups, showEyeIcons, usedValuesByGroup) {
24215
24247
  const groups = [];
24216
24248
  for (const group of tagGroups) {
24217
24249
  if (group.entries.length === 0) continue;
24218
- const usedValues = usedValuesByGroup?.get(group.name.toLowerCase());
24250
+ const usedValues = usedValuesByGroup?.get(tagAttrKey(group.name));
24219
24251
  const visibleEntries = usedValues ? group.entries.filter((e) => usedValues.has(e.value.toLowerCase())) : group.entries;
24220
24252
  if (visibleEntries.length === 0) continue;
24221
24253
  const pillWidth2 = measureLegendText(group.name, LEGEND_PILL_FONT_SIZE) + LEGEND_PILL_PAD;
@@ -24828,7 +24860,7 @@ function layoutOrg(parsed, hiddenCounts, activeTagGroup, hiddenAttributes, expan
24828
24860
  const totalHeight = finalMaxY - minY + MARGIN * 2;
24829
24861
  const usedValuesByGroup = /* @__PURE__ */ new Map();
24830
24862
  for (const group of parsed.tagGroups) {
24831
- const key = group.name.toLowerCase();
24863
+ const key = tagAttrKey(group.name);
24832
24864
  const used = /* @__PURE__ */ new Set();
24833
24865
  const walk = (node) => {
24834
24866
  if (!node.isContainer && node.metadata[key]) {
@@ -24887,6 +24919,7 @@ var init_layout = __esm({
24887
24919
  "src/org/layout.ts"() {
24888
24920
  "use strict";
24889
24921
  init_tag_groups();
24922
+ init_tag_groups();
24890
24923
  init_legend_constants();
24891
24924
  init_text_measure();
24892
24925
  LABEL_FONT_SIZE = 13;
@@ -25247,14 +25280,14 @@ function renderOrg(container, parsed, layout, palette, isDark, onClickItem, expo
25247
25280
  const contentG = mainG.append("g").attr("transform", `translate(0, ${contentYShift})`);
25248
25281
  const displayNames = /* @__PURE__ */ new Map();
25249
25282
  for (const group of parsed.tagGroups) {
25250
- displayNames.set(group.name.toLowerCase(), group.name);
25283
+ displayNames.set(tagAttrKey(group.name), group.name);
25251
25284
  }
25252
25285
  const rootNodeIds = new Set(parsed.roots.map((r) => r.id));
25253
25286
  const colorOff = parsed.options?.["color"] === "off";
25254
25287
  for (const c of layout.containers) {
25255
25288
  const cG = contentG.append("g").attr("transform", `translate(${c.x}, ${c.y})`).attr("class", "org-container").attr("data-line-number", String(c.lineNumber));
25256
25289
  if (activeTagGroup) {
25257
- const tagKey = activeTagGroup.toLowerCase();
25290
+ const tagKey = tagAttrKey(activeTagGroup);
25258
25291
  const metaValue = c.tagMetadata[tagKey];
25259
25292
  if (metaValue) {
25260
25293
  cG.attr(`data-tag-${tagKey}`, metaValue.toLowerCase());
@@ -25336,7 +25369,7 @@ function renderOrg(container, parsed, layout, palette, isDark, onClickItem, expo
25336
25369
  if (containerNodeIds.has(node.id)) continue;
25337
25370
  const nodeG = contentG.append("g").attr("transform", `translate(${node.x - node.width / 2}, ${node.y})`).attr("class", "org-node").attr("data-line-number", String(node.lineNumber));
25338
25371
  if (activeTagGroup) {
25339
- const tagKey = activeTagGroup.toLowerCase();
25372
+ const tagKey = tagAttrKey(activeTagGroup);
25340
25373
  const metaValue = node.tagMetadata[tagKey];
25341
25374
  if (metaValue) {
25342
25375
  nodeG.attr(`data-tag-${tagKey}`, metaValue.toLowerCase());
@@ -25509,7 +25542,7 @@ function renderOrg(container, parsed, layout, palette, isDark, onClickItem, expo
25509
25542
  const computedLayout = legendHandle.getLayout();
25510
25543
  if (computedLayout.activeCapsule?.addonX != null) {
25511
25544
  const capsule = computedLayout.activeCapsule;
25512
- const groupKey2 = capsule.groupName.toLowerCase();
25545
+ const groupKey2 = tagAttrKey(capsule.groupName);
25513
25546
  const isHidden = hiddenAttributes?.has(groupKey2) ?? false;
25514
25547
  const activeGroupEl = legendParentBase.select(
25515
25548
  `[data-legend-group="${groupKey2}"]`
@@ -25548,6 +25581,7 @@ var DIAGRAM_PADDING, MAX_SCALE, TITLE_HEIGHT, ANCESTOR_DOT_R, ANCESTOR_LABEL_FON
25548
25581
  var init_renderer = __esm({
25549
25582
  "src/org/renderer.ts"() {
25550
25583
  "use strict";
25584
+ init_tag_groups();
25551
25585
  init_fonts();
25552
25586
  init_export_container();
25553
25587
  init_scaling();
@@ -25633,7 +25667,7 @@ function computeLegendGroups2(tagGroups, usedValuesByGroup) {
25633
25667
  const groups = [];
25634
25668
  for (const group of tagGroups) {
25635
25669
  if (group.entries.length === 0) continue;
25636
- const usedValues = usedValuesByGroup?.get(group.name.toLowerCase());
25670
+ const usedValues = usedValuesByGroup?.get(tagAttrKey(group.name));
25637
25671
  const visibleEntries = usedValues ? group.entries.filter((e) => usedValues.has(e.value.toLowerCase())) : group.entries;
25638
25672
  if (visibleEntries.length === 0) continue;
25639
25673
  const pillWidth2 = measureLegendText(group.name, LEGEND_PILL_FONT_SIZE) + LEGEND_PILL_PAD;
@@ -26113,7 +26147,7 @@ function layoutSitemap(parsed, hiddenCounts, activeTagGroup, hiddenAttributes, e
26113
26147
  totalHeight += MARGIN2;
26114
26148
  const usedValuesByGroup = /* @__PURE__ */ new Map();
26115
26149
  for (const group of parsed.tagGroups) {
26116
- const key = group.name.toLowerCase();
26150
+ const key = tagAttrKey(group.name);
26117
26151
  const used = /* @__PURE__ */ new Set();
26118
26152
  const walk = (node) => {
26119
26153
  if (!node.isContainer && node.metadata[key]) {
@@ -26158,6 +26192,7 @@ var init_layout2 = __esm({
26158
26192
  "src/sitemap/layout.ts"() {
26159
26193
  "use strict";
26160
26194
  init_tag_groups();
26195
+ init_tag_groups();
26161
26196
  init_legend_constants();
26162
26197
  init_text_measure();
26163
26198
  LABEL_FONT_SIZE3 = 13;
@@ -26427,10 +26462,10 @@ function renderSitemap(container, parsed, layout, palette, isDark, onClickItem,
26427
26462
  const displayNames = /* @__PURE__ */ new Map();
26428
26463
  const tagColors = /* @__PURE__ */ new Map();
26429
26464
  for (const group of parsed.tagGroups) {
26430
- displayNames.set(group.name.toLowerCase(), group.name);
26465
+ displayNames.set(tagAttrKey(group.name), group.name);
26431
26466
  for (const entry of group.entries) {
26432
26467
  tagColors.set(
26433
- `${group.name.toLowerCase()}:${entry.value.toLowerCase()}`,
26468
+ `${tagAttrKey(group.name)}:${entry.value.toLowerCase()}`,
26434
26469
  entry.color
26435
26470
  );
26436
26471
  }
@@ -26447,7 +26482,7 @@ function renderSitemap(container, parsed, layout, palette, isDark, onClickItem,
26447
26482
  );
26448
26483
  }
26449
26484
  if (activeTagGroup) {
26450
- const tagKey = activeTagGroup.toLowerCase();
26485
+ const tagKey = tagAttrKey(activeTagGroup);
26451
26486
  const tagVal = c.tagMetadata[tagKey];
26452
26487
  if (tagVal) cG.attr(`data-tag-${tagKey}`, tagVal.toLowerCase());
26453
26488
  }
@@ -26510,7 +26545,7 @@ function renderSitemap(container, parsed, layout, palette, isDark, onClickItem,
26510
26545
  nodeG.style("cursor", "pointer").on("click", () => onClickItem(node.lineNumber));
26511
26546
  }
26512
26547
  if (activeTagGroup) {
26513
- const tagKey = activeTagGroup.toLowerCase();
26548
+ const tagKey = tagAttrKey(activeTagGroup);
26514
26549
  const tagVal = node.tagMetadata[tagKey];
26515
26550
  if (tagVal) nodeG.attr(`data-tag-${tagKey}`, tagVal.toLowerCase());
26516
26551
  }
@@ -26637,7 +26672,7 @@ function renderLegend(parent, legendGroups, palette, isDark, activeTagGroup, fix
26637
26672
  const computedLayout = legendHandle.getLayout();
26638
26673
  if (computedLayout.activeCapsule?.addonX != null) {
26639
26674
  const capsule = computedLayout.activeCapsule;
26640
- const groupKey2 = capsule.groupName.toLowerCase();
26675
+ const groupKey2 = tagAttrKey(capsule.groupName);
26641
26676
  const isHidden = hiddenAttributes?.has(groupKey2) ?? false;
26642
26677
  const activeGroupEl = parent.select(`[data-legend-group="${groupKey2}"]`);
26643
26678
  if (!activeGroupEl.empty()) {
@@ -26702,6 +26737,7 @@ var DIAGRAM_PADDING2, MAX_SCALE2, TITLE_HEIGHT2, ARROWHEAD_W, ARROWHEAD_H, EDGE_
26702
26737
  var init_renderer2 = __esm({
26703
26738
  "src/sitemap/renderer.ts"() {
26704
26739
  "use strict";
26740
+ init_tag_groups();
26705
26741
  init_fonts();
26706
26742
  init_color_utils();
26707
26743
  init_inline_markdown();
@@ -26839,7 +26875,7 @@ function resolveCardTagMeta(card, tagGroups, hiddenMetaGroups) {
26839
26875
  const meta = [];
26840
26876
  for (const group of tagGroups) {
26841
26877
  if (hiddenMetaGroups?.includes(group.name.toLowerCase())) continue;
26842
- const tagValue = card.tags[group.name.toLowerCase()];
26878
+ const tagValue = card.tags[tagAttrKey(group.name)];
26843
26879
  const value = tagValue ?? group.defaultValue;
26844
26880
  if (!value) continue;
26845
26881
  const entry = group.entries.find(
@@ -26859,7 +26895,7 @@ function resolveCardTagColor(card, tagGroups, activeTagGroup) {
26859
26895
  (g) => g.name.toLowerCase() === activeTagGroup.toLowerCase()
26860
26896
  );
26861
26897
  if (!group) return card.color;
26862
- const tagValue = card.tags[group.name.toLowerCase()];
26898
+ const tagValue = card.tags[tagAttrKey(group.name)];
26863
26899
  const value = tagValue ?? group.defaultValue;
26864
26900
  if (!value) return void 0;
26865
26901
  const entry = group.entries.find(
@@ -27169,7 +27205,7 @@ function renderKanban(container, parsed, palette, isDark, options) {
27169
27205
  );
27170
27206
  const cg = g.append("g").attr("class", "kanban-card").attr("data-card-id", card.id).attr("data-line-number", card.lineNumber);
27171
27207
  if (activeTagGroup) {
27172
- const tagKey = activeTagGroup.toLowerCase();
27208
+ const tagKey = tagAttrKey(activeTagGroup);
27173
27209
  const tagValue = card.tags[tagKey];
27174
27210
  const group = parsed.tagGroups.find(
27175
27211
  (tg) => tg.name.toLowerCase() === tagKey
@@ -27230,7 +27266,7 @@ function drawSwimlaneIcon(parent, x, y, isActive, palette) {
27230
27266
  return iconG;
27231
27267
  }
27232
27268
  function bucketCardsBySwimlane(columns, swimlaneGroup) {
27233
- const tagKey = swimlaneGroup.name.toLowerCase();
27269
+ const tagKey = tagAttrKey(swimlaneGroup.name);
27234
27270
  const buckets = /* @__PURE__ */ new Map();
27235
27271
  for (const entry of swimlaneGroup.entries) {
27236
27272
  buckets.set(entry.value.toLowerCase(), {
@@ -27518,7 +27554,7 @@ function renderSwimlaneCard(parent, cardLayout, tagGroups, activeTagGroup, palet
27518
27554
  );
27519
27555
  const cg = parent.append("g").attr("class", "kanban-card").attr("data-card-id", card.id).attr("data-line-number", card.lineNumber);
27520
27556
  if (activeTagGroup) {
27521
- const tagKey = activeTagGroup.toLowerCase();
27557
+ const tagKey = tagAttrKey(activeTagGroup);
27522
27558
  const group = tagGroups.find((tg) => tg.name.toLowerCase() === tagKey);
27523
27559
  const value = card.tags[tagKey] ?? group?.defaultValue;
27524
27560
  if (value) {
@@ -27551,6 +27587,7 @@ var DIAGRAM_PADDING3, COLUMN_GAP, COLUMN_HEADER_HEIGHT, COLUMN_PADDING, COLUMN_M
27551
27587
  var init_renderer3 = __esm({
27552
27588
  "src/kanban/renderer.ts"() {
27553
27589
  "use strict";
27590
+ init_tag_groups();
27554
27591
  init_fonts();
27555
27592
  init_color_utils();
27556
27593
  init_inline_markdown();
@@ -28532,7 +28569,7 @@ function classifyEREntities(tables, relationships) {
28532
28569
  const variance = indegreeValues.reduce((a, b) => a + (b - mean) ** 2, 0) / indegreeValues.length;
28533
28570
  const stddev = Math.sqrt(variance);
28534
28571
  const sorted = [...indegreeValues].sort((a, b) => a - b);
28535
- const median = sorted.length % 2 === 0 ? (sorted[sorted.length / 2 - 1] + sorted[sorted.length / 2]) / 2 : sorted[Math.floor(sorted.length / 2)];
28572
+ const median2 = sorted.length % 2 === 0 ? (sorted[sorted.length / 2 - 1] + sorted[sorted.length / 2]) / 2 : sorted[Math.floor(sorted.length / 2)];
28536
28573
  for (const table of tables) {
28537
28574
  const id = table.id;
28538
28575
  const cols = table.columns;
@@ -28573,7 +28610,7 @@ function classifyEREntities(tables, relationships) {
28573
28610
  const nameMatchesLookup = LOOKUP_NAME_SUFFIXES.some(
28574
28611
  (s) => nameLower.endsWith(s)
28575
28612
  );
28576
- if (nameMatchesLookup && cols.length <= 6 && fkCount <= 1 && indegree > median) {
28613
+ if (nameMatchesLookup && cols.length <= 6 && fkCount <= 1 && indegree > median2) {
28577
28614
  result.set(id, "lookup");
28578
28615
  continue;
28579
28616
  }
@@ -28814,7 +28851,7 @@ function renderERDiagram(container, parsed, layout, palette, isDark, onClickItem
28814
28851
  seriesColors2[ni % seriesColors2.length];
28815
28852
  const nodeG = contentG.append("g").attr("transform", `translate(${node.x}, ${node.y})`).attr("class", "er-table").attr("data-line-number", String(node.lineNumber)).attr("data-node-id", node.id);
28816
28853
  if (activeTagGroup) {
28817
- const tagKey = activeTagGroup.toLowerCase();
28854
+ const tagKey = tagAttrKey(activeTagGroup);
28818
28855
  const tagValue = node.metadata[tagKey];
28819
28856
  if (tagValue) {
28820
28857
  nodeG.attr(`data-tag-${tagKey}`, tagValue.toLowerCase());
@@ -28993,6 +29030,7 @@ var DIAGRAM_PADDING5, MAX_SCALE4, TABLE_FONT_SIZE2, COLUMN_FONT_SIZE2, EDGE_LABE
28993
29030
  var init_renderer5 = __esm({
28994
29031
  "src/er/renderer.ts"() {
28995
29032
  "use strict";
29033
+ init_tag_groups();
28996
29034
  init_fonts();
28997
29035
  init_color_utils();
28998
29036
  init_palettes();
@@ -29986,7 +30024,7 @@ function layeredCandidates(parsed, sizes, opts) {
29986
30024
  for (let r = 0; r < maxRank; r++) c += gapCrossings(r);
29987
30025
  return c;
29988
30026
  };
29989
- const median = (vals) => {
30027
+ const median2 = (vals) => {
29990
30028
  if (vals.length === 0) return -1;
29991
30029
  vals.sort((x, y) => x - y);
29992
30030
  const m = Math.floor(vals.length / 2);
@@ -30001,7 +30039,7 @@ function layeredCandidates(parsed, sizes, opts) {
30001
30039
  const neigh = useUp ? up : down;
30002
30040
  const med = /* @__PURE__ */ new Map();
30003
30041
  for (const id of ids)
30004
- med.set(id, median(neigh.get(id).map((x) => orderOf.get(x))));
30042
+ med.set(id, median2(neigh.get(id).map((x) => orderOf.get(x))));
30005
30043
  const movable = ids.filter((id) => med.get(id) >= 0).sort((a, b) => med.get(a) - med.get(b));
30006
30044
  const out = new Array(ids.length).fill(null);
30007
30045
  for (const id of ids) if (med.get(id) < 0) out[orderOf.get(id)] = id;
@@ -30331,6 +30369,695 @@ var init_layout_layered = __esm({
30331
30369
  }
30332
30370
  });
30333
30371
 
30372
+ // src/boxes-and-lines/layout-grouped.ts
30373
+ function rng2(s) {
30374
+ return () => {
30375
+ s |= 0;
30376
+ s = s + 1831565813 | 0;
30377
+ let t = Math.imul(s ^ s >>> 15, 1 | s);
30378
+ t = t + Math.imul(t ^ t >>> 7, 61 | t) ^ t;
30379
+ return ((t ^ t >>> 14) >>> 0) / 4294967296;
30380
+ };
30381
+ }
30382
+ function median(vals) {
30383
+ if (vals.length === 0) return -1;
30384
+ vals.sort((x, y) => x - y);
30385
+ const m = Math.floor(vals.length / 2);
30386
+ if (vals.length % 2 === 1) return vals[m];
30387
+ if (vals.length === 2) return (vals[0] + vals[1]) / 2;
30388
+ const left = vals[m - 1] - vals[0];
30389
+ const right = vals[vals.length - 1] - vals[m];
30390
+ return left + right === 0 ? (vals[m - 1] + vals[m]) / 2 : (vals[m - 1] * right + vals[m] * left) / (left + right);
30391
+ }
30392
+ function acyclicOrient(ids, edges) {
30393
+ const adj = /* @__PURE__ */ new Map();
30394
+ for (const l of ids) adj.set(l, []);
30395
+ for (const e of edges) adj.get(e.from)?.push({ to: e.to, idx: e.idx });
30396
+ const reversed = /* @__PURE__ */ new Set();
30397
+ const state = /* @__PURE__ */ new Map();
30398
+ for (const l of ids) state.set(l, 0);
30399
+ const stack = [];
30400
+ const visit = (root) => {
30401
+ stack.push(root);
30402
+ const it = /* @__PURE__ */ new Map();
30403
+ while (stack.length) {
30404
+ const u = stack[stack.length - 1];
30405
+ if (state.get(u) === 0) state.set(u, 1);
30406
+ const list = adj.get(u);
30407
+ let i = it.get(u) ?? 0;
30408
+ let descended = false;
30409
+ for (; i < list.length; i++) {
30410
+ const { to, idx } = list[i];
30411
+ const st = state.get(to);
30412
+ if (st === 1) reversed.add(idx);
30413
+ else if (st === 0) {
30414
+ it.set(u, i + 1);
30415
+ stack.push(to);
30416
+ descended = true;
30417
+ break;
30418
+ }
30419
+ }
30420
+ if (!descended) {
30421
+ it.set(u, list.length);
30422
+ state.set(u, 2);
30423
+ stack.pop();
30424
+ }
30425
+ }
30426
+ };
30427
+ for (const l of ids) if (state.get(l) === 0) visit(l);
30428
+ const dag = edges.map(
30429
+ (e) => reversed.has(e.idx) ? { from: e.to, to: e.from, idx: e.idx, rev: true } : { from: e.from, to: e.to, idx: e.idx, rev: false }
30430
+ );
30431
+ return { dag };
30432
+ }
30433
+ function longestPath(ids, dag) {
30434
+ const out = /* @__PURE__ */ new Map();
30435
+ const inDeg = /* @__PURE__ */ new Map();
30436
+ for (const l of ids) {
30437
+ out.set(l, []);
30438
+ inDeg.set(l, 0);
30439
+ }
30440
+ for (const d of dag) {
30441
+ out.get(d.from).push(d.to);
30442
+ inDeg.set(d.to, (inDeg.get(d.to) ?? 0) + 1);
30443
+ }
30444
+ const rank = /* @__PURE__ */ new Map();
30445
+ for (const l of ids) rank.set(l, 0);
30446
+ const queue = ids.filter((l) => (inDeg.get(l) ?? 0) === 0);
30447
+ const indeg2 = new Map(inDeg);
30448
+ let seen = 0;
30449
+ while (queue.length) {
30450
+ const u = queue.shift();
30451
+ seen++;
30452
+ for (const v of out.get(u)) {
30453
+ rank.set(v, Math.max(rank.get(v), rank.get(u) + 1));
30454
+ indeg2.set(v, indeg2.get(v) - 1);
30455
+ if (indeg2.get(v) === 0) queue.push(v);
30456
+ }
30457
+ }
30458
+ return seen === ids.length ? rank : null;
30459
+ }
30460
+ function groupedTierCandidates(parsed, sizes, opts) {
30461
+ if (parsed.groups.length === 0) return [];
30462
+ const nCount = parsed.nodes.length;
30463
+ if (nCount < 3 || nCount > 60) return [];
30464
+ const isTB = parsed.direction === "TB";
30465
+ const labels = parsed.nodes.map((n) => n.label);
30466
+ const labelSet = new Set(labels);
30467
+ for (const e of parsed.edges)
30468
+ if (e.source.startsWith("__group_") || e.target.startsWith("__group_"))
30469
+ return [];
30470
+ const groupByLabel = new Map(parsed.groups.map((g) => [g.label, g]));
30471
+ const directGroupOf = /* @__PURE__ */ new Map();
30472
+ for (const grp of parsed.groups)
30473
+ for (const c of grp.children)
30474
+ if (labelSet.has(c)) directGroupOf.set(c, grp.label);
30475
+ const topGroupOf = (groupLabel) => {
30476
+ let cur = groupLabel;
30477
+ const guard = /* @__PURE__ */ new Set();
30478
+ while (true) {
30479
+ const g = groupByLabel.get(cur);
30480
+ if (!g?.parentGroup || guard.has(cur)) return cur;
30481
+ guard.add(cur);
30482
+ cur = g.parentGroup;
30483
+ }
30484
+ };
30485
+ const pathOf = (label) => {
30486
+ const direct = directGroupOf.get(label);
30487
+ if (!direct) return [];
30488
+ const chain = [direct];
30489
+ let cur = direct;
30490
+ const guard = /* @__PURE__ */ new Set([direct]);
30491
+ while (true) {
30492
+ const g = groupByLabel.get(cur);
30493
+ if (!g?.parentGroup || guard.has(g.parentGroup)) break;
30494
+ chain.push(g.parentGroup);
30495
+ guard.add(g.parentGroup);
30496
+ cur = g.parentGroup;
30497
+ }
30498
+ return chain.reverse();
30499
+ };
30500
+ const SINGLE = "\0node\0";
30501
+ const unitOf = (label) => {
30502
+ const direct = directGroupOf.get(label);
30503
+ return direct ? topGroupOf(direct) : SINGLE + label;
30504
+ };
30505
+ const edges = parsed.edges.map((e, i) => ({ e, i })).filter(
30506
+ ({ e }) => labelSet.has(e.source) && labelSet.has(e.target) && e.source !== e.target
30507
+ );
30508
+ if (edges.length === 0) return [];
30509
+ const unitOfNode = /* @__PURE__ */ new Map();
30510
+ for (const l of labels) unitOfNode.set(l, unitOf(l));
30511
+ const units = Array.from(new Set(labels.map((l) => unitOfNode.get(l))));
30512
+ const interUnit = edges.filter(({ e }) => unitOfNode.get(e.source) !== unitOfNode.get(e.target)).map(({ e, i }) => ({
30513
+ from: unitOfNode.get(e.source),
30514
+ to: unitOfNode.get(e.target),
30515
+ idx: i
30516
+ }));
30517
+ const { dag: unitDag } = acyclicOrient(units, interUnit);
30518
+ const tier = longestPath(
30519
+ units,
30520
+ unitDag.map((d) => ({ from: d.from, to: d.to }))
30521
+ );
30522
+ if (!tier) return [];
30523
+ const localRank = /* @__PURE__ */ new Map();
30524
+ const unitMembers = /* @__PURE__ */ new Map();
30525
+ for (const l of labels) {
30526
+ const u = unitOfNode.get(l);
30527
+ (unitMembers.get(u) ?? unitMembers.set(u, []).get(u)).push(l);
30528
+ }
30529
+ for (const [u, members] of unitMembers) {
30530
+ const memberSet = new Set(members);
30531
+ const intra = edges.filter(
30532
+ ({ e }) => unitOfNode.get(e.source) === u && unitOfNode.get(e.target) === u && memberSet.has(e.source) && memberSet.has(e.target)
30533
+ ).map(({ e, i }) => ({ from: e.source, to: e.target, idx: i }));
30534
+ const { dag } = acyclicOrient(members, intra);
30535
+ const lr = longestPath(
30536
+ members,
30537
+ dag.map((d) => ({ from: d.from, to: d.to }))
30538
+ );
30539
+ if (!lr) return [];
30540
+ for (const m of members) localRank.set(m, lr.get(m));
30541
+ }
30542
+ const maxTier = Math.max(...units.map((u) => tier.get(u)));
30543
+ const bandHeight = Array.from({ length: maxTier + 1 }, () => 1);
30544
+ for (const u of units) {
30545
+ const h = Math.max(
30546
+ ...unitMembers.get(u).map((m) => localRank.get(m) + 1)
30547
+ );
30548
+ const t = tier.get(u);
30549
+ bandHeight[t] = Math.max(bandHeight[t], h);
30550
+ }
30551
+ const tierBase = [];
30552
+ let accBase = 0;
30553
+ for (let t = 0; t <= maxTier; t++) {
30554
+ tierBase[t] = accBase;
30555
+ accBase += bandHeight[t];
30556
+ }
30557
+ const tierStartRanks = new Set(tierBase.slice(1));
30558
+ const globalRank = /* @__PURE__ */ new Map();
30559
+ for (const l of labels)
30560
+ globalRank.set(
30561
+ l,
30562
+ tierBase[tier.get(unitOfNode.get(l))] + localRank.get(l)
30563
+ );
30564
+ const maxRank = Math.max(...labels.map((l) => globalRank.get(l)));
30565
+ const node = /* @__PURE__ */ new Map();
30566
+ for (const n of parsed.nodes) {
30567
+ const s = sizes.get(n.label) ?? { width: NODE_WIDTH, height: NODE_HEIGHT };
30568
+ node.set(n.label, {
30569
+ id: n.label,
30570
+ dummy: false,
30571
+ rank: globalRank.get(n.label),
30572
+ cross: 0,
30573
+ thick: isTB ? s.width : s.height,
30574
+ depth: isTB ? s.height : s.width,
30575
+ realW: s.width,
30576
+ realH: s.height,
30577
+ unit: unitOfNode.get(n.label),
30578
+ path: pathOf(n.label)
30579
+ });
30580
+ }
30581
+ const chains = [];
30582
+ const segs = [];
30583
+ const backEdges = [];
30584
+ const flatEdges = [];
30585
+ for (const { e, i } of edges) {
30586
+ const rs = globalRank.get(e.source);
30587
+ const rt = globalRank.get(e.target);
30588
+ if (rs === rt) {
30589
+ flatEdges.push({ edgeIdx: i, a: e.source, b: e.target });
30590
+ continue;
30591
+ }
30592
+ if (rt < rs) {
30593
+ backEdges.push({ edgeIdx: i, src: e.source, tgt: e.target });
30594
+ continue;
30595
+ }
30596
+ const chain = [e.source];
30597
+ let prev = e.source;
30598
+ for (let r = rs + 1; r < rt; r++) {
30599
+ const id = `__d${i}_${r}`;
30600
+ node.set(id, {
30601
+ id,
30602
+ dummy: true,
30603
+ rank: r,
30604
+ cross: 0,
30605
+ thick: DUMMY_THICK2,
30606
+ depth: 0,
30607
+ realW: 0,
30608
+ realH: 0,
30609
+ unit: SINGLE + id,
30610
+ path: []
30611
+ });
30612
+ segs.push({ a: prev, b: id });
30613
+ chain.push(id);
30614
+ prev = id;
30615
+ }
30616
+ segs.push({ a: prev, b: e.target });
30617
+ chain.push(e.target);
30618
+ chains.push({ edgeIdx: i, chain });
30619
+ }
30620
+ const ranks = Array.from({ length: maxRank + 1 }, () => []);
30621
+ for (const n of node.values()) ranks[n.rank].push(n.id);
30622
+ const up = /* @__PURE__ */ new Map();
30623
+ const down = /* @__PURE__ */ new Map();
30624
+ for (const id of node.keys()) {
30625
+ up.set(id, []);
30626
+ down.set(id, []);
30627
+ }
30628
+ for (const s of segs) {
30629
+ down.get(s.a).push(s.b);
30630
+ up.get(s.b).push(s.a);
30631
+ }
30632
+ const orderOf = /* @__PURE__ */ new Map();
30633
+ const gapCrossings = (r) => {
30634
+ const lower = ranks[r + 1];
30635
+ if (!lower) return 0;
30636
+ const es = [];
30637
+ for (const a of ranks[r]) {
30638
+ const pa = orderOf.get(a);
30639
+ for (const b of down.get(a)) es.push({ p: pa, q: orderOf.get(b) });
30640
+ }
30641
+ let c = 0;
30642
+ for (let i = 0; i < es.length; i++)
30643
+ for (let j = i + 1; j < es.length; j++) {
30644
+ const A = es[i], B = es[j];
30645
+ if ((A.p - B.p) * (A.q - B.q) < 0) c++;
30646
+ }
30647
+ return c;
30648
+ };
30649
+ const totalCrossings = () => {
30650
+ let c = 0;
30651
+ for (let r = 0; r < maxRank; r++) c += gapCrossings(r);
30652
+ return c;
30653
+ };
30654
+ const wmedianRank = (r, useUp) => {
30655
+ const ids = ranks[r];
30656
+ const neigh = useUp ? up : down;
30657
+ const med = /* @__PURE__ */ new Map();
30658
+ for (const id of ids)
30659
+ med.set(id, median(neigh.get(id).map((x) => orderOf.get(x))));
30660
+ const movable = ids.filter((id) => med.get(id) >= 0).sort((a, b) => med.get(a) - med.get(b));
30661
+ const out = new Array(ids.length).fill(null);
30662
+ for (const id of ids) if (med.get(id) < 0) out[orderOf.get(id)] = id;
30663
+ let mi = 0;
30664
+ for (let slot = 0; slot < out.length; slot++)
30665
+ if (out[slot] === null) out[slot] = movable[mi++];
30666
+ const final = out;
30667
+ final.forEach((id, i) => orderOf.set(id, i));
30668
+ ranks[r] = final;
30669
+ };
30670
+ const transpose = () => {
30671
+ let improved = true;
30672
+ let guard = 0;
30673
+ while (improved && guard++ < 12) {
30674
+ improved = false;
30675
+ for (let r = 0; r <= maxRank; r++) {
30676
+ const ids = ranks[r];
30677
+ for (let i = 0; i < ids.length - 1; i++) {
30678
+ const before = (r > 0 ? gapCrossings(r - 1) : 0) + (r < maxRank ? gapCrossings(r) : 0);
30679
+ const a = ids[i], b = ids[i + 1];
30680
+ ids[i] = b;
30681
+ ids[i + 1] = a;
30682
+ orderOf.set(b, i);
30683
+ orderOf.set(a, i + 1);
30684
+ const after = (r > 0 ? gapCrossings(r - 1) : 0) + (r < maxRank ? gapCrossings(r) : 0);
30685
+ if (after < before) improved = true;
30686
+ else {
30687
+ ids[i] = a;
30688
+ ids[i + 1] = b;
30689
+ orderOf.set(a, i);
30690
+ orderOf.set(b, i + 1);
30691
+ }
30692
+ }
30693
+ }
30694
+ }
30695
+ };
30696
+ const initOrder = (seed) => {
30697
+ const r = seed === 0 ? null : rng2(seed);
30698
+ for (let rr = 0; rr <= maxRank; rr++) {
30699
+ const ids = ranks[rr].slice();
30700
+ if (r)
30701
+ for (let i = ids.length - 1; i > 0; i--) {
30702
+ const j = Math.floor(r() * (i + 1));
30703
+ [ids[i], ids[j]] = [ids[j], ids[i]];
30704
+ }
30705
+ ranks[rr] = ids;
30706
+ ids.forEach((id, i) => orderOf.set(id, i));
30707
+ }
30708
+ };
30709
+ const restarts = opts?.restarts ?? (nCount <= 16 ? 20 : nCount <= 30 ? 12 : 6);
30710
+ const keepK = opts?.keepK ?? 3;
30711
+ const found = [];
30712
+ for (let s = 0; s < restarts; s++) {
30713
+ initOrder(s);
30714
+ let best = totalCrossings();
30715
+ let bestSnap = new Map(orderOf);
30716
+ for (let iter = 0; iter < 8; iter++) {
30717
+ if (iter % 2 === 0)
30718
+ for (let r = 1; r <= maxRank; r++) wmedianRank(r, true);
30719
+ else for (let r = maxRank - 1; r >= 0; r--) wmedianRank(r, false);
30720
+ transpose();
30721
+ const c = totalCrossings();
30722
+ if (c < best) {
30723
+ best = c;
30724
+ bestSnap = new Map(orderOf);
30725
+ }
30726
+ if (c === 0) break;
30727
+ }
30728
+ for (const [k, v] of bestSnap) orderOf.set(k, v);
30729
+ for (let r = 0; r <= maxRank; r++)
30730
+ ranks[r].sort((a, b) => orderOf.get(a) - orderOf.get(b));
30731
+ const sig = ranks.map((r) => r.join(",")).join("|");
30732
+ if (!found.some((f) => f.sig === sig))
30733
+ found.push({ sig, cross: best, order: new Map(bestSnap) });
30734
+ }
30735
+ found.sort((a, b) => a.cross - b.cross);
30736
+ const keep = found.slice(0, keepK);
30737
+ if (keep.length === 0) return [];
30738
+ const results = [];
30739
+ for (const cand of keep) {
30740
+ for (const [k, v] of cand.order) orderOf.set(k, v);
30741
+ const rk = Array.from({ length: maxRank + 1 }, () => []);
30742
+ for (const n of node.values()) rk[n.rank].push(n.id);
30743
+ for (let r = 0; r <= maxRank; r++)
30744
+ rk[r].sort((a, b) => cand.order.get(a) - cand.order.get(b));
30745
+ results.push(realize(rk));
30746
+ }
30747
+ return results;
30748
+ function weight(a, b) {
30749
+ if (a.dummy && b.dummy) return W_DUMMY_DUMMY2;
30750
+ if (a.dummy || b.dummy) return W_REAL_DUMMY2;
30751
+ return W_REAL_REAL2;
30752
+ }
30753
+ function placeWithSeparation(ids, desired, gap) {
30754
+ const n = ids.length;
30755
+ if (n === 0) return;
30756
+ const off = [0];
30757
+ for (let i = 1; i < n; i++) off[i] = off[i - 1] + gap[i];
30758
+ const d = desired.map((v, i) => v - off[i]);
30759
+ const blocks = [];
30760
+ for (let i = 0; i < n; i++) {
30761
+ let b = { pos: d[i], count: 1, sumOffset: d[i], first: i };
30762
+ while (blocks.length && blocks[blocks.length - 1].pos >= b.pos) {
30763
+ const prev = blocks.pop();
30764
+ const count = prev.count + b.count;
30765
+ const sumOffset = prev.sumOffset + b.sumOffset;
30766
+ b = { pos: sumOffset / count, count, sumOffset, first: prev.first };
30767
+ }
30768
+ blocks.push(b);
30769
+ }
30770
+ const xs = new Array(n);
30771
+ for (const b of blocks)
30772
+ for (let k = 0; k < b.count; k++) xs[b.first + k] = b.pos;
30773
+ for (let i = 0; i < n; i++) node.get(ids[i]).cross = xs[i] + off[i];
30774
+ }
30775
+ function gapBetween(a, b) {
30776
+ const base = a.thick / 2 + b.thick / 2 + NODESEP2;
30777
+ if (a.dummy || b.dummy) return base;
30778
+ if (a.unit !== b.unit) return base + 2 * GROUP_PAD + GROUP_GAP;
30779
+ const da = a.path[a.path.length - 1];
30780
+ const db = b.path[b.path.length - 1];
30781
+ if (da !== db && (da || db)) return base + 2 * GROUP_PAD;
30782
+ return base;
30783
+ }
30784
+ function realize(rk) {
30785
+ const bandDepth = rk.map(
30786
+ (ids) => Math.max(0, ...ids.map((id) => node.get(id).depth))
30787
+ );
30788
+ const bandCenter = [];
30789
+ let acc = MARGIN4;
30790
+ for (let r = 0; r <= maxRank; r++) {
30791
+ if (r > 0) acc += RANKSEP2 + (tierStartRanks.has(r) ? TIER_GAP : 0);
30792
+ bandCenter[r] = acc + bandDepth[r] / 2;
30793
+ acc += bandDepth[r];
30794
+ }
30795
+ const gapsFor = (ids) => ids.map(
30796
+ (id, i) => i === 0 ? 0 : gapBetween(node.get(ids[i - 1]), node.get(id))
30797
+ );
30798
+ for (let r = 0; r <= maxRank; r++) {
30799
+ const ids = rk[r];
30800
+ const gap = gapsFor(ids);
30801
+ let x = MARGIN4;
30802
+ for (let i = 0; i < ids.length; i++) {
30803
+ const n = node.get(ids[i]);
30804
+ x += gap[i];
30805
+ n.cross = i === 0 ? MARGIN4 + n.thick / 2 : x;
30806
+ }
30807
+ }
30808
+ const ITER = 18;
30809
+ for (let it = 0; it < ITER; it++) {
30810
+ const downPass = it % 2 === 0;
30811
+ const order = downPass ? Array.from({ length: maxRank + 1 }, (_, i) => i) : Array.from({ length: maxRank + 1 }, (_, i) => maxRank - i);
30812
+ for (const r of order) {
30813
+ const ids = rk[r];
30814
+ const desired = ids.map((id) => {
30815
+ const n = node.get(id);
30816
+ let sw = 0, sx2 = 0;
30817
+ for (const nb of up.get(id)) {
30818
+ const w = weight(n, node.get(nb));
30819
+ sw += w;
30820
+ sx2 += w * node.get(nb).cross;
30821
+ }
30822
+ for (const nb of down.get(id)) {
30823
+ const w = weight(n, node.get(nb));
30824
+ sw += w;
30825
+ sx2 += w * node.get(nb).cross;
30826
+ }
30827
+ return sw > 0 ? sx2 / sw : n.cross;
30828
+ });
30829
+ placeWithSeparation(ids, desired, gapsFor(ids));
30830
+ }
30831
+ }
30832
+ let minC = Infinity;
30833
+ for (const n of node.values()) minC = Math.min(minC, n.cross - n.thick / 2);
30834
+ const shiftC = MARGIN4 - minC;
30835
+ for (const n of node.values()) n.cross += shiftC;
30836
+ const coord = (n) => isTB ? { x: n.cross, y: bandCenter[n.rank] } : { x: bandCenter[n.rank], y: n.cross };
30837
+ const layoutNodes = parsed.nodes.map((pn) => {
30838
+ const n = node.get(pn.label);
30839
+ const c = coord(n);
30840
+ return {
30841
+ label: pn.label,
30842
+ x: c.x,
30843
+ y: c.y,
30844
+ width: n.realW,
30845
+ height: n.realH
30846
+ };
30847
+ });
30848
+ const chainById = /* @__PURE__ */ new Map();
30849
+ for (const ch of chains) chainById.set(ch.edgeIdx, ch.chain);
30850
+ let minCross = Infinity, maxCross = -Infinity;
30851
+ for (const n of node.values()) {
30852
+ if (n.dummy) continue;
30853
+ minCross = Math.min(minCross, n.cross - n.thick / 2);
30854
+ maxCross = Math.max(maxCross, n.cross + n.thick / 2);
30855
+ }
30856
+ const GAP = 34;
30857
+ const LANE = 28;
30858
+ const faceLim = (depth) => Math.max(0, depth / 2 - 6);
30859
+ const sideOf = (b) => {
30860
+ const d = node.get(b.src).cross - node.get(b.tgt).cross;
30861
+ return d >= 0 ? 1 : -1;
30862
+ };
30863
+ const backSorted = backEdges.map((b) => ({
30864
+ ...b,
30865
+ side: sideOf(b),
30866
+ span: Math.abs(node.get(b.src).rank - node.get(b.tgt).rank)
30867
+ })).sort((a, b) => a.side - b.side || a.span - b.span);
30868
+ const sideCounts = /* @__PURE__ */ new Map();
30869
+ for (const b of backSorted)
30870
+ sideCounts.set(b.side, (sideCounts.get(b.side) ?? 0) + 1);
30871
+ const laneCounter = /* @__PURE__ */ new Map();
30872
+ const backPoints = /* @__PURE__ */ new Map();
30873
+ for (const b of backSorted) {
30874
+ const s = b.side;
30875
+ const k = laneCounter.get(s) ?? 0;
30876
+ laneCounter.set(s, k + 1);
30877
+ const K = sideCounts.get(s);
30878
+ const src = node.get(b.src);
30879
+ const tgt = node.get(b.tgt);
30880
+ const laneC = s > 0 ? maxCross + GAP + k * LANE : minCross - GAP - k * LANE;
30881
+ const srcCtr = bandCenter[src.rank];
30882
+ const tgtCtr = bandCenter[tgt.rank];
30883
+ const frac = K > 1 ? (k + 1) / (K + 1) : 0;
30884
+ const srcR = srcCtr - frac * faceLim(src.depth);
30885
+ const tgtR = tgtCtr + frac * faceLim(tgt.depth);
30886
+ const m = (c, rr) => isTB ? { x: c, y: rr } : { x: rr, y: c };
30887
+ const off = 10;
30888
+ const srcEdgeC = src.cross + s * (src.thick / 2 + off);
30889
+ const tgtEdgeC = tgt.cross + s * (tgt.thick / 2 + off);
30890
+ backPoints.set(b.edgeIdx, [
30891
+ m(src.cross, srcCtr),
30892
+ m(srcEdgeC, srcR),
30893
+ m(laneC, srcR),
30894
+ m(laneC, tgtR),
30895
+ m(tgtEdgeC, tgtR),
30896
+ m(tgt.cross, tgtCtr)
30897
+ ]);
30898
+ }
30899
+ const flatPoints = /* @__PURE__ */ new Map();
30900
+ for (const f of flatEdges) {
30901
+ const a = node.get(f.a);
30902
+ const b = node.get(f.b);
30903
+ const ctr = bandCenter[a.rank];
30904
+ const bow = ctr - (a.depth / 2 + 26);
30905
+ const m = (c, rr) => isTB ? { x: c, y: rr } : { x: rr, y: c };
30906
+ flatPoints.set(f.edgeIdx, [
30907
+ m(a.cross, ctr),
30908
+ m(a.cross, bow),
30909
+ m(b.cross, bow),
30910
+ m(b.cross, ctr)
30911
+ ]);
30912
+ }
30913
+ const layoutEdges = [];
30914
+ for (let i = 0; i < parsed.edges.length; i++) {
30915
+ const e = parsed.edges[i];
30916
+ if (!labelSet.has(e.source) || !labelSet.has(e.target)) continue;
30917
+ let points;
30918
+ if (e.source === e.target) {
30919
+ const c = coord(node.get(e.source));
30920
+ points = [c, c];
30921
+ } else if (backPoints.has(i)) points = backPoints.get(i);
30922
+ else if (flatPoints.has(i)) points = flatPoints.get(i);
30923
+ else {
30924
+ const chain = chainById.get(i);
30925
+ if (!chain) continue;
30926
+ points = chain.map((id) => coord(node.get(id)));
30927
+ }
30928
+ layoutEdges.push({
30929
+ source: e.source,
30930
+ target: e.target,
30931
+ ...e.label !== void 0 && { label: e.label },
30932
+ bidirectional: e.bidirectional,
30933
+ lineNumber: e.lineNumber,
30934
+ points,
30935
+ yOffset: 0,
30936
+ parallelCount: 1,
30937
+ metadata: e.metadata
30938
+ });
30939
+ }
30940
+ const nodeBox = new Map(
30941
+ layoutNodes.map((n) => [
30942
+ n.label,
30943
+ {
30944
+ l: n.x - n.width / 2,
30945
+ r: n.x + n.width / 2,
30946
+ t: n.y - n.height / 2,
30947
+ b: n.y + n.height / 2
30948
+ }
30949
+ ])
30950
+ );
30951
+ const depthOf = (label) => {
30952
+ let d = 0;
30953
+ let cur = label;
30954
+ const guard = /* @__PURE__ */ new Set();
30955
+ while (cur && !guard.has(cur)) {
30956
+ guard.add(cur);
30957
+ cur = groupByLabel.get(cur)?.parentGroup;
30958
+ if (cur) d++;
30959
+ }
30960
+ return d;
30961
+ };
30962
+ const childGroups = /* @__PURE__ */ new Map();
30963
+ for (const g of parsed.groups)
30964
+ if (g.parentGroup)
30965
+ (childGroups.get(g.parentGroup) ?? childGroups.set(g.parentGroup, []).get(g.parentGroup)).push(g.label);
30966
+ const rectByGroup = /* @__PURE__ */ new Map();
30967
+ const ordered = [...parsed.groups].sort(
30968
+ (a, b) => depthOf(b.label) - depthOf(a.label)
30969
+ );
30970
+ const layoutGroups = [];
30971
+ for (const grp of ordered) {
30972
+ let l = Infinity, r = -Infinity, t = Infinity, bm = -Infinity;
30973
+ const acc2 = (box) => {
30974
+ l = Math.min(l, box.l);
30975
+ r = Math.max(r, box.r);
30976
+ t = Math.min(t, box.t);
30977
+ bm = Math.max(bm, box.b);
30978
+ };
30979
+ for (const c of grp.children) {
30980
+ const nb = nodeBox.get(c);
30981
+ if (nb) acc2(nb);
30982
+ }
30983
+ for (const sub of childGroups.get(grp.label) ?? []) {
30984
+ const sr = rectByGroup.get(sub);
30985
+ if (sr) acc2(sr);
30986
+ }
30987
+ if (!Number.isFinite(l)) continue;
30988
+ const x0 = l - GROUP_PAD;
30989
+ const x1 = r + GROUP_PAD;
30990
+ const y0 = t - GROUP_LABEL_ZONE2;
30991
+ const y1 = bm + GROUP_PAD;
30992
+ rectByGroup.set(grp.label, { l: x0, r: x1, t: y0, b: y1 });
30993
+ layoutGroups.push({
30994
+ label: grp.label,
30995
+ lineNumber: grp.lineNumber,
30996
+ x: (x0 + x1) / 2,
30997
+ y: (y0 + y1) / 2,
30998
+ width: x1 - x0,
30999
+ height: y1 - y0,
31000
+ collapsed: false,
31001
+ childCount: grp.children.length
31002
+ });
31003
+ }
31004
+ let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
31005
+ const ext = (x0, y0, x1, y1) => {
31006
+ minX = Math.min(minX, x0);
31007
+ minY = Math.min(minY, y0);
31008
+ maxX = Math.max(maxX, x1);
31009
+ maxY = Math.max(maxY, y1);
31010
+ };
31011
+ for (const n of layoutNodes)
31012
+ ext(
31013
+ n.x - n.width / 2,
31014
+ n.y - n.height / 2,
31015
+ n.x + n.width / 2,
31016
+ n.y + n.height / 2
31017
+ );
31018
+ for (const e of layoutEdges)
31019
+ for (const p of e.points) ext(p.x, p.y, p.x, p.y);
31020
+ for (const g of layoutGroups)
31021
+ ext(
31022
+ g.x - g.width / 2,
31023
+ g.y - g.height / 2,
31024
+ g.x + g.width / 2,
31025
+ g.y + g.height / 2
31026
+ );
31027
+ const sx = MARGIN4 - minX;
31028
+ const sy = MARGIN4 - minY;
31029
+ const shift = sx !== 0 || sy !== 0;
31030
+ return {
31031
+ nodes: shift ? layoutNodes.map((n) => ({ ...n, x: n.x + sx, y: n.y + sy })) : layoutNodes,
31032
+ edges: shift ? layoutEdges.map((e) => ({
31033
+ ...e,
31034
+ points: e.points.map((p) => ({ x: p.x + sx, y: p.y + sy }))
31035
+ })) : layoutEdges,
31036
+ groups: shift ? layoutGroups.map((g) => ({ ...g, x: g.x + sx, y: g.y + sy })) : layoutGroups,
31037
+ width: maxX + sx + MARGIN4,
31038
+ height: maxY + sy + MARGIN4
31039
+ };
31040
+ }
31041
+ }
31042
+ var NODESEP2, RANKSEP2, DUMMY_THICK2, MARGIN4, GROUP_PAD, GROUP_LABEL_ZONE2, GROUP_GAP, TIER_GAP, W_REAL_REAL2, W_REAL_DUMMY2, W_DUMMY_DUMMY2;
31043
+ var init_layout_grouped = __esm({
31044
+ "src/boxes-and-lines/layout-grouped.ts"() {
31045
+ "use strict";
31046
+ init_layout5();
31047
+ NODESEP2 = 50;
31048
+ RANKSEP2 = 60;
31049
+ DUMMY_THICK2 = 10;
31050
+ MARGIN4 = 40;
31051
+ GROUP_PAD = 16;
31052
+ GROUP_LABEL_ZONE2 = 32;
31053
+ GROUP_GAP = 24;
31054
+ TIER_GAP = GROUP_LABEL_ZONE2 + 2 * GROUP_PAD;
31055
+ W_REAL_REAL2 = 1;
31056
+ W_REAL_DUMMY2 = 2;
31057
+ W_DUMMY_DUMMY2 = 8;
31058
+ }
31059
+ });
31060
+
30334
31061
  // src/boxes-and-lines/layout-search.ts
30335
31062
  var layout_search_exports = {};
30336
31063
  __export(layout_search_exports, {
@@ -30348,7 +31075,7 @@ __export(layout_search_exports, {
30348
31075
  });
30349
31076
  import dagre4 from "@dagrejs/dagre";
30350
31077
  import { line as d3line, curveBasis as curveBasis5 } from "d3-shape";
30351
- function rng2(s) {
31078
+ function rng3(s) {
30352
31079
  return () => {
30353
31080
  s |= 0;
30354
31081
  s = s + 1831565813 | 0;
@@ -30703,7 +31430,7 @@ function detectGroupOverlaps(layout, opts) {
30703
31430
  const contains = (outer, inner) => outer.l <= inner.l + margin && outer.r >= inner.r - margin && outer.t <= inner.t + margin && outer.b >= inner.b - margin;
30704
31431
  const rend = raw.map((a, i) => {
30705
31432
  const parent = raw.some((b, j) => j !== i && contains(a, b));
30706
- return parent ? { ...a, t: a.t - GROUP_LABEL_ZONE2 } : a;
31433
+ return parent ? { ...a, t: a.t - GROUP_LABEL_ZONE3 } : a;
30707
31434
  });
30708
31435
  const hit = /* @__PURE__ */ new Set();
30709
31436
  for (let i = 0; i < raw.length; i++)
@@ -30730,7 +31457,7 @@ function countGroupOverlaps(layout, opts) {
30730
31457
  const contains = (outer, inner) => outer.l <= inner.l + margin && outer.r >= inner.r - margin && outer.t <= inner.t + margin && outer.b >= inner.b - margin;
30731
31458
  const rend = raw.map((a, i) => {
30732
31459
  const parent = raw.some((b, j) => j !== i && contains(a, b));
30733
- return parent ? { ...a, t: a.t - GROUP_LABEL_ZONE2 } : a;
31460
+ return parent ? { ...a, t: a.t - GROUP_LABEL_ZONE3 } : a;
30734
31461
  });
30735
31462
  let count = 0;
30736
31463
  for (let i = 0; i < raw.length; i++)
@@ -30773,7 +31500,7 @@ function separateGroupBands(layout, parsed) {
30773
31500
  let lo = (axis === "y" ? g.y : g.x) - half2;
30774
31501
  const hi = (axis === "y" ? g.y : g.x) + half2;
30775
31502
  const isParent = parsed.groups.some((c) => c.parentGroup === label);
30776
- if (axis === "y" && isParent) lo -= GROUP_LABEL_ZONE2;
31503
+ if (axis === "y" && isParent) lo -= GROUP_LABEL_ZONE3;
30777
31504
  return { label, lo, hi, c: (lo + hi) / 2 };
30778
31505
  });
30779
31506
  bands.sort((a, b) => a.c - b.c);
@@ -30844,7 +31571,7 @@ function separateGroupBands(layout, parsed) {
30844
31571
  acc(n.x + n.width / 2, n.y + n.height / 2);
30845
31572
  }
30846
31573
  for (const g of groups) {
30847
- acc(g.x - g.width / 2, g.y - g.height / 2 - GROUP_LABEL_ZONE2);
31574
+ acc(g.x - g.width / 2, g.y - g.height / 2 - GROUP_LABEL_ZONE3);
30848
31575
  acc(g.x + g.width / 2, g.y + g.height / 2);
30849
31576
  }
30850
31577
  for (const e of edges) for (const p of e.points) acc(p.x, p.y);
@@ -31108,7 +31835,7 @@ async function layoutBoxesAndLinesSearch(parsed, collapseInfo, opts) {
31108
31835
  }
31109
31836
  ];
31110
31837
  });
31111
- const GROUP_PAD = 16;
31838
+ const GROUP_PAD2 = 16;
31112
31839
  const nodeByLabel = new Map(nodes.map((n2) => [n2.label, n2]));
31113
31840
  const groups = [];
31114
31841
  for (const grp of parsed.groups) {
@@ -31122,10 +31849,10 @@ async function layoutBoxesAndLinesSearch(parsed, collapseInfo, opts) {
31122
31849
  gy1 = Math.max(gy1, n2.y + n2.height / 2);
31123
31850
  }
31124
31851
  if (!Number.isFinite(gx0)) continue;
31125
- const x0 = gx0 - GROUP_PAD;
31126
- const x1 = gx1 + GROUP_PAD;
31127
- const y0 = gy0 - GROUP_LABEL_ZONE2;
31128
- const y1 = gy1 + GROUP_PAD;
31852
+ const x0 = gx0 - GROUP_PAD2;
31853
+ const x1 = gx1 + GROUP_PAD2;
31854
+ const y0 = gy0 - GROUP_LABEL_ZONE3;
31855
+ const y1 = gy1 + GROUP_PAD2;
31129
31856
  groups.push({
31130
31857
  label: grp.label,
31131
31858
  lineNumber: grp.lineNumber,
@@ -31182,7 +31909,7 @@ async function layoutBoxesAndLinesSearch(parsed, collapseInfo, opts) {
31182
31909
  };
31183
31910
  }
31184
31911
  function place(cfg) {
31185
- const r = cfg.seed === void 0 ? null : rng2(cfg.seed + 1);
31912
+ const r = cfg.seed === void 0 ? null : rng3(cfg.seed + 1);
31186
31913
  const ord = (a) => r ? shuffle(a, r) : a.slice();
31187
31914
  const g = new dagre4.graphlib.Graph({ compound: true, multigraph: true });
31188
31915
  g.setGraph({
@@ -31369,8 +32096,15 @@ async function layoutBoxesAndLinesSearch(parsed, collapseInfo, opts) {
31369
32096
  layered = layeredCandidates(parsed, sizes);
31370
32097
  } catch {
31371
32098
  }
32099
+ if (parsed.groups.length > 0 && collapsedGroupLabels.size === 0) {
32100
+ try {
32101
+ layered = layered.concat(groupedTierCandidates(parsed, sizes));
32102
+ } catch {
32103
+ }
32104
+ }
31372
32105
  const fastKey = /* @__PURE__ */ new Map();
31373
- for (const lay of pool) fastKey.set(lay, objective(lay, countCrossingsFast(lay)));
32106
+ for (const lay of pool)
32107
+ fastKey.set(lay, objective(lay, countCrossingsFast(lay)));
31374
32108
  pool.sort((a, b) => fastKey.get(a) - fastKey.get(b));
31375
32109
  const refineK = Math.min(REFINE_K, pool.length);
31376
32110
  let best = pool[0];
@@ -31435,12 +32169,13 @@ async function layoutBoxesAndLinesSearch(parsed, collapseInfo, opts) {
31435
32169
  }
31436
32170
  return best;
31437
32171
  }
31438
- var DEFAULT_LAMBDA, ESCALATE_THRESHOLD, ESCALATE_MAX_N, ESCALATE_SEEDS, ESCALATE_REFINE, splineGen, PATH_TOKEN_RE, FLAT_CACHE, GROUP_LABEL_ZONE2;
32172
+ var DEFAULT_LAMBDA, ESCALATE_THRESHOLD, ESCALATE_MAX_N, ESCALATE_SEEDS, ESCALATE_REFINE, splineGen, PATH_TOKEN_RE, FLAT_CACHE, GROUP_LABEL_ZONE3;
31439
32173
  var init_layout_search = __esm({
31440
32174
  "src/boxes-and-lines/layout-search.ts"() {
31441
32175
  "use strict";
31442
32176
  init_layout5();
31443
32177
  init_layout_layered();
32178
+ init_layout_grouped();
31444
32179
  DEFAULT_LAMBDA = 4;
31445
32180
  ESCALATE_THRESHOLD = 4;
31446
32181
  ESCALATE_MAX_N = 45;
@@ -31449,7 +32184,7 @@ var init_layout_search = __esm({
31449
32184
  splineGen = d3line().x((d) => d.x).y((d) => d.y).curve(curveBasis5);
31450
32185
  PATH_TOKEN_RE = /[MLQC]|-?\d*\.?\d+(?:e-?\d+)?/gi;
31451
32186
  FLAT_CACHE = /* @__PURE__ */ new WeakMap();
31452
- GROUP_LABEL_ZONE2 = 32;
32187
+ GROUP_LABEL_ZONE3 = 32;
31453
32188
  }
31454
32189
  });
31455
32190
 
@@ -31626,8 +32361,8 @@ function attachNotes(layout, parsed, collapsedNotes) {
31626
32361
  nodes: finalNodes,
31627
32362
  edges: finalEdges,
31628
32363
  groups: finalGroups,
31629
- width: bbMaxX + shiftX + MARGIN4,
31630
- height: bbMaxY + shiftY + MARGIN4
32364
+ width: bbMaxX + shiftX + MARGIN5,
32365
+ height: bbMaxY + shiftY + MARGIN5
31631
32366
  };
31632
32367
  }
31633
32368
  function applyParallelEdgeOffsets(layout) {
@@ -31660,13 +32395,13 @@ function applyParallelEdgeOffsets(layout) {
31660
32395
  }))
31661
32396
  };
31662
32397
  }
31663
- var MARGIN4, MAX_PARALLEL_EDGES, PARALLEL_SPACING, PHI, NODE_HEIGHT, NODE_WIDTH, DESC_NODE_WIDTH, DESC_FONT_SIZE2, DESC_LINE_HEIGHT2, DESC_PADDING, SEPARATOR_GAP4, MAX_DESC_LINES2, MAX_LABEL_LINES, LABEL_LINE_HEIGHT, LABEL_PAD, VALUE_ROW_FONT, VALUE_ROW_H;
32398
+ var MARGIN5, MAX_PARALLEL_EDGES, PARALLEL_SPACING, PHI, NODE_HEIGHT, NODE_WIDTH, DESC_NODE_WIDTH, DESC_FONT_SIZE2, DESC_LINE_HEIGHT2, DESC_PADDING, SEPARATOR_GAP4, MAX_DESC_LINES2, MAX_LABEL_LINES, LABEL_LINE_HEIGHT, LABEL_PAD, VALUE_ROW_FONT, VALUE_ROW_H;
31664
32399
  var init_layout5 = __esm({
31665
32400
  "src/boxes-and-lines/layout.ts"() {
31666
32401
  "use strict";
31667
32402
  init_text_measure();
31668
32403
  init_notes();
31669
- MARGIN4 = 40;
32404
+ MARGIN5 = 40;
31670
32405
  MAX_PARALLEL_EDGES = 5;
31671
32406
  PARALLEL_SPACING = 22;
31672
32407
  PHI = 1.618;
@@ -31837,7 +32572,7 @@ function layoutMindmap(parsed, _palette, options) {
31837
32572
  leafWidth: ctx.structural(LEAF_WIDTH),
31838
32573
  hGap: ctx.aesthetic(H_GAP2),
31839
32574
  vGap: ctx.aesthetic(V_GAP2),
31840
- margin: ctx.aesthetic(MARGIN5),
32575
+ margin: ctx.aesthetic(MARGIN6),
31841
32576
  multiRootGap: ctx.aesthetic(MULTI_ROOT_GAP)
31842
32577
  };
31843
32578
  populateDepthCache(roots);
@@ -32215,7 +32950,7 @@ function populateDepthCache(roots) {
32215
32950
  };
32216
32951
  walk(roots, 0);
32217
32952
  }
32218
- var ROOT_WIDTH, DEPTH1_WIDTH, LEAF_WIDTH, SINGLE_LABEL_HEIGHT, LABEL_LINE_HEIGHT2, DESC_LINE_HEIGHT3, NODE_V_PAD, H_GAP2, V_GAP2, MARGIN5, MULTI_ROOT_GAP, nodeDepthCache;
32953
+ var ROOT_WIDTH, DEPTH1_WIDTH, LEAF_WIDTH, SINGLE_LABEL_HEIGHT, LABEL_LINE_HEIGHT2, DESC_LINE_HEIGHT3, NODE_V_PAD, H_GAP2, V_GAP2, MARGIN6, MULTI_ROOT_GAP, nodeDepthCache;
32219
32954
  var init_layout6 = __esm({
32220
32955
  "src/mindmap/layout.ts"() {
32221
32956
  "use strict";
@@ -32231,7 +32966,7 @@ var init_layout6 = __esm({
32231
32966
  NODE_V_PAD = 10;
32232
32967
  H_GAP2 = 40;
32233
32968
  V_GAP2 = 12;
32234
- MARGIN5 = 40;
32969
+ MARGIN6 = 40;
32235
32970
  MULTI_ROOT_GAP = 60;
32236
32971
  nodeDepthCache = /* @__PURE__ */ new Map();
32237
32972
  }
@@ -32489,7 +33224,7 @@ function renderMindmap(container, parsed, layout, palette, isDark, onClickItem,
32489
33224
  );
32490
33225
  const nodeG = mainG.append("g").attr("class", "mindmap-node").attr("data-line-number", node.lineNumber);
32491
33226
  if (activeTagGroup) {
32492
- const tagKey = activeTagGroup.toLowerCase();
33227
+ const tagKey = tagAttrKey(activeTagGroup);
32493
33228
  const metaValue = node.metadata[tagKey];
32494
33229
  if (metaValue) {
32495
33230
  nodeG.attr(`data-tag-${tagKey}`, metaValue.toLowerCase());
@@ -32608,6 +33343,7 @@ var DIAGRAM_PADDING7, TITLE_HEIGHT4, SINGLE_LABEL_HEIGHT2, LABEL_LINE_HEIGHT3, D
32608
33343
  var init_renderer7 = __esm({
32609
33344
  "src/mindmap/renderer.ts"() {
32610
33345
  "use strict";
33346
+ init_tag_groups();
32611
33347
  init_fonts();
32612
33348
  init_export_container();
32613
33349
  init_color_utils();
@@ -33818,7 +34554,7 @@ function resolveNodeColor4(el, tagGroups, activeGroupName, ancestors) {
33818
34554
  (g) => g.name.toLowerCase() === activeGroupName.toLowerCase()
33819
34555
  );
33820
34556
  if (!group) return void 0;
33821
- const key = group.name.toLowerCase();
34557
+ const key = tagAttrKey(group.name);
33822
34558
  let metaValue = el.metadata[key];
33823
34559
  if (!metaValue && ancestors) {
33824
34560
  for (const ancestor of ancestors) {
@@ -34020,8 +34756,8 @@ function layoutC4Context(parsed, activeTagGroup) {
34020
34756
  }
34021
34757
  }
34022
34758
  if (nodes.length > 0) {
34023
- const shiftX = MARGIN6 - minX;
34024
- const shiftY = MARGIN6 - minY;
34759
+ const shiftX = MARGIN7 - minX;
34760
+ const shiftY = MARGIN7 - minY;
34025
34761
  for (const node of nodes) {
34026
34762
  node.x += shiftX;
34027
34763
  node.y += shiftY;
@@ -34033,12 +34769,12 @@ function layoutC4Context(parsed, activeTagGroup) {
34033
34769
  }
34034
34770
  }
34035
34771
  }
34036
- let totalWidth = nodes.length > 0 ? maxX - minX + MARGIN6 * 2 : 0;
34037
- let totalHeight = nodes.length > 0 ? maxY - minY + MARGIN6 * 2 : 0;
34772
+ let totalWidth = nodes.length > 0 ? maxX - minX + MARGIN7 * 2 : 0;
34773
+ let totalHeight = nodes.length > 0 ? maxY - minY + MARGIN7 * 2 : 0;
34038
34774
  const legendGroups = computeLegendGroups3(parsed.tagGroups);
34039
34775
  if (legendGroups.length > 0) {
34040
- const legendY = totalHeight + MARGIN6;
34041
- let legendX = MARGIN6;
34776
+ const legendY = totalHeight + MARGIN7;
34777
+ let legendX = MARGIN7;
34042
34778
  for (const lg of legendGroups) {
34043
34779
  lg.x = legendX;
34044
34780
  lg.y = legendY;
@@ -34370,8 +35106,8 @@ function layoutC4Containers(parsed, systemName, activeTagGroup) {
34370
35106
  if (pt.y > maxY) maxY = pt.y;
34371
35107
  }
34372
35108
  }
34373
- const shiftX = MARGIN6 - minX;
34374
- const shiftY = MARGIN6 - minY;
35109
+ const shiftX = MARGIN7 - minX;
35110
+ const shiftY = MARGIN7 - minY;
34375
35111
  for (const node of nodes) {
34376
35112
  node.x += shiftX;
34377
35113
  node.y += shiftY;
@@ -34388,12 +35124,12 @@ function layoutC4Containers(parsed, systemName, activeTagGroup) {
34388
35124
  pt.y += shiftY;
34389
35125
  }
34390
35126
  }
34391
- let totalWidth = maxX - minX + MARGIN6 * 2;
34392
- let totalHeight = maxY - minY + MARGIN6 * 2;
35127
+ let totalWidth = maxX - minX + MARGIN7 * 2;
35128
+ let totalHeight = maxY - minY + MARGIN7 * 2;
34393
35129
  const legendGroups = computeLegendGroups3(parsed.tagGroups);
34394
35130
  if (legendGroups.length > 0) {
34395
- const legendY = totalHeight + MARGIN6;
34396
- let legendX = MARGIN6;
35131
+ const legendY = totalHeight + MARGIN7;
35132
+ let legendX = MARGIN7;
34397
35133
  for (const lg of legendGroups) {
34398
35134
  lg.x = legendX;
34399
35135
  lg.y = legendY;
@@ -34785,8 +35521,8 @@ function layoutC4Components(parsed, systemName, containerName, activeTagGroup) {
34785
35521
  if (pt.y > maxY) maxY = pt.y;
34786
35522
  }
34787
35523
  }
34788
- const shiftX = MARGIN6 - minX;
34789
- const shiftY = MARGIN6 - minY;
35524
+ const shiftX = MARGIN7 - minX;
35525
+ const shiftY = MARGIN7 - minY;
34790
35526
  for (const node of nodes) {
34791
35527
  node.x += shiftX;
34792
35528
  node.y += shiftY;
@@ -34803,12 +35539,12 @@ function layoutC4Components(parsed, systemName, containerName, activeTagGroup) {
34803
35539
  pt.y += shiftY;
34804
35540
  }
34805
35541
  }
34806
- let totalWidth = maxX - minX + MARGIN6 * 2;
34807
- let totalHeight = maxY - minY + MARGIN6 * 2;
35542
+ let totalWidth = maxX - minX + MARGIN7 * 2;
35543
+ let totalHeight = maxY - minY + MARGIN7 * 2;
34808
35544
  const legendGroups = computeLegendGroups3(parsed.tagGroups);
34809
35545
  if (legendGroups.length > 0) {
34810
- const legendY = totalHeight + MARGIN6;
34811
- let legendX = MARGIN6;
35546
+ const legendY = totalHeight + MARGIN7;
35547
+ let legendX = MARGIN7;
34812
35548
  for (const lg of legendGroups) {
34813
35549
  lg.x = legendX;
34814
35550
  lg.y = legendY;
@@ -35096,8 +35832,8 @@ function layoutC4Deployment(parsed, activeTagGroup) {
35096
35832
  if (pt.y > maxY) maxY = pt.y;
35097
35833
  }
35098
35834
  }
35099
- const shiftX = MARGIN6 - minX;
35100
- const shiftY = MARGIN6 - minY;
35835
+ const shiftX = MARGIN7 - minX;
35836
+ const shiftY = MARGIN7 - minY;
35101
35837
  for (const node of nodes) {
35102
35838
  node.x += shiftX;
35103
35839
  node.y += shiftY;
@@ -35112,12 +35848,12 @@ function layoutC4Deployment(parsed, activeTagGroup) {
35112
35848
  pt.y += shiftY;
35113
35849
  }
35114
35850
  }
35115
- let totalWidth = maxX - minX + MARGIN6 * 2;
35116
- let totalHeight = maxY - minY + MARGIN6 * 2;
35851
+ let totalWidth = maxX - minX + MARGIN7 * 2;
35852
+ let totalHeight = maxY - minY + MARGIN7 * 2;
35117
35853
  const legendGroups = computeLegendGroups3(parsed.tagGroups);
35118
35854
  if (legendGroups.length > 0) {
35119
- const legendY = totalHeight + MARGIN6;
35120
- let legendX = MARGIN6;
35855
+ const legendY = totalHeight + MARGIN7;
35856
+ let legendX = MARGIN7;
35121
35857
  for (const lg of legendGroups) {
35122
35858
  lg.x = legendX;
35123
35859
  lg.y = legendY;
@@ -35137,10 +35873,11 @@ function layoutC4Deployment(parsed, activeTagGroup) {
35137
35873
  height: totalHeight
35138
35874
  };
35139
35875
  }
35140
- var gNode, gEdge, MIN_NODE_WIDTH, MAX_NODE_WIDTH, TYPE_LABEL_HEIGHT, DIVIDER_GAP, NAME_HEIGHT, NAME_FONT_SIZE, DESC_LINE_HEIGHT5, DESC_FONT_SIZE4, CARD_V_PAD3, CARD_H_PAD3, META_LINE_HEIGHT4, META_FONT_SIZE4, MARGIN6, BOUNDARY_PAD, GROUP_BOUNDARY_PAD, EDGE_NODE_COLLISION_WEIGHT, META_EXCLUDE_KEYS;
35876
+ var gNode, gEdge, MIN_NODE_WIDTH, MAX_NODE_WIDTH, TYPE_LABEL_HEIGHT, DIVIDER_GAP, NAME_HEIGHT, NAME_FONT_SIZE, DESC_LINE_HEIGHT5, DESC_FONT_SIZE4, CARD_V_PAD3, CARD_H_PAD3, META_LINE_HEIGHT4, META_FONT_SIZE4, MARGIN7, BOUNDARY_PAD, GROUP_BOUNDARY_PAD, EDGE_NODE_COLLISION_WEIGHT, META_EXCLUDE_KEYS;
35141
35877
  var init_layout8 = __esm({
35142
35878
  "src/c4/layout.ts"() {
35143
35879
  "use strict";
35880
+ init_tag_groups();
35144
35881
  init_legend_constants();
35145
35882
  init_text_measure();
35146
35883
  gNode = (g, name) => g.node(name);
@@ -35157,7 +35894,7 @@ var init_layout8 = __esm({
35157
35894
  CARD_H_PAD3 = 20;
35158
35895
  META_LINE_HEIGHT4 = 16;
35159
35896
  META_FONT_SIZE4 = 11;
35160
- MARGIN6 = 40;
35897
+ MARGIN7 = 40;
35161
35898
  BOUNDARY_PAD = 40;
35162
35899
  GROUP_BOUNDARY_PAD = 24;
35163
35900
  EDGE_NODE_COLLISION_WEIGHT = 5e3;
@@ -35314,7 +36051,7 @@ function renderC4Context(container, parsed, layout, palette, isDark, onClickItem
35314
36051
  for (const node of layout.nodes) {
35315
36052
  const nodeG = contentG.append("g").attr("transform", `translate(${node.x}, ${node.y})`).attr("class", "c4-card").attr("data-line-number", String(node.lineNumber)).attr("data-node-id", node.id);
35316
36053
  if (activeTagGroup) {
35317
- const tagKey = activeTagGroup.toLowerCase();
36054
+ const tagKey = tagAttrKey(activeTagGroup);
35318
36055
  const tagValue = node.metadata[tagKey];
35319
36056
  if (tagValue) {
35320
36057
  nodeG.attr(`data-tag-${tagKey}`, tagValue.toLowerCase());
@@ -35873,7 +36610,7 @@ function renderC4Containers(container, parsed, layout, palette, isDark, onClickI
35873
36610
  for (const node of layout.nodes) {
35874
36611
  const nodeG = contentG.append("g").attr("transform", `translate(${node.x}, ${node.y})`).attr("class", "c4-card").attr("data-line-number", String(node.lineNumber)).attr("data-node-id", node.id);
35875
36612
  if (activeTagGroup) {
35876
- const tagKey = activeTagGroup.toLowerCase();
36613
+ const tagKey = tagAttrKey(activeTagGroup);
35877
36614
  const tagValue = node.metadata[tagKey];
35878
36615
  if (tagValue) {
35879
36616
  nodeG.attr(`data-tag-${tagKey}`, tagValue.toLowerCase());
@@ -36137,6 +36874,7 @@ var DIAGRAM_PADDING8, MAX_SCALE5, TITLE_HEIGHT6, TYPE_FONT_SIZE, NAME_FONT_SIZE2
36137
36874
  var init_renderer9 = __esm({
36138
36875
  "src/c4/renderer.ts"() {
36139
36876
  "use strict";
36877
+ init_tag_groups();
36140
36878
  init_fonts();
36141
36879
  init_color_utils();
36142
36880
  init_inline_markdown();
@@ -38200,7 +38938,7 @@ function separateGroups(groups, nodes, isLR, maxIterations = 20) {
38200
38938
  const crossOverlap = isLR ? Math.min(ga.x + ga.width, gb.x + gb.width) - Math.max(ga.x, gb.x) : Math.min(ga.y + ga.height, gb.y + gb.height) - Math.max(ga.y, gb.y);
38201
38939
  if (crossOverlap <= 0) continue;
38202
38940
  anyOverlap = true;
38203
- const shift = primaryOverlap + GROUP_GAP;
38941
+ const shift = primaryOverlap + GROUP_GAP2;
38204
38942
  const aCenter = isLR ? ga.y + ga.height / 2 : ga.x + ga.width / 2;
38205
38943
  const bCenter = isLR ? gb.y + gb.height / 2 : gb.x + gb.width / 2;
38206
38944
  const groupToShift = aCenter <= bCenter ? gb : ga;
@@ -38494,7 +39232,7 @@ function layoutInfra(computed, expandedNodeIds, collapsedNodes) {
38494
39232
  height: totalHeight
38495
39233
  };
38496
39234
  }
38497
- var MIN_NODE_WIDTH2, NODE_HEADER_HEIGHT, META_LINE_HEIGHT5, NODE_SEPARATOR_GAP, NODE_PAD_BOTTOM, ROLE_DOT_ROW, COLLAPSE_BAR_HEIGHT3, NODE_FONT_SIZE3, META_FONT_SIZE5, EDGE_LABEL_FONT_SIZE8, PADDING_X3, GROUP_PADDING2, GROUP_HEADER_HEIGHT, EDGE_MARGIN, DISPLAY_KEYS, DISPLAY_NAMES, GROUP_GAP;
39235
+ var MIN_NODE_WIDTH2, NODE_HEADER_HEIGHT, META_LINE_HEIGHT5, NODE_SEPARATOR_GAP, NODE_PAD_BOTTOM, ROLE_DOT_ROW, COLLAPSE_BAR_HEIGHT3, NODE_FONT_SIZE3, META_FONT_SIZE5, EDGE_LABEL_FONT_SIZE8, PADDING_X3, GROUP_PADDING2, GROUP_HEADER_HEIGHT, EDGE_MARGIN, DISPLAY_KEYS, DISPLAY_NAMES, GROUP_GAP2;
38498
39236
  var init_layout10 = __esm({
38499
39237
  "src/infra/layout.ts"() {
38500
39238
  "use strict";
@@ -38549,7 +39287,7 @@ var init_layout10 = __esm({
38549
39287
  "retention-hours": "retention",
38550
39288
  partitions: "partitions"
38551
39289
  };
38552
- GROUP_GAP = GROUP_PADDING2 * 2 + GROUP_HEADER_HEIGHT;
39290
+ GROUP_GAP2 = GROUP_PADDING2 * 2 + GROUP_HEADER_HEIGHT;
38553
39291
  }
38554
39292
  });
38555
39293
 
@@ -39358,7 +40096,7 @@ function resolveActiveTagStroke(node, activeGroup, tagGroups, palette) {
39358
40096
  (t) => t.name.toLowerCase() === activeGroup.toLowerCase()
39359
40097
  );
39360
40098
  if (!tg) return null;
39361
- const tagKey = (tg.alias ?? tg.name).toLowerCase();
40099
+ const tagKey = tagAttrKey(tg.alias ?? tg.name);
39362
40100
  const tagVal = node.tags[tagKey];
39363
40101
  if (!tagVal) return null;
39364
40102
  const tv = tg.values.find(
@@ -39687,7 +40425,7 @@ function computeInfraLegendGroups(nodes, tagGroups, palette, edges) {
39687
40425
  groups.push({
39688
40426
  name: tg.name,
39689
40427
  type: "tag",
39690
- tagKey: (tg.alias ?? tg.name).toLowerCase(),
40428
+ tagKey: tagAttrKey(tg.alias ?? tg.name),
39691
40429
  entries,
39692
40430
  width: LEGEND_CAPSULE_PAD * 2 + pillWidth2 + 4 + entriesWidth2,
39693
40431
  minifiedWidth: pillWidth2
@@ -39735,7 +40473,7 @@ function renderLegend3(rootSvg, legendGroups, totalWidth, legendY, palette, isDa
39735
40473
  });
39736
40474
  legendG.selectAll("[data-legend-group]").classed("infra-legend-group", true);
39737
40475
  for (const group of legendGroups) {
39738
- const groupKey2 = group.name.toLowerCase();
40476
+ const groupKey2 = tagAttrKey(group.name);
39739
40477
  for (const entry of group.entries) {
39740
40478
  const entryEl = legendG.select(
39741
40479
  `[data-legend-group="${groupKey2}"] [data-legend-entry="${entry.value.toLowerCase()}"]`
@@ -39958,6 +40696,7 @@ var NODE_FONT_SIZE4, META_FONT_SIZE6, META_LINE_HEIGHT6, EDGE_LABEL_FONT_SIZE9,
39958
40696
  var init_renderer10 = __esm({
39959
40697
  "src/infra/renderer.ts"() {
39960
40698
  "use strict";
40699
+ init_tag_groups();
39961
40700
  init_fonts();
39962
40701
  init_color_utils();
39963
40702
  init_colors();
@@ -40054,17 +40793,17 @@ function mulberry32(seed) {
40054
40793
  return ((t ^ t >>> 14) >>> 0) / 4294967296;
40055
40794
  };
40056
40795
  }
40057
- function standardNormal(rng3) {
40796
+ function standardNormal(rng4) {
40058
40797
  let u = 0;
40059
40798
  let v = 0;
40060
- while (u === 0) u = rng3();
40061
- while (v === 0) v = rng3();
40799
+ while (u === 0) u = rng4();
40800
+ while (v === 0) v = rng4();
40062
40801
  return Math.sqrt(-2 * Math.log(u)) * Math.cos(2 * Math.PI * v);
40063
40802
  }
40064
- function gamma(shape, rng3) {
40803
+ function gamma(shape, rng4) {
40065
40804
  if (shape < 1) {
40066
- const u = rng3();
40067
- return gamma(shape + 1, rng3) * Math.pow(u, 1 / shape);
40805
+ const u = rng4();
40806
+ return gamma(shape + 1, rng4) * Math.pow(u, 1 / shape);
40068
40807
  }
40069
40808
  const d = shape - 1 / 3;
40070
40809
  const c = 1 / Math.sqrt(9 * d);
@@ -40072,29 +40811,29 @@ function gamma(shape, rng3) {
40072
40811
  let x;
40073
40812
  let v;
40074
40813
  do {
40075
- x = standardNormal(rng3);
40814
+ x = standardNormal(rng4);
40076
40815
  v = 1 + c * x;
40077
40816
  } while (v <= 0);
40078
40817
  v = v * v * v;
40079
- const u = rng3();
40818
+ const u = rng4();
40080
40819
  if (u < 1 - 0.0331 * x * x * x * x) return d * v;
40081
40820
  if (Math.log(u) < x * x / 2 + d * (1 - v + Math.log(v))) return d * v;
40082
40821
  }
40083
40822
  }
40084
- function sampleBeta(alpha, beta, rng3) {
40085
- const x = gamma(alpha, rng3);
40086
- const y = gamma(beta, rng3);
40823
+ function sampleBeta(alpha, beta, rng4) {
40824
+ const x = gamma(alpha, rng4);
40825
+ const y = gamma(beta, rng4);
40087
40826
  return x / (x + y);
40088
40827
  }
40089
- function sampleBetaPert(o, m, p, rng3) {
40828
+ function sampleBetaPert(o, m, p, rng4) {
40090
40829
  if (o === p) return m;
40091
40830
  const range = p - o;
40092
40831
  const alpha = 1 + 4 * (m - o) / range;
40093
40832
  const beta = 1 + 4 * (p - m) / range;
40094
- return o + sampleBeta(alpha, beta, rng3) * range;
40833
+ return o + sampleBeta(alpha, beta, rng4) * range;
40095
40834
  }
40096
40835
  function simulate(resolved, expanded, _predecessors, _successors, topo, terminals, poisoned, opts) {
40097
- const rng3 = mulberry32(opts.seed);
40836
+ const rng4 = mulberry32(opts.seed);
40098
40837
  const expById = /* @__PURE__ */ new Map();
40099
40838
  for (const e of expanded) expById.set(e.id, e);
40100
40839
  const completionTimes = new Array(opts.trials);
@@ -40117,7 +40856,7 @@ function simulate(resolved, expanded, _predecessors, _successors, topo, terminal
40117
40856
  } else if (exp.o === exp.p) {
40118
40857
  duration = exp.m;
40119
40858
  } else {
40120
- duration = sampleBetaPert(exp.o, exp.m, exp.p, rng3);
40859
+ duration = sampleBetaPert(exp.o, exp.m, exp.p, rng4);
40121
40860
  }
40122
40861
  let esLower = 0;
40123
40862
  let efLower = -Infinity;
@@ -44407,7 +45146,7 @@ function renderGantt(container, resolved, palette, isDark, options, exportDims)
44407
45146
  const ctx = exportDims ? ScaleContext.identity() : ScaleContext.from(containerWidth, idealWidth);
44408
45147
  const sBarH = ctx.structural(BAR_H);
44409
45148
  const sRowGap = ctx.structural(ROW_GAP);
44410
- const sGroupGap = ctx.structural(GROUP_GAP2);
45149
+ const sGroupGap = ctx.structural(GROUP_GAP3);
44411
45150
  const sMilestoneSize = ctx.structural(MILESTONE_SIZE);
44412
45151
  const sBottomMargin = ctx.aesthetic(BOTTOM_MARGIN);
44413
45152
  const sRightMargin = ctx.aesthetic(RIGHT_MARGIN);
@@ -44579,7 +45318,7 @@ function renderGantt(container, resolved, palette, isDark, options, exportDims)
44579
45318
  (tg) => tg.name.toLowerCase() === currentSwimlaneGroup.toLowerCase()
44580
45319
  );
44581
45320
  if (tagGroup) {
44582
- const tagKey = tagGroup.name.toLowerCase();
45321
+ const tagKey = tagAttrKey(tagGroup.name);
44583
45322
  for (const rt of resolved.tasks) {
44584
45323
  let value = rt.effectiveMetadata[tagKey];
44585
45324
  if (!value && tagGroup.defaultValue) value = tagGroup.defaultValue;
@@ -45320,7 +46059,7 @@ function renderTagLegend(svg, chartG, tagGroups, activeGroupName, chartLeftMargi
45320
46059
  const usedValues = /* @__PURE__ */ new Map();
45321
46060
  if (resolvedTasks) {
45322
46061
  for (const group of visibleGroups) {
45323
- const key = group.name.toLowerCase();
46062
+ const key = tagAttrKey(group.name);
45324
46063
  const used = /* @__PURE__ */ new Set();
45325
46064
  for (const rt of resolvedTasks) {
45326
46065
  const val = rt.effectiveMetadata[key];
@@ -45331,7 +46070,7 @@ function renderTagLegend(svg, chartG, tagGroups, activeGroupName, chartLeftMargi
45331
46070
  }
45332
46071
  const filteredEntries = /* @__PURE__ */ new Map();
45333
46072
  for (const group of visibleGroups) {
45334
- const key = group.name.toLowerCase();
46073
+ const key = tagAttrKey(group.name);
45335
46074
  const used = usedValues.get(key);
45336
46075
  if (used && used.size > 0) {
45337
46076
  filteredEntries.set(
@@ -45352,7 +46091,7 @@ function renderTagLegend(svg, chartG, tagGroups, activeGroupName, chartLeftMargi
45352
46091
  const pillW = measureLegendText(group.name, LEGEND_PILL_FONT_SIZE) + LEGEND_PILL_PAD + iconReserve;
45353
46092
  let groupW = pillW;
45354
46093
  if (isActive) {
45355
- const entries = filteredEntries.get(group.name.toLowerCase()) ?? group.entries;
46094
+ const entries = filteredEntries.get(tagAttrKey(group.name)) ?? group.entries;
45356
46095
  let entriesW = 0;
45357
46096
  for (const entry of entries) {
45358
46097
  entriesW += LEGEND_DOT_R * 2 + LEGEND_ENTRY_DOT_GAP + measureLegendText(entry.value, LEGEND_ENTRY_FONT_SIZE) + LEGEND_ENTRY_TRAIL;
@@ -45409,7 +46148,7 @@ function renderTagLegend(svg, chartG, tagGroups, activeGroupName, chartLeftMargi
45409
46148
  },
45410
46149
  ...onControlsToggle !== void 0 && { onControlsToggle },
45411
46150
  onEntryHover: (groupName, entryValue) => {
45412
- const tagKey = groupName.toLowerCase();
46151
+ const tagKey = tagAttrKey(groupName);
45413
46152
  if (entryValue) {
45414
46153
  const ev = entryValue.toLowerCase();
45415
46154
  chartG.selectAll(".gantt-task").each(function() {
@@ -46099,7 +46838,7 @@ function buildTagLaneRowList(resolved, swimlaneGroup, collapsedLanes) {
46099
46838
  (g) => g.name.toLowerCase() === swimlaneGroup.toLowerCase()
46100
46839
  );
46101
46840
  if (!tagGroup) return null;
46102
- const tagKey = tagGroup.name.toLowerCase();
46841
+ const tagKey = tagAttrKey(tagGroup.name);
46103
46842
  const rows = [];
46104
46843
  const buckets = /* @__PURE__ */ new Map();
46105
46844
  const unbucketed = [];
@@ -46283,10 +47022,11 @@ function renderTimeScaleHorizontal(g, scale, _innerWidth, innerHeight, textColor
46283
47022
  g.append("text").attr("class", "gantt-scale-tick").attr("x", tick.pos).attr("y", innerHeight + tickLen + 12).attr("text-anchor", "middle").attr("font-size", "10px").attr("fill", textColor).attr("opacity", opacity).text(tick.label);
46284
47023
  }
46285
47024
  }
46286
- var BAR_H, ROW_GAP, GROUP_GAP2, MILESTONE_SIZE, MIN_LEFT_MARGIN, BOTTOM_MARGIN, RIGHT_MARGIN, LABEL_PAD2, LABEL_GAP, BAND_ACCENT_W, BAND_RADIUS, bandClipCounter, JS_DAY_TO_WEEKDAY2, ERA_COLORS, SPRINT_BAND_OPACITY, SPRINT_HOVER_OPACITY, SPRINT_BOUNDARY_OPACITY, FADE_OPACITY;
47025
+ var BAR_H, ROW_GAP, GROUP_GAP3, MILESTONE_SIZE, MIN_LEFT_MARGIN, BOTTOM_MARGIN, RIGHT_MARGIN, LABEL_PAD2, LABEL_GAP, BAND_ACCENT_W, BAND_RADIUS, bandClipCounter, JS_DAY_TO_WEEKDAY2, ERA_COLORS, SPRINT_BAND_OPACITY, SPRINT_HOVER_OPACITY, SPRINT_BOUNDARY_OPACITY, FADE_OPACITY;
46287
47026
  var init_renderer12 = __esm({
46288
47027
  "src/gantt/renderer.ts"() {
46289
47028
  "use strict";
47029
+ init_tag_groups();
46290
47030
  init_fonts();
46291
47031
  init_palettes();
46292
47032
  init_color_utils();
@@ -46301,7 +47041,7 @@ var init_renderer12 = __esm({
46301
47041
  init_title_constants();
46302
47042
  BAR_H = 22;
46303
47043
  ROW_GAP = 6;
46304
- GROUP_GAP2 = 14;
47044
+ GROUP_GAP3 = 14;
46305
47045
  MILESTONE_SIZE = 10;
46306
47046
  MIN_LEFT_MARGIN = 120;
46307
47047
  BOTTOM_MARGIN = 40;
@@ -48596,7 +49336,7 @@ function renderJourneyMap(container, parsed, palette, isDark, options) {
48596
49336
  const panelWidth = 280;
48597
49337
  const textAreaWidth = panelWidth - silhouetteZone - CARD_PADDING_X3;
48598
49338
  const descLineH = 14;
48599
- const descLines = parsed.persona.description ? wrapText2(parsed.persona.description, textAreaWidth, FONT_SIZE_META) : [];
49339
+ const descLines = parsed.persona.description ? wrapNoteBody(parsed.persona.description, textAreaWidth, FONT_SIZE_META) : [];
48600
49340
  const descRowH = descLines.length > 0 ? descLines.length * descLineH + 8 : 0;
48601
49341
  const panelHeight = titleRowH + descRowH;
48602
49342
  const panelX = layout.totalWidth - PADDING2 - panelWidth;
@@ -48621,8 +49361,14 @@ function renderJourneyMap(container, parsed, palette, isDark, options) {
48621
49361
  personaG.append("rect").attr("x", panelX).attr("y", panelY).attr("width", panelWidth).attr("height", panelHeight).attr("rx", CARD_RADIUS).attr("fill", "none").attr("stroke", personaColor).attr("stroke-width", CARD_STROKE_WIDTH2);
48622
49362
  personaG.append("text").attr("x", textX).attr("y", panelY + CARD_PADDING_Y3 + FONT_SIZE_STEP).attr("font-size", FONT_SIZE_STEP).attr("font-weight", "500").attr("fill", onPersonaText).text(parsed.persona.name);
48623
49363
  for (let li = 0; li < descLines.length; li++) {
48624
- const lineEl = personaG.append("text").attr("x", textX).attr("y", panelY + titleRowH + descLineH * (li + 1)).attr("font-size", FONT_SIZE_META).attr("fill", onPersonaText);
48625
- renderInlineText(lineEl, descLines[li], palette, FONT_SIZE_META);
49364
+ const line11 = descLines[li];
49365
+ const indent = line11.kind === "plain" ? 0 : NOTE_BULLET_INDENT;
49366
+ const lineY = panelY + titleRowH + descLineH * (li + 1);
49367
+ if (line11.kind === "bullet-first") {
49368
+ personaG.append("text").attr("x", textX).attr("y", lineY).attr("font-size", FONT_SIZE_META).attr("fill", onPersonaText).text("\u2022");
49369
+ }
49370
+ const lineEl = personaG.append("text").attr("x", textX + indent).attr("y", lineY).attr("font-size", FONT_SIZE_META).attr("fill", onPersonaText);
49371
+ renderInlineText(lineEl, line11.text, palette, FONT_SIZE_META);
48626
49372
  }
48627
49373
  if (onNavigateToLine) {
48628
49374
  personaG.style("cursor", "pointer").on("click", () => {
@@ -48661,7 +49407,7 @@ function renderJourneyMap(container, parsed, palette, isDark, options) {
48661
49407
  return;
48662
49408
  }
48663
49409
  const isScore = groupName === "Score";
48664
- const attrName = isScore ? "data-score" : `data-tag-${groupName.toLowerCase()}`;
49410
+ const attrName = isScore ? "data-score" : `data-tag-${tagAttrKey(groupName)}`;
48665
49411
  const matches = (el) => el.getAttribute(attrName) === entryValue;
48666
49412
  svg.selectAll(".journey-step").each(function() {
48667
49413
  const hit = matches(this);
@@ -49142,7 +49888,7 @@ function resolveStepColor(step, scoreColor, activeGroup, tagGroups, _palette) {
49142
49888
  (g) => g.name.toLowerCase() === activeGroup.toLowerCase()
49143
49889
  );
49144
49890
  if (!group) return void 0;
49145
- const tagValue = step.tags[group.name.toLowerCase()];
49891
+ const tagValue = step.tags[tagAttrKey(group.name)];
49146
49892
  if (!tagValue) return void 0;
49147
49893
  const entry = group.entries.find(
49148
49894
  (e) => e.value.toLowerCase() === tagValue.toLowerCase()
@@ -49357,11 +50103,13 @@ var DIAGRAM_PADDING13, PADDING2, CARD_PADDING_X3, CARD_PADDING_Y3, CARD_HEADER_H
49357
50103
  var init_renderer14 = __esm({
49358
50104
  "src/journey-map/renderer.ts"() {
49359
50105
  "use strict";
50106
+ init_tag_groups();
49360
50107
  init_color_utils();
49361
50108
  init_fonts();
49362
50109
  init_parser23();
49363
50110
  init_layout13();
49364
50111
  init_inline_markdown();
50112
+ init_note_box();
49365
50113
  init_legend_integration();
49366
50114
  init_tag_groups();
49367
50115
  init_scaling();
@@ -52196,7 +52944,7 @@ function layoutMap(resolvedIn, data, size, opts) {
52196
52944
  (g) => g.name.toLowerCase() === groupName.toLowerCase()
52197
52945
  );
52198
52946
  if (!group) return null;
52199
- const val = tags[group.name.toLowerCase()] ?? group.defaultValue;
52947
+ const val = tags[tagAttrKey(group.name)] ?? group.defaultValue;
52200
52948
  if (!val) return null;
52201
52949
  const entry = group.entries.find(
52202
52950
  (e) => e.value.toLowerCase() === val.toLowerCase()
@@ -52810,7 +53558,7 @@ function layoutMap(resolvedIn, data, size, opts) {
52810
53558
  if (directHex)
52811
53559
  return { fill: directHex, stroke: mix(directHex, palette.text, 18) };
52812
53560
  for (const group of resolved.tagGroups) {
52813
- const val = p.tags[group.name.toLowerCase()];
53561
+ const val = p.tags[tagAttrKey(group.name)];
52814
53562
  if (!val) continue;
52815
53563
  const entry = group.entries.find(
52816
53564
  (e) => e.value.toLowerCase() === val.toLowerCase()
@@ -52825,7 +53573,7 @@ function layoutMap(resolvedIn, data, size, opts) {
52825
53573
  };
52826
53574
  const lineColor = (tags) => {
52827
53575
  for (const group of resolved.tagGroups) {
52828
- const val = tags[group.name.toLowerCase()];
53576
+ const val = tags[tagAttrKey(group.name)];
52829
53577
  if (!val) continue;
52830
53578
  const entry = group.entries.find(
52831
53579
  (e) => e.value.toLowerCase() === val.toLowerCase()
@@ -54091,6 +54839,7 @@ var FIT_PAD, PATH_DIGITS, THIN_TOL, ALBERS_SKEW_MAX, RAMP_FLOOR2, R_DEFAULT, R_M
54091
54839
  var init_layout15 = __esm({
54092
54840
  "src/map/layout.ts"() {
54093
54841
  "use strict";
54842
+ init_tag_groups();
54094
54843
  init_color_utils();
54095
54844
  init_geo();
54096
54845
  init_colorize();
@@ -56504,7 +57253,7 @@ function resolveSequenceTags(parsed, activeTagGroup) {
56504
57253
  }
56505
57254
  return result;
56506
57255
  }
56507
- const groupKey2 = group.name.toLowerCase();
57256
+ const groupKey2 = tagAttrKey(group.name);
56508
57257
  const participantMeta = /* @__PURE__ */ new Map();
56509
57258
  for (const p of parsed.participants) {
56510
57259
  participantMeta.set(p.id, { ...p.metadata || {} });
@@ -56540,6 +57289,7 @@ function resolveSequenceTags(parsed, activeTagGroup) {
56540
57289
  var init_tag_resolution = __esm({
56541
57290
  "src/sequence/tag-resolution.ts"() {
56542
57291
  "use strict";
57292
+ init_tag_groups();
56543
57293
  }
56544
57294
  });
56545
57295
 
@@ -57015,7 +57765,7 @@ function renderSequenceDiagram(container, parsed, palette, isDark, _onNavigateTo
57015
57765
  }
57016
57766
  }
57017
57767
  const getTagColor = (value) => value ? tagValueToColor.get(value.toLowerCase()) : void 0;
57018
- const tagKey = activeTagGroup?.toLowerCase();
57768
+ const tagKey = activeTagGroup ? tagAttrKey(activeTagGroup) : void 0;
57019
57769
  const hiddenMsgIndices = /* @__PURE__ */ new Set();
57020
57770
  if (collapsedSections && collapsedSections.size > 0) {
57021
57771
  const sectionGroups = groupMessagesBySection(elements, messages);
@@ -57607,11 +58357,7 @@ function renderSequenceDiagram(container, parsed, palette, isDark, _onNavigateTo
57607
58357
  const boxH = sBoxH + GROUP_PADDING_TOP2 + GROUP_PADDING_BOTTOM2;
57608
58358
  const groupTagValue = tagKey && group.metadata?.[tagKey];
57609
58359
  const groupTagColor = getTagColor(groupTagValue || void 0);
57610
- const fillColor = groupTagColor ? mix(
57611
- groupTagColor,
57612
- themeBaseBg(palette, isDark),
57613
- isDark ? 15 : 20
57614
- ) : isDark ? palette.surface : palette.bg;
58360
+ const fillColor = groupTagColor ? mix(groupTagColor, themeBaseBg(palette, isDark), isDark ? 15 : 20) : isDark ? palette.surface : palette.bg;
57615
58361
  const strokeColor = groupTagColor || palette.textMuted;
57616
58362
  const groupG = svg.append("g").attr("class", "group-box-wrapper").attr("data-group-toggle", "").attr("data-group-line", String(group.lineNumber)).attr("tabindex", "0").attr("role", "button").attr("aria-expanded", "true").attr("cursor", "pointer");
57617
58363
  groupG.append("title").text("Click to collapse");
@@ -57661,11 +58407,7 @@ function renderSequenceDiagram(container, parsed, palette, isDark, _onNavigateTo
57661
58407
  );
57662
58408
  participantG.attr("data-group-toggle", "").attr("data-group-line", String(meta.lineNumber)).attr("tabindex", "0").attr("role", "button").attr("aria-expanded", "false").attr("cursor", "pointer");
57663
58409
  participantG.append("title").text("Click to expand");
57664
- const pFill = effectiveTagColor ? mix(
57665
- effectiveTagColor,
57666
- themeBaseBg(palette, isDark),
57667
- isDark ? 30 : 40
57668
- ) : isDark ? mix(palette.overlay, palette.surface, 50) : mix(palette.bg, palette.surface, 50);
58410
+ const pFill = effectiveTagColor ? mix(effectiveTagColor, themeBaseBg(palette, isDark), isDark ? 30 : 40) : isDark ? mix(palette.overlay, palette.surface, 50) : mix(palette.bg, palette.surface, 50);
57669
58411
  const pStroke = effectiveTagColor || palette.border;
57670
58412
  participantG.append("rect").attr("x", -boxW / 2).attr("y", -GROUP_PADDING_TOP2).attr("width", boxW).attr("height", fullH).attr("rx", 6).attr("fill", pFill).attr("stroke", pStroke).attr("stroke-width", 1.5);
57671
58413
  participantG.append("text").attr("x", 0).attr("y", -GROUP_PADDING_TOP2 + fullH / 2).attr("text-anchor", "middle").attr("dominant-baseline", "central").attr("fill", palette.text).attr("font-size", ctx.text(13)).attr("font-weight", 500).text(participant.label);
@@ -58161,6 +58903,7 @@ var PARTICIPANT_GAP, PARTICIPANT_BOX_WIDTH, PARTICIPANT_BOX_HEIGHT, LABEL_FONT_S
58161
58903
  var init_renderer20 = __esm({
58162
58904
  "src/sequence/renderer.ts"() {
58163
58905
  "use strict";
58906
+ init_tag_groups();
58164
58907
  init_color_utils();
58165
58908
  init_inline_markdown();
58166
58909
  init_fonts();
@@ -59319,6 +60062,7 @@ function renderArcDiagram(container, parsed, palette, _isDark, onClickItem, expo
59319
60062
  init_parser3();
59320
60063
 
59321
60064
  // src/timeline/renderer.ts
60065
+ init_tag_groups();
59322
60066
  init_fonts();
59323
60067
  init_time_ticks();
59324
60068
  init_scaling();
@@ -59583,7 +60327,7 @@ function setupTimeline(container, parsed, palette, isDark, exportDims, activeTag
59583
60327
  });
59584
60328
  let tagLanes = null;
59585
60329
  if (resolvedSwimlaneTG) {
59586
- const tagKey = resolvedSwimlaneTG.toLowerCase();
60330
+ const tagKey = tagAttrKey(resolvedSwimlaneTG);
59587
60331
  const tagGroup = parsed.timelineTagGroups.find(
59588
60332
  (g) => g.name.toLowerCase() === tagKey
59589
60333
  );
@@ -59889,7 +60633,7 @@ function renderTimelineTagLegendOverlay(container, parsed, palette, isDark, setu
59889
60633
  onTagStateChange?.(currentActiveGroup, currentSwimlaneGroup);
59890
60634
  },
59891
60635
  onEntryHover: (groupName, entryValue) => {
59892
- const tagKey = groupName.toLowerCase();
60636
+ const tagKey = tagAttrKey(groupName);
59893
60637
  if (entryValue) {
59894
60638
  const tagVal = entryValue.toLowerCase();
59895
60639
  fadeToTagValue(mainG, tagKey, tagVal);
@@ -59908,7 +60652,7 @@ function renderTimelineTagLegendOverlay(container, parsed, palette, isDark, setu
59908
60652
  }
59909
60653
  },
59910
60654
  onGroupRendered: (groupName, groupEl, isActive) => {
59911
- const groupKey2 = groupName.toLowerCase();
60655
+ const groupKey2 = tagAttrKey(groupName);
59912
60656
  groupEl.attr("data-tag-group", groupKey2);
59913
60657
  if (isActive && !viewMode) {
59914
60658
  const isSwimActive = currentSwimlaneGroup?.toLowerCase() === groupKey2;
@@ -63773,6 +64517,71 @@ function completeMapPlaces(query, gazetteer, opts) {
63773
64517
  });
63774
64518
  return [...cityItems, ...airportItems].slice(0, limit);
63775
64519
  }
64520
+ function searchMapLocations(query, gazetteer, opts) {
64521
+ const q = fold2(query);
64522
+ if (!q) return [];
64523
+ const limit = opts?.limit ?? 20;
64524
+ const rankOf = (hay) => hay === q ? 0 : hay.startsWith(q) ? 1 : hay.includes(q) ? 2 : -1;
64525
+ const cityRank = /* @__PURE__ */ new Map();
64526
+ const consider = (idx, rank) => {
64527
+ if (rank < 0) return;
64528
+ const prev = cityRank.get(idx);
64529
+ if (prev === void 0 || rank < prev) cityRank.set(idx, rank);
64530
+ };
64531
+ for (const [key, list] of Object.entries(gazetteer.byName)) {
64532
+ const r = rankOf(key);
64533
+ if (r >= 0) for (const i of list) consider(i, r);
64534
+ }
64535
+ for (const [key, idx] of Object.entries(gazetteer.alt)) {
64536
+ consider(idx, rankOf(key));
64537
+ }
64538
+ const cityItems = [...cityRank].filter(([i]) => gazetteer.cities[i] !== void 0).map(([i, rank]) => {
64539
+ const c = gazetteer.cities[i];
64540
+ const [, , iso, pop, name, sub] = c;
64541
+ const ambiguous = (gazetteer.byName[fold2(name)]?.length ?? 0) > 1;
64542
+ const qualifier = sub ?? iso;
64543
+ return {
64544
+ name,
64545
+ token: ambiguous ? `${name} ${qualifier}` : name,
64546
+ kind: "city",
64547
+ iso,
64548
+ ...sub !== void 0 && { sub },
64549
+ pop,
64550
+ detail: `${qualifier} \xB7 ${groupThousands(pop)}`,
64551
+ _rank: rank
64552
+ };
64553
+ });
64554
+ const airports = opts?.airports;
64555
+ const airportItems = [];
64556
+ if (airports?.airportIata) {
64557
+ for (const [code, idx] of Object.entries(airports.airportIata)) {
64558
+ const a = airports.airports[idx];
64559
+ if (!a) continue;
64560
+ const codeRank = rankOf(code);
64561
+ const nameRank = rankOf(fold2(a[4]));
64562
+ const rank = Math.min(
64563
+ codeRank < 0 ? 99 : codeRank,
64564
+ nameRank < 0 ? 99 : nameRank
64565
+ );
64566
+ if (rank > 2) continue;
64567
+ airportItems.push({
64568
+ name: a[4],
64569
+ token: code.toUpperCase(),
64570
+ kind: "airport",
64571
+ iso: a[2],
64572
+ pop: 0,
64573
+ detail: `Airport \xB7 ${a[4]}`,
64574
+ _rank: rank
64575
+ });
64576
+ }
64577
+ }
64578
+ return [...cityItems, ...airportItems].sort(
64579
+ (x, y) => x._rank - y._rank || // best match tier first
64580
+ (x.kind === y.kind ? 0 : x.kind === "city" ? -1 : 1) || // city before airport
64581
+ y.pop - x.pop || // populous city first
64582
+ (x.token < y.token ? -1 : x.token > y.token ? 1 : 0)
64583
+ ).slice(0, limit).map(({ _rank, ...rest }) => rest);
64584
+ }
63776
64585
  function completeMapRegions(query, regions, opts) {
63777
64586
  const q = fold2(query);
63778
64587
  if (!q) return [];
@@ -64996,6 +65805,7 @@ export {
64996
65805
  resolveTaskName,
64997
65806
  rollUpContextRelationships,
64998
65807
  sampleBetaPert,
65808
+ searchMapLocations,
64999
65809
  seriesColors,
65000
65810
  shade,
65001
65811
  shapeFill,