@heroiclands/package-build 22.3.0 → 22.4.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/CONTENT.md CHANGED
@@ -179,10 +179,8 @@ publish:
179
179
  # How this repository frames the website `content-build site` publishes.
180
180
  # Framing only: addresses come from `publish.address` above.
181
181
  site:
182
- out: kb/content
183
- landing: { title: Knowledgebase, type: knowledgebase }
184
- sections:
185
- being: { title: Beings, banner: banners/creature.webp }
182
+ assets: https://cdn.heroiclands.org
183
+ description: The rules, the setting, and the reference material.
186
184
  ```
187
185
 
188
186
  The loader validates the document, resolves every path against the directory
@@ -1033,7 +1031,7 @@ npx content-build links [root] [--manifests <dir>]
1033
1031
  npx content-build format [paths..] [--write]
1034
1032
  npx content-build markdown [paths..] [--fix]
1035
1033
  npx content-build content-index [root] [--out <dir>]
1036
- npx content-build site [--out <dir>]
1034
+ npx content-build site
1037
1035
  npx content-build reachability <dir> [file] [--index <shortcode>]
1038
1036
  npx content-build addresses diff --from <zip|dir> [--strict]
1039
1037
  ```
@@ -1105,12 +1103,11 @@ package whose front page is not the page a person chose.
1105
1103
  authored homepage exists to prevent, and a silent one: the site build reports
1106
1104
  `wrote 0 homepage(s)` and exits 0.
1107
1105
  - _Two_ and it serves a page nobody chose. **This is a cardinality rule, and
1108
- only that.** A homepage is written at its own address, so two of them publish
1109
- two pages and collide over nothing: the duplicate-address
1110
- check catches only the pair that happen to share a shortcode, and says nothing
1111
- at all about a `homepage-root` beside a `homepage-front`. Which of the two the
1112
- redirect at `/<package>/` should name is a question nothing here can answer,
1113
- and both being reachable is not an answer to it.
1106
+ only that.** Both are written to the mount's `_index.md`, so the second
1107
+ silently overwrites the first: the duplicate-address check catches only the
1108
+ pair that happen to share a shortcode, and says nothing at all about a
1109
+ `homepage-root` beside a `homepage-front`. Which of the two should be the
1110
+ front page is a question nothing here can answer.
1114
1111
 
1115
1112
  Neither has a safe default, so neither is a warning: a build that proceeded past
1116
1113
  either would publish the wrong front page while reporting success, which is
@@ -1136,7 +1133,7 @@ naming the other, because each note is a place an author has to open and edit:
1136
1133
 
1137
1134
  ```text
1138
1135
  assets/content: error: holds no `type: homepage` note, so package "sohl" publishes nothing at its own address /sohl/ — a package's front page is one authored note in this tree, routed by `type:` rather than by filename
1139
- assets/content/homepage.md:3:7: error: duplicate `type: homepage` note, also declared by assets/content/Landing.md; a package has one front page, at /sohl/, and each of these publishes at an address of its own — so nothing here can say which one that address should redirect to. Keep one, and make the rest ordinary notes
1136
+ assets/content/homepage.md:3:7: error: duplicate `type: homepage` note, also declared by assets/content/Landing.md; a package has one front page, at /sohl/, and nothing here can say which of these it should be. Keep one, and make the rest ordinary notes
1140
1137
  ```
1141
1138
 
1142
1139
  ### Frontmatter, against the schema its type declares
@@ -1332,190 +1329,125 @@ because the emitter spreads a note's frontmatter wholesale and stripping it
1332
1329
  there would mean referencing it. Write it if you have a use for it later —
1333
1330
  nothing today will read it.
1334
1331
 
