@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
package/CHANGELOG.md CHANGED
@@ -1,5 +1,228 @@
1
1
  # @heroiclands/package-build
2
2
 
3
+ ## 22.23.0
4
+
5
+ ### Minor Changes
6
+
7
+ **Figures**
8
+
9
+ - A picture and the words about it are written as one block: the caption is optional, several pictures can share one, and a border is available.
10
+ - Prose refers to a figure by name, in its own note or in another, and reads back its number, its number and caption, or the caption alone.
11
+ - A figure drawn from a map counts as a Map, numbered the same way on every surface.
12
+ - A figure holding one picture opens as an illustrated page in Foundry, with that viewer's own pop-out and zoom.
13
+
14
+ **Pictures**
15
+
16
+ - A picture carrying no caption draws no line beneath it, on a page, in a journal or in print.
17
+ - A picture states what it is for, and is drawn at the size that choice gives it — never larger than it was made.
18
+ - A page's hero image is 1792×768, and one of any other size is refused, naming the size to cut it to.
19
+
20
+ **Art provenance**
21
+
22
+ - A record saying a file is not machine-generated is published saying so.
23
+ - A package may refuse generated art in its own tree.
24
+ - A package may declare that a dependency's art answers its own addresses, and the book draws the replacement.
25
+
26
+ **Notes**
27
+
28
+ - A lore note states the occasions it records, each with a date and an optional recurrence, and publishes the next one due.
29
+ - A note may write a shared field at the position it is being retired from, and the build accepts it with a warning rather than refusing the note.
30
+
31
+ ### Patch Changes
32
+
33
+ **Item-frontmatter reference**: regenerating the page no longer renders a broken sentence for `affiliation`'s `title`, which now says plainly that a top-level key is refused outright.
34
+
35
+ ## 22.22.0
36
+
37
+ ### Minor Changes
38
+
39
+ **Diagnostics**
40
+
41
+ - A dead `#anchor` now fails the website build, matching the compendium build
42
+ and the link checker — a link that used to publish a dead section quietly now
43
+ reports it.
44
+ - A note with more than one problem in its body now reports every one of them
45
+ in a single build, instead of only the first.
46
+
47
+ **The book**
48
+
49
+ - A page link in a book now goes where it says, and one into a package the book
50
+ depends on reaches that package's pages; the address they are built from is
51
+ `package.json`'s `homepage`, which a book now needs.
52
+ - A note written for the next author — a `markdownlint` pragma, an aside about a
53
+ quoted notice — no longer appears on the page.
54
+ - A box written inside a GM-only section prints as a box.
55
+ - A box's title prints as written, emphasis included, and one carrying a bracket,
56
+ a `#` or a `$` no longer stops the book being made.
57
+ - Two captions sharing one id each print their own block once, under their own
58
+ number.
59
+ - A link that resolves nowhere now fails the book build, as it already fails the
60
+ compendium and website builds, and the book is still written so the page
61
+ carrying the link can be found.
62
+ - Markup the book cannot set is reported at the file and line it is written on
63
+ rather than printed on the page, in a title page and a prose file as well as in
64
+ a note.
65
+ - A table query that selects no rows now says so, and names the line, where it
66
+ read `[object Object]`.
67
+ - A link the book cannot resolve names the line and column it is written on.
68
+
69
+ **Affiliations**
70
+
71
+ - A rank with no title or no description is now reported instead of shipping, so
72
+ a member's standing can no longer resolve to a blank on a sheet or a page.
73
+ - A rank level that is not a whole number, and a key a rank does not take, are
74
+ reported too, each naming the line the rank is written on.
75
+ - Every problem on one rank is reported at once, so a ladder is fixed in a single
76
+ pass.
77
+ - A rank needs only its level, title and description; the lore note is for a
78
+ standing that needs more said about it than a description can hold.
79
+ - An affiliation's rank ladder now appears on its page, alongside its offices,
80
+ ordered by level.
81
+ - A note's `lore`, `parents`, `domains` and `economy` lists now reach the page
82
+ wherever they are authored, instead of vanishing silently.
83
+ - A being's entry in a body states the rung it stands on — an ordinary member is
84
+ 1, someone cast out is 0 — and an entry that states none is reported.
85
+ - An office is not a rung, so an entry naming a post and no standing is reported
86
+ with the rest of them.
87
+ - A body that gives two rungs one level is reported on the second, so what a
88
+ standing is called does not depend on which rung is written first.
89
+
90
+ **Authoring**
91
+
92
+ - A note's frontmatter accepts only the documented keys; an unrecognised one is an error at its own line, naming every accepted key and the one it was probably meant to be.
93
+ - A page takes its heading from the note's name, so a homepage stating a title any other way publishes the name its package manifest carries.
94
+ - Active Effects and document flags are read from the game-system block that carries them, so a note stating either outside one ships a document without them.
95
+ - Info boxes, warning boxes and captions can be written again: any note using
96
+ one failed its build with an error about a secret block it did not contain.
97
+ - A note may now mix boxes, captions and GM-only sections freely, and a box
98
+ inside a GM-only section stays inside it.
99
+ - A `:::` line that closes nothing says so plainly instead of naming the wrong
100
+ construct.
101
+ - An image may be sized `full-width`, and a float takes a named corner — both
102
+ always worked and neither was written down.
103
+
104
+ **Beings**
105
+
106
+ - A being's complexion can now hold several values, because a face is weathered
107
+ and ruddy at once rather than only one of them.
108
+ - Several of them read as one phrase on a sheet or a page — "weathered, ruddy and
109
+ scarred complexion" — rather than saying the word once per value.
110
+ - A being may be recorded as `nonbinary`, or as having no gender at all, where
111
+ before only `male`, `female` and `other` were named.
112
+ - A frame is `scant`, `light`, `medium`, `heavy` or `massive`. The reference named
113
+ a `heavy` frame `large`, which nothing wrote.
114
+ - A being's body and movement are authored under `sohl.system`, and its
115
+ attributes and skills as `sohl.items` entries; a note writing either
116
+ elsewhere in the block is reported.
117
+
118
+ **Actors**
119
+
120
+ - An `{#appearance}` or `{#dossier}` anchor written on a heading below the top
121
+ level now fails the build naming the file and the line, instead of compiling
122
+ a blank appearance or biography with nothing to say why.
123
+
124
+ **Dates**
125
+
126
+ - A birth or death date written without quotes is reported where a day's
127
+ trailing zero could have been lost, naming both days the value could mean. A
128
+ calendar's epoch and a moon's new day read the same way.
129
+
130
+ **Definition lists**
131
+
132
+ - Every package now ships a definition-list style, so a term and its definition
133
+ line up in two columns with the terms sharing one right edge.
134
+ - A narrow journal window stacks each term above its own definition instead of
135
+ cramping both into columns, and widening the window restores them.
136
+ - A term is bold wherever it appears, so writing one needs no `**` around it.
137
+ - Infobox rows in the book sit against the same right edge, with space between
138
+ rows rather than run together.
139
+
140
+ **Packages**
141
+
142
+ - `packageBuild.baseStyles: false` declines the shared style for a package that
143
+ wants the surface entirely to itself.
144
+
145
+ **Named blocks**
146
+
147
+ - A `:::secret`, `:::info` or `:::warn` block now takes a title of its own, so a
148
+ GM passage can say "For the GM" or "If they ask about the harbour" instead of
149
+ one fixed word.
150
+ - A block also takes an id, classes and other attributes, so a note can link to
151
+ one and a package can style a particular kind its own way.
152
+ - Without a title a block heads itself **Secret**, **Info** or **Warn**. A
153
+ warning that used to head "Warning" now heads "Warn", and a secret that used to
154
+ head "GM note" now heads "Secret"; writing `title="GM note"` keeps the old
155
+ wording.
156
+ - A secret on a web page now opens with its own title rather than the word
157
+ "Spoiler", and carries its kind so a site can style it.
158
+ - A block's colours come from a stylesheet rather than from the block itself,
159
+ so a light panel no longer keeps a light background against dark page text.
160
+ - A misspelled block name is now reported instead of being left in the page as
161
+ the literal `:::name` an author typed.
162
+ - One malformed block no longer stops every other block in the same note from
163
+ rendering.
164
+ - A box written inside a GM-only section stays inside it, and a box inside
165
+ another box says so rather than rendering as a muddle.
166
+
167
+ **Configuration**
168
+
169
+ - A `compatibility.verified` build below the package's own `compatibility.minimum`
170
+ is now reported instead of shipping a manifest that claims a build was verified
171
+ against a floor it cannot install on.
172
+ - The same check applies to a declared system's and a related package's own
173
+ `compatibility`, naming the file and line of the offending `verified`.
174
+
175
+ ### Patch Changes
176
+
177
+ **Lists**
178
+
179
+ - A list item opened with `- [ ]` or `- [x]` reads as its own words everywhere
180
+ — on the website, in the compendium, and in the book — never as a checkbox.
181
+ - Writing one that way is reported at its own line, naming the ordinary list
182
+ it belongs in.
183
+
184
+ **Footnotes**
185
+
186
+ - A footnote referenced inside a box or a caption now prints and numbers with
187
+ the rest of the note instead of showing as literal text.
188
+ - A definition written below the top level of a note is reported, as is a
189
+ reference nothing defines.
190
+ - A definition no reference uses is reported rather than dropped from the page.
191
+
192
+ **Named blocks and captions**
193
+
194
+ - A heading that would open a page of its own is reported when it is written
195
+ inside a box or a caption.
196
+
197
+ **Headings**
198
+
199
+ - A heading carrying classes or attributes beside its anchor now anchors on the
200
+ anchor alone, so a link written to it resolves on every surface.
201
+ - A Foundry journal and a web page carry a heading's classes and attributes onto
202
+ the heading, and no surface prints the braces.
203
+ - A heading whose braces hold something other than an attribute block is
204
+ reported at its own line instead of being typeset.
205
+ - `.secret` on the heading that opens a section makes its Foundry page the
206
+ GM's alone.
207
+ - A withheld section reads as a spoiler on the web and a labelled block in the
208
+ book, which conceal it rather than withhold it.
209
+ - A heading refuses `id=`, `class=` and any key beginning `on`, as a named block
210
+ does, so no note writes an event handler onto a page.
211
+
212
+ **Page lists**
213
+
214
+ - A note can now write a page list: name a tag, and the pages carrying it come out as a list of links.
215
+ - The list reaches a reader in Foundry, on the website and in the book alike, from the one directive.
216
+ - A page joins the list by carrying its tag and leaves by losing it, so a landing page stays true as notes are written.
217
+ - A list can print each page's description beside its link, and can be restricted to one note type.
218
+ - A tag that no page carries is reported with the tag it looked for, rather than printing nothing.
219
+
220
+ **Beings**
221
+
222
+ - A being's `socialTies` map now builds without the two findings it always
223
+ produced, so a patron, friend, dependent, acquaintance, rival or nemesis
224
+ can be written.
225
+
3
226
  ## 22.21.0
