@heroiclands/package-build 22.21.0 → 22.23.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 (152) hide show
  1. package/CHANGELOG.md +223 -0
  2. package/assets/styles/content-base.css +162 -0
  3. package/bin/content-commands.mjs +77 -17
  4. package/bin/package-build.mjs +15 -2
  5. package/ci/content-format-reference.mjs +68 -1
  6. package/config.mjs +26 -0
  7. package/content-config.mjs +153 -6
  8. package/docs/api.md +120 -89
  9. package/docs/authoring/assets.md +14 -0
  10. package/docs/authoring/dates-and-calendars.md +41 -3
  11. package/docs/authoring/frontmatter.md +2 -2
  12. package/docs/authoring/links-and-markup.md +325 -35
  13. package/docs/commands.md +12 -10
  14. package/docs/configuration.md +117 -14
  15. package/docs/content-format.md +4 -2
  16. package/docs/guides/book.md +5 -4
  17. package/docs/guides/reference-pages.md +15 -0
  18. package/docs/reference/format-details.md +545 -104
  19. package/docs/reference/note-types.md +30 -13
  20. package/engine/actor-compiler.mjs +23 -0
  21. package/engine/anchored-sections.mjs +35 -4
  22. package/engine/anchors.mjs +26 -30
  23. package/engine/art-fields.mjs +118 -0
  24. package/engine/art-slots.mjs +7 -1
  25. package/engine/asset-index.mjs +160 -10
  26. package/engine/asset-replacement.mjs +93 -0
  27. package/engine/asset-types.mjs +240 -0
  28. package/engine/base-compiler.mjs +139 -22
  29. package/engine/base-styles.mjs +149 -0
  30. package/engine/bundles.mjs +13 -2
  31. package/engine/compendiums.mjs +8 -1
  32. package/engine/content-blocks.mjs +410 -0
  33. package/engine/content-embeds.mjs +19 -0
  34. package/engine/content-figures.mjs +468 -0
  35. package/engine/content-footnotes.mjs +197 -0
  36. package/engine/content-format-check.mjs +15 -12
  37. package/engine/content-format.yaml +1 -1
  38. package/engine/content-images.mjs +126 -25
  39. package/engine/content-index.mjs +38 -12
  40. package/engine/content-links.mjs +14 -6
  41. package/engine/content-tables.mjs +45 -4
  42. package/engine/content-tasklists.mjs +189 -0
  43. package/engine/docs-checks.mjs +4 -4
  44. package/engine/extension-attributes.mjs +48 -2
  45. package/engine/field-reference.mjs +31 -10
  46. package/engine/field-spec.mjs +10 -8
  47. package/engine/foreign-catalog.mjs +143 -18
  48. package/engine/frontmatter-lint.mjs +120 -65
  49. package/engine/frontmatter.mjs +3 -3
  50. package/engine/heading-attributes.mjs +321 -0
  51. package/engine/helpers.mjs +109 -41
  52. package/engine/homepage.mjs +8 -8
  53. package/engine/index.mjs +3 -0
  54. package/engine/infobox-registry.mjs +4 -1
  55. package/engine/infobox-render.mjs +15 -3
  56. package/engine/infobox.mjs +129 -15
  57. package/engine/itinerary-scenes.mjs +4 -10
  58. package/engine/journals.mjs +294 -50
  59. package/engine/lore-events.mjs +197 -0
  60. package/engine/macros.mjs +1 -1
  61. package/engine/markdown-expressions.mjs +434 -59
  62. package/engine/note-dates.mjs +354 -12
  63. package/engine/note-frontmatter.mjs +7 -16
  64. package/engine/note-vocabulary.mjs +183 -42
  65. package/engine/page-lists.mjs +358 -0
  66. package/engine/pathnames.mjs +36 -0
  67. package/engine/pdf-build.mjs +431 -74
  68. package/engine/pdf-images.mjs +64 -0
  69. package/engine/pdf-render.mjs +563 -66
  70. package/engine/rank-ladder.mjs +198 -0
  71. package/engine/reckoning-markers.mjs +1 -1
  72. package/engine/retired-fields.mjs +8 -13
  73. package/engine/scenes.mjs +26 -7
  74. package/engine/site-build.mjs +166 -23
  75. package/engine/site-config.mjs +10 -1
  76. package/engine/site-index.mjs +90 -3
  77. package/engine/site-preview.mjs +20 -7
  78. package/engine/social-ties.mjs +52 -14
  79. package/engine/sql-tables.mjs +66 -9
  80. package/engine/standings.mjs +89 -6
  81. package/engine/system-block.mjs +56 -30
  82. package/engine/web-wikilinks.mjs +48 -1
  83. package/hm3/actors.mjs +8 -5
  84. package/manifest.mjs +43 -24
  85. package/package.json +1 -1
  86. package/packagebuild-metadata.jsonl +13 -13
  87. package/sohl/actors.mjs +22 -99
  88. package/sohl/item-fields.mjs +5 -5
  89. package/sohl/items.mjs +1 -1
  90. package/sohl/note-schemas.mjs +11 -21
  91. package/types/config.d.mts +9 -0
  92. package/types/content-config.d.mts +48 -1
  93. package/types/engine/actor-compiler.d.mts +11 -0
  94. package/types/engine/anchored-sections.d.mts +24 -0
  95. package/types/engine/anchors.d.mts +9 -4
  96. package/types/engine/art-fields.d.mts +64 -0
  97. package/types/engine/asset-index.d.mts +43 -0
  98. package/types/engine/asset-replacement.d.mts +22 -0
  99. package/types/engine/asset-types.d.mts +18 -0
  100. package/types/engine/base-compiler.d.mts +19 -17
  101. package/types/engine/base-styles.d.mts +93 -0
  102. package/types/engine/content-blocks.d.mts +87 -0
  103. package/types/engine/content-embeds.d.mts +16 -0
  104. package/types/engine/content-figures.d.mts +101 -0
  105. package/types/engine/content-footnotes.d.mts +80 -0
  106. package/types/engine/content-format-check.d.mts +8 -6
  107. package/types/engine/content-images.d.mts +78 -12
  108. package/types/engine/content-index.d.mts +8 -2
  109. package/types/engine/content-tables.d.mts +12 -4
  110. package/types/engine/content-tasklists.d.mts +72 -0
  111. package/types/engine/extension-attributes.d.mts +22 -1
  112. package/types/engine/field-reference.d.mts +21 -0
  113. package/types/engine/field-spec.d.mts +20 -16
  114. package/types/engine/foreign-catalog.d.mts +39 -0
  115. package/types/engine/frontmatter.d.mts +1 -1
  116. package/types/engine/heading-attributes.d.mts +179 -0
  117. package/types/engine/helpers.d.mts +29 -10
  118. package/types/engine/homepage.d.mts +7 -7
  119. package/types/engine/index.d.mts +1 -0
  120. package/types/engine/infobox-registry.d.mts +4 -1
  121. package/types/engine/infobox-render.d.mts +6 -1
  122. package/types/engine/infobox.d.mts +6 -0
  123. package/types/engine/journals.d.mts +41 -12
  124. package/types/engine/lore-events.d.mts +14 -0
  125. package/types/engine/markdown-expressions.d.mts +46 -2
  126. package/types/engine/note-dates.d.mts +64 -1
  127. package/types/engine/note-frontmatter.d.mts +1 -4
  128. package/types/engine/note-vocabulary.d.mts +82 -6
  129. package/types/engine/page-lists.d.mts +126 -0
  130. package/types/engine/pathnames.d.mts +26 -0
  131. package/types/engine/pdf-build.d.mts +28 -6
  132. package/types/engine/pdf-images.d.mts +52 -0
  133. package/types/engine/pdf-render.d.mts +84 -2
  134. package/types/engine/rank-ladder.d.mts +32 -0
  135. package/types/engine/retired-fields.d.mts +4 -6
  136. package/types/engine/site-build.d.mts +27 -4
  137. package/types/engine/site-config.d.mts +9 -0
  138. package/types/engine/site-index.d.mts +67 -1
  139. package/types/engine/site-preview.d.mts +1 -1
  140. package/types/engine/sql-tables.d.mts +31 -7
  141. package/types/engine/standings.d.mts +21 -3
  142. package/types/engine/system-block.d.mts +37 -14
  143. package/types/engine/web-wikilinks.d.mts +3 -1
  144. package/types/hm3/actors.d.mts +21 -3
  145. package/types/manifest.d.mts +19 -23
  146. package/types/sohl/actors.d.mts +6 -7
  147. package/engine/content-admonitions.mjs +0 -114
  148. package/engine/content-captions.mjs +0 -181
  149. package/engine/content-secrets.mjs +0 -112
  150. package/types/engine/content-admonitions.d.mts +0 -24
  151. package/types/engine/content-captions.d.mts +0 -45
  152. package/types/engine/content-secrets.d.mts +0 -17
