@heroiclands/package-build 20.3.1 → 20.4.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 (178) hide show
  1. package/CHANGELOG.md +75 -0
  2. package/CONTENT.md +111 -92
  3. package/README.md +2 -9
  4. package/bin/content-build.mjs +70 -61
  5. package/bin/package-build.mjs +2 -2
  6. package/config.mjs +6 -8
  7. package/content-config.mjs +187 -64
  8. package/docs/content-format.md +57 -68
  9. package/e2e.mjs +4 -4
  10. package/engine/actor-compiler.mjs +36 -36
  11. package/engine/address-charset.mjs +6 -6
  12. package/engine/address-diff.mjs +12 -11
  13. package/engine/anchored-sections.mjs +1 -1
  14. package/engine/anchors.mjs +1 -1
  15. package/engine/base-compiler.mjs +36 -38
  16. package/engine/bundle-notes.mjs +4 -4
  17. package/engine/bundles.mjs +8 -8
  18. package/engine/code-fences.mjs +3 -3
  19. package/engine/compendiums.mjs +8 -8
  20. package/engine/compile-corpus.mjs +4 -5
  21. package/engine/content-address.mjs +15 -15
  22. package/engine/content-charset.mjs +1 -1
  23. package/engine/content-format-check.mjs +24 -25
  24. package/engine/content-format.mjs +8 -8
  25. package/engine/content-html.mjs +202 -0
  26. package/engine/content-icons.mjs +196 -165
  27. package/engine/content-index.mjs +17 -17
  28. package/engine/content-links.mjs +30 -30
  29. package/engine/content-lint.mjs +20 -24
  30. package/engine/content-package.mjs +7 -9
  31. package/engine/content-slug.mjs +2 -2
  32. package/engine/content-tables.mjs +7 -8
  33. package/engine/content-tree.mjs +1 -1
  34. package/engine/derived-fields.mjs +174 -0
  35. package/engine/diagnostics.mjs +3 -4
  36. package/engine/document-subtypes.mjs +6 -7
  37. package/engine/field-reference.mjs +8 -8
  38. package/engine/field-spec.mjs +29 -30
  39. package/engine/folder-notes.mjs +14 -14
  40. package/engine/foreign-catalog.mjs +8 -9
  41. package/engine/foundry-entries.mjs +19 -20
  42. package/engine/frontmatter-lint.mjs +64 -69
  43. package/engine/frontmatter.mjs +11 -11
  44. package/engine/generate.mjs +39 -39
  45. package/engine/helpers.mjs +63 -54
  46. package/engine/homepage.mjs +20 -22
  47. package/engine/ids.mjs +15 -15
  48. package/engine/index-records.mjs +4 -4
  49. package/engine/index.mjs +14 -8
  50. package/engine/item-compiler.mjs +54 -29
  51. package/engine/item-docs.mjs +11 -13
  52. package/engine/item-registry.mjs +13 -13
  53. package/engine/journals.mjs +15 -15
  54. package/engine/macros.mjs +3 -3
  55. package/engine/map-notes.mjs +6 -6
  56. package/engine/metadata-index.mjs +5 -5
  57. package/engine/note-claims.mjs +36 -34
  58. package/engine/note-ids.mjs +5 -5
  59. package/engine/note-package.mjs +4 -4
  60. package/engine/note-renames.mjs +3 -3
  61. package/engine/note-schemas.mjs +3 -3
  62. package/engine/note-vocabulary.mjs +27 -26
  63. package/engine/pack-config.mjs +13 -13
  64. package/engine/pack-router.mjs +0 -0
  65. package/engine/prose-config.mjs +9 -9
  66. package/engine/prose-lint.mjs +12 -13
  67. package/engine/region-events.mjs +2 -2
  68. package/engine/retired-fields.mjs +24 -22
  69. package/engine/runtime-only-fields.mjs +1 -1
  70. package/engine/scene-levels.mjs +1 -1
  71. package/engine/scenes.mjs +19 -20
  72. package/engine/schema-check.mjs +20 -20
  73. package/engine/schema-extract.mjs +1 -1
  74. package/engine/site-build.mjs +45 -47
  75. package/engine/site-index.mjs +17 -18
  76. package/engine/sql-tables.mjs +21 -13
  77. package/engine/subtype-registry.mjs +5 -4
  78. package/engine/system-block.mjs +18 -19
  79. package/engine/systems.mjs +2 -2
  80. package/engine/web-wikilinks.mjs +35 -35
  81. package/engine/wikilink-syntax.mjs +16 -16
  82. package/engine/wikilinks.mjs +39 -39
  83. package/hm3/actors.mjs +22 -22
  84. package/hm3/document-subtypes.mjs +5 -5
  85. package/hm3/index.mjs +2 -2
  86. package/hm3/item-builders.mjs +1 -1
  87. package/hm3/item-fields.mjs +3 -3
  88. package/hm3/items.mjs +20 -8
  89. package/hm3/template-priority.mjs +2 -2
  90. package/lang.mjs +3 -3
  91. package/manifest.mjs +17 -20
  92. package/package.json +1 -2
  93. package/release.mjs +3 -3
  94. package/sohl/actors.mjs +28 -28
  95. package/sohl/affiliation-standings.mjs +2 -2
  96. package/sohl/being-info.mjs +5 -5
  97. package/sohl/default-item-art.mjs +5 -5
  98. package/sohl/document-subtypes.mjs +5 -5
  99. package/sohl/index.mjs +3 -3
  100. package/sohl/item-builders.mjs +7 -7
  101. package/sohl/item-fields.mjs +8 -9
  102. package/sohl/items.mjs +20 -6
  103. package/sohl/kb-passes.mjs +5 -5
  104. package/sohl/note-schemas.mjs +7 -7
  105. package/sohl/skill-base.mjs +1 -1
  106. package/types/content-config.d.mts +25 -24
  107. package/types/e2e.d.mts +3 -3
  108. package/types/engine/actor-compiler.d.mts +14 -13
  109. package/types/engine/address-charset.d.mts +6 -6
  110. package/types/engine/address-diff.d.mts +3 -3
  111. package/types/engine/base-compiler.d.mts +17 -19
  112. package/types/engine/bundle-notes.d.mts +3 -3
  113. package/types/engine/bundles.d.mts +1 -1
  114. package/types/engine/code-fences.d.mts +3 -3
  115. package/types/engine/compendiums.d.mts +3 -3
  116. package/types/engine/compile-corpus.d.mts +1 -1
  117. package/types/engine/content-address.d.mts +11 -11
  118. package/types/engine/content-format-check.d.mts +6 -6
  119. package/types/engine/content-format.d.mts +2 -2
  120. package/types/engine/content-html.d.mts +78 -0
  121. package/types/engine/content-icons.d.mts +139 -92
  122. package/types/engine/content-index.d.mts +2 -2
  123. package/types/engine/content-links.d.mts +8 -8
  124. package/types/engine/content-lint.d.mts +2 -2
  125. package/types/engine/content-package.d.mts +6 -8
  126. package/types/engine/derived-fields.d.mts +101 -0
  127. package/types/engine/diagnostics.d.mts +2 -2
  128. package/types/engine/document-subtypes.d.mts +3 -3
  129. package/types/engine/field-spec.d.mts +37 -39
  130. package/types/engine/folder-notes.d.mts +4 -5
  131. package/types/engine/foreign-catalog.d.mts +3 -3
  132. package/types/engine/foundry-entries.d.mts +6 -7
  133. package/types/engine/frontmatter-lint.d.mts +7 -7
  134. package/types/engine/frontmatter.d.mts +7 -7
  135. package/types/engine/generate.d.mts +7 -7
  136. package/types/engine/helpers.d.mts +25 -27
  137. package/types/engine/homepage.d.mts +16 -18
  138. package/types/engine/ids.d.mts +13 -13
  139. package/types/engine/index-records.d.mts +3 -3
  140. package/types/engine/index.d.mts +2 -0
  141. package/types/engine/item-compiler.d.mts +21 -5
  142. package/types/engine/item-docs.d.mts +2 -2
  143. package/types/engine/item-registry.d.mts +6 -6
  144. package/types/engine/journals.d.mts +3 -3
  145. package/types/engine/map-notes.d.mts +2 -2
  146. package/types/engine/metadata-index.d.mts +4 -4
  147. package/types/engine/note-claims.d.mts +16 -15
  148. package/types/engine/note-ids.d.mts +4 -4
  149. package/types/engine/note-package.d.mts +1 -1
  150. package/types/engine/note-renames.d.mts +3 -3
  151. package/types/engine/note-vocabulary.d.mts +9 -8
  152. package/types/engine/pack-config.d.mts +3 -3
  153. package/types/engine/pack-router.d.mts +1 -1
  154. package/types/engine/prose-config.d.mts +9 -9
  155. package/types/engine/prose-lint.d.mts +3 -4
  156. package/types/engine/region-events.d.mts +2 -2
  157. package/types/engine/retired-fields.d.mts +10 -9
  158. package/types/engine/schema-check.d.mts +11 -11
  159. package/types/engine/site-build.d.mts +18 -19
  160. package/types/engine/site-index.d.mts +3 -4
  161. package/types/engine/sql-tables.d.mts +11 -5
  162. package/types/engine/subtype-registry.d.mts +3 -3
  163. package/types/engine/system-block.d.mts +3 -3
  164. package/types/engine/web-wikilinks.d.mts +7 -7
  165. package/types/engine/wikilink-syntax.d.mts +17 -17
  166. package/types/engine/wikilinks.d.mts +13 -12
  167. package/types/hm3/actors.d.mts +1 -1
  168. package/types/hm3/document-subtypes.d.mts +1 -1
  169. package/types/hm3/items.d.mts +1 -2
  170. package/types/hm3/template-priority.d.mts +1 -1
  171. package/types/manifest.d.mts +8 -8
  172. package/types/sohl/actors.d.mts +2 -2
  173. package/types/sohl/affiliation-standings.d.mts +2 -2
  174. package/types/sohl/being-info.d.mts +2 -2
  175. package/types/sohl/document-subtypes.d.mts +1 -1
  176. package/types/sohl/items.d.mts +1 -2
  177. package/types/sohl/note-schemas.d.mts +1 -1
  178. package/MIGRATING.md +0 -608
