@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.js CHANGED
@@ -35,6 +35,17 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
35
35
  function makeDgmoError(line11, message, severity = "error", code) {
36
36
  return code !== void 0 ? { line: line11, message, severity, code } : { line: line11, message, severity };
37
37
  }
38
+ function dedupeDiagnostics(diagnostics) {
39
+ const seen = /* @__PURE__ */ new Set();
40
+ const out = [];
41
+ for (const d of diagnostics) {
42
+ const key = `${d.line}\0${d.column ?? ""}\0${d.severity}\0${d.code ?? ""}\0${d.message}`;
43
+ if (seen.has(key)) continue;
44
+ seen.add(key);
45
+ out.push(d);
46
+ }
47
+ return out;
48
+ }
38
49
  function formatDgmoError(err) {
39
50
  return err.line > 0 ? `Line ${err.line}: ${err.message}` : err.message;
40
51
  }
@@ -92,7 +103,7 @@ function vennAliasKeywordRemovedMessage(args) {
92
103
  return `Venn 'alias' keyword was removed. Use 'as' instead \u2014 '${args.name} as ${args.alias}'.`;
93
104
  }
94
105
  function pipeOperatorRemovedMessage() {
95
- return `'|' removed \u2014 'Node | c: red' \u2192 'Node c: red'. Run 'dgmo migrate'`;
106
+ return `'|' removed \u2014 'Node | c: red' \u2192 'Node c: red'`;
96
107
  }
97
108
  function ganttBarePercentRemovedMessage(percent) {
98
109
  return `Bare-percent progress shorthand '| ${percent}' was removed. Use 'progress: ${percent.replace(/%$/, "")}' (integer 0\u2013100, no '%').`;
@@ -1245,7 +1256,7 @@ var init_parsing = __esm({
1245
1256
  "no-title",
1246
1257
  "no-notes"
1247
1258
  ]);
1248
- MULTIPLE_PIPE_ERROR = 'Use a single "|" to start metadata, then separate items with commas.';
1259
+ 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.";
1249
1260
  RESERVED_KEY_RE = /^[A-Za-z][A-Za-z0-9_-]*$/;
1250
1261
  AS_ALIAS_RE = /\s+as\s+([A-Za-z][A-Za-z0-9_]{0,11})$/;
1251
1262
  POTENTIAL_META_RE = /\b([\w-]+)\s*:/;
@@ -1426,12 +1437,10 @@ function validateTagValues(entities, tagGroups, pushWarning, suggestFn) {
1426
1437
  );
1427
1438
  if (!isPrefix) {
1428
1439
  const defined = group.entries.map((e) => e.value);
1429
- let msg = `Unknown value '${value}' for tag group '${group.name}'`;
1440
+ let msg = `Unknown value '${value}' for tag group '${group.name}' \u2014 use one of: ${defined.join(", ")}`;
1430
1441
  const hint = suggestFn?.(value, defined);
1431
1442
  if (hint) {
1432
1443
  msg += `. ${hint}`;
1433
- } else {
1434
- msg += ` \u2014 defined values: ${defined.join(", ")}`;
1435
1444
  }
1436
1445
  pushWarning(entity.lineNumber, msg);
1437
1446
  }
@@ -2207,7 +2216,10 @@ function parseVisualizationFull(content, palette) {
2207
2216
  lineNumber
2208
2217
  });
2209
2218
  } else {
2210
- warn(lineNumber, `Unrecognized era entry: "${line11}"`);
2219
+ warn(
2220
+ lineNumber,
2221
+ `Unrecognized era entry: "${line11}" \u2014 use '<start> -> <end> Label [color]' (e.g. '1716 -> 1717 Reign').`
2222
+ );
2211
2223
  }
2212
2224
  continue;
2213
2225
  }
@@ -2235,7 +2247,10 @@ function parseVisualizationFull(content, palette) {
2235
2247
  lineNumber
2236
2248
  });
2237
2249
  } else {
2238
- warn(lineNumber, `Unrecognized marker entry: "${line11}"`);
2250
+ warn(
2251
+ lineNumber,
2252
+ `Unrecognized marker entry: "${line11}" \u2014 use '<date> Label [color]' (e.g. '1776 Independence').`
2253
+ );
2239
2254
  }
2240
2255
  continue;
2241
2256
  }
@@ -2913,7 +2928,7 @@ function parseVisualizationFull(content, palette) {
2913
2928
  result.diagnostics.push(
2914
2929
  makeDgmoError(
2915
2930
  ov.lineNumber,
2916
- `Intersection references unknown set or alias "${ref}"`
2931
+ `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)`
2917
2932
  )
2918
2933
  );
2919
2934
  if (!result.error)
@@ -6296,10 +6311,10 @@ function parseSequenceDgmo(content, palette) {
6296
6311
  const rawInside = fallbackMatch[1];
6297
6312
  const pipeIdx = rawInside.indexOf("|");
6298
6313
  const cleanName = rawInside.substring(0, pipeIdx).trim().replace(/\([^)]*\)$/, "").trim();
6299
- const metaPart = rawInside.substring(pipeIdx).trim();
6314
+ const metaPart = rawInside.substring(pipeIdx).replace(/^\|\s*/, "").trim();
6300
6315
  pushError(
6301
6316
  lineNumber,
6302
- `Pipe metadata must go outside brackets \u2014 use '[${cleanName}] ${metaPart}' instead of '[${rawInside.trim()}]'`
6317
+ `Metadata goes after the bracket with no pipe \u2014 use '[${cleanName}] ${metaPart}' (e.g. '[Backend] t: Product'), not '[${rawInside.trim()}]'`
6303
6318
  );
6304
6319
  continue;
6305
6320
  }
@@ -6909,7 +6924,7 @@ function parseSequenceDgmo(content, palette) {
6909
6924
  }
6910
6925
  pushWarning(
6911
6926
  lineNumber,
6912
- `Unexpected line: '${trimmed}'. Expected a message (A -> B), participant, section (== Name ==), or block keyword (if/loop/parallel).`
6927
+ `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>').`
6913
6928
  );
6914
6929
  }
6915
6930
  if (!hasExplicitChart && result.messages.length === 0) {
@@ -8071,7 +8086,11 @@ function parseFlowchart(content, palette) {
8071
8086
  const value = optMatch[2].trim();
8072
8087
  if (key === "no-color") {
8073
8088
  result.diagnostics.push(
8074
- makeDgmoError(lineNumber, '"no-color" has been removed.', "warning")
8089
+ makeDgmoError(
8090
+ lineNumber,
8091
+ '"no-color" was removed \u2014 node colors are assigned automatically by shape; delete this line.',
8092
+ "warning"
8093
+ )
8075
8094
  );
8076
8095
  continue;
8077
8096
  }
@@ -8400,7 +8419,11 @@ function parseState(content, palette) {
8400
8419
  const value = optMatch[2].trim();
8401
8420
  if (key === "no-color") {
8402
8421
  result.diagnostics.push(
8403
- makeDgmoError(lineNumber, '"no-color" has been removed.', "warning")
8422
+ makeDgmoError(
8423
+ lineNumber,
8424
+ '"no-color" was removed \u2014 state colors are assigned automatically; delete this line.',
8425
+ "warning"
8426
+ )
8404
8427
  );
8405
8428
  continue;
8406
8429
  }
@@ -8715,7 +8738,7 @@ function parseClassDiagram(content, palette) {
8715
8738
  result.diagnostics.push(
8716
8739
  makeDgmoError(
8717
8740
  lineNumber,
8718
- '"no-auto-color" has been removed.',
8741
+ '"no-auto-color" was removed \u2014 colors are always auto-assigned from the palette; delete this line.',
8719
8742
  "warning"
8720
8743
  )
8721
8744
  );
@@ -8830,7 +8853,11 @@ function parseClassDiagram(content, palette) {
8830
8853
  continue;
8831
8854
  }
8832
8855
  result.diagnostics.push(
8833
- makeDgmoError(lineNumber, `Unexpected line: '${trimmed}'.`, "warning")
8856
+ makeDgmoError(
8857
+ lineNumber,
8858
+ `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)`,
8859
+ "warning"
8860
+ )
8834
8861
  );
8835
8862
  }
8836
8863
  if (notes.length > 0) {
@@ -9308,7 +9335,11 @@ function parseERDiagram(content, palette) {
9308
9335
  continue;
9309
9336
  }
9310
9337
  result.diagnostics.push(
9311
- makeDgmoError(lineNumber, `Unexpected line: '${trimmed}'.`, "warning")
9338
+ makeDgmoError(
9339
+ lineNumber,
9340
+ `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)`,
9341
+ "warning"
9342
+ )
9312
9343
  );
9313
9344
  }
9314
9345
  if (result.tables.length === 0 && !result.error) {
@@ -9652,7 +9683,10 @@ function parseOrg(content, palette) {
9652
9683
  const value = metadataMatch[2].trim();
9653
9684
  const parent = findMetadataParent(indent, indentStack);
9654
9685
  if (!parent) {
9655
- pushError(lineNumber, "Metadata has no parent node");
9686
+ pushError(
9687
+ lineNumber,
9688
+ "Metadata 'key: value' must be indented under a node \u2014 no node precedes it."
9689
+ );
9656
9690
  } else {
9657
9691
  parent.metadata = { ...parent.metadata, [key]: value };
9658
9692
  }
@@ -9670,7 +9704,10 @@ function parseOrg(content, palette) {
9670
9704
  );
9671
9705
  attachNode(node, indent, indentStack, result);
9672
9706
  } else {
9673
- pushError(lineNumber, "Metadata has no parent node");
9707
+ pushError(
9708
+ lineNumber,
9709
+ "Metadata 'key: value' must be indented under a node \u2014 no node precedes it."
9710
+ );
9674
9711
  }
9675
9712
  } else {
9676
9713
  const node = parseNodeLabel(
@@ -10019,7 +10056,10 @@ function parseSitemap(content, palette) {
10019
10056
  if (arrowInfo) {
10020
10057
  const source = findParentNode(indent, indentStack);
10021
10058
  if (!source) {
10022
- pushError(lineNumber, "Arrow has no source node");
10059
+ pushError(
10060
+ lineNumber,
10061
+ "Arrow '-> target' must be indented under its source page \u2014 no page precedes it."
10062
+ );
10023
10063
  } else {
10024
10064
  deferredArrows.push({
10025
10065
  sourceNode: source,
@@ -10089,7 +10129,10 @@ function parseSitemap(content, palette) {
10089
10129
  const value = metadataMatch[2].trim();
10090
10130
  const parent = findParentNode(indent, indentStack);
10091
10131
  if (!parent) {
10092
- pushError(lineNumber, "Metadata has no parent node");
10132
+ pushError(
10133
+ lineNumber,
10134
+ "Metadata 'key: value' must be indented under a page \u2014 no page precedes it."
10135
+ );
10093
10136
  } else {
10094
10137
  parent.metadata = { ...parent.metadata, [key]: value };
10095
10138
  }
@@ -10109,7 +10152,10 @@ function parseSitemap(content, palette) {
10109
10152
  const key = normalizeName(node.label);
10110
10153
  labelToNode.set(key, node);
10111
10154
  } else {
10112
- pushError(lineNumber, "Metadata has no parent node");
10155
+ pushError(
10156
+ lineNumber,
10157
+ "Metadata 'key: value' must be indented under a page \u2014 no page precedes it."
10158
+ );
10113
10159
  }
10114
10160
  } else {
10115
10161
  const descResult = tryStripDescriptionKeyword(trimmed);
@@ -11927,7 +11973,7 @@ function parseChart(content, palette) {
11927
11973
  result.diagnostics.push(
11928
11974
  makeDgmoError(
11929
11975
  lineNumber,
11930
- `'${trimmed}' \u2014 ## syntax is no longer supported. Use [Group] containers instead`
11976
+ `'${trimmed}' removed \u2014 use a bracket container '[${trimmed.replace(/^#+\s*/, "")}]' instead (## headers are no longer supported).`
11931
11977
  )
11932
11978
  );
11933
11979
  continue;
@@ -12919,7 +12965,11 @@ function parseExtendedChartFull(content, palette) {
12919
12965
  continue;
12920
12966
  }
12921
12967
  result.diagnostics.push(
12922
- makeDgmoError(lineNumber, `Unexpected line: '${trimmed}'.`, "warning")
12968
+ makeDgmoError(
12969
+ lineNumber,
12970
+ `Unexpected line: '${trimmed}'. Expected a data row ('Label value', space-separated) or a known option.`,
12971
+ "warning"
12972
+ )
12923
12973
  );
12924
12974
  }
12925
12975
  const warn = (line11, message) => {
@@ -15277,7 +15327,10 @@ function parseKanban(content, palette) {
15277
15327
  continue;
15278
15328
  }
15279
15329
  if (!currentColumn) {
15280
- warn(lineNumber, "Card line found before any column");
15330
+ warn(
15331
+ lineNumber,
15332
+ "Card line found before any column. Declare a column first with '[Column Name]', then indent cards beneath it. (\xA711)"
15333
+ );
15281
15334
  continue;
15282
15335
  }
15283
15336
  if (currentCard && indent > cardBaseIndent) {
@@ -15416,7 +15469,7 @@ var init_parser13 = __esm({
15416
15469
  KNOWN_OPTIONS4 = /* @__PURE__ */ new Set(["hide", "active-tag"]);
15417
15470
  KNOWN_BOOLEANS3 = /* @__PURE__ */ new Set(["solid-fill", "no-title"]);
15418
15471
  REMOVED_BOOLEANS = {
15419
- "no-auto-color": '"no-auto-color" has been removed.'
15472
+ "no-auto-color": '"no-auto-color" was removed \u2014 colors are always auto-assigned from the palette; delete this line.'
15420
15473
  };
15421
15474
  }
15422
15475
  });
@@ -15653,7 +15706,7 @@ function parseC4(content, palette) {
15653
15706
  } else {
15654
15707
  pushError(
15655
15708
  lineNumber,
15656
- `"container ${refName}" must be inside a deployment node`
15709
+ `"container ${refName}" must be indented under a deployment node (e.g. under 'Prod is a deployment-node')`
15657
15710
  );
15658
15711
  }
15659
15712
  continue;
@@ -15708,7 +15761,10 @@ function parseC4(content, palette) {
15708
15761
  indent
15709
15762
  });
15710
15763
  } else {
15711
- pushError(lineNumber, `"${sectionType}" must be inside an element`);
15764
+ pushError(
15765
+ lineNumber,
15766
+ `"${sectionType}" must be indented under an element (e.g. under 'Web App is a container')`
15767
+ );
15712
15768
  }
15713
15769
  continue;
15714
15770
  }
@@ -15735,7 +15791,10 @@ function parseC4(content, palette) {
15735
15791
  indent
15736
15792
  });
15737
15793
  } else {
15738
- pushError(lineNumber, `Group [${groupName}] must be inside an element`);
15794
+ pushError(
15795
+ lineNumber,
15796
+ `Group [${groupName}] must be indented under an element (e.g. under 'Web App is a container')`
15797
+ );
15739
15798
  }
15740
15799
  continue;
15741
15800
  }
@@ -16065,7 +16124,10 @@ function parseC4(content, palette) {
16065
16124
  }
16066
16125
  desc.push(descResult.text);
16067
16126
  } else {
16068
- pushError(lineNumber, `Unexpected content: "${trimmed}"`);
16127
+ pushError(
16128
+ lineNumber,
16129
+ `Unexpected content: "${trimmed}". Expected an element ('Web App is a container'), a relationship ('-Uses-> Database'), indented metadata ('tech: React'), or a group ('[Backend]'). (\xA78)`
16130
+ );
16069
16131
  }
16070
16132
  }
16071
16133
  finalizeAutoTagColors(result.tagGroups, palette);
@@ -16127,7 +16189,7 @@ function validateRelationshipTargets(result, knownNames, pushWarning) {
16127
16189
  for (const el of elements) {
16128
16190
  for (const rel of el.relationships) {
16129
16191
  if (!knownNames.has(normalizeName(rel.target))) {
16130
- let msg = `Relationship target "${rel.target}" not found`;
16192
+ 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`;
16131
16193
  const hint = suggest(normalizeName(rel.target), allNames);
16132
16194
  if (hint) msg += `. ${hint}`;
16133
16195
  pushWarning(rel.lineNumber, msg, "warning");
@@ -16155,7 +16217,7 @@ function validateDeploymentRefs(result, knownNames, pushWarning) {
16155
16217
  for (const node of nodes) {
16156
16218
  for (const ref of node.containerRefs) {
16157
16219
  if (!knownNames.has(normalizeName(ref))) {
16158
- let msg = `Deployment reference "container ${ref}" not found`;
16220
+ let msg = `Deployment reference "container ${ref}" not found \u2014 declare the container element first, then reference it under the deployment node`;
16159
16221
  const hint = suggest(normalizeName(ref), allNames);
16160
16222
  if (hint) msg += `. ${hint}`;
16161
16223
  pushWarning(node.lineNumber, msg, "warning");
@@ -16874,7 +16936,7 @@ function parseInfra(content) {
16874
16936
  }
16875
16937
  warn(
16876
16938
  lineNumber,
16877
- `Invalid tag value '${trimmed}' in tag group '${currentTagGroup.name}'.`
16939
+ `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 '|'.`
16878
16940
  );
16879
16941
  continue;
16880
16942
  }
@@ -16947,7 +17009,7 @@ function parseInfra(content) {
16947
17009
  const n = deprecatedFanout[1];
16948
17010
  setError(
16949
17011
  lineNumber,
16950
- `'x${n}' fanout syntax is no longer supported \u2014 use '| fanout: ${n}' instead`
17012
+ `'x${n}' fanout syntax is no longer supported \u2014 use 'fanout: ${n}' instead`
16951
17013
  );
16952
17014
  continue;
16953
17015
  }
@@ -17121,8 +17183,10 @@ function parseInfra(content) {
17121
17183
  const hint = suggest(key, allKeys);
17122
17184
  const valueLooksNumeric = /^[\d.]+%?$/.test(rawVal);
17123
17185
  if (hint || valueLooksNumeric) {
17124
- let msg = `Unknown property '${key}'.`;
17186
+ let msg = `Unknown infra property '${key}'.`;
17125
17187
  if (hint) msg += ` ${hint}`;
17188
+ else
17189
+ msg += ` Properties are colon-keyed 'key: value' from the known schema (e.g. latency-ms, max-rps, cache-hit, instances, concurrency).`;
17126
17190
  warn(lineNumber, msg);
17127
17191
  } else if (!currentNode.isEdge) {
17128
17192
  pushDescription(currentNode, trimmed);
@@ -17133,7 +17197,7 @@ function parseInfra(content) {
17133
17197
  if (EDGE_ONLY_KEYS.has(key) && !currentNode.isEdge) {
17134
17198
  warn(
17135
17199
  lineNumber,
17136
- `Property '${key}' is only valid on the entry point (Edge/Internet).`
17200
+ `Property '${key}' belongs on the entry point \u2014 declare it indented under an 'internet' or 'edge' node, not on '${currentNode.label}'.`
17137
17201
  );
17138
17202
  }
17139
17203
  const value = parsePropertyValue(rawVal);
@@ -17240,7 +17304,7 @@ function parseInfra(content) {
17240
17304
  }
17241
17305
  warn(
17242
17306
  lineNumber,
17243
- currentGroup ? `Unexpected line: '${trimmed}'. Expected an indented component name.` : `Unexpected line: '${trimmed}'. Expected a component name, [Group Name], tag group, or option.`
17307
+ 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'.`
17244
17308
  );
17245
17309
  }
17246
17310
  finishCurrentNode();
@@ -17616,7 +17680,10 @@ function parseGantt(content, palette) {
17616
17680
  continue;
17617
17681
  }
17618
17682
  if (COMMENT_RE.test(line11)) continue;
17619
- warn(lineNumber, `Unrecognized holiday entry: "${line11}"`);
17683
+ warn(
17684
+ lineNumber,
17685
+ `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)`
17686
+ );
17620
17687
  continue;
17621
17688
  }
17622
17689
  }
@@ -17642,7 +17709,10 @@ function parseGantt(content, palette) {
17642
17709
  lineNumber
17643
17710
  });
17644
17711
  } else {
17645
- warn(lineNumber, `Unrecognized era entry: "${line11}"`);
17712
+ warn(
17713
+ lineNumber,
17714
+ `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)`
17715
+ );
17646
17716
  }
17647
17717
  continue;
17648
17718
  }
@@ -17668,7 +17738,10 @@ function parseGantt(content, palette) {
17668
17738
  lineNumber
17669
17739
  });
