@heroiclands/package-build 22.15.0 → 22.16.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.
Files changed (223) hide show
  1. package/CHANGELOG.md +103 -0
  2. package/README.md +39 -678
  3. package/bin/{content-build.mjs → content-commands.mjs} +197 -118
  4. package/bin/package-build.mjs +303 -42
  5. package/bin/report.mjs +1 -1
  6. package/ci/ci-docker.mjs +6 -3
  7. package/ci/ci-steps.mjs +6 -4
  8. package/ci/content-format-reference.mjs +106 -0
  9. package/config.mjs +22 -2
  10. package/content-config.mjs +74 -134
  11. package/docs/api.md +294 -234
  12. package/docs/authoring/assets.md +31 -0
  13. package/docs/authoring/dates-and-calendars.md +33 -0
  14. package/docs/authoring/first-note.md +48 -0
  15. package/docs/authoring/frontmatter.md +40 -0
  16. package/docs/authoring/links-and-markup.md +137 -0
  17. package/docs/commands.md +631 -162
  18. package/docs/configuration.md +176 -140
  19. package/docs/content-format.md +50 -4229
  20. package/docs/design.md +32 -0
  21. package/docs/diagnostics.md +29 -22
  22. package/docs/getting-started.md +21 -877
  23. package/docs/guides/book.md +153 -0
  24. package/docs/guides/e2e.md +143 -0
  25. package/docs/guides/icons.md +126 -0
  26. package/docs/guides/localization.md +120 -0
  27. package/docs/guides/module-project.md +915 -0
  28. package/docs/guides/packs.md +167 -0
  29. package/docs/guides/reference-pages.md +148 -0
  30. package/docs/guides/release-and-deploy.md +114 -0
  31. package/docs/guides/site.md +152 -0
  32. package/docs/guides/system-project.md +45 -0
  33. package/docs/index.md +16 -0
  34. package/docs/project-setup.md +54 -35
  35. package/docs/reference/format-details.md +4420 -0
  36. package/docs/reference/note-types.md +531 -0
  37. package/engine/address-render.mjs +26 -0
  38. package/engine/address.mjs +3 -20
  39. package/engine/anchors.mjs +15 -6
  40. package/engine/asset-bindings.mjs +6 -2
  41. package/engine/base-compiler.mjs +47 -11
  42. package/engine/being-measurements.mjs +70 -0
  43. package/engine/calendar-artifacts.mjs +1 -1
  44. package/engine/calendar-choice.mjs +32 -0
  45. package/engine/calendar-human.mjs +71 -0
  46. package/engine/calendar-notes.mjs +41 -3
  47. package/engine/calendars.mjs +8 -1
  48. package/engine/changelog-lint.mjs +2 -2
  49. package/engine/code-fences.mjs +23 -97
  50. package/engine/compile-corpus.mjs +6 -7
  51. package/engine/content-admonitions.mjs +114 -0
  52. package/engine/content-captions.mjs +181 -0
  53. package/engine/content-embeds.mjs +2 -2
  54. package/engine/content-footnotes.mjs +34 -0
  55. package/engine/content-format-check.mjs +2 -2
  56. package/engine/content-format.mjs +127 -73
  57. package/engine/content-format.yaml +745 -0
  58. package/engine/content-html.mjs +1 -1
  59. package/engine/content-icons.mjs +45 -92
  60. package/engine/content-images.mjs +22 -74
  61. package/engine/content-index.mjs +12 -3
  62. package/engine/content-links.mjs +21 -26
  63. package/engine/content-lint.mjs +1 -5
  64. package/engine/content-secrets.mjs +3 -1
  65. package/engine/content-tables.mjs +47 -1257
  66. package/engine/culture-choice.mjs +32 -0
  67. package/engine/date-conversion.mjs +47 -0
  68. package/engine/declaration-check.mjs +87 -0
  69. package/engine/docs-checks.mjs +140 -0
  70. package/engine/documentation-links.mjs +31 -0
  71. package/engine/exported-scene.mjs +1 -1
  72. package/engine/extension-attributes.mjs +74 -0
  73. package/engine/field-reference.mjs +1 -1
  74. package/engine/field-spec.mjs +4 -0
  75. package/engine/foreign-catalog.mjs +5 -5
  76. package/engine/format-generated.mjs +15 -0
  77. package/engine/foundry-entries.mjs +3 -1
  78. package/engine/frontmatter-lint.mjs +5 -1
  79. package/engine/frontmatter.mjs +10 -0
  80. package/engine/generate.mjs +2 -2
  81. package/engine/helpers.mjs +71 -117
  82. package/engine/homepage.mjs +2 -3
  83. package/engine/index-records.mjs +1 -0
  84. package/engine/index.mjs +18 -3
  85. package/engine/infobox-registry.mjs +3 -1
  86. package/engine/infobox.mjs +38 -20
  87. package/engine/item-compiler.mjs +10 -8
  88. package/engine/item-docs.mjs +7 -6
  89. package/engine/itinerary-scenes.mjs +4 -2
  90. package/engine/journals.mjs +51 -10
  91. package/engine/macros.mjs +1 -1
  92. package/engine/map-build.mjs +1 -1
  93. package/engine/map-dot.mjs +1 -1
  94. package/engine/map-raster.mjs +4 -2
  95. package/engine/markdown-expressions.mjs +117 -0
  96. package/engine/metadata-index.mjs +7 -3
  97. package/engine/note-addresses.mjs +1 -0
  98. package/engine/note-dates.mjs +283 -61
  99. package/engine/note-package.mjs +5 -25
  100. package/engine/note-state.mjs +6 -2
  101. package/engine/note-vocabulary.mjs +74 -19
  102. package/engine/number-words.mjs +75 -0
  103. package/engine/office-holders.mjs +188 -0
  104. package/engine/pack-config.mjs +2 -15
  105. package/engine/pack-router.mjs +24 -5
  106. package/engine/packages.mjs +3 -2
  107. package/engine/pathnames.mjs +21 -24
  108. package/engine/pdf-build.mjs +217 -55
  109. package/engine/pdf-fonts.mjs +2 -2
  110. package/engine/pdf-images.mjs +82 -0
  111. package/engine/pdf-render.mjs +174 -22
  112. package/engine/populations.mjs +3 -2
  113. package/engine/project-init.mjs +499 -0
  114. package/engine/prose-config.mjs +1 -1
  115. package/engine/prose-lint.mjs +7 -6
  116. package/engine/readability-lint.mjs +86 -0
  117. package/engine/reckoning-markers.mjs +205 -93
  118. package/engine/scenes.mjs +5 -2
  119. package/engine/schema-check.mjs +3 -3
  120. package/engine/site-build.mjs +87 -37
  121. package/engine/site-config.mjs +8 -8
  122. package/engine/site-index.mjs +4 -0
  123. package/engine/site-maps.mjs +1 -1
  124. package/engine/site-preview.mjs +19 -8
  125. package/engine/social-ties.mjs +5 -1
  126. package/engine/sql-tables.mjs +92 -22
  127. package/engine/web-wikilinks.mjs +15 -31
  128. package/engine/wikilink-syntax.mjs +4 -1
  129. package/engine/wikilinks.mjs +12 -14
  130. package/hm3/item-fields.mjs +1 -1
  131. package/hm3/items.mjs +12 -45
  132. package/index.mjs +2 -14
  133. package/manifest.mjs +31 -4
  134. package/markdownlint-config.mjs +1 -1
  135. package/package.json +32 -11
  136. package/prettier-config.mjs +1 -1
  137. package/sohl/being-info.mjs +3 -145
  138. package/sohl/index.mjs +1 -1
  139. package/sohl/infobox.mjs +2 -1
  140. package/sohl/item-builders.mjs +3 -27
  141. package/sohl/item-fields.mjs +61 -22
  142. package/sohl/kb-passes.mjs +2 -2
  143. package/sohl/note-schemas.mjs +0 -8
  144. package/stage.mjs +19 -0
  145. package/types/config.d.mts +12 -3
  146. package/types/content-config.d.mts +15 -81
  147. package/types/engine/address-render.d.mts +13 -0
  148. package/types/engine/address.d.mts +2 -11
  149. package/types/engine/anchors.d.mts +3 -4
  150. package/types/engine/asset-bindings.d.mts +9 -2
  151. package/types/engine/base-compiler.d.mts +1 -2
  152. package/types/engine/being-measurements.d.mts +16 -0
  153. package/types/engine/calendar-choice.d.mts +14 -0
  154. package/types/engine/calendar-human.d.mts +4 -0
  155. package/types/engine/calendar-notes.d.mts +2 -0
  156. package/types/engine/calendars.d.mts +5 -3
  157. package/types/engine/code-fences.d.mts +6 -72
  158. package/types/engine/compile-corpus.d.mts +1 -3
  159. package/types/engine/content-admonitions.d.mts +24 -0
  160. package/types/engine/content-captions.d.mts +45 -0
  161. package/types/engine/content-footnotes.d.mts +10 -0
  162. package/types/engine/content-format-check.d.mts +17 -4
  163. package/types/engine/content-format.d.mts +24 -11
  164. package/types/engine/content-icons.d.mts +14 -23
  165. package/types/engine/content-images.d.mts +2 -2
  166. package/types/engine/content-index.d.mts +9 -2
  167. package/types/engine/content-links.d.mts +3 -5
  168. package/types/engine/content-tables.d.mts +11 -140
  169. package/types/engine/culture-choice.d.mts +14 -0
  170. package/types/engine/date-conversion.d.mts +4 -0
  171. package/types/engine/declaration-check.d.mts +24 -0
  172. package/types/engine/docs-checks.d.mts +28 -0
  173. package/types/engine/documentation-links.d.mts +7 -0
  174. package/types/engine/exported-scene.d.mts +6 -2
  175. package/types/engine/extension-attributes.d.mts +14 -0
  176. package/types/engine/field-spec.d.mts +9 -0
  177. package/types/engine/format-generated.d.mts +7 -0
  178. package/types/engine/frontmatter.d.mts +14 -4
  179. package/types/engine/generate.d.mts +1 -1
  180. package/types/engine/helpers.d.mts +37 -92
  181. package/types/engine/homepage.d.mts +2 -3
  182. package/types/engine/index.d.mts +5 -0
  183. package/types/engine/infobox-registry.d.mts +3 -1
  184. package/types/engine/infobox.d.mts +17 -6
  185. package/types/engine/item-compiler.d.mts +4 -4
  186. package/types/engine/item-docs.d.mts +4 -3
  187. package/types/engine/journals.d.mts +10 -7
  188. package/types/engine/map-graphviz.d.mts +1 -1
  189. package/types/engine/markdown-expressions.d.mts +17 -0
  190. package/types/engine/note-dates.d.mts +47 -1
  191. package/types/engine/note-package.d.mts +3 -19
  192. package/types/engine/note-state.d.mts +2 -1
  193. package/types/engine/note-vocabulary.d.mts +9 -0
  194. package/types/engine/number-words.d.mts +4 -0
  195. package/types/engine/office-holders.d.mts +14 -0
  196. package/types/engine/pack-config.d.mts +1 -1
  197. package/types/engine/packages.d.mts +3 -2
  198. package/types/engine/pdf-images.d.mts +38 -0
  199. package/types/engine/populations.d.mts +2 -2
  200. package/types/engine/project-init.d.mts +17 -0
  201. package/types/engine/prose-config.d.mts +1 -1
  202. package/types/engine/prose-lint.d.mts +3 -2
  203. package/types/engine/readability-lint.d.mts +22 -0
  204. package/types/engine/reckoning-markers.d.mts +4 -2
  205. package/types/engine/schema-check.d.mts +3 -3
  206. package/types/engine/site-build.d.mts +38 -28
  207. package/types/engine/site-config.d.mts +2 -2
  208. package/types/engine/site-index.d.mts +4 -0
  209. package/types/engine/site-preview.d.mts +1 -1
  210. package/types/engine/social-ties.d.mts +8 -2
  211. package/types/engine/sql-tables.d.mts +13 -2
  212. package/types/engine/web-wikilinks.d.mts +2 -1
  213. package/types/engine/wikilinks.d.mts +7 -1
  214. package/types/hm3/items.d.mts +5 -6
  215. package/types/labels.d.mts +40 -0
  216. package/types/manifest.d.mts +3 -3
  217. package/types/sohl/actors.d.mts +2 -2
  218. package/types/sohl/being-info.d.mts +3 -43
  219. package/types/sohl/index.d.mts +1 -1
  220. package/types/sohl/infobox.d.mts +5 -2
  221. package/types/sohl/item-fields.d.mts +4 -13
  222. package/types/stage.d.mts +9 -0
  223. package/CONTENT.md +0 -3239
package/docs/api.md CHANGED
@@ -1,3 +1,10 @@
1
+ ---
2
+ shortcode: api
3
+ name: { full: "API reference" }
4
+ type: doc
5
+ subType: reference
6
+ ---
7
+
1
8
  # API reference
2
9
 
3
10
  `@heroiclands/package-build` exposes its programmatic surface through
@@ -111,6 +118,16 @@ Deterministic document ids, derived by hashing rather than stored, so compile pa
111
118
  | `compendiumUuid` | `compendiumUuid(packageId, type, id, packName)` | `string` — `Compendium.<packageId>.<pack>.<DocumentType>.<id>` | composing a document's full compendium UUID in the one place it is spelled |
112
119
  | `pageUuid` | `pageUuid(entryUuid, pageId)` | `string` — the page's UUID | composing the UUID of a JournalEntry page |
113
120
 
121
+ ### `engine.formatGenerated`
122
+
123
+ `formatGenerated(text, filepath)` returns a promise for text formatted with
124
+ the Prettier configuration that applies to the destination path. Generators
125
+ can write the result directly without introducing formatting drift.
126
+
127
+ | Export | Signature | Returns | Use it when |
128
+ | ----------------- | --------------------------------- | ----------------- | ----------------------------- |
129
+ | `formatGenerated` | `formatGenerated(text, filepath)` | `Promise<string>` | writing a generated text file |
130
+
114
131
  ### `engine.systemBlock`
115
132
 
116
133
  The per-system frontmatter block: how one note feeds more than one game system through properties named after that system (`<system>.system`, `<system>.type`, `<system>.img`, `<system>.items` on actors). Resolves a field's value through the block, the shared top level, and a retiring position in that order, and merges an authored `<system>.system` onto a compiler-built one without disturbing what the builder already wrote.
@@ -146,7 +163,7 @@ Where code lives in a markdown body, so a rewriter can leave it alone. Every bui
146
163
  | Export | Signature | Returns | Use it when |
