@diagrammo/dgmo 0.32.1 → 0.34.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 (60) 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 +1434 -443
  6. package/dist/advanced.d.cts +41 -1
  7. package/dist/advanced.d.ts +41 -1
  8. package/dist/advanced.js +1433 -443
  9. package/dist/auto.cjs +1242 -318
  10. package/dist/auto.js +91 -91
  11. package/dist/auto.mjs +1242 -318
  12. package/dist/cli.cjs +138 -136
  13. package/dist/index.cjs +1308 -317
  14. package/dist/index.d.cts +35 -1
  15. package/dist/index.d.ts +35 -1
  16. package/dist/index.js +1307 -317
  17. package/docs/ai-integration.md +7 -2
  18. package/docs/language-reference.md +39 -8
  19. package/package.json +2 -2
  20. package/src/advanced.ts +6 -1
  21. package/src/boxes-and-lines/label-placement.ts +339 -0
  22. package/src/boxes-and-lines/layout-grouped.ts +909 -0
  23. package/src/boxes-and-lines/layout-search.ts +34 -3
  24. package/src/boxes-and-lines/layout.ts +32 -7
  25. package/src/boxes-and-lines/parser.ts +3 -2
  26. package/src/boxes-and-lines/renderer.ts +67 -139
  27. package/src/c4/layout.ts +2 -1
  28. package/src/c4/parser.ts +3 -2
  29. package/src/c4/renderer.ts +3 -2
  30. package/src/cli.ts +68 -0
  31. package/src/er/parser.ts +2 -1
  32. package/src/er/renderer.ts +2 -1
  33. package/src/gantt/parser.ts +3 -5
  34. package/src/gantt/renderer.ts +7 -6
  35. package/src/index.ts +6 -1
  36. package/src/infra/parser.ts +2 -1
  37. package/src/infra/renderer.ts +4 -3
  38. package/src/journey-map/parser.ts +27 -8
  39. package/src/journey-map/renderer.ts +26 -9
  40. package/src/kanban/parser.ts +4 -3
  41. package/src/kanban/renderer.ts +6 -5
  42. package/src/map/completion.ts +116 -0
  43. package/src/map/layout.ts +4 -3
  44. package/src/map/parser.ts +3 -2
  45. package/src/mindmap/parser.ts +4 -3
  46. package/src/mindmap/renderer.ts +2 -1
  47. package/src/org/layout.ts +3 -2
  48. package/src/org/parser.ts +3 -2
  49. package/src/org/renderer.ts +5 -4
  50. package/src/pert/parser.ts +3 -2
  51. package/src/sequence/renderer.ts +10 -12
  52. package/src/sequence/tag-resolution.ts +2 -1
  53. package/src/sitemap/layout.ts +3 -2
  54. package/src/sitemap/parser.ts +36 -2
  55. package/src/sitemap/renderer.ts +12 -6
  56. package/src/timeline/renderer.ts +4 -3
  57. package/src/utils/legend-d3.ts +4 -3
  58. package/src/utils/legend-svg.ts +3 -2
  59. package/src/utils/tag-groups.ts +28 -7
  60. package/src/visualizations/parse.ts +2 -1
package/dist/advanced.js CHANGED
@@ -1253,6 +1253,9 @@ var init_parsing = __esm({
1253
1253
  });
1254
1254
 
1255
1255
  // src/utils/tag-groups.ts