17670
17740
  } else {
17671
- warn(lineNumber, `Unrecognized marker entry: "${line11}"`);
17741
+ warn(
17742
+ lineNumber,
17743
+ `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)`
17744
+ );
17672
17745
  }
17673
17746
  continue;
17674
17747
  }
@@ -17834,7 +17907,7 @@ function parseGantt(content, palette) {
17834
17907
  } else {
17835
17908
  softError(
17836
17909
  lineNumber,
17837
- `Arrow reference "${content2.name}" has no parent task context.`
17910
+ `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)`
17838
17911
  );
17839
17912
  }
17840
17913
  }
@@ -19054,7 +19127,10 @@ function parseMap(content, palette) {
19054
19127
  function handleTag(trimmed, line11) {
19055
19128
  const m = matchTagBlockHeading(trimmed);
19056
19129
  if (!m) {
19057
- pushError(line11, `Malformed tag declaration: "${trimmed}".`);
19130
+ pushError(
19131
+ line11,
19132
+ `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.`
19133
+ );
19058
19134
  return;
19059
19135
  }
19060
19136
  emitTagLegacyDiagnostic(m, line11, diagnostics);
@@ -19166,7 +19242,7 @@ function parseMap(content, palette) {
19166
19242
  if (!pos || pos.kind === "name" && !pos.name) {
19167
19243
  pushError(
19168
19244
  line11,
19169
- "route requires an origin: `route <origin> [style: arc]`."
19245
+ "route needs an origin place \u2014 `route <origin>` then indented legs (`-> dest`); origin may be a city name, IATA code, or `<lat> <lon>`. (\xA724B)"
19170
19246
  );
19171
19247
  return;
19172
19248
  }
@@ -19229,7 +19305,10 @@ function parseMap(content, palette) {
19229
19305
  function handleEdges(trimmed, line11) {
19230
19306
  const parts = trimmed.split(ARROW_SPLIT);
19231
19307
  if (parts.length < 3) {
19232
- pushError(line11, `Malformed edge: "${trimmed}".`);
19308
+ pushError(
19309
+ line11,
19310
+ `Malformed edge: "${trimmed}" \u2014 an edge is 'A -> B' (or 'A -label-> B', '~>' for arc, '--' undirected), both endpoints non-empty. (\xA724B)`
19311
+ );
19233
19312
  return;
19234
19313
  }
19235
19314
  const endpoints = [];
@@ -19252,7 +19331,10 @@ function parseMap(content, palette) {
19252
19331
  const from = endpoints[k];
19253
19332
  const to = endpoints[k + 1];
19254
19333
  if (!from || !to) {
19255
- pushError(line11, `Edge has an empty endpoint: "${trimmed}".`);
19334
+ pushError(
19335
+ line11,
19336
+ `Edge has an empty endpoint: "${trimmed}" \u2014 both sides of the arrow need a place, e.g. 'Paris -> Berlin'. (\xA724B)`
19337
+ );
19256
19338
  continue;
19257
19339
  }
19258
19340
  const isLast = k === links.length - 1;
@@ -21371,7 +21453,10 @@ function parseTechRadar(content) {
21371
21453
  }
21372
21454
  if (indent === 0 && trimmed.toLowerCase() === "rings") {
21373
21455
  if (result.rings.length > 0) {
21374
- warn(lineNumber, 'Duplicate "rings" block \u2014 using last one');
21456
+ warn(
21457
+ lineNumber,
21458
+ 'Duplicate "rings" block \u2014 only the last is kept; merge all ring names into one "rings" block.'
21459
+ );
21375
21460
  result.rings = [];
21376
21461
  }
21377
21462
  inRingsBlock = true;
@@ -21590,7 +21675,7 @@ function parseTechRadar(content) {
21590
21675
  result.diagnostics.push(
21591
21676
  makeDgmoError(
21592
21677
  lineNumber,
21593
- `Blip "${trimmed}" has no ring assignment. Place it under a ring section header or use: ${trimmed} | ring: RingName`
21678
+ `Blip "${trimmed}" has no ring. Put it under a ring section header, or add a colon-keyed ring \u2014 '${trimmed} ring: RingName' (per \xA720).`
21594
21679
  )
21595
21680
  );
21596
21681
  continue;
@@ -21599,7 +21684,7 @@ function parseTechRadar(content) {
21599
21684
  if (!trimmed.includes("|") && headerParsed && result.rings.length > 0) {
21600
21685
  warn(
21601
21686
  lineNumber,
21602
- `Unrecognized line "${trimmed}". Quadrant headers require pipe metadata: ${trimmed} | quadrant: top-left`
21687
+ `Unrecognized line "${trimmed}". A quadrant header is 'Name quadrant: <top-left|top-right|bottom-left|bottom-right>' (per \xA720).`
21603
21688
  );
21604
21689
  }
21605
21690
  }
@@ -21892,7 +21977,10 @@ function parseCycle(content) {
21892
21977
  currentNode.description.push(descLine);
21893
21978
  continue;
21894
21979
  }
21895
- warn(lineNum, `Unexpected indented line: "${trimmed}".`);
21980
+ warn(
21981
+ lineNum,
21982
+ `Indented line "${trimmed}" has no step above it \u2014 add a step on a non-indented line first, then indent its details. (\xA721)`
21983
+ );
21896
21984
  continue;
21897
21985
  }
21898
21986
  }
@@ -22195,7 +22283,7 @@ function parseJourneyMap(content, palette) {
22195
22283
  if (trimmed.includes("|") || !PHASE_RE.test(trimmed)) {
22196
22284
  warn(
22197
22285
  lineNumber,
22198
- "Steps outside phases will be ignored when phases are present"
22286
+ `Step "${trimmed}" sits outside any phase and is ignored \u2014 indent it under a '[Phase]' header (per \xA722).`
22199
22287
  );
22200
22288
  continue;
22201
22289
  }
@@ -22252,7 +22340,10 @@ function parseJourneyMap(content, palette) {
22252
22340
  continue;
22253
22341
  }
22254
22342
  if (indent > 0 && !currentPhase && hasPhases) {
22255
- warn(lineNumber, `Unexpected indented line outside of a phase`);
22343
+ warn(
22344
+ lineNumber,
22345
+ `Indented line outside any phase \u2014 add a '[Phase]' header above it, or unindent it to a top-level step. (\xA722)`
22346
+ );
22256
22347
  }
22257
22348
  }
22258
22349
  if (currentStep) {
@@ -22598,7 +22689,10 @@ function parsePyramid(content) {
22598
22689
  continue;
22599
22690
  }
22600
22691
  if (!currentLayer) {
22601
- warn(lineNum, `Unexpected indented line: "${trimmed}".`);
22692
+ warn(
22693
+ lineNum,
22694
+ `Indented description "${trimmed}" has no layer above it \u2014 add a layer name on a non-indented line first. (\xA723)`
22695
+ );
22602
22696
  continue;
22603
22697
  }
22604
22698
  const descLine = trimmed.startsWith("- ") ? `\u2022 ${trimmed.substring(2)}` : trimmed;
@@ -22772,7 +22866,10 @@ function parseRing(content) {
22772
22866
  continue;
22773
22867
  }
22774
22868
  if (!currentLayer) {
22775
- warn(lineNum, `Unexpected indented line: "${trimmed}".`);
22869
+ warn(
22870
+ lineNum,
22871
+ `Indented description "${trimmed}" has no layer above it \u2014 add a layer name on a non-indented line first. (\xA724)`
22872
+ );
22776
22873
  continue;
22777
22874
  }
22778
22875
  const descLine = trimmed.startsWith("- ") ? `\u2022 ${trimmed.substring(2)}` : trimmed;
@@ -23498,7 +23595,10 @@ function parseRaci(content, palette) {
23498
23595
  continue;
23499
23596
  }
23500
23597
  if (bodyStarted) {
23501
- errorAt(lineNumber, `Unexpected line: '${trimmed}'.`);
23598
+ errorAt(
23599
+ lineNumber,
23600
+ `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)`
23601
+ );
23502
23602
  } else {
23503
23603
  warn(lineNumber, `Unrecognized directive: '${trimmed}'.`);
23504
23604
  }
@@ -24045,6 +24145,13 @@ function getAllChartTypes() {
24045
24145
  return chartTypes.map((c) => c.id);
24046
24146
  }
24047
24147
  function parseDgmo(content) {
24148
+ const result = parseDgmoUndeduped(content);
24149
+ return {
24150
+ diagnostics: dedupeDiagnostics(result.diagnostics),
24151
+ chartType: result.chartType
24152
+ };
24153
+ }
24154
+ function parseDgmoUndeduped(content) {
24048
24155
  const chartType = parseDgmoChartType(content);
24049
24156
  if (!chartType) {
24050
24157
  const colonDiag = detectColonChartType(content);
@@ -29201,63 +29308,6 @@ function ensureArrowMarkers(defs, colors) {
29201
29308
  ).attr("fill", color);
29202
29309
  }
29203
29310
  }
29204
- function edgePolylineMidpoint(points) {
29205
- if (points.length === 0) return { x: 0, y: 0 };
29206
- if (points.length === 1) return { x: points[0].x, y: points[0].y };
29207
- let total = 0;
29208
- const segLen = [];
29209
- for (let k = 1; k < points.length; k++) {
29210
- const len = Math.hypot(
29211
- points[k].x - points[k - 1].x,
29212
- points[k].y - points[k - 1].y
29213
- );
29214
- segLen.push(len);
29215
- total += len;
29216
- }
29217
- let half = total / 2;
29218
- for (let k = 1; k < points.length; k++) {
29219
- const len = segLen[k - 1];
29220
- if (half <= len || k === points.length - 1) {
29221
- const t = len === 0 ? 0 : Math.min(1, half / len);
29222
- return {
29223
- x: points[k - 1].x + (points[k].x - points[k - 1].x) * t,
29224
- y: points[k - 1].y + (points[k].y - points[k - 1].y) * t
29225
- };
29226
- }
29227
- half -= len;
29228
- }
29229
- const last = points[points.length - 1];
29230
- return { x: last.x, y: last.y };
29231
- }
29232
- function resolveEdgeLabelOverlaps(labels) {
29233
- const MAX_PASSES = 8;
29234
- const PAD = 4;
29235
- for (let pass = 0; pass < MAX_PASSES; pass++) {
29236
- let moved = false;
29237
- for (let i = 0; i < labels.length; i++) {
29238
- for (let j = i + 1; j < labels.length; j++) {
29239
- const a = labels[i];
29240
- const b = labels[j];
29241
- const dx = Math.abs(a.x - b.x);
29242
- const dy = Math.abs(a.y - b.y);
29243
- const overlapX = (a.width + b.width) / 2 + PAD - dx;
29244
- const overlapY = (a.height + b.height) / 2 + PAD - dy;
29245
- if (overlapX > 0 && overlapY > 0) {
29246
- const shift = overlapY / 2 + 1;
29247
- if (a.y < b.y) {
29248
- a.y -= shift;
29249
- b.y += shift;
29250
- } else {
29251
- a.y += shift;
29252
- b.y -= shift;
29253
- }
29254
- moved = true;
29255
- }
29256
- }
29257
- }
29258
- if (!moved) break;
29259
- }
29260
- }
29261
29311
  function renderBoxesAndLines(container, parsed, layout, palette, isDark, options) {
29262
29312
  const {
29263
29313
  onClickItem,
@@ -29464,8 +29514,6 @@ function renderBoxesAndLines(container, parsed, layout, palette, isDark, options
29464
29514
  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);
29465
29515
  }
29466
29516
  }
29467
- const labelPositions = [];
29468
- const edgeGroups = /* @__PURE__ */ new Map();
29469
29517
  for (let i = 0; i < layout.edges.length; i++) {
29470
29518
  const le = layout.edges[i];
29471
29519
  const color = edgeColorMap.get(i) ?? palette.textMuted;
@@ -29484,7 +29532,6 @@ function renderBoxesAndLines(container, parsed, layout, palette, isDark, options
29484
29532
  const nodeLayout = layoutNodeMap.get(le.source);
29485
29533
  if (nodeLayout) {
29486
29534
  const edgeG2 = diagramG.append("g").attr("class", "bl-edge-group").attr("data-line-number", String(le.lineNumber));
29487
- edgeGroups.set(i, edgeG2);
29488
29535
  const markerId2 = `bl-arrow-${color.replace("#", "")}`;
29489
29536
  const cx = nodeLayout.x;
29490
29537
  const cy = nodeLayout.y;
@@ -29533,7 +29580,6 @@ function renderBoxesAndLines(container, parsed, layout, palette, isDark, options
29533
29580
  }
29534
29581
  if (points.length < 2) continue;
29535
29582
  const edgeG = diagramG.append("g").attr("class", "bl-edge-group").attr("data-line-number", String(le.lineNumber));
29536
- edgeGroups.set(i, edgeG);
29537
29583
  const markerId = `bl-arrow-${color.replace("#", "")}`;
29538
29584
  const gen = le.straight ? lineGeneratorStraight : parsed.direction === "TB" ? lineGeneratorTB : lineGeneratorLR;
29539
29585
  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})`);
@@ -29541,31 +29587,6 @@ function renderBoxesAndLines(container, parsed, layout, palette, isDark, options
29541
29587
  const revId = `bl-arrow-rev-${color.replace("#", "")}`;
29542
29588
  path.attr("marker-start", `url(#${revId})`);
29543
29589
  }
29544
- if (le.label && le.points.length > 0) {
29545
- const lw = measureText(le.label, sEdgeLabelFontSize);
29546
- const labelH = sEdgeLabelFontSize + 6;
29547
- const mid = edgePolylineMidpoint(le.points);
29548
- let ly = mid.y;
29549
- if (le.parallelCount > 1 && le.yOffset !== 0) {
29550
- ly += (le.yOffset < 0 ? -labelH : labelH) * 0.5;
29551
- }
29552
- labelPositions.push({
29553
- x: mid.x,
29554
- y: ly,
29555
- width: lw + 8,
29556
- height: labelH,
29557
- idx: i
29558
- });
29559
- }
29560
- }
29561
- resolveEdgeLabelOverlaps(labelPositions);
29562
- for (const lp of labelPositions) {
29563
- const le = layout.edges[lp.idx];
29564
- if (!le.label) continue;
29565
- const edgeG = edgeGroups.get(lp.idx);
29566
- const target = edgeG ?? diagramG;
29567
- 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);
29568
- 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);
29569
29590
  }
29570
29591
  for (const ln of layout.nodes) {
29571
29592
  const node = nodeMap.get(ln.label);
@@ -29616,8 +29637,8 @@ function renderBoxesAndLines(container, parsed, layout, palette, isDark, options
29616
29637
  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);
29617
29638
  const desc = node.description;
29618
29639
  if (desc && desc.length > 0 && !hideDescriptions) {
29619
- const MAX_LABEL_LINES3 = 3;
29620
- const fitted = fitLabelToHeader(node.label, ln.width, MAX_LABEL_LINES3);
29640
+ const MAX_LABEL_LINES4 = 3;
29641
+ const fitted = fitLabelToHeader(node.label, ln.width, MAX_LABEL_LINES4);
29621
29642
  const labelLines = fitted.lines;
29622
29643
  const labelLineH = fitted.fontSize * 1.3;
29623
29644
  const labelTotalH = labelLines.length * labelLineH;
@@ -29751,6 +29772,35 @@ function renderBoxesAndLines(container, parsed, layout, palette, isDark, options
29751
29772
  }
29752
29773
  }
29753
29774
  }
29775
+ const labelLayer = diagramG.append("g").attr("class", "bl-edge-labels");
29776
+ const labelLineHeight = sEdgeLabelFontSize * 1.3;
29777
+ for (const le of layout.edges) {
29778
+ if (!le.labelLines || le.labelLines.length === 0) continue;
29779
+ if (le.labelX === void 0 || le.labelY === void 0) continue;
29780
+ const lx = le.labelX;
29781
+ const ly = le.labelY;
29782
+ if (hidden.size > 0) {
29783
+ let isHidden = false;
29784
+ for (const [groupKey2, hiddenVals] of hidden) {
29785
+ const val = le.metadata[groupKey2];
29786
+ if (val && hiddenVals.has(val.toLowerCase())) {
29787
+ isHidden = true;
29788
+ break;
29789
+ }
29790
+ }
29791
+ if (isHidden) continue;
29792
+ }
29793
+ const lw = le.labelWidth ?? 0;
29794
+ const lh = le.labelHeight ?? sEdgeLabelFontSize + 6;
29795
+ const lines = le.labelLines;
29796
+ const labelG = labelLayer.append("g").attr("class", "bl-edge-label").attr("data-line-number", String(le.lineNumber));
29797
+ 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);
29798
+ 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);
29799
+ const firstY = ly - (lines.length - 1) / 2 * labelLineHeight + sEdgeLabelFontSize / 3;
29800
+ lines.forEach((line11, k) => {
29801
+ text.append("tspan").attr("x", lx).attr("y", firstY + k * labelLineHeight).text(line11);
29802
+ });
29803
+ }
29754
29804
  const hasDescriptions = parsed.nodes.some(
29755
29805
  (n) => n.description && n.description.length > 0
29756
29806
  );
@@ -29865,6 +29915,222 @@ var init_renderer6 = __esm({
29865
29915
  }
29866
29916
  });
29867
29917
 
29918
+ // src/boxes-and-lines/label-placement.ts
29919
+ function measureEdgeLabel(label, fontSize = EDGE_LABEL_FONT_SIZE6, maxWidth = LABEL_MAX_WIDTH) {
29920
+ let lines = wrapTextToWidth(label, fontSize, maxWidth, { hardBreak: true });
29921
+ if (lines.length > MAX_LABEL_LINES) {
29922
+ const kept = lines.slice(0, MAX_LABEL_LINES - 1);
29923
+ const rest = lines.slice(MAX_LABEL_LINES - 1).join(" ");
29924
+ kept.push(truncateText(rest, fontSize, maxWidth));
29925
+ lines = kept;
29926
+ }
29927
+ let textW = 0;
29928
+ for (const ln of lines) {
29929
+ const w = measureText(ln, fontSize);
29930
+ if (w > textW) textW = w;
29931
+ }
29932
+ return {
29933
+ lines,
29934
+ width: textW + 2 * H_PAD,
29935
+ height: lines.length * fontSize * LABEL_LINE_HEIGHT + 2 * V_PAD
29936
+ };
29937
+ }
29938
+ function edgePolylineMidpoint(points) {
29939
+ return pointAtArcFraction(points, 0.5).p;
29940
+ }
29941
+ function pointAtArcFraction(points, t) {
29942
+ if (points.length === 0) return { p: { x: 0, y: 0 }, nx: 0, ny: 1 };
29943
+ if (points.length === 1)
29944
+ return { p: { x: points[0].x, y: points[0].y }, nx: 0, ny: 1 };
29945
+ const segLen = [];
29946
+ let total = 0;
29947
+ for (let k = 1; k < points.length; k++) {
29948
+ const len = Math.hypot(
29949
+ points[k].x - points[k - 1].x,
29950
+ points[k].y - points[k - 1].y
29951
+ );
29952
+ segLen.push(len);
29953
+ total += len;
29954
+ }
29955
+ let want = total * Math.max(0, Math.min(1, t));
29956
+ for (let k = 1; k < points.length; k++) {
29957
+ const len = segLen[k - 1];
29958
+ if (want <= len || k === points.length - 1) {
29959
+ const f = len === 0 ? 0 : Math.min(1, want / len);
29960
+ const ax = points[k - 1].x;
29961
+ const ay = points[k - 1].y;
29962
+ const bx = points[k].x;
29963
+ const by = points[k].y;
29964
+ const dx = bx - ax;
29965
+ const dy = by - ay;
29966
+ const dlen = Math.hypot(dx, dy) || 1;
29967
+ return {
29968
+ p: { x: ax + dx * f, y: ay + dy * f },
29969
+ nx: -dy / dlen,
29970
+ ny: dx / dlen
29971
+ };
29972
+ }
29973
+ want -= len;
29974
+ }
29975
+ const last = points[points.length - 1];
29976
+ return { p: { x: last.x, y: last.y }, nx: 0, ny: 1 };
29977
+ }
29978
+ function rectFromCenter(cx, cy, w, h) {
29979
+ return {
29980
+ minX: cx - w / 2,
29981
+ minY: cy - h / 2,
29982
+ maxX: cx + w / 2,
29983
+ maxY: cy + h / 2
29984
+ };
29985
+ }
29986
+ function overlapsAny(cx, cy, w, h, obstacles, pad2) {
29987
+ const minX = cx - w / 2 - pad2;
29988
+ const minY = cy - h / 2 - pad2;
29989
+ const maxX = cx + w / 2 + pad2;
29990
+ const maxY = cy + h / 2 + pad2;
29991
+ for (const o of obstacles) {
29992
+ if (minX < o.maxX && maxX > o.minX && minY < o.maxY && maxY > o.minY)
29993
+ return true;
29994
+ }
29995
+ return false;
29996
+ }
29997
+ function slideFractions() {
29998
+ const out = [0.5];
29999
+ for (let k = 1; k <= SLIDE_SAMPLES; k++) {
30000
+ const off = k * 0.08;
30001
+ if (0.5 - off >= 0.12) out.push(0.5 - off);
30002
+ if (0.5 + off <= 0.88) out.push(0.5 + off);
30003
+ }
30004
+ return out;
30005
+ }
30006
+ function findClearPosition(w, h, points, obstacles) {
30007
+ const ts = slideFractions();
30008
+ for (const t of ts) {
30009
+ const { p } = pointAtArcFraction(points, t);
30010
+ if (!overlapsAny(p.x, p.y, w, h, obstacles, BOX_CLEAR_PAD)) return p;
30011
+ }
30012
+ for (let mag = PERP_STEP; mag <= PERP_MAX; mag += PERP_STEP) {
30013
+ for (const t of ts) {
30014
+ const { p, nx, ny } = pointAtArcFraction(points, t);
30015
+ for (const sign of [-1, 1]) {
30016
+ const x = p.x + nx * mag * sign;
30017
+ const y = p.y + ny * mag * sign;
30018
+ if (!overlapsAny(x, y, w, h, obstacles, BOX_CLEAR_PAD)) return { x, y };
30019
+ }
30020
+ }
30021
+ }
30022
+ return null;
30023
+ }
30024
+ function resolveLabelOverlaps(boxes) {
30025
+ const MAX_PASSES = 8;
30026
+ const PAD = 4;
30027
+ for (let pass = 0; pass < MAX_PASSES; pass++) {
30028
+ let moved = false;
30029
+ for (let i = 0; i < boxes.length; i++) {
30030
+ for (let j = i + 1; j < boxes.length; j++) {
30031
+ const a = boxes[i];
30032
+ const b = boxes[j];
30033
+ const dx = Math.abs(a.cx - b.cx);
30034
+ const dy = Math.abs(a.cy - b.cy);
30035
+ const overlapX = (a.w + b.w) / 2 + PAD - dx;
30036
+ const overlapY = (a.h + b.h) / 2 + PAD - dy;
30037
+ if (overlapX > 0 && overlapY > 0) {
30038
+ const shift = overlapY / 2 + 1;
30039
+ if (a.cy < b.cy) {
30040
+ a.cy -= shift;
30041
+ b.cy += shift;
30042
+ } else {
30043
+ a.cy += shift;
30044
+ b.cy -= shift;
30045
+ }
30046
+ moved = true;
30047
+ }
30048
+ }
30049
+ }
30050
+ if (!moved) break;
30051
+ }
30052
+ }
30053
+ function placeEdgeLabels(layout, opts) {
30054
+ const fontSize = opts?.fontSize ?? EDGE_LABEL_FONT_SIZE6;
30055
+ const maxWidth = opts?.maxWidth ?? LABEL_MAX_WIDTH;
30056
+ const obstacles = [];
30057
+ for (const n of layout.nodes)
30058
+ obstacles.push(rectFromCenter(n.x, n.y, n.width, n.height));
30059
+ for (const g of layout.groups)
30060
+ if (g.collapsed)
30061
+ obstacles.push(rectFromCenter(g.x, g.y, g.width, g.height));
30062
+ const boxes = [];
30063
+ layout.edges.forEach((e, idx) => {
30064
+ if (!e.label || e.points.length < 2) return;
30065
+ const m = measureEdgeLabel(e.label, fontSize, maxWidth);
30066
+ const mid = edgePolylineMidpoint(e.points);
30067
+ let cy = mid.y;
30068
+ if (e.parallelCount > 1 && e.yOffset !== 0)
30069
+ cy += (e.yOffset < 0 ? -1 : 1) * (m.height / 2);
30070
+ boxes.push({
30071
+ edgeIdx: idx,
30072
+ cx: mid.x,
30073
+ cy,
30074
+ w: m.width,
30075
+ h: m.height,
30076
+ lines: m.lines,
30077
+ resolved: true
30078
+ });
30079
+ });
30080
+ for (const box of boxes) {
30081
+ if (!overlapsAny(box.cx, box.cy, box.w, box.h, obstacles, BOX_CLEAR_PAD))
30082
+ continue;
30083
+ const e = layout.edges[box.edgeIdx];
30084
+ const clear = findClearPosition(box.w, box.h, e.points, obstacles);
30085
+ if (clear) {
30086
+ box.cx = clear.x;
30087
+ box.cy = clear.y;
30088
+ } else {
30089
+ box.resolved = false;
30090
+ }
30091
+ }
30092
+ resolveLabelOverlaps(boxes);
30093
+ for (const box of boxes) {
30094
+ if (overlapsAny(box.cx, box.cy, box.w, box.h, obstacles, BOX_CLEAR_PAD))
30095
+ box.resolved = false;
30096
+ }
30097
+ const boxByIdx = /* @__PURE__ */ new Map();
30098
+ for (const b of boxes) boxByIdx.set(b.edgeIdx, b);
30099
+ const edges = layout.edges.map((e, idx) => {
30100
+ const b = boxByIdx.get(idx);
30101
+ if (!b) return e;
30102
+ return {
30103
+ ...e,
30104
+ labelX: b.cx,
30105
+ labelY: b.cy,
30106
+ labelWidth: b.w,
30107
+ labelHeight: b.h,
30108
+ labelLines: b.lines
30109
+ };
30110
+ });
30111
+ return {
30112
+ layout: { ...layout, edges },
30113
+ unresolved: boxes.filter((b) => !b.resolved).map((b) => b.edgeIdx)
30114
+ };
30115
+ }
30116
+ 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;
30117
+ var init_label_placement = __esm({
30118
+ "src/boxes-and-lines/label-placement.ts"() {
30119
+ "use strict";
30120
+ init_text_measure();
30121
+ EDGE_LABEL_FONT_SIZE6 = 11;
30122
+ LABEL_MAX_WIDTH = 160;
30123
+ MAX_LABEL_LINES = 3;
30124
+ LABEL_LINE_HEIGHT = 1.3;
30125
+ H_PAD = 6;
30126
+ V_PAD = 3;
30127
+ BOX_CLEAR_PAD = 4;
30128
+ PERP_STEP = 8;
30129
+ PERP_MAX = 40;
30130
+ SLIDE_SAMPLES = 9;
30131
+ }
30132
+ });
30133
+
29868
30134
  // src/boxes-and-lines/layout-layered.ts
29869
30135
  function rng(s) {
29870
30136
  return () => {
@@ -31654,6 +31920,7 @@ function edgeLength(layout) {
31654
31920
  }
31655
31921
  async function layoutBoxesAndLinesSearch(parsed, collapseInfo, opts) {
31656
31922
  const hideDescriptions = opts?.hideDescriptions ?? false;
31923
+ const reserveEdgeLabels = opts?.reserveEdgeLabels ?? false;
31657
31924
  const onProgress = opts?.onProgress;
31658
31925
  const tick = onProgress ? () => new Promise((r) => setTimeout(r)) : () => void 0;
31659
31926
  const collapsedGroupLabels = /* @__PURE__ */ new Set();
@@ -31946,8 +32213,14 @@ async function layoutBoxesAndLinesSearch(parsed, collapseInfo, opts) {
31946
32213
  for (const e of ord(parsed.edges)) {
31947
32214
  const s = srcRep(e.source);
31948
32215
  const t = tgtRep(e.target);
31949
- if (s !== t && g.hasNode(s) && g.hasNode(t))
31950
- g.setEdge(s, t, {}, edgeKey(e));
32216
+ if (s !== t && g.hasNode(s) && g.hasNode(t)) {
32217
+ let edgeLabel = {};
32218
+ if (reserveEdgeLabels && e.label) {
32219
+ const m = measureEdgeLabel(e.label, EDGE_LABEL_FONT_SIZE6);
32220
+ edgeLabel = { width: m.width, height: m.height, labelpos: "c" };
32221
+ }
32222
+ g.setEdge(s, t, edgeLabel, edgeKey(e));
32223
+ }
31951
32224
  }
31952
32225
  dagre4.layout(g);
31953
32226
  const nodes = parsed.nodes.map((n2) => {
@@ -32176,6 +32449,7 @@ var init_layout_search = __esm({
32176
32449
  init_layout5();
32177
32450
  init_layout_layered();
32178
32451
  init_layout_grouped();
32452
+ init_label_placement();
32179
32453
  DEFAULT_LAMBDA = 4;
32180
32454
  ESCALATE_THRESHOLD = 4;
32181
32455
  ESCALATE_MAX_N = 45;
@@ -32234,9 +32508,9 @@ function estimateLabelLines(label, nodeWidth = NODE_WIDTH) {
32234
32508
  current = word;
32235
32509
  }
32236
32510
  }
32237
- if (lines <= MAX_LABEL_LINES) return Math.min(lines, MAX_LABEL_LINES);
32511
+ if (lines <= MAX_LABEL_LINES2) return Math.min(lines, MAX_LABEL_LINES2);
32238
32512
  }
32239
- return MAX_LABEL_LINES;
32513
+ return MAX_LABEL_LINES2;
32240
32514
  }
32241
32515
  function computeNodeSize(node, reserveValueRow) {
32242
32516
  if (!node.description || node.description.length === 0) {
@@ -32244,7 +32518,7 @@ function computeNodeSize(node, reserveValueRow) {
32244
32518
  }
32245
32519
  const w = DESC_NODE_WIDTH;
32246
32520
  const labelLines = estimateLabelLines(node.label, w);
32247
- const labelHeight = labelLines * 13 * LABEL_LINE_HEIGHT + LABEL_PAD;
32521
+ const labelHeight = labelLines * 13 * LABEL_LINE_HEIGHT2 + LABEL_PAD;
32248
32522
  const maxTextWidth = w - 24;
32249
32523
  let totalRenderedLines = 0;
32250
32524
  for (const line11 of node.description) {
@@ -32268,7 +32542,7 @@ function computeNodeSize(node, reserveValueRow) {
32268
32542
  }
32269
32543
  async function layoutBoxesAndLines(parsed, collapseInfo, layoutOptions) {
32270
32544
  const { layoutBoxesAndLinesSearch: layoutBoxesAndLinesSearch2 } = await Promise.resolve().then(() => (init_layout_search(), layout_search_exports));
32271
- const searched = await layoutBoxesAndLinesSearch2(parsed, collapseInfo, {
32545
+ const searchOpts = {
32272
32546
  ...layoutOptions?.hideDescriptions !== void 0 && {
32273
32547
  hideDescriptions: layoutOptions.hideDescriptions
32274
32548
  },
@@ -32278,12 +32552,23 @@ async function layoutBoxesAndLines(parsed, collapseInfo, layoutOptions) {
32278
32552
  ...layoutOptions?.onProgress !== void 0 && {
32279
32553
  onProgress: layoutOptions.onProgress
32280
32554
  }
32281
- });
32282
- return attachNotes(
32283
- applyParallelEdgeOffsets(searched),
32555
+ };
32556
+ const searched = await layoutBoxesAndLinesSearch2(
32284
32557
  parsed,
32285
- layoutOptions?.collapsedNotes
32558
+ collapseInfo,
32559
+ searchOpts
32286
32560
  );
32561
+ let placed = placeEdgeLabels(applyParallelEdgeOffsets(searched));
32562
+ if (placed.unresolved.length > 0) {
32563
+ const relaid = await layoutBoxesAndLinesSearch2(parsed, collapseInfo, {
32564
+ ...searchOpts,
32565
+ reserveEdgeLabels: true
32566
+ });
32567
+ const relaidPlaced = placeEdgeLabels(applyParallelEdgeOffsets(relaid));
32568
+ if (relaidPlaced.unresolved.length < placed.unresolved.length)
32569
+ placed = relaidPlaced;
32570
+ }
32571
+ return attachNotes(placed.layout, parsed, layoutOptions?.collapsedNotes);
32287
32572
  }
32288
32573
  function attachNotes(layout, parsed, collapsedNotes) {
32289
32574
  const notesSuppressed = parsed.options?.["no-notes"] === "on";
@@ -32351,6 +32636,7 @@ function attachNotes(layout, parsed, collapsedNotes) {
32351
32636
  points: e.points.map((pt) => ({ x: pt.x + shiftX, y: pt.y + shiftY })),
32352
32637
  ...e.labelX !== void 0 && { labelX: e.labelX + shiftX },
32353
32638
  ...e.labelY !== void 0 && { labelY: e.labelY + shiftY }
32639
+ // labelWidth/labelHeight/labelLines are shift-invariant — carried via spread.
32354
32640
  })) : layout.edges;
32355
32641
  const finalGroups = shifted ? layout.groups.map((grp) => ({
32356
32642
  ...grp,
@@ -32395,11 +32681,12 @@ function applyParallelEdgeOffsets(layout) {
32395
32681
  }))
32396
32682
  };
32397
32683
  }
32398
- var MARGIN5, MAX_PARALLEL_EDGES, PARALLEL_SPACING, PHI, NODE_HEIGHT, NODE_WIDTH, DESC_NODE_WIDTH, DESC_FONT_SIZE2, DESC_LINE_HEIGHT2, DESC_PADDING, SEPARATOR_GAP4, MAX_DESC_LINES2, MAX_LABEL_LINES, LABEL_LINE_HEIGHT, LABEL_PAD, VALUE_ROW_FONT, VALUE_ROW_H;
32684
+ 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;
32399
32685
  var init_layout5 = __esm({
32400
32686
  "src/boxes-and-lines/layout.ts"() {
32401
32687
  "use strict";
32402
32688
  init_text_measure();
32689
+ init_label_placement();
32403
32690
  init_notes();
32404
32691
  MARGIN5 = 40;
32405
32692
  MAX_PARALLEL_EDGES = 5;
@@ -32413,8 +32700,8 @@ var init_layout5 = __esm({
32413
32700
  DESC_PADDING = 8;
32414
32701
  SEPARATOR_GAP4 = 4;
32415
32702
  MAX_DESC_LINES2 = 6;
32416
- MAX_LABEL_LINES = 3;
32417
- LABEL_LINE_HEIGHT = 1.3;
32703
+ MAX_LABEL_LINES2 = 3;
32704
+ LABEL_LINE_HEIGHT2 = 1.3;
32418
32705
  LABEL_PAD = 12;
32419
32706
  VALUE_ROW_FONT = 11;
32420
32707
  VALUE_ROW_H = SEPARATOR_GAP4 + VALUE_ROW_FONT * DESC_LINE_HEIGHT2 + DESC_PADDING;
@@ -32433,7 +32720,7 @@ function tokenize(text) {
32433
32720
  return tokens;
32434
32721
  }
32435
32722
  function tryWrap(tokens, maxWidth, fontSize, maxLines) {
32436
- const availWidth = maxWidth - H_PAD;
32723
+ const availWidth = maxWidth - H_PAD2;
32437
32724
  const lines = [];
32438
32725
  let currentLine = "";
32439
32726
  for (const token of tokens) {
@@ -32458,7 +32745,7 @@ function tryWrap(tokens, maxWidth, fontSize, maxLines) {
32458
32745
  return lines;
32459
32746
  }
32460
32747
  function truncateLastLine(lines, maxWidth, fontSize) {
32461
- const availWidth = maxWidth - H_PAD;
32748
+ const availWidth = maxWidth - H_PAD2;
32462
32749
  const result = [...lines];
32463
32750
  const last = result[result.length - 1];
32464
32751
  if (measureText(last, fontSize) > availWidth) {
@@ -32466,7 +32753,7 @@ function truncateLastLine(lines, maxWidth, fontSize) {
32466
32753
  }
32467
32754
  return result;
32468
32755
  }
32469
- function wrapText(text, maxWidth, baseFontSize, minFontSize, maxLines = MAX_LABEL_LINES2) {
32756
+ function wrapText(text, maxWidth, baseFontSize, minFontSize, maxLines = MAX_LABEL_LINES3) {
32470
32757
  if (!text) return { lines: [""], fontSize: baseFontSize };
32471
32758
  const tokens = tokenize(text);
32472
32759
  for (let fs = baseFontSize; fs >= minFontSize; fs--) {
@@ -32481,7 +32768,7 @@ function wrapText(text, maxWidth, baseFontSize, minFontSize, maxLines = MAX_LABE
32481
32768
  if (allLines.length > maxLines) {
32482
32769
  const last = truncated[truncated.length - 1];
32483
32770
  if (!last.endsWith("\u2026")) {
32484
- const availWidth = maxWidth - H_PAD;
32771
+ const availWidth = maxWidth - H_PAD2;
32485
32772
  if (measureText(last + "\u2026", minFontSize) > availWidth) {
32486
32773
  truncated[truncated.length - 1] = truncateText(
32487
32774
  last,
@@ -32508,7 +32795,7 @@ function computeNodeText(label, description, depth, nodeWidth, hideDescriptions)
32508
32795
  nodeWidth,
32509
32796
  baseFontSize,
32510
32797
  MIN_FONT_SIZE,
32511
- MAX_LABEL_LINES2
32798
+ MAX_LABEL_LINES3
32512
32799
  );
32513
32800
  const descLines = [];
32514
32801
  let descFontSize = DESC_FONT_SIZE3;
@@ -32536,14 +32823,14 @@ function computeNodeText(label, description, depth, nodeWidth, hideDescriptions)
32536
32823
  descFontSize
32537
32824
  };
32538
32825
  }
32539
- var H_PAD, MAX_LABEL_LINES2, MAX_DESC_LINES3, ROOT_FONT_SIZE, MIN_FONT_SIZE, FONT_STEP, DESC_FONT_SIZE3;
32826
+ var H_PAD2, MAX_LABEL_LINES3, MAX_DESC_LINES3, ROOT_FONT_SIZE, MIN_FONT_SIZE, FONT_STEP, DESC_FONT_SIZE3;
32540
32827
  var init_text_wrap = __esm({
32541
32828
  "src/mindmap/text-wrap.ts"() {
32542
32829
  "use strict";
32543
32830
  init_description_helpers();
32544
32831
  init_text_measure();
32545
- H_PAD = 16;
32546
- MAX_LABEL_LINES2 = 3;
32832
+ H_PAD2 = 16;
32833
+ MAX_LABEL_LINES3 = 3;
32547
32834
  MAX_DESC_LINES3 = 2;
32548
32835
  ROOT_FONT_SIZE = 17;
32549
32836
  MIN_FONT_SIZE = 9;
@@ -32930,7 +33217,7 @@ function nodeHeightS(node, hideDescriptions, sc) {
32930
33217
  hideDescriptions
32931
33218
  );
32932
33219
  const labelLineCount = text.labelLines.length;
32933
- const labelH = labelLineCount <= 1 ? SINGLE_LABEL_HEIGHT : LABEL_LINE_HEIGHT2 * labelLineCount;
33220
+ const labelH = labelLineCount <= 1 ? SINGLE_LABEL_HEIGHT : LABEL_LINE_HEIGHT3 * labelLineCount;
32934
33221
  let h = labelH + NODE_V_PAD;
32935
33222
  if (text.descLines.length > 0) {
32936
33223
  h += DESC_LINE_HEIGHT3 * text.descLines.length + 4;
@@ -32950,7 +33237,7 @@ function populateDepthCache(roots) {
32950
33237
  };
32951
33238
  walk(roots, 0);
32952
33239
  }
32953
- var ROOT_WIDTH, DEPTH1_WIDTH, LEAF_WIDTH, SINGLE_LABEL_HEIGHT, LABEL_LINE_HEIGHT2, DESC_LINE_HEIGHT3, NODE_V_PAD, H_GAP2, V_GAP2, MARGIN6, MULTI_ROOT_GAP, nodeDepthCache;
33240
+ 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;
32954
33241
  var init_layout6 = __esm({
32955
33242
  "src/mindmap/layout.ts"() {
32956
33243
  "use strict";
@@ -32961,7 +33248,7 @@ var init_layout6 = __esm({
32961
33248
  DEPTH1_WIDTH = 150;
32962
33249
  LEAF_WIDTH = 120;
32963
33250
  SINGLE_LABEL_HEIGHT = 28;
32964
- LABEL_LINE_HEIGHT2 = 18;
33251
+ LABEL_LINE_HEIGHT3 = 18;
32965
33252
  DESC_LINE_HEIGHT3 = 14;
32966
33253
  NODE_V_PAD = 10;
32967
33254
  H_GAP2 = 40;
@@ -33253,18 +33540,18 @@ function renderMindmap(container, parsed, layout, palette, isDark, onClickItem,
33253
33540
  descFontSize
33254
33541
  } = textLayout;
33255
33542
  const labelLineCount = labelLines.length;
33256
- const labelZoneH = labelLineCount <= 1 ? SINGLE_LABEL_HEIGHT2 : LABEL_LINE_HEIGHT3 * labelLineCount;
33543
+ const labelZoneH = labelLineCount <= 1 ? SINGLE_LABEL_HEIGHT2 : LABEL_LINE_HEIGHT4 * labelLineCount;
33257
33544
  const labelZoneHeight = showDesc ? labelZoneH : node.height;
33258
33545
  const centerX = node.x + node.width / 2;
33259
33546
  if (labelLineCount <= 1) {
33260
33547
  const labelY = node.y + labelZoneHeight / 2 + fontSize * 0.35;
33261
33548
  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]);
33262
33549
  } else {
33263
- const blockH = LABEL_LINE_HEIGHT3 * (labelLineCount - 1);
33550
+ const blockH = LABEL_LINE_HEIGHT4 * (labelLineCount - 1);
33264
33551
  const firstBaselineY = node.y + labelZoneHeight / 2 - blockH / 2 + fontSize * 0.35;
33265
33552
  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);
33266
33553
  for (let i = 0; i < labelLines.length; i++) {
33267
- textEl.append("tspan").attr("x", centerX).attr("y", firstBaselineY + i * LABEL_LINE_HEIGHT3).text(labelLines[i]);
33554
+ textEl.append("tspan").attr("x", centerX).attr("y", firstBaselineY + i * LABEL_LINE_HEIGHT4).text(labelLines[i]);
33268
33555
  }
33269
33556
  }
33270
33557
  if (labelLines.length > 1 || labelLines[0] !== node.label) {
@@ -33339,7 +33626,7 @@ function renderMindmapForExport(content, theme, palette) {
33339
33626
  return extractExportSvg(container, theme);
33340
33627
  });
33341
33628
  }
33342
- var DIAGRAM_PADDING7, TITLE_HEIGHT4, SINGLE_LABEL_HEIGHT2, LABEL_LINE_HEIGHT3, DESC_LINE_HEIGHT4, NODE_RADIUS, ROOT_STROKE_WIDTH, DEPTH_COLOR_KEYS;
33629
+ var DIAGRAM_PADDING7, TITLE_HEIGHT4, SINGLE_LABEL_HEIGHT2, LABEL_LINE_HEIGHT4, DESC_LINE_HEIGHT4, NODE_RADIUS, ROOT_STROKE_WIDTH, DEPTH_COLOR_KEYS;
33343
33630
  var init_renderer7 = __esm({
33344
33631
  "src/mindmap/renderer.ts"() {
33345
33632
  "use strict";
@@ -33361,7 +33648,7 @@ var init_renderer7 = __esm({
33361
33648
  DIAGRAM_PADDING7 = 20;
33362
33649
  TITLE_HEIGHT4 = 30;
33363
33650
  SINGLE_LABEL_HEIGHT2 = 28;
33364
- LABEL_LINE_HEIGHT3 = 18;
33651
+ LABEL_LINE_HEIGHT4 = 18;
33365
33652
  DESC_LINE_HEIGHT4 = 14;
33366
33653
  NODE_RADIUS = 6;
33367
33654
  ROOT_STROKE_WIDTH = 2.5;
@@ -36030,7 +36317,7 @@ function renderC4Context(container, parsed, layout, palette, isDark, onClickItem
36030
36317
  const labelText = edge.label ?? "";
36031
36318
  const techText = edge.technology ? `[${edge.technology}]` : "";
36032
36319
  const textW = Math.max(
36033
- measureText(labelText, EDGE_LABEL_FONT_SIZE6),
36320
+ measureText(labelText, EDGE_LABEL_FONT_SIZE7),
36034
36321
  measureText(techText, TECH_FONT_SIZE)
36035
36322
  );
36036
36323
  const bgW = textW + 12;
@@ -36041,7 +36328,7 @@ function renderC4Context(container, parsed, layout, palette, isDark, onClickItem
36041
36328
  textY = midPt.y - 4;
36042
36329
  }
36043
36330
  if (labelText) {
36044
- 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);
36331
+ 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);
36045
36332
  }
36046
36333
  if (techText) {
36047
36334
  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);
@@ -36225,7 +36512,7 @@ function renderEdges(contentG, edges, palette, onClickItem, obstacleRects) {
36225
36512
  const labelText = edge.label ?? "";
36226
36513
  const techText = edge.technology ? `[${edge.technology}]` : "";
36227
36514
  const textW = Math.max(
36228
- measureText(labelText, EDGE_LABEL_FONT_SIZE6),
36515
+ measureText(labelText, EDGE_LABEL_FONT_SIZE7),
36229
36516
  measureText(techText, TECH_FONT_SIZE)
36230
36517
  );
36231
36518
  const bgW = textW + 12;
@@ -36243,7 +36530,7 @@ function renderEdges(contentG, edges, palette, onClickItem, obstacleRects) {
36243
36530
  });
36244
36531
  }
36245
36532
  }
36246
- placeEdgeLabels(pendingLabels, edges, obstacleRects);
36533
+ placeEdgeLabels2(pendingLabels, edges, obstacleRects);
36247
36534
  for (const lbl of pendingLabels) {
36248
36535
  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");
36249
36536
  let textY = lbl.y;
@@ -36251,7 +36538,7 @@ function renderEdges(contentG, edges, palette, onClickItem, obstacleRects) {
36251
36538
  textY = lbl.y - 4;
36252
36539
  }
36253
36540
  if (lbl.labelText) {
36254
- 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);
36541
+ 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);
36255
36542
  }
36256
36543
  if (lbl.techText) {
36257
36544
  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);
@@ -36332,7 +36619,7 @@ function tangentAt(points, t) {
36332
36619
  }
36333
36620
  return { x: 0, y: 1 };
36334
36621
  }
36335
- function placeEdgeLabels(labels, edges, obstacleRects) {
36622
+ function placeEdgeLabels2(labels, edges, obstacleRects) {
36336
36623
  if (labels.length === 0) return;
36337
36624
  const allPaths = edges.map((e) => e.points);
36338
36625
  const placedRects = [];
@@ -36870,7 +37157,7 @@ function renderC4DeploymentForExport(content, theme, palette) {
36870
37157
  document.body.removeChild(el);
36871
37158
  }
36872
37159
  }
36873
- var DIAGRAM_PADDING8, MAX_SCALE5, TITLE_HEIGHT6, TYPE_FONT_SIZE, NAME_FONT_SIZE2, DESC_FONT_SIZE5, DESC_LINE_HEIGHT6, EDGE_LABEL_FONT_SIZE6, TECH_FONT_SIZE, CARD_H_PAD4, CARD_V_PAD4, TYPE_LABEL_HEIGHT2, DIVIDER_GAP2, NAME_HEIGHT2, BOUNDARY_LABEL_FONT_SIZE, BOUNDARY_STROKE_WIDTH, BOUNDARY_RADIUS, DRILL_BAR_HEIGHT, CYLINDER_RY, PERSON_HEAD_R, PERSON_ARM_SPAN, PERSON_LEG_SPAN, PERSON_ICON_W, PERSON_SW, lineGenerator4;
37160
+ var DIAGRAM_PADDING8, MAX_SCALE5, TITLE_HEIGHT6, TYPE_FONT_SIZE, NAME_FONT_SIZE2, DESC_FONT_SIZE5, DESC_LINE_HEIGHT6, EDGE_LABEL_FONT_SIZE7, TECH_FONT_SIZE, CARD_H_PAD4, CARD_V_PAD4, TYPE_LABEL_HEIGHT2, DIVIDER_GAP2, NAME_HEIGHT2, BOUNDARY_LABEL_FONT_SIZE, BOUNDARY_STROKE_WIDTH, BOUNDARY_RADIUS, DRILL_BAR_HEIGHT, CYLINDER_RY, PERSON_HEAD_R, PERSON_ARM_SPAN, PERSON_LEG_SPAN, PERSON_ICON_W, PERSON_SW, lineGenerator4;
36874
37161
  var init_renderer9 = __esm({
36875
37162
  "src/c4/renderer.ts"() {
36876
37163
  "use strict";
@@ -36893,7 +37180,7 @@ var init_renderer9 = __esm({
36893
37180
  NAME_FONT_SIZE2 = 14;
36894
37181
  DESC_FONT_SIZE5 = 11;
36895
37182
  DESC_LINE_HEIGHT6 = 16;
36896
- EDGE_LABEL_FONT_SIZE6 = 11;
37183
+ EDGE_LABEL_FONT_SIZE7 = 11;
36897
37184
  TECH_FONT_SIZE = 10;
36898
37185
  CARD_H_PAD4 = 20;
36899
37186
  CARD_V_PAD4 = 14;
@@ -37518,7 +37805,7 @@ function renderFlowchart(container, graph, layout, palette, isDark, onClickItem,
37518
37805
  const sTitleFontSize = ctx.text(TITLE_FONT_SIZE);
37519
37806
  const sTitleY = ctx.structural(TITLE_Y);
37520
37807
  const sNodeFontSize = ctx.text(NODE_FONT_SIZE2);
37521
- const sEdgeLabelFontSize = ctx.text(EDGE_LABEL_FONT_SIZE7);
37808
+ const sEdgeLabelFontSize = ctx.text(EDGE_LABEL_FONT_SIZE8);
37522
37809
  const sEdgeStrokeWidth = ctx.structural(EDGE_STROKE_WIDTH);
37523
37810
  const sNodeStrokeWidth = ctx.structural(NODE_STROKE_WIDTH);
37524
37811
  const sArrowheadW = ctx.structural(ARROWHEAD_W3);
@@ -37744,7 +38031,7 @@ function renderFlowchartForExport(content, theme, palette) {
37744
38031
  document.body.removeChild(container);
37745
38032
  }
37746
38033
  }
37747
- var DIAGRAM_PADDING9, MAX_SCALE6, NODE_FONT_SIZE2, EDGE_LABEL_FONT_SIZE7, ARROWHEAD_W3, ARROWHEAD_H3, IO_SKEW, SUBROUTINE_INSET, DOC_WAVE_HEIGHT;
38034
+ var DIAGRAM_PADDING9, MAX_SCALE6, NODE_FONT_SIZE2, EDGE_LABEL_FONT_SIZE8, ARROWHEAD_W3, ARROWHEAD_H3, IO_SKEW, SUBROUTINE_INSET, DOC_WAVE_HEIGHT;
37748
38035
  var init_flowchart_renderer = __esm({
37749
38036
  "src/graph/flowchart-renderer.ts"() {
37750
38037
  "use strict";
@@ -37763,7 +38050,7 @@ var init_flowchart_renderer = __esm({
37763
38050
  DIAGRAM_PADDING9 = 20;
37764
38051
  MAX_SCALE6 = 3;
37765
38052
  NODE_FONT_SIZE2 = 13;
37766
- EDGE_LABEL_FONT_SIZE7 = 11;
38053
+ EDGE_LABEL_FONT_SIZE8 = 11;
37767
38054
  ARROWHEAD_W3 = 10;
37768
38055
  ARROWHEAD_H3 = 7;
37769
38056
  IO_SKEW = 15;
@@ -39198,7 +39485,7 @@ function layoutInfra(computed, expandedNodeIds, collapsedNodes) {
39198
39485
  const midIdx = Math.floor(edge.points.length / 2);
39199
39486
  const midPt = edge.points[midIdx];
39200
39487
  if (midPt) {
39201
- const halfWidth = (measureText(edge.label, EDGE_LABEL_FONT_SIZE8) + 8) / 2;
39488
+ const halfWidth = (measureText(edge.label, EDGE_LABEL_FONT_SIZE9) + 8) / 2;
39202
39489
  if (midPt.x - halfWidth < minX) minX = midPt.x - halfWidth;
39203
39490
  if (midPt.x + halfWidth > maxX) maxX = midPt.x + halfWidth;
39204
39491
  }
@@ -39232,7 +39519,7 @@ function layoutInfra(computed, expandedNodeIds, collapsedNodes) {
39232
39519
  height: totalHeight
39233
39520
  };
39234
39521
  }
39235
- var MIN_NODE_WIDTH2, NODE_HEADER_HEIGHT, META_LINE_HEIGHT5, NODE_SEPARATOR_GAP, NODE_PAD_BOTTOM, ROLE_DOT_ROW, COLLAPSE_BAR_HEIGHT3, NODE_FONT_SIZE3, META_FONT_SIZE5, EDGE_LABEL_FONT_SIZE8, PADDING_X3, GROUP_PADDING2, GROUP_HEADER_HEIGHT, EDGE_MARGIN, DISPLAY_KEYS, DISPLAY_NAMES, GROUP_GAP2;
39522
+ var MIN_NODE_WIDTH2, NODE_HEADER_HEIGHT, META_LINE_HEIGHT5, NODE_SEPARATOR_GAP, NODE_PAD_BOTTOM, ROLE_DOT_ROW, COLLAPSE_BAR_HEIGHT3, NODE_FONT_SIZE3, META_FONT_SIZE5, EDGE_LABEL_FONT_SIZE9, PADDING_X3, GROUP_PADDING2, GROUP_HEADER_HEIGHT, EDGE_MARGIN, DISPLAY_KEYS, DISPLAY_NAMES, GROUP_GAP2;
39236
39523
  var init_layout10 = __esm({
39237
39524
  "src/infra/layout.ts"() {
39238
39525
  "use strict";
@@ -39246,7 +39533,7 @@ var init_layout10 = __esm({
39246
39533
  COLLAPSE_BAR_HEIGHT3 = 6;
39247
39534
  NODE_FONT_SIZE3 = 13;
39248
39535
  META_FONT_SIZE5 = 10;
39249
- EDGE_LABEL_FONT_SIZE8 = 11;
39536
+ EDGE_LABEL_FONT_SIZE9 = 11;
39250
39537
  PADDING_X3 = 24;
39251
39538
  GROUP_PADDING2 = 20;
39252
39539
  GROUP_HEADER_HEIGHT = 24;
@@ -39353,7 +39640,7 @@ function buildScaledConstants(ctx) {
39353
39640
  sNodeFontSize: ctx.text(NODE_FONT_SIZE4),
39354
39641
  sMetaFontSize: ctx.text(META_FONT_SIZE6),
39355
39642
  sMetaLineHeight: ctx.structural(META_LINE_HEIGHT6),
39356
- sEdgeLabelFontSize: ctx.text(EDGE_LABEL_FONT_SIZE9),
39643
+ sEdgeLabelFontSize: ctx.text(EDGE_LABEL_FONT_SIZE10),
39357
39644
  sGroupLabelFontSize: ctx.text(GROUP_LABEL_FONT_SIZE2),
39358
39645
  sNodeBorderRadius: ctx.structural(NODE_BORDER_RADIUS),
39359
39646
  sEdgeStrokeWidth: ctx.structural(EDGE_STROKE_WIDTH),
@@ -40082,7 +40369,7 @@ function renderEdgeLabels(svg, edges, nodes, groups, palette, _isDark, animate,
40082
40369
  if (!midPt) continue;
40083
40370
  const labelText = edge.label;
40084
40371
  const g = svg.append("g").attr("class", animate ? "infra-edge-label" : "");
40085
- const textWidth = measureText(labelText, EDGE_LABEL_FONT_SIZE9) + 8;
40372
+ const textWidth = measureText(labelText, EDGE_LABEL_FONT_SIZE10) + 8;
40086
40373
  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);
40087
40374
  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);
40088
40375
  if (animate) {
@@ -40692,7 +40979,7 @@ function parseAndLayoutInfra(content) {
40692
40979
  const layout = layoutInfra(computed);
40693
40980
  return { parsed, computed, layout };
40694
40981
  }
40695
- var NODE_FONT_SIZE4, META_FONT_SIZE6, META_LINE_HEIGHT6, EDGE_LABEL_FONT_SIZE9, GROUP_LABEL_FONT_SIZE2, NODE_BORDER_RADIUS, OVERLOAD_STROKE_WIDTH, ROLE_DOT_RADIUS, NODE_HEADER_HEIGHT2, NODE_SEPARATOR_GAP2, NODE_PAD_BOTTOM2, LEGEND_FIXED_GAP3, SPEED_BADGE_H_PAD, SPEED_BADGE_V_PAD, SPEED_BADGE_GAP, COLOR_HEALTHY, COLOR_WARNING, COLOR_OVERLOADED, FLOW_SPEED_MIN, FLOW_SPEED_MAX, PARTICLE_R, PARTICLE_COUNT_MIN, PARTICLE_COUNT_MAX, NODE_PULSE_SPEED, NODE_PULSE_OVERLOAD, REJECT_DROP_DISTANCE, REJECT_DURATION_MIN, REJECT_DURATION_MAX, REJECT_COUNT_MIN, REJECT_COUNT_MAX, PROP_DISPLAY, DESC_MAX_CHARS, DESC_MAX_WIDTH, RPS_FORMAT_KEYS, MS_FORMAT_KEYS, PCT_FORMAT_KEYS;
40982
+ var NODE_FONT_SIZE4, META_FONT_SIZE6, META_LINE_HEIGHT6, EDGE_LABEL_FONT_SIZE10, GROUP_LABEL_FONT_SIZE2, NODE_BORDER_RADIUS, OVERLOAD_STROKE_WIDTH, ROLE_DOT_RADIUS, NODE_HEADER_HEIGHT2, NODE_SEPARATOR_GAP2, NODE_PAD_BOTTOM2, LEGEND_FIXED_GAP3, SPEED_BADGE_H_PAD, SPEED_BADGE_V_PAD, SPEED_BADGE_GAP, COLOR_HEALTHY, COLOR_WARNING, COLOR_OVERLOADED, FLOW_SPEED_MIN, FLOW_SPEED_MAX, PARTICLE_R, PARTICLE_COUNT_MIN, PARTICLE_COUNT_MAX, NODE_PULSE_SPEED, NODE_PULSE_OVERLOAD, REJECT_DROP_DISTANCE, REJECT_DURATION_MIN, REJECT_DURATION_MAX, REJECT_COUNT_MIN, REJECT_COUNT_MAX, PROP_DISPLAY, DESC_MAX_CHARS, DESC_MAX_WIDTH, RPS_FORMAT_KEYS, MS_FORMAT_KEYS, PCT_FORMAT_KEYS;
40696
40983
  var init_renderer10 = __esm({
40697
40984
  "src/infra/renderer.ts"() {
40698
40985
  "use strict";
@@ -40716,7 +41003,7 @@ var init_renderer10 = __esm({
40716
41003
  NODE_FONT_SIZE4 = 13;
40717
41004
  META_FONT_SIZE6 = 10;
40718
41005
  META_LINE_HEIGHT6 = 14;
40719
- EDGE_LABEL_FONT_SIZE9 = 11;
41006
+ EDGE_LABEL_FONT_SIZE10 = 11;
40720
41007
  GROUP_LABEL_FONT_SIZE2 = 14;
40721
41008
  NODE_BORDER_RADIUS = 8;
40722
41009
  OVERLOAD_STROKE_WIDTH = 3;
@@ -47108,7 +47395,7 @@ function renderState(container, graph, layout, palette, isDark, onClickItem, exp
47108
47395
  const sTitleFontSize = ctx.text(TITLE_FONT_SIZE);
47109
47396
  const sTitleY = ctx.structural(TITLE_Y);
47110
47397
  const sNodeFontSize = ctx.text(NODE_FONT_SIZE6);
47111
- const sEdgeLabelFontSize = ctx.text(EDGE_LABEL_FONT_SIZE10);
47398
+ const sEdgeLabelFontSize = ctx.text(EDGE_LABEL_FONT_SIZE11);
47112
47399
  const sGroupLabelFontSize = ctx.text(GROUP_LABEL_FONT_SIZE3);
47113
47400
  const sEdgeStrokeWidth = ctx.structural(EDGE_STROKE_WIDTH);
47114
47401
  const sNodeStrokeWidth = ctx.structural(NODE_STROKE_WIDTH);
@@ -47388,7 +47675,7 @@ function renderStateForExport(content, theme, palette) {
47388
47675
  document.body.removeChild(container);
47389
47676
  }
47390
47677
  }
47391
- var DIAGRAM_PADDING12, MAX_SCALE7, NODE_FONT_SIZE6, EDGE_LABEL_FONT_SIZE10, GROUP_LABEL_FONT_SIZE3, ARROWHEAD_W5, ARROWHEAD_H5, PSEUDOSTATE_RADIUS, STATE_CORNER_RADIUS, GROUP_EXTRA_PADDING;
47678
+ var DIAGRAM_PADDING12, MAX_SCALE7, NODE_FONT_SIZE6, EDGE_LABEL_FONT_SIZE11, GROUP_LABEL_FONT_SIZE3, ARROWHEAD_W5, ARROWHEAD_H5, PSEUDOSTATE_RADIUS, STATE_CORNER_RADIUS, GROUP_EXTRA_PADDING;
47392
47679
  var init_state_renderer = __esm({
47393
47680
  "src/graph/state-renderer.ts"() {
47394
47681
  "use strict";
@@ -47407,7 +47694,7 @@ var init_state_renderer = __esm({
47407
47694
  DIAGRAM_PADDING12 = 20;
47408
47695
  MAX_SCALE7 = 3;
47409
47696
  NODE_FONT_SIZE6 = 13;
47410
- EDGE_LABEL_FONT_SIZE10 = 11;
47697
+ EDGE_LABEL_FONT_SIZE11 = 11;
47411
47698
  GROUP_LABEL_FONT_SIZE3 = 11;
47412
47699
  ARROWHEAD_W5 = 10;
47413
47700
  ARROWHEAD_H5 = 7;
@@ -50449,10 +50736,10 @@ function renderedDescNodeHeight(numLines, scale) {
50449
50736
  return headerH + textBlockH + bottomPad;
50450
50737
  }
50451
50738
  function wrapEdgeLabelText(label, description, maxWidth = EDGE_LABEL_MAX_WIDTH) {
50452
- const labelLines = label ? wrapTextToWidth(label, EDGE_LABEL_FONT_SIZE11, maxWidth) : [];
50739
+ const labelLines = label ? wrapTextToWidth(label, EDGE_LABEL_FONT_SIZE12, maxWidth) : [];
50453
50740
  const descLines = [];
50454
50741
  for (const d of description) {
50455
- descLines.push(...wrapTextToWidth(d, EDGE_LABEL_FONT_SIZE11, maxWidth));
50742
+ descLines.push(...wrapTextToWidth(d, EDGE_LABEL_FONT_SIZE12, maxWidth));
50456
50743
  }
50457
50744
  return { labelLines, descLines };
50458
50745
  }
@@ -50590,9 +50877,9 @@ function computeEdgePaths(layoutNodes, parsed, cx, cy, radius, isClockwise) {
50590
50877
  const lineCount = labelLines.length + descLines.length;
50591
50878
  let labelPxW = 0;
50592
50879
  for (const l of labelLines)
50593
- labelPxW = Math.max(labelPxW, measureText(l, EDGE_LABEL_FONT_SIZE11));
50880
+ labelPxW = Math.max(labelPxW, measureText(l, EDGE_LABEL_FONT_SIZE12));
50594
50881
  for (const l of descLines)
50595
- labelPxW = Math.max(labelPxW, measureText(l, EDGE_LABEL_FONT_SIZE11));
50882
+ labelPxW = Math.max(labelPxW, measureText(l, EDGE_LABEL_FONT_SIZE12));
50596
50883
  const { labelX, labelY, labelAngle } = computeEdgeLabelPosition(
50597
50884
  midAngle,
50598
50885
  radius,
@@ -50694,9 +50981,9 @@ function fitToCanvas(nodes, edges, parsed, _cx, _cy, radius, width, height, _isC
50694
50981
  );
50695
50982
  let textWidth = 0;
50696
50983
  for (const l of labelLines)
50697
- textWidth = Math.max(textWidth, measureText(l, EDGE_LABEL_FONT_SIZE11));
50984
+ textWidth = Math.max(textWidth, measureText(l, EDGE_LABEL_FONT_SIZE12));
50698
50985
  for (const l of descLines)
50699
- textWidth = Math.max(textWidth, measureText(l, EDGE_LABEL_FONT_SIZE11));
50986
+ textWidth = Math.max(textWidth, measureText(l, EDGE_LABEL_FONT_SIZE12));
50700
50987
  if (textWidth === 0) continue;
50701
50988
  const normAngle = (le.labelAngle % (2 * Math.PI) + 2 * Math.PI) % (2 * Math.PI);
50702
50989
  const isRight = normAngle < Math.PI * 0.4 || normAngle > Math.PI * 1.6;
@@ -50770,7 +51057,7 @@ function buildEdgeArc(src, tgt, cx, cy, radius, isClockwise, arrowLength = 0) {
50770
51057
  const midAngle = srcExitAngle + dir * visibleSweep / 2;
50771
51058
  return { path, midAngle };
50772
51059
  }
50773
- 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;
51060
+ 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;
50774
51061
  var init_layout14 = __esm({
50775
51062
  "src/cycle/layout.ts"() {
50776
51063
  "use strict";
@@ -50781,7 +51068,7 @@ var init_layout14 = __esm({
50781
51068
  LABEL_FONT_SIZE4 = 13;
50782
51069
  CIRCLE_LABEL_FONT_SIZE = 16;
50783
51070
  DESC_FONT_SIZE6 = 11;
50784
- EDGE_LABEL_FONT_SIZE11 = 11;
51071
+ EDGE_LABEL_FONT_SIZE12 = 11;
50785
51072
  MIN_NODE_WIDTH4 = 70;
50786
51073
  MAX_NODE_WIDTH3 = 260;
50787
51074
  DESC_MIN_WIDTH = 140;
@@ -50927,7 +51214,7 @@ function renderCycle(container, parsed, palette, isDark, onClickItem, exportDims
50927
51214
  const scaledNodeFont = Math.max(6, Math.round(NODE_FONT_SIZE7 * layout.scale));
50928
51215
  const scaledEdgeLabelFont = Math.max(
50929
51216
  6,
50930
- Math.round(EDGE_LABEL_FONT_SIZE12 * layout.scale)
51217
+ Math.round(EDGE_LABEL_FONT_SIZE13 * layout.scale)
50931
51218
  );
50932
51219
  const CIRCLE_LABEL_FONT_SIZE2 = 16;
50933
51220
  const scaledCircleLabelFont = Math.max(
@@ -51090,7 +51377,7 @@ function ensureArrowMarker(defs, color, strokeWidth) {
51090
51377
  const mh = Math.max(1.5, mw * 0.5);
51091
51378
  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);
51092
51379
  }
51093
- var NODE_FONT_SIZE7, DESC_FONT_SIZE7, EDGE_LABEL_FONT_SIZE12, DESC_LINE_HEIGHT8, TITLE_AREA_HEIGHT;
51380
+ var NODE_FONT_SIZE7, DESC_FONT_SIZE7, EDGE_LABEL_FONT_SIZE13, DESC_LINE_HEIGHT8, TITLE_AREA_HEIGHT;
51094
51381
  var init_renderer15 = __esm({
51095
51382
  "src/cycle/renderer.ts"() {
51096
51383
  "use strict";
@@ -51107,7 +51394,7 @@ var init_renderer15 = __esm({
51107
51394
  init_text_measure();
51108
51395
  NODE_FONT_SIZE7 = 13;
51109
51396
  DESC_FONT_SIZE7 = 11;
51110
- EDGE_LABEL_FONT_SIZE12 = 11;
51397
+ EDGE_LABEL_FONT_SIZE13 = 11;
51111
51398
  DESC_LINE_HEIGHT8 = 15;
51112
51399
  TITLE_AREA_HEIGHT = 50;
51113
51400
  }
@@ -51505,7 +51792,7 @@ function resolveMap(parsed, data) {
51505
51792
  const hint = suggest(r.name, allNames);
51506
51793
  err(
51507
51794
  r.lineNumber,
51508
- `Unknown subdivision "${r.name}".${hint ? " " + hint : ""}`,
51795
+ `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.`,
51509
51796
  "E_MAP_UNKNOWN_SUBDIVISION"
51510
51797
  );
51511
51798
  continue;
@@ -51585,7 +51872,7 @@ function resolveMap(parsed, data) {
51585
51872
  const hint = suggest(name, cityNames);
51586
51873
  err(
51587
51874
  line11,
51588
- `Unknown place "${name}" (not in the gazetteer; use coordinates).${hint ? " " + hint : ""}`,
51875
+ `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>\`.`,
51589
51876
  "E_MAP_UNKNOWN_PLACE"
51590
51877
  );
51591
51878
  return { kind: "miss" };
@@ -52127,6 +52414,21 @@ function mapLegendBand(legend, opts) {
52127
52414
  if (height <= 0) return 0;
52128
52415
  return mapLegendTop(opts.hasTitle, opts.hasSubtitle) + height + LEGEND_BOTTOM_GAP2;
52129
52416
  }
52417
+ function mapLegendBox(legend, opts) {
52418
+ if (!legend) return null;
52419
+ const groups = mapLegendGroups(legend);
52420
+ if (groups.length === 0) return null;
52421
+ const config = mapLegendConfig(groups, opts.mode);
52422
+ const state = { activeGroup: legend.activeGroup };
52423
+ const { width, height } = computeLegendLayout(config, state, opts.width);
52424
+ if (height <= 0 || width <= 0) return null;
52425
+ return {
52426
+ x: (opts.width - width) / 2,
52427
+ y: mapLegendTop(opts.hasTitle, opts.hasSubtitle),
52428
+ width,
52429
+ height
52430
+ };
52431
+ }
52130
52432
  var LEGEND_TOP_GAP2, LEGEND_BOTTOM_GAP2;
52131
52433
  var init_legend_band = __esm({
52132
52434
  "src/map/legend-band.ts"() {
@@ -52251,6 +52553,8 @@ function placeContextLabels(args) {
52251
52553
  waterBodies,
52252
52554
  countries,
52253
52555
  palette,
52556
+ countryTone,
52557
+ waterTone,
52254
52558
  project,
52255
52559
  collides,
52256
52560
  contentPoints,
@@ -52260,8 +52564,8 @@ function placeContextLabels(args) {
52260
52564
  const band = tierBand(Math.max(dLonSpan, dLatSpan));
52261
52565
  const budget = labelBudget(width, height, band);
52262
52566
  if (budget <= 0) return [];
52263
- const waterColor = mix(palette.colors.blue, palette.textMuted, 50);
52264
- const countryColor = palette.textMuted;
52567
+ const waterColor = waterTone ?? mix(palette.colors.blue, palette.textMuted, 50);
52568
+ const countryColor = countryTone ?? palette.textMuted;
52265
52569
  const haloColor = palette.bg;
52266
52570
  const candidates = [];
52267
52571
  const center = [width / 2, height / 2];
@@ -52350,8 +52654,7 @@ function placeContextLabels(args) {
52350
52654
  )
52351
52655
  );
52352
52656
  const fontSize = Math.round(FONT + t * (COUNTRY_FONT_MAX - FONT));
52353
- const fade = Math.round(t * COUNTRY_FADE_MAX);
52354
- const color = fade > 0 ? mix(countryColor, palette.bg, 100 - fade) : countryColor;
52657
+ const color = countryColor;
52355
52658
  const text = c.name;
52356
52659
  const tw = labelWidth(text, 0, fontSize);
52357
52660
  if (tw > w || fontSize + 2 * PADY > h) continue;
@@ -52445,7 +52748,7 @@ function placeContextLabels(args) {
52445
52748
  }
52446
52749
  return placed;
52447
52750
  }
52448
- 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;
52751
+ 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;
52449
52752
  var init_context_labels = __esm({
52450
52753
  "src/map/context-labels.ts"() {
52451
52754
  "use strict";
@@ -52462,7 +52765,6 @@ var init_context_labels = __esm({
52462
52765
  COUNTRY_FONT_MAX = 22;
52463
52766
  COUNTRY_SIZE_FRAC_MIN = 0.06;
52464
52767
  COUNTRY_SIZE_FRAC_MAX = 0.32;
52465
- COUNTRY_FADE_MAX = 45;
52466
52768
  MAX_COUNTRY_POSITIONS = 4;
52467
52769
  COUNTRY_POS_GRID = 5;
52468
52770
  COUNTRY_POS_TOPN_MARGIN = 3;
@@ -52600,36 +52902,23 @@ function countryLabelPositions(args) {
52600
52902
  return n;
52601
52903
  };
52602
52904
  const dist2ToCentre = (c) => (c.sx - cx) ** 2 + (c.sy - cy) ** 2;
52603
- const pool = [...kept];
52604
- pool.sort((a, b) => {
52605
- const d = interiorness(b) - interiorness(a);
52606
- return d !== 0 ? d : dist2ToCentre(a) - dist2ToCentre(b);
52607
- });
52608
- const ordered = [pool.shift()];
52609
- while (ordered.length < MAX_COUNTRY_POSITIONS && pool.length) {
52610
- let bestIdx = 0;
52611
- let bestMin = -1;
52612
- for (let k = 0; k < pool.length; k++) {
52613
- const c = pool[k];
52614
- let minD = Infinity;
52615
- for (const o of ordered) {
52616
- const d = (c.sx - o.sx) ** 2 + (c.sy - o.sy) ** 2;
52617
- if (d < minD) minD = d;
52618
- }
52619
- if (minD > bestMin) {
52620
- bestMin = minD;
52621
- bestIdx = k;
52622
- }
52905
+ const centreLon = kept.reduce((s, c) => s + c.lon, 0) / kept.length;
52906
+ const centreLat = kept.reduce((s, c) => s + c.lat, 0) / kept.length;
52907
+ const centreScreen = project(centreLon, centreLat);
52908
+ 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 ? [
52909
+ {
52910
+ lonLat: [centreLon, centreLat],
52911
+ screen: [centreScreen[0], centreScreen[1]]
52623
52912
  }
52624
- ordered.push(pool.splice(bestIdx, 1)[0]);
52625
- }
52626
- const grid = ordered.map((c) => ({
52913
+ ] : [];
52914
+ const grid = [...kept].sort(
52915
+ (a, b) => interiorness(b) - interiorness(a) || dist2ToCentre(a) - dist2ToCentre(b)
52916
+ ).map((c) => ({
52627
52917
  lonLat: [c.lon, c.lat],
52628
52918
  screen: [c.sx, c.sy]
52629
52919
  }));
52630
52920
  const curatedPos = curated ? mkCurated(curated, project) : [];
52631
- const out = [...curatedPos, ...grid].slice(0, MAX_COUNTRY_POSITIONS);
52632
- return out;
52921
+ return [...curatedPos, ...lead, ...grid].slice(0, MAX_COUNTRY_POSITIONS);
52633
52922
  }
52634
52923
  function mkCurated(curated, project) {
52635
52924
  if (!curated) return [];
@@ -52895,6 +53184,25 @@ function layoutMap(resolvedIn, data, size, opts) {
52895
53184
  palette.bg,
52896
53185
  mutedBasemap ? isDark ? MUTED_FOREIGN_DARK : MUTED_FOREIGN_LIGHT : isDark ? FOREIGN_TINT_DARK : FOREIGN_TINT_LIGHT
52897
53186
  );
53187
+ const contrastFloorTone = (tone, ...substrates) => {
53188
+ const clears = (t) => substrates.every((s) => contrastRatio(t, s) >= REGION_LABEL_HALO_RATIO);
53189
+ if (clears(tone)) return tone;
53190
+ for (let s = 10; s <= 100; s += 10) {
53191
+ const t = mix(palette.text, tone, s);
53192
+ if (clears(t)) return t;
53193
+ }
53194
+ return palette.text;
53195
+ };
53196
+ const backdropLandTone = contrastFloorTone(
53197
+ palette.textMuted,
53198
+ neutralFill,
53199
+ water
53200
+ );
53201
+ const backdropWaterTone = contrastFloorTone(
53202
+ mix(palette.colors.blue, palette.textMuted, 50),
53203
+ water,
53204
+ neutralFill
53205
+ );
52898
53206
  const hasDirectColor = resolved.regions.some((r) => r.color !== void 0);
52899
53207
  const colorizeActive = resolved.directives.noColorize !== true && !hasRamp && !hasDirectColor && fillGroupNames.size === 0;
52900
53208
  const colorByIso = /* @__PURE__ */ new Map();
@@ -53009,6 +53317,33 @@ function layoutMap(resolvedIn, data, size, opts) {
53009
53317
  hasSubtitle: Boolean(resolved.subtitle)
53010
53318
  });
53011
53319
  if (legendBand > topPad) topPad = legendBand;
53320
+ const topReserved = [];
53321
+ if (shownTitle) {
53322
+ const lines = resolved.subtitle ? 2 : 1;
53323
+ const tw = Math.max(
53324
+ measureLegendText(shownTitle, TITLE_FONT_SIZE),
53325
+ resolved.subtitle ? measureLegendText(resolved.subtitle, TITLE_FONT_SIZE) : 0
53326
+ ) + 2 * TITLE_FONT_SIZE;
53327
+ topReserved.push({
53328
+ x: width / 2 - tw / 2,
53329
+ y: TITLE_Y - TITLE_FONT_SIZE,
53330
+ w: tw,
53331
+ h: TITLE_FONT_SIZE * (lines + 0.5)
53332
+ });
53333
+ }
53334
+ const legendBox = mapLegendBox(legend, {
53335
+ width,
53336
+ mode: opts.legendMode ?? "preview",
53337
+ hasTitle: Boolean(shownTitle),
53338
+ hasSubtitle: Boolean(resolved.subtitle)
53339
+ });
53340
+ if (legendBox)
53341
+ topReserved.push({
53342
+ x: legendBox.x,
53343
+ y: legendBox.y,
53344
+ w: legendBox.width,
53345
+ h: legendBox.height
53346
+ });
53012
53347
  const reserve = opts._calloutReserve;
53013
53348
  const fitLeft = FIT_PAD + (reserve?.left ?? 0);
53014
53349
  const fitRight = width - FIT_PAD - (reserve?.right ?? 0);
@@ -53738,6 +54073,19 @@ function layoutMap(resolvedIn, data, size, opts) {
53738
54073
  const by = b.cy - (b.cy - py) / tb * trimB;
53739
54074
  return `M${ax},${ay}Q${px},${py} ${bx},${by}`;
53740
54075
  };
54076
+ const legLabelPoint = (a, b, curved, offset) => {
54077
+ const mx = (a.cx + b.cx) / 2;
54078
+ const my = (a.cy + b.cy) / 2;
54079
+ if (!curved && offset === 0) return { x: mx, y: my - 4 };
54080
+ const dx = b.cx - a.cx;
54081
+ const dy = b.cy - a.cy;
54082
+ const len = Math.hypot(dx, dy) || 1;
54083
+ const nx = -dy / len;
54084
+ const ny = dx / len;
54085
+ const bow = offset !== 0 ? offset : len * ARC_CURVE_FRAC;
54086
+ const off = bow * 0.5 + Math.sign(bow || 1) * 8;
54087
+ return { x: mx + nx * off, y: my + ny * off };
54088
+ };
53741
54089
  const routeLegVals = resolved.routes.flatMap((rt) => rt.legs).map((l) => Number(l.value)).filter((n) => Number.isFinite(n) && n > 0);
53742
54090
  const rlMin = routeLegVals.length ? Math.min(...routeLegVals) : 0;
53743
54091
  const rlMax = routeLegVals.length ? Math.max(...routeLegVals) : 0;
@@ -53751,13 +54099,12 @@ function layoutMap(resolvedIn, data, size, opts) {
53751
54099
  const a = poiScreen.get(leg.fromId);
53752
54100
  const b = poiScreen.get(leg.toId);
53753
54101
  if (!a || !b) continue;
53754
- const mx = (a.cx + b.cx) / 2;
53755
- const my = (a.cy + b.cy) / 2;
54102
+ const lp = legLabelPoint(a, b, leg.style === "arc", 0);
53756
54103
  const bow = {
53757
54104
  curved: leg.style === "arc",
53758
54105
  offset: 0,
53759
- labelX: mx,
53760
- labelY: my - 4
54106
+ labelX: lp.x,
54107
+ labelY: lp.y
53761
54108
  };
53762
54109
  const routeLabelStyle = leg.label !== void 0 ? labelOnFill(fillAt(bow.labelX, bow.labelY)) : void 0;
53763
54110
  legs.push({
@@ -53804,13 +54151,13 @@ function layoutMap(resolvedIn, data, size, opts) {
53804
54151
  const b = poiScreen.get(e.toId);
53805
54152
  if (!a || !b) return;
53806
54153
  const fanOffset = n > 1 ? (i - (n - 1) / 2) * FAN_STEP : 0;
53807
- const mx = (a.cx + b.cx) / 2;
53808
- const my = (a.cy + b.cy) / 2;
54154
+ const curved = e.style === "arc" || n > 1;
54155
+ const lp = legLabelPoint(a, b, curved, fanOffset);
53809
54156
  const bow = {
53810
- curved: e.style === "arc" || n > 1,
54157
+ curved,
53811
54158
  offset: fanOffset,
53812
- labelX: mx,
53813
- labelY: my - 4
54159
+ labelX: lp.x,
54160
+ labelY: lp.y
53814
54161
  };
53815
54162
  const edgeLabelStyle = e.label !== void 0 ? labelOnFill(fillAt(bow.labelX, bow.labelY)) : void 0;
53816
54163
  legs.push({
@@ -53834,7 +54181,7 @@ function layoutMap(resolvedIn, data, size, opts) {
53834
54181
  });
53835
54182
  }
53836
54183
  const labels = [];
53837
- const obstacles = [];
54184
+ const obstacles = [...topReserved];
53838
54185
  const regionLabelGuards = [];
53839
54186
  const markers = pois.map((p) => ({
53840
54187
  cx: p.cx,
@@ -53872,6 +54219,7 @@ function layoutMap(resolvedIn, data, size, opts) {
53872
54219
  }
53873
54220
  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));
53874
54221
  const showRegionLabels = resolved.directives.noRegionLabels !== true;
54222
+ const labeledRegionIds = /* @__PURE__ */ new Set();
53875
54223
  const showRegionValues = resolved.directives.noRegionValue !== true && activeIsScore;
53876
54224
  const regionValueStr = (value) => showRegionValues && value !== void 0 ? compactNumber(value) : void 0;
53877
54225
  const isCompact = width < COMPACT_WIDTH_PX;
@@ -53892,7 +54240,9 @@ function layoutMap(resolvedIn, data, size, opts) {
53892
54240
  const REGION_FONT_MAX_ORIENT = 22;
53893
54241
  const REGION_SIZE_FRAC_MIN = 0.06;
53894
54242
  const REGION_SIZE_FRAC_MAX = 0.32;
53895
- const REGION_FADE_ORIENT = 45;
54243
+ const SUBJECT_FONT_MAX = 18;
54244
+ const SUBJECT_MIN_PROMINENCE = 13;
54245
+ const SUBJECT_LEADER_FONT = 15;
53896
54246
  const canvasLinear = Math.sqrt(Math.max(1, width * height));
53897
54247
  const sizeT = (boxW, boxH) => {
53898
54248
  const frac = Math.sqrt(Math.max(0, boxW * boxH)) / canvasLinear;
@@ -53904,9 +54254,9 @@ function layoutMap(resolvedIn, data, size, opts) {
53904
54254
  )
53905
54255
  );
53906
54256
  };
53907
- const pushRegionLabel = (x, y, text, fill2, lineNumber, valueLine, fontSize = FONT2, fade = 0) => {
54257
+ const pushRegionLabel = (x, y, text, fill2, lineNumber, valueLine, fontSize = FONT2, colorOverride) => {
53908
54258
  const { color: baseColor, haloColor } = labelOnFill(fill2);
53909
- const color = fade > 0 ? mix(baseColor, palette.bg, fade) : baseColor;
54259
+ const color = colorOverride ?? baseColor;
53910
54260
  const vf = Math.round(fontSize * 0.82);
53911
54261
  const halfW = Math.max(
53912
54262
  measureLegendText(text, fontSize),
@@ -53921,7 +54271,11 @@ function layoutMap(resolvedIn, data, size, opts) {
53921
54271
  text,
53922
54272
  anchor: "middle",
53923
54273
  color,
53924
- halo: overflows,
54274
+ // A backdrop label (colorOverride) NEVER haloes: the placement gate above
54275
+ // guarantees it sits on a floored-readable substrate (own fill, neutral
54276
+ // land, foreign land, or water), so a halo is pure noise. Data labels keep
54277
+ // the containment-gated halo until the role-tier pass (Phase 3).
54278
+ halo: colorOverride ? false : overflows,
53925
54279
  haloColor,
53926
54280
  ...fontSize !== FONT2 && { fontSize },
53927
54281
  ...valueLine !== void 0 && { valueLine },
@@ -53957,7 +54311,7 @@ function layoutMap(resolvedIn, data, size, opts) {
53957
54311
  if (!c || !Number.isFinite(c[0])) return null;
53958
54312
  return { r, c, boxW, boxH, area: boxW * boxH, candidates };
53959
54313
  }).filter((e) => e !== null).sort((a, b) => b.area - a.area || a.r.lineNumber - b.r.lineNumber);
53960
- const placedRegionRects = [];
54314
+ const placedRegionRects = [...topReserved];
53961
54315
  const placedLeaders = [];
53962
54316
  const isEmptyFill = (f) => f === water || f === neutralFill || f === foreignFill;
53963
54317
  const POI_LABEL_PAD = 14;
@@ -53970,9 +54324,9 @@ function layoutMap(resolvedIn, data, size, opts) {
53970
54324
  const SHORT_HOP_MAX = Math.max(46, Math.min(width, height) * 0.11);
53971
54325
  const STEP = 4;
53972
54326
  const HOP_GAP = 9;
53973
- const tryShortHopCallout = (cx, cy, fill2, name, value) => {
53974
- const chipW = stackW(name, value);
53975
- const chipH = stackH(value !== void 0);
54327
+ const tryShortHopCallout = (cx, cy, fill2, name, value, font = FONT2, gap = HOP_GAP, maxLen = SHORT_HOP_MAX) => {
54328
+ const chipW = stackW(name, value, font);
54329
+ const chipH = stackH(value !== void 0, font);
53976
54330
  const dirs = [
53977
54331
  [0, -1],
53978
54332
  // north (over Canada)
@@ -53988,7 +54342,7 @@ function layoutMap(resolvedIn, data, size, opts) {
53988
54342
  let ex = cx;
53989
54343
  let ey = cy;
53990
54344
  let steps = 0;
53991
- const maxSteps = Math.ceil(SHORT_HOP_MAX / STEP) + 1;
54345
+ const maxSteps = Math.ceil(maxLen / STEP) + 1;
53992
54346
  while (steps < maxSteps && fillAt(ex, ey) === fill2) {
53993
54347
  ex += dx * STEP;
53994
54348
  ey += dy * STEP;
@@ -53996,8 +54350,8 @@ function layoutMap(resolvedIn, data, size, opts) {
53996
54350
  }
53997
54351
  if (fillAt(ex, ey) === fill2) continue;
53998
54352
  const halfAlong = (Math.abs(dx) * chipW + Math.abs(dy) * chipH) / 2;
53999
- const ccx = ex + dx * (HOP_GAP + halfAlong);
54000
- const ccy = ey + dy * (HOP_GAP + halfAlong);
54353
+ const ccx = ex + dx * (gap + halfAlong);
54354
+ const ccy = ey + dy * (gap + halfAlong);
54001
54355
  const rect = {
54002
54356
  x: ccx - chipW / 2,
54003
54357
  y: ccy - chipH / 2,
@@ -54022,7 +54376,7 @@ function layoutMap(resolvedIn, data, size, opts) {
54022
54376
  const innerX = ccx - dx * (chipW / 2);
54023
54377
  const innerY = ccy - dy * (chipH / 2);
54024
54378
  const len = Math.hypot(innerX - cx, innerY - cy);
54025
- if (len > SHORT_HOP_MAX) continue;
54379
+ if (len > maxLen) continue;
54026
54380
  if (placedLeaders.some(
54027
54381
  (l) => segmentsCross(cx, cy, innerX, innerY, l[0], l[1], l[2], l[3])
54028
54382
  ))
@@ -54046,6 +54400,78 @@ function layoutMap(resolvedIn, data, size, opts) {
54046
54400
  const valStr = regionValueStr(r.value);
54047
54401
  const fitsRegions = (rect) => !placedRegionRects.some((p) => rectsOverlap(rect, p));
54048
54402
  const fitsPois = (rect) => !poiObstacles.some((o) => rectsOverlap(rect, o));
54403
+ const isSubject = regionById.has(r.id);
54404
+ const extremesOnFill = (ax, ay, text) => {
54405
+ const halfW = measureLegendText(text, FONT2) / 2;
54406
+ return fillAt(ax - halfW, ay) === r.fill && fillAt(ax + halfW, ay) === r.fill;
54407
+ };
54408
+ if (isSubject && valStr === void 0 && r.label !== void 0) {
54409
+ const name = candidates[0];
54410
+ let best = -1;
54411
+ for (let f = SUBJECT_FONT_MAX; f >= FONT2; f--) {
54412
+ if (measureLegendText(name, f) > boxW || f + 2 * LABEL_PADY > boxH)
54413
+ continue;
54414
+ const rect = regionLabelRect(c[0], c[1], name, void 0, f);
54415
+ if (!fitsRegions(rect) || !fitsPois(rect) || collides(rect)) continue;
54416
+ const halfW = measureLegendText(name, f) / 2;
54417
+ if (fillAt(c[0] - halfW, c[1]) !== r.fill || fillAt(c[0] + halfW, c[1]) !== r.fill)
54418
+ continue;
54419
+ best = f;
54420
+ break;
54421
+ }
54422
+ if (best >= SUBJECT_MIN_PROMINENCE) {
54423
+ const rect = regionLabelRect(c[0], c[1], name, void 0, best);
54424
+ placedRegionRects.push(rect);
54425
+ pushRegionLabel(
54426
+ c[0],
54427
+ c[1],
54428
+ name,
54429
+ r.fill,
54430
+ r.lineNumber,
54431
+ void 0,
54432
+ best
54433
+ );
54434
+ labeledRegionIds.add(r.id);
54435
+ regionLabelGuards.push({ label: labels[labels.length - 1], rect });
54436
+ } else {
54437
+ const hop = tryShortHopCallout(
54438
+ c[0],
54439
+ c[1],
54440
+ r.fill,
54441
+ r.label,
54442
+ void 0,
54443
+ SUBJECT_LEADER_FONT,
54444
+ HOP_GAP + SUBJECT_LEADER_FONT,
54445
+ SHORT_HOP_MAX * 2.2
54446
+ );
54447
+ if (hop) {
54448
+ placedRegionRects.push(hop.rect);
54449
+ placedLeaders.push(hop.leader);
54450
+ const dark = mix(r.fill, palette.text, 60);
54451
+ labels.push({
54452
+ x: hop.x,
54453
+ y: hop.y,
54454
+ text: r.label,
54455
+ anchor: "middle",
54456
+ color: palette.text,
54457
+ halo: false,
54458
+ haloColor: palette.bg,
54459
+ fontSize: SUBJECT_LEADER_FONT,
54460
+ leader: {
54461
+ x1: hop.leader[0],
54462
+ y1: hop.leader[1],
54463
+ x2: hop.leader[2],
54464
+ y2: hop.leader[3]
54465
+ },
54466
+ leaderColor: dark,
54467
+ calloutDot: { x: c[0], y: c[1], color: dark },
54468
+ lineNumber: r.lineNumber
54469
+ });
54470
+ labeledRegionIds.add(r.id);
54471
+ }
54472
+ }
54473
+ continue;
54474
+ }
54049
54475
  const seekAnchors = [
54050
54476
  { x: c[0], y: c[1], guard: false }
54051
54477
  ];
@@ -54068,13 +54494,13 @@ function layoutMap(resolvedIn, data, size, opts) {
54068
54494
  const nameRect = regionLabelRect(a.x, a.y, t);
54069
54495
  if (valStr && stackW(t, valStr) <= boxW && stackH(true) <= boxH) {
54070
54496
  const stackRect = regionLabelRect(a.x, a.y, t, valStr);
54071
- if (fitsRegions(stackRect) && fitsPois(nameRect)) {
54497
+ if (fitsRegions(stackRect) && fitsPois(nameRect) && extremesOnFill(a.x, a.y, t)) {
54072
54498
  chosen = { text: t, valueLine: valStr, ax: a.x, ay: a.y };
54073
54499
  break;
54074
54500
  }
54075
54501
  }
54076
54502
  if (labelW(t) <= boxW && labelH <= boxH) {
54077
- if (fitsRegions(nameRect) && fitsPois(nameRect)) {
54503
+ if (fitsRegions(nameRect) && fitsPois(nameRect) && (valStr === void 0 || extremesOnFill(a.x, a.y, t))) {
54078
54504
  chosen = { text: t, ax: a.x, ay: a.y };
54079
54505
  break;
54080
54506
  }
@@ -54082,7 +54508,7 @@ function layoutMap(resolvedIn, data, size, opts) {
54082
54508
  }
54083
54509
  if (chosen) break;
54084
54510
  }
54085
- if (chosen === void 0 && valStr && r.label !== void 0) {
54511
+ if (chosen === void 0 && r.label !== void 0 && isSubject) {
54086
54512
  const hop = tryShortHopCallout(c[0], c[1], r.fill, r.label, valStr);
54087
54513
  if (hop) {
54088
54514
  placedRegionRects.push(hop.rect);
@@ -54094,9 +54520,9 @@ function layoutMap(resolvedIn, data, size, opts) {
54094
54520
  text: r.label,
54095
54521
  anchor: "middle",
54096
54522
  color: palette.text,
54097
- halo: true,
54523
+ halo: false,
54098
54524
  haloColor: palette.bg,
54099
- valueLine: valStr,
54525
+ ...valStr !== void 0 && { valueLine: valStr },
54100
54526
  leader: {
54101
54527
  x1: hop.leader[0],
54102
54528
  y1: hop.leader[1],
@@ -54107,20 +54533,28 @@ function layoutMap(resolvedIn, data, size, opts) {
54107
54533
  calloutDot: { x: c[0], y: c[1], color: dark },
54108
54534
  lineNumber: r.lineNumber
54109
54535
  });
54536
+ labeledRegionIds.add(r.id);
54110
54537
  }
54111
54538
  continue;
54112
54539
  }
54113
54540
  if (chosen === void 0) continue;
54114
54541
  const isOrient = r.value === void 0 && r.layer === "base";
54115
54542
  let font = FONT2;
54116
- let fade = 0;
54117
54543
  if (isOrient) {
54118
54544
  const growT = sizeT(boxW, boxH);
54119
54545
  const desiredFont = Math.round(
54120
54546
  FONT2 + growT * (REGION_FONT_MAX_ORIENT - FONT2)
54121
54547
  );
54122
54548
  const hasVal = chosen.valueLine !== void 0;
54123
- for (let f = desiredFont; f > FONT2; f--) {
54549
+ const extremesClean = (f) => {
54550
+ const halfW = measureLegendText(chosen.text, f) / 2;
54551
+ return [chosen.ax - halfW, chosen.ax + halfW].every((sx) => {
54552
+ const fAt = fillAt(sx, chosen.ay);
54553
+ return fAt === r.fill || isEmptyFill(fAt);
54554
+ });
54555
+ };
54556
+ let fit = -1;
54557
+ for (let f = desiredFont; f >= FONT2; f--) {
54124
54558
  if (stackW(chosen.text, chosen.valueLine, f) > boxW || stackH(hasVal, f) > boxH)
54125
54559
  continue;
54126
54560
  const gRect = regionLabelRect(
@@ -54137,14 +54571,18 @@ function layoutMap(resolvedIn, data, size, opts) {
54137
54571
  void 0,
54138
54572
  f
54139
54573
  );
54140
- if (!fitsRegions(gRect) || !fitsPois(gName)) continue;
54141
- const halfW = measureLegendText(chosen.text, f) / 2;
54142
- if (fillAt(chosen.ax - halfW, chosen.ay) !== r.fill || fillAt(chosen.ax + halfW, chosen.ay) !== r.fill)
54574
+ if (!fitsRegions(gRect) || !fitsPois(gName) || collides(gName))
54143
54575
  continue;
54144
- font = f;
54576
+ if (!extremesClean(f)) continue;
54577
+ fit = f;
54145
54578
  break;
54146
54579
  }
54147
- fade = font > FONT2 ? Math.round(growT * REGION_FADE_ORIENT) : 0;
54580
+ if (fit < 0) {
54581
+ if (!frameContainers.has(r.id)) continue;
54582
+ font = FONT2;
54583
+ } else {
54584
+ font = fit;
54585
+ }
54148
54586
  }
54149
54587
  const rRect = regionLabelRect(
54150
54588
  chosen.ax,
@@ -54162,8 +54600,9 @@ function layoutMap(resolvedIn, data, size, opts) {
54162
54600
  r.lineNumber,
54163
54601
  chosen.valueLine,
54164
54602
  font,
54165
- fade
54603
+ isOrient ? backdropLandTone : void 0
54166
54604
  );
54605
+ labeledRegionIds.add(r.id);
54167
54606
  regionLabelGuards.push({
54168
54607
  label: labels[labels.length - 1],
54169
54608
  rect: rRect
@@ -54181,6 +54620,7 @@ function layoutMap(resolvedIn, data, size, opts) {
54181
54620
  seed.lineNumber,
54182
54621
  valStr
54183
54622
  );
54623
+ labeledRegionIds.add(seed.iso);
54184
54624
  regionLabelGuards.push({
54185
54625
  label: labels[labels.length - 1],
54186
54626
  rect: regionLabelRect(seed.x, seed.y, text, valStr)
@@ -54612,13 +55052,30 @@ function layoutMap(resolvedIn, data, size, opts) {
54612
55052
  const nx = -dy / len;
54613
55053
  const ny = dx / len;
54614
55054
  const perp = labelH + 2;
55055
+ const pm = /^M(-?[\d.]+),(-?[\d.]+)(?:L(-?[\d.]+),(-?[\d.]+)|Q(-?[\d.]+),(-?[\d.]+) (-?[\d.]+),(-?[\d.]+))$/.exec(
55056
+ lg.d
55057
+ );
55058
+ const pointAt = (t) => {
55059
+ if (pm?.[5] !== void 0) {
55060
+ const u = 1 - t;
55061
+ return [
55062
+ u * u * +pm[1] + 2 * u * t * +pm[5] + t * t * +pm[7],
55063
+ u * u * +pm[2] + 2 * u * t * +pm[6] + t * t * +pm[8]
55064
+ ];
55065
+ }
55066
+ if (pm?.[3] !== void 0)
55067
+ return [
55068
+ +pm[1] + (+pm[3] - +pm[1]) * t,
55069
+ +pm[2] + (+pm[4] - +pm[2]) * t
55070
+ ];
55071
+ return [a.cx + dx * t, a.cy + dy * t];
55072
+ };
54615
55073
  const candidates = [];
54616
55074
  for (const off of [0, perp, -perp, 2 * perp, -2 * perp])
54617
- for (const t of T_LIST)
54618
- candidates.push([
54619
- a.cx + dx * t + nx * off,
54620
- a.cy + dy * t + ny * off - 4
54621
- ]);
55075
+ for (const t of T_LIST) {
55076
+ const [bx, by] = pointAt(t);
55077
+ candidates.push([bx + nx * off, by + ny * off - 4]);
55078
+ }
54622
55079
  const clean = ([cx2, cy2]) => {
54623
55080
  const rect2 = { x: cx2 - w / 2, y: cy2 - labelH / 2, w, h: labelH };
54624
55081
  if (rect2.x < 0 || rect2.x + w > width || rect2.y < 0 || rect2.y + rect2.h > height)
@@ -54628,10 +55085,8 @@ function layoutMap(resolvedIn, data, size, opts) {
54628
55085
  if (placedEdge.some((o) => rectsOverlap(o, rect2))) return false;
54629
55086
  return true;
54630
55087
  };
54631
- const [cx, cy] = candidates.find(clean) ?? [
54632
- a.cx + dx * 0.5,
54633
- a.cy + dy * 0.5 - 4
54634
- ];
55088
+ const mid = pointAt(0.5);
55089
+ const [cx, cy] = candidates.find(clean) ?? [mid[0], mid[1] - 4];
54635
55090
  const style = labelOnFill(fillAt(cx, cy));
54636
55091
  legs[i] = {
54637
55092
  ...lg,
@@ -54659,7 +55114,7 @@ function layoutMap(resolvedIn, data, size, opts) {
54659
55114
  const rawCountries = [];
54660
55115
  for (const f of worldLayer.values()) {
54661
55116
  const iso = typeof f.id === "string" ? f.id : String(f.id ?? "");
54662
- if (!iso || regionById.has(iso)) continue;
55117
+ if (!iso || regionById.has(iso) || labeledRegionIds.has(iso)) continue;
54663
55118
  let hasReferencedSub = false;
54664
55119
  for (const k of regionById.keys())
54665
55120
  if (k.startsWith(iso + "-")) {
@@ -54733,7 +55188,7 @@ function layoutMap(resolvedIn, data, size, opts) {
54733
55188
  );
54734
55189
  if (usLayer && framedStateContainers) {
54735
55190
  for (const [iso, f] of usLayer) {
54736
- if (regionById.has(iso)) continue;
55191
+ if (regionById.has(iso) || labeledRegionIds.has(iso)) continue;
54737
55192
  const viewF = cullFeatureToView(f);
54738
55193
  if (!viewF) continue;
54739
55194
  const b = path.bounds(viewF);
@@ -54757,6 +55212,11 @@ function layoutMap(resolvedIn, data, size, opts) {
54757
55212
  waterBodies: data.waterBodies,
54758
55213
  countries: countryCandidates,
54759
55214
  palette,
55215
+ // One muted, contrast-floored tone shared with the orientation backdrop
55216
+ // (region pass) so a context country (Canada) and an orientation region
55217
+ // (Minnesota) read identically — never below the WCAG-AA floor, no fade.
55218
+ countryTone: backdropLandTone,
55219
+ waterTone: backdropWaterTone,
54760
55220
  project,
54761
55221
  collides,
54762
55222
  contentPoints,
@@ -55310,19 +55770,60 @@ function renderMap(container, resolved, data, palette, isDark, onClickItem, expo
55310
55770
  sel.style("cursor", "pointer").on("click", () => onClickItem(lineNumber));
55311
55771
  };
55312
55772
  const gLegs = svg.append("g").attr("class", "dgmo-map-legs").attr("fill", "none");
55313
- layout.legs.forEach((leg, i) => {
55314
- 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);
55773
+ const legEnd = (d) => {
55774
+ const m = /^M(-?[\d.]+),(-?[\d.]+)(?:L(-?[\d.]+),(-?[\d.]+)|Q(-?[\d.]+),(-?[\d.]+) (-?[\d.]+),(-?[\d.]+))$/.exec(
55775
+ d
55776
+ );
55777
+ if (!m) return null;
55778
+ const x0 = +m[1];
55779
+ const y0 = +m[2];
55780
+ let ex;
55781
+ let ey;
55782
+ let fromX;
55783
+ let fromY;
55784
+ if (m[3] !== void 0) {
55785
+ ex = +m[3];
55786
+ ey = +m[4];
55787
+ fromX = x0;
55788
+ fromY = y0;
55789
+ } else {
55790
+ ex = +m[7];
55791
+ ey = +m[8];
55792
+ fromX = +m[5];
55793
+ fromY = +m[6];
55794
+ }
55795
+ const dx = ex - fromX;
55796
+ const dy = ey - fromY;
55797
+ const len = Math.hypot(dx, dy);
55798
+ if (!(len > 0)) return null;
55799
+ return { ex, ey, tx: dx / len, ty: dy / len };
55800
+ };
55801
+ layout.legs.forEach((leg) => {
55802
+ const end = leg.arrow ? legEnd(leg.d) : null;
55803
+ let drawD = leg.d;
55804
+ let arrowTri = null;
55805
+ if (end) {
55806
+ const aLen = arrowSize(leg.width);
55807
+ const aHalf = Math.max(aLen * 0.5, leg.width * 0.6 + 1);
55808
+ const bx = end.ex - end.tx * aLen;
55809
+ const by = end.ey - end.ty * aLen;
55810
+ const px = -end.ty;
55811
+ const py = end.tx;
55812
+ arrowTri = `M${bx + px * aHalf},${by + py * aHalf}L${end.ex},${end.ey}L${bx - px * aHalf},${by - py * aHalf}z`;
55813
+ drawD = leg.d.replace(
55814
+ /(-?[\d.]+),(-?[\d.]+)$/,
55815
+ `${bx.toFixed(2)},${by.toFixed(2)}`
55816
+ );
55817
+ }
55818
+ 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);
55315
55819
  if (leg.tags) {
55316
55820
  for (const [group, value] of Object.entries(leg.tags)) {
55317
55821
  p.attr(`data-tag-${group.toLowerCase()}`, value.toLowerCase());
55318
55822
  }
55319
55823
  }
55320
55824
  wireSync(p, leg.lineNumber);
55321
- if (leg.arrow) {
55322
- const id = nid(`dgmo-map-arrow-${i}`);
55323
- const s = arrowSize(leg.width);
55324
- 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);
55325
- p.attr("marker-end", `url(#${id})`);
55825
+ if (arrowTri) {
55826
+ 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);
55326
55827
  }
55327
55828
  if (leg.label !== void 0 && leg.labelX !== void 0) {
55328
55829
  const lt = emitText(
@@ -58949,9 +59450,9 @@ var init_renderer20 = __esm({
58949
59450
  }
58950
59451
  });
58951
59452
 
58952
- // node_modules/.pnpm/lz-string@1.5.0/node_modules/lz-string/libs/lz-string.js
59453
+ // ../../diagrammo/dgmo/node_modules/.pnpm/lz-string@1.5.0/node_modules/lz-string/libs/lz-string.js
58953
59454
  var require_lz_string = __commonJS({
58954
- "node_modules/.pnpm/lz-string@1.5.0/node_modules/lz-string/libs/lz-string.js"(exports, module) {
59455
+ "../../diagrammo/dgmo/node_modules/.pnpm/lz-string@1.5.0/node_modules/lz-string/libs/lz-string.js"(exports, module) {
58955
59456
  "use strict";
58956
59457
  var LZString = (function() {
58957
59458
  var f = String.fromCharCode;
@@ -65237,326 +65738,6 @@ var themes = {
65237
65738
 
65238
65739
  // src/advanced.ts
65239
65740
  init_dgmo_router();
65240
-
65241
- // src/migrate/index.ts
65242
- init_dgmo_router();
65243
- import {
65244
- readFileSync,
65245
- writeFileSync,
65246
- existsSync,
65247
- statSync,
65248
- readdirSync
65249
- } from "fs";
65250
- import { join, extname } from "path";
65251
-
65252
- // src/migrate/line-classifier.ts
65253
- function findUnsafePipePositions(line11) {
65254
- const positions = [];
65255
- let i = 0;
65256
- let inDouble = false;
65257
- let inSingle = false;
65258
- let braceDepth = 0;
65259
- let inArrow = false;
65260
- let arrowOpenChar = null;
65261
- while (i < line11.length) {
65262
- const ch = line11[i];
65263
- const ch2 = i + 1 < line11.length ? line11[i + 1] : "";
65264
- if (inDouble) {
65265
- if (ch === '"') inDouble = false;
65266
- i++;
65267
- continue;
65268
- }
65269
- if (inSingle) {
65270
- if (ch === "'") inSingle = false;
65271
- i++;
65272
- continue;
65273
- }
65274
- if (inArrow) {
65275
- if (arrowOpenChar === "-" && ch === "-" && ch2 === ">" || arrowOpenChar === "~" && ch === "~" && ch2 === ">") {
65276
- inArrow = false;
65277
- arrowOpenChar = null;
65278
- i += 2;
65279
- continue;
65280
- }
65281
- i++;
65282
- continue;
65283
- }
65284
- if (ch === '"') {
65285
- inDouble = true;
65286
- i++;
65287
- continue;
65288
- }
65289
- if (ch === "'") {
65290
- inSingle = true;
65291
- i++;
65292
- continue;
65293
- }
65294
- if (ch === "{") {
65295
- braceDepth++;
65296
- i++;
65297
- continue;
65298
- }
65299
- if (ch === "}") {
65300
- if (braceDepth > 0) braceDepth--;
65301
- i++;
65302
- continue;
65303
- }
65304
- if ((ch === "-" || ch === "~") && i > 0) {
65305
- const prev = line11[i - 1];
65306
- const onBoundary = prev === " " || prev === " ";
65307
- const nextIsLabelChar = ch2 !== "" && ch2 !== ch && ch2 !== ">" && ch2 !== " " && ch2 !== " ";
65308
- if (onBoundary && nextIsLabelChar) {
65309
- const closer = ch + ">";
65310
- const closeIdx = line11.indexOf(closer, i + 1);
65311
- if (closeIdx > i) {
65312
- const labelRegion = line11.substring(i + 1, closeIdx);
65313
- if (!labelRegion.includes("->") && !labelRegion.includes("~>")) {
65314
- inArrow = true;
65315
- arrowOpenChar = ch;
65316
- i++;
65317
- continue;
65318
- }
65319
- }
65320
- }
65321
- }
65322
- if (ch === "|" && braceDepth === 0) {
65323
- positions.push(i);
65324
- }
65325
- i++;
65326
- }
65327
- return positions;
65328
- }
65329
- function isLegacyMetadataLine(line11) {
65330
- return findUnsafePipePositions(line11).length > 0;
65331
- }
65332
-
65333
- // src/migrate/line-transformer.ts
65334
- function replaceUnsafePipesWithCommas(s) {
65335
- const positions = findUnsafePipePositions(s);
65336
- if (positions.length === 0) return s;
65337
- const chars = s.split("");
65338
- for (const pos of positions) {
65339
- chars[pos] = ",";
65340
- }
65341
- return chars.join("");
65342
- }
65343
- var GANTT_PERCENT = {
65344
- match: (t) => /^\d+(?:\.\d+)?\s*%$/.test(t.trim()),
65345
- promote: (t) => `progress: ${t.trim().replace(/\s*%$/, "")}`
65346
- };
65347
- var JOURNEY_SCORE = {
65348
- match: (t) => /^\d+(?:\s+[A-Za-z][A-Za-z0-9_-]*)?$/.test(t.trim()),
65349
- promote: (t) => {
65350
- const m = t.trim().match(/^(\d+)(?:\s+([A-Za-z][A-Za-z0-9_-]*))?$/);
65351
- if (!m) return t;
65352
- return m[2] ? `score: ${m[1]}, emotion: ${m[2]}` : `score: ${m[1]}`;
65353
- }
65354
- };
65355
- var PROMOTIONS_BY_CHART = {
65356
- gantt: [GANTT_PERCENT],
65357
- "journey-map": [JOURNEY_SCORE]
65358
- };
65359
- var BARE_DESCRIPTION_CHARTS = /* @__PURE__ */ new Set(["pyramid", "ring"]);
65360
- function tokenizeMetaRegion(region) {
65361
- const tokens = [];
65362
- let i = 0;
65363
- let buf = "";
65364
- let inQuote = null;
65365
- while (i < region.length) {
65366
- const ch = region[i];
65367
- if (inQuote) {
65368
- buf += ch;
65369
- if (ch === inQuote) inQuote = null;
65370
- i++;
65371
- continue;
65372
- }
65373
- if (ch === '"' || ch === "'") {
65374
- inQuote = ch;
65375
- buf += ch;
65376
- i++;
65377
- continue;
65378
- }
65379
- if (ch === ",") {
65380
- const t = buf.trim();
65381
- if (t) tokens.push(t);
65382
- buf = "";
65383
- i++;
65384
- continue;
65385
- }
65386
- buf += ch;
65387
- i++;
65388
- }
65389
- const tail = buf.trim();
65390
- if (tail) tokens.push(tail);
65391
- return tokens;
65392
- }
65393
- function transformLine(line11, chartType) {
65394
- const indentMatch = line11.match(/^[ \t]*/);
65395
- const indent = indentMatch ? indentMatch[0] : "";
65396
- const body = line11.substring(indent.length);
65397
- const pipePositions = findUnsafePipePositions(body);
65398
- if (pipePositions.length === 0) {
65399
- return { line: line11, changed: false };
65400
- }
65401
- const cut = pipePositions[0];
65402
- const nameRegion = body.substring(0, cut).trimEnd();
65403
- let metaRegion = body.substring(cut + 1).trimStart();
65404
- if (pipePositions.length > 1) {
65405
- metaRegion = replaceUnsafePipesWithCommas(metaRegion);
65406
- }
65407
- if (chartType !== null && BARE_DESCRIPTION_CHARTS.has(chartType) && !metaRegion.includes(":")) {
65408
- const text = metaRegion.trim();
65409
- if (!text) {
65410
- return {
65411
- line: indent + nameRegion,
65412
- changed: true
65413
- };
65414
- }
65415
- const value = text.includes(",") ? `"${text}"` : text;
65416
- return {
65417
- line: `${indent}${nameRegion} description: ${value}`,
65418
- changed: true
65419
- };
65420
- }
65421
- const tokens = tokenizeMetaRegion(metaRegion);
65422
- const promotions = chartType ? PROMOTIONS_BY_CHART[chartType] ?? [] : [];
65423
- const emitted = [];
65424
- for (const raw of tokens) {
65425
- if (raw.includes(":")) {
65426
- emitted.push(raw);
65427
- continue;
65428
- }
65429
- const promo = promotions.find((p) => p.match(raw));
65430
- if (promo) {
65431
- emitted.push(promo.promote(raw));
65432
- continue;
65433
- }
65434
- emitted.push(raw);
65435
- }
65436
- if (emitted.length === 0) {
65437
- return {
65438
- line: indent + nameRegion,
65439
- changed: true
65440
- };
65441
- }
65442
- return {
65443
- line: `${indent}${nameRegion} ${emitted.join(", ")}`,
65444
- changed: true
65445
- };
65446
- }
65447
-
65448
- // src/migrate/data-comma.ts
65449
- init_colors();
65450
- var DATA_COMMA_CHART_TYPES = /* @__PURE__ */ new Set([
65451
- // Simple charts (chart.ts)
65452
- "bar",
65453
- "line",
65454
- "multi-line",
65455
- "pie",
65456
- "doughnut",
65457
- "area",
65458
- "polar-area",
65459
- "radar",
65460
- "bar-stacked",
65461
- // Extended charts (echarts.ts)
65462
- "scatter",
65463
- "heatmap",
65464
- "sankey",
65465
- "chord",
65466
- "funnel",
65467
- // Visualizations (d3 / visualizations/parse.ts)
65468
- "quadrant",
65469
- "slope",
65470
- "arc",
65471
- "wordcloud"
65472
- ]);
65473
- var COLOR_SET = new Set(RECOGNIZED_COLOR_NAMES);
65474
- var NUMERIC_VALUE_RE = /^-?\d[\d,_]*(?:\.\d+)?,?$/;
65475
- function isNumericValueToken(tok) {
65476
- if (!NUMERIC_VALUE_RE.test(tok)) return false;
65477
- return /\d/.test(tok);
65478
- }
65479
- function migrateDataRowCommaLine(line11) {
65480
- const indentMatch = line11.match(/^[ \t]*/);
65481
- const indent = indentMatch ? indentMatch[0] : "";
65482
- const body = line11.substring(indent.length);
65483
- if (!body || body.startsWith("//")) return { line: line11, changed: false };
65484
- const tokens = body.split(/\s+/);
65485
- if (tokens.length < 2) return { line: line11, changed: false };
65486
- let i = tokens.length - 1;
65487
- if (i >= 1 && COLOR_SET.has(tokens[i])) {
65488
- i--;
65489
- }
65490
- const valueStart = (() => {
65491
- let j = i;
65492
- while (j >= 1 && isNumericValueToken(tokens[j])) j--;
65493
- return j + 1;
65494
- })();
65495
- if (valueStart > i || valueStart < 1) return { line: line11, changed: false };
65496
- const valueTokens = tokens.slice(valueStart, i + 1);
65497
- const hasComma = valueTokens.some((t) => t.includes(","));
65498
- if (!hasComma) return { line: line11, changed: false };
65499
- const cleaned = valueTokens.map((t) => t.replace(/,/g, ""));
65500
- const labelPart = tokens.slice(0, valueStart);
65501
- const tail = tokens.slice(i + 1);
65502
- const rebuiltBody = [...labelPart, ...cleaned, ...tail].join(" ");
65503
- return { line: indent + rebuiltBody, changed: rebuiltBody !== body };
65504
- }
65505
-
65506
- // src/migrate/index.ts
65507
- function migrateContent(source) {
65508
- const chartType = parseDgmoChartType(source);
65509
- const lines = source.split("\n");
65510
- const out = [];
65511
- const changedLines = [];
65512
- let changed = false;
65513
- const migrateDataCommas = chartType !== null && DATA_COMMA_CHART_TYPES.has(chartType);
65514
- let firstContentSeen = false;
65515
- for (let i = 0; i < lines.length; i++) {
65516
- let line11 = lines[i];
65517
- const trimmed = line11.trim();
65518
- if (migrateDataCommas && firstContentSeen && trimmed && !trimmed.startsWith("//")) {
65519
- const dc = migrateDataRowCommaLine(line11);
65520
- if (dc.changed) {
65521
- line11 = dc.line;
65522
- changed = true;
65523
- changedLines.push(i + 1);
65524
- }
65525
- }
65526
- if (trimmed && !trimmed.startsWith("//")) firstContentSeen = true;
65527
- if (!isLegacyMetadataLine(line11)) {
65528
- out.push(line11);
65529
- continue;
65530
- }
65531
- const result = transformLine(line11, chartType);
65532
- if (result.changed) {
65533
- changed = true;
65534
- if (!changedLines.includes(i + 1)) changedLines.push(i + 1);
65535
- }
65536
- out.push(result.line);
65537
- }
65538
- return {
65539
- migrated: out.join("\n"),
65540
- changed,
65541
- chartType,
65542
- changedLines
65543
- };
65544
- }
65545
- function formatLineDiff(path, original, migrated) {
65546
- const origLines = original.split("\n");
65547
- const newLines = migrated.split("\n");
65548
- const lines = [`--- ${path}`, `+++ ${path}`];
65549
- const max = Math.max(origLines.length, newLines.length);
65550
- for (let i = 0; i < max; i++) {
65551
- const a = origLines[i];
65552
- const b = newLines[i];
65553
- if (a === b) continue;
65554
- if (a !== void 0) lines.push(`@@ line ${i + 1}
65555
- - ${a}`);
65556
- if (b !== void 0) lines.push(`+ ${b}`);
65557
- }
65558
- return lines.join("\n");
65559
- }
65560
65741
  export {
65561
65742
  ALL_CHART_TYPES,
65562
65743
  ARROW_DIAGNOSTIC_CODES,
@@ -65625,12 +65806,10 @@ export {
65625
65806
  extractSymbols as extractFlowchartSymbols,
65626
65807
  extractSymbols4 as extractInfraSymbols,
65627
65808
  extractPertSymbols,
65628
- findUnsafePipePositions,
65629
65809
  focusBoxesAndLines,
65630
65810
  focusOrgTree,
65631
65811
  formatDateLabel,
65632
65812
  formatDgmoError,
65633
- formatLineDiff,
65634
65813
  getAllChartTypes,
65635
65814
  getAvailablePalettes,
65636
65815
  getExtendedChartLegendGroups,
@@ -65651,7 +65830,6 @@ export {
65651
65830
  isArchiveColumn,
65652
65831
  isExtendedChartType,
65653
65832
  isInvalidColorToken,
65654
- isLegacyMetadataLine,
65655
65833
  isRecognizedColorName,
65656
65834
  isSequenceBlock,
65657
65835
  isSequenceNote,
@@ -65688,7 +65866,6 @@ export {
65688
65866
  mapExportDimensions,
65689
65867
  mapNeutralLandColor,
65690
65868
  measurePertAnalysisBlock,
65691
- migrateContent,
65692
65869
  mix,
65693
65870
  mulberry32,
65694
65871
  nearestNamedColor,
@@ -65816,7 +65993,6 @@ export {
65816
65993
  tidewaterPalette,
65817
65994
  tint,
65818
65995
  tokyoNightPalette,
65819
- transformLine,
65820
65996
  truncateBareUrl,
65821
65997
  parseDgmo as validate,
65822
65998
  validateComputed,