@heroiclands/package-build 22.4.5 → 22.4.7

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/CHANGELOG.md CHANGED
@@ -1,5 +1,21 @@
1
1
  # @heroiclands/package-build
2
2
 
3
+ ## 22.4.7
4
+
5
+ ### Patch Changes
6
+
7
+ Every place page that states a border or a route, or is named in one, carries the map from that place: `from-<shortcode>.svg` beside the page and `map:` naming it in the front matter, with every place name on it a link to that place's page. GraphViz draws it; when GraphViz is not installed the site build says so once and writes every page without a map, and `site.maps: false` draws none.
8
+
9
+ A place's page carries what lies within it and who holds it, and an affiliation's page carries what it holds, read from the `parents` and `domains` the notes already state — a dependency's places and affiliations included. `content-build lint` warns of unheld land: a settlement, site or structure that no affiliation's `domains` names.
10
+
11
+ A `place` note can state a settlement's market class: `data.market`, a whole number from 1 to 6 beside `population`. `1` is a hamlet, with no market beyond what neighbours trade among themselves; `2` a village and its weekly market; `3` a town with several trades working full time; `4` a market town, its chartered fair and its moneylender; `5` a city, where anything ordinary can be had in quantity; `6` a great city that banks, imports as a matter of course, and is the market from which the other markets buy. The key is optional, and a value off the scale is reported at the line that states it rather than rounded onto a class nobody wrote.
12
+
13
+ ## 22.4.6
14
+
15
+ ### Patch Changes
16
+
17
+ `content-build map --chart <shortcode>` draws the chart from a place: the map from it at a larger horizon, north up, the hops chaining on as long as none turns back, each place on the ring of its summed days and each hop beyond the neighbours drawn fainter. `--horizon <days>` sets how far the rings reach, a year by default, and a place farther than that is left out.
18
+
3
19
  ## 22.4.5
4
20
 
5
21
  ### Patch Changes
package/CONTENT.md CHANGED
@@ -1183,6 +1183,25 @@ parent or child, or a pair listed twice by one mode. Each finding lands on the
1183
1183
  entry that states it. The keys, the sets and the days scale are specified under
1184
1184
  `type: place` in `docs/content-format.md`.
1185
1185
 
1186
+ **A settlement is held by someone.** An affiliation's `domains` names the
1187
+ places it holds, and a `place` of subType `settlement`, `site` or `structure`
1188
+ that no affiliation's `domains` names — in this tree or in a fetched index —
1189
+ is a warning, `unheld land`, at the note's `type:` line, so a gap in tenure
1190
+ shows. A region is held through its polity's `domains` and a feature by
1191
+ nobody, so neither is checked.
1192
+
1193
+ **A settlement's market class is one of six.** `data.market` states what trade
1194
+ a settlement supports — `1` a hamlet, with no market beyond what neighbours
1195
+ trade among themselves; `2` a village and its weekly market; `3` a town with
1196
+ several trades working full time; `4` a market town, its chartered fair and its
1197
+ moneylender; `5` a city, where anything ordinary can be had in quantity; `6` a
1198
+ great city that banks, imports as a matter of course, and is the market from
1199
+ which the other markets buy. The numbers order the steps and do nothing else, so a value off the
1200
+ scale is an error at the value that states it rather than a number rounded onto
1201
+ a class the author did not write. The key is optional and the lint checks no
1202
+ `subType` condition, as it checks none for `population`. The scale is stated
1203
+ under `type: place` in `docs/content-format.md`.
1204
+
1186
1205
  Nothing here writes. A check reports and an author fixes.
1187
1206
 
1188
1207
  **`content-build map` draws what those keys state.** `--tree` is the author's
@@ -2409,6 +2428,96 @@ The theme's `related.html` partial reads both lists and renders them as one
2409
2428
  `related` is derived, never authored: what links to a page is a fact about
2410
2429
  every other page in the tree. A note that writes one has it replaced.
2411
2430
 
