@heroiclands/package-build 23.11.0 → 23.12.1

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 (62) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/README.md +1 -0
  3. package/assets/preview/check-sanitizer.go +68 -0
  4. package/assets/preview/gitea-theme.js +36 -0
  5. package/assets/preview/gitea.ini +127 -0
  6. package/assets/styles/content-base.css +119 -16
  7. package/bin/content-commands.mjs +2 -2
  8. package/bin/package-build-preview.mjs +75 -0
  9. package/content-config.mjs +11 -0
  10. package/docs/api.md +105 -46
  11. package/docs/authoring/links-and-markup.md +52 -14
  12. package/docs/commands.md +46 -2
  13. package/docs/configuration.md +21 -4
  14. package/docs/design.md +4 -3
  15. package/docs/guides/book.md +10 -6
  16. package/docs/reference/format-details.md +44 -40
  17. package/docs/reference/note-types.md +0 -1
  18. package/docs/reference/theme-pages.md +1 -1
  19. package/docs/source-preview.md +172 -0
  20. package/engine/base-compiler.mjs +7 -8
  21. package/engine/being-age.mjs +19 -105
  22. package/engine/calendar-notes.mjs +3 -25
  23. package/engine/compile-corpus.mjs +2 -1
  24. package/engine/content-format.yaml +0 -1
  25. package/engine/content-index.mjs +9 -9
  26. package/engine/content-spans.mjs +28 -6
  27. package/engine/current-date.mjs +37 -0
  28. package/engine/definition-list.mjs +58 -0
  29. package/engine/first-use.mjs +4 -2
  30. package/engine/frontmatter-lint.mjs +7 -2
  31. package/engine/helpers.mjs +2 -0
  32. package/engine/index.mjs +2 -0
  33. package/engine/infobox.mjs +1 -5
  34. package/engine/note-vocabulary.mjs +2 -2
  35. package/engine/npc-generate.mjs +7 -6
  36. package/engine/pdf-render.mjs +37 -4
  37. package/engine/preview-current-date.mjs +60 -0
  38. package/engine/site-build.mjs +5 -5
  39. package/engine/site-index.mjs +5 -2
  40. package/engine/site-preview.mjs +641 -325
  41. package/hugo-theme/layouts/_default/list.html +1 -1
  42. package/hugo-theme/layouts/blog/list.html +1 -1
  43. package/hugo-theme/layouts/blog-post/single.html +1 -1
  44. package/hugo-theme/layouts/partials/dictionary-lists.html +53 -0
  45. package/hugo-theme/layouts/partials/page-body.html +1 -1
  46. package/hugo-theme/static/css/style.css +22 -8
  47. package/package.json +8 -2
  48. package/types/content-config.d.mts +8 -0
  49. package/types/engine/base-compiler.d.mts +1 -1
  50. package/types/engine/being-age.d.mts +10 -44
  51. package/types/engine/calendar-notes.d.mts +2 -6
  52. package/types/engine/compile-corpus.d.mts +2 -1
  53. package/types/engine/content-index.d.mts +3 -1
  54. package/types/engine/content-spans.d.mts +4 -2
  55. package/types/engine/current-date.d.mts +9 -0
  56. package/types/engine/definition-list.d.mts +8 -0
  57. package/types/engine/frontmatter-lint.d.mts +6 -2
  58. package/types/engine/index.d.mts +2 -0
  59. package/types/engine/npc-generate.d.mts +2 -2
  60. package/types/engine/preview-current-date.d.mts +8 -0
  61. package/types/engine/site-index.d.mts +5 -1
  62. package/types/engine/site-preview.d.mts +40 -33
package/docs/api.md CHANGED
@@ -744,23 +744,23 @@ The corpus registry anchors each calendar's year 1, day 1 to its canonical `data
744
744
 
745
745
  A calendar is a `lore` note with `subType: calendar`, declaring the months it keeps, the weekdays it names, the seasons it marks and the eras it counts years in. How long the year is, how the day divides and how the moon moves are facts about the **world**, written on its `place` note and its moon's, and no calendar may restate them — which is why the months summing to the world's year is the only arithmetic a calendar note is held to. One definition is emitted per calendar, in array shape: Foundry core prunes the keys outside its schema, and Calendaria converts the arrays to its own keyed collections.
746
746
 