1335
- ### The homepage is addressed like every other note
1336
-
1337
- A homepage declares a `shortcode` — conventionally `root` — and publishes at its
1338
- address, `/<package>/homepage-root/`, written by the same rule as everything
1339
- else. So `[[homepage-root|Read the introduction]]` is an ordinary
1340
- wikilink, resolving to the page the build actually writes.
1341
-
1342
- It did not use to be. A page's URL derived from `name.full` while a homepage's
1343
- destination was fixed at `_index.md`, so `content-build lint` **refused** `name`
1344
- and `shortcode` on one — not out of tidiness, but because they were not
1345
- inert. A `shortcode` put the note in the address index, so
1346
- `[[homepage-<shortcode>]]` resolved _green_ to a page the site build never
1347
- wrote, and a build reporting a live link to a 404 is worse than one saying
1348
- nothing.
1349
-
1350
- [A page's URL is its address](#a-pages-url-is-its-address) removed the premise:
1351
- the address a `shortcode` computes is now the address the build publishes. Both
1352
- fields are therefore permitted, and `shortcode` is **required**, like every
1353
- other note's. A homepage that declares none is refused, located at the `type:`
1332
+ ### The homepage is the package root, addressed like every other note
1333
+
1334
+ A homepage declares a `shortcode` — conventionally `root` — because that is
1335
+ what a link is written with: `[[homepage-root|Read the introduction]]` is an
1336
+ ordinary wikilink. It resolves to `/<package>/`, because the homepage _is_ the
1337
+ package root: the site build writes it as the mount's `_index.md`, and Hugo
1338
+ renders that as the `home` kind at `baseURL`. The shortcode names the page in
1339
+ links; the address is the package root, and `[[thalorna-homepage-root]]` from
1340
+ another package lands on `/thalorna/` for the same reason.
1341
+
1342
+ A homepage that declares no `shortcode` is refused, located at the `type:`
1354
1343
  value that makes it necessary:
1355
1344
 
1356
1345
  ```text
1357
- assets/content/homepage.md:3:7: error: a `type: homepage` note declares a `shortcode`, like every other note: it is addressed as `homepage-<shortcode>` and published at `/<package>/homepage-<shortcode>/`, which is where `[[homepage-<shortcode>|Text]]` lands. Write `shortcode: root` — the package landing is `homepage-root` in every package
1346
+ assets/content/homepage.md:3:7: error: a `type: homepage` note declares a `shortcode`, like every other note: it is addressed as `homepage-<shortcode>`, which is what `[[homepage-<shortcode>|Text]]` is written with to reach the package's front page at `/<package>/`. Write `shortcode: root` — the front page is `homepage-root` in every package
1358
1347
  ```
1359
1348
 
1360
1349
  `root` is a **convention, not a rule**: the address only has to be unique within
1361
1350
  the package, which `(type, shortcode)` already guarantees, and nothing here
1362
- knows better than an author what their landing is called. What the convention
1363
- buys is one spelling shared by all six trees, so `[[homepage-root|…]]` is the
1364
- same link in every package.
1351
+ knows better than an author what their front page is called. What the
1352
+ convention buys is one spelling shared by every tree, so `[[homepage-root|…]]`
1353
+ is the same link in every package.
1365
1354
 
1366
1355
  **Not a bare `[[homepage]]`.** With no hyphen it does not parse as an address,
1367
- so it would need a hardcoded single-token exception in the grammar — the one
1368
- thing the addressing work removes.
1356
+ so it would need a hardcoded single-token exception in the grammar.
1369
1357
 
1370
- #### `id` is still refused
1358
+ #### `id` and `landing` are refused
1371
1359
 
1372
- One field is left in the class, and on ground the change does not touch: `id` is
1373
- the Foundry document id a compendium UUID is built from, and a homepage compiles
1374
- into **no document**.
1360
+ Two top-level keys are refused, because neither decides anything on a page.
1361
+ `id` is the Foundry document id a compendium UUID is built from, and a
1362
+ homepage compiles into **no document**:
1375
1363
 
1376
1364
  ```text
1377
- assets/content/homepage.md:4:1: error: `id` decides nothing on a `type: homepage` note: it is the Foundry document id a compendium UUID is built from, and a homepage compiles into no document — it appears in no pack and states no Foundry address. Delete it
1365
+ assets/content/homepage.md:4:1: error: `id` decides nothing on a `type: homepage` note: it is the Foundry document id a compendium UUID is built from, and a homepage compiles into no document — it appears in no pack and in no link manifest. Delete it
1378
1366
  ```
1379
1367
 
1380
- That is also why a homepage states **no Foundry address**, now that a
1381
- shortcode alone would put it in. A manifest entry is how another package
1382
- resolves a _document_; a cross-package link to a package's front page is its
1383
- bare `/<package>/` address, which needs no index.
1368
+ `landing` is a card block. The homepage is a page with a body, rendered as
1369
+ one, and no card block is read off it an index of what the package
1370
+ publishes is a `doc` note carrying a content table, linked from the homepage
1371
+ like any other page:
1372
+
1373
+ ```text
1374
+ assets/content/homepage.md:5:1: error: `landing` decides nothing on a `type: homepage` note: the homepage is a page with a body, rendered as one, and no card block is read off it. Write the page's links in its body, and author an index of what the package publishes as a `doc` note carrying a content table. Delete it
1375
+ ```
1376
+
1377
+ That is also why a homepage states **no Foundry address**. A manifest entry is
1378
+ how another package resolves a _document_; a cross-package link to a package's
1379
+ front page is its bare `/<package>/` address, which needs no index.
1384
1380
 
1385
1381
  **A named class, not an allow-list.** The documented envelope is `type` and
1386
- `shortcode`, with `name`, `title`, `landing`, `description` and `banner`
1387
- legitimate beside them — but an unknown top-level key is **not** refused, and
1388
- that boundary is the decision rather than an omission. A homepage's frontmatter
1389
- is emitted into the published page, so an unrecognised key is a Hugo or theme
1390
- parameter this build has never heard of and has no standing to reject; a closed
1391
- list would make every new theme parameter wait on a package-build release.
1392
- `aliases` is not in the class either — it is a retired field now, refused on
1393
- every note whatever its type.
1382
+ `shortcode`, with `name`, `title`, `description` and `banner` legitimate beside
1383
+ them — but an unknown top-level key is **not** refused, and that boundary is
1384
+ the decision rather than an omission. A homepage's frontmatter is emitted into
1385
+ the published page, so an unrecognised key is a Hugo or theme parameter this
1386
+ build has never heard of and has no standing to reject; a closed list would
1387
+ make every new theme parameter wait on a package-build release. `aliases` is
1388
+ not in the class either — it is a retired field, refused on every note
1389
+ whatever its type.
1394
1390
 
1395
1391
  **Where it fires: `content-build lint` only.** Unlike a rule about the shape of
1396
1392
  the _tree_, which the site build has its own reason to gate on, this is a
1397
1393
  _frontmatter-schema_ rule and `content-build site` runs none of them — wiring in
1398
1394
  one type's field rule would have the site build refuse `id` on a homepage while
1399
1395
  accepting `weight: heavy` on a weapon. The site build does refuse a homepage it
1400
- cannot address, because it cannot write the page otherwise, and it reports that
1401
- beside the count so the finding reaches `publish.site: homepage` mode as well.
1402
- The remaining gap is `HarnMaster-3-FoundryVTT`, which runs no `content-build
1403
- lint` at all and so receives no frontmatter finding of any kind; that is a
1404
- missing script in that repository, not a rule to duplicate one at a time.
1405
-
1406
- ### `/<package>/` is a redirect the package authors
1407
-
1408
- Nothing is written at `/<package>/` any more. The package's own address is a
1409
- **routing fact**, and it belongs in the package's own `_redirects`:
1410
-
1411
- ```text
1412
- # _redirects
1413
- /sohl/ /sohl/homepage-root/ 301
1414
- /sohl /sohl/homepage-root/ 301
1415
- ```
1416
-
1417
- Both forms, because Cloudflare Pages matches the raw path: redirect matching
1418
- runs before any trailing-slash or `index.html` handling, so `/sohl` and `/sohl/`
1419
- are distinct keys and a rule on one does not catch the other. A redirect also
1420
- **wins over a static asset at the same path** — _"Redirects are always followed,
1421
- regardless of whether or not an asset matches the incoming request"_ — so the
1422
- rule fires whatever else happens to be published there.
1423
-
1424
- **The 301 carries a pinned lifetime**, and that is the part worth being
1425
- deliberate about:
1426
-
1427
- ```text
1428
- # _headers
1429
- /sohl/
1430
- Cache-Control: max-age=3600
1431
- /sohl
1432
- Cache-Control: max-age=3600
1433
- ```
1434
-
1435
- Cloudflare Pages sets **no** `Cache-Control` on a redirect it generates — its
1436
- redirect responses carry `location` and nothing else — and a 301 with no
1437
- `Cache-Control` is cacheable indefinitely by default under RFC 9111. Browsers
1438
- persist one to disk and stop asking the server, so a scheme that later changed
1439
- would strand every returning visitor on the package's most-linked URL. An
1440
- explicit `Cache-Control` overrides that heuristic and keeps the 301's canonical
1441
- signal without the permanence.
1442
-
1443
- **`_headers` does apply to a `_redirects` response, and this is verified rather
1444
- than documented.** Cloudflare's docs say only that _"redirects are applied
1445
- before headers, so when a request matches both a redirect and a header, the
1446
- redirect takes priority"_ — a sentence routinely misread as "headers are skipped
1447
- on a redirect". Its open-source asset server settles it: the redirect response
1448
- returns from `generateResponse()` and then flows through `attachHeaders()`, and
1449
- the only short-circuit past that is `status >= 500`. The one documented "headers
1450
- are not applied" carve-out is Pages Functions, not redirects.
1451
-
1452
- Because that is observed behaviour rather than a documented guarantee, **verify
1453
- it once after deploying** and treat a regression as a Cloudflare change rather
1454
- than a content bug:
1455
-
1456
- ```bash
1457
- curl -sSI https://www.heroiclands.org/sohl/ | grep -i 'location\|cache-control'
1458
- ```
1459
-
1460
- If it ever stops holding, the documented alternative is a zone-level **Response
1461
- Header Transform Rule** (or Bulk Redirects, which sets both), not a Pages
1462
- Function — `_redirects` and `_headers` both stop applying to a route a Function
1463
- serves.
1396
+ cannot address, because a link to it could not resolve otherwise, and it
1397
+ reports that beside the count so the finding reaches `publish.site: homepage`
1398
+ mode as well. The remaining gap is `HarnMaster-3-FoundryVTT`, which runs no
1399
+ `content-build lint` at all and so receives no frontmatter finding of any kind;
1400
+ that is a missing script in that repository, not a rule to duplicate one at a
1401
+ time.
1402
+
1403
+ ### `/<package>/` is the homepage, and nothing redirects
1404
+
1405
+ The package's own address serves the homepage directly: Hugo renders the
1406
+ mount's `_index.md` as the `home` kind at `baseURL`, which is
1407
+ `https://www.heroiclands.org/<package>/`. `package-build site-root` writes no
1408
+ `_redirects` beside the site — and removes one an earlier build left there,
1409
+ since Cloudflare Pages would apply it. Its `_headers` suppress indexing on
1410
+ every host-assigned address and nothing else: no `Cache-Control` is pinned on
1411
+ the prefix root, because a lifetime on the homepage would hold a stale copy at
1412
+ the most-linked address after a deploy.
1464
1413
 
1465
1414
  ### The homepage's own links
1466
1415
 
1467
- The homepage is the page a reader arrives at, and it is the one page
1468
- nothing checked. SoHL's landing pointed at `kb/creature/` and `kb/character/`
1469
- from the day those two types merged into `being` two 404s on the package's
1470
- front page, through every build, because a landing's links went through no
1471
- checker at all.
1472
-
1473
- `links` therefore audits a `type: homepage` note as well, and it reads **both**
1474
- halves of it. Of the six homepages authored today four carry every link in the
1475
- body as ordinary markdown and two carry them in `landing:` — and the one whose
1476
- dead links prompted this has an _empty body_. A dead link in a card is exactly
1477
- as broken as one in a paragraph, so `landing.install.url`, every
1478
- `cards…​.url` / `.href`, the markdown links inside the prose fields (`lead`,
1479
- `closing`, `install.intro`, `install.note`, a card's `description`, a link's
1480
- `note`) and the body's own markdown links are all read.
1481
-
1482
- **`url` and `href` are not the same address and are not checked the same way.**
1483
- The theme resolves a `url` against the site with `relURL`, so a package writes
1484
- `kb/rules/` and is served `/sohl/kb/rules/` without naming its own prefix; an
1485
- `href` is an address that is _already_ resolved and is used verbatim, which is
1486
- what `cards.source: sections` fills in. A leading `/` is therefore a defect in a
1487
- `url` — Hugo prefixes it a second time — and correct in an `href`.
1488
-
1489
- Four findings, and each one names the form to write instead:
1416
+ The homepage is the page a reader arrives at, and it is the one page no
1417
+ wikilink resolver reaches: a homepage is published verbatim in every
1418
+ publishing mode, so its links are markdown links in its body, and `links`
1419
+ audits them. A body link is emitted as written and resolved by the browser
1420
+ against the homepage's own address, which _is_ the package root, so a
1421
+ package-relative one (`kb/rules/`) lands where a reader expects.
1422
+
1423
+ Three findings, and each one names the form to write instead:
1490
1424
 
1491
1425
  | Finding | Why |
1492
1426
  | ---------------------------- | --------------------------------------------------------------------------------- |
1493
1427
  | A **retired content type** | `kb/creature/` when `creature` became `being`. The engine knows what was retired. |
1494
- | A **hardcoded absolute URL** | Into this package's own prefix, or into one a vendored manifest names. |
1495
- | A **root-relative `url:`** | `relURL` prefixes it again. `href:` is exempt — verbatim is what it means. |
1428
+ | A **hardcoded absolute URL** | Into this package's own prefix, or into one a fetched index names. |
1496
1429
  | A **wikilink** | Nothing resolves one here: a homepage is published verbatim in every mode. |
1497
1430
 
1498
1431
  That last one is why a homepage does **not** get the wikilink resolution every
1499
1432
  other note body gets. In `homepage` mode the content tree is never walked, so
1500
1433
  there is no index for a wikilink to resolve against — and giving the page one
1501
1434
  would make the mode depend on exactly the machinery its licensing fence exists
1502
- to not build. So a landing addresses the web the way the web does, and a
1435
+ to not build. So a homepage addresses the web the way the web does, and a
1503
1436
  wikilink on one is reported rather than resolved.
1504
1437
 
1505
1438
  **What is checkable, and what is not.** Only an address into this site is, and
1506
1439
  only against facts the build already holds — the retired-type table and the
1507
- package prefixes a vendored manifest names. Two things are deliberately not
1440
+ package prefixes a fetched index names. Two things are deliberately not
1508
1441
  attempted:
1509
1442
 
1510
1443
  - **Whether an external URL answers.** There is no network at build time, and a
1511
1444
  build must not go red because a third party is down.
1512
1445
  - **Whether a live in-site address names a page that exists.** Several surfaces
1513
- a landing routes to are produced by other tools entirely — generated API
1514
- documentation, hand-authored Hugo sections — so this build does not hold the
1515
- set of published pages and would report a working link as dead. A bare
1516
- `https://www.heroiclands.org/<package>/` is left alone for the same reason it
1517
- cannot be improved: a package homepage compiles into no document, so there is no
1518
- better form to write.
1446
+ a homepage routes to are produced by other tools entirely — generated API
1447
+ documentation, say — so this build does not hold the set of published pages
1448
+ and would report a working link as dead. A bare `/<package>/` names another
1449
+ package's front page and needs no index: it is left alone because it cannot
1450
+ be improved.
1519
1451
 
1520
1452
  ## The content format specification
1521
1453
 
@@ -1895,18 +1827,16 @@ to the legacy-URL map" — and no such map was ever written, here or in any
1895
1827
  consumer. All of it is gone.
1896
1828
 
1897
1829
  The `type-` half earns its place: it keeps every content address clear of the
1898
- package's fixed mounts (`/<package>/` for the landing page, `/<package>/api/` for
1830
+ package's fixed mounts (`/<package>/` for the homepage, `/<package>/api/` for
1899
1831
  generated API docs), neither of which contains a hyphen or names a type. So the