@@ -13,7 +13,7 @@
13
13
 
14
14
  /**
15
15
  * **The per-system frontmatter block** — how one note feeds more than one game
16
- * system (#58).
16
+ * system.
17
17
  *
18
18
  * A note is system-agnostic. The only system-specific things it carries are the
19
19
  * properties *named after a system*, and within one:
@@ -21,7 +21,7 @@
21
21
  * | property | maps to |
22
22
  * | --- | --- |
23
23
  * | `<system>.system` | `document.system` — the DataModel schema, verbatim paths |
24
- * | `<system>.type` | `document.type` — the subtype (#79's discriminator) |
24
+ * | `<system>.type` | `document.type` — the subtype discriminator |
25
25
  * | `<system>.img` | `document.img` |
26
26
  * | `<system>.items` | `document.items` — actors only |
27
27
  * | `<system>.effects` | `document.effects` |
@@ -47,7 +47,7 @@
47
47
  *
48
48
  * 1. `S.system.<to>` — authored directly, wins outright;
49
49
  * 2. `S.<legacyKey>` — the legacy in-block position the corpus still writes,
50
- * kept until #126 moves it;
50
+ * kept until the corpus moves it;
51
51
  * 3. the shared top-level property the field **declares** as its source, which
52
52
  * may be a dotted path (`data.portrait`) rather than a sibling key;
53
53
  * 3b. for a `data.` source, the bare top-level key that container gathered it
@@ -56,15 +56,15 @@
56
56
  *
57
57
  * ## Steps 2 and 3 are two declarations, because they are two positions
58
58
  *
59
- * They used to be one: both were keyed on `name`, which was fine only while the
60
- * shared source and the in-block key were the same word. `data:` (#128) ended
59
+ * They are not one: keying both on `name` holds only while the
60
+ * shared source and the in-block key were the same word. `data:` ended
61
61
  * that — a shared source is now a path *into* a container, so `data.species`
62
62
  * and `species` are two spellings of two different places, and no single value
63
63
  * of `name` reached both. `name: "species"` read `hm3.species` and could not
64
64
  * see `data.species`; `name: "data.species"` read the shared source and could
65
65
  * not see `hm3.species`. Each yielded the field's **default** wherever only the
66
66
  * other position was authored — silently, since the field compiles and the
67
- * document is emitted with the value simply gone (#305).
67
+ * document is emitted with the value simply gone.
68
68
  *
69
69
  * That made every move into `data:` a flag day. Each of this package's other
70
70
  * retirements — `package:`, `image`, `archetype`, `relation` — works because
@@ -77,11 +77,11 @@
77
77
  * that declares one is mid-sweep by construction, which is what
78
78
  * {@link module:engine/field-spec.readsLegacyKey} reports on.
79
79
  *
80
- * ## Step 3 has a retiring position too, and it is derived (#332)
80
+ * ## Step 3 has a retiring position too, and it is derived
81
81
  *
82
82
  * `legacyKey` retires the *in-block* position, and for a while that looked like
83
83
  * the whole of what `data:` left behind. It is not. The facts `data:` holds
84
- * were not invented by it — #128 **gathered** them out of the note's open top
84
+ * were not invented by it — the sweep **gathered** them out of the note's open top
85
85
  * level, where `portrait:` sat beside `img:` and `shortcode:` — so a field that
86
86
  * declares `data.portrait` has two shared spellings to read, not one, and
87
87
  * reading only the current one is the same silent miss `legacyKey` exists to
@@ -112,7 +112,7 @@
112
112
  * It was not a harmless coincidence either, because step 3 answers **without**
113
113
  * applying `field.default` — only step 2 does — so an authored `title: null`
114
114
  * reached the field's coercion unguarded and shipped as the literal string
115
- * `"null"` in fifteen documents (#218).
115
+ * `"null"` in fifteen documents.
116
116
  *
117
117
  * So a field may declare `topLevelMeans`: what the top-level key of that name
118
118
  * means *instead*. Declaring it removes the whole shared level — step 3 and the
@@ -132,9 +132,8 @@
132
132
  *
133
133
  * A block is addressed by name, and the name arrives from the caller — a
134
134
  * system's own document-subtype map declares it (`DocumentSubtypeMap.block`).
135
- * That is the `engine/` ÷ `sohl/` line this package draws everywhere else
136
- * (#36): note-format knowledge here, game-system knowledge in the system's own
137
- * half.
135
+ * That is the `engine/` ÷ `sohl/` line this package draws everywhere else:
136
+ * note-format knowledge here, game-system knowledge in the system's own half.
138
137
  *
139
138
  * @module
140
139
  */