@@ -17,7 +17,11 @@ An Address names content independently of its filename or folder. Its full form
17
17
 
18
18
  The part after `|` is the displayed link text or an embed's alternative text. An empty label uses the target's current name in a regular link and marks an embed decorative. A missing pipe is an error. Search may use `name.full` and aliases; authored links use Addresses. The [Address reference](../reference/format-details.md#addresses) explains short forms, package qualification, and ambiguity diagnostics.
19
19
 
20
- A link to a note tagged `draft` retains its target and carries a visible draft cue. In a book, the cue reads `(draft)` after the link. A link that resolves nowhere produces a diagnostic and reads `(unresolved link)` after its label in the book.
20
+ A link to a note tagged `draft` retains its target and carries a visible draft cue. In a book, the cue reads `(draft)` after the link.
21
+
22
+ **An address that resolves nowhere is an error on all three surfaces**, worded the same way by each. The pack build refuses the note and produces no pack from it. The site build and the book build write what they can and fail the run: a page marks the link for the reader, and a book sets `(unresolved link)` after its label. The book is written so the page carrying the mistake can be found, not because the mistake is tolerated there.
23
+
24
+ An HTML comment is an aside to whoever reads the note next, and no surface shows one — the packs and the website emit it into the HTML, where it stays a comment, and a book leaves it out. Write a `markdownlint` pragma or a note about a quoted passage freely. A comment inside a fence or a code span is an example and is printed as written, and a comment that is never closed is an error.
21
25
 
22
26
  ## Journal pages and anchors
23
27
 
@@ -33,42 +37,133 @@ The harbor faces west.
33
37
  Travelers present their papers here. See [[#harbor|the harbor]].
34
38
  ```
35
39
 
36
- Another note can link to `[[lore-harbor#customs-house|Customs House]]`. The special Actor anchors `{#appearance}` and `{#dossier}` feed the same authored sections to each system's Actor fields. For Items, `{#description}` selects the JournalEntryPage both systems reference. A map note can use an anchored heading for a scene pin; the compiler replaces the pin's exported JournalEntry and page IDs with the built note's IDs and uses the heading text as its label. See [map notes](../reference/format-details.md#type-map) for scene fields.
40
+ A `:::figure` fence starts a JournalEntryPage too, in Foundry alone — the web and the book keep the fence in the flow of the page around it. A fence holding exactly one picture, captioned with plain text and nothing else, becomes a page of type `image`: the picture is its `src`, the caption is `image.caption`, and the page is named for the figure's own number, such as "Figure 3". A fence carrying no caption still takes the `image` type when it holds one picture, named the same way. Every other fence takes a page of type `text` instead — a grouped fence holding more than one picture, an audio embed, a fence whose caption carries emphasis, an icon, or a link, and a fence that shares its page with trailing prose before the next heading or fence.
37
41
 
38
- ## Captions and numbered references
42
+ The prose introducing a fence and the fence itself land on two different Foundry pages, so a reference such as `{{ref "#trade"}}` written just above a fence sends a Foundry reader to a different page than the one carrying the sentence. Give the introducing prose its own heading, or accept that following the reference leaves the page, before relying on a fence to sit inside a longer page's flow.
39
43
 
40
- A caption fence labels the next Markdown block. Give it an ID so prose can refer to the block without hard-coding its number:
44
+ A heading's braces hold an attribute block, not an anchor alone: `#slug` names the anchor, `.class` adds a CSS class, and `key="value"` writes an attribute. The website and a Foundry journal carry all three onto the heading; a book keeps the anchor and drops the rest, because Typst has no stylesheet to name a class in. Braces holding anything but an attribute block — `# The set {a, b}` — are reported at the heading's line and set as written.
41
45
 
42
- ````markdown
43
- Refer to [[#trade|]] for the market routes.
46
+ Another note can link to `[[lore-harbor#customs-house|Customs House]]`. For Items, `{#description}` selects the JournalEntryPage both systems reference, and accepts a heading at any level. A map note can use an anchored heading for a scene pin; the compiler replaces the pin's exported JournalEntry and page IDs with the built note's IDs and uses the heading text as its label. See [map notes](../reference/format-details.md#type-map) for scene fields.
44
47
 
45
- :::caption {#trade}
46
- Regional trade routes
47
- :::
48
+ ### A section players must not read
49
+
50
+ Write `.secret` on the heading that opens the section:
51
+
52
+ ```markdown
53
+ # The Cellar {#cellar .secret}
54
+
55
+ The contraband is behind the false wall.
56
+ ```
57
+
58
+ The section runs from that heading to the next heading that opens a page. In Foundry the page is the GM's alone. On the website and in the book the section sits inside the same expandable spoiler and the same labelled block a `:::secret` block gets. The heading keeps its id, so `[[lore-inn#cellar|the cellar]]` still resolves to it.
59
+
60
+ **On the website this is a spoiler, not access control.** The withheld section is in the published page, so anyone who opens the element or reads the page source reads it. Foundry is the only surface that withholds anything. Write nothing in a `.secret` section that would matter if a player read it on the web.
61
+
62
+ A `:::secret` block inside the section is legal and renders as it does anywhere else. `.secret` on a heading that opens no page — a lower-level heading with no anchor — covers no page, and is reported at its own line: give that heading an anchor, or raise it to the top level.
63
+
64
+ The special Actor anchors `{#appearance}` and `{#dossier}` feed the same authored sections to each system's Actor fields, and take an H1 only — not the lower-level heading a journal page may otherwise start from. The section runs to the next H1, nested headings included. An anchor declared on a lower heading is still found, and is a build error rather than a quietly empty field: move the heading to the top level, or drop the anchor if the section is meant to stay unaddressed prose.
65
+
66
+ ## Figures and numbered references
67
+
68
+ A `:::figure` fence numbers whatever it holds and, written with an id, makes it a target `{{ref}}` can point to:
69
+
70
+ ````markdown
71
+ :::figure {#trade}
48
72
 
49
73
  ```sql
50
74
  SELECT name.full AS "Market" FROM notes WHERE type = 'place'
51
75
  ```
76
+
77
+ ///
78
+ Regional trade routes
79
+ :::
80
+
81
+ Refer to {{ref "#trade"}} for the market routes.
52
82
  ````
53
83
 
54
- The table displays **Table 1: Regional trade routes**, and the reference displays **Table 1**. The number is assigned by the output. An explicit link label, such as `[[#trade|the trade table]]`, displays the written label. A caption can precede a code fence, table, image, or any prose block. An ordinary code fence is code; an SQL result is a table; an image is a figure; every other block is prose. Web pages and Foundry journals number each kind within the note. Books number each kind across the book in reading order. A caption needs a unique ID, nonempty text, a closing `:::`, and a following block.
84
+ The fence displays **Table 1: Regional trade routes**, and `{{ref "#trade"}}` displays **Table 1** as a link. **A figure is numbered because it is a fence, not because it carries caption text** — an ordinary table or picture written outside any fence is numbered not at all.
55
85
 
56
- ## Images and protected content
86
+ **The construct is `:::figure`**, with an optional `{#id .border}` attribute block, closed by `:::`. Everything between the opening line and the closing `:::` is the captioned thing.
87
+
88
+ **The first `///` at the top level of the fence separates the captioned thing from its caption.** A second `///` at the top level is an error — a figure holds one caption. A `///` written inside a nested code fence is content, not a separator, so a captioned code listing can carry `///` as a comment of its own.
89
+
90
+ **The caption is optional.** A fence with no `///` draws its label alone, with nothing under it. So the counter and the label come from the fence itself, whether or not an author writes a caption.
91
+
92
+ **`{#id}` is optional too.** A fence that declares none is still numbered and drawn; it carries no anchor, so nothing can refer to it. Give a figure an id only when prose needs to point at it.
93
+
94
+ **`.border` draws the figure and its label inside a border.** It is the only class the construct accepts — any other class, such as `.wide`, is an error naming the class. `key="value"` is refused outright: a figure takes an id and classes, nothing else.
95
+
96
+ **The counter is derived from the fence's contents, never authored.** A table is a table; an `sql` fence is a table, because its rendered output is one; any other fence or code block is code; pictures alone, one or several, are a figure; everything else is prose. A fence around prose with `.border` is a numbered, referable boxed aside — the `Prose` counter's whole purpose. The counters are `Code`, `Table`, `Figure`, `Map`, and `Prose`. The website and a Foundry journal number each counter within the note; a book numbers each counter across the book in reading order.
97
+
98
+ **A picture counts as `Map` instead of `Figure` when the asset it draws declares `role: map`** in its provenance — see [the asset provenance reference](../reference/format-details.md). The fence's own markup cannot say so; a picture of a map looks like any other picture, so the fact rides entirely on the asset's own record. A fence grouping several pictures counts as `Map` only when every one of them declares the role; one picture without it keeps the whole fence a `Figure`.
57
99
 
58
- An image or icon embed stands on its own line. `size` accepts `auto`, `small`, `medium`, `large`, and `xlarge`; the medium maps those names to suitable dimensions. `float` controls placement. The asset remains an Address, so moving the file within its asset root does not change the link. See [assets](assets.md) and [image directives](../reference/format-details.md#images).
100
+ **A reference is written `{{ref "#thorn"}}`**, rendering as a link reading "Figure 13". `form="full"` renders the number and the caption; `form="title"` renders the caption alone; `form="number"` is the default, and may be written explicitly. A link written inside the caption contributes only its label text to a `full` or `title` reference, because the reference is itself a link and an anchor cannot nest inside another.
59
101
 
60
- A `:::secret` block marks GM material while keeping it in the same authored note:
102
+ **A reference may name another note's figure**, writing the note's address before the anchor: `{{ref "place-thornford#trade"}}` points at the figure `#trade` in `place-thornford`. It renders the number that figure carries **on the surface doing the rendering** — its own count within `place-thornford` on the website and in a Foundry journal, and its place in the book's reading order in the book, which may differ from the number it carries on its own page. An anchor matching no figure in the addressed note is refused, and so is an address naming no note at all.
103
+
104
+ In a Foundry journal, a reference resolves to the figure's own JournalEntryPage — the `image` page a solitary, plainly captioned picture becomes, or the `text` page any other fence becomes — addressed exactly as an authored `[[#anchor|Text]]` link to the same anchor would be. A reference naming another note's figure is not resolved there: a journal addresses a page by UUID, and has none for a note outside the one compiling.
105
+
106
+ **A figure needs contents and a closing `:::`.** Its id, when one is given, must be unique in the note; its caption, when one is given, is the one `///` section the fence carries.
107
+
108
+ A few worked examples. A captioned picture:
61
109
 
62
110
  ```markdown
63
- :::secret
64
- The harbor master is working for the smugglers.
111
+ :::figure {#thorn}
112
+ ![[being-foobar|The Great Beast]]
113
+ ///
114
+ The great beast, as drawn by [[person-havard|Havard]].
115
+ :::
116
+ ```
117
+
118
+ This draws **Figure 1**, labelled "Figure 1: The great beast, as drawn by Havard." beneath the picture.
119
+
120
+ A grouped fence holding two pictures as one figure — a plate rather than two separate plates:
121
+
122
+ ```markdown
123
+ :::figure {#passes}
124
+ ![[image-northpass|Northern approach]]
125
+ ![[image-southpass|Southern approach]]
126
+ ///
127
+ The two approaches to the pass.
128
+ :::
129
+ ```
130
+
131
+ Both pictures draw together under one label, **Figure 2**, because the fence is the unit the counter numbers — not the pictures inside it.
132
+
133
+ A fence with no caption:
134
+
135
+ ```markdown
136
+ :::figure {#anvil}
137
+ ![[icon-anvil|A worn anvil]]
65
138
  :::
66
139
  ```
67
140
 
68
- Players who deliberately inspect source or generated output may see it; the block is a presentation distinction, not access control. The [detailed reference](../reference/format-details.md#what-a-note-produces) describes each medium's handling.
141
+ This draws **Figure 3**, with no caption line beneath it, and still carries the anchor `#anvil`.
142
+
143
+ A reference in running prose:
144
+
145
+ ```markdown
146
+ See {{ref "#thorn"}} for the plate, or {{ref "#thorn" form="full"}} for the full description.
147
+ ```
148
+
149
+ This displays as "See [Figure 1](#thorn) for the plate, or [Figure 1: The great beast, as drawn by Havard.](#thorn) for the full description," each bracketed phrase a link to the figure.
150
+
151
+ A reference to a figure in another note:
152
+
153
+ ```markdown
154
+ The market routes are drawn in {{ref "place-thornford#trade"}}.
155
+ ```
156
+
157
+ On the website this links into `place-thornford`'s own page, reading "Table 1" — the number that fence carries there, whatever number it carries in the book or in a note of its own.
158
+
159
+ ## Images and protected content
160
+
161
+ An image or icon embed stands on its own line; one that does not is a lint error naming the line, because a width or a position means nothing applied to a word in the middle of a sentence. `size` accepts `auto`, `small`, `medium`, `large`, `xlarge` and `full-width`; the medium maps those names to suitable dimensions, and `full-width` is the full page in the book and the full content width elsewhere. `float` controls placement and takes `top-left`, `top-right`, `bottom-left`, `bottom-right` or `center`; on a page a float occupies the column measure, so only the vertical half of a corner position has an effect there. The asset remains an Address, so moving the file within its asset root does not change the link. See [assets](assets.md) and [image directives](../reference/format-details.md#images).
162
+
163
+ ### Named blocks
69
164
 
70
- Use `:::info` for a neutral note and `:::warn` for a caution. Both render as
71
- labelled, colored boxes on the web and in Foundry, and as print boxes in a book:
165
+ Three named blocks set a passage apart from the prose around it. `:::secret`
166
+ marks GM material, `:::info` a neutral note, and `:::warn` a caution:
72
167
 
73
168
  ```markdown
74
169
  :::warn
@@ -76,8 +171,66 @@ The bridge closes during the spring flood.
76
171
  :::
77
172
  ```
78
173
 
79
- An optional `{#id}` gives the box an HTML anchor. These blocks need a closing
80
- `:::` and cannot be nested.
174
+ Each heads itself with its own name — **Secret**, **Info**, **Warn** — and takes
175
+ a closing `:::`. A name that is not one of the three is reported.
176
+
177
+ **A GM-only section holds a box.** A `:::secret` is a container for whatever the
178
+ GM reads, boxes included, and a box written inside one stays inside it. A box
179
+ holds no named block of its own, so a second box inside a box is reported. A
180
+ `:::figure` fence may be written inside any of them. The two draw a box for
181
+ different reasons: a named block says what kind of aside a passage is, while a
182
+ `:::figure` fence makes a passage numbered and referable — nesting one inside
183
+ the other gives a passage both.
184
+
185
+ **An attribute block follows the name.** It is the same braced grammar the other
186
+ body extensions use, so it carries an id, classes, and any attribute the element
187
+ should have. `title` is reserved and supplies the heading in place of the default:
188
+
189
+ ```markdown
190
+ :::secret {#harbor title="For the GM" .wide data-stage="two"}
191
+ The harbor master is working for the smugglers.
192
+ :::
193
+ ```
194
+
195
+ That block carries the classes `secret wide`, the id `harbor`, and
196
+ `data-stage="two"`, and it heads **For the GM**. A title carries emphasis of its
197
+ own — `title="The *Genzet* only"` — and nothing else: a tag written there is
198
+ shown rather than obeyed. Set the id with `#id` and classes with `.class` rather
199
+ than through an `id=` or `class=` key, and note that an event handler such as
200
+ `onclick=` is refused rather than written.
201
+
202
+ **The name is the first class on the element, and the appearance belongs to a
203
+ stylesheet.** No block is emitted with an inline `style`, so a package restyles
204
+ one from its own sheet. `info` and `warn` are styled by the base stylesheet;
205
+ `secret` is left to Foundry, which styles `section.secret` itself and owns the
206
+ reveal control on it.
207
+
208
+ Each surface renders the same block its own way:
209
+
210
+ | Surface | Output |
211
+ | ------- | ---------------------------------------------------------------------------------- |
212
+ | Foundry | `<section class="secret" id="harbor">` with the title as its first line |
213
+ | Web | `<details class="secret" id="harbor">` the reader opens, titled in its `<summary>` |
214
+ | Book | a coloured print box headed by the title |
215
+
216
+ A `secret` with no id of its own is given one derived from its body on the
217
+ Foundry surface, because Foundry remembers a revealed section by its id.
218
+
219
+ **A secret is a presentation distinction, not access control.** Players who
220
+ deliberately inspect source or generated output may see it. The
221
+ [detailed reference](../reference/format-details.md#what-a-note-produces)
222
+ describes each medium's handling.
223
+
224
+ **One malformed block does not silence the others.** A block whose attributes do
225
+ not parse is reported at its own line and left as written; every well-formed
226
+ block in the same note still renders.
227
+
228
+ **A heading that would start its own journal page cannot be written inside a
229
+ block, or inside a `:::figure` fence.** An H1, or a heading of any level
230
+ carrying an `{#anchor}`, is reported at its own line there — starting a page
231
+ would tear the block or the figure in two, and a reader could open the second
232
+ half with none of the box around it. A lower heading with no anchor is
233
+ unaffected and is the ordinary way to structure a long box.
81
234
 
82
235
  ## Footnotes and definition lists
83
236
 
@@ -95,14 +248,19 @@ The road is passable in summer.[^season] The bridge has a toll.[^2]
95
248
  [^2]: The toll is collected at the eastern gate.
96
249
  ```
97
250
 
98
- Definitions can appear anywhere at the top level of the note. Indent a
99
- following paragraph or code block by four spaces to include it in the same
100
- footnote. Keep definitions outside lists, block quotes, and tables. On the web,
101
- linked footnotes appear in a Footnotes section at the bottom of the HTML page.
102
- In Foundry, each JournalEntryPage places its referenced footnotes in a Footnotes
103
- section at the bottom of that page; definitions remain available across pages
104
- of the same note. In a book, footnotes appear in smaller type at the bottom of
105
- the page that contains their reference.
251
+ A reference may be written anywhere prose can be written — a word in a
252
+ quotation, a bullet, a table cell, a heading, a named block, a figure's
253
+ caption. A
254
+ definition belongs at the top level of the note: indent a following paragraph
255
+ or code block by four spaces to include it in the same footnote, but never
256
+ inside a list, a block quote, or a table. A definition written there, or a
257
+ reference with none at the top level to resolve against, is reported at its
258
+ own line rather than rendered. On the web, linked footnotes appear in a
259
+ Footnotes section at the bottom of the HTML page. In Foundry, each
260
+ JournalEntryPage places its referenced footnotes in a Footnotes section at the
261
+ bottom of that page; definitions remain available across pages of the same
262
+ note. In a book, footnotes appear in smaller type at the bottom of the page
263
+ that contains their reference.
106
264
 
107
265
  A definition list gives a term one or more definitions:
108
266
 
@@ -115,9 +273,19 @@ Janapada
115
273
  The web and Foundry render a definition list with HTML `<dl>`, `<dt>`, and
116
274
  `<dd>` elements. The book renders it as a term list.
117
275
 
118
- ## Tables and expressions
276
+ ## Lists
277
+
278
+ A list item's text begins right after its marker. `[ ]` and `[x]` there are
279
+ plain words, not a checkbox — write what is done into the item itself:
280
+
281
+ ```markdown
282
+ - the harbor toll: paid
283
+ - the ferry crossing: owed
284
+ ```
285
+
286
+ ## SQL generated tables
119
287
 
120
- Use a SQL fence to render a table from the content index. The table reads frontmatter from indexed notes. A zero-row result is an error unless the fence explicitly permits an empty result.
288
+ A fence marked `sql` is replaced by a table built from the content index. The query reads the frontmatter of every indexed note, so a table is written once and stays true as notes are added.
121
289
 
122
290
  ````markdown
123
291
  ```sql
@@ -125,13 +293,135 @@ SELECT name.full AS "Name" FROM notes WHERE type = 'lore' ORDER BY name.full
125
293
  ```
126
294
  ````
127
295
 
128
- Inline expressions can read the note, format dates, combine values, or run scalar SQL:
296
+ A column's heading is its SQL alias, so `AS "Name"` is what a reader sees. A cell that is absent prints an em dash. A `|` or a line break inside a value is escaped, so neither breaks the table.
297
+
298
+ ### What a query can read
299
+
300
+ | Relation | What it holds |
301
+ | ----------------- | ----------------------------------------------------------------------------------------------------------------------------- |
302
+ | `notes` | Every note except a stub. This is the one to use unless a stub is wanted. |
303
+ | `entries` | Every note, stubs included. |
304
+ | `market` | The market scale as three columns — `value`, `name`, `trade` — so a table can print `village` beside the number a note wrote. |
305
+ | `<package>.notes` | The same two relations for each package this one declares a dependency on, in a schema named for it. |
306
+
307
+ Nested frontmatter is addressed exactly as a note writes it: `name.full`, `sohl.weight`, `data.born`. Every note carries a computed `state` column of `full`, `draft` or `stub`. Notes tagged `gm` are absent from the relations on public surfaces.
308
+
309
+ ### Fence attributes
310
+
311
+ A braced list may follow the language on the opening fence. Two attributes are accepted, and anything else is reported by name.
312
+
313
+ | Attribute | Value | Default | What it does |
314
+ | --------------- | --------------------------- | ------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
315
+ | `allow-empty` | `true` or `false` | `false` | Whether a query selecting no rows is allowed. A zero-row result is a finding otherwise, because a table that silently prints nothing is the commonest way a query goes wrong unnoticed. |
316
+ | `section-level` | an integer from 1 through 6 | `2` | The heading level given to each group a `_section` column creates. |
317
+
318
+ ````markdown
319
+ ```sql {allow-empty=true section-level=3}
320
+ SELECT name.full AS "Name" FROM notes WHERE type = 'being' AND data.gender = 'none'
321
+ ```
322
+ ````
323
+
324
+ A zero-row result reports which relation was queried: a query over `notes` that would have found rows in `entries` says so, so the fix is `FROM entries` rather than `{allow-empty=true}`.
325
+
326
+ ### Two column names the renderer reads
327
+
328
+ Two aliases are instructions rather than columns, and neither is printed.
329
+
330
+ | Alias | What it does |
331
+ | ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
332
+ | `_ref` | The note a row points at, written as the `type-shortcode` address a wikilink resolves. The row's first column becomes a link to that note. |
333
+ | `_section` | Partitions the result into separate tables, each under a heading of its own. Order the query by the same expression, because a group ends where the value changes. |
334
+
335
+ ```sql
336
+ SELECT type AS "_section", name.full AS "Name", address.slug AS "_ref"
337
+ FROM notes WHERE type IN ('lore', 'place') ORDER BY type, name.full
338
+ ```
339
+
340
+ See [SQL details](../reference/format-details.md#content-tables) for query options.
341
+
342
+ ## Page lists
343
+
344
+ A fence marked `pagelist` is replaced by a list of the pages carrying a tag. Where a content table answers "what do these notes say?", a page list answers "which pages belong to this group?" — and the group is expressed by intent rather than enumerated, so a page joins it by being tagged and leaves it by being untagged.
345
+
346
+ ````markdown
347
+ ```pagelist {tag="key-concept"}
348
+
349
+ ```
350
+ ````
351
+
352
+ **The fence holds nothing.** Everything the directive says about itself is written in the braced attribute block, in the same grammar every other body extension uses. Text written inside the fence is reported rather than printed.
353
+
354
+ Each page comes out as a link to the note, so one authored directive reaches a reader as a compendium link in Foundry, a page link on the website, and a page reference in the book. A link into a note tagged `draft` carries the same cue an authored link to it would.
355
+
356
+ ### Page-list attributes
357
+
358
+ <!-- page-list-attributes:start -->
359
+
360
+ | Attribute | Value | Default | What it does |
361
+ | -------------- | ----------------- | ---------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
362
+ | `tag` | a tag | required | The tag a page carries to join the list. Matched the way `tags:` is read — case and a leading `#` are not significant, the spelling of the tag itself is. |
363
+ | `type` | a note type | every type | Restricts the list to one note type. A page's address is `<type>-<shortcode>`, so the type is what divides the corpus into kinds of page. |
364
+ | `sort` | `name` or `type` | `name` | The order the pages come out in. `name` is alphabetical; `type` groups the note types and orders by name within each. |
365
+ | `descriptions` | `true` or `false` | `false` | Whether each page's `description` follows its link. A page that states none carries its link alone. |
366
+ | `allow-empty` | `true` or `false` | `false` | Whether a tag carried by no page is allowed. It is a finding otherwise, because a heading with nothing under it is how a misspelled tag goes unnoticed. |
367
+
368
+ <!-- page-list-attributes:end -->
369
+
370
+ ````markdown
371
+ ```pagelist {tag="key-concept" type=lore sort=type descriptions=true}
372
+
373
+ ```
374
+ ````
375
+
376
+ That list names the `lore` notes tagged `key-concept`, grouped by type and alphabetical within each, with every page's `description` after its link. A page that states no description carries its link alone.
377
+
378
+ **A tag no page carries is a finding** at the fence's own line, naming the tag it looked for. A misspelled tag is the whole of what goes wrong here, and a heading with an empty list under it is how one goes unnoticed. Write `{allow-empty=true}` where a group that is genuinely empty is intended; the directive then leaves nothing behind rather than an empty list.
379
+
380
+ A page list names this package's own pages. A **stub** — a note with an empty body — publishes no page, so it joins no list. A note tagged `gm` is listed in Foundry and left out of the website and the book, exactly as it is left out of a content table's relations.
381
+
382
+ See [page-list details](../reference/format-details.md#page-lists) for the directive's full behaviour.
383
+
384
+ ## Expressions
385
+
386
+ An expression in `{{ }}` is replaced by a value, inline in a sentence. The note's own frontmatter is the context, so a field is read by the path a note writes it at.
129
387
 
130
388
  ```markdown
131
- {{name.full}} was born on {{dateformat "vrcal" data.born}}.
389
+ {{name.full}} was born on {{dateformat data.calendar data.born}}.
132
390
  There are {{words (sql "SELECT COUNT(*) FROM notes WHERE type = 'being'")}} beings.
133
- The index contains {{digits (sql "SELECT COUNT(*) FROM entries")}} entries.
134
- {{and (gt 3 5) (lt 4 2)}}
135
391
  ```
136
392
 
137
- Use `{{dateformat data.calendar data.born}}` when the note supplies a calendar Address. A third argument selects a named output pattern, as in `{{dateformat data.calendar data.born "long"}}`. SQL fences produce tables; the `sql` helper returns one value for an inline expression. Wrap a numeric result in `words` for running prose or `digits` for a grouped numeral. See [date rules](dates-and-calendars.md) and [SQL details](../reference/format-details.md#content-tables) for query options.
393
+ ### The rules of the construct
394
+
395
+ - **A field that the note does not carry is a finding, not a blank.** Expressions are compiled strictly, so a misspelled path is reported rather than quietly printing nothing.
396
+ - **An expression is one line.** It may not contain a line break or a brace.
397
+ - **A failed expression leaves its own text in the page** and reports the line and column it is written on, so the page is readable and the fault is locatable.
398
+ - **`\{{` writes an expression without evaluating it**, for an example in prose.
399
+ - **`{{{…}}}`, `{{<…}}` and `{{%…}}` are left alone.** A Hugo shortcode is not an expression.
400
+ - **An expression inside a code fence is left alone.**
401
+ - Helpers nest, with the inner call in parentheses: `{{words (sql "…")}}`.
402
+
403
+ ### Helpers
404
+
405
+ <!-- expression-helpers:start -->
406
+
407
+ | Helper | Parameters | What it gives you |
408
+ | ------------ | ------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
409
+ | `eq` | `a b` | True when both are the same value. Compares exactly, so 1 and "1" differ. |
410
+ | `gt` | `a b` | True when a is greater than b. Both must be finite numbers. |
411
+ | `gte` | `a b` | True when a is greater than or equal to b. Both must be finite numbers. |
412
+ | `lt` | `a b` | True when a is less than b. Both must be finite numbers. |
413
+ | `lte` | `a b` | True when a is less than or equal to b. Both must be finite numbers. |
414
+ | `not` | `value` | True when the value is false, zero, empty or absent. |
415
+ | `and` | `value …` | True when every value given is true. Takes two or more. |
416
+ | `or` | `value …` | True when any value given is true. Takes two or more. |
417
+ | `words` | `number` | The number spelled out for running prose — `1200` becomes one thousand two hundred. Whole numbers only; a decimal is a finding, and `digits` takes one. |
418
+ | `digits` | `number` | The number as a grouped numeral — `1200` becomes 1,200, and a fractional part is kept. |
419
+ | `sql` | `"query"` | The single value a query returns, for use inside a sentence. The query is a quoted string, the same SQL a table fence takes. |
420
+ | `dateformat` | `calendar date ["format"]` | A date written in a calendar. The calendar is an Address or its shortcode, the date is the value a note carries, and the optional third argument names an output pattern. |
421
+ | `ref` | `address [form="number"\|"full"\|"title"]` | A link to a `:::figure` fence, by its anchor — `"#thorn"` on this note, `"note-address#thorn"` on another. `form` is `number` (the default, and admitted explicitly), rendering "Figure 13"; `full`, the number and the caption; or `title`, the caption alone. Always renders as a link to the figure, and a link inside the caption contributes only its label text. The number rendered is the one the figure carries on the surface doing the rendering, whichever note names it. |
422
+
423
+ <!-- expression-helpers:end -->
424
+
425
+ A helper that is given the wrong kind of value reports it rather than printing something wrong: `words` refuses a decimal, the four comparisons refuse anything that is not a finite number, and `dateformat` refuses a date it cannot resolve in the calendar it was given.
426
+
427
+ See [date rules](dates-and-calendars.md) for what `dateformat` accepts and the patterns its third argument names.
package/docs/commands.md CHANGED
@@ -1455,15 +1455,17 @@ package-build deps fetch [--from <zip|dir>] [--id <id>]
1455
1455
  **DESCRIPTION**
1456
1456
 
1457
1457
  The only action is `fetch`, which fills three caches under `build/cache/`:
1458
- the **content index** of every declared dependency, the **item catalogue**
1459
- of those that additionally declare `itemCatalog: true`, and the **site
1460
- navigation** — `https://www.heroiclands.org/nav.json`, the header menu
1461
- every package site renders, fetched for every package because every package
1462
- publishes a site. Its own command rather than a step of `package compile`
1463
- or `site`, so neither reaches the network — a build that downloads silently
1464
- is not reproducible and hides a dependency's version change behind a
1465
- passing run. Each cache is stamped complete only once its fetch finishes,
1466
- so a half-finished one reads as cold.
1458
+ the **content index** of every declared dependency, the **item catalogue or
1459
+ asset archive** of those that additionally declare `itemCatalog: true` or
1460
+ `assetArchive: true`, and the **site navigation** —
1461
+ `https://www.heroiclands.org/nav.json`, the header menu every package site
1462
+ renders, fetched for every package because every package publishes a site.
1463
+ A relationship declaring both `itemCatalog: true` and `assetArchive: true`
1464
+ is fetched once. Its own command rather than a step of `package compile` or
1465
+ `site`, so neither reaches the network — a build that downloads silently is
1466
+ not reproducible and hides a dependency's version change behind a passing
1467
+ run. Each cache is stamped complete only once its fetch finishes, so a
1468
+ half-finished one reads as cold.
1467
1469
 
1468
1470
  `lint`, `links`, `site`, `pdf`, `map`, `reachability`, and `package compile`
1469
1471
  check the declared dependencies' content-index cache before reading notes or
@@ -1501,7 +1503,7 @@ including when the repository declares no dependencies at all.
1501
1503
  ```
1502
1504
  $ package-build deps fetch
1503
1505
  […] Fetched the site navigation to build/cache/navigation/nav.json.
1504
- […] No relationship declares `itemCatalog: true`; nothing to fetch.
1506
+ […] No relationship declares `itemCatalog: true` or `assetArchive: true`; nothing to fetch.
1505
1507
  […] This package declares no dependencies.
1506
1508
 
1507
1509
  $ package-build deps fetch --from build/dist/module.zip