1900
1832
  namespace is provably disjoint rather than conventionally so.
1901
1833
 
1902
- **A page is written flat, named by its address**, not filed
1903
- into `<section>/`, because Hugo derives a page's section from where the file is
1904
- written and a section gave it a landing page, `.CurrentSection` and a per-section
1905
- layout lookup. But a section appears in no address, so the note format was
1906
- carrying a key, a filename convention, a landing rule and a synthesis pass in
1907
- order to satisfy a rendering engine's directory semantics. The file is now
1908
- `<mount>/<type>-<shortcode>.md` and the front-matter `url:` still publishes it at
1909
- the package root, one level above.
1834
+ **A page is written flat, named by its address**, not filed into a
1835
+ directory, because Hugo derives a page's section from where the file is
1836
+ written, and a section appears in no address. The file is
1837
+ `<mount>/<type>-<shortcode>.md` and the front-matter `url:` publishes it at
1838
+ the package root, one level above; the `section` kind is disabled on every
1839
+ site, so no directory would answer even if one were written.
1910
1840
 
1911
1841
  **A page states its address without the package base; everything pointing _at_
1912
1842
  it composes one**. They read as one quantity and are two:
@@ -1923,15 +1853,11 @@ its own base to a value that already carried one and published every content
1923
1853
  page a segment too deep — `/sohl/sohl/doc-rulesintro/`, 404 at the address the