@@ -393,7 +392,7 @@ const DATA_PREFIX = "data.";
393
392
  /**
394
393
  * The bare top-level key a `data:`-sourced field is being swept off — step 3b.
395
394
  *
396
- * `data:` (#128) did not invent the facts it holds; it *gathered* them, out of
395
+ * `data:` did not invent the facts it holds; it *gathered* them, out of
397
396
  * the note's open top level where each was a sibling of `img` and `shortcode`.
398
397
  * So the retiring spelling of `data.portrait` is not a second declaration
399
398
  * anyone has to write — it is `portrait`, mechanically, and the same holds for
@@ -466,8 +465,8 @@ export function resolveFieldValue(field, fm, { block = "sohl" } = {}) {
466
465
 
467
466
  // 2. The legacy in-block position, keyed on `legacyKey` — the shared
468
467
  // source is a path into `data:` and the in-block key is a bare word, so
469
- // the two are declared separately (#305). Every note in every tree
470
- // writes here today, and will until #126 moves them; dropping it would
468
+ // the two are declared separately. Every note in every tree
469
+ // writes here today, and will until the corpus moves them; dropping it would
471
470
  // be a corpus migration disguised as a mechanism change.
472
471
  const declared = systemBlock(fm, block);
473
472
  const legacyKey = legacyKeyOf(field);
@@ -482,14 +481,14 @@ export function resolveFieldValue(field, fm, { block = "sohl" } = {}) {
482
481
 
483
482
  // 3. The shared property this field declares as its source — unless the
484
483
  // field declares that the top-level key of that name means something
485
- // else, in which case there is no shared position to read (#218).
484
+ // else, in which case there is no shared position to read.
486
485
  if (field.topLevelMeans === undefined) {
487
486
  const shared = getFrontmatter(fm, field.name, undefined);
488
487
  if (shared !== undefined) return { value: shared, from: "shared" };
489
488
 
490
489
  // 3b. The bare top-level key the `data:` source was gathered off — the
491
490
  // retiring *shared* position, exactly as `legacyKey` is the
492
- // retiring *in-block* one (#332). Without it a field declaring
491
+ // retiring *in-block* one. Without it a field declaring
493
492
  // `data.portrait` cannot see the `portrait:` every tree still
494
493
  // writes, and the miss arrives at the caller's `?? default` as an
495
494
  // ordinary absence: 646 `sohl-thalorna` beings compiled the generic
@@ -556,7 +555,7 @@ export function systemDataPaths(data, prefix = "") {
556
555
  *
557
556
  * It stayed invisible while those maps were authored *outside* `<system>.system`
558
557
  * and so were never walked. The moment the corpus moves them to the destination
559
- * (#126) every one of them lights up — 62 findings on `sohl-thalorna` alone,
558
+ * every one of them lights up — 62 findings on `sohl-thalorna` alone,
560
559
  * none of them a defect.
561
560
  *
562
561
  * So descent is conditional on the schema declaring something *beneath* the
@@ -621,7 +620,7 @@ function declaresChildren(declared, path) {
621
620
  * @param {object} options - Options.
622
621
  * @param {Iterable<string>} options.known - The keys this system declares on
623
622
  * top of the shared vocabulary: its generators, its toolchain keys, and —
624
- * until #126 moves them — the field names its notes still author in the
623
+ * until the corpus moves them — the field names its notes still author in the
625
624
  * block.
626
625
  * @returns {string[]} The unrecognized keys, in authored order.
627
626
  */