1256
+ function tagAttrKey(name) {
1257
+ return name.trim().toLowerCase().replace(/[^a-z0-9_-]+/g, "-").replace(/^-+|-+$/g, "");
1258
+ }
1256
1259
  function stripDefaultModifier(text) {
1257
1260
  if (/\bdefault\s*$/.test(text)) {
1258
1261
  return { text: text.replace(/\s+default\s*$/, "").trim(), isDefault: true };
@@ -1398,17 +1401,17 @@ function parseTagDeclaration(line11) {
1398
1401
  function resolveTagColor(metadata, tagGroups, activeGroupName, isContainer) {
1399
1402
  if (!activeGroupName) return void 0;
1400
1403
  const group = tagGroups.find(
1401
- (g) => g.name.toLowerCase() === activeGroupName.toLowerCase()
1404
+ (g) => tagAttrKey(g.name) === tagAttrKey(activeGroupName)
1402
1405
  );
1403
1406
  if (!group) return void 0;
1404
- const metaValue = metadata[group.name.toLowerCase()] ?? (isContainer ? void 0 : group.defaultValue);
1407
+ const metaValue = metadata[tagAttrKey(group.name)] ?? (isContainer ? void 0 : group.defaultValue);
1405
1408
  if (!metaValue) return "#999999";
1406
1409
  return group.entries.find((e) => e.value.toLowerCase() === metaValue.toLowerCase())?.color ?? "#999999";
1407
1410
  }
1408
1411
  function validateTagValues(entities, tagGroups, pushWarning, suggestFn) {
1409
1412
  if (tagGroups.length === 0) return;
1410
1413
  const groupMap = /* @__PURE__ */ new Map();
1411
- for (const g of tagGroups) groupMap.set(g.name.toLowerCase(), g);
1414
+ for (const g of tagGroups) groupMap.set(tagAttrKey(g.name), g);
1412
1415
  for (const entity of entities) {
1413
1416
  for (const [key, value] of Object.entries(entity.metadata)) {
1414
1417
  const group = groupMap.get(key);
@@ -1445,10 +1448,10 @@ function validateTagGroupNames(tagGroups, pushWarning, pushError) {
1445
1448
  `'none' is a reserved keyword and cannot be used as a tag group name`
1446
1449
  );
1447
1450
  }
1448
- if (!VALID_TAG_IDENT_RE.test(group.name)) {
1451
+ if (!VALID_TAG_IDENT_RE.test(tagAttrKey(group.name))) {
1449
1452
  report(
1450
1453
  group.lineNumber,
1451
- `Tag group name "${group.name}" contains invalid characters \u2014 use a single identifier (letters, digits, underscore, hyphen)`
1454
+ `Tag group name "${group.name}" can't form a valid key \u2014 use letters, digits, underscore, or hyphen, and quote a multi-word name (e.g. tag "Trust Zone" as tz)`
1452
1455
  );
1453
1456
  }
1454
1457
  if (group.alias != null && !VALID_TAG_IDENT_RE.test(group.alias)) {
@@ -1460,7 +1463,7 @@ function validateTagGroupNames(tagGroups, pushWarning, pushError) {
1460
1463
  }
1461
1464
  }
1462
1465
  function cascadeTagMetadata(roots, tagGroups) {
1463
- const keys = tagGroups.map((g) => g.name.toLowerCase());
1466
+ const keys = tagGroups.map((g) => tagAttrKey(g.name));
1464
1467
  if (keys.length === 0) return;
1465
1468
  const walk = (node, inherited) => {
1466
1469
  const childInherited = { ...inherited };
@@ -1481,7 +1484,7 @@ function injectDefaultTagMetadata(entities, tagGroups, skip) {
1481
1484
  for (const group of tagGroups) {
1482
1485
  if (group.defaultValue) {
1483
1486
  defaults.push({
1484
- key: group.name.toLowerCase(),
1487
+ key: tagAttrKey(group.name),
1485
1488
  value: group.defaultValue
1486
1489
  });
1487
1490
  }
@@ -2872,7 +2875,7 @@ function parseVisualizationFull(content, palette) {
2872
2875
  );
2873
2876
  for (const group of result.timelineTagGroups) {
2874
2877
  if (!group.defaultValue) continue;
2875
- const key = group.name.toLowerCase();
2878
+ const key = tagAttrKey(group.name);
2876
2879
  for (const event of result.timelineEvents) {
2877
2880
  if (!event.metadata[key]) {
2878
2881
  event.metadata[key] = group.defaultValue;
@@ -5268,7 +5271,7 @@ function renderLegendD3(container, config, state, palette, isDark, callbacks, co
5268
5271
  };
5269
5272
  }
5270
5273
  function renderCapsule(parent, capsule, palette, groupBg, pillBorder, isDark, callbacks) {
5271
- const g = parent.append("g").attr("transform", `translate(${capsule.x},${capsule.y})`).attr("data-legend-group", capsule.groupName.toLowerCase()).style("cursor", "pointer");
5274
+ const g = parent.append("g").attr("transform", `translate(${capsule.x},${capsule.y})`).attr("data-legend-group", tagAttrKey(capsule.groupName)).style("cursor", "pointer");
5272
5275
  g.append("rect").attr("width", capsule.width).attr("height", capsule.height).attr("rx", LEGEND_HEIGHT / 2).attr("fill", groupBg);
5273
5276
  const pill = capsule.pill;
5274
5277
  g.append("rect").attr("x", pill.x).attr("y", pill.y).attr("width", pill.width).attr("height", pill.height).attr("rx", pill.height / 2).attr("fill", palette.bg);
@@ -5276,7 +5279,7 @@ function renderCapsule(parent, capsule, palette, groupBg, pillBorder, isDark, ca
5276
5279
  g.append("text").attr("x", pill.x + pill.width / 2).attr("y", LEGEND_HEIGHT / 2).attr("text-anchor", "middle").call(centerText).attr("font-size", LEGEND_PILL_FONT_SIZE).attr("font-weight", 500).attr("fill", palette.text).attr("pointer-events", "none").attr("font-family", FONT_FAMILY).text(capsule.groupName);
5277
5280
  if (capsule.gradient) {
5278
5281
  const gr = capsule.gradient;
5279
- const gradId = `dgmo-legend-ramp-${capsule.groupName.toLowerCase().replace(/[^a-z0-9]+/g, "-")}`;
5282
+ const gradId = `dgmo-legend-ramp-${tagAttrKey(capsule.groupName).replace(/[^a-z0-9]+/g, "-")}`;
5280
5283
  const def = g.append("defs").append("linearGradient").attr("id", gradId);
5281
5284
  for (const stop of valueRampStops(gr.low, gr.high, { isDark })) {
5282
5285
  def.append("stop").attr("offset", `${stop.offset * 100}%`).attr("stop-color", stop.color);
@@ -5306,7 +5309,7 @@ function renderCapsule(parent, capsule, palette, groupBg, pillBorder, isDark, ca
5306
5309
  }
5307
5310
  }
5308
5311
  function renderPill(parent, pill, palette, groupBg, callbacks) {
5309
- const g = parent.append("g").attr("transform", `translate(${pill.x},${pill.y})`).attr("data-legend-group", pill.groupName.toLowerCase()).style("cursor", "pointer");
5312
+ const g = parent.append("g").attr("transform", `translate(${pill.x},${pill.y})`).attr("data-legend-group", tagAttrKey(pill.groupName)).style("cursor", "pointer");
5310
5313
  g.append("rect").attr("width", pill.width).attr("height", pill.height).attr("rx", pill.height / 2).attr("fill", groupBg);
5311
5314
  g.append("text").attr("x", pill.width / 2).attr("y", pill.height / 2).attr("text-anchor", "middle").call(centerText).attr("font-size", LEGEND_PILL_FONT_SIZE).attr("font-weight", 500).attr("fill", palette.textMuted).attr("pointer-events", "none").attr("font-family", FONT_FAMILY).text(pill.groupName);
5312
5315
  if (callbacks?.onGroupToggle) {
@@ -5412,6 +5415,7 @@ var LEGEND_TEXT_DY;
5412
5415
  var init_legend_d3 = __esm({
5413
5416
  "src/utils/legend-d3.ts"() {
5414
5417
  "use strict";
5418
+ init_tag_groups();
5415
5419
  init_legend_constants();
5416
5420
  init_legend_layout();
5417
5421
  init_color_utils();
@@ -9333,7 +9337,7 @@ function parseERDiagram(content, palette) {
9333
9337
  );
9334
9338
  for (const group of result.tagGroups) {
9335
9339
  if (!group.defaultValue) continue;
9336
- const key = group.name.toLowerCase();
9340
+ const key = tagAttrKey(group.name);
9337
9341
  for (const table of result.tables) {
9338
9342
  if (!table.metadata[key]) {
9339
9343
  const mutableMeta = tableMetadataMap.get(table.id);
@@ -9563,12 +9567,12 @@ function parseOrg(content, palette) {
9563
9567
  if (tagBlockMatch.alias) {
9564
9568
  metaAliasMap.set(
9565
9569
  normalizeName(tagBlockMatch.alias),
9566
- tagBlockMatch.name.toLowerCase()
9570
+ tagAttrKey(tagBlockMatch.name)
9567
9571
  );
9568
9572
  }
9569
9573
  metaAliasMap.set(
9570
9574
  normalizeName(tagBlockMatch.name),
9571
- tagBlockMatch.name.toLowerCase()
9575
+ tagAttrKey(tagBlockMatch.name)
9572
9576
  );
9573
9577
  result.tagGroups.push(currentTagGroup);
9574
9578
  continue;
@@ -9954,12 +9958,12 @@ function parseSitemap(content, palette) {
9954
9958
  if (tagBlockMatch.alias) {
9955
9959
  metaAliasMap.set(
9956
9960
  normalizeName(tagBlockMatch.alias),
9957
- tagBlockMatch.name.toLowerCase()
9961
+ tagAttrKey(tagBlockMatch.name)
9958
9962
  );
9959
9963
  }
9960
9964
  metaAliasMap.set(
9961
9965
  normalizeName(tagBlockMatch.name),
9962
- tagBlockMatch.name.toLowerCase()
9966
+ tagAttrKey(tagBlockMatch.name)
9963
9967
  );
9964
9968
  result.tagGroups.push(newTagGroup);
9965
9969
  continue;
@@ -10129,6 +10133,14 @@ function parseSitemap(content, palette) {
10129
10133
  continue;
10130
10134
  }
10131
10135
  }
10136
+ const enclosingContainer = nestedPageInsideContainer(indent, indentStack);
10137
+ if (enclosingContainer) {
10138
+ pushError(
10139
+ lineNumber,
10140
+ `Pages inside container '[${enclosingContainer}]' cannot have indented sub-pages. Containers hold a flat list of pages.`
10141
+ );
10142
+ continue;
10143
+ }
10132
10144
  const node = parseNodeLabel2(
10133
10145
  trimmed,
10134
10146
  lineNumber,
@@ -10276,6 +10288,16 @@ function attachNode2(node, indent, indentStack, result) {
10276
10288
  }
10277
10289
  indentStack.push({ node, indent });
10278
10290
  }
10291
+ function nestedPageInsideContainer(indent, indentStack) {
10292
+ let idx = indentStack.length - 1;
10293
+ while (idx >= 0 && indentStack[idx].indent >= indent) idx--;
10294
+ if (idx < 0) return null;
10295
+ if (indentStack[idx].node.isContainer) return null;
10296
+ for (let k = idx - 1; k >= 0; k--) {
10297
+ if (indentStack[k].node.isContainer) return indentStack[k].node.label;
10298
+ }
10299
+ return null;
10300
+ }
10279
10301
  function findParentNode(indent, indentStack) {
10280
10302
  for (let i = indentStack.length - 1; i >= 0; i--) {
10281
10303
  if (indentStack[i].indent < indent) {
@@ -10859,12 +10881,12 @@ function parsePert(content, parseOpts = {}) {
10859
10881
  if (tagBlockMatch.alias) {
10860
10882
  metaAliasMap.set(
10861
10883
  tagBlockMatch.alias.toLowerCase(),
10862
- tagBlockMatch.name.toLowerCase()
10884
+ tagAttrKey(tagBlockMatch.name)
10863
10885
  );
10864
10886
  }
10865
10887
  metaAliasMap.set(
10866
10888
  normalizeName(tagBlockMatch.name),
10867
- tagBlockMatch.name.toLowerCase()
10889
+ tagAttrKey(tagBlockMatch.name)
10868
10890
  );
10869
10891
  tagGroups.push(currentTagGroup);
10870
10892
  if (tagBlockMatch.inlineValues) {
@@ -12329,10 +12351,10 @@ function emitCapsule(capsule, palette, groupBg, pillBorder) {
12329
12351
  `<g data-legend-entry="${esc(entry.value.toLowerCase())}" data-series-name="${esc(entry.value)}" style="cursor:pointer"><circle cx="${entry.dotCx}" cy="${entry.dotCy}" r="${LEGEND_DOT_R}" fill="${esc(entry.color)}"/><text x="${entry.textX}" y="${entry.dotCy + LEGEND_ENTRY_FONT_SIZE / 2 - 1}" font-size="${LEGEND_ENTRY_FONT_SIZE}" fill="${esc(palette.textMuted)}" font-family="${esc(FONT_FAMILY)}">${esc(label)}</text></g>`
12330
12352
  );
12331
12353
  }
12332
- return `<g transform="translate(${capsule.x},${capsule.y})" data-legend-group="${esc(capsule.groupName.toLowerCase())}" style="cursor:pointer">${inner.join("")}</g>`;
12354
+ return `<g transform="translate(${capsule.x},${capsule.y})" data-legend-group="${esc(tagAttrKey(capsule.groupName))}" style="cursor:pointer">${inner.join("")}</g>`;
12333
12355
  }
12334
12356
  function emitPill(pill, palette, groupBg) {
12335
- return `<g transform="translate(${pill.x},${pill.y})" data-legend-group="${esc(pill.groupName.toLowerCase())}" style="cursor:pointer"><rect width="${pill.width}" height="${pill.height}" rx="${pill.height / 2}" fill="${esc(groupBg)}"/><text x="${pill.width / 2}" y="${pill.height / 2 + LEGEND_PILL_FONT_SIZE / 2 - 2}" font-size="${LEGEND_PILL_FONT_SIZE}" font-weight="500" fill="${esc(palette.textMuted)}" text-anchor="middle" font-family="${esc(FONT_FAMILY)}">${esc(pill.groupName)}</text></g>`;
12357
+ return `<g transform="translate(${pill.x},${pill.y})" data-legend-group="${esc(tagAttrKey(pill.groupName))}" style="cursor:pointer"><rect width="${pill.width}" height="${pill.height}" rx="${pill.height / 2}" fill="${esc(groupBg)}"/><text x="${pill.width / 2}" y="${pill.height / 2 + LEGEND_PILL_FONT_SIZE / 2 - 2}" font-size="${LEGEND_PILL_FONT_SIZE}" font-weight="500" fill="${esc(palette.textMuted)}" text-anchor="middle" font-family="${esc(FONT_FAMILY)}">${esc(pill.groupName)}</text></g>`;
12336
12358
  }
12337
12359
  function renderLegendSvg(groups, options) {
12338
12360
  if (groups.length === 0) return { svg: "", height: 0, width: 0 };
@@ -12393,6 +12415,7 @@ function renderLegendSvgFromConfig(config, state, palette, containerWidth) {
12393
12415
  var init_legend_svg = __esm({
12394
12416
  "src/utils/legend-svg.ts"() {
12395
12417
  "use strict";
12418
+ init_tag_groups();
12396
12419
  init_legend_constants();
12397
12420
  init_legend_layout();
12398
12421
  init_color_utils();
@@ -15101,12 +15124,12 @@ function parseKanban(content, palette) {
15101
15124
  if (tagBlockMatch.alias) {
15102
15125
  metaAliasMap.set(
15103
15126
  normalizeName(tagBlockMatch.alias),
15104
- tagBlockMatch.name.toLowerCase()
15127
+ tagAttrKey(tagBlockMatch.name)
15105
15128
  );
15106
15129
  }
15107
15130
  metaAliasMap.set(
15108
15131
  normalizeName(tagBlockMatch.name),
15109
- tagBlockMatch.name.toLowerCase()
15132
+ tagAttrKey(tagBlockMatch.name)
15110
15133
  );
15111
15134
  result.tagGroups.push(currentTagGroup);
15112
15135
  continue;
@@ -15295,7 +15318,7 @@ function parseKanban(content, palette) {
15295
15318
  }
15296
15319
  for (const group of result.tagGroups) {
15297
15320
  const values = new Set(group.entries.map((e) => e.value.toLowerCase()));
15298
- tagValueSets.set(group.name.toLowerCase(), values);
15321
+ tagValueSets.set(tagAttrKey(group.name), values);
15299
15322
  }
15300
15323
  for (const col of result.columns) {
15301
15324
  if (col.wipLimit != null && col.cards.length > col.wipLimit) {
@@ -15557,12 +15580,12 @@ function parseC4(content, palette) {
15557
15580
  if (tagBlockMatch.alias) {
15558
15581
  metaAliasMap.set(
15559
15582
  normalizeName(tagBlockMatch.alias),
15560
- tagBlockMatch.name.toLowerCase()
15583
+ tagAttrKey(tagBlockMatch.name)
15561
15584
  );
15562
15585
  }
15563
15586
  metaAliasMap.set(
15564
15587
  normalizeName(tagBlockMatch.name),
15565
- tagBlockMatch.name.toLowerCase()
15588
+ tagAttrKey(tagBlockMatch.name)
15566
15589
  );
15567
15590
  result.tagGroups.push(currentTagGroup);
15568
15591
  continue;
@@ -17245,7 +17268,7 @@ function parseInfra(content) {
17245
17268
  }
17246
17269
  for (const tg of result.tagGroups) {
17247
17270
  if (!tg.defaultValue) continue;
17248
- const key = (tg.alias ?? tg.name).toLowerCase();
17271
+ const key = tagAttrKey(tg.alias ?? tg.name);
17249
17272
  for (const node of result.nodes) {
17250
17273
  if (node.isEdge) continue;
17251
17274
  if (!(key in node.tags)) {
@@ -17382,6 +17405,7 @@ var CONNECTION_RE, SIMPLE_CONNECTION_RE, ASYNC_CONNECTION_RE, ASYNC_SIMPLE_CONNE
17382
17405
  var init_parser15 = __esm({
17383
17406
  "src/infra/parser.ts"() {
17384
17407
  "use strict";
17408
+ init_tag_groups();
17385
17409
  init_diagnostics();
17386
17410
  init_description_helpers();
17387
17411
  init_arrows();
@@ -17927,13 +17951,10 @@ function parseGantt(content, palette) {
17927
17951
  if (tagMatch.alias) {
17928
17952
  metaAliasMap.set(
17929
17953
  normalizeName(tagMatch.alias),
17930
- tagMatch.name.toLowerCase()
17954
+ tagAttrKey(tagMatch.name)
17931
17955
  );
17932
17956
  }
17933
- metaAliasMap.set(
17934
- normalizeName(tagMatch.name),
17935
- tagMatch.name.toLowerCase()
17936
- );
17957
+ metaAliasMap.set(normalizeName(tagMatch.name), tagAttrKey(tagMatch.name));
17937
17958
  continue;
17938
17959
  }
17939
17960
  const topWorkweekMatch = line11.match(WORKWEEK_RE);
@@ -18859,7 +18880,7 @@ function parseMap(content, palette) {
18859
18880
  const aliasMap = /* @__PURE__ */ new Map();
18860
18881
  const open = { tag: null, route: null, poi: null };
18861
18882
  const registry = () => withTagAliases(MAP_REGISTRY, new Set(aliasMap.keys()));
18862
- const tagGroupNames = () => new Set(tagGroups.map((g) => g.name.toLowerCase()));
18883
+ const tagGroupNames = () => new Set(tagGroups.map((g) => tagAttrKey(g.name)));
18863
18884
  const closeBlocks = () => {
18864
18885
  open.tag = null;
18865
18886
  open.route = null;
@@ -19043,7 +19064,7 @@ function parseMap(content, palette) {
19043
19064
  entries: [],
19044
19065
  lineNumber: line11
19045
19066
  };
19046
- if (m.alias) aliasMap.set(m.alias.toLowerCase(), m.name.toLowerCase());
19067
+ if (m.alias) aliasMap.set(m.alias.toLowerCase(), tagAttrKey(m.name));
19047
19068
  tagGroups.push(group);
19048
19069
  open.tag = group;
19049
19070
  if (m.inlineValues?.length) {
@@ -19576,12 +19597,12 @@ function parseBoxesAndLines(content, palette) {
19576
19597
  if (tagBlockMatch.alias) {
19577
19598
  metaAliasMap.set(
19578
19599
  normalizeName(tagBlockMatch.alias),
19579
- tagBlockMatch.name.toLowerCase()
19600
+ tagAttrKey(tagBlockMatch.name)
19580
19601
  );
19581
19602
  }
19582
19603
  metaAliasMap.set(
19583
19604
  normalizeName(tagBlockMatch.name),
19584
- tagBlockMatch.name.toLowerCase()
19605
+ tagAttrKey(tagBlockMatch.name)
19585
19606
  );
19586
19607
  if (tagBlockMatch.inlineValues) {
19587
19608
  for (const rawVal of tagBlockMatch.inlineValues) {
@@ -20322,12 +20343,12 @@ function parseMindmap(content, palette) {
20322
20343
  if (tagBlockMatch.alias) {
20323
20344
  aliasMap.set(
20324
20345
  tagBlockMatch.alias.toLowerCase(),
20325
- tagBlockMatch.name.toLowerCase()
20346
+ tagAttrKey(tagBlockMatch.name)
20326
20347
  );
20327
20348
  }
20328
20349
  aliasMap.set(
20329
- tagBlockMatch.name.toLowerCase(),
20330
- tagBlockMatch.name.toLowerCase()
20350
+ tagAttrKey(tagBlockMatch.name),
20351
+ tagAttrKey(tagBlockMatch.name)
20331
20352
  );
20332
20353
  result.tagGroups.push(currentTagGroup);
20333
20354
  continue;
@@ -22036,7 +22057,18 @@ function parseJourneyMap(content, palette) {
22036
22057
  palette
22037
22058
  ) ?? void 0;
22038
22059
  } else {
22039
- personaName = afterKeyword;
22060
+ const { label, colorName } = peelTrailingColorName(afterKeyword);
22061
+ if (colorName) {
22062
+ personaName = label;
22063
+ personaColor = resolveColorWithDiagnostic(
22064
+ colorName,
22065
+ lineNumber,
22066
+ result.diagnostics,
22067
+ palette
22068
+ ) ?? void 0;
22069
+ } else {
22070
+ personaName = afterKeyword;
22071
+ }
22040
22072
  }
22041
22073
  }
22042
22074
  if (!personaName) {
@@ -22079,12 +22111,12 @@ function parseJourneyMap(content, palette) {
22079
22111
  if (tagBlockMatch.alias) {
22080
22112
  aliasMap.set(
22081
22113
  tagBlockMatch.alias.toLowerCase(),
22082
- tagBlockMatch.name.toLowerCase()
22114
+ tagAttrKey(tagBlockMatch.name)
22083
22115
  );
22084
22116
  }
22085
22117
  aliasMap.set(
22086
- tagBlockMatch.name.toLowerCase(),
22087
- tagBlockMatch.name.toLowerCase()
22118
+ tagAttrKey(tagBlockMatch.name),
22119
+ tagAttrKey(tagBlockMatch.name)
22088
22120
  );
22089
22121
  result.tagGroups.push(currentTagGroup);
22090
22122
  continue;
@@ -22228,7 +22260,7 @@ function parseJourneyMap(content, palette) {
22228
22260
  }
22229
22261
  for (const group of result.tagGroups) {
22230
22262
  const values = new Set(group.entries.map((e) => e.value.toLowerCase()));
22231
- tagValueSets.set(group.name.toLowerCase(), values);
22263
+ tagValueSets.set(tagAttrKey(group.name), values);
22232
22264
  }
22233
22265
  const allSteps = hasPhases ? result.phases.flatMap((p) => p.steps) : result.steps;
22234
22266
  for (const step of allSteps) {
@@ -24215,7 +24247,7 @@ function computeLegendGroups(tagGroups, showEyeIcons, usedValuesByGroup) {
24215
24247
  const groups = [];
24216
24248
  for (const group of tagGroups) {
24217
24249
  if (group.entries.length === 0) continue;
24218
- const usedValues = usedValuesByGroup?.get(group.name.toLowerCase());
24250
+ const usedValues = usedValuesByGroup?.get(tagAttrKey(group.name));
24219
24251
  const visibleEntries = usedValues ? group.entries.filter((e) => usedValues.has(e.value.toLowerCase())) : group.entries;
24220
24252
  if (visibleEntries.length === 0) continue;
24221
24253
  const pillWidth2 = measureLegendText(group.name, LEGEND_PILL_FONT_SIZE) + LEGEND_PILL_PAD;
@@ -24828,7 +24860,7 @@ function layoutOrg(parsed, hiddenCounts, activeTagGroup, hiddenAttributes, expan
24828
24860
  const totalHeight = finalMaxY - minY + MARGIN * 2;
24829
24861
  const usedValuesByGroup = /* @__PURE__ */ new Map();
24830
24862
  for (const group of parsed.tagGroups) {
24831
- const key = group.name.toLowerCase();
24863
+ const key = tagAttrKey(group.name);
24832
24864
  const used = /* @__PURE__ */ new Set();
24833
24865
  const walk = (node) => {
24834
24866
  if (!node.isContainer && node.metadata[key]) {
@@ -24887,6 +24919,7 @@ var init_layout = __esm({
24887
24919
  "src/org/layout.ts"() {
24888
24920
  "use strict";
24889
24921
  init_tag_groups();
24922
+ init_tag_groups();
24890
24923
  init_legend_constants();
24891
24924
  init_text_measure();
24892
24925
  LABEL_FONT_SIZE = 13;
@@ -25247,14 +25280,14 @@ function renderOrg(container, parsed, layout, palette, isDark, onClickItem, expo
25247
25280
  const contentG = mainG.append("g").attr("transform", `translate(0, ${contentYShift})`);
25248
25281
  const displayNames = /* @__PURE__ */ new Map();
25249
25282
  for (const group of parsed.tagGroups) {
25250
- displayNames.set(group.name.toLowerCase(), group.name);
25283
+ displayNames.set(tagAttrKey(group.name), group.name);
25251
25284
  }
25252
25285
  const rootNodeIds = new Set(parsed.roots.map((r) => r.id));
25253
25286
  const colorOff = parsed.options?.["color"] === "off";
25254
25287
  for (const c of layout.containers) {
25255
25288
  const cG = contentG.append("g").attr("transform", `translate(${c.x}, ${c.y})`).attr("class", "org-container").attr("data-line-number", String(c.lineNumber));
25256
25289
  if (activeTagGroup) {
25257
- const tagKey = activeTagGroup.toLowerCase();
25290
+ const tagKey = tagAttrKey(activeTagGroup);
25258
25291
  const metaValue = c.tagMetadata[tagKey];
25259
25292
  if (metaValue) {
25260
25293
  cG.attr(`data-tag-${tagKey}`, metaValue.toLowerCase());
@@ -25336,7 +25369,7 @@ function renderOrg(container, parsed, layout, palette, isDark, onClickItem, expo
25336
25369
  if (containerNodeIds.has(node.id)) continue;
25337
25370
  const nodeG = contentG.append("g").attr("transform", `translate(${node.x - node.width / 2}, ${node.y})`).attr("class", "org-node").attr("data-line-number", String(node.lineNumber));
25338
25371
  if (activeTagGroup) {
25339
- const tagKey = activeTagGroup.toLowerCase();
25372
+ const tagKey = tagAttrKey(activeTagGroup);
25340
25373
  const metaValue = node.tagMetadata[tagKey];
25341
25374
  if (metaValue) {
25342
25375
  nodeG.attr(`data-tag-${tagKey}`, metaValue.toLowerCase());
@@ -25509,7 +25542,7 @@ function renderOrg(container, parsed, layout, palette, isDark, onClickItem, expo
25509
25542
  const computedLayout = legendHandle.getLayout();
25510
25543
  if (computedLayout.activeCapsule?.addonX != null) {
25511
25544
  const capsule = computedLayout.activeCapsule;
25512
- const groupKey2 = capsule.groupName.toLowerCase();
25545
+ const groupKey2 = tagAttrKey(capsule.groupName);
25513
25546
  const isHidden = hiddenAttributes?.has(groupKey2) ?? false;
25514
25547
  const activeGroupEl = legendParentBase.select(
25515
25548
  `[data-legend-group="${groupKey2}"]`
@@ -25548,6 +25581,7 @@ var DIAGRAM_PADDING, MAX_SCALE, TITLE_HEIGHT, ANCESTOR_DOT_R, ANCESTOR_LABEL_FON
25548
25581
  var init_renderer = __esm({
25549
25582
  "src/org/renderer.ts"() {
25550
25583
  "use strict";
25584
+ init_tag_groups();
25551
25585
  init_fonts();
25552
25586
  init_export_container();
25553
25587
  init_scaling();
@@ -25633,7 +25667,7 @@ function computeLegendGroups2(tagGroups, usedValuesByGroup) {
25633
25667
  const groups = [];
25634
25668
  for (const group of tagGroups) {
25635
25669
  if (group.entries.length === 0) continue;
25636
- const usedValues = usedValuesByGroup?.get(group.name.toLowerCase());
25670
+ const usedValues = usedValuesByGroup?.get(tagAttrKey(group.name));
25637
25671
  const visibleEntries = usedValues ? group.entries.filter((e) => usedValues.has(e.value.toLowerCase())) : group.entries;
25638
25672
  if (visibleEntries.length === 0) continue;
25639
25673
  const pillWidth2 = measureLegendText(group.name, LEGEND_PILL_FONT_SIZE) + LEGEND_PILL_PAD;
@@ -26113,7 +26147,7 @@ function layoutSitemap(parsed, hiddenCounts, activeTagGroup, hiddenAttributes, e
26113
26147
  totalHeight += MARGIN2;
26114
26148
  const usedValuesByGroup = /* @__PURE__ */ new Map();
26115
26149
  for (const group of parsed.tagGroups) {
26116
- const key = group.name.toLowerCase();
26150
+ const key = tagAttrKey(group.name);
26117
26151
  const used = /* @__PURE__ */ new Set();
26118
26152
  const walk = (node) => {
26119
26153
  if (!node.isContainer && node.metadata[key]) {
@@ -26158,6 +26192,7 @@ var init_layout2 = __esm({
26158
26192
  "src/sitemap/layout.ts"() {
26159
26193
  "use strict";
26160
26194
  init_tag_groups();
26195
+ init_tag_groups();
26161
26196
  init_legend_constants();
26162
26197
  init_text_measure();
26163
26198
  LABEL_FONT_SIZE3 = 13;
@@ -26427,10 +26462,10 @@ function renderSitemap(container, parsed, layout, palette, isDark, onClickItem,
26427
26462
  const displayNames = /* @__PURE__ */ new Map();
26428
26463
  const tagColors = /* @__PURE__ */ new Map();
26429
26464
  for (const group of parsed.tagGroups) {
26430
- displayNames.set(group.name.toLowerCase(), group.name);
26465
+ displayNames.set(tagAttrKey(group.name), group.name);
26431
26466
  for (const entry of group.entries) {
26432
26467
  tagColors.set(
26433
- `${group.name.toLowerCase()}:${entry.value.toLowerCase()}`,
26468
+ `${tagAttrKey(group.name)}:${entry.value.toLowerCase()}`,
26434
26469
  entry.color
26435
26470
  );
26436
26471
  }
@@ -26447,7 +26482,7 @@ function renderSitemap(container, parsed, layout, palette, isDark, onClickItem,
26447
26482
  );
26448
26483
  }
26449
26484
  if (activeTagGroup) {
26450
- const tagKey = activeTagGroup.toLowerCase();
26485
+ const tagKey = tagAttrKey(activeTagGroup);
26451
26486
  const tagVal = c.tagMetadata[tagKey];
26452
26487
  if (tagVal) cG.attr(`data-tag-${tagKey}`, tagVal.toLowerCase());
26453
26488
  }
@@ -26510,7 +26545,7 @@ function renderSitemap(container, parsed, layout, palette, isDark, onClickItem,
26510
26545
  nodeG.style("cursor", "pointer").on("click", () => onClickItem(node.lineNumber));
26511
26546
  }
26512
26547
  if (activeTagGroup) {
26513
- const tagKey = activeTagGroup.toLowerCase();
26548
+ const tagKey = tagAttrKey(activeTagGroup);
26514
26549
  const tagVal = node.tagMetadata[tagKey];
26515
26550
  if (tagVal) nodeG.attr(`data-tag-${tagKey}`, tagVal.toLowerCase());
26516
26551
  }
@@ -26637,7 +26672,7 @@ function renderLegend(parent, legendGroups, palette, isDark, activeTagGroup, fix
26637
26672
  const computedLayout = legendHandle.getLayout();
26638
26673
  if (computedLayout.activeCapsule?.addonX != null) {
26639
26674
  const capsule = computedLayout.activeCapsule;
26640
- const groupKey2 = capsule.groupName.toLowerCase();
26675
+ const groupKey2 = tagAttrKey(capsule.groupName);
26641
26676
  const isHidden = hiddenAttributes?.has(groupKey2) ?? false;
26642
26677
  const activeGroupEl = parent.select(`[data-legend-group="${groupKey2}"]`);
26643
26678
  if (!activeGroupEl.empty()) {
@@ -26702,6 +26737,7 @@ var DIAGRAM_PADDING2, MAX_SCALE2, TITLE_HEIGHT2, ARROWHEAD_W, ARROWHEAD_H, EDGE_
26702
26737
  var init_renderer2 = __esm({
26703
26738
  "src/sitemap/renderer.ts"() {
26704
26739
  "use strict";
26740
+ init_tag_groups();
26705
26741
  init_fonts();
26706
26742
  init_color_utils();
26707
26743
  init_inline_markdown();
@@ -26839,7 +26875,7 @@ function resolveCardTagMeta(card, tagGroups, hiddenMetaGroups) {
26839
26875
  const meta = [];
26840
26876
  for (const group of tagGroups) {
26841
26877
  if (hiddenMetaGroups?.includes(group.name.toLowerCase())) continue;
26842
- const tagValue = card.tags[group.name.toLowerCase()];
26878
+ const tagValue = card.tags[tagAttrKey(group.name)];
26843
26879
  const value = tagValue ?? group.defaultValue;
26844
26880
  if (!value) continue;
26845
26881
  const entry = group.entries.find(
@@ -26859,7 +26895,7 @@ function resolveCardTagColor(card, tagGroups, activeTagGroup) {
26859
26895
  (g) => g.name.toLowerCase() === activeTagGroup.toLowerCase()
26860
26896
  );
26861
26897
  if (!group) return card.color;
26862
- const tagValue = card.tags[group.name.toLowerCase()];
26898
+ const tagValue = card.tags[tagAttrKey(group.name)];
26863
26899
  const value = tagValue ?? group.defaultValue;
26864
26900
  if (!value) return void 0;
26865
26901
  const entry = group.entries.find(
@@ -27169,7 +27205,7 @@ function renderKanban(container, parsed, palette, isDark, options) {
27169
27205
  );
27170
27206
  const cg = g.append("g").attr("class", "kanban-card").attr("data-card-id", card.id).attr("data-line-number", card.lineNumber);
27171
27207
  if (activeTagGroup) {
27172
- const tagKey = activeTagGroup.toLowerCase();
27208
+ const tagKey = tagAttrKey(activeTagGroup);
27173
27209
  const tagValue = card.tags[tagKey];
27174
27210
  const group = parsed.tagGroups.find(
27175
27211
  (tg) => tg.name.toLowerCase() === tagKey
@@ -27230,7 +27266,7 @@ function drawSwimlaneIcon(parent, x, y, isActive, palette) {
27230
27266
  return iconG;
27231
27267
  }
27232
27268
  function bucketCardsBySwimlane(columns, swimlaneGroup) {
27233
- const tagKey = swimlaneGroup.name.toLowerCase();
27269
+ const tagKey = tagAttrKey(swimlaneGroup.name);
27234
27270
  const buckets = /* @__PURE__ */ new Map();
27235
27271
  for (const entry of swimlaneGroup.entries) {
27236
27272
  buckets.set(entry.value.toLowerCase(), {
@@ -27518,7 +27554,7 @@ function renderSwimlaneCard(parent, cardLayout, tagGroups, activeTagGroup, palet
27518
27554
  );
27519
27555
  const cg = parent.append("g").attr("class", "kanban-card").attr("data-card-id", card.id).attr("data-line-number", card.lineNumber);
27520
27556
  if (activeTagGroup) {
27521
- const tagKey = activeTagGroup.toLowerCase();
27557
+ const tagKey = tagAttrKey(activeTagGroup);
27522
27558
  const group = tagGroups.find((tg) => tg.name.toLowerCase() === tagKey);
27523
27559
  const value = card.tags[tagKey] ?? group?.defaultValue;
27524
27560
  if (value) {
@@ -27551,6 +27587,7 @@ var DIAGRAM_PADDING3, COLUMN_GAP, COLUMN_HEADER_HEIGHT, COLUMN_PADDING, COLUMN_M
27551
27587
  var init_renderer3 = __esm({
27552
27588
  "src/kanban/renderer.ts"() {
27553
27589
  "use strict";
27590
+ init_tag_groups();
27554
27591
  init_fonts();
27555
27592
  init_color_utils();
27556
27593
  init_inline_markdown();
@@ -28532,7 +28569,7 @@ function classifyEREntities(tables, relationships) {
28532
28569
  const variance = indegreeValues.reduce((a, b) => a + (b - mean) ** 2, 0) / indegreeValues.length;
28533
28570
  const stddev = Math.sqrt(variance);
28534
28571
  const sorted = [...indegreeValues].sort((a, b) => a - b);
28535
- const median = sorted.length % 2 === 0 ? (sorted[sorted.length / 2 - 1] + sorted[sorted.length / 2]) / 2 : sorted[Math.floor(sorted.length / 2)];
28572
+ const median2 = sorted.length % 2 === 0 ? (sorted[sorted.length / 2 - 1] + sorted[sorted.length / 2]) / 2 : sorted[Math.floor(sorted.length / 2)];
28536
28573
  for (const table of tables) {
28537
28574
  const id = table.id;
28538
28575
  const cols = table.columns;
@@ -28573,7 +28610,7 @@ function classifyEREntities(tables, relationships) {
28573
28610
  const nameMatchesLookup = LOOKUP_NAME_SUFFIXES.some(
28574
28611
  (s) => nameLower.endsWith(s)
28575
28612
  );
28576
- if (nameMatchesLookup && cols.length <= 6 && fkCount <= 1 && indegree > median) {
28613
+ if (nameMatchesLookup && cols.length <= 6 && fkCount <= 1 && indegree > median2) {
28577
28614
  result.set(id, "lookup");
28578
28615
  continue;
28579
28616
  }
@@ -28814,7 +28851,7 @@ function renderERDiagram(container, parsed, layout, palette, isDark, onClickItem
28814
28851
  seriesColors2[ni % seriesColors2.length];
28815
28852
  const nodeG = contentG.append("g").attr("transform", `translate(${node.x}, ${node.y})`).attr("class", "er-table").attr("data-line-number", String(node.lineNumber)).attr("data-node-id", node.id);
28816
28853
  if (activeTagGroup) {
28817
- const tagKey = activeTagGroup.toLowerCase();
28854
+ const tagKey = tagAttrKey(activeTagGroup);
28818
28855
  const tagValue = node.metadata[tagKey];
28819
28856
  if (tagValue) {
28820
28857
  nodeG.attr(`data-tag-${tagKey}`, tagValue.toLowerCase());
@@ -28993,6 +29030,7 @@ var DIAGRAM_PADDING5, MAX_SCALE4, TABLE_FONT_SIZE2, COLUMN_FONT_SIZE2, EDGE_LABE
28993
29030
  var init_renderer5 = __esm({
28994
29031
  "src/er/renderer.ts"() {
28995
29032
  "use strict";
29033
+ init_tag_groups();
28996
29034
  init_fonts();
28997
29035
  init_color_utils();
28998
29036
  init_palettes();
@@ -29163,63 +29201,6 @@ function ensureArrowMarkers(defs, colors) {
29163
29201
  ).attr("fill", color);
29164
29202
  }
29165
29203
  }
29166
- function edgePolylineMidpoint(points) {
29167
- if (points.length === 0) return { x: 0, y: 0 };
29168
- if (points.length === 1) return { x: points[0].x, y: points[0].y };
29169
- let total = 0;
29170
- const segLen = [];
29171
- for (let k = 1; k < points.length; k++) {
29172
- const len = Math.hypot(
29173
- points[k].x - points[k - 1].x,
29174
- points[k].y - points[k - 1].y
29175
- );
29176
- segLen.push(len);
29177
- total += len;
29178
- }
29179
- let half = total / 2;
29180
- for (let k = 1; k < points.length; k++) {
29181
- const len = segLen[k - 1];
29182
- if (half <= len || k === points.length - 1) {
29183
- const t = len === 0 ? 0 : Math.min(1, half / len);
29184
- return {
29185
- x: points[k - 1].x + (points[k].x - points[k - 1].x) * t,
29186
- y: points[k - 1].y + (points[k].y - points[k - 1].y) * t
29187
- };
29188
- }
29189
- half -= len;
29190
- }
29191
- const last = points[points.length - 1];
29192
- return { x: last.x, y: last.y };
29193
- }
29194
- function resolveEdgeLabelOverlaps(labels) {
29195
- const MAX_PASSES = 8;
29196
- const PAD = 4;
29197
- for (let pass = 0; pass < MAX_PASSES; pass++) {
29198
- let moved = false;
29199
- for (let i = 0; i < labels.length; i++) {
29200
- for (let j = i + 1; j < labels.length; j++) {
29201
- const a = labels[i];
29202
- const b = labels[j];
29203
- const dx = Math.abs(a.x - b.x);
29204
- const dy = Math.abs(a.y - b.y);
29205
- const overlapX = (a.width + b.width) / 2 + PAD - dx;
29206
- const overlapY = (a.height + b.height) / 2 + PAD - dy;
29207
- if (overlapX > 0 && overlapY > 0) {
29208
- const shift = overlapY / 2 + 1;
29209
- if (a.y < b.y) {
29210
- a.y -= shift;
29211
- b.y += shift;
29212
- } else {
29213
- a.y += shift;
29214
- b.y -= shift;
29215
- }
29216
- moved = true;
29217
- }
29218
- }
29219
- }
29220
- if (!moved) break;
29221
- }
29222
- }
29223
29204
  function renderBoxesAndLines(container, parsed, layout, palette, isDark, options) {
29224
29205
  const {
29225
29206
  onClickItem,
@@ -29426,8 +29407,6 @@ function renderBoxesAndLines(container, parsed, layout, palette, isDark, options
29426
29407
  groupG.append("text").attr("class", "bl-group-label").attr("x", gx + group.width / 2).attr("y", gy + 18).attr("text-anchor", "middle").attr("font-family", FONT_FAMILY).attr("font-size", sGroupLabelFontSize).attr("font-weight", "600").attr("fill", palette.text).text(group.label);
29427
29408
  }
29428
29409
  }
29429
- const labelPositions = [];
29430
- const edgeGroups = /* @__PURE__ */ new Map();
29431
29410
  for (let i = 0; i < layout.edges.length; i++) {
29432
29411
  const le = layout.edges[i];
29433
29412
  const color = edgeColorMap.get(i) ?? palette.textMuted;
@@ -29446,7 +29425,6 @@ function renderBoxesAndLines(container, parsed, layout, palette, isDark, options
29446
29425
  const nodeLayout = layoutNodeMap.get(le.source);
29447
29426
  if (nodeLayout) {
29448
29427
  const edgeG2 = diagramG.append("g").attr("class", "bl-edge-group").attr("data-line-number", String(le.lineNumber));
29449
- edgeGroups.set(i, edgeG2);
29450
29428
  const markerId2 = `bl-arrow-${color.replace("#", "")}`;
29451
29429
  const cx = nodeLayout.x;
29452
29430
  const cy = nodeLayout.y;
@@ -29495,7 +29473,6 @@ function renderBoxesAndLines(container, parsed, layout, palette, isDark, options
29495
29473
  }
29496
29474
  if (points.length < 2) continue;
29497
29475
  const edgeG = diagramG.append("g").attr("class", "bl-edge-group").attr("data-line-number", String(le.lineNumber));
29498
- edgeGroups.set(i, edgeG);
29499
29476
  const markerId = `bl-arrow-${color.replace("#", "")}`;
29500
29477
  const gen = le.straight ? lineGeneratorStraight : parsed.direction === "TB" ? lineGeneratorTB : lineGeneratorLR;
29501
29478
  const path = edgeG.append("path").attr("class", "bl-edge").attr("d", gen(points) ?? "").attr("fill", "none").attr("stroke", color).attr("stroke-width", sEdgeStrokeWidth).attr("marker-end", `url(#${markerId})`);
@@ -29503,31 +29480,6 @@ function renderBoxesAndLines(container, parsed, layout, palette, isDark, options
29503
29480
  const revId = `bl-arrow-rev-${color.replace("#", "")}`;
29504
29481
  path.attr("marker-start", `url(#${revId})`);
29505
29482
  }
29506
- if (le.label && le.points.length > 0) {
29507
- const lw = measureText(le.label, sEdgeLabelFontSize);
29508
- const labelH = sEdgeLabelFontSize + 6;
29509
- const mid = edgePolylineMidpoint(le.points);
29510
- let ly = mid.y;
29511
- if (le.parallelCount > 1 && le.yOffset !== 0) {
29512
- ly += (le.yOffset < 0 ? -labelH : labelH) * 0.5;
29513
- }
29514
- labelPositions.push({
29515
- x: mid.x,
29516
- y: ly,
29517
- width: lw + 8,
29518
- height: labelH,
29519
- idx: i
29520
- });
29521
- }
29522
- }
29523
- resolveEdgeLabelOverlaps(labelPositions);
29524
- for (const lp of labelPositions) {
29525
- const le = layout.edges[lp.idx];
29526
- if (!le.label) continue;
29527
- const edgeG = edgeGroups.get(lp.idx);
29528
- const target = edgeG ?? diagramG;
29529
- target.append("rect").attr("x", lp.x - lp.width / 2).attr("y", lp.y - lp.height / 2).attr("width", lp.width).attr("height", lp.height).attr("rx", 3).attr("fill", palette.bg).attr("opacity", 0.85);
29530
- target.append("text").attr("x", lp.x).attr("y", lp.y + sEdgeLabelFontSize / 3).attr("text-anchor", "middle").attr("font-size", sEdgeLabelFontSize).attr("fill", palette.textMuted).text(le.label);
29531
29483
  }
29532
29484
  for (const ln of layout.nodes) {
29533
29485
  const node = nodeMap.get(ln.label);
@@ -29578,8 +29530,8 @@ function renderBoxesAndLines(container, parsed, layout, palette, isDark, options
29578
29530
  nodeG.append("rect").attr("x", x).attr("y", y).attr("width", ln.width).attr("height", ln.height).attr("rx", NODE_RX).attr("ry", NODE_RX).attr("fill", colors.fill).attr("stroke", colors.stroke).attr("stroke-width", sNodeStrokeWidth);
29579
29531
  const desc = node.description;
29580
29532
  if (desc && desc.length > 0 && !hideDescriptions) {
29581
- const MAX_LABEL_LINES3 = 3;
29582
- const fitted = fitLabelToHeader(node.label, ln.width, MAX_LABEL_LINES3);
29533
+ const MAX_LABEL_LINES4 = 3;
29534
+ const fitted = fitLabelToHeader(node.label, ln.width, MAX_LABEL_LINES4);
29583
29535
  const labelLines = fitted.lines;
29584
29536
  const labelLineH = fitted.fontSize * 1.3;
29585
29537
  const labelTotalH = labelLines.length * labelLineH;
@@ -29713,6 +29665,35 @@ function renderBoxesAndLines(container, parsed, layout, palette, isDark, options
29713
29665
  }
29714
29666
  }
29715
29667
  }
29668
+ const labelLayer = diagramG.append("g").attr("class", "bl-edge-labels");
29669
+ const labelLineHeight = sEdgeLabelFontSize * 1.3;
29670
+ for (const le of layout.edges) {
29671
+ if (!le.labelLines || le.labelLines.length === 0) continue;
29672
+ if (le.labelX === void 0 || le.labelY === void 0) continue;
29673
+ const lx = le.labelX;
29674
+ const ly = le.labelY;
29675
+ if (hidden.size > 0) {
29676
+ let isHidden = false;
29677
+ for (const [groupKey2, hiddenVals] of hidden) {
29678
+ const val = le.metadata[groupKey2];
29679
+ if (val && hiddenVals.has(val.toLowerCase())) {
29680
+ isHidden = true;
29681
+ break;
29682
+ }
29683
+ }
29684
+ if (isHidden) continue;
29685
+ }
29686
+ const lw = le.labelWidth ?? 0;
29687
+ const lh = le.labelHeight ?? sEdgeLabelFontSize + 6;
29688
+ const lines = le.labelLines;
29689
+ const labelG = labelLayer.append("g").attr("class", "bl-edge-label").attr("data-line-number", String(le.lineNumber));
29690
+ labelG.append("rect").attr("x", lx - lw / 2).attr("y", ly - lh / 2).attr("width", lw).attr("height", lh).attr("rx", 3).attr("fill", palette.bg).attr("opacity", 0.72);
29691
+ const text = labelG.append("text").attr("x", lx).attr("text-anchor", "middle").attr("font-family", FONT_FAMILY).attr("font-size", sEdgeLabelFontSize).attr("fill", palette.textMuted);
29692
+ const firstY = ly - (lines.length - 1) / 2 * labelLineHeight + sEdgeLabelFontSize / 3;
29693
+ lines.forEach((line11, k) => {
29694
+ text.append("tspan").attr("x", lx).attr("y", firstY + k * labelLineHeight).text(line11);
29695
+ });
29696
+ }
29716
29697
  const hasDescriptions = parsed.nodes.some(
29717
29698
  (n) => n.description && n.description.length > 0
29718
29699
  );
@@ -29827,6 +29808,222 @@ var init_renderer6 = __esm({
29827
29808
  }
29828
29809
  });
29829
29810
 
29811
+ // src/boxes-and-lines/label-placement.ts
29812
+ function measureEdgeLabel(label, fontSize = EDGE_LABEL_FONT_SIZE6, maxWidth = LABEL_MAX_WIDTH) {
29813
+ let lines = wrapTextToWidth(label, fontSize, maxWidth, { hardBreak: true });
29814
+ if (lines.length > MAX_LABEL_LINES) {
29815
+ const kept = lines.slice(0, MAX_LABEL_LINES - 1);
29816
+ const rest = lines.slice(MAX_LABEL_LINES - 1).join(" ");
29817
+ kept.push(truncateText(rest, fontSize, maxWidth));
29818
+ lines = kept;
29819
+ }
29820
+ let textW = 0;
29821
+ for (const ln of lines) {
29822
+ const w = measureText(ln, fontSize);
29823
+ if (w > textW) textW = w;
29824
+ }
29825
+ return {
29826
+ lines,
29827
+ width: textW + 2 * H_PAD,
29828
+ height: lines.length * fontSize * LABEL_LINE_HEIGHT + 2 * V_PAD
29829
+ };
29830
+ }
29831
+ function edgePolylineMidpoint(points) {
29832
+ return pointAtArcFraction(points, 0.5).p;
29833
+ }
29834
+ function pointAtArcFraction(points, t) {
29835
+ if (points.length === 0) return { p: { x: 0, y: 0 }, nx: 0, ny: 1 };
29836
+ if (points.length === 1)
29837
+ return { p: { x: points[0].x, y: points[0].y }, nx: 0, ny: 1 };
29838
+ const segLen = [];
29839
+ let total = 0;
29840
+ for (let k = 1; k < points.length; k++) {
29841
+ const len = Math.hypot(
29842
+ points[k].x - points[k - 1].x,
29843
+ points[k].y - points[k - 1].y
29844
+ );
29845
+ segLen.push(len);
29846
+ total += len;
29847
+ }
29848
+ let want = total * Math.max(0, Math.min(1, t));
29849
+ for (let k = 1; k < points.length; k++) {
29850
+ const len = segLen[k - 1];
29851
+ if (want <= len || k === points.length - 1) {
29852
+ const f = len === 0 ? 0 : Math.min(1, want / len);
29853
+ const ax = points[k - 1].x;
29854
+ const ay = points[k - 1].y;
29855
+ const bx = points[k].x;
29856
+ const by = points[k].y;
29857
+ const dx = bx - ax;
29858
+ const dy = by - ay;
29859
+ const dlen = Math.hypot(dx, dy) || 1;
29860
+ return {
29861
+ p: { x: ax + dx * f, y: ay + dy * f },
29862
+ nx: -dy / dlen,
29863
+ ny: dx / dlen
29864
+ };
29865
+ }
29866
+ want -= len;
29867
+ }
29868
+ const last = points[points.length - 1];
29869
+ return { p: { x: last.x, y: last.y }, nx: 0, ny: 1 };
29870
+ }
29871
+ function rectFromCenter(cx, cy, w, h) {
29872
+ return {
29873
+ minX: cx - w / 2,
29874
+ minY: cy - h / 2,
29875
+ maxX: cx + w / 2,
29876
+ maxY: cy + h / 2
29877
+ };
29878
+ }
29879
+ function overlapsAny(cx, cy, w, h, obstacles, pad2) {
29880
+ const minX = cx - w / 2 - pad2;
29881
+ const minY = cy - h / 2 - pad2;
29882
+ const maxX = cx + w / 2 + pad2;
29883
+ const maxY = cy + h / 2 + pad2;
29884
+ for (const o of obstacles) {
29885
+ if (minX < o.maxX && maxX > o.minX && minY < o.maxY && maxY > o.minY)
29886
+ return true;
29887
+ }
29888
+ return false;
29889
+ }
29890
+ function slideFractions() {
29891
+ const out = [0.5];
29892
+ for (let k = 1; k <= SLIDE_SAMPLES; k++) {
29893
+ const off = k * 0.08;
29894
+ if (0.5 - off >= 0.12) out.push(0.5 - off);
29895
+ if (0.5 + off <= 0.88) out.push(0.5 + off);
29896
+ }
29897
+ return out;
29898
+ }
29899
+ function findClearPosition(w, h, points, obstacles) {
29900
+ const ts = slideFractions();
29901
+ for (const t of ts) {
29902
+ const { p } = pointAtArcFraction(points, t);
29903
+ if (!overlapsAny(p.x, p.y, w, h, obstacles, BOX_CLEAR_PAD)) return p;
29904
+ }
29905
+ for (let mag = PERP_STEP; mag <= PERP_MAX; mag += PERP_STEP) {
29906
+ for (const t of ts) {
29907
+ const { p, nx, ny } = pointAtArcFraction(points, t);
29908
+ for (const sign of [-1, 1]) {
29909
+ const x = p.x + nx * mag * sign;
29910
+ const y = p.y + ny * mag * sign;
29911
+ if (!overlapsAny(x, y, w, h, obstacles, BOX_CLEAR_PAD)) return { x, y };
29912
+ }
29913
+ }
29914
+ }
29915
+ return null;
29916
+ }
29917
+ function resolveLabelOverlaps(boxes) {
29918
+ const MAX_PASSES = 8;
29919
+ const PAD = 4;
29920
+ for (let pass = 0; pass < MAX_PASSES; pass++) {
29921
+ let moved = false;
29922
+ for (let i = 0; i < boxes.length; i++) {
29923
+ for (let j = i + 1; j < boxes.length; j++) {
29924
+ const a = boxes[i];
29925
+ const b = boxes[j];
29926
+ const dx = Math.abs(a.cx - b.cx);
29927
+ const dy = Math.abs(a.cy - b.cy);
29928
+ const overlapX = (a.w + b.w) / 2 + PAD - dx;
29929
+ const overlapY = (a.h + b.h) / 2 + PAD - dy;
29930
+ if (overlapX > 0 && overlapY > 0) {
29931
+ const shift = overlapY / 2 + 1;
29932
+ if (a.cy < b.cy) {
29933
+ a.cy -= shift;
29934
+ b.cy += shift;
29935
+ } else {
29936
+ a.cy += shift;
29937
+ b.cy -= shift;
29938
+ }
29939
+ moved = true;
29940
+ }
29941
+ }
29942
+ }
29943
+ if (!moved) break;
29944
+ }
29945
+ }
29946
+ function placeEdgeLabels(layout, opts) {
29947
+ const fontSize = opts?.fontSize ?? EDGE_LABEL_FONT_SIZE6;
29948
+ const maxWidth = opts?.maxWidth ?? LABEL_MAX_WIDTH;
29949
+ const obstacles = [];
29950
+ for (const n of layout.nodes)
29951
+ obstacles.push(rectFromCenter(n.x, n.y, n.width, n.height));
29952
+ for (const g of layout.groups)
29953
+ if (g.collapsed)
29954
+ obstacles.push(rectFromCenter(g.x, g.y, g.width, g.height));
29955
+ const boxes = [];
29956
+ layout.edges.forEach((e, idx) => {
29957
+ if (!e.label || e.points.length < 2) return;
29958
+ const m = measureEdgeLabel(e.label, fontSize, maxWidth);
29959
+ const mid = edgePolylineMidpoint(e.points);
29960
+ let cy = mid.y;
29961
+ if (e.parallelCount > 1 && e.yOffset !== 0)
29962
+ cy += (e.yOffset < 0 ? -1 : 1) * (m.height / 2);
29963
+ boxes.push({
29964
+ edgeIdx: idx,
29965
+ cx: mid.x,
29966
+ cy,
29967
+ w: m.width,
29968
+ h: m.height,
29969
+ lines: m.lines,
29970
+ resolved: true
29971
+ });
29972
+ });
29973
+ for (const box of boxes) {
29974
+ if (!overlapsAny(box.cx, box.cy, box.w, box.h, obstacles, BOX_CLEAR_PAD))
29975
+ continue;
29976
+ const e = layout.edges[box.edgeIdx];
29977
+ const clear = findClearPosition(box.w, box.h, e.points, obstacles);
29978
+ if (clear) {
29979
+ box.cx = clear.x;
29980
+ box.cy = clear.y;
29981
+ } else {
29982
+ box.resolved = false;
29983
+ }
29984
+ }
29985
+ resolveLabelOverlaps(boxes);
29986
+ for (const box of boxes) {
29987
+ if (overlapsAny(box.cx, box.cy, box.w, box.h, obstacles, BOX_CLEAR_PAD))
29988
+ box.resolved = false;
29989
+ }
29990
+ const boxByIdx = /* @__PURE__ */ new Map();
29991
+ for (const b of boxes) boxByIdx.set(b.edgeIdx, b);
29992
+ const edges = layout.edges.map((e, idx) => {
29993
+ const b = boxByIdx.get(idx);
29994
+ if (!b) return e;
29995
+ return {
29996
+ ...e,
29997
+ labelX: b.cx,
29998
+ labelY: b.cy,
29999
+ labelWidth: b.w,
30000
+ labelHeight: b.h,
30001
+ labelLines: b.lines
30002
+ };
30003
+ });
30004
+ return {
30005
+ layout: { ...layout, edges },
30006
+ unresolved: boxes.filter((b) => !b.resolved).map((b) => b.edgeIdx)
30007
+ };
30008
+ }
30009
+ var EDGE_LABEL_FONT_SIZE6, LABEL_MAX_WIDTH, MAX_LABEL_LINES, LABEL_LINE_HEIGHT, H_PAD, V_PAD, BOX_CLEAR_PAD, PERP_STEP, PERP_MAX, SLIDE_SAMPLES;
30010
+ var init_label_placement = __esm({
30011
+ "src/boxes-and-lines/label-placement.ts"() {
30012
+ "use strict";
30013
+ init_text_measure();
30014
+ EDGE_LABEL_FONT_SIZE6 = 11;
30015
+ LABEL_MAX_WIDTH = 160;
30016
+ MAX_LABEL_LINES = 3;
30017
+ LABEL_LINE_HEIGHT = 1.3;
30018
+ H_PAD = 6;
30019
+ V_PAD = 3;
30020
+ BOX_CLEAR_PAD = 4;
30021
+ PERP_STEP = 8;
30022
+ PERP_MAX = 40;
30023
+ SLIDE_SAMPLES = 9;
30024
+ }
30025
+ });
30026
+
29830
30027
  // src/boxes-and-lines/layout-layered.ts
29831
30028
  function rng(s) {
29832
30029
  return () => {
@@ -29986,7 +30183,7 @@ function layeredCandidates(parsed, sizes, opts) {
29986
30183
  for (let r = 0; r < maxRank; r++) c += gapCrossings(r);
29987
30184
  return c;
29988
30185
  };
29989
- const median = (vals) => {
30186
+ const median2 = (vals) => {
29990
30187
  if (vals.length === 0) return -1;
29991
30188
  vals.sort((x, y) => x - y);
29992
30189
  const m = Math.floor(vals.length / 2);
@@ -29996,6 +30193,623 @@ function layeredCandidates(parsed, sizes, opts) {
29996
30193
  const right = vals[vals.length - 1] - vals[m];
29997
30194
  return left + right === 0 ? (vals[m - 1] + vals[m]) / 2 : (vals[m - 1] * right + vals[m] * left) / (left + right);
29998
30195
  };
30196
+ const wmedianRank = (r, useUp) => {
30197
+ const ids = ranks[r];
30198
+ const neigh = useUp ? up : down;
30199
+ const med = /* @__PURE__ */ new Map();
30200
+ for (const id of ids)
30201
+ med.set(id, median2(neigh.get(id).map((x) => orderOf.get(x))));
30202
+ const movable = ids.filter((id) => med.get(id) >= 0).sort((a, b) => med.get(a) - med.get(b));
30203
+ const out = new Array(ids.length).fill(null);
30204
+ for (const id of ids) if (med.get(id) < 0) out[orderOf.get(id)] = id;
30205
+ let mi = 0;
30206
+ for (let slot = 0; slot < out.length; slot++)
30207
+ if (out[slot] === null) out[slot] = movable[mi++];
30208
+ const final = out;
30209
+ final.forEach((id, i) => orderOf.set(id, i));
30210
+ ranks[r] = final;
30211
+ };
30212
+ const transpose = () => {
30213
+ let improved = true;
30214
+ let guard = 0;
30215
+ while (improved && guard++ < 12) {
30216
+ improved = false;
30217
+ for (let r = 0; r <= maxRank; r++) {
30218
+ const ids = ranks[r];
30219
+ for (let i = 0; i < ids.length - 1; i++) {
30220
+ const before = (r > 0 ? gapCrossings(r - 1) : 0) + (r < maxRank ? gapCrossings(r) : 0);
30221
+ const a = ids[i], b = ids[i + 1];
30222
+ ids[i] = b;
30223
+ ids[i + 1] = a;
30224
+ orderOf.set(b, i);
30225
+ orderOf.set(a, i + 1);
30226
+ const after = (r > 0 ? gapCrossings(r - 1) : 0) + (r < maxRank ? gapCrossings(r) : 0);
30227
+ if (after < before) {
30228
+ improved = true;
30229
+ } else {
30230
+ ids[i] = a;
30231
+ ids[i + 1] = b;
30232
+ orderOf.set(a, i);
30233
+ orderOf.set(b, i + 1);
30234
+ }
30235
+ }
30236
+ }
30237
+ }
30238
+ };
30239
+ const initOrder = (seed) => {
30240
+ const r = seed === 0 ? null : rng(seed);
30241
+ for (let rr = 0; rr <= maxRank; rr++) {
30242
+ const ids = ranks[rr].slice();
30243
+ if (r) {
30244
+ for (let i = ids.length - 1; i > 0; i--) {
30245
+ const j = Math.floor(r() * (i + 1));
30246
+ [ids[i], ids[j]] = [ids[j], ids[i]];
30247
+ }
30248
+ }
30249
+ ranks[rr] = ids;
30250
+ ids.forEach((id, i) => orderOf.set(id, i));
30251
+ }
30252
+ };
30253
+ const found = [];
30254
+ for (let s = 0; s < restarts; s++) {
30255
+ initOrder(s);
30256
+ let best = totalCrossings();
30257
+ let bestSnap = new Map(orderOf);
30258
+ for (let iter = 0; iter < 8; iter++) {
30259
+ const down1 = iter % 2 === 0;
30260
+ if (down1) for (let r = 1; r <= maxRank; r++) wmedianRank(r, true);
30261
+ else for (let r = maxRank - 1; r >= 0; r--) wmedianRank(r, false);
30262
+ transpose();
30263
+ const c = totalCrossings();
30264
+ if (c < best) {
30265
+ best = c;
30266
+ bestSnap = new Map(orderOf);
30267
+ }
30268
+ if (c === 0) break;
30269
+ }
30270
+ applyOrder(bestSnap);
30271
+ for (let r = 0; r <= maxRank; r++)
30272
+ ranks[r].sort((a, b) => orderOf.get(a) - orderOf.get(b));
30273
+ const sig = ranks.map((r) => r.join(",")).join("|");
30274
+ if (!found.some((f) => f.sig === sig))
30275
+ found.push({ sig, cross: best, order: new Map(bestSnap) });
30276
+ }
30277
+ found.sort((a, b) => a.cross - b.cross);
30278
+ const keep = found.slice(0, keepK);
30279
+ if (keep.length === 0) return [];
30280
+ const results = [];
30281
+ for (const cand of keep) {
30282
+ applyOrder(cand.order);
30283
+ const rk = Array.from({ length: maxRank + 1 }, () => []);
30284
+ for (const n of node.values()) rk[n.rank].push(n.id);
30285
+ for (let r = 0; r <= maxRank; r++)
30286
+ rk[r].sort((a, b) => cand.order.get(a) - cand.order.get(b));
30287
+ results.push(realize(rk));
30288
+ }
30289
+ return results;
30290
+ function realize(rk) {
30291
+ const bandDepth = rk.map(
30292
+ (ids) => Math.max(0, ...ids.map((id) => node.get(id).depth))
30293
+ );
30294
+ const bandCenter = [];
30295
+ let acc = MARGIN3;
30296
+ for (let r = 0; r <= maxRank; r++) {
30297
+ bandCenter[r] = acc + bandDepth[r] / 2;
30298
+ acc += bandDepth[r] + ranksep;
30299
+ }
30300
+ for (let r = 0; r <= maxRank; r++) {
30301
+ let x = MARGIN3;
30302
+ for (const id of rk[r]) {
30303
+ const n = node.get(id);
30304
+ n.cross = x + n.thick / 2;
30305
+ x += n.thick + nodesep;
30306
+ }
30307
+ }
30308
+ const ITER = 18;
30309
+ for (let it = 0; it < ITER; it++) {
30310
+ const downPass = it % 2 === 0;
30311
+ const order = downPass ? Array.from({ length: maxRank + 1 }, (_, i) => i) : Array.from({ length: maxRank + 1 }, (_, i) => maxRank - i);
30312
+ for (const r of order) {
30313
+ const ids = rk[r];
30314
+ const desired = ids.map((id) => {
30315
+ const n = node.get(id);
30316
+ let sw = 0, sx2 = 0;
30317
+ for (const nb of up.get(id)) {
30318
+ const w = weight(n, node.get(nb));
30319
+ sw += w;
30320
+ sx2 += w * node.get(nb).cross;
30321
+ }
30322
+ for (const nb of down.get(id)) {
30323
+ const w = weight(n, node.get(nb));
30324
+ sw += w;
30325
+ sx2 += w * node.get(nb).cross;
30326
+ }
30327
+ return sw > 0 ? sx2 / sw : n.cross;
30328
+ });
30329
+ placeWithSeparation(ids, desired);
30330
+ }
30331
+ }
30332
+ let minC = Infinity;
30333
+ for (const n of node.values()) minC = Math.min(minC, n.cross - n.thick / 2);
30334
+ const shift = MARGIN3 - minC;
30335
+ for (const n of node.values()) n.cross += shift;
30336
+ const coord = (n) => isTB ? { x: n.cross, y: bandCenter[n.rank] } : { x: bandCenter[n.rank], y: n.cross };
30337
+ const layoutNodes = parsed.nodes.map((pn) => {
30338
+ const n = node.get(pn.label);
30339
+ const c = coord(n);
30340
+ return {
30341
+ label: pn.label,
30342
+ x: c.x,
30343
+ y: c.y,
30344
+ width: n.realW,
30345
+ height: n.realH
30346
+ };
30347
+ });
30348
+ const chainById = /* @__PURE__ */ new Map();
30349
+ for (const ch of chains) chainById.set(ch.edgeIdx, ch.chain);
30350
+ let minCross = Infinity, maxCross = -Infinity;
30351
+ for (const n of node.values()) {
30352
+ if (n.dummy) continue;
30353
+ minCross = Math.min(minCross, n.cross - n.thick / 2);
30354
+ maxCross = Math.max(maxCross, n.cross + n.thick / 2);
30355
+ }
30356
+ const GAP = 34;
30357
+ const LANE = 28;
30358
+ const sideOf = (b) => {
30359
+ if (backEdgeSide === "left") return -1;
30360
+ if (backEdgeSide === "right") return 1;
30361
+ const d = node.get(b.src).cross - node.get(b.tgt).cross;
30362
+ return d >= 0 ? 1 : -1;
30363
+ };
30364
+ const backSorted = backEdges.map((b) => ({
30365
+ ...b,
30366
+ side: sideOf(b),
30367
+ span: Math.abs(node.get(b.src).rank - node.get(b.tgt).rank)
30368
+ })).sort((a, b) => a.side - b.side || a.span - b.span);
30369
+ const sideCounts = /* @__PURE__ */ new Map();
30370
+ for (const b of backSorted)
30371
+ sideCounts.set(b.side, (sideCounts.get(b.side) ?? 0) + 1);
30372
+ const laneCounter = /* @__PURE__ */ new Map();
30373
+ const backPoints = /* @__PURE__ */ new Map();
30374
+ const faceLim = (depth) => Math.max(0, depth / 2 - 6);
30375
+ const hubIndex = /* @__PURE__ */ new Map();
30376
+ const hubSize = /* @__PURE__ */ new Map();
30377
+ {
30378
+ const counter = /* @__PURE__ */ new Map();
30379
+ for (const b of backSorted) {
30380
+ const key = `${b.side}|${b.tgt}`;
30381
+ const idx = counter.get(key) ?? 0;
30382
+ counter.set(key, idx + 1);
30383
+ hubIndex.set(b.edgeIdx, idx);
30384
+ }
30385
+ for (const [key, v] of counter) hubSize.set(key, v);
30386
+ }
30387
+ for (const b of backSorted) {
30388
+ const s = b.side;
30389
+ const k = laneCounter.get(s) ?? 0;
30390
+ laneCounter.set(s, k + 1);
30391
+ const K = sideCounts.get(s);
30392
+ const src = node.get(b.src);
30393
+ const tgt = node.get(b.tgt);
30394
+ const laneC = s > 0 ? maxCross + GAP + k * LANE : minCross - GAP - k * LANE;
30395
+ const srcCtr = bandCenter[src.rank];
30396
+ const tgtCtr = bandCenter[tgt.rank];
30397
+ const frac = K > 1 ? (k + 1) / (K + 1) : 0;
30398
+ const srcR = srcCtr + frac * faceLim(src.depth);
30399
+ const tgtR = tgtCtr - frac * faceLim(tgt.depth);
30400
+ const m = (c, r) => isTB ? { x: c, y: r } : { x: r, y: c };
30401
+ const off = 10;
30402
+ const srcEdgeC = src.cross + s * (src.thick / 2 + off);
30403
+ const tgtEdgeC = tgt.cross + s * (tgt.thick / 2 + off);
30404
+ const hubK = hubIndex.get(b.edgeIdx);
30405
+ const hubN = hubSize.get(`${s}|${b.tgt}`);
30406
+ if (hubN > 1 && hubK > 0) {
30407
+ const tgtTop = tgtCtr - (tgt.depth / 2 + GAP + (hubK - 1) * LANE);
30408
+ const entryCross = tgt.cross + s * faceLim(tgt.thick) * (hubK / hubN);
30409
+ backPoints.set(b.edgeIdx, [
30410
+ m(src.cross, srcCtr),
30411
+ m(srcEdgeC, srcR),
30412
+ m(laneC, srcR),
30413
+ m(laneC, tgtTop),
30414
+ m(entryCross, tgtTop),
30415
+ m(tgt.cross, tgtCtr)
30416
+ ]);
30417
+ } else {
30418
+ backPoints.set(b.edgeIdx, [
30419
+ m(src.cross, srcCtr),
30420
+ m(srcEdgeC, srcR),
30421
+ m(laneC, srcR),
30422
+ m(laneC, tgtR),
30423
+ m(tgtEdgeC, tgtR),
30424
+ m(tgt.cross, tgtCtr)
30425
+ ]);
30426
+ }
30427
+ }
30428
+ const layoutEdges = [];
30429
+ for (let i = 0; i < parsed.edges.length; i++) {
30430
+ const e = parsed.edges[i];
30431
+ if (!labelSet.has(e.source) || !labelSet.has(e.target)) continue;
30432
+ let points;
30433
+ if (e.source === e.target) {
30434
+ const c = coord(node.get(e.source));
30435
+ points = [c, c];
30436
+ } else if (backPoints.has(i)) {
30437
+ points = backPoints.get(i);
30438
+ } else {
30439
+ const chain = chainById.get(i);
30440
+ if (!chain) continue;
30441
+ points = chain.map((id) => coord(node.get(id)));
30442
+ }
30443
+ layoutEdges.push({
30444
+ source: e.source,
30445
+ target: e.target,
30446
+ ...e.label !== void 0 && { label: e.label },
30447
+ bidirectional: e.bidirectional,
30448
+ lineNumber: e.lineNumber,
30449
+ points,
30450
+ yOffset: 0,
30451
+ parallelCount: 1,
30452
+ metadata: e.metadata
30453
+ });
30454
+ }
30455
+ let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
30456
+ for (const n of layoutNodes) {
30457
+ minX = Math.min(minX, n.x - n.width / 2);
30458
+ minY = Math.min(minY, n.y - n.height / 2);
30459
+ maxX = Math.max(maxX, n.x + n.width / 2);
30460
+ maxY = Math.max(maxY, n.y + n.height / 2);
30461
+ }
30462
+ for (const e of layoutEdges)
30463
+ for (const p of e.points) {
30464
+ minX = Math.min(minX, p.x);
30465
+ minY = Math.min(minY, p.y);
30466
+ maxX = Math.max(maxX, p.x);
30467
+ maxY = Math.max(maxY, p.y);
30468
+ }
30469
+ const sx = MARGIN3 - minX;
30470
+ const sy = MARGIN3 - minY;
30471
+ const shifted = sx !== 0 || sy !== 0 ? {
30472
+ nodes: layoutNodes.map((n) => ({ ...n, x: n.x + sx, y: n.y + sy })),
30473
+ edges: layoutEdges.map((e) => ({
30474
+ ...e,
30475
+ points: e.points.map((p) => ({ x: p.x + sx, y: p.y + sy }))
30476
+ }))
30477
+ } : { nodes: layoutNodes, edges: layoutEdges };
30478
+ return {
30479
+ nodes: shifted.nodes,
30480
+ edges: shifted.edges,
30481
+ groups: [],
30482
+ width: maxX + sx + MARGIN3,
30483
+ height: maxY + sy + MARGIN3
30484
+ };
30485
+ }
30486
+ function weight(a, b) {
30487
+ if (a.dummy && b.dummy) return W_DUMMY_DUMMY;
30488
+ if (a.dummy || b.dummy) return W_REAL_DUMMY;
30489
+ return W_REAL_REAL;
30490
+ }
30491
+ function placeWithSeparation(ids, desired) {
30492
+ const n = ids.length;
30493
+ if (n === 0) return;
30494
+ const half = ids.map((id) => node.get(id).thick / 2);
30495
+ const off = [0];
30496
+ for (let i = 1; i < n; i++)
30497
+ off[i] = off[i - 1] + half[i - 1] + nodesep + half[i];
30498
+ const d = desired.map((v, i) => v - off[i]);
30499
+ const blocks = [];
30500
+ for (let i = 0; i < n; i++) {
30501
+ let b = { pos: d[i], count: 1, sumOffset: d[i], first: i };
30502
+ while (blocks.length && blocks[blocks.length - 1].pos >= b.pos) {
30503
+ const prev = blocks.pop();
30504
+ const count = prev.count + b.count;
30505
+ const sumOffset = prev.sumOffset + b.sumOffset;
30506
+ b = { pos: sumOffset / count, count, sumOffset, first: prev.first };
30507
+ }
30508
+ blocks.push(b);
30509
+ }
30510
+ const xs = new Array(n);
30511
+ for (const b of blocks)
30512
+ for (let k = 0; k < b.count; k++) xs[b.first + k] = b.pos;
30513
+ for (let i = 0; i < n; i++) node.get(ids[i]).cross = xs[i] + off[i];
30514
+ }
30515
+ }
30516
+ var NODESEP, RANKSEP, DUMMY_THICK, MARGIN3, W_REAL_REAL, W_REAL_DUMMY, W_DUMMY_DUMMY;
30517
+ var init_layout_layered = __esm({
30518
+ "src/boxes-and-lines/layout-layered.ts"() {
30519
+ "use strict";
30520
+ init_layout5();
30521
+ NODESEP = 50;
30522
+ RANKSEP = 60;
30523
+ DUMMY_THICK = 10;
30524
+ MARGIN3 = 40;
30525
+ W_REAL_REAL = 1;
30526
+ W_REAL_DUMMY = 2;
30527
+ W_DUMMY_DUMMY = 8;
30528
+ }
30529
+ });
30530
+
30531
+ // src/boxes-and-lines/layout-grouped.ts
30532
+ function rng2(s) {
30533
+ return () => {
30534
+ s |= 0;
30535
+ s = s + 1831565813 | 0;
30536
+ let t = Math.imul(s ^ s >>> 15, 1 | s);
30537
+ t = t + Math.imul(t ^ t >>> 7, 61 | t) ^ t;
30538
+ return ((t ^ t >>> 14) >>> 0) / 4294967296;
30539
+ };
30540
+ }
30541
+ function median(vals) {
30542
+ if (vals.length === 0) return -1;
30543
+ vals.sort((x, y) => x - y);
30544
+ const m = Math.floor(vals.length / 2);
30545
+ if (vals.length % 2 === 1) return vals[m];
30546
+ if (vals.length === 2) return (vals[0] + vals[1]) / 2;
30547
+ const left = vals[m - 1] - vals[0];
30548
+ const right = vals[vals.length - 1] - vals[m];
30549
+ return left + right === 0 ? (vals[m - 1] + vals[m]) / 2 : (vals[m - 1] * right + vals[m] * left) / (left + right);
30550
+ }
30551
+ function acyclicOrient(ids, edges) {
30552
+ const adj = /* @__PURE__ */ new Map();
30553
+ for (const l of ids) adj.set(l, []);
30554
+ for (const e of edges) adj.get(e.from)?.push({ to: e.to, idx: e.idx });
30555
+ const reversed = /* @__PURE__ */ new Set();
30556
+ const state = /* @__PURE__ */ new Map();
30557
+ for (const l of ids) state.set(l, 0);
30558
+ const stack = [];
30559
+ const visit = (root) => {
30560
+ stack.push(root);
30561
+ const it = /* @__PURE__ */ new Map();
30562
+ while (stack.length) {
30563
+ const u = stack[stack.length - 1];
30564
+ if (state.get(u) === 0) state.set(u, 1);
30565
+ const list = adj.get(u);
30566
+ let i = it.get(u) ?? 0;
30567
+ let descended = false;
30568
+ for (; i < list.length; i++) {
30569
+ const { to, idx } = list[i];
30570
+ const st = state.get(to);
30571
+ if (st === 1) reversed.add(idx);
30572
+ else if (st === 0) {
30573
+ it.set(u, i + 1);
30574
+ stack.push(to);
30575
+ descended = true;
30576
+ break;
30577
+ }
30578
+ }
30579
+ if (!descended) {
30580
+ it.set(u, list.length);
30581
+ state.set(u, 2);
30582
+ stack.pop();
30583
+ }
30584
+ }
30585
+ };
30586
+ for (const l of ids) if (state.get(l) === 0) visit(l);
30587
+ const dag = edges.map(
30588
+ (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 }
30589
+ );
30590
+ return { dag };
30591
+ }
30592
+ function longestPath(ids, dag) {
30593
+ const out = /* @__PURE__ */ new Map();
30594
+ const inDeg = /* @__PURE__ */ new Map();
30595
+ for (const l of ids) {
30596
+ out.set(l, []);
30597
+ inDeg.set(l, 0);
30598
+ }
30599
+ for (const d of dag) {
30600
+ out.get(d.from).push(d.to);
30601
+ inDeg.set(d.to, (inDeg.get(d.to) ?? 0) + 1);
30602
+ }
30603
+ const rank = /* @__PURE__ */ new Map();
30604
+ for (const l of ids) rank.set(l, 0);
30605
+ const queue = ids.filter((l) => (inDeg.get(l) ?? 0) === 0);
30606
+ const indeg2 = new Map(inDeg);
30607
+ let seen = 0;
30608
+ while (queue.length) {
30609
+ const u = queue.shift();
30610
+ seen++;
30611
+ for (const v of out.get(u)) {
30612
+ rank.set(v, Math.max(rank.get(v), rank.get(u) + 1));
30613
+ indeg2.set(v, indeg2.get(v) - 1);
30614
+ if (indeg2.get(v) === 0) queue.push(v);
30615
+ }
30616
+ }
30617
+ return seen === ids.length ? rank : null;
30618
+ }
30619
+ function groupedTierCandidates(parsed, sizes, opts) {
30620
+ if (parsed.groups.length === 0) return [];
30621
+ const nCount = parsed.nodes.length;
30622
+ if (nCount < 3 || nCount > 60) return [];
30623
+ const isTB = parsed.direction === "TB";
30624
+ const labels = parsed.nodes.map((n) => n.label);
30625
+ const labelSet = new Set(labels);
30626
+ for (const e of parsed.edges)
30627
+ if (e.source.startsWith("__group_") || e.target.startsWith("__group_"))
30628
+ return [];
30629
+ const groupByLabel = new Map(parsed.groups.map((g) => [g.label, g]));
30630
+ const directGroupOf = /* @__PURE__ */ new Map();
30631
+ for (const grp of parsed.groups)
30632
+ for (const c of grp.children)
30633
+ if (labelSet.has(c)) directGroupOf.set(c, grp.label);
30634
+ const topGroupOf = (groupLabel) => {
30635
+ let cur = groupLabel;
30636
+ const guard = /* @__PURE__ */ new Set();
30637
+ while (true) {
30638
+ const g = groupByLabel.get(cur);
30639
+ if (!g?.parentGroup || guard.has(cur)) return cur;
30640
+ guard.add(cur);
30641
+ cur = g.parentGroup;
30642
+ }
30643
+ };
30644
+ const pathOf = (label) => {
30645
+ const direct = directGroupOf.get(label);
30646
+ if (!direct) return [];
30647
+ const chain = [direct];
30648
+ let cur = direct;
30649
+ const guard = /* @__PURE__ */ new Set([direct]);
30650
+ while (true) {
30651
+ const g = groupByLabel.get(cur);
30652
+ if (!g?.parentGroup || guard.has(g.parentGroup)) break;
30653
+ chain.push(g.parentGroup);
30654
+ guard.add(g.parentGroup);
30655
+ cur = g.parentGroup;
30656
+ }
30657
+ return chain.reverse();
30658
+ };
30659
+ const SINGLE = "\0node\0";
30660
+ const unitOf = (label) => {
30661
+ const direct = directGroupOf.get(label);
30662
+ return direct ? topGroupOf(direct) : SINGLE + label;
30663
+ };
30664
+ const edges = parsed.edges.map((e, i) => ({ e, i })).filter(
30665
+ ({ e }) => labelSet.has(e.source) && labelSet.has(e.target) && e.source !== e.target
30666
+ );
30667
+ if (edges.length === 0) return [];
30668
+ const unitOfNode = /* @__PURE__ */ new Map();
30669
+ for (const l of labels) unitOfNode.set(l, unitOf(l));
30670
+ const units = Array.from(new Set(labels.map((l) => unitOfNode.get(l))));
30671
+ const interUnit = edges.filter(({ e }) => unitOfNode.get(e.source) !== unitOfNode.get(e.target)).map(({ e, i }) => ({
30672
+ from: unitOfNode.get(e.source),
30673
+ to: unitOfNode.get(e.target),
30674
+ idx: i
30675
+ }));
30676
+ const { dag: unitDag } = acyclicOrient(units, interUnit);
30677
+ const tier = longestPath(
30678
+ units,
30679
+ unitDag.map((d) => ({ from: d.from, to: d.to }))
30680
+ );
30681
+ if (!tier) return [];
30682
+ const localRank = /* @__PURE__ */ new Map();
30683
+ const unitMembers = /* @__PURE__ */ new Map();
30684
+ for (const l of labels) {
30685
+ const u = unitOfNode.get(l);
30686
+ (unitMembers.get(u) ?? unitMembers.set(u, []).get(u)).push(l);
30687
+ }
30688
+ for (const [u, members] of unitMembers) {
30689
+ const memberSet = new Set(members);
30690
+ const intra = edges.filter(
30691
+ ({ e }) => unitOfNode.get(e.source) === u && unitOfNode.get(e.target) === u && memberSet.has(e.source) && memberSet.has(e.target)
30692
+ ).map(({ e, i }) => ({ from: e.source, to: e.target, idx: i }));
30693
+ const { dag } = acyclicOrient(members, intra);
30694
+ const lr = longestPath(
30695
+ members,
30696
+ dag.map((d) => ({ from: d.from, to: d.to }))
30697
+ );
30698
+ if (!lr) return [];
30699
+ for (const m of members) localRank.set(m, lr.get(m));
30700
+ }
30701
+ const maxTier = Math.max(...units.map((u) => tier.get(u)));
30702
+ const bandHeight = Array.from({ length: maxTier + 1 }, () => 1);
30703
+ for (const u of units) {
30704
+ const h = Math.max(
30705
+ ...unitMembers.get(u).map((m) => localRank.get(m) + 1)
30706
+ );
30707
+ const t = tier.get(u);
30708
+ bandHeight[t] = Math.max(bandHeight[t], h);
30709
+ }
30710
+ const tierBase = [];
30711
+ let accBase = 0;
30712
+ for (let t = 0; t <= maxTier; t++) {
30713
+ tierBase[t] = accBase;
30714
+ accBase += bandHeight[t];
30715
+ }
30716
+ const tierStartRanks = new Set(tierBase.slice(1));
30717
+ const globalRank = /* @__PURE__ */ new Map();
30718
+ for (const l of labels)
30719
+ globalRank.set(
30720
+ l,
30721
+ tierBase[tier.get(unitOfNode.get(l))] + localRank.get(l)
30722
+ );
30723
+ const maxRank = Math.max(...labels.map((l) => globalRank.get(l)));
30724
+ const node = /* @__PURE__ */ new Map();
30725
+ for (const n of parsed.nodes) {
30726
+ const s = sizes.get(n.label) ?? { width: NODE_WIDTH, height: NODE_HEIGHT };
30727
+ node.set(n.label, {
30728
+ id: n.label,
30729
+ dummy: false,
30730
+ rank: globalRank.get(n.label),
30731
+ cross: 0,
30732
+ thick: isTB ? s.width : s.height,
30733
+ depth: isTB ? s.height : s.width,
30734
+ realW: s.width,
30735
+ realH: s.height,
30736
+ unit: unitOfNode.get(n.label),
30737
+ path: pathOf(n.label)
30738
+ });
30739
+ }
30740
+ const chains = [];
30741
+ const segs = [];
30742
+ const backEdges = [];
30743
+ const flatEdges = [];
30744
+ for (const { e, i } of edges) {
30745
+ const rs = globalRank.get(e.source);
30746
+ const rt = globalRank.get(e.target);
30747
+ if (rs === rt) {
30748
+ flatEdges.push({ edgeIdx: i, a: e.source, b: e.target });
30749
+ continue;
30750
+ }
30751
+ if (rt < rs) {
30752
+ backEdges.push({ edgeIdx: i, src: e.source, tgt: e.target });
30753
+ continue;
30754
+ }
30755
+ const chain = [e.source];
30756
+ let prev = e.source;
30757
+ for (let r = rs + 1; r < rt; r++) {
30758
+ const id = `__d${i}_${r}`;
30759
+ node.set(id, {
30760
+ id,
30761
+ dummy: true,
30762
+ rank: r,
30763
+ cross: 0,
30764
+ thick: DUMMY_THICK2,
30765
+ depth: 0,
30766
+ realW: 0,
30767
+ realH: 0,
30768
+ unit: SINGLE + id,
30769
+ path: []
30770
+ });
30771
+ segs.push({ a: prev, b: id });
30772
+ chain.push(id);
30773
+ prev = id;
30774
+ }
30775
+ segs.push({ a: prev, b: e.target });
30776
+ chain.push(e.target);
30777
+ chains.push({ edgeIdx: i, chain });
30778
+ }
30779
+ const ranks = Array.from({ length: maxRank + 1 }, () => []);
30780
+ for (const n of node.values()) ranks[n.rank].push(n.id);
30781
+ const up = /* @__PURE__ */ new Map();
30782
+ const down = /* @__PURE__ */ new Map();
30783
+ for (const id of node.keys()) {
30784
+ up.set(id, []);
30785
+ down.set(id, []);
30786
+ }
30787
+ for (const s of segs) {
30788
+ down.get(s.a).push(s.b);
30789
+ up.get(s.b).push(s.a);
30790
+ }
30791
+ const orderOf = /* @__PURE__ */ new Map();
30792
+ const gapCrossings = (r) => {
30793
+ const lower = ranks[r + 1];
30794
+ if (!lower) return 0;
30795
+ const es = [];
30796
+ for (const a of ranks[r]) {
30797
+ const pa = orderOf.get(a);
30798
+ for (const b of down.get(a)) es.push({ p: pa, q: orderOf.get(b) });
30799
+ }
30800
+ let c = 0;
30801
+ for (let i = 0; i < es.length; i++)
30802
+ for (let j = i + 1; j < es.length; j++) {
30803
+ const A = es[i], B = es[j];
30804
+ if ((A.p - B.p) * (A.q - B.q) < 0) c++;
30805
+ }
30806
+ return c;
30807
+ };
30808
+ const totalCrossings = () => {
30809
+ let c = 0;
30810
+ for (let r = 0; r < maxRank; r++) c += gapCrossings(r);
30811
+ return c;
30812
+ };
29999
30813
  const wmedianRank = (r, useUp) => {
30000
30814
  const ids = ranks[r];
30001
30815
  const neigh = useUp ? up : down;
@@ -30027,9 +30841,8 @@ function layeredCandidates(parsed, sizes, opts) {
30027
30841
  orderOf.set(b, i);
30028
30842
  orderOf.set(a, i + 1);
30029
30843
  const after = (r > 0 ? gapCrossings(r - 1) : 0) + (r < maxRank ? gapCrossings(r) : 0);
30030
- if (after < before) {
30031
- improved = true;
30032
- } else {
30844
+ if (after < before) improved = true;
30845
+ else {
30033
30846
  ids[i] = a;
30034
30847
  ids[i + 1] = b;
30035
30848
  orderOf.set(a, i);
@@ -30040,27 +30853,28 @@ function layeredCandidates(parsed, sizes, opts) {
30040
30853
  }
30041
30854
  };
30042
30855
  const initOrder = (seed) => {
30043
- const r = seed === 0 ? null : rng(seed);
30856
+ const r = seed === 0 ? null : rng2(seed);
30044
30857
  for (let rr = 0; rr <= maxRank; rr++) {
30045
30858
  const ids = ranks[rr].slice();
30046
- if (r) {
30859
+ if (r)
30047
30860
  for (let i = ids.length - 1; i > 0; i--) {
30048
30861
  const j = Math.floor(r() * (i + 1));
30049
30862
  [ids[i], ids[j]] = [ids[j], ids[i]];
30050
30863
  }
30051
- }
30052
30864
  ranks[rr] = ids;
30053
30865
  ids.forEach((id, i) => orderOf.set(id, i));
30054
30866
  }
30055
30867
  };
30868
+ const restarts = opts?.restarts ?? (nCount <= 16 ? 20 : nCount <= 30 ? 12 : 6);
30869
+ const keepK = opts?.keepK ?? 3;
30056
30870
  const found = [];
30057
30871
  for (let s = 0; s < restarts; s++) {
30058
30872
  initOrder(s);
30059
30873
  let best = totalCrossings();
30060
30874
  let bestSnap = new Map(orderOf);
30061
30875
  for (let iter = 0; iter < 8; iter++) {
30062
- const down1 = iter % 2 === 0;
30063
- if (down1) for (let r = 1; r <= maxRank; r++) wmedianRank(r, true);
30876
+ if (iter % 2 === 0)
30877
+ for (let r = 1; r <= maxRank; r++) wmedianRank(r, true);
30064
30878
  else for (let r = maxRank - 1; r >= 0; r--) wmedianRank(r, false);
30065
30879
  transpose();
30066
30880
  const c = totalCrossings();
@@ -30070,7 +30884,7 @@ function layeredCandidates(parsed, sizes, opts) {
30070
30884
  }
30071
30885
  if (c === 0) break;
30072
30886
  }
30073
- applyOrder(bestSnap);
30887
+ for (const [k, v] of bestSnap) orderOf.set(k, v);
30074
30888
  for (let r = 0; r <= maxRank; r++)
30075
30889
  ranks[r].sort((a, b) => orderOf.get(a) - orderOf.get(b));
30076
30890
  const sig = ranks.map((r) => r.join(",")).join("|");
@@ -30082,7 +30896,7 @@ function layeredCandidates(parsed, sizes, opts) {
30082
30896
  if (keep.length === 0) return [];
30083
30897
  const results = [];
30084
30898
  for (const cand of keep) {
30085
- applyOrder(cand.order);
30899
+ for (const [k, v] of cand.order) orderOf.set(k, v);
30086
30900
  const rk = Array.from({ length: maxRank + 1 }, () => []);
30087
30901
  for (const n of node.values()) rk[n.rank].push(n.id);
30088
30902
  for (let r = 0; r <= maxRank; r++)
@@ -30090,22 +30904,64 @@ function layeredCandidates(parsed, sizes, opts) {
30090
30904
  results.push(realize(rk));
30091
30905
  }
30092
30906
  return results;
30907
+ function weight(a, b) {
30908
+ if (a.dummy && b.dummy) return W_DUMMY_DUMMY2;
30909
+ if (a.dummy || b.dummy) return W_REAL_DUMMY2;
30910
+ return W_REAL_REAL2;
30911
+ }
30912
+ function placeWithSeparation(ids, desired, gap) {
30913
+ const n = ids.length;
30914
+ if (n === 0) return;
30915
+ const off = [0];
30916
+ for (let i = 1; i < n; i++) off[i] = off[i - 1] + gap[i];
30917
+ const d = desired.map((v, i) => v - off[i]);
30918
+ const blocks = [];
30919
+ for (let i = 0; i < n; i++) {
30920
+ let b = { pos: d[i], count: 1, sumOffset: d[i], first: i };
30921
+ while (blocks.length && blocks[blocks.length - 1].pos >= b.pos) {
30922
+ const prev = blocks.pop();
30923
+ const count = prev.count + b.count;
30924
+ const sumOffset = prev.sumOffset + b.sumOffset;
30925
+ b = { pos: sumOffset / count, count, sumOffset, first: prev.first };
30926
+ }
30927
+ blocks.push(b);
30928
+ }
30929
+ const xs = new Array(n);
30930
+ for (const b of blocks)
30931
+ for (let k = 0; k < b.count; k++) xs[b.first + k] = b.pos;
30932
+ for (let i = 0; i < n; i++) node.get(ids[i]).cross = xs[i] + off[i];
30933
+ }
30934
+ function gapBetween(a, b) {
30935
+ const base = a.thick / 2 + b.thick / 2 + NODESEP2;
30936
+ if (a.dummy || b.dummy) return base;
30937
+ if (a.unit !== b.unit) return base + 2 * GROUP_PAD + GROUP_GAP;
30938
+ const da = a.path[a.path.length - 1];
30939
+ const db = b.path[b.path.length - 1];
30940
+ if (da !== db && (da || db)) return base + 2 * GROUP_PAD;
30941
+ return base;
30942
+ }
30093
30943
  function realize(rk) {
30094
30944
  const bandDepth = rk.map(
30095
30945
  (ids) => Math.max(0, ...ids.map((id) => node.get(id).depth))
30096
30946
  );
30097
30947
  const bandCenter = [];
30098
- let acc = MARGIN3;
30948
+ let acc = MARGIN4;
30099
30949
  for (let r = 0; r <= maxRank; r++) {
30950
+ if (r > 0) acc += RANKSEP2 + (tierStartRanks.has(r) ? TIER_GAP : 0);
30100
30951
  bandCenter[r] = acc + bandDepth[r] / 2;
30101
- acc += bandDepth[r] + ranksep;
30952
+ acc += bandDepth[r];
30102
30953
  }
30954
+ const gapsFor = (ids) => ids.map(
30955
+ (id, i) => i === 0 ? 0 : gapBetween(node.get(ids[i - 1]), node.get(id))
30956
+ );
30103
30957
  for (let r = 0; r <= maxRank; r++) {
30104
- let x = MARGIN3;
30105
- for (const id of rk[r]) {
30106
- const n = node.get(id);
30107
- n.cross = x + n.thick / 2;
30108
- x += n.thick + nodesep;
30958
+ const ids = rk[r];
30959
+ const gap = gapsFor(ids);
30960
+ let x = MARGIN4;
30961
+ for (let i = 0; i < ids.length; i++) {
30962
+ const n = node.get(ids[i]);
30963
+ x += gap[i];
30964
+ n.cross = i === 0 ? MARGIN4 + n.thick / 2 : x;
30109
30965
  }
30110
30966
  }
30111
30967
  const ITER = 18;
@@ -30129,13 +30985,13 @@ function layeredCandidates(parsed, sizes, opts) {
30129
30985
  }
30130
30986
  return sw > 0 ? sx2 / sw : n.cross;
30131
30987
  });
30132
- placeWithSeparation(ids, desired);
30988
+ placeWithSeparation(ids, desired, gapsFor(ids));
30133
30989
  }
30134
30990
  }
30135
30991
  let minC = Infinity;
30136
30992
  for (const n of node.values()) minC = Math.min(minC, n.cross - n.thick / 2);
30137
- const shift = MARGIN3 - minC;
30138
- for (const n of node.values()) n.cross += shift;
30993
+ const shiftC = MARGIN4 - minC;
30994
+ for (const n of node.values()) n.cross += shiftC;
30139
30995
  const coord = (n) => isTB ? { x: n.cross, y: bandCenter[n.rank] } : { x: bandCenter[n.rank], y: n.cross };
30140
30996
  const layoutNodes = parsed.nodes.map((pn) => {
30141
30997
  const n = node.get(pn.label);
@@ -30158,9 +31014,8 @@ function layeredCandidates(parsed, sizes, opts) {
30158
31014
  }
30159
31015
  const GAP = 34;
30160
31016
  const LANE = 28;
31017
+ const faceLim = (depth) => Math.max(0, depth / 2 - 6);
30161
31018
  const sideOf = (b) => {
30162
- if (backEdgeSide === "left") return -1;
30163
- if (backEdgeSide === "right") return 1;
30164
31019
  const d = node.get(b.src).cross - node.get(b.tgt).cross;
30165
31020
  return d >= 0 ? 1 : -1;
30166
31021
  };
@@ -30174,19 +31029,6 @@ function layeredCandidates(parsed, sizes, opts) {
30174
31029
  sideCounts.set(b.side, (sideCounts.get(b.side) ?? 0) + 1);
30175
31030
  const laneCounter = /* @__PURE__ */ new Map();
30176
31031
  const backPoints = /* @__PURE__ */ new Map();
30177
- const faceLim = (depth) => Math.max(0, depth / 2 - 6);
30178
- const hubIndex = /* @__PURE__ */ new Map();
30179
- const hubSize = /* @__PURE__ */ new Map();
30180
- {
30181
- const counter = /* @__PURE__ */ new Map();
30182
- for (const b of backSorted) {
30183
- const key = `${b.side}|${b.tgt}`;
30184
- const idx = counter.get(key) ?? 0;
30185
- counter.set(key, idx + 1);
30186
- hubIndex.set(b.edgeIdx, idx);
30187
- }
30188
- for (const [key, v] of counter) hubSize.set(key, v);
30189
- }
30190
31032
  for (const b of backSorted) {
30191
31033
  const s = b.side;
30192
31034
  const k = laneCounter.get(s) ?? 0;
@@ -30198,35 +31040,34 @@ function layeredCandidates(parsed, sizes, opts) {
30198
31040
  const srcCtr = bandCenter[src.rank];
30199
31041
  const tgtCtr = bandCenter[tgt.rank];
30200
31042
  const frac = K > 1 ? (k + 1) / (K + 1) : 0;
30201
- const srcR = srcCtr + frac * faceLim(src.depth);
30202
- const tgtR = tgtCtr - frac * faceLim(tgt.depth);
30203
- const m = (c, r) => isTB ? { x: c, y: r } : { x: r, y: c };
31043
+ const srcR = srcCtr - frac * faceLim(src.depth);
31044
+ const tgtR = tgtCtr + frac * faceLim(tgt.depth);
31045
+ const m = (c, rr) => isTB ? { x: c, y: rr } : { x: rr, y: c };
30204
31046
  const off = 10;
30205
31047
  const srcEdgeC = src.cross + s * (src.thick / 2 + off);
30206
31048
  const tgtEdgeC = tgt.cross + s * (tgt.thick / 2 + off);
30207
- const hubK = hubIndex.get(b.edgeIdx);
30208
- const hubN = hubSize.get(`${s}|${b.tgt}`);
30209
- if (hubN > 1 && hubK > 0) {
30210
- const tgtTop = tgtCtr - (tgt.depth / 2 + GAP + (hubK - 1) * LANE);
30211
- const entryCross = tgt.cross + s * faceLim(tgt.thick) * (hubK / hubN);
30212
- backPoints.set(b.edgeIdx, [
30213
- m(src.cross, srcCtr),
30214
- m(srcEdgeC, srcR),
30215
- m(laneC, srcR),
30216
- m(laneC, tgtTop),
30217
- m(entryCross, tgtTop),
30218
- m(tgt.cross, tgtCtr)
30219
- ]);
30220
- } else {
30221
- backPoints.set(b.edgeIdx, [
30222
- m(src.cross, srcCtr),
30223
- m(srcEdgeC, srcR),
30224
- m(laneC, srcR),
30225
- m(laneC, tgtR),
30226
- m(tgtEdgeC, tgtR),
30227
- m(tgt.cross, tgtCtr)
30228
- ]);
30229
- }
31049
+ backPoints.set(b.edgeIdx, [
31050
+ m(src.cross, srcCtr),
31051
+ m(srcEdgeC, srcR),
31052
+ m(laneC, srcR),
31053
+ m(laneC, tgtR),
31054
+ m(tgtEdgeC, tgtR),
31055
+ m(tgt.cross, tgtCtr)
31056
+ ]);
31057
+ }
31058
+ const flatPoints = /* @__PURE__ */ new Map();
31059
+ for (const f of flatEdges) {
31060
+ const a = node.get(f.a);
31061
+ const b = node.get(f.b);
31062
+ const ctr = bandCenter[a.rank];
31063
+ const bow = ctr - (a.depth / 2 + 26);
31064
+ const m = (c, rr) => isTB ? { x: c, y: rr } : { x: rr, y: c };
31065
+ flatPoints.set(f.edgeIdx, [
31066
+ m(a.cross, ctr),
31067
+ m(a.cross, bow),
31068
+ m(b.cross, bow),
31069
+ m(b.cross, ctr)
31070
+ ]);
30230
31071
  }
30231
31072
  const layoutEdges = [];
30232
31073
  for (let i = 0; i < parsed.edges.length; i++) {
@@ -30236,9 +31077,9 @@ function layeredCandidates(parsed, sizes, opts) {
30236
31077
  if (e.source === e.target) {
30237
31078
  const c = coord(node.get(e.source));
30238
31079
  points = [c, c];
30239
- } else if (backPoints.has(i)) {
30240
- points = backPoints.get(i);
30241
- } else {
31080
+ } else if (backPoints.has(i)) points = backPoints.get(i);
31081
+ else if (flatPoints.has(i)) points = flatPoints.get(i);
31082
+ else {
30242
31083
  const chain = chainById.get(i);
30243
31084
  if (!chain) continue;
30244
31085
  points = chain.map((id) => coord(node.get(id)));
@@ -30255,79 +31096,124 @@ function layeredCandidates(parsed, sizes, opts) {
30255
31096
  metadata: e.metadata
30256
31097
  });
30257
31098
  }
30258
- let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
30259
- for (const n of layoutNodes) {
30260
- minX = Math.min(minX, n.x - n.width / 2);
30261
- minY = Math.min(minY, n.y - n.height / 2);
30262
- maxX = Math.max(maxX, n.x + n.width / 2);
30263
- maxY = Math.max(maxY, n.y + n.height / 2);
31099
+ const nodeBox = new Map(
31100
+ layoutNodes.map((n) => [
31101
+ n.label,
31102
+ {
31103
+ l: n.x - n.width / 2,
31104
+ r: n.x + n.width / 2,
31105
+ t: n.y - n.height / 2,
31106
+ b: n.y + n.height / 2
31107
+ }
31108
+ ])
31109
+ );
31110
+ const depthOf = (label) => {
31111
+ let d = 0;
31112
+ let cur = label;
31113
+ const guard = /* @__PURE__ */ new Set();
31114
+ while (cur && !guard.has(cur)) {
31115
+ guard.add(cur);
31116
+ cur = groupByLabel.get(cur)?.parentGroup;
31117
+ if (cur) d++;
31118
+ }
31119
+ return d;
31120
+ };
31121
+ const childGroups = /* @__PURE__ */ new Map();
31122
+ for (const g of parsed.groups)
31123
+ if (g.parentGroup)
31124
+ (childGroups.get(g.parentGroup) ?? childGroups.set(g.parentGroup, []).get(g.parentGroup)).push(g.label);
31125
+ const rectByGroup = /* @__PURE__ */ new Map();
31126
+ const ordered = [...parsed.groups].sort(
31127
+ (a, b) => depthOf(b.label) - depthOf(a.label)
31128
+ );
31129
+ const layoutGroups = [];
31130
+ for (const grp of ordered) {
31131
+ let l = Infinity, r = -Infinity, t = Infinity, bm = -Infinity;
31132
+ const acc2 = (box) => {
31133
+ l = Math.min(l, box.l);
31134
+ r = Math.max(r, box.r);
31135
+ t = Math.min(t, box.t);
31136
+ bm = Math.max(bm, box.b);
31137
+ };
31138
+ for (const c of grp.children) {
31139
+ const nb = nodeBox.get(c);
31140
+ if (nb) acc2(nb);
31141
+ }
31142
+ for (const sub of childGroups.get(grp.label) ?? []) {
31143
+ const sr = rectByGroup.get(sub);
31144
+ if (sr) acc2(sr);
31145
+ }
31146
+ if (!Number.isFinite(l)) continue;
31147
+ const x0 = l - GROUP_PAD;
31148
+ const x1 = r + GROUP_PAD;
31149
+ const y0 = t - GROUP_LABEL_ZONE2;
31150
+ const y1 = bm + GROUP_PAD;
31151
+ rectByGroup.set(grp.label, { l: x0, r: x1, t: y0, b: y1 });
31152
+ layoutGroups.push({
31153
+ label: grp.label,
31154
+ lineNumber: grp.lineNumber,
31155
+ x: (x0 + x1) / 2,
31156
+ y: (y0 + y1) / 2,
31157
+ width: x1 - x0,
31158
+ height: y1 - y0,
31159
+ collapsed: false,
31160
+ childCount: grp.children.length
31161
+ });
30264
31162
  }
31163
+ let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
31164
+ const ext = (x0, y0, x1, y1) => {
31165
+ minX = Math.min(minX, x0);
31166
+ minY = Math.min(minY, y0);
31167
+ maxX = Math.max(maxX, x1);
31168
+ maxY = Math.max(maxY, y1);
31169
+ };
31170
+ for (const n of layoutNodes)
31171
+ ext(
31172
+ n.x - n.width / 2,
31173
+ n.y - n.height / 2,
31174
+ n.x + n.width / 2,
31175
+ n.y + n.height / 2
31176
+ );
30265
31177
  for (const e of layoutEdges)
30266
- for (const p of e.points) {
30267
- minX = Math.min(minX, p.x);
30268
- minY = Math.min(minY, p.y);
30269
- maxX = Math.max(maxX, p.x);
30270
- maxY = Math.max(maxY, p.y);
30271
- }
30272
- const sx = MARGIN3 - minX;
30273
- const sy = MARGIN3 - minY;
30274
- const shifted = sx !== 0 || sy !== 0 ? {
30275
- nodes: layoutNodes.map((n) => ({ ...n, x: n.x + sx, y: n.y + sy })),
30276
- edges: layoutEdges.map((e) => ({
31178
+ for (const p of e.points) ext(p.x, p.y, p.x, p.y);
31179
+ for (const g of layoutGroups)
31180
+ ext(
31181
+ g.x - g.width / 2,
31182
+ g.y - g.height / 2,
31183
+ g.x + g.width / 2,
31184
+ g.y + g.height / 2
31185
+ );
31186
+ const sx = MARGIN4 - minX;
31187
+ const sy = MARGIN4 - minY;
31188
+ const shift = sx !== 0 || sy !== 0;
31189
+ return {
31190
+ nodes: shift ? layoutNodes.map((n) => ({ ...n, x: n.x + sx, y: n.y + sy })) : layoutNodes,
31191
+ edges: shift ? layoutEdges.map((e) => ({
30277
31192
  ...e,
30278
31193
  points: e.points.map((p) => ({ x: p.x + sx, y: p.y + sy }))
30279
- }))
30280
- } : { nodes: layoutNodes, edges: layoutEdges };
30281
- return {
30282
- nodes: shifted.nodes,
30283
- edges: shifted.edges,
30284
- groups: [],
30285
- width: maxX + sx + MARGIN3,
30286
- height: maxY + sy + MARGIN3
31194
+ })) : layoutEdges,
31195
+ groups: shift ? layoutGroups.map((g) => ({ ...g, x: g.x + sx, y: g.y + sy })) : layoutGroups,
31196
+ width: maxX + sx + MARGIN4,
31197
+ height: maxY + sy + MARGIN4
30287
31198
  };
30288
31199
  }
30289
- function weight(a, b) {
30290
- if (a.dummy && b.dummy) return W_DUMMY_DUMMY;
30291
- if (a.dummy || b.dummy) return W_REAL_DUMMY;
30292
- return W_REAL_REAL;
30293
- }
30294
- function placeWithSeparation(ids, desired) {
30295
- const n = ids.length;
30296
- if (n === 0) return;
30297
- const half = ids.map((id) => node.get(id).thick / 2);
30298
- const off = [0];
30299
- for (let i = 1; i < n; i++)
30300
- off[i] = off[i - 1] + half[i - 1] + nodesep + half[i];
30301
- const d = desired.map((v, i) => v - off[i]);
30302
- const blocks = [];
30303
- for (let i = 0; i < n; i++) {
30304
- let b = { pos: d[i], count: 1, sumOffset: d[i], first: i };
30305
- while (blocks.length && blocks[blocks.length - 1].pos >= b.pos) {
30306
- const prev = blocks.pop();
30307
- const count = prev.count + b.count;
30308
- const sumOffset = prev.sumOffset + b.sumOffset;
30309
- b = { pos: sumOffset / count, count, sumOffset, first: prev.first };
30310
- }
30311
- blocks.push(b);
30312
- }
30313
- const xs = new Array(n);
30314
- for (const b of blocks)
30315
- for (let k = 0; k < b.count; k++) xs[b.first + k] = b.pos;
30316
- for (let i = 0; i < n; i++) node.get(ids[i]).cross = xs[i] + off[i];
30317
- }
30318
31200
  }
30319
- var NODESEP, RANKSEP, DUMMY_THICK, MARGIN3, W_REAL_REAL, W_REAL_DUMMY, W_DUMMY_DUMMY;
30320
- var init_layout_layered = __esm({
30321
- "src/boxes-and-lines/layout-layered.ts"() {
31201
+ var NODESEP2, RANKSEP2, DUMMY_THICK2, MARGIN4, GROUP_PAD, GROUP_LABEL_ZONE2, GROUP_GAP, TIER_GAP, W_REAL_REAL2, W_REAL_DUMMY2, W_DUMMY_DUMMY2;
31202
+ var init_layout_grouped = __esm({
31203
+ "src/boxes-and-lines/layout-grouped.ts"() {
30322
31204
  "use strict";
30323
31205
  init_layout5();
30324
- NODESEP = 50;
30325
- RANKSEP = 60;
30326
- DUMMY_THICK = 10;
30327
- MARGIN3 = 40;
30328
- W_REAL_REAL = 1;
30329
- W_REAL_DUMMY = 2;
30330
- W_DUMMY_DUMMY = 8;
31206
+ NODESEP2 = 50;
31207
+ RANKSEP2 = 60;
31208
+ DUMMY_THICK2 = 10;
31209
+ MARGIN4 = 40;
31210
+ GROUP_PAD = 16;
31211
+ GROUP_LABEL_ZONE2 = 32;
31212
+ GROUP_GAP = 24;
31213
+ TIER_GAP = GROUP_LABEL_ZONE2 + 2 * GROUP_PAD;
31214
+ W_REAL_REAL2 = 1;
31215
+ W_REAL_DUMMY2 = 2;
31216
+ W_DUMMY_DUMMY2 = 8;
30331
31217
  }
30332
31218
  });
30333
31219
 
@@ -30348,7 +31234,7 @@ __export(layout_search_exports, {
30348
31234
  });
30349
31235
  import dagre4 from "@dagrejs/dagre";
30350
31236
  import { line as d3line, curveBasis as curveBasis5 } from "d3-shape";
30351
- function rng2(s) {
31237
+ function rng3(s) {
30352
31238
  return () => {
30353
31239
  s |= 0;
30354
31240
  s = s + 1831565813 | 0;
@@ -30703,7 +31589,7 @@ function detectGroupOverlaps(layout, opts) {
30703
31589
  const contains = (outer, inner) => outer.l <= inner.l + margin && outer.r >= inner.r - margin && outer.t <= inner.t + margin && outer.b >= inner.b - margin;
30704
31590
  const rend = raw.map((a, i) => {
30705
31591
  const parent = raw.some((b, j) => j !== i && contains(a, b));
30706
- return parent ? { ...a, t: a.t - GROUP_LABEL_ZONE2 } : a;
31592
+ return parent ? { ...a, t: a.t - GROUP_LABEL_ZONE3 } : a;
30707
31593
  });
30708
31594
  const hit = /* @__PURE__ */ new Set();
30709
31595
  for (let i = 0; i < raw.length; i++)
@@ -30730,7 +31616,7 @@ function countGroupOverlaps(layout, opts) {
30730
31616
  const contains = (outer, inner) => outer.l <= inner.l + margin && outer.r >= inner.r - margin && outer.t <= inner.t + margin && outer.b >= inner.b - margin;
30731
31617
  const rend = raw.map((a, i) => {
30732
31618
  const parent = raw.some((b, j) => j !== i && contains(a, b));
30733
- return parent ? { ...a, t: a.t - GROUP_LABEL_ZONE2 } : a;
31619
+ return parent ? { ...a, t: a.t - GROUP_LABEL_ZONE3 } : a;
30734
31620
  });
30735
31621
  let count = 0;
30736
31622
  for (let i = 0; i < raw.length; i++)
@@ -30773,7 +31659,7 @@ function separateGroupBands(layout, parsed) {
30773
31659
  let lo = (axis === "y" ? g.y : g.x) - half2;
30774
31660
  const hi = (axis === "y" ? g.y : g.x) + half2;
30775
31661
  const isParent = parsed.groups.some((c) => c.parentGroup === label);
30776
- if (axis === "y" && isParent) lo -= GROUP_LABEL_ZONE2;
31662
+ if (axis === "y" && isParent) lo -= GROUP_LABEL_ZONE3;
30777
31663
  return { label, lo, hi, c: (lo + hi) / 2 };
30778
31664
  });
30779
31665
  bands.sort((a, b) => a.c - b.c);
@@ -30844,7 +31730,7 @@ function separateGroupBands(layout, parsed) {
30844
31730
  acc(n.x + n.width / 2, n.y + n.height / 2);
30845
31731
  }
30846
31732
  for (const g of groups) {
30847
- acc(g.x - g.width / 2, g.y - g.height / 2 - GROUP_LABEL_ZONE2);
31733
+ acc(g.x - g.width / 2, g.y - g.height / 2 - GROUP_LABEL_ZONE3);
30848
31734
  acc(g.x + g.width / 2, g.y + g.height / 2);
30849
31735
  }
30850
31736
  for (const e of edges) for (const p of e.points) acc(p.x, p.y);
@@ -30927,6 +31813,7 @@ function edgeLength(layout) {
30927
31813
  }
30928
31814
  async function layoutBoxesAndLinesSearch(parsed, collapseInfo, opts) {
30929
31815
  const hideDescriptions = opts?.hideDescriptions ?? false;
31816
+ const reserveEdgeLabels = opts?.reserveEdgeLabels ?? false;
30930
31817
  const onProgress = opts?.onProgress;
30931
31818
  const tick = onProgress ? () => new Promise((r) => setTimeout(r)) : () => void 0;
30932
31819
  const collapsedGroupLabels = /* @__PURE__ */ new Set();
@@ -31108,7 +31995,7 @@ async function layoutBoxesAndLinesSearch(parsed, collapseInfo, opts) {
31108
31995
  }
31109
31996
  ];
31110
31997
  });
31111
- const GROUP_PAD = 16;
31998
+ const GROUP_PAD2 = 16;
31112
31999
  const nodeByLabel = new Map(nodes.map((n2) => [n2.label, n2]));
31113
32000
  const groups = [];
31114
32001
  for (const grp of parsed.groups) {
@@ -31122,10 +32009,10 @@ async function layoutBoxesAndLinesSearch(parsed, collapseInfo, opts) {
31122
32009
  gy1 = Math.max(gy1, n2.y + n2.height / 2);
31123
32010
  }
31124
32011
  if (!Number.isFinite(gx0)) continue;
31125
- const x0 = gx0 - GROUP_PAD;
31126
- const x1 = gx1 + GROUP_PAD;
31127
- const y0 = gy0 - GROUP_LABEL_ZONE2;
31128
- const y1 = gy1 + GROUP_PAD;
32012
+ const x0 = gx0 - GROUP_PAD2;
32013
+ const x1 = gx1 + GROUP_PAD2;
32014
+ const y0 = gy0 - GROUP_LABEL_ZONE3;
32015
+ const y1 = gy1 + GROUP_PAD2;
31129
32016
  groups.push({
31130
32017
  label: grp.label,
31131
32018
  lineNumber: grp.lineNumber,
@@ -31182,7 +32069,7 @@ async function layoutBoxesAndLinesSearch(parsed, collapseInfo, opts) {
31182
32069
  };
31183
32070
  }
31184
32071
  function place(cfg) {
31185
- const r = cfg.seed === void 0 ? null : rng2(cfg.seed + 1);
32072
+ const r = cfg.seed === void 0 ? null : rng3(cfg.seed + 1);
31186
32073
  const ord = (a) => r ? shuffle(a, r) : a.slice();
31187
32074
  const g = new dagre4.graphlib.Graph({ compound: true, multigraph: true });
31188
32075
  g.setGraph({
@@ -31219,8 +32106,14 @@ async function layoutBoxesAndLinesSearch(parsed, collapseInfo, opts) {
31219
32106
  for (const e of ord(parsed.edges)) {
31220
32107
  const s = srcRep(e.source);
31221
32108
  const t = tgtRep(e.target);
31222
- if (s !== t && g.hasNode(s) && g.hasNode(t))
31223
- g.setEdge(s, t, {}, edgeKey(e));
32109
+ if (s !== t && g.hasNode(s) && g.hasNode(t)) {
32110
+ let edgeLabel = {};
32111
+ if (reserveEdgeLabels && e.label) {
32112
+ const m = measureEdgeLabel(e.label, EDGE_LABEL_FONT_SIZE6);
32113
+ edgeLabel = { width: m.width, height: m.height, labelpos: "c" };
32114
+ }
32115
+ g.setEdge(s, t, edgeLabel, edgeKey(e));
32116
+ }
31224
32117
  }
31225
32118
  dagre4.layout(g);
31226
32119
  const nodes = parsed.nodes.map((n2) => {
@@ -31369,8 +32262,15 @@ async function layoutBoxesAndLinesSearch(parsed, collapseInfo, opts) {
31369
32262
  layered = layeredCandidates(parsed, sizes);
31370
32263
  } catch {
31371
32264
  }
32265
+ if (parsed.groups.length > 0 && collapsedGroupLabels.size === 0) {
32266
+ try {
32267
+ layered = layered.concat(groupedTierCandidates(parsed, sizes));
32268
+ } catch {
32269
+ }
32270
+ }
31372
32271
  const fastKey = /* @__PURE__ */ new Map();
31373
- for (const lay of pool) fastKey.set(lay, objective(lay, countCrossingsFast(lay)));
32272
+ for (const lay of pool)
32273
+ fastKey.set(lay, objective(lay, countCrossingsFast(lay)));
31374
32274
  pool.sort((a, b) => fastKey.get(a) - fastKey.get(b));
31375
32275
  const refineK = Math.min(REFINE_K, pool.length);
31376
32276
  let best = pool[0];
@@ -31435,12 +32335,14 @@ async function layoutBoxesAndLinesSearch(parsed, collapseInfo, opts) {
31435
32335
  }
31436
32336
  return best;
31437
32337
  }
31438
- var DEFAULT_LAMBDA, ESCALATE_THRESHOLD, ESCALATE_MAX_N, ESCALATE_SEEDS, ESCALATE_REFINE, splineGen, PATH_TOKEN_RE, FLAT_CACHE, GROUP_LABEL_ZONE2;
32338
+ var DEFAULT_LAMBDA, ESCALATE_THRESHOLD, ESCALATE_MAX_N, ESCALATE_SEEDS, ESCALATE_REFINE, splineGen, PATH_TOKEN_RE, FLAT_CACHE, GROUP_LABEL_ZONE3;
31439
32339
  var init_layout_search = __esm({
31440
32340
  "src/boxes-and-lines/layout-search.ts"() {
31441
32341
  "use strict";
31442
32342
  init_layout5();
31443
32343
  init_layout_layered();
32344
+ init_layout_grouped();
32345
+ init_label_placement();
31444
32346
  DEFAULT_LAMBDA = 4;
31445
32347
  ESCALATE_THRESHOLD = 4;
31446
32348
  ESCALATE_MAX_N = 45;
@@ -31449,7 +32351,7 @@ var init_layout_search = __esm({
31449
32351
  splineGen = d3line().x((d) => d.x).y((d) => d.y).curve(curveBasis5);
31450
32352
  PATH_TOKEN_RE = /[MLQC]|-?\d*\.?\d+(?:e-?\d+)?/gi;
31451
32353
  FLAT_CACHE = /* @__PURE__ */ new WeakMap();
31452
- GROUP_LABEL_ZONE2 = 32;
32354
+ GROUP_LABEL_ZONE3 = 32;
31453
32355
  }
31454
32356
  });
31455
32357
 
@@ -31499,9 +32401,9 @@ function estimateLabelLines(label, nodeWidth = NODE_WIDTH) {
31499
32401
  current = word;
31500
32402
  }
31501
32403
  }
31502
- if (lines <= MAX_LABEL_LINES) return Math.min(lines, MAX_LABEL_LINES);
32404
+ if (lines <= MAX_LABEL_LINES2) return Math.min(lines, MAX_LABEL_LINES2);
31503
32405
  }
31504
- return MAX_LABEL_LINES;
32406
+ return MAX_LABEL_LINES2;
31505
32407
  }
31506
32408
  function computeNodeSize(node, reserveValueRow) {
31507
32409
  if (!node.description || node.description.length === 0) {
@@ -31509,7 +32411,7 @@ function computeNodeSize(node, reserveValueRow) {
31509
32411
  }
31510
32412
  const w = DESC_NODE_WIDTH;
31511
32413
  const labelLines = estimateLabelLines(node.label, w);
31512
- const labelHeight = labelLines * 13 * LABEL_LINE_HEIGHT + LABEL_PAD;
32414
+ const labelHeight = labelLines * 13 * LABEL_LINE_HEIGHT2 + LABEL_PAD;
31513
32415
  const maxTextWidth = w - 24;
31514
32416
  let totalRenderedLines = 0;
31515
32417
  for (const line11 of node.description) {
@@ -31533,7 +32435,7 @@ function computeNodeSize(node, reserveValueRow) {
31533
32435
  }
31534
32436
  async function layoutBoxesAndLines(parsed, collapseInfo, layoutOptions) {
31535
32437
  const { layoutBoxesAndLinesSearch: layoutBoxesAndLinesSearch2 } = await Promise.resolve().then(() => (init_layout_search(), layout_search_exports));
31536
- const searched = await layoutBoxesAndLinesSearch2(parsed, collapseInfo, {
32438
+ const searchOpts = {
31537
32439
  ...layoutOptions?.hideDescriptions !== void 0 && {
31538
32440
  hideDescriptions: layoutOptions.hideDescriptions
31539
32441
  },
@@ -31543,12 +32445,23 @@ async function layoutBoxesAndLines(parsed, collapseInfo, layoutOptions) {
31543
32445
  ...layoutOptions?.onProgress !== void 0 && {
31544
32446
  onProgress: layoutOptions.onProgress
31545
32447
  }
31546
- });
31547
- return attachNotes(
31548
- applyParallelEdgeOffsets(searched),
32448
+ };
32449
+ const searched = await layoutBoxesAndLinesSearch2(
31549
32450
  parsed,
31550
- layoutOptions?.collapsedNotes
32451
+ collapseInfo,
32452
+ searchOpts
31551
32453
  );
32454
+ let placed = placeEdgeLabels(applyParallelEdgeOffsets(searched));
32455
+ if (placed.unresolved.length > 0) {
32456
+ const relaid = await layoutBoxesAndLinesSearch2(parsed, collapseInfo, {
32457
+ ...searchOpts,
32458
+ reserveEdgeLabels: true
32459
+ });
32460
+ const relaidPlaced = placeEdgeLabels(applyParallelEdgeOffsets(relaid));
32461
+ if (relaidPlaced.unresolved.length < placed.unresolved.length)
32462
+ placed = relaidPlaced;
32463
+ }
32464
+ return attachNotes(placed.layout, parsed, layoutOptions?.collapsedNotes);
31552
32465
  }
31553
32466
  function attachNotes(layout, parsed, collapsedNotes) {
31554
32467
  const notesSuppressed = parsed.options?.["no-notes"] === "on";
@@ -31616,6 +32529,7 @@ function attachNotes(layout, parsed, collapsedNotes) {
31616
32529
  points: e.points.map((pt) => ({ x: pt.x + shiftX, y: pt.y + shiftY })),
31617
32530
  ...e.labelX !== void 0 && { labelX: e.labelX + shiftX },
31618
32531
  ...e.labelY !== void 0 && { labelY: e.labelY + shiftY }
32532
+ // labelWidth/labelHeight/labelLines are shift-invariant — carried via spread.
31619
32533
  })) : layout.edges;
31620
32534
  const finalGroups = shifted ? layout.groups.map((grp) => ({
31621
32535
  ...grp,
@@ -31626,8 +32540,8 @@ function attachNotes(layout, parsed, collapsedNotes) {
31626
32540
  nodes: finalNodes,
31627
32541
  edges: finalEdges,
31628
32542
  groups: finalGroups,
31629
- width: bbMaxX + shiftX + MARGIN4,
31630
- height: bbMaxY + shiftY + MARGIN4
32543
+ width: bbMaxX + shiftX + MARGIN5,
32544
+ height: bbMaxY + shiftY + MARGIN5
31631
32545
  };
31632
32546
  }
31633
32547
  function applyParallelEdgeOffsets(layout) {
@@ -31660,13 +32574,14 @@ function applyParallelEdgeOffsets(layout) {
31660
32574
  }))
31661
32575
  };
31662
32576
  }
31663
- var MARGIN4, MAX_PARALLEL_EDGES, PARALLEL_SPACING, PHI, NODE_HEIGHT, NODE_WIDTH, DESC_NODE_WIDTH, DESC_FONT_SIZE2, DESC_LINE_HEIGHT2, DESC_PADDING, SEPARATOR_GAP4, MAX_DESC_LINES2, MAX_LABEL_LINES, LABEL_LINE_HEIGHT, LABEL_PAD, VALUE_ROW_FONT, VALUE_ROW_H;
32577
+ 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_LINES2, LABEL_LINE_HEIGHT2, LABEL_PAD, VALUE_ROW_FONT, VALUE_ROW_H;
31664
32578
  var init_layout5 = __esm({
31665
32579
  "src/boxes-and-lines/layout.ts"() {
31666
32580
  "use strict";
31667
32581
  init_text_measure();
32582
+ init_label_placement();
31668
32583
  init_notes();
31669
- MARGIN4 = 40;
32584
+ MARGIN5 = 40;
31670
32585
  MAX_PARALLEL_EDGES = 5;
31671
32586
  PARALLEL_SPACING = 22;
31672
32587
  PHI = 1.618;
@@ -31678,8 +32593,8 @@ var init_layout5 = __esm({
31678
32593
  DESC_PADDING = 8;
31679
32594
  SEPARATOR_GAP4 = 4;
31680
32595
  MAX_DESC_LINES2 = 6;
31681
- MAX_LABEL_LINES = 3;
31682
- LABEL_LINE_HEIGHT = 1.3;
32596
+ MAX_LABEL_LINES2 = 3;
32597
+ LABEL_LINE_HEIGHT2 = 1.3;
31683
32598
  LABEL_PAD = 12;
31684
32599
  VALUE_ROW_FONT = 11;
31685
32600
  VALUE_ROW_H = SEPARATOR_GAP4 + VALUE_ROW_FONT * DESC_LINE_HEIGHT2 + DESC_PADDING;
@@ -31698,7 +32613,7 @@ function tokenize(text) {
31698
32613
  return tokens;
31699
32614
  }
31700
32615
  function tryWrap(tokens, maxWidth, fontSize, maxLines) {
31701
- const availWidth = maxWidth - H_PAD;
32616
+ const availWidth = maxWidth - H_PAD2;
31702
32617
  const lines = [];
31703
32618
  let currentLine = "";
31704
32619
  for (const token of tokens) {
@@ -31723,7 +32638,7 @@ function tryWrap(tokens, maxWidth, fontSize, maxLines) {
31723
32638
  return lines;
31724
32639
  }
31725
32640
  function truncateLastLine(lines, maxWidth, fontSize) {
31726
- const availWidth = maxWidth - H_PAD;
32641
+ const availWidth = maxWidth - H_PAD2;
31727
32642
  const result = [...lines];
31728
32643
  const last = result[result.length - 1];
31729
32644
  if (measureText(last, fontSize) > availWidth) {
@@ -31731,7 +32646,7 @@ function truncateLastLine(lines, maxWidth, fontSize) {
31731
32646
  }
31732
32647
  return result;
31733
32648
  }
31734
- function wrapText(text, maxWidth, baseFontSize, minFontSize, maxLines = MAX_LABEL_LINES2) {
32649
+ function wrapText(text, maxWidth, baseFontSize, minFontSize, maxLines = MAX_LABEL_LINES3) {
31735
32650
  if (!text) return { lines: [""], fontSize: baseFontSize };
31736
32651
  const tokens = tokenize(text);
31737
32652
  for (let fs = baseFontSize; fs >= minFontSize; fs--) {
@@ -31746,7 +32661,7 @@ function wrapText(text, maxWidth, baseFontSize, minFontSize, maxLines = MAX_LABE
31746
32661
  if (allLines.length > maxLines) {
31747
32662
  const last = truncated[truncated.length - 1];
31748
32663
  if (!last.endsWith("\u2026")) {
31749
- const availWidth = maxWidth - H_PAD;
32664
+ const availWidth = maxWidth - H_PAD2;
31750
32665
  if (measureText(last + "\u2026", minFontSize) > availWidth) {
31751
32666
  truncated[truncated.length - 1] = truncateText(
31752
32667
  last,
@@ -31773,7 +32688,7 @@ function computeNodeText(label, description, depth, nodeWidth, hideDescriptions)
31773
32688
  nodeWidth,
31774
32689
  baseFontSize,
31775
32690
  MIN_FONT_SIZE,
31776
- MAX_LABEL_LINES2
32691
+ MAX_LABEL_LINES3
31777
32692
  );
31778
32693
  const descLines = [];
31779
32694
  let descFontSize = DESC_FONT_SIZE3;
@@ -31801,14 +32716,14 @@ function computeNodeText(label, description, depth, nodeWidth, hideDescriptions)
31801
32716
  descFontSize
31802
32717
  };
31803
32718
  }
31804
- var H_PAD, MAX_LABEL_LINES2, MAX_DESC_LINES3, ROOT_FONT_SIZE, MIN_FONT_SIZE, FONT_STEP, DESC_FONT_SIZE3;
32719
+ var H_PAD2, MAX_LABEL_LINES3, MAX_DESC_LINES3, ROOT_FONT_SIZE, MIN_FONT_SIZE, FONT_STEP, DESC_FONT_SIZE3;
31805
32720
  var init_text_wrap = __esm({
31806
32721
  "src/mindmap/text-wrap.ts"() {
31807
32722
  "use strict";
31808
32723
  init_description_helpers();
31809
32724
  init_text_measure();
31810
- H_PAD = 16;
31811
- MAX_LABEL_LINES2 = 3;
32725
+ H_PAD2 = 16;
32726
+ MAX_LABEL_LINES3 = 3;
31812
32727
  MAX_DESC_LINES3 = 2;
31813
32728
  ROOT_FONT_SIZE = 17;
31814
32729
  MIN_FONT_SIZE = 9;
@@ -31837,7 +32752,7 @@ function layoutMindmap(parsed, _palette, options) {
31837
32752
  leafWidth: ctx.structural(LEAF_WIDTH),
31838
32753
  hGap: ctx.aesthetic(H_GAP2),
31839
32754
  vGap: ctx.aesthetic(V_GAP2),
31840
- margin: ctx.aesthetic(MARGIN5),
32755
+ margin: ctx.aesthetic(MARGIN6),
31841
32756
  multiRootGap: ctx.aesthetic(MULTI_ROOT_GAP)
31842
32757
  };
31843
32758
  populateDepthCache(roots);
@@ -32195,7 +33110,7 @@ function nodeHeightS(node, hideDescriptions, sc) {
32195
33110
  hideDescriptions
32196
33111
  );
32197
33112
  const labelLineCount = text.labelLines.length;
32198
- const labelH = labelLineCount <= 1 ? SINGLE_LABEL_HEIGHT : LABEL_LINE_HEIGHT2 * labelLineCount;
33113
+ const labelH = labelLineCount <= 1 ? SINGLE_LABEL_HEIGHT : LABEL_LINE_HEIGHT3 * labelLineCount;
32199
33114
  let h = labelH + NODE_V_PAD;
32200
33115
  if (text.descLines.length > 0) {
32201
33116
  h += DESC_LINE_HEIGHT3 * text.descLines.length + 4;
@@ -32215,7 +33130,7 @@ function populateDepthCache(roots) {
32215
33130
  };
32216
33131
  walk(roots, 0);
32217
33132
  }
32218
- var ROOT_WIDTH, DEPTH1_WIDTH, LEAF_WIDTH, SINGLE_LABEL_HEIGHT, LABEL_LINE_HEIGHT2, DESC_LINE_HEIGHT3, NODE_V_PAD, H_GAP2, V_GAP2, MARGIN5, MULTI_ROOT_GAP, nodeDepthCache;
33133
+ var ROOT_WIDTH, DEPTH1_WIDTH, LEAF_WIDTH, SINGLE_LABEL_HEIGHT, LABEL_LINE_HEIGHT3, DESC_LINE_HEIGHT3, NODE_V_PAD, H_GAP2, V_GAP2, MARGIN6, MULTI_ROOT_GAP, nodeDepthCache;
32219
33134
  var init_layout6 = __esm({
32220
33135
  "src/mindmap/layout.ts"() {
32221
33136
  "use strict";
@@ -32226,12 +33141,12 @@ var init_layout6 = __esm({
32226
33141
  DEPTH1_WIDTH = 150;
32227
33142
  LEAF_WIDTH = 120;
32228
33143
  SINGLE_LABEL_HEIGHT = 28;
32229
- LABEL_LINE_HEIGHT2 = 18;
33144
+ LABEL_LINE_HEIGHT3 = 18;
32230
33145
  DESC_LINE_HEIGHT3 = 14;
32231
33146
  NODE_V_PAD = 10;
32232
33147
  H_GAP2 = 40;
32233
33148
  V_GAP2 = 12;
32234
- MARGIN5 = 40;
33149
+ MARGIN6 = 40;
32235
33150
  MULTI_ROOT_GAP = 60;
32236
33151
  nodeDepthCache = /* @__PURE__ */ new Map();
32237
33152
  }
@@ -32489,7 +33404,7 @@ function renderMindmap(container, parsed, layout, palette, isDark, onClickItem,
32489
33404
  );
32490
33405
  const nodeG = mainG.append("g").attr("class", "mindmap-node").attr("data-line-number", node.lineNumber);
32491
33406
  if (activeTagGroup) {
32492
- const tagKey = activeTagGroup.toLowerCase();
33407
+ const tagKey = tagAttrKey(activeTagGroup);
32493
33408
  const metaValue = node.metadata[tagKey];
32494
33409
  if (metaValue) {
32495
33410
  nodeG.attr(`data-tag-${tagKey}`, metaValue.toLowerCase());
@@ -32518,18 +33433,18 @@ function renderMindmap(container, parsed, layout, palette, isDark, onClickItem,
32518
33433
  descFontSize
32519
33434
  } = textLayout;
32520
33435
  const labelLineCount = labelLines.length;
32521
- const labelZoneH = labelLineCount <= 1 ? SINGLE_LABEL_HEIGHT2 : LABEL_LINE_HEIGHT3 * labelLineCount;
33436
+ const labelZoneH = labelLineCount <= 1 ? SINGLE_LABEL_HEIGHT2 : LABEL_LINE_HEIGHT4 * labelLineCount;
32522
33437
  const labelZoneHeight = showDesc ? labelZoneH : node.height;
32523
33438
  const centerX = node.x + node.width / 2;
32524
33439
  if (labelLineCount <= 1) {
32525
33440
  const labelY = node.y + labelZoneHeight / 2 + fontSize * 0.35;
32526
33441
  nodeG.append("text").attr("x", centerX).attr("y", labelY).attr("text-anchor", "middle").attr("font-size", fontSize).attr("font-weight", isRoot ? "bold" : "normal").attr("fill", onFillText).text(labelLines[0]);
32527
33442
  } else {
32528
- const blockH = LABEL_LINE_HEIGHT3 * (labelLineCount - 1);
33443
+ const blockH = LABEL_LINE_HEIGHT4 * (labelLineCount - 1);
32529
33444
  const firstBaselineY = node.y + labelZoneHeight / 2 - blockH / 2 + fontSize * 0.35;
32530
33445
  const textEl = nodeG.append("text").attr("x", centerX).attr("text-anchor", "middle").attr("font-size", fontSize).attr("font-weight", isRoot ? "bold" : "normal").attr("fill", onFillText);
32531
33446
  for (let i = 0; i < labelLines.length; i++) {
32532
- textEl.append("tspan").attr("x", centerX).attr("y", firstBaselineY + i * LABEL_LINE_HEIGHT3).text(labelLines[i]);
33447
+ textEl.append("tspan").attr("x", centerX).attr("y", firstBaselineY + i * LABEL_LINE_HEIGHT4).text(labelLines[i]);
32533
33448
  }
32534
33449
  }
32535
33450
  if (labelLines.length > 1 || labelLines[0] !== node.label) {
@@ -32604,10 +33519,11 @@ function renderMindmapForExport(content, theme, palette) {
32604
33519
  return extractExportSvg(container, theme);
32605
33520
  });
32606
33521
  }
32607
- var DIAGRAM_PADDING7, TITLE_HEIGHT4, SINGLE_LABEL_HEIGHT2, LABEL_LINE_HEIGHT3, DESC_LINE_HEIGHT4, NODE_RADIUS, ROOT_STROKE_WIDTH, DEPTH_COLOR_KEYS;
33522
+ var DIAGRAM_PADDING7, TITLE_HEIGHT4, SINGLE_LABEL_HEIGHT2, LABEL_LINE_HEIGHT4, DESC_LINE_HEIGHT4, NODE_RADIUS, ROOT_STROKE_WIDTH, DEPTH_COLOR_KEYS;
32608
33523
  var init_renderer7 = __esm({
32609
33524
  "src/mindmap/renderer.ts"() {
32610
33525
  "use strict";
33526
+ init_tag_groups();
32611
33527
  init_fonts();
32612
33528
  init_export_container();
32613
33529
  init_color_utils();
@@ -32625,7 +33541,7 @@ var init_renderer7 = __esm({
32625
33541
  DIAGRAM_PADDING7 = 20;
32626
33542
  TITLE_HEIGHT4 = 30;
32627
33543
  SINGLE_LABEL_HEIGHT2 = 28;
32628
- LABEL_LINE_HEIGHT3 = 18;
33544
+ LABEL_LINE_HEIGHT4 = 18;
32629
33545
  DESC_LINE_HEIGHT4 = 14;
32630
33546
  NODE_RADIUS = 6;
32631
33547
  ROOT_STROKE_WIDTH = 2.5;
@@ -33818,7 +34734,7 @@ function resolveNodeColor4(el, tagGroups, activeGroupName, ancestors) {
33818
34734
  (g) => g.name.toLowerCase() === activeGroupName.toLowerCase()
33819
34735
  );
33820
34736
  if (!group) return void 0;
33821
- const key = group.name.toLowerCase();
34737
+ const key = tagAttrKey(group.name);
33822
34738
  let metaValue = el.metadata[key];
33823
34739
  if (!metaValue && ancestors) {
33824
34740
  for (const ancestor of ancestors) {
@@ -34020,8 +34936,8 @@ function layoutC4Context(parsed, activeTagGroup) {
34020
34936
  }
34021
34937
  }
34022
34938
  if (nodes.length > 0) {
34023
- const shiftX = MARGIN6 - minX;
34024
- const shiftY = MARGIN6 - minY;
34939
+ const shiftX = MARGIN7 - minX;
34940
+ const shiftY = MARGIN7 - minY;
34025
34941
  for (const node of nodes) {
34026
34942
  node.x += shiftX;
34027
34943
  node.y += shiftY;
@@ -34033,12 +34949,12 @@ function layoutC4Context(parsed, activeTagGroup) {
34033
34949
  }
34034
34950
  }
34035
34951
  }
34036
- let totalWidth = nodes.length > 0 ? maxX - minX + MARGIN6 * 2 : 0;
34037
- let totalHeight = nodes.length > 0 ? maxY - minY + MARGIN6 * 2 : 0;
34952
+ let totalWidth = nodes.length > 0 ? maxX - minX + MARGIN7 * 2 : 0;
34953
+ let totalHeight = nodes.length > 0 ? maxY - minY + MARGIN7 * 2 : 0;
34038
34954
  const legendGroups = computeLegendGroups3(parsed.tagGroups);
34039
34955
  if (legendGroups.length > 0) {
34040
- const legendY = totalHeight + MARGIN6;
34041
- let legendX = MARGIN6;
34956
+ const legendY = totalHeight + MARGIN7;
34957
+ let legendX = MARGIN7;
34042
34958
  for (const lg of legendGroups) {
34043
34959
  lg.x = legendX;
34044
34960
  lg.y = legendY;
@@ -34370,8 +35286,8 @@ function layoutC4Containers(parsed, systemName, activeTagGroup) {
34370
35286
  if (pt.y > maxY) maxY = pt.y;
34371
35287
  }
34372
35288
  }
34373
- const shiftX = MARGIN6 - minX;
34374
- const shiftY = MARGIN6 - minY;
35289
+ const shiftX = MARGIN7 - minX;
35290
+ const shiftY = MARGIN7 - minY;
34375
35291
  for (const node of nodes) {
34376
35292
  node.x += shiftX;
34377
35293
  node.y += shiftY;
@@ -34388,12 +35304,12 @@ function layoutC4Containers(parsed, systemName, activeTagGroup) {
34388
35304
  pt.y += shiftY;
34389
35305
  }
34390
35306
  }
34391
- let totalWidth = maxX - minX + MARGIN6 * 2;
34392
- let totalHeight = maxY - minY + MARGIN6 * 2;
35307
+ let totalWidth = maxX - minX + MARGIN7 * 2;
35308
+ let totalHeight = maxY - minY + MARGIN7 * 2;
34393
35309
  const legendGroups = computeLegendGroups3(parsed.tagGroups);
34394
35310
  if (legendGroups.length > 0) {
34395
- const legendY = totalHeight + MARGIN6;
34396
- let legendX = MARGIN6;
35311
+ const legendY = totalHeight + MARGIN7;
35312
+ let legendX = MARGIN7;
34397
35313
  for (const lg of legendGroups) {
34398
35314
  lg.x = legendX;
34399
35315
  lg.y = legendY;
@@ -34785,8 +35701,8 @@ function layoutC4Components(parsed, systemName, containerName, activeTagGroup) {
34785
35701
  if (pt.y > maxY) maxY = pt.y;
34786
35702
  }
34787
35703
  }
34788
- const shiftX = MARGIN6 - minX;
34789
- const shiftY = MARGIN6 - minY;
35704
+ const shiftX = MARGIN7 - minX;
35705
+ const shiftY = MARGIN7 - minY;
34790
35706
  for (const node of nodes) {
34791
35707
  node.x += shiftX;
34792
35708
  node.y += shiftY;
@@ -34803,12 +35719,12 @@ function layoutC4Components(parsed, systemName, containerName, activeTagGroup) {
34803
35719
  pt.y += shiftY;
34804
35720
  }
34805
35721
  }
34806
- let totalWidth = maxX - minX + MARGIN6 * 2;
34807
- let totalHeight = maxY - minY + MARGIN6 * 2;
35722
+ let totalWidth = maxX - minX + MARGIN7 * 2;
35723
+ let totalHeight = maxY - minY + MARGIN7 * 2;
34808
35724
  const legendGroups = computeLegendGroups3(parsed.tagGroups);
34809
35725
  if (legendGroups.length > 0) {
34810
- const legendY = totalHeight + MARGIN6;
34811
- let legendX = MARGIN6;
35726
+ const legendY = totalHeight + MARGIN7;
35727
+ let legendX = MARGIN7;
34812
35728
  for (const lg of legendGroups) {
34813
35729
  lg.x = legendX;
34814
35730
  lg.y = legendY;
@@ -35096,8 +36012,8 @@ function layoutC4Deployment(parsed, activeTagGroup) {
35096
36012
  if (pt.y > maxY) maxY = pt.y;
35097
36013
  }
35098
36014
  }
35099
- const shiftX = MARGIN6 - minX;
35100
- const shiftY = MARGIN6 - minY;
36015
+ const shiftX = MARGIN7 - minX;
36016
+ const shiftY = MARGIN7 - minY;
35101
36017
  for (const node of nodes) {
35102
36018
  node.x += shiftX;
35103
36019
  node.y += shiftY;
@@ -35112,12 +36028,12 @@ function layoutC4Deployment(parsed, activeTagGroup) {
35112
36028
  pt.y += shiftY;
35113
36029
  }
35114
36030
  }
35115
- let totalWidth = maxX - minX + MARGIN6 * 2;
35116
- let totalHeight = maxY - minY + MARGIN6 * 2;
36031
+ let totalWidth = maxX - minX + MARGIN7 * 2;
36032
+ let totalHeight = maxY - minY + MARGIN7 * 2;
35117
36033
  const legendGroups = computeLegendGroups3(parsed.tagGroups);
35118
36034
  if (legendGroups.length > 0) {
35119
- const legendY = totalHeight + MARGIN6;
35120
- let legendX = MARGIN6;
36035
+ const legendY = totalHeight + MARGIN7;
36036
+ let legendX = MARGIN7;
35121
36037
  for (const lg of legendGroups) {
35122
36038
  lg.x = legendX;
35123
36039
  lg.y = legendY;
@@ -35137,10 +36053,11 @@ function layoutC4Deployment(parsed, activeTagGroup) {
35137
36053
  height: totalHeight
35138
36054
  };
35139
36055
  }
35140
- var gNode, gEdge, MIN_NODE_WIDTH, MAX_NODE_WIDTH, TYPE_LABEL_HEIGHT, DIVIDER_GAP, NAME_HEIGHT, NAME_FONT_SIZE, DESC_LINE_HEIGHT5, DESC_FONT_SIZE4, CARD_V_PAD3, CARD_H_PAD3, META_LINE_HEIGHT4, META_FONT_SIZE4, MARGIN6, BOUNDARY_PAD, GROUP_BOUNDARY_PAD, EDGE_NODE_COLLISION_WEIGHT, META_EXCLUDE_KEYS;
36056
+ var gNode, gEdge, MIN_NODE_WIDTH, MAX_NODE_WIDTH, TYPE_LABEL_HEIGHT, DIVIDER_GAP, NAME_HEIGHT, NAME_FONT_SIZE, DESC_LINE_HEIGHT5, DESC_FONT_SIZE4, CARD_V_PAD3, CARD_H_PAD3, META_LINE_HEIGHT4, META_FONT_SIZE4, MARGIN7, BOUNDARY_PAD, GROUP_BOUNDARY_PAD, EDGE_NODE_COLLISION_WEIGHT, META_EXCLUDE_KEYS;
35141
36057
  var init_layout8 = __esm({
35142
36058
  "src/c4/layout.ts"() {
35143
36059
  "use strict";
36060
+ init_tag_groups();
35144
36061
  init_legend_constants();
35145
36062
  init_text_measure();
35146
36063
  gNode = (g, name) => g.node(name);
@@ -35157,7 +36074,7 @@ var init_layout8 = __esm({
35157
36074
  CARD_H_PAD3 = 20;
35158
36075
  META_LINE_HEIGHT4 = 16;
35159
36076
  META_FONT_SIZE4 = 11;
35160
- MARGIN6 = 40;
36077
+ MARGIN7 = 40;
35161
36078
  BOUNDARY_PAD = 40;
35162
36079
  GROUP_BOUNDARY_PAD = 24;
35163
36080
  EDGE_NODE_COLLISION_WEIGHT = 5e3;
@@ -35293,7 +36210,7 @@ function renderC4Context(container, parsed, layout, palette, isDark, onClickItem
35293
36210
  const labelText = edge.label ?? "";
35294
36211
  const techText = edge.technology ? `[${edge.technology}]` : "";
35295
36212
  const textW = Math.max(
35296
- measureText(labelText, EDGE_LABEL_FONT_SIZE6),
36213
+ measureText(labelText, EDGE_LABEL_FONT_SIZE7),
35297
36214
  measureText(techText, TECH_FONT_SIZE)
35298
36215
  );
35299
36216
  const bgW = textW + 12;
@@ -35304,7 +36221,7 @@ function renderC4Context(container, parsed, layout, palette, isDark, onClickItem
35304
36221
  textY = midPt.y - 4;
35305
36222
  }
35306
36223
  if (labelText) {
35307
- edgeG.append("text").attr("x", midPt.x).attr("y", textY + 4).attr("text-anchor", "middle").attr("fill", edgeColor3).attr("font-size", EDGE_LABEL_FONT_SIZE6).attr("class", "c4-edge-label").text(labelText);
36224
+ edgeG.append("text").attr("x", midPt.x).attr("y", textY + 4).attr("text-anchor", "middle").attr("fill", edgeColor3).attr("font-size", EDGE_LABEL_FONT_SIZE7).attr("class", "c4-edge-label").text(labelText);
35308
36225
  }
35309
36226
  if (techText) {
35310
36227
  edgeG.append("text").attr("x", midPt.x).attr("y", labelText ? textY + 18 : textY + 4).attr("text-anchor", "middle").attr("fill", edgeColor3).attr("font-size", TECH_FONT_SIZE).attr("font-style", "italic").attr("class", "c4-edge-tech").text(techText);
@@ -35314,7 +36231,7 @@ function renderC4Context(container, parsed, layout, palette, isDark, onClickItem
35314
36231
  for (const node of layout.nodes) {
35315
36232
  const nodeG = contentG.append("g").attr("transform", `translate(${node.x}, ${node.y})`).attr("class", "c4-card").attr("data-line-number", String(node.lineNumber)).attr("data-node-id", node.id);
35316
36233
  if (activeTagGroup) {
35317
- const tagKey = activeTagGroup.toLowerCase();
36234
+ const tagKey = tagAttrKey(activeTagGroup);
35318
36235
  const tagValue = node.metadata[tagKey];
35319
36236
  if (tagValue) {
35320
36237
  nodeG.attr(`data-tag-${tagKey}`, tagValue.toLowerCase());
@@ -35488,7 +36405,7 @@ function renderEdges(contentG, edges, palette, onClickItem, obstacleRects) {
35488
36405
  const labelText = edge.label ?? "";
35489
36406
  const techText = edge.technology ? `[${edge.technology}]` : "";
35490
36407
  const textW = Math.max(
35491
- measureText(labelText, EDGE_LABEL_FONT_SIZE6),
36408
+ measureText(labelText, EDGE_LABEL_FONT_SIZE7),
35492
36409
  measureText(techText, TECH_FONT_SIZE)
35493
36410
  );
35494
36411
  const bgW = textW + 12;
@@ -35506,7 +36423,7 @@ function renderEdges(contentG, edges, palette, onClickItem, obstacleRects) {
35506
36423
  });
35507
36424
  }
35508
36425
  }
35509
- placeEdgeLabels(pendingLabels, edges, obstacleRects);
36426
+ placeEdgeLabels2(pendingLabels, edges, obstacleRects);
35510
36427
  for (const lbl of pendingLabels) {
35511
36428
  lbl.edgeG.append("rect").attr("x", lbl.x - lbl.bgW / 2).attr("y", lbl.y - lbl.bgH / 2).attr("width", lbl.bgW).attr("height", lbl.bgH).attr("rx", 3).attr("fill", palette.bg).attr("opacity", 0.9).attr("class", "c4-edge-label-bg");
35512
36429
  let textY = lbl.y;
@@ -35514,7 +36431,7 @@ function renderEdges(contentG, edges, palette, onClickItem, obstacleRects) {
35514
36431
  textY = lbl.y - 4;
35515
36432
  }
35516
36433
  if (lbl.labelText) {
35517
- lbl.edgeG.append("text").attr("x", lbl.x).attr("y", textY + 4).attr("text-anchor", "middle").attr("fill", lbl.edgeColor).attr("font-size", EDGE_LABEL_FONT_SIZE6).attr("class", "c4-edge-label").text(lbl.labelText);
36434
+ lbl.edgeG.append("text").attr("x", lbl.x).attr("y", textY + 4).attr("text-anchor", "middle").attr("fill", lbl.edgeColor).attr("font-size", EDGE_LABEL_FONT_SIZE7).attr("class", "c4-edge-label").text(lbl.labelText);
35518
36435
  }
35519
36436
  if (lbl.techText) {
35520
36437
  lbl.edgeG.append("text").attr("x", lbl.x).attr("y", lbl.labelText ? textY + 18 : textY + 4).attr("text-anchor", "middle").attr("fill", lbl.edgeColor).attr("font-size", TECH_FONT_SIZE).attr("font-style", "italic").attr("class", "c4-edge-tech").text(lbl.techText);
@@ -35595,7 +36512,7 @@ function tangentAt(points, t) {
35595
36512
  }
35596
36513
  return { x: 0, y: 1 };
35597
36514
  }
35598
- function placeEdgeLabels(labels, edges, obstacleRects) {
36515
+ function placeEdgeLabels2(labels, edges, obstacleRects) {
35599
36516
  if (labels.length === 0) return;
35600
36517
  const allPaths = edges.map((e) => e.points);
35601
36518
  const placedRects = [];
@@ -35873,7 +36790,7 @@ function renderC4Containers(container, parsed, layout, palette, isDark, onClickI
35873
36790
  for (const node of layout.nodes) {
35874
36791
  const nodeG = contentG.append("g").attr("transform", `translate(${node.x}, ${node.y})`).attr("class", "c4-card").attr("data-line-number", String(node.lineNumber)).attr("data-node-id", node.id);
35875
36792
  if (activeTagGroup) {
35876
- const tagKey = activeTagGroup.toLowerCase();
36793
+ const tagKey = tagAttrKey(activeTagGroup);
35877
36794
  const tagValue = node.metadata[tagKey];
35878
36795
  if (tagValue) {
35879
36796
  nodeG.attr(`data-tag-${tagKey}`, tagValue.toLowerCase());
@@ -36133,10 +37050,11 @@ function renderC4DeploymentForExport(content, theme, palette) {
36133
37050
  document.body.removeChild(el);
36134
37051
  }
36135
37052
  }
36136
- var DIAGRAM_PADDING8, MAX_SCALE5, TITLE_HEIGHT6, TYPE_FONT_SIZE, NAME_FONT_SIZE2, DESC_FONT_SIZE5, DESC_LINE_HEIGHT6, EDGE_LABEL_FONT_SIZE6, TECH_FONT_SIZE, CARD_H_PAD4, CARD_V_PAD4, TYPE_LABEL_HEIGHT2, DIVIDER_GAP2, NAME_HEIGHT2, BOUNDARY_LABEL_FONT_SIZE, BOUNDARY_STROKE_WIDTH, BOUNDARY_RADIUS, DRILL_BAR_HEIGHT, CYLINDER_RY, PERSON_HEAD_R, PERSON_ARM_SPAN, PERSON_LEG_SPAN, PERSON_ICON_W, PERSON_SW, lineGenerator4;
37053
+ var DIAGRAM_PADDING8, MAX_SCALE5, TITLE_HEIGHT6, TYPE_FONT_SIZE, NAME_FONT_SIZE2, DESC_FONT_SIZE5, DESC_LINE_HEIGHT6, EDGE_LABEL_FONT_SIZE7, TECH_FONT_SIZE, CARD_H_PAD4, CARD_V_PAD4, TYPE_LABEL_HEIGHT2, DIVIDER_GAP2, NAME_HEIGHT2, BOUNDARY_LABEL_FONT_SIZE, BOUNDARY_STROKE_WIDTH, BOUNDARY_RADIUS, DRILL_BAR_HEIGHT, CYLINDER_RY, PERSON_HEAD_R, PERSON_ARM_SPAN, PERSON_LEG_SPAN, PERSON_ICON_W, PERSON_SW, lineGenerator4;
36137
37054
  var init_renderer9 = __esm({
36138
37055
  "src/c4/renderer.ts"() {
36139
37056
  "use strict";
37057
+ init_tag_groups();
36140
37058
  init_fonts();
36141
37059
  init_color_utils();
36142
37060
  init_inline_markdown();
@@ -36155,7 +37073,7 @@ var init_renderer9 = __esm({
36155
37073
  NAME_FONT_SIZE2 = 14;
36156
37074
  DESC_FONT_SIZE5 = 11;
36157
37075
  DESC_LINE_HEIGHT6 = 16;
36158
- EDGE_LABEL_FONT_SIZE6 = 11;
37076
+ EDGE_LABEL_FONT_SIZE7 = 11;
36159
37077
  TECH_FONT_SIZE = 10;
36160
37078
  CARD_H_PAD4 = 20;
36161
37079
  CARD_V_PAD4 = 14;
@@ -36780,7 +37698,7 @@ function renderFlowchart(container, graph, layout, palette, isDark, onClickItem,
36780
37698
  const sTitleFontSize = ctx.text(TITLE_FONT_SIZE);
36781
37699
  const sTitleY = ctx.structural(TITLE_Y);
36782
37700
  const sNodeFontSize = ctx.text(NODE_FONT_SIZE2);
36783
- const sEdgeLabelFontSize = ctx.text(EDGE_LABEL_FONT_SIZE7);
37701
+ const sEdgeLabelFontSize = ctx.text(EDGE_LABEL_FONT_SIZE8);
36784
37702
  const sEdgeStrokeWidth = ctx.structural(EDGE_STROKE_WIDTH);
36785
37703
  const sNodeStrokeWidth = ctx.structural(NODE_STROKE_WIDTH);
36786
37704
  const sArrowheadW = ctx.structural(ARROWHEAD_W3);
@@ -37006,7 +37924,7 @@ function renderFlowchartForExport(content, theme, palette) {
37006
37924
  document.body.removeChild(container);
37007
37925
  }
37008
37926
  }
37009
- var DIAGRAM_PADDING9, MAX_SCALE6, NODE_FONT_SIZE2, EDGE_LABEL_FONT_SIZE7, ARROWHEAD_W3, ARROWHEAD_H3, IO_SKEW, SUBROUTINE_INSET, DOC_WAVE_HEIGHT;
37927
+ var DIAGRAM_PADDING9, MAX_SCALE6, NODE_FONT_SIZE2, EDGE_LABEL_FONT_SIZE8, ARROWHEAD_W3, ARROWHEAD_H3, IO_SKEW, SUBROUTINE_INSET, DOC_WAVE_HEIGHT;
37010
37928
  var init_flowchart_renderer = __esm({
37011
37929
  "src/graph/flowchart-renderer.ts"() {
37012
37930
  "use strict";
@@ -37025,7 +37943,7 @@ var init_flowchart_renderer = __esm({
37025
37943
  DIAGRAM_PADDING9 = 20;
37026
37944
  MAX_SCALE6 = 3;
37027
37945
  NODE_FONT_SIZE2 = 13;
37028
- EDGE_LABEL_FONT_SIZE7 = 11;
37946
+ EDGE_LABEL_FONT_SIZE8 = 11;
37029
37947
  ARROWHEAD_W3 = 10;
37030
37948
  ARROWHEAD_H3 = 7;
37031
37949
  IO_SKEW = 15;
@@ -38200,7 +39118,7 @@ function separateGroups(groups, nodes, isLR, maxIterations = 20) {
38200
39118
  const crossOverlap = isLR ? Math.min(ga.x + ga.width, gb.x + gb.width) - Math.max(ga.x, gb.x) : Math.min(ga.y + ga.height, gb.y + gb.height) - Math.max(ga.y, gb.y);
38201
39119
  if (crossOverlap <= 0) continue;
38202
39120
  anyOverlap = true;
38203
- const shift = primaryOverlap + GROUP_GAP;
39121
+ const shift = primaryOverlap + GROUP_GAP2;
38204
39122
  const aCenter = isLR ? ga.y + ga.height / 2 : ga.x + ga.width / 2;
38205
39123
  const bCenter = isLR ? gb.y + gb.height / 2 : gb.x + gb.width / 2;
38206
39124
  const groupToShift = aCenter <= bCenter ? gb : ga;
@@ -38460,7 +39378,7 @@ function layoutInfra(computed, expandedNodeIds, collapsedNodes) {
38460
39378
  const midIdx = Math.floor(edge.points.length / 2);
38461
39379
  const midPt = edge.points[midIdx];
38462
39380
  if (midPt) {
38463
- const halfWidth = (measureText(edge.label, EDGE_LABEL_FONT_SIZE8) + 8) / 2;
39381
+ const halfWidth = (measureText(edge.label, EDGE_LABEL_FONT_SIZE9) + 8) / 2;
38464
39382
  if (midPt.x - halfWidth < minX) minX = midPt.x - halfWidth;
38465
39383
  if (midPt.x + halfWidth > maxX) maxX = midPt.x + halfWidth;
38466
39384
  }
@@ -38494,7 +39412,7 @@ function layoutInfra(computed, expandedNodeIds, collapsedNodes) {
38494
39412
  height: totalHeight
38495
39413
  };
38496
39414
  }
38497
- var MIN_NODE_WIDTH2, NODE_HEADER_HEIGHT, META_LINE_HEIGHT5, NODE_SEPARATOR_GAP, NODE_PAD_BOTTOM, ROLE_DOT_ROW, COLLAPSE_BAR_HEIGHT3, NODE_FONT_SIZE3, META_FONT_SIZE5, EDGE_LABEL_FONT_SIZE8, PADDING_X3, GROUP_PADDING2, GROUP_HEADER_HEIGHT, EDGE_MARGIN, DISPLAY_KEYS, DISPLAY_NAMES, GROUP_GAP;
39415
+ var MIN_NODE_WIDTH2, NODE_HEADER_HEIGHT, META_LINE_HEIGHT5, NODE_SEPARATOR_GAP, NODE_PAD_BOTTOM, ROLE_DOT_ROW, COLLAPSE_BAR_HEIGHT3, NODE_FONT_SIZE3, META_FONT_SIZE5, EDGE_LABEL_FONT_SIZE9, PADDING_X3, GROUP_PADDING2, GROUP_HEADER_HEIGHT, EDGE_MARGIN, DISPLAY_KEYS, DISPLAY_NAMES, GROUP_GAP2;
38498
39416
  var init_layout10 = __esm({
38499
39417
  "src/infra/layout.ts"() {
38500
39418
  "use strict";
@@ -38508,7 +39426,7 @@ var init_layout10 = __esm({
38508
39426
  COLLAPSE_BAR_HEIGHT3 = 6;
38509
39427
  NODE_FONT_SIZE3 = 13;
38510
39428
  META_FONT_SIZE5 = 10;
38511
- EDGE_LABEL_FONT_SIZE8 = 11;
39429
+ EDGE_LABEL_FONT_SIZE9 = 11;
38512
39430
  PADDING_X3 = 24;
38513
39431
  GROUP_PADDING2 = 20;
38514
39432
  GROUP_HEADER_HEIGHT = 24;
@@ -38549,7 +39467,7 @@ var init_layout10 = __esm({
38549
39467
  "retention-hours": "retention",
38550
39468
  partitions: "partitions"
38551
39469
  };
38552
- GROUP_GAP = GROUP_PADDING2 * 2 + GROUP_HEADER_HEIGHT;
39470
+ GROUP_GAP2 = GROUP_PADDING2 * 2 + GROUP_HEADER_HEIGHT;
38553
39471
  }
38554
39472
  });
38555
39473
 
@@ -38615,7 +39533,7 @@ function buildScaledConstants(ctx) {
38615
39533
  sNodeFontSize: ctx.text(NODE_FONT_SIZE4),
38616
39534
  sMetaFontSize: ctx.text(META_FONT_SIZE6),
38617
39535
  sMetaLineHeight: ctx.structural(META_LINE_HEIGHT6),
38618
- sEdgeLabelFontSize: ctx.text(EDGE_LABEL_FONT_SIZE9),
39536
+ sEdgeLabelFontSize: ctx.text(EDGE_LABEL_FONT_SIZE10),
38619
39537
  sGroupLabelFontSize: ctx.text(GROUP_LABEL_FONT_SIZE2),
38620
39538
  sNodeBorderRadius: ctx.structural(NODE_BORDER_RADIUS),
38621
39539
  sEdgeStrokeWidth: ctx.structural(EDGE_STROKE_WIDTH),
@@ -39344,7 +40262,7 @@ function renderEdgeLabels(svg, edges, nodes, groups, palette, _isDark, animate,
39344
40262
  if (!midPt) continue;
39345
40263
  const labelText = edge.label;
39346
40264
  const g = svg.append("g").attr("class", animate ? "infra-edge-label" : "");
39347
- const textWidth = measureText(labelText, EDGE_LABEL_FONT_SIZE9) + 8;
40265
+ const textWidth = measureText(labelText, EDGE_LABEL_FONT_SIZE10) + 8;
39348
40266
  g.append("rect").attr("x", midPt.x - textWidth / 2).attr("y", midPt.y - 8).attr("width", textWidth).attr("height", 16).attr("rx", 3).attr("fill", palette.bg).attr("opacity", 0.9);
39349
40267
  g.append("text").attr("x", midPt.x).attr("y", midPt.y + 4).attr("text-anchor", "middle").attr("font-family", FONT_FAMILY).attr("font-size", sc.sEdgeLabelFontSize).attr("fill", palette.textMuted).text(labelText);
39350
40268
  if (animate) {
@@ -39358,7 +40276,7 @@ function resolveActiveTagStroke(node, activeGroup, tagGroups, palette) {
39358
40276
  (t) => t.name.toLowerCase() === activeGroup.toLowerCase()
39359
40277
  );
39360
40278
  if (!tg) return null;
39361
- const tagKey = (tg.alias ?? tg.name).toLowerCase();
40279
+ const tagKey = tagAttrKey(tg.alias ?? tg.name);
39362
40280
  const tagVal = node.tags[tagKey];
39363
40281
  if (!tagVal) return null;
39364
40282
  const tv = tg.values.find(
@@ -39687,7 +40605,7 @@ function computeInfraLegendGroups(nodes, tagGroups, palette, edges) {
39687
40605
  groups.push({
39688
40606
  name: tg.name,
39689
40607
  type: "tag",
39690
- tagKey: (tg.alias ?? tg.name).toLowerCase(),
40608
+ tagKey: tagAttrKey(tg.alias ?? tg.name),
39691
40609
  entries,
39692
40610
  width: LEGEND_CAPSULE_PAD * 2 + pillWidth2 + 4 + entriesWidth2,
39693
40611
  minifiedWidth: pillWidth2
@@ -39735,7 +40653,7 @@ function renderLegend3(rootSvg, legendGroups, totalWidth, legendY, palette, isDa
39735
40653
  });
39736
40654
  legendG.selectAll("[data-legend-group]").classed("infra-legend-group", true);
39737
40655
  for (const group of legendGroups) {
39738
- const groupKey2 = group.name.toLowerCase();
40656
+ const groupKey2 = tagAttrKey(group.name);
39739
40657
  for (const entry of group.entries) {
39740
40658
  const entryEl = legendG.select(
39741
40659
  `[data-legend-group="${groupKey2}"] [data-legend-entry="${entry.value.toLowerCase()}"]`
@@ -39954,10 +40872,11 @@ function parseAndLayoutInfra(content) {
39954
40872
  const layout = layoutInfra(computed);
39955
40873
  return { parsed, computed, layout };
39956
40874
  }
39957
- var NODE_FONT_SIZE4, META_FONT_SIZE6, META_LINE_HEIGHT6, EDGE_LABEL_FONT_SIZE9, GROUP_LABEL_FONT_SIZE2, NODE_BORDER_RADIUS, OVERLOAD_STROKE_WIDTH, ROLE_DOT_RADIUS, NODE_HEADER_HEIGHT2, NODE_SEPARATOR_GAP2, NODE_PAD_BOTTOM2, LEGEND_FIXED_GAP3, SPEED_BADGE_H_PAD, SPEED_BADGE_V_PAD, SPEED_BADGE_GAP, COLOR_HEALTHY, COLOR_WARNING, COLOR_OVERLOADED, FLOW_SPEED_MIN, FLOW_SPEED_MAX, PARTICLE_R, PARTICLE_COUNT_MIN, PARTICLE_COUNT_MAX, NODE_PULSE_SPEED, NODE_PULSE_OVERLOAD, REJECT_DROP_DISTANCE, REJECT_DURATION_MIN, REJECT_DURATION_MAX, REJECT_COUNT_MIN, REJECT_COUNT_MAX, PROP_DISPLAY, DESC_MAX_CHARS, DESC_MAX_WIDTH, RPS_FORMAT_KEYS, MS_FORMAT_KEYS, PCT_FORMAT_KEYS;
40875
+ var NODE_FONT_SIZE4, META_FONT_SIZE6, META_LINE_HEIGHT6, EDGE_LABEL_FONT_SIZE10, GROUP_LABEL_FONT_SIZE2, NODE_BORDER_RADIUS, OVERLOAD_STROKE_WIDTH, ROLE_DOT_RADIUS, NODE_HEADER_HEIGHT2, NODE_SEPARATOR_GAP2, NODE_PAD_BOTTOM2, LEGEND_FIXED_GAP3, SPEED_BADGE_H_PAD, SPEED_BADGE_V_PAD, SPEED_BADGE_GAP, COLOR_HEALTHY, COLOR_WARNING, COLOR_OVERLOADED, FLOW_SPEED_MIN, FLOW_SPEED_MAX, PARTICLE_R, PARTICLE_COUNT_MIN, PARTICLE_COUNT_MAX, NODE_PULSE_SPEED, NODE_PULSE_OVERLOAD, REJECT_DROP_DISTANCE, REJECT_DURATION_MIN, REJECT_DURATION_MAX, REJECT_COUNT_MIN, REJECT_COUNT_MAX, PROP_DISPLAY, DESC_MAX_CHARS, DESC_MAX_WIDTH, RPS_FORMAT_KEYS, MS_FORMAT_KEYS, PCT_FORMAT_KEYS;
39958
40876
  var init_renderer10 = __esm({
39959
40877
  "src/infra/renderer.ts"() {
39960
40878
  "use strict";
40879
+ init_tag_groups();
39961
40880
  init_fonts();
39962
40881
  init_color_utils();
39963
40882
  init_colors();
@@ -39977,7 +40896,7 @@ var init_renderer10 = __esm({
39977
40896
  NODE_FONT_SIZE4 = 13;
39978
40897
  META_FONT_SIZE6 = 10;
39979
40898
  META_LINE_HEIGHT6 = 14;
39980
- EDGE_LABEL_FONT_SIZE9 = 11;
40899
+ EDGE_LABEL_FONT_SIZE10 = 11;
39981
40900
  GROUP_LABEL_FONT_SIZE2 = 14;
39982
40901
  NODE_BORDER_RADIUS = 8;
39983
40902
  OVERLOAD_STROKE_WIDTH = 3;
@@ -40054,17 +40973,17 @@ function mulberry32(seed) {
40054
40973
  return ((t ^ t >>> 14) >>> 0) / 4294967296;
40055
40974
  };
40056
40975
  }
40057
- function standardNormal(rng3) {
40976
+ function standardNormal(rng4) {
40058
40977
  let u = 0;
40059
40978
  let v = 0;
40060
- while (u === 0) u = rng3();
40061
- while (v === 0) v = rng3();
40979
+ while (u === 0) u = rng4();
40980
+ while (v === 0) v = rng4();
40062
40981
  return Math.sqrt(-2 * Math.log(u)) * Math.cos(2 * Math.PI * v);
40063
40982
  }
40064
- function gamma(shape, rng3) {
40983
+ function gamma(shape, rng4) {
40065
40984
  if (shape < 1) {
40066
- const u = rng3();
40067
- return gamma(shape + 1, rng3) * Math.pow(u, 1 / shape);
40985
+ const u = rng4();
40986
+ return gamma(shape + 1, rng4) * Math.pow(u, 1 / shape);
40068
40987
  }
40069
40988
  const d = shape - 1 / 3;
40070
40989
  const c = 1 / Math.sqrt(9 * d);
@@ -40072,29 +40991,29 @@ function gamma(shape, rng3) {
40072
40991
  let x;
40073
40992
  let v;
40074
40993
  do {
40075
- x = standardNormal(rng3);
40994
+ x = standardNormal(rng4);
40076
40995
  v = 1 + c * x;
40077
40996
  } while (v <= 0);
40078
40997
  v = v * v * v;
40079
- const u = rng3();
40998
+ const u = rng4();
40080
40999
  if (u < 1 - 0.0331 * x * x * x * x) return d * v;
40081
41000
  if (Math.log(u) < x * x / 2 + d * (1 - v + Math.log(v))) return d * v;
40082
41001
  }
40083
41002
  }
40084
- function sampleBeta(alpha, beta, rng3) {
40085
- const x = gamma(alpha, rng3);
40086
- const y = gamma(beta, rng3);
41003
+ function sampleBeta(alpha, beta, rng4) {
41004
+ const x = gamma(alpha, rng4);
41005
+ const y = gamma(beta, rng4);
40087
41006
  return x / (x + y);
40088
41007
  }
40089
- function sampleBetaPert(o, m, p, rng3) {
41008
+ function sampleBetaPert(o, m, p, rng4) {
40090
41009
  if (o === p) return m;
40091
41010
  const range = p - o;
40092
41011
  const alpha = 1 + 4 * (m - o) / range;
40093
41012
  const beta = 1 + 4 * (p - m) / range;
40094
- return o + sampleBeta(alpha, beta, rng3) * range;
41013
+ return o + sampleBeta(alpha, beta, rng4) * range;
40095
41014
  }
40096
41015
  function simulate(resolved, expanded, _predecessors, _successors, topo, terminals, poisoned, opts) {
40097
- const rng3 = mulberry32(opts.seed);
41016
+ const rng4 = mulberry32(opts.seed);
40098
41017
  const expById = /* @__PURE__ */ new Map();
40099
41018
  for (const e of expanded) expById.set(e.id, e);
40100
41019
  const completionTimes = new Array(opts.trials);
@@ -40117,7 +41036,7 @@ function simulate(resolved, expanded, _predecessors, _successors, topo, terminal
40117
41036
  } else if (exp.o === exp.p) {
40118
41037
  duration = exp.m;
40119
41038
  } else {
40120
- duration = sampleBetaPert(exp.o, exp.m, exp.p, rng3);
41039
+ duration = sampleBetaPert(exp.o, exp.m, exp.p, rng4);
40121
41040
  }
40122
41041
  let esLower = 0;
40123
41042
  let efLower = -Infinity;
@@ -44407,7 +45326,7 @@ function renderGantt(container, resolved, palette, isDark, options, exportDims)
44407
45326
  const ctx = exportDims ? ScaleContext.identity() : ScaleContext.from(containerWidth, idealWidth);
44408
45327
  const sBarH = ctx.structural(BAR_H);
44409
45328
  const sRowGap = ctx.structural(ROW_GAP);
44410
- const sGroupGap = ctx.structural(GROUP_GAP2);
45329
+ const sGroupGap = ctx.structural(GROUP_GAP3);
44411
45330
  const sMilestoneSize = ctx.structural(MILESTONE_SIZE);
44412
45331
  const sBottomMargin = ctx.aesthetic(BOTTOM_MARGIN);
44413
45332
  const sRightMargin = ctx.aesthetic(RIGHT_MARGIN);
@@ -44579,7 +45498,7 @@ function renderGantt(container, resolved, palette, isDark, options, exportDims)
44579
45498
  (tg) => tg.name.toLowerCase() === currentSwimlaneGroup.toLowerCase()
44580
45499
  );
44581
45500
  if (tagGroup) {
44582
- const tagKey = tagGroup.name.toLowerCase();
45501
+ const tagKey = tagAttrKey(tagGroup.name);
44583
45502
  for (const rt of resolved.tasks) {
44584
45503
  let value = rt.effectiveMetadata[tagKey];
44585
45504
  if (!value && tagGroup.defaultValue) value = tagGroup.defaultValue;
@@ -45320,7 +46239,7 @@ function renderTagLegend(svg, chartG, tagGroups, activeGroupName, chartLeftMargi
45320
46239
  const usedValues = /* @__PURE__ */ new Map();
45321
46240
  if (resolvedTasks) {
45322
46241
  for (const group of visibleGroups) {
45323
- const key = group.name.toLowerCase();
46242
+ const key = tagAttrKey(group.name);
45324
46243
  const used = /* @__PURE__ */ new Set();
45325
46244
  for (const rt of resolvedTasks) {
45326
46245
  const val = rt.effectiveMetadata[key];
@@ -45331,7 +46250,7 @@ function renderTagLegend(svg, chartG, tagGroups, activeGroupName, chartLeftMargi
45331
46250
  }
45332
46251
  const filteredEntries = /* @__PURE__ */ new Map();
45333
46252
  for (const group of visibleGroups) {
45334
- const key = group.name.toLowerCase();
46253
+ const key = tagAttrKey(group.name);
45335
46254
  const used = usedValues.get(key);
45336
46255
  if (used && used.size > 0) {
45337
46256
  filteredEntries.set(
@@ -45352,7 +46271,7 @@ function renderTagLegend(svg, chartG, tagGroups, activeGroupName, chartLeftMargi
45352
46271
  const pillW = measureLegendText(group.name, LEGEND_PILL_FONT_SIZE) + LEGEND_PILL_PAD + iconReserve;
45353
46272
  let groupW = pillW;
45354
46273
  if (isActive) {
45355
- const entries = filteredEntries.get(group.name.toLowerCase()) ?? group.entries;
46274
+ const entries = filteredEntries.get(tagAttrKey(group.name)) ?? group.entries;
45356
46275
  let entriesW = 0;
45357
46276
  for (const entry of entries) {
45358
46277
  entriesW += LEGEND_DOT_R * 2 + LEGEND_ENTRY_DOT_GAP + measureLegendText(entry.value, LEGEND_ENTRY_FONT_SIZE) + LEGEND_ENTRY_TRAIL;
@@ -45409,7 +46328,7 @@ function renderTagLegend(svg, chartG, tagGroups, activeGroupName, chartLeftMargi
45409
46328
  },
45410
46329
  ...onControlsToggle !== void 0 && { onControlsToggle },
45411
46330
  onEntryHover: (groupName, entryValue) => {
45412
- const tagKey = groupName.toLowerCase();
46331
+ const tagKey = tagAttrKey(groupName);
45413
46332
  if (entryValue) {
45414
46333
  const ev = entryValue.toLowerCase();
45415
46334
  chartG.selectAll(".gantt-task").each(function() {
@@ -46099,7 +47018,7 @@ function buildTagLaneRowList(resolved, swimlaneGroup, collapsedLanes) {
46099
47018
  (g) => g.name.toLowerCase() === swimlaneGroup.toLowerCase()
46100
47019
  );
46101
47020
  if (!tagGroup) return null;
46102
- const tagKey = tagGroup.name.toLowerCase();
47021
+ const tagKey = tagAttrKey(tagGroup.name);
46103
47022
  const rows = [];
46104
47023
  const buckets = /* @__PURE__ */ new Map();
46105
47024
  const unbucketed = [];
@@ -46283,10 +47202,11 @@ function renderTimeScaleHorizontal(g, scale, _innerWidth, innerHeight, textColor
46283
47202
  g.append("text").attr("class", "gantt-scale-tick").attr("x", tick.pos).attr("y", innerHeight + tickLen + 12).attr("text-anchor", "middle").attr("font-size", "10px").attr("fill", textColor).attr("opacity", opacity).text(tick.label);
46284
47203
  }
46285
47204
  }
46286
- var BAR_H, ROW_GAP, GROUP_GAP2, MILESTONE_SIZE, MIN_LEFT_MARGIN, BOTTOM_MARGIN, RIGHT_MARGIN, LABEL_PAD2, LABEL_GAP, BAND_ACCENT_W, BAND_RADIUS, bandClipCounter, JS_DAY_TO_WEEKDAY2, ERA_COLORS, SPRINT_BAND_OPACITY, SPRINT_HOVER_OPACITY, SPRINT_BOUNDARY_OPACITY, FADE_OPACITY;
47205
+ var BAR_H, ROW_GAP, GROUP_GAP3, MILESTONE_SIZE, MIN_LEFT_MARGIN, BOTTOM_MARGIN, RIGHT_MARGIN, LABEL_PAD2, LABEL_GAP, BAND_ACCENT_W, BAND_RADIUS, bandClipCounter, JS_DAY_TO_WEEKDAY2, ERA_COLORS, SPRINT_BAND_OPACITY, SPRINT_HOVER_OPACITY, SPRINT_BOUNDARY_OPACITY, FADE_OPACITY;
46287
47206
  var init_renderer12 = __esm({
46288
47207
  "src/gantt/renderer.ts"() {
46289
47208
  "use strict";
47209
+ init_tag_groups();
46290
47210
  init_fonts();
46291
47211
  init_palettes();
46292
47212
  init_color_utils();
@@ -46301,7 +47221,7 @@ var init_renderer12 = __esm({
46301
47221
  init_title_constants();
46302
47222
  BAR_H = 22;
46303
47223
  ROW_GAP = 6;
46304
- GROUP_GAP2 = 14;
47224
+ GROUP_GAP3 = 14;
46305
47225
  MILESTONE_SIZE = 10;
46306
47226
  MIN_LEFT_MARGIN = 120;
46307
47227
  BOTTOM_MARGIN = 40;
@@ -46368,7 +47288,7 @@ function renderState(container, graph, layout, palette, isDark, onClickItem, exp
46368
47288
  const sTitleFontSize = ctx.text(TITLE_FONT_SIZE);
46369
47289
  const sTitleY = ctx.structural(TITLE_Y);
46370
47290
  const sNodeFontSize = ctx.text(NODE_FONT_SIZE6);
46371
- const sEdgeLabelFontSize = ctx.text(EDGE_LABEL_FONT_SIZE10);
47291
+ const sEdgeLabelFontSize = ctx.text(EDGE_LABEL_FONT_SIZE11);
46372
47292
  const sGroupLabelFontSize = ctx.text(GROUP_LABEL_FONT_SIZE3);
46373
47293
  const sEdgeStrokeWidth = ctx.structural(EDGE_STROKE_WIDTH);
46374
47294
  const sNodeStrokeWidth = ctx.structural(NODE_STROKE_WIDTH);
@@ -46648,7 +47568,7 @@ function renderStateForExport(content, theme, palette) {
46648
47568
  document.body.removeChild(container);
46649
47569
  }
46650
47570
  }
46651
- var DIAGRAM_PADDING12, MAX_SCALE7, NODE_FONT_SIZE6, EDGE_LABEL_FONT_SIZE10, GROUP_LABEL_FONT_SIZE3, ARROWHEAD_W5, ARROWHEAD_H5, PSEUDOSTATE_RADIUS, STATE_CORNER_RADIUS, GROUP_EXTRA_PADDING;
47571
+ var DIAGRAM_PADDING12, MAX_SCALE7, NODE_FONT_SIZE6, EDGE_LABEL_FONT_SIZE11, GROUP_LABEL_FONT_SIZE3, ARROWHEAD_W5, ARROWHEAD_H5, PSEUDOSTATE_RADIUS, STATE_CORNER_RADIUS, GROUP_EXTRA_PADDING;
46652
47572
  var init_state_renderer = __esm({
46653
47573
  "src/graph/state-renderer.ts"() {
46654
47574
  "use strict";
@@ -46667,7 +47587,7 @@ var init_state_renderer = __esm({
46667
47587
  DIAGRAM_PADDING12 = 20;
46668
47588
  MAX_SCALE7 = 3;
46669
47589
  NODE_FONT_SIZE6 = 13;
46670
- EDGE_LABEL_FONT_SIZE10 = 11;
47590
+ EDGE_LABEL_FONT_SIZE11 = 11;
46671
47591
  GROUP_LABEL_FONT_SIZE3 = 11;
46672
47592
  ARROWHEAD_W5 = 10;
46673
47593
  ARROWHEAD_H5 = 7;
@@ -48596,7 +49516,7 @@ function renderJourneyMap(container, parsed, palette, isDark, options) {
48596
49516
  const panelWidth = 280;
48597
49517
  const textAreaWidth = panelWidth - silhouetteZone - CARD_PADDING_X3;
48598
49518
  const descLineH = 14;
48599
- const descLines = parsed.persona.description ? wrapText2(parsed.persona.description, textAreaWidth, FONT_SIZE_META) : [];
49519
+ const descLines = parsed.persona.description ? wrapNoteBody(parsed.persona.description, textAreaWidth, FONT_SIZE_META) : [];
48600
49520
  const descRowH = descLines.length > 0 ? descLines.length * descLineH + 8 : 0;
48601
49521
  const panelHeight = titleRowH + descRowH;
48602
49522
  const panelX = layout.totalWidth - PADDING2 - panelWidth;
@@ -48621,8 +49541,14 @@ function renderJourneyMap(container, parsed, palette, isDark, options) {
48621
49541
  personaG.append("rect").attr("x", panelX).attr("y", panelY).attr("width", panelWidth).attr("height", panelHeight).attr("rx", CARD_RADIUS).attr("fill", "none").attr("stroke", personaColor).attr("stroke-width", CARD_STROKE_WIDTH2);
48622
49542
  personaG.append("text").attr("x", textX).attr("y", panelY + CARD_PADDING_Y3 + FONT_SIZE_STEP).attr("font-size", FONT_SIZE_STEP).attr("font-weight", "500").attr("fill", onPersonaText).text(parsed.persona.name);
48623
49543
  for (let li = 0; li < descLines.length; li++) {
48624
- const lineEl = personaG.append("text").attr("x", textX).attr("y", panelY + titleRowH + descLineH * (li + 1)).attr("font-size", FONT_SIZE_META).attr("fill", onPersonaText);
48625
- renderInlineText(lineEl, descLines[li], palette, FONT_SIZE_META);
49544
+ const line11 = descLines[li];
49545
+ const indent = line11.kind === "plain" ? 0 : NOTE_BULLET_INDENT;
49546
+ const lineY = panelY + titleRowH + descLineH * (li + 1);
49547
+ if (line11.kind === "bullet-first") {
49548
+ personaG.append("text").attr("x", textX).attr("y", lineY).attr("font-size", FONT_SIZE_META).attr("fill", onPersonaText).text("\u2022");
49549
+ }
49550
+ const lineEl = personaG.append("text").attr("x", textX + indent).attr("y", lineY).attr("font-size", FONT_SIZE_META).attr("fill", onPersonaText);
49551
+ renderInlineText(lineEl, line11.text, palette, FONT_SIZE_META);
48626
49552
  }
48627
49553
  if (onNavigateToLine) {
48628
49554
  personaG.style("cursor", "pointer").on("click", () => {
@@ -48661,7 +49587,7 @@ function renderJourneyMap(container, parsed, palette, isDark, options) {
48661
49587
  return;
48662
49588
  }
48663
49589
  const isScore = groupName === "Score";
48664
- const attrName = isScore ? "data-score" : `data-tag-${groupName.toLowerCase()}`;
49590
+ const attrName = isScore ? "data-score" : `data-tag-${tagAttrKey(groupName)}`;
48665
49591
  const matches = (el) => el.getAttribute(attrName) === entryValue;
48666
49592
  svg.selectAll(".journey-step").each(function() {
48667
49593
  const hit = matches(this);
@@ -49142,7 +50068,7 @@ function resolveStepColor(step, scoreColor, activeGroup, tagGroups, _palette) {
49142
50068
  (g) => g.name.toLowerCase() === activeGroup.toLowerCase()
49143
50069
  );
49144
50070
  if (!group) return void 0;
49145
- const tagValue = step.tags[group.name.toLowerCase()];
50071
+ const tagValue = step.tags[tagAttrKey(group.name)];
49146
50072
  if (!tagValue) return void 0;
49147
50073
  const entry = group.entries.find(
49148
50074
  (e) => e.value.toLowerCase() === tagValue.toLowerCase()
@@ -49357,11 +50283,13 @@ var DIAGRAM_PADDING13, PADDING2, CARD_PADDING_X3, CARD_PADDING_Y3, CARD_HEADER_H
49357
50283
  var init_renderer14 = __esm({
49358
50284
  "src/journey-map/renderer.ts"() {
49359
50285
  "use strict";
50286
+ init_tag_groups();
49360
50287
  init_color_utils();
49361
50288
  init_fonts();
49362
50289
  init_parser23();
49363
50290
  init_layout13();
49364
50291
  init_inline_markdown();
50292
+ init_note_box();
49365
50293
  init_legend_integration();
49366
50294
  init_tag_groups();
49367
50295
  init_scaling();
@@ -49701,10 +50629,10 @@ function renderedDescNodeHeight(numLines, scale) {
49701
50629
  return headerH + textBlockH + bottomPad;
49702
50630
  }
49703
50631
  function wrapEdgeLabelText(label, description, maxWidth = EDGE_LABEL_MAX_WIDTH) {
49704
- const labelLines = label ? wrapTextToWidth(label, EDGE_LABEL_FONT_SIZE11, maxWidth) : [];
50632
+ const labelLines = label ? wrapTextToWidth(label, EDGE_LABEL_FONT_SIZE12, maxWidth) : [];
49705
50633
  const descLines = [];
49706
50634
  for (const d of description) {
49707
- descLines.push(...wrapTextToWidth(d, EDGE_LABEL_FONT_SIZE11, maxWidth));
50635
+ descLines.push(...wrapTextToWidth(d, EDGE_LABEL_FONT_SIZE12, maxWidth));
49708
50636
  }
49709
50637
  return { labelLines, descLines };
49710
50638
  }
@@ -49842,9 +50770,9 @@ function computeEdgePaths(layoutNodes, parsed, cx, cy, radius, isClockwise) {
49842
50770
  const lineCount = labelLines.length + descLines.length;
49843
50771
  let labelPxW = 0;
49844
50772
  for (const l of labelLines)
49845
- labelPxW = Math.max(labelPxW, measureText(l, EDGE_LABEL_FONT_SIZE11));
50773
+ labelPxW = Math.max(labelPxW, measureText(l, EDGE_LABEL_FONT_SIZE12));
49846
50774
  for (const l of descLines)
49847
- labelPxW = Math.max(labelPxW, measureText(l, EDGE_LABEL_FONT_SIZE11));
50775
+ labelPxW = Math.max(labelPxW, measureText(l, EDGE_LABEL_FONT_SIZE12));
49848
50776
  const { labelX, labelY, labelAngle } = computeEdgeLabelPosition(
49849
50777
  midAngle,
49850
50778
  radius,
@@ -49946,9 +50874,9 @@ function fitToCanvas(nodes, edges, parsed, _cx, _cy, radius, width, height, _isC
49946
50874
  );
49947
50875
  let textWidth = 0;
49948
50876
  for (const l of labelLines)
49949
- textWidth = Math.max(textWidth, measureText(l, EDGE_LABEL_FONT_SIZE11));
50877
+ textWidth = Math.max(textWidth, measureText(l, EDGE_LABEL_FONT_SIZE12));
49950
50878
  for (const l of descLines)
49951
- textWidth = Math.max(textWidth, measureText(l, EDGE_LABEL_FONT_SIZE11));
50879
+ textWidth = Math.max(textWidth, measureText(l, EDGE_LABEL_FONT_SIZE12));
49952
50880
  if (textWidth === 0) continue;
49953
50881
  const normAngle = (le.labelAngle % (2 * Math.PI) + 2 * Math.PI) % (2 * Math.PI);
49954
50882
  const isRight = normAngle < Math.PI * 0.4 || normAngle > Math.PI * 1.6;
@@ -50022,7 +50950,7 @@ function buildEdgeArc(src, tgt, cx, cy, radius, isClockwise, arrowLength = 0) {
50022
50950
  const midAngle = srcExitAngle + dir * visibleSweep / 2;
50023
50951
  return { path, midAngle };
50024
50952
  }
50025
- var MIN_ARC_ANGLE, LABEL_FONT_SIZE4, CIRCLE_LABEL_FONT_SIZE, DESC_FONT_SIZE6, EDGE_LABEL_FONT_SIZE11, MIN_NODE_WIDTH4, MAX_NODE_WIDTH3, DESC_MIN_WIDTH, DESC_WIDTH_STEP, DESC_TARGET_RATIO, PLAIN_NODE_HEIGHT, HEADER_HEIGHT4, DESC_LINE_HEIGHT7, DESC_PAD_Y, NODE_PAD_X, MIN_CIRCLE_RADIUS, CIRCLE_PAD, RENDERER_DESC_FONT, RENDERER_DESC_FONT_MIN, RENDERER_DESC_LINE_H, RENDERER_DESC_LINE_H_MIN, EDGE_LABEL_MAX_WIDTH;
50953
+ var MIN_ARC_ANGLE, LABEL_FONT_SIZE4, CIRCLE_LABEL_FONT_SIZE, DESC_FONT_SIZE6, EDGE_LABEL_FONT_SIZE12, MIN_NODE_WIDTH4, MAX_NODE_WIDTH3, DESC_MIN_WIDTH, DESC_WIDTH_STEP, DESC_TARGET_RATIO, PLAIN_NODE_HEIGHT, HEADER_HEIGHT4, DESC_LINE_HEIGHT7, DESC_PAD_Y, NODE_PAD_X, MIN_CIRCLE_RADIUS, CIRCLE_PAD, RENDERER_DESC_FONT, RENDERER_DESC_FONT_MIN, RENDERER_DESC_LINE_H, RENDERER_DESC_LINE_H_MIN, EDGE_LABEL_MAX_WIDTH;
50026
50954
  var init_layout14 = __esm({
50027
50955
  "src/cycle/layout.ts"() {
50028
50956
  "use strict";
@@ -50033,7 +50961,7 @@ var init_layout14 = __esm({
50033
50961
  LABEL_FONT_SIZE4 = 13;
50034
50962
  CIRCLE_LABEL_FONT_SIZE = 16;
50035
50963
  DESC_FONT_SIZE6 = 11;
50036
- EDGE_LABEL_FONT_SIZE11 = 11;
50964
+ EDGE_LABEL_FONT_SIZE12 = 11;
50037
50965
  MIN_NODE_WIDTH4 = 70;
50038
50966
  MAX_NODE_WIDTH3 = 260;
50039
50967
  DESC_MIN_WIDTH = 140;
@@ -50179,7 +51107,7 @@ function renderCycle(container, parsed, palette, isDark, onClickItem, exportDims
50179
51107
  const scaledNodeFont = Math.max(6, Math.round(NODE_FONT_SIZE7 * layout.scale));
50180
51108
  const scaledEdgeLabelFont = Math.max(
50181
51109
  6,
50182
- Math.round(EDGE_LABEL_FONT_SIZE12 * layout.scale)
51110
+ Math.round(EDGE_LABEL_FONT_SIZE13 * layout.scale)
50183
51111
  );
50184
51112
  const CIRCLE_LABEL_FONT_SIZE2 = 16;
50185
51113
  const scaledCircleLabelFont = Math.max(
@@ -50342,7 +51270,7 @@ function ensureArrowMarker(defs, color, strokeWidth) {
50342
51270
  const mh = Math.max(1.5, mw * 0.5);
50343
51271
  defs.append("marker").attr("id", id).attr("viewBox", `0 0 ${mw} ${mh}`).attr("refX", mw * 0.1).attr("refY", mh / 2).attr("markerWidth", mw).attr("markerHeight", mh).attr("orient", "auto").append("polygon").attr("points", `0,0 ${mw},${mh / 2} 0,${mh}`).attr("fill", color);
50344
51272
  }
50345
- var NODE_FONT_SIZE7, DESC_FONT_SIZE7, EDGE_LABEL_FONT_SIZE12, DESC_LINE_HEIGHT8, TITLE_AREA_HEIGHT;
51273
+ var NODE_FONT_SIZE7, DESC_FONT_SIZE7, EDGE_LABEL_FONT_SIZE13, DESC_LINE_HEIGHT8, TITLE_AREA_HEIGHT;
50346
51274
  var init_renderer15 = __esm({
50347
51275
  "src/cycle/renderer.ts"() {
50348
51276
  "use strict";
@@ -50359,7 +51287,7 @@ var init_renderer15 = __esm({
50359
51287
  init_text_measure();
50360
51288
  NODE_FONT_SIZE7 = 13;
50361
51289
  DESC_FONT_SIZE7 = 11;
50362
- EDGE_LABEL_FONT_SIZE12 = 11;
51290
+ EDGE_LABEL_FONT_SIZE13 = 11;
50363
51291
  DESC_LINE_HEIGHT8 = 15;
50364
51292
  TITLE_AREA_HEIGHT = 50;
50365
51293
  }
@@ -52196,7 +53124,7 @@ function layoutMap(resolvedIn, data, size, opts) {
52196
53124
  (g) => g.name.toLowerCase() === groupName.toLowerCase()
52197
53125
  );
52198
53126
  if (!group) return null;
52199
- const val = tags[group.name.toLowerCase()] ?? group.defaultValue;
53127
+ const val = tags[tagAttrKey(group.name)] ?? group.defaultValue;
52200
53128
  if (!val) return null;
52201
53129
  const entry = group.entries.find(
52202
53130
  (e) => e.value.toLowerCase() === val.toLowerCase()
@@ -52810,7 +53738,7 @@ function layoutMap(resolvedIn, data, size, opts) {
52810
53738
  if (directHex)
52811
53739
  return { fill: directHex, stroke: mix(directHex, palette.text, 18) };
52812
53740
  for (const group of resolved.tagGroups) {
52813
- const val = p.tags[group.name.toLowerCase()];
53741
+ const val = p.tags[tagAttrKey(group.name)];
52814
53742
  if (!val) continue;
52815
53743
  const entry = group.entries.find(
52816
53744
  (e) => e.value.toLowerCase() === val.toLowerCase()
@@ -52825,7 +53753,7 @@ function layoutMap(resolvedIn, data, size, opts) {
52825
53753
  };
52826
53754
  const lineColor = (tags) => {
52827
53755
  for (const group of resolved.tagGroups) {
52828
- const val = tags[group.name.toLowerCase()];
53756
+ const val = tags[tagAttrKey(group.name)];
52829
53757
  if (!val) continue;
52830
53758
  const entry = group.entries.find(
52831
53759
  (e) => e.value.toLowerCase() === val.toLowerCase()
@@ -54091,6 +55019,7 @@ var FIT_PAD, PATH_DIGITS, THIN_TOL, ALBERS_SKEW_MAX, RAMP_FLOOR2, R_DEFAULT, R_M
54091
55019
  var init_layout15 = __esm({
54092
55020
  "src/map/layout.ts"() {
54093
55021
  "use strict";
55022
+ init_tag_groups();
54094
55023
  init_color_utils();
54095
55024
  init_geo();
54096
55025
  init_colorize();
@@ -56504,7 +57433,7 @@ function resolveSequenceTags(parsed, activeTagGroup) {
56504
57433
  }
56505
57434
  return result;
56506
57435
  }
56507
- const groupKey2 = group.name.toLowerCase();
57436
+ const groupKey2 = tagAttrKey(group.name);
56508
57437
  const participantMeta = /* @__PURE__ */ new Map();
56509
57438
  for (const p of parsed.participants) {
56510
57439
  participantMeta.set(p.id, { ...p.metadata || {} });
@@ -56540,6 +57469,7 @@ function resolveSequenceTags(parsed, activeTagGroup) {
56540
57469
  var init_tag_resolution = __esm({
56541
57470
  "src/sequence/tag-resolution.ts"() {
56542
57471
  "use strict";
57472
+ init_tag_groups();
56543
57473
  }
56544
57474
  });
56545
57475
 
@@ -57015,7 +57945,7 @@ function renderSequenceDiagram(container, parsed, palette, isDark, _onNavigateTo
57015
57945
  }
57016
57946
  }
57017
57947
  const getTagColor = (value) => value ? tagValueToColor.get(value.toLowerCase()) : void 0;
57018
- const tagKey = activeTagGroup?.toLowerCase();
57948
+ const tagKey = activeTagGroup ? tagAttrKey(activeTagGroup) : void 0;
57019
57949
  const hiddenMsgIndices = /* @__PURE__ */ new Set();
57020
57950
  if (collapsedSections && collapsedSections.size > 0) {
57021
57951
  const sectionGroups = groupMessagesBySection(elements, messages);
@@ -57607,11 +58537,7 @@ function renderSequenceDiagram(container, parsed, palette, isDark, _onNavigateTo
57607
58537
  const boxH = sBoxH + GROUP_PADDING_TOP2 + GROUP_PADDING_BOTTOM2;
57608
58538
  const groupTagValue = tagKey && group.metadata?.[tagKey];
57609
58539
  const groupTagColor = getTagColor(groupTagValue || void 0);
57610
- const fillColor = groupTagColor ? mix(
57611
- groupTagColor,
57612
- themeBaseBg(palette, isDark),
57613
- isDark ? 15 : 20
57614
- ) : isDark ? palette.surface : palette.bg;
58540
+ const fillColor = groupTagColor ? mix(groupTagColor, themeBaseBg(palette, isDark), isDark ? 15 : 20) : isDark ? palette.surface : palette.bg;
57615
58541
  const strokeColor = groupTagColor || palette.textMuted;
57616
58542
  const groupG = svg.append("g").attr("class", "group-box-wrapper").attr("data-group-toggle", "").attr("data-group-line", String(group.lineNumber)).attr("tabindex", "0").attr("role", "button").attr("aria-expanded", "true").attr("cursor", "pointer");
57617
58543
  groupG.append("title").text("Click to collapse");
@@ -57661,11 +58587,7 @@ function renderSequenceDiagram(container, parsed, palette, isDark, _onNavigateTo
57661
58587
  );
57662
58588
  participantG.attr("data-group-toggle", "").attr("data-group-line", String(meta.lineNumber)).attr("tabindex", "0").attr("role", "button").attr("aria-expanded", "false").attr("cursor", "pointer");
57663
58589
  participantG.append("title").text("Click to expand");
57664
- const pFill = effectiveTagColor ? mix(
57665
- effectiveTagColor,
57666
- themeBaseBg(palette, isDark),
57667
- isDark ? 30 : 40
57668
- ) : isDark ? mix(palette.overlay, palette.surface, 50) : mix(palette.bg, palette.surface, 50);
58590
+ const pFill = effectiveTagColor ? mix(effectiveTagColor, themeBaseBg(palette, isDark), isDark ? 30 : 40) : isDark ? mix(palette.overlay, palette.surface, 50) : mix(palette.bg, palette.surface, 50);
57669
58591
  const pStroke = effectiveTagColor || palette.border;
57670
58592
  participantG.append("rect").attr("x", -boxW / 2).attr("y", -GROUP_PADDING_TOP2).attr("width", boxW).attr("height", fullH).attr("rx", 6).attr("fill", pFill).attr("stroke", pStroke).attr("stroke-width", 1.5);
57671
58593
  participantG.append("text").attr("x", 0).attr("y", -GROUP_PADDING_TOP2 + fullH / 2).attr("text-anchor", "middle").attr("dominant-baseline", "central").attr("fill", palette.text).attr("font-size", ctx.text(13)).attr("font-weight", 500).text(participant.label);
@@ -58161,6 +59083,7 @@ var PARTICIPANT_GAP, PARTICIPANT_BOX_WIDTH, PARTICIPANT_BOX_HEIGHT, LABEL_FONT_S
58161
59083
  var init_renderer20 = __esm({
58162
59084
  "src/sequence/renderer.ts"() {
58163
59085
  "use strict";
59086
+ init_tag_groups();
58164
59087
  init_color_utils();
58165
59088
  init_inline_markdown();
58166
59089
  init_fonts();
@@ -59319,6 +60242,7 @@ function renderArcDiagram(container, parsed, palette, _isDark, onClickItem, expo
59319
60242
  init_parser3();
59320
60243
 
59321
60244
  // src/timeline/renderer.ts
60245
+ init_tag_groups();
59322
60246
  init_fonts();
59323
60247
  init_time_ticks();
59324
60248
  init_scaling();
@@ -59583,7 +60507,7 @@ function setupTimeline(container, parsed, palette, isDark, exportDims, activeTag
59583
60507
  });
59584
60508
  let tagLanes = null;
59585
60509
  if (resolvedSwimlaneTG) {
59586
- const tagKey = resolvedSwimlaneTG.toLowerCase();
60510
+ const tagKey = tagAttrKey(resolvedSwimlaneTG);
59587
60511
  const tagGroup = parsed.timelineTagGroups.find(
59588
60512
  (g) => g.name.toLowerCase() === tagKey
59589
60513
  );
@@ -59889,7 +60813,7 @@ function renderTimelineTagLegendOverlay(container, parsed, palette, isDark, setu
59889
60813
  onTagStateChange?.(currentActiveGroup, currentSwimlaneGroup);
59890
60814
  },
59891
60815
  onEntryHover: (groupName, entryValue) => {
59892
- const tagKey = groupName.toLowerCase();
60816
+ const tagKey = tagAttrKey(groupName);
59893
60817
  if (entryValue) {
59894
60818
  const tagVal = entryValue.toLowerCase();
59895
60819
  fadeToTagValue(mainG, tagKey, tagVal);
@@ -59908,7 +60832,7 @@ function renderTimelineTagLegendOverlay(container, parsed, palette, isDark, setu
59908
60832
  }
59909
60833
  },
59910
60834
  onGroupRendered: (groupName, groupEl, isActive) => {
59911
- const groupKey2 = groupName.toLowerCase();
60835
+ const groupKey2 = tagAttrKey(groupName);
59912
60836
  groupEl.attr("data-tag-group", groupKey2);
59913
60837
  if (isActive && !viewMode) {
59914
60838
  const isSwimActive = currentSwimlaneGroup?.toLowerCase() === groupKey2;
@@ -63773,6 +64697,71 @@ function completeMapPlaces(query, gazetteer, opts) {
63773
64697
  });
63774
64698
  return [...cityItems, ...airportItems].slice(0, limit);
63775
64699
  }
64700
+ function searchMapLocations(query, gazetteer, opts) {
64701
+ const q = fold2(query);
64702
+ if (!q) return [];
64703
+ const limit = opts?.limit ?? 20;
64704
+ const rankOf = (hay) => hay === q ? 0 : hay.startsWith(q) ? 1 : hay.includes(q) ? 2 : -1;
64705
+ const cityRank = /* @__PURE__ */ new Map();
64706
+ const consider = (idx, rank) => {
64707
+ if (rank < 0) return;
64708
+ const prev = cityRank.get(idx);
64709
+ if (prev === void 0 || rank < prev) cityRank.set(idx, rank);
64710
+ };
64711
+ for (const [key, list] of Object.entries(gazetteer.byName)) {
64712
+ const r = rankOf(key);
64713
+ if (r >= 0) for (const i of list) consider(i, r);
64714
+ }
64715
+ for (const [key, idx] of Object.entries(gazetteer.alt)) {
64716
+ consider(idx, rankOf(key));
64717
+ }
64718
+ const cityItems = [...cityRank].filter(([i]) => gazetteer.cities[i] !== void 0).map(([i, rank]) => {
64719
+ const c = gazetteer.cities[i];
64720
+ const [, , iso, pop, name, sub] = c;
64721
+ const ambiguous = (gazetteer.byName[fold2(name)]?.length ?? 0) > 1;
64722
+ const qualifier = sub ?? iso;
64723
+ return {
64724
+ name,
64725
+ token: ambiguous ? `${name} ${qualifier}` : name,
64726
+ kind: "city",
64727
+ iso,
64728
+ ...sub !== void 0 && { sub },
64729
+ pop,
64730
+ detail: `${qualifier} \xB7 ${groupThousands(pop)}`,
64731
+ _rank: rank
64732
+ };
64733
+ });
64734
+ const airports = opts?.airports;
64735
+ const airportItems = [];
64736
+ if (airports?.airportIata) {
64737
+ for (const [code, idx] of Object.entries(airports.airportIata)) {
64738
+ const a = airports.airports[idx];
64739
+ if (!a) continue;
64740
+ const codeRank = rankOf(code);
64741
+ const nameRank = rankOf(fold2(a[4]));
64742
+ const rank = Math.min(
64743
+ codeRank < 0 ? 99 : codeRank,
64744
+ nameRank < 0 ? 99 : nameRank
64745
+ );
64746
+ if (rank > 2) continue;
64747
+ airportItems.push({
64748
+ name: a[4],
64749
+ token: code.toUpperCase(),
64750
+ kind: "airport",
64751
+ iso: a[2],
64752
+ pop: 0,
64753
+ detail: `Airport \xB7 ${a[4]}`,
64754
+ _rank: rank
64755
+ });
64756
+ }
64757
+ }
64758
+ return [...cityItems, ...airportItems].sort(
64759
+ (x, y) => x._rank - y._rank || // best match tier first
64760
+ (x.kind === y.kind ? 0 : x.kind === "city" ? -1 : 1) || // city before airport
64761
+ y.pop - x.pop || // populous city first
64762
+ (x.token < y.token ? -1 : x.token > y.token ? 1 : 0)
64763
+ ).slice(0, limit).map(({ _rank, ...rest }) => rest);
64764
+ }
63776
64765
  function completeMapRegions(query, regions, opts) {
63777
64766
  const q = fold2(query);
63778
64767
  if (!q) return [];
@@ -64996,6 +65985,7 @@ export {
64996
65985
  resolveTaskName,
64997
65986
  rollUpContextRelationships,
64998
65987
  sampleBetaPert,
65988
+ searchMapLocations,
64999
65989
  seriesColors,
65000
65990
  shade,
65001
65991
  shapeFill,