4
227
 
5
228
  ### Minor Changes
@@ -0,0 +1,162 @@
1
+ /*
2
+ * This file is part of the Song of Heroic Lands (SoHL) system for Foundry VTT.
3
+ * Copyright (c) 2024-2026 Tom Rodriguez ("Toasty") — <toasty@heroiclands.org>
4
+ *
5
+ * This work is licensed under the GNU General Public License v3.0 (GPLv3).
6
+ * You may copy, modify, and distribute it under the terms of that license.
7
+ *
8
+ * For full terms, see the LICENSE.md file in the project root or visit:
9
+ * https://www.gnu.org/licenses/gpl-3.0.html
10
+ *
11
+ * SPDX-License-Identifier: GPL-3.0-or-later
12
+ */
13
+
14
+ /*
15
+ * The base stylesheet every package ships, staged into the package root and
16
+ * named first in the manifest's `styles`.
17
+ *
18
+ * It carries only what every package needs for its compiled content to be
19
+ * legible, which today is the definition list: an infobox emits one, and so
20
+ * does the ordinary markdown `Term` / `: definition` syntax. A package keeps
21
+ * its own stylesheet for its own needs and overrides anything here from it.
22
+ *
23
+ * `packageBuild.baseStyles: false` declines it.
24
+ *
25
+ * **The scope is the content, not the interface.** Foundry styles `dl dt` and
26
+ * `dl dd` itself, application-wide, and a package stylesheet loads into a later
27
+ * cascade layer than those rules sit in — so an unscoped `dl` here would
28
+ * restyle every definition list in Foundry's own chrome and in every other
29
+ * package's sheets. The four roots are where compiled content renders: a
30
+ * journal page, a journal entry, an enriched field, and the editor.
31
+ */
32
+
33
+ /*
34
+ * Definition lists, in two states of one style.
35
+ *
36
+ * **Wide.** Two columns, the term right-aligned against a fixed gutter so every
37
+ * term in the list shares one right edge, the definition in the remaining
38
+ * space. The fixed track is what produces the shared edge; a `max-content`
39
+ * track cannot, because each row then sizes itself.
40
+ *
41
+ * **Narrow.** Stacked, the term above its own definition, with space between
42
+ * pairs and none between a term and the definition it belongs to.
43
+ *
44
+ * **The measurement is the list, not the screen.** A Foundry journal is a
45
+ * resizable window, usually far narrower than the viewport, so a viewport media
46
+ * query would hold a journal stacked while the screen said otherwise — or cramp
47
+ * it into two columns at 300px. `container-type: inline-size` on the `dl` and a
48
+ * `@container` query on its children measure the list itself, and give the same
49
+ * two states on a page, in a window and in a sidebar.
50
+ *
51
+ * The narrow state is reached by spanning both tracks rather than by changing
52
+ * the template, because an element cannot query the container it establishes:
53
+ * the `dl` holds the grid and the container, so only `dt` and `dd` can respond.
54
+ * Row spacing is the definition's bottom margin rather than the grid's
55
+ * `row-gap` for the same reason — `row-gap` sits on the `dl`, and the two
56
+ * states need different values.
57
+ *
58
+ * The three tunable values are custom properties, so a package adjusts the term
59
+ * column, the gutter or the stacking point without copying the rule.
60
+ */
61
+ :is(.journal-entry-page, .journal-entry-content, .editor-content, .prosemirror) dl {
62
+ display: grid;
63
+ /* `minmax(0, …)` keeps the track at its full width wherever there is room
64
+ * and lets it shrink rather than overflow where there is not. */
65
+ grid-template-columns: minmax(0, var(--pb-deflist-term-width, 11rem)) 1fr;
66
+ column-gap: var(--pb-deflist-gutter, 2rem);
67
+ container-type: inline-size;
68
+ container-name: pb-deflist;
69
+ }
70
+
71
+ :is(.journal-entry-page, .journal-entry-content, .editor-content, .prosemirror) dl > dt {
72
+ grid-column: 1;
73
+ /* The one rule that holds on every surface, so an author writes the term
74
+ * and nothing else rather than bolding it by hand. */
75
+ font-weight: 600;
76
+ text-align: right;
77
+ line-height: 1.6;
78
+ margin: 0;
79
+ }
80
+
81
+ :is(.journal-entry-page, .journal-entry-content, .editor-content, .prosemirror) dl > dd {
82
+ grid-column: 2;
83
+ margin: 0 0 var(--pb-deflist-row-gap, 1.5rem);
84
+ line-height: 1.6;
85
+ }
86
+
87
+ :is(.journal-entry-page, .journal-entry-content, .editor-content, .prosemirror) dl > dd:last-child {
88
+ margin-bottom: 0;
89
+ }
90
+
91
+ /*
92
+ * Stacked, below the width two columns need: the term column, the gutter, and
93
+ * enough left for a definition to read as prose rather than as a word per line.
94
+ *
95
+ * Named, so the query resolves against a list this sheet set up rather than
96
+ * against whichever ancestor container happens to be nearest.
97
+ */
98
+ @container pb-deflist (max-width: 32rem) {
99
+ dt {
100
+ grid-column: 1 / -1;
101
+ text-align: left;
102
+ }
103
+
104
+ dd {
105
+ grid-column: 1 / -1;
106
+ }
107
+ }
108
+
109
+ /*
110
+ * Named body blocks. `:::info` and `:::warn` emit a `<section>` carrying the
111
+ * block's name as its class, so the appearance belongs here rather than in an
112
+ * inline `style` a consumer cannot override.
113
+ *
114
+ * `secret` is deliberately absent: Foundry styles `section.secret` itself and
115
+ * owns the reveal control on it, and a rule here would fight both.
116
+ *
117
+ * Every colour is a custom property, so a package overrides one from its own
118
+ * sheet without restating the layout.
119
+ */
120
+ :is(.journal-entry-page, .journal-entry-content, .editor-content, .prosemirror)
121
+ :is(section.info, section.warn) {
122
+ border-left: 3px solid var(--pb-block-edge);
123
+ background: var(--pb-block-fill);
124
+ color: var(--pb-block-text, inherit);
125
+ padding: 0.5em 0.8em;
126
+ margin: 0.75em 0;
127
+ }
128
+
129
+ :is(.journal-entry-page, .journal-entry-content, .editor-content, .prosemirror)
130
+ :is(section.info, section.warn)
131
+ > strong:first-child {
132
+ color: var(--pb-block-edge);
133
+ }
134
+
135
+ :is(.journal-entry-page, .journal-entry-content, .editor-content, .prosemirror) section.info {
136
+ --pb-block-edge: #2f6f9f;
137
+ --pb-block-fill: #eef6fb;
138
+ }
139
+
140
+ :is(.journal-entry-page, .journal-entry-content, .editor-content, .prosemirror) section.warn {
141
+ --pb-block-edge: #9a6700;
142
+ --pb-block-fill: #fff5db;
143
+ }
144
+
145
+ /*
146
+ * A light panel keeps a dark foreground whatever the surrounding theme does to
147
+ * the inherited colour, because the fill does not follow it.
148
+ */
149
+ @media (prefers-color-scheme: dark) {
150
+ :is(.journal-entry-page, .journal-entry-content, .editor-content, .prosemirror)
151
+ :is(section.info, section.warn) {
152
+ --pb-block-text: #1b1b1b;
153
+ }
154
+
155
+ :is(.journal-entry-page, .journal-entry-content, .editor-content, .prosemirror) section.info {
156
+ --pb-block-edge: #1d4f75;
157
+ }
158
+
159
+ :is(.journal-entry-page, .journal-entry-content, .editor-content, .prosemirror) section.warn {
160
+ --pb-block-edge: #7a5100;
161
+ }
162
+ }
@@ -64,12 +64,14 @@ import {
64
64
  import { fetchNavigation, generateHugoConfig, writeHugoConfig } from "../engine/site-config.mjs";
65
65
  import { renderItemFieldReference, renderItemFieldsPage } from "../engine/field-reference.mjs";
66
66
  import { checkDocLinks, checkDocIndex } from "../engine/docs-checks.mjs";
67
+ import { checkArtSlotSizes } from "../engine/art-fields.mjs";
67
68
  import { lintContentTree } from "../engine/content-lint.mjs";
68
69
  import { lintNoteStates } from "../engine/stub-lint.mjs";
69
70
  import { lintContentCharset } from "../engine/content-charset.mjs";
70
71
  import { lintContentHtml } from "../engine/content-html.mjs";
71
72
  import { lintContentIcons } from "../engine/content-icons.mjs";
72
73
  import { lintContentImages } from "../engine/content-images.mjs";
74
+ import { lintContentTaskLists } from "../engine/content-tasklists.mjs";
73
75
  import {
74
76
  declaredSystems,
75
77
  lintFrontmatter,
@@ -108,6 +110,7 @@ import {
108
110
  authoredFrontmatter,
109
111
  emitContentIndex,
110
112
  indexRecordsFor,
113
+ isAssetRecord,
111
114
  isNoteRecord,
112
115
  noteFile,
113
116
  } from "../engine/content-index.mjs";
@@ -138,6 +141,7 @@ import { buildMaps, FROM_ALL, MAP_DIR } from "../engine/map-build.mjs";
138
141
  import { CHART_HORIZON_DAYS } from "../engine/map-layout.mjs";
139
142
  import { GRAPHVIZ_ENGINES } from "../engine/map-graphviz.mjs";
140
143
  import { loadMapWorld } from "../engine/map-places.mjs";
144
+ import { encodeAddresses } from "../engine/address-values.mjs";
141
145
 
142
146
  /**
143
147
  * The packs `unpack` extracts.
@@ -1156,6 +1160,13 @@ function lintCommand() {
1156
1160
  config,
1157
1161
  });
1158
1162
 
1163
+ // A list item opened with `[ ]` or `[x]` — nothing here is
1164
+ // miscompiled, since every surface sets the marker as the
1165
+ // item's own text and all three agree, so this is advisory.
1166
+ const taskLists = lintContentTaskLists(root, {
1167
+ skipDirectories: config.skipDirectories,
1168
+ });
1169
+
1159
1170
  // Whether every empty body is a stub on purpose, and how
1160
1171
  // finished the tree is. The counts and the oldest drafts are
1161
1172
  // prose rather than findings: a note nobody has finished is not
@@ -1167,6 +1178,32 @@ function lintCommand() {
1167
1178
  });
1168
1179
  for (const line of states.summary) log.info(line);
1169
1180
 
1181
+ // Generated art, when this package declares it carries none.
1182
+ // `records` already holds every asset of this build, so the
1183
+ // check is a filter over the corpus already in hand rather
1184
+ // than a second enumeration of the asset trees.
1185
+ const generatedArt =
1186
+ config.forbidGeneratedArt ?
1187
+ records
1188
+ .filter(isAssetRecord)
1189
+ .filter((record) => record.asset.ai === true)
1190
+ .map((record) => ({
1191
+ file: path.join(config.paths.assets, record.asset.path),
1192
+ severity: "error",
1193
+ message:
1194
+ `\`${encodeAddresses(record.address.canonical)}\` is ` +
1195
+ "machine-generated art (`ai: true`), and " +
1196
+ "`forbidGeneratedArt` refuses it in this package",
1197
+ }))
1198
+ : [];
1199
+
1200
+ // A hero image against the size its slot is cut to, read
1201
+ // from the records already in hand.
1202
+ const artSizes = checkArtSlotSizes(records, {
1203
+ config,
1204
+ assetsBase: config.paths.assets,
1205
+ });
1206
+
1170
1207
  const findings = [
1171
1208
  ...addresses.findings,
1172
1209
  ...frontmatter.findings,
@@ -1175,7 +1212,10 @@ function lintCommand() {
1175
1212
  ...icons.findings,
1176
1213
  ...html.findings,
1177
1214
  ...images.findings,
1215
+ ...taskLists.findings,
1178
1216
  ...states.findings,
1217
+ ...generatedArt,
1218
+ ...artSizes,
1179
1219
  ];
1180
1220
  // Only an **error** fails the run. Every finding was an error
1181
1221
  // by then, so this changes nothing on its own —
@@ -1461,14 +1501,20 @@ function linksCommand() {
1461
1501
  }),
1462
1502
  );
1463
1503
 
1504
+ // Worded by the shared table, like every finding below it: a
1505
+ // sentence written here is free to describe the same defect
1506
+ // differently from the build that also refuses it, which is the
1507
+ // drift the table exists to prevent.
1464
1508
  for (const d of deadAnchors) {
1465
1509
  emitDiagnostic({
1466
1510
  file: d.note.file,
1467
1511
  ...positionOfLiteral(d.note.raw, d.text, d.occurrence),
1468
1512
  severity: "error",
1469
- message:
1470
- `link [[${d.link}]] points at an anchor no ` +
1471
- `heading in ${d.dest.rel} declares`,
1513
+ message: linkFindingMessage({
1514
+ reason: "unknown-anchor",
1515
+ target: d.target,
1516
+ anchor: d.anchor,
1517
+ }),
1472
1518
  });
1473
1519
  }
1474
1520
  // Every link is an address and every address must
@@ -1799,6 +1845,15 @@ function siteCommand() {
1799
1845
  for (const e of result.captionErrors) {
1800
1846
  emitDiagnostic({ ...e, severity: "error" });
1801
1847
  }
1848
+ for (const e of result.headingErrors) {
1849
+ emitDiagnostic({ ...e, severity: "error" });
1850
+ }
1851
+ for (const e of result.footnoteErrors) {
1852
+ emitDiagnostic({ ...e, severity: "error" });
1853
+ }
1854
+ for (const e of result.embedErrors) {
1855
+ emitDiagnostic({ ...e, severity: "error" });
1856
+ }
1802
1857
  // Reported the way the pack build reports the very same
1803
1858
  // finding: `file:line:column: error: message`, path first, and
1804
1859
  // the message from the shared table, not a
@@ -1838,8 +1893,11 @@ function siteCommand() {
1838
1893
  result.expressionErrors.length ||
1839
1894
  result.secretErrors.length ||
1840
1895
  result.captionErrors.length ||
1896
+ result.headingErrors.length ||
1897
+ result.footnoteErrors.length ||
1841
1898
  result.wikiErrors.length ||
1842
- result.imageErrors.length
1899
+ result.imageErrors.length ||
1900
+ result.embedErrors.length
1843
1901
  ) {
1844
1902
  process.exitCode = 1;
1845
1903
  return;
@@ -2172,16 +2230,18 @@ async function fetchFromLocalArtifact(config, argv) {
2172
2230
 
2173
2231
  /**
2174
2232
  * `deps fetch` — fill the caches this build resolves other packages through:
2175
- * the **content index** of every declared dependency, the **item catalogue**
2176
- * of those additionally declaring `itemCatalog: true`, and the **site
2177
- * navigation** heroiclands.org publishes, which the site build writes its
2178
- * menu from.
2179
- *
2180
- * The two dependency sets differ deliberately. Citing another package's
2181
- * *addresses* and embedding its *items* are separate edges, and a package may
2182
- * have either without the other — `harn-ensemble` cites no foreign address and
2183
- * embeds 324,016 item references. The navigation is fetched for every package,
2184
- * because every package publishes a site.
2233
+ * the **content index** of every declared dependency, the **item catalogue
2234
+ * or asset archive** of those additionally declaring `itemCatalog: true` or
2235
+ * `assetArchive: true`, and the **site navigation** heroiclands.org
2236
+ * publishes, which the site build writes its menu from.
2237
+ *
2238
+ * The dependency sets differ deliberately. Citing another package's
2239
+ * *addresses*, embedding its *items*, and staging its *asset bytes* are
2240
+ * separate edges, and a package may declare any of them without the others —
2241
+ * `harn-ensemble` cites no foreign address and embeds 324,016 item
2242
+ * references. The navigation is fetched for every package, because every
2243
+ * package publishes a site. A relationship declaring both `itemCatalog: true`
2244
+ * and `assetArchive: true` is fetched once.
2185
2245
  *
2186
2246
  * Its own command rather than a step of `package compile` or `site`, so that
2187
2247
  * neither reaches the network. A build that downloads silently is not
@@ -2242,7 +2302,7 @@ function depsCommand() {
2242
2302
  const indexes = await fetchAllMetadata(config);
2243
2303
  if (indexes) log.info(`Fetched ${indexes} dependency content index(es).`);
2244
2304
  const count = await fetchAllCatalogs(config);
2245
- if (count) log.info(`Fetched ${count} dependency catalogue(s).`);
2305
+ if (count) log.info(`Fetched ${count} dependency catalogue(s) or archive(s).`);
2246
2306
  if (!indexes && !count) {
2247
2307
  // Distinguished from a package declaring nothing at all: a
2248
2308
  // relationship may still be declared, just narrowed to the
@@ -2254,8 +2314,8 @@ function depsCommand() {
2254
2314
  );
2255
2315
  log.info(
2256
2316
  declared ?
2257
- "No declared dependency needs a fetched content index or item " +
2258
- "catalogue."
2317
+ "No declared dependency needs a fetched content index, item " +
2318
+ "catalogue, or asset archive."
2259
2319
  : "This package declares no dependencies.",
2260
2320
  );
2261
2321
  }
@@ -93,6 +93,7 @@ import { DEPLOY_ROOT } from "../engine/site-config.mjs";
93
93
  import { compilesFoundryDocuments } from "../content-config.mjs";
94
94
  import { loadPackConfig, packConfigPath, resolveConfigFile } from "../engine/pack-config.mjs";
95
95
  import { cleanBuildArtifacts, resetStage, stageAssets } from "../stage.mjs";
96
+ import { baseStyleStageEntry } from "../engine/base-styles.mjs";
96
97
  import { buildSchemaArtifact } from "../engine/schema-extract.mjs";
97
98
  import { emitCalendarArtifacts } from "../engine/calendar-artifacts.mjs";
98
99
  import { dateFromCalendar, dateToCalendar } from "../engine/date-conversion.mjs";
@@ -424,7 +425,7 @@ function assetsCommand() {
424
425
  builder: (y) => y,
425
426
  handler: handler(async () => {
426
427
  const config = loadPackageBuildConfig();
427
- if (!config.assets.length) {
428
+ if (!config.assets.length && !config.baseStyles) {
428
429
  console.log("package-build: no `packageBuild.assets` declared; nothing to stage.");
429
430
  return;
430
431
  }
@@ -462,10 +463,22 @@ function assetsCommand() {
462
463
  from,
463
464
  path.join(config.stageDir, to),
464
465
  ]);
465
- const { entries: count, files } = stageAssets(entries, {
466
+ let { entries: count, files } = stageAssets(entries, {
466
467
  cwd: config.rootDir,
467
468
  transform,
468
469
  });
470
+
471
+ // Staged in its own pass, without the repository's transform: the
472
+ // sheet is this toolchain's file rather than one of the
473
+ // repository's assets, and a `transform` written to retheme a
474
+ // package's own SVGs has no business being handed it.
475
+ if (config.baseStyles) {
476
+ const staged = stageAssets([baseStyleStageEntry(config.stageDir)], {
477
+ cwd: config.rootDir,
478
+ });
479
+ count += staged.entries;
480
+ files += staged.files;
481
+ }
469
482
  console.log(`✅ Static assets staged (${count} entries, ${files} files).`);
470
483
  }),
471
484
  };