2431
+ ### A place page says what lies within it and who holds it; an affiliation page says what it holds
2432
+
2433
+ Two keys the notes already carry answer three questions a reader asks of a
2434
+ page. A place's `data.parents` is geography — what it sits within — and an
2435
+ affiliation's `data.domains` is tenure — what it holds. The build reads both
2436
+ across the tree and every fetched index, inverts them, and writes each page
2437
+ the lists that concern it, shaped like `related`:
2438
+
2439
+ ```yaml
2440
+ # on a place
2441
+ contains: # every place whose `parents` names this one
2442
+ - title: Khaset-Mehtet
2443
+ url: /thalorna/place-khasetmehtet/
2444
+ type: place
2445
+ subType: settlement
2446
+ held_by: # every affiliation whose `domains` names this one
2447
+ - title: The Nome of Ankhsetun
2448
+ url: /thalorna/affiliation-nomenkhstn/
2449
+ type: affiliation
2450
+ subType: polity
2451
+
2452
+ # on an affiliation
2453
+ holdings: # every place its `domains` names
2454
+ - title: Ankhsetun
2455
+ url: /thalorna/place-ankhsetunnome/
2456
+ type: place
2457
+ subType: region
2458
+ ```
2459
+
2460
+ - **`contains` and `holdings` are sorted by `subType`, then `title`**;
2461
+ `held_by` by `title`. An entry carries `subType` where the note declares
2462
+ one.
2463
+ - **A key is present only when it has an entry.** A place nobody holds and
2464
+ nothing sits within carries neither key; an affiliation whose `domains` is
2465
+ empty carries no `holdings`. The theme's silent-disappear convention, kept
2466
+ at the source.
2467
+ - **`domains` names what an affiliation holds directly, and is never
2468
+ expanded.** A polity whose `domains` names a region holds the region; the
2469
+ settlements within it are reached through the region's `contains`, and are
2470
+ not repeated in the polity's `holdings`. Tenure runs through
2471
+ `affiliation.parents` — a house of its earl, an earl of the crown — and
2472
+ geography through `place.parents`. A subinfeudated manor sits in one region
2473
+ by the second and under a lord of another polity by the first, and both
2474
+ pages say so.
2475
+ - **A dependency's places and affiliations take part.** A fetched index entry
2476
+ carries the `parents` and `domains` its record stated, so a place another
2477
+ package publishes is listed within a local region, and a house another
2478
+ package publishes is named on the local manor it holds. A local note
2479
+ declaring a shortcode shadows a fetched entry declaring the same one.
2480
+ - **`url` composes `<base><slug>/`**, as every `related` entry does.
2481
+
2482
+ All three are derived, never authored: a note that writes one has it
2483
+ replaced. The theme renders each list as a table on the page.
2484
+
2485
+ ### A place page carries the map from that place
2486
+
2487
+ A place that states a border or a route, or is named in one, has a map from
2488
+ it — the drawing `content-build map --from` makes, with the place at the
2489
+ centre and each neighbour at its bearing. The site build draws that map for
2490
+ every such place and writes it with the page: the page is a leaf bundle,
2491
+ `place-<shortcode>/index.md`, the drawing sits beside it as
2492
+ `from-<shortcode>.svg`, and the front matter names it:
2493
+
2494
+ ```yaml
2495
+ map: from-kthrmr.svg
2496
+ ```
2497
+
2498
+ The theme's "From here" panel reads the key, inlines the file as a page
2499
+ resource, and so every place name on the map is a link to that place's page.
2500
+ What the build guarantees:
2501
+
2502
+ - **The address does not move.** A bundled page states the same `url` a flat
2503
+ one does, `/<slug>/`, so `place-kthrmr/index.md` publishes exactly where
2504
+ `place-kthrmr.md` would.
2505
+ - **Every name links through the site base.** The map is drawn with the base
2506
+ the pages are served under, so each `href` composes `<base><slug>/` the way
2507
+ every other href the build renders does.
2508
+ - **It is written for inlining.** No XML prologue, no DOCTYPE, no comment, no
2509
+ reference to anything outside the file; the root carries a `viewBox` and no
2510
+ fixed width or height, so the theme sizes it to the column.
2511
+ - **A place with no relation carries nothing**: no file beside it, no `map`
2512
+ key, and the page stays a flat file. So does every page that is not a place.
2513
+ - **A site never fails for want of a map.** GraphViz draws; when it is not
2514
+ installed the build says so once, as a warning naming what to install, and
2515
+ every page is written as it would be without maps. `site.maps: false` in
2516
+ `package-build.config.yaml` draws nothing and asks nothing of GraphViz.
2517
+
2518
+ `map` is derived, never authored: a note that writes one has it replaced when
2519
+ the build draws a map, and dropped when there is none to draw.
2520
+
2412
2521
  ### Why the output location is fixed
2413
2522
 
2414
2523
  The content mount is a build artifact and is **deleted on every run**, so that a
@@ -47,7 +47,7 @@
47
47
  * npx content-build markdown [paths..] [--fix]
48
48
  * npx content-build manifest [root] [--out <dir>]
49
49
  * npx content-build site [--out <dir>]
50
- * npx content-build map [--tree] [--from <shortcode>] [--travel] [--out <dir>]
50
+ * npx content-build map [--tree] [--from <shortcode>] [--chart <shortcode>] [--travel] [--out <dir>]
51
51
  * npx content-build reachability <dir> [file] [--index <shortcode>]
52
52
  * npx content-build addresses diff --from <zip|dir> [--strict]
53
53
  *
@@ -146,6 +146,7 @@ import {
146
146
  } from "../engine/address-diff.mjs";
147
147
  import { emittedArtFor, itemPackJsonDirs } from "../engine/generate.mjs";
148
148
  import { buildMaps, FROM_ALL, MAP_DIR } from "../engine/map-build.mjs";