747
- | Export | Signature | Returns | Use it when |
748
- | --------------------------- | ----------------------------------------------------- | ------------------------------------------ | --------------------------------------------------------------------------------- |
749
- | `CALENDAR_SUBTYPE` | `const CALENDAR_SUBTYPE` | — | naming the `lore` subType whose notes are calendars |
750
- | `INVARIANT_SUBTYPES` | `const INVARIANT_SUBTYPES` | — | naming the `place` subTypes a world fact may be written on |
751
- | `CALENDAR_FIELDS` | `const CALENDAR_FIELDS` | — | reading the closed list of what a calendar note may declare |
752
- | `CALENDAR_ENTRY_FIELDS` | `const CALENDAR_ENTRY_FIELDS` | — | reading the keys one entry of each calendar list takes, as inner-key declarations |
753
- | `CALENDAR_DATE_FORMAT_KEYS` | `const CALENDAR_DATE_FORMAT_KEYS` | — | reading the supported Calendaria display slots |
754
- | `CALENDAR_FORMAT_TOKENS` | `const CALENDAR_FORMAT_TOKENS` | — | checking bare date tokens in a format string |
755
- | `INVARIANT_FIELDS` | `const INVARIANT_FIELDS` | — | reading the closed list of what a body may declare about itself |
756
- | `checkCalendarNote` | `checkCalendarNote(note, opts)` | `object[]` — findings | holding a `lore` note to the family and to the year the world keeps |
757
- | `checkCalendarDateFormats` | `checkCalendarDateFormats(note)` | `object[]` — findings | checking named patterns and the readable default |
758
- | `checkWorldFacts` | `checkWorldFacts(note, opts)` | `object[]` — findings | holding a `place` note's world facts to one body, one year and one moon |
759
- | `worldInvariants` | `worldInvariants(index)` | `{year, present, moon, body, moonName, …}` | reading the year, the present and the moon a package's own notes state |
760
- | `quarterDays` | `quarterDays(daysPerYear)` | `number[]` — four days of the year | asking which days the year turns on |
761
- | `compileCalendar` | `compileCalendar({note, invariants, contentPackage})` | `object` — the definition | building the one object both a core consumer and Calendaria read |
762
- | `compileCalendars` | `compileCalendars(index, opts)` | `{calendars, invariants}` | building every calendar a package declares, or none where it states no year |
763
- | `calendariaEnvelope` | `calendariaEnvelope(definition, opts)` | `object` — the import envelope | wrapping a definition for a hand import through Calendaria's settings |
747
+ | Export | Signature | Returns | Use it when |
748
+ | --------------------------- | ----------------------------------------------------- | ---------------------------------- | --------------------------------------------------------------------------------- |
749
+ | `CALENDAR_SUBTYPE` | `const CALENDAR_SUBTYPE` | — | naming the `lore` subType whose notes are calendars |
750
+ | `INVARIANT_SUBTYPES` | `const INVARIANT_SUBTYPES` | — | naming the `place` subTypes a world fact may be written on |
751
+ | `CALENDAR_FIELDS` | `const CALENDAR_FIELDS` | — | reading the closed list of what a calendar note may declare |
752
+ | `CALENDAR_ENTRY_FIELDS` | `const CALENDAR_ENTRY_FIELDS` | — | reading the keys one entry of each calendar list takes, as inner-key declarations |
753
+ | `CALENDAR_DATE_FORMAT_KEYS` | `const CALENDAR_DATE_FORMAT_KEYS` | — | reading the supported Calendaria display slots |
754
+ | `CALENDAR_FORMAT_TOKENS` | `const CALENDAR_FORMAT_TOKENS` | — | checking bare date tokens in a format string |
755
+ | `INVARIANT_FIELDS` | `const INVARIANT_FIELDS` | — | reading the closed list of what a body may declare about itself |
756
+ | `checkCalendarNote` | `checkCalendarNote(note, opts)` | `object[]` — findings | holding a `lore` note to the family and to the year the world keeps |
757
+ | `checkCalendarDateFormats` | `checkCalendarDateFormats(note)` | `object[]` — findings | checking named patterns and the readable default |
758
+ | `checkWorldFacts` | `checkWorldFacts(note, opts)` | `object[]` — findings | holding a `place` note's world facts to one body, one year and one moon |
759
+ | `worldInvariants` | `worldInvariants(index)` | `{year, moon, body, moonName, …}` | reading the year and moon a package's own notes state |
760
+ | `quarterDays` | `quarterDays(daysPerYear)` | `number[]` — four days of the year | asking which days the year turns on |
761
+ | `compileCalendar` | `compileCalendar({note, invariants, contentPackage})` | `object` — the definition | building the one object both a core consumer and Calendaria read |
762
+ | `compileCalendars` | `compileCalendars(index, opts)` | `{calendars, invariants}` | building every calendar a package declares, or none where it states no year |
763
+ | `calendariaEnvelope` | `calendariaEnvelope(definition, opts)` | `object` — the import envelope | wrapping a definition for a hand import through Calendaria's settings |
764
764
 
765
765
  ### `engine.calendarArtifacts`
766
766
 
@@ -776,18 +776,45 @@ identical bytes.
776
776
  | ----------------------- | ---------------------------------------------------- | ------------------------------- | ---------------------------------------------------- |
777
777
  | `emitCalendarArtifacts` | `emitCalendarArtifacts({config, calendariaVersion})` | `{directory, calendars, files}` | writing calendar files for a package's Foundry stage |
778
778
 
