@heroiclands/package-build 22.26.0 → 22.28.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 (70) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/README.md +3 -1
  3. package/assets/styles/content-base.css +46 -3
  4. package/bin/content-commands.mjs +53 -5
  5. package/ci/publish-npm.mjs +49 -0
  6. package/docs/api.md +44 -24
  7. package/docs/authoring/first-note.md +1 -1
  8. package/docs/authoring/frontmatter.md +2 -2
  9. package/docs/authoring/links-and-markup.md +96 -149
  10. package/docs/content-format.md +7 -3
  11. package/docs/guides/government-migration.md +52 -0
  12. package/docs/guides/site.md +5 -5
  13. package/docs/reference/format-details.md +131 -230
  14. package/docs/reference/note-types.md +27 -29
  15. package/docs/reference/theme-pages.md +31 -29
  16. package/engine/anchors.mjs +85 -13
  17. package/engine/art-fields.mjs +1 -1
  18. package/engine/base-compiler.mjs +9 -1
  19. package/engine/bundles.mjs +1 -1
  20. package/engine/code-fences.mjs +6 -2
  21. package/engine/content-alerts.mjs +195 -0
  22. package/engine/content-blocks.mjs +119 -241
  23. package/engine/content-embeds.mjs +1 -1
  24. package/engine/content-figures.mjs +166 -230
  25. package/engine/content-format-check.mjs +3 -2
  26. package/engine/content-format.yaml +0 -5
  27. package/engine/content-images.mjs +3 -3
  28. package/engine/content-poetry.mjs +141 -0
  29. package/engine/content-spans.mjs +177 -0
  30. package/engine/folder-notes.mjs +2 -3
  31. package/engine/foundry-entries.mjs +2 -0
  32. package/engine/frontmatter-lint.mjs +2 -1
  33. package/engine/heading-attributes.mjs +1 -1
  34. package/engine/helpers.mjs +8 -3
  35. package/engine/holdings.mjs +165 -273
  36. package/engine/index.mjs +2 -2
  37. package/engine/infobox.mjs +1 -1
  38. package/engine/journals.mjs +8 -6
  39. package/engine/markdown-expressions.mjs +9 -3
  40. package/engine/metadata-index.mjs +2 -3
  41. package/engine/note-vocabulary.mjs +19 -15
  42. package/engine/pdf-build.mjs +1 -1
  43. package/engine/pdf-render.mjs +90 -31
  44. package/engine/populations.mjs +17 -83
  45. package/engine/site-build.mjs +44 -16
  46. package/engine/site-preview.mjs +22 -6
  47. package/engine/stub-lint.mjs +20 -64
  48. package/engine/wikilinks.mjs +18 -4
  49. package/githooks/pre-push +1 -1
  50. package/hugo-theme/layouts/partials/holdings/entry.html +2 -2
  51. package/hugo-theme/layouts/partials/holdings.html +14 -14
  52. package/hugo-theme/static/css/style.css +40 -2
  53. package/package.json +3 -1
  54. package/sohl/item-fields.mjs +0 -16
  55. package/types/engine/anchors.d.mts +13 -4
  56. package/types/engine/art-fields.d.mts +1 -1
  57. package/types/engine/code-fences.d.mts +3 -1
  58. package/types/engine/content-alerts.d.mts +62 -0
  59. package/types/engine/content-blocks.d.mts +3 -10
  60. package/types/engine/content-embeds.d.mts +1 -1
  61. package/types/engine/content-figures.d.mts +13 -20
  62. package/types/engine/content-images.d.mts +1 -1
  63. package/types/engine/content-poetry.d.mts +32 -0
  64. package/types/engine/content-spans.d.mts +18 -0
  65. package/types/engine/holdings.d.mts +59 -105
  66. package/types/engine/markdown-expressions.d.mts +1 -1
  67. package/types/engine/note-vocabulary.d.mts +12 -4
  68. package/types/engine/populations.d.mts +4 -13
  69. package/types/engine/site-build.d.mts +3 -3
  70. package/types/engine/wikilinks.d.mts +5 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,22 @@
1
1
  # @heroiclands/package-build
2
2
 