147
164
  | --------------------- | ---------------------------------------------------------- | ------------------------------- | -------------------------------------------------------------------------------------------- |
148
165
  | `FENCE_LINE` | `const FENCE_LINE` | — | matching a fence line, capturing its indent, marker, and info string |
149
- | `parseHeaderArgs` | `parseHeaderArgs(info)` | `{language, args}` | reading a fence's info string as org-babel header arguments |
166
+ | `parseHeaderArgs` | `parseHeaderArgs(info)` | `{language, args, problems}` | reading a fence's language and braced attributes |
150
167
  | `codeRegions` | `codeRegions(markdown, ...)` | `Array<{start, end}>` | getting every code region in a markdown body as character offsets |
151
168
  | `replaceOutsideCode` | `replaceOutsideCode(markdown, pattern, replacer, options)` | `string` — the rewritten body | running `String.prototype.replace` while skipping anything inside code |
152
169
  | `matchAllOutsideCode` | `matchAllOutsideCode(markdown, pattern, options)` | `Array<RegExpMatchArray>` | running `String.prototype.matchAll` while skipping anything inside code |
@@ -335,6 +352,7 @@ The closed half of a note's frontmatter: the `data:` container and each type's `
335
352
  | Export | Signature | Returns | Use it when |
336
353
  | ------------------------- | -------------------------------------------- | ---------------------------------------- | -------------------------------------------------------------------------------------------- |
337
354
  | `DRAFT_TAG` | `const DRAFT_TAG` | — | naming the declared tag that marks a note as unfinished |
355
+ | `GM_TAG` | `const GM_TAG` | — | naming the declared tag that marks a GM-only note |
338
356
  | `NOTE_VOCABULARY` | `const NOTE_VOCABULARY` | — | looking up, per content type, its `data:` field list and its closed `subType` values |
339
357
  | `SHARED_DATA_FIELDS` | `const SHARED_DATA_FIELDS` | `readonly DataFieldSpec[]` | reading the `data:` keys every type accepts, whatever it is — the art slots legal everywhere |
340
358
  | `DECLARED_TAGS` | `const DECLARED_TAGS` | — | looking up the tags a note type may declare, grouped |
@@ -343,6 +361,7 @@ The closed half of a note's frontmatter: the `data:` container and each type's `
343
361
  | `exclusiveTagGroups` | `exclusiveTagGroups(type, groups)` | `Array<{slot, tags}>` | reading the single-valued tag slots a note type has, such as a being's kind |
344
362
  | `hasTag` | `hasTag(fm, tag)` | `boolean` | checking whether a note carries a given tag, whatever scalar-or-list form it was authored in |
345
363
  | `isDraftNote` | `isDraftNote(fm)` | `boolean` | checking whether a note is tagged as an unfinished draft |
364
+ | `isGmNote` | `isGmNote(fm)` | `boolean` | checking whether a note is restricted to GM-facing output |
346
365
  | `subTypeCharsetMessage` | `subTypeCharsetMessage(value)` | `string` | building the message for a `subType` outside the address charset |
347
366
  | `typeCharsetMessage` | `typeCharsetMessage(type)` | `string` | building the message for a `type` outside the address charset |
348
367
  | `assertVocabularyCharset` | `assertVocabularyCharset(vocabulary, where)` | throws | refusing a vocabulary declaration whose type or subType breaks the address charset |
@@ -423,7 +442,7 @@ The three states of a note — **stub**, **draft**, **full** — and the one obs
423
442
  | `journalHasContent` | `journalHasContent(body)` | `boolean` | asking whether the JournalEntry a body compiles into would hold anything, which is what decides whether one is made at all |
424
443
  | `PLACEHOLDER_PHRASES` | `const PLACEHOLDER_PHRASES` | `ReadonlyArray<string>` | reading the phrases that stand in for prose nobody has written |
425
444
  | `placeholderBody` | `placeholderBody(body)` | `Array<{phrase: string, line: number}>` | finding a body that reduces to nothing but placeholders, which is an abandoned draft rather than a stub |
426
- | `bodyWordCount` | `bodyWordCount(body)` | `number` | counting the prose a reader meets, with a wikilink counting as the one word it renders |
445
+ | `bodyWordCount` | `bodyWordCount(body)` | `number` | counting rendered prose, with wikilinks as one word and closed dashes, slashes, and ellipses separating adjacent words |
427
446
 
428
447
  ### `engine.draftNotice`
429
448
 
@@ -471,7 +490,7 @@ What a place is next to (`data.borders`) and reachable from (`data.routes`): the
471
490
 
472
491
  ### `engine.holdings`
473
492
 
474
- 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.
493
+ 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 `package-build lint` runs on a place.
475
494
 
476
495
  | Export | Signature | Returns | Use it when |
477
496
  | ---------------------- | ------------------------------------ | ----------------------- | ---------------------------------------------------------------------------------------------------------------------------- |
@@ -485,7 +504,7 @@ What lies within a place, who holds it, and what an affiliation holds — read o
485
504
 
486
505
  ### `engine.populations`
487
506
 
488
- Whether a world's population figures agree from the region down — the polities and regions inside a place against what it states, a settlement against every place containing it, and a `doc` note's cited figures against the notes that own them. `content-build lint` runs all four as warnings.
507
+ Whether a world's population figures agree from the region down — the polities and regions inside a place against what it states, a settlement against every place containing it, and a `doc` note's cited figures against the notes that own them. `package-build lint` runs all four as warnings.
489
508
 
490
509
  | Export | Signature | Returns | Use it when |
491
510
  | ----------------------- | ---------------------------------------- | ---------------------- | ------------------------------------------------------------------------------------------------------------ |
@@ -545,7 +564,7 @@ The geometry of the map from a place and of the chart from one, as arithmetic ov
545
564
 
546
565
  ### `engine.mapDot`
547
566
 
548
- 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.
567
+ The drawings of `package-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.
549
568
 
550
569
  | Export | Signature | Returns | Use it when |
551
570
  | ------------ | -------------------------------------------------- | -------- | ---------------------------------------------------------------------------------------------------------------- |
@@ -567,7 +586,7 @@ The npm-installed Graphviz runtime draws SVG maps and supplies the plain positio
567
586
 
568
587
  ### `engine.mapBuild`
569
588
 
570
- `content-build map` as a function: drawings written under an output directory, with each `.dot` beside its `.svg` and tree anomalies returned as findings.
589
+ `package-build map` as a function: drawings written under an output directory, with each `.dot` beside its `.svg` and tree anomalies returned as findings.
571
590
 
572
591
  | Export | Signature | Returns | Use it when |
573
592
  | --------------- | ---------------------------------------------------------------------------------------------------------------------------- | --------------------- | ----------------------------------------------------------------------------- |
@@ -578,7 +597,7 @@ The npm-installed Graphviz runtime draws SVG maps and supplies the plain positio
578
597
 
579
598
  ### `engine.siteMaps`
580
599
 
581
- 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`. `site.maps: false` draws none.
600
+ The map from each place, drawn for the site: `package-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`. `site.maps: false` draws none.
582
601
 
583
602
  | Export | Signature | Returns | Use it when |
584
603
  | -------------- | ------------------------------------------------------- | ------------------ | --------------------------------------------------------------------------------------------------------------- |
@@ -612,6 +631,7 @@ The neutral day spelling is `<year>.<day>[:HHMMSS]`. The pure conversion functio
612
631
  | ------------------------- | ---------------------------------------------------------- | -------------------------------------- | ------------------------------------------------------------------------------- |
613
632
  | `CANONICAL_EPOCH` | `const CANONICAL_EPOCH` | — | naming where the canonical axis's own year 1 sits |
614
633
  | `canonicalYear` | `canonicalYear(year, epoch?)` | `number` — the signed year on the axis | converting a year written in one reckoning to the number everything compares on |
634
+ | `eraYear` | `eraYear(canonical, epoch?)` | signed era year without zero | printing a canonical year in a chosen reckoning |
615
635
  | `dateSortKey` | `dateSortKey(year, month, day)` | `number` | ordering mixed-precision dates against a single number |
616
636
  | `calendarStructure` | `calendarStructure(calendar)` | `{months}` — `null` where unstated | reading the months a calendar keeps, before phrasing a finding |
617
637
  | `daysInMonth` | `daysInMonth(calendar, month)` | `number` — `null` where unstated | bounding a written day against the month it names |
@@ -627,23 +647,65 @@ The neutral day spelling is `<year>.<day>[:HHMMSS]`. The pure conversion functio
627
647
 
628
648
  ### `engine.noteDates`
629
649
 
630
- A date on a note is one authored string. `<year>.<day>[:HHMMSS]` names a calendar-neutral day; `VR(720/5/14)` names an era marker and a date in that era's calendar. A marker is resolved from calendar notes in the corpus and contributes a canonical year, day, and sort key. Print `text`, order on `sort`, and do arithmetic on `canonicalYear`. The literal `unknown` has no sort key or canonical year. Unmarked slash dates and the addressed era qualifier are also accepted by the parser; a caller supplies the corpus marker map and world year length when it needs a marker resolved.
650
+ A date on a note is one authored string. `<year>.<day>[:HHMMSS]` names a calendar-neutral day; `datefrom vrcal 23 Taranis 326 VR` names a calendar, a named month, and an era. Either form may begin with `~` for an approximate date. A resolved calendar date carries a canonical year, day, span and sort key. Print `prose ?? text`, order on `sort`, and do arithmetic on `canonicalYear`. The literal `unknown` has no sort key or canonical year. `resolvedDateFields` supplies identical derived records to JSONL and generated page frontmatter.
651
+
652
+ | Export | Signature | Returns | Use it when |
653
+ | ----------------------- | ------------------------------------------------ | -------------------------------------------------- | -------------------------------------------------------------- |
654
+ | `UNKNOWN_DATE` | `const UNKNOWN_DATE` | — | naming the literal that says a date is not recorded |
655
+ | `NOTE_DATE_PATTERN` | `const NOTE_DATE_PATTERN` | — | matching the date grammar directly |
656
+ | `DATEFROM_PATTERN` | `const DATEFROM_PATTERN` | — | recognizing a named calendar conversion in frontmatter |
657
+ | `ERA_QUALIFIER_PATTERN` | `const ERA_QUALIFIER_PATTERN` | — | checking that a string is an era qualifier a date could carry |
658
+ | `parseNoteDate` | `parseNoteDate(value, options)` | `{date, findings}` — `date` is `null` when refused | reading a note's authored date, with every finding it earned |
659
+ | `eraCovering` | `eraCovering(date, eras, daysPerYear)` | one era or `null`; throws on overlap | selecting the era covering a resolved date's full precision |
660
+ | `formatNoteDate` | `formatNoteDate(date, era, daysPerYear)` | `{era, year, month, day, text, prose}` or `null` | printing a resolved date in a chosen era or its authored form |
661
+ | `formatDateInCalendar` | `formatDateInCalendar(date, reference, context)` | printable record or `null` | selecting a calendar's era and printing a resolved date in it |
662
+ | `calendarEras` | `calendarEras(reference, context)` | ordered era records | resolving an addressed calendar or one of its eras |
663
+ | `resolvedDateFields` | `resolvedDateFields(fm, context)` | map of normalized dates | deriving being and lore event date records for index and pages |
664
+
665
+ ### `engine.dateConversion`
666
+
667
+ The exact-day conversion functions use the corpus reckoning context. A calendar with several eras requires an era in the named date. They reject dates outside the selected calendar and gaps between its eras. Approximate input keeps its `~` prefix in the result.
668
+
669
+ | Export | Signature | Returns | Use it when |
670
+ | ------------------ | --------------------------------------------------- | --------------------- | --------------------------------------------- |
671
+ | `dateFromCalendar` | `dateFromCalendar(reference, value, context)` | canonical date string | converting one calendar day to the world axis |
672
+ | `dateToCalendar` | `dateToCalendar(reference, canonicalDate, context)` | calendar date string | expressing one canonical day in a calendar |
673
+
674
+ ### `engine.calendarChoice`
675
+
676
+ `checkCalendarChoice(note, { index })` checks that an explicitly selected calendar Address on a being or place resolves to a calendar note.
677
+
678
+ | Export | Signature | Returns | Use it when |
679
+ | --------------------- | ------------------------------------ | -------- | ----------------------------------------------- |
680
+ | `checkCalendarChoice` | `checkCalendarChoice(note, options)` | findings | validating a being's or place's chosen calendar |
681
+
682
+ ### `engine.cultureChoice`
683
+
684
+ `checkCultureChoice(note, { index })` checks that a being's `data.culture`
685
+ Address resolves to a lore note with `subType: culture`. The being vocabulary
686
+ invokes the check during frontmatter lint.
687
+
688
+ | Export | Signature | Returns | Use it when |
689
+ | -------------------- | ----------------------------------- | -------- | ----------------------------------------- |
690
+ | `checkCultureChoice` | `checkCultureChoice(note, options)` | findings | validating a being's primary culture note |
691
+
692
+ ### `engine.markdownExpressions`
693
+
694
+ `renderMarkdownExpressions(body, { fm, dates, sqlResults, file, bodyLine })` expands scalar frontmatter references and registered Handlebars helpers outside Markdown code. The `dateformat` helper uses `formatDateInCalendar`; `gt`, `gte`, `lt`, `lte`, `eq`, `and`, `or`, and `not` evaluate explicit comparisons. The `sql` helper reads a prepared scalar result, which must contain one row and one column. `sqlQueriesInMarkdown` discovers SQL calls, including nested calls; `prepareInlineSqlExpressions` evaluates them through the same DuckDB connection as SQL tables. The renderer returns `{ markdown, findings }`, with source positions for invalid expressions. Site, Foundry and PDF passes invoke it after generated tables expand and before links resolve. Hugo shortcodes remain literal for the site renderer.
631
695
 
632
- | Export | Signature | Returns | Use it when |
633
- | ----------------------- | ------------------------------- | -------------------------------------------------- | ------------------------------------------------------------- |
634
- | `UNKNOWN_DATE` | `const UNKNOWN_DATE` | — | naming the literal that says a date is not recorded |
635
- | `NOTE_DATE_PATTERN` | `const NOTE_DATE_PATTERN` | — | matching the date grammar directly |
636
- | `ERA_QUALIFIER_PATTERN` | `const ERA_QUALIFIER_PATTERN` | — | checking that a string is an era qualifier a date could carry |
637
- | `parseNoteDate` | `parseNoteDate(value, options)` | `{date, findings}` — `date` is `null` when refused | reading a note's authored date, with every finding it earned |
696
+ | Export | Signature | Returns | Use it when |
697
+ | --------------------------- | ------------------------------------------ | ---------------------- | ----------------------------------------------- |
698
+ | `renderMarkdownExpressions` | `renderMarkdownExpressions(body, options)` | `{markdown, findings}` | expanding authored expressions in prose |
699
+ | `sqlQueriesInMarkdown` | `sqlQueriesInMarkdown(body)` | query strings | preparing inline SQL alongside generated tables |
638
700
 
