@diagrammo/dgmo 0.36.0 → 0.37.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.
package/.cursorrules CHANGED
@@ -112,6 +112,8 @@ Valid markup is the floor, not the goal. A good diagram reads at a glance. Apply
112
112
 
113
113
  **Need more than the index gives you?** Fetch the per-type section: MCP `get_language_reference(type)` / `get_examples(type)`, or read that type's section below. The `suggest_chart_type` tool returns the chosen type's section automatically.
114
114
 
115
+ **When the type isn't obvious, ask — don't guess.** Call `suggest_chart_type` first. If it returns an `⚠️ ASK THE USER` directive (the request is ambiguous between candidates, or nothing matched), present those candidate options to the user and wait for their pick before generating — never silently choose. A confident result (high/medium) you can proceed with.
116
+
115
117
  ### Common examples (curated, parse-clean)
116
118
 
117
119
  _The most common types, inline so you can generate them without a fetch. For the other 37, get the per-type section (see below)._
@@ -112,6 +112,8 @@ Valid markup is the floor, not the goal. A good diagram reads at a glance. Apply
112
112
 
113
113
  **Need more than the index gives you?** Fetch the per-type section: MCP `get_language_reference(type)` / `get_examples(type)`, or read that type's section below. The `suggest_chart_type` tool returns the chosen type's section automatically.
114
114
 
115
+ **When the type isn't obvious, ask — don't guess.** Call `suggest_chart_type` first. If it returns an `⚠️ ASK THE USER` directive (the request is ambiguous between candidates, or nothing matched), present those candidate options to the user and wait for their pick before generating — never silently choose. A confident result (high/medium) you can proceed with.
116
+
115
117
  ### Common examples (curated, parse-clean)
116
118
 
117
119
  _The most common types, inline so you can generate them without a fetch. For the other 37, get the per-type section (see below)._
package/.windsurfrules CHANGED
@@ -112,6 +112,8 @@ Valid markup is the floor, not the goal. A good diagram reads at a glance. Apply
112
112
 
113
113
  **Need more than the index gives you?** Fetch the per-type section: MCP `get_language_reference(type)` / `get_examples(type)`, or read that type's section below. The `suggest_chart_type` tool returns the chosen type's section automatically.
114
114
 
115
+ **When the type isn't obvious, ask — don't guess.** Call `suggest_chart_type` first. If it returns an `⚠️ ASK THE USER` directive (the request is ambiguous between candidates, or nothing matched), present those candidate options to the user and wait for their pick before generating — never silently choose. A confident result (high/medium) you can proceed with.
116
+
115
117
  ### Common examples (curated, parse-clean)
116
118
 
117
119
  _The most common types, inline so you can generate them without a fetch. For the other 37, get the per-type section (see below)._
package/SKILL.md CHANGED
@@ -117,6 +117,8 @@ Valid markup is the floor, not the goal. A good diagram reads at a glance. Apply
117
117
 
118
118
  **Need more than the index gives you?** Fetch the per-type section: MCP `get_language_reference(type)` / `get_examples(type)`, or read that type's section below. The `suggest_chart_type` tool returns the chosen type's section automatically.
119
119
 
120
+ **When the type isn't obvious, ask — don't guess.** Call `suggest_chart_type` first. If it returns an `⚠️ ASK THE USER` directive (the request is ambiguous between candidates, or nothing matched), present those candidate options to the user and wait for their pick before generating — never silently choose. A confident result (high/medium) you can proceed with.
121
+
120
122
  ### Common examples (curated, parse-clean)
121
123
 
122
124
  _The most common types, inline so you can generate them without a fetch. For the other 37, get the per-type section (see below)._
