@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/dist/index.js CHANGED
@@ -16681,6 +16681,7 @@ function parseInfra(content) {
16681
16681
  let currentGroup = null;
16682
16682
  let currentTagGroup = null;
16683
16683
  let baseIndent = 0;
16684
+ let firstLineConsumed = false;
16684
16685
  function finishCurrentNode() {
16685
16686
  if (!currentNode) return;
16686
16687
  const key = currentNode.id;
@@ -16688,7 +16689,8 @@ function parseInfra(content) {
16688
16689
  if (existing) {
16689
16690
  const incomingDisplay = displayName(currentNode.label);
16690
16691
  const existingDisplay = displayName(existing.label);
16691
- if (incomingDisplay !== existingDisplay) {
16692
+ const isAliasRef = nameAliasMap.get(currentNode.label) === key;
16693
+ if (incomingDisplay !== existingDisplay && !isAliasRef) {
16692
16694
  result.diagnostics.push(
16693
16695
  makeDgmoError(
16694
16696
  currentNode.lineNumber,
@@ -16754,19 +16756,22 @@ function parseInfra(content) {
16754
16756
  if (indent === 0 && currentNode && !trimmed.startsWith("-")) {
16755
16757
  finishCurrentNode();
16756
16758
  }
16757
- const firstLineResult = parseFirstLine(trimmed);
16758
- if (firstLineResult) {
16759
- if (firstLineResult.chartType !== "infra") {
16760
- setError(
16761
- lineNumber,
16762
- `Expected chart type 'infra', got '${firstLineResult.chartType}'`
16763
- );
16764
- }
16765
- if (firstLineResult.title) {
16766
- result.title = firstLineResult.title;
16767
- result.titleLineNumber = lineNumber;
16759
+ if (!firstLineConsumed) {
16760
+ firstLineConsumed = true;
16761
+ const firstLineResult = parseFirstLine(trimmed);
16762
+ if (firstLineResult) {
16763
+ if (firstLineResult.chartType !== "infra") {
16764
+ setError(
16765
+ lineNumber,
16766
+ `Expected chart type 'infra', got '${firstLineResult.chartType}'`
16767
+ );
16768
+ }
16769
+ if (firstLineResult.title) {
16770
+ result.title = firstLineResult.title;
16771
+ result.titleLineNumber = lineNumber;
16772
+ }
16773
+ continue;
16768
16774
  }
16769
- continue;
16770
16775
  }
16771
16776
  if (/^direction-tb$/i.test(trimmed)) {
16772
16777
  result.direction = "TB";
@@ -16832,18 +16837,20 @@ function parseInfra(content) {
16832
16837
  if (compMatch) {
16833
16838
  finishCurrentNode();
16834
16839
  finishCurrentTagGroup();
16835
- const rawName = (compMatch[1] ?? compMatch[2] ?? "").trim();
16840
+ const { qname, uname, alias: asAlias, meta } = compMatch.groups;
16841
+ const rawName = (qname ?? uname ?? "").trim();
16836
16842
  const peeled = peelInfraDecorations(rawName, lineNumber);
16837
16843
  const name = peeled.label;
16838
- const rest = compMatch[3] || "";
16844
+ const alias = asAlias ?? peeled.alias;
16845
+ const rest = meta ?? "";
16839
16846
  const { tags } = extractPipeMetadata(rest);
16840
16847
  warnUnknownMetaKeys(
16841
16848
  tags,
16842
16849
  withTagAliases(INFRA_REGISTRY, tagAliasSet),
16843
16850
  (msg) => warn(lineNumber, msg)
16844
16851
  );
16845
- const id = nodeId2(name);
16846
- if (peeled.alias) nameAliasMap.set(peeled.alias, id);
16852
+ const id = nameAliasMap.get(name) ?? nodeId2(name);
16853
+ if (alias) nameAliasMap.set(alias, id);
16847
16854
  const isEdge = EDGE_NODE_NAMES.has(id.toLowerCase());
16848
16855
  currentNode = {
16849
16856
  id,
@@ -16927,18 +16934,20 @@ function parseInfra(content) {
16927
16934
  const compMatch = trimmed.match(COMPONENT_RE);
16928
16935
  if (compMatch) {
16929
16936
  finishCurrentTagGroup();
16930
- const rawName = (compMatch[1] ?? compMatch[2] ?? "").trim();
16937
+ const { qname, uname, alias: asAlias, meta } = compMatch.groups;
16938
+ const rawName = (qname ?? uname ?? "").trim();
16931
16939
  const peeled = peelInfraDecorations(rawName, lineNumber);
16932
16940
  const name = peeled.label;
16933
- const rest = compMatch[3] || "";
16941
+ const alias = asAlias ?? peeled.alias;
16942
+ const rest = meta ?? "";
16934
16943
  const { tags: nodeTags } = extractPipeMetadata(rest);
16935
16944
  warnUnknownMetaKeys(
16936
16945
  nodeTags,
16937
16946
  withTagAliases(INFRA_REGISTRY, tagAliasSet),
16938
16947
  (msg) => warn(lineNumber, msg)
16939
16948
  );
16940
- const id = nodeId2(name);
16941
- if (peeled.alias) nameAliasMap.set(peeled.alias, id);
16949
+ const id = nameAliasMap.get(name) ?? nodeId2(name);
16950
+ if (alias) nameAliasMap.set(alias, id);
16942
16951
  const tags = currentGroup.metadata ? { ...currentGroup.metadata, ...nodeTags } : nodeTags;
16943
16952
  currentNode = {
16944
16953
  id,
@@ -17129,6 +17138,11 @@ function parseInfra(content) {
17129
17138
  }
17130
17139
  continue;
17131
17140
  }
17141
+ if (currentNode && tagAliasSet.has(normalizeName(key))) {
17142
+ const mut = nodeMutableTags.get(currentNode.id);
17143
+ if (mut) mut[tagAttrKey(key)] = rawVal;
17144
+ continue;
17145
+ }
17132
17146
  if (!INFRA_BEHAVIOR_KEYS.has(key) && !EDGE_ONLY_KEYS.has(key)) {
17133
17147
  const allKeys = [...INFRA_BEHAVIOR_KEYS, ...EDGE_ONLY_KEYS];
17134
17148
  const hint = suggest(key, allKeys);
@@ -17194,18 +17208,20 @@ function parseInfra(content) {
17194
17208
  finishCurrentNode();
17195
17209
  const compMatch = trimmed.match(COMPONENT_RE);
17196
17210
  if (compMatch) {
17197
- const rawName = (compMatch[1] ?? compMatch[2] ?? "").trim();
17211
+ const { qname, uname, alias: asAlias, meta } = compMatch.groups;
17212
+ const rawName = (qname ?? uname ?? "").trim();
17198
17213
  const peeled = peelInfraDecorations(rawName, lineNumber);
17199
17214
  const name = peeled.label;
17200
- const rest = compMatch[3] || "";
17215
+ const alias = asAlias ?? peeled.alias;
17216
+ const rest = meta ?? "";
17201
17217
  const { tags: nodeTags } = extractPipeMetadata(rest);
17202
17218
  warnUnknownMetaKeys(
17203
17219
  nodeTags,
17204
17220
  withTagAliases(INFRA_REGISTRY, tagAliasSet),
17205
17221
  (msg) => warn(lineNumber, msg)
17206
17222
  );
17207
- const id = nodeId2(name);
17208
- if (peeled.alias) nameAliasMap.set(peeled.alias, id);
17223
+ const id = nameAliasMap.get(name) ?? nodeId2(name);
17224
+ if (alias) nameAliasMap.set(alias, id);
17209
17225
  const tags = currentGroup.metadata ? { ...currentGroup.metadata, ...nodeTags } : nodeTags;
17210
17226
  currentNode = {
17211
17227
  id,
@@ -17227,18 +17243,20 @@ function parseInfra(content) {
17227
17243
  finishCurrentNode();
17228
17244
  finishCurrentTagGroup();
17229
17245
  currentGroup = null;
17230
- const rawName = (compMatch[1] ?? compMatch[2] ?? "").trim();
17246
+ const { qname, uname, alias: asAlias, meta } = compMatch.groups;
17247
+ const rawName = (qname ?? uname ?? "").trim();
17231
17248
  const peeled = peelInfraDecorations(rawName, lineNumber);
17232
17249
  const name = peeled.label;
17233
- const rest = compMatch[3] || "";
17250
+ const alias = asAlias ?? peeled.alias;
17251
+ const rest = meta ?? "";
17234
17252
  const { tags } = extractPipeMetadata(rest);
17235
17253
  warnUnknownMetaKeys(
17236
17254
  tags,
17237
17255
  withTagAliases(INFRA_REGISTRY, tagAliasSet),
17238
17256
  (msg) => warn(lineNumber, msg)
17239
17257
  );
17240
- const id = nodeId2(name);
17241
- if (peeled.alias) nameAliasMap.set(peeled.alias, id);
17258
+ const id = nameAliasMap.get(name) ?? nodeId2(name);
17259
+ if (alias) nameAliasMap.set(alias, id);
17242
17260
  currentNode = {
17243
17261
  id,
17244
17262
  label: name,
@@ -17438,7 +17456,7 @@ var init_parser15 = __esm({
17438
17456
  DEPRECATED_FANOUT_RE = /\bx(\d+)\s*$/;
17439
17457
  GROUP_RE = /^\[([^\]]+)\]\s*(?:as\s+([A-Za-z][A-Za-z0-9_]{0,11})\s*)?((?:\|.*)|(?:[a-zA-Z_]\w*\s*:.*))?$/;
17440
17458
  TAG_VALUE_RE = /^(\w[\w\s]+?)\s*$/;
17441
- COMPONENT_RE = /^(?:"([^"]+)"|([a-zA-Z_][\w -]*?))\s*((?:\|.*)|(?:[a-zA-Z_]\w*\s*:.*))?$/;
17459
+ 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*:.*))?$/;
17442
17460
  PIPE_META_RE = /[|,]\s*(\w+)\s*:\s*([^|,]+)/g;
17443
17461
  SAMELINE_META_RE = /(?:^|[\s,])(\w+)\s*:\s*([^,]+?)(?=\s*,|\s*$)/g;
17444
17462
  PROPERTY_RE = /^([\w-]+):\s*(.+)$/;
@@ -18922,8 +18940,8 @@ function parseMap(content, palette) {
18922
18940
  continue;
18923
18941
  }
18924
18942
  if (open.route && indent > open.route.indent) {
18925
- const leg = parseLeg(trimmed, lineNumber, open.route.route.style);
18926
- open.route.route.legs.push(leg);
18943
+ const leg = parseLeg(trimmed, lineNumber);
18944
+ if (leg) open.route.route.legs.push(leg);
18927
18945
  continue;
18928
18946
  }
18929
18947
  if (open.poi && indent > open.poi.indent) {
@@ -18952,6 +18970,14 @@ function parseMap(content, palette) {
18952
18970
  Object.assign(open.poi.poi.meta, split.meta);
18953
18971
  continue;
18954
18972
  }
18973
+ if (split.name) {
18974
+ const src = open.poi.poi.alias ?? poiName(open.poi.poi.pos) ?? "the poi";
18975
+ pushError(
18976
+ lineNumber,
18977
+ `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)`
18978
+ );
18979
+ continue;
18980
+ }
18955
18981
  }
18956
18982
  closeBlocks();
18957
18983
  const firstWord = trimmed.split(/\s+/)[0];
@@ -19200,31 +19226,44 @@ function parseMap(content, palette) {
19200
19226
  const { tags, meta } = partitionMeta(split.meta, tagGroupNames());
19201
19227
  const originLabel = meta["label"];
19202
19228
  const originValue = meta["value"];
19203
- const style = meta["style"] === "arc" ? "arc" : "straight";
19229
+ if (meta["style"] !== void 0) {
19230
+ pushError(
19231
+ line11,
19232
+ "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)"
19233
+ );
19234
+ }
19204
19235
  const route = {
19205
19236
  origin: pos,
19206
19237
  ...split.alias !== void 0 && { originAlias: split.alias },
19207
19238
  ...originLabel !== void 0 && { originLabel },
19208
19239
  ...originValue !== void 0 && { originValue },
19209
19240
  originTags: tags,
19210
- style,
19211
19241
  legs: [],
19212
19242
  lineNumber: line11
19213
19243
  };
19214
19244
  routes.push(route);
19215
19245
  open.route = { route, indent };
19216
19246
  }
19217
- function parseLeg(trimmed, line11, headerStyle) {
19218
- let arrowStyle = "straight";
19219
- let label;
19220
- let rest = trimmed;
19247
+ function parseLeg(trimmed, line11) {
19221
19248
  const m = trimmed.match(LEG_ARROW_RE);
19222
- if (m) {
19223
- const arr = classifyArrow(m[1], line11);
19224
- arrowStyle = arr.style;
19225
- label = arr.label;
19226
- rest = m[2];
19249
+ if (!m) {
19250
+ pushError(
19251
+ line11,
19252
+ `Malformed route leg: "${trimmed}" \u2014 a leg needs an arrow glyph (\`-> dest\`, \`~> dest\`, or labeled \`-label-> dest\` / \`~label~> dest\`). (\xA724B.6)`
19253
+ );
19254
+ return null;
19227
19255
  }
19256
+ const arr = classifyArrow(m[1], line11);
19257
+ if (!arr.directed) {
19258
+ pushError(
19259
+ line11,
19260
+ `A route leg is directional \u2014 "${trimmed}" uses an undirected glyph; use \`-> \` (straight) or \`~> \` (arc). (\xA724B.6)`
19261
+ );
19262
+ return null;
19263
+ }
19264
+ const arrowStyle = arr.style;
19265
+ const label = arr.label;
19266
+ const rest = m[2];
19228
19267
  const split = splitNameAndMeta(
19229
19268
  rest,
19230
19269
  registry(),
@@ -19240,10 +19279,9 @@ function parseMap(content, palette) {
19240
19279
  const { tags, meta } = partitionMeta(split.meta, tagGroupNames());
19241
19280
  const value = meta["value"];
19242
19281
  const destLabel = meta["label"];
19243
- const style = arrowStyle === "arc" || headerStyle === "arc" ? "arc" : "straight";
19244
19282
  return {
19245
19283
  ...label !== void 0 && { label },
19246
- style,
19284
+ style: arrowStyle,
19247
19285
  ...value !== void 0 && { value },
19248
19286
  dest: pos,
19249
19287
  ...split.alias !== void 0 && { destAlias: split.alias },
@@ -53745,13 +53783,15 @@ function layoutMap(resolvedIn, data, size, opts) {
53745
53783
  labelY: lp.y
53746
53784
  };
53747
53785
  const routeLabelStyle = leg.label !== void 0 ? labelOnFill(fillAt(bow.labelX, bow.labelY)) : void 0;
53786
+ const routeVal = Number(leg.value);
53748
53787
  legs.push({
53749
53788
  d: legPath(a, b, bow.curved, bow.offset),
53750
- width: routeWidthFor(Number(leg.value)),
53789
+ width: routeWidthFor(routeVal),
53751
53790
  color: lineColor(leg.tags) ?? mix(palette.text, palette.bg, 72),
53752
53791
  arrow: true,
53753
53792
  fromId: leg.fromId,
53754
53793
  toId: leg.toId,
53794
+ ...Number.isFinite(routeVal) && routeVal > 0 && { value: routeVal },
53755
53795
  ...Object.keys(leg.tags).length > 0 && { tags: leg.tags },
53756
53796
  lineNumber: leg.lineNumber,
53757
53797
  ...leg.label !== void 0 && {
@@ -53798,6 +53838,7 @@ function layoutMap(resolvedIn, data, size, opts) {
53798
53838
  labelY: lp.y
53799
53839
  };
53800
53840
  const edgeLabelStyle = e.label !== void 0 ? labelOnFill(fillAt(bow.labelX, bow.labelY)) : void 0;
53841
+ const edgeVal = Number(e.meta["value"]);
53801
53842
  legs.push({
53802
53843
  d: legPath(a, b, bow.curved, bow.offset),
53803
53844
  width: widthFor(e),
@@ -53805,6 +53846,7 @@ function layoutMap(resolvedIn, data, size, opts) {
53805
53846
  arrow: e.directed,
53806
53847
  fromId: e.fromId,
53807
53848
  toId: e.toId,
53849
+ ...Number.isFinite(edgeVal) && edgeVal > 0 && { value: edgeVal },
53808
53850
  ...Object.keys(e.tags).length > 0 && { tags: e.tags },
53809
53851
  lineNumber: e.lineNumber,
53810
53852
  ...e.label !== void 0 && {
@@ -53962,18 +54004,49 @@ function layoutMap(resolvedIn, data, size, opts) {
53962
54004
  const SHORT_HOP_MAX = Math.max(46, Math.min(width, height) * 0.11);
53963
54005
  const STEP = 4;
53964
54006
  const HOP_GAP = 9;
54007
+ const CLEAR_STEP = 6;
54008
+ const CLEAR_MAX = SHORT_HOP_MAX * 3;
54009
+ const CLEAR_TOL = CLEAR_STEP;
54010
+ const clearanceAt = (px, py, ox, oy) => {
54011
+ const baseAng = Math.atan2(oy, ox);
54012
+ let minClear = CLEAR_MAX;
54013
+ for (let k = -2; k <= 2; k++) {
54014
+ const a = baseAng + k * Math.PI / 4;
54015
+ const rx = Math.cos(a);
54016
+ const ry = Math.sin(a);
54017
+ let d = CLEAR_STEP;
54018
+ for (; d <= CLEAR_MAX; d += CLEAR_STEP) {
54019
+ const qx = px + rx * d;
54020
+ const qy = py + ry * d;
54021
+ if (qx < FIT_PAD || qy < topPad || qx > width - FIT_PAD || qy > height - FIT_PAD)
54022
+ break;
54023
+ if (fillAt(qx, qy) !== water) break;
54024
+ }
54025
+ if (d < minClear) minClear = d;
54026
+ }
54027
+ return minClear;
54028
+ };
53965
54029
  const tryShortHopCallout = (cx, cy, fill2, name, value, font = FONT2, gap = HOP_GAP, maxLen = SHORT_HOP_MAX) => {
53966
54030
  const chipW = stackW(name, value, font);
53967
54031
  const chipH = stackH(value !== void 0, font);
54032
+ const D = Math.SQRT1_2;
53968
54033
  const dirs = [
53969
54034
  [0, -1],
53970
- // north (over Canada)
54035
+ // north
53971
54036
  [0, 1],
53972
- // south (over Mexico / Gulf)
54037
+ // south
53973
54038
  [1, 0],
53974
- // east (Atlantic)
53975
- [-1, 0]
53976
- // west (Pacific)
54039
+ // east
54040
+ [-1, 0],
54041
+ // west
54042
+ [D, -D],
54043
+ // NE
54044
+ [D, D],
54045
+ // SE
54046
+ [-D, -D],
54047
+ // NW
54048
+ [-D, D]
54049
+ // SW
53977
54050
  ];
53978
54051
  let best = null;
53979
54052
  for (const [dx, dy] of dirs) {
@@ -54023,13 +54096,16 @@ function layoutMap(resolvedIn, data, size, opts) {
54023
54096
  (p) => segmentRectOverlap(cx, cy, innerX, innerY, p)
54024
54097
  ))
54025
54098
  continue;
54026
- if (best === null || len < best.len)
54099
+ const clear = clearanceAt(ccx, ccy, dx, dy);
54100
+ const better = best === null || clear > best.clear + CLEAR_TOL || clear >= best.clear - CLEAR_TOL && len < best.len;
54101
+ if (better)
54027
54102
  best = {
54028
54103
  x: ccx,
54029
54104
  y: ccy,
54030
54105
  rect,
54031
54106
  leader: [cx, cy, innerX, innerY],
54032
- len
54107
+ len,
54108
+ clear
54033
54109
  };
54034
54110
  }
54035
54111
  return best ? { x: best.x, y: best.y, rect: best.rect, leader: best.leader } : null;
@@ -54690,6 +54766,9 @@ function layoutMap(resolvedIn, data, size, opts) {
54690
54766
  const nx = -dy / len;
54691
54767
  const ny = dx / len;
54692
54768
  const perp = labelH + 2;
54769
+ const stroke2 = lg.width ?? W_MIN;
54770
+ const clearW = stroke2 / 2 + labelH / 2 + 3;
54771
+ const offList = stroke2 >= 4 ? [clearW, -clearW, clearW + perp, -(clearW + perp), 0] : [0, perp, -perp, 2 * perp, -2 * perp];
54693
54772
  const pm = /^M(-?[\d.]+),(-?[\d.]+)(?:L(-?[\d.]+),(-?[\d.]+)|Q(-?[\d.]+),(-?[\d.]+) (-?[\d.]+),(-?[\d.]+))$/.exec(
54694
54773
  lg.d
54695
54774
  );
@@ -54709,7 +54788,7 @@ function layoutMap(resolvedIn, data, size, opts) {
54709
54788
  return [a.cx + dx * t, a.cy + dy * t];
54710
54789
  };
54711
54790
  const candidates = [];
54712
- for (const off of [0, perp, -perp, 2 * perp, -2 * perp])
54791
+ for (const off of offList)
54713
54792
  for (const t of T_LIST) {
54714
54793
  const [bx, by] = pointAt(t);
54715
54794
  candidates.push([bx + nx * off, by + ny * off - 4]);
@@ -55460,6 +55539,12 @@ function renderMap(container, resolved, data, palette, isDark, onClickItem, expo
55460
55539
  }
55461
55540
  }
55462
55541
  wireSync(p, leg.lineNumber);
55542
+ if (leg.value !== void 0) {
55543
+ p.attr("data-value", leg.value);
55544
+ p.append("title").text(
55545
+ leg.label !== void 0 ? `${leg.label}: ${leg.value}` : `${leg.value}`
55546
+ );
55547
+ }
55463
55548
  if (arrowTri) {
55464
55549
  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);
55465
55550
  }
@@ -167,6 +167,8 @@ Valid markup is the floor, not the goal. A good diagram reads at a glance. Apply
167
167
 
168
168
  **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.
169
169
 
170
+ **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.
171
+
170
172
  ### Common examples (curated, parse-clean)
171
173
 
172
174
  _The most common types, inline so you can generate them without a fetch. For the other 37, get the per-type section (see below)._
@@ -152,6 +152,8 @@ Valid markup is the floor, not the goal. A good diagram reads at a glance. Apply
152
152
  | `function` | mathematical expressions (colon required: `f(x): x^2`) |
153
153
 
154
154
  **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.
155
+
156
+ **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.
155
157
  <!-- AI-CORE:TYPE-INDEX end -->
156
158
 
157
159
  <!-- The grouped data-chart / matrix ids share one documented section. This map is the single source of truth for which TYPE block each id resolves to (read by gen-ai-core.mjs and the MCP slicer). -->
@@ -3264,15 +3266,17 @@ poi Austin red # direct marker color (trailing token, §1.5
3264
3266
 
3265
3267
  ### Routes & connectors
3266
3268
 
3267
- `route <origin> [style: arc]` — an ordered, auto-numbered voyage; the origin gets a distinct marker. Each indented line is a `[-label->] destination` leg that continues from the previous stop — a leg is an edge (in-arrow label, `value:` thickness, `->`/`~>` or header `style: arc` shape). A **tag on a leg colours the LINE** (categorical leg-type colouring — flights vs cruises vs trains); `label:`/`as` still name the destination stop. Repeat the origin as a leg's destination to close a loop (no second marker):
3269
+ `route <origin>` — an ordered, auto-numbered voyage; the origin gets a distinct marker, and the header takes **no shape option**. Each indented line is a `<arrow> destination` leg that continues from the previous stop, using the same indented arrow idiom as a sitemap — a leg is an edge (in-arrow label, `value:` thickness, and the **arrow glyph alone sets shape**: `-…->` straight, `~…~>` arc, mixable per leg). The **arrow is required and must be directional** — a bare destination errors, and so does an undirected `--`/`~~` glyph (a voyage always flows forward; use `->`/`~>`). A **tag on a leg colours the LINE** (categorical leg-type colouring — flights vs cruises vs trains); `label:`/`as` still name the destination stop. Repeat the origin as a leg's destination to close a loop (no second marker):
3268
3270
 
3269
3271
  ```
3270
- route Miami style: arc
3271
- -weigh anchor-> Havana value: 40
3272
- -> Kingston
3273
- -> Miami # destination == origin → closed loop
3272
+ route Miami
3273
+ ~weigh anchor~> Havana value: 40 # arc leg (its own ~> glyph)
3274
+ ~> Kingston
3275
+ ~> Miami # destination == origin → closed loop
3274
3276
  ```
3275
3277
 
3278
+ There is no header `style:` — bow a whole voyage by using `~>` on every leg.
3279
+
3276
3280
  Native edges handle any other connection (no `link`/`leg` keyword). A token draws an arrowhead iff it ends in `>`, an arc iff it starts with `~`; drop the `>` for a plain line when an arrow would mislead. The label always sits between the delimiters:
3277
3281
 
3278
3282
  | | no label | labeled |
@@ -3292,9 +3296,9 @@ JFK ~daily~> LHR # (NOT indented — indented legs are only valid
3292
3296
  JFK ~2x daily~> SFO # inside a `route` block, which is an ordered voyage)
3293
3297
  ```
3294
3298
 
3295
- Each native edge is ONE full line — `<origin> <connector> <destination>`. There is no indented-edge / shared-source hub form for native edges (it errors as "Malformed edge"); to fan out from a hub, repeat the origin on each line as above. Endpoints auto-create their POIs, so a connected map needs no separate `poi` lines.
3299
+ Each native edge is ONE full line — `<origin> <connector> <destination>`. A top-level indented `-> dest` with no parent errors as "Malformed edge"; to fan out, either repeat the origin on each line as above OR indent the spokes under a `poi` (a hub — `poi JFK` then indented `~> LAX`, `~> LHR`). An indented child under a `poi` that names a place **must carry an arrow** — a bare name there errors as a malformed hub edge. Endpoints auto-create their POIs, so a connected map needs no separate `poi` lines.
3296
3300
 
3297
- There is no geographic path-finding and no `surface:` — legs are plain straight or arced geometry (`style: arc` to bow one) and may cross land.
3301
+ There is no geographic path-finding and no `surface:` — legs are plain straight or arced geometry (use a `~>` glyph to bow one) and may cross land.
3298
3302
 
3299
3303
  **Tagging the line.** A tag value on any connector or route leg colours the **line itself** (the universal tag model applied to edges) — the "trip-leg type" idiom. Edge-only tag groups show in the legend as a line-colour key; hovering a legend entry dims the lines that don't match. To categorise a **stop** instead, tag its own `poi` line. An edge-only tag group never tints regions or suppresses the colorize dress.
3300
3304
 
@@ -3,5 +3,5 @@ map Smuggler's Run
3
3
  poi Havana value: 90
4
4
  poi Kingston value: 120
5
5
 
6
- route Havana style: arc
7
- -run the blockade-> Kingston
6
+ route Havana
7
+ ~run the blockade~> Kingston
@@ -1,7 +1,7 @@
1
1
  map Caribbean Cruise
2
2
 
3
- route Miami style: arc
4
- -weigh anchor-> Havana
5
- -> Kingston
6
- -> Santo Domingo
7
- -> Miami
3
+ route Miami
4
+ ~weigh anchor~> Havana
5
+ ~> Kingston
6
+ ~> Santo Domingo
7
+ ~> Miami
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@diagrammo/dgmo",
3
- "version": "0.36.0",
3
+ "version": "0.37.0",
4
4
  "description": "DGMO diagram markup language — parser, renderer, and color system",
5
5
  "license": "MIT",
6
6
  "repository": {