@@ -13,7 +13,7 @@
13
13
 
14
14
  /**
15
15
  * The closed registry of system ids, and the `none` that stands for no system
16
- * at all (#59).
16
+ * at all.
17
17
  *
18
18
  * A canonical address carries the system in a fixed position —
19
19
  * `harnadventures-none-being-grod` — so "which systems exist" is a question the
@@ -47,7 +47,7 @@
47
47
 
48
48
  // The one charset, read rather than restated — a system id is an address
49
49
  // segment like any other, and a second spelling of the pattern is how the
50
- // disagreements in #202/#203 happened.
50
+ // disagreements between them arise.
51
51
  import { ADDRESS_SEGMENT_PATTERN, isAddressSegment } from "./address-charset.mjs";
52
52
 
53
53
  /**
@@ -22,7 +22,7 @@
22
22
  * `[[type-shortcode#slug|Text]]` → `[Text](/section/slug/#slug)`
23
23
  * `[[#slug|Text]]` → `[Text](#slug)`
24
24
  *
25
- * **Every link is an address and carries a label** (#180). One written without
25
+ * **Every link is an address and carries a label**. One written without
26
26
  * a label addresses nothing and is reported — see
27
27
  * {@link unlabelledLinkMessage}, which states the rule for this build and the
28
28
  * pack build together.
@@ -34,22 +34,22 @@
34
34
  * Lives here rather than in a consumer so every package resolves a link the
35
35
  * same way. `sohl-thalorna` carried a forked copy of this as
36
36
  * `utils/site-wikilinks.mjs`, already 3 KB adrift from the original, which is
37
- * exactly the drift one rule with two implementations produces (#20).
37
+ * exactly the drift one rule with two implementations produces.
38
38
  */
39
39
 
40
40
  // How an address *parses* is the pack build's own rule, so the two builds
41
41
  // cannot drift apart on it: they disagreed once over the unlabelled hyphen
42
42
  // form, which the packs showed as a raw shortcode and the knowledgebase as a
43
- // name (#1409).
43
+ // name.
44
44
  import { readQualifier } from "./wikilinks.mjs";
45
45
  import { replaceOutsideCode } from "./code-fences.mjs";
46
46
  // The canonical `package-system-type-shortcode` key, so a package-qualified
47
47
  // address is matched the way a vendored manifest publishes it — by the