3
+ ## 22.28.0
4
+
5
+ ### Minor Changes
6
+
7
+ Notes now require a nonempty body, including drafts, folders and homepages. Short written notes still warn unless tagged as drafts.
8
+
9
+ ## 22.27.0
10
+
11
+ ### Minor Changes
12
+
13
+ - Governing bodies list the places they govern from each place’s stated government, keeping those lists consistent with place descriptions.
14
+ - Tables that listed governing bodies through affiliation territory lists must query each place’s government instead.
15
+ - Captions now precede the item they describe; use `:@` for numbered captions or `:` for captions without numbers.
16
+ - Write poetry in a `poetry` code fence, and use attributed divs and inline spans to group or style text.
17
+ - Replace former figure wrappers with leading captions; the old figure and poetry div fences are no longer supported.
18
+ - Blockquote alerts provide note, tip, important and warning callouts; the former info and warn div fences are removed.
19
+
3
20
  ## 22.26.0
4
21
 
5
22
  ### Minor Changes
package/README.md CHANGED
@@ -46,6 +46,8 @@ The documentation lives in the Markdown files in this repository. Start with [th
46
46
  | Error locations and severities | [Diagnostics](docs/diagnostics.md) |
47
47
  | Architectural rationale | [Design](docs/design.md) |
48
48
 
49
- Place notes may declare a governing affiliation with `data.government`, or explicit `null` for complete anarchy. Positive population with an omitted government produces an advisory. See [place governments](docs/authoring/frontmatter.md#place-governments).
49
+ Place notes may declare a governing affiliation with `data.government` (`Address`, default target type `affiliation`), or explicit `null` for complete anarchy. Positive population with an omitted government produces an advisory. See [place governments](docs/authoring/frontmatter.md#place-governments) and [government migration](docs/guides/government-migration.md).
50
+
51
+ Leading captions, generic fenced divs, inline spans, and poetry code fences are described in [links and markup](docs/authoring/links-and-markup.md).
50
52
 
51
53
  The package is GPL-3.0-or-later for code and CC-BY-SA-4.0 for original content. See [LICENSE.md](LICENSE.md).
@@ -152,9 +152,8 @@
152
152
  }
153
153
 
154
154
  /*
155
- * Named body blocks. `:::info` and `:::warn` emit a `<section>` carrying the
156
- * block's name as its class, so the appearance belongs here rather than in an
157
- * inline `style` a consumer cannot override.
155
+ * Historical info/warn section classes remain available for styled prose.
156
+ * New alerts use the separate alert classes below.
158
157
  *
159
158
  * `secret` is deliberately absent: Foundry styles `section.secret` itself and
160
159
  * owns the reveal control on it, and a rule here would fight both.
@@ -205,3 +204,47 @@
205
204
  --pb-block-edge: #7a5100;
206
205
  }
207
206
  }
207
+
208
+ /* Alerts share ordinary body colors, with an accent on their edge and title. */
209
+ :is(.journal-entry-page, .journal-entry-content, .editor-content, .prosemirror) .alert {
210
+ border-inline-start: 0.25rem solid var(--pb-alert-color);
211
+ padding: 0.5rem 1rem;
212
+ margin: 1rem 0;
213
+ color: inherit;
214
+ background: transparent;
215
+ }
216
+ :is(.journal-entry-page, .journal-entry-content, .editor-content, .prosemirror) .alert-title {
217
+ display: flex;
218
+ align-items: center;
219
+ gap: 0.5em;
220
+ margin: 0 0 0.5rem;
221
+ font-weight: 600;
222
+ color: var(--pb-alert-color);
223
+ }
224
+ :is(.journal-entry-page, .journal-entry-content, .editor-content, .prosemirror) .alert-icon {
225
+ width: 1em;
226
+ height: 1em;
227
+ flex-shrink: 0;
228
+ fill: currentColor;
229
+ }
230
+ :is(.journal-entry-page, .journal-entry-content, .editor-content, .prosemirror)
231
+ .alert
232
+ > :last-child {
233
+ margin-bottom: 0;
234
+ }
235
+ :is(.journal-entry-page, .journal-entry-content, .editor-content, .prosemirror) .alert-note {
236
+ --pb-alert-color: #0969da;
237
+ --pb-alert-color: light-dark(#0969da, #4493f8);
238
+ }
239
+ :is(.journal-entry-page, .journal-entry-content, .editor-content, .prosemirror) .alert-tip {
240
+ --pb-alert-color: #1a7f37;
241
+ --pb-alert-color: light-dark(#1a7f37, #3fb950);
242
+ }
243
+ :is(.journal-entry-page, .journal-entry-content, .editor-content, .prosemirror) .alert-important {
244
+ --pb-alert-color: #8250df;
245
+ --pb-alert-color: light-dark(#8250df, #ab7df8);
246
+ }
247
+ :is(.journal-entry-page, .journal-entry-content, .editor-content, .prosemirror) .alert-warning {
248
+ --pb-alert-color: #9a6700;
249
+ --pb-alert-color: light-dark(#9a6700, #d29922);
250
+ }
@@ -226,7 +226,12 @@ const SHIPPED_ITEM_FIELDS = { sohl: ITEM_FIELDS, hm3: HM3_ITEM_FIELDS };
226
226
  /** Register the content operations on the package command line. */
227
227
  export function registerContentCommands(cli) {
228
228
  return cli
229
- .command(withIndexPreflight(packageCommand(), (_config, argv) => argv.action === "compile"))
229
+ .command(
230
+ withIndexPreflight(
231
+ withBodyPreflight(packageCommand(), (_config, argv) => argv.action === "compile"),
232
+ (_config, argv) => argv.action === "compile",
233
+ ),
234
+ )
230
235
  .command(depsCommand())
231
236
  .command(docsCommand())
232
237
  .command(withIndexPreflight(lintCommand()))
@@ -235,11 +240,17 @@ export function registerContentCommands(cli) {
235
240
  .command(withIndexPreflight(linksCommand()))
236
241
  .command(formatCommand())
237
242
  .command(markdownCommand())
238
- .command(contentIndexCommand())
239
- .command(withIndexPreflight(siteCommand(), publishesContentPages))
243
+ .command(
244
+ withBodyPreflight(
245
+ contentIndexCommand(),
246
+ () => true,
247
+ (config, argv) => argv.root || config.paths.content,
248
+ ),
249
+ )
250
+ .command(withIndexPreflight(withBodyPreflight(siteCommand()), publishesContentPages))
240
251
  .command(
241
252
  withIndexPreflight(
242
- pdfCommand(),
253
+ withBodyPreflight(pdfCommand(), (config) => Boolean(config.pdf)),
243
254
  (config) => config.pdf && publishesContentPages(config),
244
255
  ),
245
256
  )
@@ -380,6 +391,43 @@ function proseCommand() {
380
391
  };
381
392
  }
382
393
 
394
+ /** Refuse invalid note bodies before emitting metadata, packs, pages or a book. */
395
+ function withBodyPreflight(
396
+ command,
397
+ shouldCheck = () => true,
398
+ contentRoot = (config) => config.paths.content,
399
+ ) {
400
+ const handler = command.handler;
401
+ return {
402
+ ...command,
403
+ handler: async (argv) => {
404
+ try {
405
+ const config = loadPackConfig();
406
+ if (shouldCheck(config, argv)) {
407
+ const contentBase = contentRoot(config, argv);
408
+ if (!fs.existsSync(contentBase)) return handler(argv);
409
+ const records = indexRecordsFor({ config, contentBase });
410
+ const { findings } = lintNoteStates(contentBase, {
411
+ records,
412
+ contentPackage: config.contentPackage,
413
+ });
414
+ const errors = findings.filter((finding) => finding.severity === "error");
415
+ if (errors.length) {
416
+ for (const finding of errors) emitDiagnostic(finding);
417
+ process.exitCode = 1;
418
+ return;
419
+ }
420
+ }
421
+ } catch (err) {
422
+ reportFailure(err);
423
+ process.exitCode = 1;
424
+ return;
425
+ }
426
+ return handler(argv);
427
+ },
428
+ };
429
+ }
430
+
383
431
  /** Check declared dependency indexes before an index-consuming command does any work. */
384
432
  function withIndexPreflight(command, shouldCheck = () => true) {
385
433
  const handler = command.handler;
@@ -1167,7 +1215,7 @@ function lintCommand() {
1167
1215
  skipDirectories: config.skipDirectories,
1168
1216
  });
1169
1217
 
1170
- // Whether every empty body is a stub on purpose, and how
1218
+ // Whether every note has a body, and how
1171
1219
  // finished the tree is. The counts and the oldest drafts are
1172
1220
  // prose rather than findings: a note nobody has finished is not
1173
1221
  // wrong, and 464 warnings a reader cannot clear is how a report
@@ -0,0 +1,49 @@
1
+ /*
2
+ * This file is part of the Song of Heroic Lands (SoHL) system for Foundry VTT.
3
+ * Copyright (c) 2024-2026 Tom Rodriguez ("Toasty") — <toasty@heroiclands.org>
4
+ *
5
+ * This work is licensed under the GNU General Public License v3.0 (GPLv3).
6
+ * You may copy, modify, and distribute it under the terms of that license.
7
+ *
8
+ * For full terms, see the LICENSE.md file in the project root or visit:
9
+ * https://www.gnu.org/licenses/gpl-3.0.html
10
+ *
11
+ * SPDX-License-Identifier: GPL-3.0-or-later
12
+ */
13
+
14
+ /**
15
+ * Publish this package to npm, with provenance wherever it can be generated.
16
+ *
17
+ * `publishConfig.provenance` is `true`, and npm refuses a publish with
18
+ * `Automatic provenance generation not supported for provider: null` anywhere
19
+ * that cannot mint a Sigstore identity. That is a CI runner holding an OIDC
20
+ * token: GitHub Actions with `id-token: write`, which exposes
21
+ * `ACTIONS_ID_TOKEN_REQUEST_URL`, or GitLab CI with `SIGSTORE_ID_TOKEN`.
22
+ * Everywhere else — a desk — this passes `--provenance=false`, so the publish
23
+ * goes ahead unattested rather than failing.
24
+ *
25
+ * Arguments after `--` reach `npm publish` unchanged, so
26
+ * `npm run deploy:npm -- --dry-run` shows the tarball without publishing it.
27
+ * `prepack` builds the declarations and the asset index on the way.
28
+ *
29
+ * Not a `package-build` subcommand: it publishes this package and no other.
30
+ */
31
+
32
+ import { spawnSync } from "node:child_process";
33
+
34
+ const env = process.env;
35
+ const provenance =
36
+ (env.GITHUB_ACTIONS === "true" && Boolean(env.ACTIONS_ID_TOKEN_REQUEST_URL)) ||
37
+ (env.GITLAB_CI === "true" && Boolean(env.SIGSTORE_ID_TOKEN));
38
+
39
+ if (!provenance) {
40
+ console.log("deploy:npm: no OIDC identity here; publishing without provenance.");
41
+ }
42
+
43
+ const args = ["publish", ...(provenance ? ["--provenance"] : ["--provenance=false"])];
44
+ const result = spawnSync("npm", [...args, ...process.argv.slice(2)], {
45
+ stdio: "inherit",
46
+ shell: process.platform === "win32",
47
+ });
48
+ if (result.error) throw result.error;
49
+ process.exit(result.status ?? 1);
package/docs/api.md CHANGED
@@ -372,6 +372,7 @@ An addressed content note accepts exactly these top-level keys, in this order: `
372
372
  | `subTypeCharsetMessage` | `subTypeCharsetMessage(value)` | `string` | building the message for a `subType` outside the address charset |
373
373
  | `typeCharsetMessage` | `typeCharsetMessage(type)` | `string` | building the message for a `type` outside the address charset |
374
374
  | `assertVocabularyCharset` | `assertVocabularyCharset(vocabulary, where)` | throws | refusing a vocabulary declaration whose type or subType breaks the address charset |
375
+ | `isLegacyDataField` | `isLegacyDataField(type, key)` | `boolean` | testing accepted but ignored transitional affiliation `data.domains` input |
375
376
  | `dataFields` | `dataFields(type, vocabulary)` | `readonly DataFieldSpec[] \| undefined` | looking up the `data:` keys a note type may carry |
376
377
  | `subTypes` | `subTypes(type, vocabulary)` | `readonly string[] \| null \| undefined` | looking up the closed `subType` values a note type declares |
377
378
 
@@ -440,16 +441,16 @@ Drawing a declared infobox: HTML for separate Foundry pages appended after autho
440
441
 
441
442
  The three states of a note — **stub**, **draft**, **full** — and the one observable fact they rest on. A note whose body is empty is a stub: it carries its facts into the index and is queryable there, and it has no page, no address and no link target. Nothing is stored: the ladder is derived once in the SQL `entries` view and no note can assert its own state.
442
443
 
443
- | Export | Signature | Returns | Use it when |
444
- | --------------------- | ------------------------------------------- | --------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- |
445
- | `NOTE_STATES` | `const NOTE_STATES` | `ReadonlyArray<string>` | naming the three states in ladder order |
446
- | `isEmptyBody` | `isEmptyBody(body)` | `boolean` | asking the exact question: is everything after the closing frontmatter fence whitespace? |
447
- | `isStubbableType` | `isStubbableType(type, vocabulary)` | `boolean` | asking whether an empty body suppresses this type's page; `folder` and `homepage` are structural and exempt |
448
- | `isStubNote` | `isStubNote(frontmatter, body, vocabulary)` | `boolean` | deciding whether one note is a stub, which is what the index and the site build both ask |
449
- | `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 |
450
- | `PLACEHOLDER_PHRASES` | `const PLACEHOLDER_PHRASES` | `ReadonlyArray<string>` | reading the phrases that stand in for prose nobody has written |
451
- | `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 |
452
- | `bodyWordCount` | `bodyWordCount(body)` | `number` | counting rendered prose, with wikilinks as one word and closed dashes, slashes, and ellipses separating adjacent words |
444
+ | Export | Signature | Returns | Use it when |
445
+ | --------------------- | ------------------------------------------- | --------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
446
+ | `NOTE_STATES` | `const NOTE_STATES` | `ReadonlyArray<string>` | naming the three states in ladder order |
447
+ | `isEmptyBody` | `isEmptyBody(body)` | `boolean` | asking the exact question: is everything after the closing frontmatter fence whitespace? |
448
+ | `isStubbableType` | `isStubbableType(type, vocabulary)` | `boolean` | asking whether an empty body suppresses this type's page; `folder` and `homepage` retain their addresses in this classification, but still require bodies |
449
+ | `isStubNote` | `isStubNote(frontmatter, body, vocabulary)` | `boolean` | deciding whether one note is a stub, which is what the index and the site build both ask |
450
+ | `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 |
451
+ | `PLACEHOLDER_PHRASES` | `const PLACEHOLDER_PHRASES` | `ReadonlyArray<string>` | reading the phrases that stand in for prose nobody has written |
452
+ | `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 |
453
+ | `bodyWordCount` | `bodyWordCount(body)` | `number` | counting rendered prose, with wikilinks as one word and closed dashes, slashes, and ellipses separating adjacent words |
453
454
 
454
455
  ### `engine.draftNotice`
455
456
 
@@ -470,7 +471,7 @@ What a draft note says about itself, in one sentence, on every surface it reache
470
471
 
471
472
  ### `engine.stubLint`
472
473
 
473
- An empty body must be deliberate. Four rules hold the state honest — a stub states what it is, a stub is not tagged `draft`, a body that renders to nothing is an abandoned draft, and a short unmarked body is asked whether it forgot its marker — and two reports say what the rules cannot decide.
474
+ Every typed note must have a nonempty body. Empty and whitespace-only bodies are errors regardless of description or `draft` tag, including structural types such as `folder` and `homepage`. Placeholder-only bodies also produce errors. Nonempty bodies with fewer than 25 prose words produce warnings unless tagged `draft`. Untyped vault scaffolding is excluded. The lint, content-index, package compile, site and PDF commands enforce body errors before publishing. The state classification helpers still describe incomplete records for inspection; their classifications do not exempt notes from validation.
474
475
 
475
476
  | Export | Signature | Returns | Use it when |
476
477
  | ------------------ | ---------------------------------------------------------- | --------------------------------------------------- | --------------------------------------------------------------------------------------------- |
@@ -497,22 +498,24 @@ What a place is next to (`data.borders`) and reachable from (`data.routes`): the
497
498
 
498
499
  ### `engine.holdings`
499
500
 
500
- 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 population-based government advisory `package-build lint` runs on a place.
501
+ What lies within a place and which affiliation explicitly governs it — read off `data.parents` and resolved explicit `data.government` across this package and every fetched index, inverted into the `contains`, `governed_by` and `governed_places` lists the site build writes — and the population-based government advisory `package-build lint` runs on a place.
501
502
 
502
- | Export | Signature | Returns | Use it when |
503
- | ---------------------- | ------------------------------------ | ----------------------- | --------------------------------------------------------------------------------------------------------------------------- |
504
- | `HOLDINGS_KEYS` | `const HOLDINGS_KEYS` | `readonly string[]` | the three derived keys — `contains`, `held_by`, `holdings` — which a note cannot author |
505
- | `HELD_SUBTYPES` | `const HELD_SUBTYPES` | `readonly string[]` | deprecated historical tenure subtypes; government checks apply to every place subtype |
506
- | `holdingsNode` | `holdingsNode(fm, { title, url })` | `HoldingsNode\|null` | reading a local note as a node of the derivation, or `null` for one that is neither a place nor an affiliation |
507
- | `foreignHoldingsNodes` | `foreignHoldingsNodes(foreignIndex)` | `HoldingsNode[]` | reading a fetched index's places and affiliations as nodes |
508
- | `holdingsPages` | `holdingsPages(nodes)` | `Map<string, Holdings>` | inverting `parents` and `domains` into each page's sorted lists, keyed by URL, for every page with at least one entry |
509
- | `shortcodesOf` | `shortcodesOf(value)` | `string[]` | the shortcodes a `parents` or `domains` value names — bare, addressed or wikilinked — empties dropped and repeats collapsed |
510
- | `checkGovernment` | `checkGovernment(note)` | `object[]` | warning at `data.population` for positive population with no own `data.government` key; explicit null means anarchy |
511
- | `checkHeld` | `checkHeld(note, { index })` | `object[]` | deprecated compatibility wrapper for `checkGovernment`; holdings do not determine government |
503
+ | Export | Signature | Returns | Use it when |
504
+ | ---------------------- | --------------------------------------------------- | ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ |
505
+ | `HOLDINGS_KEYS` | `const HOLDINGS_KEYS` | `readonly string[]` | derived `contains`, `governed_by`, `governed_places` and retired `held_by`/`holdings` keys removed from authored page metadata |
506
+ | `HELD_SUBTYPES` | `const HELD_SUBTYPES` | `readonly string[]` | deprecated historical tenure subtypes; government checks apply to every place subtype |
507
+ | `holdingsNode` | `holdingsNode(fm, { title, url, package, system })` | `HoldingsNode\|null` | reading a local note as a node of the derivation, or `null` for one that is neither a place nor an affiliation |
508
+ | `foreignHoldingsNodes` | `foreignHoldingsNodes(foreignIndex)` | `HoldingsNode[]` | reading a fetched index's places and affiliations as nodes |
509
+ | `holdingsPages` | `holdingsPages(nodes, { governmentNodes })` | `Map<string, Holdings>` | inverting `parents` and resolved explicit `government` into each page's sorted lists, keyed by URL, for every page with at least one entry |
510
+ | `shortcodesOf` | `shortcodesOf(value)` | `string[]` | the shortcodes a `parents` value names — bare or qualified Addresses — empties dropped and repeats collapsed |
511
+ | `checkGovernment` | `checkGovernment(note)` | `object[]` | warning at `data.population` for positive population with no own `data.government` key; explicit null means anarchy |
512
+ | `checkHeld` | `checkHeld(note, { index })` | `object[]` | deprecated compatibility wrapper for `checkGovernment`; holdings do not determine government |
513
+
514
+ Government-only supplemental records passed in `holdingsPages` options include public local stubs in the reverse lists without adding them to the geographical graph. Published nodes retain precedence; stubs without URLs render as plain text.
512
515
 
513
516
  ### `engine.populations`
514
517
 
515
- 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.
518
+ Whether a world's population figures agree from the region down — the 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 three as warnings.
516
519
 
517
520
  | Export | Signature | Returns | Use it when |
518
521
  | ----------------------- | ---------------------------------------- | ---------------------- | ------------------------------------------------------------------------------------------------------------ |
@@ -700,7 +703,7 @@ invokes the check during frontmatter lint.
700
703
 
701
704
  ### `engine.markdownExpressions`
702
705
 
703
- `renderMarkdownExpressions(body, { fm, dates, sqlResults, figures, 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. The `ref` helper links to a `:::figure` fence by its anchor — `"#thorn"` on the same note — in `number` (the default, and admitted explicitly), `full` or `title` form; `figures` is a `Map` of this note's own figures, by id, to `{label, caption, hasCaption}`, and an address naming another note is reported as unresolved rather than looked up. `sqlQueriesInMarkdown` discovers SQL calls, including nested calls; `prepareInlineSqlExpressions` evaluates them through the same DuckDB connection as SQL tables. `EXPRESSION_HELPERS` is the one list of helpers: the engine registers from it and the authoring document is checked against it, so each name there carries the parameters it takes and a sentence on what it does. 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.
706
+ `renderMarkdownExpressions(body, { fm, dates, sqlResults, figures, 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. The `ref` helper links to a captioned item by its anchor — `"#thorn"` on the same note — in `number` (the default, and admitted explicitly), `full` or `title` form; `figures` is a `Map` of this note's own figures, by id, to `{label, caption, hasCaption}`, and an address naming another note is reported as unresolved rather than looked up. `sqlQueriesInMarkdown` discovers SQL calls, including nested calls; `prepareInlineSqlExpressions` evaluates them through the same DuckDB connection as SQL tables. `EXPRESSION_HELPERS` is the one list of helpers: the engine registers from it and the authoring document is checked against it, so each name there carries the parameters it takes and a sentence on what it does. 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.
704
707
 
705
708
  | Export | Signature | Returns | Use it when |
706
709
  | --------------------------- | ------------------------------------------ | ---------------------- | ------------------------------------------------ |
@@ -1964,3 +1967,20 @@ The Changesets changelog generator, loaded with `require` — a CommonJS module
1964
1967
  ## `./package.json`
1965
1968
 
1966
1969
  Not a JavaScript module — this subpath entry exists so tooling (bundlers, `import.meta.resolve`, a script reading the installed version) can resolve the package's own `package.json` through Node's package-exports resolution instead of reaching outside the declared export map.
1970
+
1971
+ ## Markup parsing modules
1972
+
1973
+ The engine wildcard subpath also exposes the body parsers below. Historical figure API names now operate on leading captions; `:::figure` is removed.
1974
+
1975
+ | Module | Exports | Contract |
1976
+ | ---------------------------- | -------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
1977
+ | `engine/content-figures.mjs` | `FIGURE_NAMES`, `FIGURE_CLASSES`, `scanFigures`, `numberFigures`, `renderFigureBlocks` | Scan leading captions and their next supported blocks; `numbered` distinguishes `:` and `:@`. `type` may override inference; `example` is supported. `FIGURE_CLASSES` retains built-in styled classes, not a closed author vocabulary. |
1978
+ | `engine/content-blocks.mjs` | `BLOCK_NAMES`, `BLOCK_CONTAINERS`, `scanBlocks`, `renderBlocks`, `renderWithheldSections` | Read div and secret fences; info/warn names are retired; generic div records have `name: "div"` and no generated title. Scanning includes poetry errors. |
1979
+ | `engine/content-poetry.mjs` | `verseLines`, `poetryStanzas`, `inlineVerse`, `poetryMarkdown`, `scanPoetry`, `renderPoetry` | Read `poetry` code fences, validate authored metadata and tabs, and preserve relative indent levels and stanzas. |
1980
+ | `engine/content-spans.mjs` | `spanMarkdownPlugin`, `scanSpans`, `renderSpans` | Parse bracket spans after recognized links. The scanner returns spans and located errors; the renderer preserves inline Markdown in attributed HTML spans. |
1981
+
1982
+ `scanFigures` retains `line` (1-based caption line), `bodyStart`/`bodyEnd` (0-based, exclusive-end target range), and `close` (last consumed target line), plus caption attributes, kind, label and number. Unnumbered captions have an empty label and consume no number. `collectAnchors` discovers heading, caption, div, poem and span identifiers; `markupAnchorFindings(body)` reports collisions involving non-page identifiers; Foundry maps non-page anchors to the actual containing page UUID. An unnumbered `ref` requires `form="title"` or `form="full"`.
1983
+
1984
+ `engine/content-alerts.mjs` exposes `ALERT_TYPES`, `scanAlerts(source)` and `renderAlerts(source, target)` and `alertMarkdownPlugin(md)`. The scanner returns `{blocks, errors}`; alert records carry `type`, `id`, `classes`, `attributes`, `title`, `body` and inclusive zero-based `start`/`end` source lines. The renderer produces attributed `aside.alert` wrappers while leaving alert body Markdown in the document's scope. Alert validation and anchor discovery run in site, Foundry and book pipelines.
1985
+
1986
+ Alert `title` is the displayed heading override; absent, it defaults to the label in `ALERT_TYPES`. It does not change the alert type’s icon or color.
@@ -45,4 +45,4 @@ The full Address is `<package>-note-lore-harbor`; `<package>` comes from `conten
45
45
 
46
46
  Add `description` for a short page summary, `tags: [draft]` to mark unfinished prose, or `data` for fields the note's type permits. A system block such as `sohl:` or `hm3:` adds mechanics and can produce a Foundry Actor or Item. It does not replace the shared prose or make another JournalEntry. [Frontmatter](frontmatter.md) explains the allowed keys, and the [note-type reference](../reference/note-types.md) lists every type-specific field.
47
47
 
48
- A note with an empty body is a stub. It remains addressable as data, while a document that would have no content is omitted. See [note states](../reference/format-details.md#three-states-of-a-note) for the precise emission rules.
48
+ Every typed note must have a nonempty body after its frontmatter. An empty or whitespace-only body is an error, even with `tags: [draft]` or a description. This includes folder and homepage notes. A nonempty body with fewer than 25 prose words produces a warning unless the note has a `draft` tag. See [note states](../reference/format-details.md#three-states-of-a-note).
@@ -41,7 +41,7 @@ One body describes the subject for both systems. A being's `{#appearance}` and `
41
41
 
42
42
  ## Place governments
43
43
 
44
- A place may state its governing affiliation in `data.government`. Use an affiliation Address, either a local shortcode or a fully qualified Address from this package or a fetched dependency:
44
+ A place may state its governing affiliation in `data.government`. The field type is `Address`, with `affiliation` as the default target type when the type is omitted. Use either a local shortcode or a fully qualified affiliation Address from this package or a fetched dependency:
45
45
 
46
46
  ```yaml
47
47
  data:
@@ -58,7 +58,7 @@ Write `government: null` for **complete anarchy**. Omitting the key leaves gover
58
58
  | Any | Explicit `null` | None; complete anarchy |
59
59
  | Any | Affiliation Address | None; the reference must resolve |
60
60
 
61
- These rules apply to every place subtype, including settlements, sites, structures, features, regions and worlds. Empty strings, numbers, lists, maps, references to another note type and unresolved references are errors. They do not declare anarchy. `domains`, `parents`, containment and capitals do not supply a government. Holdings remain tenure facts; the former unconditional “unheld land” warning is retired.
61
+ These rules apply to every place subtype, including settlements, sites, structures, features, regions and worlds. Empty strings, numbers, lists, maps, references to another note type and unresolved references are errors. They do not declare anarchy. `domains`, `parents`, containment and capitals do not supply a government. The generated `governed_by` and `governed_places` lists use only explicit government references. Legacy affiliation `data.domains` is accepted temporarily but ignored; record any distinct ownership or influence in prose. See [government migration](../guides/government-migration.md).
62
62
 
63
63
  ## Publication and summaries
64
64