@diagrammo/dgmo 0.33.0 → 0.35.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 (59) hide show
  1. package/.claude/commands/dgmo.md +17 -32
  2. package/.github/copilot-instructions.md +1 -1
  3. package/README.md +7 -1
  4. package/SKILL.md +1 -1
  5. package/dist/advanced.cjs +791 -614
  6. package/dist/advanced.d.cts +8 -51
  7. package/dist/advanced.d.ts +8 -51
  8. package/dist/advanced.js +791 -615
  9. package/dist/auto.cjs +793 -292
  10. package/dist/auto.js +95 -95
  11. package/dist/auto.mjs +793 -292
  12. package/dist/cli.cjs +145 -328
  13. package/dist/index.cjs +792 -291
  14. package/dist/index.js +792 -291
  15. package/docs/ai-integration.md +46 -26
  16. package/docs/language-reference.md +2 -2
  17. package/package.json +3 -3
  18. package/src/advanced.ts +0 -12
  19. package/src/boxes-and-lines/label-placement.ts +339 -0
  20. package/src/boxes-and-lines/layout-search.ts +16 -2
  21. package/src/boxes-and-lines/layout.ts +32 -7
  22. package/src/boxes-and-lines/renderer.ts +67 -139
  23. package/src/c4/parser.ts +15 -6
  24. package/src/chart.ts +1 -1
  25. package/src/class/parser.ts +6 -2
  26. package/src/cli-banner.ts +2 -3
  27. package/src/cli.ts +539 -1223
  28. package/src/cycle/parser.ts +4 -1
  29. package/src/dgmo-router.ts +16 -2
  30. package/src/diagnostics.ts +0 -0
  31. package/src/echarts.ts +5 -1
  32. package/src/er/parser.ts +5 -1
  33. package/src/gantt/parser.ts +13 -4
  34. package/src/graph/flowchart-parser.ts +5 -1
  35. package/src/graph/state-parser.ts +5 -1
  36. package/src/infra/parser.ts +10 -6
  37. package/src/journey-map/parser.ts +5 -2
  38. package/src/kanban/parser.ts +6 -2
  39. package/src/map/context-labels.ts +29 -19
  40. package/src/map/layout.ts +403 -108
  41. package/src/map/legend-band.ts +28 -0
  42. package/src/map/parser.ts +13 -4
  43. package/src/map/renderer.ts +74 -23
  44. package/src/map/resolver.ts +2 -2
  45. package/src/org/parser.ts +8 -2
  46. package/src/pyramid/parser.ts +4 -1
  47. package/src/raci/parser.ts +4 -1
  48. package/src/ring/parser.ts +4 -1
  49. package/src/sequence/parser.ts +6 -3
  50. package/src/sitemap/parser.ts +12 -3
  51. package/src/tech-radar/parser.ts +6 -3
  52. package/src/utils/parsing.ts +2 -2
  53. package/src/utils/tag-groups.ts +3 -3
  54. package/src/visualizations/parse.ts +9 -3
  55. package/src/migrate/data-comma.ts +0 -117
  56. package/src/migrate/embedded.ts +0 -238
  57. package/src/migrate/index.ts +0 -295
  58. package/src/migrate/line-classifier.ts +0 -128
  59. package/src/migrate/line-transformer.ts +0 -218
package/dist/advanced.cjs CHANGED
@@ -37,6 +37,17 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
37
37
  function makeDgmoError(line11, message, severity = "error", code) {
38
38
  return code !== void 0 ? { line: line11, message, severity, code } : { line: line11, message, severity };
39
39
  }
40
+ function dedupeDiagnostics(diagnostics) {
41
+ const seen = /* @__PURE__ */ new Set();
42
+ const out = [];
43
+ for (const d of diagnostics) {
44
+ const key = `${d.line}\0${d.column ?? ""}\0${d.severity}\0${d.code ?? ""}\0${d.message}`;
45
+ if (seen.has(key)) continue;
46
+ seen.add(key);
47
+ out.push(d);
48
+ }
49
+ return out;
50
+ }
40
51
  function formatDgmoError(err) {
41
52
  return err.line > 0 ? `Line ${err.line}: ${err.message}` : err.message;
42
53
  }
@@ -94,7 +105,7 @@ function vennAliasKeywordRemovedMessage(args) {
94
105
  return `Venn 'alias' keyword was removed. Use 'as' instead \u2014 '${args.name} as ${args.alias}'.`;
95
106
  }
96
107
  function pipeOperatorRemovedMessage() {
97
- return `'|' removed \u2014 'Node | c: red' \u2192 'Node c: red'. Run 'dgmo migrate'`;
108
+ return `'|' removed \u2014 'Node | c: red' \u2192 'Node c: red'`;
98
109
  }
99
110
  function ganttBarePercentRemovedMessage(percent) {
100
111
  return `Bare-percent progress shorthand '| ${percent}' was removed. Use 'progress: ${percent.replace(/%$/, "")}' (integer 0\u2013100, no '%').`;
@@ -1247,7 +1258,7 @@ var init_parsing = __esm({
1247
1258
  "no-title",
1248
1259
  "no-notes"
1249
1260
  ]);
1250
- MULTIPLE_PIPE_ERROR = 'Use a single "|" to start metadata, then separate items with commas.';
1261
+ MULTIPLE_PIPE_ERROR = "'|' is no longer metadata syntax \u2014 put metadata on the same line as 'key: value, key2: value2' (no pipes), per \xA71.4.";
1251
1262
  RESERVED_KEY_RE = /^[A-Za-z][A-Za-z0-9_-]*$/;
1252
1263
  AS_ALIAS_RE = /\s+as\s+([A-Za-z][A-Za-z0-9_]{0,11})$/;
1253
1264
  POTENTIAL_META_RE = /\b([\w-]+)\s*:/;
@@ -1428,12 +1439,10 @@ function validateTagValues(entities, tagGroups, pushWarning, suggestFn) {
1428
1439
  );
1429
1440
  if (!isPrefix) {
1430
1441
  const defined = group.entries.map((e) => e.value);
1431
- let msg = `Unknown value '${value}' for tag group '${group.name}'`;
1442
+ let msg = `Unknown value '${value}' for tag group '${group.name}' \u2014 use one of: ${defined.join(", ")}`;
1432
1443
  const hint = suggestFn?.(value, defined);
1433
1444
  if (hint) {
1434
1445
  msg += `. ${hint}`;
1435
- } else {
1436
- msg += ` \u2014 defined values: ${defined.join(", ")}`;
1437
1446
  }
1438
1447
  pushWarning(entity.lineNumber, msg);
1439
1448
  }
@@ -2209,7 +2218,10 @@ function parseVisualizationFull(content, palette) {
2209
2218
  lineNumber
2210
2219
  });
2211
2220
  } else {
2212
- warn(lineNumber, `Unrecognized era entry: "${line11}"`);
2221
+ warn(
2222
+ lineNumber,
2223
+ `Unrecognized era entry: "${line11}" \u2014 use '<start> -> <end> Label [color]' (e.g. '1716 -> 1717 Reign').`
2224
+ );
2213
2225
  }
2214
2226
  continue;
2215
2227
  }
@@ -2237,7 +2249,10 @@ function parseVisualizationFull(content, palette) {
2237
2249
  lineNumber
2238
2250
  });
2239
2251
  } else {
2240
- warn(lineNumber, `Unrecognized marker entry: "${line11}"`);
2252
+ warn(
2253
+ lineNumber,
2254
+ `Unrecognized marker entry: "${line11}" \u2014 use '<date> Label [color]' (e.g. '1776 Independence').`
2255
+ );
2241
2256
  }
2242
2257
  continue;
2243
2258
  }
@@ -2915,7 +2930,7 @@ function parseVisualizationFull(content, palette) {
2915
2930
  result.diagnostics.push(
2916
2931
  makeDgmoError(
2917
2932
  ov.lineNumber,
2918
- `Intersection references unknown set or alias "${ref}"`
2933
+ `Intersection references unknown set or alias "${ref}" \u2014 declare the set first on its own line ('Name [as <alias>] [color]', e.g. 'Navigation as nav blue'), then reference it by that name or alias. (\xA716.4)`
2919
2934
  )
2920
2935
  );
2921
2936
  if (!result.error)
@@ -6298,10 +6313,10 @@ function parseSequenceDgmo(content, palette) {
6298
6313
  const rawInside = fallbackMatch[1];
6299
6314
  const pipeIdx = rawInside.indexOf("|");
6300
6315
  const cleanName = rawInside.substring(0, pipeIdx).trim().replace(/\([^)]*\)$/, "").trim();
6301
- const metaPart = rawInside.substring(pipeIdx).trim();
6316
+ const metaPart = rawInside.substring(pipeIdx).replace(/^\|\s*/, "").trim();
6302
6317
  pushError(
6303
6318
  lineNumber,
6304
- `Pipe metadata must go outside brackets \u2014 use '[${cleanName}] ${metaPart}' instead of '[${rawInside.trim()}]'`
6319
+ `Metadata goes after the bracket with no pipe \u2014 use '[${cleanName}] ${metaPart}' (e.g. '[Backend] t: Product'), not '[${rawInside.trim()}]'`
6305
6320
  );
6306
6321
  continue;
6307
6322
  }
@@ -6911,7 +6926,7 @@ function parseSequenceDgmo(content, palette) {
6911
6926
  }
6912
6927
  pushWarning(
6913
6928
  lineNumber,
6914
- `Unexpected line: '${trimmed}'. Expected a message (A -> B), participant, section (== Name ==), or block keyword (if/loop/parallel).`
6929
+ `Unexpected line: '${trimmed}'. Expected a message ('A -> B' or 'A -label-> B'), participant ('Name' or 'Name is a actor'), note ('note right of A Text'), section ('== Name =='), or block ('if/loop/parallel <label>').`
6915
6930
  );
6916
6931
  }
