@heroiclands/package-build 11.0.0 → 13.0.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/CHANGELOG.md CHANGED
@@ -1,5 +1,284 @@
1
1
  # @heroiclands/package-build
2
2
 
3
+ ## 13.0.0
4
+
5
+ ### Major Changes
6
+
7
+ - 0c2def0: A section is a Hugo directory concept, and the note format no longer carries one
8
+ (#204). Content pages emit **flat** under the mount, named by their address; the
9
+ `README.md` landing convention, `sectionOf`, the `<section>/` output routing, and
10
+ the "no section, so nowhere to file the page" refusal are all gone.
11
+
12
+ **Why it goes.** Since #181 a page's URL _is_ its address,
13
+ `/<package>/<type>-<shortcode>/`, so a section appears in **no address at all**.
14
+ Its only remaining job was choosing the directory a page was written into, and
15
+ the only reason that mattered was Hugo's rule about what counts as a section —
16
+ `writeSectionLandings`'s own docstring said both of its jobs "exist because of
17
+ how Hugo decides what a section is". So the note format carried a filename
18
+ convention, a landing rule, a routing function and a refusal in order to satisfy
19
+ a rendering engine's directory semantics. #197 was the bill for that: `subType`
20
+ did double duty — a genre for an ordinary `doc`, a URL section for a `README` —
21
+ the two vocabularies collided, and it took #198, #200, #201 and a release to
22
+ settle by widening one of them rather than by removing the overload.
23
+
24
+ **A page that introduces the notes of a type is now an ordinary note**, named by
25
+ convention and with no build path of its own: `type: doc`, `subType: reference`,
26
+ `shortcode: <type>`, addressed `doc-<type>`. The package's own front page already
27
+ worked this way (`homepage-root`, #182).
28
+
29
+ | Surface | Before | After |
30
+ | -------------------------------------- | --------------------------------------------- | ------------------------------------------------- |
31
+ | A content page's file | `<mount>/<section>/<type>-<shortcode>.md` | `<mount>/<type>-<shortcode>.md` |
32
+ | A content page's `url:` | `/<package>/<type>-<shortcode>/` | **unchanged** |
33
+ | A `README.md` in the content tree | its section's landing, addressed `<section>/` | an ordinary page, addressed `<type>-<shortcode>/` |
34
+ | A `doc` with no `subType` | refused — "no section" | published, at `doc-<shortcode>/` |
35
+ | A `doc`'s `subType` | a genre, or a section address on a `README` | a genre, closed to what the type declares |
36
+ | `packageAddress(fm, { isReadme })` | branched on the filename | a pure function of the frontmatter |
37
+ | `sectionOf` | exported | removed |
38
+ | `declaredSections` | exported, fed the lint | removed — it had no other reader |
39
+ | `lintNote` / `lintFrontmatter` options | `landing`, `types`, `sections` | removed |
40
+
41
+ **The closed-set check becomes meaningful again.** #206, released alongside this,
42
+ put a retired-spelling warning and an address-charset error ahead of it in
43
+ `checkSubType`; removing the section branch from underneath leaves three checks
44
+ in one order — retired spelling, then charset, then the genres the type declares
45
+ — with nothing widening the last of them. A `doc` note whose `subType` names a
46
+ content type is refused again, `README` or not.
47
+
48
+ **`site.sections` is not retired — it is now the whole of what a section is.**
49
+ With no page filed into `<section>/`, nothing else makes
50
+ `/<package>/<prefix><section>/` exist at all, so `writeSectionLandings` stays and
51
+ its role changes from backfilling directories that pages created to _declaring_
52
+ the Hugo sections a site wants. Two consequences follow for a consuming site:
53
+
54
+ - **Declare every section the site links to.** A card, menu entry or breadcrumb
55
+ pointing at an undeclared section is a 404.
56
+ - **A section landing lists no child pages.** Its directory holds only its own
57
+ `_index.md`, so a layout reading `.Pages` finds nothing; one that queries
58
+ `site.RegularPages` by `Params.type` is unaffected, and that is the shape a
59
+ content catalog wants anyway — it groups by what a page _is_, not by where its
60
+ file happened to be written.
61
+
62
+ **`publish.address.landing` is inert, and still accepted.** Both publishing
63
+ consumers declare `landing: readme`, which stated something true when they wrote
64
+ it; refusing it now would break them over a correct statement, and silently
65
+ ignoring it would be worse. It selects nothing and is deleted once no
66
+ configuration writes it — the third step, and a separate change. The retired
67
+ `collection` value stays refused by name (#202).
68
+
69
+ **Migration.** No note edit is required, and **no published URL moves** — an
70
+ address never contained a section. What a consuming site must check is its
71
+ layouts and its `site.sections`, per the two consequences above. Verified against
72
+ `sohl`, `sohl-thalorna` and `sohl-kethira-basic` at `origin/main`: `lint`,
73
+ `links`, `manifest` and `package compile` are byte-identical for all three (both
74
+ link manifests and every compiled pack document), and `sohl`'s emitted site is
75
+ byte-identical page for page, keyed by URL — all 1,606 addresses unchanged, 1,670
76
+ of 1,671 files identical. The one difference is a backfilled `kb/macro/_index.md`
77
+ that no longer exists, `macro` being the one section `sohl` did not declare;
78
+ nothing in its site or the shared theme links it.
79
+
80
+ ## 12.0.0
81
+
82
+ ### Major Changes
83
+
84
+ - ab34b1c: Retire the `collection` landing rule, the `section:` frontmatter key it read,
85
+ and the `collection` doc subtype that selected it (#202). A section is landed by
86
+ the `README.md` in its directory, and that is now the only rule.
87
+
88
+ **Why it goes.** Every publishing tree had already migrated: no note in `sohl`,
89
+ `sohl-thalorna` or `sohl-kethira-basic` declares `section:` or writes
90
+ `subType: collection`, and both publishing consumers configure `landing: readme`.
91
+ What was left was not merely unused but **unimplemented on one of the two sides
92
+ that must agree**. `engine/content-address.mjs` branched on the configured rule;
93
+ `engine/site-build.mjs` never read it — it derives `isReadme` from the basename
94
+ and treats a `README.md` as a landing whatever the configuration says. So under
95
+ `landing: collection` the link manifest and the emitted site would have disagreed
96
+ about where a page is, which is the single failure the shared address function
97
+ exists to prevent. With one rule they agree by construction.
98
+
99
+ **`landing: collection` is refused, not merely unrecognized.** Reported as a bad
100
+ value it would read as a misspelling of the rule that survives, and an author
101
+ would correct the value rather than learn that the mechanism is gone. The
102
+ diagnostic names the key, says the rule is retired, says a section is landed by
103
+ its `README.md`, and says the `section:` key went with it — located to the line
104
+ and column in `package-build.config.yaml`, as every configuration finding is.
105
+
106
+ **`section:` is refused the way `draft:`, `package:` and `aliases:` are.** It had
107
+ exactly one reader — the retired branch — and no schema or vocabulary declared
108
+ it, so nothing checked it: left in place it would be _ignored_, which reads to
109
+ its author as though it still works. It is now reported by `content-build lint`
110
+ and refused at compile, with the file, line and column.
111
+
112
+ | Surface | Before | After |
113
+ | -------------------------------- | ------------------------------------- | -------------------------------------- |
114
+ | `publish.address.landing` | `readme` \| `collection` | `readme` — the value is still accepted |
115
+ | `section:` in a note | read under `collection`, else ignored | refused, at lint and at compile |
116
+ | `subType: collection` on a `doc` | selected the rule | not a subtype the format declares |
117
+ | `LANDING_RULES` | `["readme", "collection"]` | `["readme"]` |
118
+
119
+ **Migration.** Delete `landing: collection` from `publish.address` — or write
120
+ `landing: readme` — and make each section's landing the `README.md` in its
121
+ directory. Delete any `section:` a note still carries. A repository that already
122
+ configures `landing: readme` and writes no `section:` needs no change: `lint`,
123
+ `links` and `site` were verified byte-identical against `sohl`, `sohl-thalorna`
124
+ and `sohl-kethira-basic` at `origin/main`, including all 1,657 pages `sohl`
125
+ emits.
126
+
127
+ **`publish.address.landing` itself survives, for now.** It is the key both
128
+ publishing consumers declare, and refusing a correct `landing: readme` would
129
+ break them over a statement that is still true. With one rule it selects nothing,
130
+ so it is a candidate for deletion once no configuration writes it — the third
131
+ step `package:` took (#56), and a separate change.
132
+
133
+ ### Minor Changes
134
+
135
+ - ed06be0: Hold `type` and `subType` to the address charset, and rename a `doc`'s
136
+ `user-guide` subtype to `userguide` (#206).
137
+
138
+ **The rule.** An address is `package-type-shortcode`, read back by counting
139
+ hyphen-separated segments, and that is sound for exactly one reason: no segment
140
+ may contain a hyphen. `ADDRESS_SEGMENT_PATTERN` (`^[A-Za-z0-9]+$`) stated it and
141
+ `SHORTCODE_PATTERN` aliased it, but only a **shortcode** was checked against it.
142
+ The other two values that reach an address were not: a `type` is the first
143
+ segment of every address, and a `doc`'s `subType` is the section it routes to —
144
+ a path segment of its own, and, under #204, a shortcode. Both are now held to
145
+ the same constant, read rather than restated; a third spelling of one rule is how
146
+ the disagreements found in #202 and #203 happened.
147
+
148
+ **The diagnostic** is located where the value was written, in the standard form:
149
+
150
+ ```text
151
+ assets/content/Beings/Folk.md:3:1: error: `subType` "common-folk" is not an address segment — a subType is letters and digits only (^[A-Za-z0-9]+$), the same charset a shortcode is held to. …
152
+ ```
153
+
154
+ The charset is checked **ahead of** the closed-set check, which is what makes it
155
+ reach a type whose `subTypes` are declared but not yet enumerated (`being`) —
156
+ values nothing may otherwise claim to check.
157
+
158
+ **`user-guide` became `userguide`**, the one declared value that broke the rule
159
+ and the only hyphenated `type` or `subType` in the vocabulary. A hyphenated
160
+ declaration can no longer be imported at all: the registry is checked against the
161
+ charset as `engine/note-vocabulary.mjs` loads.
162
+
163
+ **The old spelling is accepted for one release, and says so.** A `doc` written
164
+ `subType: user-guide` is reported as a **warning** naming the note, the retired
165
+ value and its replacement, and the note still compiles:
166
+
167
+ ```text
168
+ User_Guide/Actions.md:3:1: warning: `subType` "user-guide" is a retired spelling of "userguide" on a doc; write "userguide". …
169
+ ```
170
+
171
+ _The ordering is the reverse of the usual, deliberately._ For a retired field the
172
+ sweep goes first; here it must go last. 43 `sohl` notes author `user-guide`
173
+ today, and declaring only the new spelling would invalidate all 43 with a release
174
+ they had no chance to sweep ahead of. So the acceptance ships first, consumers
175
+ rename, and a later change removes the acceptance — at which point the old
176
+ spelling falls through to the ordinary undeclared-value error with no code left
177
+ to remove. That later change is the breaking one; this one breaks nothing, which
178
+ is why it is a minor.
179
+
180
+ **`type` gets no transitional path**, deliberately: no note in `sohl`,
181
+ `sohl-thalorna` or `sohl-kethira-basic` authors a hyphenated type, so an
182
+ acceptance would be dead code guarding a case that does not exist. Measured
183
+ against a pristine `origin/main` extraction of each tree, the only hyphenated
184
+ value of either key anywhere is the 43 `user-guide` notes.
185
+
186
+ ## 11.1.0
187
+
188
+ ### Minor Changes
189
+
190
+ - 8906d7a: **A `README` landing's `subType` is checked against the sections the repository declares, not the genre list.**
191
+
192
+ Two vocabularies were spelled with one key. For every note, `subType` is a
193
+ sub-kind of its type, checked against a closed list. For a `README` under
194
+ `publish.address.landing: readme` it is additionally the **URL section** —
195
+ `sectionOf` reads a `doc`'s `subType` as the segment the landing addresses — and
196
+ that is an open set the consuming repository names in `site.sections` /
197
+ `site.readmeSections`. `engine/frontmatter-lint.mjs` applied the closed reading
198
+ while `engine/content-address.mjs` applied the open one, so the two halves of the
199
+ toolchain disagreed about the same note: `content-build site` addressed and
200
+ published `Weapons/README.md` at `weapongear/`, and `content-build lint` refused
201
+ it because `doc` declares only `rules`, `user-guide` and `reference`. An item
202
+ section's landing was not expressible.
203
+
204
+ The check now widens **for a landing only**, and only under `landing: readme`: a
205
+ `README` whose `subType` is its section may name any section the repository
206
+ declares, as well as any of its type's own genres — a genre is a section a `doc`
207
+ tree publishes under whether or not the repository describes it, so narrowing to
208
+ the configured set alone would refuse a correct `README`. An ordinary note's
209
+ `subType` stays closed to its type's genres, unchanged.
210
+
211
+ **The guard survives**, checked against the set that actually decides where the
212
+ page goes:
213
+
214
+ ```text
215
+ Weapons/README.md:4:1: error: `subType` "weapongeer" is the section this README
216
+ lands at, and nothing declares it: it is neither a section this repository
217
+ configures under `site.sections` / `site.readmeSections` (rules, user-guide,
218
+ weapongear, …) nor one of the subtypes doc declares (rules, user-guide,
219
+ reference). Did you mean "weapongear"?
220
+ ```
221
+
222
+ Whether the value is an address is asked of `sectionOf` rather than by naming a
223
+ type, so the linter still knows no type names of its own; the section list is
224
+ read from the resolved configuration the site build renders those landings from,
225
+ through a new `declaredSections(config)`, so neither can name a section the other
226
+ does not.
227
+
228
+ _Minor rather than patch_: `lintNote` and `lintFrontmatter` take two new
229
+ options (`landing`, `sections`) and `content-config.mjs` exports
230
+ `declaredSections`. Both options default to today's behaviour, so a caller that
231
+ passes neither — and a repository that declares no sections — is unaffected.
232
+
233
+ Closes #197
234
+
235
+ ### Patch Changes
236
+
237
+ - 68910b6: **A `README` landing may name any section that exists, not only a configured one.**
238
+
239
+ #197 widened a landing's `subType` check by one term and it was the wrong one:
240
+ the sections the repository configures in `site.sections` /
241
+ `site.readmeSections`. That keys on configuration a consumer may legitimately not
242
+ have. `sohl-thalorna` has **no `site:` block at all** — it renders its site
243
+ through a local fork of the emitter — so `declaredSections` answered `[]`, the
244
+ accepted set collapsed back to the three `doc` genres, and five of its landings
245
+ were still refused for naming their own content type:
246
+
247
+ ```text
248
+ assets/content/Characters/README.md:7:1: error: `subType` "being" is not one of the subtypes doc declares (rules, user-guide, reference)
249
+ ```
250
+
251
+ A landing's `subType` is an **address**, so it is now checked against the
252
+ addresses that exist — the range of `sectionOf` over every note the format
253
+ permits, plus whatever the repository names:
254
+
255
+ 1. **Every content type the specification declares.** `sectionOf` returns
256
+ `fm.type` for a non-`doc` note, so `being`, `lore`, `scenario` and
257
+ `weapongear` are sections _by construction_, configured or not.
258
+ 2. **The type's own subtypes** — `rules`, `user-guide`, `reference`.
259
+ 3. **The configured sections**, which may name one that is neither: `sohl` has
260
+ `credits` and `dev-docs`.
261
+
262
+ **The guard survives.** A misspelling is in none of the three, so it still fails,
263
+ and the near miss is drawn from the whole union. What is deliberately no longer
264
+ caught is a landing for a section that exists but is currently empty — which is
265
+ legitimate, and is why the set is the types the format _declares_ rather than
266
+ those _present in the tree_: `sohl` ships two such landings above tables that
267
+ stay empty until the first note of each type does.
268
+
269
+ **The type list is the specification's, not the schema map's.** `NOTE_SCHEMAS`
270
+ declares 18 types and `docs/content-format.md` declares 23; `lore`, `place`,
271
+ `scenario`, `vehicle` and `armorlocation` are in the second only. Checking an
272
+ address against the schema map would refuse `Lore/README.md` for a reason that
273
+ is not about addresses, and would report one gap twice in two vocabularies.
274
+ Whether a note's fields can be checked is a different question, answered
275
+ elsewhere and reported on the notes themselves.
276
+
277
+ `lintNote` / `lintFrontmatter` take a `types` option beside `sections`; both
278
+ default to empty, so a caller that passes neither is unaffected.
279
+
280
+ Closes #200
281
+
3
282
  ## 11.0.0
4
283
 
5
284
  ### Major Changes
package/CONTENT.md CHANGED
@@ -796,6 +796,26 @@ values. A `weapon` declares none — SoHL distinguishes a weapon's uses by strik
796
796
  mode rather than by kind — so `subType` on one is a finding; a `skill` declares
797
797
  ten, so `subType: crafte` is a finding naming `craft`.
798
798
 
799
+ **A `type` and a `subType` are both held to `^[A-Za-z0-9]+$`** (#206) — the same
800
+ constant a `shortcode` is held to, read rather than restated. A type is the
801
+ first segment of every address, so a hyphen in one is read back as a segment
802
+ boundary nobody wrote. A `subType` reaches no address since #204 retired
803
+ sections, and keeps the rule anyway: it is a vocabulary term the toolchain keys
804
+ on, and one charset that holds for every term is a rule an author can state. The
805
+ rule is checked ahead of the closed-set check, which is what makes it reach a
806
+ type whose values are declared but not yet enumerated:
807
+
808
+ ```text
809
+ assets/content/Beings/Folk.md:3:1: error: `subType` "common-folk" is not an address segment — a subType is letters and digits only (^[A-Za-z0-9]+$), the same charset a shortcode is held to. …
810
+ ```
811
+
812
+ One declared value broke that rule: a `doc`'s `user-guide`, now **`userguide`**.
813
+ The old spelling is accepted for one transitional release and reported as a
814
+ **warning** naming the replacement — an error would red every tree that took the
815
+ release before it had a chance to sweep, and the note still compiles to the
816
+ correct page. A later release removes the acceptance, and the old spelling then
817
+ falls through to the ordinary undeclared-value error.
818
+
799
819
  The vocabulary lives in `engine/note-vocabulary.mjs`, one entry per note type,
800
820
  taken from the content-format specification. It is note-format knowledge rather
801
821
  than any system's: `data:` holds what is true of the thing, and what a system
@@ -1206,15 +1226,24 @@ package's fixed mounts (`/<package>/` for the landing page, `/<package>/api/` fo
1206
1226
  generated API docs), neither of which contains a hyphen or names a type. So the
1207
1227
  namespace is provably disjoint rather than conventionally so.
1208
1228
 
1209
- **Sections stay, as directories.** Hugo derives a page's section from where the
1210
- file is written, not from its URL, and that section is what gives a landing page,
1211
- `.CurrentSection` and the per-section layout lookup. So a page is still written
1212
- into `<section>/`, and carries a front-matter `url:` publishing it at its
1213
- address. The two are free to differ, and do.
1214
-
1215
- **A landing page is the one exception**, because it is not addressed by
1216
- `(type, shortcode)` at all: it _is_ its section, so it addresses the section —
1217
- under the content mount, where the section directories live (`kb/rules/`).
1229
+ **A page is written flat, named by its address** (#204). It used to be filed
1230
+ into `<section>/`, because Hugo derives a page's section from where the file is
1231
+ written and a section gave it a landing page, `.CurrentSection` and a per-section
1232
+ layout lookup. But a section appears in no address, so the note format was
1233
+ carrying a key, a filename convention, a landing rule and a synthesis pass in
1234
+ order to satisfy a rendering engine's directory semantics. The file is now
1235
+ `<mount>/<type>-<shortcode>.md` and the front-matter `url:` still publishes it at
1236
+ the package root, one level above.
1237
+
1238
+ **There is no landing page.** A `README.md` was its section's landing and
1239
+ addressed the section itself; that is retired with the section. A page that
1240
+ introduces the notes of a type is an ordinary note addressed `doc-<type>`, with
1241
+ no build path of its own — exactly as the package's own front page is
1242
+ `homepage-root` (#182).
1243
+
1244
+ **Sections stay, as configuration.** `site.sections` still writes an `_index.md`
1245
+ per section, and that is now the _only_ thing that makes one exist — see
1246
+ [What a section may declare](#what-a-section-may-declare).
1218
1247
 
1219
1248
  ### The address scheme
1220
1249
 
@@ -1231,33 +1260,35 @@ publish:
1231
1260
  manifests: { publish: true, consume: true }
1232
1261
  address:
1233
1262
  prefix: kb/ # default: "" — the package root
1234
- landing: readme # default: readme
1263
+ landing: readme # inert since #204; still accepted
1235
1264
  ```
1236
1265
 
1237
- - **`prefix`** — the content tree's mount within the package: where its section
1238
- directories and their landing pages live. `sohl` publishes a knowledgebase
1239
- alongside generated API docs, so its sections sit under `kb/` (`kb/affliction/`)
1240
- while its pages address the package root (`affliction-aconite/`); `thalorna`'s
1241
- site is nothing but its content, so it has no prefix. It must end in a slash and
1242
- must not begin with one — where the _package_ is mounted is the consuming
1243
- build's knowledge and is never recorded here.
1244
- - **`landing`** — which note is a section's landing page, and so is addressed by
1245
- the section rather than by `(type, shortcode)`:
1246
- - `readme` a `README.md` addresses its section. A `doc` note then routes by
1247
- its `category` like any other, so a `category: collection` note publishes
1248
- under a literal `collection/` section.
1249
- - `collection` a `doc` note whose `category` is `collection` addresses the
1250
- section it introduces, named by its authored `section`.
1251
-
1252
- The two are alternatives rather than a pair that could both apply: each live
1253
- content tree holds notes the other rule would move.
1254
-
1255
- A note the scheme yields no address for one carrying no `shortcode`, a `doc`
1256
- with no subtype (so no section to be filed under), a collection note naming no
1257
- section — is **reported and omitted**, never guessed. The command
1258
- prints one located diagnostic per note and still writes the file, because a note
1259
- with no address is ordinary while a manifest entry pointing at a page that does
1260
- not exist is not.
1266
+ - **`prefix`** — the content tree's mount within the package: the Hugo directory
1267
+ its pages are written under. `sohl` publishes a knowledgebase alongside
1268
+ generated API docs, so its tree sits under `kb/` while its pages address the
1269
+ package root (`affliction-aconite/`); `thalorna`'s site is nothing but its
1270
+ content, so it has no prefix. It must end in a slash and must not begin with
1271
+ one — where the _package_ is mounted is the consuming build's knowledge and is
1272
+ never recorded here.
1273
+ - **`landing`** — **inert, and accepted only so it keeps loading.** It named
1274
+ which note addressed a whole section rather than a page within one. There are
1275
+ no sections in the note format (#204) and so no landings, so it selects
1276
+ nothing; it is still accepted because both publishing consumers declare
1277
+ `landing: readme` and refusing a correct statement would break them. It is
1278
+ removed once no configuration writes it. The retired second rule, `collection`,
1279
+ is still refused by name (#202), along with the `collection` subtype and the
1280
+ top-level `section:` key.
1281
+
1282
+ A note's `subType` is checked against the values its type declares, and only
1283
+ those. It briefly had a second reading — a `README` landing's `subType` was the
1284
+ _address_ it landed at, so the closed genre list could not answer for it (#197,
1285
+ #198, #200, #201) and #204 removed the cause rather than widening the
1286
+ vocabulary again.
1287
+
1288
+ The only note the scheme yields no address for is one carrying no `shortcode`.
1289
+ It is **reported and omitted**, never guessed: the command prints one located
1290
+ diagnostic per note and still writes the file, because a note with no address is
1291
+ ordinary while a manifest entry pointing at a page that does not exist is not.
1261
1292
 
1262
1293
  ## Publishing a website
1263
1294
 
@@ -1270,7 +1301,7 @@ The sibling of `package compile`: the same content tree, rendered as pages
1270
1301
  instead of compiled into packs. It does the walk, the frontmatter read, the
1271
1302
  address derivation, the address index, table expansion, wikilink resolution,
1272
1303
  code-fence protection, the foreign-manifest merge, the page emission and the
1273
- section-landing backfill.
1304
+ section-landing synthesis.
1274
1305
 
1275
1306
  ### The homepage, and how much else is published
1276
1307
 
@@ -1300,8 +1331,9 @@ still refuses `id`. Everything else about its address is ordinary: it declares a
1300
1331
  [The homepage is addressed like every other note](#the-homepage-is-addressed-like-every-other-note)).
1301
1332
  `/<contentPackage>/` itself is a redirect the package authors, not a page this
1302
1333
  build writes. It is dispatched on `type` like every other note, not on a
1303
- filename: `README.md` is already a section landing under `landing: readme`, and
1304
- in `sohl-thalorna` it is a developer explainer about the source tree.
1334
+ filename nothing in this format is decided by a file's name, which is why
1335
+ `sohl-thalorna` can keep a `README.md` in its content tree as a developer
1336
+ explainer about the source tree.
1305
1337
 
1306
1338
  `type: homepage` is declared by the **engine**, not by the `sohl` item registry,
1307
1339
  so a package that configures no `itemBuilders` at all — `HarnMaster-3-FoundryVTT`
@@ -1338,8 +1370,8 @@ module being withdrawable, and a homepage is one row in a routing table.
1338
1370
  The homepage's file is written at the root of `site.out` — the package's own
1339
1371
  site root, one level above the content mount, which is where
1340
1372
  `publish.address.prefix` puts everything else — under the name its address gives
1341
- it, `homepage-root.md`. As with every other page, the file's location decides
1342
- the Hugo section and the front matter's `url` decides where it publishes.
1373
+ it, `homepage-root.md`. As with every other page, the front matter's `url`
1374
+ decides where it publishes.
1343
1375
 
1344
1376
  **What it does not do is decide addresses.** Those come from `publish.address`,
1345
1377
  the same setting the link manifest reads, so a page and its manifest entry cannot
@@ -1375,20 +1407,33 @@ site:
1375
1407
  | `out` | The Hugo content root. **Required** in both modes, and wiped on every run — see below. |
1376
1408
  | `base` | Where the package is served. Defaults to `/<contentPackage>/`. |
1377
1409
  | `packages` | Which content packages this site renders. Defaults to its own. |
1378
- | `sections` | What each section says about itself on its landing — see below. |
1379
- | `readmeSections` | The same, for a section whose landing comes from a `README`. |
1410
+ | `sections` | The Hugo sections this site declares, and what each says about itself — see below. |
1411
+ | `readmeSections` | The same, for a `trees` entry, whose landing comes from its own `README`. |
1380
1412
  | `landing` | Frontmatter for the mount's own `_index.md`. Passed through — the vocabulary is the theme's. |
1381
- | `backfillSections` | Write a bare `_index.md` for any other section directly under the mount. |
1413
+ | `backfillSections` | Write a bare `_index.md` for any other directory directly under the mount. |
1382
1414
  | `trees` | Extra source trees published beside the content, preserving their source layout below a section. |
1383
1415
  | `pass` | A named bundle of this repository's own body rewrites. |
1384
1416
  | `passOptions` | That bundle's options. |
1385
1417
 
1386
1418
  ### What a section may declare
1387
1419
 
1388
- A generated section landing is the **only** place a section can describe itself.
1389
- A content package authors no `_index.md` for `weapongear` or `affliction`, so
1390
- the file the theme reads is the one this build writes from `sections` — and
1391
- whatever that entry may carry is the whole of what the section can say.
1420
+ **`sections` is what a section _is_ now** (#204). A content page is addressed
1421
+ `(type, shortcode)` and written flat under the mount, so no page creates a
1422
+ directory and nothing else makes `/<package>/<prefix><section>/` answer at all.
1423
+ A site that wants that address says so here, and this build writes the
1424
+ `_index.md` that makes Hugo agree it is a section.
1425
+
1426
+ Two consequences follow, and neither is optional:
1427
+
1428
+ - **Declare every section the site links to.** A card, a menu entry or a
1429
+ breadcrumb pointing at a section nobody declared is a 404.
1430
+ - **A section landing lists no child pages.** Its directory holds only its own
1431
+ `_index.md`, so a layout reading `.Pages` finds nothing. A layout that queries
1432
+ `site.RegularPages` by `Params.type` is unaffected, and that is the shape a
1433
+ content catalog wants anyway — it groups by what a page _is_, not by where its
1434
+ file happened to be written.
1435
+
1436
+ Whatever the entry may carry is the whole of what the section can say.
1392
1437
 
1393
1438
  | Key | Required | What it does |
1394
1439
  | ------------- | -------- | ------------------------------------------------------------------------- |
@@ -1396,9 +1441,11 @@ whatever that entry may carry is the whole of what the section can say.
1396
1441
  | `banner` | no | The hero image, resolved as a CDN asset like any other `banner:`. |
1397
1442
  | `description` | no | The hero standfirst under the heading, and the blurb a landing card uses. |
1398
1443
 
1399
- `readmeSections` takes the same three, for a section whose landing comes from a
1400
- `README` rather than from nothing. What the section declares wins over what the
1401
- `README` happens to carry the landing has to match the card that links to it.
1444
+ `readmeSections` takes the same three, for a **`trees`** entry: those pages keep
1445
+ their source layout below a named section, so the tree's own `README.md` is that
1446
+ section's landing. What the section declares wins over what the `README` happens
1447
+ to carry — the landing has to match the card that links to it. No content note
1448
+ reaches this map any more.
1402
1449
 
1403
1450
  **The vocabulary is closed, and a key outside it is refused by name:**
1404
1451
 
package/MIGRATING.md CHANGED
@@ -1,3 +1,68 @@
1
+ # Migrating to `@heroiclands/package-build` 12.0.0
2
+
3
+ **No note edit, no URL change, and one thing to check in the Hugo layer.** A
4
+ section is a Hugo directory concept that the note format no longer carries
5
+ (#204): content pages are written **flat** under the content mount, named by
6
+ their address, instead of into a `<section>/` directory.
7
+
8
+ ## 1. Nothing in the content tree changes
9
+
10
+ A page's address never contained a section, so **no published URL moves**. A
11
+ `README.md` in the content tree stops being its section's landing and becomes an
12
+ ordinary page addressed `<type>-<shortcode>/` like every other note; if a tree
13
+ still has one that was serving as a landing, it now publishes at its own address.
14
+ No tree in this project had one.
15
+
16
+ A `doc` with no `subType` used to be refused ("no section, so there is nowhere to
17
+ file the page") and now publishes. Its `subType` is a **genre** again — closed to
18
+ `rules`, `userguide`, `reference` — because it no longer doubles as a section
19
+ address on a `README`. That closed check runs after the two #206 added ahead of
20
+ it: a retired spelling is a warning naming its replacement, a hyphenated value
21
+ is an error, and only then is the type's own list the reason.
22
+
23
+ ## 2. Declare every section your site links to
24
+
25
+ `site.sections` is now the whole of what a section is. No page is filed into a
26
+ section directory any more, so nothing else makes `/<package>/<prefix><section>/`
27
+ exist:
28
+
29
+ ```yaml
30
+ site:
31
+ sections:
32
+ being: { title: Beings, banner: banners/creature.webp }
33
+ weapongear: { title: Weapons, banner: banners/weapons.webp }
34
+ ```
35
+
36
+ A card, menu entry or breadcrumb pointing at a section nobody declares is a 404.
37
+
38
+ ## 3. Check how a section landing lists its members
39
+
40
+ A declared section's directory holds only its own `_index.md`, so a layout
41
+ reading `.Pages` renders an empty listing. Query the site instead, on the page's
42
+ own `type`:
43
+
44
+ ```go-html-template
45
+ {{- $pages := where site.RegularPages "Type" "weapongear" -}}
46
+ ```
47
+
48
+ That is the shape a content catalog wants regardless — it groups by what a page
49
+ _is_ rather than by where its file happened to be written — and it is what
50
+ `sohl`'s catalog layouts already do.
51
+
52
+ ## 4. `publish.address.landing` is inert, and still accepted
53
+
54
+ It named which note addressed a whole section. There are no landings, so it
55
+ selects nothing; `landing: readme` keeps loading because it stated something true
56
+ when it was written, and the key is removed once no configuration declares it.
57
+ `landing: collection` is still refused by name (#202).
58
+
59
+ ## What did not change
60
+
61
+ - Every content page's `url:`, and so every published address.
62
+ - Every link-manifest entry, including each entry's `path`.
63
+ - Every compiled compendium document.
64
+ - `site.sections` / `site.readmeSections` and what an entry may declare.
65
+
1
66
  # Migrating to `@heroiclands/package-build` 11.0.0
2
67
 
3
68
  **Two edits, and the second is one line per repository.** Every published page