@heroiclands/package-build 22.20.0 → 22.22.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 (118) hide show
  1. package/CHANGELOG-content-build.md +0 -8
  2. package/CHANGELOG.md +204 -0
  3. package/assets/styles/content-base.css +162 -0
  4. package/bin/content-commands.mjs +26 -3
  5. package/bin/package-build.mjs +15 -2
  6. package/ci/content-format-reference.mjs +68 -1
  7. package/config.mjs +26 -0
  8. package/content-config.mjs +73 -4
  9. package/docs/api.md +46 -28
  10. package/docs/authoring/dates-and-calendars.md +4 -2
  11. package/docs/authoring/frontmatter.md +2 -2
  12. package/docs/authoring/links-and-markup.md +237 -28
  13. package/docs/configuration.md +27 -1
  14. package/docs/content-format.md +3 -1
  15. package/docs/guides/reference-pages.md +15 -0
  16. package/docs/reference/format-details.md +265 -42
  17. package/docs/reference/note-types.md +45 -28
  18. package/engine/actor-compiler.mjs +23 -0
  19. package/engine/anchored-sections.mjs +35 -4
  20. package/engine/anchors.mjs +10 -20
  21. package/engine/base-compiler.mjs +86 -21
  22. package/engine/base-styles.mjs +149 -0
  23. package/engine/bundles.mjs +0 -1
  24. package/engine/compendiums.mjs +8 -1
  25. package/engine/content-blocks.mjs +410 -0
  26. package/engine/content-captions.mjs +57 -8
  27. package/engine/content-footnotes.mjs +189 -0
  28. package/engine/content-format-check.mjs +15 -12
  29. package/engine/content-index.mjs +2 -2
  30. package/engine/content-links.mjs +14 -6
  31. package/engine/content-tables.mjs +45 -4
  32. package/engine/content-tasklists.mjs +189 -0
  33. package/engine/docs-checks.mjs +4 -4
  34. package/engine/extension-attributes.mjs +48 -2
  35. package/engine/frontmatter-lint.mjs +62 -66
  36. package/engine/frontmatter.mjs +3 -3
  37. package/engine/heading-attributes.mjs +308 -0
  38. package/engine/helpers.mjs +19 -13
  39. package/engine/homepage.mjs +8 -8
  40. package/engine/index.mjs +3 -0
  41. package/engine/infobox-render.mjs +21 -4
  42. package/engine/infobox.mjs +167 -7
  43. package/engine/journals.mjs +36 -17
  44. package/engine/macros.mjs +1 -1
  45. package/engine/markdown-expressions.mjs +178 -53
  46. package/engine/note-addresses.mjs +17 -8
  47. package/engine/note-dates.mjs +49 -0
  48. package/engine/note-frontmatter.mjs +7 -16
  49. package/engine/note-vocabulary.mjs +216 -42
  50. package/engine/page-lists.mjs +358 -0
  51. package/engine/pdf-build.mjs +76 -42
  52. package/engine/pdf-render.mjs +422 -49
  53. package/engine/rank-ladder.mjs +198 -0
  54. package/engine/reckoning-markers.mjs +1 -1
  55. package/engine/reference-targets.mjs +13 -1
  56. package/engine/retired-fields.mjs +8 -13
  57. package/engine/scenes.mjs +10 -3
  58. package/engine/site-build.mjs +41 -12
  59. package/engine/site-config.mjs +10 -1
  60. package/engine/site-index.mjs +39 -4
  61. package/engine/site-preview.mjs +18 -7
  62. package/engine/social-ties.mjs +52 -14
  63. package/engine/sql-tables.mjs +66 -9
  64. package/engine/standing-terms.mjs +19 -0
  65. package/engine/standings.mjs +538 -0
  66. package/engine/svg-theme.mjs +15 -4
  67. package/engine/system-block.mjs +12 -14
  68. package/engine/web-wikilinks.mjs +48 -1
  69. package/engine/wikilinks.mjs +3 -1
  70. package/hm3/actors.mjs +6 -3
  71. package/manifest.mjs +13 -0
  72. package/package.json +1 -1
  73. package/sohl/actors.mjs +82 -103
  74. package/sohl/item-fields.mjs +4 -5
  75. package/sohl/items.mjs +1 -1
  76. package/sohl/note-schemas.mjs +11 -21
  77. package/types/config.d.mts +9 -0
  78. package/types/engine/actor-compiler.d.mts +11 -0
  79. package/types/engine/anchored-sections.d.mts +24 -0
  80. package/types/engine/base-compiler.d.mts +19 -0
  81. package/types/engine/base-styles.d.mts +93 -0
  82. package/types/engine/content-blocks.d.mts +87 -0
  83. package/types/engine/content-captions.d.mts +14 -1
  84. package/types/engine/content-footnotes.d.mts +73 -0
  85. package/types/engine/content-format-check.d.mts +8 -6
  86. package/types/engine/content-tables.d.mts +12 -4
  87. package/types/engine/content-tasklists.d.mts +72 -0
  88. package/types/engine/extension-attributes.d.mts +22 -1
  89. package/types/engine/frontmatter.d.mts +1 -1
  90. package/types/engine/heading-attributes.d.mts +174 -0
  91. package/types/engine/helpers.d.mts +7 -4
  92. package/types/engine/homepage.d.mts +7 -7
  93. package/types/engine/index.d.mts +1 -0
  94. package/types/engine/infobox-render.d.mts +6 -1
  95. package/types/engine/journals.d.mts +14 -7
  96. package/types/engine/markdown-expressions.d.mts +23 -0
  97. package/types/engine/note-frontmatter.d.mts +1 -4
  98. package/types/engine/note-vocabulary.d.mts +98 -8
  99. package/types/engine/page-lists.d.mts +126 -0
  100. package/types/engine/pdf-render.d.mts +72 -2
  101. package/types/engine/rank-ladder.d.mts +32 -0
  102. package/types/engine/retired-fields.d.mts +4 -6
  103. package/types/engine/site-build.d.mts +13 -1
  104. package/types/engine/site-config.d.mts +9 -0
  105. package/types/engine/site-index.d.mts +13 -1
  106. package/types/engine/site-preview.d.mts +1 -1
  107. package/types/engine/sql-tables.d.mts +31 -7
  108. package/types/engine/standing-terms.d.mts +16 -0
  109. package/types/engine/standings.d.mts +147 -0
  110. package/types/engine/system-block.d.mts +11 -12
  111. package/types/engine/web-wikilinks.d.mts +3 -1
  112. package/types/engine/wikilinks.d.mts +2 -1
  113. package/types/hm3/actors.d.mts +3 -3
  114. package/types/sohl/actors.d.mts +25 -7
  115. package/engine/content-admonitions.mjs +0 -114
  116. package/engine/content-secrets.mjs +0 -112
  117. package/types/engine/content-admonitions.d.mts +0 -24
  118. 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,7 +37,29 @@ 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 `:::caption` block starts a JournalEntryPage too, named for the caption's own label, in Foundry alone — the web and the book keep the caption in the flow of the page around it. The prose introducing a captioned block and the block itself land on two different Foundry pages, so a reference such as `[[#trade|]]` written just above a caption 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 caption to sit inside a longer page's flow.
