@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/index.cjs CHANGED
@@ -1158,6 +1158,9 @@ var init_parsing = __esm({
1158
1158
  });
1159
1159
 
1160
1160
  // src/utils/tag-groups.ts
1161
+ function tagAttrKey(name) {
1162
+ return name.trim().toLowerCase().replace(/[^a-z0-9_-]+/g, "-").replace(/^-+|-+$/g, "");
1163
+ }
1161
1164
  function stripDefaultModifier(text) {
1162
1165
  if (/\bdefault\s*$/.test(text)) {
1163
1166
  return { text: text.replace(/\s+default\s*$/, "").trim(), isDefault: true };
@@ -1303,17 +1306,17 @@ function parseTagDeclaration(line11) {
1303
1306
  function resolveTagColor(metadata, tagGroups, activeGroupName, isContainer) {
1304
1307
  if (!activeGroupName) return void 0;
1305
1308
  const group = tagGroups.find(
1306
- (g) => g.name.toLowerCase() === activeGroupName.toLowerCase()
1309
+ (g) => tagAttrKey(g.name) === tagAttrKey(activeGroupName)
1307
1310
  );
1308
1311
  if (!group) return void 0;
1309
- const metaValue = metadata[group.name.toLowerCase()] ?? (isContainer ? void 0 : group.defaultValue);
1312
+ const metaValue = metadata[tagAttrKey(group.name)] ?? (isContainer ? void 0 : group.defaultValue);
1310
1313
  if (!metaValue) return "#999999";
1311
1314
  return group.entries.find((e) => e.value.toLowerCase() === metaValue.toLowerCase())?.color ?? "#999999";
1312
1315
  }
1313
1316
  function validateTagValues(entities, tagGroups, pushWarning, suggestFn) {
1314
1317
  if (tagGroups.length === 0) return;
1315
1318
  const groupMap = /* @__PURE__ */ new Map();
1316
- for (const g of tagGroups) groupMap.set(g.name.toLowerCase(), g);
1319
+ for (const g of tagGroups) groupMap.set(tagAttrKey(g.name), g);
1317
1320
  for (const entity of entities) {
1318
1321
  for (const [key, value] of Object.entries(entity.metadata)) {
1319
1322
  const group = groupMap.get(key);
@@ -1350,10 +1353,10 @@ function validateTagGroupNames(tagGroups, pushWarning, pushError) {
1350
1353
  `'none' is a reserved keyword and cannot be used as a tag group name`
1351
1354
  );
1352
1355
  }
1353
- if (!VALID_TAG_IDENT_RE.test(group.name)) {
1356
+ if (!VALID_TAG_IDENT_RE.test(tagAttrKey(group.name))) {
1354
1357
  report(
1355
1358
  group.lineNumber,
1356
- `Tag group name "${group.name}" contains invalid characters \u2014 use a single identifier (letters, digits, underscore, hyphen)`
1359
+ `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)`
1357
1360
  );
1358
1361
  }
1359
1362
  if (group.alias != null && !VALID_TAG_IDENT_RE.test(group.alias)) {
@@ -1365,7 +1368,7 @@ function validateTagGroupNames(tagGroups, pushWarning, pushError) {
1365
1368
  }
1366
1369
  }
1367
1370
  function cascadeTagMetadata(roots, tagGroups) {
1368
- const keys = tagGroups.map((g) => g.name.toLowerCase());
1371
+ const keys = tagGroups.map((g) => tagAttrKey(g.name));
1369
1372
  if (keys.length === 0) return;
1370
1373
  const walk = (node, inherited) => {
1371
1374
  const childInherited = { ...inherited };
@@ -1386,7 +1389,7 @@ function injectDefaultTagMetadata(entities, tagGroups, skip) {
1386
1389
  for (const group of tagGroups) {
1387
1390
  if (group.defaultValue) {
1388
1391
  defaults.push({
1389
- key: group.name.toLowerCase(),
1392
+ key: tagAttrKey(group.name),
1390
1393
  value: group.defaultValue
1391
1394
  });
1392
1395
  }
@@ -2777,7 +2780,7 @@ function parseVisualizationFull(content, palette) {
2777
2780
  );
2778
2781
  for (const group of result.timelineTagGroups) {
2779
2782
  if (!group.defaultValue) continue;
2780
- const key = group.name.toLowerCase();
2783
+ const key = tagAttrKey(group.name);
2781
2784
  for (const event of result.timelineEvents) {
2782
2785
  if (!event.metadata[key]) {
2783
2786
  event.metadata[key] = group.defaultValue;
@@ -5169,7 +5172,7 @@ function renderLegendD3(container, config, state, palette, isDark, callbacks, co
5169
5172
  };
5170
5173
  }
5171
5174
  function renderCapsule(parent, capsule, palette, groupBg, pillBorder, isDark, callbacks) {
5172
- const g = parent.append("g").attr("transform", `translate(${capsule.x},${capsule.y})`).attr("data-legend-group", capsule.groupName.toLowerCase()).style("cursor", "pointer");
5175
+ const g = parent.append("g").attr("transform", `translate(${capsule.x},${capsule.y})`).attr("data-legend-group", tagAttrKey(capsule.groupName)).style("cursor", "pointer");
5173
5176
  g.append("rect").attr("width", capsule.width).attr("height", capsule.height).attr("rx", LEGEND_HEIGHT / 2).attr("fill", groupBg);
5174
5177
  const pill = capsule.pill;
5175
5178
  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);
@@ -5177,7 +5180,7 @@ function renderCapsule(parent, capsule, palette, groupBg, pillBorder, isDark, ca
5177
5180
  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);
5178
5181
  if (capsule.gradient) {
5179
5182
  const gr = capsule.gradient;
5180
- const gradId = `dgmo-legend-ramp-${capsule.groupName.toLowerCase().replace(/[^a-z0-9]+/g, "-")}`;
5183
+ const gradId = `dgmo-legend-ramp-${tagAttrKey(capsule.groupName).replace(/[^a-z0-9]+/g, "-")}`;
5181
5184
  const def = g.append("defs").append("linearGradient").attr("id", gradId);
5182
5185
  for (const stop of valueRampStops(gr.low, gr.high, { isDark })) {
5183
5186
  def.append("stop").attr("offset", `${stop.offset * 100}%`).attr("stop-color", stop.color);
@@ -5207,7 +5210,7 @@ function renderCapsule(parent, capsule, palette, groupBg, pillBorder, isDark, ca
5207
5210
  }
5208
5211
  }
5209
5212
  function renderPill(parent, pill, palette, groupBg, callbacks) {
5210
- const g = parent.append("g").attr("transform", `translate(${pill.x},${pill.y})`).attr("data-legend-group", pill.groupName.toLowerCase()).style("cursor", "pointer");
5213
+ const g = parent.append("g").attr("transform", `translate(${pill.x},${pill.y})`).attr("data-legend-group", tagAttrKey(pill.groupName)).style("cursor", "pointer");
5211
5214
  g.append("rect").attr("width", pill.width).attr("height", pill.height).attr("rx", pill.height / 2).attr("fill", groupBg);
5212
5215
  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);
5213
5216
  if (callbacks?.onGroupToggle) {
@@ -5313,6 +5316,7 @@ var LEGEND_TEXT_DY;
5313
5316
  var init_legend_d3 = __esm({
5314
5317
  "src/utils/legend-d3.ts"() {
5315
5318
  "use strict";
5319
+ init_tag_groups();
5316
5320
  init_legend_constants();
5317
5321
  init_legend_layout();
5318
5322
  init_color_utils();
@@ -9299,7 +9303,7 @@ function parseERDiagram(content, palette) {
9299
9303
  );
9300
9304
  for (const group of result.tagGroups) {
9301
9305
  if (!group.defaultValue) continue;
9302
- const key = group.name.toLowerCase();
9306
+ const key = tagAttrKey(group.name);
9303
9307
  for (const table of result.tables) {
9304
9308
  if (!table.metadata[key]) {
9305
9309
  const mutableMeta = tableMetadataMap.get(table.id);
@@ -9529,12 +9533,12 @@ function parseOrg(content, palette) {
9529
9533
  if (tagBlockMatch.alias) {
9530
9534
  metaAliasMap.set(
9531
9535
  normalizeName(tagBlockMatch.alias),
9532
- tagBlockMatch.name.toLowerCase()
9536
+ tagAttrKey(tagBlockMatch.name)
9533
9537
  );
9534
9538
  }
9535
9539
  metaAliasMap.set(
9536
9540
  normalizeName(tagBlockMatch.name),
9537
- tagBlockMatch.name.toLowerCase()
9541
+ tagAttrKey(tagBlockMatch.name)
9538
9542
  );
9539
9543
  result.tagGroups.push(currentTagGroup);
9540
9544
  continue;
@@ -9920,12 +9924,12 @@ function parseSitemap(content, palette) {
9920
9924
  if (tagBlockMatch.alias) {
9921
9925
  metaAliasMap.set(
9922
9926
  normalizeName(tagBlockMatch.alias),
9923
- tagBlockMatch.name.toLowerCase()
9927
+ tagAttrKey(tagBlockMatch.name)
9924
9928
  );
9925
9929
  }
9926
9930
  metaAliasMap.set(
9927
9931
  normalizeName(tagBlockMatch.name),
9928
- tagBlockMatch.name.toLowerCase()
9932
+ tagAttrKey(tagBlockMatch.name)
9929
9933
  );
9930
9934
  result.tagGroups.push(newTagGroup);
9931
9935
  continue;
@@ -10095,6 +10099,14 @@ function parseSitemap(content, palette) {
10095
10099
  continue;
10096
10100
  }
10097
10101
  }
10102
+ const enclosingContainer = nestedPageInsideContainer(indent, indentStack);
10103
+ if (enclosingContainer) {
10104
+ pushError(
10105
+ lineNumber,
10106
+ `Pages inside container '[${enclosingContainer}]' cannot have indented sub-pages. Containers hold a flat list of pages.`
10107
+ );
10108
+ continue;
10109
+ }
10098
10110
  const node = parseNodeLabel2(
10099
10111
  trimmed,
10100
10112
  lineNumber,
@@ -10242,6 +10254,16 @@ function attachNode2(node, indent, indentStack, result) {
10242
10254
  }
10243
10255
  indentStack.push({ node, indent });
10244
10256
  }
10257
+ function nestedPageInsideContainer(indent, indentStack) {
10258
+ let idx = indentStack.length - 1;
10259
+ while (idx >= 0 && indentStack[idx].indent >= indent) idx--;
10260
+ if (idx < 0) return null;
10261
+ if (indentStack[idx].node.isContainer) return null;
10262
+ for (let k = idx - 1; k >= 0; k--) {
10263
+ if (indentStack[k].node.isContainer) return indentStack[k].node.label;
10264
+ }
10265
+ return null;
10266
+ }
10245
10267
  function findParentNode(indent, indentStack) {
10246
10268
  for (let i = indentStack.length - 1; i >= 0; i--) {
10247
10269
  if (indentStack[i].indent < indent) {
@@ -10825,12 +10847,12 @@ function parsePert(content, parseOpts = {}) {
10825
10847
  if (tagBlockMatch.alias) {
10826
10848
  metaAliasMap.set(
10827
10849
  tagBlockMatch.alias.toLowerCase(),
10828
- tagBlockMatch.name.toLowerCase()
10850
+ tagAttrKey(tagBlockMatch.name)
10829
10851
  );
10830
10852
  }
10831
10853
  metaAliasMap.set(
10832
10854
  normalizeName(tagBlockMatch.name),
10833
- tagBlockMatch.name.toLowerCase()
10855
+ tagAttrKey(tagBlockMatch.name)
10834
10856
  );
10835
10857
  tagGroups.push(currentTagGroup);
10836
10858
  if (tagBlockMatch.inlineValues) {
@@ -12295,10 +12317,10 @@ function emitCapsule(capsule, palette, groupBg, pillBorder) {
12295
12317
  `<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>`
12296
12318
  );
12297
12319
  }
12298
- return `<g transform="translate(${capsule.x},${capsule.y})" data-legend-group="${esc(capsule.groupName.toLowerCase())}" style="cursor:pointer">${inner.join("")}</g>`;
12320
+ return `<g transform="translate(${capsule.x},${capsule.y})" data-legend-group="${esc(tagAttrKey(capsule.groupName))}" style="cursor:pointer">${inner.join("")}</g>`;
12299
12321
  }
12300
12322
  function emitPill(pill, palette, groupBg) {
12301
- 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>`;
12323
+ 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>`;
12302
12324
  }
12303
12325
  function renderLegendSvg(groups, options) {
12304
12326
  if (groups.length === 0) return { svg: "", height: 0, width: 0 };
@@ -12346,6 +12368,7 @@ function renderLegendSvg(groups, options) {
12346
12368
  var init_legend_svg = __esm({
12347
12369
  "src/utils/legend-svg.ts"() {
12348
12370
  "use strict";
12371
+ init_tag_groups();
12349
12372
  init_legend_constants();
12350
12373
  init_legend_layout();
12351
12374
  init_color_utils();
@@ -15036,12 +15059,12 @@ function parseKanban(content, palette) {
15036
15059
  if (tagBlockMatch.alias) {
15037
15060
  metaAliasMap.set(
15038
15061
  normalizeName(tagBlockMatch.alias),
15039
- tagBlockMatch.name.toLowerCase()
15062
+ tagAttrKey(tagBlockMatch.name)
15040
15063
  );
15041
15064
  }
15042
15065
  metaAliasMap.set(
15043
15066
  normalizeName(tagBlockMatch.name),
15044
- tagBlockMatch.name.toLowerCase()
15067
+ tagAttrKey(tagBlockMatch.name)
15045
15068
  );
15046
15069
  result.tagGroups.push(currentTagGroup);
15047
15070
  continue;
@@ -15230,7 +15253,7 @@ function parseKanban(content, palette) {
15230
15253
  }
15231
15254
  for (const group of result.tagGroups) {
15232
15255
  const values = new Set(group.entries.map((e) => e.value.toLowerCase()));
15233
- tagValueSets.set(group.name.toLowerCase(), values);
15256
+ tagValueSets.set(tagAttrKey(group.name), values);
15234
15257
  }
15235
15258
  for (const col of result.columns) {
15236
15259
  if (col.wipLimit != null && col.cards.length > col.wipLimit) {
@@ -15492,12 +15515,12 @@ function parseC4(content, palette) {
15492
15515
  if (tagBlockMatch.alias) {
15493
15516
  metaAliasMap.set(
15494
15517
  normalizeName(tagBlockMatch.alias),
15495
- tagBlockMatch.name.toLowerCase()
15518
+ tagAttrKey(tagBlockMatch.name)
15496
15519
  );
15497
15520
  }
15498
15521
  metaAliasMap.set(
15499
15522
  normalizeName(tagBlockMatch.name),
15500
- tagBlockMatch.name.toLowerCase()
15523
+ tagAttrKey(tagBlockMatch.name)
15501
15524
  );
15502
15525
  result.tagGroups.push(currentTagGroup);
15503
15526
  continue;
@@ -17180,7 +17203,7 @@ function parseInfra(content) {
17180
17203
  }
17181
17204
  for (const tg of result.tagGroups) {
17182
17205
  if (!tg.defaultValue) continue;
17183
- const key = (tg.alias ?? tg.name).toLowerCase();
17206
+ const key = tagAttrKey(tg.alias ?? tg.name);
17184
17207
  for (const node of result.nodes) {
17185
17208
  if (node.isEdge) continue;
17186
17209
  if (!(key in node.tags)) {
@@ -17317,6 +17340,7 @@ var CONNECTION_RE, SIMPLE_CONNECTION_RE, ASYNC_CONNECTION_RE, ASYNC_SIMPLE_CONNE
17317
17340
  var init_parser15 = __esm({
17318
17341
  "src/infra/parser.ts"() {
17319
17342
  "use strict";
17343
+ init_tag_groups();
17320
17344
  init_diagnostics();
17321
17345
  init_description_helpers();
17322
17346
  init_arrows();
@@ -17862,13 +17886,10 @@ function parseGantt(content, palette) {
17862
17886
  if (tagMatch.alias) {
17863
17887
  metaAliasMap.set(
17864
17888
  normalizeName(tagMatch.alias),
17865
- tagMatch.name.toLowerCase()
17889
+ tagAttrKey(tagMatch.name)
17866
17890
  );
17867
17891
  }
17868
- metaAliasMap.set(
17869
- normalizeName(tagMatch.name),
17870
- tagMatch.name.toLowerCase()
17871
- );
17892
+ metaAliasMap.set(normalizeName(tagMatch.name), tagAttrKey(tagMatch.name));
17872
17893
  continue;
17873
17894
  }
17874
17895
  const topWorkweekMatch = line11.match(WORKWEEK_RE);
@@ -18794,7 +18815,7 @@ function parseMap(content, palette) {
18794
18815
  const aliasMap = /* @__PURE__ */ new Map();
18795
18816
  const open = { tag: null, route: null, poi: null };
18796
18817
  const registry = () => withTagAliases(MAP_REGISTRY, new Set(aliasMap.keys()));
18797
- const tagGroupNames = () => new Set(tagGroups.map((g) => g.name.toLowerCase()));
18818
+ const tagGroupNames = () => new Set(tagGroups.map((g) => tagAttrKey(g.name)));
18798
18819
  const closeBlocks = () => {
18799
18820
  open.tag = null;
18800
18821
  open.route = null;
@@ -18978,7 +18999,7 @@ function parseMap(content, palette) {
18978
18999
  entries: [],
18979
19000
  lineNumber: line11
18980
19001
  };
18981
- if (m.alias) aliasMap.set(m.alias.toLowerCase(), m.name.toLowerCase());
19002
+ if (m.alias) aliasMap.set(m.alias.toLowerCase(), tagAttrKey(m.name));
18982
19003
  tagGroups.push(group);
18983
19004
  open.tag = group;
18984
19005
  if (m.inlineValues?.length) {
@@ -19511,12 +19532,12 @@ function parseBoxesAndLines(content, palette) {
19511
19532
  if (tagBlockMatch.alias) {
19512
19533
  metaAliasMap.set(
19513
19534
  normalizeName(tagBlockMatch.alias),
19514
- tagBlockMatch.name.toLowerCase()
19535
+ tagAttrKey(tagBlockMatch.name)
19515
19536
  );
19516
19537
  }
19517
19538
  metaAliasMap.set(
19518
19539
  normalizeName(tagBlockMatch.name),
19519
- tagBlockMatch.name.toLowerCase()
19540
+ tagAttrKey(tagBlockMatch.name)
19520
19541
  );
19521
19542
  if (tagBlockMatch.inlineValues) {
19522
19543
  for (const rawVal of tagBlockMatch.inlineValues) {
@@ -20257,12 +20278,12 @@ function parseMindmap(content, palette) {
20257
20278
  if (tagBlockMatch.alias) {
20258
20279
  aliasMap.set(
20259
20280
  tagBlockMatch.alias.toLowerCase(),
20260
- tagBlockMatch.name.toLowerCase()
20281
+ tagAttrKey(tagBlockMatch.name)
20261
20282
  );
20262
20283
  }
20263
20284
  aliasMap.set(
20264
- tagBlockMatch.name.toLowerCase(),
20265
- tagBlockMatch.name.toLowerCase()
20285
+ tagAttrKey(tagBlockMatch.name),
20286
+ tagAttrKey(tagBlockMatch.name)
20266
20287
  );
20267
20288
  result.tagGroups.push(currentTagGroup);
20268
20289
  continue;
@@ -21971,7 +21992,18 @@ function parseJourneyMap(content, palette) {
21971
21992
  palette
21972
21993
  ) ?? void 0;
21973
21994
  } else {
21974
- personaName = afterKeyword;
21995
+ const { label, colorName } = peelTrailingColorName(afterKeyword);
21996
+ if (colorName) {
21997
+ personaName = label;
21998
+ personaColor = resolveColorWithDiagnostic(
21999
+ colorName,
22000
+ lineNumber,
22001
+ result.diagnostics,
22002
+ palette
22003
+ ) ?? void 0;
22004
+ } else {
22005
+ personaName = afterKeyword;
22006
+ }
21975
22007
  }
21976
22008
  }
21977
22009
  if (!personaName) {
@@ -22014,12 +22046,12 @@ function parseJourneyMap(content, palette) {
22014
22046
  if (tagBlockMatch.alias) {
22015
22047
  aliasMap.set(
22016
22048
  tagBlockMatch.alias.toLowerCase(),
22017
- tagBlockMatch.name.toLowerCase()
22049
+ tagAttrKey(tagBlockMatch.name)
22018
22050
  );
22019
22051
  }
22020
22052
  aliasMap.set(
22021
- tagBlockMatch.name.toLowerCase(),
22022
- tagBlockMatch.name.toLowerCase()
22053
+ tagAttrKey(tagBlockMatch.name),
22054
+ tagAttrKey(tagBlockMatch.name)
22023
22055
  );
22024
22056
  result.tagGroups.push(currentTagGroup);
22025
22057
  continue;
@@ -22163,7 +22195,7 @@ function parseJourneyMap(content, palette) {
22163
22195
  }
22164
22196
  for (const group of result.tagGroups) {
22165
22197
  const values = new Set(group.entries.map((e) => e.value.toLowerCase()));
22166
- tagValueSets.set(group.name.toLowerCase(), values);
22198
+ tagValueSets.set(tagAttrKey(group.name), values);
22167
22199
  }
22168
22200
  const allSteps = hasPhases ? result.phases.flatMap((p) => p.steps) : result.steps;
22169
22201
  for (const step of allSteps) {
@@ -24149,7 +24181,7 @@ function computeLegendGroups(tagGroups, showEyeIcons, usedValuesByGroup) {
24149
24181
  const groups = [];
24150
24182
  for (const group of tagGroups) {
24151
24183
  if (group.entries.length === 0) continue;
24152
- const usedValues = usedValuesByGroup?.get(group.name.toLowerCase());
24184
+ const usedValues = usedValuesByGroup?.get(tagAttrKey(group.name));
24153
24185
  const visibleEntries = usedValues ? group.entries.filter((e) => usedValues.has(e.value.toLowerCase())) : group.entries;
24154
24186
  if (visibleEntries.length === 0) continue;
24155
24187
  const pillWidth2 = measureLegendText(group.name, LEGEND_PILL_FONT_SIZE) + LEGEND_PILL_PAD;
@@ -24762,7 +24794,7 @@ function layoutOrg(parsed, hiddenCounts, activeTagGroup, hiddenAttributes, expan
24762
24794
  const totalHeight = finalMaxY - minY + MARGIN * 2;
24763
24795
  const usedValuesByGroup = /* @__PURE__ */ new Map();
24764
24796
  for (const group of parsed.tagGroups) {
24765
- const key = group.name.toLowerCase();
24797
+ const key = tagAttrKey(group.name);
24766
24798
  const used = /* @__PURE__ */ new Set();
24767
24799
  const walk = (node) => {
24768
24800
  if (!node.isContainer && node.metadata[key]) {
@@ -24820,6 +24852,7 @@ var import_d3_hierarchy, LABEL_FONT_SIZE, META_FONT_SIZE, META_LINE_HEIGHT, HEAD
24820
24852
  var init_layout = __esm({
24821
24853
  "src/org/layout.ts"() {
24822
24854
  "use strict";
24855
+ init_tag_groups();
24823
24856
  import_d3_hierarchy = require("d3-hierarchy");
24824
24857
  init_tag_groups();
24825
24858
  init_legend_constants();
@@ -25181,14 +25214,14 @@ function renderOrg(container, parsed, layout, palette, isDark, onClickItem, expo
25181
25214
  const contentG = mainG.append("g").attr("transform", `translate(0, ${contentYShift})`);
25182
25215
  const displayNames = /* @__PURE__ */ new Map();
25183
25216
  for (const group of parsed.tagGroups) {
25184
- displayNames.set(group.name.toLowerCase(), group.name);
25217
+ displayNames.set(tagAttrKey(group.name), group.name);
25185
25218
  }
25186
25219
  const rootNodeIds = new Set(parsed.roots.map((r) => r.id));
25187
25220
  const colorOff = parsed.options?.["color"] === "off";
25188
25221
  for (const c of layout.containers) {
25189
25222
  const cG = contentG.append("g").attr("transform", `translate(${c.x}, ${c.y})`).attr("class", "org-container").attr("data-line-number", String(c.lineNumber));
25190
25223
  if (activeTagGroup) {
25191
- const tagKey = activeTagGroup.toLowerCase();
25224
+ const tagKey = tagAttrKey(activeTagGroup);
25192
25225
  const metaValue = c.tagMetadata[tagKey];
25193
25226
  if (metaValue) {
25194
25227
  cG.attr(`data-tag-${tagKey}`, metaValue.toLowerCase());
@@ -25270,7 +25303,7 @@ function renderOrg(container, parsed, layout, palette, isDark, onClickItem, expo
25270
25303
  if (containerNodeIds.has(node.id)) continue;
25271
25304
  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));
25272
25305
  if (activeTagGroup) {
25273
- const tagKey = activeTagGroup.toLowerCase();
25306
+ const tagKey = tagAttrKey(activeTagGroup);
25274
25307
  const metaValue = node.tagMetadata[tagKey];
25275
25308
  if (metaValue) {
25276
25309
  nodeG.attr(`data-tag-${tagKey}`, metaValue.toLowerCase());
@@ -25443,7 +25476,7 @@ function renderOrg(container, parsed, layout, palette, isDark, onClickItem, expo
25443
25476
  const computedLayout = legendHandle.getLayout();
25444
25477
  if (computedLayout.activeCapsule?.addonX != null) {
25445
25478
  const capsule = computedLayout.activeCapsule;
25446
- const groupKey = capsule.groupName.toLowerCase();
25479
+ const groupKey = tagAttrKey(capsule.groupName);
25447
25480
  const isHidden = hiddenAttributes?.has(groupKey) ?? false;
25448
25481
  const activeGroupEl = legendParentBase.select(
25449
25482
  `[data-legend-group="${groupKey}"]`
@@ -25482,6 +25515,7 @@ var d3Selection6, DIAGRAM_PADDING, MAX_SCALE, TITLE_HEIGHT, ANCESTOR_DOT_R, ANCE
25482
25515
  var init_renderer = __esm({
25483
25516
  "src/org/renderer.ts"() {
25484
25517
  "use strict";
25518
+ init_tag_groups();
25485
25519
  d3Selection6 = __toESM(require("d3-selection"), 1);
25486
25520
  init_fonts();
25487
25521
  init_export_container();
@@ -25567,7 +25601,7 @@ function computeLegendGroups2(tagGroups, usedValuesByGroup) {
25567
25601
  const groups = [];
25568
25602
  for (const group of tagGroups) {
25569
25603
  if (group.entries.length === 0) continue;
25570
- const usedValues = usedValuesByGroup?.get(group.name.toLowerCase());
25604
+ const usedValues = usedValuesByGroup?.get(tagAttrKey(group.name));
25571
25605
  const visibleEntries = usedValues ? group.entries.filter((e) => usedValues.has(e.value.toLowerCase())) : group.entries;
25572
25606
  if (visibleEntries.length === 0) continue;
25573
25607
  const pillWidth2 = measureLegendText(group.name, LEGEND_PILL_FONT_SIZE) + LEGEND_PILL_PAD;
@@ -26047,7 +26081,7 @@ function layoutSitemap(parsed, hiddenCounts, activeTagGroup, hiddenAttributes, e
26047
26081
  totalHeight += MARGIN2;
26048
26082
  const usedValuesByGroup = /* @__PURE__ */ new Map();
26049
26083
  for (const group of parsed.tagGroups) {
26050
- const key = group.name.toLowerCase();
26084
+ const key = tagAttrKey(group.name);
26051
26085
  const used = /* @__PURE__ */ new Set();
26052
26086
  const walk = (node) => {
26053
26087
  if (!node.isContainer && node.metadata[key]) {
@@ -26091,6 +26125,7 @@ var import_dagre, LABEL_FONT_SIZE3, META_FONT_SIZE3, CONTAINER_LABEL_FONT_SIZE2,
26091
26125
  var init_layout2 = __esm({
26092
26126
  "src/sitemap/layout.ts"() {
26093
26127
  "use strict";
26128
+ init_tag_groups();
26094
26129
  import_dagre = __toESM(require("@dagrejs/dagre"), 1);
26095
26130
  init_tag_groups();
26096
26131
  init_legend_constants();
@@ -26360,10 +26395,10 @@ function renderSitemap(container, parsed, layout, palette, isDark, onClickItem,
26360
26395
  const displayNames = /* @__PURE__ */ new Map();
26361
26396
  const tagColors = /* @__PURE__ */ new Map();
26362
26397
  for (const group of parsed.tagGroups) {
26363
- displayNames.set(group.name.toLowerCase(), group.name);
26398
+ displayNames.set(tagAttrKey(group.name), group.name);
26364
26399
  for (const entry of group.entries) {
26365
26400
  tagColors.set(
26366
- `${group.name.toLowerCase()}:${entry.value.toLowerCase()}`,
26401
+ `${tagAttrKey(group.name)}:${entry.value.toLowerCase()}`,
26367
26402
  entry.color
26368
26403
  );
26369
26404
  }
@@ -26380,7 +26415,7 @@ function renderSitemap(container, parsed, layout, palette, isDark, onClickItem,
26380
26415
  );
26381
26416
  }
26382
26417
  if (activeTagGroup) {
26383
- const tagKey = activeTagGroup.toLowerCase();
26418
+ const tagKey = tagAttrKey(activeTagGroup);
26384
26419
  const tagVal = c.tagMetadata[tagKey];
26385
26420
  if (tagVal) cG.attr(`data-tag-${tagKey}`, tagVal.toLowerCase());
26386
26421
  }
@@ -26443,7 +26478,7 @@ function renderSitemap(container, parsed, layout, palette, isDark, onClickItem,
26443
26478
  nodeG.style("cursor", "pointer").on("click", () => onClickItem(node.lineNumber));
26444
26479
  }
26445
26480
  if (activeTagGroup) {
26446
- const tagKey = activeTagGroup.toLowerCase();
26481
+ const tagKey = tagAttrKey(activeTagGroup);
26447
26482
  const tagVal = node.tagMetadata[tagKey];
26448
26483
  if (tagVal) nodeG.attr(`data-tag-${tagKey}`, tagVal.toLowerCase());
26449
26484
  }
@@ -26570,7 +26605,7 @@ function renderLegend(parent, legendGroups, palette, isDark, activeTagGroup, fix
26570
26605
  const computedLayout = legendHandle.getLayout();
26571
26606
  if (computedLayout.activeCapsule?.addonX != null) {
26572
26607
  const capsule = computedLayout.activeCapsule;
26573
- const groupKey = capsule.groupName.toLowerCase();
26608
+ const groupKey = tagAttrKey(capsule.groupName);
26574
26609
  const isHidden = hiddenAttributes?.has(groupKey) ?? false;
26575
26610
  const activeGroupEl = parent.select(`[data-legend-group="${groupKey}"]`);
26576
26611
  if (!activeGroupEl.empty()) {
@@ -26635,6 +26670,7 @@ var d3Selection7, d3Shape2, DIAGRAM_PADDING2, MAX_SCALE2, TITLE_HEIGHT2, ARROWHE
26635
26670
  var init_renderer2 = __esm({
26636
26671
  "src/sitemap/renderer.ts"() {
26637
26672
  "use strict";
26673
+ init_tag_groups();
26638
26674
  d3Selection7 = __toESM(require("d3-selection"), 1);
26639
26675
  d3Shape2 = __toESM(require("d3-shape"), 1);
26640
26676
  init_fonts();
@@ -26683,7 +26719,7 @@ function resolveCardTagMeta(card, tagGroups, hiddenMetaGroups) {
26683
26719
  const meta = [];
26684
26720
  for (const group of tagGroups) {
26685
26721
  if (hiddenMetaGroups?.includes(group.name.toLowerCase())) continue;
26686
- const tagValue = card.tags[group.name.toLowerCase()];
26722
+ const tagValue = card.tags[tagAttrKey(group.name)];
26687
26723
  const value = tagValue ?? group.defaultValue;
26688
26724
  if (!value) continue;
26689
26725
  const entry = group.entries.find(
@@ -26703,7 +26739,7 @@ function resolveCardTagColor(card, tagGroups, activeTagGroup) {
26703
26739
  (g) => g.name.toLowerCase() === activeTagGroup.toLowerCase()
26704
26740
  );
26705
26741
  if (!group) return card.color;
26706
- const tagValue = card.tags[group.name.toLowerCase()];
26742
+ const tagValue = card.tags[tagAttrKey(group.name)];
26707
26743
  const value = tagValue ?? group.defaultValue;
26708
26744
  if (!value) return void 0;
26709
26745
  const entry = group.entries.find(
@@ -27013,7 +27049,7 @@ function renderKanban(container, parsed, palette, isDark, options) {
27013
27049
  );
27014
27050
  const cg = g.append("g").attr("class", "kanban-card").attr("data-card-id", card.id).attr("data-line-number", card.lineNumber);
27015
27051
  if (activeTagGroup) {
27016
- const tagKey = activeTagGroup.toLowerCase();
27052
+ const tagKey = tagAttrKey(activeTagGroup);
27017
27053
  const tagValue = card.tags[tagKey];
27018
27054
  const group = parsed.tagGroups.find(
27019
27055
  (tg) => tg.name.toLowerCase() === tagKey
@@ -27074,7 +27110,7 @@ function drawSwimlaneIcon(parent, x, y, isActive, palette) {
27074
27110
  return iconG;
27075
27111
  }
27076
27112
  function bucketCardsBySwimlane(columns, swimlaneGroup) {
27077
- const tagKey = swimlaneGroup.name.toLowerCase();
27113
+ const tagKey = tagAttrKey(swimlaneGroup.name);
27078
27114
  const buckets = /* @__PURE__ */ new Map();
27079
27115
  for (const entry of swimlaneGroup.entries) {
27080
27116
  buckets.set(entry.value.toLowerCase(), {
@@ -27362,7 +27398,7 @@ function renderSwimlaneCard(parent, cardLayout, tagGroups, activeTagGroup, palet
27362
27398
  );
27363
27399
  const cg = parent.append("g").attr("class", "kanban-card").attr("data-card-id", card.id).attr("data-line-number", card.lineNumber);
27364
27400
  if (activeTagGroup) {
27365
- const tagKey = activeTagGroup.toLowerCase();
27401
+ const tagKey = tagAttrKey(activeTagGroup);
27366
27402
  const group = tagGroups.find((tg) => tg.name.toLowerCase() === tagKey);
27367
27403
  const value = card.tags[tagKey] ?? group?.defaultValue;
27368
27404
  if (value) {
@@ -27395,6 +27431,7 @@ var d3Selection8, DIAGRAM_PADDING3, COLUMN_GAP, COLUMN_HEADER_HEIGHT, COLUMN_PAD
27395
27431
  var init_renderer3 = __esm({
27396
27432
  "src/kanban/renderer.ts"() {
27397
27433
  "use strict";
27434
+ init_tag_groups();
27398
27435
  d3Selection8 = __toESM(require("d3-selection"), 1);
27399
27436
  init_fonts();
27400
27437
  init_color_utils();
@@ -28377,7 +28414,7 @@ function classifyEREntities(tables, relationships) {
28377
28414
  const variance = indegreeValues.reduce((a, b) => a + (b - mean) ** 2, 0) / indegreeValues.length;
28378
28415
  const stddev = Math.sqrt(variance);
28379
28416
  const sorted = [...indegreeValues].sort((a, b) => a - b);
28380
- const median = sorted.length % 2 === 0 ? (sorted[sorted.length / 2 - 1] + sorted[sorted.length / 2]) / 2 : sorted[Math.floor(sorted.length / 2)];
28417
+ const median2 = sorted.length % 2 === 0 ? (sorted[sorted.length / 2 - 1] + sorted[sorted.length / 2]) / 2 : sorted[Math.floor(sorted.length / 2)];
28381
28418
  for (const table of tables) {
28382
28419
  const id = table.id;
28383
28420
  const cols = table.columns;
@@ -28418,7 +28455,7 @@ function classifyEREntities(tables, relationships) {
28418
28455
  const nameMatchesLookup = LOOKUP_NAME_SUFFIXES.some(
28419
28456
  (s) => nameLower.endsWith(s)
28420
28457
  );
28421
- if (nameMatchesLookup && cols.length <= 6 && fkCount <= 1 && indegree > median) {
28458
+ if (nameMatchesLookup && cols.length <= 6 && fkCount <= 1 && indegree > median2) {
28422
28459
  result.set(id, "lookup");
28423
28460
  continue;
28424
28461
  }
@@ -28657,7 +28694,7 @@ function renderERDiagram(container, parsed, layout, palette, isDark, onClickItem
28657
28694
  seriesColors2[ni % seriesColors2.length];
28658
28695
  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);
28659
28696
  if (activeTagGroup) {
28660
- const tagKey = activeTagGroup.toLowerCase();
28697
+ const tagKey = tagAttrKey(activeTagGroup);
28661
28698
  const tagValue = node.metadata[tagKey];
28662
28699
  if (tagValue) {
28663
28700
  nodeG.attr(`data-tag-${tagKey}`, tagValue.toLowerCase());
@@ -28836,6 +28873,7 @@ var d3Selection10, d3Shape4, DIAGRAM_PADDING5, MAX_SCALE4, TABLE_FONT_SIZE2, COL
28836
28873
  var init_renderer5 = __esm({
28837
28874
  "src/er/renderer.ts"() {
28838
28875
  "use strict";
28876
+ init_tag_groups();
28839
28877
  d3Selection10 = __toESM(require("d3-selection"), 1);
28840
28878
  d3Shape4 = __toESM(require("d3-shape"), 1);
28841
28879
  init_fonts();
@@ -29831,7 +29869,7 @@ function layeredCandidates(parsed, sizes, opts) {
29831
29869
  for (let r = 0; r < maxRank; r++) c += gapCrossings(r);
29832
29870
  return c;
29833
29871
  };
29834
- const median = (vals) => {
29872
+ const median2 = (vals) => {
29835
29873
  if (vals.length === 0) return -1;
29836
29874
  vals.sort((x, y) => x - y);
29837
29875
  const m = Math.floor(vals.length / 2);
@@ -29846,7 +29884,7 @@ function layeredCandidates(parsed, sizes, opts) {
29846
29884
  const neigh = useUp ? up : down;
29847
29885
  const med = /* @__PURE__ */ new Map();
29848
29886
  for (const id of ids)
29849
- med.set(id, median(neigh.get(id).map((x) => orderOf.get(x))));
29887
+ med.set(id, median2(neigh.get(id).map((x) => orderOf.get(x))));
29850
29888
  const movable = ids.filter((id) => med.get(id) >= 0).sort((a, b) => med.get(a) - med.get(b));
29851
29889
  const out = new Array(ids.length).fill(null);
29852
29890
  for (const id of ids) if (med.get(id) < 0) out[orderOf.get(id)] = id;
@@ -30176,6 +30214,695 @@ var init_layout_layered = __esm({
30176
30214
  }
30177
30215
  });
30178
30216
 
30217
+ // src/boxes-and-lines/layout-grouped.ts
30218
+ function rng2(s) {
30219
+ return () => {
30220
+ s |= 0;
30221
+ s = s + 1831565813 | 0;
30222
+ let t = Math.imul(s ^ s >>> 15, 1 | s);
30223
+ t = t + Math.imul(t ^ t >>> 7, 61 | t) ^ t;
30224
+ return ((t ^ t >>> 14) >>> 0) / 4294967296;
30225
+ };
30226
+ }
30227
+ function median(vals) {
30228
+ if (vals.length === 0) return -1;
30229
+ vals.sort((x, y) => x - y);
30230
+ const m = Math.floor(vals.length / 2);
30231
+ if (vals.length % 2 === 1) return vals[m];
30232
+ if (vals.length === 2) return (vals[0] + vals[1]) / 2;
30233
+ const left = vals[m - 1] - vals[0];
30234
+ const right = vals[vals.length - 1] - vals[m];
30235
+ return left + right === 0 ? (vals[m - 1] + vals[m]) / 2 : (vals[m - 1] * right + vals[m] * left) / (left + right);
30236
+ }
30237
+ function acyclicOrient(ids, edges) {
30238
+ const adj = /* @__PURE__ */ new Map();
30239
+ for (const l of ids) adj.set(l, []);
30240
+ for (const e of edges) adj.get(e.from)?.push({ to: e.to, idx: e.idx });
30241
+ const reversed = /* @__PURE__ */ new Set();
30242
+ const state = /* @__PURE__ */ new Map();
30243
+ for (const l of ids) state.set(l, 0);
30244
+ const stack = [];
30245
+ const visit = (root) => {
30246
+ stack.push(root);
30247
+ const it = /* @__PURE__ */ new Map();
30248
+ while (stack.length) {
30249
+ const u = stack[stack.length - 1];
30250
+ if (state.get(u) === 0) state.set(u, 1);
30251
+ const list = adj.get(u);
30252
+ let i = it.get(u) ?? 0;
30253
+ let descended = false;
30254
+ for (; i < list.length; i++) {
30255
+ const { to, idx } = list[i];
30256
+ const st = state.get(to);
30257
+ if (st === 1) reversed.add(idx);
30258
+ else if (st === 0) {
30259
+ it.set(u, i + 1);
30260
+ stack.push(to);
30261
+ descended = true;
30262
+ break;
30263
+ }
30264
+ }
30265
+ if (!descended) {
30266
+ it.set(u, list.length);
30267
+ state.set(u, 2);
30268
+ stack.pop();
30269
+ }
30270
+ }
30271
+ };
30272
+ for (const l of ids) if (state.get(l) === 0) visit(l);
30273
+ const dag = edges.map(
30274
+ (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 }
30275
+ );
30276
+ return { dag };
30277
+ }
30278
+ function longestPath(ids, dag) {
30279
+ const out = /* @__PURE__ */ new Map();
30280
+ const inDeg = /* @__PURE__ */ new Map();
30281
+ for (const l of ids) {
30282
+ out.set(l, []);
30283
+ inDeg.set(l, 0);
30284
+ }
30285
+ for (const d of dag) {
30286
+ out.get(d.from).push(d.to);
30287
+ inDeg.set(d.to, (inDeg.get(d.to) ?? 0) + 1);
30288
+ }
30289
+ const rank = /* @__PURE__ */ new Map();
30290
+ for (const l of ids) rank.set(l, 0);
30291
+ const queue = ids.filter((l) => (inDeg.get(l) ?? 0) === 0);
30292
+ const indeg2 = new Map(inDeg);
30293
+ let seen = 0;
30294
+ while (queue.length) {
30295
+ const u = queue.shift();
30296
+ seen++;
30297
+ for (const v of out.get(u)) {
30298
+ rank.set(v, Math.max(rank.get(v), rank.get(u) + 1));
30299
+ indeg2.set(v, indeg2.get(v) - 1);
30300
+ if (indeg2.get(v) === 0) queue.push(v);
30301
+ }
30302
+ }
30303
+ return seen === ids.length ? rank : null;
30304
+ }
30305
+ function groupedTierCandidates(parsed, sizes, opts) {
30306
+ if (parsed.groups.length === 0) return [];
30307
+ const nCount = parsed.nodes.length;
30308
+ if (nCount < 3 || nCount > 60) return [];
30309
+ const isTB = parsed.direction === "TB";
30310
+ const labels = parsed.nodes.map((n) => n.label);
30311
+ const labelSet = new Set(labels);
30312
+ for (const e of parsed.edges)
30313
+ if (e.source.startsWith("__group_") || e.target.startsWith("__group_"))
30314
+ return [];
30315
+ const groupByLabel = new Map(parsed.groups.map((g) => [g.label, g]));
30316
+ const directGroupOf = /* @__PURE__ */ new Map();
30317
+ for (const grp of parsed.groups)
30318
+ for (const c of grp.children)
30319
+ if (labelSet.has(c)) directGroupOf.set(c, grp.label);
30320
+ const topGroupOf = (groupLabel) => {
30321
+ let cur = groupLabel;
30322
+ const guard = /* @__PURE__ */ new Set();
30323
+ while (true) {
30324
+ const g = groupByLabel.get(cur);
30325
+ if (!g?.parentGroup || guard.has(cur)) return cur;
30326
+ guard.add(cur);
30327
+ cur = g.parentGroup;
30328
+ }
30329
+ };
30330
+ const pathOf = (label) => {
30331
+ const direct = directGroupOf.get(label);
30332
+ if (!direct) return [];
30333
+ const chain = [direct];
30334
+ let cur = direct;
30335
+ const guard = /* @__PURE__ */ new Set([direct]);
30336
+ while (true) {
30337
+ const g = groupByLabel.get(cur);
30338
+ if (!g?.parentGroup || guard.has(g.parentGroup)) break;
30339
+ chain.push(g.parentGroup);
30340
+ guard.add(g.parentGroup);
30341
+ cur = g.parentGroup;
30342
+ }
30343
+ return chain.reverse();
30344
+ };
30345
+ const SINGLE = "\0node\0";
30346
+ const unitOf = (label) => {
30347
+ const direct = directGroupOf.get(label);
30348
+ return direct ? topGroupOf(direct) : SINGLE + label;
30349
+ };
30350
+ const edges = parsed.edges.map((e, i) => ({ e, i })).filter(
30351
+ ({ e }) => labelSet.has(e.source) && labelSet.has(e.target) && e.source !== e.target
30352
+ );
30353
+ if (edges.length === 0) return [];
30354
+ const unitOfNode = /* @__PURE__ */ new Map();
30355
+ for (const l of labels) unitOfNode.set(l, unitOf(l));
30356
+ const units = Array.from(new Set(labels.map((l) => unitOfNode.get(l))));
30357
+ const interUnit = edges.filter(({ e }) => unitOfNode.get(e.source) !== unitOfNode.get(e.target)).map(({ e, i }) => ({
30358
+ from: unitOfNode.get(e.source),
30359
+ to: unitOfNode.get(e.target),
30360
+ idx: i
30361
+ }));
30362
+ const { dag: unitDag } = acyclicOrient(units, interUnit);
30363
+ const tier = longestPath(
30364
+ units,
30365
+ unitDag.map((d) => ({ from: d.from, to: d.to }))
30366
+ );
30367
+ if (!tier) return [];
30368
+ const localRank = /* @__PURE__ */ new Map();
30369
+ const unitMembers = /* @__PURE__ */ new Map();
30370
+ for (const l of labels) {
30371
+ const u = unitOfNode.get(l);
30372
+ (unitMembers.get(u) ?? unitMembers.set(u, []).get(u)).push(l);
30373
+ }
30374
+ for (const [u, members] of unitMembers) {
30375
+ const memberSet = new Set(members);
30376
+ const intra = edges.filter(
30377
+ ({ e }) => unitOfNode.get(e.source) === u && unitOfNode.get(e.target) === u && memberSet.has(e.source) && memberSet.has(e.target)
30378
+ ).map(({ e, i }) => ({ from: e.source, to: e.target, idx: i }));
30379
+ const { dag } = acyclicOrient(members, intra);
30380
+ const lr = longestPath(
30381
+ members,
30382
+ dag.map((d) => ({ from: d.from, to: d.to }))
30383
+ );
30384
+ if (!lr) return [];
30385
+ for (const m of members) localRank.set(m, lr.get(m));
30386
+ }
30387
+ const maxTier = Math.max(...units.map((u) => tier.get(u)));
30388
+ const bandHeight = Array.from({ length: maxTier + 1 }, () => 1);
30389
+ for (const u of units) {
30390
+ const h = Math.max(
30391
+ ...unitMembers.get(u).map((m) => localRank.get(m) + 1)
30392
+ );
30393
+ const t = tier.get(u);
30394
+ bandHeight[t] = Math.max(bandHeight[t], h);
30395
+ }
30396
+ const tierBase = [];
30397
+ let accBase = 0;
30398
+ for (let t = 0; t <= maxTier; t++) {
30399
+ tierBase[t] = accBase;
30400
+ accBase += bandHeight[t];
30401
+ }
30402
+ const tierStartRanks = new Set(tierBase.slice(1));
30403
+ const globalRank = /* @__PURE__ */ new Map();
30404
+ for (const l of labels)
30405
+ globalRank.set(
30406
+ l,
30407
+ tierBase[tier.get(unitOfNode.get(l))] + localRank.get(l)
30408
+ );
30409
+ const maxRank = Math.max(...labels.map((l) => globalRank.get(l)));
30410
+ const node = /* @__PURE__ */ new Map();
30411
+ for (const n of parsed.nodes) {
30412
+ const s = sizes.get(n.label) ?? { width: NODE_WIDTH, height: NODE_HEIGHT };
30413
+ node.set(n.label, {
30414
+ id: n.label,
30415
+ dummy: false,
30416
+ rank: globalRank.get(n.label),
30417
+ cross: 0,
30418
+ thick: isTB ? s.width : s.height,
30419
+ depth: isTB ? s.height : s.width,
30420
+ realW: s.width,
30421
+ realH: s.height,
30422
+ unit: unitOfNode.get(n.label),
30423
+ path: pathOf(n.label)
30424
+ });
30425
+ }
30426
+ const chains = [];
30427
+ const segs = [];
30428
+ const backEdges = [];
30429
+ const flatEdges = [];
30430
+ for (const { e, i } of edges) {
30431
+ const rs = globalRank.get(e.source);
30432
+ const rt = globalRank.get(e.target);
30433
+ if (rs === rt) {
30434
+ flatEdges.push({ edgeIdx: i, a: e.source, b: e.target });
30435
+ continue;
30436
+ }
30437
+ if (rt < rs) {
30438
+ backEdges.push({ edgeIdx: i, src: e.source, tgt: e.target });
30439
+ continue;
30440
+ }
30441
+ const chain = [e.source];
30442
+ let prev = e.source;
30443
+ for (let r = rs + 1; r < rt; r++) {
30444
+ const id = `__d${i}_${r}`;
30445
+ node.set(id, {
30446
+ id,
30447
+ dummy: true,
30448
+ rank: r,
30449
+ cross: 0,
30450
+ thick: DUMMY_THICK2,
30451
+ depth: 0,
30452
+ realW: 0,
30453
+ realH: 0,
30454
+ unit: SINGLE + id,
30455
+ path: []
30456
+ });
30457
+ segs.push({ a: prev, b: id });
30458
+ chain.push(id);
30459
+ prev = id;
30460
+ }
30461
+ segs.push({ a: prev, b: e.target });
30462
+ chain.push(e.target);
30463
+ chains.push({ edgeIdx: i, chain });
30464
+ }
30465
+ const ranks = Array.from({ length: maxRank + 1 }, () => []);
30466
+ for (const n of node.values()) ranks[n.rank].push(n.id);
30467
+ const up = /* @__PURE__ */ new Map();
30468
+ const down = /* @__PURE__ */ new Map();
30469
+ for (const id of node.keys()) {
30470
+ up.set(id, []);
30471
+ down.set(id, []);
30472
+ }
30473
+ for (const s of segs) {
30474
+ down.get(s.a).push(s.b);
30475
+ up.get(s.b).push(s.a);
30476
+ }
30477
+ const orderOf = /* @__PURE__ */ new Map();
30478
+ const gapCrossings = (r) => {
30479
+ const lower = ranks[r + 1];
30480
+ if (!lower) return 0;
30481
+ const es = [];
30482
+ for (const a of ranks[r]) {
30483
+ const pa = orderOf.get(a);
30484
+ for (const b of down.get(a)) es.push({ p: pa, q: orderOf.get(b) });
30485
+ }
30486
+ let c = 0;
30487
+ for (let i = 0; i < es.length; i++)
30488
+ for (let j = i + 1; j < es.length; j++) {
30489
+ const A = es[i], B = es[j];
30490
+ if ((A.p - B.p) * (A.q - B.q) < 0) c++;
30491
+ }
30492
+ return c;
30493
+ };
30494
+ const totalCrossings = () => {
30495
+ let c = 0;
30496
+ for (let r = 0; r < maxRank; r++) c += gapCrossings(r);
30497
+ return c;
30498
+ };
30499
+ const wmedianRank = (r, useUp) => {
30500
+ const ids = ranks[r];
30501
+ const neigh = useUp ? up : down;
30502
+ const med = /* @__PURE__ */ new Map();
30503
+ for (const id of ids)
30504
+ med.set(id, median(neigh.get(id).map((x) => orderOf.get(x))));
30505
+ const movable = ids.filter((id) => med.get(id) >= 0).sort((a, b) => med.get(a) - med.get(b));
30506
+ const out = new Array(ids.length).fill(null);
30507
+ for (const id of ids) if (med.get(id) < 0) out[orderOf.get(id)] = id;
30508
+ let mi = 0;
30509
+ for (let slot = 0; slot < out.length; slot++)
30510
+ if (out[slot] === null) out[slot] = movable[mi++];
30511
+ const final = out;
30512
+ final.forEach((id, i) => orderOf.set(id, i));
30513
+ ranks[r] = final;
30514
+ };
30515
+ const transpose = () => {
30516
+ let improved = true;
30517
+ let guard = 0;
30518
+ while (improved && guard++ < 12) {
30519
+ improved = false;
30520
+ for (let r = 0; r <= maxRank; r++) {
30521
+ const ids = ranks[r];
30522
+ for (let i = 0; i < ids.length - 1; i++) {
30523
+ const before = (r > 0 ? gapCrossings(r - 1) : 0) + (r < maxRank ? gapCrossings(r) : 0);
30524
+ const a = ids[i], b = ids[i + 1];
30525
+ ids[i] = b;
30526
+ ids[i + 1] = a;
30527
+ orderOf.set(b, i);
30528
+ orderOf.set(a, i + 1);
30529
+ const after = (r > 0 ? gapCrossings(r - 1) : 0) + (r < maxRank ? gapCrossings(r) : 0);
30530
+ if (after < before) improved = true;
30531
+ else {
30532
+ ids[i] = a;
30533
+ ids[i + 1] = b;
30534
+ orderOf.set(a, i);
30535
+ orderOf.set(b, i + 1);
30536
+ }
30537
+ }
30538
+ }
30539
+ }
30540
+ };
30541
+ const initOrder = (seed) => {
30542
+ const r = seed === 0 ? null : rng2(seed);
30543
+ for (let rr = 0; rr <= maxRank; rr++) {
30544
+ const ids = ranks[rr].slice();
30545
+ if (r)
30546
+ for (let i = ids.length - 1; i > 0; i--) {
30547
+ const j = Math.floor(r() * (i + 1));
30548
+ [ids[i], ids[j]] = [ids[j], ids[i]];
30549
+ }
30550
+ ranks[rr] = ids;
30551
+ ids.forEach((id, i) => orderOf.set(id, i));
30552
+ }
30553
+ };
30554
+ const restarts = opts?.restarts ?? (nCount <= 16 ? 20 : nCount <= 30 ? 12 : 6);
30555
+ const keepK = opts?.keepK ?? 3;
30556
+ const found = [];
30557
+ for (let s = 0; s < restarts; s++) {
30558
+ initOrder(s);
30559
+ let best = totalCrossings();
30560
+ let bestSnap = new Map(orderOf);
30561
+ for (let iter = 0; iter < 8; iter++) {
30562
+ if (iter % 2 === 0)
30563
+ for (let r = 1; r <= maxRank; r++) wmedianRank(r, true);
30564
+ else for (let r = maxRank - 1; r >= 0; r--) wmedianRank(r, false);
30565
+ transpose();
30566
+ const c = totalCrossings();
30567
+ if (c < best) {
30568
+ best = c;
30569
+ bestSnap = new Map(orderOf);
30570
+ }
30571
+ if (c === 0) break;
30572
+ }
30573
+ for (const [k, v] of bestSnap) orderOf.set(k, v);
30574
+ for (let r = 0; r <= maxRank; r++)
30575
+ ranks[r].sort((a, b) => orderOf.get(a) - orderOf.get(b));
30576
+ const sig = ranks.map((r) => r.join(",")).join("|");
30577
+ if (!found.some((f) => f.sig === sig))
30578
+ found.push({ sig, cross: best, order: new Map(bestSnap) });
30579
+ }
30580
+ found.sort((a, b) => a.cross - b.cross);
30581
+ const keep = found.slice(0, keepK);
30582
+ if (keep.length === 0) return [];
30583
+ const results = [];
30584
+ for (const cand of keep) {
30585
+ for (const [k, v] of cand.order) orderOf.set(k, v);
30586
+ const rk = Array.from({ length: maxRank + 1 }, () => []);
30587
+ for (const n of node.values()) rk[n.rank].push(n.id);
30588
+ for (let r = 0; r <= maxRank; r++)
30589
+ rk[r].sort((a, b) => cand.order.get(a) - cand.order.get(b));
30590
+ results.push(realize(rk));
30591
+ }
30592
+ return results;
30593
+ function weight(a, b) {
30594
+ if (a.dummy && b.dummy) return W_DUMMY_DUMMY2;
30595
+ if (a.dummy || b.dummy) return W_REAL_DUMMY2;
30596
+ return W_REAL_REAL2;
30597
+ }
30598
+ function placeWithSeparation(ids, desired, gap) {
30599
+ const n = ids.length;
30600
+ if (n === 0) return;
30601
+ const off = [0];
30602
+ for (let i = 1; i < n; i++) off[i] = off[i - 1] + gap[i];
30603
+ const d = desired.map((v, i) => v - off[i]);
30604
+ const blocks = [];
30605
+ for (let i = 0; i < n; i++) {
30606
+ let b = { pos: d[i], count: 1, sumOffset: d[i], first: i };
30607
+ while (blocks.length && blocks[blocks.length - 1].pos >= b.pos) {
30608
+ const prev = blocks.pop();
30609
+ const count = prev.count + b.count;
30610
+ const sumOffset = prev.sumOffset + b.sumOffset;
30611
+ b = { pos: sumOffset / count, count, sumOffset, first: prev.first };
30612
+ }
30613
+ blocks.push(b);
30614
+ }
30615
+ const xs = new Array(n);
30616
+ for (const b of blocks)
30617
+ for (let k = 0; k < b.count; k++) xs[b.first + k] = b.pos;
30618
+ for (let i = 0; i < n; i++) node.get(ids[i]).cross = xs[i] + off[i];
30619
+ }
30620
+ function gapBetween(a, b) {
30621
+ const base = a.thick / 2 + b.thick / 2 + NODESEP2;
30622
+ if (a.dummy || b.dummy) return base;
30623
+ if (a.unit !== b.unit) return base + 2 * GROUP_PAD + GROUP_GAP;
30624
+ const da = a.path[a.path.length - 1];
30625
+ const db = b.path[b.path.length - 1];
30626
+ if (da !== db && (da || db)) return base + 2 * GROUP_PAD;
30627
+ return base;
30628
+ }
30629
+ function realize(rk) {
30630
+ const bandDepth = rk.map(
30631
+ (ids) => Math.max(0, ...ids.map((id) => node.get(id).depth))
30632
+ );
30633
+ const bandCenter = [];
30634
+ let acc = MARGIN4;
30635
+ for (let r = 0; r <= maxRank; r++) {
30636
+ if (r > 0) acc += RANKSEP2 + (tierStartRanks.has(r) ? TIER_GAP : 0);
30637
+ bandCenter[r] = acc + bandDepth[r] / 2;
30638
+ acc += bandDepth[r];
30639
+ }
30640
+ const gapsFor = (ids) => ids.map(
30641
+ (id, i) => i === 0 ? 0 : gapBetween(node.get(ids[i - 1]), node.get(id))
30642
+ );
30643
+ for (let r = 0; r <= maxRank; r++) {
30644
+ const ids = rk[r];
30645
+ const gap = gapsFor(ids);
30646
+ let x = MARGIN4;
30647
+ for (let i = 0; i < ids.length; i++) {
30648
+ const n = node.get(ids[i]);
30649
+ x += gap[i];
30650
+ n.cross = i === 0 ? MARGIN4 + n.thick / 2 : x;
30651
+ }
30652
+ }
30653
+ const ITER = 18;
30654
+ for (let it = 0; it < ITER; it++) {
30655
+ const downPass = it % 2 === 0;
30656
+ const order = downPass ? Array.from({ length: maxRank + 1 }, (_, i) => i) : Array.from({ length: maxRank + 1 }, (_, i) => maxRank - i);
30657
+ for (const r of order) {
30658
+ const ids = rk[r];
30659
+ const desired = ids.map((id) => {
30660
+ const n = node.get(id);
30661
+ let sw = 0, sx2 = 0;
30662
+ for (const nb of up.get(id)) {
30663
+ const w = weight(n, node.get(nb));
30664
+ sw += w;
30665
+ sx2 += w * node.get(nb).cross;
30666
+ }
30667
+ for (const nb of down.get(id)) {
30668
+ const w = weight(n, node.get(nb));
30669
+ sw += w;
30670
+ sx2 += w * node.get(nb).cross;
30671
+ }
30672
+ return sw > 0 ? sx2 / sw : n.cross;
30673
+ });
30674
+ placeWithSeparation(ids, desired, gapsFor(ids));
30675
+ }
30676
+ }
30677
+ let minC = Infinity;
30678
+ for (const n of node.values()) minC = Math.min(minC, n.cross - n.thick / 2);
30679
+ const shiftC = MARGIN4 - minC;
30680
+ for (const n of node.values()) n.cross += shiftC;
30681
+ const coord = (n) => isTB ? { x: n.cross, y: bandCenter[n.rank] } : { x: bandCenter[n.rank], y: n.cross };
30682
+ const layoutNodes = parsed.nodes.map((pn) => {
30683
+ const n = node.get(pn.label);
30684
+ const c = coord(n);
30685
+ return {
30686
+ label: pn.label,
30687
+ x: c.x,
30688
+ y: c.y,
30689
+ width: n.realW,
30690
+ height: n.realH
30691
+ };
30692
+ });
30693
+ const chainById = /* @__PURE__ */ new Map();
30694
+ for (const ch of chains) chainById.set(ch.edgeIdx, ch.chain);
30695
+ let minCross = Infinity, maxCross = -Infinity;
30696
+ for (const n of node.values()) {
30697
+ if (n.dummy) continue;
30698
+ minCross = Math.min(minCross, n.cross - n.thick / 2);
30699
+ maxCross = Math.max(maxCross, n.cross + n.thick / 2);
30700
+ }
30701
+ const GAP = 34;
30702
+ const LANE = 28;
30703
+ const faceLim = (depth) => Math.max(0, depth / 2 - 6);
30704
+ const sideOf = (b) => {
30705
+ const d = node.get(b.src).cross - node.get(b.tgt).cross;
30706
+ return d >= 0 ? 1 : -1;
30707
+ };
30708
+ const backSorted = backEdges.map((b) => ({
30709
+ ...b,
30710
+ side: sideOf(b),
30711
+ span: Math.abs(node.get(b.src).rank - node.get(b.tgt).rank)
30712
+ })).sort((a, b) => a.side - b.side || a.span - b.span);
30713
+ const sideCounts = /* @__PURE__ */ new Map();
30714
+ for (const b of backSorted)
30715
+ sideCounts.set(b.side, (sideCounts.get(b.side) ?? 0) + 1);
30716
+ const laneCounter = /* @__PURE__ */ new Map();
30717
+ const backPoints = /* @__PURE__ */ new Map();
30718
+ for (const b of backSorted) {
30719
+ const s = b.side;
30720
+ const k = laneCounter.get(s) ?? 0;
30721
+ laneCounter.set(s, k + 1);
30722
+ const K = sideCounts.get(s);
30723
+ const src = node.get(b.src);
30724
+ const tgt = node.get(b.tgt);
30725
+ const laneC = s > 0 ? maxCross + GAP + k * LANE : minCross - GAP - k * LANE;
30726
+ const srcCtr = bandCenter[src.rank];
30727
+ const tgtCtr = bandCenter[tgt.rank];
30728
+ const frac = K > 1 ? (k + 1) / (K + 1) : 0;
30729
+ const srcR = srcCtr - frac * faceLim(src.depth);
30730
+ const tgtR = tgtCtr + frac * faceLim(tgt.depth);
30731
+ const m = (c, rr) => isTB ? { x: c, y: rr } : { x: rr, y: c };
30732
+ const off = 10;
30733
+ const srcEdgeC = src.cross + s * (src.thick / 2 + off);
30734
+ const tgtEdgeC = tgt.cross + s * (tgt.thick / 2 + off);
30735
+ backPoints.set(b.edgeIdx, [
30736
+ m(src.cross, srcCtr),
30737
+ m(srcEdgeC, srcR),
30738
+ m(laneC, srcR),
30739
+ m(laneC, tgtR),
30740
+ m(tgtEdgeC, tgtR),
30741
+ m(tgt.cross, tgtCtr)
30742
+ ]);
30743
+ }
30744
+ const flatPoints = /* @__PURE__ */ new Map();
30745
+ for (const f of flatEdges) {
30746
+ const a = node.get(f.a);
30747
+ const b = node.get(f.b);
30748
+ const ctr = bandCenter[a.rank];
30749
+ const bow = ctr - (a.depth / 2 + 26);
30750
+ const m = (c, rr) => isTB ? { x: c, y: rr } : { x: rr, y: c };
30751
+ flatPoints.set(f.edgeIdx, [
30752
+ m(a.cross, ctr),
30753
+ m(a.cross, bow),
30754
+ m(b.cross, bow),
30755
+ m(b.cross, ctr)
30756
+ ]);
30757
+ }
30758
+ const layoutEdges = [];
30759
+ for (let i = 0; i < parsed.edges.length; i++) {
30760
+ const e = parsed.edges[i];
30761
+ if (!labelSet.has(e.source) || !labelSet.has(e.target)) continue;
30762
+ let points;
30763
+ if (e.source === e.target) {
30764
+ const c = coord(node.get(e.source));
30765
+ points = [c, c];
30766
+ } else if (backPoints.has(i)) points = backPoints.get(i);
30767
+ else if (flatPoints.has(i)) points = flatPoints.get(i);
30768
+ else {
30769
+ const chain = chainById.get(i);
30770
+ if (!chain) continue;
30771
+ points = chain.map((id) => coord(node.get(id)));
30772
+ }
30773
+ layoutEdges.push({
30774
+ source: e.source,
30775
+ target: e.target,
30776
+ ...e.label !== void 0 && { label: e.label },
30777
+ bidirectional: e.bidirectional,
30778
+ lineNumber: e.lineNumber,
30779
+ points,
30780
+ yOffset: 0,
30781
+ parallelCount: 1,
30782
+ metadata: e.metadata
30783
+ });
30784
+ }
30785
+ const nodeBox = new Map(
30786
+ layoutNodes.map((n) => [
30787
+ n.label,
30788
+ {
30789
+ l: n.x - n.width / 2,
30790
+ r: n.x + n.width / 2,
30791
+ t: n.y - n.height / 2,
30792
+ b: n.y + n.height / 2
30793
+ }
30794
+ ])
30795
+ );
30796
+ const depthOf = (label) => {
30797
+ let d = 0;
30798
+ let cur = label;
30799
+ const guard = /* @__PURE__ */ new Set();
30800
+ while (cur && !guard.has(cur)) {
30801
+ guard.add(cur);
30802
+ cur = groupByLabel.get(cur)?.parentGroup;
30803
+ if (cur) d++;
30804
+ }
30805
+ return d;
30806
+ };
30807
+ const childGroups = /* @__PURE__ */ new Map();
30808
+ for (const g of parsed.groups)
30809
+ if (g.parentGroup)
30810
+ (childGroups.get(g.parentGroup) ?? childGroups.set(g.parentGroup, []).get(g.parentGroup)).push(g.label);
30811
+ const rectByGroup = /* @__PURE__ */ new Map();
30812
+ const ordered = [...parsed.groups].sort(
30813
+ (a, b) => depthOf(b.label) - depthOf(a.label)
30814
+ );
30815
+ const layoutGroups = [];
30816
+ for (const grp of ordered) {
30817
+ let l = Infinity, r = -Infinity, t = Infinity, bm = -Infinity;
30818
+ const acc2 = (box) => {
30819
+ l = Math.min(l, box.l);
30820
+ r = Math.max(r, box.r);
30821
+ t = Math.min(t, box.t);
30822
+ bm = Math.max(bm, box.b);
30823
+ };
30824
+ for (const c of grp.children) {
30825
+ const nb = nodeBox.get(c);
30826
+ if (nb) acc2(nb);
30827
+ }
30828
+ for (const sub of childGroups.get(grp.label) ?? []) {
30829
+ const sr = rectByGroup.get(sub);
30830
+ if (sr) acc2(sr);
30831
+ }
30832
+ if (!Number.isFinite(l)) continue;
30833
+ const x0 = l - GROUP_PAD;
30834
+ const x1 = r + GROUP_PAD;
30835
+ const y0 = t - GROUP_LABEL_ZONE2;
30836
+ const y1 = bm + GROUP_PAD;
30837
+ rectByGroup.set(grp.label, { l: x0, r: x1, t: y0, b: y1 });
30838
+ layoutGroups.push({
30839
+ label: grp.label,
30840
+ lineNumber: grp.lineNumber,
30841
+ x: (x0 + x1) / 2,
30842
+ y: (y0 + y1) / 2,
30843
+ width: x1 - x0,
30844
+ height: y1 - y0,
30845
+ collapsed: false,
30846
+ childCount: grp.children.length
30847
+ });
30848
+ }
30849
+ let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
30850
+ const ext = (x0, y0, x1, y1) => {
30851
+ minX = Math.min(minX, x0);
30852
+ minY = Math.min(minY, y0);
30853
+ maxX = Math.max(maxX, x1);
30854
+ maxY = Math.max(maxY, y1);
30855
+ };
30856
+ for (const n of layoutNodes)
30857
+ ext(
30858
+ n.x - n.width / 2,
30859
+ n.y - n.height / 2,
30860
+ n.x + n.width / 2,
30861
+ n.y + n.height / 2
30862
+ );
30863
+ for (const e of layoutEdges)
30864
+ for (const p of e.points) ext(p.x, p.y, p.x, p.y);
30865
+ for (const g of layoutGroups)
30866
+ ext(
30867
+ g.x - g.width / 2,
30868
+ g.y - g.height / 2,
30869
+ g.x + g.width / 2,
30870
+ g.y + g.height / 2
30871
+ );
30872
+ const sx = MARGIN4 - minX;
30873
+ const sy = MARGIN4 - minY;
30874
+ const shift = sx !== 0 || sy !== 0;
30875
+ return {
30876
+ nodes: shift ? layoutNodes.map((n) => ({ ...n, x: n.x + sx, y: n.y + sy })) : layoutNodes,
30877
+ edges: shift ? layoutEdges.map((e) => ({
30878
+ ...e,
30879
+ points: e.points.map((p) => ({ x: p.x + sx, y: p.y + sy }))
30880
+ })) : layoutEdges,
30881
+ groups: shift ? layoutGroups.map((g) => ({ ...g, x: g.x + sx, y: g.y + sy })) : layoutGroups,
30882
+ width: maxX + sx + MARGIN4,
30883
+ height: maxY + sy + MARGIN4
30884
+ };
30885
+ }
30886
+ }
30887
+ var NODESEP2, RANKSEP2, DUMMY_THICK2, MARGIN4, GROUP_PAD, GROUP_LABEL_ZONE2, GROUP_GAP, TIER_GAP, W_REAL_REAL2, W_REAL_DUMMY2, W_DUMMY_DUMMY2;
30888
+ var init_layout_grouped = __esm({
30889
+ "src/boxes-and-lines/layout-grouped.ts"() {
30890
+ "use strict";
30891
+ init_layout5();
30892
+ NODESEP2 = 50;
30893
+ RANKSEP2 = 60;
30894
+ DUMMY_THICK2 = 10;
30895
+ MARGIN4 = 40;
30896
+ GROUP_PAD = 16;
30897
+ GROUP_LABEL_ZONE2 = 32;
30898
+ GROUP_GAP = 24;
30899
+ TIER_GAP = GROUP_LABEL_ZONE2 + 2 * GROUP_PAD;
30900
+ W_REAL_REAL2 = 1;
30901
+ W_REAL_DUMMY2 = 2;
30902
+ W_DUMMY_DUMMY2 = 8;
30903
+ }
30904
+ });
30905
+
30179
30906
  // src/boxes-and-lines/layout-search.ts
30180
30907
  var layout_search_exports = {};
30181
30908
  __export(layout_search_exports, {
@@ -30191,7 +30918,7 @@ __export(layout_search_exports, {
30191
30918
  layoutBoxesAndLinesSearch: () => layoutBoxesAndLinesSearch,
30192
30919
  separateGroupBands: () => separateGroupBands
30193
30920
  });
30194
- function rng2(s) {
30921
+ function rng3(s) {
30195
30922
  return () => {
30196
30923
  s |= 0;
30197
30924
  s = s + 1831565813 | 0;
@@ -30546,7 +31273,7 @@ function detectGroupOverlaps(layout, opts) {
30546
31273
  const contains = (outer, inner) => outer.l <= inner.l + margin && outer.r >= inner.r - margin && outer.t <= inner.t + margin && outer.b >= inner.b - margin;
30547
31274
  const rend = raw.map((a, i) => {
30548
31275
  const parent = raw.some((b, j) => j !== i && contains(a, b));
30549
- return parent ? { ...a, t: a.t - GROUP_LABEL_ZONE2 } : a;
31276
+ return parent ? { ...a, t: a.t - GROUP_LABEL_ZONE3 } : a;
30550
31277
  });
30551
31278
  const hit = /* @__PURE__ */ new Set();
30552
31279
  for (let i = 0; i < raw.length; i++)
@@ -30573,7 +31300,7 @@ function countGroupOverlaps(layout, opts) {
30573
31300
  const contains = (outer, inner) => outer.l <= inner.l + margin && outer.r >= inner.r - margin && outer.t <= inner.t + margin && outer.b >= inner.b - margin;
30574
31301
  const rend = raw.map((a, i) => {
30575
31302
  const parent = raw.some((b, j) => j !== i && contains(a, b));
30576
- return parent ? { ...a, t: a.t - GROUP_LABEL_ZONE2 } : a;
31303
+ return parent ? { ...a, t: a.t - GROUP_LABEL_ZONE3 } : a;
30577
31304
  });
30578
31305
  let count = 0;
30579
31306
  for (let i = 0; i < raw.length; i++)
@@ -30616,7 +31343,7 @@ function separateGroupBands(layout, parsed) {
30616
31343
  let lo = (axis === "y" ? g.y : g.x) - half2;
30617
31344
  const hi = (axis === "y" ? g.y : g.x) + half2;
30618
31345
  const isParent = parsed.groups.some((c) => c.parentGroup === label);
30619
- if (axis === "y" && isParent) lo -= GROUP_LABEL_ZONE2;
31346
+ if (axis === "y" && isParent) lo -= GROUP_LABEL_ZONE3;
30620
31347
  return { label, lo, hi, c: (lo + hi) / 2 };
30621
31348
  });
30622
31349
  bands.sort((a, b) => a.c - b.c);
@@ -30687,7 +31414,7 @@ function separateGroupBands(layout, parsed) {
30687
31414
  acc(n.x + n.width / 2, n.y + n.height / 2);
30688
31415
  }
30689
31416
  for (const g of groups) {
30690
- acc(g.x - g.width / 2, g.y - g.height / 2 - GROUP_LABEL_ZONE2);
31417
+ acc(g.x - g.width / 2, g.y - g.height / 2 - GROUP_LABEL_ZONE3);
30691
31418
  acc(g.x + g.width / 2, g.y + g.height / 2);
30692
31419
  }
30693
31420
  for (const e of edges) for (const p of e.points) acc(p.x, p.y);
@@ -30951,7 +31678,7 @@ async function layoutBoxesAndLinesSearch(parsed, collapseInfo, opts) {
30951
31678
  }
30952
31679
  ];
30953
31680
  });
30954
- const GROUP_PAD = 16;
31681
+ const GROUP_PAD2 = 16;
30955
31682
  const nodeByLabel = new Map(nodes.map((n2) => [n2.label, n2]));
30956
31683
  const groups = [];
30957
31684
  for (const grp of parsed.groups) {
@@ -30965,10 +31692,10 @@ async function layoutBoxesAndLinesSearch(parsed, collapseInfo, opts) {
30965
31692
  gy1 = Math.max(gy1, n2.y + n2.height / 2);
30966
31693
  }
30967
31694
  if (!Number.isFinite(gx0)) continue;
30968
- const x0 = gx0 - GROUP_PAD;
30969
- const x1 = gx1 + GROUP_PAD;
30970
- const y0 = gy0 - GROUP_LABEL_ZONE2;
30971
- const y1 = gy1 + GROUP_PAD;
31695
+ const x0 = gx0 - GROUP_PAD2;
31696
+ const x1 = gx1 + GROUP_PAD2;
31697
+ const y0 = gy0 - GROUP_LABEL_ZONE3;
31698
+ const y1 = gy1 + GROUP_PAD2;
30972
31699
  groups.push({
30973
31700
  label: grp.label,
30974
31701
  lineNumber: grp.lineNumber,
@@ -31025,7 +31752,7 @@ async function layoutBoxesAndLinesSearch(parsed, collapseInfo, opts) {
31025
31752
  };
31026
31753
  }
31027
31754
  function place(cfg) {
31028
- const r = cfg.seed === void 0 ? null : rng2(cfg.seed + 1);
31755
+ const r = cfg.seed === void 0 ? null : rng3(cfg.seed + 1);
31029
31756
  const ord = (a) => r ? shuffle(a, r) : a.slice();
31030
31757
  const g = new import_dagre4.default.graphlib.Graph({ compound: true, multigraph: true });
31031
31758
  g.setGraph({
@@ -31212,8 +31939,15 @@ async function layoutBoxesAndLinesSearch(parsed, collapseInfo, opts) {
31212
31939
  layered = layeredCandidates(parsed, sizes);
31213
31940
  } catch {
31214
31941
  }
31942
+ if (parsed.groups.length > 0 && collapsedGroupLabels.size === 0) {
31943
+ try {
31944
+ layered = layered.concat(groupedTierCandidates(parsed, sizes));
31945
+ } catch {
31946
+ }
31947
+ }
31215
31948
  const fastKey = /* @__PURE__ */ new Map();
31216
- for (const lay of pool) fastKey.set(lay, objective(lay, countCrossingsFast(lay)));
31949
+ for (const lay of pool)
31950
+ fastKey.set(lay, objective(lay, countCrossingsFast(lay)));
31217
31951
  pool.sort((a, b) => fastKey.get(a) - fastKey.get(b));
31218
31952
  const refineK = Math.min(REFINE_K, pool.length);
31219
31953
  let best = pool[0];
@@ -31278,7 +32012,7 @@ async function layoutBoxesAndLinesSearch(parsed, collapseInfo, opts) {
31278
32012
  }
31279
32013
  return best;
31280
32014
  }
31281
- var import_dagre4, import_d3_shape, DEFAULT_LAMBDA, ESCALATE_THRESHOLD, ESCALATE_MAX_N, ESCALATE_SEEDS, ESCALATE_REFINE, splineGen, PATH_TOKEN_RE, FLAT_CACHE, GROUP_LABEL_ZONE2;
32015
+ var import_dagre4, import_d3_shape, DEFAULT_LAMBDA, ESCALATE_THRESHOLD, ESCALATE_MAX_N, ESCALATE_SEEDS, ESCALATE_REFINE, splineGen, PATH_TOKEN_RE, FLAT_CACHE, GROUP_LABEL_ZONE3;
31282
32016
  var init_layout_search = __esm({
31283
32017
  "src/boxes-and-lines/layout-search.ts"() {
31284
32018
  "use strict";
@@ -31286,6 +32020,7 @@ var init_layout_search = __esm({
31286
32020
  import_d3_shape = require("d3-shape");
31287
32021
  init_layout5();
31288
32022
  init_layout_layered();
32023
+ init_layout_grouped();
31289
32024
  DEFAULT_LAMBDA = 4;
31290
32025
  ESCALATE_THRESHOLD = 4;
31291
32026
  ESCALATE_MAX_N = 45;
@@ -31294,7 +32029,7 @@ var init_layout_search = __esm({
31294
32029
  splineGen = (0, import_d3_shape.line)().x((d) => d.x).y((d) => d.y).curve(import_d3_shape.curveBasis);
31295
32030
  PATH_TOKEN_RE = /[MLQC]|-?\d*\.?\d+(?:e-?\d+)?/gi;
31296
32031
  FLAT_CACHE = /* @__PURE__ */ new WeakMap();
31297
- GROUP_LABEL_ZONE2 = 32;
32032
+ GROUP_LABEL_ZONE3 = 32;
31298
32033
  }
31299
32034
  });
31300
32035
 
@@ -31471,8 +32206,8 @@ function attachNotes(layout, parsed, collapsedNotes) {
31471
32206
  nodes: finalNodes,
31472
32207
  edges: finalEdges,
31473
32208
  groups: finalGroups,
31474
- width: bbMaxX + shiftX + MARGIN4,
31475
- height: bbMaxY + shiftY + MARGIN4
32209
+ width: bbMaxX + shiftX + MARGIN5,
32210
+ height: bbMaxY + shiftY + MARGIN5
31476
32211
  };
31477
32212
  }
31478
32213
  function applyParallelEdgeOffsets(layout) {
@@ -31505,13 +32240,13 @@ function applyParallelEdgeOffsets(layout) {
31505
32240
  }))
31506
32241
  };
31507
32242
  }
31508
- 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;
32243
+ 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;
31509
32244
  var init_layout5 = __esm({
31510
32245
  "src/boxes-and-lines/layout.ts"() {
31511
32246
  "use strict";
31512
32247
  init_text_measure();
31513
32248
  init_notes();
31514
- MARGIN4 = 40;
32249
+ MARGIN5 = 40;
31515
32250
  MAX_PARALLEL_EDGES = 5;
31516
32251
  PARALLEL_SPACING = 22;
31517
32252
  PHI = 1.618;
@@ -31682,7 +32417,7 @@ function layoutMindmap(parsed, _palette, options) {
31682
32417
  leafWidth: ctx.structural(LEAF_WIDTH),
31683
32418
  hGap: ctx.aesthetic(H_GAP2),
31684
32419
  vGap: ctx.aesthetic(V_GAP2),
31685
- margin: ctx.aesthetic(MARGIN5),
32420
+ margin: ctx.aesthetic(MARGIN6),
31686
32421
  multiRootGap: ctx.aesthetic(MULTI_ROOT_GAP)
31687
32422
  };
31688
32423
  populateDepthCache(roots);
@@ -32060,7 +32795,7 @@ function populateDepthCache(roots) {
32060
32795
  };
32061
32796
  walk(roots, 0);
32062
32797
  }
32063
- 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;
32798
+ 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;
32064
32799
  var init_layout6 = __esm({
32065
32800
  "src/mindmap/layout.ts"() {
32066
32801
  "use strict";
@@ -32076,7 +32811,7 @@ var init_layout6 = __esm({
32076
32811
  NODE_V_PAD = 10;
32077
32812
  H_GAP2 = 40;
32078
32813
  V_GAP2 = 12;
32079
- MARGIN5 = 40;
32814
+ MARGIN6 = 40;
32080
32815
  MULTI_ROOT_GAP = 60;
32081
32816
  nodeDepthCache = /* @__PURE__ */ new Map();
32082
32817
  }
@@ -32333,7 +33068,7 @@ function renderMindmap(container, parsed, layout, palette, isDark, onClickItem,
32333
33068
  );
32334
33069
  const nodeG = mainG.append("g").attr("class", "mindmap-node").attr("data-line-number", node.lineNumber);
32335
33070
  if (activeTagGroup) {
32336
- const tagKey = activeTagGroup.toLowerCase();
33071
+ const tagKey = tagAttrKey(activeTagGroup);
32337
33072
  const metaValue = node.metadata[tagKey];
32338
33073
  if (metaValue) {
32339
33074
  nodeG.attr(`data-tag-${tagKey}`, metaValue.toLowerCase());
@@ -32452,6 +33187,7 @@ var d3Selection12, DIAGRAM_PADDING7, TITLE_HEIGHT4, SINGLE_LABEL_HEIGHT2, LABEL_
32452
33187
  var init_renderer7 = __esm({
32453
33188
  "src/mindmap/renderer.ts"() {
32454
33189
  "use strict";
33190
+ init_tag_groups();
32455
33191
  d3Selection12 = __toESM(require("d3-selection"), 1);
32456
33192
  init_fonts();
32457
33193
  init_export_container();
@@ -33662,7 +34398,7 @@ function resolveNodeColor4(el, tagGroups, activeGroupName, ancestors) {
33662
34398
  (g) => g.name.toLowerCase() === activeGroupName.toLowerCase()
33663
34399
  );
33664
34400
  if (!group) return void 0;
33665
- const key = group.name.toLowerCase();
34401
+ const key = tagAttrKey(group.name);
33666
34402
  let metaValue = el.metadata[key];
33667
34403
  if (!metaValue && ancestors) {
33668
34404
  for (const ancestor of ancestors) {
@@ -33864,8 +34600,8 @@ function layoutC4Context(parsed, activeTagGroup) {
33864
34600
  }
33865
34601
  }
33866
34602
  if (nodes.length > 0) {
33867
- const shiftX = MARGIN6 - minX;
33868
- const shiftY = MARGIN6 - minY;
34603
+ const shiftX = MARGIN7 - minX;
34604
+ const shiftY = MARGIN7 - minY;
33869
34605
  for (const node of nodes) {
33870
34606
  node.x += shiftX;
33871
34607
  node.y += shiftY;
@@ -33877,12 +34613,12 @@ function layoutC4Context(parsed, activeTagGroup) {
33877
34613
  }
33878
34614
  }
33879
34615
  }
33880
- let totalWidth = nodes.length > 0 ? maxX - minX + MARGIN6 * 2 : 0;
33881
- let totalHeight = nodes.length > 0 ? maxY - minY + MARGIN6 * 2 : 0;
34616
+ let totalWidth = nodes.length > 0 ? maxX - minX + MARGIN7 * 2 : 0;
34617
+ let totalHeight = nodes.length > 0 ? maxY - minY + MARGIN7 * 2 : 0;
33882
34618
  const legendGroups = computeLegendGroups3(parsed.tagGroups);
33883
34619
  if (legendGroups.length > 0) {
33884
- const legendY = totalHeight + MARGIN6;
33885
- let legendX = MARGIN6;
34620
+ const legendY = totalHeight + MARGIN7;
34621
+ let legendX = MARGIN7;
33886
34622
  for (const lg of legendGroups) {
33887
34623
  lg.x = legendX;
33888
34624
  lg.y = legendY;
@@ -34214,8 +34950,8 @@ function layoutC4Containers(parsed, systemName, activeTagGroup) {
34214
34950
  if (pt.y > maxY) maxY = pt.y;
34215
34951
  }
34216
34952
  }
34217
- const shiftX = MARGIN6 - minX;
34218
- const shiftY = MARGIN6 - minY;
34953
+ const shiftX = MARGIN7 - minX;
34954
+ const shiftY = MARGIN7 - minY;
34219
34955
  for (const node of nodes) {
34220
34956
  node.x += shiftX;
34221
34957
  node.y += shiftY;
@@ -34232,12 +34968,12 @@ function layoutC4Containers(parsed, systemName, activeTagGroup) {
34232
34968
  pt.y += shiftY;
34233
34969
  }
34234
34970
  }
34235
- let totalWidth = maxX - minX + MARGIN6 * 2;
34236
- let totalHeight = maxY - minY + MARGIN6 * 2;
34971
+ let totalWidth = maxX - minX + MARGIN7 * 2;
34972
+ let totalHeight = maxY - minY + MARGIN7 * 2;
34237
34973
  const legendGroups = computeLegendGroups3(parsed.tagGroups);
34238
34974
  if (legendGroups.length > 0) {
34239
- const legendY = totalHeight + MARGIN6;
34240
- let legendX = MARGIN6;
34975
+ const legendY = totalHeight + MARGIN7;
34976
+ let legendX = MARGIN7;
34241
34977
  for (const lg of legendGroups) {
34242
34978
  lg.x = legendX;
34243
34979
  lg.y = legendY;
@@ -34629,8 +35365,8 @@ function layoutC4Components(parsed, systemName, containerName, activeTagGroup) {
34629
35365
  if (pt.y > maxY) maxY = pt.y;
34630
35366
  }
34631
35367
  }
34632
- const shiftX = MARGIN6 - minX;
34633
- const shiftY = MARGIN6 - minY;
35368
+ const shiftX = MARGIN7 - minX;
35369
+ const shiftY = MARGIN7 - minY;
34634
35370
  for (const node of nodes) {
34635
35371
  node.x += shiftX;
34636
35372
  node.y += shiftY;
@@ -34647,12 +35383,12 @@ function layoutC4Components(parsed, systemName, containerName, activeTagGroup) {
34647
35383
  pt.y += shiftY;
34648
35384
  }
34649
35385
  }
34650
- let totalWidth = maxX - minX + MARGIN6 * 2;
34651
- let totalHeight = maxY - minY + MARGIN6 * 2;
35386
+ let totalWidth = maxX - minX + MARGIN7 * 2;
35387
+ let totalHeight = maxY - minY + MARGIN7 * 2;
34652
35388
  const legendGroups = computeLegendGroups3(parsed.tagGroups);
34653
35389
  if (legendGroups.length > 0) {
34654
- const legendY = totalHeight + MARGIN6;
34655
- let legendX = MARGIN6;
35390
+ const legendY = totalHeight + MARGIN7;
35391
+ let legendX = MARGIN7;
34656
35392
  for (const lg of legendGroups) {
34657
35393
  lg.x = legendX;
34658
35394
  lg.y = legendY;
@@ -34940,8 +35676,8 @@ function layoutC4Deployment(parsed, activeTagGroup) {
34940
35676
  if (pt.y > maxY) maxY = pt.y;
34941
35677
  }
34942
35678
  }
34943
- const shiftX = MARGIN6 - minX;
34944
- const shiftY = MARGIN6 - minY;
35679
+ const shiftX = MARGIN7 - minX;
35680
+ const shiftY = MARGIN7 - minY;
34945
35681
  for (const node of nodes) {
34946
35682
  node.x += shiftX;
34947
35683
  node.y += shiftY;
@@ -34956,12 +35692,12 @@ function layoutC4Deployment(parsed, activeTagGroup) {
34956
35692
  pt.y += shiftY;
34957
35693
  }
34958
35694
  }
34959
- let totalWidth = maxX - minX + MARGIN6 * 2;
34960
- let totalHeight = maxY - minY + MARGIN6 * 2;
35695
+ let totalWidth = maxX - minX + MARGIN7 * 2;
35696
+ let totalHeight = maxY - minY + MARGIN7 * 2;
34961
35697
  const legendGroups = computeLegendGroups3(parsed.tagGroups);
34962
35698
  if (legendGroups.length > 0) {
34963
- const legendY = totalHeight + MARGIN6;
34964
- let legendX = MARGIN6;
35699
+ const legendY = totalHeight + MARGIN7;
35700
+ let legendX = MARGIN7;
34965
35701
  for (const lg of legendGroups) {
34966
35702
  lg.x = legendX;
34967
35703
  lg.y = legendY;
@@ -34981,10 +35717,11 @@ function layoutC4Deployment(parsed, activeTagGroup) {
34981
35717
  height: totalHeight
34982
35718
  };
34983
35719
  }
34984
- var import_dagre5, 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;
35720
+ var import_dagre5, 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;
34985
35721
  var init_layout8 = __esm({
34986
35722
  "src/c4/layout.ts"() {
34987
35723
  "use strict";
35724
+ init_tag_groups();
34988
35725
  import_dagre5 = __toESM(require("@dagrejs/dagre"), 1);
34989
35726
  init_legend_constants();
34990
35727
  init_text_measure();
@@ -35002,7 +35739,7 @@ var init_layout8 = __esm({
35002
35739
  CARD_H_PAD3 = 20;
35003
35740
  META_LINE_HEIGHT4 = 16;
35004
35741
  META_FONT_SIZE4 = 11;
35005
- MARGIN6 = 40;
35742
+ MARGIN7 = 40;
35006
35743
  BOUNDARY_PAD = 40;
35007
35744
  GROUP_BOUNDARY_PAD = 24;
35008
35745
  EDGE_NODE_COLLISION_WEIGHT = 5e3;
@@ -35157,7 +35894,7 @@ function renderC4Context(container, parsed, layout, palette, isDark, onClickItem
35157
35894
  for (const node of layout.nodes) {
35158
35895
  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);
35159
35896
  if (activeTagGroup) {
35160
- const tagKey = activeTagGroup.toLowerCase();
35897
+ const tagKey = tagAttrKey(activeTagGroup);
35161
35898
  const tagValue = node.metadata[tagKey];
35162
35899
  if (tagValue) {
35163
35900
  nodeG.attr(`data-tag-${tagKey}`, tagValue.toLowerCase());
@@ -35716,7 +36453,7 @@ function renderC4Containers(container, parsed, layout, palette, isDark, onClickI
35716
36453
  for (const node of layout.nodes) {
35717
36454
  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);
35718
36455
  if (activeTagGroup) {
35719
- const tagKey = activeTagGroup.toLowerCase();
36456
+ const tagKey = tagAttrKey(activeTagGroup);
35720
36457
  const tagValue = node.metadata[tagKey];
35721
36458
  if (tagValue) {
35722
36459
  nodeG.attr(`data-tag-${tagKey}`, tagValue.toLowerCase());
@@ -35980,6 +36717,7 @@ var d3Selection14, d3Shape6, DIAGRAM_PADDING8, MAX_SCALE5, TITLE_HEIGHT6, TYPE_F
35980
36717
  var init_renderer9 = __esm({
35981
36718
  "src/c4/renderer.ts"() {
35982
36719
  "use strict";
36720
+ init_tag_groups();
35983
36721
  d3Selection14 = __toESM(require("d3-selection"), 1);
35984
36722
  d3Shape6 = __toESM(require("d3-shape"), 1);
35985
36723
  init_fonts();
@@ -38044,7 +38782,7 @@ function separateGroups(groups, nodes, isLR, maxIterations = 20) {
38044
38782
  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);
38045
38783
  if (crossOverlap <= 0) continue;
38046
38784
  anyOverlap = true;
38047
- const shift = primaryOverlap + GROUP_GAP;
38785
+ const shift = primaryOverlap + GROUP_GAP2;
38048
38786
  const aCenter = isLR ? ga.y + ga.height / 2 : ga.x + ga.width / 2;
38049
38787
  const bCenter = isLR ? gb.y + gb.height / 2 : gb.x + gb.width / 2;
38050
38788
  const groupToShift = aCenter <= bCenter ? gb : ga;
@@ -38338,7 +39076,7 @@ function layoutInfra(computed, expandedNodeIds, collapsedNodes) {
38338
39076
  height: totalHeight
38339
39077
  };
38340
39078
  }
38341
- var import_dagre7, 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;
39079
+ var import_dagre7, 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;
38342
39080
  var init_layout10 = __esm({
38343
39081
  "src/infra/layout.ts"() {
38344
39082
  "use strict";
@@ -38394,7 +39132,7 @@ var init_layout10 = __esm({
38394
39132
  "retention-hours": "retention",
38395
39133
  partitions: "partitions"
38396
39134
  };
38397
- GROUP_GAP = GROUP_PADDING2 * 2 + GROUP_HEADER_HEIGHT;
39135
+ GROUP_GAP2 = GROUP_PADDING2 * 2 + GROUP_HEADER_HEIGHT;
38398
39136
  }
38399
39137
  });
38400
39138
 
@@ -39201,7 +39939,7 @@ function resolveActiveTagStroke(node, activeGroup, tagGroups, palette) {
39201
39939
  (t) => t.name.toLowerCase() === activeGroup.toLowerCase()
39202
39940
  );
39203
39941
  if (!tg) return null;
39204
- const tagKey = (tg.alias ?? tg.name).toLowerCase();
39942
+ const tagKey = tagAttrKey(tg.alias ?? tg.name);
39205
39943
  const tagVal = node.tags[tagKey];
39206
39944
  if (!tagVal) return null;
39207
39945
  const tv = tg.values.find(
@@ -39530,7 +40268,7 @@ function computeInfraLegendGroups(nodes, tagGroups, palette, edges) {
39530
40268
  groups.push({
39531
40269
  name: tg.name,
39532
40270
  type: "tag",
39533
- tagKey: (tg.alias ?? tg.name).toLowerCase(),
40271
+ tagKey: tagAttrKey(tg.alias ?? tg.name),
39534
40272
  entries,
39535
40273
  width: LEGEND_CAPSULE_PAD * 2 + pillWidth2 + 4 + entriesWidth2,
39536
40274
  minifiedWidth: pillWidth2
@@ -39578,7 +40316,7 @@ function renderLegend3(rootSvg, legendGroups, totalWidth, legendY, palette, isDa
39578
40316
  });
39579
40317
  legendG.selectAll("[data-legend-group]").classed("infra-legend-group", true);
39580
40318
  for (const group of legendGroups) {
39581
- const groupKey = group.name.toLowerCase();
40319
+ const groupKey = tagAttrKey(group.name);
39582
40320
  for (const entry of group.entries) {
39583
40321
  const entryEl = legendG.select(
39584
40322
  `[data-legend-group="${groupKey}"] [data-legend-entry="${entry.value.toLowerCase()}"]`
@@ -39801,6 +40539,7 @@ var d3Selection16, d3Shape8, NODE_FONT_SIZE4, META_FONT_SIZE6, META_LINE_HEIGHT6
39801
40539
  var init_renderer10 = __esm({
39802
40540
  "src/infra/renderer.ts"() {
39803
40541
  "use strict";
40542
+ init_tag_groups();
39804
40543
  d3Selection16 = __toESM(require("d3-selection"), 1);
39805
40544
  d3Shape8 = __toESM(require("d3-shape"), 1);
39806
40545
  init_fonts();
@@ -39899,17 +40638,17 @@ function mulberry32(seed) {
39899
40638
  return ((t ^ t >>> 14) >>> 0) / 4294967296;
39900
40639
  };
39901
40640
  }
39902
- function standardNormal(rng3) {
40641
+ function standardNormal(rng4) {
39903
40642
  let u = 0;
39904
40643
  let v = 0;
39905
- while (u === 0) u = rng3();
39906
- while (v === 0) v = rng3();
40644
+ while (u === 0) u = rng4();
40645
+ while (v === 0) v = rng4();
39907
40646
  return Math.sqrt(-2 * Math.log(u)) * Math.cos(2 * Math.PI * v);
39908
40647
  }
39909
- function gamma(shape, rng3) {
40648
+ function gamma(shape, rng4) {
39910
40649
  if (shape < 1) {
39911
- const u = rng3();
39912
- return gamma(shape + 1, rng3) * Math.pow(u, 1 / shape);
40650
+ const u = rng4();
40651
+ return gamma(shape + 1, rng4) * Math.pow(u, 1 / shape);
39913
40652
  }
39914
40653
  const d = shape - 1 / 3;
39915
40654
  const c = 1 / Math.sqrt(9 * d);
@@ -39917,29 +40656,29 @@ function gamma(shape, rng3) {
39917
40656
  let x;
39918
40657
  let v;
39919
40658
  do {
39920
- x = standardNormal(rng3);
40659
+ x = standardNormal(rng4);
39921
40660
  v = 1 + c * x;
39922
40661
  } while (v <= 0);
39923
40662
  v = v * v * v;
39924
- const u = rng3();
40663
+ const u = rng4();
39925
40664
  if (u < 1 - 0.0331 * x * x * x * x) return d * v;
39926
40665
  if (Math.log(u) < x * x / 2 + d * (1 - v + Math.log(v))) return d * v;
39927
40666
  }
39928
40667
  }
39929
- function sampleBeta(alpha, beta, rng3) {
39930
- const x = gamma(alpha, rng3);
39931
- const y = gamma(beta, rng3);
40668
+ function sampleBeta(alpha, beta, rng4) {
40669
+ const x = gamma(alpha, rng4);
40670
+ const y = gamma(beta, rng4);
39932
40671
  return x / (x + y);
39933
40672
  }
39934
- function sampleBetaPert(o, m, p, rng3) {
40673
+ function sampleBetaPert(o, m, p, rng4) {
39935
40674
  if (o === p) return m;
39936
40675
  const range = p - o;
39937
40676
  const alpha = 1 + 4 * (m - o) / range;
39938
40677
  const beta = 1 + 4 * (p - m) / range;
39939
- return o + sampleBeta(alpha, beta, rng3) * range;
40678
+ return o + sampleBeta(alpha, beta, rng4) * range;
39940
40679
  }
39941
40680
  function simulate(resolved, expanded, _predecessors, _successors, topo, terminals, poisoned, opts) {
39942
- const rng3 = mulberry32(opts.seed);
40681
+ const rng4 = mulberry32(opts.seed);
39943
40682
  const expById = /* @__PURE__ */ new Map();
39944
40683
  for (const e of expanded) expById.set(e.id, e);
39945
40684
  const completionTimes = new Array(opts.trials);
@@ -39962,7 +40701,7 @@ function simulate(resolved, expanded, _predecessors, _successors, topo, terminal
39962
40701
  } else if (exp.o === exp.p) {
39963
40702
  duration = exp.m;
39964
40703
  } else {
39965
- duration = sampleBetaPert(exp.o, exp.m, exp.p, rng3);
40704
+ duration = sampleBetaPert(exp.o, exp.m, exp.p, rng4);
39966
40705
  }
39967
40706
  let esLower = 0;
39968
40707
  let efLower = -Infinity;
@@ -44247,7 +44986,7 @@ function renderGantt(container, resolved, palette, isDark, options, exportDims)
44247
44986
  const ctx = exportDims ? ScaleContext.identity() : ScaleContext.from(containerWidth, idealWidth);
44248
44987
  const sBarH = ctx.structural(BAR_H);
44249
44988
  const sRowGap = ctx.structural(ROW_GAP);
44250
- const sGroupGap = ctx.structural(GROUP_GAP2);
44989
+ const sGroupGap = ctx.structural(GROUP_GAP3);
44251
44990
  const sMilestoneSize = ctx.structural(MILESTONE_SIZE);
44252
44991
  const sBottomMargin = ctx.aesthetic(BOTTOM_MARGIN);
44253
44992
  const sRightMargin = ctx.aesthetic(RIGHT_MARGIN);
@@ -44419,7 +45158,7 @@ function renderGantt(container, resolved, palette, isDark, options, exportDims)
44419
45158
  (tg) => tg.name.toLowerCase() === currentSwimlaneGroup.toLowerCase()
44420
45159
  );
44421
45160
  if (tagGroup) {
44422
- const tagKey = tagGroup.name.toLowerCase();
45161
+ const tagKey = tagAttrKey(tagGroup.name);
44423
45162
  for (const rt of resolved.tasks) {
44424
45163
  let value = rt.effectiveMetadata[tagKey];
44425
45164
  if (!value && tagGroup.defaultValue) value = tagGroup.defaultValue;
@@ -45160,7 +45899,7 @@ function renderTagLegend(svg, chartG, tagGroups, activeGroupName, chartLeftMargi
45160
45899
  const usedValues = /* @__PURE__ */ new Map();
45161
45900
  if (resolvedTasks) {
45162
45901
  for (const group of visibleGroups) {
45163
- const key = group.name.toLowerCase();
45902
+ const key = tagAttrKey(group.name);
45164
45903
  const used = /* @__PURE__ */ new Set();
45165
45904
  for (const rt of resolvedTasks) {
45166
45905
  const val = rt.effectiveMetadata[key];
@@ -45171,7 +45910,7 @@ function renderTagLegend(svg, chartG, tagGroups, activeGroupName, chartLeftMargi
45171
45910
  }
45172
45911
  const filteredEntries = /* @__PURE__ */ new Map();
45173
45912
  for (const group of visibleGroups) {
45174
- const key = group.name.toLowerCase();
45913
+ const key = tagAttrKey(group.name);
45175
45914
  const used = usedValues.get(key);
45176
45915
  if (used && used.size > 0) {
45177
45916
  filteredEntries.set(
@@ -45192,7 +45931,7 @@ function renderTagLegend(svg, chartG, tagGroups, activeGroupName, chartLeftMargi
45192
45931
  const pillW = measureLegendText(group.name, LEGEND_PILL_FONT_SIZE) + LEGEND_PILL_PAD + iconReserve;
45193
45932
  let groupW = pillW;
45194
45933
  if (isActive) {
45195
- const entries = filteredEntries.get(group.name.toLowerCase()) ?? group.entries;
45934
+ const entries = filteredEntries.get(tagAttrKey(group.name)) ?? group.entries;
45196
45935
  let entriesW = 0;
45197
45936
  for (const entry of entries) {
45198
45937
  entriesW += LEGEND_DOT_R * 2 + LEGEND_ENTRY_DOT_GAP + measureLegendText(entry.value, LEGEND_ENTRY_FONT_SIZE) + LEGEND_ENTRY_TRAIL;
@@ -45249,7 +45988,7 @@ function renderTagLegend(svg, chartG, tagGroups, activeGroupName, chartLeftMargi
45249
45988
  },
45250
45989
  ...onControlsToggle !== void 0 && { onControlsToggle },
45251
45990
  onEntryHover: (groupName, entryValue) => {
45252
- const tagKey = groupName.toLowerCase();
45991
+ const tagKey = tagAttrKey(groupName);
45253
45992
  if (entryValue) {
45254
45993
  const ev = entryValue.toLowerCase();
45255
45994
  chartG.selectAll(".gantt-task").each(function() {
@@ -45939,7 +46678,7 @@ function buildTagLaneRowList(resolved, swimlaneGroup, collapsedLanes) {
45939
46678
  (g) => g.name.toLowerCase() === swimlaneGroup.toLowerCase()
45940
46679
  );
45941
46680
  if (!tagGroup) return null;
45942
- const tagKey = tagGroup.name.toLowerCase();
46681
+ const tagKey = tagAttrKey(tagGroup.name);
45943
46682
  const rows = [];
45944
46683
  const buckets = /* @__PURE__ */ new Map();
45945
46684
  const unbucketed = [];
@@ -46123,10 +46862,11 @@ function renderTimeScaleHorizontal(g, scale, _innerWidth, innerHeight, textColor
46123
46862
  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);
46124
46863
  }
46125
46864
  }
46126
- var d3Scale5, d3Selection18, 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;
46865
+ var d3Scale5, d3Selection18, 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;
46127
46866
  var init_renderer12 = __esm({
46128
46867
  "src/gantt/renderer.ts"() {
46129
46868
  "use strict";
46869
+ init_tag_groups();
46130
46870
  d3Scale5 = __toESM(require("d3-scale"), 1);
46131
46871
  d3Selection18 = __toESM(require("d3-selection"), 1);
46132
46872
  init_fonts();
@@ -46143,7 +46883,7 @@ var init_renderer12 = __esm({
46143
46883
  init_title_constants();
46144
46884
  BAR_H = 22;
46145
46885
  ROW_GAP = 6;
46146
- GROUP_GAP2 = 14;
46886
+ GROUP_GAP3 = 14;
46147
46887
  MILESTONE_SIZE = 10;
46148
46888
  MIN_LEFT_MARGIN = 120;
46149
46889
  BOTTOM_MARGIN = 40;
@@ -48216,7 +48956,7 @@ function renderJourneyMap(container, parsed, palette, isDark, options) {
48216
48956
  const panelWidth = 280;
48217
48957
  const textAreaWidth = panelWidth - silhouetteZone - CARD_PADDING_X3;
48218
48958
  const descLineH = 14;
48219
- const descLines = parsed.persona.description ? wrapText2(parsed.persona.description, textAreaWidth, FONT_SIZE_META) : [];
48959
+ const descLines = parsed.persona.description ? wrapNoteBody(parsed.persona.description, textAreaWidth, FONT_SIZE_META) : [];
48220
48960
  const descRowH = descLines.length > 0 ? descLines.length * descLineH + 8 : 0;
48221
48961
  const panelHeight = titleRowH + descRowH;
48222
48962
  const panelX = layout.totalWidth - PADDING2 - panelWidth;
@@ -48241,8 +48981,14 @@ function renderJourneyMap(container, parsed, palette, isDark, options) {
48241
48981
  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);
48242
48982
  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);
48243
48983
  for (let li = 0; li < descLines.length; li++) {
48244
- const lineEl = personaG.append("text").attr("x", textX).attr("y", panelY + titleRowH + descLineH * (li + 1)).attr("font-size", FONT_SIZE_META).attr("fill", onPersonaText);
48245
- renderInlineText(lineEl, descLines[li], palette, FONT_SIZE_META);
48984
+ const line11 = descLines[li];
48985
+ const indent = line11.kind === "plain" ? 0 : NOTE_BULLET_INDENT;
48986
+ const lineY = panelY + titleRowH + descLineH * (li + 1);
48987
+ if (line11.kind === "bullet-first") {
48988
+ personaG.append("text").attr("x", textX).attr("y", lineY).attr("font-size", FONT_SIZE_META).attr("fill", onPersonaText).text("\u2022");
48989
+ }
48990
+ const lineEl = personaG.append("text").attr("x", textX + indent).attr("y", lineY).attr("font-size", FONT_SIZE_META).attr("fill", onPersonaText);
48991
+ renderInlineText(lineEl, line11.text, palette, FONT_SIZE_META);
48246
48992
  }
48247
48993
  if (onNavigateToLine) {
48248
48994
  personaG.style("cursor", "pointer").on("click", () => {
@@ -48281,7 +49027,7 @@ function renderJourneyMap(container, parsed, palette, isDark, options) {
48281
49027
  return;
48282
49028
  }
48283
49029
  const isScore = groupName === "Score";
48284
- const attrName = isScore ? "data-score" : `data-tag-${groupName.toLowerCase()}`;
49030
+ const attrName = isScore ? "data-score" : `data-tag-${tagAttrKey(groupName)}`;
48285
49031
  const matches = (el) => el.getAttribute(attrName) === entryValue;
48286
49032
  svg.selectAll(".journey-step").each(function() {
48287
49033
  const hit = matches(this);
@@ -48762,7 +49508,7 @@ function resolveStepColor(step, scoreColor, activeGroup, tagGroups, _palette) {
48762
49508
  (g) => g.name.toLowerCase() === activeGroup.toLowerCase()
48763
49509
  );
48764
49510
  if (!group) return void 0;
48765
- const tagValue = step.tags[group.name.toLowerCase()];
49511
+ const tagValue = step.tags[tagAttrKey(group.name)];
48766
49512
  if (!tagValue) return void 0;
48767
49513
  const entry = group.entries.find(
48768
49514
  (e) => e.value.toLowerCase() === tagValue.toLowerCase()
@@ -48977,6 +49723,7 @@ var d3, d3Shape10, DIAGRAM_PADDING13, PADDING2, CARD_PADDING_X3, CARD_PADDING_Y3
48977
49723
  var init_renderer14 = __esm({
48978
49724
  "src/journey-map/renderer.ts"() {
48979
49725
  "use strict";
49726
+ init_tag_groups();
48980
49727
  d3 = __toESM(require("d3-selection"), 1);
48981
49728
  d3Shape10 = __toESM(require("d3-shape"), 1);
48982
49729
  init_color_utils();
@@ -48984,6 +49731,7 @@ var init_renderer14 = __esm({
48984
49731
  init_parser23();
48985
49732
  init_layout13();
48986
49733
  init_inline_markdown();
49734
+ init_note_box();
48987
49735
  init_legend_integration();
48988
49736
  init_tag_groups();
48989
49737
  init_scaling();
@@ -51807,7 +52555,7 @@ function layoutMap(resolvedIn, data, size, opts) {
51807
52555
  (g) => g.name.toLowerCase() === groupName.toLowerCase()
51808
52556
  );
51809
52557
  if (!group) return null;
51810
- const val = tags[group.name.toLowerCase()] ?? group.defaultValue;
52558
+ const val = tags[tagAttrKey(group.name)] ?? group.defaultValue;
51811
52559
  if (!val) return null;
51812
52560
  const entry = group.entries.find(
51813
52561
  (e) => e.value.toLowerCase() === val.toLowerCase()
@@ -52421,7 +53169,7 @@ function layoutMap(resolvedIn, data, size, opts) {
52421
53169
  if (directHex)
52422
53170
  return { fill: directHex, stroke: mix(directHex, palette.text, 18) };
52423
53171
  for (const group of resolved.tagGroups) {
52424
- const val = p.tags[group.name.toLowerCase()];
53172
+ const val = p.tags[tagAttrKey(group.name)];
52425
53173
  if (!val) continue;
52426
53174
  const entry = group.entries.find(
52427
53175
  (e) => e.value.toLowerCase() === val.toLowerCase()
@@ -52436,7 +53184,7 @@ function layoutMap(resolvedIn, data, size, opts) {
52436
53184
  };
52437
53185
  const lineColor = (tags) => {
52438
53186
  for (const group of resolved.tagGroups) {
52439
- const val = tags[group.name.toLowerCase()];
53187
+ const val = tags[tagAttrKey(group.name)];
52440
53188
  if (!val) continue;
52441
53189
  const entry = group.entries.find(
52442
53190
  (e) => e.value.toLowerCase() === val.toLowerCase()
@@ -53702,6 +54450,7 @@ var import_d3_geo2, import_topojson_client2, FIT_PAD, PATH_DIGITS, THIN_TOL, ALB
53702
54450
  var init_layout15 = __esm({
53703
54451
  "src/map/layout.ts"() {
53704
54452
  "use strict";
54453
+ init_tag_groups();
53705
54454
  import_d3_geo2 = require("d3-geo");
53706
54455
  import_topojson_client2 = require("topojson-client");
53707
54456
  init_color_utils();
@@ -56118,7 +56867,7 @@ function resolveSequenceTags(parsed, activeTagGroup) {
56118
56867
  }
56119
56868
  return result;
56120
56869
  }
56121
- const groupKey = group.name.toLowerCase();
56870
+ const groupKey = tagAttrKey(group.name);
56122
56871
  const participantMeta = /* @__PURE__ */ new Map();
56123
56872
  for (const p of parsed.participants) {
56124
56873
  participantMeta.set(p.id, { ...p.metadata || {} });
@@ -56154,6 +56903,7 @@ function resolveSequenceTags(parsed, activeTagGroup) {
56154
56903
  var init_tag_resolution = __esm({
56155
56904
  "src/sequence/tag-resolution.ts"() {
56156
56905
  "use strict";
56906
+ init_tag_groups();
56157
56907
  }
56158
56908
  });
56159
56909
 
@@ -56628,7 +57378,7 @@ function renderSequenceDiagram(container, parsed, palette, isDark, _onNavigateTo
56628
57378
  }
56629
57379
  }
56630
57380
  const getTagColor = (value) => value ? tagValueToColor.get(value.toLowerCase()) : void 0;
56631
- const tagKey = activeTagGroup?.toLowerCase();
57381
+ const tagKey = activeTagGroup ? tagAttrKey(activeTagGroup) : void 0;
56632
57382
  const hiddenMsgIndices = /* @__PURE__ */ new Set();
56633
57383
  if (collapsedSections && collapsedSections.size > 0) {
56634
57384
  const sectionGroups = groupMessagesBySection(elements, messages);
@@ -57220,11 +57970,7 @@ function renderSequenceDiagram(container, parsed, palette, isDark, _onNavigateTo
57220
57970
  const boxH = sBoxH + GROUP_PADDING_TOP2 + GROUP_PADDING_BOTTOM2;
57221
57971
  const groupTagValue = tagKey && group.metadata?.[tagKey];
57222
57972
  const groupTagColor = getTagColor(groupTagValue || void 0);
57223
- const fillColor = groupTagColor ? mix(
57224
- groupTagColor,
57225
- themeBaseBg(palette, isDark),
57226
- isDark ? 15 : 20
57227
- ) : isDark ? palette.surface : palette.bg;
57973
+ const fillColor = groupTagColor ? mix(groupTagColor, themeBaseBg(palette, isDark), isDark ? 15 : 20) : isDark ? palette.surface : palette.bg;
57228
57974
  const strokeColor = groupTagColor || palette.textMuted;
57229
57975
  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");
57230
57976
  groupG.append("title").text("Click to collapse");
@@ -57274,11 +58020,7 @@ function renderSequenceDiagram(container, parsed, palette, isDark, _onNavigateTo
57274
58020
  );
57275
58021
  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");
57276
58022
  participantG.append("title").text("Click to expand");
57277
- const pFill = effectiveTagColor ? mix(
57278
- effectiveTagColor,
57279
- themeBaseBg(palette, isDark),
57280
- isDark ? 30 : 40
57281
- ) : isDark ? mix(palette.overlay, palette.surface, 50) : mix(palette.bg, palette.surface, 50);
58023
+ const pFill = effectiveTagColor ? mix(effectiveTagColor, themeBaseBg(palette, isDark), isDark ? 30 : 40) : isDark ? mix(palette.overlay, palette.surface, 50) : mix(palette.bg, palette.surface, 50);
57282
58024
  const pStroke = effectiveTagColor || palette.border;
57283
58025
  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);
57284
58026
  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);
@@ -57774,6 +58516,7 @@ var d3Selection27, PARTICIPANT_GAP, PARTICIPANT_BOX_WIDTH, PARTICIPANT_BOX_HEIGH
57774
58516
  var init_renderer20 = __esm({
57775
58517
  "src/sequence/renderer.ts"() {
57776
58518
  "use strict";
58519
+ init_tag_groups();
57777
58520
  d3Selection27 = __toESM(require("d3-selection"), 1);
57778
58521
  init_color_utils();
57779
58522
  init_inline_markdown();
@@ -58293,6 +59036,7 @@ __export(index_exports, {
58293
59036
  palettes: () => palettes,
58294
59037
  render: () => render2,
58295
59038
  resolvePaletteOrFallback: () => resolvePaletteOrFallback,
59039
+ searchMapLocations: () => searchMapLocations,
58296
59040
  themes: () => themes,
58297
59041
  validate: () => parseDgmo
58298
59042
  });
@@ -58950,6 +59694,7 @@ function renderArcDiagram(container, parsed, palette, _isDark, onClickItem, expo
58950
59694
  init_parser3();
58951
59695
 
58952
59696
  // src/timeline/renderer.ts
59697
+ init_tag_groups();
58953
59698
  var d3Scale3 = __toESM(require("d3-scale"), 1);
58954
59699
  var d3Selection3 = __toESM(require("d3-selection"), 1);
58955
59700
  init_fonts();
@@ -59214,7 +59959,7 @@ function setupTimeline(container, parsed, palette, isDark, exportDims, activeTag
59214
59959
  });
59215
59960
  let tagLanes = null;
59216
59961
  if (resolvedSwimlaneTG) {
59217
- const tagKey = resolvedSwimlaneTG.toLowerCase();
59962
+ const tagKey = tagAttrKey(resolvedSwimlaneTG);
59218
59963
  const tagGroup = parsed.timelineTagGroups.find(
59219
59964
  (g) => g.name.toLowerCase() === tagKey
59220
59965
  );
@@ -59520,7 +60265,7 @@ function renderTimelineTagLegendOverlay(container, parsed, palette, isDark, setu
59520
60265
  onTagStateChange?.(currentActiveGroup, currentSwimlaneGroup);
59521
60266
  },
59522
60267
  onEntryHover: (groupName, entryValue) => {
59523
- const tagKey = groupName.toLowerCase();
60268
+ const tagKey = tagAttrKey(groupName);
59524
60269
  if (entryValue) {
59525
60270
  const tagVal = entryValue.toLowerCase();
59526
60271
  fadeToTagValue(mainG, tagKey, tagVal);
@@ -59539,7 +60284,7 @@ function renderTimelineTagLegendOverlay(container, parsed, palette, isDark, setu
59539
60284
  }
59540
60285
  },
59541
60286
  onGroupRendered: (groupName, groupEl, isActive) => {
59542
- const groupKey = groupName.toLowerCase();
60287
+ const groupKey = tagAttrKey(groupName);
59543
60288
  groupEl.attr("data-tag-group", groupKey);
59544
60289
  if (isActive && !viewMode) {
59545
60290
  const isSwimActive = currentSwimlaneGroup?.toLowerCase() === groupKey;
@@ -62957,6 +63702,71 @@ function completeMapPlaces(query, gazetteer, opts) {
62957
63702
  });
62958
63703
  return [...cityItems, ...airportItems].slice(0, limit);
62959
63704
  }
63705
+ function searchMapLocations(query, gazetteer, opts) {
63706
+ const q = fold2(query);
63707
+ if (!q) return [];
63708
+ const limit = opts?.limit ?? 20;
63709
+ const rankOf = (hay) => hay === q ? 0 : hay.startsWith(q) ? 1 : hay.includes(q) ? 2 : -1;
63710
+ const cityRank = /* @__PURE__ */ new Map();
63711
+ const consider = (idx, rank) => {
63712
+ if (rank < 0) return;
63713
+ const prev = cityRank.get(idx);
63714
+ if (prev === void 0 || rank < prev) cityRank.set(idx, rank);
63715
+ };
63716
+ for (const [key, list] of Object.entries(gazetteer.byName)) {
63717
+ const r = rankOf(key);
63718
+ if (r >= 0) for (const i of list) consider(i, r);
63719
+ }
63720
+ for (const [key, idx] of Object.entries(gazetteer.alt)) {
63721
+ consider(idx, rankOf(key));
63722
+ }
63723
+ const cityItems = [...cityRank].filter(([i]) => gazetteer.cities[i] !== void 0).map(([i, rank]) => {
63724
+ const c = gazetteer.cities[i];
63725
+ const [, , iso, pop, name, sub] = c;
63726
+ const ambiguous = (gazetteer.byName[fold2(name)]?.length ?? 0) > 1;
63727
+ const qualifier = sub ?? iso;
63728
+ return {
63729
+ name,
63730
+ token: ambiguous ? `${name} ${qualifier}` : name,
63731
+ kind: "city",
63732
+ iso,
63733
+ ...sub !== void 0 && { sub },
63734
+ pop,
63735
+ detail: `${qualifier} \xB7 ${groupThousands(pop)}`,
63736
+ _rank: rank
63737
+ };
63738
+ });
63739
+ const airports = opts?.airports;
63740
+ const airportItems = [];
63741
+ if (airports?.airportIata) {
63742
+ for (const [code, idx] of Object.entries(airports.airportIata)) {
63743
+ const a = airports.airports[idx];
63744
+ if (!a) continue;
63745
+ const codeRank = rankOf(code);
63746
+ const nameRank = rankOf(fold2(a[4]));
63747
+ const rank = Math.min(
63748
+ codeRank < 0 ? 99 : codeRank,
63749
+ nameRank < 0 ? 99 : nameRank
63750
+ );
63751
+ if (rank > 2) continue;
63752
+ airportItems.push({
63753
+ name: a[4],
63754
+ token: code.toUpperCase(),
63755
+ kind: "airport",
63756
+ iso: a[2],
63757
+ pop: 0,
63758
+ detail: `Airport \xB7 ${a[4]}`,
63759
+ _rank: rank
63760
+ });
63761
+ }
63762
+ }
63763
+ return [...cityItems, ...airportItems].sort(
63764
+ (x, y) => x._rank - y._rank || // best match tier first
63765
+ (x.kind === y.kind ? 0 : x.kind === "city" ? -1 : 1) || // city before airport
63766
+ y.pop - x.pop || // populous city first
63767
+ (x.token < y.token ? -1 : x.token > y.token ? 1 : 0)
63768
+ ).slice(0, limit).map(({ _rank, ...rest }) => rest);
63769
+ }
62960
63770
  function completeMapRegions(query, regions, opts) {
62961
63771
  const q = fold2(query);
62962
63772
  if (!q) return [];
@@ -63051,6 +63861,7 @@ function decodeDiagramUrl2(url) {
63051
63861
  palettes,
63052
63862
  render,
63053
63863
  resolvePaletteOrFallback,
63864
+ searchMapLocations,
63054
63865
  themes,
63055
63866
  validate
63056
63867
  });