48
- // segments the target supplies, with the system wildcarded unless stated (#59).
48
+ // segments the target supplies, with the system wildcarded unless stated.
49
49
  import { canonicalKey, expandAddress, readCanonicalKey } from "./content-address.mjs";
50
50
  import { NO_SYSTEM } from "./systems.mjs";
51
51
  // The one rule about a link's shape both builds share: it carries a label, and
52
- // {@link unlabelledLinkMessage} is the one place that says so (#180).
52
+ // {@link unlabelledLinkMessage} is the one place that says so.
53
53
  import { unlabelledLinkMessage } from "./wikilink-syntax.mjs";
54
54
  // One slug rule for the whole build — see `./content-slug.mjs`. This module
55
55
  // carried a copy that dropped non-ASCII letters rather than transliterating
@@ -68,7 +68,7 @@ import { authoredLabel, WIKILINK, isSamePage, parseWikilink } from "./wikilink-s
68
68
  * parse as an address at all.
69
69
  *
70
70
  * The KB index is keyed by the canonical `type/shortcode`, so a target written
71
- * in the hyphen separator — which is what the content tree authors (#1398)
71
+ * in the hyphen separator — which is what the content tree authors —
72
72
  * has to be rewritten to it before lookup. The target is read by the pack
73
73
  * build's own {@link readQualifier}, so recognising an address and resolving
74
74
  * one can never disagree: the two separators and the optional leading package
@@ -77,7 +77,7 @@ import { authoredLabel, WIKILINK, isSamePage, parseWikilink } from "./wikilink-s
77
77
  * It takes the **parsed** qualifier rather than the raw target because the
78
78
  * caller needs the parse for a second purpose: `unknown-type` and
79
79
  * `not-an-address` are different findings with different fixes, and only the
80
- * `reason` tells them apart (#184). Reading the target twice would let the two
80
+ * `reason` tells them apart. Reading the target twice would let the two
81
81
  * readings drift.
82
82
  *
83
83
  * The build indexes an item note under both `skill/climb` and `docskill/climb`,
@@ -88,7 +88,7 @@ import { authoredLabel, WIKILINK, isSamePage, parseWikilink } from "./wikilink-s
88
88
  */
89
89
  function lookupRead(index, read, contentPackage) {
90
90
  if (!read || read.reason) return undefined;
91
- // Every omitted segment defaults from where the link is written (#336)
91
+ // Every omitted segment defaults from where the link is written —
92
92
  // package from the citing package, system from the block, which on a page
93
93
  // body is `none` — so the target expands to exactly one canonical address
94
94
  // and this is a plain lookup. No filter, no single-hit rule, and no
@@ -104,7 +104,7 @@ function lookupRead(index, read, contentPackage) {
104
104
  // A local target may also be keyed by its short form. On the KB that is not
105
105
  // a second answer: an item note renders as **one page** which is its own
106
106
  // documentation, so `skill/climb`, `docskill/climb` and both canonical
107
- // addresses are all the same value (#1362) — the fallback cannot pick
107
+ // addresses are all the same value — the fallback cannot pick
108
108
  // differently, only earlier. It stays because an index built before the
109
109
  // canonical documentation key was added still carries the short one, and
110
110
  // because a page collision here is caught by the site index's own
@@ -127,10 +127,10 @@ function lookupRead(index, read, contentPackage) {
127
127
  * This is deliberately identical to the pack compiler's own `unresolvedLink`,
128
128
  * down to the class name and the `title` wording. One authored link renders on
129
129
  * two surfaces, and the two builds have drifted before over exactly this kind
130
- * of detail (#1409) — matching markup is what keeps a reader's cue the same in
130
+ * of detail — matching markup is what keeps a reader's cue the same in
131
131
  * a journal and on the page. Duplicated rather than imported only because the
132
132
  * function is not exported from `@heroiclands/package-build`; hoisting it there
133
- * is HeroicLands/content-build#13.
133
+ * is.
134
134
  *
135
135
  * The knowledgebase renders with `unsafe = true` (`kb/hugo.toml`), so raw HTML
136
136
  * in generated markdown reaches the page. That makes escaping obligatory: this
@@ -154,7 +154,7 @@ function unresolvedLink(text, target) {
154
154
  }
155
155
 
156
156
  /**
157
- * How a link to a **draft** note renders (#183).
157
+ * How a link to a **draft** note renders.
158
158
  *
159
159
  * A note tagged `draft` exists so a link into it is not dead, and nothing more.
160
160
  * Unmarked, a reader follows a promising link into an empty page and an author
@@ -173,8 +173,8 @@ function unresolvedLink(text, target) {
173
173
  * **Byte-identical with the pack build's copy** in `wikilinks.mjs`, down to the
174
174
  * class name and the `title` wording — one authored link renders on two
175
175
  * surfaces, and the two builds have drifted before over exactly this kind of
176
- * detail (#1409). Duplicated rather than imported for the same reason
177
- * {@link unresolvedLink} is; hoisting both is HeroicLands/content-build#13.
176
+ * detail. Duplicated rather than imported for the same reason
177
+ * {@link unresolvedLink} is; hoisting both is.
178
178
  *
179
179
  * The argument is already-built markup and is deliberately not escaped; the
180
180
  * *authored* text inside it was escaped, or made into a link, by the caller.
@@ -198,7 +198,7 @@ function draftLink(inner) {
198
198
  const FRONTMATTER_WIKILINK = new RegExp(WIKILINK.source, "g");
199
199
 
200
200
  /**
201
- * Every wikilink authored inside a frontmatter value (#1428).
201
+ * Every wikilink authored inside a frontmatter value.
202
202
  *
203
203
  * Wikilinks are resolved in a note's **body** — by {@link resolveWebWikilinks}
204
204
  * here, and by the pack compilers' `convertWikilinks` for Foundry. Frontmatter
@@ -267,19 +267,19 @@ function isPlainMap(value) {
267
267
  * `section/slug`), then in the vendored `ctx.foreign` manifests. A target is a
268
268
  * *partial* address: an omitted package means this package, an omitted system
269
269
  * is a wildcard, and exactly one match resolves — two are `ambiguous`. A link written
270
- * without a label addresses nothing at all and is reported as such (#180)
270
+ * without a label addresses nothing at all and is reported as such —
271
271
  * there is no second namespace left for it to name.
272
272
  *
273
273
  * Only a slash-qualified target reaches the raw key, which is what keeps
274
274
  * `section/slug` addressable without a page's own slug answering for it.
275
275
  *
276
- * **Every target that resolves nowhere fails the build** (#184), and is
276
+ * **Every target that resolves nowhere fails the build**, and is
277
277
  * classified into the vocabulary all three resolvers share — `unlabelled`,
278
278
  * `not-an-address`, `unknown-type`, `ambiguous`, `unresolved`. Failures are
279
279
  * collected in `ctx.errors`, each carrying the authored `link` and its
280
280
  * `occurrence` so a caller can report the line and column it sits on.
281
281
  *
282
- * There used to be one exception: a hyphen-form address was let through while
282
+ * There is deliberately no exception letting a hyphen-form address through while
283
283
  * any linkable package had no vendored manifest, since a real cross-package
284
284
  * reference and a typo look identical from here. The pack compilers and the
285
285
  * link checker never made that allowance, so its only surviving effect was to
@@ -287,13 +287,13 @@ function isPlainMap(value) {
287
287
  * instead.
288
288
  *
289
289
  * A target that resolves nowhere still renders through {@link unresolvedLink}
290
- * rather than as bare prose (#1665): the author's text is kept, marked so a
290
+ * rather than as bare prose: the author's text is kept, marked so a
291
291
  * reader can see a link was intended. The marking and the failure are separate
292
292
  * jobs and always were — the mark is for whoever reads the page a *previous*
293
293
  * build emitted, the failure is for the author of this one.
294
294
  *
295
295
  * A target that **resolved** to an entry with no page is not this case and is
296
- * not marked: a pack-only package (#1516) publishes Foundry addresses and no
296
+ * not marked: a pack-only package publishes Foundry addresses and no
297
297
  * web pages, so the author wrote a real address and there is simply nothing to
298
298
  * link to.
299
299
  *
@@ -302,7 +302,7 @@ function isPlainMap(value) {
302
302
  * foreign, type, errors, src, file }`.
303
303
  * `packages` is every package an address may name, without which the leading
304
304
  * package segment of a canonical address reads as an unknown type; `foreign`
305
- * is the cross-package manifest index (#1446). `src` is the page's display
305
+ * is the cross-package manifest index. `src` is the page's display
306
306
  * path and `file` the source file a diagnostic should name — absent, `src`
307
307
  * stands in.
308
308
  * @returns {string} The body with wikilinks rewritten.
@@ -311,7 +311,7 @@ export function resolveWebWikilinks(body, ctx) {
311
311
  // How many times each authored link has been seen, so two identical links
312
312
  // on one page are located at their own positions in the source file — the
313
313
  // same counting the checker does, and what turns a finding into a
314
- // `file:line:column:` diagnostic rather than a note-wide one (#17, #184).
314
+ // `file:line:column:` diagnostic rather than a note-wide one.
315
315
  const seen = new Map();
316
316
  /**
317
317
  * Records a finding, and returns the marked-up link it renders as.
@@ -338,16 +338,16 @@ export function resolveWebWikilinks(body, ctx) {
338
338
  };
339
339
 
340
340
  // Code is verbatim: a `[[…]]` inside a code fence, an indented block or an
341
- // inline span is source text, not a link (#1505).
341
+ // inline span is source text, not a link.
342
342
  return replaceOutsideCode(body, WIKILINK, (all, rawInner) => {
343
343
  const parsed = parseWikilink(rawInner);
344
344
  const { target, anchor, display } = parsed;
345
345
  // An empty label is not a label: `[[x|]]` addresses the target and
346
- // shows its name, so `""` falls through to the same place `null` does
347
- // (#113). One reading, from {@link authoredLabel}.
346
+ // shows its name, so `""` falls through to the same place `null` does.
347
+ // One reading, from {@link authoredLabel}.
348
348
  const label = authoredLabel({ display });
349
349
 
350
- // **Every link carries a label** (#180). Without one there is nothing
350
+ // **Every link carries a label**. Without one there is nothing
351
351
  // to resolve against — the alias namespace a bare `[[Text]]` named is
352
352
  // retired — and nothing to show either, a shortcode being an address
353
353
  // rather than prose. Reported before the same-page form, because the
@@ -362,7 +362,7 @@ export function resolveWebWikilinks(body, ctx) {
362
362
  return `[${label ?? anchor}](#${slugify(anchor)})`;
363
363
  }
364
364
 
365
- // The canonical separator (#1398) has to be resolved, not merely
365
+ // The canonical separator has to be resolved, not merely
366
366
  // recognised. `null` here means the target is not an address at all,
367
367
  // which is a defect: there is no other namespace to try.
368
368
  const read = readQualifier(target, ctx.contentTypes ?? new Set(), ctx.packages);
@@ -374,10 +374,10 @@ export function resolveWebWikilinks(body, ctx) {
374
374
  // a page's bare slug cannot answer for an address.
375
375
  (rawKey.includes("/") ? ctx.index.get(rawKey) : undefined) ??
376
376
  // A manifest entry carries the same `{ url, name }` shape as a
377
- // local one (#1446), so a cross-package hit needs no special case
377
+ // local one, so a cross-package hit needs no special case
378
378
  // below. Local wins: a live build is authoritative and a vendored
379
379
  // manifest can only be staler.
380
- // A short form names *this* package (#336), so it never reaches a
380
+ // A short form names *this* package, so it never reaches a
381
381
  // vendored manifest; only a fully qualified address does.
382
382
  (ctx.foreign && read?.package ?
383
383
  lookupRead(ctx.foreign, read, ctx.contentPackage)
@@ -387,8 +387,8 @@ export function resolveWebWikilinks(body, ctx) {
387
387
  // shortcode is not display text), so the document's **current**
388
388
  // name stands in and a rename shows at every citation.
389
389
  const text = label ?? hit.name;
390
- // A pack-only package publishes Foundry addresses and no pages
391
- // (#1516), so its entries carry no `path` and resolve to no URL.
390
+ // A pack-only package publishes Foundry addresses and no pages,
391
+ // so its entries carry no `path` and resolve to no URL.
392
392
  // The address is real — this is not a typo and must not fail the
393
393
  // build — but there is nothing on the web to point at, so the
394
394
  // reader gets the text and no href. Emitting the href anyway is
@@ -396,8 +396,8 @@ export function resolveWebWikilinks(body, ctx) {
396
396
  // as a link and goes nowhere.
397
397
  const link =
398
398
  hit.url ? `[${text}](${anchor ? `${hit.url}#${slugify(anchor)}` : hit.url})` : text;
399
- // A link into a note that exists but is not written renders marked
400
- // (#183). Presentation only — the href above is unchanged, and a
399
+ // A link into a note that exists but is not written renders marked.
400
+ // Presentation only — the href above is unchanged, and a
401
401
  // `[[#anchor]]` self-link is not marked because the reader is
402
402
  // already on the page it would be telling them about.
403
403
  return hit.draft ? draftLink(link) : link;
@@ -413,7 +413,7 @@ export function resolveWebWikilinks(body, ctx) {
413
413
  // it parses as an address.)
414
414
  const siteAddress = prefix !== null && ctx.sections.has(prefix);
415
415
 
416
- // **An address resolving nowhere is a failure, unconditionally** (#184).
416
+ // **An address resolving nowhere is a failure, unconditionally**.
417
417
  //
418
418
  // It was gated on a manifest-completeness check — while any linkable package was
419
419
  // invisible here, `Rules/Bestiary.md` addressing `being-grkrahk` in the
@@ -443,7 +443,7 @@ export function resolveWebWikilinks(body, ctx) {
443
443
  : read?.reason === "unknown-type" ? "unknown-type"
444
444
  // Every link is an address, and this is not one. Distinct from
445
445
  // a dead address, because the fix is different: a name has to
446
- // become an address, not be corrected (#180).
446
+ // become an address, not be corrected.
447
447
  : "not-an-address";
448
448
 
449
449
  // Whether or not it failed the build, the link renders marked: the
@@ -29,7 +29,7 @@
29
29
  * The web side's pattern omitted `\n` from the excluded set, so an unclosed
30
30
  * bracket consumed everything up to the next `]]` anywhere in the document —
31
31
  * the same shape of corruption a hand-rolled code-fence regex caused on the one
32
- * page whose subject is link syntax (SoHL#1665). It was also internally
32
+ * page whose subject is link syntax. It was also internally
33
33
  * inconsistent: its *frontmatter* scan excluded newlines while its body scan did
34
34
  * not.
35
35
  *
@@ -45,7 +45,7 @@
45
45
  * ({@link LINK_FINDING_REASONS}) and the message each one reports through
46
46
  * ({@link linkFindingMessage}). Three builds read one authored link; an author
47
47
  * meets whichever ran first, and a consumer switching on a `reason` should not
48
- * be switching on which build produced it (#184).
48
+ * be switching on which build produced it.
49
49
  *
50
50
  * @module
51
51
  */
@@ -72,7 +72,7 @@ export const WIKILINK = /\[\[([^\]\n]+)\]\]/g;
72
72
  * @property {string|null} display - The text after `|`, or `null` when the link
73
73
  * is unlabelled. `null` and `""` differ: an author may write `[[x|]]`.
74
74
  * @property {boolean} labelled - Whether a `|` was present at all. A link
75
- * without one addresses nothing and is a finding (#180) — see
75
+ * without one addresses nothing and is a finding — see
76
76
  * {@link unlabelledLinkMessage} — so every reader has to be able to ask.
77
77
  */
78
78
 
@@ -111,12 +111,12 @@ export function parseWikilink(rawInner) {
111
111
  * `display: null` does. The two are still distinguishable through
112
112
  * {@link ParsedWikilink.labelled}, which is the thing that genuinely differs:
113
113
  * `[[x|]]` is labelled and `[[x]]` is not, and only the first addresses
114
- * anything (#180).
114
+ * anything.
115
115
  *
116
116
  * Stated here because the two resolvers had already drawn the line in two
117
117
  * places and drawn it differently: the packs tested falsiness and were right,
118
118
  * the web tested `??` — which falls through on `null` only — and emitted
119
- * `[](/url/)`, a link with no clickable text, through every build (#113). That
119
+ * `[](/url/)`, a link with no clickable text, through every build. That
120
120
  * is the same drift this module exists to prevent, in the case its own
121
121
  * {@link ParsedWikilink} docstring calls out. One reading, one place.
122
122
  *
@@ -136,12 +136,12 @@ export function authoredLabel({ display }) {
136
136
  * the form to write rather than a value to correct: there is no value that
137
137
  * makes an unlabelled link resolve.
138
138
  *
139
- * **Why there is nothing left for a bare link to mean** (#180). The pipe used
139
+ * **Why there is nothing left for a bare link to mean**. The pipe used
140
140
  * to select between two namespaces — address and alias — and the alias one was
141
141
  * empty in practice: across 8,305 wikilinks in three content trees, not one
142
142
  * bare `[[Alias]]` resolved to a note. What the index it looked up in *did* do
143
143
  * was fold every note's `name.full` into itself, which forbade two notes of a
144
- * type from sharing a display name (#179). So the namespace is gone, every
144
+ * type from sharing a display name. So the namespace is gone, every
145
145
  * link is an address, and an address needs the pipe that says so.
146
146
  *
147
147
  * The **link part may still be an anchor**: `[[#slug|Text]]` addresses a
@@ -160,16 +160,16 @@ export function unlabelledLinkMessage(target) {
160
160
  }
161
161
 
162
162
  /**
163
- * Every way a link can fail, named once for all three resolvers (#184).
163
+ * Every way a link can fail, named once for all three resolvers.
164
164
  *
165
165
  * A link is read in three places — the checker (`content-links.mjs`), the pack
166
166
  * compilers (`wikilinks.mjs`) and the web resolver (`web-wikilinks.mjs`) — and
167
- * each used to name the failures in its own words. `unknown` in one was
167
+ * each would otherwise name the failures in its own words. `unknown` in one is
168
168
  * `unresolved` in another and `broken type/shortcode` in the third, so a
169
169
  * consumer switching on a `reason` was switching on which build had produced
170
170
  * it. The set is closed and lives here, beside the syntax the three share.
171
171
  *
172
- * - `unlabelled` — no `|`, so the link addresses nothing (#180).
172
+ * - `unlabelled` — no `|`, so the link addresses nothing.
173
173
  * - `not-an-address` — labelled, but the target does not parse as an address.
174
174
  * - `unknown-type` — definitely qualified, but names no type this build knows.
175
175
  * - `unresolved` — parses as an address, and nothing publishes it.
@@ -198,12 +198,12 @@ export const LINK_FINDING_REASONS = Object.freeze(
198
198
  * package publishing it is not a declared dependency — or is one whose index
199
199
  * has not been fetched — and the link itself looks identical in every case.
200
200
  *
201
- * This used to be a **warning** in the checker and, in the site build, nothing
201
+ * As a **warning** in the checker and, in the site build, nothing
202
202
  * at all until every linkable package's manifest was accounted for — on the
203
203
  * reasoning that a bare `[[Name]]` might be a placeholder for a note nobody had
204
- * written yet. That reasoning was a property of the bare form, which is retired
205
- * (#180); the intent behind it now has a real spelling, a `draft`-tagged note
206
- * that exists and resolves and renders marked (#183). So an address naming no
204
+ * written yet. That reasoning was a property of the bare form, which is retired;
205
+ * the intent behind it now has a real spelling, a `draft`-tagged note
206
+ * that exists and resolves and renders marked. So an address naming no
207
207
  * note is a typo or an omission, both want fixing, and all three builds say so.
208
208
  *
209
209
  * @param {string} target - The address as authored, named in the message.
@@ -226,9 +226,9 @@ export function unresolvedAddressMessage(target) {
226
226
  * message names the claimants so the author can choose between them without
227
227
  * going looking.
228
228
  *
229
- * **The correction is the canonical form**, all four segments (#59). Omission
229
+ * **The correction is the canonical form**, all four segments. Omission
230
230
  * runs strictly left to right, so there is no `package-type-shortcode` to offer
231
- * — naming a package means naming the system before the type. That used to be
231
+ * — naming a package means naming the system before the type. That would be
232
232
  * the correction here, back when a written target could state three segments at
233
233
  * most; the grammar is positional now, and a three-segment target names a
234
234
  * *system*, not a package.