package/dist/advanced.cjs CHANGED
@@ -16714,6 +16714,7 @@ function parseInfra(content) {
16714
16714
  let currentGroup = null;
16715
16715
  let currentTagGroup = null;
16716
16716
  let baseIndent = 0;
16717
+ let firstLineConsumed = false;
16717
16718
  function finishCurrentNode() {
16718
16719
  if (!currentNode) return;
16719
16720
  const key = currentNode.id;
@@ -16721,7 +16722,8 @@ function parseInfra(content) {
16721
16722
  if (existing) {
16722
16723
  const incomingDisplay = displayName(currentNode.label);
16723
16724
  const existingDisplay = displayName(existing.label);
16724
- if (incomingDisplay !== existingDisplay) {
16725
+ const isAliasRef = nameAliasMap.get(currentNode.label) === key;
16726
+ if (incomingDisplay !== existingDisplay && !isAliasRef) {
16725
16727
  result.diagnostics.push(
16726
16728
  makeDgmoError(
16727
16729
  currentNode.lineNumber,
@@ -16787,19 +16789,22 @@ function parseInfra(content) {
16787
16789
  if (indent === 0 && currentNode && !trimmed.startsWith("-")) {
16788
16790
  finishCurrentNode();
16789
16791
  }
16790
- const firstLineResult = parseFirstLine(trimmed);
16791
- if (firstLineResult) {
16792
- if (firstLineResult.chartType !== "infra") {
16793
- setError(
16794
- lineNumber,
16795
- `Expected chart type 'infra', got '${firstLineResult.chartType}'`
16796
- );
16797
- }
16798
- if (firstLineResult.title) {
16799
- result.title = firstLineResult.title;
16800
- result.titleLineNumber = lineNumber;
16792
+ if (!firstLineConsumed) {
16793
+ firstLineConsumed = true;
16794
+ const firstLineResult = parseFirstLine(trimmed);
16795
+ if (firstLineResult) {
16796
+ if (firstLineResult.chartType !== "infra") {
16797
+ setError(
16798
+ lineNumber,
16799
+ `Expected chart type 'infra', got '${firstLineResult.chartType}'`
16800
+ );
16801
+ }
16802
+ if (firstLineResult.title) {
16803
+ result.title = firstLineResult.title;
16804
+ result.titleLineNumber = lineNumber;
16805
+ }
16806
+ continue;
16801
16807
  }
16802
- continue;
16803
16808
  }
16804
16809
  if (/^direction-tb$/i.test(trimmed)) {
16805
16810
  result.direction = "TB";
@@ -16865,18 +16870,20 @@ function parseInfra(content) {
16865
16870
  if (compMatch) {
16866
16871
  finishCurrentNode();
16867
16872
  finishCurrentTagGroup();
16868
- const rawName = (compMatch[1] ?? compMatch[2] ?? "").trim();
16873
+ const { qname, uname, alias: asAlias, meta } = compMatch.groups;
16874
+ const rawName = (qname ?? uname ?? "").trim();
16869
16875
  const peeled = peelInfraDecorations(rawName, lineNumber);
16870
16876
  const name = peeled.label;
16871
- const rest = compMatch[3] || "";
16877
+ const alias = asAlias ?? peeled.alias;
16878
+ const rest = meta ?? "";
16872
16879
  const { tags } = extractPipeMetadata(rest);
16873
16880
  warnUnknownMetaKeys(
16874
16881
  tags,
16875
16882
  withTagAliases(INFRA_REGISTRY, tagAliasSet),
16876
16883
  (msg) => warn(lineNumber, msg)
16877
16884
  );
16878
- const id = nodeId2(name);
16879
- if (peeled.alias) nameAliasMap.set(peeled.alias, id);
16885
+ const id = nameAliasMap.get(name) ?? nodeId2(name);
16886
+ if (alias) nameAliasMap.set(alias, id);
16880
16887
  const isEdge = EDGE_NODE_NAMES.has(id.toLowerCase());
16881
16888
  currentNode = {
16882
16889
  id,
@@ -16960,18 +16967,20 @@ function parseInfra(content) {
16960
16967
  const compMatch = trimmed.match(COMPONENT_RE);
16961
16968
  if (compMatch) {
16962
16969
  finishCurrentTagGroup();
16963
- const rawName = (compMatch[1] ?? compMatch[2] ?? "").trim();
16970
+ const { qname, uname, alias: asAlias, meta } = compMatch.groups;
16971
+ const rawName = (qname ?? uname ?? "").trim();
16964
16972
  const peeled = peelInfraDecorations(rawName, lineNumber);
16965
16973
  const name = peeled.label;
16966
- const rest = compMatch[3] || "";
16974
+ const alias = asAlias ?? peeled.alias;
16975
+ const rest = meta ?? "";
16967
16976
  const { tags: nodeTags } = extractPipeMetadata(rest);
16968
16977
  warnUnknownMetaKeys(
16969
16978
  nodeTags,
16970
16979
  withTagAliases(INFRA_REGISTRY, tagAliasSet),
16971
16980
  (msg) => warn(lineNumber, msg)
16972
16981
  );
16973
- const id = nodeId2(name);
16974
- if (peeled.alias) nameAliasMap.set(peeled.alias, id);
16982
+ const id = nameAliasMap.get(name) ?? nodeId2(name);
16983
+ if (alias) nameAliasMap.set(alias, id);
16975
16984
  const tags = currentGroup.metadata ? { ...currentGroup.metadata, ...nodeTags } : nodeTags;
16976
16985
  currentNode = {
16977
16986
  id,
@@ -17162,6 +17171,11 @@ function parseInfra(content) {
17162
17171
  }
17163
17172
  continue;
17164
17173
  }
17174
+ if (currentNode && tagAliasSet.has(normalizeName(key))) {
17175
+ const mut = nodeMutableTags.get(currentNode.id);
17176
+ if (mut) mut[tagAttrKey(key)] = rawVal;
17177
+ continue;
17178
+ }
17165
17179
  if (!INFRA_BEHAVIOR_KEYS.has(key) && !EDGE_ONLY_KEYS.has(key)) {
17166
17180
  const allKeys = [...INFRA_BEHAVIOR_KEYS, ...EDGE_ONLY_KEYS];
17167
17181
  const hint = suggest(key, allKeys);
@@ -17227,18 +17241,20 @@ function parseInfra(content) {
17227
17241
  finishCurrentNode();
17228
17242
  const compMatch = trimmed.match(COMPONENT_RE);
17229
17243
  if (compMatch) {
17230
- const rawName = (compMatch[1] ?? compMatch[2] ?? "").trim();
17244
+ const { qname, uname, alias: asAlias, meta } = compMatch.groups;
17245
+ const rawName = (qname ?? uname ?? "").trim();
17231
17246
  const peeled = peelInfraDecorations(rawName, lineNumber);
17232
17247
  const name = peeled.label;
17233
- const rest = compMatch[3] || "";
17248
+ const alias = asAlias ?? peeled.alias;
17249
+ const rest = meta ?? "";
17234
17250
  const { tags: nodeTags } = extractPipeMetadata(rest);
17235
17251
  warnUnknownMetaKeys(
17236
17252
  nodeTags,
17237
17253
  withTagAliases(INFRA_REGISTRY, tagAliasSet),
17238
17254
  (msg) => warn(lineNumber, msg)
17239
17255
  );
17240
- const id = nodeId2(name);
17241
- if (peeled.alias) nameAliasMap.set(peeled.alias, id);
17256
+ const id = nameAliasMap.get(name) ?? nodeId2(name);
17257
+ if (alias) nameAliasMap.set(alias, id);
17242
17258
  const tags = currentGroup.metadata ? { ...currentGroup.metadata, ...nodeTags } : nodeTags;
17243
17259
  currentNode = {
17244
17260
  id,
@@ -17260,18 +17276,20 @@ function parseInfra(content) {
17260
17276
  finishCurrentNode();
17261
17277
  finishCurrentTagGroup();
17262
17278
  currentGroup = null;
17263
- const rawName = (compMatch[1] ?? compMatch[2] ?? "").trim();
17279
+ const { qname, uname, alias: asAlias, meta } = compMatch.groups;
17280
+ const rawName = (qname ?? uname ?? "").trim();
17264
17281
  const peeled = peelInfraDecorations(rawName, lineNumber);
17265
17282
  const name = peeled.label;
17266
- const rest = compMatch[3] || "";
17283
+ const alias = asAlias ?? peeled.alias;
17284
+ const rest = meta ?? "";
17267
17285
  const { tags } = extractPipeMetadata(rest);
17268
17286
  warnUnknownMetaKeys(
17269
17287
  tags,
17270
17288
  withTagAliases(INFRA_REGISTRY, tagAliasSet),
17271
17289
  (msg) => warn(lineNumber, msg)
17272
17290
  );
17273
- const id = nodeId2(name);
17274
- if (peeled.alias) nameAliasMap.set(peeled.alias, id);
17291
+ const id = nameAliasMap.get(name) ?? nodeId2(name);
17292
+ if (alias) nameAliasMap.set(alias, id);
17275
17293
  currentNode = {
17276
17294
  id,
17277
17295
  label: name,
@@ -17471,7 +17489,7 @@ var init_parser15 = __esm({
17471
17489
  DEPRECATED_FANOUT_RE = /\bx(\d+)\s*$/;
17472
17490
  GROUP_RE = /^\[([^\]]+)\]\s*(?:as\s+([A-Za-z][A-Za-z0-9_]{0,11})\s*)?((?:\|.*)|(?:[a-zA-Z_]\w*\s*:.*))?$/;
17473
17491
  TAG_VALUE_RE = /^(\w[\w\s]+?)\s*$/;
17474
- COMPONENT_RE = /^(?:"([^"]+)"|([a-zA-Z_][\w -]*?))\s*((?:\|.*)|(?:[a-zA-Z_]\w*\s*:.*))?$/;
17492
+ COMPONENT_RE = /^(?:"(?<qname>[^"]+)"|(?<uname>[a-zA-Z_][\w -]*?))(?:\s+as\s+(?<alias>[A-Za-z][A-Za-z0-9_]{0,11}))?\s*(?<meta>(?:\|.*)|(?:[a-zA-Z_]\w*\s*:.*))?$/;
17475
17493
  PIPE_META_RE = /[|,]\s*(\w+)\s*:\s*([^|,]+)/g;
17476
17494
  SAMELINE_META_RE = /(?:^|[\s,])(\w+)\s*:\s*([^,]+?)(?=\s*,|\s*$)/g;
17477
17495
  PROPERTY_RE = /^([\w-]+):\s*(.+)$/;
@@ -18955,8 +18973,8 @@ function parseMap(content, palette) {
18955
18973
  continue;
18956
18974
  }
18957
18975
  if (open.route && indent > open.route.indent) {
18958
- const leg = parseLeg(trimmed, lineNumber, open.route.route.style);
18959
- open.route.route.legs.push(leg);
18976
+ const leg = parseLeg(trimmed, lineNumber);
18977
+ if (leg) open.route.route.legs.push(leg);
18960
18978
  continue;
18961
18979
  }
18962
18980
  if (open.poi && indent > open.poi.indent) {
@@ -18985,6 +19003,14 @@ function parseMap(content, palette) {
18985
19003
  Object.assign(open.poi.poi.meta, split.meta);
18986
19004
  continue;
18987
19005
  }
19006
+ if (split.name) {
19007
+ const src = open.poi.poi.alias ?? poiName(open.poi.poi.pos) ?? "the poi";
19008
+ pushError(
19009
+ lineNumber,
19010
+ `Malformed hub edge: "${trimmed}" \u2014 an indented line under a poi is an edge from "${src}" and needs an arrow glyph (\`-> dest\`, \`~> dest\`, or labeled \`-label-> dest\`). For a standalone place, declare it at the top level. (\xA724B.5)`
19011
+ );
19012
+ continue;
19013
+ }
18988
19014
  }
18989
19015
  closeBlocks();
18990
19016
  const firstWord = trimmed.split(/\s+/)[0];
@@ -19233,31 +19259,44 @@ function parseMap(content, palette) {
19233
19259
  const { tags, meta } = partitionMeta(split.meta, tagGroupNames());
19234
19260
  const originLabel = meta["label"];
19235
19261
  const originValue = meta["value"];
19236
- const style = meta["style"] === "arc" ? "arc" : "straight";
19262
+ if (meta["style"] !== void 0) {
19263
+ pushError(
19264
+ line11,
19265
+ "route header no longer takes `style:` \u2014 set leg shape with the arrow glyph instead (`~\u2026~>` for an arc leg, `-\u2026->` for a straight one). (\xA724B.6)"
19266
+ );
19267
+ }
19237
19268
  const route = {
19238
19269
  origin: pos,
19239
19270
  ...split.alias !== void 0 && { originAlias: split.alias },
19240
19271
  ...originLabel !== void 0 && { originLabel },
19241
19272
  ...originValue !== void 0 && { originValue },
19242
19273
  originTags: tags,
19243
- style,
19244
19274
  legs: [],
19245
19275
  lineNumber: line11
19246
19276
  };
19247
19277
  routes.push(route);
19248
19278
  open.route = { route, indent };
19249
19279
  }
19250
- function parseLeg(trimmed, line11, headerStyle) {
19251
- let arrowStyle = "straight";
19252
- let label;
19253
- let rest = trimmed;
19280
+ function parseLeg(trimmed, line11) {
19254
19281
  const m = trimmed.match(LEG_ARROW_RE);
19255
- if (m) {
19256
- const arr = classifyArrow(m[1], line11);
19257
- arrowStyle = arr.style;
19258
- label = arr.label;
19259
- rest = m[2];
19282
+ if (!m) {
19283
+ pushError(
19284
+ line11,
19285
+ `Malformed route leg: "${trimmed}" \u2014 a leg needs an arrow glyph (\`-> dest\`, \`~> dest\`, or labeled \`-label-> dest\` / \`~label~> dest\`). (\xA724B.6)`
19286
+ );
19287
+ return null;
19260
19288
  }
19289
+ const arr = classifyArrow(m[1], line11);
19290
+ if (!arr.directed) {
19291
+ pushError(
19292
+ line11,
19293
+ `A route leg is directional \u2014 "${trimmed}" uses an undirected glyph; use \`-> \` (straight) or \`~> \` (arc). (\xA724B.6)`
19294
+ );
19295
+ return null;
19296
+ }
19297
+ const arrowStyle = arr.style;
19298
+ const label = arr.label;
19299
+ const rest = m[2];
19261
19300
  const split = splitNameAndMeta(
19262
19301
  rest,
19263
19302
  registry(),
@@ -19273,10 +19312,9 @@ function parseMap(content, palette) {
19273
19312
  const { tags, meta } = partitionMeta(split.meta, tagGroupNames());
19274
19313
  const value = meta["value"];
19275
19314
  const destLabel = meta["label"];
19276
- const style = arrowStyle === "arc" || headerStyle === "arc" ? "arc" : "straight";
19277
19315
  return {
19278
19316
  ...label !== void 0 && { label },
19279
- style,
19317
+ style: arrowStyle,
19280
19318
  ...value !== void 0 && { value },
19281
19319
  dest: pos,
19282
19320
  ...split.alias !== void 0 && { destAlias: split.alias },
@@ -54080,13 +54118,15 @@ function layoutMap(resolvedIn, data, size, opts) {
54080
54118
  labelY: lp.y
54081
54119
  };
54082
54120
  const routeLabelStyle = leg.label !== void 0 ? labelOnFill(fillAt(bow.labelX, bow.labelY)) : void 0;
54121
+ const routeVal = Number(leg.value);
54083
54122
  legs.push({
54084
54123
  d: legPath(a, b, bow.curved, bow.offset),
54085
- width: routeWidthFor(Number(leg.value)),
54124
+ width: routeWidthFor(routeVal),
54086
54125
  color: lineColor(leg.tags) ?? mix(palette.text, palette.bg, 72),
54087
54126
  arrow: true,
54088
54127
  fromId: leg.fromId,
54089
54128
  toId: leg.toId,
54129
+ ...Number.isFinite(routeVal) && routeVal > 0 && { value: routeVal },
54090
54130
  ...Object.keys(leg.tags).length > 0 && { tags: leg.tags },
54091
54131
  lineNumber: leg.lineNumber,
54092
54132
  ...leg.label !== void 0 && {
@@ -54133,6 +54173,7 @@ function layoutMap(resolvedIn, data, size, opts) {
54133
54173
  labelY: lp.y
54134
54174
  };
54135
54175
  const edgeLabelStyle = e.label !== void 0 ? labelOnFill(fillAt(bow.labelX, bow.labelY)) : void 0;
54176
+ const edgeVal = Number(e.meta["value"]);
54136
54177
  legs.push({
54137
54178
  d: legPath(a, b, bow.curved, bow.offset),
54138
54179
  width: widthFor(e),
@@ -54140,6 +54181,7 @@ function layoutMap(resolvedIn, data, size, opts) {
54140
54181
  arrow: e.directed,
54141
54182
  fromId: e.fromId,
54142
54183
  toId: e.toId,
54184
+ ...Number.isFinite(edgeVal) && edgeVal > 0 && { value: edgeVal },
54143
54185
  ...Object.keys(e.tags).length > 0 && { tags: e.tags },
54144
54186
  lineNumber: e.lineNumber,
54145
54187
  ...e.label !== void 0 && {
@@ -54297,18 +54339,49 @@ function layoutMap(resolvedIn, data, size, opts) {
54297
54339
  const SHORT_HOP_MAX = Math.max(46, Math.min(width, height) * 0.11);
54298
54340
  const STEP = 4;
54299
54341
  const HOP_GAP = 9;
54342
+ const CLEAR_STEP = 6;
54343
+ const CLEAR_MAX = SHORT_HOP_MAX * 3;
54344
+ const CLEAR_TOL = CLEAR_STEP;
54345
+ const clearanceAt = (px, py, ox, oy) => {
54346
+ const baseAng = Math.atan2(oy, ox);
54347
+ let minClear = CLEAR_MAX;
54348
+ for (let k = -2; k <= 2; k++) {
54349
+ const a = baseAng + k * Math.PI / 4;
54350
+ const rx = Math.cos(a);
54351
+ const ry = Math.sin(a);
54352
+ let d = CLEAR_STEP;
54353
+ for (; d <= CLEAR_MAX; d += CLEAR_STEP) {
54354
+ const qx = px + rx * d;
54355
+ const qy = py + ry * d;
54356
+ if (qx < FIT_PAD || qy < topPad || qx > width - FIT_PAD || qy > height - FIT_PAD)
54357
+ break;
54358
+ if (fillAt(qx, qy) !== water) break;
54359
+ }
54360
+ if (d < minClear) minClear = d;
54361
+ }
54362
+ return minClear;
54363
+ };
54300
54364
  const tryShortHopCallout = (cx, cy, fill2, name, value, font = FONT2, gap = HOP_GAP, maxLen = SHORT_HOP_MAX) => {
54301
54365
  const chipW = stackW(name, value, font);
54302
54366
  const chipH = stackH(value !== void 0, font);
54367
+ const D = Math.SQRT1_2;
54303
54368
  const dirs = [
54304
54369
  [0, -1],
54305
- // north (over Canada)
54370
+ // north
54306
54371
  [0, 1],
54307
- // south (over Mexico / Gulf)
54372
+ // south
54308
54373
  [1, 0],
54309
- // east (Atlantic)
54310
- [-1, 0]
54311
- // west (Pacific)
54374
+ // east
54375
+ [-1, 0],
54376
+ // west
54377
+ [D, -D],
54378
+ // NE
54379
+ [D, D],
54380
+ // SE
54381
+ [-D, -D],
54382
+ // NW
54383
+ [-D, D]
54384
+ // SW
54312
54385
  ];
54313
54386
  let best = null;
54314
54387
  for (const [dx, dy] of dirs) {
@@ -54358,13 +54431,16 @@ function layoutMap(resolvedIn, data, size, opts) {
54358
54431
  (p) => segmentRectOverlap(cx, cy, innerX, innerY, p)
54359
54432
  ))
54360
54433
  continue;
54361
- if (best === null || len < best.len)
54434
+ const clear = clearanceAt(ccx, ccy, dx, dy);
54435
+ const better = best === null || clear > best.clear + CLEAR_TOL || clear >= best.clear - CLEAR_TOL && len < best.len;
54436
+ if (better)
54362
54437
  best = {
54363
54438
  x: ccx,
54364
54439
  y: ccy,
54365
54440
  rect,
54366
54441
  leader: [cx, cy, innerX, innerY],
54367
- len
54442
+ len,
54443
+ clear
54368
54444
  };
54369
54445
  }
54370
54446
  return best ? { x: best.x, y: best.y, rect: best.rect, leader: best.leader } : null;
@@ -55025,6 +55101,9 @@ function layoutMap(resolvedIn, data, size, opts) {
55025
55101
  const nx = -dy / len;
55026
55102
  const ny = dx / len;
55027
55103
  const perp = labelH + 2;
55104
+ const stroke2 = lg.width ?? W_MIN;
55105
+ const clearW = stroke2 / 2 + labelH / 2 + 3;
55106
+ const offList = stroke2 >= 4 ? [clearW, -clearW, clearW + perp, -(clearW + perp), 0] : [0, perp, -perp, 2 * perp, -2 * perp];
55028
55107
  const pm = /^M(-?[\d.]+),(-?[\d.]+)(?:L(-?[\d.]+),(-?[\d.]+)|Q(-?[\d.]+),(-?[\d.]+) (-?[\d.]+),(-?[\d.]+))$/.exec(
55029
55108
  lg.d
55030
55109
  );
@@ -55044,7 +55123,7 @@ function layoutMap(resolvedIn, data, size, opts) {
55044
55123
  return [a.cx + dx * t, a.cy + dy * t];
55045
55124
  };
55046
55125
  const candidates = [];
55047
- for (const off of [0, perp, -perp, 2 * perp, -2 * perp])
55126
+ for (const off of offList)
55048
55127
  for (const t of T_LIST) {
55049
55128
  const [bx, by] = pointAt(t);
55050
55129
  candidates.push([bx + nx * off, by + ny * off - 4]);
@@ -55796,6 +55875,12 @@ function renderMap(container, resolved, data, palette, isDark, onClickItem, expo
55796
55875
  }
55797
55876
  }
55798
55877
  wireSync(p, leg.lineNumber);
55878
+ if (leg.value !== void 0) {
55879
+ p.attr("data-value", leg.value);
55880
+ p.append("title").text(
55881
+ leg.label !== void 0 ? `${leg.label}: ${leg.value}` : `${leg.value}`
55882
+ );
55883
+ }
55799
55884
  if (arrowTri) {
55800
55885
  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);
55801
55886
  }
@@ -299,9 +299,10 @@ interface MapPoi {
299
299
  readonly lineNumber: number;
300
300
  }
301
301
  /** One leg of a route (§24B.6): an edge from the previous stop to `dest`. Reuses
302
- * the edge arrow idiom — in-arrow text = leg label, `value:` = leg thickness,
303
- * `->`/`~>` (or the header `style: arc`) = shape. A tag on the leg line colours
304
- * the LINE (§24B.6); `label:`/`as` still name the DESTINATION stop. */
302
+ * the indented edge arrow idiom (same as sitemap) — in-arrow text = leg label,
303
+ * `value:` = leg thickness, the arrow glyph = shape (`-…->` straight, `~…~>`
304
+ * arc). A tag on the leg line colours the LINE (§24B.6); `label:`/`as` still
305
+ * name the DESTINATION stop. The arrow is required — a bare destination errors. */
305
306
  interface MapRouteLeg {
306
307
  readonly label?: string;
307
308
  readonly style: 'straight' | 'arc';
@@ -314,16 +315,16 @@ interface MapRouteLeg {
314
315
  readonly tags: Readonly<Record<string, string>>;
315
316
  readonly lineNumber: number;
316
317
  }
317
- /** An ordered, auto-numbered route (§24B.6): `route <origin> [style: arc]` + a
318
- * sequence of indented arrow legs, each continuing from the previous stop.
319
- * Repeat the origin as a leg's destination to close a loop. */
318
+ /** An ordered, auto-numbered route (§24B.6): `route <origin>` + a sequence of
319
+ * indented arrow legs, each continuing from the previous stop. Leg shape is
320
+ * per-leg (the arrow glyph) — there is no header-level shape option. Repeat the
321
+ * origin as a leg's destination to close a loop. */
320
322
  interface MapRoute {
321
323
  readonly origin: PoiPos;
322
324
  readonly originAlias?: string;
323
325
  readonly originLabel?: string;
324
326
  readonly originValue?: string;
325
327
  readonly originTags: Readonly<Record<string, string>>;
326
- readonly style: 'straight' | 'arc';
327
328
  readonly legs: readonly MapRouteLeg[];
328
329
  readonly lineNumber: number;
329
330
  }
@@ -5014,6 +5015,10 @@ interface MapLayoutLeg {
5014
5015
  * (§24B.6). Omitted when the leg carries no tag. */
5015
5016
  readonly tags?: Readonly<Record<string, string>>;
5016
5017
  readonly label?: string;
5018
+ /** The leg's numeric weight (the `value:` metadata) when present and positive.
5019
+ * Drives {@link width}, but kept here verbatim so the renderer can surface it
5020
+ * on hover (a `<title>` tooltip) — the width alone is lossy. */
5021
+ readonly value?: number;
5017
5022
  readonly labelX?: number;
5018
5023
  readonly labelY?: number;
5019
5024
  /** Text colour for the label — contrast-picked against the background fill the
@@ -299,9 +299,10 @@ interface MapPoi {
299
299
  readonly lineNumber: number;
300
300
  }
301
301
  /** One leg of a route (§24B.6): an edge from the previous stop to `dest`. Reuses
302
- * the edge arrow idiom — in-arrow text = leg label, `value:` = leg thickness,
303
- * `->`/`~>` (or the header `style: arc`) = shape. A tag on the leg line colours
304
- * the LINE (§24B.6); `label:`/`as` still name the DESTINATION stop. */
302
+ * the indented edge arrow idiom (same as sitemap) — in-arrow text = leg label,
303
+ * `value:` = leg thickness, the arrow glyph = shape (`-…->` straight, `~…~>`
304
+ * arc). A tag on the leg line colours the LINE (§24B.6); `label:`/`as` still
305
+ * name the DESTINATION stop. The arrow is required — a bare destination errors. */
305
306
  interface MapRouteLeg {
306
307
  readonly label?: string;
307
308
  readonly style: 'straight' | 'arc';
@@ -314,16 +315,16 @@ interface MapRouteLeg {
314
315
  readonly tags: Readonly<Record<string, string>>;
315
316
  readonly lineNumber: number;
316
317
  }
317
- /** An ordered, auto-numbered route (§24B.6): `route <origin> [style: arc]` + a
318
- * sequence of indented arrow legs, each continuing from the previous stop.
319
- * Repeat the origin as a leg's destination to close a loop. */
318
+ /** An ordered, auto-numbered route (§24B.6): `route <origin>` + a sequence of
319
+ * indented arrow legs, each continuing from the previous stop. Leg shape is
320
+ * per-leg (the arrow glyph) — there is no header-level shape option. Repeat the
321
+ * origin as a leg's destination to close a loop. */
320
322
  interface MapRoute {
321
323
  readonly origin: PoiPos;
322
324
  readonly originAlias?: string;
323
325
  readonly originLabel?: string;
324
326
  readonly originValue?: string;
325
327
  readonly originTags: Readonly<Record<string, string>>;
326
- readonly style: 'straight' | 'arc';
327
328
  readonly legs: readonly MapRouteLeg[];
328
329
  readonly lineNumber: number;
329
330
  }
@@ -5014,6 +5015,10 @@ interface MapLayoutLeg {
5014
5015
  * (§24B.6). Omitted when the leg carries no tag. */
5015
5016
  readonly tags?: Readonly<Record<string, string>>;
5016
5017
  readonly label?: string;
5018
+ /** The leg's numeric weight (the `value:` metadata) when present and positive.
5019
+ * Drives {@link width}, but kept here verbatim so the renderer can surface it
5020
+ * on hover (a `<title>` tooltip) — the width alone is lossy. */
5021
+ readonly value?: number;
5017
5022
  readonly labelX?: number;
5018
5023
  readonly labelY?: number;
5019
5024
  /** Text colour for the label — contrast-picked against the background fill the