149
+ import { CHART_HORIZON_DAYS } from "../engine/map-layout.mjs";
149
150
  import { GRAPHVIZ_ENGINES } from "../engine/map-graphviz.mjs";
150
151
  import { loadMapWorld } from "../engine/map-places.mjs";
151
152
 
@@ -1514,7 +1515,8 @@ function pdfCommand() {
1514
1515
  * wikilink resolution, code-fence protection and the foreign-manifest merge —
1515
1516
  * happens here, from configuration. So does the Hugo configuration: the whole
1516
1517
  * source tree Hugo reads lands under `build/hugo/`, and the consumer's script
1517
- * runs Hugo over it.
1518
+ * runs Hugo over it. With `site.maps` on and GraphViz installed, every place
1519
+ * page with a border or a route carries the map from that place.
1518
1520
  *
1519
1521
  * **The Hugo configuration is generated before anything is written.** Its
1520
1522
  * sources — `package.json`'s `homepage`, the cached navigation, the installed
@@ -1652,10 +1654,17 @@ function siteCommand() {
1652
1654
  return;
1653
1655
  }
1654
1656
 
1657
+ // What drawing the maps found: warnings, every one. A site
1658
+ // never fails for want of a map, so none of these touches the
1659
+ // exit code — GraphViz absent is one line naming what to
1660
+ // install, and every page is written as it would be without.
1661
+ for (const f of result.mapFindings) emitDiagnostic(f);
1662
+
1655
1663
  const s = result.stats;
1656
1664
  log.info(
1657
1665
  `wrote ${s.homepages ?? 0} homepage(s) + ` +
1658
- `${s.content ?? 0} content page(s) to ` +
1666
+ `${s.content ?? 0} content page(s), ` +
1667
+ `${s.maps ?? 0} with a map, to ` +
1659
1668
  `${path.relative(process.cwd(), s.out)}`,
1660
1669
  );
1661
1670
  const { file } = writeHugoConfig(config, hugo);
@@ -1673,12 +1682,13 @@ function siteCommand() {
1673
1682
  *
1674
1683
  * Reads the content index — this package's and every declared dependency's —
1675
1684
  * so a consumer's map draws its dependencies' places beside its own, and
1676
- * writes under `build/map/`, always the `.dot` beside the `.svg`. Three
1685
+ * writes under `build/map/`, always the `.dot` beside the `.svg`. Four
1677
1686
  * drawings: the containment tree from `parents` (`--tree`, the author's
1678
1687
  * check, whose anomalies are reported as findings), the map from a place
1679
- * from its `borders` and `routes` (`--from`), and the whole route graph
1680
- * (`--travel`). GraphViz draws; when it is absent the command says what to
1681
- * install and stops.
1688
+ * from its `borders` and `routes` (`--from`), the chart from a place — the
1689
+ * same drawing at a larger horizon (`--chart`, `--horizon`) — and the whole
1690
+ * route graph (`--travel`). GraphViz draws; when it is absent the command
1691
+ * says what to install and stops.
1682
1692
  *
1683
1693
  * @returns {object} The yargs command module.
1684
1694
  */
@@ -1686,7 +1696,8 @@ function siteCommand() {
1686
1696
  function mapCommand() {
1687
1697
  return {
1688
1698
  command: "map",
1689
- describe: "Draw the containment tree, the map from a place, or the route graph",
1699
+ describe:
1700
+ "Draw the containment tree, the map or the chart from a place, or the route graph",
1690
1701
  builder: (yargs) => {
1691
1702
  yargs
1692
1703
  .option("tree", {
@@ -1709,6 +1720,21 @@ function mapCommand() {
1709
1720
  type: "string",
1710
1721
  array: true,
1711
1722
  })
1723
+ .option("chart", {
1724
+ describe:
1725
+ "Draw the chart from this place as `chart-<shortcode>.svg` — the map " +
1726
+ "from it at a larger horizon, the hops chaining on; repeatable, " +
1727
+ `or \`${FROM_ALL}\` for every place with a border or a route.`,
1728
+ type: "string",
1729
+ array: true,
1730
+ })
1731
+ .option("horizon", {
1732
+ describe:
1733
+ "The charts' horizon in days, a marker of the scale: rings are " +
1734
+ "drawn to it, and a place farther than it is left out.",
1735
+ type: "number",
1736
+ default: CHART_HORIZON_DAYS,
1737
+ })
1712
1738
  .option("travel", {
1713
1739
  describe: "Draw the whole route graph as `travel.svg`.",
1714
1740
  type: "boolean",
@@ -1783,6 +1809,8 @@ function mapCommand() {
1783
1809
  tree: argv.tree,
1784
1810
  root: argv.root,
1785
1811
  from: argv.from ?? [],
1812
+ chart: argv.chart ?? [],
1813
+ horizon: argv.horizon,
1786
1814
  travel: argv.travel,
1787
1815
  engine: argv.engine,
1788
1816
  rankdir: argv.rankdir,
@@ -772,6 +772,7 @@ const SITE_KEYS = [
772
772
  "notfound",
773
773
  "hugo",
774
774
  "search",
775
+ "maps",
775
776
  ];
776
777
  const SITE_NOTFOUND_KEYS = ["tagline", "sitenoun", "heroimage", "links"];
777
778
  const SITE_NOTFOUND_LINK_KEYS = ["title", "url", "text"];
@@ -1711,9 +1712,11 @@ const SITE_INDEX_MESSAGE =
1711
1712
  * shared with the link manifest so the two cannot disagree about where a
1712
1713
  * page is.
1713
1714
  *
1714
- * `search` is the one switch here: whether `package-build site-root` writes a
1715
- * search index beside the rendered pages. On by default — every site carries
1716
- * one unless it says otherwise.
1715
+ * Two switches sit here, both on by default. `search` is whether
1716
+ * `package-build site-root` writes a search index beside the rendered pages.
1717
+ * `maps` is whether `content-build site` draws the map from each place that
1718
+ * states a border or a route and writes it into that place's page — a site
1719
+ * carries both unless it says otherwise.
1717
1720
  *
1718
1721
  * **What the site publishes is the homepage and the content tree, and nothing
1719
1722
  * beside them.** A page of documentation is a note — `type: doc`, addressed
@@ -1739,6 +1742,7 @@ function normalizeSite(value) {
1739
1742
  notfound: null,
1740
1743
  hugo: Object.freeze({}),
1741
1744
  search: true,
1745
+ maps: true,
1742
1746
  });
1743
1747
  if (value === undefined) return empty;
1744
1748
  if (!isPlainObject(value)) fail("site", "must be a mapping");
@@ -1795,6 +1799,7 @@ function normalizeSite(value) {
1795
1799
  notfound: normalizeSiteNotfound(input.notfound),
1796
1800
  hugo: normalizeSiteHugo(input.hugo),
1797
1801
  search: optionalBoolean(input.search, "site.search", true),
1802
+ maps: optionalBoolean(input.maps, "site.maps", true),
1798
1803
  });
1799
1804
  }
1800
1805
 
package/docs/api.md CHANGED
@@ -429,6 +429,30 @@ What a place is next to (`data.borders`) and reachable from (`data.routes`): the
429
429
  | `checkBorders` | `checkBorders(note, { index })` | `object[]` | linting a place's `borders` — targets, values, once per pair, no parent or child, and reciprocity with the other end |
430
430
  | `checkRoutes` | `checkRoutes(note, { index })` | `object[]` | linting a place's `routes` — as `checkBorders`, plus `mode`, `days` and `terrain`, once per pair per mode |
431
431
 
432
+ ### `engine.holdings`
433
+
434
+ What lies within a place, who holds it, and what an affiliation holds — read off `data.parents` and `data.domains` across this package and every fetched index, inverted into the `contains`, `held_by` and `holdings` lists the site build writes — and the tenure check `content-build lint` runs on a place.
435
+
436
+ | Export | Signature | Returns | Use it when |
437
+ | ---------------------- | ------------------------------------ | ----------------------- | ---------------------------------------------------------------------------------------------------------------------------- |
438
+ | `HOLDINGS_KEYS` | `const HOLDINGS_KEYS` | `readonly string[]` | the three derived keys — `contains`, `held_by`, `holdings` — which a note cannot author |
439
+ | `HELD_SUBTYPES` | `const HELD_SUBTYPES` | `readonly string[]` | the place subTypes tenure is checked on — `settlement`, `site`, `structure` |
440
+ | `holdingsNode` | `holdingsNode(fm, { title, url })` | `HoldingsNode\|null` | reading a local note as a node of the derivation, or `null` for one that is neither a place nor an affiliation |
441
+ | `foreignHoldingsNodes` | `foreignHoldingsNodes(foreignIndex)` | `HoldingsNode[]` | reading a fetched index's places and affiliations as nodes |
442
+ | `holdingsPages` | `holdingsPages(nodes)` | `Map<string, Holdings>` | inverting `parents` and `domains` into each page's sorted lists, keyed by URL, for every page with at least one entry |
443
+ | `checkHeld` | `checkHeld(note, { index })` | `object[]` | linting a place's tenure — a settlement, site or structure no affiliation's `domains` names is a warning at its `type:` line |
444
+
445
+ ### `engine.marketClass`
446
+
447
+ What trade a settlement supports (`data.market`): the six-step scale, stated once for the lint, the author-facing reference and the specification, and the check that holds a note's value to it.
448
+
449
+ | Export | Signature | Returns | Use it when |
450
+ | ---------------- | ---------------------- | ------------------- | ------------------------------------------------------------------------------------------------- |
451
+ | `MARKET_CLASSES` | `const MARKET_CLASSES` | `readonly object[]` | the scale in order, each step its `value`, the `name` a settlement of it goes by, and its `trade` |
452
+ | `isMarketClass` | `isMarketClass(value)` | `boolean` | asking whether an authored value names a class, a quoted number included |
453
+ | `marketClass` | `marketClass(value)` | `object\|undefined` | the class a value names, for rendering what a number means |
454
+ | `checkMarket` | `checkMarket(note)` | `object[]` | linting a place's `market` — a value off the scale, located at the value that states it |
455
+
432
456
  ### `engine.mapPlaces`
433
457
 
434
458
  The places a map is drawn from — this package's index records and every fetched dependency's entries, read through one shape — and what their `parents` say: the world, the continents, each place's continent, and everything the containment tree can get wrong, reported as findings located at the entry that states it.
@@ -437,42 +461,47 @@ The places a map is drawn from — this package's index records and every fetche
437
461
  | --------------------- | ---------------------------------------------------------------- | ------------- | ------------------------------------------------------------------------------------------------------------------------ |
438
462
  | `placesFromRecords` | `placesFromRecords(records, { contentBase, base })` | `MapWorld` | reading this package's places and polities out of its index records, with a page URL each where a site base is given |
439
463
  | `loadMapWorld` | `loadMapWorld({ config, contentBase, base, problems })` | `MapWorld` | loading the world a map is drawn from — this package's index and every declared dependency's |
464
+ | `mapWorld` | `mapWorld({ records, foreignIndex, contentBase, base, config })` | `MapWorld` | the same world out of records and indexes a caller already holds, as the site build does |
440
465
  | `analyzeContainment` | `analyzeContainment(places, polities)` | `Containment` | finding the world, the continents, each place's continent and the anomalies of `parents` |
441
466
  | `subtreeOf` | `subtreeOf(analysis, root)` | `Set<string>` | every place beneath one, by following `parents` downward |
442
467
  | `containmentFindings` | `containmentFindings(analysis, places, { contentBase, config })` | `object[]` | the anomalies as `file:line:column: severity: message` findings — no parent, an unresolved parent and a cycle are errors |
443
468
 
444
469
  ### `engine.mapLayout`
445
470
 
446
- The geometry of the map from a place, as arithmetic over the place records: north up, east right, the angle from the bearing and the radius from the days marker on log-spaced rings, two hops composed into one bearing only where they agree. Nothing here touches a file or GraphViz.
447
-
448
- | Export | Signature | Returns | Use it when |
449
- | --------------- | -------------------------------- | ------------------- | ------------------------------------------------------------------------------------------------------------------- |
450
- | `HORIZON_DAYS` | `const HORIZON_DAYS` | `number` | the horizon of the map from a place, 90 days; beyond it a place is a name at the rim |
451
- | `RING_DAYS` | `const RING_DAYS` | `readonly number[]` | the days markers that are rings — every marker up to the horizon |
452
- | `bearingAngle` | `bearingAngle(bearing)` | `number\|undefined` | the angle of a bearing in degrees counter-clockwise from east — `N` is 90, `E` 0 |
453
- | `bearingVector` | `bearingVector(bearing)` | `{x, y}\|undefined` | the unit vector of a bearing |
454
- | `bearingSteps` | `bearingSteps(a, b)` | `number\|undefined` | how many steps around the compass separate two bearings, the shorter way |
455
- | `rimRadius` | `rimRadius()` | `number` | the radius of the rim, one log step beyond the horizon |
456
- | `ringRadius` | `ringRadius(days)` | `number` | the radius of the ring a days value sits on — the rim for none, or for a value beyond the horizon |
457
- | `rings` | `rings()` | `object[]` | the rings inside out, each marker with its radius |
458
- | `daysMarker` | `daysMarker(total)` | `number\|undefined` | the smallest marker of the scale that covers a total of days |
459
- | `edgeLength` | `edgeLength(days)` | `number` | the length a route asks of `neato` in the route graph, inches, on the rings' log scale |
460
- | `composeHops` | `composeHops(first, second)` | `object\|undefined` | two hops composed into one bearing and one total where they agree — the same bearing or adjacent — else `undefined` |
461
- | `relationsOf` | `relationsOf(places, shortcode)` | `object` | every border and route touching one place, read from both ends, plus the targets that resolved to nothing |
462
- | `layoutFrom` | `layoutFrom(places, centre)` | `object` | laying out the map from a place: every node pinned in points, every edge, the rings and the rim |
463
- | `travelGraph` | `travelGraph(places)` | `object` | the whole route graph — one edge per pair, however many ends and modes state it, each with a length from its days |
471
+ The geometry of the map from a place and of the chart from one, as arithmetic over the place records: north up, east right, the angle from the bearing and the radius from the days marker on log-spaced rings, hops composed into one bearing as long as none turns back. Nothing here touches a file or GraphViz.
472
+
473
+ | Export | Signature | Returns | Use it when |
474
+ | -------------------- | ------------------------------------------ | ------------------- | ----------------------------------------------------------------------------------------------------------------------------- |
475
+ | `HORIZON_DAYS` | `const HORIZON_DAYS` | `number` | the horizon of the map from a place, 90 days; beyond it a place is a name at the rim |
476
+ | `CHART_HORIZON_DAYS` | `const CHART_HORIZON_DAYS` | `number` | the horizon of the chart from a place when none is asked for, 360 days — the end of the scale |
477
+ | `RING_DAYS` | `const RING_DAYS` | `readonly number[]` | the days markers that are rings on the map from a place — every marker up to its horizon |
478
+ | `bearingAngle` | `bearingAngle(bearing)` | `number\|undefined` | the angle of a bearing in degrees counter-clockwise from east — `N` is 90, `E` 0 |
479
+ | `bearingVector` | `bearingVector(bearing)` | `{x, y}\|undefined` | the unit vector of a bearing |
480
+ | `bearingSteps` | `bearingSteps(a, b)` | `number\|undefined` | how many steps around the compass separate two bearings, the shorter way |
481
+ | `rimRadius` | `rimRadius(horizon?)` | `number` | the radius of the rim, one log step beyond the horizon — the map from a place's by default |
482
+ | `ringRadius` | `ringRadius(days, horizon?)` | `number` | the radius of the ring a days value sits on — the rim for none, or for a value beyond the horizon |
483
+ | `rings` | `rings(horizon?)` | `object[]` | the rings inside out, each marker up to the horizon with its radius |
484
+ | `daysMarker` | `daysMarker(total)` | `number\|undefined` | the smallest marker of the scale that covers a total of days |
485
+ | `edgeLength` | `edgeLength(days)` | `number` | the length a route asks of `neato` in the route graph, inches, on the rings' log scale |
486
+ | `composeChain` | `composeChain(hops)` | `object\|undefined` | a chain of hops composed into one bearing and one total, each hop within an octant of the running direction, else `undefined` |
487
+ | `composeHops` | `composeHops(first, second)` | `object\|undefined` | two hops composed into one bearing and one total where they agree — the same bearing or adjacent — else `undefined` |
488
+ | `relationsOf` | `relationsOf(places, shortcode)` | `object` | every border and route touching one place, read from both ends, plus the targets that resolved to nothing |
489
+ | `layoutFrom` | `layoutFrom(places, centre)` | `object` | laying out the map from a place: every node pinned in points, every edge, the rings and the rim |
490
+ | `layoutChart` | `layoutChart(places, centre, { horizon })` | `object` | laying out the chart from a place: the map from it at a larger horizon, the hops chaining on as long as none turns back |
491
+ | `travelGraph` | `travelGraph(places)` | `object` | the whole route graph — one edge per pair, however many ends and modes state it, each with a length from its days |
464
492
 
465
493
  ### `engine.mapDot`
466
494
 
467
- The three drawings of `content-build map` as DOT text, sharing one style table: shape, colour and size by `subType`, an anomaly overriding colour alone, every label a `name.full`, every tooltip an address, and a `URL` where the place carries one so the SVG's names are links.
495
+ The drawings of `content-build map` as DOT text, sharing one style table: shape, colour and size by `subType`, an anomaly overriding colour alone, every label a `name.full`, every tooltip an address, and a `URL` where the place carries one so the SVG's names are links.
468
496
 
469
- | Export | Signature | Returns | Use it when |
470
- | ----------- | -------------------------------------------------- | -------- | --------------------------------------------------------------------------------------------------- |
471
- | `STYLE` | `const STYLE` | — | the shape, colour and size of each `subType`, and of a polity |
472
- | `dotString` | `dotString(value)` | `string` | quoting text for DOT |
473
- | `treeDot` | `treeDot({ places, polities, analysis, root, … })` | `string` | the containment tree — the whole world clustered by continent, or the subtree beneath `root` |
474
- | `fromDot` | `fromDot(layout, places, { scale, title })` | `string` | the map from a place, every node pinned for `neato -n2`, the rings drawn faintly with their markers |
475
- | `travelDot` | `travelDot(graph, places, { scale, title })` | `string` | the whole route graph for `neato`, each edge asking for a length from its days |
497
+ | Export | Signature | Returns | Use it when |
498
+ | ------------ | -------------------------------------------------- | -------- | ---------------------------------------------------------------------------------------------------------------- |
499
+ | `STYLE` | `const STYLE` | — | the shape, colour and size of each `subType`, and of a polity |
500
+ | `dotString` | `dotString(value)` | `string` | quoting text for DOT |
501
+ | `treeDot` | `treeDot({ places, polities, analysis, root, … })` | `string` | the containment tree — the whole world clustered by continent, or the subtree beneath `root` |
502
+ | `hopOpacity` | `hopOpacity(hop)` | `number` | how faint a place is drawn by its hop count — full to the second hop, a step down each hop beyond, floored |
503
+ | `fromDot` | `fromDot(layout, places, { scale, title })` | `string` | the map or the chart from a place, every node pinned for `neato -n2`, the rings drawn faintly with their markers |
504
+ | `travelDot` | `travelDot(graph, places, { scale, title })` | `string` | the whole route graph for `neato`, each edge asking for a length from its days |
476
505
 
477
506
  ### `engine.mapGraphviz`
478
507
 
@@ -481,6 +510,7 @@ GraphViz, as the map command reaches it: a build-time tool of that command alone
481
510
  | Export | Signature | Returns | Use it when |
482
511
  | ------------------------ | ------------------------------------------------------- | ------------------- | ------------------------------------------------------------------------------------------ |
483
512
  | `GRAPHVIZ_ENGINES` | `const GRAPHVIZ_ENGINES` | `readonly string[]` | the engines the map command may ask for — `dot`, `twopi`, `neato` |
513
+ | `GRAPHVIZ_INSTALL` | `const GRAPHVIZ_INSTALL` | `string` | what installs GraphViz, as the clause every "not installed" message ends with |
484
514
  | `findGraphviz` | `findGraphviz(engine, { env, fallbacks })` | `string\|undefined` | finding an engine's binary on `PATH` or in the directories a package manager installs into |
485
515
  | `graphvizMissingMessage` | `graphvizMissingMessage(engine)` | `string` | saying an engine is not installed, and what installs it |
486
516
  | `renderDot` | `renderDot(dotPath, outPath, { binary, format, args })` | `{warnings}` | rendering a `.dot` file, the engine's warnings returned and a non-zero exit thrown |
@@ -489,12 +519,22 @@ GraphViz, as the map command reaches it: a build-time tool of that command alone
489
519
 
490
520
  `content-build map` as a function: the drawings written under an output directory, always the `.dot` beside the `.svg`, the tree's anomalies returned as findings, and a missing GraphViz either an error or — for a build that would rather skip — one warning.
491
521
 
492
- | Export | Signature | Returns | Use it when |
493
- | --------------- | ------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------ | ---------------------------------------------------------------- |
494
- | `MAP_DIR` | `const MAP_DIR` | `string` | where the maps land below a package's root, `build/map` |
495
- | `FROM_ALL` | `const FROM_ALL` | `string` | the `--from` value that means every place with a relation, `all` |
496
- | `relatedPlaces` | `relatedPlaces(places)` | `string[]` | every place that states, or is named in, a border or a route |
497
- | `buildMaps` | `buildMaps({ world, outDir, tree, root, from, travel, engine, rankdir, nodesep, ranksep, scale, polities, locate, requireGraphviz })` | `{written, findings, skipped}` | drawing any of the three from a loaded world |
522
+ | Export | Signature | Returns | Use it when |
523
+ | --------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------ | ----------------------------------------------------------------------------- |
524
+ | `MAP_DIR` | `const MAP_DIR` | `string` | where the maps land below a package's root, `build/map` |
525
+ | `FROM_ALL` | `const FROM_ALL` | `string` | the `--from` or `--chart` value that means every place with a relation, `all` |
526
+ | `relatedPlaces` | `relatedPlaces(places)` | `string[]` | every place that states, or is named in, a border or a route |
527
+ | `buildMaps` | `buildMaps({ world, outDir, tree, root, from, chart, horizon, travel, engine, rankdir, nodesep, ranksep, scale, polities, locate, requireGraphviz })` | `{written, findings, skipped}` | drawing any of the four from a loaded world |
528
+
529
+ ### `engine.siteMaps`
530
+
531
+ The map from each place, drawn for the site: `content-build site` draws the map from every place of this package that states, or is named in, a border or a route — with the site's base, so every name links through it — and the page writer lays each drawing beside its page as `from-<shortcode>.svg` and names it in the front matter as `map`. GraphViz absent is one warning and no map; `site.maps: false` draws none.
532
+
533
+ | Export | Signature | Returns | Use it when |
534
+ | -------------- | --------------------------------------------------------------- | ------------------ | --------------------------------------------------------------------------------------------------------------- |
535
+ | `SITE_MAP_DIR` | `const SITE_MAP_DIR` | `string` | where the site build draws below a package's root, `build/map/site` |
536
+ | `inlineSvg` | `inlineSvg(svg)` | `string` | rewriting a GraphViz SVG for inlining — no prologue, no comment, no `xlink:`, the root sized by `viewBox` alone |
537
+ | `drawSiteMaps` | `drawSiteMaps({ records, foreignIndex, config, base, locate })` | `{maps, findings}` | drawing the map from every related place of this package, keyed by the URL of the page that carries it |
498
538
 
499
539
  ### `engine.systems`
500
540
 
@@ -690,20 +730,20 @@ The toolchain's own content index — the files it ships, addressed. Every other
690
730
 
691
731
  Publishing a content tree as a website. Compiling a content tree into compendium packs is `content-build package compile`. Publishing the _same tree_ as a website was a script each consumer wrote for itself — 473 code lines in `sohl` and 462 in `sohl-thalorna`, 87 of them identical — and the copies drifted in ways neither repository could see. `sohl-thalorna` reimplemented four things this package already exported, not because it needed different behaviour but because its script predates the extraction. That is the failure a command removes: a consumer cannot accidentally reimplement one.
692
732
 
693
- | Export | Signature | Returns | Use it when |
694
- | --------------------- | ------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------- |
695
- | `collectContentPages` | `function collectContentPages(contentBase, ctx)` | {{pages: object[], addressFindings: object[], fmLinkFindings: object[]}} | The content tree's pages, and what could not be addressed. |
696
- | `collectHomepages` | `function collectHomepages(contentBase, ctx)` | {{pages: object[], addressFindings: object[]}} The homepage notes, in walk order, and the ones among them that could not be addressed. | The package's homepage notes — the authored page at `/<contentPackage>/`. |
697
- | `writeHomepages` | `function writeHomepages(outRoot, pages, config)` | {number} How many pages were written. | Writes each homepage as the package root's `_index.md`. |
698
- | `siteGates` | `function siteGates(pages, findings,` | {object} The gate results and, when they pass, the built index. | The integrity gates a site build runs before it writes anything. |
699
- | `emptyGates` | `function emptyGates()` | {object} An all-clear gate result. | The gate result of a build that ran none of them. |
700
- | `gatesFailed` | `function gatesFailed(gates)` | — | Whether any gate produced a finding. |
701
- | `tableUniverse` | `function tableUniverse(pages)` | {Map<string, object[]>} Package → the notes it may tabulate. | The universe a generated table searches, grouped by package. |
702
- | `pageFrontmatter` | `function pageFrontmatter(page,` | {object} The frontmatter to write. | The frontmatter a page publishes with. |
703
- | `pageDestination` | `function pageDestination(page)` | — | Where a page is written, relative to the output root. |
704
- | `renderPages` | `function renderPages(pages, options)` | {{written: number, byKind: Record<string, number>, tableErrors: object[], wikiErrors: object[]}} | Renders and writes every page. |
705
- | `resolveSitePass` | `function resolveSitePass(name, options)` | {{beforeLinks?: Function}} The bundle. | Resolves `site.pass` to its bundle. |
706
- | `buildSite` | `buildSite({ config, sqlTables })` | {{gates: object, stats: object\|null, tableErrors: object[], wikiErrors: object[], manifests: object\|null}} | Builds a Hugo content tree from a content tree, and reports what it found. |
733
+ | Export | Signature | Returns | Use it when |
734
+ | --------------------- | ------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |
735
+ | `collectContentPages` | `function collectContentPages(contentBase, ctx)` | {{pages: object[], addressFindings: object[], fmLinkFindings: object[]}} | The content tree's pages, and what could not be addressed. |
736
+ | `collectHomepages` | `function collectHomepages(contentBase, ctx)` | {{pages: object[], addressFindings: object[]}} The homepage notes, in walk order, and the ones among them that could not be addressed. | The package's homepage notes — the authored page at `/<contentPackage>/`. |
737
+ | `writeHomepages` | `function writeHomepages(outRoot, pages, config)` | {number} How many pages were written. | Writes each homepage as the package root's `_index.md`. |
738
+ | `siteGates` | `function siteGates(pages, findings,` | {object} The gate results and, when they pass, the built index. | The integrity gates a site build runs before it writes anything. |
739
+ | `emptyGates` | `function emptyGates()` | {object} An all-clear gate result. | The gate result of a build that ran none of them. |
740
+ | `gatesFailed` | `function gatesFailed(gates)` | — | Whether any gate produced a finding. |
741
+ | `tableUniverse` | `function tableUniverse(pages)` | {Map<string, object[]>} Package → the notes it may tabulate. | The universe a generated table searches, grouped by package. |
742
+ | `pageFrontmatter` | `function pageFrontmatter(page,` | {object} The frontmatter to write. | The frontmatter a page publishes with. |
743
+ | `pageDestination` | `function pageDestination(page, { bundle })` | — | Where a page is written, relative to the output root: flat, or as a leaf bundle when it carries a file beside itself. |
744
+ | `renderPages` | `function renderPages(pages, options)` | {{written: number, byKind: Record<string, number>, tableErrors: object[], wikiErrors: object[]}} | Renders and writes every page. |
745
+ | `resolveSitePass` | `function resolveSitePass(name, options)` | {{beforeLinks?: Function}} The bundle. | Resolves `site.pass` to its bundle. |
746
+ | `buildSite` | `buildSite({ config, sqlTables, locate })` | {{gates: object, stats: object\|null, tableErrors: object[], wikiErrors: object[], mapFindings: object[], manifests: object\|null}} | Builds a Hugo content tree from a content tree, and reports what it found. |
707
747
 
708
748
  ### `engine.contentLint`
709
749