41
+
42
+ 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.
43
+
44
+ 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.
45
+
46
+ ### A section players must not read
47
+
48
+ Write `.secret` on the heading that opens the section:
49
+
50
+ ```markdown
51
+ # The Cellar {#cellar .secret}
52
+
53
+ The contraband is behind the false wall.
54
+ ```
55
+
56
+ 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.
57
+
58
+ **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.
59
+
60
+ 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.
61
+
62
+ 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.
37
63
 
38
64
  ## Captions and numbered references
39
65
 
@@ -55,29 +81,77 @@ The table displays **Table 1: Regional trade routes**, and the reference display
55
81
 
56
82
  ## Images and protected content
57
83
 
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).
84
+ 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).
85
+
86
+ ### Named blocks
59
87
 
60
- A `:::secret` block marks GM material while keeping it in the same authored note:
88
+ Three named blocks set a passage apart from the prose around it. `:::secret`
89
+ marks GM material, `:::info` a neutral note, and `:::warn` a caution:
61
90
 
62
91
  ```markdown
63
- :::secret
64
- The harbor master is working for the smugglers.
92
+ :::warn
93
+ The bridge closes during the spring flood.
65
94
  :::
66
95
  ```
67
96
 
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.
97
+ Each heads itself with its own name — **Secret**, **Info**, **Warn** — and takes
98
+ a closing `:::`. A name that is not one of the three is reported.
99
+
100
+ **A GM-only section holds a box.** A `:::secret` is a container for whatever the
101
+ GM reads, boxes included, and a box written inside one stays inside it. A box
102
+ holds no named block of its own, so a second box inside a box is reported. A
103
+ `:::caption` belongs to the caption construct and may be written inside any of
104
+ them.
69
105
 
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:
106
+ **An attribute block follows the name.** It is the same braced grammar the other
107
+ body extensions use, so it carries an id, classes, and any attribute the element
108
+ should have. `title` is reserved and supplies the heading in place of the default:
72
109
 