779
+ ### `engine.definitionList`
780
+
781
+ HTML definition lists use semantic dictionary rows. The plugin groups each term
782
+ and all its definitions inside one `div` under `dl.dictionary`, preserving
783
+ nested blocks, inline formatting and document footnotes. Register it after
784
+ `markdown-it-deflist`.
785
+
786
+ | Export | Signature | Returns | Use it when |
787
+ | ---------------------- | -------------------------- | ------- | --------------------------------------------------------------- |
788
+ | `dictionaryListPlugin` | `dictionaryListPlugin(md)` | `void` | Giving an HTML Markdown parser the shared dictionary structure. |
789
+
790
+ The website groups Goldmark's final HTML through the `dictionary-lists.html`
791
+ partial, retaining its headings, TOC and footnotes. Shared content styles use a
792
+ 150px hanging indent, a `.75em` term gap and `.5em` space between entries. At
793
+ viewport widths of 600px or less the indent is 100px. Terms are bold and stay on
794
+ one line. A long term pushes the first definition farther right; continuation
795
+ lines return to the common indent. Profile infobox grids keep their own layout.
796
+
797
+ ### `engine.currentDate`
798
+
799
+ The shared current-date setting is a neutral string and requires no calendar or
800
+ configuration loading. Undefined selects the default; invalid explicit values
801
+ throw `TypeError`. Day digits remain exactly as authored.
802
+
803
+ | Export | Signature | Returns | Use it when |
804
+ | ---------------------- | ------------------------------ | --------- | ----------------------------------------------- |
805
+ | `DEFAULT_CURRENT_DATE` | Constant | `"720.1"` | Selecting the setting-wide default. |
806
+ | `normalizeCurrentDate` | `normalizeCurrentDate(value?)` | `string` | Validating a configuration or adapter override. |
807
+
779
808
  ### `engine.beingAge`
780
809
 
781
- The middle state of a being's `age`: absent beside a dated `born`, it is computed against the package's declared present — the two dates compared, not the two years subtracted, so a birthday later in the year than the present counts one less. `applyComputedBeingAge` fills it in on the frontmatter object a pass already holds, never on the note; `checkBeingAge` is `being`'s `NOTE_VOCABULARY` check, raising a warning where an authored `age` disagrees with what a dated `born` computes to. An estimate's `~` is read the way a date's is: the authored string stays verbatim, and `ageYears` carries the magnitude beside it.
810
+ The middle state of a being's `age`: absent beside a dated `born`, it is computed against the configured `currentDate` (default `"720.1"`) — the two dates compared, not the two years subtracted, so a birthday later in the year than the present counts one less. `applyComputedBeingAge` fills it in on the frontmatter object a pass already holds, never on the note; `checkBeingAge` is `being`'s `NOTE_VOCABULARY` check, raising a warning where an authored `age` disagrees with what a dated `born` computes to. An estimate's `~` is read the way a date's is: the authored string stays verbatim, and `ageYears` carries the magnitude beside it.
782
811
 
783
- | Export | Signature | Returns | Use it when |
784
- | -------------------------- | ---------------------------------------- | ------------------------------ | ------------------------------------------------------------------------- |
785
- | `computeAge` | `computeAge(bornRaw, presentRaw)` | `number \| null` | reading the age two dates imply, in whole years |
786
- | `parseAgeMagnitude` | `parseAgeMagnitude(raw)` | `{approximate, value} \| null` | reading an authored `age`'s `~` mark and its magnitude |
787
- | `applyComputedBeingAge` | `applyComputedBeingAge(fm, presentRaw)` | `void` — mutates `fm.data` | filling in a being's computed `age`, or the `ageYears` beside an estimate |
788
- | `presentAmongRecords` | `presentAmongRecords(records)` | the present, or `null` | reading the declared present from a batch of flat index records |
789
- | `presentAmongFrontmatters` | `presentAmongFrontmatters(frontmatters)` | the present, or `null` | reading the declared present from a batch of raw frontmatter objects |
790
- | `checkBeingAge` | `checkBeingAge(note, opts)` | `object[]` — findings | holding a being's authored `age` to what `born` and the present compute |
812
+ | Export | Signature | Returns | Use it when |
813
+ | ----------------------- | --------------------------------------------- | ------------------------------ | ------------------------------------------------------------------------- |
814
+ | `computeAge` | `computeAge(bornRaw, presentRaw)` | `number \| null` | reading the age two dates imply, in whole years |
815
+ | `parseAgeMagnitude` | `parseAgeMagnitude(raw)` | `{approximate, value} \| null` | reading an authored `age`'s `~` mark and its magnitude |
816
+ | `applyComputedBeingAge` | `applyComputedBeingAge(fm, presentRaw)` | `void` — mutates `fm.data` | filling in a being's computed `age`, or the `ageYears` beside an estimate |
817
+ | `checkBeingAge` | `checkBeingAge(note, { index, currentDate })` | `object[]` — findings | holding a being's authored `age` to what `born` and the present compute |
791
818
 
792
819
  ### `engine.contentAddress`
793
820
 
@@ -1290,30 +1317,33 @@ index for checking page links.
1290
1317
 
1291
1318
  ### `engine.sitePreview`
1292
1319
 