639
701
  ### `engine.reckoningMarkers`
640
702
 
641
- The corpus registry maps each declared marker to exactly one era and its calendar. An era start may use the canonical axis or a marker already resolvable from the corpus; duplicate markers and cycles are findings.
703
+ The corpus registry maps every declared era to its calendar and each marker to exactly one era. Era starts may use the canonical axis or another declared era. Within one calendar, starts increase; the next start bounds the preceding era unless an explicit end leaves a gap. The first era also covers dates before its start and counts them with negative years. Later eras accept only forward year counts. Unknown eras, duplicate markers, invalid bounds, overlapping eras and dependency cycles are findings. Short qualifiers are ambiguous when they name eras in several packages; a full address selects one. This registry governs note dates; calendar definitions emitted for Foundry retain their consumer format.
642
704
 
643
- | Export | Signature | Returns | Use it when |
644
- | ------------------------- | --------------------------------------------- | ---------------------------------- | ----------------------------------------------------------- |
645
- | `resolveReckoningMarkers` | `resolveReckoningMarkers(index, daysPerYear)` | `{markers, findings}` | resolving authored era markers and their starts |
646
- | `reckoningContext` | `reckoningContext(index)` | `{markers, daysPerYear, findings}` | sharing one resolved date context across checks on a corpus |
705
+ | Export | Signature | Returns | Use it when |
706
+ | ------------------------- | --------------------------------------------- | ---------------------------------------- | ----------------------------------------------------------- |
707
+ | `resolveReckoningMarkers` | `resolveReckoningMarkers(index, daysPerYear)` | `{eras, markers, findings}` | resolving authored eras, their markers, and their bounds |
708
+ | `reckoningContext` | `reckoningContext(index)` | `{eras, markers, daysPerYear, findings}` | sharing one resolved date context across checks on a corpus |
647
709
 
648
710
  ### `engine.calendarNotes`
649
711
 
@@ -672,7 +734,7 @@ A calendar is a `lore` note with `subType: calendar`, declaring the months it ke
672
734
  calendar files under `build/calendars/`. Each calendar produces a definition
673
735
  and an import envelope containing that same definition. The command line entry
674
736
  point is `package-build calendars --calendaria-version <version>`; run
675
- `content-build content-index` first. The generated import envelope carries a
737
+ `package-build content-index` first. The generated import envelope carries a
676
738
  fixed ISO `exportedAt` sentinel so repeated builds of unchanged source emit
677
739
  identical bytes.
678
740
 
@@ -761,52 +823,52 @@ The shortcodes a note declares it used to be published under. A package's `(type
761
823
 
762
824
  The published content index — the artifact packages exchange addresses through. **A package publishes its own index; a consumer fetches the ones it depends on.** That is the whole mechanism, and it replaces a vendored link manifest that each repository committed a copy of every other repository's file into. Vendoring failed three ways, and only the last is about staleness:
763
825
 
764
- | Export | Signature | Returns | Use it when |
765
- | ------------------------------ | ---------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------- |
766
- | `METADATA_RELATIONSHIP_KINDS` | `const METADATA_RELATIONSHIP_KINDS` | — | The relationship kinds that are dependencies, and therefore citable. |
767
- | `metadataFileName` | `function metadataFileName(pkg)` | {string} The file name, e.g. | What a package's content index is called, wherever it is written or fetched. |
768
- | `metadataRelationships` | `function metadataRelationships(config)` | {Array<{id: string, manifest: string, kind: string, verified: string\|undefined}>} The dependencies, in declaration order. | Every dependency whose published index this build resolves addresses through. |
769
- | `noContentIndexPackages` | `function noContentIndexPackages(config)` | {ReadonlySet<string>} The content package names. | Every package a relationship declares `contentIndex: false` on, keyed by the content package name a link into it would use. |
770
- | `metadataCacheDir` | `function metadataCacheDir(config, id, version)` | {string} The directory. | The cache directory for one dependency's index at one version. |
771
- | `isComplete` | `const isComplete` | {boolean} True when it was fetched to completion. | Whether a dependency's cache is present and complete. |
772
- | `markComplete` | `function markComplete(dir)` | {void} | Mark a dependency's cache complete. |
773
- | `cachedMetadataFiles` | `function cachedMetadataFiles(config)` | {string[]} One index file per declared dependency. | The fetched index files this build resolves foreign addresses against. |
774
- | `cachedMetadataIndexes` | `function cachedMetadataIndexes(config)` | {Array<{id: string, file: string}>} One entry per declared dependency. | The same fetched indexes, each paired with the package that published it. |
775
- | `newestVersionDir` | `function newestVersionDir(dirs)` | {string} The newest one. | The newest cached version among several version-keyed cache directories. |
776
- | `loadForeignIndexes` | `function loadForeignIndexes(config, localPackages, bases` | {{index: Map<string, object>, packages: Set<string>, stale: Array<{package: string, reason: string}>}} The resolved addresses, which packages contributed, and what could not be read. | Resolve every foreign address this build can cite, from the fetched indexes. |
777
- | `cachedIndexPath` | `function cachedIndexPath(config, pkg)` | {string} A path to name in a diagnostic. | Where a dependency's fetched index sits, for naming it in a diagnostic. |
778
- | `unaddressableForeignPackages` | `function unaddressableForeignPackages(foreignIndex)` | {Array<{package: string, entries: number, sampleKey: string}>} One finding per drifted package, in the order the index first names each. | Whether a fetched index can still be _addressed_, as distinct from read. |
779
- | `formatUnaddressableFinding` | `function formatUnaddressableFinding(finding, config)` | {string} The formatted diagnostic, path first on the line. | One finding, in the standard `file:line:column: severity: message` form. |
826
+ | Export | Signature | Returns | Use it when |
827
+ | ------------------------------ | ---------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------- |
828
+ | `METADATA_RELATIONSHIP_KINDS` | `const METADATA_RELATIONSHIP_KINDS` | — | The relationship kinds that are dependencies, and therefore citable. |
829
+ | `metadataFileName` | `function metadataFileName(pkg)` | {string} The file name, e.g. | What a package's content index is called, wherever it is written or fetched. |
830
+ | `metadataRelationships` | `function metadataRelationships(config)` | {Array<{id: string, manifest: string, kind: string, verified: string\|undefined}>} The dependencies, in declaration order. | Every dependency whose published index this build resolves addresses through. |
831
+ | `noContentIndexPackages` | `function noContentIndexPackages(config)` | {ReadonlySet<string>} The content package names. | Every package a relationship declares `contentIndex: false` on, keyed by the content package name a link into it would use. |
832
+ | `metadataCacheDir` | `function metadataCacheDir(config, id, version)` | {string} The directory. | The cache directory for one dependency's index at one version. |
833
+ | `isComplete` | `const isComplete` | {boolean} True when it was fetched to completion. | Whether a dependency's cache is present and complete. |
834
+ | `markComplete` | `function markComplete(dir)` | {void} | Mark a dependency's cache complete. |
835
+ | `cachedMetadataFiles` | `function cachedMetadataFiles(config)` | {string[]} One index file per declared dependency. | The fetched index files this build resolves foreign addresses against. |
836
+ | `cachedMetadataIndexes` | `function cachedMetadataIndexes(config)` | {Array<{id: string, file: string}>} One entry per declared dependency. | The same fetched indexes, each paired with the package that published it. |
837
+ | `newestVersionDir` | `function newestVersionDir(dirs)` | {string} The newest one. | The newest cached version among several version-keyed cache directories. |
838
+ | `loadForeignIndexes` | `function loadForeignIndexes(config, localPackages, bases` | `{{index: Map<string, object>, packages: Set<string>, stale: Array<{package: string, reason: string}>}}` The resolved addresses, which packages contributed, and what could not be read. | Resolve every foreign address this build can cite, from the fetched indexes. |
839
+ | `cachedIndexPath` | `function cachedIndexPath(config, pkg)` | {string} A path to name in a diagnostic. | Where a dependency's fetched index sits, for naming it in a diagnostic. |
840
+ | `unaddressableForeignPackages` | `function unaddressableForeignPackages(foreignIndex)` | {Array<{package: string, entries: number, sampleKey: string}>} One finding per drifted package, in the order the index first names each. | Whether a fetched index can still be _addressed_, as distinct from read. |
841
+ | `formatUnaddressableFinding` | `function formatUnaddressableFinding(finding, config)` | {string} The formatted diagnostic, path first on the line. | One finding, in the standard `file:line:column: severity: message` form. |
780
842
 
781
843
  ### `engine.foundryEntries`
782
844
 
783
845
  Emitting this package's cross-package link manifest. `engine/content-address.mjs` owns the address _grammar_ — how a key is version is read, how a foreign file resolves. This module owns the _pass_: walking a content tree and deriving, for every note it publishes, the addresses that entry states. The two halves were split across the format module and a hand-written script in each consuming repository, which is how the two scripts came to differ in ways nobody chose — one routes its UUIDs through the pack router and one does not, and neither knew.
784
846
 
785
- | Export | Signature | Returns | Use it when |
786
- | ----------------------- | ------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------- |
787
- | `LEAD_ANCHOR` | `const LEAD_ANCHOR` | — | The reserved anchor name for a journal's **first** page. |
788
- | `anchorsOf` | `function anchorsOf(entryUuid, entryId, body, name)` | {Record<string, string>} The anchors. | Every page of a note's journal, as `anchorName → whole UUID`. |
789
- | `entriesForNote` | `function entriesForNote(fm, name, address, body, ctx)` | {Array<object>} One or two entries, in {@link buildManifest}'s shape. | The manifest entries a single note produces. |
790
- | `collectFoundryEntries` | `function collectFoundryEntries(contentBase, ctx)` | {{entries: Array<object>, notes: number, skipped: Array<{file: string, reason: string}>}} | Every note this package publishes, as manifest entries. |
791
- | `foundryIdentities` | `function foundryIdentities(config` | {{contentPackage: string, foundryPackageId: string, packRouter: object, web: boolean, skipDirectories: readonly string[]}} | The identities an emission runs against, from configuration. |
792
- | `entryContext` | `function entryContext(config` | {{contentPackage: string, foundryPackageId: string, packRouter: object, web: boolean, skipDirectories: readonly string[]}} | The identities an emission runs against, from configuration. |
847
+ | Export | Signature | Returns | Use it when |
848
+ | ----------------------- | ------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------- |
849
+ | `LEAD_ANCHOR` | `const LEAD_ANCHOR` | — | The reserved anchor name for a journal's **first** page. |
850
+ | `anchorsOf` | `function anchorsOf(entryUuid, entryId, body, name)` | {Record<string, string>} The anchors. | Every page of a note's journal, as `anchorName → whole UUID`. |
851
+ | `entriesForNote` | `function entriesForNote(fm, name, address, body, ctx)` | {Array<object>} One or two entries, in {@link buildManifest}'s shape. | The manifest entries a single note produces. |
852
+ | `collectFoundryEntries` | `function collectFoundryEntries(contentBase, ctx)` | `{{entries: Array<object>, notes: number, skipped: Array<{file: string, reason: string}>}}` | Every note this package publishes, as manifest entries. |
853
+ | `foundryIdentities` | `function foundryIdentities(config` | `{{contentPackage: string, foundryPackageId: string, packRouter: object, web: boolean, skipDirectories: readonly string[]}}` | The identities an emission runs against, from configuration. |
854
+ | `entryContext` | `function entryContext(config` | `{{contentPackage: string, foundryPackageId: string, packRouter: object, web: boolean, skipDirectories: readonly string[]}}` | The identities an emission runs against, from configuration. |
793
855
 
794
856
  ### `engine.contentIndex`
795
857
 
796
858
  Emitting this package's content index. Every content build already walks the whole note tree and parses every note's frontmatter — the pack compilers, the site build, and the content-table expander each do it — and every one of them throws the result away when it finishes. So nothing outside a build can ask a question about the content: "which beings carry no `kbcat`?", "what does this table actually select?", "did that type rename leave anything behind?" have no answer short of writing a throwaway script that re-walks the tree. Eight dead Bestiary tables shipped for weeks behind exactly that gap.
797
859
 