73
110
  ```markdown
74
- :::warn
75
- The bridge closes during the spring flood.
111
+ :::secret {#harbor title="For the GM" .wide data-stage="two"}
112
+ The harbor master is working for the smugglers.
76
113
  :::
77
114
  ```
78
115
 
79
- An optional `{#id}` gives the box an HTML anchor. These blocks need a closing
80
- `:::` and cannot be nested.
116
+ That block carries the classes `secret wide`, the id `harbor`, and
117
+ `data-stage="two"`, and it heads **For the GM**. A title carries emphasis of its
118
+ own — `title="The *Genzet* only"` — and nothing else: a tag written there is
119
+ shown rather than obeyed. Set the id with `#id` and classes with `.class` rather
120
+ than through an `id=` or `class=` key, and note that an event handler such as
121
+ `onclick=` is refused rather than written.
122
+
123
+ **The name is the first class on the element, and the appearance belongs to a
124
+ stylesheet.** No block is emitted with an inline `style`, so a package restyles
125
+ one from its own sheet. `info` and `warn` are styled by the base stylesheet;
126
+ `secret` is left to Foundry, which styles `section.secret` itself and owns the
127
+ reveal control on it.
128
+
129
+ Each surface renders the same block its own way:
130
+
131
+ | Surface | Output |
132
+ | ------- | ---------------------------------------------------------------------------------- |
133
+ | Foundry | `<section class="secret" id="harbor">` with the title as its first line |
134
+ | Web | `<details class="secret" id="harbor">` the reader opens, titled in its `<summary>` |
135
+ | Book | a coloured print box headed by the title |
136
+
137
+ A `secret` with no id of its own is given one derived from its body on the
138
+ Foundry surface, because Foundry remembers a revealed section by its id.
139
+
140
+ **A secret is a presentation distinction, not access control.** Players who
141
+ deliberately inspect source or generated output may see it. The
142
+ [detailed reference](../reference/format-details.md#what-a-note-produces)
143
+ describes each medium's handling.
144
+
145
+ **One malformed block does not silence the others.** A block whose attributes do
146
+ not parse is reported at its own line and left as written; every well-formed
147
+ block in the same note still renders.
148
+
149
+ **A heading that would start its own journal page cannot be written inside a
150
+ block, or inside a caption.** An H1, or a heading of any level carrying an
151
+ `{#anchor}`, is reported at its own line there — starting a page would tear
152
+ the block in two, and a reader could open the second half with none of the
153
+ box around it. A lower heading with no anchor is unaffected and is the
154
+ ordinary way to structure a long box.
81
155
 
82
156
  ## Footnotes and definition lists
83
157
 
@@ -95,14 +169,18 @@ The road is passable in summer.[^season] The bridge has a toll.[^2]
95
169
  [^2]: The toll is collected at the eastern gate.
96
170
  ```
97
171
 
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.
172
+ A reference may be written anywhere prose can be written — a word in a
173
+ quotation, a bullet, a table cell, a heading, a named block, a caption. A
174
+ definition belongs at the top level of the note: indent a following paragraph
175
+ or code block by four spaces to include it in the same footnote, but never
176
+ inside a list, a block quote, or a table. A definition written there, or a
177
+ reference with none at the top level to resolve against, is reported at its
178
+ own line rather than rendered. On the web, linked footnotes appear in a
179
+ Footnotes section at the bottom of the HTML page. In Foundry, each
180
+ JournalEntryPage places its referenced footnotes in a Footnotes section at the
181
+ bottom of that page; definitions remain available across pages of the same
182
+ note. In a book, footnotes appear in smaller type at the bottom of the page
183
+ that contains their reference.
106
184
 
107
185
  A definition list gives a term one or more definitions:
108
186
 
@@ -115,9 +193,19 @@ Janapada
115
193
  The web and Foundry render a definition list with HTML `<dl>`, `<dt>`, and
116
194
  `<dd>` elements. The book renders it as a term list.
117
195
 
118
- ## Tables and expressions
196
+ ## Lists
197
+
198
+ A list item's text begins right after its marker. `[ ]` and `[x]` there are
199
+ plain words, not a checkbox — write what is done into the item itself:
200
+
201
+ ```markdown
202
+ - the harbor toll: paid
203
+ - the ferry crossing: owed
204
+ ```
205
+
206
+ ## SQL generated tables
119
207
 
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.
208
+ 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
209
 
122
210
  ````markdown
123
211
  ```sql
@@ -125,13 +213,134 @@ SELECT name.full AS "Name" FROM notes WHERE type = 'lore' ORDER BY name.full
125
213
  ```
126
214
  ````
127
215
 
128
- Inline expressions can read the note, format dates, combine values, or run scalar SQL:
216
+ 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.
217
+
218
+ ### What a query can read
219
+
220
+ | Relation | What it holds |
221
+ | ----------------- | ----------------------------------------------------------------------------------------------------------------------------- |
222
+ | `notes` | Every note except a stub. This is the one to use unless a stub is wanted. |
223
+ | `entries` | Every note, stubs included. |
224
+ | `market` | The market scale as three columns — `value`, `name`, `trade` — so a table can print `village` beside the number a note wrote. |
225
+ | `<package>.notes` | The same two relations for each package this one declares a dependency on, in a schema named for it. |
226
+
227
+ 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.
228
+
229
+ ### Fence attributes
230
+
231
+ A braced list may follow the language on the opening fence. Two attributes are accepted, and anything else is reported by name.
232
+
233
+ | Attribute | Value | Default | What it does |
234
+ | --------------- | --------------------------- | ------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
235
+ | `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. |
236
+ | `section-level` | an integer from 1 through 6 | `2` | The heading level given to each group a `_section` column creates. |
237
+
238
+ ````markdown
239
+ ```sql {allow-empty=true section-level=3}
240
+ SELECT name.full AS "Name" FROM notes WHERE type = 'being' AND data.gender = 'none'
241
+ ```
242
+ ````
243
+
244
+ 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}`.
245
+
246
+ ### Two column names the renderer reads
247
+
248
+ Two aliases are instructions rather than columns, and neither is printed.
249
+
250
+ | Alias | What it does |
251
+ | ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
252
+ | `_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. |
253
+ | `_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. |
254
+
255
+ ```sql
256
+ SELECT type AS "_section", name.full AS "Name", address.slug AS "_ref"
257
+ FROM notes WHERE type IN ('lore', 'place') ORDER BY type, name.full
258
+ ```
259
+
260
+ See [SQL details](../reference/format-details.md#content-tables) for query options.
261
+
262
+ ## Page lists
263
+
264
+ 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.
265
+
266
+ ````markdown
267
+ ```pagelist {tag="key-concept"}
268
+
269
+ ```
270
+ ````
271
+
272
+ **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.
273
+
274
+ 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.
275
+
276
+ ### Page-list attributes
277
+
278
+ <!-- page-list-attributes:start -->
279
+
280
+ | Attribute | Value | Default | What it does |
281
+ | -------------- | ----------------- | ---------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
282
+ | `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. |
283
+ | `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. |
284
+ | `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. |
285
+ | `descriptions` | `true` or `false` | `false` | Whether each page's `description` follows its link. A page that states none carries its link alone. |
286
+ | `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. |
287
+
288
+ <!-- page-list-attributes:end -->
289
+
290
+ ````markdown
291
+ ```pagelist {tag="key-concept" type=lore sort=type descriptions=true}
292
+
293
+ ```
294
+ ````
295
+
296
+ 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.
297
+
298
+ **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.
299
+
300
+ 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.
301
+
302
+ See [page-list details](../reference/format-details.md#page-lists) for the directive's full behaviour.
303
+
304
+ ## Expressions
305
+
306
+ 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
307
 
130
308
  ```markdown