1293
- `prepareSitePreview({ config })` reads the saved content tree and declared dependency indexes, prepares its SQL corpus, and returns a private preview context. Pass the active note's absolute path and complete buffer text to `render(file, text)`. A successful result has `{ ok: true, markdown, frontmatter, findings: [] }`; a failed result has `{ ok: false, findings }` and no replacement content. Findings identify the source file and carry a line and column when the offending text can be located.
1320
+ `renderPreview(text, { dark = false, infobox = true, file = "", currentDate = "720.1" })` renders the current Markdown buffer directly to HTML. Frontmatter is optional. It needs no package configuration, saved note, content tree, dependency cache, database or Pandoc installation. The result is `{ ok, html, frontmatter, findings }`; `file` only labels diagnostics. Malformed frontmatter produces a warning and remains visible without preventing a preview update.
1294
1321
 
1295
- | Export | Signature | Returns | Use it when |
1296
- | -------------------- | -------------------------------- | ------------------------------------------ | -------------------------------------------------- |
1297
- | `prepareSitePreview` | `prepareSitePreview({ config })` | `{render(file, text), refresh(), close()}` | An editor needs a private, reusable page renderer. |
1322
+ | Export | Signature | Returns | Use it when |
1323
+ | ------------------------ | ------------------------------- | ------------------------------------- | ---------------------------------------------------------------------- |
1324
+ | `renderPreview` | `renderPreview(text, options?)` | `{ ok, html, frontmatter, findings }` | An editor or external renderer needs a preview from one buffer. |
1325
+ | `previewStyles` | `previewStyles()` | CSS string | A host needs the shared, scoped light and dark palettes. |
1326
+ | `readPreviewCurrentDate` | `readPreviewCurrentDate(root)` | `Promise<string>` | Opting into the nearest project's validated root current-date setting. |
1327
+ | `PREVIEW_HELPERS` | Constant | Helper dependency policy | Checking preview coverage when adding an expression helper. |
1298
1328
 
1299
- The context keeps other notes and foreign packages at their saved state. Call `refresh()` after a save to read them again, and `close()` when the editor releases the project. Rendering and refreshing never write the authored notes or the site's `build/hugo/content` mount. The editor owns any private HTML artifact and keeps its last complete artifact when a render fails.
1329
+ The returned HTML includes an `.hl-preview` wrapper with exactly one explicit mode class, `.hl-preview-light` or `.hl-preview-dark`. Include `previewStyles()` once in the host document. The stylesheet colors only the preview container, including its backgrounds, source tokens, alerts and optional local infobox. Set `dark` from the host's selected theme; browser preference does not override it.
1300
1330
 
1301
1331
  ```js
1302
- import { sitePreview } from "@heroiclands/package-build/engine";
1332
+ import { renderPreview, previewStyles } from "@heroiclands/package-build/engine/site-preview";
1303
1333
 
1304
- const preview = await sitePreview.prepareSitePreview({ config });
1305
- try {
1306
- const result = await preview.render(absoluteFile, currentBufferText);
1307
- if (result.ok) {
1308
- // Convert result.markdown and result.frontmatter to the editor's HTML preview.
1309
- } else {
1310
- showDiagnostics(result.findings);
1311
- }
1312
- } finally {
1313
- await preview.close();
1314
- }
1334
+ const result = renderPreview(currentBufferText, { dark: true });
1335
+ // Include previewStyles() in a stylesheet and result.html in the preview document.
1336
+ showDiagnostics(result.findings);
1315
1337
  ```
1316
1338
 
1339
+ Ordinary Markdown, definition lists, footnotes, alert boxes, poetry, captions, local figures and container blocks render locally. Alerts reuse the four shared icons and titles, accept custom titles and nested alerts, and have opaque tinted backgrounds in both modes. Definition terms are bold; definitions retain ordinary Markdown styling. Wikilinks and image embeds retain their entire authored source: blue opening markers (including all of `![[`), brackets and pipes, purple addresses and anchors, bold foreground labels, and grey attributes. Spans retain their entire source in bold, with blue brackets and grey attributes. Code examples stay literal. Incomplete syntax remains visible while editing.
1340
+
1341
+ SQL and pagelist fences remain grey source, including delimiters. Calendar/date expressions, cross-note references and maps remain green source. A dependent call nested inside a pure helper preserves the entire outer expression. Pure helpers and scalar frontmatter expressions evaluate locally; same-note figure references and non-date event facts can resolve from this buffer. Infobox fields show authored values and green reference source; image and portrait wikilinks use the image syntax colors. Missing ages are calculated locally from neutral birth dates and `currentDate`, defaulting to `"720.1"`; authored ages, including `0`, take precedence. Unavailable ages remain `null` internally, have no infobox row, and render as empty text in `{{data.age}}`. Named-calendar birth dates remain unavailable without calendar context. Set `infobox: false` to omit the profile.
1342
+
1343
+ `readPreviewCurrentDate(root)` is an explicit configuration adapter. It discovers the nearest `package-build.config.yaml`, `.yml` or `.mjs` using the shared resolver. YAML reads only top-level `currentDate`, without loading the build pipeline or validating unrelated fields; MJS executes the trusted configuration module. Missing config or an absent setting returns the shared default. Invalid explicit values reject with a configuration-file diagnostic. Repeated calls reread YAML and refresh a changed MJS file. `renderPreview` never calls this adapter or reads configuration itself.
1344
+
1345
+ Generated sections, Related pages and authored See also sections are absent. Preview performs no publication eligibility or link checks and never resolves other notes. It does not write notes or generated site files. Callers render each buffer directly without preparing or maintaining a snapshot.
1346
+
1317
1347
  ### `engine.contentLint`