798
- | Export | Signature | Returns | Use it when |
799
- | ----------------------- | ------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
800
- | `noteAddress` | `function noteAddress(frontmatter, contentPackage)` | {{slug: string, canonical: string}\|null} `slug` is what goes inside `[[…]]` within this package; `canonical` is the fully qualified key the manifest files the note under, carrying the package and the system as well. | The address a wikilink writes to reach a note, or `null` when it has none. |
801
- | `sortKeysDeep` | `function sortKeysDeep(value)` | {unknown} The value with every plain object's keys in sorted order. | Recursively sort an object's keys, so serialization is order-independent. |
802
- | `asciiName` | `function asciiName(name)` | {string\|null} The ASCII form, or `null` when there is no name, or nothing printable survives. | A note's display name reduced to printable 7-bit ASCII. |
803
- | `asciiAliases` | `function asciiAliases(aliases)` | {Array<string>} Possibly empty, never null: a note with no aliases has an empty set of them, which is a fact rather than a missing value, and a consumer iterating it should not have to check first. | A note's `name.aliases` reduced to printable 7-bit ASCII, in order. |
804
- | `serializeContentIndex` | `function serializeContentIndex(records)` | {string} One compact JSON object per line, newline-terminated. | Serialize records as JSON Lines. |
805
- | `indexRecordsFor` | `indexRecordsFor({ contentBase, config, skipDirectories, problems })` | {object[]} One record per note, plus one per documentation entry. | The index records for a content tree, without writing anything. |
806
- | `indexRecordsForNote` | `indexRecordsForNote({ frontmatter, body, bodyLine, relPath, absPath, contentPackage, manifest, addressContext })` | `object[]` | Derive the note's own record and its documentation record from parsed text. |
807
- | `emitContentIndex` | `emitContentIndex({ contentBase, outDir, config })` | {{file: string, notes: number, assets: number, records: number, bytes: number, full: number, draft: number, stub: number}} Where it was written, how many notes and how many assets it holds, how many records that is in all, its size, and the ladder those notes sit on. | Emit this package's content index. |
808
- | `buildIndexRecord` | `buildIndexRecord({ frontmatter, relPath, absPath, contentPackage, body, bodyLine, manifest })` | `Record<string, any>` — the record, with derived fields sorted deep | building one note's content-index record; refuses a note that authors a key the index derives itself (`package` among them) before building anything |
809
- | `collectContentIndex` | `collectContentIndex(contentBase, { contentPackage, skipDirectories, assetsBase, manifest, problems })` | `Array<Record<string, any>>` | reading a whole content tree into index records, in a total order independent of directory-read order; an item note yields two records (its own and its documentation journal's), and each asset root yields one per file |
860
+ | Export | Signature | Returns | Use it when |
861
+ | ----------------------- | ------------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
862
+ | `noteAddress` | `function noteAddress(frontmatter, contentPackage)` | {{slug: string, canonical: string}\|null} `slug` is what goes inside `[[…]]` within this package; `canonical` is the fully qualified key the manifest files the note under, carrying the package and the system as well. | The address a wikilink writes to reach a note, or `null` when it has none. |
863
+ | `sortKeysDeep` | `function sortKeysDeep(value)` | {unknown} The value with every plain object's keys in sorted order. | Recursively sort an object's keys, so serialization is order-independent. |
864
+ | `asciiName` | `function asciiName(name)` | {string\|null} The ASCII form, or `null` when there is no name, or nothing printable survives. | A note's display name reduced to printable 7-bit ASCII. |
865
+ | `asciiAliases` | `function asciiAliases(aliases)` | {Array<string>} Possibly empty, never null: a note with no aliases has an empty set of them, which is a fact rather than a missing value, and a consumer iterating it should not have to check first. | A note's `name.aliases` reduced to printable 7-bit ASCII, in order. |
866
+ | `serializeContentIndex` | `function serializeContentIndex(records)` | {string} One compact JSON object per line, newline-terminated. | Serialize records as JSON Lines. |
867
+ | `indexRecordsFor` | `indexRecordsFor({ contentBase, config, skipDirectories, problems })` | {object[]} One record per note, plus one per documentation entry. | The index records for a content tree, without writing anything. |
868
+ | `indexRecordsForNote` | `indexRecordsForNote({ frontmatter, body, bodyLine, relPath, absPath, contentPackage, manifest, addressContext })` | `object[]` | Derive the note's own record and its documentation record from parsed text. |
869
+ | `emitContentIndex` | `emitContentIndex({ contentBase, outDir, config })` | `{{file: string, notes: number, assets: number, records: number, bytes: number, full: number, draft: number, stub: number}}` Where it was written, how many notes and how many assets it holds, how many records that is in all, its size, and the ladder those notes sit on. | Emit this package's content index. |
870
+ | `buildIndexRecord` | `buildIndexRecord({ frontmatter, relPath, absPath, contentPackage, body, bodyLine, manifest })` | `Record<string, any>` — the record, with derived fields sorted deep | building one note's content-index record; refuses a note that authors a key the index derives itself (`package` among them) before building anything |
871
+ | `collectContentIndex` | `collectContentIndex(contentBase, { contentPackage, skipDirectories, assetsBase, manifest, problems })` | `Array<Record<string, any>>` | reading a whole content tree into index records, in a total order independent of directory-read order; an item note yields two records (its own and its documentation journal's), and each asset root yields one per file |
810
872
 
811
873
  `engine.contentIndex` also re-exports eight names from neighbouring leaf modules, at this same import path:
812
874
 
@@ -874,24 +936,27 @@ The toolchain's own content index — the files it ships, addressed. Every other
874
936
 
875
937
  ### `engine.siteBuild`
876
938
 
877
- 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.
878
-
879
- | Export | Signature | Returns | Use it when |
880
- | --------------------- | ------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |
881
- | `collectContentPages` | `function collectContentPages(contentBase, ctx)` | {{pages: object[], addressFindings: object[], fmLinkFindings: object[]}} | The content tree's pages, and what could not be addressed. |
882
- | `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>/`. |
883
- | `writeHomepages` | `function writeHomepages(outRoot, pages, config)` | {number} How many pages were written. | Writes each homepage as the package root's `_index.md`. |
884
- | `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. |
885
- | `emptyGates` | `function emptyGates()` | {object} An all-clear gate result. | The gate result of a build that ran none of them. |
886
- | `gatesFailed` | `function gatesFailed(gates)` | — | Whether any gate produced a finding. |
887
- | `tableUniverse` | `function tableUniverse(pages)` | {Map<string, object[]>} Package → the notes it may tabulate. | The universe a generated table searches, grouped by package. |
888
- | `pageFrontmatter` | `function pageFrontmatter(page,` | {object} The frontmatter to write. | The frontmatter a page publishes with. |
889
- | `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. |
890
- | `renderSitePage` | `renderSitePage(page, options)` | `{body, data, resolved, tableErrors, secretErrors, wikiErrors, imageErrors}` | Transform one page with the same passes the site writer uses. |
891
- | `renderPages` | `function renderPages(pages, options)` | {{written: number, byKind: Record<string, number>, tableErrors: object[], wikiErrors: object[]}} | Renders and writes every page. |
892
- | `resolveSitePass` | `function resolveSitePass(name, options)` | {{beforeLinks?: Function}} The bundle. | Resolves `site.pass` to its bundle. |
893
- | `sitePageDecorator` | `sitePageDecorator(config, index)` | Function | Add the site's infobox and being metadata to a rendered page. |
894
- | `buildSite` | `buildSite({ config, sqlTables })` | {{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. |
939
+ Publishing a content tree as a website. `collectContentPages` gathers authored
940
+ pages and link findings, while `collectHomepages` gathers package homepages.
941
+ `writeHomepages` places those homepages at each package's site root. The same
942
+ content tree can also compile into Foundry compendium packs.
943
+
944
+ | Export | Signature | Returns | Use it when |
945
+ | --------------------- | ------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |
946
+ | `collectContentPages` | `function collectContentPages(contentBase, ctx)` | `{{pages: object[], addressFindings: object[], fmLinkFindings: object[]}}` | The content tree's pages, and what could not be addressed. |
947
+ | `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>/`. |
948
+ | `writeHomepages` | `function writeHomepages(outRoot, pages, config)` | {number} How many pages were written. | Writes each homepage as the package root's `_index.md`. |
949
+ | `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. |
950
+ | `emptyGates` | `function emptyGates()` | {object} An all-clear gate result. | The gate result of a build that ran none of them. |
951
+ | `gatesFailed` | `function gatesFailed(gates)` | — | Whether any gate produced a finding. |
952
+ | `tableUniverse` | `function tableUniverse(pages)` | {Map<string, object[]>} Package → the notes it may tabulate. | The universe a generated table searches, grouped by package. |
953
+ | `pageFrontmatter` | `function pageFrontmatter(page,` | {object} The frontmatter to write. | The frontmatter a page publishes with. |
954
+ | `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. |
955
+ | `renderSitePage` | `renderSitePage(page, options)` | `{body, data, resolved, tableErrors, secretErrors, wikiErrors, imageErrors}` | Transform one page with the same passes the site writer uses. |
956
+ | `renderPages` | `function renderPages(pages, options)` | `{{written: number, byKind: Record<string, number>, tableErrors: object[], wikiErrors: object[]}}` | Renders and writes every page. |
957
+ | `resolveSitePass` | `function resolveSitePass(name, options)` | `{{beforeLinks?: Function}}` The bundle. | Resolves `site.pass` to its bundle. |
958
+ | `sitePageDecorator` | `sitePageDecorator(config, index)` | Function | Add the site's infobox and being metadata to a rendered page. |
959
+ | `buildSite` | `buildSite({ config, sqlTables })` | `{{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. |
895
960
 
896
961
  ### `engine.sitePreview`
897
962
 
@@ -923,96 +988,96 @@ try {
923
988
 
924
989
  Linting a content tree's **addresses** — the rules every package's notes are authored against, wherever those notes live. These rules do not live in a consumer's `utils/`, which has two consequences and no upside. `thalorna` and `kethira` notes were checked by nothing at all, so the packages most likely to carry authoring mistakes were the ones nothing inspected. And one rule with two implementations can disagree without anything detecting it, which the canonical-separator handling already did once on each side.
925
990
 
926
- | Export | Signature | Returns | Use it when |
927
- | ------------------- | ---------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------- |
928
- | `SHORTCODE_PATTERN` | `const SHORTCODE_PATTERN` | — | The shape every `shortcode` must match: ASCII letters and digits only. |
929
- | `isValidShortcode` | `function isValidShortcode(value)` | {boolean} `true` when it matches {@link SHORTCODE_PATTERN}. | Whether a value is a well-formed shortcode. |
930
- | `lintContentTree` | `lintContentTree(contentBase, { skipDirectories, contentPackage, config, records, problems })` | {{findings: Array<{file: string, line?: number, column?: number, severity: "error"\|"warning", message: string}>, notes: number, keys: number}} The findings, and what was inspected to produce them. | Lint every address in a content tree. |
991
+ | Export | Signature | Returns | Use it when |
992
+ | ------------------- | ---------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------- |
993
+ | `SHORTCODE_PATTERN` | `const SHORTCODE_PATTERN` | — | The shape every `shortcode` must match: ASCII letters and digits only. |
994
+ | `isValidShortcode` | `function isValidShortcode(value)` | {boolean} `true` when it matches {@link SHORTCODE_PATTERN}. | Whether a value is a well-formed shortcode. |
995
+ | `lintContentTree` | `lintContentTree(contentBase, { skipDirectories, contentPackage, config, records, problems })` | `{{findings: Array<{file: string, line?: number, column?: number, severity: "error"\|"warning", message: string}>, notes: number, keys: number}}` The findings, and what was inspected to produce them. | Lint every address in a content tree. |
931
996
 
932
997
  ### `engine.contentCharset`
933
998
 
934
999
  The charset authored content is held to, so a book can choose its face. The packs and the website render in whatever font the reader's browser or Foundry supplies, and a glyph nobody has is somebody else's problem. **A book is not that.** A PDF embeds the faces it sets, so every character in the corpus is a claim on the book's typeface — and the claim is silent, which is what makes it expensive.
935
1000
 
936
- | Export | Signature | Returns | Use it when |
937
- | -------------------- | ------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------- |
938
- | `isLetterTier` | `function isLetterTier(cp)` | {boolean} Whether Tier 1 admits it. | Tier 1 — the letters, and the two whitespace characters a file is made of. |
939
- | `TYPOGRAPHY` | `const TYPOGRAPHY` | — | Tier 2 — typography, enumerated one codepoint at a time. |
940
- | `NOTATION` | `const NOTATION` | — | Tier 3 — the notation the rules and price tables are written in. |
941
- | `isAllowedCodePoint` | `function isAllowedCodePoint(cp)` | {boolean} Whether it is allowed outside a code fence. | Whether the charset admits a code point anywhere in a note. |
942
- | `isDiagramCodePoint` | `function isDiagramCodePoint(cp)` | {boolean} Whether a fence may carry it. | Whether a code point is diagram furniture, admitted inside a fence only. |
943
- | `refusalFor` | `function refusalFor(cp)` | {string} A clause naming what it is and what to do instead. | The reason a code point is refused. |
944
- | `decomposedRuns` | `function decomposedRuns(text)` | {Array<{sequence: string, composed: string, index: number}>} Each offending run, in the order it appears. | Every non-NFC run in a string, with the composed form it should have been. |
945
- | `checkText` | `function checkText(text, file)` | {Array<{file: string, line: number, column: number, severity: "warning", message: string}>} What is wrong, in file order. | Check one file's text against the charset and the normalization rule. |
946
- | `lintContentCharset` | `function lintContentCharset(contentBase,` | {{findings: Array<{file: string, line: number, column: number, severity: "warning", message: string}>, files: number}} The findings, and how many files produced them. | Walk a content tree and check every authored file in it. |
1001
+ | Export | Signature | Returns | Use it when |
1002
+ | -------------------- | ------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------- |
1003
+ | `isLetterTier` | `function isLetterTier(cp)` | {boolean} Whether Tier 1 admits it. | Tier 1 — the letters, and the two whitespace characters a file is made of. |
1004
+ | `TYPOGRAPHY` | `const TYPOGRAPHY` | — | Tier 2 — typography, enumerated one codepoint at a time. |
1005
+ | `NOTATION` | `const NOTATION` | — | Tier 3 — the notation the rules and price tables are written in. |
1006
+ | `isAllowedCodePoint` | `function isAllowedCodePoint(cp)` | {boolean} Whether it is allowed outside a code fence. | Whether the charset admits a code point anywhere in a note. |
1007
+ | `isDiagramCodePoint` | `function isDiagramCodePoint(cp)` | {boolean} Whether a fence may carry it. | Whether a code point is diagram furniture, admitted inside a fence only. |
1008
+ | `refusalFor` | `function refusalFor(cp)` | {string} A clause naming what it is and what to do instead. | The reason a code point is refused. |
1009
+ | `decomposedRuns` | `function decomposedRuns(text)` | {Array<{sequence: string, composed: string, index: number}>} Each offending run, in the order it appears. | Every non-NFC run in a string, with the composed form it should have been. |
1010
+ | `checkText` | `function checkText(text, file)` | {Array<{file: string, line: number, column: number, severity: "warning", message: string}>} What is wrong, in file order. | Check one file's text against the charset and the normalization rule. |
1011
+ | `lintContentCharset` | `function lintContentCharset(contentBase,` | `{{findings: Array<{file: string, line: number, column: number, severity: "warning", message: string}>, files: number}}` The findings, and how many files produced them. | Walk a content tree and check every authored file in it. |
947
1012
 
948
1013
  ### `engine.contentIcons`
949
1014
 
950
1015
  Naming an interface icon in a note, without drawing it there. The user guide describes Foundry's interface, and it did so by pasting Unicode lookalikes of the icons the sheets actually draw: `☆` for the improve flag, `✎` for the formula editor, `◆` in the success-value table, `★★★` for mastery. The system renders every one of those with **Font Awesome** — a `fa-regular fa-star`, a `fa-solid fa-pen-to-square` — so the note and the screen it describes were drawing different pictures, and drifting apart with every sheet change.
951
1016
 
952
- | Export | Signature | Returns | Use it when |
953
- | --------------------- | -------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------- |
954
- | `EMPTY_ICON_REGISTRY` | `const EMPTY_ICON_REGISTRY` | — | The registry a package that declares none gets: nothing at all. |
955
- | `familyOf` | `function familyOf(entry, registry` | {string\|undefined} The family name, or nothing when neither the entry nor the registry says. | The family an entry draws from, named or defaulted. |
956
- | `ICON_SIZES` | `const ICON_SIZES` | — | The sizes a note may ask for, and what each means on a page. |
957
- | `ICON_ATTRIBUTES` | `const ICON_ATTRIBUTES` | — | The attribute names a note may write, and how each is validated. |
958
- | `parseIconAttributes` | `function parseIconAttributes(raw)` | {{attrs: Record<string, string>, problems: string[]}} What was written, and what cannot be honoured. | Read the brace of an icon token. |
959
- | `ICON_PATTERN` | `const ICON_PATTERN` | — | The shape a note writes, and the one this module claims. |
960
- | `resolveIcon` | `function resolveIcon(name, registry` | {object\|null} The entry, or `null` when the registry does not declare it. | Look one name up. |
961
- | `iconHtml` | `function iconHtml(entry, attrs` | {string} An `<i>` element. | The HTML the journals and the website emit — what the system already renders. |
962
- | `iconsIn` | `function iconsIn(text)` | {Array<{name: string, index: number, raw: string}>} What it names. | Every icon a string names, in the order written. |
963
- | `lintIcons` | `function lintIcons(text, file, registry` | {Array<{file: string, line: number, column: number, severity: "warning", message: string}>} The unknown names. | Report every icon a tree names that its registry does not declare. |
964
- | `checkIconRegistry` | `function checkIconRegistry(registry, where` | {Array<{severity: "warning", message: string}>} What is wrong with it. | What is wrong with a package's declared registry. |
965
- | `lintContentIcons` | `function lintContentIcons(contentBase,` | {{findings: Array<{file: string, line: number, column: number, severity: "warning", message: string}>, files: number}} What it found. | Walk a content tree and report every icon name its registry does not declare. |
966
- | `iconPlugin` | `function iconPlugin(registry` | {(md: object) => void} A markdown-it plugin. | A markdown-it plugin rendering `:icon-name:` inline. |
1017
+ | Export | Signature | Returns | Use it when |
1018
+ | --------------------- | -------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------- |
1019
+ | `EMPTY_ICON_REGISTRY` | `const EMPTY_ICON_REGISTRY` | — | The registry a package that declares none gets: nothing at all. |
1020
+ | `familyOf` | `function familyOf(entry, registry` | {string\|undefined} The family name, or nothing when neither the entry nor the registry says. | The family an entry draws from, named or defaulted. |
1021
+ | `ICON_SIZES` | `const ICON_SIZES` | — | The sizes a note may ask for, and what each means on a page. |
1022
+ | `ICON_ATTRIBUTES` | `const ICON_ATTRIBUTES` | — | The attribute names a note may write, and how each is validated. |
1023
+ | `parseIconAttributes` | `function parseIconAttributes(raw)` | `{{attrs: Record<string, string>, problems: string[]}}` What was written, and what cannot be honoured. | Read the brace of an icon token. |
1024
+ | `ICON_PATTERN` | `const ICON_PATTERN` | — | The shape a note writes, and the one this module claims. |
1025
+ | `resolveIcon` | `function resolveIcon(name, registry` | {object\|null} The entry, or `null` when the registry does not declare it. | Look one name up. |
1026
+ | `iconHtml` | `function iconHtml(entry, attrs` | {string} An `<i>` element. | The HTML the journals and the website emit — what the system already renders. |
1027
+ | `iconsIn` | `function iconsIn(text)` | {Array<{name: string, index: number, raw: string}>} What it names. | Every icon a string names, in the order written. |
1028
+ | `lintIcons` | `function lintIcons(text, file, registry` | {Array<{file: string, line: number, column: number, severity: "warning"\|"error", message: string}>} Findings for names and syntax. | Report every icon a tree names that its registry does not declare. |
1029
+ | `checkIconRegistry` | `function checkIconRegistry(registry, where` | {Array<{severity: "warning", message: string}>} What is wrong with it. | What is wrong with a package's declared registry. |
1030
+ | `lintContentIcons` | `function lintContentIcons(contentBase,` | `{{findings: Array<{file: string, line: number, column: number, severity: "warning", message: string}>, files: number}}` What it found. | Walk a content tree and report every icon name its registry does not declare. |
1031
+ | `iconPlugin` | `function iconPlugin(registry` | {(md: object) => void} A markdown-it plugin. | A markdown-it plugin rendering `:icon name:` inline. |
967
1032
 
968
1033
  ### `engine.contentHtml`
969
1034
 
970
- Raw HTML in a note's prose, reported. **A note is markdown.** What markdown cannot say, a note does not say — it gets a construct every surface can render, the way `:icon-…:` replaces a pasted glyph.
1035
+ Raw HTML in a note's prose, reported. **A note is markdown.** What markdown cannot say, a note does not say — it gets a construct every surface can render, the way `:icon …:` replaces a pasted glyph.
971
1036
 
972
- | Export | Signature | Returns | Use it when |
973
- | ----------------- | --------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------- |
974
- | `HTML_TAG` | `const HTML_TAG` | — | A raw HTML tag, opening, closing or self-closing. |
975
- | `htmlMessage` | `function htmlMessage(tag)` | {string} The message, unpunctuated at the end as a finding is. | What a note carrying raw HTML is told. |
976
- | `checkHtml` | `function checkHtml(body, file,` | {Array<{file: string, line: number, column: number, severity: "warning", message: string}>} One finding per tag, in source order. | Every raw HTML tag in one note's body. |
977
- | `lintContentHtml` | `function lintContentHtml(contentBase,` | {{findings: Array<{file: string, line: number, column: number, severity: "warning", message: string}>, files: number}} The findings, and how many files were read. | Walk a content tree and report raw HTML in every note's prose. |
1037
+ | Export | Signature | Returns | Use it when |
1038
+ | ----------------- | --------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------- |
1039
+ | `HTML_TAG` | `const HTML_TAG` | — | A raw HTML tag, opening, closing or self-closing. |
1040
+ | `htmlMessage` | `function htmlMessage(tag)` | {string} The message, unpunctuated at the end as a finding is. | What a note carrying raw HTML is told. |
1041
+ | `checkHtml` | `function checkHtml(body, file,` | {Array<{file: string, line: number, column: number, severity: "warning", message: string}>} One finding per tag, in source order. | Every raw HTML tag in one note's body. |
1042
+ | `lintContentHtml` | `function lintContentHtml(contentBase,` | `{{findings: Array<{file: string, line: number, column: number, severity: "warning", message: string}>, files: number}}` The findings, and how many files were read. | Walk a content tree and report raw HTML in every note's prose. |
978
1043
 
979
1044
  ### `engine.contentEmbeds`
980
1045
 
981
1046
  `![[address|label]]` — the wikilink that renders a picture where it stands. An embed is the wikilink the syntax module defines, with `!` meaning _render it here_, and the syntax supplying the default type `image`. It resolves to an ordinary markdown image before any surface renders it, so the figure, the staging list and the directive all come from the image machinery that already exists. An embed reaches asset types only.
982
1047
 
983
- | Export | Signature | Returns | Use it when |
984
- | -------------------- | ---------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------- |
985
- | `EMBED_DEFAULT_TYPE` | `const EMBED_DEFAULT_TYPE` | — | The type a bare shortcode takes, supplied by the syntax itself. |
986
- | `EMBED_PATTERN` | `const EMBED_PATTERN` | — | An embed, as authored, with the directive it may carry. |
987
- | `embedsIn` | `function embedsIn(body)` | {ParsedEmbed[]} One entry per embed. | Every embed in one body, in source order. |
988
- | `resolveEmbed` | `function resolveEmbed(index, embed)` | {{pathname: string}\|{reason: string, target: string, type?: string}} The authored pathname, or the finding. | What one embed resolves to, or why it does not. |
989
- | `embedProblems` | `function embedProblems(embed)` | {Array<{link: string, offset: number, message: string}>} One entry per defect. | What is wrong with one embed's directive or placement. |
990
- | `resolveEmbeds` | `function resolveEmbeds(body, ctx)` | {{markdown: string, unresolved: Array<object>, problems: Array<object>}} The body, the embeds that named nothing, and the directives that could not be honoured. | Rewrite every embed into the image each surface renders. |
991
- | `checkEmbeds` | `function checkEmbeds(body, file, opts)` | {Array<{file: string, line: number, column: number\|undefined, severity: "error", message: string}>} One finding per defect, in source order. | Every defect in one note's embeds, located. |
1048
+ | Export | Signature | Returns | Use it when |
1049
+ | -------------------- | ---------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------- |
1050
+ | `EMBED_DEFAULT_TYPE` | `const EMBED_DEFAULT_TYPE` | — | The type a bare shortcode takes, supplied by the syntax itself. |
1051
+ | `EMBED_PATTERN` | `const EMBED_PATTERN` | — | An embed, as authored, with the directive it may carry. |
1052
+ | `embedsIn` | `function embedsIn(body)` | {ParsedEmbed[]} One entry per embed. | Every embed in one body, in source order. |
1053
+ | `resolveEmbed` | `function resolveEmbed(index, embed)` | `{{pathname: string}\|{reason: string, target: string, type?: string}}` The authored pathname, or the finding. | What one embed resolves to, or why it does not. |
1054
+ | `embedProblems` | `function embedProblems(embed)` | {Array<{link: string, offset: number, message: string}>} One entry per defect. | What is wrong with one embed's directive or placement. |
1055
+ | `resolveEmbeds` | `function resolveEmbeds(body, ctx)` | `{{markdown: string, unresolved: Array<object>, problems: Array<object>}}` The body, the embeds that named nothing, and the directives that could not be honoured. | Rewrite every embed into the image each surface renders. |
1056
+ | `checkEmbeds` | `function checkEmbeds(body, file, opts)` | {Array<{file: string, line: number, column: number\|undefined, severity: "error", message: string}>} One finding per defect, in source order. | Every defect in one note's embeds, located. |
992
1057
 
993
1058
  ### `engine.contentImages`
994
1059
 
995
- An image saying how wide it is and where it sits. A directive in the curly-attribute convention Pandoc and Kramdown use supplies a width class, a named `size:`, and a `float:` position. The named size sets a maximum display width for each surface.
996
-
997
- | Export | Signature | Returns | Use it when |
998
- | --------------------- | ----------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------- |
999
- | `IMAGE_CLASSES` | `const IMAGE_CLASSES` | — | The width classes an image may carry, and what each means to a renderer. |
1000
- | `IMAGE_SIZES` | `const IMAGE_SIZES` | — | The named sizes an image directive accepts. |
1001
- | `IMAGE_FLOATS` | `const IMAGE_FLOATS` | — | The `float:` positions an image may take, and where each puts it. |
1002
- | `IMAGE_FIGURE_CLASS` | `const IMAGE_FIGURE_CLASS` | — | The class every figure carries, whatever its width or position. |
1003
- | `IMAGE_PATTERN` | `const IMAGE_PATTERN` | — | A markdown image, with the directive it may carry. |
1004
- | `imageSourceProblem` | `function imageSourceProblem(src)` | {string} The problem, as a finding's sentence, or `""`. | What is wrong with an image's address, or `""` when nothing is. |
1005
- | `parseImageDirective` | `function parseImageDirective(raw)` | {{classes: string[], size: string, float: string, problems: string[]}} What was written, and what cannot be honoured. | Read the directive on an image. |
1006
- | `figureClasses` | `function figureClasses(directive)` | {string} A space-separated class list. | The classes a figure carries, from a parsed directive. |
1007
- | `escapeHtml` | `function escapeHtml(text)` | {string} The same value, safe in markup. | Text going inside an HTML attribute or between tags. |
1008
- | `imageFigureHtml` | `function imageFigureHtml(image)` | {string} The figure, as one HTML block. | One image as the `<figure>` both HTML surfaces render. |
1009
- | `standsAlone` | `function standsAlone(text, start, end)` | {boolean} Whether the match is a block of its own. | Whether a match sits alone in its own paragraph. |
1010
- | `imagesIn` | `function imagesIn(body)` | {Array<{alt: string, src: string, title: string, directive: string, index: number, length: number, block: boolean}>} One entry per image. | Every image in one body, with its directive and its position. |
1011
- | `imageSourcesIn` | `function imageSourcesIn(body)` | {string[]} The addresses, with repeats. | Every image address one body names, in order of appearance. |
1012
- | `checkImages` | `function checkImages(body, file,` | {Array<{file: string, line: number, column: number\|undefined, severity: "error", message: string}>} One finding per defect, in source order. | Every defect in one note's images. |
1013
- | `lintContentImages` | `function lintContentImages(contentBase,` | {{findings: Array<{file: string, line: number, column: number\|undefined, severity: "error", message: string}>, files: number}} What it found. | Walk a content tree and report every image it cannot render as authored. |
1014
- | `renderImageFigures` | `function renderImageFigures(body, resolveSrc)` | {string} The same body, with each block image as a `<figure>`. | Rewrite every block image in a body into the figure the website publishes. |
1015
- | `imagePlugin` | `function imagePlugin()` | {(md: object) => void} A markdown-it plugin. | A markdown-it plugin that reads an image's directive and renders its figure. |
1060
+ An image saying how wide it is and where it sits. A directive in the curly-attribute convention Pandoc and Kramdown use supplies a width class, a named `size`, and a `float` position. The named size sets a maximum display width for each surface.
1061
+
1062
+ | Export | Signature | Returns | Use it when |
1063
+ | --------------------- | ----------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------- |
1064
+ | `IMAGE_CLASSES` | `const IMAGE_CLASSES` | — | The width classes an image may carry, and what each means to a renderer. |
1065
+ | `IMAGE_SIZES` | `const IMAGE_SIZES` | — | The named sizes an image directive accepts. |
1066
+ | `IMAGE_FLOATS` | `const IMAGE_FLOATS` | — | The `float` positions an image may take, and where each puts it. |
1067
+ | `IMAGE_FIGURE_CLASS` | `const IMAGE_FIGURE_CLASS` | — | The class every figure carries, whatever its width or position. |
1068
+ | `IMAGE_PATTERN` | `const IMAGE_PATTERN` | — | A markdown image, with the directive it may carry. |
1069
+ | `imageSourceProblem` | `function imageSourceProblem(src)` | {string} The problem, as a finding's sentence, or `""`. | What is wrong with an image's address, or `""` when nothing is. |
1070
+ | `parseImageDirective` | `function parseImageDirective(raw)` | `{{classes: string[], size: string, float: string, problems: string[]}}` What was written, and what cannot be honoured. | Read the directive on an image. |
1071
+ | `figureClasses` | `function figureClasses(directive)` | {string} A space-separated class list. | The classes a figure carries, from a parsed directive. |
1072
+ | `escapeHtml` | `function escapeHtml(text)` | {string} The same value, safe in markup. | Text going inside an HTML attribute or between tags. |
1073
+ | `imageFigureHtml` | `function imageFigureHtml(image)` | {string} The figure, as one HTML block. | One image as the `<figure>` both HTML surfaces render. |
1074
+ | `standsAlone` | `function standsAlone(text, start, end)` | {boolean} Whether the match is a block of its own. | Whether a match sits alone in its own paragraph. |
1075
+ | `imagesIn` | `function imagesIn(body)` | {Array<{alt: string, src: string, title: string, directive: string, index: number, length: number, block: boolean}>} One entry per image. | Every image in one body, with its directive and its position. |
1076
+ | `imageSourcesIn` | `function imageSourcesIn(body)` | {string[]} The addresses, with repeats. | Every image address one body names, in order of appearance. |
1077
+ | `checkImages` | `function checkImages(body, file,` | {Array<{file: string, line: number, column: number\|undefined, severity: "error", message: string}>} One finding per defect, in source order. | Every defect in one note's images. |
1078
+ | `lintContentImages` | `function lintContentImages(contentBase,` | `{{findings: Array<{file: string, line: number, column: number\|undefined, severity: "error", message: string}>, files: number}}` What it found. | Walk a content tree and report every image it cannot render as authored. |
1079
+ | `renderImageFigures` | `function renderImageFigures(body, resolveSrc)` | {string} The same body, with each block image as a `<figure>`. | Rewrite every block image in a body into the figure the website publishes. |
1080
+ | `imagePlugin` | `function imagePlugin()` | {(md: object) => void} A markdown-it plugin. | A markdown-it plugin that reads an image's directive and renders its figure. |
1016
1081
 
1017
1082
  ### `engine.pathnames`
1018
1083
 
@@ -1032,13 +1097,13 @@ One authored pathname, and the four addresses it resolves to. A note names a fil
1032
1097
 
1033
1098
  Resolving every link in a content tree, and reporting the ones that land nowhere. Three link defects survive both content builds silently, so neither the pack compilers nor a site build catches them:
1034
1099
 
1035
- | Export | Signature | Returns | Use it when |
1036
- | -------------------- | ---------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------- |
1037
- | `anchorsOf` | `function anchorsOf(body)` | {Set<string>} The declared anchor slugs. | Every `{#anchor}` a note declares on a heading. |
1038
- | `buildLinkIndex` | `buildLinkIndex(contentBase, { config, skipDirectories, sqlTables, records, problems })` | {object} The notes, the index, and the resolvers built over it. | Read a content tree into the index a link resolves against. |
1039
- | `auditHomepageLinks` | `function auditHomepageLinks(index)` | {Array<{note: object, field: string, url: string, text: string, occurrence: number, message: string}>} One finding per defect, `text` and `occurrence` locating it in the note's raw source. | Every defect in the addresses a package homepage carries. |
1040
- | `auditLinks` | `function auditLinks(index)` | {{deadAnchors: object[], deadAddresses: object[], unlabelledLinks: object[], frontmatterLinks: object[], homepageLinks: object[], usedManifest: Set<string>}} The findings, and which addresses a foreign manifest answered. | Every link in a tree that lands nowhere. |
1041
- | `walkReachability` | `function walkReachability(index,` | {{root: object, reached: Set<object>, orphans: object[]}} The root, everything reached from it, and the corpus members that were not. | Walk a corpus from its root and report what nothing links to. |
1100
+ | Export | Signature | Returns | Use it when |
1101
+ | -------------------- | ---------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------- |
1102
+ | `anchorsOf` | `function anchorsOf(body)` | {Set<string>} The declared anchor slugs. | Every `{#anchor}` a note declares on a heading. |
1103
+ | `buildLinkIndex` | `buildLinkIndex(contentBase, { config, skipDirectories, sqlTables, records, problems })` | {object} The notes, the index, and the resolvers built over it. | Read a content tree into the index a link resolves against. |
1104
+ | `auditHomepageLinks` | `function auditHomepageLinks(index)` | {Array<{note: object, field: string, url: string, text: string, occurrence: number, message: string}>} One finding per defect, `text` and `occurrence` locating it in the note's raw source. | Every defect in the addresses a package homepage carries. |
1105
+ | `auditLinks` | `function auditLinks(index)` | `{{deadAnchors: object[], deadAddresses: object[], unlabelledLinks: object[], frontmatterLinks: object[], homepageLinks: object[], usedManifest: Set<string>}}` The findings, and which addresses a foreign manifest answered. | Every link in a tree that lands nowhere. |
1106
+ | `walkReachability` | `function walkReachability(index,` | `{{root: object, reached: Set<object>, orphans: object[]}}` The root, everything reached from it, and the corpus members that were not. | Walk a corpus from its root and report what nothing links to. |
1042
1107
 
1043
1108
  ### `engine.webWikilinks`
1044
1109
 
@@ -1051,44 +1116,40 @@ Wikilink resolution for the knowledgebase build. The same authored links the pac
1051
1116
 
1052
1117
  ### `engine.contentTables`
1053
1118
 
1054
- Generated content tables from fenced `dataview` `TABLE` queries. A catalog table (every cloth armour, every animal's abilities) is data that already lives in the frontmatter of the notes it describes. Authoring such a table by hand duplicates that data and guarantees drift, so a content body instead declares what it wants tabulated, in a fenced `dataview` block:
1119
+ Prepared SQL content tables are expanded into Markdown before link resolution.
1120
+ A query selecting no rows reports an error unless its fence states
1121
+ `{allow-empty=true}`.
1055
1122
 
1056
- | Export | Signature | Returns | Use it when |
1057
- | --------------------- | ---------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
1058
- | `parseDataviewQuery` | `function parseDataviewQuery(source)` | {{columns: Array<{header: string, expr: object}>, from: object\|null, where: object\|null, sort: Array<{expr: object, descending: boolean}>, limit: number\|null}} | Parse one `dataview` block's query. |
1059
- | `resolveField` | `function resolveField(doc, path)` | {unknown} `null` when the note has no such field. | Resolve a dotted path against a content note. |
1060
- | `evaluate` | `function evaluate(node, doc, self)` | {unknown} | Evaluate one parsed expression against a note. |
1061
- | `selectRows` | `function selectRows(spec, docs, self)` | {Array<ContentTableDoc>} The matching notes, sorted and limited. | The notes a query selects, in the order its `SORT` keys give. |
1062
- | `renderContentTable` | `function renderContentTable(spec, rows, linkable, self)` | {string} The markdown table (no trailing newline). | Build the markdown table for one query. |
1063
- | `expandContentTables` | `expandContentTables(markdown, { docs, linkable, source, self, sqlTables })` | `{markdown: string, errors: object[], lineMap: object[], warnings: object[]}` | expanding every fenced `dataview` block in a markdown body; a block that cannot be honoured is left verbatim and reported in `errors` rather than failing the build, and a query matching no note renders as an empty table |
1123
+ | Export | Signature | Returns | Use it when |
1124
+ | --------------------- | ------------------------------------------------------ | ----------------------------------------------------------------------------- | ---------------------------------------------------------- |
1125
+ | `expandContentTables` | `expandContentTables(markdown, { source, sqlTables })` | `{markdown: string, errors: object[], lineMap: object[], warnings: object[]}` | Replace prepared SQL fences and preserve source locations. |
1064
1126
 
1065
1127
  ### `engine.helpers`
1066
1128
 
1067
- Shared helpers for the pack compilers in `packages/content-build/`. The HeroicLands vault is authoritative for compendium item data. Pack compilers walk the vault, read markdown files with YAML frontmatter, and emit Foundry-compatible JSON. These helpers handle the common shape: markdown parsing, frontmatter access (including the nested `sohl:` block), filename generation, and slug normalization.
1068
-
1069
- | Export | Signature | Returns | Use it when |
1070
- | ------------------------- | ----------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
1071
- | `md` | `const md` | — | The markdown renderer every surface shares. |
1072
- | `renderFoundryMarkdown` | `function renderFoundryMarkdown(body)` | `string` — HTML with secret sections for GM passages. | Rendering a note body for a Foundry document. |
1073
- | `parseMarkdownFile` | `function parseMarkdownFile(filePath)` | — | Parses a markdown file with YAML frontmatter. |
1074
- | `assertStatedScope` | `function assertStatedScope(skipDirectories, who)` | {void} | Refuse a corpus read whose scope its caller did not state. |
1075
- | `assertSuppliedCorpus` | `function assertSuppliedCorpus(records, who)` | {void} | Refuse a corpus read whose records its caller did not supply. |
1076
- | `statedTemplatePriority` | `function statedTemplatePriority(fm, label,` | {number\|null} The priority, or `null` when the note is not a template or states nothing. | The template priority a note states, for a system that treats an unstated one as "not a template" rather than as an authoring error. |
1077
- | `resolveTemplatePriority` | `function resolveTemplatePriority(fm, label,` | {number\|undefined} The template priority, or `undefined` when null. | Resolve the required `templatePriority` frontmatter for an Item/Actor entry (the archetype contract). |
1078
- | `systemTemplatePriority` | `function systemTemplatePriority(fm, label)` | {number\|null} The template priority, or `null` for a document that is not a template. | The value a document's `system.templatePriority` carries, from the required `templatePriority` frontmatter (`archetype` is the legacy spelling). |
1079
- | `makeFilename` | `function makeFilename(name, id)` | — | Generates a compendium-source filename: `Name_id.json` with non- alphanumeric runs replaced by underscores. |
1080
- | `resolveImg` | `function resolveImg(raw, config` | {string \| null} the Foundry-relative path; `""` for a deliberate blank, and `null` when the note names no art at all. | Translate a content-relative image path into its Foundry-relative form. |
1081
- | `resolveName` | `function resolveName(fm, defaultValue` | — | Resolves the display name from frontmatter, preferring `name.full`, falling back to `name` (if string), then `defaultValue`. |
1082
- | `supportedCoreVersion` | `function supportedCoreVersion(config` | {string} The declared `compatibility.minimum`. | The oldest Foundry core this package supports, stamped into every compiled document as `_stats.coreVersion`. |
1083
- | `buildStats` | `function buildStats(systemVersion` | {object} The `_stats` block. | Default `_stats` block for compiled compendium entries. |
1084
- | `statsForPack` | `function statsForPack(packSystem, config` | {object} The `_stats` block for that pack. | The `_stats` block for one pack, stamped with the system that pack is for. |
1085
- | `defaultStats` | `function defaultStats()` | {object} The default `_stats` block, shared by every compiler. | The `_stats` block every compiler stamps on an entry it emits, built once. |
1086
- | `buildContentLinkIndex` | `buildContentLinkIndex(contentBase, router, { skipDirectories, config, records, problems })` | {{byShortcode: Map, types: Set}} From `buildWikilinkIndex`. | Indexes **every** note in the content tree so any pack compiler can resolve a wikilink to any other document. |
1087
- | `convertNoteWikilinks` | `convertNoteWikilinks(body, { type, id, pack, docPack, index, name, file, bodyLine, bodyColumn, lineMap })` | {{markdown: string, unresolved: Array<object>}} | Converts the wikilinks in one note's markdown, reporting any that have no target in the content tree. |
1088
- | `collectContentDocs` | `collectContentDocs(contentBase, { skipDirectories, config, records, problems })` | {Array<{fm: object, path: string, tld: string, folder: string, absPath: string}>} | Every note in the content tree, in the shape the `dataview` table expander searches: its frontmatter plus where it sits in the tree. |
1089
- | `expandNoteTables` | `function expandNoteTables(body,` | {{markdown: string, lineMap: Array<{line: number, generated: boolean}>}} The body with every table expanded, and where each emitted line came from — which is what lets a diagnostic about the expanded body name an authored position. | Expand the fenced `dataview` tables in one note's markdown, before wikilinks are resolved — so a generated cell may itself be a wikilink. |
1090
- | `folderFilename` | `function folderFilename(name, id)` | — | Builds a compendium-source filename for a folder JSON document: `folder_Name_id.json` with non-alphanumeric runs replaced by underscores. |
1091
- | `walkMarkdownTree` | `walkMarkdownTree(rootDir, { skipDirectories })` | generator yielding `{frontmatter, body, description, bodyLine?, bodyColumn?, file, absPath}` | walking a content tree and reading every markdown file's frontmatter and body; `skipDirectories` is required rather than defaulted, so two callers cannot silently disagree about the corpus's scope |
1129
+ Shared helpers for the pack compilers in `packages/package-build/`. The HeroicLands vault is authoritative for compendium item data. Pack compilers walk the vault, read markdown files with YAML frontmatter, and emit Foundry-compatible JSON. These helpers handle the common shape: markdown parsing, frontmatter access (including the nested `sohl:` block), filename generation, and slug normalization.
1130
+
1131
+ | Export | Signature | Returns | Use it when |
1132
+ | ------------------------- | ----------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
1133
+ | `md` | `const md` | — | The markdown renderer every surface shares. |
1134
+ | `renderFoundryMarkdown` | `function renderFoundryMarkdown(body)` | `string` — HTML with secret sections for GM passages. | Rendering a note body for a Foundry document. |
1135
+ | `parseMarkdownFile` | `function parseMarkdownFile(filePath)` | — | Parses a markdown file with YAML frontmatter. |
1136
+ | `assertStatedScope` | `function assertStatedScope(skipDirectories, who)` | {void} | Refuse a corpus read whose scope its caller did not state. |
1137
+ | `assertSuppliedCorpus` | `function assertSuppliedCorpus(records, who)` | {void} | Refuse a corpus read whose records its caller did not supply. |
1138
+ | `statedTemplatePriority` | `function statedTemplatePriority(fm, label,` | {number\|null} The priority, or `null` when the note is not a template or states nothing. | The template priority a note states, for a system that treats an unstated one as "not a template" rather than as an authoring error. |
1139
+ | `resolveTemplatePriority` | `function resolveTemplatePriority(fm, label,` | {number\|undefined} The template priority, or `undefined` when null. | Resolve the required `templatePriority` frontmatter for an Item/Actor entry (the archetype contract). |
1140
+ | `systemTemplatePriority` | `function systemTemplatePriority(fm, label)` | {number\|null} The template priority, or `null` for a document that is not a template. | The value a document's `system.templatePriority` carries, from the required `templatePriority` frontmatter (`archetype` is the legacy spelling). |
1141
+ | `makeFilename` | `function makeFilename(name, id)` | — | Generates a compendium-source filename: `Name_id.json` with non- alphanumeric runs replaced by underscores. |
1142
+ | `resolveImg` | `function resolveImg(raw, config` | {string \| null} the Foundry-relative path; `""` for a deliberate blank, and `null` when the note names no art at all. | Translate a content-relative image path into its Foundry-relative form. |
1143
+ | `resolveName` | `function resolveName(fm, defaultValue` | — | Resolves the display name from frontmatter, preferring `name.full`, falling back to `name` (if string), then `defaultValue`. |
1144
+ | `supportedCoreVersion` | `function supportedCoreVersion(config` | {string} The declared `compatibility.minimum`. | The oldest Foundry core this package supports, stamped into every compiled document as `_stats.coreVersion`. |
1145
+ | `buildStats` | `function buildStats(systemVersion` | {object} The `_stats` block. | Default `_stats` block for compiled compendium entries. |
1146
+ | `statsForPack` | `function statsForPack(packSystem, config` | {object} The `_stats` block for that pack. | The `_stats` block for one pack, stamped with the system that pack is for. |
1147
+ | `defaultStats` | `function defaultStats()` | {object} The default `_stats` block, shared by every compiler. | The `_stats` block every compiler stamps on an entry it emits, built once. |
1148
+ | `buildContentLinkIndex` | `buildContentLinkIndex(contentBase, router, { skipDirectories, config, records, problems })` | `{{byShortcode: Map, types: Set}}` From `buildWikilinkIndex`. | Indexes **every** note in the content tree so any pack compiler can resolve a wikilink to any other document. |
1149
+ | `convertNoteWikilinks` | `convertNoteWikilinks(body, { type, id, pack, docPack, index, name, file, bodyLine, bodyColumn, lineMap })` | `{{markdown: string, unresolved: Array<object>}}` | Converts the wikilinks in one note's markdown, reporting any that have no target in the content tree. |
1150
+ | `expandNoteTables` | `function expandNoteTables(body,` | `{{markdown: string, lineMap: Array<{line: number, generated: boolean}>}}` The body with every table expanded, and where each emitted line came from — which is what lets a diagnostic about the expanded body name an authored position. | Expand prepared SQL tables in one note's markdown, before wikilinks are resolved — so a generated cell may itself be a wikilink. |
1151
+ | `folderFilename` | `function folderFilename(name, id)` | — | Builds a compendium-source filename for a folder JSON document: `folder_Name_id.json` with non-alphanumeric runs replaced by underscores. |
1152
+ | `walkMarkdownTree` | `walkMarkdownTree(rootDir, { skipDirectories })` | generator yielding `{frontmatter, body, description, bodyLine?, bodyColumn?, file, absPath}` | walking a content tree and reading every markdown file's frontmatter and body; `skipDirectories` is required rather than defaulted, so two callers cannot silently disagree about the corpus's scope |
1092
1153
 
1093
1154
  ### `engine.itemRegistry`
1094
1155
 
@@ -1137,9 +1198,9 @@ Wikilink resolution for the pack compilers. Content notes link to one another wi
1137
1198
  | `resolveItemDocType` | `function resolveItemDocType(qualifier, types)` | {string\|null} The underlying document type, or `null` when the qualifier is not a virtual one. | Reads an accepted `doc<type>` input alias as its authored type. |
1138
1199
  | `readQualifier` | `function readQualifier(target, types, packages, noIndexPackages)` | {{type: string, shortcode: string, itemDoc: boolean, package?: string, system?: string, reason?: undefined} \| {reason: "unknown-type"\|"no-content-index", package?: string} \| null} The resolved qualifier; a `reason` when the target is definitely qualified but names no known type or no fetched index; or `null` when it is not an address at all. | Read a link target as a **qualified** `type-shortcode` reference, or report that it does not parse as one. |
1139
1200
  | `anchorPageId` | `function anchorPageId(noteId, anchorSlug)` | {string} A 16-character alphanumeric id. | The deterministic JournalEntryPage id for one anchor: SHA-256 of `"<noteId>-<anchorSlug>"`, base64-encoded, reduced to the 16 alphanumeric characters a Foundry id allows. |
1140
- | `buildWikilinkIndex` | `function buildWikilinkIndex(docs, packageId, foreign, contentPackage)` | {{byShortcode: Map<string, object>, types: Set<string>}} `types` is every type the tree actually contains, so a qualifier naming no real type can be told apart from a missing target. | Builds the link-resolution tables for a content tree. |
1201
+ | `buildWikilinkIndex` | `function buildWikilinkIndex(docs, packageId, foreign, contentPackage)` | `{{byShortcode: Map<string, object>, types: Set<string>}}` `types` is every type the tree actually contains, so a qualifier naming no real type can be told apart from a missing target. | Builds the link-resolution tables for a content tree. |
1141
1202
  | `resolveReference` | `function resolveReference(index, ref, hint)` | {{name?: string, uuid?: string, address?: string, subType?: string}\|undefined} The target, or `undefined` where nothing answers. | Resolve one reference — a bare shortcode, a short address or a canonical one — to what a compendium can use. |
1142
- | `convertWikilinks` | `function convertWikilinks(markdown,` | {{markdown: string, unresolved: Array<{link: string, target: string, offset: number, reason: string, packages?: string[], anchor?: string}>}} Each `reason` is one of {@link LINK_FINDING_REASONS}, the vocabulary all three resolvers share — `ambiguous` carries the claiming `packages` and `unknown-anchor` the section it named. | Rewrites every wikilink in a markdown body as a Foundry UUID enricher. |
1203
+ | `convertWikilinks` | `function convertWikilinks(markdown,` | `{{markdown: string, unresolved: Array<{link: string, target: string, offset: number, reason: string, packages?: string[], anchor?: string}>}}` Each `reason` is one of {@link LINK_FINDING_REASONS}, the vocabulary all three resolvers share — `ambiguous` carries the claiming `packages` and `unknown-anchor` the section it named. | Rewrites every wikilink in a markdown body as a Foundry UUID enricher. |
1143
1204
 
1144
1205
  ### `engine.wikilinkSyntax`
1145
1206
 
@@ -1161,7 +1222,9 @@ What a `[[…]]` **is**, before anything decides where it points. One authored l
1161
1222
 
1162
1223
  ### `engine.siteIndex`
1163
1224
 
1164
- **The address index a site build resolves its wikilinks against.** Every consumer that publishes a content tree as a website has to answer the same question — given `[[Something]]`, which page? — and every one of them answered it with its own copy of the same 150 lines. `sohl`'s and `sohl-thalorna`'s site builds still share 147 identical lines of it, comments and indentation aside. This is that shared half, lifted out whole.
1225
+ **The address index a site build resolves its wikilinks against.**
1226
+ `buildSiteIndex` gathers addressable pages and ambiguity findings. `wikiContext`
1227
+ provides the per-page context used when resolving a link such as `[[Something]]`.
1165
1228
 
1166
1229
  | Export | Signature | Returns | Use it when |
1167
1230
  | ------------------- | -------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------ |
@@ -1173,12 +1236,12 @@ What a `[[…]]` **is**, before anything decides where it points. One authored l
1173
1236
 
1174
1237
  The document tree a PDF is built from, and the plan it resolves to. The packs and the website both render the _whole_ content tree: every note becomes a document and a page, and the three surfaces agreeing about what the content is, is the point. **A book is not that.** It is a selection — a declared structure whose leaves pick notes out of the corpus by a `WHERE` clause, interleaved with prose that may not live in the content tree at all.
1175
1238
 
1176
- | Export | Signature | Returns | Use it when |
1177
- | ------------------- | ------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------ |
1178
- | `PRESENTATION_KEYS` | `const PRESENTATION_KEYS` | — | Presentation a node may declare, and that its descendants inherit. |
1179
- | `parseDocumentTree` | `function parseDocumentTree(raw,` | {{nodes: object[], findings: object[]}} The flattened tree and what was wrong with it. | Validate the raw tree and flatten it into nodes. |
1180
- | `runTreeFilters` | `async function runTreeFilters(nodes, db,` | {Promise<{selections: Map<string, object[]>, findings: object[]}>} | Run every filter, and report the ones that would not run. |
1181
- | `planDocument` | `function planDocument(nodes,` | {{entries: object[], links: Map<string, string>, stats: object}} The plan, the address→anchor map inbound wikilinks resolve through, and what the selection came to. | Resolve the flattened tree into the document plan. |
1239
+ | Export | Signature | Returns | Use it when |
1240
+ | ------------------- | ------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------ |
1241
+ | `PRESENTATION_KEYS` | `const PRESENTATION_KEYS` | — | Presentation a node may declare, and that its descendants inherit. |
1242
+ | `parseDocumentTree` | `function parseDocumentTree(raw,` | `{{nodes: object[], findings: object[]}}` The flattened tree and what was wrong with it. | Validate the raw tree and flatten it into nodes. |
1243
+ | `runTreeFilters` | `async function runTreeFilters(nodes, db,` | {Promise<{selections: Map<string, object[]>, findings: object[]}>} | Run every filter, and report the ones that would not run. |
1244
+ | `planDocument` | `function planDocument(nodes,` | `{{entries: object[], links: Map<string, string>, stats: object}}` The plan, the address→anchor map inbound wikilinks resolve through, and what the selection came to. | Resolve the flattened tree into the document plan. |
1182
1245
 
1183
1246
  ### `engine.pdfRender`
1184
1247
 
@@ -1217,7 +1280,7 @@ The content tree, built into a book. The I/O half of the PDF surface: it reads t
1217
1280
  | `buildPdf` | `async buildPdf({ config, out, version, compile })` | {Promise<object>} `{ built, reason, findings, typ, pdf, stats }`. | Build the book. |
1218
1281
  | `stagedImagePath` | `function stagedImagePath(src, config)` | {{from: string, to: string}\|null} The file, and where under the output directory it is staged. | The file on disk an authored image address names, or `null`. |
1219
1282
  | `stageBanners` | `function stageBanners(entries, config, outDir, findings)` | {Map<string, string>} Declared path → the staged file's path, relative to the `.typ`. | Copy every banner the document tree names into the output directory. |
1220
- | `compileTypst` | `function compileTypst(typPath, pdfPath, pdf` | {{ok: boolean, message: string, findings: object[]}} What happened. | Run Typst over the emitted source. |
1283
+ | `compileTypst` | `function compileTypst(typPath, pdfPath, pdf` | `{{ok: boolean, message: string, findings: object[]}}` What happened. | Run Typst over the emitted source. |
1221
1284
  | `typstArgs` | `function typstArgs(typPath, pdfPath, pdf)` | {string[]} The arguments, in order. | The command line the compile runs, as data. |
1222
1285
  | `typstWarnings` | `function typstWarnings(output)` | {object[]} One finding per warning the compiler wrote. | The compiler's own warnings, as findings. |
1223
1286
  | `BOOK_FONTS_PATH` | `const BOOK_FONTS_PATH` | {string} The directory holding them. | The faces the book is set in, shipped with this package. |
@@ -1265,29 +1328,29 @@ Macros pack compiler — produces JSON pack files for the "macros" Foundry compe
1265
1328
 
1266
1329
  **Map notes** — the markdown → Foundry `Scene` translation. A map note carries an _essence_: a curated, hand-owned subset of what a Scene record holds, exactly as a weapon note carries a weapon's essence rather than an Item's schema. Everything a Scene needs and nobody should have to author — the canvas defaults, the embedded `Level`, every derived region field — is synthesised here.
1267
1330
 
1268
- | Export | Signature | Returns | Use it when |
1269
- | ------------------------------ | ----------------------------------------------- | ------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------- |
1270
- | `isMapType` | `function isMapType(type)` | {boolean} True for a map type. | Whether a content note's type compiles into a Scene. |
1271
- | `MAP_SUBTYPE_PROFILES` | `const MAP_SUBTYPE_PROFILES` | — | Per-subtype canvas defaults, emitted **explicitly** on every scene. |
1272
- | `mapProfile` | `function mapProfile(subType)` | {object} The profile from {@link MAP_SUBTYPE_PROFILES}. | The canvas profile for a map subType. |
1273
- | `DEFAULT_LEVEL_ID` | `const DEFAULT_LEVEL_ID` | — | Foundry's own id for the level a scene is created with (`Scene.metadata.defaultLevelId`). |
1274
- | `regionDocId` | `function regionDocId(sceneId, key, pinned)` | {string} A 16-character Foundry id. | The id of one region within its scene. |
1275
- | `behaviorDocId` | `function behaviorDocId(regionId, key, pinned)` | {string} A 16-character Foundry id. | The id of one behaviour within its region. |
1276
- | `regionColor` | `function regionColor(key)` | {string} A CSS hex colour. | A region's highlight colour, derived from its key. |
1277
- | `assertPixelGeometry` | `function assertPixelGeometry(coords, geom)` | — | Reject geometry authored in grid squares where pixels belong. |
1278
- | `assertGridLocation` | `function assertGridLocation(at, geom)` | — | Reject a map pin authored in pixels where grid squares belong. |
1279
- | `wallRestrictions` | `function wallRestrictions(spec, label)` | {{move: number, sight: number, light: number, sound: number}} The Wall restriction fields. | Compile a wall's `blocks:` / `limits:` lists into Foundry's four numeric restriction fields. |
1280
- | `buildShape` | `function buildShape(spec, geom)` | {object} The Foundry shape record. | Compile one authored shape into a Foundry shape record. |
1281
- | `REGION_BEHAVIOR_TYPES` | `const REGION_BEHAVIOR_TYPES` | — | The behaviour types a map note may carry (v1). |
1282
- | `BANNED_REGION_BEHAVIOR_TYPES` | `const BANNED_REGION_BEHAVIOR_TYPES` | — | Behaviour types a map note may **never** carry, and why. |
1283
- | `buildScene` | `function buildScene(fm, ctx)` | {object} The Scene document, keyed for the pack. | Compile a map note into a Scene document, embedded documents and all. |
1284
- | `buildLevel` | `function buildLevel(sohl, sceneId, img` | {object} The Level document, keyed for the pack. | Synthesise the scene's single embedded Level from `img:` / `overlay:`. |
1285
- | `buildWalls` | `function buildWalls(sohl, geom, ctx)` | {object[]} The Wall documents. | Compile the `walls:` and `doors:` blocks into Wall documents. |
1286
- | `buildLights` | `function buildLights(sohl, geom, ctx)` | {object[]} The AmbientLight documents. | Compile the `lights:` block into AmbientLight documents. |
1287
- | `buildTiles` | `function buildTiles(sohl, geom, ctx)` | {object[]} The Tile documents. | Compile the `tiles:` block into Tile documents. |
1288
- | `buildSounds` | `function buildSounds(sohl, geom, ctx)` | {object[]} The AmbientSound documents. | Compile the `sounds:` block into AmbientSound documents. |
1289
- | `buildLocations` | `function buildLocations(sohl, geom, ctx)` | {object[]} The Note documents. | Compile the `locations:` block into Note documents — the map pins. |
1290
- | `buildRegions` | `function buildRegions(sohl, geom, ctx)` | {object[]} The Region documents. | Compile the `regions:` block into Region documents with their behaviours. |
1331
+ | Export | Signature | Returns | Use it when |
1332
+ | ------------------------------ | ----------------------------------------------- | -------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------- |
1333
+ | `isMapType` | `function isMapType(type)` | {boolean} True for a map type. | Whether a content note's type compiles into a Scene. |
1334
+ | `MAP_SUBTYPE_PROFILES` | `const MAP_SUBTYPE_PROFILES` | — | Per-subtype canvas defaults, emitted **explicitly** on every scene. |
1335
+ | `mapProfile` | `function mapProfile(subType)` | {object} The profile from {@link MAP_SUBTYPE_PROFILES}. | The canvas profile for a map subType. |
1336
+ | `DEFAULT_LEVEL_ID` | `const DEFAULT_LEVEL_ID` | — | Foundry's own id for the level a scene is created with (`Scene.metadata.defaultLevelId`). |
1337
+ | `regionDocId` | `function regionDocId(sceneId, key, pinned)` | {string} A 16-character Foundry id. | The id of one region within its scene. |
1338
+ | `behaviorDocId` | `function behaviorDocId(regionId, key, pinned)` | {string} A 16-character Foundry id. | The id of one behaviour within its region. |
1339
+ | `regionColor` | `function regionColor(key)` | {string} A CSS hex colour. | A region's highlight colour, derived from its key. |
1340
+ | `assertPixelGeometry` | `function assertPixelGeometry(coords, geom)` | — | Reject geometry authored in grid squares where pixels belong. |
1341
+ | `assertGridLocation` | `function assertGridLocation(at, geom)` | — | Reject a map pin authored in pixels where grid squares belong. |
1342
+ | `wallRestrictions` | `function wallRestrictions(spec, label)` | `{{move: number, sight: number, light: number, sound: number}}` The Wall restriction fields. | Compile a wall's `blocks:` / `limits:` lists into Foundry's four numeric restriction fields. |
1343
+ | `buildShape` | `function buildShape(spec, geom)` | {object} The Foundry shape record. | Compile one authored shape into a Foundry shape record. |
1344
+ | `REGION_BEHAVIOR_TYPES` | `const REGION_BEHAVIOR_TYPES` | — | The behaviour types a map note may carry (v1). |
1345
+ | `BANNED_REGION_BEHAVIOR_TYPES` | `const BANNED_REGION_BEHAVIOR_TYPES` | — | Behaviour types a map note may **never** carry, and why. |
1346
+ | `buildScene` | `function buildScene(fm, ctx)` | {object} The Scene document, keyed for the pack. | Compile a map note into a Scene document, embedded documents and all. |
1347
+ | `buildLevel` | `function buildLevel(sohl, sceneId, img` | {object} The Level document, keyed for the pack. | Synthesise the scene's single embedded Level from `img:` / `overlay:`. |
1348
+ | `buildWalls` | `function buildWalls(sohl, geom, ctx)` | {object[]} The Wall documents. | Compile the `walls:` and `doors:` blocks into Wall documents. |
1349
+ | `buildLights` | `function buildLights(sohl, geom, ctx)` | {object[]} The AmbientLight documents. | Compile the `lights:` block into AmbientLight documents. |
1350
+ | `buildTiles` | `function buildTiles(sohl, geom, ctx)` | {object[]} The Tile documents. | Compile the `tiles:` block into Tile documents. |
1351
+ | `buildSounds` | `function buildSounds(sohl, geom, ctx)` | {object[]} The AmbientSound documents. | Compile the `sounds:` block into AmbientSound documents. |
1352
+ | `buildLocations` | `function buildLocations(sohl, geom, ctx)` | {object[]} The Note documents. | Compile the `locations:` block into Note documents — the map pins. |
1353
+ | `buildRegions` | `function buildRegions(sohl, geom, ctx)` | {object[]} The Region documents. | Compile the `regions:` block into Region documents with their behaviours. |
1291
1354
 
1292
1355
  ### `engine.scenes`
1293
1356
 
@@ -1376,14 +1439,12 @@ The SoHL-specific half of the toolchain: the knowledge of the Song of Heroic Lan
1376
1439
  Every module beneath `./sohl` is also reachable as its own entry point, e.g. `@heroiclands/package-build/sohl/being-info`.
1377
1440
 
1378
1441
  ```js
1379
- import { isBeing, GEAR_TYPE_TO_KEY, deriveBeingInfo } from "@heroiclands/package-build/sohl";
1442
+ import { isBeing, GEAR_TYPE_TO_KEY } from "@heroiclands/package-build/sohl";
1380
1443
 
1381
1444
  console.log(isBeing({ type: "being" }));
1382
1445
  // -> true
1383
1446
  console.log(GEAR_TYPE_TO_KEY.weapongear);
1384
1447
  // -> "weapons"
1385
- console.log(deriveBeingInfo({ items: [] }, new Map()));
1386
- // -> { items: [] }
1387
1448
  ```
1388
1449
 
1389
1450
  ### `sohl.itemBuilders`
@@ -1448,15 +1509,14 @@ The `sohl` knowledgebase's own body pass: a rewrite driven by a TypeDoc symbol m
1448
1509
 
1449
1510
  ### Flat exports (not under a namespace)
1450
1511
 
1451
- | Export | Signature | Returns | Use it when |
1452
- | ----------------------- | ------------------------------ | --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
1453
- | `DEFAULT_ITEM_ART` | `const DEFAULT_ITEM_ART` | — | reading the full map of default item artwork keyed by SoHL Item document subtype — the single source both the compendium builder and the runtime `SohlItem.getDefaultArtwork` read |
1454
- | `defaultItemArt` | `defaultItemArt(type)` | `string` — the default image path | getting the default art path for a document subtype, throwing (fail-fast) rather than defaulting silently when the type is unknown |
1455
- | `AFFILIATION_STANDINGS` | `const AFFILIATION_STANDINGS` | — | reading the closed set of stances an authored `relation` map may use between two affiliations |
1456
- | `BEING_TYPE` | `const BEING_TYPE` | — | the one note `type` value (`"being"`) whose pages carry a being info block; the retired `character`/`creature` spellings are deliberately not accepted here |
1457
- | `GEAR_TYPE_TO_KEY` | `const GEAR_TYPE_TO_KEY` | — | mapping a gear note type (`weapongear`, `armorgear`, …) to the sidebar heading it displays under (`weapons`, `armor`, …) |
1458
- | `isBeing` | `isBeing(fm)` | `boolean` | checking whether a note's frontmatter describes a being, using the one shared definition instead of a per-repository copy |
1459
- | `deriveBeingInfo` | `deriveBeingInfo(sohl, index)` | `object\|null\|undefined` | deriving a being's info-block fields (`skills`, `gear`, `spells`, `talents`) from its raw embedded `sohl.items[]`, resolved against a content index; authored values always win over derived ones |
1512
+ | Export | Signature | Returns | Use it when |
1513
+ | ----------------------- | ----------------------------- | --------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
1514
+ | `DEFAULT_ITEM_ART` | `const DEFAULT_ITEM_ART` | — | reading the full map of default item artwork keyed by SoHL Item document subtype — the single source both the compendium builder and the runtime `SohlItem.getDefaultArtwork` read |
1515
+ | `defaultItemArt` | `defaultItemArt(type)` | `string` — the default image path | getting the default art path for a document subtype, throwing (fail-fast) rather than defaulting silently when the type is unknown |
1516
+ | `AFFILIATION_STANDINGS` | `const AFFILIATION_STANDINGS` | — | reading the closed set of stances an authored `relation` map may use between two affiliations |
1517
+ | `BEING_TYPE` | `const BEING_TYPE` | — | identifying the note type `"being"` |
1518
+ | `GEAR_TYPE_TO_KEY` | `const GEAR_TYPE_TO_KEY` | — | mapping each gear note type to its infobox group |
1519
+ | `isBeing` | `isBeing(fm)` | `boolean` | checking whether a note's frontmatter describes a being |
1460
1520
 
1461
1521
  ## `./hm3`
1462
1522
 
@@ -1567,14 +1627,13 @@ const config = defineConfig({
1567
1627
  | `DEFAULT_PATHS` | `const DEFAULT_PATHS` | — | reading the conventional directory layout a build reads from and writes to, relative to `rootDir` |
1568
1628
  | `DEFAULT_ADDRESS_SCHEME` | `const DEFAULT_ADDRESS_SCHEME` | — | reading an unconfigured repository's address-scheme defaults (`prefix`, where the content tree mounts inside the package) |
1569
1629
  | `RETIRED_ADDRESS_KEYS` | `const RETIRED_ADDRESS_KEYS` | — | reading which address-scheme keys a configuration may no longer declare (e.g. `landing`) — declaring one is a refusal, not a silent no-op |
1570
- | `SITE_MODES` | `const SITE_MODES` | — | reading the publishing modes `publish.site` may name, weakest first |
1571
1630
  | `DERIVED_SYSTEM_VERSION` | `const DERIVED_SYSTEM_VERSION` | — | the loader-only symbol key `defineConfig` uses internally to receive a resolved system version; not something a configuration author writes |
1572
- | `publishesContentPages` | `publishesContentPages(config)` | `boolean` | checking whether a resolved configuration publishes the pages its content tree compiles to — the one question the site build and the content index both need answered identically |
1631
+ | `publishesContentPages` | `publishesContentPages(config)` | `boolean` | checking whether the authored content tree contains notes beyond its homepage, shared by site, PDF, index, and Foundry address emission |
1573
1632
  | `DERIVED_HUGO_KEYS` | `const DERIVED_HUGO_KEYS` | — | reading which Hugo keys `site.hugo` may not declare because the site build generates them, each naming its source (declaring one is an error naming the key); a dotted key covers everything beneath it |
1574
1633
 
1575
1634
  ## `./config`
1576
1635
 
1577
- The per-repository **packaging** configuration — `packageBuild:` within the same `package-build.config.yaml` that `content-config` validates the rest of. Read through the content half's loader rather than a second file, so `packageKind` and `foundryPackage` are stated once. The two halves validate independently but share one document.
1636
+ The per-repository **packaging** configuration is the `packageBuild:` section of `package-build.config.yaml`. The content configuration loader reads the same file, so `packageKind` and `foundryPackage` are declared once. The content and packaging validators each check their own keys.
1578
1637
 
1579
1638
  ```js
1580
1639
  // Run from a consuming repository's root, where package-build.config.yaml lives.
@@ -1589,12 +1648,12 @@ console.log(config.stageDir);
1589
1648
  | `DERIVED_MANIFEST_KEYS` | `const DERIVED_MANIFEST_KEYS` | — | reading which manifest keys a repository may not declare because the build derives them (declaring one is an error naming the key) |
1590
1649
  | `resolvePackageBuildConfig` | `resolvePackageBuildConfig(shared)` | `Readonly<PackageBuildConfig>` | validating the `packageBuild:` section from an already-loaded shared configuration — the pure half, usable without touching disk |
1591
1650
  | `loadPackageBuildConfig` | `loadPackageBuildConfig()` | `Readonly<PackageBuildConfig>` | reading and validating the repository's resolved package-build configuration from disk, read fresh on each call rather than cached at import |
1592
- | `checkHomepage` | `checkHomepage(homepage, contentPackage)` | `void` | validating a resolved `homepage` against `contentPackage` — called by `content-build site` before the generated `baseURL` is written, not by `resolvePackageBuildConfig` itself |
1651
+ | `checkHomepage` | `checkHomepage(homepage, contentPackage)` | `void` | validating a resolved `homepage` against `contentPackage` — called by `package-build site` before the generated `baseURL` is written, not by `resolvePackageBuildConfig` itself |
1593
1652
  | `fail` | `fail(where, problem)` | `never` | rejecting a configured value from a sibling module, naming the dotted key path it was written under, so the loader can resolve it to a line and column |
1594
1653
 
1595
1654
  ## `./prettier`
1596
1655
 
1597
- The shared Prettier configuration, published as a Prettier config module so an editor's format-on-save agrees with `content-build format`, which already applies these rules without a consumer declaring anything.
1656
+ The shared Prettier configuration, published as a Prettier config module so an editor's format-on-save agrees with `package-build format`, which already applies these rules without a consumer declaring anything.
1598
1657
 
1599
1658
  ```js
1600
1659
  // prettier.config.mjs
@@ -1607,7 +1666,7 @@ export { default } from "@heroiclands/package-build/prettier";
1607
1666
 
1608
1667
  ## `./markdownlint`
1609
1668
 
1610
- The shared markdownlint rules, as a markdownlint-cli2 options module, applied without a consumer declaring anything by `content-build markdown`. Exists for an editor's markdownlint extension, and for a consumer that wants to extend rather than replace the set.
1669
+ The shared markdownlint rules, as a markdownlint-cli2 options module, applied without a consumer declaring anything by `package-build markdown`. Exists for an editor's markdownlint extension, and for a consumer that wants to extend rather than replace the set.
1611
1670
 
1612
1671
  ```js
1613
1672
  // .markdownlint-cli2.mjs
@@ -1841,6 +1900,7 @@ console.log(missingSources([["does/not/exist", "x"]]));
1841
1900
  | `missingSources` | `missingSources(entries, cwd = process.cwd())` | `string[]` | checking a whole `[source, dest]` list for absent sources up front, so every problem is reported at once rather than one rebuild at a time |
1842
1901
  | `copyTree` | `copyTree(src, dest, { transform } = {})` | `number` — files written | recursively copying a file or directory, optionally rewriting each file's content as it's staged instead of copying bytes verbatim |
1843
1902
  | `stageAssets` | `stageAssets(entries, { cwd = process.cwd(), transform } = {})` | `{entries: number, files: number}` | copying every listed `[source, dest]` pair into the stage, refusing to start at all if any source is absent |
1903
+ | `resetStage` | `resetStage(root, stageDir)` | `boolean` — whether stage existed | clearing the assembled package before a complete build while preserving other generated outputs |
1844
1904
  | `cleanBuildArtifacts` | `cleanBuildArtifacts(root, { extra = [], includeNodeModules = false } = {})` | `string[]` — directories removed | removing the build artefacts a repository regenerates, safely repeatable since an already-clean directory is not an error |
1845
1905
 
1846
1906
  ## `./templates`