131
- {{name.full}} was born on {{dateformat "vrcal" data.born}}.
309
+ {{name.full}} was born on {{dateformat data.calendar data.born}}.
132
310
  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
311
  ```
136
312
 
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.
313
+ ### The rules of the construct
314
+
315
+ - **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.
316
+ - **An expression is one line.** It may not contain a line break or a brace.
317
+ - **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.
318
+ - **`\{{` writes an expression without evaluating it**, for an example in prose.
319
+ - **`{{{…}}}`, `{{<…}}` and `{{%…}}` are left alone.** A Hugo shortcode is not an expression.
320
+ - **An expression inside a code fence is left alone.**
321
+ - Helpers nest, with the inner call in parentheses: `{{words (sql "…")}}`.
322
+
323
+ ### Helpers
324
+
325
+ <!-- expression-helpers:start -->
326
+
327
+ | Helper | Parameters | What it gives you |
328
+ | ------------ | -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
329
+ | `eq` | `a b` | True when both are the same value. Compares exactly, so 1 and "1" differ. |
330
+ | `gt` | `a b` | True when a is greater than b. Both must be finite numbers. |
331
+ | `gte` | `a b` | True when a is greater than or equal to b. Both must be finite numbers. |
332
+ | `lt` | `a b` | True when a is less than b. Both must be finite numbers. |
333
+ | `lte` | `a b` | True when a is less than or equal to b. Both must be finite numbers. |
334
+ | `not` | `value` | True when the value is false, zero, empty or absent. |
335
+ | `and` | `value …` | True when every value given is true. Takes two or more. |
336
+ | `or` | `value …` | True when any value given is true. Takes two or more. |
337
+ | `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. |
338
+ | `digits` | `number` | The number as a grouped numeral — `1200` becomes 1,200, and a fractional part is kept. |
339
+ | `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. |
340
+ | `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. |
341
+
342
+ <!-- expression-helpers:end -->
343
+
344
+ 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.
345
+
346
+ See [date rules](dates-and-calendars.md) for what `dateformat` accepts and the patterns its third argument names.
@@ -1462,6 +1462,7 @@ packageBuild:
1462
1462
  | [`packageBuild.stageDir`](#packagebuildstagedir-and-packagebuildassets) | string | no | `build/stage` |
1463
1463
  | [`packageBuild.assets`](#packagebuildstagedir-and-packagebuildassets) | array | no | `[]` |
1464
1464
  | [`packageBuild.assetTransform`](#packagebuildassettransform-and-packagebuildmanifestflags) | string (path to a module) | no | `null` |
1465
+ | [`packageBuild.baseStyles`](#packagebuildbasestyles) | boolean | no | `true` |
1465
1466
  | [`packageBuild.manifest`](#packagebuildmanifest) | object, pass-through | no | `{}` |
1466
1467
  | [`packageBuild.manifestFlags`](#packagebuildassettransform-and-packagebuildmanifestflags) | string (path to a module) | no | `null` |
1467
1468
  | [`packageBuild.schema`](#packagebuildschema) | object (`{documentType: {from, registry}}`) | no | `[]` |
@@ -1475,7 +1476,7 @@ packageBuild:
1475
1476
  | [`packageBuild.proseLint`](#packagebuildproselint) | object | no | `{age: 21, threshold: 5, minWords: 8, rules: readability}` |
1476
1477
  | [`packageBuild.proseScore`](#packagebuildprosescore) | object | no | `{minWords: 80, bands: {}}` |
1477
1478
 
1478
- > ``package-build config: `packageBuild.<key>` is not a recognised key (expected one of: stageDir, assets, assetTransform, manifest, manifestFlags, schema, clean, lang, deploy, release, bundle, container, e2e, proseLint, proseScore).``
1479
+ > ``package-build config: `packageBuild.<key>` is not a recognised key (expected one of: stageDir, assets, assetTransform, baseStyles, manifest, manifestFlags, schema, clean, lang, deploy, release, bundle, container, e2e, proseLint, proseScore).``
1479
1480
 
1480
1481
  ### `packageBuild.proseLint`
1481
1482
 
@@ -1561,6 +1562,31 @@ repository has to compute. Neither declares anything, both default to
1561
1562
 
1562
1563
  > ``package-build config: `packageBuild.manifestFlags` must be a non-empty string.``
1563
1564
 
1565
+ ### `packageBuild.baseStyles`
1566
+
1567
+ Whether the package takes the shared base stylesheet. It defaults to `true`,
1568
+ is staged to `styles/package-build-base.css` under the stage root, and is
1569
+ named first in the generated manifest's `styles`.
1570
+
1571
+ The sheet carries only what every package's compiled content relies on to be
1572
+ legible — the definition-list style an infobox and the markdown `Term` /
1573
+ `: definition` syntax both emit — and is scoped to the journal, enriched-field
1574
+ and editor containers so it cannot reach Foundry's own interface.
1575
+
1576
+ It loads into the cascade layer Foundry reserves for the package kind,
1577
+ `system` or `modules`, which is the weakest layer a package can occupy. A
1578
+ package overrides any rule in it from its own stylesheet, whether that sheet
1579
+ names a layer or none at all, with no specificity contest and no
1580
+ `!important`. The term column, the gutter and the width at which the list
1581
+ stacks are custom properties — `--pb-deflist-term-width`,
1582
+ `--pb-deflist-gutter` and `--pb-deflist-row-gap` — so a package adjusts them
1583
+ without copying the rule.
1584
+
1585
+ Declare `false` to decline it; nothing is staged and nothing is added to
1586
+ `styles`:
1587
+
1588
+ > ``package-build config: `packageBuild.baseStyles` must be true or false.``
1589
+
1564
1590
  ### `packageBuild.manifest`
1565
1591
 
1566
1592
  `packageBuild.manifest.title` is a required, non-empty string for a Foundry
@@ -31,7 +31,9 @@ Body extensions use braces with space-separated `key=value` attributes:
31
31
  `![[icon-harbor|Harbor]]{float=top-left size=medium}` and an SQL fence with
32
32
  `{allow-empty=true}` are examples. Inline font glyphs use the package's icon
33
33
  registry, as in `:icon warning:{size=lg}`; image icons use asset Addresses.
34
- `:::info` and `:::warn` add labelled boxes within the prose. The
34
+ `:::info` and `:::warn` add labelled boxes within the prose. A
35
+ `pagelist` fence renders the pages carrying a tag as a list of links, so a
36
+ landing page states a group by intent rather than enumerating it. The
35
37
  [links and markup guide](authoring/links-and-markup.md) gives the complete syntax.
36
38
  Footnotes use `[^id]` references and `[^id]:` definitions, with numeric output
37
39
  markers scoped to each note. Definition lists use a term followed by one or
@@ -45,6 +45,21 @@ WHERE type = 'affiliation'
45
45
  ```
46
46
  ````
47
47
 
48
+ Where the page introduces a _group_ rather than a type, a `pagelist` fence
49
+ states the group by its tag and lists the pages carrying it:
50
+
51
+ ````markdown
52
+ ```pagelist {tag="key-concept" descriptions=true}
53
+
54
+ ```
55
+ ````
56
+
57
+ That keeps the entry page true as notes are written: a page joins the list by
58
+ carrying the tag, so nobody has to remember to add it here. A tag no page
59
+ carries is a build error naming the tag. See
60
+ [page lists](../authoring/links-and-markup.md#page-lists) for the attributes it
61
+ takes.
62
+
48
63
  `data.pack: none` is appropriate when the page belongs on the site and in the
49
64
  index but does not need a Foundry compendium entry. A documentation package
50
65
  has no packs at all. A page with an empty body is a stub: it remains in the