1318
1348
 
1319
1349
  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.
@@ -1999,6 +2029,29 @@ Where HM3 records the template priority — one statement (`data.templatePriorit
1999
2029
  | `HM3_DEFAULT_ITEM_ART` | `const HM3_DEFAULT_ITEM_ART` | — | reading the full map of default item artwork keyed by **note** type (not document subtype) for HM3 items |
2000
2030
  | `hm3DefaultItemArt` | `hm3DefaultItemArt(type)` | `string` — the default image path | getting the default art path for an HM3 item's note type, throwing when the type is unknown |
2001
2031
 
2032
+ ## `./preview`
2033
+
2034
+ Import the buffer-only renderer directly without loading the build pipeline:
2035
+
2036
+ ```js
2037
+ import { renderPreview, previewStyles } from "@heroiclands/package-build/preview";
2038
+
2039
+ const { html, findings } = renderPreview(currentBufferText, { dark: true });
2040
+ const css = previewStyles();
2041
+ ```
2042
+
2043
+ | Export | Signature | Returns | Use it when |
2044
+ | ------------------------ | ------------------------------- | ------------------------------------- | ---------------------------------------------------------------------- |
2045
+ | `renderPreview` | `renderPreview(text, options?)` | `{ ok, html, frontmatter, findings }` | Rendering one Markdown buffer without configuration or an index. |
2046
+ | `previewStyles` | `previewStyles()` | CSS string | Installing both scoped palettes in an editor or external host. |
2047
+ | `readPreviewCurrentDate` | `readPreviewCurrentDate(root)` | `Promise<string>` | Opting into the nearest project's validated root current-date setting. |
2048
+ | `PREVIEW_HELPERS` | Constant | Helper dependency policy | Checking coverage for every registered expression helper. |
2049
+
2050
+ These are the same exports as [`engine.sitePreview`](#enginesitepreview), with
2051
+ the same explicit palette, local infobox, source-preservation and diagnostics
2052
+ contract. See [Source preview](source-preview.md) for the standalone command and
2053
+ Gitea installation.
2054
+
2002
2055
  ## `./content-config`
2003
2056
 
2004
2057
  The per-repository configuration contract: what `package-build.config.yaml` (or an `.mjs` config calling `defineConfig` directly) must satisfy. `defineConfig` validates and normalizes the whole thing, returning a deeply frozen copy, and performs no I/O — `engine.packConfig` is what locates and reads a repository's file, deriving the three fields absent from authored YAML (`rootDir`, `stats.systemVersion`, the resolved `itemBuilders` table). See `docs/configuration.md` for the full key-by-key reference; this section covers the module's own exported shape.
@@ -2391,9 +2444,15 @@ The engine wildcard subpath also exposes the body parsers below. Their figure ex
2391
2444
 
2392
2445
  Inline `.secret` spans preserve their inline content, name classes and
2393
2446
  attributes. Site rendering uses a separate show/hide button and content span,
2394
- so a link inside the content does not become a nested control. Foundry renders
2395
- visible inline text with a **Secret:** label, without native secret-section
2396
- concealment; book rendering uses static secret styling and the same label.
2447
+ so a link inside the content does not become a nested control. Foundry emits a
2448
+ focusable inline spoiler: scoped content CSS redacts its text and descendants
2449
+ until pointer hover or keyboard focus reveals them. Its focus outline and
2450
+ accessible cue identify the reveal interaction. Native Foundry secret sections
2451
+ keep their separate controls. Book rendering discards inline `.secret` classes
2452
+ and prints their content normally, retaining emphasis, links, anchors and `.name`
2453
+ bold styling. Book secret sections and fences retain their visible labelled
2454
+ boxes. The span parser's opaque source tokens retain a `secret` boolean from
2455
+ their parsed classes for preview presentation.
2397
2456
  `collectAnchors` includes `secret: true` on secret anchors for destination
2398
2457
  presentation. Block and heading secrecy retain their separate scope and
2399
2458
  Foundry permission handling.
@@ -40,11 +40,12 @@ Linking to the public note as a whole does not inherit a section's secrecy.
40
40
  | Surface | Reference to a secret destination |
41
41
  | ------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
42
42
  | Website | An inline show/hide spoiler conceals its label. An omitted GM-only destination produces text without an unusable link. A published secret heading can still be linked. |
43
- | Foundry | A visible inline spoiler labelled **Secret:** contains the reference. Private pack and page permissions govern whether its destination can be opened. |
44
- | Book | The reference prints with secret styling and a **Secret:** label. An omitted GM-only destination produces text without a link. |
43
+ | Foundry | A CSS-redacted inline spoiler reveals the reference on hover or keyboard focus. Private pack and page permissions govern whether its destination can be opened. |
44
+ | Book | The reference prints normally, preserving emphasis and name styling. An omitted GM-only destination produces text without a link. |
45
45
 
46
- `.show` removes that automatic spoiler presentation on every surface. Where
47
- the target is unavailable it still produces text without a link.
46
+ `.show` removes automatic spoiler presentation on the website and in Foundry.
47
+ Book references already print normally. Where the target is unavailable the
48
+ reference still produces text without a link.
48
49
 
49
50
  To intentionally reveal this reference, add `.show` immediately after the link:
50
51
 
@@ -90,9 +91,12 @@ the attribute adds no book presentation.
90
91
 
91
92
  `package-build lint` warns when an indexed name or alias uses `.name`
92
93
  instead of a link, when the same name is marked or linked again, or when a
93
- short form appears before its full name. Only marked names and authored link
94
- labels count toward these rules. Introduce the full name before an alias such
95
- as `Arden`, then use plain text for later mentions. Full names and their
94
+ `.name` span or Markdown link uses a short form before its full name. Only
95
+ marked names and authored link labels count toward these rules. A wikilink
96
+ introduces its note under whatever label reads naturally, so
97
+ `[[place-arden|Arden]]` needs no earlier `Arden Vale`, and an article
98
+ mid-sentence stays lowercase. A `.name` span introduces the full name before
99
+ an alias such as `Arden`. Later mentions are plain text. Full names and their
96
100
  declared aliases count as the same name, including in links to different
97
101
  headings. Different labels do not become the same name just because they link
98
102
  to the same page.
@@ -251,11 +255,13 @@ reference concealment; an enclosing authored `.secret` still marks its text.
251
255
 
252
256
  On the website an inline spoiler has a button to show or hide its content.
253
257
  It stays within the surrounding sentence, and links inside it can be followed
254
- after revealing it. Foundry prints a visible inline `secret` span labelled
255
- **Secret:**; this span does not withhold its text from players. The book prints
256
- the text with secret styling and a **Secret:** label; printed text cannot be
257
- interactively concealed.
258
- These presentations preserve inline emphasis and the `.name` classification.
258
+ after revealing it. Foundry redacts inline `secret` spans with a solid cover;
259
+ hover the span or focus it with the keyboard to reveal its text, links and
260
+ images. A visible focus outline and a spoiler cue identify the interaction.
261
+ This does not change native Foundry secret-section controls. The book ignores
262
+ `.secret` on inline spans and prints their text normally, preserving emphasis,
263
+ links, anchors and `.name` bold styling. Secret sections and `:::secret` fences
264
+ retain their visible labelled boxes.
259
265
 
260
266
  Published web HTML contains the spoiler's text. Revealing the spoiler or reading
261
267
  the page source exposes it; this is presentation, not access control. GM-only
@@ -432,8 +438,40 @@ Janapada
432
438
  : Also the land associated with that community.
433
439
  ```
434
440
 
435
- The web and Foundry render a definition list with HTML `<dl>`, `<dt>`, and
436
- `<dd>` elements. The book renders it as a term list.
441
+ To continue the same definition, leave a blank line and indent the next
442
+ paragraph or block by four spaces. For a fenced code block, indent both fences
443
+ and its contents:
444
+
445
+ ````markdown
446
+ Harbour due
447
+ : A toll on every hull that ties up.
448
+
449
+ The harbourmaster collects it before unloading begins.
450
+
451
+ ```text
452
+ One hull: two silver pieces.
453
+ ```
454
+
455
+ Anchorage
456
+ : A sheltered place where a ship can lie at anchor.
457
+ ````
458
+
459
+ The continuation paragraph and code block belong to the same definition.
460
+ Repeat `:` only when starting a separate definition for the same term, as in
461
+ the first example. The next unindented term starts a new entry.
462
+
463
+ Definition lists use a dictionary hanging indent in the website, Foundry,
464
+ preview and book. A bold term begins the row, followed by its normally styled
465
+ definition. Short terms share a definition column 150px from the row's left
466
+ edge; browser viewports of 600px or less use 100px. The gap after a term is
467
+ `.75em`, with `.5em` between entries. A long term stays on one line and pushes
468
+ the first definition farther right; wrapped lines return to the common column.
469
+ The book uses the equivalent print dimensions.
470
+
471
+ HTML surfaces preserve the semantic `<dl class="dictionary">`, `<dt>` and `<dd>`
472
+ elements, with one grouping `<div>` for each term and all its definitions.
473
+ Multiple definitions, continuation paragraphs, nested lists and footnotes keep
474
+ their normal Markdown meaning. Infobox profiles retain their own layout.
437
475
 
438
476
  ## Lists
439
477
 
package/docs/commands.md CHANGED
@@ -66,6 +66,50 @@ behaviour where they appear below; read the command, not the name.
66
66
 
67
67
  ---
68
68
 
69
+ ## Standalone source preview
70
+
71
+ ### `package-build-preview`
72
+
73
+ **NAME**
74
+
75
+ Render a Markdown buffer without a content index.
76
+
77
+ **SYNOPSIS**
78
+
79
+ `package-build-preview [--dark] [--current-date DATE] [--css] < note.md`
80
+
81
+ **DESCRIPTION**
82
+
83
+ Reads Markdown from stdin and writes an HTML fragment. Requires no package
84
+ configuration. Local Markdown and extensions render; constructs needing other
85
+ notes remain highlighted source. Shared CSS is supplied separately.
86
+
87
+ **OPTIONS**
88
+
89
+ - `--dark` — select dark mode; light is the default.
90
+ - `--current-date DATE` — override the setting-wide neutral current date; otherwise
91
+ read the nearest root configuration setting, defaulting to `"720.1"`.
92
+ - `--css` — emit the shared scoped stylesheet without reading stdin.
93
+ - `--help`, `-h` — show usage.
94
+
95
+ **EXIT STATUS**
96
+
97
+ 0 on success, 1 for an invalid argument or rendering error. Findings go to
98
+ stderr; stdout contains only HTML (or CSS with `--css`).
99
+
100
+ **EXAMPLES**
101
+
102
+ ```sh
103
+ package-build-preview --dark < note.md > preview.html
104
+ package-build-preview --css > preview.css
105
+ ```
106
+
107
+ **SEE ALSO**
108
+
109
+ [Source preview and Gitea renderer](source-preview.md), [API](api.md).
110
+
111
+ ---
112
+
69
113
  ## Package and release operations
70
114
 
71
115
  ### `package-build ci`
@@ -3017,7 +3061,7 @@ exactly as given, at every depth, and every field it leaves out is generated:
3017
3061
  | `data.templatePriority`, `data.occupation`, `data.died` | `null`. |
3018
3062
  | `data.lore`, `data.homes` | `[]`. |
3019
3063
  | `data.affiliations` | `{}`. |
3020
- | `data.born` | An adult age of 18 to 60 years before the package's present, as a `<year>.<day>` date; `unknown` where the package states no present. |
3064
+ | `data.born` | An adult age of 18 to 60 years before the configured `currentDate` (default `"720.1"`), as a `<year>.<day>` date. |
3021
3065
  | `data.frame`, `data.appearance.*` | Weighted draws from the culture's appearance profile; `extra_features` is `[]`. |
3022
3066
  | `data.height` | From the profile's range for the gender, in feet and inches. |
3023
3067
  | `data.weight` | From the height and the frame's body-mass band, in pounds. A spec giving height and weight but no frame gets the frame they imply. |
@@ -3105,7 +3149,7 @@ dossier: |
3105
3149
  record rather than of reputation.
3106
3150
  ```
3107
3151
 
3108
- Run against a package whose world states `present: "720.300"`, whose
3152
+ Run against a package configured with `currentDate: "720.300"`, whose
3109
3153
  `khelathi` culture carries the profile in
3110
3154
  [appearance profiles](reference/format-details.md#appearance-profiles) and
3111
3155
  names `language: skill-khelathilng`, and whose language carries the lists in
@@ -54,7 +54,7 @@ around its own evaluation.
54
54
 
55
55
  ### Quick reference
56
56
 
57
- 23 top-level keys. `rootDir` is not one of them — a data configuration never
57
+ 24 top-level keys. `rootDir` is not one of them — a data configuration never
58
58
  writes it — and is documented under [Derived values](#derived-values) instead,
59
59
  alongside `foundryPackage`, `homepage`, `author` and `itemBuilders`, whose
60
60
  data-configuration behaviour is also derivation rather than ordinary
@@ -62,6 +62,7 @@ authoring.
62
62
 
63
63
  | Key | Type | Required | Default |
64
64
  | ------------------------------------------- | -------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------- | ------------------------- |
65
+ | [`currentDate`](#currentdate) | quoted neutral date string | no | `"720.1"` |
65
66
  | [`contentPackage`](#contentpackage) | string | yes | — |
66
67
  | [`foundryPackage`](#foundrypackage) | string | yes (`.mjs` only — derived in YAML); refused in a `documentation` package | — |
67
68
  | [`homepage`](#homepage) | string | no (`.mjs` only — derived in YAML) | `null` |
@@ -87,7 +88,7 @@ authoring.
87
88
 
88
89
  Any key outside this list is refused:
89
90
 
90
- > `` `<key>` is not a recognized option (expected one of: rootDir, contentPackage, foundryPackage, homepage, authors, packageKind, stats, itemBuilders, paths, skipDirectories, forbidGeneratedArt, icons, packs, docs, site, pdf, compatibility, relationships, systems, requiresSystem, packageBuild, publish, changelog). ``
91
+ > `` `<key>` is not a recognized option (expected one of: rootDir, contentPackage, currentDate, foundryPackage, homepage, authors, packageKind, stats, itemBuilders, paths, skipDirectories, forbidGeneratedArt, icons, packs, docs, site, pdf, compatibility, relationships, systems, requiresSystem, packageBuild, publish, changelog). ``
91
92
 
92
93
  (`rootDir` appears in that list because it is a key `defineConfig` itself
93
94
  accepts — an `.mjs` configuration authors it directly. A YAML configuration
@@ -194,7 +195,7 @@ build writes its `name` as `params.author`. `null` when `authors` is empty.
194
195
  as an unknown key, at the key's own line, because `authors` is the one list a
195
196
  package names its people in:
196
197
 
197
- > ``package-build config: `author` is not a recognized option (expected one of: rootDir, contentPackage, foundryPackage, homepage, authors, packageKind, stats, itemBuilders, paths, skipDirectories, forbidGeneratedArt, icons, packs, docs, site, pdf, compatibility, relationships, systems, requiresSystem, packageBuild, publish, changelog).``
198
+ > ``package-build config: `author` is not a recognized option (expected one of: rootDir, contentPackage, currentDate, foundryPackage, homepage, authors, packageKind, stats, itemBuilders, paths, skipDirectories, forbidGeneratedArt, icons, packs, docs, site, pdf, compatibility, relationships, systems, requiresSystem, packageBuild, publish, changelog).``
198
199
 
199
200
  `package.json`'s own `author` is npm's and is not read.
200
201
 
@@ -294,7 +295,23 @@ Unlike the first three, authoring `itemBuilders` is not an error — it is
294
295
 
295
296
  ---
296
297
 
297
- ## The 22 keys
298
+ ## Configuration keys
299
+
300
+ ### `currentDate`
301
+
302
+ The setting-wide date used for computed ages, age lint, content-index dates,
303
+ Foundry and website output, NPC generation and editor preview. It defaults to
304
+ `"720.1"`. Quote the value so YAML preserves the exact ordinal day digits:
305
+
306
+ ```yaml
307
+ contentPackage: thalorna
308
+ currentDate: "720.130"
309
+ ```
310
+
311
+ Use a neutral `<year>[.<day>[:HHMMSS]]` string. A bare quoted year means its first
312
+ day. Named calendars, `unknown`, numeric YAML values and malformed dates are
313
+ configuration errors located at `currentDate`. Birth dates can still use named
314
+ calendars when the output has the relevant calendar context.
298
315
 
299
316
  ### `contentPackage`
300
317
 
package/docs/design.md CHANGED
@@ -33,9 +33,10 @@ Concealment and access remain separate decisions. Public site and book builds
33
33
  omit GM-only notes; a reference to an omitted destination carries no unusable
34
34
  link. Foundry's private packs and page permissions govern destination access.
35
35
  Web inline spoilers use a separate show/hide control beside their content, so
36
- a revealed link is independently usable. Foundry and book inline spoilers are
37
- visible labelled text; an inline Foundry span has no native secret-section
38
- permission behavior. A spoiler in published HTML remains readable in the source. See the
36
+ a revealed link is independently usable. Foundry inline spoilers use a CSS
37
+ redaction revealed on hover or keyboard focus; they have no native secret-section
38
+ permission behavior. Books ignore inline `.secret` and preserve normal text
39
+ formatting. A spoiler in published HTML remains readable in the source. See the
39
40
  [authoring contract](authoring/links-and-markup.md#references-to-secret-subjects)
40
41
  for syntax and surface presentation.
41
42
 
@@ -522,12 +522,16 @@ Anchors on headings are scoped to their entries, so two entries can use the
522
522
  same heading anchor. The build reports an unresolved destination while
523
523
  keeping the source available for inspection.
524
524
 
525
- A reference to a GM-only note or secret heading prints with secret styling
526
- and a **Secret:** label. GM-only notes remain omitted, and references to them
527
- print as text without a link. `.show` removes the automatic spoiler presentation
528
- of that reference, without adding the destination to the book. An authored
529
- `[text]{.secret}` uses the same static inline styling. Printed text remains
530
- visible; see [secret references](../authoring/links-and-markup.md#references-to-secret-subjects).
525
+ A reference to a GM-only note or secret heading prints normally. GM-only notes
526
+ remain omitted, and references to them print as text without a link. `.show`
527
+ does not add an omitted destination to the book. Inline `[text]{.secret}` spans
528
+ print as ordinary text, preserving emphasis, links, anchors and `.name` bold
529
+ styling; the book ignores their `.secret` class.
530
+
531
+ Secret sections and `:::secret` fences keep their visible contents in pale
532
+ lavender boxes (`#f2eefb`) with a purple left border and title (`#5c4b8a`). The
533
+ default title is **! Secret**; a fence can supply its own title. See
534
+ [secret references](../authoring/links-and-markup.md#references-to-secret-subjects).
531
535
 
532
536
  The [generated sections](../reference/format-details.md#generated-sections)
533
537
  an entry carries — **Within**, **Governed by**, **In song and story**, the