1924
1854
  manifest, the sitemap and every inbound link named.
1925
1855
 
1926
- **There is no landing page.** A `README.md` was its section's landing and
1927
- addressed the section itself; that is retired with the section. A page that
1928
- introduces the notes of a type is an ordinary note addressed `doc-<type>`, with
1929
- no build path of its own exactly as the package's own front page is
1930
- `homepage-root`.
1931
-
1932
- **Sections stay, as configuration.** `site.sections` still writes an `_index.md`
1933
- per section, and that is now the _only_ thing that makes one exist — see
1934
- [What a section may declare](#what-a-section-may-declare).
1856
+ **There is no landing page and no section.** A page that introduces the notes
1857
+ of a type is an ordinary note addressed `doc-<type>`, with no build path of its
1858
+ own, carrying a content table over what it introduces. A site is its homepage
1859
+ and its pages, and nothing is generated between them see
1860
+ [What the site publishes](#what-the-site-publishes).
1935
1861
 
1936
1862
  ### The address scheme
1937
1863
 
@@ -1961,9 +1887,7 @@ publish:
1961
1887
  `section:` key are refused by name.
1962
1888
 
1963
1889
  A note's `subType` is checked against the values its type declares, and only
1964
- those. It briefly had a second reading — a `README` landing's `subType` was the
1965
- _address_ it landed at, so the closed genre list could not answer for it. The
1966
- cause is removed rather than the vocabulary widened again.
1890
+ those.
1967
1891
 
1968
1892
  The only note the scheme yields no address for is one carrying no `shortcode`.
1969
1893
  It is **reported and omitted**, never guessed: the command prints one located
@@ -2217,15 +2141,15 @@ indistinguishable from one built against a mis-pointed tree.
2217
2141
  ## Publishing a website
2218
2142
 
2219
2143
  ```bash
2220
- npx content-build site # the configured tree and output
2221
- npx content-build site --out tmp/kb # or somewhere else
2144
+ npx content-build site # the configured tree, under build/hugo/
2222
2145
  ```
2223
2146
 
2224
2147
  The sibling of `package compile`: the same content tree, rendered as pages
2225
2148
  instead of compiled into packs. It does the walk, the frontmatter read, the
2226
2149
  address derivation, the address index, table expansion, wikilink resolution,
2227
- code-fence protection, the foreign-manifest merge, the page emission and the
2228
- section-landing synthesis.
2150
+ code-fence protection, the foreign-manifest merge and the page emission. A
2151
+ site is its homepage and its pages — see
2152
+ [What the site publishes](#what-the-site-publishes).
2229
2153
 
2230
2154
  ### The homepage, and how much else is published
2231
2155
 
@@ -2249,15 +2173,14 @@ A package declares **exactly one** of these, and both `content-build lint` and
2249
2173
 
2250
2174
  That is the whole envelope. A homepage **compiles into no compendium
2251
2175
  document**, and so appears in no pack and in no link manifest — which is why it
2252
- still refuses `id`. Everything else about its address is ordinary: it declares a
2253
- `shortcode`, publishes at `/<contentPackage>/homepage-root/`, and is cited as
2254
- `[[homepage-root|Text]]` (see
2255
- [The homepage is addressed like every other note](#the-homepage-is-addressed-like-every-other-note)).
2256
- `/<contentPackage>/` itself is a redirect the package authors, not a page this
2257
- build writes. It is dispatched on `type` like every other note, not on a
2258
- filename nothing in this format is decided by a file's name, which is why
2259
- `sohl-thalorna` can keep a `README.md` in its content tree as a developer
2260
- explainer about the source tree.
2176
+ refuses `id`. It is the package root: written as the mount's `_index.md`,
2177
+ rendered at `/<contentPackage>/`, and cited as `[[homepage-root|Text]]` by
2178
+ the `shortcode` it declares (see
2179
+ [The homepage is the package root](#the-homepage-is-the-package-root-addressed-like-every-other-note)).
2180
+ It is dispatched on `type` like every other note, not on a filename — nothing
2181
+ in this format is decided by a file's name, which is why `sohl-thalorna` can
2182
+ keep a `README.md` in its content tree as a developer explainer about the
2183
+ source tree.
2261
2184
 
2262
2185
  `type: homepage` is declared by the **engine**, not by the `sohl` item registry,
2263
2186
  so a package that configures no `itemBuilders` at all — `HarnMaster-3-FoundryVTT`
@@ -2284,8 +2207,7 @@ content_ — journal text, artwork, item descriptions, compiled notes — and a
2284
2207
  announcing the module discloses none of it. Because the failure mode is silent,
2285
2208
  the mode **fences the content surfaces off** rather than trusting a
2286
2209
  configuration to stay empty: in `homepage` mode the tree is never walked for
2287
- pages, and `sections`, `landing` and `backfillSections` emit nothing even when
2288
- they are declared.
2210
+ pages, whatever else the `site:` block declares.
2289
2211
 
2290
2212
  That is separate from the **dependency** edge, which such a module also
2291
2213
  declines: being cited by another package is what would stop it being
@@ -2294,60 +2216,47 @@ address anyone links to. A package publishes its content index regardless — th
2294
2216
  licensing constraint is against publishing _pages_, not against the artifact
2295
2217
  existing — but nothing may declare it as a dependency.
2296
2218
 
2297
- The homepage's file is written at the root of the content mount,
2298
- `build/hugo/content/` — the package's own site root, one level above the
2299
- content mount, which is where `publish.address.prefix` puts everything else —
2300
- under the name its address gives it, `homepage-root.md`. As with every other page, the front matter's `url`
2301
- decides where it publishes, and states it relative to the site root — `site.base`
2302
- does not reach it.
2219
+ The homepage's file is the mount's `_index.md`, written at the root of the
2220
+ content tree, `build/hugo/content/` — the package's own site root, one level
2221
+ above the content mount, which is where `publish.address.prefix` puts
2222
+ everything else. Hugo renders it as the `home` kind at `baseURL`, so it states
2223
+ no `url` of its own.
2303
2224
 
2304
2225
  **What it does not do is decide addresses.** Those come from `publish.address`,
2305
2226
  the same setting the content index reads, so a page and its index record cannot
2306
2227
  disagree about where the page is. Everything under `site:` is _framing_ —
2307
- what a section is called, and the residue of the generated Hugo configuration
2308
- that is this repository's own. What the site publishes is the content tree and
2309
- nothing beside it: a page of documentation is a note (`type: doc`, with
2310
- `pack: none` where it compiles into no document), so there is no second
2311
- mechanism for mounting a directory of markdown, and a `trees:` or
2312
- `readmeSections:` key is refused with a message saying so.
2228
+ the repository's own body rewrites, and the residue of the generated Hugo
2229
+ configuration that is this repository's own. What the site publishes is the
2230
+ homepage and the content tree and nothing beside them: a page of
2231
+ documentation is a note (`type: doc`, with `pack: none` where it compiles into
2232
+ no document), so there is no second mechanism for mounting a directory of
2233
+ markdown, and a `trees:` or `readmeSections:` key is refused with a message
2234
+ saying so.
2313
2235
 
2314
2236
  ```yaml
2315
2237
  site:
2316
2238
  base: /sohl/ # default: /<contentPackage>/ — hrefs only, never a page's `url:`
2317
2239
  packages: [sohl, thalorna] # default: just contentPackage
2318
- backfillSections: true
2319
- landing: { title: Knowledgebase, type: knowledgebase }
2320
2240
  pass: sohlKb
2321
2241
  passOptions:
2322
2242
  apiBase: /sohl/api/
2323
2243
  symbolMap: kb/data/api-symbols.json
2324
- sections:
2325
- being:
2326
- title: Beings
2327
- banner: banners/creature.webp
2328
- description: Folk, animals and the things that walk the world.
2329
- reference: { title: Reference, listType: doc, listSubType: reference }
2330
- list: { shortcodes: true }
2331
2244
  notfound:
2332
2245
  tagline: Song of Heroic Lands has no page at
2333
2246
  sitenoun: site
2334
2247
  links:
2335
- - { title: Knowledgebase, url: kb/, text: Every section, with the full content catalog. }
2336
- ```
2337
-
2338
- | Key | What it decides |
2339
- | ------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
2340
- | `base` | Where the package is served: the prefix on every rendered `href`, and what a manifest `path` is measured against. It reaches no page's own `url:` — see [A page's URL is its address](#a-pages-url-is-its-address). Defaults to `/<contentPackage>/`. |
2341
- | `packages` | Which content packages this site renders. Defaults to its own. |
2342
- | `sections` | The Hugo sections this site declares, and what each says about itself — see below. |
2343
- | `landing` | Frontmatter for the mount's own `_index.md`. Passed through — the vocabulary is the theme's. |
2344
- | `backfillSections` | Write a bare `_index.md` for any other directory directly under the mount. |
2345
- | `pass` | A named bundle of this repository's own body rewrites. |
2346
- | `passOptions` | That bundle's options. |
2347
- | `assets` | The host every package's imagery is served from; the generated `params.cdnBaseURL`. |
2348
- | `list` | How a listing page renders; the generated `params.list`. |
2349
- | `notfound` | The wording of the "page not found" page; the generated `params.notfound`. |
2350
- | `hugo` | A mapping deep-merged over the generated Hugo configuration, last. Every key the generator writes is refused here — see `docs/configuration.md`. |
2248
+ - { title: Rules, url: doc-rulesintro/, text: Success and opposed tests, injury, healing. }
2249
+ ```
2250
+
2251
+ | Key | What it decides |
2252
+ | ------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
2253
+ | `base` | Where the package is served: the prefix on every rendered `href`, and what a manifest `path` is measured against. It reaches no page's own `url:` — see [A page's URL is its address](#a-pages-url-is-its-address). Defaults to `/<contentPackage>/`. |
2254
+ | `packages` | Which content packages this site renders. Defaults to its own. |
2255
+ | `pass` | A named bundle of this repository's own body rewrites. |
2256
+ | `passOptions` | That bundle's options. |
2257
+ | `assets` | The host every package's imagery is served from; the generated `params.cdnBaseURL`. |
2258
+ | `notfound` | The wording of the "page not found" page; the generated `params.notfound`. |
2259
+ | `hugo` | A mapping deep-merged over the generated Hugo configuration, last. Every key the generator writes is refused here — see `docs/configuration.md`. |
2351
2260
 
2352
2261
  Where the tree is written is not among them. `content-build site` writes the
2353
2262
  whole Hugo source tree under `build/hugo/` — the generated `hugo.toml`, the
@@ -2356,99 +2265,66 @@ script runs `hugo --source build/hugo` over it. The generated file's every
2356
2265
  value has a source the repository already states; `docs/configuration.md`
2357
2266
  lists them.
2358
2267
 
2359
- ### What a section may declare
2268
+ ### What the site publishes
2360
2269
 
2361
- **`sections` is what a section _is_ now**. A content page is addressed
2362
- `(type, shortcode)` and written flat under the mount, so no page creates a
2363
- directory and nothing else makes `/<package>/<prefix><section>/` answer at all.
2364
- A site that wants that address says so here, and this build writes the
2365
- `_index.md` that makes Hugo agree it is a section.
2270
+ **A site is its homepage and its pages, and nothing is generated between
2271
+ them.** The `type: homepage` note is `/<package>/`; every other note is one
2272
+ page at `/<package>/<type>-<shortcode>/`. No section directory is written, no
2273
+ listing of a type, no tag page: the generated Hugo configuration disables the
2274
+ `section`, `taxonomy`, `term` and `RSS` kinds on every site, so `home` and
2275
+ `page` are the only kinds that render, and a request for `/<package>/being/`
2276
+ is a 404.
2366
2277
 
2367
- Two consequences follow, and neither is optional:
2278
+ Every structure above the pages — which notes belong together, in what order,
2279
+ under which headings, with which columns — is **authored**, as a `doc` note
2280
+ carrying a content table over the content index:
2368
2281
 
2369
- - **Declare every section the site links to.** A card, a menu entry or a
2370
- breadcrumb pointing at a section nobody declared is a 404.
2371
- - **A section landing lists no child pages.** Its directory holds only its own
2372
- `_index.md`, so a layout reading `.Pages` finds nothing. A layout that queries
2373
- `site.RegularPages` by `Params.type` is unaffected, and that is the shape a
2374
- content catalog wants anyway — it groups by what a page _is_, not by where its
2375
- file happened to be written. A section that wants that query run for it says
2376
- so with `listType` — see below.
2377
-
2378
- Whatever the entry may carry is the whole of what the section can say.
2379
-
2380
- | Key | Required | What it does |
2381
- | ------------- | -------- | ------------------------------------------------------------------------- |
2382
- | `title` | yes | The landing's heading, so it matches the card that links to it. |
2383
- | `banner` | no | The hero image, resolved as a CDN asset like any other `banner:`. |
2384
- | `description` | no | The hero standfirst under the heading, and the blurb a landing card uses. |
2385
- | `listType` | no | The content **type** whose pages this section lists. |
2386
- | `listSubType` | no | Narrows that to one **subType**. Only with a `listType`. |
2387
-
2388
- #### Saying what a section lists
2282
+ ````markdown
2283
+ ---
2284
+ type: doc
2285
+ subType: reference
2286
+ shortcode: beings
2287
+ pack: none
2288
+ name:
2289
+ full: Beings
2290
+ ---
2389
2291
 
2390
- A generic list layout has nothing to render, because the membership a section
2391
- landing used to get free from Hugo's page tree no longer exists anywhere the
2392
- theme can read: not on the page, not on the landing, not in any URL — only here.
2393
- So a section states its own query, and a layout substitutes it when `.Pages` is
2394
- empty:
2292
+ Every person and creature of the setting, by realm.
2395
2293
 
2396
- ```yaml
2397
- sections:
2398
- being: { title: Beings, listType: being }
2399
- rules: { title: Rules, listType: doc, listSubType: rules }
2400
- user-guide: { title: User Guide, listType: doc, listSubType: userguide }
2294
+ ```dataview
2295
+ TABLE name.full AS Name, subType AS Kind, data.realm AS Realm
2296
+ FROM type = "being"
2297
+ SORT data.realm, name.full
2401
2298
  ```
2299
+ ````
2402
2300
 
2403
- emits, for the last of those, `user-guide/_index.md`:
2404
-
2405
- ```yaml
2406
- ---
2407
- title: User Guide
2408
- listType: doc
2409
- listSubType: userguide
2410
- ---
2411
- ```
2301
+ ````
2412
2302
 
2413
- Three things about the spelling, each of them load-bearing:
2303
+ That page is `doc-beings`, published at `/<package>/doc-beings/`, and it is the
2304
+ index — linked from the homepage like any other page, and choosing its own
2305
+ membership, order, headings and columns. A tag is a field such a table
2306
+ filters on, not a page of its own.
2414
2307
 
2415
- - **Not `type`.** On an `_index.md` that is Hugo's own layout selectora
2416
- landing carrying `type: doc` is rendered by `layouts/doc/list.html` rather
2417
- than the default list template so writing the content type there would
2418
- silently change which template serves the landing. This build already relies
2419
- on that behaviour for the mount's own `landing`.
2420
- - **Not inferred from the section's name.** A section is named for a URL the
2421
- site chose; a type and a subType are addresses. They need not agree, and on
2422
- `sohl` they do not: the section is `user-guide`, because that is a published
2423
- URL, while the subType is `userguide` because an address segment is
2424
- lowercase alphanumeric. Both values are checked against that charset here, so
2425
- copying the section's name in is refused rather than quietly matching nothing.
2426
- - **`listSubType` needs a `listType`.** A subType only tells pages apart within
2427
- a type — `rules`, `userguide` and `reference` are all `doc` — so alone it
2428
- names no query.
2308
+ **One page per note, whatever it compiles into.** A system-bearing notean
2309
+ `affiliation`, a `being`, every item type compiles into two Foundry
2310
+ documents, the Item and the JournalEntry carrying its prose, addressed
2311
+ `doc<type>-<shortcode>`. On the web it renders as one page, which is its own
2312
+ documentation: the site index keys that page under both `<type>/<shortcode>`
2313
+ and `doc<type>/<shortcode>`, so a link written either way lands on it, and a
2314
+ content table that lists a type surfaces each note once.
2429
2315
 
2430
- **The vocabulary is closed, and a key outside it is refused by name:**
2316
+ A configuration that asks the build to generate an index `site.sections`
2317
+ (with the `listType` / `listSubType` an entry carried), `site.landing`,
2318
+ `site.backfillSections`, and `site.list`, which said how such a listing
2319
+ renders — is refused by name, with one message:
2431
2320
 
2432
2321
  ```text
2433
- package-build config: `site.sections.affliction.descrption` is not a
2434
- recognized option (expected one of: title, banner, description, listType,
2435
- listSubType).
2436
- ```
2437
-
2438
- That refusal is the point. `landing` is passed through unvalidated because it is
2439
- written once, for the mount, in one landing template's own vocabulary. A section
2440
- entry is written fourteen to twenty times per build against a contract every
2441
- package and every section shares, so an unbounded one would let a mistyped
2442
- `descrption:` publish into front matter, be read by nobody, and say nothing to
2443
- anyone. Refusing it costs one line here when the vocabulary genuinely grows, and
2444
- buys a build that cannot quietly emit a key no theme reads. `listType` and
2445
- `listSubType` are that growth: two named keys, checked, rather than an open
2446
- passthrough in which `listTpye:` would publish and no landing would list
2447
- anything.
2448
-
2449
- The **writers** name no keys: a section's `_index.md` is whatever the entry
2450
- resolved to, `title` first. So extending the vocabulary is a change to the
2451
- schema alone, and the two can no longer drift apart.
2322
+ package-build config: `site.sections` is retired a site is its homepage and
2323
+ its pages, and any index between them is a `doc` note: write one with
2324
+ `type: doc`, a `shortcode` and `pack: none`, carrying a content table over the
2325
+ notes it lists, and link it from the homepage. Nothing is generated between
2326
+ the homepage and the pages, so delete the key.
2327
+ ````
2452
2328
 
2453
2329
  ### Why the output location is fixed
2454
2330
 
@@ -2833,7 +2709,7 @@ path now rides on the error, and the loader that read the file resolves it
2833
2709
  against the YAML, so all of them come out located:
2834
2710
 
2835
2711
  ```text
2836
- package-build.config.yaml:382:64: error: package-build config: `site.sections.being.descrption` is not a recognized option (expected one of: title, banner, description, listType, listSubType).
2712
+ package-build.config.yaml:382:64: error: package-build config: `site.notfound.links[0].descrption` is not a recognized option (expected one of: title, url, text).
2837
2713
  ```
2838
2714
 
2839
2715
  The same two rules apply. A key the file never declares — a required one that is