@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.cjs CHANGED
@@ -1255,6 +1255,9 @@ var init_parsing = __esm({
1255
1255
  });
1256
1256
 
1257
1257
  // src/utils/tag-groups.ts
1258
+ function tagAttrKey(name) {
1259
+ return name.trim().toLowerCase().replace(/[^a-z0-9_-]+/g, "-").replace(/^-+|-+$/g, "");
1260
+ }
1258
1261
  function stripDefaultModifier(text) {
1259
1262
  if (/\bdefault\s*$/.test(text)) {
1260
1263
  return { text: text.replace(/\s+default\s*$/, "").trim(), isDefault: true };
@@ -1400,17 +1403,17 @@ function parseTagDeclaration(line11) {
1400
1403
  function resolveTagColor(metadata, tagGroups, activeGroupName, isContainer) {
1401
1404
  if (!activeGroupName) return void 0;
1402
1405
  const group = tagGroups.find(
1403
- (g) => g.name.toLowerCase() === activeGroupName.toLowerCase()
1406
+ (g) => tagAttrKey(g.name) === tagAttrKey(activeGroupName)
1404
1407
  );
1405
1408
  if (!group) return void 0;
1406
- const metaValue = metadata[group.name.toLowerCase()] ?? (isContainer ? void 0 : group.defaultValue);
1409
+ const metaValue = metadata[tagAttrKey(group.name)] ?? (isContainer ? void 0 : group.defaultValue);
1407
1410
  if (!metaValue) return "#999999";
1408
1411
  return group.entries.find((e) => e.value.toLowerCase() === metaValue.toLowerCase())?.color ?? "#999999";
1409
1412
  }
1410
1413
  function validateTagValues(entities, tagGroups, pushWarning, suggestFn) {
1411
1414
  if (tagGroups.length === 0) return;
1412
1415
  const groupMap = /* @__PURE__ */ new Map();
1413
- for (const g of tagGroups) groupMap.set(g.name.toLowerCase(), g);
1416
+ for (const g of tagGroups) groupMap.set(tagAttrKey(g.name), g);
1414
1417
  for (const entity of entities) {
1415
1418
  for (const [key, value] of Object.entries(entity.metadata)) {
1416
1419
  const group = groupMap.get(key);
@@ -1447,10 +1450,10 @@ function validateTagGroupNames(tagGroups, pushWarning, pushError) {
1447
1450
  `'none' is a reserved keyword and cannot be used as a tag group name`
1448
1451
  );
1449
1452
  }
1450
- if (!VALID_TAG_IDENT_RE.test(group.name)) {
1453
+ if (!VALID_TAG_IDENT_RE.test(tagAttrKey(group.name))) {
1451
1454
  report(
1452
1455
  group.lineNumber,
1453
- `Tag group name "${group.name}" contains invalid characters \u2014 use a single identifier (letters, digits, underscore, hyphen)`
1456
+ `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)`
1454
1457
  );
1455
1458
  }
1456
1459
  if (group.alias != null && !VALID_TAG_IDENT_RE.test(group.alias)) {
@@ -1462,7 +1465,7 @@ function validateTagGroupNames(tagGroups, pushWarning, pushError) {
1462
1465
  }
1463
1466
  }
1464
1467
  function cascadeTagMetadata(roots, tagGroups) {
1465
- const keys = tagGroups.map((g) => g.name.toLowerCase());
1468
+ const keys = tagGroups.map((g) => tagAttrKey(g.name));
1466
1469
  if (keys.length === 0) return;
1467
1470
  const walk = (node, inherited) => {
1468
1471
  const childInherited = { ...inherited };
@@ -1483,7 +1486,7 @@ function injectDefaultTagMetadata(entities, tagGroups, skip) {
1483
1486
  for (const group of tagGroups) {
1484
1487
  if (group.defaultValue) {
1485
1488
  defaults.push({
1486
- key: group.name.toLowerCase(),
1489
+ key: tagAttrKey(group.name),
1487
1490
  value: group.defaultValue
1488
1491
  });
1489
1492
  }
@@ -2874,7 +2877,7 @@ function parseVisualizationFull(content, palette) {
2874
2877
  );
2875
2878
  for (const group of result.timelineTagGroups) {
2876
2879
  if (!group.defaultValue) continue;
2877
- const key = group.name.toLowerCase();
2880
+ const key = tagAttrKey(group.name);
2878
2881
  for (const event of result.timelineEvents) {
2879
2882
  if (!event.metadata[key]) {
2880
2883
  event.metadata[key] = group.defaultValue;
@@ -5270,7 +5273,7 @@ function renderLegendD3(container, config, state, palette, isDark, callbacks, co
5270
5273
  };
5271
5274
  }
5272
5275
  function renderCapsule(parent, capsule, palette, groupBg, pillBorder, isDark, callbacks) {
5273
- const g = parent.append("g").attr("transform", `translate(${capsule.x},${capsule.y})`).attr("data-legend-group", capsule.groupName.toLowerCase()).style("cursor", "pointer");
5276
+ const g = parent.append("g").attr("transform", `translate(${capsule.x},${capsule.y})`).attr("data-legend-group", tagAttrKey(capsule.groupName)).style("cursor", "pointer");
5274
5277
  g.append("rect").attr("width", capsule.width).attr("height", capsule.height).attr("rx", LEGEND_HEIGHT / 2).attr("fill", groupBg);
5275
5278
  const pill = capsule.pill;
5276
5279
  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);
@@ -5278,7 +5281,7 @@ function renderCapsule(parent, capsule, palette, groupBg, pillBorder, isDark, ca
5278
5281
  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);
5279
5282
  if (capsule.gradient) {
5280
5283
  const gr = capsule.gradient;
5281
- const gradId = `dgmo-legend-ramp-${capsule.groupName.toLowerCase().replace(/[^a-z0-9]+/g, "-")}`;
5284
+ const gradId = `dgmo-legend-ramp-${tagAttrKey(capsule.groupName).replace(/[^a-z0-9]+/g, "-")}`;
5282
5285
  const def = g.append("defs").append("linearGradient").attr("id", gradId);
5283
5286
  for (const stop of valueRampStops(gr.low, gr.high, { isDark })) {
5284
5287
  def.append("stop").attr("offset", `${stop.offset * 100}%`).attr("stop-color", stop.color);
@@ -5308,7 +5311,7 @@ function renderCapsule(parent, capsule, palette, groupBg, pillBorder, isDark, ca
5308
5311
  }
5309
5312
  }
5310
5313
  function renderPill(parent, pill, palette, groupBg, callbacks) {
5311
- const g = parent.append("g").attr("transform", `translate(${pill.x},${pill.y})`).attr("data-legend-group", pill.groupName.toLowerCase()).style("cursor", "pointer");
5314
+ const g = parent.append("g").attr("transform", `translate(${pill.x},${pill.y})`).attr("data-legend-group", tagAttrKey(pill.groupName)).style("cursor", "pointer");
5312
5315
  g.append("rect").attr("width", pill.width).attr("height", pill.height).attr("rx", pill.height / 2).attr("fill", groupBg);
5313
5316
  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);
5314
5317
  if (callbacks?.onGroupToggle) {
@@ -5414,6 +5417,7 @@ var LEGEND_TEXT_DY;
5414
5417
  var init_legend_d3 = __esm({
5415
5418
  "src/utils/legend-d3.ts"() {
5416
5419
  "use strict";
5420
+ init_tag_groups();
5417
5421
  init_legend_constants();
5418
5422
  init_legend_layout();
5419
5423
  init_color_utils();
@@ -9335,7 +9339,7 @@ function parseERDiagram(content, palette) {
9335
9339
  );
9336
9340
  for (const group of result.tagGroups) {
9337
9341
  if (!group.defaultValue) continue;
9338
- const key = group.name.toLowerCase();
9342
+ const key = tagAttrKey(group.name);
9339
9343
  for (const table of result.tables) {
9340
9344
  if (!table.metadata[key]) {
9341
9345
  const mutableMeta = tableMetadataMap.get(table.id);
@@ -9565,12 +9569,12 @@ function parseOrg(content, palette) {
9565
9569
  if (tagBlockMatch.alias) {
9566
9570
  metaAliasMap.set(
9567
9571
  normalizeName(tagBlockMatch.alias),
9568
- tagBlockMatch.name.toLowerCase()
9572
+ tagAttrKey(tagBlockMatch.name)
9569
9573
  );
9570
9574
  }
9571
9575
  metaAliasMap.set(
9572
9576
  normalizeName(tagBlockMatch.name),
9573
- tagBlockMatch.name.toLowerCase()
9577
+ tagAttrKey(tagBlockMatch.name)
9574
9578
  );
9575
9579
  result.tagGroups.push(currentTagGroup);
9576
9580
  continue;
@@ -9956,12 +9960,12 @@ function parseSitemap(content, palette) {
9956
9960
  if (tagBlockMatch.alias) {
9957
9961
  metaAliasMap.set(
9958
9962
  normalizeName(tagBlockMatch.alias),
9959
- tagBlockMatch.name.toLowerCase()
9963
+ tagAttrKey(tagBlockMatch.name)
9960
9964
  );
9961
9965
  }
9962
9966
  metaAliasMap.set(
9963
9967
  normalizeName(tagBlockMatch.name),
9964
- tagBlockMatch.name.toLowerCase()
9968
+ tagAttrKey(tagBlockMatch.name)
9965
9969
  );
9966
9970
  result.tagGroups.push(newTagGroup);
9967
9971
  continue;
@@ -10131,6 +10135,14 @@ function parseSitemap(content, palette) {
10131
10135
  continue;
10132
10136
  }
10133
10137
  }
10138
+ const enclosingContainer = nestedPageInsideContainer(indent, indentStack);
10139
+ if (enclosingContainer) {
10140
+ pushError(
10141
+ lineNumber,
10142
+ `Pages inside container '[${enclosingContainer}]' cannot have indented sub-pages. Containers hold a flat list of pages.`
10143
+ );
10144
+ continue;
10145
+ }
10134
10146
  const node = parseNodeLabel2(
10135
10147
  trimmed,
10136
10148
  lineNumber,
@@ -10278,6 +10290,16 @@ function attachNode2(node, indent, indentStack, result) {
10278
10290
  }
10279
10291
  indentStack.push({ node, indent });
10280
10292
  }
10293
+ function nestedPageInsideContainer(indent, indentStack) {
10294
+ let idx = indentStack.length - 1;
10295
+ while (idx >= 0 && indentStack[idx].indent >= indent) idx--;
10296
+ if (idx < 0) return null;
10297
+ if (indentStack[idx].node.isContainer) return null;
10298
+ for (let k = idx - 1; k >= 0; k--) {
10299
+ if (indentStack[k].node.isContainer) return indentStack[k].node.label;
10300
+ }
10301
+ return null;
10302
+ }
10281
10303
  function findParentNode(indent, indentStack) {
10282
10304
  for (let i = indentStack.length - 1; i >= 0; i--) {
10283
10305
  if (indentStack[i].indent < indent) {
@@ -10861,12 +10883,12 @@ function parsePert(content, parseOpts = {}) {
10861
10883
  if (tagBlockMatch.alias) {
10862
10884
  metaAliasMap.set(
10863
10885
  tagBlockMatch.alias.toLowerCase(),
10864
- tagBlockMatch.name.toLowerCase()
10886
+ tagAttrKey(tagBlockMatch.name)
10865
10887
  );
10866
10888
  }
10867
10889
  metaAliasMap.set(
10868
10890
  normalizeName(tagBlockMatch.name),
10869
- tagBlockMatch.name.toLowerCase()
10891
+ tagAttrKey(tagBlockMatch.name)
10870
10892
  );
10871
10893
  tagGroups.push(currentTagGroup);
10872
10894
  if (tagBlockMatch.inlineValues) {
@@ -12331,10 +12353,10 @@ function emitCapsule(capsule, palette, groupBg, pillBorder) {
12331
12353
  `<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>`
12332
12354
  );
12333
12355
  }
12334
- return `<g transform="translate(${capsule.x},${capsule.y})" data-legend-group="${esc(capsule.groupName.toLowerCase())}" style="cursor:pointer">${inner.join("")}</g>`;
12356
+ return `<g transform="translate(${capsule.x},${capsule.y})" data-legend-group="${esc(tagAttrKey(capsule.groupName))}" style="cursor:pointer">${inner.join("")}</g>`;
12335
12357
  }
12336
12358
  function emitPill(pill, palette, groupBg) {
12337
- 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>`;
12359
+ 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>`;
12338
12360
  }
12339
12361
  function renderLegendSvg(groups, options) {
12340
12362
  if (groups.length === 0) return { svg: "", height: 0, width: 0 };
@@ -12395,6 +12417,7 @@ function renderLegendSvgFromConfig(config, state, palette, containerWidth) {
12395
12417
  var init_legend_svg = __esm({
12396
12418
  "src/utils/legend-svg.ts"() {
12397
12419
  "use strict";
12420
+ init_tag_groups();
12398
12421
  init_legend_constants();
12399
12422
  init_legend_layout();
12400
12423
  init_color_utils();
@@ -15085,12 +15108,12 @@ function parseKanban(content, palette) {
15085
15108
  if (tagBlockMatch.alias) {
15086
15109
  metaAliasMap.set(
15087
15110
  normalizeName(tagBlockMatch.alias),
15088
- tagBlockMatch.name.toLowerCase()
15111
+ tagAttrKey(tagBlockMatch.name)
15089
15112
  );
15090
15113
  }
15091
15114
  metaAliasMap.set(
15092
15115
  normalizeName(tagBlockMatch.name),
15093
- tagBlockMatch.name.toLowerCase()
15116
+ tagAttrKey(tagBlockMatch.name)
15094
15117
  );
15095
15118
  result.tagGroups.push(currentTagGroup);
15096
15119
  continue;
@@ -15279,7 +15302,7 @@ function parseKanban(content, palette) {
15279
15302
  }
15280
15303
  for (const group of result.tagGroups) {
15281
15304
  const values = new Set(group.entries.map((e) => e.value.toLowerCase()));
15282
- tagValueSets.set(group.name.toLowerCase(), values);
15305
+ tagValueSets.set(tagAttrKey(group.name), values);
15283
15306
  }
15284
15307
  for (const col of result.columns) {
15285
15308
  if (col.wipLimit != null && col.cards.length > col.wipLimit) {
@@ -15541,12 +15564,12 @@ function parseC4(content, palette) {
15541
15564
  if (tagBlockMatch.alias) {
15542
15565
  metaAliasMap.set(
15543
15566
  normalizeName(tagBlockMatch.alias),
15544
- tagBlockMatch.name.toLowerCase()
15567
+ tagAttrKey(tagBlockMatch.name)
15545
15568
  );
15546
15569
  }
15547
15570
  metaAliasMap.set(
15548
15571
  normalizeName(tagBlockMatch.name),
15549
- tagBlockMatch.name.toLowerCase()
15572
+ tagAttrKey(tagBlockMatch.name)
15550
15573
  );
15551
15574
  result.tagGroups.push(currentTagGroup);
15552
15575
  continue;
@@ -17229,7 +17252,7 @@ function parseInfra(content) {
17229
17252
  }
17230
17253
  for (const tg of result.tagGroups) {
17231
17254
  if (!tg.defaultValue) continue;
17232
- const key = (tg.alias ?? tg.name).toLowerCase();
17255
+ const key = tagAttrKey(tg.alias ?? tg.name);
17233
17256
  for (const node of result.nodes) {
17234
17257
  if (node.isEdge) continue;
17235
17258
  if (!(key in node.tags)) {
@@ -17366,6 +17389,7 @@ var CONNECTION_RE, SIMPLE_CONNECTION_RE, ASYNC_CONNECTION_RE, ASYNC_SIMPLE_CONNE
17366
17389
  var init_parser15 = __esm({
17367
17390
  "src/infra/parser.ts"() {
17368
17391
  "use strict";
17392
+ init_tag_groups();
17369
17393
  init_diagnostics();
17370
17394
  init_description_helpers();
17371
17395
  init_arrows();
@@ -17911,13 +17935,10 @@ function parseGantt(content, palette) {
17911
17935
  if (tagMatch.alias) {
17912
17936
  metaAliasMap.set(
17913
17937
  normalizeName(tagMatch.alias),
17914
- tagMatch.name.toLowerCase()
17938
+ tagAttrKey(tagMatch.name)
17915
17939
  );
17916
17940
  }
17917
- metaAliasMap.set(
17918
- normalizeName(tagMatch.name),
17919
- tagMatch.name.toLowerCase()
17920
- );
17941
+ metaAliasMap.set(normalizeName(tagMatch.name), tagAttrKey(tagMatch.name));
17921
17942
  continue;
17922
17943
  }
17923
17944
  const topWorkweekMatch = line11.match(WORKWEEK_RE);
@@ -18843,7 +18864,7 @@ function parseMap(content, palette) {
18843
18864
  const aliasMap = /* @__PURE__ */ new Map();
18844
18865
  const open = { tag: null, route: null, poi: null };
18845
18866
  const registry = () => withTagAliases(MAP_REGISTRY, new Set(aliasMap.keys()));
18846
- const tagGroupNames = () => new Set(tagGroups.map((g) => g.name.toLowerCase()));
18867
+ const tagGroupNames = () => new Set(tagGroups.map((g) => tagAttrKey(g.name)));
18847
18868
  const closeBlocks = () => {
18848
18869
  open.tag = null;
18849
18870
  open.route = null;
@@ -19027,7 +19048,7 @@ function parseMap(content, palette) {
19027
19048
  entries: [],
19028
19049
  lineNumber: line11
19029
19050
  };
19030
- if (m.alias) aliasMap.set(m.alias.toLowerCase(), m.name.toLowerCase());
19051
+ if (m.alias) aliasMap.set(m.alias.toLowerCase(), tagAttrKey(m.name));
19031
19052
  tagGroups.push(group);
19032
19053
  open.tag = group;
19033
19054
  if (m.inlineValues?.length) {
@@ -19560,12 +19581,12 @@ function parseBoxesAndLines(content, palette) {
19560
19581
  if (tagBlockMatch.alias) {
19561
19582
  metaAliasMap.set(
19562
19583
  normalizeName(tagBlockMatch.alias),
19563
- tagBlockMatch.name.toLowerCase()
19584
+ tagAttrKey(tagBlockMatch.name)
19564
19585
  );
19565
19586
  }
19566
19587
  metaAliasMap.set(
19567
19588
  normalizeName(tagBlockMatch.name),
19568
- tagBlockMatch.name.toLowerCase()
19589
+ tagAttrKey(tagBlockMatch.name)
19569
19590
  );
19570
19591
  if (tagBlockMatch.inlineValues) {
19571
19592
  for (const rawVal of tagBlockMatch.inlineValues) {
@@ -20306,12 +20327,12 @@ function parseMindmap(content, palette) {
20306
20327
  if (tagBlockMatch.alias) {
20307
20328
  aliasMap.set(
20308
20329
  tagBlockMatch.alias.toLowerCase(),
20309
- tagBlockMatch.name.toLowerCase()
20330
+ tagAttrKey(tagBlockMatch.name)
20310
20331
  );
20311
20332
  }
20312
20333
  aliasMap.set(
20313
- tagBlockMatch.name.toLowerCase(),
20314
- tagBlockMatch.name.toLowerCase()
20334
+ tagAttrKey(tagBlockMatch.name),
20335
+ tagAttrKey(tagBlockMatch.name)
20315
20336
  );
20316
20337
  result.tagGroups.push(currentTagGroup);
20317
20338
  continue;
@@ -22020,7 +22041,18 @@ function parseJourneyMap(content, palette) {
22020
22041
  palette
22021
22042
  ) ?? void 0;
22022
22043
  } else {
22023
- personaName = afterKeyword;
22044
+ const { label, colorName } = peelTrailingColorName(afterKeyword);
22045
+ if (colorName) {
22046
+ personaName = label;
22047
+ personaColor = resolveColorWithDiagnostic(
22048
+ colorName,
22049
+ lineNumber,
22050
+ result.diagnostics,
22051
+ palette
22052
+ ) ?? void 0;
22053
+ } else {
22054
+ personaName = afterKeyword;
22055
+ }
22024
22056
  }
22025
22057
  }
22026
22058
  if (!personaName) {
@@ -22063,12 +22095,12 @@ function parseJourneyMap(content, palette) {
22063
22095
  if (tagBlockMatch.alias) {
22064
22096
  aliasMap.set(
22065
22097
  tagBlockMatch.alias.toLowerCase(),
22066
- tagBlockMatch.name.toLowerCase()
22098
+ tagAttrKey(tagBlockMatch.name)
22067
22099
  );
22068
22100
  }
22069
22101
  aliasMap.set(
22070
- tagBlockMatch.name.toLowerCase(),
22071
- tagBlockMatch.name.toLowerCase()
22102
+ tagAttrKey(tagBlockMatch.name),
22103
+ tagAttrKey(tagBlockMatch.name)
22072
22104
  );
22073
22105
  result.tagGroups.push(currentTagGroup);
22074
22106
  continue;
@@ -22212,7 +22244,7 @@ function parseJourneyMap(content, palette) {
22212
22244
  }
22213
22245
  for (const group of result.tagGroups) {
22214
22246
  const values = new Set(group.entries.map((e) => e.value.toLowerCase()));
22215
- tagValueSets.set(group.name.toLowerCase(), values);
22247
+ tagValueSets.set(tagAttrKey(group.name), values);
22216
22248
  }
22217
22249
  const allSteps = hasPhases ? result.phases.flatMap((p) => p.steps) : result.steps;
22218
22250
  for (const step of allSteps) {
@@ -24198,7 +24230,7 @@ function computeLegendGroups(tagGroups, showEyeIcons, usedValuesByGroup) {
24198
24230
  const groups = [];
24199
24231
  for (const group of tagGroups) {
24200
24232
  if (group.entries.length === 0) continue;
24201
- const usedValues = usedValuesByGroup?.get(group.name.toLowerCase());
24233
+ const usedValues = usedValuesByGroup?.get(tagAttrKey(group.name));
24202
24234
  const visibleEntries = usedValues ? group.entries.filter((e) => usedValues.has(e.value.toLowerCase())) : group.entries;
24203
24235
  if (visibleEntries.length === 0) continue;
24204
24236
  const pillWidth2 = measureLegendText(group.name, LEGEND_PILL_FONT_SIZE) + LEGEND_PILL_PAD;
@@ -24811,7 +24843,7 @@ function layoutOrg(parsed, hiddenCounts, activeTagGroup, hiddenAttributes, expan
24811
24843
  const totalHeight = finalMaxY - minY + MARGIN * 2;
24812
24844
  const usedValuesByGroup = /* @__PURE__ */ new Map();
24813
24845
  for (const group of parsed.tagGroups) {
24814
- const key = group.name.toLowerCase();
24846
+ const key = tagAttrKey(group.name);
24815
24847
  const used = /* @__PURE__ */ new Set();
24816
24848
  const walk = (node) => {
24817
24849
  if (!node.isContainer && node.metadata[key]) {
@@ -24869,6 +24901,7 @@ var import_d3_hierarchy, LABEL_FONT_SIZE, META_FONT_SIZE, META_LINE_HEIGHT, HEAD
24869
24901
  var init_layout = __esm({
24870
24902
  "src/org/layout.ts"() {
24871
24903
  "use strict";
24904
+ init_tag_groups();
24872
24905
  import_d3_hierarchy = require("d3-hierarchy");
24873
24906
  init_tag_groups();
24874
24907
  init_legend_constants();
@@ -25230,14 +25263,14 @@ function renderOrg(container, parsed, layout, palette, isDark, onClickItem, expo
25230
25263
  const contentG = mainG.append("g").attr("transform", `translate(0, ${contentYShift})`);
25231
25264
  const displayNames = /* @__PURE__ */ new Map();
25232
25265
  for (const group of parsed.tagGroups) {
25233
- displayNames.set(group.name.toLowerCase(), group.name);
25266
+ displayNames.set(tagAttrKey(group.name), group.name);
25234
25267
  }
25235
25268
  const rootNodeIds = new Set(parsed.roots.map((r) => r.id));
25236
25269
  const colorOff = parsed.options?.["color"] === "off";
25237
25270
  for (const c of layout.containers) {
25238
25271
  const cG = contentG.append("g").attr("transform", `translate(${c.x}, ${c.y})`).attr("class", "org-container").attr("data-line-number", String(c.lineNumber));
25239
25272
  if (activeTagGroup) {
25240
- const tagKey = activeTagGroup.toLowerCase();
25273
+ const tagKey = tagAttrKey(activeTagGroup);
25241
25274
  const metaValue = c.tagMetadata[tagKey];
25242
25275
  if (metaValue) {
25243
25276
  cG.attr(`data-tag-${tagKey}`, metaValue.toLowerCase());
@@ -25319,7 +25352,7 @@ function renderOrg(container, parsed, layout, palette, isDark, onClickItem, expo
25319
25352
  if (containerNodeIds.has(node.id)) continue;
25320
25353
  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));
25321
25354
  if (activeTagGroup) {
25322
- const tagKey = activeTagGroup.toLowerCase();
25355
+ const tagKey = tagAttrKey(activeTagGroup);
25323
25356
  const metaValue = node.tagMetadata[tagKey];
25324
25357
  if (metaValue) {
25325
25358
  nodeG.attr(`data-tag-${tagKey}`, metaValue.toLowerCase());
@@ -25492,7 +25525,7 @@ function renderOrg(container, parsed, layout, palette, isDark, onClickItem, expo
25492
25525
  const computedLayout = legendHandle.getLayout();
25493
25526
  if (computedLayout.activeCapsule?.addonX != null) {
25494
25527
  const capsule = computedLayout.activeCapsule;
25495
- const groupKey2 = capsule.groupName.toLowerCase();
25528
+ const groupKey2 = tagAttrKey(capsule.groupName);
25496
25529
  const isHidden = hiddenAttributes?.has(groupKey2) ?? false;
25497
25530
  const activeGroupEl = legendParentBase.select(
25498
25531
  `[data-legend-group="${groupKey2}"]`
@@ -25531,6 +25564,7 @@ var d3Selection6, DIAGRAM_PADDING, MAX_SCALE, TITLE_HEIGHT, ANCESTOR_DOT_R, ANCE
25531
25564
  var init_renderer = __esm({
25532
25565
  "src/org/renderer.ts"() {
25533
25566
  "use strict";
25567
+ init_tag_groups();
25534
25568
  d3Selection6 = __toESM(require("d3-selection"), 1);
25535
25569
  init_fonts();
25536
25570
  init_export_container();
@@ -25616,7 +25650,7 @@ function computeLegendGroups2(tagGroups, usedValuesByGroup) {
25616
25650
  const groups = [];
25617
25651
  for (const group of tagGroups) {
25618
25652
  if (group.entries.length === 0) continue;
25619
- const usedValues = usedValuesByGroup?.get(group.name.toLowerCase());
25653
+ const usedValues = usedValuesByGroup?.get(tagAttrKey(group.name));
25620
25654
  const visibleEntries = usedValues ? group.entries.filter((e) => usedValues.has(e.value.toLowerCase())) : group.entries;
25621
25655
  if (visibleEntries.length === 0) continue;
25622
25656
  const pillWidth2 = measureLegendText(group.name, LEGEND_PILL_FONT_SIZE) + LEGEND_PILL_PAD;
@@ -26096,7 +26130,7 @@ function layoutSitemap(parsed, hiddenCounts, activeTagGroup, hiddenAttributes, e
26096
26130
  totalHeight += MARGIN2;
26097
26131
  const usedValuesByGroup = /* @__PURE__ */ new Map();
26098
26132
  for (const group of parsed.tagGroups) {
26099
- const key = group.name.toLowerCase();
26133
+ const key = tagAttrKey(group.name);
26100
26134
  const used = /* @__PURE__ */ new Set();
26101
26135
  const walk = (node) => {
26102
26136
  if (!node.isContainer && node.metadata[key]) {
@@ -26140,6 +26174,7 @@ var import_dagre, LABEL_FONT_SIZE3, META_FONT_SIZE3, CONTAINER_LABEL_FONT_SIZE2,
26140
26174
  var init_layout2 = __esm({
26141
26175
  "src/sitemap/layout.ts"() {
26142
26176
  "use strict";
26177
+ init_tag_groups();
26143
26178
  import_dagre = __toESM(require("@dagrejs/dagre"), 1);
26144
26179
  init_tag_groups();
26145
26180
  init_legend_constants();
@@ -26409,10 +26444,10 @@ function renderSitemap(container, parsed, layout, palette, isDark, onClickItem,
26409
26444
  const displayNames = /* @__PURE__ */ new Map();
26410
26445
  const tagColors = /* @__PURE__ */ new Map();
26411
26446
  for (const group of parsed.tagGroups) {
26412
- displayNames.set(group.name.toLowerCase(), group.name);
26447
+ displayNames.set(tagAttrKey(group.name), group.name);
26413
26448
  for (const entry of group.entries) {
26414
26449
  tagColors.set(
26415
- `${group.name.toLowerCase()}:${entry.value.toLowerCase()}`,
26450
+ `${tagAttrKey(group.name)}:${entry.value.toLowerCase()}`,
26416
26451
  entry.color
26417
26452
  );
26418
26453
  }
@@ -26429,7 +26464,7 @@ function renderSitemap(container, parsed, layout, palette, isDark, onClickItem,
26429
26464
  );
26430
26465
  }
26431
26466
  if (activeTagGroup) {
26432
- const tagKey = activeTagGroup.toLowerCase();
26467
+ const tagKey = tagAttrKey(activeTagGroup);
26433
26468
  const tagVal = c.tagMetadata[tagKey];
26434
26469
  if (tagVal) cG.attr(`data-tag-${tagKey}`, tagVal.toLowerCase());
26435
26470
  }
@@ -26492,7 +26527,7 @@ function renderSitemap(container, parsed, layout, palette, isDark, onClickItem,
26492
26527
  nodeG.style("cursor", "pointer").on("click", () => onClickItem(node.lineNumber));
26493
26528
  }
26494
26529
  if (activeTagGroup) {
26495
- const tagKey = activeTagGroup.toLowerCase();
26530
+ const tagKey = tagAttrKey(activeTagGroup);
26496
26531
  const tagVal = node.tagMetadata[tagKey];
26497
26532
  if (tagVal) nodeG.attr(`data-tag-${tagKey}`, tagVal.toLowerCase());
26498
26533
  }
@@ -26619,7 +26654,7 @@ function renderLegend(parent, legendGroups, palette, isDark, activeTagGroup, fix
26619
26654
  const computedLayout = legendHandle.getLayout();
26620
26655
  if (computedLayout.activeCapsule?.addonX != null) {
26621
26656
  const capsule = computedLayout.activeCapsule;
26622
- const groupKey2 = capsule.groupName.toLowerCase();
26657
+ const groupKey2 = tagAttrKey(capsule.groupName);
26623
26658
  const isHidden = hiddenAttributes?.has(groupKey2) ?? false;
26624
26659
  const activeGroupEl = parent.select(`[data-legend-group="${groupKey2}"]`);
26625
26660
  if (!activeGroupEl.empty()) {
@@ -26684,6 +26719,7 @@ var d3Selection7, d3Shape2, DIAGRAM_PADDING2, MAX_SCALE2, TITLE_HEIGHT2, ARROWHE
26684
26719
  var init_renderer2 = __esm({
26685
26720
  "src/sitemap/renderer.ts"() {
26686
26721
  "use strict";
26722
+ init_tag_groups();
26687
26723
  d3Selection7 = __toESM(require("d3-selection"), 1);
26688
26724
  d3Shape2 = __toESM(require("d3-shape"), 1);
26689
26725
  init_fonts();
@@ -26822,7 +26858,7 @@ function resolveCardTagMeta(card, tagGroups, hiddenMetaGroups) {
26822
26858
  const meta = [];
26823
26859
  for (const group of tagGroups) {
26824
26860
  if (hiddenMetaGroups?.includes(group.name.toLowerCase())) continue;
26825
- const tagValue = card.tags[group.name.toLowerCase()];
26861
+ const tagValue = card.tags[tagAttrKey(group.name)];
26826
26862
  const value = tagValue ?? group.defaultValue;
26827
26863
  if (!value) continue;
26828
26864
  const entry = group.entries.find(
@@ -26842,7 +26878,7 @@ function resolveCardTagColor(card, tagGroups, activeTagGroup) {
26842
26878
  (g) => g.name.toLowerCase() === activeTagGroup.toLowerCase()
26843
26879
  );
26844
26880
  if (!group) return card.color;
26845
- const tagValue = card.tags[group.name.toLowerCase()];
26881
+ const tagValue = card.tags[tagAttrKey(group.name)];
26846
26882
  const value = tagValue ?? group.defaultValue;
26847
26883
  if (!value) return void 0;
26848
26884
  const entry = group.entries.find(
@@ -27152,7 +27188,7 @@ function renderKanban(container, parsed, palette, isDark, options) {
27152
27188
  );
27153
27189
  const cg = g.append("g").attr("class", "kanban-card").attr("data-card-id", card.id).attr("data-line-number", card.lineNumber);
27154
27190
  if (activeTagGroup) {
27155
- const tagKey = activeTagGroup.toLowerCase();
27191
+ const tagKey = tagAttrKey(activeTagGroup);
27156
27192
  const tagValue = card.tags[tagKey];
27157
27193
  const group = parsed.tagGroups.find(
27158
27194
  (tg) => tg.name.toLowerCase() === tagKey
@@ -27213,7 +27249,7 @@ function drawSwimlaneIcon(parent, x, y, isActive, palette) {
27213
27249
  return iconG;
27214
27250
  }
27215
27251
  function bucketCardsBySwimlane(columns, swimlaneGroup) {
27216
- const tagKey = swimlaneGroup.name.toLowerCase();
27252
+ const tagKey = tagAttrKey(swimlaneGroup.name);
27217
27253
  const buckets = /* @__PURE__ */ new Map();
27218
27254
  for (const entry of swimlaneGroup.entries) {
27219
27255
  buckets.set(entry.value.toLowerCase(), {
@@ -27501,7 +27537,7 @@ function renderSwimlaneCard(parent, cardLayout, tagGroups, activeTagGroup, palet
27501
27537
  );
27502
27538
  const cg = parent.append("g").attr("class", "kanban-card").attr("data-card-id", card.id).attr("data-line-number", card.lineNumber);
27503
27539
  if (activeTagGroup) {
27504
- const tagKey = activeTagGroup.toLowerCase();
27540
+ const tagKey = tagAttrKey(activeTagGroup);
27505
27541
  const group = tagGroups.find((tg) => tg.name.toLowerCase() === tagKey);
27506
27542
  const value = card.tags[tagKey] ?? group?.defaultValue;
27507
27543
  if (value) {
@@ -27534,6 +27570,7 @@ var d3Selection8, DIAGRAM_PADDING3, COLUMN_GAP, COLUMN_HEADER_HEIGHT, COLUMN_PAD
27534
27570
  var init_renderer3 = __esm({
27535
27571
  "src/kanban/renderer.ts"() {
27536
27572
  "use strict";
27573
+ init_tag_groups();
27537
27574
  d3Selection8 = __toESM(require("d3-selection"), 1);
27538
27575
  init_fonts();
27539
27576
  init_color_utils();
@@ -28516,7 +28553,7 @@ function classifyEREntities(tables, relationships) {
28516
28553
  const variance = indegreeValues.reduce((a, b) => a + (b - mean) ** 2, 0) / indegreeValues.length;
28517
28554
  const stddev = Math.sqrt(variance);
28518
28555
  const sorted = [...indegreeValues].sort((a, b) => a - b);
28519
- const median = sorted.length % 2 === 0 ? (sorted[sorted.length / 2 - 1] + sorted[sorted.length / 2]) / 2 : sorted[Math.floor(sorted.length / 2)];
28556
+ const median2 = sorted.length % 2 === 0 ? (sorted[sorted.length / 2 - 1] + sorted[sorted.length / 2]) / 2 : sorted[Math.floor(sorted.length / 2)];
28520
28557
  for (const table of tables) {
28521
28558
  const id = table.id;
28522
28559
  const cols = table.columns;
@@ -28557,7 +28594,7 @@ function classifyEREntities(tables, relationships) {
28557
28594
  const nameMatchesLookup = LOOKUP_NAME_SUFFIXES.some(
28558
28595
  (s) => nameLower.endsWith(s)
28559
28596
  );
28560
- if (nameMatchesLookup && cols.length <= 6 && fkCount <= 1 && indegree > median) {
28597
+ if (nameMatchesLookup && cols.length <= 6 && fkCount <= 1 && indegree > median2) {
28561
28598
  result.set(id, "lookup");
28562
28599
  continue;
28563
28600
  }
@@ -28796,7 +28833,7 @@ function renderERDiagram(container, parsed, layout, palette, isDark, onClickItem
28796
28833
  seriesColors2[ni % seriesColors2.length];
28797
28834
  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);
28798
28835
  if (activeTagGroup) {
28799
- const tagKey = activeTagGroup.toLowerCase();
28836
+ const tagKey = tagAttrKey(activeTagGroup);
28800
28837
  const tagValue = node.metadata[tagKey];
28801
28838
  if (tagValue) {
28802
28839
  nodeG.attr(`data-tag-${tagKey}`, tagValue.toLowerCase());
@@ -28975,6 +29012,7 @@ var d3Selection10, d3Shape4, DIAGRAM_PADDING5, MAX_SCALE4, TABLE_FONT_SIZE2, COL
28975
29012
  var init_renderer5 = __esm({
28976
29013
  "src/er/renderer.ts"() {
28977
29014
  "use strict";
29015
+ init_tag_groups();
28978
29016
  d3Selection10 = __toESM(require("d3-selection"), 1);
28979
29017
  d3Shape4 = __toESM(require("d3-shape"), 1);
28980
29018
  init_fonts();
@@ -29145,63 +29183,6 @@ function ensureArrowMarkers(defs, colors) {
29145
29183
  ).attr("fill", color);
29146
29184
  }
29147
29185
  }
29148
- function edgePolylineMidpoint(points) {
29149
- if (points.length === 0) return { x: 0, y: 0 };
29150
- if (points.length === 1) return { x: points[0].x, y: points[0].y };
29151
- let total = 0;
29152
- const segLen = [];
29153
- for (let k = 1; k < points.length; k++) {
29154
- const len = Math.hypot(
29155
- points[k].x - points[k - 1].x,
29156
- points[k].y - points[k - 1].y
29157
- );
29158
- segLen.push(len);
29159
- total += len;
29160
- }
29161
- let half = total / 2;
29162
- for (let k = 1; k < points.length; k++) {
29163
- const len = segLen[k - 1];
29164
- if (half <= len || k === points.length - 1) {
29165
- const t = len === 0 ? 0 : Math.min(1, half / len);
29166
- return {
29167
- x: points[k - 1].x + (points[k].x - points[k - 1].x) * t,
29168
- y: points[k - 1].y + (points[k].y - points[k - 1].y) * t
29169
- };
29170
- }
29171
- half -= len;
29172
- }
29173
- const last = points[points.length - 1];
29174
- return { x: last.x, y: last.y };
29175
- }
29176
- function resolveEdgeLabelOverlaps(labels) {
29177
- const MAX_PASSES = 8;
29178
- const PAD = 4;
29179
- for (let pass = 0; pass < MAX_PASSES; pass++) {
29180
- let moved = false;
29181
- for (let i = 0; i < labels.length; i++) {
29182
- for (let j = i + 1; j < labels.length; j++) {
29183
- const a = labels[i];
29184
- const b = labels[j];
29185
- const dx = Math.abs(a.x - b.x);
29186
- const dy = Math.abs(a.y - b.y);
29187
- const overlapX = (a.width + b.width) / 2 + PAD - dx;
29188
- const overlapY = (a.height + b.height) / 2 + PAD - dy;
29189
- if (overlapX > 0 && overlapY > 0) {
29190
- const shift = overlapY / 2 + 1;
29191
- if (a.y < b.y) {
29192
- a.y -= shift;
29193
- b.y += shift;
29194
- } else {
29195
- a.y += shift;
29196
- b.y -= shift;
29197
- }
29198
- moved = true;
29199
- }
29200
- }
29201
- }
29202
- if (!moved) break;
29203
- }
29204
- }
29205
29186
  function renderBoxesAndLines(container, parsed, layout, palette, isDark, options) {
29206
29187
  const {
29207
29188
  onClickItem,
@@ -29408,8 +29389,6 @@ function renderBoxesAndLines(container, parsed, layout, palette, isDark, options
29408
29389
  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);
29409
29390
  }
29410
29391
  }
29411
- const labelPositions = [];
29412
- const edgeGroups = /* @__PURE__ */ new Map();
29413
29392
  for (let i = 0; i < layout.edges.length; i++) {
29414
29393
  const le = layout.edges[i];
29415
29394
  const color = edgeColorMap.get(i) ?? palette.textMuted;
@@ -29428,7 +29407,6 @@ function renderBoxesAndLines(container, parsed, layout, palette, isDark, options
29428
29407
  const nodeLayout = layoutNodeMap.get(le.source);
29429
29408
  if (nodeLayout) {
29430
29409
  const edgeG2 = diagramG.append("g").attr("class", "bl-edge-group").attr("data-line-number", String(le.lineNumber));
29431
- edgeGroups.set(i, edgeG2);
29432
29410
  const markerId2 = `bl-arrow-${color.replace("#", "")}`;
29433
29411
  const cx = nodeLayout.x;
29434
29412
  const cy = nodeLayout.y;
@@ -29477,7 +29455,6 @@ function renderBoxesAndLines(container, parsed, layout, palette, isDark, options
29477
29455
  }
29478
29456
  if (points.length < 2) continue;
29479
29457
  const edgeG = diagramG.append("g").attr("class", "bl-edge-group").attr("data-line-number", String(le.lineNumber));
29480
- edgeGroups.set(i, edgeG);
29481
29458
  const markerId = `bl-arrow-${color.replace("#", "")}`;
29482
29459
  const gen = le.straight ? lineGeneratorStraight : parsed.direction === "TB" ? lineGeneratorTB : lineGeneratorLR;
29483
29460
  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})`);
@@ -29485,31 +29462,6 @@ function renderBoxesAndLines(container, parsed, layout, palette, isDark, options
29485
29462
  const revId = `bl-arrow-rev-${color.replace("#", "")}`;
29486
29463
  path.attr("marker-start", `url(#${revId})`);
29487
29464
  }
29488
- if (le.label && le.points.length > 0) {
29489
- const lw = measureText(le.label, sEdgeLabelFontSize);
29490
- const labelH = sEdgeLabelFontSize + 6;
29491
- const mid = edgePolylineMidpoint(le.points);
29492
- let ly = mid.y;
29493
- if (le.parallelCount > 1 && le.yOffset !== 0) {
29494
- ly += (le.yOffset < 0 ? -labelH : labelH) * 0.5;
29495
- }
29496
- labelPositions.push({
29497
- x: mid.x,
29498
- y: ly,
29499
- width: lw + 8,
29500
- height: labelH,
29501
- idx: i
29502
- });
29503
- }
29504
- }
29505
- resolveEdgeLabelOverlaps(labelPositions);
29506
- for (const lp of labelPositions) {
29507
- const le = layout.edges[lp.idx];
29508
- if (!le.label) continue;
29509
- const edgeG = edgeGroups.get(lp.idx);
29510
- const target = edgeG ?? diagramG;
29511
- 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);
29512
- 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);
29513
29465
  }
29514
29466
  for (const ln of layout.nodes) {
29515
29467
  const node = nodeMap.get(ln.label);
@@ -29560,8 +29512,8 @@ function renderBoxesAndLines(container, parsed, layout, palette, isDark, options
29560
29512
  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);
29561
29513
  const desc = node.description;
29562
29514
  if (desc && desc.length > 0 && !hideDescriptions) {
29563
- const MAX_LABEL_LINES3 = 3;
29564
- const fitted = fitLabelToHeader(node.label, ln.width, MAX_LABEL_LINES3);
29515
+ const MAX_LABEL_LINES4 = 3;
29516
+ const fitted = fitLabelToHeader(node.label, ln.width, MAX_LABEL_LINES4);
29565
29517
  const labelLines = fitted.lines;
29566
29518
  const labelLineH = fitted.fontSize * 1.3;
29567
29519
  const labelTotalH = labelLines.length * labelLineH;
@@ -29695,6 +29647,35 @@ function renderBoxesAndLines(container, parsed, layout, palette, isDark, options
29695
29647
  }
29696
29648
  }
29697
29649
  }
29650
+ const labelLayer = diagramG.append("g").attr("class", "bl-edge-labels");
29651
+ const labelLineHeight = sEdgeLabelFontSize * 1.3;
29652
+ for (const le of layout.edges) {
29653
+ if (!le.labelLines || le.labelLines.length === 0) continue;
29654
+ if (le.labelX === void 0 || le.labelY === void 0) continue;
29655
+ const lx = le.labelX;
29656
+ const ly = le.labelY;
29657
+ if (hidden.size > 0) {
29658
+ let isHidden = false;
29659
+ for (const [groupKey2, hiddenVals] of hidden) {
29660
+ const val = le.metadata[groupKey2];
29661
+ if (val && hiddenVals.has(val.toLowerCase())) {
29662
+ isHidden = true;
29663
+ break;
29664
+ }
29665
+ }
29666
+ if (isHidden) continue;
29667
+ }
29668
+ const lw = le.labelWidth ?? 0;
29669
+ const lh = le.labelHeight ?? sEdgeLabelFontSize + 6;
29670
+ const lines = le.labelLines;
29671
+ const labelG = labelLayer.append("g").attr("class", "bl-edge-label").attr("data-line-number", String(le.lineNumber));
29672
+ 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);
29673
+ 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);
29674
+ const firstY = ly - (lines.length - 1) / 2 * labelLineHeight + sEdgeLabelFontSize / 3;
29675
+ lines.forEach((line11, k) => {
29676
+ text.append("tspan").attr("x", lx).attr("y", firstY + k * labelLineHeight).text(line11);
29677
+ });
29678
+ }
29698
29679
  const hasDescriptions = parsed.nodes.some(
29699
29680
  (n) => n.description && n.description.length > 0
29700
29681
  );
@@ -29811,6 +29792,222 @@ var init_renderer6 = __esm({
29811
29792
  }
29812
29793
  });
29813
29794
 
29795
+ // src/boxes-and-lines/label-placement.ts
29796
+ function measureEdgeLabel(label, fontSize = EDGE_LABEL_FONT_SIZE6, maxWidth = LABEL_MAX_WIDTH) {
29797
+ let lines = wrapTextToWidth(label, fontSize, maxWidth, { hardBreak: true });
29798
+ if (lines.length > MAX_LABEL_LINES) {
29799
+ const kept = lines.slice(0, MAX_LABEL_LINES - 1);
29800
+ const rest = lines.slice(MAX_LABEL_LINES - 1).join(" ");
29801
+ kept.push(truncateText(rest, fontSize, maxWidth));
29802
+ lines = kept;
29803
+ }
29804
+ let textW = 0;
29805
+ for (const ln of lines) {
29806
+ const w = measureText(ln, fontSize);
29807
+ if (w > textW) textW = w;
29808
+ }
29809
+ return {
29810
+ lines,
29811
+ width: textW + 2 * H_PAD,
29812
+ height: lines.length * fontSize * LABEL_LINE_HEIGHT + 2 * V_PAD
29813
+ };
29814
+ }
29815
+ function edgePolylineMidpoint(points) {
29816
+ return pointAtArcFraction(points, 0.5).p;
29817
+ }
29818
+ function pointAtArcFraction(points, t) {
29819
+ if (points.length === 0) return { p: { x: 0, y: 0 }, nx: 0, ny: 1 };
29820
+ if (points.length === 1)
29821
+ return { p: { x: points[0].x, y: points[0].y }, nx: 0, ny: 1 };
29822
+ const segLen = [];
29823
+ let total = 0;
29824
+ for (let k = 1; k < points.length; k++) {
29825
+ const len = Math.hypot(
29826
+ points[k].x - points[k - 1].x,
29827
+ points[k].y - points[k - 1].y
29828
+ );
29829
+ segLen.push(len);
29830
+ total += len;
29831
+ }
29832
+ let want = total * Math.max(0, Math.min(1, t));
29833
+ for (let k = 1; k < points.length; k++) {
29834
+ const len = segLen[k - 1];
29835
+ if (want <= len || k === points.length - 1) {
29836
+ const f = len === 0 ? 0 : Math.min(1, want / len);
29837
+ const ax = points[k - 1].x;
29838
+ const ay = points[k - 1].y;
29839
+ const bx = points[k].x;
29840
+ const by = points[k].y;
29841
+ const dx = bx - ax;
29842
+ const dy = by - ay;
29843
+ const dlen = Math.hypot(dx, dy) || 1;
29844
+ return {
29845
+ p: { x: ax + dx * f, y: ay + dy * f },
29846
+ nx: -dy / dlen,
29847
+ ny: dx / dlen
29848
+ };
29849
+ }
29850
+ want -= len;
29851
+ }
29852
+ const last = points[points.length - 1];
29853
+ return { p: { x: last.x, y: last.y }, nx: 0, ny: 1 };
29854
+ }
29855
+ function rectFromCenter(cx, cy, w, h) {
29856
+ return {
29857
+ minX: cx - w / 2,
29858
+ minY: cy - h / 2,
29859
+ maxX: cx + w / 2,
29860
+ maxY: cy + h / 2
29861
+ };
29862
+ }
29863
+ function overlapsAny(cx, cy, w, h, obstacles, pad2) {
29864
+ const minX = cx - w / 2 - pad2;
29865
+ const minY = cy - h / 2 - pad2;
29866
+ const maxX = cx + w / 2 + pad2;
29867
+ const maxY = cy + h / 2 + pad2;
29868
+ for (const o of obstacles) {
29869
+ if (minX < o.maxX && maxX > o.minX && minY < o.maxY && maxY > o.minY)
29870
+ return true;
29871
+ }
29872
+ return false;
29873
+ }
29874
+ function slideFractions() {
29875
+ const out = [0.5];
29876
+ for (let k = 1; k <= SLIDE_SAMPLES; k++) {
29877
+ const off = k * 0.08;
29878
+ if (0.5 - off >= 0.12) out.push(0.5 - off);
29879
+ if (0.5 + off <= 0.88) out.push(0.5 + off);
29880
+ }
29881
+ return out;
29882
+ }
29883
+ function findClearPosition(w, h, points, obstacles) {
29884
+ const ts = slideFractions();
29885
+ for (const t of ts) {
29886
+ const { p } = pointAtArcFraction(points, t);
29887
+ if (!overlapsAny(p.x, p.y, w, h, obstacles, BOX_CLEAR_PAD)) return p;
29888
+ }
29889
+ for (let mag = PERP_STEP; mag <= PERP_MAX; mag += PERP_STEP) {
29890
+ for (const t of ts) {
29891
+ const { p, nx, ny } = pointAtArcFraction(points, t);
29892
+ for (const sign of [-1, 1]) {
29893
+ const x = p.x + nx * mag * sign;
29894
+ const y = p.y + ny * mag * sign;
29895
+ if (!overlapsAny(x, y, w, h, obstacles, BOX_CLEAR_PAD)) return { x, y };
29896
+ }
29897
+ }
29898
+ }
29899
+ return null;
29900
+ }
29901
+ function resolveLabelOverlaps(boxes) {
29902
+ const MAX_PASSES = 8;
29903
+ const PAD = 4;
29904
+ for (let pass = 0; pass < MAX_PASSES; pass++) {
29905
+ let moved = false;
29906
+ for (let i = 0; i < boxes.length; i++) {
29907
+ for (let j = i + 1; j < boxes.length; j++) {
29908
+ const a = boxes[i];
29909
+ const b = boxes[j];
29910
+ const dx = Math.abs(a.cx - b.cx);
29911
+ const dy = Math.abs(a.cy - b.cy);
29912
+ const overlapX = (a.w + b.w) / 2 + PAD - dx;
29913
+ const overlapY = (a.h + b.h) / 2 + PAD - dy;
29914
+ if (overlapX > 0 && overlapY > 0) {
29915
+ const shift = overlapY / 2 + 1;
29916
+ if (a.cy < b.cy) {
29917
+ a.cy -= shift;
29918
+ b.cy += shift;
29919
+ } else {
29920
+ a.cy += shift;
29921
+ b.cy -= shift;
29922
+ }
29923
+ moved = true;
29924
+ }
29925
+ }
29926
+ }
29927
+ if (!moved) break;
29928
+ }
29929
+ }
29930
+ function placeEdgeLabels(layout, opts) {
29931
+ const fontSize = opts?.fontSize ?? EDGE_LABEL_FONT_SIZE6;
29932
+ const maxWidth = opts?.maxWidth ?? LABEL_MAX_WIDTH;
29933
+ const obstacles = [];
29934
+ for (const n of layout.nodes)
29935
+ obstacles.push(rectFromCenter(n.x, n.y, n.width, n.height));
29936
+ for (const g of layout.groups)
29937
+ if (g.collapsed)
29938
+ obstacles.push(rectFromCenter(g.x, g.y, g.width, g.height));
29939
+ const boxes = [];
29940
+ layout.edges.forEach((e, idx) => {
29941
+ if (!e.label || e.points.length < 2) return;
29942
+ const m = measureEdgeLabel(e.label, fontSize, maxWidth);
29943
+ const mid = edgePolylineMidpoint(e.points);
29944
+ let cy = mid.y;
29945
+ if (e.parallelCount > 1 && e.yOffset !== 0)
29946
+ cy += (e.yOffset < 0 ? -1 : 1) * (m.height / 2);
29947
+ boxes.push({
29948
+ edgeIdx: idx,
29949
+ cx: mid.x,
29950
+ cy,
29951
+ w: m.width,
29952
+ h: m.height,
29953
+ lines: m.lines,
29954
+ resolved: true
29955
+ });
29956
+ });
29957
+ for (const box of boxes) {
29958
+ if (!overlapsAny(box.cx, box.cy, box.w, box.h, obstacles, BOX_CLEAR_PAD))
29959
+ continue;
29960
+ const e = layout.edges[box.edgeIdx];
29961
+ const clear = findClearPosition(box.w, box.h, e.points, obstacles);
29962
+ if (clear) {
29963
+ box.cx = clear.x;
29964
+ box.cy = clear.y;
29965
+ } else {
29966
+ box.resolved = false;
29967
+ }
29968
+ }
29969
+ resolveLabelOverlaps(boxes);
29970
+ for (const box of boxes) {
29971
+ if (overlapsAny(box.cx, box.cy, box.w, box.h, obstacles, BOX_CLEAR_PAD))
29972
+ box.resolved = false;
29973
+ }
29974
+ const boxByIdx = /* @__PURE__ */ new Map();
29975
+ for (const b of boxes) boxByIdx.set(b.edgeIdx, b);
29976
+ const edges = layout.edges.map((e, idx) => {
29977
+ const b = boxByIdx.get(idx);
29978
+ if (!b) return e;
29979
+ return {
29980
+ ...e,
29981
+ labelX: b.cx,
29982
+ labelY: b.cy,
29983
+ labelWidth: b.w,
29984
+ labelHeight: b.h,
29985
+ labelLines: b.lines
29986
+ };
29987
+ });
29988
+ return {
29989
+ layout: { ...layout, edges },
29990
+ unresolved: boxes.filter((b) => !b.resolved).map((b) => b.edgeIdx)
29991
+ };
29992
+ }
29993
+ 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;
29994
+ var init_label_placement = __esm({
29995
+ "src/boxes-and-lines/label-placement.ts"() {
29996
+ "use strict";
29997
+ init_text_measure();
29998
+ EDGE_LABEL_FONT_SIZE6 = 11;
29999
+ LABEL_MAX_WIDTH = 160;
30000
+ MAX_LABEL_LINES = 3;
30001
+ LABEL_LINE_HEIGHT = 1.3;
30002
+ H_PAD = 6;
30003
+ V_PAD = 3;
30004
+ BOX_CLEAR_PAD = 4;
30005
+ PERP_STEP = 8;
30006
+ PERP_MAX = 40;
30007
+ SLIDE_SAMPLES = 9;
30008
+ }
30009
+ });
30010
+
29814
30011
  // src/boxes-and-lines/layout-layered.ts
29815
30012
  function rng(s) {
29816
30013
  return () => {
@@ -29970,7 +30167,7 @@ function layeredCandidates(parsed, sizes, opts) {
29970
30167
  for (let r = 0; r < maxRank; r++) c += gapCrossings(r);
29971
30168
  return c;
29972
30169
  };
29973
- const median = (vals) => {
30170
+ const median2 = (vals) => {
29974
30171
  if (vals.length === 0) return -1;
29975
30172
  vals.sort((x, y) => x - y);
29976
30173
  const m = Math.floor(vals.length / 2);
@@ -29980,6 +30177,623 @@ function layeredCandidates(parsed, sizes, opts) {
29980
30177
  const right = vals[vals.length - 1] - vals[m];
29981
30178
  return left + right === 0 ? (vals[m - 1] + vals[m]) / 2 : (vals[m - 1] * right + vals[m] * left) / (left + right);
29982
30179
  };
30180
+ const wmedianRank = (r, useUp) => {
30181
+ const ids = ranks[r];
30182
+ const neigh = useUp ? up : down;
30183
+ const med = /* @__PURE__ */ new Map();
30184
+ for (const id of ids)
30185
+ med.set(id, median2(neigh.get(id).map((x) => orderOf.get(x))));
30186
+ const movable = ids.filter((id) => med.get(id) >= 0).sort((a, b) => med.get(a) - med.get(b));
30187
+ const out = new Array(ids.length).fill(null);
30188
+ for (const id of ids) if (med.get(id) < 0) out[orderOf.get(id)] = id;
30189
+ let mi = 0;
30190
+ for (let slot = 0; slot < out.length; slot++)
30191
+ if (out[slot] === null) out[slot] = movable[mi++];
30192
+ const final = out;
30193
+ final.forEach((id, i) => orderOf.set(id, i));
30194
+ ranks[r] = final;
30195
+ };
30196
+ const transpose = () => {
30197
+ let improved = true;
30198
+ let guard = 0;
30199
+ while (improved && guard++ < 12) {
30200
+ improved = false;
30201
+ for (let r = 0; r <= maxRank; r++) {
30202
+ const ids = ranks[r];
30203
+ for (let i = 0; i < ids.length - 1; i++) {
30204
+ const before = (r > 0 ? gapCrossings(r - 1) : 0) + (r < maxRank ? gapCrossings(r) : 0);
30205
+ const a = ids[i], b = ids[i + 1];
30206
+ ids[i] = b;
30207
+ ids[i + 1] = a;
30208
+ orderOf.set(b, i);
30209
+ orderOf.set(a, i + 1);
30210
+ const after = (r > 0 ? gapCrossings(r - 1) : 0) + (r < maxRank ? gapCrossings(r) : 0);
30211
+ if (after < before) {
30212
+ improved = true;
30213
+ } else {
30214
+ ids[i] = a;
30215
+ ids[i + 1] = b;
30216
+ orderOf.set(a, i);
30217
+ orderOf.set(b, i + 1);
30218
+ }
30219
+ }
30220
+ }
30221
+ }
30222
+ };
30223
+ const initOrder = (seed) => {
30224
+ const r = seed === 0 ? null : rng(seed);
30225
+ for (let rr = 0; rr <= maxRank; rr++) {
30226
+ const ids = ranks[rr].slice();
30227
+ if (r) {
30228
+ for (let i = ids.length - 1; i > 0; i--) {
30229
+ const j = Math.floor(r() * (i + 1));
30230
+ [ids[i], ids[j]] = [ids[j], ids[i]];
30231
+ }
30232
+ }
30233
+ ranks[rr] = ids;
30234
+ ids.forEach((id, i) => orderOf.set(id, i));
30235
+ }
30236
+ };
30237
+ const found = [];
30238
+ for (let s = 0; s < restarts; s++) {
30239
+ initOrder(s);
30240
+ let best = totalCrossings();
30241
+ let bestSnap = new Map(orderOf);
30242
+ for (let iter = 0; iter < 8; iter++) {
30243
+ const down1 = iter % 2 === 0;
30244
+ if (down1) for (let r = 1; r <= maxRank; r++) wmedianRank(r, true);
30245
+ else for (let r = maxRank - 1; r >= 0; r--) wmedianRank(r, false);
30246
+ transpose();
30247
+ const c = totalCrossings();
30248
+ if (c < best) {
30249
+ best = c;
30250
+ bestSnap = new Map(orderOf);
30251
+ }
30252
+ if (c === 0) break;
30253
+ }
30254
+ applyOrder(bestSnap);
30255
+ for (let r = 0; r <= maxRank; r++)
30256
+ ranks[r].sort((a, b) => orderOf.get(a) - orderOf.get(b));
30257
+ const sig = ranks.map((r) => r.join(",")).join("|");
30258
+ if (!found.some((f) => f.sig === sig))
30259
+ found.push({ sig, cross: best, order: new Map(bestSnap) });
30260
+ }
30261
+ found.sort((a, b) => a.cross - b.cross);
30262
+ const keep = found.slice(0, keepK);
30263
+ if (keep.length === 0) return [];
30264
+ const results = [];
30265
+ for (const cand of keep) {
30266
+ applyOrder(cand.order);
30267
+ const rk = Array.from({ length: maxRank + 1 }, () => []);
30268
+ for (const n of node.values()) rk[n.rank].push(n.id);
30269
+ for (let r = 0; r <= maxRank; r++)
30270
+ rk[r].sort((a, b) => cand.order.get(a) - cand.order.get(b));
30271
+ results.push(realize(rk));
30272
+ }
30273
+ return results;
30274
+ function realize(rk) {
30275
+ const bandDepth = rk.map(
30276
+ (ids) => Math.max(0, ...ids.map((id) => node.get(id).depth))
30277
+ );
30278
+ const bandCenter = [];
30279
+ let acc = MARGIN3;
30280
+ for (let r = 0; r <= maxRank; r++) {
30281
+ bandCenter[r] = acc + bandDepth[r] / 2;
30282
+ acc += bandDepth[r] + ranksep;
30283
+ }
30284
+ for (let r = 0; r <= maxRank; r++) {
30285
+ let x = MARGIN3;
30286
+ for (const id of rk[r]) {
30287
+ const n = node.get(id);
30288
+ n.cross = x + n.thick / 2;
30289
+ x += n.thick + nodesep;
30290
+ }
30291
+ }
30292
+ const ITER = 18;
30293
+ for (let it = 0; it < ITER; it++) {
30294
+ const downPass = it % 2 === 0;
30295
+ const order = downPass ? Array.from({ length: maxRank + 1 }, (_, i) => i) : Array.from({ length: maxRank + 1 }, (_, i) => maxRank - i);
30296
+ for (const r of order) {
30297
+ const ids = rk[r];
30298
+ const desired = ids.map((id) => {
30299
+ const n = node.get(id);
30300
+ let sw = 0, sx2 = 0;
30301
+ for (const nb of up.get(id)) {
30302
+ const w = weight(n, node.get(nb));
30303
+ sw += w;
30304
+ sx2 += w * node.get(nb).cross;
30305
+ }
30306
+ for (const nb of down.get(id)) {
30307
+ const w = weight(n, node.get(nb));
30308
+ sw += w;
30309
+ sx2 += w * node.get(nb).cross;
30310
+ }
30311
+ return sw > 0 ? sx2 / sw : n.cross;
30312
+ });
30313
+ placeWithSeparation(ids, desired);
30314
+ }
30315
+ }
30316
+ let minC = Infinity;
30317
+ for (const n of node.values()) minC = Math.min(minC, n.cross - n.thick / 2);
30318
+ const shift = MARGIN3 - minC;
30319
+ for (const n of node.values()) n.cross += shift;
30320
+ const coord = (n) => isTB ? { x: n.cross, y: bandCenter[n.rank] } : { x: bandCenter[n.rank], y: n.cross };
30321
+ const layoutNodes = parsed.nodes.map((pn) => {
30322
+ const n = node.get(pn.label);
30323
+ const c = coord(n);
30324
+ return {
30325
+ label: pn.label,
30326
+ x: c.x,
30327
+ y: c.y,
30328
+ width: n.realW,
30329
+ height: n.realH
30330
+ };
30331
+ });
30332
+ const chainById = /* @__PURE__ */ new Map();
30333
+ for (const ch of chains) chainById.set(ch.edgeIdx, ch.chain);
30334
+ let minCross = Infinity, maxCross = -Infinity;
30335
+ for (const n of node.values()) {
30336
+ if (n.dummy) continue;
30337
+ minCross = Math.min(minCross, n.cross - n.thick / 2);
30338
+ maxCross = Math.max(maxCross, n.cross + n.thick / 2);
30339
+ }
30340
+ const GAP = 34;
30341
+ const LANE = 28;
30342
+ const sideOf = (b) => {
30343
+ if (backEdgeSide === "left") return -1;
30344
+ if (backEdgeSide === "right") return 1;
30345
+ const d = node.get(b.src).cross - node.get(b.tgt).cross;
30346
+ return d >= 0 ? 1 : -1;
30347
+ };
30348
+ const backSorted = backEdges.map((b) => ({
30349
+ ...b,
30350
+ side: sideOf(b),
30351
+ span: Math.abs(node.get(b.src).rank - node.get(b.tgt).rank)
30352
+ })).sort((a, b) => a.side - b.side || a.span - b.span);
30353
+ const sideCounts = /* @__PURE__ */ new Map();
30354
+ for (const b of backSorted)
30355
+ sideCounts.set(b.side, (sideCounts.get(b.side) ?? 0) + 1);
30356
+ const laneCounter = /* @__PURE__ */ new Map();
30357
+ const backPoints = /* @__PURE__ */ new Map();
30358
+ const faceLim = (depth) => Math.max(0, depth / 2 - 6);
30359
+ const hubIndex = /* @__PURE__ */ new Map();
30360
+ const hubSize = /* @__PURE__ */ new Map();
30361
+ {
30362
+ const counter = /* @__PURE__ */ new Map();
30363
+ for (const b of backSorted) {
30364
+ const key = `${b.side}|${b.tgt}`;
30365
+ const idx = counter.get(key) ?? 0;
30366
+ counter.set(key, idx + 1);
30367
+ hubIndex.set(b.edgeIdx, idx);
30368
+ }
30369
+ for (const [key, v] of counter) hubSize.set(key, v);
30370
+ }
30371
+ for (const b of backSorted) {
30372
+ const s = b.side;
30373
+ const k = laneCounter.get(s) ?? 0;
30374
+ laneCounter.set(s, k + 1);
30375
+ const K = sideCounts.get(s);
30376
+ const src = node.get(b.src);
30377
+ const tgt = node.get(b.tgt);
30378
+ const laneC = s > 0 ? maxCross + GAP + k * LANE : minCross - GAP - k * LANE;
30379
+ const srcCtr = bandCenter[src.rank];
30380
+ const tgtCtr = bandCenter[tgt.rank];
30381
+ const frac = K > 1 ? (k + 1) / (K + 1) : 0;
30382
+ const srcR = srcCtr + frac * faceLim(src.depth);
30383
+ const tgtR = tgtCtr - frac * faceLim(tgt.depth);
30384
+ const m = (c, r) => isTB ? { x: c, y: r } : { x: r, y: c };
30385
+ const off = 10;
30386
+ const srcEdgeC = src.cross + s * (src.thick / 2 + off);
30387
+ const tgtEdgeC = tgt.cross + s * (tgt.thick / 2 + off);
30388
+ const hubK = hubIndex.get(b.edgeIdx);
30389
+ const hubN = hubSize.get(`${s}|${b.tgt}`);
30390
+ if (hubN > 1 && hubK > 0) {
30391
+ const tgtTop = tgtCtr - (tgt.depth / 2 + GAP + (hubK - 1) * LANE);
30392
+ const entryCross = tgt.cross + s * faceLim(tgt.thick) * (hubK / hubN);
30393
+ backPoints.set(b.edgeIdx, [
30394
+ m(src.cross, srcCtr),
30395
+ m(srcEdgeC, srcR),
30396
+ m(laneC, srcR),
30397
+ m(laneC, tgtTop),
30398
+ m(entryCross, tgtTop),
30399
+ m(tgt.cross, tgtCtr)
30400
+ ]);
30401
+ } else {
30402
+ backPoints.set(b.edgeIdx, [
30403
+ m(src.cross, srcCtr),
30404
+ m(srcEdgeC, srcR),
30405
+ m(laneC, srcR),
30406
+ m(laneC, tgtR),
30407
+ m(tgtEdgeC, tgtR),
30408
+ m(tgt.cross, tgtCtr)
30409
+ ]);
30410
+ }
30411
+ }
30412
+ const layoutEdges = [];
30413
+ for (let i = 0; i < parsed.edges.length; i++) {
30414
+ const e = parsed.edges[i];
30415
+ if (!labelSet.has(e.source) || !labelSet.has(e.target)) continue;
30416
+ let points;
30417
+ if (e.source === e.target) {
30418
+ const c = coord(node.get(e.source));
30419
+ points = [c, c];
30420
+ } else if (backPoints.has(i)) {
30421
+ points = backPoints.get(i);
30422
+ } else {
30423
+ const chain = chainById.get(i);
30424
+ if (!chain) continue;
30425
+ points = chain.map((id) => coord(node.get(id)));
30426
+ }
30427
+ layoutEdges.push({
30428
+ source: e.source,
30429
+ target: e.target,
30430
+ ...e.label !== void 0 && { label: e.label },
30431
+ bidirectional: e.bidirectional,
30432
+ lineNumber: e.lineNumber,
30433
+ points,
30434
+ yOffset: 0,
30435
+ parallelCount: 1,
30436
+ metadata: e.metadata
30437
+ });
30438
+ }
30439
+ let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
30440
+ for (const n of layoutNodes) {
30441
+ minX = Math.min(minX, n.x - n.width / 2);
30442
+ minY = Math.min(minY, n.y - n.height / 2);
30443
+ maxX = Math.max(maxX, n.x + n.width / 2);
30444
+ maxY = Math.max(maxY, n.y + n.height / 2);
30445
+ }
30446
+ for (const e of layoutEdges)
30447
+ for (const p of e.points) {
30448
+ minX = Math.min(minX, p.x);
30449
+ minY = Math.min(minY, p.y);
30450
+ maxX = Math.max(maxX, p.x);
30451
+ maxY = Math.max(maxY, p.y);
30452
+ }
30453
+ const sx = MARGIN3 - minX;
30454
+ const sy = MARGIN3 - minY;
30455
+ const shifted = sx !== 0 || sy !== 0 ? {
30456
+ nodes: layoutNodes.map((n) => ({ ...n, x: n.x + sx, y: n.y + sy })),
30457
+ edges: layoutEdges.map((e) => ({
30458
+ ...e,
30459
+ points: e.points.map((p) => ({ x: p.x + sx, y: p.y + sy }))
30460
+ }))
30461
+ } : { nodes: layoutNodes, edges: layoutEdges };
30462
+ return {
30463
+ nodes: shifted.nodes,
30464
+ edges: shifted.edges,
30465
+ groups: [],
30466
+ width: maxX + sx + MARGIN3,
30467
+ height: maxY + sy + MARGIN3
30468
+ };
30469
+ }
30470
+ function weight(a, b) {
30471
+ if (a.dummy && b.dummy) return W_DUMMY_DUMMY;
30472
+ if (a.dummy || b.dummy) return W_REAL_DUMMY;
30473
+ return W_REAL_REAL;
30474
+ }
30475
+ function placeWithSeparation(ids, desired) {
30476
+ const n = ids.length;
30477
+ if (n === 0) return;
30478
+ const half = ids.map((id) => node.get(id).thick / 2);
30479
+ const off = [0];
30480
+ for (let i = 1; i < n; i++)
30481
+ off[i] = off[i - 1] + half[i - 1] + nodesep + half[i];
30482
+ const d = desired.map((v, i) => v - off[i]);
30483
+ const blocks = [];
30484
+ for (let i = 0; i < n; i++) {
30485
+ let b = { pos: d[i], count: 1, sumOffset: d[i], first: i };
30486
+ while (blocks.length && blocks[blocks.length - 1].pos >= b.pos) {
30487
+ const prev = blocks.pop();
30488
+ const count = prev.count + b.count;
30489
+ const sumOffset = prev.sumOffset + b.sumOffset;
30490
+ b = { pos: sumOffset / count, count, sumOffset, first: prev.first };
30491
+ }
30492
+ blocks.push(b);
30493
+ }
30494
+ const xs = new Array(n);
30495
+ for (const b of blocks)
30496
+ for (let k = 0; k < b.count; k++) xs[b.first + k] = b.pos;
30497
+ for (let i = 0; i < n; i++) node.get(ids[i]).cross = xs[i] + off[i];
30498
+ }
30499
+ }
30500
+ var NODESEP, RANKSEP, DUMMY_THICK, MARGIN3, W_REAL_REAL, W_REAL_DUMMY, W_DUMMY_DUMMY;
30501
+ var init_layout_layered = __esm({
30502
+ "src/boxes-and-lines/layout-layered.ts"() {
30503
+ "use strict";
30504
+ init_layout5();
30505
+ NODESEP = 50;
30506
+ RANKSEP = 60;
30507
+ DUMMY_THICK = 10;
30508
+ MARGIN3 = 40;
30509
+ W_REAL_REAL = 1;
30510
+ W_REAL_DUMMY = 2;
30511
+ W_DUMMY_DUMMY = 8;
30512
+ }
30513
+ });
30514
+
30515
+ // src/boxes-and-lines/layout-grouped.ts
30516
+ function rng2(s) {
30517
+ return () => {
30518
+ s |= 0;
30519
+ s = s + 1831565813 | 0;
30520
+ let t = Math.imul(s ^ s >>> 15, 1 | s);
30521
+ t = t + Math.imul(t ^ t >>> 7, 61 | t) ^ t;
30522
+ return ((t ^ t >>> 14) >>> 0) / 4294967296;
30523
+ };
30524
+ }
30525
+ function median(vals) {
30526
+ if (vals.length === 0) return -1;
30527
+ vals.sort((x, y) => x - y);
30528
+ const m = Math.floor(vals.length / 2);
30529
+ if (vals.length % 2 === 1) return vals[m];
30530
+ if (vals.length === 2) return (vals[0] + vals[1]) / 2;
30531
+ const left = vals[m - 1] - vals[0];
30532
+ const right = vals[vals.length - 1] - vals[m];
30533
+ return left + right === 0 ? (vals[m - 1] + vals[m]) / 2 : (vals[m - 1] * right + vals[m] * left) / (left + right);
30534
+ }
30535
+ function acyclicOrient(ids, edges) {
30536
+ const adj = /* @__PURE__ */ new Map();
30537
+ for (const l of ids) adj.set(l, []);
30538
+ for (const e of edges) adj.get(e.from)?.push({ to: e.to, idx: e.idx });
30539
+ const reversed = /* @__PURE__ */ new Set();
30540
+ const state = /* @__PURE__ */ new Map();
30541
+ for (const l of ids) state.set(l, 0);
30542
+ const stack = [];
30543
+ const visit = (root) => {
30544
+ stack.push(root);
30545
+ const it = /* @__PURE__ */ new Map();
30546
+ while (stack.length) {
30547
+ const u = stack[stack.length - 1];
30548
+ if (state.get(u) === 0) state.set(u, 1);
30549
+ const list = adj.get(u);
30550
+ let i = it.get(u) ?? 0;
30551
+ let descended = false;
30552
+ for (; i < list.length; i++) {
30553
+ const { to, idx } = list[i];
30554
+ const st = state.get(to);
30555
+ if (st === 1) reversed.add(idx);
30556
+ else if (st === 0) {
30557
+ it.set(u, i + 1);
30558
+ stack.push(to);
30559
+ descended = true;
30560
+ break;
30561
+ }
30562
+ }
30563
+ if (!descended) {
30564
+ it.set(u, list.length);
30565
+ state.set(u, 2);
30566
+ stack.pop();
30567
+ }
30568
+ }
30569
+ };
30570
+ for (const l of ids) if (state.get(l) === 0) visit(l);
30571
+ const dag = edges.map(
30572
+ (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 }
30573
+ );
30574
+ return { dag };
30575
+ }
30576
+ function longestPath(ids, dag) {
30577
+ const out = /* @__PURE__ */ new Map();
30578
+ const inDeg = /* @__PURE__ */ new Map();
30579
+ for (const l of ids) {
30580
+ out.set(l, []);
30581
+ inDeg.set(l, 0);
30582
+ }
30583
+ for (const d of dag) {
30584
+ out.get(d.from).push(d.to);
30585
+ inDeg.set(d.to, (inDeg.get(d.to) ?? 0) + 1);
30586
+ }
30587
+ const rank = /* @__PURE__ */ new Map();
30588
+ for (const l of ids) rank.set(l, 0);
30589
+ const queue = ids.filter((l) => (inDeg.get(l) ?? 0) === 0);
30590
+ const indeg2 = new Map(inDeg);
30591
+ let seen = 0;
30592
+ while (queue.length) {
30593
+ const u = queue.shift();
30594
+ seen++;
30595
+ for (const v of out.get(u)) {
30596
+ rank.set(v, Math.max(rank.get(v), rank.get(u) + 1));
30597
+ indeg2.set(v, indeg2.get(v) - 1);
30598
+ if (indeg2.get(v) === 0) queue.push(v);
30599
+ }
30600
+ }
30601
+ return seen === ids.length ? rank : null;
30602
+ }
30603
+ function groupedTierCandidates(parsed, sizes, opts) {
30604
+ if (parsed.groups.length === 0) return [];
30605
+ const nCount = parsed.nodes.length;
30606
+ if (nCount < 3 || nCount > 60) return [];
30607
+ const isTB = parsed.direction === "TB";
30608
+ const labels = parsed.nodes.map((n) => n.label);
30609
+ const labelSet = new Set(labels);
30610
+ for (const e of parsed.edges)
30611
+ if (e.source.startsWith("__group_") || e.target.startsWith("__group_"))
30612
+ return [];
30613
+ const groupByLabel = new Map(parsed.groups.map((g) => [g.label, g]));
30614
+ const directGroupOf = /* @__PURE__ */ new Map();
30615
+ for (const grp of parsed.groups)
30616
+ for (const c of grp.children)
30617
+ if (labelSet.has(c)) directGroupOf.set(c, grp.label);
30618
+ const topGroupOf = (groupLabel) => {
30619
+ let cur = groupLabel;
30620
+ const guard = /* @__PURE__ */ new Set();
30621
+ while (true) {
30622
+ const g = groupByLabel.get(cur);
30623
+ if (!g?.parentGroup || guard.has(cur)) return cur;
30624
+ guard.add(cur);
30625
+ cur = g.parentGroup;
30626
+ }
30627
+ };
30628
+ const pathOf = (label) => {
30629
+ const direct = directGroupOf.get(label);
30630
+ if (!direct) return [];
30631
+ const chain = [direct];
30632
+ let cur = direct;
30633
+ const guard = /* @__PURE__ */ new Set([direct]);
30634
+ while (true) {
30635
+ const g = groupByLabel.get(cur);
30636
+ if (!g?.parentGroup || guard.has(g.parentGroup)) break;
30637
+ chain.push(g.parentGroup);
30638
+ guard.add(g.parentGroup);
30639
+ cur = g.parentGroup;
30640
+ }
30641
+ return chain.reverse();
30642
+ };
30643
+ const SINGLE = "\0node\0";
30644
+ const unitOf = (label) => {
30645
+ const direct = directGroupOf.get(label);
30646
+ return direct ? topGroupOf(direct) : SINGLE + label;
30647
+ };
30648
+ const edges = parsed.edges.map((e, i) => ({ e, i })).filter(
30649
+ ({ e }) => labelSet.has(e.source) && labelSet.has(e.target) && e.source !== e.target
30650
+ );
30651
+ if (edges.length === 0) return [];
30652
+ const unitOfNode = /* @__PURE__ */ new Map();
30653
+ for (const l of labels) unitOfNode.set(l, unitOf(l));
30654
+ const units = Array.from(new Set(labels.map((l) => unitOfNode.get(l))));
30655
+ const interUnit = edges.filter(({ e }) => unitOfNode.get(e.source) !== unitOfNode.get(e.target)).map(({ e, i }) => ({
30656
+ from: unitOfNode.get(e.source),
30657
+ to: unitOfNode.get(e.target),
30658
+ idx: i
30659
+ }));
30660
+ const { dag: unitDag } = acyclicOrient(units, interUnit);
30661
+ const tier = longestPath(
30662
+ units,
30663
+ unitDag.map((d) => ({ from: d.from, to: d.to }))
30664
+ );
30665
+ if (!tier) return [];
30666
+ const localRank = /* @__PURE__ */ new Map();
30667
+ const unitMembers = /* @__PURE__ */ new Map();
30668
+ for (const l of labels) {
30669
+ const u = unitOfNode.get(l);
30670
+ (unitMembers.get(u) ?? unitMembers.set(u, []).get(u)).push(l);
30671
+ }
30672
+ for (const [u, members] of unitMembers) {
30673
+ const memberSet = new Set(members);
30674
+ const intra = edges.filter(
30675
+ ({ e }) => unitOfNode.get(e.source) === u && unitOfNode.get(e.target) === u && memberSet.has(e.source) && memberSet.has(e.target)
30676
+ ).map(({ e, i }) => ({ from: e.source, to: e.target, idx: i }));
30677
+ const { dag } = acyclicOrient(members, intra);
30678
+ const lr = longestPath(
30679
+ members,
30680
+ dag.map((d) => ({ from: d.from, to: d.to }))
30681
+ );
30682
+ if (!lr) return [];
30683
+ for (const m of members) localRank.set(m, lr.get(m));
30684
+ }
30685
+ const maxTier = Math.max(...units.map((u) => tier.get(u)));
30686
+ const bandHeight = Array.from({ length: maxTier + 1 }, () => 1);
30687
+ for (const u of units) {
30688
+ const h = Math.max(
30689
+ ...unitMembers.get(u).map((m) => localRank.get(m) + 1)
30690
+ );
30691
+ const t = tier.get(u);
30692
+ bandHeight[t] = Math.max(bandHeight[t], h);
30693
+ }
30694
+ const tierBase = [];
30695
+ let accBase = 0;
30696
+ for (let t = 0; t <= maxTier; t++) {
30697
+ tierBase[t] = accBase;
30698
+ accBase += bandHeight[t];
30699
+ }
30700
+ const tierStartRanks = new Set(tierBase.slice(1));
30701
+ const globalRank = /* @__PURE__ */ new Map();
30702
+ for (const l of labels)
30703
+ globalRank.set(
30704
+ l,
30705
+ tierBase[tier.get(unitOfNode.get(l))] + localRank.get(l)
30706
+ );
30707
+ const maxRank = Math.max(...labels.map((l) => globalRank.get(l)));
30708
+ const node = /* @__PURE__ */ new Map();
30709
+ for (const n of parsed.nodes) {
30710
+ const s = sizes.get(n.label) ?? { width: NODE_WIDTH, height: NODE_HEIGHT };
30711
+ node.set(n.label, {
30712
+ id: n.label,
30713
+ dummy: false,
30714
+ rank: globalRank.get(n.label),
30715
+ cross: 0,
30716
+ thick: isTB ? s.width : s.height,
30717
+ depth: isTB ? s.height : s.width,
30718
+ realW: s.width,
30719
+ realH: s.height,
30720
+ unit: unitOfNode.get(n.label),
30721
+ path: pathOf(n.label)
30722
+ });
30723
+ }
30724
+ const chains = [];
30725
+ const segs = [];
30726
+ const backEdges = [];
30727
+ const flatEdges = [];
30728
+ for (const { e, i } of edges) {
30729
+ const rs = globalRank.get(e.source);
30730
+ const rt = globalRank.get(e.target);
30731
+ if (rs === rt) {
30732
+ flatEdges.push({ edgeIdx: i, a: e.source, b: e.target });
30733
+ continue;
30734
+ }
30735
+ if (rt < rs) {
30736
+ backEdges.push({ edgeIdx: i, src: e.source, tgt: e.target });
30737
+ continue;
30738
+ }
30739
+ const chain = [e.source];
30740
+ let prev = e.source;
30741
+ for (let r = rs + 1; r < rt; r++) {
30742
+ const id = `__d${i}_${r}`;
30743
+ node.set(id, {
30744
+ id,
30745
+ dummy: true,
30746
+ rank: r,
30747
+ cross: 0,
30748
+ thick: DUMMY_THICK2,
30749
+ depth: 0,
30750
+ realW: 0,
30751
+ realH: 0,
30752
+ unit: SINGLE + id,
30753
+ path: []
30754
+ });
30755
+ segs.push({ a: prev, b: id });
30756
+ chain.push(id);
30757
+ prev = id;
30758
+ }
30759
+ segs.push({ a: prev, b: e.target });
30760
+ chain.push(e.target);
30761
+ chains.push({ edgeIdx: i, chain });
30762
+ }
30763
+ const ranks = Array.from({ length: maxRank + 1 }, () => []);
30764
+ for (const n of node.values()) ranks[n.rank].push(n.id);
30765
+ const up = /* @__PURE__ */ new Map();
30766
+ const down = /* @__PURE__ */ new Map();
30767
+ for (const id of node.keys()) {
30768
+ up.set(id, []);
30769
+ down.set(id, []);
30770
+ }
30771
+ for (const s of segs) {
30772
+ down.get(s.a).push(s.b);
30773
+ up.get(s.b).push(s.a);
30774
+ }
30775
+ const orderOf = /* @__PURE__ */ new Map();
30776
+ const gapCrossings = (r) => {
30777
+ const lower = ranks[r + 1];
30778
+ if (!lower) return 0;
30779
+ const es = [];
30780
+ for (const a of ranks[r]) {
30781
+ const pa = orderOf.get(a);
30782
+ for (const b of down.get(a)) es.push({ p: pa, q: orderOf.get(b) });
30783
+ }
30784
+ let c = 0;
30785
+ for (let i = 0; i < es.length; i++)
30786
+ for (let j = i + 1; j < es.length; j++) {
30787
+ const A = es[i], B = es[j];
30788
+ if ((A.p - B.p) * (A.q - B.q) < 0) c++;
30789
+ }
30790
+ return c;
30791
+ };
30792
+ const totalCrossings = () => {
30793
+ let c = 0;
30794
+ for (let r = 0; r < maxRank; r++) c += gapCrossings(r);
30795
+ return c;
30796
+ };
29983
30797
  const wmedianRank = (r, useUp) => {
29984
30798
  const ids = ranks[r];
29985
30799
  const neigh = useUp ? up : down;
@@ -30011,9 +30825,8 @@ function layeredCandidates(parsed, sizes, opts) {
30011
30825
  orderOf.set(b, i);
30012
30826
  orderOf.set(a, i + 1);
30013
30827
  const after = (r > 0 ? gapCrossings(r - 1) : 0) + (r < maxRank ? gapCrossings(r) : 0);
30014
- if (after < before) {
30015
- improved = true;
30016
- } else {
30828
+ if (after < before) improved = true;
30829
+ else {
30017
30830
  ids[i] = a;
30018
30831
  ids[i + 1] = b;
30019
30832
  orderOf.set(a, i);
@@ -30024,27 +30837,28 @@ function layeredCandidates(parsed, sizes, opts) {
30024
30837
  }
30025
30838
  };
30026
30839
  const initOrder = (seed) => {
30027
- const r = seed === 0 ? null : rng(seed);
30840
+ const r = seed === 0 ? null : rng2(seed);
30028
30841
  for (let rr = 0; rr <= maxRank; rr++) {
30029
30842
  const ids = ranks[rr].slice();
30030
- if (r) {
30843
+ if (r)
30031
30844
  for (let i = ids.length - 1; i > 0; i--) {
30032
30845
  const j = Math.floor(r() * (i + 1));
30033
30846
  [ids[i], ids[j]] = [ids[j], ids[i]];
30034
30847
  }
30035
- }
30036
30848
  ranks[rr] = ids;
30037
30849
  ids.forEach((id, i) => orderOf.set(id, i));
30038
30850
  }
30039
30851
  };
30852
+ const restarts = opts?.restarts ?? (nCount <= 16 ? 20 : nCount <= 30 ? 12 : 6);
30853
+ const keepK = opts?.keepK ?? 3;
30040
30854
  const found = [];
30041
30855
  for (let s = 0; s < restarts; s++) {
30042
30856
  initOrder(s);
30043
30857
  let best = totalCrossings();
30044
30858
  let bestSnap = new Map(orderOf);
30045
30859
  for (let iter = 0; iter < 8; iter++) {
30046
- const down1 = iter % 2 === 0;
30047
- if (down1) for (let r = 1; r <= maxRank; r++) wmedianRank(r, true);
30860
+ if (iter % 2 === 0)
30861
+ for (let r = 1; r <= maxRank; r++) wmedianRank(r, true);
30048
30862
  else for (let r = maxRank - 1; r >= 0; r--) wmedianRank(r, false);
30049
30863
  transpose();
30050
30864
  const c = totalCrossings();
@@ -30054,7 +30868,7 @@ function layeredCandidates(parsed, sizes, opts) {
30054
30868
  }
30055
30869
  if (c === 0) break;
30056
30870
  }
30057
- applyOrder(bestSnap);
30871
+ for (const [k, v] of bestSnap) orderOf.set(k, v);
30058
30872
  for (let r = 0; r <= maxRank; r++)
30059
30873
  ranks[r].sort((a, b) => orderOf.get(a) - orderOf.get(b));
30060
30874
  const sig = ranks.map((r) => r.join(",")).join("|");
@@ -30066,7 +30880,7 @@ function layeredCandidates(parsed, sizes, opts) {
30066
30880
  if (keep.length === 0) return [];
30067
30881
  const results = [];
30068
30882
  for (const cand of keep) {
30069
- applyOrder(cand.order);
30883
+ for (const [k, v] of cand.order) orderOf.set(k, v);
30070
30884
  const rk = Array.from({ length: maxRank + 1 }, () => []);
30071
30885
  for (const n of node.values()) rk[n.rank].push(n.id);
30072
30886
  for (let r = 0; r <= maxRank; r++)
@@ -30074,22 +30888,64 @@ function layeredCandidates(parsed, sizes, opts) {
30074
30888
  results.push(realize(rk));
30075
30889
  }
30076
30890
  return results;
30891
+ function weight(a, b) {
30892
+ if (a.dummy && b.dummy) return W_DUMMY_DUMMY2;
30893
+ if (a.dummy || b.dummy) return W_REAL_DUMMY2;
30894
+ return W_REAL_REAL2;
30895
+ }
30896
+ function placeWithSeparation(ids, desired, gap) {
30897
+ const n = ids.length;
30898
+ if (n === 0) return;
30899
+ const off = [0];
30900
+ for (let i = 1; i < n; i++) off[i] = off[i - 1] + gap[i];
30901
+ const d = desired.map((v, i) => v - off[i]);
30902
+ const blocks = [];
30903
+ for (let i = 0; i < n; i++) {
30904
+ let b = { pos: d[i], count: 1, sumOffset: d[i], first: i };
30905
+ while (blocks.length && blocks[blocks.length - 1].pos >= b.pos) {
30906
+ const prev = blocks.pop();
30907
+ const count = prev.count + b.count;
30908
+ const sumOffset = prev.sumOffset + b.sumOffset;
30909
+ b = { pos: sumOffset / count, count, sumOffset, first: prev.first };
30910
+ }
30911
+ blocks.push(b);
30912
+ }
30913
+ const xs = new Array(n);
30914
+ for (const b of blocks)
30915
+ for (let k = 0; k < b.count; k++) xs[b.first + k] = b.pos;
30916
+ for (let i = 0; i < n; i++) node.get(ids[i]).cross = xs[i] + off[i];
30917
+ }
30918
+ function gapBetween(a, b) {
30919
+ const base = a.thick / 2 + b.thick / 2 + NODESEP2;
30920
+ if (a.dummy || b.dummy) return base;
30921
+ if (a.unit !== b.unit) return base + 2 * GROUP_PAD + GROUP_GAP;
30922
+ const da = a.path[a.path.length - 1];
30923
+ const db = b.path[b.path.length - 1];
30924
+ if (da !== db && (da || db)) return base + 2 * GROUP_PAD;
30925
+ return base;
30926
+ }
30077
30927
  function realize(rk) {
30078
30928
  const bandDepth = rk.map(
30079
30929
  (ids) => Math.max(0, ...ids.map((id) => node.get(id).depth))
30080
30930
  );
30081
30931
  const bandCenter = [];
30082
- let acc = MARGIN3;
30932
+ let acc = MARGIN4;
30083
30933
  for (let r = 0; r <= maxRank; r++) {
30934
+ if (r > 0) acc += RANKSEP2 + (tierStartRanks.has(r) ? TIER_GAP : 0);
30084
30935
  bandCenter[r] = acc + bandDepth[r] / 2;
30085
- acc += bandDepth[r] + ranksep;
30936
+ acc += bandDepth[r];
30086
30937
  }
30938
+ const gapsFor = (ids) => ids.map(
30939
+ (id, i) => i === 0 ? 0 : gapBetween(node.get(ids[i - 1]), node.get(id))
30940
+ );
30087
30941
  for (let r = 0; r <= maxRank; r++) {
30088
- let x = MARGIN3;
30089
- for (const id of rk[r]) {
30090
- const n = node.get(id);
30091
- n.cross = x + n.thick / 2;
30092
- x += n.thick + nodesep;
30942
+ const ids = rk[r];
30943
+ const gap = gapsFor(ids);
30944
+ let x = MARGIN4;
30945
+ for (let i = 0; i < ids.length; i++) {
30946
+ const n = node.get(ids[i]);
30947
+ x += gap[i];
30948
+ n.cross = i === 0 ? MARGIN4 + n.thick / 2 : x;
30093
30949
  }
30094
30950
  }
30095
30951
  const ITER = 18;
@@ -30113,13 +30969,13 @@ function layeredCandidates(parsed, sizes, opts) {
30113
30969
  }
30114
30970
  return sw > 0 ? sx2 / sw : n.cross;
30115
30971
  });
30116
- placeWithSeparation(ids, desired);
30972
+ placeWithSeparation(ids, desired, gapsFor(ids));
30117
30973
  }
30118
30974
  }
30119
30975
  let minC = Infinity;
30120
30976
  for (const n of node.values()) minC = Math.min(minC, n.cross - n.thick / 2);
30121
- const shift = MARGIN3 - minC;
30122
- for (const n of node.values()) n.cross += shift;
30977
+ const shiftC = MARGIN4 - minC;
30978
+ for (const n of node.values()) n.cross += shiftC;
30123
30979
  const coord = (n) => isTB ? { x: n.cross, y: bandCenter[n.rank] } : { x: bandCenter[n.rank], y: n.cross };
30124
30980
  const layoutNodes = parsed.nodes.map((pn) => {
30125
30981
  const n = node.get(pn.label);
@@ -30142,9 +30998,8 @@ function layeredCandidates(parsed, sizes, opts) {
30142
30998
  }
30143
30999
  const GAP = 34;
30144
31000
  const LANE = 28;
31001
+ const faceLim = (depth) => Math.max(0, depth / 2 - 6);
30145
31002
  const sideOf = (b) => {
30146
- if (backEdgeSide === "left") return -1;
30147
- if (backEdgeSide === "right") return 1;
30148
31003
  const d = node.get(b.src).cross - node.get(b.tgt).cross;
30149
31004
  return d >= 0 ? 1 : -1;
30150
31005
  };
@@ -30158,19 +31013,6 @@ function layeredCandidates(parsed, sizes, opts) {
30158
31013
  sideCounts.set(b.side, (sideCounts.get(b.side) ?? 0) + 1);
30159
31014
  const laneCounter = /* @__PURE__ */ new Map();
30160
31015
  const backPoints = /* @__PURE__ */ new Map();
30161
- const faceLim = (depth) => Math.max(0, depth / 2 - 6);
30162
- const hubIndex = /* @__PURE__ */ new Map();
30163
- const hubSize = /* @__PURE__ */ new Map();
30164
- {
30165
- const counter = /* @__PURE__ */ new Map();
30166
- for (const b of backSorted) {
30167
- const key = `${b.side}|${b.tgt}`;
30168
- const idx = counter.get(key) ?? 0;
30169
- counter.set(key, idx + 1);
30170
- hubIndex.set(b.edgeIdx, idx);
30171
- }
30172
- for (const [key, v] of counter) hubSize.set(key, v);
30173
- }
30174
31016
  for (const b of backSorted) {
30175
31017
  const s = b.side;
30176
31018
  const k = laneCounter.get(s) ?? 0;
@@ -30182,35 +31024,34 @@ function layeredCandidates(parsed, sizes, opts) {
30182
31024
  const srcCtr = bandCenter[src.rank];
30183
31025
  const tgtCtr = bandCenter[tgt.rank];
30184
31026
  const frac = K > 1 ? (k + 1) / (K + 1) : 0;
30185
- const srcR = srcCtr + frac * faceLim(src.depth);
30186
- const tgtR = tgtCtr - frac * faceLim(tgt.depth);
30187
- const m = (c, r) => isTB ? { x: c, y: r } : { x: r, y: c };
31027
+ const srcR = srcCtr - frac * faceLim(src.depth);
31028
+ const tgtR = tgtCtr + frac * faceLim(tgt.depth);
31029
+ const m = (c, rr) => isTB ? { x: c, y: rr } : { x: rr, y: c };
30188
31030
  const off = 10;
30189
31031
  const srcEdgeC = src.cross + s * (src.thick / 2 + off);
30190
31032
  const tgtEdgeC = tgt.cross + s * (tgt.thick / 2 + off);
30191
- const hubK = hubIndex.get(b.edgeIdx);
30192
- const hubN = hubSize.get(`${s}|${b.tgt}`);
30193
- if (hubN > 1 && hubK > 0) {
30194
- const tgtTop = tgtCtr - (tgt.depth / 2 + GAP + (hubK - 1) * LANE);
30195
- const entryCross = tgt.cross + s * faceLim(tgt.thick) * (hubK / hubN);
30196
- backPoints.set(b.edgeIdx, [
30197
- m(src.cross, srcCtr),
30198
- m(srcEdgeC, srcR),
30199
- m(laneC, srcR),
30200
- m(laneC, tgtTop),
30201
- m(entryCross, tgtTop),
30202
- m(tgt.cross, tgtCtr)
30203
- ]);
30204
- } else {
30205
- backPoints.set(b.edgeIdx, [
30206
- m(src.cross, srcCtr),
30207
- m(srcEdgeC, srcR),
30208
- m(laneC, srcR),
30209
- m(laneC, tgtR),
30210
- m(tgtEdgeC, tgtR),
30211
- m(tgt.cross, tgtCtr)
30212
- ]);
30213
- }
31033
+ backPoints.set(b.edgeIdx, [
31034
+ m(src.cross, srcCtr),
31035
+ m(srcEdgeC, srcR),
31036
+ m(laneC, srcR),
31037
+ m(laneC, tgtR),
31038
+ m(tgtEdgeC, tgtR),
31039
+ m(tgt.cross, tgtCtr)
31040
+ ]);
31041
+ }
31042
+ const flatPoints = /* @__PURE__ */ new Map();
31043
+ for (const f of flatEdges) {
31044
+ const a = node.get(f.a);
31045
+ const b = node.get(f.b);
31046
+ const ctr = bandCenter[a.rank];
31047
+ const bow = ctr - (a.depth / 2 + 26);
31048
+ const m = (c, rr) => isTB ? { x: c, y: rr } : { x: rr, y: c };
31049
+ flatPoints.set(f.edgeIdx, [
31050
+ m(a.cross, ctr),
31051
+ m(a.cross, bow),
31052
+ m(b.cross, bow),
31053
+ m(b.cross, ctr)
31054
+ ]);
30214
31055
  }
30215
31056
  const layoutEdges = [];
30216
31057
  for (let i = 0; i < parsed.edges.length; i++) {
@@ -30220,9 +31061,9 @@ function layeredCandidates(parsed, sizes, opts) {
30220
31061
  if (e.source === e.target) {
30221
31062
  const c = coord(node.get(e.source));
30222
31063
  points = [c, c];
30223
- } else if (backPoints.has(i)) {
30224
- points = backPoints.get(i);
30225
- } else {
31064
+ } else if (backPoints.has(i)) points = backPoints.get(i);
31065
+ else if (flatPoints.has(i)) points = flatPoints.get(i);
31066
+ else {
30226
31067
  const chain = chainById.get(i);
30227
31068
  if (!chain) continue;
30228
31069
  points = chain.map((id) => coord(node.get(id)));
@@ -30239,79 +31080,124 @@ function layeredCandidates(parsed, sizes, opts) {
30239
31080
  metadata: e.metadata
30240
31081
  });
30241
31082
  }
30242
- let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
30243
- for (const n of layoutNodes) {
30244
- minX = Math.min(minX, n.x - n.width / 2);
30245
- minY = Math.min(minY, n.y - n.height / 2);
30246
- maxX = Math.max(maxX, n.x + n.width / 2);
30247
- maxY = Math.max(maxY, n.y + n.height / 2);
31083
+ const nodeBox = new Map(
31084
+ layoutNodes.map((n) => [
31085
+ n.label,
31086
+ {
31087
+ l: n.x - n.width / 2,
31088
+ r: n.x + n.width / 2,
31089
+ t: n.y - n.height / 2,
31090
+ b: n.y + n.height / 2
31091
+ }
31092
+ ])
31093
+ );
31094
+ const depthOf = (label) => {
31095
+ let d = 0;
31096
+ let cur = label;
31097
+ const guard = /* @__PURE__ */ new Set();
31098
+ while (cur && !guard.has(cur)) {
31099
+ guard.add(cur);
31100
+ cur = groupByLabel.get(cur)?.parentGroup;
31101
+ if (cur) d++;
31102
+ }
31103
+ return d;
31104
+ };
31105
+ const childGroups = /* @__PURE__ */ new Map();
31106
+ for (const g of parsed.groups)
31107
+ if (g.parentGroup)
31108
+ (childGroups.get(g.parentGroup) ?? childGroups.set(g.parentGroup, []).get(g.parentGroup)).push(g.label);
31109
+ const rectByGroup = /* @__PURE__ */ new Map();
31110
+ const ordered = [...parsed.groups].sort(
31111
+ (a, b) => depthOf(b.label) - depthOf(a.label)
31112
+ );
31113
+ const layoutGroups = [];
31114
+ for (const grp of ordered) {
31115
+ let l = Infinity, r = -Infinity, t = Infinity, bm = -Infinity;
31116
+ const acc2 = (box) => {
31117
+ l = Math.min(l, box.l);
31118
+ r = Math.max(r, box.r);
31119
+ t = Math.min(t, box.t);
31120
+ bm = Math.max(bm, box.b);
31121
+ };
31122
+ for (const c of grp.children) {
31123
+ const nb = nodeBox.get(c);
31124
+ if (nb) acc2(nb);
31125
+ }
31126
+ for (const sub of childGroups.get(grp.label) ?? []) {
31127
+ const sr = rectByGroup.get(sub);
31128
+ if (sr) acc2(sr);
31129
+ }
31130
+ if (!Number.isFinite(l)) continue;
31131
+ const x0 = l - GROUP_PAD;
31132
+ const x1 = r + GROUP_PAD;
31133
+ const y0 = t - GROUP_LABEL_ZONE2;
31134
+ const y1 = bm + GROUP_PAD;
31135
+ rectByGroup.set(grp.label, { l: x0, r: x1, t: y0, b: y1 });
31136
+ layoutGroups.push({
31137
+ label: grp.label,
31138
+ lineNumber: grp.lineNumber,
31139
+ x: (x0 + x1) / 2,
31140
+ y: (y0 + y1) / 2,
31141
+ width: x1 - x0,
31142
+ height: y1 - y0,
31143
+ collapsed: false,
31144
+ childCount: grp.children.length
31145
+ });
30248
31146
  }
31147
+ let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
31148
+ const ext = (x0, y0, x1, y1) => {
31149
+ minX = Math.min(minX, x0);
31150
+ minY = Math.min(minY, y0);
31151
+ maxX = Math.max(maxX, x1);
31152
+ maxY = Math.max(maxY, y1);
31153
+ };
31154
+ for (const n of layoutNodes)
31155
+ ext(
31156
+ n.x - n.width / 2,
31157
+ n.y - n.height / 2,
31158
+ n.x + n.width / 2,
31159
+ n.y + n.height / 2
31160
+ );
30249
31161
  for (const e of layoutEdges)
30250
- for (const p of e.points) {
30251
- minX = Math.min(minX, p.x);
30252
- minY = Math.min(minY, p.y);
30253
- maxX = Math.max(maxX, p.x);
30254
- maxY = Math.max(maxY, p.y);
30255
- }
30256
- const sx = MARGIN3 - minX;
30257
- const sy = MARGIN3 - minY;
30258
- const shifted = sx !== 0 || sy !== 0 ? {
30259
- nodes: layoutNodes.map((n) => ({ ...n, x: n.x + sx, y: n.y + sy })),
30260
- edges: layoutEdges.map((e) => ({
31162
+ for (const p of e.points) ext(p.x, p.y, p.x, p.y);
31163
+ for (const g of layoutGroups)
31164
+ ext(
31165
+ g.x - g.width / 2,
31166
+ g.y - g.height / 2,
31167
+ g.x + g.width / 2,
31168
+ g.y + g.height / 2
31169
+ );
31170
+ const sx = MARGIN4 - minX;
31171
+ const sy = MARGIN4 - minY;
31172
+ const shift = sx !== 0 || sy !== 0;
31173
+ return {
31174
+ nodes: shift ? layoutNodes.map((n) => ({ ...n, x: n.x + sx, y: n.y + sy })) : layoutNodes,
31175
+ edges: shift ? layoutEdges.map((e) => ({
30261
31176
  ...e,
30262
31177
  points: e.points.map((p) => ({ x: p.x + sx, y: p.y + sy }))
30263
- }))
30264
- } : { nodes: layoutNodes, edges: layoutEdges };
30265
- return {
30266
- nodes: shifted.nodes,
30267
- edges: shifted.edges,
30268
- groups: [],
30269
- width: maxX + sx + MARGIN3,
30270
- height: maxY + sy + MARGIN3
31178
+ })) : layoutEdges,
31179
+ groups: shift ? layoutGroups.map((g) => ({ ...g, x: g.x + sx, y: g.y + sy })) : layoutGroups,
31180
+ width: maxX + sx + MARGIN4,
31181
+ height: maxY + sy + MARGIN4
30271
31182
  };
30272
31183
  }
30273
- function weight(a, b) {
30274
- if (a.dummy && b.dummy) return W_DUMMY_DUMMY;
30275
- if (a.dummy || b.dummy) return W_REAL_DUMMY;
30276
- return W_REAL_REAL;
30277
- }
30278
- function placeWithSeparation(ids, desired) {
30279
- const n = ids.length;
30280
- if (n === 0) return;
30281
- const half = ids.map((id) => node.get(id).thick / 2);
30282
- const off = [0];
30283
- for (let i = 1; i < n; i++)
30284
- off[i] = off[i - 1] + half[i - 1] + nodesep + half[i];
30285
- const d = desired.map((v, i) => v - off[i]);
30286
- const blocks = [];
30287
- for (let i = 0; i < n; i++) {
30288
- let b = { pos: d[i], count: 1, sumOffset: d[i], first: i };
30289
- while (blocks.length && blocks[blocks.length - 1].pos >= b.pos) {
30290
- const prev = blocks.pop();
30291
- const count = prev.count + b.count;
30292
- const sumOffset = prev.sumOffset + b.sumOffset;
30293
- b = { pos: sumOffset / count, count, sumOffset, first: prev.first };
30294
- }
30295
- blocks.push(b);
30296
- }
30297
- const xs = new Array(n);
30298
- for (const b of blocks)
30299
- for (let k = 0; k < b.count; k++) xs[b.first + k] = b.pos;
30300
- for (let i = 0; i < n; i++) node.get(ids[i]).cross = xs[i] + off[i];
30301
- }
30302
31184
  }
30303
- var NODESEP, RANKSEP, DUMMY_THICK, MARGIN3, W_REAL_REAL, W_REAL_DUMMY, W_DUMMY_DUMMY;
30304
- var init_layout_layered = __esm({
30305
- "src/boxes-and-lines/layout-layered.ts"() {
31185
+ var NODESEP2, RANKSEP2, DUMMY_THICK2, MARGIN4, GROUP_PAD, GROUP_LABEL_ZONE2, GROUP_GAP, TIER_GAP, W_REAL_REAL2, W_REAL_DUMMY2, W_DUMMY_DUMMY2;
31186
+ var init_layout_grouped = __esm({
31187
+ "src/boxes-and-lines/layout-grouped.ts"() {
30306
31188
  "use strict";
30307
31189
  init_layout5();
30308
- NODESEP = 50;
30309
- RANKSEP = 60;
30310
- DUMMY_THICK = 10;
30311
- MARGIN3 = 40;
30312
- W_REAL_REAL = 1;
30313
- W_REAL_DUMMY = 2;
30314
- W_DUMMY_DUMMY = 8;
31190
+ NODESEP2 = 50;
31191
+ RANKSEP2 = 60;
31192
+ DUMMY_THICK2 = 10;
31193
+ MARGIN4 = 40;
31194
+ GROUP_PAD = 16;
31195
+ GROUP_LABEL_ZONE2 = 32;
31196
+ GROUP_GAP = 24;
31197
+ TIER_GAP = GROUP_LABEL_ZONE2 + 2 * GROUP_PAD;
31198
+ W_REAL_REAL2 = 1;
31199
+ W_REAL_DUMMY2 = 2;
31200
+ W_DUMMY_DUMMY2 = 8;
30315
31201
  }
30316
31202
  });
30317
31203
 
@@ -30330,7 +31216,7 @@ __export(layout_search_exports, {
30330
31216
  layoutBoxesAndLinesSearch: () => layoutBoxesAndLinesSearch,
30331
31217
  separateGroupBands: () => separateGroupBands
30332
31218
  });
30333
- function rng2(s) {
31219
+ function rng3(s) {
30334
31220
  return () => {
30335
31221
  s |= 0;
30336
31222
  s = s + 1831565813 | 0;
@@ -30685,7 +31571,7 @@ function detectGroupOverlaps(layout, opts) {
30685
31571
  const contains = (outer, inner) => outer.l <= inner.l + margin && outer.r >= inner.r - margin && outer.t <= inner.t + margin && outer.b >= inner.b - margin;
30686
31572
  const rend = raw.map((a, i) => {
30687
31573
  const parent = raw.some((b, j) => j !== i && contains(a, b));
30688
- return parent ? { ...a, t: a.t - GROUP_LABEL_ZONE2 } : a;
31574
+ return parent ? { ...a, t: a.t - GROUP_LABEL_ZONE3 } : a;
30689
31575
  });
30690
31576
  const hit = /* @__PURE__ */ new Set();
30691
31577
  for (let i = 0; i < raw.length; i++)
@@ -30712,7 +31598,7 @@ function countGroupOverlaps(layout, opts) {
30712
31598
  const contains = (outer, inner) => outer.l <= inner.l + margin && outer.r >= inner.r - margin && outer.t <= inner.t + margin && outer.b >= inner.b - margin;
30713
31599
  const rend = raw.map((a, i) => {
30714
31600
  const parent = raw.some((b, j) => j !== i && contains(a, b));
30715
- return parent ? { ...a, t: a.t - GROUP_LABEL_ZONE2 } : a;
31601
+ return parent ? { ...a, t: a.t - GROUP_LABEL_ZONE3 } : a;
30716
31602
  });
30717
31603
  let count = 0;
30718
31604
  for (let i = 0; i < raw.length; i++)
@@ -30755,7 +31641,7 @@ function separateGroupBands(layout, parsed) {
30755
31641
  let lo = (axis === "y" ? g.y : g.x) - half2;
30756
31642
  const hi = (axis === "y" ? g.y : g.x) + half2;
30757
31643
  const isParent = parsed.groups.some((c) => c.parentGroup === label);
30758
- if (axis === "y" && isParent) lo -= GROUP_LABEL_ZONE2;
31644
+ if (axis === "y" && isParent) lo -= GROUP_LABEL_ZONE3;
30759
31645
  return { label, lo, hi, c: (lo + hi) / 2 };
30760
31646
  });
30761
31647
  bands.sort((a, b) => a.c - b.c);
@@ -30826,7 +31712,7 @@ function separateGroupBands(layout, parsed) {
30826
31712
  acc(n.x + n.width / 2, n.y + n.height / 2);
30827
31713
  }
30828
31714
  for (const g of groups) {
30829
- acc(g.x - g.width / 2, g.y - g.height / 2 - GROUP_LABEL_ZONE2);
31715
+ acc(g.x - g.width / 2, g.y - g.height / 2 - GROUP_LABEL_ZONE3);
30830
31716
  acc(g.x + g.width / 2, g.y + g.height / 2);
30831
31717
  }
30832
31718
  for (const e of edges) for (const p of e.points) acc(p.x, p.y);
@@ -30909,6 +31795,7 @@ function edgeLength(layout) {
30909
31795
  }
30910
31796
  async function layoutBoxesAndLinesSearch(parsed, collapseInfo, opts) {
30911
31797
  const hideDescriptions = opts?.hideDescriptions ?? false;
31798
+ const reserveEdgeLabels = opts?.reserveEdgeLabels ?? false;
30912
31799
  const onProgress = opts?.onProgress;
30913
31800
  const tick = onProgress ? () => new Promise((r) => setTimeout(r)) : () => void 0;
30914
31801
  const collapsedGroupLabels = /* @__PURE__ */ new Set();
@@ -31090,7 +31977,7 @@ async function layoutBoxesAndLinesSearch(parsed, collapseInfo, opts) {
31090
31977
  }
31091
31978
  ];
31092
31979
  });
31093
- const GROUP_PAD = 16;
31980
+ const GROUP_PAD2 = 16;
31094
31981
  const nodeByLabel = new Map(nodes.map((n2) => [n2.label, n2]));
31095
31982
  const groups = [];
31096
31983
  for (const grp of parsed.groups) {
@@ -31104,10 +31991,10 @@ async function layoutBoxesAndLinesSearch(parsed, collapseInfo, opts) {
31104
31991
  gy1 = Math.max(gy1, n2.y + n2.height / 2);
31105
31992
  }
31106
31993
  if (!Number.isFinite(gx0)) continue;
31107
- const x0 = gx0 - GROUP_PAD;
31108
- const x1 = gx1 + GROUP_PAD;
31109
- const y0 = gy0 - GROUP_LABEL_ZONE2;
31110
- const y1 = gy1 + GROUP_PAD;
31994
+ const x0 = gx0 - GROUP_PAD2;
31995
+ const x1 = gx1 + GROUP_PAD2;
31996
+ const y0 = gy0 - GROUP_LABEL_ZONE3;
31997
+ const y1 = gy1 + GROUP_PAD2;
31111
31998
  groups.push({
31112
31999
  label: grp.label,
31113
32000
  lineNumber: grp.lineNumber,
@@ -31164,7 +32051,7 @@ async function layoutBoxesAndLinesSearch(parsed, collapseInfo, opts) {
31164
32051
  };
31165
32052
  }
31166
32053
  function place(cfg) {
31167
- const r = cfg.seed === void 0 ? null : rng2(cfg.seed + 1);
32054
+ const r = cfg.seed === void 0 ? null : rng3(cfg.seed + 1);
31168
32055
  const ord = (a) => r ? shuffle(a, r) : a.slice();
31169
32056
  const g = new import_dagre4.default.graphlib.Graph({ compound: true, multigraph: true });
31170
32057
  g.setGraph({
@@ -31201,8 +32088,14 @@ async function layoutBoxesAndLinesSearch(parsed, collapseInfo, opts) {
31201
32088
  for (const e of ord(parsed.edges)) {
31202
32089
  const s = srcRep(e.source);
31203
32090
  const t = tgtRep(e.target);
31204
- if (s !== t && g.hasNode(s) && g.hasNode(t))
31205
- g.setEdge(s, t, {}, edgeKey(e));
32091
+ if (s !== t && g.hasNode(s) && g.hasNode(t)) {
32092
+ let edgeLabel = {};
32093
+ if (reserveEdgeLabels && e.label) {
32094
+ const m = measureEdgeLabel(e.label, EDGE_LABEL_FONT_SIZE6);
32095
+ edgeLabel = { width: m.width, height: m.height, labelpos: "c" };
32096
+ }
32097
+ g.setEdge(s, t, edgeLabel, edgeKey(e));
32098
+ }
31206
32099
  }
31207
32100
  import_dagre4.default.layout(g);
31208
32101
  const nodes = parsed.nodes.map((n2) => {
@@ -31351,8 +32244,15 @@ async function layoutBoxesAndLinesSearch(parsed, collapseInfo, opts) {
31351
32244
  layered = layeredCandidates(parsed, sizes);
31352
32245
  } catch {
31353
32246
  }
32247
+ if (parsed.groups.length > 0 && collapsedGroupLabels.size === 0) {
32248
+ try {
32249
+ layered = layered.concat(groupedTierCandidates(parsed, sizes));
32250
+ } catch {
32251
+ }
32252
+ }
31354
32253
  const fastKey = /* @__PURE__ */ new Map();
31355
- for (const lay of pool) fastKey.set(lay, objective(lay, countCrossingsFast(lay)));
32254
+ for (const lay of pool)
32255
+ fastKey.set(lay, objective(lay, countCrossingsFast(lay)));
31356
32256
  pool.sort((a, b) => fastKey.get(a) - fastKey.get(b));
31357
32257
  const refineK = Math.min(REFINE_K, pool.length);
31358
32258
  let best = pool[0];
@@ -31417,7 +32317,7 @@ async function layoutBoxesAndLinesSearch(parsed, collapseInfo, opts) {
31417
32317
  }
31418
32318
  return best;
31419
32319
  }
31420
- var import_dagre4, import_d3_shape, DEFAULT_LAMBDA, ESCALATE_THRESHOLD, ESCALATE_MAX_N, ESCALATE_SEEDS, ESCALATE_REFINE, splineGen, PATH_TOKEN_RE, FLAT_CACHE, GROUP_LABEL_ZONE2;
32320
+ var import_dagre4, import_d3_shape, DEFAULT_LAMBDA, ESCALATE_THRESHOLD, ESCALATE_MAX_N, ESCALATE_SEEDS, ESCALATE_REFINE, splineGen, PATH_TOKEN_RE, FLAT_CACHE, GROUP_LABEL_ZONE3;
31421
32321
  var init_layout_search = __esm({
31422
32322
  "src/boxes-and-lines/layout-search.ts"() {
31423
32323
  "use strict";
@@ -31425,6 +32325,8 @@ var init_layout_search = __esm({
31425
32325
  import_d3_shape = require("d3-shape");
31426
32326
  init_layout5();
31427
32327
  init_layout_layered();
32328
+ init_layout_grouped();
32329
+ init_label_placement();
31428
32330
  DEFAULT_LAMBDA = 4;
31429
32331
  ESCALATE_THRESHOLD = 4;
31430
32332
  ESCALATE_MAX_N = 45;
@@ -31433,7 +32335,7 @@ var init_layout_search = __esm({
31433
32335
  splineGen = (0, import_d3_shape.line)().x((d) => d.x).y((d) => d.y).curve(import_d3_shape.curveBasis);
31434
32336
  PATH_TOKEN_RE = /[MLQC]|-?\d*\.?\d+(?:e-?\d+)?/gi;
31435
32337
  FLAT_CACHE = /* @__PURE__ */ new WeakMap();
31436
- GROUP_LABEL_ZONE2 = 32;
32338
+ GROUP_LABEL_ZONE3 = 32;
31437
32339
  }
31438
32340
  });
31439
32341
 
@@ -31483,9 +32385,9 @@ function estimateLabelLines(label, nodeWidth = NODE_WIDTH) {
31483
32385
  current = word;
31484
32386
  }
31485
32387
  }
31486
- if (lines <= MAX_LABEL_LINES) return Math.min(lines, MAX_LABEL_LINES);
32388
+ if (lines <= MAX_LABEL_LINES2) return Math.min(lines, MAX_LABEL_LINES2);
31487
32389
  }
31488
- return MAX_LABEL_LINES;
32390
+ return MAX_LABEL_LINES2;
31489
32391
  }
31490
32392
  function computeNodeSize(node, reserveValueRow) {
31491
32393
  if (!node.description || node.description.length === 0) {
@@ -31493,7 +32395,7 @@ function computeNodeSize(node, reserveValueRow) {
31493
32395
  }
31494
32396
  const w = DESC_NODE_WIDTH;
31495
32397
  const labelLines = estimateLabelLines(node.label, w);
31496
- const labelHeight = labelLines * 13 * LABEL_LINE_HEIGHT + LABEL_PAD;
32398
+ const labelHeight = labelLines * 13 * LABEL_LINE_HEIGHT2 + LABEL_PAD;
31497
32399
  const maxTextWidth = w - 24;
31498
32400
  let totalRenderedLines = 0;
31499
32401
  for (const line11 of node.description) {
@@ -31517,7 +32419,7 @@ function computeNodeSize(node, reserveValueRow) {
31517
32419
  }
31518
32420
  async function layoutBoxesAndLines(parsed, collapseInfo, layoutOptions) {
31519
32421
  const { layoutBoxesAndLinesSearch: layoutBoxesAndLinesSearch2 } = await Promise.resolve().then(() => (init_layout_search(), layout_search_exports));
31520
- const searched = await layoutBoxesAndLinesSearch2(parsed, collapseInfo, {
32422
+ const searchOpts = {
31521
32423
  ...layoutOptions?.hideDescriptions !== void 0 && {
31522
32424
  hideDescriptions: layoutOptions.hideDescriptions
31523
32425
  },
@@ -31527,12 +32429,23 @@ async function layoutBoxesAndLines(parsed, collapseInfo, layoutOptions) {
31527
32429
  ...layoutOptions?.onProgress !== void 0 && {
31528
32430
  onProgress: layoutOptions.onProgress
31529
32431
  }
31530
- });
31531
- return attachNotes(
31532
- applyParallelEdgeOffsets(searched),
32432
+ };
32433
+ const searched = await layoutBoxesAndLinesSearch2(
31533
32434
  parsed,
31534
- layoutOptions?.collapsedNotes
32435
+ collapseInfo,
32436
+ searchOpts
31535
32437
  );
32438
+ let placed = placeEdgeLabels(applyParallelEdgeOffsets(searched));
32439
+ if (placed.unresolved.length > 0) {
32440
+ const relaid = await layoutBoxesAndLinesSearch2(parsed, collapseInfo, {
32441
+ ...searchOpts,
32442
+ reserveEdgeLabels: true
32443
+ });
32444
+ const relaidPlaced = placeEdgeLabels(applyParallelEdgeOffsets(relaid));
32445
+ if (relaidPlaced.unresolved.length < placed.unresolved.length)
32446
+ placed = relaidPlaced;
32447
+ }
32448
+ return attachNotes(placed.layout, parsed, layoutOptions?.collapsedNotes);
31536
32449
  }
31537
32450
  function attachNotes(layout, parsed, collapsedNotes) {
31538
32451
  const notesSuppressed = parsed.options?.["no-notes"] === "on";
@@ -31600,6 +32513,7 @@ function attachNotes(layout, parsed, collapsedNotes) {
31600
32513
  points: e.points.map((pt) => ({ x: pt.x + shiftX, y: pt.y + shiftY })),
31601
32514
  ...e.labelX !== void 0 && { labelX: e.labelX + shiftX },
31602
32515
  ...e.labelY !== void 0 && { labelY: e.labelY + shiftY }
32516
+ // labelWidth/labelHeight/labelLines are shift-invariant — carried via spread.
31603
32517
  })) : layout.edges;
31604
32518
  const finalGroups = shifted ? layout.groups.map((grp) => ({
31605
32519
  ...grp,
@@ -31610,8 +32524,8 @@ function attachNotes(layout, parsed, collapsedNotes) {
31610
32524
  nodes: finalNodes,
31611
32525
  edges: finalEdges,
31612
32526
  groups: finalGroups,
31613
- width: bbMaxX + shiftX + MARGIN4,
31614
- height: bbMaxY + shiftY + MARGIN4
32527
+ width: bbMaxX + shiftX + MARGIN5,
32528
+ height: bbMaxY + shiftY + MARGIN5
31615
32529
  };
31616
32530
  }
31617
32531
  function applyParallelEdgeOffsets(layout) {
@@ -31644,13 +32558,14 @@ function applyParallelEdgeOffsets(layout) {
31644
32558
  }))
31645
32559
  };
31646
32560
  }
31647
- 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;
32561
+ 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;
31648
32562
  var init_layout5 = __esm({
31649
32563
  "src/boxes-and-lines/layout.ts"() {
31650
32564
  "use strict";
31651
32565
  init_text_measure();
32566
+ init_label_placement();
31652
32567
  init_notes();
31653
- MARGIN4 = 40;
32568
+ MARGIN5 = 40;
31654
32569
  MAX_PARALLEL_EDGES = 5;
31655
32570
  PARALLEL_SPACING = 22;
31656
32571
  PHI = 1.618;
@@ -31662,8 +32577,8 @@ var init_layout5 = __esm({
31662
32577
  DESC_PADDING = 8;
31663
32578
  SEPARATOR_GAP4 = 4;
31664
32579
  MAX_DESC_LINES2 = 6;
31665
- MAX_LABEL_LINES = 3;
31666
- LABEL_LINE_HEIGHT = 1.3;
32580
+ MAX_LABEL_LINES2 = 3;
32581
+ LABEL_LINE_HEIGHT2 = 1.3;
31667
32582
  LABEL_PAD = 12;
31668
32583
  VALUE_ROW_FONT = 11;
31669
32584
  VALUE_ROW_H = SEPARATOR_GAP4 + VALUE_ROW_FONT * DESC_LINE_HEIGHT2 + DESC_PADDING;
@@ -31682,7 +32597,7 @@ function tokenize(text) {
31682
32597
  return tokens;
31683
32598
  }
31684
32599
  function tryWrap(tokens, maxWidth, fontSize, maxLines) {
31685
- const availWidth = maxWidth - H_PAD;
32600
+ const availWidth = maxWidth - H_PAD2;
31686
32601
  const lines = [];
31687
32602
  let currentLine = "";
31688
32603
  for (const token of tokens) {
@@ -31707,7 +32622,7 @@ function tryWrap(tokens, maxWidth, fontSize, maxLines) {
31707
32622
  return lines;
31708
32623
  }
31709
32624
  function truncateLastLine(lines, maxWidth, fontSize) {
31710
- const availWidth = maxWidth - H_PAD;
32625
+ const availWidth = maxWidth - H_PAD2;
31711
32626
  const result = [...lines];
31712
32627
  const last = result[result.length - 1];
31713
32628
  if (measureText(last, fontSize) > availWidth) {
@@ -31715,7 +32630,7 @@ function truncateLastLine(lines, maxWidth, fontSize) {
31715
32630
  }
31716
32631
  return result;
31717
32632
  }
31718
- function wrapText(text, maxWidth, baseFontSize, minFontSize, maxLines = MAX_LABEL_LINES2) {
32633
+ function wrapText(text, maxWidth, baseFontSize, minFontSize, maxLines = MAX_LABEL_LINES3) {
31719
32634
  if (!text) return { lines: [""], fontSize: baseFontSize };
31720
32635
  const tokens = tokenize(text);
31721
32636
  for (let fs = baseFontSize; fs >= minFontSize; fs--) {
@@ -31730,7 +32645,7 @@ function wrapText(text, maxWidth, baseFontSize, minFontSize, maxLines = MAX_LABE
31730
32645
  if (allLines.length > maxLines) {
31731
32646
  const last = truncated[truncated.length - 1];
31732
32647
  if (!last.endsWith("\u2026")) {
31733
- const availWidth = maxWidth - H_PAD;
32648
+ const availWidth = maxWidth - H_PAD2;
31734
32649
  if (measureText(last + "\u2026", minFontSize) > availWidth) {
31735
32650
  truncated[truncated.length - 1] = truncateText(
31736
32651
  last,
@@ -31757,7 +32672,7 @@ function computeNodeText(label, description, depth, nodeWidth, hideDescriptions)
31757
32672
  nodeWidth,
31758
32673
  baseFontSize,
31759
32674
  MIN_FONT_SIZE,
31760
- MAX_LABEL_LINES2
32675
+ MAX_LABEL_LINES3
31761
32676
  );
31762
32677
  const descLines = [];
31763
32678
  let descFontSize = DESC_FONT_SIZE3;
@@ -31785,14 +32700,14 @@ function computeNodeText(label, description, depth, nodeWidth, hideDescriptions)
31785
32700
  descFontSize
31786
32701
  };
31787
32702
  }
31788
- var H_PAD, MAX_LABEL_LINES2, MAX_DESC_LINES3, ROOT_FONT_SIZE, MIN_FONT_SIZE, FONT_STEP, DESC_FONT_SIZE3;
32703
+ var H_PAD2, MAX_LABEL_LINES3, MAX_DESC_LINES3, ROOT_FONT_SIZE, MIN_FONT_SIZE, FONT_STEP, DESC_FONT_SIZE3;
31789
32704
  var init_text_wrap = __esm({
31790
32705
  "src/mindmap/text-wrap.ts"() {
31791
32706
  "use strict";
31792
32707
  init_description_helpers();
31793
32708
  init_text_measure();
31794
- H_PAD = 16;
31795
- MAX_LABEL_LINES2 = 3;
32709
+ H_PAD2 = 16;
32710
+ MAX_LABEL_LINES3 = 3;
31796
32711
  MAX_DESC_LINES3 = 2;
31797
32712
  ROOT_FONT_SIZE = 17;
31798
32713
  MIN_FONT_SIZE = 9;
@@ -31821,7 +32736,7 @@ function layoutMindmap(parsed, _palette, options) {
31821
32736
  leafWidth: ctx.structural(LEAF_WIDTH),
31822
32737
  hGap: ctx.aesthetic(H_GAP2),
31823
32738
  vGap: ctx.aesthetic(V_GAP2),
31824
- margin: ctx.aesthetic(MARGIN5),
32739
+ margin: ctx.aesthetic(MARGIN6),
31825
32740
  multiRootGap: ctx.aesthetic(MULTI_ROOT_GAP)
31826
32741
  };
31827
32742
  populateDepthCache(roots);
@@ -32179,7 +33094,7 @@ function nodeHeightS(node, hideDescriptions, sc) {
32179
33094
  hideDescriptions
32180
33095
  );
32181
33096
  const labelLineCount = text.labelLines.length;
32182
- const labelH = labelLineCount <= 1 ? SINGLE_LABEL_HEIGHT : LABEL_LINE_HEIGHT2 * labelLineCount;
33097
+ const labelH = labelLineCount <= 1 ? SINGLE_LABEL_HEIGHT : LABEL_LINE_HEIGHT3 * labelLineCount;
32183
33098
  let h = labelH + NODE_V_PAD;
32184
33099
  if (text.descLines.length > 0) {
32185
33100
  h += DESC_LINE_HEIGHT3 * text.descLines.length + 4;
@@ -32199,7 +33114,7 @@ function populateDepthCache(roots) {
32199
33114
  };
32200
33115
  walk(roots, 0);
32201
33116
  }
32202
- 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;
33117
+ 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;
32203
33118
  var init_layout6 = __esm({
32204
33119
  "src/mindmap/layout.ts"() {
32205
33120
  "use strict";
@@ -32210,12 +33125,12 @@ var init_layout6 = __esm({
32210
33125
  DEPTH1_WIDTH = 150;
32211
33126
  LEAF_WIDTH = 120;
32212
33127
  SINGLE_LABEL_HEIGHT = 28;
32213
- LABEL_LINE_HEIGHT2 = 18;
33128
+ LABEL_LINE_HEIGHT3 = 18;
32214
33129
  DESC_LINE_HEIGHT3 = 14;
32215
33130
  NODE_V_PAD = 10;
32216
33131
  H_GAP2 = 40;
32217
33132
  V_GAP2 = 12;
32218
- MARGIN5 = 40;
33133
+ MARGIN6 = 40;
32219
33134
  MULTI_ROOT_GAP = 60;
32220
33135
  nodeDepthCache = /* @__PURE__ */ new Map();
32221
33136
  }
@@ -32472,7 +33387,7 @@ function renderMindmap(container, parsed, layout, palette, isDark, onClickItem,
32472
33387
  );
32473
33388
  const nodeG = mainG.append("g").attr("class", "mindmap-node").attr("data-line-number", node.lineNumber);
32474
33389
  if (activeTagGroup) {
32475
- const tagKey = activeTagGroup.toLowerCase();
33390
+ const tagKey = tagAttrKey(activeTagGroup);
32476
33391
  const metaValue = node.metadata[tagKey];
32477
33392
  if (metaValue) {
32478
33393
  nodeG.attr(`data-tag-${tagKey}`, metaValue.toLowerCase());
@@ -32501,18 +33416,18 @@ function renderMindmap(container, parsed, layout, palette, isDark, onClickItem,
32501
33416
  descFontSize
32502
33417
  } = textLayout;
32503
33418
  const labelLineCount = labelLines.length;
32504
- const labelZoneH = labelLineCount <= 1 ? SINGLE_LABEL_HEIGHT2 : LABEL_LINE_HEIGHT3 * labelLineCount;
33419
+ const labelZoneH = labelLineCount <= 1 ? SINGLE_LABEL_HEIGHT2 : LABEL_LINE_HEIGHT4 * labelLineCount;
32505
33420
  const labelZoneHeight = showDesc ? labelZoneH : node.height;
32506
33421
  const centerX = node.x + node.width / 2;
32507
33422
  if (labelLineCount <= 1) {
32508
33423
  const labelY = node.y + labelZoneHeight / 2 + fontSize * 0.35;
32509
33424
  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]);
32510
33425
  } else {
32511
- const blockH = LABEL_LINE_HEIGHT3 * (labelLineCount - 1);
33426
+ const blockH = LABEL_LINE_HEIGHT4 * (labelLineCount - 1);
32512
33427
  const firstBaselineY = node.y + labelZoneHeight / 2 - blockH / 2 + fontSize * 0.35;
32513
33428
  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);
32514
33429
  for (let i = 0; i < labelLines.length; i++) {
32515
- textEl.append("tspan").attr("x", centerX).attr("y", firstBaselineY + i * LABEL_LINE_HEIGHT3).text(labelLines[i]);
33430
+ textEl.append("tspan").attr("x", centerX).attr("y", firstBaselineY + i * LABEL_LINE_HEIGHT4).text(labelLines[i]);
32516
33431
  }
32517
33432
  }
32518
33433
  if (labelLines.length > 1 || labelLines[0] !== node.label) {
@@ -32587,10 +33502,11 @@ function renderMindmapForExport(content, theme, palette) {
32587
33502
  return extractExportSvg(container, theme);
32588
33503
  });
32589
33504
  }
32590
- var d3Selection12, DIAGRAM_PADDING7, TITLE_HEIGHT4, SINGLE_LABEL_HEIGHT2, LABEL_LINE_HEIGHT3, DESC_LINE_HEIGHT4, NODE_RADIUS, ROOT_STROKE_WIDTH, DEPTH_COLOR_KEYS;
33505
+ var d3Selection12, DIAGRAM_PADDING7, TITLE_HEIGHT4, SINGLE_LABEL_HEIGHT2, LABEL_LINE_HEIGHT4, DESC_LINE_HEIGHT4, NODE_RADIUS, ROOT_STROKE_WIDTH, DEPTH_COLOR_KEYS;
32591
33506
  var init_renderer7 = __esm({
32592
33507
  "src/mindmap/renderer.ts"() {
32593
33508
  "use strict";
33509
+ init_tag_groups();
32594
33510
  d3Selection12 = __toESM(require("d3-selection"), 1);
32595
33511
  init_fonts();
32596
33512
  init_export_container();
@@ -32609,7 +33525,7 @@ var init_renderer7 = __esm({
32609
33525
  DIAGRAM_PADDING7 = 20;
32610
33526
  TITLE_HEIGHT4 = 30;
32611
33527
  SINGLE_LABEL_HEIGHT2 = 28;
32612
- LABEL_LINE_HEIGHT3 = 18;
33528
+ LABEL_LINE_HEIGHT4 = 18;
32613
33529
  DESC_LINE_HEIGHT4 = 14;
32614
33530
  NODE_RADIUS = 6;
32615
33531
  ROOT_STROKE_WIDTH = 2.5;
@@ -33801,7 +34717,7 @@ function resolveNodeColor4(el, tagGroups, activeGroupName, ancestors) {
33801
34717
  (g) => g.name.toLowerCase() === activeGroupName.toLowerCase()
33802
34718
  );
33803
34719
  if (!group) return void 0;
33804
- const key = group.name.toLowerCase();
34720
+ const key = tagAttrKey(group.name);
33805
34721
  let metaValue = el.metadata[key];
33806
34722
  if (!metaValue && ancestors) {
33807
34723
  for (const ancestor of ancestors) {
@@ -34003,8 +34919,8 @@ function layoutC4Context(parsed, activeTagGroup) {
34003
34919
  }
34004
34920
  }
34005
34921
  if (nodes.length > 0) {
34006
- const shiftX = MARGIN6 - minX;
34007
- const shiftY = MARGIN6 - minY;
34922
+ const shiftX = MARGIN7 - minX;
34923
+ const shiftY = MARGIN7 - minY;
34008
34924
  for (const node of nodes) {
34009
34925
  node.x += shiftX;
34010
34926
  node.y += shiftY;
@@ -34016,12 +34932,12 @@ function layoutC4Context(parsed, activeTagGroup) {
34016
34932
  }
34017
34933
  }
34018
34934
  }
34019
- let totalWidth = nodes.length > 0 ? maxX - minX + MARGIN6 * 2 : 0;
34020
- let totalHeight = nodes.length > 0 ? maxY - minY + MARGIN6 * 2 : 0;
34935
+ let totalWidth = nodes.length > 0 ? maxX - minX + MARGIN7 * 2 : 0;
34936
+ let totalHeight = nodes.length > 0 ? maxY - minY + MARGIN7 * 2 : 0;
34021
34937
  const legendGroups = computeLegendGroups3(parsed.tagGroups);
34022
34938
  if (legendGroups.length > 0) {
34023
- const legendY = totalHeight + MARGIN6;
34024
- let legendX = MARGIN6;
34939
+ const legendY = totalHeight + MARGIN7;
34940
+ let legendX = MARGIN7;
34025
34941
  for (const lg of legendGroups) {
34026
34942
  lg.x = legendX;
34027
34943
  lg.y = legendY;
@@ -34353,8 +35269,8 @@ function layoutC4Containers(parsed, systemName, activeTagGroup) {
34353
35269
  if (pt.y > maxY) maxY = pt.y;
34354
35270
  }
34355
35271
  }
34356
- const shiftX = MARGIN6 - minX;
34357
- const shiftY = MARGIN6 - minY;
35272
+ const shiftX = MARGIN7 - minX;
35273
+ const shiftY = MARGIN7 - minY;
34358
35274
  for (const node of nodes) {
34359
35275
  node.x += shiftX;
34360
35276
  node.y += shiftY;
@@ -34371,12 +35287,12 @@ function layoutC4Containers(parsed, systemName, activeTagGroup) {
34371
35287
  pt.y += shiftY;
34372
35288
  }
34373
35289
  }
34374
- let totalWidth = maxX - minX + MARGIN6 * 2;
34375
- let totalHeight = maxY - minY + MARGIN6 * 2;
35290
+ let totalWidth = maxX - minX + MARGIN7 * 2;
35291
+ let totalHeight = maxY - minY + MARGIN7 * 2;
34376
35292
  const legendGroups = computeLegendGroups3(parsed.tagGroups);
34377
35293
  if (legendGroups.length > 0) {
34378
- const legendY = totalHeight + MARGIN6;
34379
- let legendX = MARGIN6;
35294
+ const legendY = totalHeight + MARGIN7;
35295
+ let legendX = MARGIN7;
34380
35296
  for (const lg of legendGroups) {
34381
35297
  lg.x = legendX;
34382
35298
  lg.y = legendY;
@@ -34768,8 +35684,8 @@ function layoutC4Components(parsed, systemName, containerName, activeTagGroup) {
34768
35684
  if (pt.y > maxY) maxY = pt.y;
34769
35685
  }
34770
35686
  }
34771
- const shiftX = MARGIN6 - minX;
34772
- const shiftY = MARGIN6 - minY;
35687
+ const shiftX = MARGIN7 - minX;
35688
+ const shiftY = MARGIN7 - minY;
34773
35689
  for (const node of nodes) {
34774
35690
  node.x += shiftX;
34775
35691
  node.y += shiftY;
@@ -34786,12 +35702,12 @@ function layoutC4Components(parsed, systemName, containerName, activeTagGroup) {
34786
35702
  pt.y += shiftY;
34787
35703
  }
34788
35704
  }
34789
- let totalWidth = maxX - minX + MARGIN6 * 2;
34790
- let totalHeight = maxY - minY + MARGIN6 * 2;
35705
+ let totalWidth = maxX - minX + MARGIN7 * 2;
35706
+ let totalHeight = maxY - minY + MARGIN7 * 2;
34791
35707
  const legendGroups = computeLegendGroups3(parsed.tagGroups);
34792
35708
  if (legendGroups.length > 0) {
34793
- const legendY = totalHeight + MARGIN6;
34794
- let legendX = MARGIN6;
35709
+ const legendY = totalHeight + MARGIN7;
35710
+ let legendX = MARGIN7;
34795
35711
  for (const lg of legendGroups) {
34796
35712
  lg.x = legendX;
34797
35713
  lg.y = legendY;
@@ -35079,8 +35995,8 @@ function layoutC4Deployment(parsed, activeTagGroup) {
35079
35995
  if (pt.y > maxY) maxY = pt.y;
35080
35996
  }
35081
35997
  }
35082
- const shiftX = MARGIN6 - minX;
35083
- const shiftY = MARGIN6 - minY;
35998
+ const shiftX = MARGIN7 - minX;
35999
+ const shiftY = MARGIN7 - minY;
35084
36000
  for (const node of nodes) {
35085
36001
  node.x += shiftX;
35086
36002
  node.y += shiftY;
@@ -35095,12 +36011,12 @@ function layoutC4Deployment(parsed, activeTagGroup) {
35095
36011
  pt.y += shiftY;
35096
36012
  }
35097
36013
  }
35098
- let totalWidth = maxX - minX + MARGIN6 * 2;
35099
- let totalHeight = maxY - minY + MARGIN6 * 2;
36014
+ let totalWidth = maxX - minX + MARGIN7 * 2;
36015
+ let totalHeight = maxY - minY + MARGIN7 * 2;
35100
36016
  const legendGroups = computeLegendGroups3(parsed.tagGroups);
35101
36017
  if (legendGroups.length > 0) {
35102
- const legendY = totalHeight + MARGIN6;
35103
- let legendX = MARGIN6;
36018
+ const legendY = totalHeight + MARGIN7;
36019
+ let legendX = MARGIN7;
35104
36020
  for (const lg of legendGroups) {
35105
36021
  lg.x = legendX;
35106
36022
  lg.y = legendY;
@@ -35120,10 +36036,11 @@ function layoutC4Deployment(parsed, activeTagGroup) {
35120
36036
  height: totalHeight
35121
36037
  };
35122
36038
  }
35123
- var import_dagre5, gNode, gEdge, MIN_NODE_WIDTH, MAX_NODE_WIDTH, TYPE_LABEL_HEIGHT, DIVIDER_GAP, NAME_HEIGHT, NAME_FONT_SIZE, DESC_LINE_HEIGHT5, DESC_FONT_SIZE4, CARD_V_PAD3, CARD_H_PAD3, META_LINE_HEIGHT4, META_FONT_SIZE4, MARGIN6, BOUNDARY_PAD, GROUP_BOUNDARY_PAD, EDGE_NODE_COLLISION_WEIGHT, META_EXCLUDE_KEYS;
36039
+ var import_dagre5, gNode, gEdge, MIN_NODE_WIDTH, MAX_NODE_WIDTH, TYPE_LABEL_HEIGHT, DIVIDER_GAP, NAME_HEIGHT, NAME_FONT_SIZE, DESC_LINE_HEIGHT5, DESC_FONT_SIZE4, CARD_V_PAD3, CARD_H_PAD3, META_LINE_HEIGHT4, META_FONT_SIZE4, MARGIN7, BOUNDARY_PAD, GROUP_BOUNDARY_PAD, EDGE_NODE_COLLISION_WEIGHT, META_EXCLUDE_KEYS;
35124
36040
  var init_layout8 = __esm({
35125
36041
  "src/c4/layout.ts"() {
35126
36042
  "use strict";
36043
+ init_tag_groups();
35127
36044
  import_dagre5 = __toESM(require("@dagrejs/dagre"), 1);
35128
36045
  init_legend_constants();
35129
36046
  init_text_measure();
@@ -35141,7 +36058,7 @@ var init_layout8 = __esm({
35141
36058
  CARD_H_PAD3 = 20;
35142
36059
  META_LINE_HEIGHT4 = 16;
35143
36060
  META_FONT_SIZE4 = 11;
35144
- MARGIN6 = 40;
36061
+ MARGIN7 = 40;
35145
36062
  BOUNDARY_PAD = 40;
35146
36063
  GROUP_BOUNDARY_PAD = 24;
35147
36064
  EDGE_NODE_COLLISION_WEIGHT = 5e3;
@@ -35275,7 +36192,7 @@ function renderC4Context(container, parsed, layout, palette, isDark, onClickItem
35275
36192
  const labelText = edge.label ?? "";
35276
36193
  const techText = edge.technology ? `[${edge.technology}]` : "";
35277
36194
  const textW = Math.max(
35278
- measureText(labelText, EDGE_LABEL_FONT_SIZE6),
36195
+ measureText(labelText, EDGE_LABEL_FONT_SIZE7),
35279
36196
  measureText(techText, TECH_FONT_SIZE)
35280
36197
  );
35281
36198
  const bgW = textW + 12;
@@ -35286,7 +36203,7 @@ function renderC4Context(container, parsed, layout, palette, isDark, onClickItem
35286
36203
  textY = midPt.y - 4;
35287
36204
  }
35288
36205
  if (labelText) {
35289
- 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);
36206
+ 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);
35290
36207
  }
35291
36208
  if (techText) {
35292
36209
  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);
@@ -35296,7 +36213,7 @@ function renderC4Context(container, parsed, layout, palette, isDark, onClickItem
35296
36213
  for (const node of layout.nodes) {
35297
36214
  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);
35298
36215
  if (activeTagGroup) {
35299
- const tagKey = activeTagGroup.toLowerCase();
36216
+ const tagKey = tagAttrKey(activeTagGroup);
35300
36217
  const tagValue = node.metadata[tagKey];
35301
36218
  if (tagValue) {
35302
36219
  nodeG.attr(`data-tag-${tagKey}`, tagValue.toLowerCase());
@@ -35470,7 +36387,7 @@ function renderEdges(contentG, edges, palette, onClickItem, obstacleRects) {
35470
36387
  const labelText = edge.label ?? "";
35471
36388
  const techText = edge.technology ? `[${edge.technology}]` : "";
35472
36389
  const textW = Math.max(
35473
- measureText(labelText, EDGE_LABEL_FONT_SIZE6),
36390
+ measureText(labelText, EDGE_LABEL_FONT_SIZE7),
35474
36391
  measureText(techText, TECH_FONT_SIZE)
35475
36392
  );
35476
36393
  const bgW = textW + 12;
@@ -35488,7 +36405,7 @@ function renderEdges(contentG, edges, palette, onClickItem, obstacleRects) {
35488
36405
  });
35489
36406
  }
35490
36407
  }
35491
- placeEdgeLabels(pendingLabels, edges, obstacleRects);
36408
+ placeEdgeLabels2(pendingLabels, edges, obstacleRects);
35492
36409
  for (const lbl of pendingLabels) {
35493
36410
  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");
35494
36411
  let textY = lbl.y;
@@ -35496,7 +36413,7 @@ function renderEdges(contentG, edges, palette, onClickItem, obstacleRects) {
35496
36413
  textY = lbl.y - 4;
35497
36414
  }
35498
36415
  if (lbl.labelText) {
35499
- 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);
36416
+ 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);
35500
36417
  }
35501
36418
  if (lbl.techText) {
35502
36419
  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);
@@ -35577,7 +36494,7 @@ function tangentAt(points, t) {
35577
36494
  }
35578
36495
  return { x: 0, y: 1 };
35579
36496
  }
35580
- function placeEdgeLabels(labels, edges, obstacleRects) {
36497
+ function placeEdgeLabels2(labels, edges, obstacleRects) {
35581
36498
  if (labels.length === 0) return;
35582
36499
  const allPaths = edges.map((e) => e.points);
35583
36500
  const placedRects = [];
@@ -35855,7 +36772,7 @@ function renderC4Containers(container, parsed, layout, palette, isDark, onClickI
35855
36772
  for (const node of layout.nodes) {
35856
36773
  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);
35857
36774
  if (activeTagGroup) {
35858
- const tagKey = activeTagGroup.toLowerCase();
36775
+ const tagKey = tagAttrKey(activeTagGroup);
35859
36776
  const tagValue = node.metadata[tagKey];
35860
36777
  if (tagValue) {
35861
36778
  nodeG.attr(`data-tag-${tagKey}`, tagValue.toLowerCase());
@@ -36115,10 +37032,11 @@ function renderC4DeploymentForExport(content, theme, palette) {
36115
37032
  document.body.removeChild(el);
36116
37033
  }
36117
37034
  }
36118
- var d3Selection14, d3Shape6, 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;
37035
+ var d3Selection14, d3Shape6, 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;
36119
37036
  var init_renderer9 = __esm({
36120
37037
  "src/c4/renderer.ts"() {
36121
37038
  "use strict";
37039
+ init_tag_groups();
36122
37040
  d3Selection14 = __toESM(require("d3-selection"), 1);
36123
37041
  d3Shape6 = __toESM(require("d3-shape"), 1);
36124
37042
  init_fonts();
@@ -36139,7 +37057,7 @@ var init_renderer9 = __esm({
36139
37057
  NAME_FONT_SIZE2 = 14;
36140
37058
  DESC_FONT_SIZE5 = 11;
36141
37059
  DESC_LINE_HEIGHT6 = 16;
36142
- EDGE_LABEL_FONT_SIZE6 = 11;
37060
+ EDGE_LABEL_FONT_SIZE7 = 11;
36143
37061
  TECH_FONT_SIZE = 10;
36144
37062
  CARD_H_PAD4 = 20;
36145
37063
  CARD_V_PAD4 = 14;
@@ -36763,7 +37681,7 @@ function renderFlowchart(container, graph, layout, palette, isDark, onClickItem,
36763
37681
  const sTitleFontSize = ctx.text(TITLE_FONT_SIZE);
36764
37682
  const sTitleY = ctx.structural(TITLE_Y);
36765
37683
  const sNodeFontSize = ctx.text(NODE_FONT_SIZE2);
36766
- const sEdgeLabelFontSize = ctx.text(EDGE_LABEL_FONT_SIZE7);
37684
+ const sEdgeLabelFontSize = ctx.text(EDGE_LABEL_FONT_SIZE8);
36767
37685
  const sEdgeStrokeWidth = ctx.structural(EDGE_STROKE_WIDTH);
36768
37686
  const sNodeStrokeWidth = ctx.structural(NODE_STROKE_WIDTH);
36769
37687
  const sArrowheadW = ctx.structural(ARROWHEAD_W3);
@@ -36989,7 +37907,7 @@ function renderFlowchartForExport(content, theme, palette) {
36989
37907
  document.body.removeChild(container);
36990
37908
  }
36991
37909
  }
36992
- var d3Selection15, DIAGRAM_PADDING9, MAX_SCALE6, NODE_FONT_SIZE2, EDGE_LABEL_FONT_SIZE7, ARROWHEAD_W3, ARROWHEAD_H3, IO_SKEW, SUBROUTINE_INSET, DOC_WAVE_HEIGHT;
37910
+ var d3Selection15, DIAGRAM_PADDING9, MAX_SCALE6, NODE_FONT_SIZE2, EDGE_LABEL_FONT_SIZE8, ARROWHEAD_W3, ARROWHEAD_H3, IO_SKEW, SUBROUTINE_INSET, DOC_WAVE_HEIGHT;
36993
37911
  var init_flowchart_renderer = __esm({
36994
37912
  "src/graph/flowchart-renderer.ts"() {
36995
37913
  "use strict";
@@ -37009,7 +37927,7 @@ var init_flowchart_renderer = __esm({
37009
37927
  DIAGRAM_PADDING9 = 20;
37010
37928
  MAX_SCALE6 = 3;
37011
37929
  NODE_FONT_SIZE2 = 13;
37012
- EDGE_LABEL_FONT_SIZE7 = 11;
37930
+ EDGE_LABEL_FONT_SIZE8 = 11;
37013
37931
  ARROWHEAD_W3 = 10;
37014
37932
  ARROWHEAD_H3 = 7;
37015
37933
  IO_SKEW = 15;
@@ -38183,7 +39101,7 @@ function separateGroups(groups, nodes, isLR, maxIterations = 20) {
38183
39101
  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);
38184
39102
  if (crossOverlap <= 0) continue;
38185
39103
  anyOverlap = true;
38186
- const shift = primaryOverlap + GROUP_GAP;
39104
+ const shift = primaryOverlap + GROUP_GAP2;
38187
39105
  const aCenter = isLR ? ga.y + ga.height / 2 : ga.x + ga.width / 2;
38188
39106
  const bCenter = isLR ? gb.y + gb.height / 2 : gb.x + gb.width / 2;
38189
39107
  const groupToShift = aCenter <= bCenter ? gb : ga;
@@ -38443,7 +39361,7 @@ function layoutInfra(computed, expandedNodeIds, collapsedNodes) {
38443
39361
  const midIdx = Math.floor(edge.points.length / 2);
38444
39362
  const midPt = edge.points[midIdx];
38445
39363
  if (midPt) {
38446
- const halfWidth = (measureText(edge.label, EDGE_LABEL_FONT_SIZE8) + 8) / 2;
39364
+ const halfWidth = (measureText(edge.label, EDGE_LABEL_FONT_SIZE9) + 8) / 2;
38447
39365
  if (midPt.x - halfWidth < minX) minX = midPt.x - halfWidth;
38448
39366
  if (midPt.x + halfWidth > maxX) maxX = midPt.x + halfWidth;
38449
39367
  }
@@ -38477,7 +39395,7 @@ function layoutInfra(computed, expandedNodeIds, collapsedNodes) {
38477
39395
  height: totalHeight
38478
39396
  };
38479
39397
  }
38480
- var import_dagre7, MIN_NODE_WIDTH2, NODE_HEADER_HEIGHT, META_LINE_HEIGHT5, NODE_SEPARATOR_GAP, NODE_PAD_BOTTOM, ROLE_DOT_ROW, COLLAPSE_BAR_HEIGHT3, NODE_FONT_SIZE3, META_FONT_SIZE5, EDGE_LABEL_FONT_SIZE8, PADDING_X3, GROUP_PADDING2, GROUP_HEADER_HEIGHT, EDGE_MARGIN, DISPLAY_KEYS, DISPLAY_NAMES, GROUP_GAP;
39398
+ var import_dagre7, MIN_NODE_WIDTH2, NODE_HEADER_HEIGHT, META_LINE_HEIGHT5, NODE_SEPARATOR_GAP, NODE_PAD_BOTTOM, ROLE_DOT_ROW, COLLAPSE_BAR_HEIGHT3, NODE_FONT_SIZE3, META_FONT_SIZE5, EDGE_LABEL_FONT_SIZE9, PADDING_X3, GROUP_PADDING2, GROUP_HEADER_HEIGHT, EDGE_MARGIN, DISPLAY_KEYS, DISPLAY_NAMES, GROUP_GAP2;
38481
39399
  var init_layout10 = __esm({
38482
39400
  "src/infra/layout.ts"() {
38483
39401
  "use strict";
@@ -38492,7 +39410,7 @@ var init_layout10 = __esm({
38492
39410
  COLLAPSE_BAR_HEIGHT3 = 6;
38493
39411
  NODE_FONT_SIZE3 = 13;
38494
39412
  META_FONT_SIZE5 = 10;
38495
- EDGE_LABEL_FONT_SIZE8 = 11;
39413
+ EDGE_LABEL_FONT_SIZE9 = 11;
38496
39414
  PADDING_X3 = 24;
38497
39415
  GROUP_PADDING2 = 20;
38498
39416
  GROUP_HEADER_HEIGHT = 24;
@@ -38533,7 +39451,7 @@ var init_layout10 = __esm({
38533
39451
  "retention-hours": "retention",
38534
39452
  partitions: "partitions"
38535
39453
  };
38536
- GROUP_GAP = GROUP_PADDING2 * 2 + GROUP_HEADER_HEIGHT;
39454
+ GROUP_GAP2 = GROUP_PADDING2 * 2 + GROUP_HEADER_HEIGHT;
38537
39455
  }
38538
39456
  });
38539
39457
 
@@ -38597,7 +39515,7 @@ function buildScaledConstants(ctx) {
38597
39515
  sNodeFontSize: ctx.text(NODE_FONT_SIZE4),
38598
39516
  sMetaFontSize: ctx.text(META_FONT_SIZE6),
38599
39517
  sMetaLineHeight: ctx.structural(META_LINE_HEIGHT6),
38600
- sEdgeLabelFontSize: ctx.text(EDGE_LABEL_FONT_SIZE9),
39518
+ sEdgeLabelFontSize: ctx.text(EDGE_LABEL_FONT_SIZE10),
38601
39519
  sGroupLabelFontSize: ctx.text(GROUP_LABEL_FONT_SIZE2),
38602
39520
  sNodeBorderRadius: ctx.structural(NODE_BORDER_RADIUS),
38603
39521
  sEdgeStrokeWidth: ctx.structural(EDGE_STROKE_WIDTH),
@@ -39326,7 +40244,7 @@ function renderEdgeLabels(svg, edges, nodes, groups, palette, _isDark, animate,
39326
40244
  if (!midPt) continue;
39327
40245
  const labelText = edge.label;
39328
40246
  const g = svg.append("g").attr("class", animate ? "infra-edge-label" : "");
39329
- const textWidth = measureText(labelText, EDGE_LABEL_FONT_SIZE9) + 8;
40247
+ const textWidth = measureText(labelText, EDGE_LABEL_FONT_SIZE10) + 8;
39330
40248
  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);
39331
40249
  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);
39332
40250
  if (animate) {
@@ -39340,7 +40258,7 @@ function resolveActiveTagStroke(node, activeGroup, tagGroups, palette) {
39340
40258
  (t) => t.name.toLowerCase() === activeGroup.toLowerCase()
39341
40259
  );
39342
40260
  if (!tg) return null;
39343
- const tagKey = (tg.alias ?? tg.name).toLowerCase();
40261
+ const tagKey = tagAttrKey(tg.alias ?? tg.name);
39344
40262
  const tagVal = node.tags[tagKey];
39345
40263
  if (!tagVal) return null;
39346
40264
  const tv = tg.values.find(
@@ -39669,7 +40587,7 @@ function computeInfraLegendGroups(nodes, tagGroups, palette, edges) {
39669
40587
  groups.push({
39670
40588
  name: tg.name,
39671
40589
  type: "tag",
39672
- tagKey: (tg.alias ?? tg.name).toLowerCase(),
40590
+ tagKey: tagAttrKey(tg.alias ?? tg.name),
39673
40591
  entries,
39674
40592
  width: LEGEND_CAPSULE_PAD * 2 + pillWidth2 + 4 + entriesWidth2,
39675
40593
  minifiedWidth: pillWidth2
@@ -39717,7 +40635,7 @@ function renderLegend3(rootSvg, legendGroups, totalWidth, legendY, palette, isDa
39717
40635
  });
39718
40636
  legendG.selectAll("[data-legend-group]").classed("infra-legend-group", true);
39719
40637
  for (const group of legendGroups) {
39720
- const groupKey2 = group.name.toLowerCase();
40638
+ const groupKey2 = tagAttrKey(group.name);
39721
40639
  for (const entry of group.entries) {
39722
40640
  const entryEl = legendG.select(
39723
40641
  `[data-legend-group="${groupKey2}"] [data-legend-entry="${entry.value.toLowerCase()}"]`
@@ -39936,10 +40854,11 @@ function parseAndLayoutInfra(content) {
39936
40854
  const layout = layoutInfra(computed);
39937
40855
  return { parsed, computed, layout };
39938
40856
  }
39939
- var d3Selection16, d3Shape8, 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;
40857
+ var d3Selection16, d3Shape8, 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;
39940
40858
  var init_renderer10 = __esm({
39941
40859
  "src/infra/renderer.ts"() {
39942
40860
  "use strict";
40861
+ init_tag_groups();
39943
40862
  d3Selection16 = __toESM(require("d3-selection"), 1);
39944
40863
  d3Shape8 = __toESM(require("d3-shape"), 1);
39945
40864
  init_fonts();
@@ -39961,7 +40880,7 @@ var init_renderer10 = __esm({
39961
40880
  NODE_FONT_SIZE4 = 13;
39962
40881
  META_FONT_SIZE6 = 10;
39963
40882
  META_LINE_HEIGHT6 = 14;
39964
- EDGE_LABEL_FONT_SIZE9 = 11;
40883
+ EDGE_LABEL_FONT_SIZE10 = 11;
39965
40884
  GROUP_LABEL_FONT_SIZE2 = 14;
39966
40885
  NODE_BORDER_RADIUS = 8;
39967
40886
  OVERLOAD_STROKE_WIDTH = 3;
@@ -40038,17 +40957,17 @@ function mulberry32(seed) {
40038
40957
  return ((t ^ t >>> 14) >>> 0) / 4294967296;
40039
40958
  };
40040
40959
  }
40041
- function standardNormal(rng3) {
40960
+ function standardNormal(rng4) {
40042
40961
  let u = 0;
40043
40962
  let v = 0;
40044
- while (u === 0) u = rng3();
40045
- while (v === 0) v = rng3();
40963
+ while (u === 0) u = rng4();
40964
+ while (v === 0) v = rng4();
40046
40965
  return Math.sqrt(-2 * Math.log(u)) * Math.cos(2 * Math.PI * v);
40047
40966
  }
40048
- function gamma(shape, rng3) {
40967
+ function gamma(shape, rng4) {
40049
40968
  if (shape < 1) {
40050
- const u = rng3();
40051
- return gamma(shape + 1, rng3) * Math.pow(u, 1 / shape);
40969
+ const u = rng4();
40970
+ return gamma(shape + 1, rng4) * Math.pow(u, 1 / shape);
40052
40971
  }
40053
40972
  const d = shape - 1 / 3;
40054
40973
  const c = 1 / Math.sqrt(9 * d);
@@ -40056,29 +40975,29 @@ function gamma(shape, rng3) {
40056
40975
  let x;
40057
40976
  let v;
40058
40977
  do {
40059
- x = standardNormal(rng3);
40978
+ x = standardNormal(rng4);
40060
40979
  v = 1 + c * x;
40061
40980
  } while (v <= 0);
40062
40981
  v = v * v * v;
40063
- const u = rng3();
40982
+ const u = rng4();
40064
40983
  if (u < 1 - 0.0331 * x * x * x * x) return d * v;
40065
40984
  if (Math.log(u) < x * x / 2 + d * (1 - v + Math.log(v))) return d * v;
40066
40985
  }
40067
40986
  }
40068
- function sampleBeta(alpha, beta, rng3) {
40069
- const x = gamma(alpha, rng3);
40070
- const y = gamma(beta, rng3);
40987
+ function sampleBeta(alpha, beta, rng4) {
40988
+ const x = gamma(alpha, rng4);
40989
+ const y = gamma(beta, rng4);
40071
40990
  return x / (x + y);
40072
40991
  }
40073
- function sampleBetaPert(o, m, p, rng3) {
40992
+ function sampleBetaPert(o, m, p, rng4) {
40074
40993
  if (o === p) return m;
40075
40994
  const range = p - o;
40076
40995
  const alpha = 1 + 4 * (m - o) / range;
40077
40996
  const beta = 1 + 4 * (p - m) / range;
40078
- return o + sampleBeta(alpha, beta, rng3) * range;
40997
+ return o + sampleBeta(alpha, beta, rng4) * range;
40079
40998
  }
40080
40999
  function simulate(resolved, expanded, _predecessors, _successors, topo, terminals, poisoned, opts) {
40081
- const rng3 = mulberry32(opts.seed);
41000
+ const rng4 = mulberry32(opts.seed);
40082
41001
  const expById = /* @__PURE__ */ new Map();
40083
41002
  for (const e of expanded) expById.set(e.id, e);
40084
41003
  const completionTimes = new Array(opts.trials);
@@ -40101,7 +41020,7 @@ function simulate(resolved, expanded, _predecessors, _successors, topo, terminal
40101
41020
  } else if (exp.o === exp.p) {
40102
41021
  duration = exp.m;
40103
41022
  } else {
40104
- duration = sampleBetaPert(exp.o, exp.m, exp.p, rng3);
41023
+ duration = sampleBetaPert(exp.o, exp.m, exp.p, rng4);
40105
41024
  }
40106
41025
  let esLower = 0;
40107
41026
  let efLower = -Infinity;
@@ -44389,7 +45308,7 @@ function renderGantt(container, resolved, palette, isDark, options, exportDims)
44389
45308
  const ctx = exportDims ? ScaleContext.identity() : ScaleContext.from(containerWidth, idealWidth);
44390
45309
  const sBarH = ctx.structural(BAR_H);
44391
45310
  const sRowGap = ctx.structural(ROW_GAP);
44392
- const sGroupGap = ctx.structural(GROUP_GAP2);
45311
+ const sGroupGap = ctx.structural(GROUP_GAP3);
44393
45312
  const sMilestoneSize = ctx.structural(MILESTONE_SIZE);
44394
45313
  const sBottomMargin = ctx.aesthetic(BOTTOM_MARGIN);
44395
45314
  const sRightMargin = ctx.aesthetic(RIGHT_MARGIN);
@@ -44561,7 +45480,7 @@ function renderGantt(container, resolved, palette, isDark, options, exportDims)
44561
45480
  (tg) => tg.name.toLowerCase() === currentSwimlaneGroup.toLowerCase()
44562
45481
  );
44563
45482
  if (tagGroup) {
44564
- const tagKey = tagGroup.name.toLowerCase();
45483
+ const tagKey = tagAttrKey(tagGroup.name);
44565
45484
  for (const rt of resolved.tasks) {
44566
45485
  let value = rt.effectiveMetadata[tagKey];
44567
45486
  if (!value && tagGroup.defaultValue) value = tagGroup.defaultValue;
@@ -45302,7 +46221,7 @@ function renderTagLegend(svg, chartG, tagGroups, activeGroupName, chartLeftMargi
45302
46221
  const usedValues = /* @__PURE__ */ new Map();
45303
46222
  if (resolvedTasks) {
45304
46223
  for (const group of visibleGroups) {
45305
- const key = group.name.toLowerCase();
46224
+ const key = tagAttrKey(group.name);
45306
46225
  const used = /* @__PURE__ */ new Set();
45307
46226
  for (const rt of resolvedTasks) {
45308
46227
  const val = rt.effectiveMetadata[key];
@@ -45313,7 +46232,7 @@ function renderTagLegend(svg, chartG, tagGroups, activeGroupName, chartLeftMargi
45313
46232
  }
45314
46233
  const filteredEntries = /* @__PURE__ */ new Map();
45315
46234
  for (const group of visibleGroups) {
45316
- const key = group.name.toLowerCase();
46235
+ const key = tagAttrKey(group.name);
45317
46236
  const used = usedValues.get(key);
45318
46237
  if (used && used.size > 0) {
45319
46238
  filteredEntries.set(
@@ -45334,7 +46253,7 @@ function renderTagLegend(svg, chartG, tagGroups, activeGroupName, chartLeftMargi
45334
46253
  const pillW = measureLegendText(group.name, LEGEND_PILL_FONT_SIZE) + LEGEND_PILL_PAD + iconReserve;
45335
46254
  let groupW = pillW;
45336
46255
  if (isActive) {
45337
- const entries = filteredEntries.get(group.name.toLowerCase()) ?? group.entries;
46256
+ const entries = filteredEntries.get(tagAttrKey(group.name)) ?? group.entries;
45338
46257
  let entriesW = 0;
45339
46258
  for (const entry of entries) {
45340
46259
  entriesW += LEGEND_DOT_R * 2 + LEGEND_ENTRY_DOT_GAP + measureLegendText(entry.value, LEGEND_ENTRY_FONT_SIZE) + LEGEND_ENTRY_TRAIL;
@@ -45391,7 +46310,7 @@ function renderTagLegend(svg, chartG, tagGroups, activeGroupName, chartLeftMargi
45391
46310
  },
45392
46311
  ...onControlsToggle !== void 0 && { onControlsToggle },
45393
46312
  onEntryHover: (groupName, entryValue) => {
45394
- const tagKey = groupName.toLowerCase();
46313
+ const tagKey = tagAttrKey(groupName);
45395
46314
  if (entryValue) {
45396
46315
  const ev = entryValue.toLowerCase();
45397
46316
  chartG.selectAll(".gantt-task").each(function() {
@@ -46081,7 +47000,7 @@ function buildTagLaneRowList(resolved, swimlaneGroup, collapsedLanes) {
46081
47000
  (g) => g.name.toLowerCase() === swimlaneGroup.toLowerCase()
46082
47001
  );
46083
47002
  if (!tagGroup) return null;
46084
- const tagKey = tagGroup.name.toLowerCase();
47003
+ const tagKey = tagAttrKey(tagGroup.name);
46085
47004
  const rows = [];
46086
47005
  const buckets = /* @__PURE__ */ new Map();
46087
47006
  const unbucketed = [];
@@ -46265,10 +47184,11 @@ function renderTimeScaleHorizontal(g, scale, _innerWidth, innerHeight, textColor
46265
47184
  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);
46266
47185
  }
46267
47186
  }
46268
- var d3Scale5, d3Selection18, BAR_H, ROW_GAP, GROUP_GAP2, MILESTONE_SIZE, MIN_LEFT_MARGIN, BOTTOM_MARGIN, RIGHT_MARGIN, LABEL_PAD2, LABEL_GAP, BAND_ACCENT_W, BAND_RADIUS, bandClipCounter, JS_DAY_TO_WEEKDAY2, ERA_COLORS, SPRINT_BAND_OPACITY, SPRINT_HOVER_OPACITY, SPRINT_BOUNDARY_OPACITY, FADE_OPACITY;
47187
+ var d3Scale5, d3Selection18, BAR_H, ROW_GAP, GROUP_GAP3, MILESTONE_SIZE, MIN_LEFT_MARGIN, BOTTOM_MARGIN, RIGHT_MARGIN, LABEL_PAD2, LABEL_GAP, BAND_ACCENT_W, BAND_RADIUS, bandClipCounter, JS_DAY_TO_WEEKDAY2, ERA_COLORS, SPRINT_BAND_OPACITY, SPRINT_HOVER_OPACITY, SPRINT_BOUNDARY_OPACITY, FADE_OPACITY;
46269
47188
  var init_renderer12 = __esm({
46270
47189
  "src/gantt/renderer.ts"() {
46271
47190
  "use strict";
47191
+ init_tag_groups();
46272
47192
  d3Scale5 = __toESM(require("d3-scale"), 1);
46273
47193
  d3Selection18 = __toESM(require("d3-selection"), 1);
46274
47194
  init_fonts();
@@ -46285,7 +47205,7 @@ var init_renderer12 = __esm({
46285
47205
  init_title_constants();
46286
47206
  BAR_H = 22;
46287
47207
  ROW_GAP = 6;
46288
- GROUP_GAP2 = 14;
47208
+ GROUP_GAP3 = 14;
46289
47209
  MILESTONE_SIZE = 10;
46290
47210
  MIN_LEFT_MARGIN = 120;
46291
47211
  BOTTOM_MARGIN = 40;
@@ -46351,7 +47271,7 @@ function renderState(container, graph, layout, palette, isDark, onClickItem, exp
46351
47271
  const sTitleFontSize = ctx.text(TITLE_FONT_SIZE);
46352
47272
  const sTitleY = ctx.structural(TITLE_Y);
46353
47273
  const sNodeFontSize = ctx.text(NODE_FONT_SIZE6);
46354
- const sEdgeLabelFontSize = ctx.text(EDGE_LABEL_FONT_SIZE10);
47274
+ const sEdgeLabelFontSize = ctx.text(EDGE_LABEL_FONT_SIZE11);
46355
47275
  const sGroupLabelFontSize = ctx.text(GROUP_LABEL_FONT_SIZE3);
46356
47276
  const sEdgeStrokeWidth = ctx.structural(EDGE_STROKE_WIDTH);
46357
47277
  const sNodeStrokeWidth = ctx.structural(NODE_STROKE_WIDTH);
@@ -46631,7 +47551,7 @@ function renderStateForExport(content, theme, palette) {
46631
47551
  document.body.removeChild(container);
46632
47552
  }
46633
47553
  }
46634
- var d3Selection19, 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;
47554
+ var d3Selection19, 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;
46635
47555
  var init_state_renderer = __esm({
46636
47556
  "src/graph/state-renderer.ts"() {
46637
47557
  "use strict";
@@ -46651,7 +47571,7 @@ var init_state_renderer = __esm({
46651
47571
  DIAGRAM_PADDING12 = 20;
46652
47572
  MAX_SCALE7 = 3;
46653
47573
  NODE_FONT_SIZE6 = 13;
46654
- EDGE_LABEL_FONT_SIZE10 = 11;
47574
+ EDGE_LABEL_FONT_SIZE11 = 11;
46655
47575
  GROUP_LABEL_FONT_SIZE3 = 11;
46656
47576
  ARROWHEAD_W5 = 10;
46657
47577
  ARROWHEAD_H5 = 7;
@@ -48578,7 +49498,7 @@ function renderJourneyMap(container, parsed, palette, isDark, options) {
48578
49498
  const panelWidth = 280;
48579
49499
  const textAreaWidth = panelWidth - silhouetteZone - CARD_PADDING_X3;
48580
49500
  const descLineH = 14;
48581
- const descLines = parsed.persona.description ? wrapText2(parsed.persona.description, textAreaWidth, FONT_SIZE_META) : [];
49501
+ const descLines = parsed.persona.description ? wrapNoteBody(parsed.persona.description, textAreaWidth, FONT_SIZE_META) : [];
48582
49502
  const descRowH = descLines.length > 0 ? descLines.length * descLineH + 8 : 0;
48583
49503
  const panelHeight = titleRowH + descRowH;
48584
49504
  const panelX = layout.totalWidth - PADDING2 - panelWidth;
@@ -48603,8 +49523,14 @@ function renderJourneyMap(container, parsed, palette, isDark, options) {
48603
49523
  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);
48604
49524
  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);
48605
49525
  for (let li = 0; li < descLines.length; li++) {
48606
- const lineEl = personaG.append("text").attr("x", textX).attr("y", panelY + titleRowH + descLineH * (li + 1)).attr("font-size", FONT_SIZE_META).attr("fill", onPersonaText);
48607
- renderInlineText(lineEl, descLines[li], palette, FONT_SIZE_META);
49526
+ const line11 = descLines[li];
49527
+ const indent = line11.kind === "plain" ? 0 : NOTE_BULLET_INDENT;
49528
+ const lineY = panelY + titleRowH + descLineH * (li + 1);
49529
+ if (line11.kind === "bullet-first") {
49530
+ personaG.append("text").attr("x", textX).attr("y", lineY).attr("font-size", FONT_SIZE_META).attr("fill", onPersonaText).text("\u2022");
49531
+ }
49532
+ const lineEl = personaG.append("text").attr("x", textX + indent).attr("y", lineY).attr("font-size", FONT_SIZE_META).attr("fill", onPersonaText);
49533
+ renderInlineText(lineEl, line11.text, palette, FONT_SIZE_META);
48608
49534
  }
48609
49535
  if (onNavigateToLine) {
48610
49536
  personaG.style("cursor", "pointer").on("click", () => {
@@ -48643,7 +49569,7 @@ function renderJourneyMap(container, parsed, palette, isDark, options) {
48643
49569
  return;
48644
49570
  }
48645
49571
  const isScore = groupName === "Score";
48646
- const attrName = isScore ? "data-score" : `data-tag-${groupName.toLowerCase()}`;
49572
+ const attrName = isScore ? "data-score" : `data-tag-${tagAttrKey(groupName)}`;
48647
49573
  const matches = (el) => el.getAttribute(attrName) === entryValue;
48648
49574
  svg.selectAll(".journey-step").each(function() {
48649
49575
  const hit = matches(this);
@@ -49124,7 +50050,7 @@ function resolveStepColor(step, scoreColor, activeGroup, tagGroups, _palette) {
49124
50050
  (g) => g.name.toLowerCase() === activeGroup.toLowerCase()
49125
50051
  );
49126
50052
  if (!group) return void 0;
49127
- const tagValue = step.tags[group.name.toLowerCase()];
50053
+ const tagValue = step.tags[tagAttrKey(group.name)];
49128
50054
  if (!tagValue) return void 0;
49129
50055
  const entry = group.entries.find(
49130
50056
  (e) => e.value.toLowerCase() === tagValue.toLowerCase()
@@ -49339,6 +50265,7 @@ var d3, d3Shape10, DIAGRAM_PADDING13, PADDING2, CARD_PADDING_X3, CARD_PADDING_Y3
49339
50265
  var init_renderer14 = __esm({
49340
50266
  "src/journey-map/renderer.ts"() {
49341
50267
  "use strict";
50268
+ init_tag_groups();
49342
50269
  d3 = __toESM(require("d3-selection"), 1);
49343
50270
  d3Shape10 = __toESM(require("d3-shape"), 1);
49344
50271
  init_color_utils();
@@ -49346,6 +50273,7 @@ var init_renderer14 = __esm({
49346
50273
  init_parser23();
49347
50274
  init_layout13();
49348
50275
  init_inline_markdown();
50276
+ init_note_box();
49349
50277
  init_legend_integration();
49350
50278
  init_tag_groups();
49351
50279
  init_scaling();
@@ -49685,10 +50613,10 @@ function renderedDescNodeHeight(numLines, scale) {
49685
50613
  return headerH + textBlockH + bottomPad;
49686
50614
  }
49687
50615
  function wrapEdgeLabelText(label, description, maxWidth = EDGE_LABEL_MAX_WIDTH) {
49688
- const labelLines = label ? wrapTextToWidth(label, EDGE_LABEL_FONT_SIZE11, maxWidth) : [];
50616
+ const labelLines = label ? wrapTextToWidth(label, EDGE_LABEL_FONT_SIZE12, maxWidth) : [];
49689
50617
  const descLines = [];
49690
50618
  for (const d of description) {
49691
- descLines.push(...wrapTextToWidth(d, EDGE_LABEL_FONT_SIZE11, maxWidth));
50619
+ descLines.push(...wrapTextToWidth(d, EDGE_LABEL_FONT_SIZE12, maxWidth));
49692
50620
  }
49693
50621
  return { labelLines, descLines };
49694
50622
  }
@@ -49826,9 +50754,9 @@ function computeEdgePaths(layoutNodes, parsed, cx, cy, radius, isClockwise) {
49826
50754
  const lineCount = labelLines.length + descLines.length;
49827
50755
  let labelPxW = 0;
49828
50756
  for (const l of labelLines)
49829
- labelPxW = Math.max(labelPxW, measureText(l, EDGE_LABEL_FONT_SIZE11));
50757
+ labelPxW = Math.max(labelPxW, measureText(l, EDGE_LABEL_FONT_SIZE12));
49830
50758
  for (const l of descLines)
49831
- labelPxW = Math.max(labelPxW, measureText(l, EDGE_LABEL_FONT_SIZE11));
50759
+ labelPxW = Math.max(labelPxW, measureText(l, EDGE_LABEL_FONT_SIZE12));
49832
50760
  const { labelX, labelY, labelAngle } = computeEdgeLabelPosition(
49833
50761
  midAngle,
49834
50762
  radius,
@@ -49930,9 +50858,9 @@ function fitToCanvas(nodes, edges, parsed, _cx, _cy, radius, width, height, _isC
49930
50858
  );
49931
50859
  let textWidth = 0;
49932
50860
  for (const l of labelLines)
49933
- textWidth = Math.max(textWidth, measureText(l, EDGE_LABEL_FONT_SIZE11));
50861
+ textWidth = Math.max(textWidth, measureText(l, EDGE_LABEL_FONT_SIZE12));
49934
50862
  for (const l of descLines)
49935
- textWidth = Math.max(textWidth, measureText(l, EDGE_LABEL_FONT_SIZE11));
50863
+ textWidth = Math.max(textWidth, measureText(l, EDGE_LABEL_FONT_SIZE12));
49936
50864
  if (textWidth === 0) continue;
49937
50865
  const normAngle = (le.labelAngle % (2 * Math.PI) + 2 * Math.PI) % (2 * Math.PI);
49938
50866
  const isRight = normAngle < Math.PI * 0.4 || normAngle > Math.PI * 1.6;
@@ -50006,7 +50934,7 @@ function buildEdgeArc(src, tgt, cx, cy, radius, isClockwise, arrowLength = 0) {
50006
50934
  const midAngle = srcExitAngle + dir * visibleSweep / 2;
50007
50935
  return { path, midAngle };
50008
50936
  }
50009
- 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;
50937
+ 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;
50010
50938
  var init_layout14 = __esm({
50011
50939
  "src/cycle/layout.ts"() {
50012
50940
  "use strict";
@@ -50017,7 +50945,7 @@ var init_layout14 = __esm({
50017
50945
  LABEL_FONT_SIZE4 = 13;
50018
50946
  CIRCLE_LABEL_FONT_SIZE = 16;
50019
50947
  DESC_FONT_SIZE6 = 11;
50020
- EDGE_LABEL_FONT_SIZE11 = 11;
50948
+ EDGE_LABEL_FONT_SIZE12 = 11;
50021
50949
  MIN_NODE_WIDTH4 = 70;
50022
50950
  MAX_NODE_WIDTH3 = 260;
50023
50951
  DESC_MIN_WIDTH = 140;
@@ -50162,7 +51090,7 @@ function renderCycle(container, parsed, palette, isDark, onClickItem, exportDims
50162
51090
  const scaledNodeFont = Math.max(6, Math.round(NODE_FONT_SIZE7 * layout.scale));
50163
51091
  const scaledEdgeLabelFont = Math.max(
50164
51092
  6,
50165
- Math.round(EDGE_LABEL_FONT_SIZE12 * layout.scale)
51093
+ Math.round(EDGE_LABEL_FONT_SIZE13 * layout.scale)
50166
51094
  );
50167
51095
  const CIRCLE_LABEL_FONT_SIZE2 = 16;
50168
51096
  const scaledCircleLabelFont = Math.max(
@@ -50325,7 +51253,7 @@ function ensureArrowMarker(defs, color, strokeWidth) {
50325
51253
  const mh = Math.max(1.5, mw * 0.5);
50326
51254
  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);
50327
51255
  }
50328
- var d3Selection22, NODE_FONT_SIZE7, DESC_FONT_SIZE7, EDGE_LABEL_FONT_SIZE12, DESC_LINE_HEIGHT8, TITLE_AREA_HEIGHT;
51256
+ var d3Selection22, NODE_FONT_SIZE7, DESC_FONT_SIZE7, EDGE_LABEL_FONT_SIZE13, DESC_LINE_HEIGHT8, TITLE_AREA_HEIGHT;
50329
51257
  var init_renderer15 = __esm({
50330
51258
  "src/cycle/renderer.ts"() {
50331
51259
  "use strict";
@@ -50343,7 +51271,7 @@ var init_renderer15 = __esm({
50343
51271
  init_text_measure();
50344
51272
  NODE_FONT_SIZE7 = 13;
50345
51273
  DESC_FONT_SIZE7 = 11;
50346
- EDGE_LABEL_FONT_SIZE12 = 11;
51274
+ EDGE_LABEL_FONT_SIZE13 = 11;
50347
51275
  DESC_LINE_HEIGHT8 = 15;
50348
51276
  TITLE_AREA_HEIGHT = 50;
50349
51277
  }
@@ -52169,7 +53097,7 @@ function layoutMap(resolvedIn, data, size, opts) {
52169
53097
  (g) => g.name.toLowerCase() === groupName.toLowerCase()
52170
53098
  );
52171
53099
  if (!group) return null;
52172
- const val = tags[group.name.toLowerCase()] ?? group.defaultValue;
53100
+ const val = tags[tagAttrKey(group.name)] ?? group.defaultValue;
52173
53101
  if (!val) return null;
52174
53102
  const entry = group.entries.find(
52175
53103
  (e) => e.value.toLowerCase() === val.toLowerCase()
@@ -52783,7 +53711,7 @@ function layoutMap(resolvedIn, data, size, opts) {
52783
53711
  if (directHex)
52784
53712
  return { fill: directHex, stroke: mix(directHex, palette.text, 18) };
52785
53713
  for (const group of resolved.tagGroups) {
52786
- const val = p.tags[group.name.toLowerCase()];
53714
+ const val = p.tags[tagAttrKey(group.name)];
52787
53715
  if (!val) continue;
52788
53716
  const entry = group.entries.find(
52789
53717
  (e) => e.value.toLowerCase() === val.toLowerCase()
@@ -52798,7 +53726,7 @@ function layoutMap(resolvedIn, data, size, opts) {
52798
53726
  };
52799
53727
  const lineColor = (tags) => {
52800
53728
  for (const group of resolved.tagGroups) {
52801
- const val = tags[group.name.toLowerCase()];
53729
+ const val = tags[tagAttrKey(group.name)];
52802
53730
  if (!val) continue;
52803
53731
  const entry = group.entries.find(
52804
53732
  (e) => e.value.toLowerCase() === val.toLowerCase()
@@ -54064,6 +54992,7 @@ var import_d3_geo2, import_topojson_client2, FIT_PAD, PATH_DIGITS, THIN_TOL, ALB
54064
54992
  var init_layout15 = __esm({
54065
54993
  "src/map/layout.ts"() {
54066
54994
  "use strict";
54995
+ init_tag_groups();
54067
54996
  import_d3_geo2 = require("d3-geo");
54068
54997
  import_topojson_client2 = require("topojson-client");
54069
54998
  init_color_utils();
@@ -56480,7 +57409,7 @@ function resolveSequenceTags(parsed, activeTagGroup) {
56480
57409
  }
56481
57410
  return result;
56482
57411
  }
56483
- const groupKey2 = group.name.toLowerCase();
57412
+ const groupKey2 = tagAttrKey(group.name);
56484
57413
  const participantMeta = /* @__PURE__ */ new Map();
56485
57414
  for (const p of parsed.participants) {
56486
57415
  participantMeta.set(p.id, { ...p.metadata || {} });
@@ -56516,6 +57445,7 @@ function resolveSequenceTags(parsed, activeTagGroup) {
56516
57445
  var init_tag_resolution = __esm({
56517
57446
  "src/sequence/tag-resolution.ts"() {
56518
57447
  "use strict";
57448
+ init_tag_groups();
56519
57449
  }
56520
57450
  });
56521
57451
 
@@ -56990,7 +57920,7 @@ function renderSequenceDiagram(container, parsed, palette, isDark, _onNavigateTo
56990
57920
  }
56991
57921
  }
56992
57922
  const getTagColor = (value) => value ? tagValueToColor.get(value.toLowerCase()) : void 0;
56993
- const tagKey = activeTagGroup?.toLowerCase();
57923
+ const tagKey = activeTagGroup ? tagAttrKey(activeTagGroup) : void 0;
56994
57924
  const hiddenMsgIndices = /* @__PURE__ */ new Set();
56995
57925
  if (collapsedSections && collapsedSections.size > 0) {
56996
57926
  const sectionGroups = groupMessagesBySection(elements, messages);
@@ -57582,11 +58512,7 @@ function renderSequenceDiagram(container, parsed, palette, isDark, _onNavigateTo
57582
58512
  const boxH = sBoxH + GROUP_PADDING_TOP2 + GROUP_PADDING_BOTTOM2;
57583
58513
  const groupTagValue = tagKey && group.metadata?.[tagKey];
57584
58514
  const groupTagColor = getTagColor(groupTagValue || void 0);
57585
- const fillColor = groupTagColor ? mix(
57586
- groupTagColor,
57587
- themeBaseBg(palette, isDark),
57588
- isDark ? 15 : 20
57589
- ) : isDark ? palette.surface : palette.bg;
58515
+ const fillColor = groupTagColor ? mix(groupTagColor, themeBaseBg(palette, isDark), isDark ? 15 : 20) : isDark ? palette.surface : palette.bg;
57590
58516
  const strokeColor = groupTagColor || palette.textMuted;
57591
58517
  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");
57592
58518
  groupG.append("title").text("Click to collapse");
@@ -57636,11 +58562,7 @@ function renderSequenceDiagram(container, parsed, palette, isDark, _onNavigateTo
57636
58562
  );
57637
58563
  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");
57638
58564
  participantG.append("title").text("Click to expand");
57639
- const pFill = effectiveTagColor ? mix(
57640
- effectiveTagColor,
57641
- themeBaseBg(palette, isDark),
57642
- isDark ? 30 : 40
57643
- ) : isDark ? mix(palette.overlay, palette.surface, 50) : mix(palette.bg, palette.surface, 50);
58565
+ const pFill = effectiveTagColor ? mix(effectiveTagColor, themeBaseBg(palette, isDark), isDark ? 30 : 40) : isDark ? mix(palette.overlay, palette.surface, 50) : mix(palette.bg, palette.surface, 50);
57644
58566
  const pStroke = effectiveTagColor || palette.border;
57645
58567
  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);
57646
58568
  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);
@@ -58136,6 +59058,7 @@ var d3Selection27, PARTICIPANT_GAP, PARTICIPANT_BOX_WIDTH, PARTICIPANT_BOX_HEIGH
58136
59058
  var init_renderer20 = __esm({
58137
59059
  "src/sequence/renderer.ts"() {
58138
59060
  "use strict";
59061
+ init_tag_groups();
58139
59062
  d3Selection27 = __toESM(require("d3-selection"), 1);
58140
59063
  init_color_utils();
58141
59064
  init_inline_markdown();
@@ -58889,6 +59812,7 @@ __export(advanced_exports, {
58889
59812
  resolveTaskName: () => resolveTaskName,
58890
59813
  rollUpContextRelationships: () => rollUpContextRelationships,
58891
59814
  sampleBetaPert: () => sampleBetaPert,
59815
+ searchMapLocations: () => searchMapLocations,
58892
59816
  seriesColors: () => seriesColors,
58893
59817
  shade: () => shade,
58894
59818
  shapeFill: () => shapeFill,
@@ -59562,6 +60486,7 @@ function renderArcDiagram(container, parsed, palette, _isDark, onClickItem, expo
59562
60486
  init_parser3();
59563
60487
 
59564
60488
  // src/timeline/renderer.ts
60489
+ init_tag_groups();
59565
60490
  var d3Scale3 = __toESM(require("d3-scale"), 1);
59566
60491
  var d3Selection3 = __toESM(require("d3-selection"), 1);
59567
60492
  init_fonts();
@@ -59826,7 +60751,7 @@ function setupTimeline(container, parsed, palette, isDark, exportDims, activeTag
59826
60751
  });
59827
60752
  let tagLanes = null;
59828
60753
  if (resolvedSwimlaneTG) {
59829
- const tagKey = resolvedSwimlaneTG.toLowerCase();
60754
+ const tagKey = tagAttrKey(resolvedSwimlaneTG);
59830
60755
  const tagGroup = parsed.timelineTagGroups.find(
59831
60756
  (g) => g.name.toLowerCase() === tagKey
59832
60757
  );
@@ -60132,7 +61057,7 @@ function renderTimelineTagLegendOverlay(container, parsed, palette, isDark, setu
60132
61057
  onTagStateChange?.(currentActiveGroup, currentSwimlaneGroup);
60133
61058
  },
60134
61059
  onEntryHover: (groupName, entryValue) => {
60135
- const tagKey = groupName.toLowerCase();
61060
+ const tagKey = tagAttrKey(groupName);
60136
61061
  if (entryValue) {
60137
61062
  const tagVal = entryValue.toLowerCase();
60138
61063
  fadeToTagValue(mainG, tagKey, tagVal);
@@ -60151,7 +61076,7 @@ function renderTimelineTagLegendOverlay(container, parsed, palette, isDark, setu
60151
61076
  }
60152
61077
  },
60153
61078
  onGroupRendered: (groupName, groupEl, isActive) => {
60154
- const groupKey2 = groupName.toLowerCase();
61079
+ const groupKey2 = tagAttrKey(groupName);
60155
61080
  groupEl.attr("data-tag-group", groupKey2);
60156
61081
  if (isActive && !viewMode) {
60157
61082
  const isSwimActive = currentSwimlaneGroup?.toLowerCase() === groupKey2;
@@ -64016,6 +64941,71 @@ function completeMapPlaces(query, gazetteer, opts) {
64016
64941
  });
64017
64942
  return [...cityItems, ...airportItems].slice(0, limit);
64018
64943
  }
64944
+ function searchMapLocations(query, gazetteer, opts) {
64945
+ const q = fold2(query);
64946
+ if (!q) return [];
64947
+ const limit = opts?.limit ?? 20;
64948
+ const rankOf = (hay) => hay === q ? 0 : hay.startsWith(q) ? 1 : hay.includes(q) ? 2 : -1;
64949
+ const cityRank = /* @__PURE__ */ new Map();
64950
+ const consider = (idx, rank) => {
64951
+ if (rank < 0) return;
64952
+ const prev = cityRank.get(idx);
64953
+ if (prev === void 0 || rank < prev) cityRank.set(idx, rank);
64954
+ };
64955
+ for (const [key, list] of Object.entries(gazetteer.byName)) {
64956
+ const r = rankOf(key);
64957
+ if (r >= 0) for (const i of list) consider(i, r);
64958
+ }
64959
+ for (const [key, idx] of Object.entries(gazetteer.alt)) {
64960
+ consider(idx, rankOf(key));
64961
+ }
64962
+ const cityItems = [...cityRank].filter(([i]) => gazetteer.cities[i] !== void 0).map(([i, rank]) => {
64963
+ const c = gazetteer.cities[i];
64964
+ const [, , iso, pop, name, sub] = c;
64965
+ const ambiguous = (gazetteer.byName[fold2(name)]?.length ?? 0) > 1;
64966
+ const qualifier = sub ?? iso;
64967
+ return {
64968
+ name,
64969
+ token: ambiguous ? `${name} ${qualifier}` : name,
64970
+ kind: "city",
64971
+ iso,
64972
+ ...sub !== void 0 && { sub },
64973
+ pop,
64974
+ detail: `${qualifier} \xB7 ${groupThousands(pop)}`,
64975
+ _rank: rank
64976
+ };
64977
+ });
64978
+ const airports = opts?.airports;
64979
+ const airportItems = [];
64980
+ if (airports?.airportIata) {
64981
+ for (const [code, idx] of Object.entries(airports.airportIata)) {
64982
+ const a = airports.airports[idx];
64983
+ if (!a) continue;
64984
+ const codeRank = rankOf(code);
64985
+ const nameRank = rankOf(fold2(a[4]));
64986
+ const rank = Math.min(
64987
+ codeRank < 0 ? 99 : codeRank,
64988
+ nameRank < 0 ? 99 : nameRank
64989
+ );
64990
+ if (rank > 2) continue;
64991
+ airportItems.push({
64992
+ name: a[4],
64993
+ token: code.toUpperCase(),
64994
+ kind: "airport",
64995
+ iso: a[2],
64996
+ pop: 0,
64997
+ detail: `Airport \xB7 ${a[4]}`,
64998
+ _rank: rank
64999
+ });
65000
+ }
65001
+ }
65002
+ return [...cityItems, ...airportItems].sort(
65003
+ (x, y) => x._rank - y._rank || // best match tier first
65004
+ (x.kind === y.kind ? 0 : x.kind === "city" ? -1 : 1) || // city before airport
65005
+ y.pop - x.pop || // populous city first
65006
+ (x.token < y.token ? -1 : x.token > y.token ? 1 : 0)
65007
+ ).slice(0, limit).map(({ _rank, ...rest }) => rest);
65008
+ }
64019
65009
  function completeMapRegions(query, regions, opts) {
64020
65010
  const q = fold2(query);
64021
65011
  if (!q) return [];
@@ -65234,6 +66224,7 @@ function formatLineDiff(path, original, migrated) {
65234
66224
  resolveTaskName,
65235
66225
  rollUpContextRelationships,
65236
66226
  sampleBetaPert,
66227
+ searchMapLocations,
65237
66228
  seriesColors,
65238
66229
  shade,
65239
66230
  shapeFill,