6917
6932
  if (!hasExplicitChart && result.messages.length === 0) {
@@ -8073,7 +8088,11 @@ function parseFlowchart(content, palette) {
8073
8088
  const value = optMatch[2].trim();
8074
8089
  if (key === "no-color") {
8075
8090
  result.diagnostics.push(
8076
- makeDgmoError(lineNumber, '"no-color" has been removed.', "warning")
8091
+ makeDgmoError(
8092
+ lineNumber,
8093
+ '"no-color" was removed \u2014 node colors are assigned automatically by shape; delete this line.',
8094
+ "warning"
8095
+ )
8077
8096
  );
8078
8097
  continue;
8079
8098
  }
@@ -8402,7 +8421,11 @@ function parseState(content, palette) {
8402
8421
  const value = optMatch[2].trim();
8403
8422
  if (key === "no-color") {
8404
8423
  result.diagnostics.push(
8405
- makeDgmoError(lineNumber, '"no-color" has been removed.', "warning")
8424
+ makeDgmoError(
8425
+ lineNumber,
8426
+ '"no-color" was removed \u2014 state colors are assigned automatically; delete this line.',
8427
+ "warning"
8428
+ )
8406
8429
  );
8407
8430
  continue;
8408
8431
  }
@@ -8717,7 +8740,7 @@ function parseClassDiagram(content, palette) {
8717
8740
  result.diagnostics.push(
8718
8741
  makeDgmoError(
8719
8742
  lineNumber,
8720
- '"no-auto-color" has been removed.',
8743
+ '"no-auto-color" was removed \u2014 colors are always auto-assigned from the palette; delete this line.',
8721
8744
  "warning"
8722
8745
  )
8723
8746
  );
@@ -8832,7 +8855,11 @@ function parseClassDiagram(content, palette) {
8832
8855
  continue;
8833
8856
  }
8834
8857
  result.diagnostics.push(
8835
- makeDgmoError(lineNumber, `Unexpected line: '${trimmed}'.`, "warning")
8858
+ makeDgmoError(
8859
+ lineNumber,
8860
+ `Unexpected line: '${trimmed}'. Classes start uppercase at indent 0 ('Ship'); members are indented and colon-typed ('+ name: string', '+ sail(): void'); relationships are indented arrows ('--|> Vessel'). (\xA710)`,
8861
+ "warning"
8862
+ )
8836
8863
  );
8837
8864
  }
8838
8865
  if (notes.length > 0) {
@@ -9310,7 +9337,11 @@ function parseERDiagram(content, palette) {
9310
9337
  continue;
9311
9338
  }
9312
9339
  result.diagnostics.push(
9313
- makeDgmoError(lineNumber, `Unexpected line: '${trimmed}'.`, "warning")
9340
+ makeDgmoError(
9341
+ lineNumber,
9342
+ `Unexpected line: '${trimmed}'. A table is a bare name at indent 0 ('users'); columns are indented and SPACE-separated, no colon ('id int pk'); relationships are indented ('1--* orders'). (\xA79)`,
9343
+ "warning"
9344
+ )
9314
9345
  );
9315
9346
  }
9316
9347
  if (result.tables.length === 0 && !result.error) {
@@ -9654,7 +9685,10 @@ function parseOrg(content, palette) {
9654
9685
  const value = metadataMatch[2].trim();
9655
9686
  const parent = findMetadataParent(indent, indentStack);
9656
9687
  if (!parent) {
9657
- pushError(lineNumber, "Metadata has no parent node");
9688
+ pushError(
9689
+ lineNumber,
9690
+ "Metadata 'key: value' must be indented under a node \u2014 no node precedes it."
9691
+ );
9658
9692
  } else {
9659
9693
  parent.metadata = { ...parent.metadata, [key]: value };
9660
9694
  }
@@ -9672,7 +9706,10 @@ function parseOrg(content, palette) {
9672
9706
  );
9673
9707
  attachNode(node, indent, indentStack, result);
9674
9708
  } else {
9675
- pushError(lineNumber, "Metadata has no parent node");
9709
+ pushError(
9710
+ lineNumber,
9711
+ "Metadata 'key: value' must be indented under a node \u2014 no node precedes it."
9712
+ );
9676
9713
  }
9677
9714
  } else {
9678
9715
  const node = parseNodeLabel(
@@ -10021,7 +10058,10 @@ function parseSitemap(content, palette) {
10021
10058
  if (arrowInfo) {
10022
10059
  const source = findParentNode(indent, indentStack);
10023
10060
  if (!source) {
10024
- pushError(lineNumber, "Arrow has no source node");
10061
+ pushError(
10062
+ lineNumber,
10063
+ "Arrow '-> target' must be indented under its source page \u2014 no page precedes it."
10064
+ );
10025
10065
  } else {
10026
10066
  deferredArrows.push({
10027
10067
  sourceNode: source,
@@ -10091,7 +10131,10 @@ function parseSitemap(content, palette) {
10091
10131
  const value = metadataMatch[2].trim();
10092
10132
  const parent = findParentNode(indent, indentStack);
10093
10133
  if (!parent) {
10094
- pushError(lineNumber, "Metadata has no parent node");
10134
+ pushError(
10135
+ lineNumber,
10136
+ "Metadata 'key: value' must be indented under a page \u2014 no page precedes it."
10137
+ );
10095
10138
  } else {
10096
10139
  parent.metadata = { ...parent.metadata, [key]: value };
10097
10140
  }
@@ -10111,7 +10154,10 @@ function parseSitemap(content, palette) {
10111
10154
  const key = normalizeName(node.label);
10112
10155
  labelToNode.set(key, node);
10113
10156
  } else {
10114
- pushError(lineNumber, "Metadata has no parent node");
10157
+ pushError(
10158
+ lineNumber,
10159
+ "Metadata 'key: value' must be indented under a page \u2014 no page precedes it."
10160
+ );
10115
10161
  }
10116
10162
  } else {
10117
10163
  const descResult = tryStripDescriptionKeyword(trimmed);
@@ -11929,7 +11975,7 @@ function parseChart(content, palette) {
11929
11975
  result.diagnostics.push(
11930
11976
  makeDgmoError(
11931
11977
  lineNumber,
11932
- `'${trimmed}' \u2014 ## syntax is no longer supported. Use [Group] containers instead`
11978
+ `'${trimmed}' removed \u2014 use a bracket container '[${trimmed.replace(/^#+\s*/, "")}]' instead (## headers are no longer supported).`
11933
11979
  )
11934
11980
  );
11935
11981
  continue;
@@ -12899,7 +12945,11 @@ function parseExtendedChartFull(content, palette) {
12899
12945
  continue;
12900
12946
  }
12901
12947
  result.diagnostics.push(
12902
- makeDgmoError(lineNumber, `Unexpected line: '${trimmed}'.`, "warning")
12948
+ makeDgmoError(
12949
+ lineNumber,
12950
+ `Unexpected line: '${trimmed}'. Expected a data row ('Label value', space-separated) or a known option.`,
12951
+ "warning"
12952
+ )
12903
12953
  );
12904
12954
  }
12905
12955
  const warn = (line11, message) => {
@@ -15261,7 +15311,10 @@ function parseKanban(content, palette) {
15261
15311
  continue;
15262
15312
  }
15263
15313
  if (!currentColumn) {
15264
- warn(lineNumber, "Card line found before any column");
15314
+ warn(
15315
+ lineNumber,
15316
+ "Card line found before any column. Declare a column first with '[Column Name]', then indent cards beneath it. (\xA711)"
15317
+ );
15265
15318
  continue;
15266
15319
  }
15267
15320
  if (currentCard && indent > cardBaseIndent) {
@@ -15400,7 +15453,7 @@ var init_parser13 = __esm({
15400
15453
  KNOWN_OPTIONS4 = /* @__PURE__ */ new Set(["hide", "active-tag"]);
15401
15454
  KNOWN_BOOLEANS3 = /* @__PURE__ */ new Set(["solid-fill", "no-title"]);
15402
15455
  REMOVED_BOOLEANS = {
15403
- "no-auto-color": '"no-auto-color" has been removed.'
15456
+ "no-auto-color": '"no-auto-color" was removed \u2014 colors are always auto-assigned from the palette; delete this line.'
15404
15457
  };
15405
15458
  }
15406
15459
  });
@@ -15637,7 +15690,7 @@ function parseC4(content, palette) {
15637
15690
  } else {
15638
15691
  pushError(
15639
15692
  lineNumber,
15640
- `"container ${refName}" must be inside a deployment node`
15693
+ `"container ${refName}" must be indented under a deployment node (e.g. under 'Prod is a deployment-node')`
15641
15694
  );
15642
15695
  }
15643
15696
  continue;
@@ -15692,7 +15745,10 @@ function parseC4(content, palette) {
15692
15745
  indent
15693
15746
  });
15694
15747
  } else {
15695
- pushError(lineNumber, `"${sectionType}" must be inside an element`);
15748
+ pushError(
15749
+ lineNumber,
15750
+ `"${sectionType}" must be indented under an element (e.g. under 'Web App is a container')`
15751
+ );
15696
15752
  }
15697
15753
  continue;
15698
15754
  }
@@ -15719,7 +15775,10 @@ function parseC4(content, palette) {
15719
15775
  indent
15720
15776
  });
15721
15777
  } else {
15722
- pushError(lineNumber, `Group [${groupName}] must be inside an element`);
15778
+ pushError(
15779
+ lineNumber,
15780
+ `Group [${groupName}] must be indented under an element (e.g. under 'Web App is a container')`
15781
+ );
15723
15782
  }
15724
15783
  continue;
15725
15784
  }
@@ -16049,7 +16108,10 @@ function parseC4(content, palette) {
16049
16108
  }
16050
16109
  desc.push(descResult.text);
16051
16110
  } else {
16052
- pushError(lineNumber, `Unexpected content: "${trimmed}"`);
16111
+ pushError(
16112
+ lineNumber,
16113
+ `Unexpected content: "${trimmed}". Expected an element ('Web App is a container'), a relationship ('-Uses-> Database'), indented metadata ('tech: React'), or a group ('[Backend]'). (\xA78)`
16114
+ );
16053
16115
  }
16054
16116
  }
16055
16117
  finalizeAutoTagColors(result.tagGroups, palette);
@@ -16111,7 +16173,7 @@ function validateRelationshipTargets(result, knownNames, pushWarning) {
16111
16173
  for (const el of elements) {
16112
16174
  for (const rel of el.relationships) {
16113
16175
  if (!knownNames.has(normalizeName(rel.target))) {
16114
- let msg = `Relationship target "${rel.target}" not found`;
16176
+ let msg = `Relationship target "${rel.target}" not found \u2014 declare it first (e.g. '${rel.target} is a container'), then reference it in the arrow`;
16115
16177
  const hint = suggest(normalizeName(rel.target), allNames);
16116
16178
  if (hint) msg += `. ${hint}`;
16117
16179
  pushWarning(rel.lineNumber, msg, "warning");
@@ -16139,7 +16201,7 @@ function validateDeploymentRefs(result, knownNames, pushWarning) {
16139
16201
  for (const node of nodes) {
16140
16202
  for (const ref of node.containerRefs) {
16141
16203
  if (!knownNames.has(normalizeName(ref))) {
16142
- let msg = `Deployment reference "container ${ref}" not found`;
16204
+ let msg = `Deployment reference "container ${ref}" not found \u2014 declare the container element first, then reference it under the deployment node`;
16143
16205
  const hint = suggest(normalizeName(ref), allNames);
16144
16206
  if (hint) msg += `. ${hint}`;
16145
16207
  pushWarning(node.lineNumber, msg, "warning");
@@ -16858,7 +16920,7 @@ function parseInfra(content) {
16858
16920
  }
16859
16921
  warn(
16860
16922
  lineNumber,
16861
- `Invalid tag value '${trimmed}' in tag group '${currentTagGroup.name}'.`
16923
+ `Invalid tag value '${trimmed}' in tag group '${currentTagGroup.name}' \u2014 a value is a bare word with an optional trailing color (e.g. 'Production red'); no ':' or '|'.`
16862
16924
  );
16863
16925
  continue;
16864
16926
  }
@@ -16931,7 +16993,7 @@ function parseInfra(content) {
16931
16993
  const n = deprecatedFanout[1];
16932
16994
  setError(
16933
16995
  lineNumber,
16934
- `'x${n}' fanout syntax is no longer supported \u2014 use '| fanout: ${n}' instead`
16996
+ `'x${n}' fanout syntax is no longer supported \u2014 use 'fanout: ${n}' instead`
16935
16997
  );
16936
16998
  continue;
16937
16999
  }
@@ -17105,8 +17167,10 @@ function parseInfra(content) {
17105
17167
  const hint = suggest(key, allKeys);
17106
17168
  const valueLooksNumeric = /^[\d.]+%?$/.test(rawVal);
17107
17169
  if (hint || valueLooksNumeric) {
17108
- let msg = `Unknown property '${key}'.`;
17170
+ let msg = `Unknown infra property '${key}'.`;
17109
17171
  if (hint) msg += ` ${hint}`;
17172
+ else
17173
+ msg += ` Properties are colon-keyed 'key: value' from the known schema (e.g. latency-ms, max-rps, cache-hit, instances, concurrency).`;
17110
17174
  warn(lineNumber, msg);
17111
17175
  } else if (!currentNode.isEdge) {
17112
17176
  pushDescription(currentNode, trimmed);
@@ -17117,7 +17181,7 @@ function parseInfra(content) {
17117
17181
  if (EDGE_ONLY_KEYS.has(key) && !currentNode.isEdge) {
17118
17182
  warn(
17119
17183
  lineNumber,
17120
- `Property '${key}' is only valid on the entry point (Edge/Internet).`
17184
+ `Property '${key}' belongs on the entry point \u2014 declare it indented under an 'internet' or 'edge' node, not on '${currentNode.label}'.`
17121
17185
  );
17122
17186
  }
17123
17187
  const value = parsePropertyValue(rawVal);
@@ -17224,7 +17288,7 @@ function parseInfra(content) {
17224
17288
  }
17225
17289
  warn(
17226
17290
  lineNumber,
17227
- currentGroup ? `Unexpected line: '${trimmed}'. Expected an indented component name.` : `Unexpected line: '${trimmed}'. Expected a component name, [Group Name], tag group, or option.`
17291
+ currentGroup ? `Unexpected line: '${trimmed}'. Inside a group, indent a component name under the '[Group]'.` : `Unexpected line: '${trimmed}'. Expected a node ('Api Gateway'), a group ('[Backend]'), a tag group ('tag Tier as t'), or an option ('default-rps 100'); node properties go indented under a node as 'key: value'.`
17228
17292
  );
17229
17293
  }
17230
17294
  finishCurrentNode();
@@ -17600,7 +17664,10 @@ function parseGantt(content, palette) {
17600
17664
  continue;
17601
17665
  }
17602
17666
  if (COMMENT_RE.test(line11)) continue;
17603
- warn(lineNumber, `Unrecognized holiday entry: "${line11}"`);
17667
+ warn(
17668
+ lineNumber,
17669
+ `Unrecognized holiday entry: "${line11}". Use a date '2024-02-19 Presidents Day' or a range '2024-05-27 -> 2024-05-29 Memorial Weekend'. (\xA713.3)`
17670
+ );
17604
17671
  continue;
17605
17672
  }
17606
17673
  }
@@ -17626,7 +17693,10 @@ function parseGantt(content, palette) {
17626
17693
  lineNumber
17627
17694
  });
17628
17695
  } else {
17629
- warn(lineNumber, `Unrecognized era entry: "${line11}"`);
17696
+ warn(
17697
+ lineNumber,
17698
+ `Unrecognized era entry: "${line11}". Use 'YYYY-MM-DD -> YYYY-MM-DD Label [color]' (e.g. '2026-04-06 -> 2026-04-10 Conference purple'). (\xA713.5)`
17699
+ );
17630
17700
  }
17631
17701
  continue;
17632
17702
  }
@@ -17652,7 +17722,10 @@ function parseGantt(content, palette) {
17652
17722
  lineNumber
17653
17723
  });
17654
17724
  } else {
17655
- warn(lineNumber, `Unrecognized marker entry: "${line11}"`);
17725
+ warn(
17726
+ lineNumber,
17727
+ `Unrecognized marker entry: "${line11}". Use 'YYYY-MM-DD Label [color]' (e.g. '2026-03-27 Board Review' or '2026-06-15 Release green'). (\xA713.6)`
17728
+ );
17656
17729
  }
17657
17730
  continue;
17658
17731
  }
@@ -17818,7 +17891,7 @@ function parseGantt(content, palette) {
17818
17891
  } else {
17819
17892
  softError(
17820
17893
  lineNumber,
17821
- `Arrow reference "${content2.name}" has no parent task context.`
17894
+ `Arrow '-> ${content2.name}' has no source task \u2014 indent it under the task it follows (e.g. 'Design 5bd' then indented '-> ${content2.name} 3bd'). (\xA713)`
17822
17895
  );
17823
17896
  }
17824
17897
  }
@@ -19038,7 +19111,10 @@ function parseMap(content, palette) {
19038
19111
  function handleTag(trimmed, line11) {
19039
19112
  const m = matchTagBlockHeading(trimmed);
19040
19113
  if (!m) {
19041
- pushError(line11, `Malformed tag declaration: "${trimmed}".`);
19114
+ pushError(
19115
+ line11,
19116
+ `Malformed tag declaration: "${trimmed}" \u2014 write 'tag <Group>' (optionally 'tag <Group> as <alias>'), e.g. 'tag Market as m', with indented 'Value color' entries below.`
19117
+ );
19042
19118
  return;
19043
19119
  }
19044
19120
  emitTagLegacyDiagnostic(m, line11, diagnostics);
@@ -19150,7 +19226,7 @@ function parseMap(content, palette) {
19150
19226
  if (!pos || pos.kind === "name" && !pos.name) {
19151
19227
  pushError(
19152
19228
  line11,
19153
- "route requires an origin: `route <origin> [style: arc]`."
19229
+ "route needs an origin place \u2014 `route <origin>` then indented legs (`-> dest`); origin may be a city name, IATA code, or `<lat> <lon>`. (\xA724B)"
19154
19230
  );
19155
19231
  return;
19156
19232
  }
@@ -19213,7 +19289,10 @@ function parseMap(content, palette) {
19213
19289
  function handleEdges(trimmed, line11) {
19214
19290
  const parts = trimmed.split(ARROW_SPLIT);
19215
19291
  if (parts.length < 3) {
19216
- pushError(line11, `Malformed edge: "${trimmed}".`);
19292
+ pushError(
19293
+ line11,
19294
+ `Malformed edge: "${trimmed}" \u2014 an edge is 'A -> B' (or 'A -label-> B', '~>' for arc, '--' undirected), both endpoints non-empty. (\xA724B)`
19295
+ );
19217
19296
  return;
19218
19297
  }
19219
19298
  const endpoints = [];
@@ -19236,7 +19315,10 @@ function parseMap(content, palette) {
19236
19315
  const from = endpoints[k];
19237
19316
  const to = endpoints[k + 1];
19238
19317
  if (!from || !to) {
19239
- pushError(line11, `Edge has an empty endpoint: "${trimmed}".`);
19318
+ pushError(
19319
+ line11,
19320
+ `Edge has an empty endpoint: "${trimmed}" \u2014 both sides of the arrow need a place, e.g. 'Paris -> Berlin'. (\xA724B)`
19321
+ );
19240
19322
  continue;
19241
19323
  }
19242
19324
  const isLast = k === links.length - 1;
@@ -21355,7 +21437,10 @@ function parseTechRadar(content) {
21355
21437
  }
21356
21438
  if (indent === 0 && trimmed.toLowerCase() === "rings") {
21357
21439
  if (result.rings.length > 0) {
21358
- warn(lineNumber, 'Duplicate "rings" block \u2014 using last one');
21440
+ warn(
21441
+ lineNumber,
21442
+ 'Duplicate "rings" block \u2014 only the last is kept; merge all ring names into one "rings" block.'
21443
+ );
21359
21444
  result.rings = [];
21360
21445
  }
21361
21446
  inRingsBlock = true;
@@ -21574,7 +21659,7 @@ function parseTechRadar(content) {
21574
21659
  result.diagnostics.push(
21575
21660
  makeDgmoError(
21576
21661
  lineNumber,
21577
- `Blip "${trimmed}" has no ring assignment. Place it under a ring section header or use: ${trimmed} | ring: RingName`
21662
+ `Blip "${trimmed}" has no ring. Put it under a ring section header, or add a colon-keyed ring \u2014 '${trimmed} ring: RingName' (per \xA720).`
21578
21663
  )
21579
21664
  );
21580
21665
  continue;
@@ -21583,7 +21668,7 @@ function parseTechRadar(content) {
21583
21668
  if (!trimmed.includes("|") && headerParsed && result.rings.length > 0) {
21584
21669
  warn(
21585
21670
  lineNumber,
21586
- `Unrecognized line "${trimmed}". Quadrant headers require pipe metadata: ${trimmed} | quadrant: top-left`
21671
+ `Unrecognized line "${trimmed}". A quadrant header is 'Name quadrant: <top-left|top-right|bottom-left|bottom-right>' (per \xA720).`
21587
21672
  );
21588
21673
  }
21589
21674
  }
@@ -21876,7 +21961,10 @@ function parseCycle(content) {
21876
21961
  currentNode.description.push(descLine);
21877
21962
  continue;
21878
21963
  }
21879
- warn(lineNum, `Unexpected indented line: "${trimmed}".`);
21964
+ warn(
21965
+ lineNum,
21966
+ `Indented line "${trimmed}" has no step above it \u2014 add a step on a non-indented line first, then indent its details. (\xA721)`
21967
+ );
21880
21968
  continue;
21881
21969
  }
21882
21970
  }
@@ -22179,7 +22267,7 @@ function parseJourneyMap(content, palette) {
22179
22267
  if (trimmed.includes("|") || !PHASE_RE.test(trimmed)) {
22180
22268
  warn(
22181
22269
  lineNumber,
22182
- "Steps outside phases will be ignored when phases are present"
22270
+ `Step "${trimmed}" sits outside any phase and is ignored \u2014 indent it under a '[Phase]' header (per \xA722).`
22183
22271
  );
22184
22272
  continue;
22185
22273
  }
@@ -22236,7 +22324,10 @@ function parseJourneyMap(content, palette) {
22236
22324
  continue;
22237
22325
  }
22238
22326
  if (indent > 0 && !currentPhase && hasPhases) {
22239
- warn(lineNumber, `Unexpected indented line outside of a phase`);
22327
+ warn(
22328
+ lineNumber,
22329
+ `Indented line outside any phase \u2014 add a '[Phase]' header above it, or unindent it to a top-level step. (\xA722)`
22330
+ );
22240
22331
  }
22241
22332
  }
22242
22333
  if (currentStep) {
@@ -22582,7 +22673,10 @@ function parsePyramid(content) {
22582
22673
  continue;
22583
22674
  }
22584
22675
  if (!currentLayer) {
22585
- warn(lineNum, `Unexpected indented line: "${trimmed}".`);
22676
+ warn(
22677
+ lineNum,
22678
+ `Indented description "${trimmed}" has no layer above it \u2014 add a layer name on a non-indented line first. (\xA723)`
22679
+ );
22586
22680
  continue;
22587
22681
  }
22588
22682
  const descLine = trimmed.startsWith("- ") ? `\u2022 ${trimmed.substring(2)}` : trimmed;
@@ -22756,7 +22850,10 @@ function parseRing(content) {
22756
22850
  continue;
22757
22851
  }
22758
22852
  if (!currentLayer) {
22759
- warn(lineNum, `Unexpected indented line: "${trimmed}".`);
22853
+ warn(
22854
+ lineNum,
22855
+ `Indented description "${trimmed}" has no layer above it \u2014 add a layer name on a non-indented line first. (\xA724)`
22856
+ );
22760
22857
  continue;
22761
22858
  }
22762
22859
  const descLine = trimmed.startsWith("- ") ? `\u2022 ${trimmed.substring(2)}` : trimmed;
@@ -23482,7 +23579,10 @@ function parseRaci(content, palette) {
23482
23579
  continue;
23483
23580
  }
23484
23581
  if (bodyStarted) {
23485
- errorAt(lineNumber, `Unexpected line: '${trimmed}'.`);
23582
+ errorAt(
23583
+ lineNumber,
23584
+ `Unexpected line: '${trimmed}'. In the body expect a '[Phase]' header, an indented task name, or an assignment 'Role: A R C I' under a task. (\xA724A)`
23585
+ );
23486
23586
  } else {
23487
23587
  warn(lineNumber, `Unrecognized directive: '${trimmed}'.`);
23488
23588
  }
@@ -24029,6 +24129,13 @@ function getAllChartTypes() {
24029
24129
  return chartTypes.map((c) => c.id);
24030
24130
  }
24031
24131
  function parseDgmo(content) {
24132
+ const result = parseDgmoUndeduped(content);
24133
+ return {
24134
+ diagnostics: dedupeDiagnostics(result.diagnostics),
24135
+ chartType: result.chartType
24136
+ };
24137
+ }
24138
+ function parseDgmoUndeduped(content) {
24032
24139
  const chartType = parseDgmoChartType(content);
24033
24140
  if (!chartType) {
24034
24141
  const colonDiag = detectColonChartType(content);
@@ -29183,63 +29290,6 @@ function ensureArrowMarkers(defs, colors) {
29183
29290
  ).attr("fill", color);
29184
29291
  }
29185
29292
  }
29186
- function edgePolylineMidpoint(points) {
29187
- if (points.length === 0) return { x: 0, y: 0 };
29188
- if (points.length === 1) return { x: points[0].x, y: points[0].y };
29189
- let total = 0;
29190
- const segLen = [];
29191
- for (let k = 1; k < points.length; k++) {
29192
- const len = Math.hypot(
29193
- points[k].x - points[k - 1].x,
29194
- points[k].y - points[k - 1].y
29195
- );
29196
- segLen.push(len);
29197
- total += len;
29198
- }
29199
- let half = total / 2;
29200
- for (let k = 1; k < points.length; k++) {
29201
- const len = segLen[k - 1];
29202
- if (half <= len || k === points.length - 1) {
29203
- const t = len === 0 ? 0 : Math.min(1, half / len);
29204
- return {
29205
- x: points[k - 1].x + (points[k].x - points[k - 1].x) * t,
29206
- y: points[k - 1].y + (points[k].y - points[k - 1].y) * t
29207
- };
29208
- }
29209
- half -= len;
29210
- }
29211
- const last = points[points.length - 1];
29212
- return { x: last.x, y: last.y };
29213
- }
29214
- function resolveEdgeLabelOverlaps(labels) {
29215
- const MAX_PASSES = 8;
29216
- const PAD = 4;
29217
- for (let pass = 0; pass < MAX_PASSES; pass++) {
29218
- let moved = false;
29219
- for (let i = 0; i < labels.length; i++) {
29220
- for (let j = i + 1; j < labels.length; j++) {
29221
- const a = labels[i];
29222
- const b = labels[j];
29223
- const dx = Math.abs(a.x - b.x);
29224
- const dy = Math.abs(a.y - b.y);
29225
- const overlapX = (a.width + b.width) / 2 + PAD - dx;
29226
- const overlapY = (a.height + b.height) / 2 + PAD - dy;
29227
- if (overlapX > 0 && overlapY > 0) {
29228
- const shift = overlapY / 2 + 1;
29229
- if (a.y < b.y) {
29230
- a.y -= shift;
29231
- b.y += shift;
29232
- } else {
29233
- a.y += shift;
29234
- b.y -= shift;
29235
- }
29236
- moved = true;
29237
- }
29238
- }
29239
- }
29240
- if (!moved) break;
29241
- }
29242
- }
29243
29293
  function renderBoxesAndLines(container, parsed, layout, palette, isDark, options) {
29244
29294
  const {
29245
29295
  onClickItem,
@@ -29446,8 +29496,6 @@ function renderBoxesAndLines(container, parsed, layout, palette, isDark, options
29446
29496
  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);
29447
29497
  }
29448
29498
  }
29449
- const labelPositions = [];
29450
- const edgeGroups = /* @__PURE__ */ new Map();
29451
29499
  for (let i = 0; i < layout.edges.length; i++) {
29452
29500
  const le = layout.edges[i];
29453
29501
  const color = edgeColorMap.get(i) ?? palette.textMuted;
@@ -29466,7 +29514,6 @@ function renderBoxesAndLines(container, parsed, layout, palette, isDark, options
29466
29514
  const nodeLayout = layoutNodeMap.get(le.source);
29467
29515
  if (nodeLayout) {
29468
29516
  const edgeG2 = diagramG.append("g").attr("class", "bl-edge-group").attr("data-line-number", String(le.lineNumber));
29469
- edgeGroups.set(i, edgeG2);
29470
29517
  const markerId2 = `bl-arrow-${color.replace("#", "")}`;
29471
29518
  const cx = nodeLayout.x;
29472
29519
  const cy = nodeLayout.y;
@@ -29515,7 +29562,6 @@ function renderBoxesAndLines(container, parsed, layout, palette, isDark, options
29515
29562
  }
29516
29563
  if (points.length < 2) continue;
29517
29564
  const edgeG = diagramG.append("g").attr("class", "bl-edge-group").attr("data-line-number", String(le.lineNumber));
29518
- edgeGroups.set(i, edgeG);
29519
29565
  const markerId = `bl-arrow-${color.replace("#", "")}`;
29520
29566
  const gen = le.straight ? lineGeneratorStraight : parsed.direction === "TB" ? lineGeneratorTB : lineGeneratorLR;
29521
29567
  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})`);
@@ -29523,31 +29569,6 @@ function renderBoxesAndLines(container, parsed, layout, palette, isDark, options
29523
29569
  const revId = `bl-arrow-rev-${color.replace("#", "")}`;
29524
29570
  path.attr("marker-start", `url(#${revId})`);
29525
29571
  }
29526
- if (le.label && le.points.length > 0) {
29527
- const lw = measureText(le.label, sEdgeLabelFontSize);
29528
- const labelH = sEdgeLabelFontSize + 6;
29529
- const mid = edgePolylineMidpoint(le.points);
29530
- let ly = mid.y;
29531
- if (le.parallelCount > 1 && le.yOffset !== 0) {
29532
- ly += (le.yOffset < 0 ? -labelH : labelH) * 0.5;
29533
- }
29534
- labelPositions.push({
29535
- x: mid.x,
29536
- y: ly,
29537
- width: lw + 8,
29538
- height: labelH,
29539
- idx: i
29540
- });
29541
- }
29542
- }
29543
- resolveEdgeLabelOverlaps(labelPositions);
29544
- for (const lp of labelPositions) {
29545
- const le = layout.edges[lp.idx];
29546
- if (!le.label) continue;
29547
- const edgeG = edgeGroups.get(lp.idx);
29548
- const target = edgeG ?? diagramG;
29549
- 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);
29550
- 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);
29551
29572
  }
29552
29573
  for (const ln of layout.nodes) {
29553
29574
  const node = nodeMap.get(ln.label);
@@ -29598,8 +29619,8 @@ function renderBoxesAndLines(container, parsed, layout, palette, isDark, options
29598
29619
  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);
29599
29620
  const desc = node.description;
29600
29621
  if (desc && desc.length > 0 && !hideDescriptions) {
29601
- const MAX_LABEL_LINES3 = 3;
29602
- const fitted = fitLabelToHeader(node.label, ln.width, MAX_LABEL_LINES3);
29622
+ const MAX_LABEL_LINES4 = 3;
29623
+ const fitted = fitLabelToHeader(node.label, ln.width, MAX_LABEL_LINES4);
29603
29624
  const labelLines = fitted.lines;
29604
29625
  const labelLineH = fitted.fontSize * 1.3;
29605
29626
  const labelTotalH = labelLines.length * labelLineH;
@@ -29733,6 +29754,35 @@ function renderBoxesAndLines(container, parsed, layout, palette, isDark, options
29733
29754
  }
29734
29755
  }
29735
29756
  }
29757
+ const labelLayer = diagramG.append("g").attr("class", "bl-edge-labels");
29758
+ const labelLineHeight = sEdgeLabelFontSize * 1.3;
29759
+ for (const le of layout.edges) {
29760
+ if (!le.labelLines || le.labelLines.length === 0) continue;
29761
+ if (le.labelX === void 0 || le.labelY === void 0) continue;
29762
+ const lx = le.labelX;
29763
+ const ly = le.labelY;
29764
+ if (hidden.size > 0) {
29765
+ let isHidden = false;
29766
+ for (const [groupKey2, hiddenVals] of hidden) {
29767
+ const val = le.metadata[groupKey2];
29768
+ if (val && hiddenVals.has(val.toLowerCase())) {
29769
+ isHidden = true;
29770
+ break;
29771
+ }
29772
+ }
29773
+ if (isHidden) continue;
29774
+ }
29775
+ const lw = le.labelWidth ?? 0;
29776
+ const lh = le.labelHeight ?? sEdgeLabelFontSize + 6;
29777
+ const lines = le.labelLines;
29778
+ const labelG = labelLayer.append("g").attr("class", "bl-edge-label").attr("data-line-number", String(le.lineNumber));
29779
+ 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);
29780
+ 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);
29781
+ const firstY = ly - (lines.length - 1) / 2 * labelLineHeight + sEdgeLabelFontSize / 3;
29782
+ lines.forEach((line11, k) => {
29783
+ text.append("tspan").attr("x", lx).attr("y", firstY + k * labelLineHeight).text(line11);
29784
+ });
29785
+ }
29736
29786
  const hasDescriptions = parsed.nodes.some(
29737
29787
  (n) => n.description && n.description.length > 0
29738
29788
  );
@@ -29849,6 +29899,222 @@ var init_renderer6 = __esm({
29849
29899
  }
29850
29900
  });
29851
29901
 
29902
+ // src/boxes-and-lines/label-placement.ts
29903
+ function measureEdgeLabel(label, fontSize = EDGE_LABEL_FONT_SIZE6, maxWidth = LABEL_MAX_WIDTH) {
29904
+ let lines = wrapTextToWidth(label, fontSize, maxWidth, { hardBreak: true });
29905
+ if (lines.length > MAX_LABEL_LINES) {
29906
+ const kept = lines.slice(0, MAX_LABEL_LINES - 1);
29907
+ const rest = lines.slice(MAX_LABEL_LINES - 1).join(" ");
29908
+ kept.push(truncateText(rest, fontSize, maxWidth));
29909
+ lines = kept;
29910
+ }
29911
+ let textW = 0;
29912
+ for (const ln of lines) {
29913
+ const w = measureText(ln, fontSize);
29914
+ if (w > textW) textW = w;
29915
+ }
29916
+ return {
29917
+ lines,
29918
+ width: textW + 2 * H_PAD,
29919
+ height: lines.length * fontSize * LABEL_LINE_HEIGHT + 2 * V_PAD
29920
+ };
29921
+ }
29922
+ function edgePolylineMidpoint(points) {
29923
+ return pointAtArcFraction(points, 0.5).p;
29924
+ }
29925
+ function pointAtArcFraction(points, t) {
29926
+ if (points.length === 0) return { p: { x: 0, y: 0 }, nx: 0, ny: 1 };
29927
+ if (points.length === 1)
29928
+ return { p: { x: points[0].x, y: points[0].y }, nx: 0, ny: 1 };
29929
+ const segLen = [];
29930
+ let total = 0;
29931
+ for (let k = 1; k < points.length; k++) {
29932
+ const len = Math.hypot(
29933
+ points[k].x - points[k - 1].x,
29934
+ points[k].y - points[k - 1].y
29935
+ );
29936
+ segLen.push(len);
29937
+ total += len;
29938
+ }
29939
+ let want = total * Math.max(0, Math.min(1, t));
29940
+ for (let k = 1; k < points.length; k++) {
29941
+ const len = segLen[k - 1];
29942
+ if (want <= len || k === points.length - 1) {
29943
+ const f = len === 0 ? 0 : Math.min(1, want / len);
29944
+ const ax = points[k - 1].x;
29945
+ const ay = points[k - 1].y;
29946
+ const bx = points[k].x;
29947
+ const by = points[k].y;
29948
+ const dx = bx - ax;
29949
+ const dy = by - ay;
29950
+ const dlen = Math.hypot(dx, dy) || 1;
29951
+ return {
29952
+ p: { x: ax + dx * f, y: ay + dy * f },
29953
+ nx: -dy / dlen,
29954
+ ny: dx / dlen
29955
+ };
29956
+ }
29957
+ want -= len;
29958
+ }
29959
+ const last = points[points.length - 1];
29960
+ return { p: { x: last.x, y: last.y }, nx: 0, ny: 1 };
29961
+ }
29962
+ function rectFromCenter(cx, cy, w, h) {
29963
+ return {
29964
+ minX: cx - w / 2,
29965
+ minY: cy - h / 2,
29966
+ maxX: cx + w / 2,
29967
+ maxY: cy + h / 2
29968
+ };
29969
+ }
29970
+ function overlapsAny(cx, cy, w, h, obstacles, pad2) {
29971
+ const minX = cx - w / 2 - pad2;
29972
+ const minY = cy - h / 2 - pad2;
29973
+ const maxX = cx + w / 2 + pad2;
29974
+ const maxY = cy + h / 2 + pad2;
29975
+ for (const o of obstacles) {
29976
+ if (minX < o.maxX && maxX > o.minX && minY < o.maxY && maxY > o.minY)
29977
+ return true;
29978
+ }
29979
+ return false;
29980
+ }
29981
+ function slideFractions() {
29982
+ const out = [0.5];
29983
+ for (let k = 1; k <= SLIDE_SAMPLES; k++) {
29984
+ const off = k * 0.08;
29985
+ if (0.5 - off >= 0.12) out.push(0.5 - off);
29986
+ if (0.5 + off <= 0.88) out.push(0.5 + off);
29987
+ }
29988
+ return out;
29989
+ }
29990
+ function findClearPosition(w, h, points, obstacles) {
29991
+ const ts = slideFractions();
29992
+ for (const t of ts) {
29993
+ const { p } = pointAtArcFraction(points, t);
29994
+ if (!overlapsAny(p.x, p.y, w, h, obstacles, BOX_CLEAR_PAD)) return p;
29995
+ }
29996
+ for (let mag = PERP_STEP; mag <= PERP_MAX; mag += PERP_STEP) {
29997
+ for (const t of ts) {
29998
+ const { p, nx, ny } = pointAtArcFraction(points, t);
29999
+ for (const sign of [-1, 1]) {
30000
+ const x = p.x + nx * mag * sign;
30001
+ const y = p.y + ny * mag * sign;
30002
+ if (!overlapsAny(x, y, w, h, obstacles, BOX_CLEAR_PAD)) return { x, y };
30003
+ }
30004
+ }
30005
+ }
30006
+ return null;
30007
+ }
30008
+ function resolveLabelOverlaps(boxes) {
30009
+ const MAX_PASSES = 8;
30010
+ const PAD = 4;
30011
+ for (let pass = 0; pass < MAX_PASSES; pass++) {
30012
+ let moved = false;
30013
+ for (let i = 0; i < boxes.length; i++) {
30014
+ for (let j = i + 1; j < boxes.length; j++) {
30015
+ const a = boxes[i];
30016
+ const b = boxes[j];
30017
+ const dx = Math.abs(a.cx - b.cx);
30018
+ const dy = Math.abs(a.cy - b.cy);
30019
+ const overlapX = (a.w + b.w) / 2 + PAD - dx;
30020
+ const overlapY = (a.h + b.h) / 2 + PAD - dy;
30021
+ if (overlapX > 0 && overlapY > 0) {
30022
+ const shift = overlapY / 2 + 1;
30023
+ if (a.cy < b.cy) {
30024
+ a.cy -= shift;
30025
+ b.cy += shift;
30026
+ } else {
30027
+ a.cy += shift;
30028
+ b.cy -= shift;
30029
+ }
30030
+ moved = true;
30031
+ }
30032
+ }
30033
+ }
30034
+ if (!moved) break;
30035
+ }
30036
+ }
30037
+ function placeEdgeLabels(layout, opts) {
30038
+ const fontSize = opts?.fontSize ?? EDGE_LABEL_FONT_SIZE6;
30039
+ const maxWidth = opts?.maxWidth ?? LABEL_MAX_WIDTH;
30040
+ const obstacles = [];
30041
+ for (const n of layout.nodes)
30042
+ obstacles.push(rectFromCenter(n.x, n.y, n.width, n.height));
30043
+ for (const g of layout.groups)
30044
+ if (g.collapsed)
30045
+ obstacles.push(rectFromCenter(g.x, g.y, g.width, g.height));
30046
+ const boxes = [];
30047
+ layout.edges.forEach((e, idx) => {
30048
+ if (!e.label || e.points.length < 2) return;
30049
+ const m = measureEdgeLabel(e.label, fontSize, maxWidth);
30050
+ const mid = edgePolylineMidpoint(e.points);
30051
+ let cy = mid.y;
30052
+ if (e.parallelCount > 1 && e.yOffset !== 0)
30053
+ cy += (e.yOffset < 0 ? -1 : 1) * (m.height / 2);
30054
+ boxes.push({
30055
+ edgeIdx: idx,
30056
+ cx: mid.x,
30057
+ cy,
30058
+ w: m.width,
30059
+ h: m.height,
30060
+ lines: m.lines,
30061
+ resolved: true
30062
+ });
30063
+ });
30064
+ for (const box of boxes) {
30065
+ if (!overlapsAny(box.cx, box.cy, box.w, box.h, obstacles, BOX_CLEAR_PAD))
30066
+ continue;
30067
+ const e = layout.edges[box.edgeIdx];
30068
+ const clear = findClearPosition(box.w, box.h, e.points, obstacles);
30069
+ if (clear) {
30070
+ box.cx = clear.x;
30071
+ box.cy = clear.y;
30072
+ } else {
30073
+ box.resolved = false;
30074
+ }
30075
+ }
30076
+ resolveLabelOverlaps(boxes);
30077
+ for (const box of boxes) {
30078
+ if (overlapsAny(box.cx, box.cy, box.w, box.h, obstacles, BOX_CLEAR_PAD))
30079
+ box.resolved = false;
30080
+ }
30081
+ const boxByIdx = /* @__PURE__ */ new Map();
30082
+ for (const b of boxes) boxByIdx.set(b.edgeIdx, b);
30083
+ const edges = layout.edges.map((e, idx) => {
30084
+ const b = boxByIdx.get(idx);
30085
+ if (!b) return e;
30086
+ return {
30087
+ ...e,
30088
+ labelX: b.cx,
30089
+ labelY: b.cy,
30090
+ labelWidth: b.w,
30091
+ labelHeight: b.h,
30092
+ labelLines: b.lines
30093
+ };
30094
+ });
30095
+ return {
30096
+ layout: { ...layout, edges },
30097
+ unresolved: boxes.filter((b) => !b.resolved).map((b) => b.edgeIdx)
30098
+ };
30099
+ }
30100
+ 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;
30101
+ var init_label_placement = __esm({
30102
+ "src/boxes-and-lines/label-placement.ts"() {
30103
+ "use strict";
30104
+ init_text_measure();
30105
+ EDGE_LABEL_FONT_SIZE6 = 11;
30106
+ LABEL_MAX_WIDTH = 160;
30107
+ MAX_LABEL_LINES = 3;
30108
+ LABEL_LINE_HEIGHT = 1.3;
30109
+ H_PAD = 6;
30110
+ V_PAD = 3;
30111
+ BOX_CLEAR_PAD = 4;
30112
+ PERP_STEP = 8;
30113
+ PERP_MAX = 40;
30114
+ SLIDE_SAMPLES = 9;
30115
+ }
30116
+ });
30117
+
29852
30118
  // src/boxes-and-lines/layout-layered.ts
29853
30119
  function rng(s) {
29854
30120
  return () => {
@@ -31636,6 +31902,7 @@ function edgeLength(layout) {
31636
31902
  }
31637
31903
  async function layoutBoxesAndLinesSearch(parsed, collapseInfo, opts) {
31638
31904
  const hideDescriptions = opts?.hideDescriptions ?? false;
31905
+ const reserveEdgeLabels = opts?.reserveEdgeLabels ?? false;
31639
31906
  const onProgress = opts?.onProgress;
31640
31907
  const tick = onProgress ? () => new Promise((r) => setTimeout(r)) : () => void 0;
31641
31908
  const collapsedGroupLabels = /* @__PURE__ */ new Set();
@@ -31928,8 +32195,14 @@ async function layoutBoxesAndLinesSearch(parsed, collapseInfo, opts) {
31928
32195
  for (const e of ord(parsed.edges)) {
31929
32196
  const s = srcRep(e.source);
31930
32197
  const t = tgtRep(e.target);
31931
- if (s !== t && g.hasNode(s) && g.hasNode(t))
31932
- g.setEdge(s, t, {}, edgeKey(e));
32198
+ if (s !== t && g.hasNode(s) && g.hasNode(t)) {
32199
+ let edgeLabel = {};
32200
+ if (reserveEdgeLabels && e.label) {
32201
+ const m = measureEdgeLabel(e.label, EDGE_LABEL_FONT_SIZE6);
32202
+ edgeLabel = { width: m.width, height: m.height, labelpos: "c" };
32203
+ }
32204
+ g.setEdge(s, t, edgeLabel, edgeKey(e));
32205
+ }
31933
32206
  }
31934
32207
  import_dagre4.default.layout(g);
31935
32208
  const nodes = parsed.nodes.map((n2) => {
@@ -32160,6 +32433,7 @@ var init_layout_search = __esm({
32160
32433
  init_layout5();
32161
32434
  init_layout_layered();
32162
32435
  init_layout_grouped();
32436
+ init_label_placement();
32163
32437
  DEFAULT_LAMBDA = 4;
32164
32438
  ESCALATE_THRESHOLD = 4;
32165
32439
  ESCALATE_MAX_N = 45;
@@ -32218,9 +32492,9 @@ function estimateLabelLines(label, nodeWidth = NODE_WIDTH) {
32218
32492
  current = word;
32219
32493
  }
32220
32494
  }
32221
- if (lines <= MAX_LABEL_LINES) return Math.min(lines, MAX_LABEL_LINES);
32495
+ if (lines <= MAX_LABEL_LINES2) return Math.min(lines, MAX_LABEL_LINES2);
32222
32496
  }
32223
- return MAX_LABEL_LINES;
32497
+ return MAX_LABEL_LINES2;
32224
32498
  }
32225
32499
  function computeNodeSize(node, reserveValueRow) {
32226
32500
  if (!node.description || node.description.length === 0) {
@@ -32228,7 +32502,7 @@ function computeNodeSize(node, reserveValueRow) {
32228
32502
  }
32229
32503
  const w = DESC_NODE_WIDTH;
32230
32504
  const labelLines = estimateLabelLines(node.label, w);
32231
- const labelHeight = labelLines * 13 * LABEL_LINE_HEIGHT + LABEL_PAD;
32505
+ const labelHeight = labelLines * 13 * LABEL_LINE_HEIGHT2 + LABEL_PAD;
32232
32506
  const maxTextWidth = w - 24;
32233
32507
  let totalRenderedLines = 0;
32234
32508
  for (const line11 of node.description) {
@@ -32252,7 +32526,7 @@ function computeNodeSize(node, reserveValueRow) {
32252
32526
  }
32253
32527
  async function layoutBoxesAndLines(parsed, collapseInfo, layoutOptions) {
32254
32528
  const { layoutBoxesAndLinesSearch: layoutBoxesAndLinesSearch2 } = await Promise.resolve().then(() => (init_layout_search(), layout_search_exports));
32255
- const searched = await layoutBoxesAndLinesSearch2(parsed, collapseInfo, {
32529
+ const searchOpts = {
32256
32530
  ...layoutOptions?.hideDescriptions !== void 0 && {
32257
32531
  hideDescriptions: layoutOptions.hideDescriptions
32258
32532
  },
@@ -32262,12 +32536,23 @@ async function layoutBoxesAndLines(parsed, collapseInfo, layoutOptions) {
32262
32536
  ...layoutOptions?.onProgress !== void 0 && {
32263
32537
  onProgress: layoutOptions.onProgress
32264
32538
  }
32265
- });
32266
- return attachNotes(
32267
- applyParallelEdgeOffsets(searched),
32539
+ };
32540
+ const searched = await layoutBoxesAndLinesSearch2(
32268
32541
  parsed,
32269
- layoutOptions?.collapsedNotes
32542
+ collapseInfo,
32543
+ searchOpts
32270
32544
  );
32545
+ let placed = placeEdgeLabels(applyParallelEdgeOffsets(searched));
32546
+ if (placed.unresolved.length > 0) {
32547
+ const relaid = await layoutBoxesAndLinesSearch2(parsed, collapseInfo, {
32548
+ ...searchOpts,
32549
+ reserveEdgeLabels: true
32550
+ });
32551
+ const relaidPlaced = placeEdgeLabels(applyParallelEdgeOffsets(relaid));
32552
+ if (relaidPlaced.unresolved.length < placed.unresolved.length)
32553
+ placed = relaidPlaced;
32554
+ }
32555
+ return attachNotes(placed.layout, parsed, layoutOptions?.collapsedNotes);
32271
32556
  }
32272
32557
  function attachNotes(layout, parsed, collapsedNotes) {
32273
32558
  const notesSuppressed = parsed.options?.["no-notes"] === "on";
@@ -32335,6 +32620,7 @@ function attachNotes(layout, parsed, collapsedNotes) {
32335
32620
  points: e.points.map((pt) => ({ x: pt.x + shiftX, y: pt.y + shiftY })),
32336
32621
  ...e.labelX !== void 0 && { labelX: e.labelX + shiftX },
32337
32622
  ...e.labelY !== void 0 && { labelY: e.labelY + shiftY }
32623
+ // labelWidth/labelHeight/labelLines are shift-invariant — carried via spread.
32338
32624
  })) : layout.edges;
32339
32625
  const finalGroups = shifted ? layout.groups.map((grp) => ({
32340
32626
  ...grp,
@@ -32379,11 +32665,12 @@ function applyParallelEdgeOffsets(layout) {
32379
32665
  }))
32380
32666
  };
32381
32667
  }
32382
- var MARGIN5, MAX_PARALLEL_EDGES, PARALLEL_SPACING, PHI, NODE_HEIGHT, NODE_WIDTH, DESC_NODE_WIDTH, DESC_FONT_SIZE2, DESC_LINE_HEIGHT2, DESC_PADDING, SEPARATOR_GAP4, MAX_DESC_LINES2, MAX_LABEL_LINES, LABEL_LINE_HEIGHT, LABEL_PAD, VALUE_ROW_FONT, VALUE_ROW_H;
32668
+ 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;
32383
32669
  var init_layout5 = __esm({
32384
32670
  "src/boxes-and-lines/layout.ts"() {
32385
32671
  "use strict";
32386
32672
  init_text_measure();
32673
+ init_label_placement();
32387
32674
  init_notes();
32388
32675
  MARGIN5 = 40;
32389
32676
  MAX_PARALLEL_EDGES = 5;
@@ -32397,8 +32684,8 @@ var init_layout5 = __esm({
32397
32684
  DESC_PADDING = 8;
32398
32685
  SEPARATOR_GAP4 = 4;
32399
32686
  MAX_DESC_LINES2 = 6;
32400
- MAX_LABEL_LINES = 3;
32401
- LABEL_LINE_HEIGHT = 1.3;
32687
+ MAX_LABEL_LINES2 = 3;
32688
+ LABEL_LINE_HEIGHT2 = 1.3;
32402
32689
  LABEL_PAD = 12;
32403
32690
  VALUE_ROW_FONT = 11;
32404
32691
  VALUE_ROW_H = SEPARATOR_GAP4 + VALUE_ROW_FONT * DESC_LINE_HEIGHT2 + DESC_PADDING;
@@ -32417,7 +32704,7 @@ function tokenize(text) {
32417
32704
  return tokens;
32418
32705
  }
32419
32706
  function tryWrap(tokens, maxWidth, fontSize, maxLines) {
32420
- const availWidth = maxWidth - H_PAD;
32707
+ const availWidth = maxWidth - H_PAD2;
32421
32708
  const lines = [];
32422
32709
  let currentLine = "";
32423
32710
  for (const token of tokens) {
@@ -32442,7 +32729,7 @@ function tryWrap(tokens, maxWidth, fontSize, maxLines) {
32442
32729
  return lines;
32443
32730
  }
32444
32731
  function truncateLastLine(lines, maxWidth, fontSize) {
32445
- const availWidth = maxWidth - H_PAD;
32732
+ const availWidth = maxWidth - H_PAD2;
32446
32733
  const result = [...lines];
32447
32734
  const last = result[result.length - 1];
32448
32735
  if (measureText(last, fontSize) > availWidth) {
@@ -32450,7 +32737,7 @@ function truncateLastLine(lines, maxWidth, fontSize) {
32450
32737
  }
32451
32738
  return result;
32452
32739
  }
32453
- function wrapText(text, maxWidth, baseFontSize, minFontSize, maxLines = MAX_LABEL_LINES2) {
32740
+ function wrapText(text, maxWidth, baseFontSize, minFontSize, maxLines = MAX_LABEL_LINES3) {
32454
32741
  if (!text) return { lines: [""], fontSize: baseFontSize };
32455
32742
  const tokens = tokenize(text);
32456
32743
  for (let fs = baseFontSize; fs >= minFontSize; fs--) {
@@ -32465,7 +32752,7 @@ function wrapText(text, maxWidth, baseFontSize, minFontSize, maxLines = MAX_LABE
32465
32752
  if (allLines.length > maxLines) {
32466
32753
  const last = truncated[truncated.length - 1];
32467
32754
  if (!last.endsWith("\u2026")) {
32468
- const availWidth = maxWidth - H_PAD;
32755
+ const availWidth = maxWidth - H_PAD2;
32469
32756
  if (measureText(last + "\u2026", minFontSize) > availWidth) {
32470
32757
  truncated[truncated.length - 1] = truncateText(
32471
32758
  last,
@@ -32492,7 +32779,7 @@ function computeNodeText(label, description, depth, nodeWidth, hideDescriptions)
32492
32779
  nodeWidth,
32493
32780
  baseFontSize,
32494
32781
  MIN_FONT_SIZE,
32495
- MAX_LABEL_LINES2
32782
+ MAX_LABEL_LINES3
32496
32783
  );
32497
32784
  const descLines = [];
32498
32785
  let descFontSize = DESC_FONT_SIZE3;
@@ -32520,14 +32807,14 @@ function computeNodeText(label, description, depth, nodeWidth, hideDescriptions)
32520
32807
  descFontSize
32521
32808
  };
32522
32809
  }
32523
- var H_PAD, MAX_LABEL_LINES2, MAX_DESC_LINES3, ROOT_FONT_SIZE, MIN_FONT_SIZE, FONT_STEP, DESC_FONT_SIZE3;
32810
+ var H_PAD2, MAX_LABEL_LINES3, MAX_DESC_LINES3, ROOT_FONT_SIZE, MIN_FONT_SIZE, FONT_STEP, DESC_FONT_SIZE3;
32524
32811
  var init_text_wrap = __esm({
32525
32812
  "src/mindmap/text-wrap.ts"() {
32526
32813
  "use strict";
32527
32814
  init_description_helpers();
32528
32815
  init_text_measure();
32529
- H_PAD = 16;
32530
- MAX_LABEL_LINES2 = 3;
32816
+ H_PAD2 = 16;
32817
+ MAX_LABEL_LINES3 = 3;
32531
32818
  MAX_DESC_LINES3 = 2;
32532
32819
  ROOT_FONT_SIZE = 17;
32533
32820
  MIN_FONT_SIZE = 9;
@@ -32914,7 +33201,7 @@ function nodeHeightS(node, hideDescriptions, sc) {
32914
33201
  hideDescriptions
32915
33202
  );
32916
33203
  const labelLineCount = text.labelLines.length;
32917
- const labelH = labelLineCount <= 1 ? SINGLE_LABEL_HEIGHT : LABEL_LINE_HEIGHT2 * labelLineCount;
33204
+ const labelH = labelLineCount <= 1 ? SINGLE_LABEL_HEIGHT : LABEL_LINE_HEIGHT3 * labelLineCount;
32918
33205
  let h = labelH + NODE_V_PAD;
32919
33206
  if (text.descLines.length > 0) {
32920
33207
  h += DESC_LINE_HEIGHT3 * text.descLines.length + 4;
@@ -32934,7 +33221,7 @@ function populateDepthCache(roots) {
32934
33221
  };
32935
33222
  walk(roots, 0);
32936
33223
  }
32937
- var ROOT_WIDTH, DEPTH1_WIDTH, LEAF_WIDTH, SINGLE_LABEL_HEIGHT, LABEL_LINE_HEIGHT2, DESC_LINE_HEIGHT3, NODE_V_PAD, H_GAP2, V_GAP2, MARGIN6, MULTI_ROOT_GAP, nodeDepthCache;
33224
+ 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;
32938
33225
  var init_layout6 = __esm({
32939
33226
  "src/mindmap/layout.ts"() {
32940
33227
  "use strict";
@@ -32945,7 +33232,7 @@ var init_layout6 = __esm({
32945
33232
  DEPTH1_WIDTH = 150;
32946
33233
  LEAF_WIDTH = 120;
32947
33234
  SINGLE_LABEL_HEIGHT = 28;
32948
- LABEL_LINE_HEIGHT2 = 18;
33235
+ LABEL_LINE_HEIGHT3 = 18;
32949
33236
  DESC_LINE_HEIGHT3 = 14;
32950
33237
  NODE_V_PAD = 10;
32951
33238
  H_GAP2 = 40;
@@ -33236,18 +33523,18 @@ function renderMindmap(container, parsed, layout, palette, isDark, onClickItem,
33236
33523
  descFontSize
33237
33524
  } = textLayout;
33238
33525
  const labelLineCount = labelLines.length;
33239
- const labelZoneH = labelLineCount <= 1 ? SINGLE_LABEL_HEIGHT2 : LABEL_LINE_HEIGHT3 * labelLineCount;
33526
+ const labelZoneH = labelLineCount <= 1 ? SINGLE_LABEL_HEIGHT2 : LABEL_LINE_HEIGHT4 * labelLineCount;
33240
33527
  const labelZoneHeight = showDesc ? labelZoneH : node.height;
33241
33528
  const centerX = node.x + node.width / 2;
33242
33529
  if (labelLineCount <= 1) {
33243
33530
  const labelY = node.y + labelZoneHeight / 2 + fontSize * 0.35;
33244
33531
  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]);
33245
33532
  } else {
33246
- const blockH = LABEL_LINE_HEIGHT3 * (labelLineCount - 1);
33533
+ const blockH = LABEL_LINE_HEIGHT4 * (labelLineCount - 1);
33247
33534
  const firstBaselineY = node.y + labelZoneHeight / 2 - blockH / 2 + fontSize * 0.35;
33248
33535
  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);
33249
33536
  for (let i = 0; i < labelLines.length; i++) {
33250
- textEl.append("tspan").attr("x", centerX).attr("y", firstBaselineY + i * LABEL_LINE_HEIGHT3).text(labelLines[i]);
33537
+ textEl.append("tspan").attr("x", centerX).attr("y", firstBaselineY + i * LABEL_LINE_HEIGHT4).text(labelLines[i]);
33251
33538
  }
33252
33539
  }
33253
33540
  if (labelLines.length > 1 || labelLines[0] !== node.label) {
@@ -33322,7 +33609,7 @@ function renderMindmapForExport(content, theme, palette) {
33322
33609
  return extractExportSvg(container, theme);
33323
33610
  });
33324
33611
  }
33325
- var d3Selection12, DIAGRAM_PADDING7, TITLE_HEIGHT4, SINGLE_LABEL_HEIGHT2, LABEL_LINE_HEIGHT3, DESC_LINE_HEIGHT4, NODE_RADIUS, ROOT_STROKE_WIDTH, DEPTH_COLOR_KEYS;
33612
+ var d3Selection12, DIAGRAM_PADDING7, TITLE_HEIGHT4, SINGLE_LABEL_HEIGHT2, LABEL_LINE_HEIGHT4, DESC_LINE_HEIGHT4, NODE_RADIUS, ROOT_STROKE_WIDTH, DEPTH_COLOR_KEYS;
33326
33613
  var init_renderer7 = __esm({
33327
33614
  "src/mindmap/renderer.ts"() {
33328
33615
  "use strict";
@@ -33345,7 +33632,7 @@ var init_renderer7 = __esm({
33345
33632
  DIAGRAM_PADDING7 = 20;
33346
33633
  TITLE_HEIGHT4 = 30;
33347
33634
  SINGLE_LABEL_HEIGHT2 = 28;
33348
- LABEL_LINE_HEIGHT3 = 18;
33635
+ LABEL_LINE_HEIGHT4 = 18;
33349
33636
  DESC_LINE_HEIGHT4 = 14;
33350
33637
  NODE_RADIUS = 6;
33351
33638
  ROOT_STROKE_WIDTH = 2.5;
@@ -36012,7 +36299,7 @@ function renderC4Context(container, parsed, layout, palette, isDark, onClickItem
36012
36299
  const labelText = edge.label ?? "";
36013
36300
  const techText = edge.technology ? `[${edge.technology}]` : "";
36014
36301
  const textW = Math.max(
36015
- measureText(labelText, EDGE_LABEL_FONT_SIZE6),
36302
+ measureText(labelText, EDGE_LABEL_FONT_SIZE7),
36016
36303
  measureText(techText, TECH_FONT_SIZE)
36017
36304
  );
36018
36305
  const bgW = textW + 12;
@@ -36023,7 +36310,7 @@ function renderC4Context(container, parsed, layout, palette, isDark, onClickItem
36023
36310
  textY = midPt.y - 4;
36024
36311
  }
36025
36312
  if (labelText) {
36026
- 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);
36313
+ 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);
36027
36314
  }
36028
36315
  if (techText) {
36029
36316
  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);
@@ -36207,7 +36494,7 @@ function renderEdges(contentG, edges, palette, onClickItem, obstacleRects) {
36207
36494
  const labelText = edge.label ?? "";
36208
36495
  const techText = edge.technology ? `[${edge.technology}]` : "";
36209
36496
  const textW = Math.max(
36210
- measureText(labelText, EDGE_LABEL_FONT_SIZE6),
36497
+ measureText(labelText, EDGE_LABEL_FONT_SIZE7),
36211
36498
  measureText(techText, TECH_FONT_SIZE)
36212
36499
  );
36213
36500
  const bgW = textW + 12;
@@ -36225,7 +36512,7 @@ function renderEdges(contentG, edges, palette, onClickItem, obstacleRects) {
36225
36512
  });
36226
36513
  }
36227
36514
  }
36228
- placeEdgeLabels(pendingLabels, edges, obstacleRects);
36515
+ placeEdgeLabels2(pendingLabels, edges, obstacleRects);
36229
36516
  for (const lbl of pendingLabels) {
36230
36517
  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");
36231
36518
  let textY = lbl.y;
@@ -36233,7 +36520,7 @@ function renderEdges(contentG, edges, palette, onClickItem, obstacleRects) {
36233
36520
  textY = lbl.y - 4;
36234
36521
  }
36235
36522
  if (lbl.labelText) {
36236
- 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);
36523
+ 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);
36237
36524
  }
36238
36525
  if (lbl.techText) {
36239
36526
  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);
@@ -36314,7 +36601,7 @@ function tangentAt(points, t) {
36314
36601
  }
36315
36602
  return { x: 0, y: 1 };
36316
36603
  }
36317
- function placeEdgeLabels(labels, edges, obstacleRects) {
36604
+ function placeEdgeLabels2(labels, edges, obstacleRects) {
36318
36605
  if (labels.length === 0) return;
36319
36606
  const allPaths = edges.map((e) => e.points);
36320
36607
  const placedRects = [];
@@ -36852,7 +37139,7 @@ function renderC4DeploymentForExport(content, theme, palette) {
36852
37139
  document.body.removeChild(el);
36853
37140
  }
36854
37141
  }
36855
- 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;
37142
+ 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;
36856
37143
  var init_renderer9 = __esm({
36857
37144
  "src/c4/renderer.ts"() {
36858
37145
  "use strict";
@@ -36877,7 +37164,7 @@ var init_renderer9 = __esm({
36877
37164
  NAME_FONT_SIZE2 = 14;
36878
37165
  DESC_FONT_SIZE5 = 11;
36879
37166
  DESC_LINE_HEIGHT6 = 16;
36880
- EDGE_LABEL_FONT_SIZE6 = 11;
37167
+ EDGE_LABEL_FONT_SIZE7 = 11;
36881
37168
  TECH_FONT_SIZE = 10;
36882
37169
  CARD_H_PAD4 = 20;
36883
37170
  CARD_V_PAD4 = 14;
@@ -37501,7 +37788,7 @@ function renderFlowchart(container, graph, layout, palette, isDark, onClickItem,
37501
37788
  const sTitleFontSize = ctx.text(TITLE_FONT_SIZE);
37502
37789
  const sTitleY = ctx.structural(TITLE_Y);
37503
37790
  const sNodeFontSize = ctx.text(NODE_FONT_SIZE2);
37504
- const sEdgeLabelFontSize = ctx.text(EDGE_LABEL_FONT_SIZE7);
37791
+ const sEdgeLabelFontSize = ctx.text(EDGE_LABEL_FONT_SIZE8);
37505
37792
  const sEdgeStrokeWidth = ctx.structural(EDGE_STROKE_WIDTH);
37506
37793
  const sNodeStrokeWidth = ctx.structural(NODE_STROKE_WIDTH);
37507
37794
  const sArrowheadW = ctx.structural(ARROWHEAD_W3);
@@ -37727,7 +38014,7 @@ function renderFlowchartForExport(content, theme, palette) {
37727
38014
  document.body.removeChild(container);
37728
38015
  }
37729
38016
  }
37730
- var d3Selection15, DIAGRAM_PADDING9, MAX_SCALE6, NODE_FONT_SIZE2, EDGE_LABEL_FONT_SIZE7, ARROWHEAD_W3, ARROWHEAD_H3, IO_SKEW, SUBROUTINE_INSET, DOC_WAVE_HEIGHT;
38017
+ var d3Selection15, DIAGRAM_PADDING9, MAX_SCALE6, NODE_FONT_SIZE2, EDGE_LABEL_FONT_SIZE8, ARROWHEAD_W3, ARROWHEAD_H3, IO_SKEW, SUBROUTINE_INSET, DOC_WAVE_HEIGHT;
37731
38018
  var init_flowchart_renderer = __esm({
37732
38019
  "src/graph/flowchart-renderer.ts"() {
37733
38020
  "use strict";
@@ -37747,7 +38034,7 @@ var init_flowchart_renderer = __esm({
37747
38034
  DIAGRAM_PADDING9 = 20;
37748
38035
  MAX_SCALE6 = 3;
37749
38036
  NODE_FONT_SIZE2 = 13;
37750
- EDGE_LABEL_FONT_SIZE7 = 11;
38037
+ EDGE_LABEL_FONT_SIZE8 = 11;
37751
38038
  ARROWHEAD_W3 = 10;
37752
38039
  ARROWHEAD_H3 = 7;
37753
38040
  IO_SKEW = 15;
@@ -39181,7 +39468,7 @@ function layoutInfra(computed, expandedNodeIds, collapsedNodes) {
39181
39468
  const midIdx = Math.floor(edge.points.length / 2);
39182
39469
  const midPt = edge.points[midIdx];
39183
39470
  if (midPt) {
39184
- const halfWidth = (measureText(edge.label, EDGE_LABEL_FONT_SIZE8) + 8) / 2;
39471
+ const halfWidth = (measureText(edge.label, EDGE_LABEL_FONT_SIZE9) + 8) / 2;
39185
39472
  if (midPt.x - halfWidth < minX) minX = midPt.x - halfWidth;
39186
39473
  if (midPt.x + halfWidth > maxX) maxX = midPt.x + halfWidth;
39187
39474
  }
@@ -39215,7 +39502,7 @@ function layoutInfra(computed, expandedNodeIds, collapsedNodes) {
39215
39502
  height: totalHeight
39216
39503
  };
39217
39504
  }
39218
- var import_dagre7, MIN_NODE_WIDTH2, NODE_HEADER_HEIGHT, META_LINE_HEIGHT5, NODE_SEPARATOR_GAP, NODE_PAD_BOTTOM, ROLE_DOT_ROW, COLLAPSE_BAR_HEIGHT3, NODE_FONT_SIZE3, META_FONT_SIZE5, EDGE_LABEL_FONT_SIZE8, PADDING_X3, GROUP_PADDING2, GROUP_HEADER_HEIGHT, EDGE_MARGIN, DISPLAY_KEYS, DISPLAY_NAMES, GROUP_GAP2;
39505
+ 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;
39219
39506
  var init_layout10 = __esm({
39220
39507
  "src/infra/layout.ts"() {
39221
39508
  "use strict";
@@ -39230,7 +39517,7 @@ var init_layout10 = __esm({
39230
39517
  COLLAPSE_BAR_HEIGHT3 = 6;
39231
39518
  NODE_FONT_SIZE3 = 13;
39232
39519
  META_FONT_SIZE5 = 10;
39233
- EDGE_LABEL_FONT_SIZE8 = 11;
39520
+ EDGE_LABEL_FONT_SIZE9 = 11;
39234
39521
  PADDING_X3 = 24;
39235
39522
  GROUP_PADDING2 = 20;
39236
39523
  GROUP_HEADER_HEIGHT = 24;
@@ -39335,7 +39622,7 @@ function buildScaledConstants(ctx) {
39335
39622
  sNodeFontSize: ctx.text(NODE_FONT_SIZE4),
39336
39623
  sMetaFontSize: ctx.text(META_FONT_SIZE6),
39337
39624
  sMetaLineHeight: ctx.structural(META_LINE_HEIGHT6),
39338
- sEdgeLabelFontSize: ctx.text(EDGE_LABEL_FONT_SIZE9),
39625
+ sEdgeLabelFontSize: ctx.text(EDGE_LABEL_FONT_SIZE10),
39339
39626
  sGroupLabelFontSize: ctx.text(GROUP_LABEL_FONT_SIZE2),
39340
39627
  sNodeBorderRadius: ctx.structural(NODE_BORDER_RADIUS),
39341
39628
  sEdgeStrokeWidth: ctx.structural(EDGE_STROKE_WIDTH),
@@ -40064,7 +40351,7 @@ function renderEdgeLabels(svg, edges, nodes, groups, palette, _isDark, animate,
40064
40351
  if (!midPt) continue;
40065
40352
  const labelText = edge.label;
40066
40353
  const g = svg.append("g").attr("class", animate ? "infra-edge-label" : "");
40067
- const textWidth = measureText(labelText, EDGE_LABEL_FONT_SIZE9) + 8;
40354
+ const textWidth = measureText(labelText, EDGE_LABEL_FONT_SIZE10) + 8;
40068
40355
  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);
40069
40356
  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);
40070
40357
  if (animate) {
@@ -40674,7 +40961,7 @@ function parseAndLayoutInfra(content) {
40674
40961
  const layout = layoutInfra(computed);
40675
40962
  return { parsed, computed, layout };
40676
40963
  }
40677
- 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;
40964
+ 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;
40678
40965
  var init_renderer10 = __esm({
40679
40966
  "src/infra/renderer.ts"() {
40680
40967
  "use strict";
@@ -40700,7 +40987,7 @@ var init_renderer10 = __esm({
40700
40987
  NODE_FONT_SIZE4 = 13;
40701
40988
  META_FONT_SIZE6 = 10;
40702
40989
  META_LINE_HEIGHT6 = 14;
40703
- EDGE_LABEL_FONT_SIZE9 = 11;
40990
+ EDGE_LABEL_FONT_SIZE10 = 11;
40704
40991
  GROUP_LABEL_FONT_SIZE2 = 14;
40705
40992
  NODE_BORDER_RADIUS = 8;
40706
40993
  OVERLOAD_STROKE_WIDTH = 3;
@@ -47091,7 +47378,7 @@ function renderState(container, graph, layout, palette, isDark, onClickItem, exp
47091
47378
  const sTitleFontSize = ctx.text(TITLE_FONT_SIZE);
47092
47379
  const sTitleY = ctx.structural(TITLE_Y);
47093
47380
  const sNodeFontSize = ctx.text(NODE_FONT_SIZE6);
47094
- const sEdgeLabelFontSize = ctx.text(EDGE_LABEL_FONT_SIZE10);
47381
+ const sEdgeLabelFontSize = ctx.text(EDGE_LABEL_FONT_SIZE11);
47095
47382
  const sGroupLabelFontSize = ctx.text(GROUP_LABEL_FONT_SIZE3);
47096
47383
  const sEdgeStrokeWidth = ctx.structural(EDGE_STROKE_WIDTH);
47097
47384
  const sNodeStrokeWidth = ctx.structural(NODE_STROKE_WIDTH);
@@ -47371,7 +47658,7 @@ function renderStateForExport(content, theme, palette) {
47371
47658
  document.body.removeChild(container);
47372
47659
  }
47373
47660
  }
47374
- 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;
47661
+ 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;
47375
47662
  var init_state_renderer = __esm({
47376
47663
  "src/graph/state-renderer.ts"() {
47377
47664
  "use strict";
@@ -47391,7 +47678,7 @@ var init_state_renderer = __esm({
47391
47678
  DIAGRAM_PADDING12 = 20;
47392
47679
  MAX_SCALE7 = 3;
47393
47680
  NODE_FONT_SIZE6 = 13;
47394
- EDGE_LABEL_FONT_SIZE10 = 11;
47681
+ EDGE_LABEL_FONT_SIZE11 = 11;
47395
47682
  GROUP_LABEL_FONT_SIZE3 = 11;
47396
47683
  ARROWHEAD_W5 = 10;
47397
47684
  ARROWHEAD_H5 = 7;
@@ -50433,10 +50720,10 @@ function renderedDescNodeHeight(numLines, scale) {
50433
50720
  return headerH + textBlockH + bottomPad;
50434
50721
  }
50435
50722
  function wrapEdgeLabelText(label, description, maxWidth = EDGE_LABEL_MAX_WIDTH) {
50436
- const labelLines = label ? wrapTextToWidth(label, EDGE_LABEL_FONT_SIZE11, maxWidth) : [];
50723
+ const labelLines = label ? wrapTextToWidth(label, EDGE_LABEL_FONT_SIZE12, maxWidth) : [];
50437
50724
  const descLines = [];
50438
50725
  for (const d of description) {
50439
- descLines.push(...wrapTextToWidth(d, EDGE_LABEL_FONT_SIZE11, maxWidth));
50726
+ descLines.push(...wrapTextToWidth(d, EDGE_LABEL_FONT_SIZE12, maxWidth));
50440
50727
  }
50441
50728
  return { labelLines, descLines };
50442
50729
  }
@@ -50574,9 +50861,9 @@ function computeEdgePaths(layoutNodes, parsed, cx, cy, radius, isClockwise) {
50574
50861
  const lineCount = labelLines.length + descLines.length;
50575
50862
  let labelPxW = 0;
50576
50863
  for (const l of labelLines)
50577
- labelPxW = Math.max(labelPxW, measureText(l, EDGE_LABEL_FONT_SIZE11));
50864
+ labelPxW = Math.max(labelPxW, measureText(l, EDGE_LABEL_FONT_SIZE12));
50578
50865
  for (const l of descLines)
50579
- labelPxW = Math.max(labelPxW, measureText(l, EDGE_LABEL_FONT_SIZE11));
50866
+ labelPxW = Math.max(labelPxW, measureText(l, EDGE_LABEL_FONT_SIZE12));
50580
50867
  const { labelX, labelY, labelAngle } = computeEdgeLabelPosition(
50581
50868
  midAngle,
50582
50869
  radius,
@@ -50678,9 +50965,9 @@ function fitToCanvas(nodes, edges, parsed, _cx, _cy, radius, width, height, _isC
50678
50965
  );
50679
50966
  let textWidth = 0;
50680
50967
  for (const l of labelLines)
50681
- textWidth = Math.max(textWidth, measureText(l, EDGE_LABEL_FONT_SIZE11));
50968
+ textWidth = Math.max(textWidth, measureText(l, EDGE_LABEL_FONT_SIZE12));
50682
50969
  for (const l of descLines)
50683
- textWidth = Math.max(textWidth, measureText(l, EDGE_LABEL_FONT_SIZE11));
50970
+ textWidth = Math.max(textWidth, measureText(l, EDGE_LABEL_FONT_SIZE12));
50684
50971
  if (textWidth === 0) continue;
50685
50972
  const normAngle = (le.labelAngle % (2 * Math.PI) + 2 * Math.PI) % (2 * Math.PI);
50686
50973
  const isRight = normAngle < Math.PI * 0.4 || normAngle > Math.PI * 1.6;
@@ -50754,7 +51041,7 @@ function buildEdgeArc(src, tgt, cx, cy, radius, isClockwise, arrowLength = 0) {
50754
51041
  const midAngle = srcExitAngle + dir * visibleSweep / 2;
50755
51042
  return { path, midAngle };
50756
51043
  }
50757
- 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;
51044
+ 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;
50758
51045
  var init_layout14 = __esm({
50759
51046
  "src/cycle/layout.ts"() {
50760
51047
  "use strict";
@@ -50765,7 +51052,7 @@ var init_layout14 = __esm({
50765
51052
  LABEL_FONT_SIZE4 = 13;
50766
51053
  CIRCLE_LABEL_FONT_SIZE = 16;
50767
51054
  DESC_FONT_SIZE6 = 11;
50768
- EDGE_LABEL_FONT_SIZE11 = 11;
51055
+ EDGE_LABEL_FONT_SIZE12 = 11;
50769
51056
  MIN_NODE_WIDTH4 = 70;
50770
51057
  MAX_NODE_WIDTH3 = 260;
50771
51058
  DESC_MIN_WIDTH = 140;
@@ -50910,7 +51197,7 @@ function renderCycle(container, parsed, palette, isDark, onClickItem, exportDims
50910
51197
  const scaledNodeFont = Math.max(6, Math.round(NODE_FONT_SIZE7 * layout.scale));
50911
51198
  const scaledEdgeLabelFont = Math.max(
50912
51199
  6,
50913
- Math.round(EDGE_LABEL_FONT_SIZE12 * layout.scale)
51200
+ Math.round(EDGE_LABEL_FONT_SIZE13 * layout.scale)
50914
51201
  );
50915
51202
  const CIRCLE_LABEL_FONT_SIZE2 = 16;
50916
51203
  const scaledCircleLabelFont = Math.max(
@@ -51073,7 +51360,7 @@ function ensureArrowMarker(defs, color, strokeWidth) {
51073
51360
  const mh = Math.max(1.5, mw * 0.5);
51074
51361
  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);
51075
51362
  }
51076
- var d3Selection22, NODE_FONT_SIZE7, DESC_FONT_SIZE7, EDGE_LABEL_FONT_SIZE12, DESC_LINE_HEIGHT8, TITLE_AREA_HEIGHT;
51363
+ var d3Selection22, NODE_FONT_SIZE7, DESC_FONT_SIZE7, EDGE_LABEL_FONT_SIZE13, DESC_LINE_HEIGHT8, TITLE_AREA_HEIGHT;
51077
51364
  var init_renderer15 = __esm({
51078
51365
  "src/cycle/renderer.ts"() {
51079
51366
  "use strict";
@@ -51091,7 +51378,7 @@ var init_renderer15 = __esm({
51091
51378
  init_text_measure();
51092
51379
  NODE_FONT_SIZE7 = 13;
51093
51380
  DESC_FONT_SIZE7 = 11;
51094
- EDGE_LABEL_FONT_SIZE12 = 11;
51381
+ EDGE_LABEL_FONT_SIZE13 = 11;
51095
51382
  DESC_LINE_HEIGHT8 = 15;
51096
51383
  TITLE_AREA_HEIGHT = 50;
51097
51384
  }
@@ -51489,7 +51776,7 @@ function resolveMap(parsed, data) {
51489
51776
  const hint = suggest(r.name, allNames);
51490
51777
  err(
51491
51778
  r.lineNumber,
51492
- `Unknown subdivision "${r.name}".${hint ? " " + hint : ""}`,
51779
+ `Unknown region "${r.name}" \u2014 not a known country or US state.${hint ? " " + hint : ""} Search the exact token with \`dgmo map-search "${r.name}"\` (or the lookup_map_location tool), use an ISO code (e.g. FR, US-CA), or coordinates.`,
51493
51780
  "E_MAP_UNKNOWN_SUBDIVISION"
51494
51781
  );
51495
51782
  continue;
@@ -51569,7 +51856,7 @@ function resolveMap(parsed, data) {
51569
51856
  const hint = suggest(name, cityNames);
51570
51857
  err(
51571
51858
  line11,
51572
- `Unknown place "${name}" (not in the gazetteer; use coordinates).${hint ? " " + hint : ""}`,
51859
+ `Unknown place "${name}" \u2014 not in the gazetteer.${hint ? " " + hint : ""} Search the exact token with \`dgmo map-search "${name}"\` (or the lookup_map_location tool), or use coordinates \`poi <lat> <lon>\`.`,
51573
51860
  "E_MAP_UNKNOWN_PLACE"
51574
51861
  );
51575
51862
  return { kind: "miss" };
@@ -52111,6 +52398,21 @@ function mapLegendBand(legend, opts) {
52111
52398
  if (height <= 0) return 0;
52112
52399
  return mapLegendTop(opts.hasTitle, opts.hasSubtitle) + height + LEGEND_BOTTOM_GAP2;
52113
52400
  }
52401
+ function mapLegendBox(legend, opts) {
52402
+ if (!legend) return null;
52403
+ const groups = mapLegendGroups(legend);
52404
+ if (groups.length === 0) return null;
52405
+ const config = mapLegendConfig(groups, opts.mode);
52406
+ const state = { activeGroup: legend.activeGroup };
52407
+ const { width, height } = computeLegendLayout(config, state, opts.width);
52408
+ if (height <= 0 || width <= 0) return null;
52409
+ return {
52410
+ x: (opts.width - width) / 2,
52411
+ y: mapLegendTop(opts.hasTitle, opts.hasSubtitle),
52412
+ width,
52413
+ height
52414
+ };
52415
+ }
52114
52416
  var LEGEND_TOP_GAP2, LEGEND_BOTTOM_GAP2;
52115
52417
  var init_legend_band = __esm({
52116
52418
  "src/map/legend-band.ts"() {
@@ -52235,6 +52537,8 @@ function placeContextLabels(args) {
52235
52537
  waterBodies,
52236
52538
  countries,
52237
52539
  palette,
52540
+ countryTone,
52541
+ waterTone,
52238
52542
  project,
52239
52543
  collides,
52240
52544
  contentPoints,
@@ -52244,8 +52548,8 @@ function placeContextLabels(args) {
52244
52548
  const band = tierBand(Math.max(dLonSpan, dLatSpan));
52245
52549
  const budget = labelBudget(width, height, band);
52246
52550
  if (budget <= 0) return [];
52247
- const waterColor = mix(palette.colors.blue, palette.textMuted, 50);
52248
- const countryColor = palette.textMuted;
52551
+ const waterColor = waterTone ?? mix(palette.colors.blue, palette.textMuted, 50);
52552
+ const countryColor = countryTone ?? palette.textMuted;
52249
52553
  const haloColor = palette.bg;
52250
52554
  const candidates = [];
52251
52555
  const center = [width / 2, height / 2];
@@ -52334,8 +52638,7 @@ function placeContextLabels(args) {
52334
52638
  )
52335
52639
  );
52336
52640
  const fontSize = Math.round(FONT + t * (COUNTRY_FONT_MAX - FONT));
52337
- const fade = Math.round(t * COUNTRY_FADE_MAX);
52338
- const color = fade > 0 ? mix(countryColor, palette.bg, 100 - fade) : countryColor;
52641
+ const color = countryColor;
52339
52642
  const text = c.name;
52340
52643
  const tw = labelWidth(text, 0, fontSize);
52341
52644
  if (tw > w || fontSize + 2 * PADY > h) continue;
@@ -52429,7 +52732,7 @@ function placeContextLabels(args) {
52429
52732
  }
52430
52733
  return placed;
52431
52734
  }
52432
- var FONT, LINE_HEIGHT, PADX, PADY, WATER_LETTER_SPACING, CONTEXT_PAD, EDGE_CLAMP_MARGIN, EDGE_CLAMP_OVERSHOOT, COUNTRY_FONT_MAX, COUNTRY_SIZE_FRAC_MIN, COUNTRY_SIZE_FRAC_MAX, COUNTRY_FADE_MAX, MAX_COUNTRY_POSITIONS, COUNTRY_POS_GRID, COUNTRY_POS_TOPN_MARGIN, KIND_ORDER;
52735
+ var FONT, LINE_HEIGHT, PADX, PADY, WATER_LETTER_SPACING, CONTEXT_PAD, EDGE_CLAMP_MARGIN, EDGE_CLAMP_OVERSHOOT, COUNTRY_FONT_MAX, COUNTRY_SIZE_FRAC_MIN, COUNTRY_SIZE_FRAC_MAX, MAX_COUNTRY_POSITIONS, COUNTRY_POS_GRID, COUNTRY_POS_TOPN_MARGIN, KIND_ORDER;
52433
52736
  var init_context_labels = __esm({
52434
52737
  "src/map/context-labels.ts"() {
52435
52738
  "use strict";
@@ -52446,7 +52749,6 @@ var init_context_labels = __esm({
52446
52749
  COUNTRY_FONT_MAX = 22;
52447
52750
  COUNTRY_SIZE_FRAC_MIN = 0.06;
52448
52751
  COUNTRY_SIZE_FRAC_MAX = 0.32;
52449
- COUNTRY_FADE_MAX = 45;
52450
52752
  MAX_COUNTRY_POSITIONS = 4;
52451
52753
  COUNTRY_POS_GRID = 5;
52452
52754
  COUNTRY_POS_TOPN_MARGIN = 3;
@@ -52573,36 +52875,23 @@ function countryLabelPositions(args) {
52573
52875
  return n;
52574
52876
  };
52575
52877
  const dist2ToCentre = (c) => (c.sx - cx) ** 2 + (c.sy - cy) ** 2;
52576
- const pool = [...kept];
52577
- pool.sort((a, b) => {
52578
- const d = interiorness(b) - interiorness(a);
52579
- return d !== 0 ? d : dist2ToCentre(a) - dist2ToCentre(b);
52580
- });
52581
- const ordered = [pool.shift()];
52582
- while (ordered.length < MAX_COUNTRY_POSITIONS && pool.length) {
52583
- let bestIdx = 0;
52584
- let bestMin = -1;
52585
- for (let k = 0; k < pool.length; k++) {
52586
- const c = pool[k];
52587
- let minD = Infinity;
52588
- for (const o of ordered) {
52589
- const d = (c.sx - o.sx) ** 2 + (c.sy - o.sy) ** 2;
52590
- if (d < minD) minD = d;
52591
- }
52592
- if (minD > bestMin) {
52593
- bestMin = minD;
52594
- bestIdx = k;
52595
- }
52878
+ const centreLon = kept.reduce((s, c) => s + c.lon, 0) / kept.length;
52879
+ const centreLat = kept.reduce((s, c) => s + c.lat, 0) / kept.length;
52880
+ const centreScreen = project(centreLon, centreLat);
52881
+ const lead = pointInGeometry(geometry, centreLon, centreLat) && centreScreen && Number.isFinite(centreScreen[0]) && Number.isFinite(centreScreen[1]) && centreScreen[0] >= 0 && centreScreen[0] <= width && centreScreen[1] >= 0 && centreScreen[1] <= height ? [
52882
+ {
52883
+ lonLat: [centreLon, centreLat],
52884
+ screen: [centreScreen[0], centreScreen[1]]
52596
52885
  }
52597
- ordered.push(pool.splice(bestIdx, 1)[0]);
52598
- }
52599
- const grid = ordered.map((c) => ({
52886
+ ] : [];
52887
+ const grid = [...kept].sort(
52888
+ (a, b) => interiorness(b) - interiorness(a) || dist2ToCentre(a) - dist2ToCentre(b)
52889
+ ).map((c) => ({
52600
52890
  lonLat: [c.lon, c.lat],
52601
52891
  screen: [c.sx, c.sy]
52602
52892
  }));
52603
52893
  const curatedPos = curated ? mkCurated(curated, project) : [];
52604
- const out = [...curatedPos, ...grid].slice(0, MAX_COUNTRY_POSITIONS);
52605
- return out;
52894
+ return [...curatedPos, ...lead, ...grid].slice(0, MAX_COUNTRY_POSITIONS);
52606
52895
  }
52607
52896
  function mkCurated(curated, project) {
52608
52897
  if (!curated) return [];
@@ -52868,6 +53157,25 @@ function layoutMap(resolvedIn, data, size, opts) {
52868
53157
  palette.bg,
52869
53158
  mutedBasemap ? isDark ? MUTED_FOREIGN_DARK : MUTED_FOREIGN_LIGHT : isDark ? FOREIGN_TINT_DARK : FOREIGN_TINT_LIGHT
52870
53159
  );
53160
+ const contrastFloorTone = (tone, ...substrates) => {
53161
+ const clears = (t) => substrates.every((s) => contrastRatio(t, s) >= REGION_LABEL_HALO_RATIO);
53162
+ if (clears(tone)) return tone;
53163
+ for (let s = 10; s <= 100; s += 10) {
53164
+ const t = mix(palette.text, tone, s);
53165
+ if (clears(t)) return t;
53166
+ }
53167
+ return palette.text;
53168
+ };
53169
+ const backdropLandTone = contrastFloorTone(
53170
+ palette.textMuted,
53171
+ neutralFill,
53172
+ water
53173
+ );
53174
+ const backdropWaterTone = contrastFloorTone(
53175
+ mix(palette.colors.blue, palette.textMuted, 50),
53176
+ water,
53177
+ neutralFill
53178
+ );
52871
53179
  const hasDirectColor = resolved.regions.some((r) => r.color !== void 0);
52872
53180
  const colorizeActive = resolved.directives.noColorize !== true && !hasRamp && !hasDirectColor && fillGroupNames.size === 0;
52873
53181
  const colorByIso = /* @__PURE__ */ new Map();
@@ -52982,6 +53290,33 @@ function layoutMap(resolvedIn, data, size, opts) {
52982
53290
  hasSubtitle: Boolean(resolved.subtitle)
52983
53291
  });
52984
53292
  if (legendBand > topPad) topPad = legendBand;
53293
+ const topReserved = [];
53294
+ if (shownTitle) {
53295
+ const lines = resolved.subtitle ? 2 : 1;
53296
+ const tw = Math.max(
53297
+ measureLegendText(shownTitle, TITLE_FONT_SIZE),
53298
+ resolved.subtitle ? measureLegendText(resolved.subtitle, TITLE_FONT_SIZE) : 0
53299
+ ) + 2 * TITLE_FONT_SIZE;
53300
+ topReserved.push({
53301
+ x: width / 2 - tw / 2,
53302
+ y: TITLE_Y - TITLE_FONT_SIZE,
53303
+ w: tw,
53304
+ h: TITLE_FONT_SIZE * (lines + 0.5)
53305
+ });
53306
+ }
53307
+ const legendBox = mapLegendBox(legend, {
53308
+ width,
53309
+ mode: opts.legendMode ?? "preview",
53310
+ hasTitle: Boolean(shownTitle),
53311
+ hasSubtitle: Boolean(resolved.subtitle)
53312
+ });
53313
+ if (legendBox)
53314
+ topReserved.push({
53315
+ x: legendBox.x,
53316
+ y: legendBox.y,
53317
+ w: legendBox.width,
53318
+ h: legendBox.height
53319
+ });
52985
53320
  const reserve = opts._calloutReserve;
52986
53321
  const fitLeft = FIT_PAD + (reserve?.left ?? 0);
52987
53322
  const fitRight = width - FIT_PAD - (reserve?.right ?? 0);
@@ -53711,6 +54046,19 @@ function layoutMap(resolvedIn, data, size, opts) {
53711
54046
  const by = b.cy - (b.cy - py) / tb * trimB;
53712
54047
  return `M${ax},${ay}Q${px},${py} ${bx},${by}`;
53713
54048
  };
54049
+ const legLabelPoint = (a, b, curved, offset) => {
54050
+ const mx = (a.cx + b.cx) / 2;
54051
+ const my = (a.cy + b.cy) / 2;
54052
+ if (!curved && offset === 0) return { x: mx, y: my - 4 };
54053
+ const dx = b.cx - a.cx;
54054
+ const dy = b.cy - a.cy;
54055
+ const len = Math.hypot(dx, dy) || 1;
54056
+ const nx = -dy / len;
54057
+ const ny = dx / len;
54058
+ const bow = offset !== 0 ? offset : len * ARC_CURVE_FRAC;
54059
+ const off = bow * 0.5 + Math.sign(bow || 1) * 8;
54060
+ return { x: mx + nx * off, y: my + ny * off };
54061
+ };
53714
54062
  const routeLegVals = resolved.routes.flatMap((rt) => rt.legs).map((l) => Number(l.value)).filter((n) => Number.isFinite(n) && n > 0);
53715
54063
  const rlMin = routeLegVals.length ? Math.min(...routeLegVals) : 0;
53716
54064
  const rlMax = routeLegVals.length ? Math.max(...routeLegVals) : 0;
@@ -53724,13 +54072,12 @@ function layoutMap(resolvedIn, data, size, opts) {
53724
54072
  const a = poiScreen.get(leg.fromId);
53725
54073
  const b = poiScreen.get(leg.toId);
53726
54074
  if (!a || !b) continue;
53727
- const mx = (a.cx + b.cx) / 2;
53728
- const my = (a.cy + b.cy) / 2;
54075
+ const lp = legLabelPoint(a, b, leg.style === "arc", 0);
53729
54076
  const bow = {
53730
54077
  curved: leg.style === "arc",
53731
54078
  offset: 0,
53732
- labelX: mx,
53733
- labelY: my - 4
54079
+ labelX: lp.x,
54080
+ labelY: lp.y
53734
54081
  };
53735
54082
  const routeLabelStyle = leg.label !== void 0 ? labelOnFill(fillAt(bow.labelX, bow.labelY)) : void 0;
53736
54083
  legs.push({
@@ -53777,13 +54124,13 @@ function layoutMap(resolvedIn, data, size, opts) {
53777
54124
  const b = poiScreen.get(e.toId);
53778
54125
  if (!a || !b) return;
53779
54126
  const fanOffset = n > 1 ? (i - (n - 1) / 2) * FAN_STEP : 0;
53780
- const mx = (a.cx + b.cx) / 2;
53781
- const my = (a.cy + b.cy) / 2;
54127
+ const curved = e.style === "arc" || n > 1;
54128
+ const lp = legLabelPoint(a, b, curved, fanOffset);
53782
54129
  const bow = {
53783
- curved: e.style === "arc" || n > 1,
54130
+ curved,
53784
54131
  offset: fanOffset,
53785
- labelX: mx,
53786
- labelY: my - 4
54132
+ labelX: lp.x,
54133
+ labelY: lp.y
53787
54134
  };
53788
54135
  const edgeLabelStyle = e.label !== void 0 ? labelOnFill(fillAt(bow.labelX, bow.labelY)) : void 0;
53789
54136
  legs.push({
@@ -53807,7 +54154,7 @@ function layoutMap(resolvedIn, data, size, opts) {
53807
54154
  });
53808
54155
  }
53809
54156
  const labels = [];
53810
- const obstacles = [];
54157
+ const obstacles = [...topReserved];
53811
54158
  const regionLabelGuards = [];
53812
54159
  const markers = pois.map((p) => ({
53813
54160
  cx: p.cx,
@@ -53845,6 +54192,7 @@ function layoutMap(resolvedIn, data, size, opts) {
53845
54192
  }
53846
54193
  const collides = (rect) => markers.some((m) => rectCircleOverlap(rect, m)) || obstacles.some((o) => rectsOverlap(rect, o)) || legSegments.some((s) => segmentRectOverlap(s[0], s[1], s[2], s[3], rect));
53847
54194
  const showRegionLabels = resolved.directives.noRegionLabels !== true;
54195
+ const labeledRegionIds = /* @__PURE__ */ new Set();
53848
54196
  const showRegionValues = resolved.directives.noRegionValue !== true && activeIsScore;
53849
54197
  const regionValueStr = (value) => showRegionValues && value !== void 0 ? compactNumber(value) : void 0;
53850
54198
  const isCompact = width < COMPACT_WIDTH_PX;
@@ -53865,7 +54213,9 @@ function layoutMap(resolvedIn, data, size, opts) {
53865
54213
  const REGION_FONT_MAX_ORIENT = 22;
53866
54214
  const REGION_SIZE_FRAC_MIN = 0.06;
53867
54215
  const REGION_SIZE_FRAC_MAX = 0.32;
53868
- const REGION_FADE_ORIENT = 45;
54216
+ const SUBJECT_FONT_MAX = 18;
54217
+ const SUBJECT_MIN_PROMINENCE = 13;
54218
+ const SUBJECT_LEADER_FONT = 15;
53869
54219
  const canvasLinear = Math.sqrt(Math.max(1, width * height));
53870
54220
  const sizeT = (boxW, boxH) => {
53871
54221
  const frac = Math.sqrt(Math.max(0, boxW * boxH)) / canvasLinear;
@@ -53877,9 +54227,9 @@ function layoutMap(resolvedIn, data, size, opts) {
53877
54227
  )
53878
54228
  );
53879
54229
  };
53880
- const pushRegionLabel = (x, y, text, fill2, lineNumber, valueLine, fontSize = FONT2, fade = 0) => {
54230
+ const pushRegionLabel = (x, y, text, fill2, lineNumber, valueLine, fontSize = FONT2, colorOverride) => {
53881
54231
  const { color: baseColor, haloColor } = labelOnFill(fill2);
53882
- const color = fade > 0 ? mix(baseColor, palette.bg, fade) : baseColor;
54232
+ const color = colorOverride ?? baseColor;
53883
54233
  const vf = Math.round(fontSize * 0.82);
53884
54234
  const halfW = Math.max(
53885
54235
  measureLegendText(text, fontSize),
@@ -53894,7 +54244,11 @@ function layoutMap(resolvedIn, data, size, opts) {
53894
54244
  text,
53895
54245
  anchor: "middle",
53896
54246
  color,
53897
- halo: overflows,
54247
+ // A backdrop label (colorOverride) NEVER haloes: the placement gate above
54248
+ // guarantees it sits on a floored-readable substrate (own fill, neutral
54249
+ // land, foreign land, or water), so a halo is pure noise. Data labels keep
54250
+ // the containment-gated halo until the role-tier pass (Phase 3).
54251
+ halo: colorOverride ? false : overflows,
53898
54252
  haloColor,
53899
54253
  ...fontSize !== FONT2 && { fontSize },
53900
54254
  ...valueLine !== void 0 && { valueLine },
@@ -53930,7 +54284,7 @@ function layoutMap(resolvedIn, data, size, opts) {
53930
54284
  if (!c || !Number.isFinite(c[0])) return null;
53931
54285
  return { r, c, boxW, boxH, area: boxW * boxH, candidates };
53932
54286
  }).filter((e) => e !== null).sort((a, b) => b.area - a.area || a.r.lineNumber - b.r.lineNumber);
53933
- const placedRegionRects = [];
54287
+ const placedRegionRects = [...topReserved];
53934
54288
  const placedLeaders = [];
53935
54289
  const isEmptyFill = (f) => f === water || f === neutralFill || f === foreignFill;
53936
54290
  const POI_LABEL_PAD = 14;
@@ -53943,9 +54297,9 @@ function layoutMap(resolvedIn, data, size, opts) {
53943
54297
  const SHORT_HOP_MAX = Math.max(46, Math.min(width, height) * 0.11);
53944
54298
  const STEP = 4;
53945
54299
  const HOP_GAP = 9;
53946
- const tryShortHopCallout = (cx, cy, fill2, name, value) => {
53947
- const chipW = stackW(name, value);
53948
- const chipH = stackH(value !== void 0);
54300
+ const tryShortHopCallout = (cx, cy, fill2, name, value, font = FONT2, gap = HOP_GAP, maxLen = SHORT_HOP_MAX) => {
54301
+ const chipW = stackW(name, value, font);
54302
+ const chipH = stackH(value !== void 0, font);
53949
54303
  const dirs = [
53950
54304
  [0, -1],
53951
54305
  // north (over Canada)
@@ -53961,7 +54315,7 @@ function layoutMap(resolvedIn, data, size, opts) {
53961
54315
  let ex = cx;
53962
54316
  let ey = cy;
53963
54317
  let steps = 0;
53964
- const maxSteps = Math.ceil(SHORT_HOP_MAX / STEP) + 1;
54318
+ const maxSteps = Math.ceil(maxLen / STEP) + 1;
53965
54319
  while (steps < maxSteps && fillAt(ex, ey) === fill2) {
53966
54320
  ex += dx * STEP;
53967
54321
  ey += dy * STEP;
@@ -53969,8 +54323,8 @@ function layoutMap(resolvedIn, data, size, opts) {
53969
54323
  }
53970
54324
  if (fillAt(ex, ey) === fill2) continue;
53971
54325
  const halfAlong = (Math.abs(dx) * chipW + Math.abs(dy) * chipH) / 2;
53972
- const ccx = ex + dx * (HOP_GAP + halfAlong);
53973
- const ccy = ey + dy * (HOP_GAP + halfAlong);
54326
+ const ccx = ex + dx * (gap + halfAlong);
54327
+ const ccy = ey + dy * (gap + halfAlong);
53974
54328
  const rect = {
53975
54329
  x: ccx - chipW / 2,
53976
54330
  y: ccy - chipH / 2,
@@ -53995,7 +54349,7 @@ function layoutMap(resolvedIn, data, size, opts) {
53995
54349
  const innerX = ccx - dx * (chipW / 2);
53996
54350
  const innerY = ccy - dy * (chipH / 2);
53997
54351
  const len = Math.hypot(innerX - cx, innerY - cy);
53998
- if (len > SHORT_HOP_MAX) continue;
54352
+ if (len > maxLen) continue;
53999
54353
  if (placedLeaders.some(
54000
54354
  (l) => segmentsCross(cx, cy, innerX, innerY, l[0], l[1], l[2], l[3])
54001
54355
  ))
@@ -54019,6 +54373,78 @@ function layoutMap(resolvedIn, data, size, opts) {
54019
54373
  const valStr = regionValueStr(r.value);
54020
54374
  const fitsRegions = (rect) => !placedRegionRects.some((p) => rectsOverlap(rect, p));
54021
54375
  const fitsPois = (rect) => !poiObstacles.some((o) => rectsOverlap(rect, o));
54376
+ const isSubject = regionById.has(r.id);
54377
+ const extremesOnFill = (ax, ay, text) => {
54378
+ const halfW = measureLegendText(text, FONT2) / 2;
54379
+ return fillAt(ax - halfW, ay) === r.fill && fillAt(ax + halfW, ay) === r.fill;
54380
+ };
54381
+ if (isSubject && valStr === void 0 && r.label !== void 0) {
54382
+ const name = candidates[0];
54383
+ let best = -1;
54384
+ for (let f = SUBJECT_FONT_MAX; f >= FONT2; f--) {
54385
+ if (measureLegendText(name, f) > boxW || f + 2 * LABEL_PADY > boxH)
54386
+ continue;
54387
+ const rect = regionLabelRect(c[0], c[1], name, void 0, f);
54388
+ if (!fitsRegions(rect) || !fitsPois(rect) || collides(rect)) continue;
54389
+ const halfW = measureLegendText(name, f) / 2;
54390
+ if (fillAt(c[0] - halfW, c[1]) !== r.fill || fillAt(c[0] + halfW, c[1]) !== r.fill)
54391
+ continue;
54392
+ best = f;
54393
+ break;
54394
+ }
54395
+ if (best >= SUBJECT_MIN_PROMINENCE) {
54396
+ const rect = regionLabelRect(c[0], c[1], name, void 0, best);
54397
+ placedRegionRects.push(rect);
54398
+ pushRegionLabel(
54399
+ c[0],
54400
+ c[1],
54401
+ name,
54402
+ r.fill,
54403
+ r.lineNumber,
54404
+ void 0,
54405
+ best
54406
+ );
54407
+ labeledRegionIds.add(r.id);
54408
+ regionLabelGuards.push({ label: labels[labels.length - 1], rect });
54409
+ } else {
54410
+ const hop = tryShortHopCallout(
54411
+ c[0],
54412
+ c[1],
54413
+ r.fill,
54414
+ r.label,
54415
+ void 0,
54416
+ SUBJECT_LEADER_FONT,
54417
+ HOP_GAP + SUBJECT_LEADER_FONT,
54418
+ SHORT_HOP_MAX * 2.2
54419
+ );
54420
+ if (hop) {
54421
+ placedRegionRects.push(hop.rect);
54422
+ placedLeaders.push(hop.leader);
54423
+ const dark = mix(r.fill, palette.text, 60);
54424
+ labels.push({
54425
+ x: hop.x,
54426
+ y: hop.y,
54427
+ text: r.label,
54428
+ anchor: "middle",
54429
+ color: palette.text,
54430
+ halo: false,
54431
+ haloColor: palette.bg,
54432
+ fontSize: SUBJECT_LEADER_FONT,
54433
+ leader: {
54434
+ x1: hop.leader[0],
54435
+ y1: hop.leader[1],
54436
+ x2: hop.leader[2],
54437
+ y2: hop.leader[3]
54438
+ },
54439
+ leaderColor: dark,
54440
+ calloutDot: { x: c[0], y: c[1], color: dark },
54441
+ lineNumber: r.lineNumber
54442
+ });
54443
+ labeledRegionIds.add(r.id);
54444
+ }
54445
+ }
54446
+ continue;
54447
+ }
54022
54448
  const seekAnchors = [
54023
54449
  { x: c[0], y: c[1], guard: false }
54024
54450
  ];
@@ -54041,13 +54467,13 @@ function layoutMap(resolvedIn, data, size, opts) {
54041
54467
  const nameRect = regionLabelRect(a.x, a.y, t);
54042
54468
  if (valStr && stackW(t, valStr) <= boxW && stackH(true) <= boxH) {
54043
54469
  const stackRect = regionLabelRect(a.x, a.y, t, valStr);
54044
- if (fitsRegions(stackRect) && fitsPois(nameRect)) {
54470
+ if (fitsRegions(stackRect) && fitsPois(nameRect) && extremesOnFill(a.x, a.y, t)) {
54045
54471
  chosen = { text: t, valueLine: valStr, ax: a.x, ay: a.y };
54046
54472
  break;
54047
54473
  }
54048
54474
  }
54049
54475
  if (labelW(t) <= boxW && labelH <= boxH) {
54050
- if (fitsRegions(nameRect) && fitsPois(nameRect)) {
54476
+ if (fitsRegions(nameRect) && fitsPois(nameRect) && (valStr === void 0 || extremesOnFill(a.x, a.y, t))) {
54051
54477
  chosen = { text: t, ax: a.x, ay: a.y };
54052
54478
  break;
54053
54479
  }
@@ -54055,7 +54481,7 @@ function layoutMap(resolvedIn, data, size, opts) {
54055
54481
  }
54056
54482
  if (chosen) break;
54057
54483
  }
54058
- if (chosen === void 0 && valStr && r.label !== void 0) {
54484
+ if (chosen === void 0 && r.label !== void 0 && isSubject) {
54059
54485
  const hop = tryShortHopCallout(c[0], c[1], r.fill, r.label, valStr);
54060
54486
  if (hop) {
54061
54487
  placedRegionRects.push(hop.rect);
@@ -54067,9 +54493,9 @@ function layoutMap(resolvedIn, data, size, opts) {
54067
54493
  text: r.label,
54068
54494
  anchor: "middle",
54069
54495
  color: palette.text,
54070
- halo: true,
54496
+ halo: false,
54071
54497
  haloColor: palette.bg,
54072
- valueLine: valStr,
54498
+ ...valStr !== void 0 && { valueLine: valStr },
54073
54499
  leader: {
54074
54500
  x1: hop.leader[0],
54075
54501
  y1: hop.leader[1],
@@ -54080,20 +54506,28 @@ function layoutMap(resolvedIn, data, size, opts) {
54080
54506
  calloutDot: { x: c[0], y: c[1], color: dark },
54081
54507
  lineNumber: r.lineNumber
54082
54508
  });
54509
+ labeledRegionIds.add(r.id);
54083
54510
  }
54084
54511
  continue;
54085
54512
  }
54086
54513
  if (chosen === void 0) continue;
54087
54514
  const isOrient = r.value === void 0 && r.layer === "base";
54088
54515
  let font = FONT2;
54089
- let fade = 0;
54090
54516
  if (isOrient) {
54091
54517
  const growT = sizeT(boxW, boxH);
54092
54518
  const desiredFont = Math.round(
54093
54519
  FONT2 + growT * (REGION_FONT_MAX_ORIENT - FONT2)
54094
54520
  );
54095
54521
  const hasVal = chosen.valueLine !== void 0;
54096
- for (let f = desiredFont; f > FONT2; f--) {
54522
+ const extremesClean = (f) => {
54523
+ const halfW = measureLegendText(chosen.text, f) / 2;
54524
+ return [chosen.ax - halfW, chosen.ax + halfW].every((sx) => {
54525
+ const fAt = fillAt(sx, chosen.ay);
54526
+ return fAt === r.fill || isEmptyFill(fAt);
54527
+ });
54528
+ };
54529
+ let fit = -1;
54530
+ for (let f = desiredFont; f >= FONT2; f--) {
54097
54531
  if (stackW(chosen.text, chosen.valueLine, f) > boxW || stackH(hasVal, f) > boxH)
54098
54532
  continue;
54099
54533
  const gRect = regionLabelRect(
@@ -54110,14 +54544,18 @@ function layoutMap(resolvedIn, data, size, opts) {
54110
54544
  void 0,
54111
54545
  f
54112
54546
  );
54113
- if (!fitsRegions(gRect) || !fitsPois(gName)) continue;
54114
- const halfW = measureLegendText(chosen.text, f) / 2;
54115
- if (fillAt(chosen.ax - halfW, chosen.ay) !== r.fill || fillAt(chosen.ax + halfW, chosen.ay) !== r.fill)
54547
+ if (!fitsRegions(gRect) || !fitsPois(gName) || collides(gName))
54116
54548
  continue;
54117
- font = f;
54549
+ if (!extremesClean(f)) continue;
54550
+ fit = f;
54118
54551
  break;
54119
54552
  }
54120
- fade = font > FONT2 ? Math.round(growT * REGION_FADE_ORIENT) : 0;
54553
+ if (fit < 0) {
54554
+ if (!frameContainers.has(r.id)) continue;
54555
+ font = FONT2;
54556
+ } else {
54557
+ font = fit;
54558
+ }
54121
54559
  }
54122
54560
  const rRect = regionLabelRect(
54123
54561
  chosen.ax,
@@ -54135,8 +54573,9 @@ function layoutMap(resolvedIn, data, size, opts) {
54135
54573
  r.lineNumber,
54136
54574
  chosen.valueLine,
54137
54575
  font,
54138
- fade
54576
+ isOrient ? backdropLandTone : void 0
54139
54577
  );
54578
+ labeledRegionIds.add(r.id);
54140
54579
  regionLabelGuards.push({
54141
54580
  label: labels[labels.length - 1],
54142
54581
  rect: rRect
@@ -54154,6 +54593,7 @@ function layoutMap(resolvedIn, data, size, opts) {
54154
54593
  seed.lineNumber,
54155
54594
  valStr
54156
54595
  );
54596
+ labeledRegionIds.add(seed.iso);
54157
54597
  regionLabelGuards.push({
54158
54598
  label: labels[labels.length - 1],
54159
54599
  rect: regionLabelRect(seed.x, seed.y, text, valStr)
@@ -54585,13 +55025,30 @@ function layoutMap(resolvedIn, data, size, opts) {
54585
55025
  const nx = -dy / len;
54586
55026
  const ny = dx / len;
54587
55027
  const perp = labelH + 2;
55028
+ const pm = /^M(-?[\d.]+),(-?[\d.]+)(?:L(-?[\d.]+),(-?[\d.]+)|Q(-?[\d.]+),(-?[\d.]+) (-?[\d.]+),(-?[\d.]+))$/.exec(
55029
+ lg.d
55030
+ );
55031
+ const pointAt = (t) => {
55032
+ if (pm?.[5] !== void 0) {
55033
+ const u = 1 - t;
55034
+ return [
55035
+ u * u * +pm[1] + 2 * u * t * +pm[5] + t * t * +pm[7],
55036
+ u * u * +pm[2] + 2 * u * t * +pm[6] + t * t * +pm[8]
55037
+ ];
55038
+ }
55039
+ if (pm?.[3] !== void 0)
55040
+ return [
55041
+ +pm[1] + (+pm[3] - +pm[1]) * t,
55042
+ +pm[2] + (+pm[4] - +pm[2]) * t
55043
+ ];
55044
+ return [a.cx + dx * t, a.cy + dy * t];
55045
+ };
54588
55046
  const candidates = [];
54589
55047
  for (const off of [0, perp, -perp, 2 * perp, -2 * perp])
54590
- for (const t of T_LIST)
54591
- candidates.push([
54592
- a.cx + dx * t + nx * off,
54593
- a.cy + dy * t + ny * off - 4
54594
- ]);
55048
+ for (const t of T_LIST) {
55049
+ const [bx, by] = pointAt(t);
55050
+ candidates.push([bx + nx * off, by + ny * off - 4]);
55051
+ }
54595
55052
  const clean = ([cx2, cy2]) => {
54596
55053
  const rect2 = { x: cx2 - w / 2, y: cy2 - labelH / 2, w, h: labelH };
54597
55054
  if (rect2.x < 0 || rect2.x + w > width || rect2.y < 0 || rect2.y + rect2.h > height)
@@ -54601,10 +55058,8 @@ function layoutMap(resolvedIn, data, size, opts) {
54601
55058
  if (placedEdge.some((o) => rectsOverlap(o, rect2))) return false;
54602
55059
  return true;
54603
55060
  };
54604
- const [cx, cy] = candidates.find(clean) ?? [
54605
- a.cx + dx * 0.5,
54606
- a.cy + dy * 0.5 - 4
54607
- ];
55061
+ const mid = pointAt(0.5);
55062
+ const [cx, cy] = candidates.find(clean) ?? [mid[0], mid[1] - 4];
54608
55063
  const style = labelOnFill(fillAt(cx, cy));
54609
55064
  legs[i] = {
54610
55065
  ...lg,
@@ -54632,7 +55087,7 @@ function layoutMap(resolvedIn, data, size, opts) {
54632
55087
  const rawCountries = [];
54633
55088
  for (const f of worldLayer.values()) {
54634
55089
  const iso = typeof f.id === "string" ? f.id : String(f.id ?? "");
54635
- if (!iso || regionById.has(iso)) continue;
55090
+ if (!iso || regionById.has(iso) || labeledRegionIds.has(iso)) continue;
54636
55091
  let hasReferencedSub = false;
54637
55092
  for (const k of regionById.keys())
54638
55093
  if (k.startsWith(iso + "-")) {
@@ -54706,7 +55161,7 @@ function layoutMap(resolvedIn, data, size, opts) {
54706
55161
  );
54707
55162
  if (usLayer && framedStateContainers) {
54708
55163
  for (const [iso, f] of usLayer) {
54709
- if (regionById.has(iso)) continue;
55164
+ if (regionById.has(iso) || labeledRegionIds.has(iso)) continue;
54710
55165
  const viewF = cullFeatureToView(f);
54711
55166
  if (!viewF) continue;
54712
55167
  const b = path.bounds(viewF);
@@ -54730,6 +55185,11 @@ function layoutMap(resolvedIn, data, size, opts) {
54730
55185
  waterBodies: data.waterBodies,
54731
55186
  countries: countryCandidates,
54732
55187
  palette,
55188
+ // One muted, contrast-floored tone shared with the orientation backdrop
55189
+ // (region pass) so a context country (Canada) and an orientation region
55190
+ // (Minnesota) read identically — never below the WCAG-AA floor, no fade.
55191
+ countryTone: backdropLandTone,
55192
+ waterTone: backdropWaterTone,
54733
55193
  project,
54734
55194
  collides,
54735
55195
  contentPoints,
@@ -55284,19 +55744,60 @@ function renderMap(container, resolved, data, palette, isDark, onClickItem, expo
55284
55744
  sel.style("cursor", "pointer").on("click", () => onClickItem(lineNumber));
55285
55745
  };
55286
55746
  const gLegs = svg.append("g").attr("class", "dgmo-map-legs").attr("fill", "none");
55287
- layout.legs.forEach((leg, i) => {
55288
- const p = gLegs.append("path").attr("d", leg.d).attr("stroke", leg.color).attr("stroke-width", leg.width).attr("stroke-linecap", "round").attr("data-from-id", leg.fromId).attr("data-to-id", leg.toId);
55747
+ const legEnd = (d) => {
55748
+ const m = /^M(-?[\d.]+),(-?[\d.]+)(?:L(-?[\d.]+),(-?[\d.]+)|Q(-?[\d.]+),(-?[\d.]+) (-?[\d.]+),(-?[\d.]+))$/.exec(
55749
+ d
55750
+ );
55751
+ if (!m) return null;
55752
+ const x0 = +m[1];
55753
+ const y0 = +m[2];
55754
+ let ex;
55755
+ let ey;
55756
+ let fromX;
55757
+ let fromY;
55758
+ if (m[3] !== void 0) {
55759
+ ex = +m[3];
55760
+ ey = +m[4];
55761
+ fromX = x0;
55762
+ fromY = y0;
55763
+ } else {
55764
+ ex = +m[7];
55765
+ ey = +m[8];
55766
+ fromX = +m[5];
55767
+ fromY = +m[6];
55768
+ }
55769
+ const dx = ex - fromX;
55770
+ const dy = ey - fromY;
55771
+ const len = Math.hypot(dx, dy);
55772
+ if (!(len > 0)) return null;
55773
+ return { ex, ey, tx: dx / len, ty: dy / len };
55774
+ };
55775
+ layout.legs.forEach((leg) => {
55776
+ const end = leg.arrow ? legEnd(leg.d) : null;
55777
+ let drawD = leg.d;
55778
+ let arrowTri = null;
55779
+ if (end) {
55780
+ const aLen = arrowSize(leg.width);
55781
+ const aHalf = Math.max(aLen * 0.5, leg.width * 0.6 + 1);
55782
+ const bx = end.ex - end.tx * aLen;
55783
+ const by = end.ey - end.ty * aLen;
55784
+ const px = -end.ty;
55785
+ const py = end.tx;
55786
+ arrowTri = `M${bx + px * aHalf},${by + py * aHalf}L${end.ex},${end.ey}L${bx - px * aHalf},${by - py * aHalf}z`;
55787
+ drawD = leg.d.replace(
55788
+ /(-?[\d.]+),(-?[\d.]+)$/,
55789
+ `${bx.toFixed(2)},${by.toFixed(2)}`
55790
+ );
55791
+ }
55792
+ const p = gLegs.append("path").attr("d", drawD).attr("stroke", leg.color).attr("stroke-width", leg.width).attr("stroke-linecap", "round").attr("data-from-id", leg.fromId).attr("data-to-id", leg.toId);
55289
55793
  if (leg.tags) {
55290
55794
  for (const [group, value] of Object.entries(leg.tags)) {
55291
55795
  p.attr(`data-tag-${group.toLowerCase()}`, value.toLowerCase());
55292
55796
  }
55293
55797
  }
55294
55798
  wireSync(p, leg.lineNumber);
55295
- if (leg.arrow) {
55296
- const id = nid(`dgmo-map-arrow-${i}`);
55297
- const s = arrowSize(leg.width);
55298
- defs.append("marker").attr("id", id).attr("viewBox", "0 0 10 10").attr("refX", 10).attr("refY", 5).attr("markerUnits", "userSpaceOnUse").attr("markerWidth", s).attr("markerHeight", s).attr("orient", "auto-start-reverse").append("path").attr("d", "M0,0L10,5L0,10z").attr("fill", leg.color);
55299
- p.attr("marker-end", `url(#${id})`);
55799
+ if (arrowTri) {
55800
+ gLegs.append("path").attr("d", arrowTri).attr("fill", leg.color).attr("stroke", leg.color).attr("stroke-width", 1).attr("stroke-linejoin", "round").attr("data-from-id", leg.fromId).attr("data-to-id", leg.toId);
55300
55801
  }
55301
55802
  if (leg.label !== void 0 && leg.labelX !== void 0) {
55302
55803
  const lt = emitText(
@@ -58925,9 +59426,9 @@ var init_renderer20 = __esm({
58925
59426
  }
58926
59427
  });
58927
59428
 
58928
- // node_modules/.pnpm/lz-string@1.5.0/node_modules/lz-string/libs/lz-string.js
59429
+ // ../../diagrammo/dgmo/node_modules/.pnpm/lz-string@1.5.0/node_modules/lz-string/libs/lz-string.js
58929
59430
  var require_lz_string = __commonJS({
58930
- "node_modules/.pnpm/lz-string@1.5.0/node_modules/lz-string/libs/lz-string.js"(exports2, module2) {
59431
+ "../../diagrammo/dgmo/node_modules/.pnpm/lz-string@1.5.0/node_modules/lz-string/libs/lz-string.js"(exports2, module2) {
58931
59432
  "use strict";
58932
59433
  var LZString = (function() {
58933
59434
  var f = String.fromCharCode;
@@ -59452,12 +59953,10 @@ __export(advanced_exports, {
59452
59953
  extractFlowchartSymbols: () => extractSymbols,
59453
59954
  extractInfraSymbols: () => extractSymbols4,
59454
59955
  extractPertSymbols: () => extractPertSymbols,
59455
- findUnsafePipePositions: () => findUnsafePipePositions,
59456
59956
  focusBoxesAndLines: () => focusBoxesAndLines,
59457
59957
  focusOrgTree: () => focusOrgTree,
59458
59958
  formatDateLabel: () => formatDateLabel,
59459
59959
  formatDgmoError: () => formatDgmoError,
59460
- formatLineDiff: () => formatLineDiff,
59461
59960
  getAllChartTypes: () => getAllChartTypes,
59462
59961
  getAvailablePalettes: () => getAvailablePalettes,
59463
59962
  getExtendedChartLegendGroups: () => getExtendedChartLegendGroups,
@@ -59478,7 +59977,6 @@ __export(advanced_exports, {
59478
59977
  isArchiveColumn: () => isArchiveColumn,
59479
59978
  isExtendedChartType: () => isExtendedChartType,
59480
59979
  isInvalidColorToken: () => isInvalidColorToken,
59481
- isLegacyMetadataLine: () => isLegacyMetadataLine,
59482
59980
  isRecognizedColorName: () => isRecognizedColorName,
59483
59981
  isSequenceBlock: () => isSequenceBlock,
59484
59982
  isSequenceNote: () => isSequenceNote,
@@ -59515,7 +60013,6 @@ __export(advanced_exports, {
59515
60013
  mapExportDimensions: () => mapExportDimensions,
59516
60014
  mapNeutralLandColor: () => mapNeutralLandColor,
59517
60015
  measurePertAnalysisBlock: () => measurePertAnalysisBlock,
59518
- migrateContent: () => migrateContent,
59519
60016
  mix: () => mix,
59520
60017
  mulberry32: () => mulberry32,
59521
60018
  nearestNamedColor: () => nearestNamedColor,
@@ -59643,7 +60140,6 @@ __export(advanced_exports, {
59643
60140
  tidewaterPalette: () => tidewaterPalette,
59644
60141
  tint: () => tint,
59645
60142
  tokyoNightPalette: () => tokyoNightPalette,
59646
- transformLine: () => transformLine,
59647
60143
  truncateBareUrl: () => truncateBareUrl,
59648
60144
  validate: () => parseDgmo,
59649
60145
  validateComputed: () => validateComputed,
@@ -65481,320 +65977,6 @@ var themes = {
65481
65977
 
65482
65978
  // src/advanced.ts
65483
65979
  init_dgmo_router();
65484
-
65485
- // src/migrate/index.ts
65486
- var import_node_fs = require("fs");
65487
- var import_node_path = require("path");
65488
- init_dgmo_router();
65489
-
65490
- // src/migrate/line-classifier.ts
65491
- function findUnsafePipePositions(line11) {
65492
- const positions = [];
65493
- let i = 0;
65494
- let inDouble = false;
65495
- let inSingle = false;
65496
- let braceDepth = 0;
65497
- let inArrow = false;
65498
- let arrowOpenChar = null;
65499
- while (i < line11.length) {
65500
- const ch = line11[i];
65501
- const ch2 = i + 1 < line11.length ? line11[i + 1] : "";
65502
- if (inDouble) {
65503
- if (ch === '"') inDouble = false;
65504
- i++;
65505
- continue;
65506
- }
65507
- if (inSingle) {
65508
- if (ch === "'") inSingle = false;
65509
- i++;
65510
- continue;
65511
- }
65512
- if (inArrow) {
65513
- if (arrowOpenChar === "-" && ch === "-" && ch2 === ">" || arrowOpenChar === "~" && ch === "~" && ch2 === ">") {
65514
- inArrow = false;
65515
- arrowOpenChar = null;
65516
- i += 2;
65517
- continue;
65518
- }
65519
- i++;
65520
- continue;
65521
- }
65522
- if (ch === '"') {
65523
- inDouble = true;
65524
- i++;
65525
- continue;
65526
- }
65527
- if (ch === "'") {
65528
- inSingle = true;
65529
- i++;
65530
- continue;
65531
- }
65532
- if (ch === "{") {
65533
- braceDepth++;
65534
- i++;
65535
- continue;
65536
- }
65537
- if (ch === "}") {
65538
- if (braceDepth > 0) braceDepth--;
65539
- i++;
65540
- continue;
65541
- }
65542
- if ((ch === "-" || ch === "~") && i > 0) {
65543
- const prev = line11[i - 1];
65544
- const onBoundary = prev === " " || prev === " ";
65545
- const nextIsLabelChar = ch2 !== "" && ch2 !== ch && ch2 !== ">" && ch2 !== " " && ch2 !== " ";
65546
- if (onBoundary && nextIsLabelChar) {
65547
- const closer = ch + ">";
65548
- const closeIdx = line11.indexOf(closer, i + 1);
65549
- if (closeIdx > i) {
65550
- const labelRegion = line11.substring(i + 1, closeIdx);
65551
- if (!labelRegion.includes("->") && !labelRegion.includes("~>")) {
65552
- inArrow = true;
65553
- arrowOpenChar = ch;
65554
- i++;
65555
- continue;
65556
- }
65557
- }
65558
- }
65559
- }
65560
- if (ch === "|" && braceDepth === 0) {
65561
- positions.push(i);
65562
- }
65563
- i++;
65564
- }
65565
- return positions;
65566
- }
65567
- function isLegacyMetadataLine(line11) {
65568
- return findUnsafePipePositions(line11).length > 0;
65569
- }
65570
-
65571
- // src/migrate/line-transformer.ts
65572
- function replaceUnsafePipesWithCommas(s) {
65573
- const positions = findUnsafePipePositions(s);
65574
- if (positions.length === 0) return s;
65575
- const chars = s.split("");
65576
- for (const pos of positions) {
65577
- chars[pos] = ",";
65578
- }
65579
- return chars.join("");
65580
- }
65581
- var GANTT_PERCENT = {
65582
- match: (t) => /^\d+(?:\.\d+)?\s*%$/.test(t.trim()),
65583
- promote: (t) => `progress: ${t.trim().replace(/\s*%$/, "")}`
65584
- };
65585
- var JOURNEY_SCORE = {
65586
- match: (t) => /^\d+(?:\s+[A-Za-z][A-Za-z0-9_-]*)?$/.test(t.trim()),
65587
- promote: (t) => {
65588
- const m = t.trim().match(/^(\d+)(?:\s+([A-Za-z][A-Za-z0-9_-]*))?$/);
65589
- if (!m) return t;
65590
- return m[2] ? `score: ${m[1]}, emotion: ${m[2]}` : `score: ${m[1]}`;
65591
- }
65592
- };
65593
- var PROMOTIONS_BY_CHART = {
65594
- gantt: [GANTT_PERCENT],
65595
- "journey-map": [JOURNEY_SCORE]
65596
- };
65597
- var BARE_DESCRIPTION_CHARTS = /* @__PURE__ */ new Set(["pyramid", "ring"]);
65598
- function tokenizeMetaRegion(region) {
65599
- const tokens = [];
65600
- let i = 0;
65601
- let buf = "";
65602
- let inQuote = null;
65603
- while (i < region.length) {
65604
- const ch = region[i];
65605
- if (inQuote) {
65606
- buf += ch;
65607
- if (ch === inQuote) inQuote = null;
65608
- i++;
65609
- continue;
65610
- }
65611
- if (ch === '"' || ch === "'") {
65612
- inQuote = ch;
65613
- buf += ch;
65614
- i++;
65615
- continue;
65616
- }
65617
- if (ch === ",") {
65618
- const t = buf.trim();
65619
- if (t) tokens.push(t);
65620
- buf = "";
65621
- i++;
65622
- continue;
65623
- }
65624
- buf += ch;
65625
- i++;
65626
- }
65627
- const tail = buf.trim();
65628
- if (tail) tokens.push(tail);
65629
- return tokens;
65630
- }
65631
- function transformLine(line11, chartType) {
65632
- const indentMatch = line11.match(/^[ \t]*/);
65633
- const indent = indentMatch ? indentMatch[0] : "";
65634
- const body = line11.substring(indent.length);
65635
- const pipePositions = findUnsafePipePositions(body);
65636
- if (pipePositions.length === 0) {
65637
- return { line: line11, changed: false };
65638
- }
65639
- const cut = pipePositions[0];
65640
- const nameRegion = body.substring(0, cut).trimEnd();
65641
- let metaRegion = body.substring(cut + 1).trimStart();
65642
- if (pipePositions.length > 1) {
65643
- metaRegion = replaceUnsafePipesWithCommas(metaRegion);
65644
- }
65645
- if (chartType !== null && BARE_DESCRIPTION_CHARTS.has(chartType) && !metaRegion.includes(":")) {
65646
- const text = metaRegion.trim();
65647
- if (!text) {
65648
- return {
65649
- line: indent + nameRegion,
65650
- changed: true
65651
- };
65652
- }
65653
- const value = text.includes(",") ? `"${text}"` : text;
65654
- return {
65655
- line: `${indent}${nameRegion} description: ${value}`,
65656
- changed: true
65657
- };
65658
- }
65659
- const tokens = tokenizeMetaRegion(metaRegion);
65660
- const promotions = chartType ? PROMOTIONS_BY_CHART[chartType] ?? [] : [];
65661
- const emitted = [];
65662
- for (const raw of tokens) {
65663
- if (raw.includes(":")) {
65664
- emitted.push(raw);
65665
- continue;
65666
- }
65667
- const promo = promotions.find((p) => p.match(raw));
65668
- if (promo) {
65669
- emitted.push(promo.promote(raw));
65670
- continue;
65671
- }
65672
- emitted.push(raw);
65673
- }
65674
- if (emitted.length === 0) {
65675
- return {
65676
- line: indent + nameRegion,
65677
- changed: true
65678
- };
65679
- }
65680
- return {
65681
- line: `${indent}${nameRegion} ${emitted.join(", ")}`,
65682
- changed: true
65683
- };
65684
- }
65685
-
65686
- // src/migrate/data-comma.ts
65687
- init_colors();
65688
- var DATA_COMMA_CHART_TYPES = /* @__PURE__ */ new Set([
65689
- // Simple charts (chart.ts)
65690
- "bar",
65691
- "line",
65692
- "multi-line",
65693
- "pie",
65694
- "doughnut",
65695
- "area",
65696
- "polar-area",
65697
- "radar",
65698
- "bar-stacked",
65699
- // Extended charts (echarts.ts)
65700
- "scatter",
65701
- "heatmap",
65702
- "sankey",
65703
- "chord",
65704
- "funnel",
65705
- // Visualizations (d3 / visualizations/parse.ts)
65706
- "quadrant",
65707
- "slope",
65708
- "arc",
65709
- "wordcloud"
65710
- ]);
65711
- var COLOR_SET = new Set(RECOGNIZED_COLOR_NAMES);
65712
- var NUMERIC_VALUE_RE = /^-?\d[\d,_]*(?:\.\d+)?,?$/;
65713
- function isNumericValueToken(tok) {
65714
- if (!NUMERIC_VALUE_RE.test(tok)) return false;
65715
- return /\d/.test(tok);
65716
- }
65717
- function migrateDataRowCommaLine(line11) {
65718
- const indentMatch = line11.match(/^[ \t]*/);
65719
- const indent = indentMatch ? indentMatch[0] : "";
65720
- const body = line11.substring(indent.length);
65721
- if (!body || body.startsWith("//")) return { line: line11, changed: false };
65722
- const tokens = body.split(/\s+/);
65723
- if (tokens.length < 2) return { line: line11, changed: false };
65724
- let i = tokens.length - 1;
65725
- if (i >= 1 && COLOR_SET.has(tokens[i])) {
65726
- i--;
65727
- }
65728
- const valueStart = (() => {
65729
- let j = i;
65730
- while (j >= 1 && isNumericValueToken(tokens[j])) j--;
65731
- return j + 1;
65732
- })();
65733
- if (valueStart > i || valueStart < 1) return { line: line11, changed: false };
65734
- const valueTokens = tokens.slice(valueStart, i + 1);
65735
- const hasComma = valueTokens.some((t) => t.includes(","));
65736
- if (!hasComma) return { line: line11, changed: false };
65737
- const cleaned = valueTokens.map((t) => t.replace(/,/g, ""));
65738
- const labelPart = tokens.slice(0, valueStart);
65739
- const tail = tokens.slice(i + 1);
65740
- const rebuiltBody = [...labelPart, ...cleaned, ...tail].join(" ");
65741
- return { line: indent + rebuiltBody, changed: rebuiltBody !== body };
65742
- }
65743
-
65744
- // src/migrate/index.ts
65745
- function migrateContent(source) {
65746
- const chartType = parseDgmoChartType(source);
65747
- const lines = source.split("\n");
65748
- const out = [];
65749
- const changedLines = [];
65750
- let changed = false;
65751
- const migrateDataCommas = chartType !== null && DATA_COMMA_CHART_TYPES.has(chartType);
65752
- let firstContentSeen = false;
65753
- for (let i = 0; i < lines.length; i++) {
65754
- let line11 = lines[i];
65755
- const trimmed = line11.trim();
65756
- if (migrateDataCommas && firstContentSeen && trimmed && !trimmed.startsWith("//")) {
65757
- const dc = migrateDataRowCommaLine(line11);
65758
- if (dc.changed) {
65759
- line11 = dc.line;
65760
- changed = true;
65761
- changedLines.push(i + 1);
65762
- }
65763
- }
65764
- if (trimmed && !trimmed.startsWith("//")) firstContentSeen = true;
65765
- if (!isLegacyMetadataLine(line11)) {
65766
- out.push(line11);
65767
- continue;
65768
- }
65769
- const result = transformLine(line11, chartType);
65770
- if (result.changed) {
65771
- changed = true;
65772
- if (!changedLines.includes(i + 1)) changedLines.push(i + 1);
65773
- }
65774
- out.push(result.line);
65775
- }
65776
- return {
65777
- migrated: out.join("\n"),
65778
- changed,
65779
- chartType,
65780
- changedLines
65781
- };
65782
- }
65783
- function formatLineDiff(path, original, migrated) {
65784
- const origLines = original.split("\n");
65785
- const newLines = migrated.split("\n");
65786
- const lines = [`--- ${path}`, `+++ ${path}`];
65787
- const max = Math.max(origLines.length, newLines.length);
65788
- for (let i = 0; i < max; i++) {
65789
- const a = origLines[i];
65790
- const b = newLines[i];
65791
- if (a === b) continue;
65792
- if (a !== void 0) lines.push(`@@ line ${i + 1}
65793
- - ${a}`);
65794
- if (b !== void 0) lines.push(`+ ${b}`);
65795
- }
65796
- return lines.join("\n");
65797
- }
65798
65980
  // Annotate the CommonJS export names for ESM import in node:
65799
65981
  0 && (module.exports = {
65800
65982
  ALL_CHART_TYPES,
@@ -65864,12 +66046,10 @@ function formatLineDiff(path, original, migrated) {
65864
66046
  extractFlowchartSymbols,
65865
66047
  extractInfraSymbols,
65866
66048
  extractPertSymbols,
65867
- findUnsafePipePositions,
65868
66049
  focusBoxesAndLines,
65869
66050
  focusOrgTree,
65870
66051
  formatDateLabel,
65871
66052
  formatDgmoError,
65872
- formatLineDiff,
65873
66053
  getAllChartTypes,
65874
66054
  getAvailablePalettes,
65875
66055
  getExtendedChartLegendGroups,
@@ -65890,7 +66070,6 @@ function formatLineDiff(path, original, migrated) {
65890
66070
  isArchiveColumn,
65891
66071
  isExtendedChartType,
65892
66072
  isInvalidColorToken,
65893
- isLegacyMetadataLine,
65894
66073
  isRecognizedColorName,
65895
66074
  isSequenceBlock,
65896
66075
  isSequenceNote,
@@ -65927,7 +66106,6 @@ function formatLineDiff(path, original, migrated) {
65927
66106
  mapExportDimensions,
65928
66107
  mapNeutralLandColor,
65929
66108
  measurePertAnalysisBlock,
65930
- migrateContent,
65931
66109
  mix,
65932
66110
  mulberry32,
65933
66111
  nearestNamedColor,
@@ -66055,7 +66233,6 @@ function formatLineDiff(path, original, migrated) {
66055
66233
  tidewaterPalette,
66056
66234
  tint,
66057
66235
  tokyoNightPalette,
66058
- transformLine,
66059
66236
  truncateBareUrl,
66060
66237
  validate,
66061
66238
  validateComputed,