@heroiclands/package-build 22.12.3 → 22.13.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,18 @@
1
1
  # @heroiclands/package-build
2
2
 
3
+ ## 22.13.0
4
+
5
+ ### Minor Changes
6
+
7
+ **Content links**
8
+
9
+ - Ordinary wikilinks open readable notes by default; image embeds and art fields resolve systemless assets.
10
+ - Readable notes use `note` addresses, while explicit game systems select Actors and Items and `none` selects systemless targets.
11
+
12
+ **Editor navigation**
13
+
14
+ - Install `@heroiclands/content-language-server` for content note search and navigation in an editor.
15
+
3
16
  ## 22.12.3
4
17
 
5
18
  ### Patch Changes
package/README.md CHANGED
@@ -14,8 +14,8 @@ The content half is documented separately in **[CONTENT.md](CONTENT.md)** — th
14
14
  note format, the pack pipeline, and the configuration contract a content tree
15
15
  declares itself with.
16
16
 
17
- Editor navigation through the content index is described in the
18
- [content language server guide](docs/content-language-server.md).
17
+ Editor navigation for content projects is provided by
18
+ [`@heroiclands/content-language-server`](https://github.com/HeroicLands/content-language-server).
19
19
 
20
20
  ## Install
21
21
 
package/docs/api.md CHANGED
@@ -596,16 +596,17 @@ The map from each place, drawn for the site: `content-build site` draws the map
596
596
 
597
597
  The closed registry of system ids, and the `none` that stands for no system at all. An unknown system value is an error; adding a system is a data change to this registry rather than a hardcoded set scattered through the pipeline.
598
598
 
599
- | Export | Signature | Returns | Use it when |
600
- | ---------------------- | -------------------------------------- | ------------------------------- | -------------------------------------------------------------------------------------------- |
601
- | `NO_SYSTEM` | `const NO_SYSTEM` | — | naming the `<system>` segment of a note that belongs to no game system |
602
- | `SYSTEM_IDS` | `const SYSTEM_IDS` | — | enumerating every game system this toolchain compiles for |
603
- | `SYSTEM_SEGMENTS` | `const SYSTEM_SEGMENTS` | — | enumerating everything the `<system>` address segment may say — the systems plus `NO_SYSTEM` |
604
- | `isSystemId` | `isSystemId(value)` | `boolean` | checking whether a value names a game system this toolchain knows (`none` is rejected) |
605
- | `isSystemSegment` | `isSystemSegment(value)` | `boolean` | checking whether a value is something the `<system>` address segment may hold |
606
- | `unknownSystemMessage` | `unknownSystemMessage(value, where)` | `string` | building the message for a caller that wrote an unknown system |
607
- | `assertSystemSegment` | `assertSystemSegment(value, where)` | `string` — the value, unchanged | refusing a value the `<system>` segment may not hold, validating inline |
608
- | `assertSystemCharset` | `assertSystemCharset(segments, where)` | throws | refusing a registry declaration whose id could not be an address segment |
599
+ | Export | Signature | Returns | Use it when |
600
+ | ---------------------- | -------------------------------------- | ------------------------------- | -------------------------------------------------------------------------------------- |
601
+ | `NO_SYSTEM` | `const NO_SYSTEM` | — | naming a systemless asset or Foundry document |
602
+ | `NOTE_SYSTEM` | `const NOTE_SYSTEM` | — | naming readable note content across web, book and Foundry |
603
+ | `SYSTEM_IDS` | `const SYSTEM_IDS` | — | enumerating every game system this toolchain compiles for |
604
+ | `SYSTEM_SEGMENTS` | `const SYSTEM_SEGMENTS` | — | enumerating the game systems, `note` and `none` |
605
+ | `isSystemId` | `isSystemId(value)` | `boolean` | checking whether a value names a game system this toolchain knows (`none` is rejected) |
606
+ | `isSystemSegment` | `isSystemSegment(value)` | `boolean` | checking whether a value is something the `<system>` address segment may hold |
607
+ | `unknownSystemMessage` | `unknownSystemMessage(value, where)` | `string` | building the message for a caller that wrote an unknown system |
608
+ | `assertSystemSegment` | `assertSystemSegment(value, where)` | `string` — the value, unchanged | refusing a value the `<system>` segment may not hold, validating inline |
609
+ | `assertSystemCharset` | `assertSystemCharset(segments, where)` | throws | refusing a registry declaration whose id could not be an address segment |
609
610
 
610
611
  ### `engine.calendars`
611
612
 
@@ -675,7 +676,7 @@ Where a content note publishes on the web. One rule, in one place, because two b
675
676
  | `addressSlug` | `function addressSlug(fm)` | {string} The address segment, e.g. | The single path segment a note is addressed by: `type-shortcode`. |
676
677
  | `packageAddress` | `function packageAddress(fm)` | {string} The package-relative address, with a trailing slash and no leading one. | A note's address: `<type>-<shortcode>/`, e.g. |
677
678
  | `canonicalKey` | `function canonicalKey(pkg, system, type, shortcode)` | {string} `package-system-type-shortcode`, lowercased. | The **canonical** address of a note: fully qualified, one spelling per document, and globally unique. |
678
- | `blockSystem` | `function blockSystem(keyPath)` | {string} The system id, or `none`. | Which system a frontmatter key path is written under. |
679
+ | `blockSystem` | `function blockSystem(keyPath)` | {string} The game system id, or `note`. | Which system a frontmatter key path is written under. |
679
680
  | `expandAddress` | `function expandAddress(read, where)` | {string} The canonical `package-system-type-shortcode`. | Expand a written address to the one canonical address it names. |
680
681
  | `CANONICAL_KEY_SEGMENTS` | `const CANONICAL_KEY_SEGMENTS` | — | How many segments a canonical key has, and therefore how many the reader below counts. |
681
682
  | `readCanonicalKey` | `function readCanonicalKey(key)` | {{package: string, system: string, type: string, shortcode: string} \|null\|undefined} The parts; `null` when there is a string that is not in canonical form; `undefined` when there is no key at all. | Reads a canonical key back into its parts. |
@@ -1062,12 +1063,12 @@ Shared helpers for the pack compilers in `packages/content-build/`. The HeroicLa
1062
1063
 
1063
1064
  **Item docs** — an item's prose compiled as a JournalEntry, with the item keeping only a pointer to it. An item note's body describes what the thing _is_. That is documentation, and documentation belongs in the journals pack, so each item note compiles into a JournalEntry and the item's `system.docHtml` becomes nothing but a `@UUID` link to that entry's first page — the description-as-pointer convention, which {@link sohl.utils.descriptionLinkTarget} recognises and Display Description follows.
1064
1065
 
1065
- | Export | Signature | Returns | Use it when |
1066
- | ---------------- | --------------------------------------------------------------- | ------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------- |
1067
- | `docEntryTypes` | `function docEntryTypes()` | {ReadonlySet<string>} The configured doc-carrying types. | Every content type whose **prose compiles into a JournalEntry of its own**, addressed by the virtual `doc<type>` qualifier. |
1068
- | `hasDocEntry` | `function hasDocEntry(type)` | {boolean} True for an item type, for `macro` and for a map type; false for `doc` and for actors. | Whether a content note's type is one whose prose becomes a JournalEntry of its own. |
1069
- | `itemDocEntryId` | `function itemDocEntryId(itemId)` | {string} A 16-character Foundry id. | The id of the JournalEntry a note's prose compiles into — an item's, or a macro's. |
1070
- | `itemDocPointer` | `function itemDocPointer(packageId, itemId, name, firstPageId)` | {string} The pointer to store in `system.docHtml`. | The description an item carries in place of its prose: a `@UUID` link to the first page of its item doc, and nothing else. |
1066
+ | Export | Signature | Returns | Use it when |
1067
+ | ---------------- | --------------------------------------------------------------- | ------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------- |
1068
+ | `docEntryTypes` | `function docEntryTypes()` | {ReadonlySet<string>} The configured doc-carrying types. | Every content type whose **prose compiles into a JournalEntry of its own**, addressed by `note` with the authored type. |
1069
+ | `hasDocEntry` | `function hasDocEntry(type)` | {boolean} True for an item type, for `macro` and for a map type; false for `doc` and for actors. | Whether a content note's type is one whose prose becomes a JournalEntry of its own. |
1070
+ | `itemDocEntryId` | `function itemDocEntryId(itemId)` | {string} A 16-character Foundry id. | The id of the JournalEntry a note's prose compiles into — an item's, or a macro's. |
1071
+ | `itemDocPointer` | `function itemDocPointer(packageId, itemId, name, firstPageId)` | {string} The pointer to store in `system.docHtml`. | The description an item carries in place of its prose: a `@UUID` link to the first page of its item doc, and nothing else. |
1071
1072
 
1072
1073
  - `itemTypes` — re-exported: Every content type that compiles into an item — and therefore into an item doc.
1073
1074
 
@@ -1077,7 +1078,7 @@ Wikilink resolution for the pack compilers. Content notes link to one another wi
1077
1078
 
1078
1079
  | Export | Signature | Returns | Use it when |
1079
1080
  | -------------------- | ----------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
1080
- | `resolveItemDocType` | `function resolveItemDocType(qualifier, types)` | {string\|null} The underlying document type, or `null` when the qualifier is not a virtual one. | Reads a qualifier as the **virtual `doc<type>`** form, or reports that it is not one. |
1081
+ | `resolveItemDocType` | `function resolveItemDocType(qualifier, types)` | {string\|null} The underlying document type, or `null` when the qualifier is not a virtual one. | Reads an accepted `doc<type>` input alias as its authored type. |
1081
1082
  | `readQualifier` | `function readQualifier(target, types, packages, noIndexPackages)` | {{type: string, shortcode: string, itemDoc: boolean, package?: string, system?: string, reason?: undefined} \| {reason: "unknown-type"\|"no-content-index", package?: string} \| null} The resolved qualifier; a `reason` when the target is definitely qualified but names no known type or no fetched index; or `null` when it is not an address at all. | Read a link target as a **qualified** `type-shortcode` reference, or report that it does not parse as one. |
1082
1083
  | `anchorPageId` | `function anchorPageId(noteId, anchorSlug)` | {string} A 16-character alphanumeric id. | The deterministic JournalEntryPage id for one anchor: SHA-256 of `"<noteId>-<anchorSlug>"`, base64-encoded, reduced to the 16 alphanumeric characters a Foundry id allows. |
1083
1084
  | `buildWikilinkIndex` | `function buildWikilinkIndex(docs, packageId, foreign, contentPackage)` | {{byShortcode: Map<string, object>, types: Set<string>}} `types` is every type the tree actually contains, so a qualifier naming no real type can be told apart from a missing target. | Builds the link-resolution tables for a content tree. |
package/docs/commands.md CHANGED
@@ -6,9 +6,6 @@ container, drive the end-to-end suite. `content-build` is the content half —
6
6
  compile a note tree into compendium packs, check it, publish it as a site or a
7
7
  book, and manage the caches a build resolves other packages through.
8
8
 
9
- The package also ships `heroiclands-content-language-server`, a stdio editor
10
- service described in the [content language server guide](content-language-server.md).
11
-
12
9
  Both read `package-build.config.yaml` from the repository root — see
13
10
  [Configuration](configuration.md) for every key. Neither reads it for
14
11
  `--version` or `--help`: those two answer in a directory with no
@@ -96,7 +96,7 @@ column regardless.
96
96
 
97
97
  **Embedded models link to documentation in an infobox.** The model Address
98
98
  continues to identify the native Item used for compilation. Its displayed link
99
- uses that same package, type and Shortcode under `none`, which names the
99
+ uses that same package, type and Shortcode under `note`, which names the
100
100
  corresponding documentation journal. A model with no published documentation
101
101
  keeps its known name as plain text; the infobox does not substitute an Actor or
102
102
  Item link.
@@ -1268,85 +1268,78 @@ below.
1268
1268
  <package>-<system>-<note_type>-<shortcode>
1269
1269
  ```
1270
1270
 
1271
- Read it from the right: the last segment is always the shortcode, the one before
1272
- it always the note type, the one before that always the system, and the first the
1273
- package. Nothing is positional-by-guess — a segment means what its position says
1274
- it means.
1271
+ The final segment is the shortcode, preceded by the note type, the system and
1272
+ the package. Each segment has one position. The system says which kind of target
1273
+ is named:
1275
1274
 
1276
- The `<system>` segment is `none` for a note that belongs to no system, which is
1277
- most of them: `harnadventures-none-being-grod`. A note that exists only for one
1278
- system names it.
1275
+ | system | target |
1276
+ | ------------- | --------------------------------------------------------------------------- |
1277
+ | `note` | Readable note content: a web page, a book entry, or a Foundry JournalEntry. |
1278
+ | `none` | A systemless target such as an asset, Folder, Macro or Scene. |
1279
+ | `sohl`, `hm3` | A game system's Actor or Item document. |
1280
+
1281
+ A note can publish more than one Address. A `being` note, for example, publishes
1282
+ `thalorna-note-being-bctrncml` for its readable content and
1283
+ `thalorna-sohl-being-bctrncml` for its SoHL Actor. An item note follows the same
1284
+ pattern for its page and Item. The type segment remains the authored note type
1285
+ in each Address.
1279
1286
 
1280
1287
  #### Shorter forms
1281
1288
 
1282
- The full address is the unambiguous form, and almost nothing uses it — 92 of
1283
- 12,056 links in the current trees. The rest rely on shorter forms, each dropping
1284
- segments from the left:
1289
+ Authors may omit segments from the left. The complete Address states all four:
1285
1290
 
1286
1291
  | form | expands to |
1287
1292
  | ------------------------------- | -------------------------------------------------------- |
1288
1293
  | `package-system-type-shortcode` | itself |
1289
1294
  | `system-type-shortcode` | `<this package>-system-type-shortcode` |
1290
- | `type-shortcode` | `<this package>-<this block's system>-type-shortcode` |
1291
- | `shortcode` | as above, with the type from the field's own declaration |
1295
+ | `type-shortcode` | `<this package>-<this position's system>-type-shortcode` |
1296
+ | `shortcode` | as above, with the type from the field's declaration |
1292
1297
 
1293
- There is no `package-type-shortcode`: the forms are exactly the **suffixes** of
1294
- the canonical address, so naming another package means naming its system too.
1295
- `kethira-place-tashal` is three segments, which reads as system `kethira`, and
1296
- fails.
1298
+ There is no `package-type-shortcode` form. A reference to another package states
1299
+ its system as well as its package.
1297
1300
 
1298
1301
  #### Authored references and generated references
1299
1302
 
1300
- Authored notes may use any permitted suffix. Builds leave those source bytes
1301
- unchanged. Generated content indexes, SQL records, site frontmatter and generated
1302
- Wikilinks contain full `package-system-type-shortcode` Addresses. For example,
1303
- `data.parents: [north]` in the `thalorna` package emits
1304
- `data.parents: [thalorna-none-place-north]`.
1303
+ Authored notes may use any permitted suffix. Builds leave source bytes unchanged.
1304
+ Generated content indexes, SQL records, site frontmatter and generated
1305
+ Wikilinks contain complete Addresses. For example, `data.parents: [north]` in
1306
+ `thalorna` emits `data.parents: [thalorna-note-place-north]`.
1305
1307
 
1306
- SQL queries compare Address-valued fields to full Addresses. A note's `shortcode`
1307
- is a distinct identity component, so it is not equal to an Address stored in
1308
- `data.parents`, `data.domains` or a governance rank's `lore`. Use the target's
1309
- complete identity for that comparison, including its package, system and type.
1310
- A stub has no published page address; its note identity still contains those
1311
- components and its Shortcode.
1308
+ SQL queries compare Address-valued fields to complete Addresses. A note's
1309
+ `shortcode` is a distinct identity component, so it is not equal to an Address
1310
+ stored in `data.parents`, `data.domains` or a governance rank's `lore`. Use the
1311
+ target's complete identity for that comparison. A stub has no published page
1312
+ address; its note identity still contains the package, system, type and shortcode.
1312
1313
 
1313
- The toolchain holds parsed Address properties as complete tuples internally.
1314
- Address-keyed relation maps hold typed targets and standing values. Only generated
1315
- output serializes these properties to Address strings. Runtime Shortcodes,
1316
- Foundry UUIDs, file paths and URLs keep their own representations.
1314
+ The toolchain holds parsed Addresses as complete tuples internally. Address-keyed
1315
+ relation maps hold typed targets and standing values. Only generated output
1316
+ serializes these properties to Address strings. Runtime Shortcodes, Foundry
1317
+ UUIDs, file paths and URLs keep their own representations.
1317
1318
 
1318
1319
  An Address-valued `data:` field must name a target in this package or a declared
1319
- dependency index. `content-build lint` reports a missing target as an error at
1320
- the value or map key that names it. A stub counts as a target for data even
1321
- though it publishes no page. Art slots use their own missing-art warning and
1322
- document fallback.
1320
+ dependency index. `content-build lint` reports a missing target at the value or
1321
+ map key that names it. A stub counts as a target for data even though it
1322
+ publishes no page. Art slots use their own missing-art warning and document
1323
+ fallback.
1323
1324
 
1324
1325
  #### An omitted segment defaults from where the link is written
1325
1326
 
1326
- It is **not** a wildcard and resolution is not a search. Every short form expands
1327
- to exactly one canonical address before anything is looked up, so a lookup either
1328
- finds one entry or none — there is no candidate set, and a cross-package
1329
- ambiguity is impossible by construction.
1330
-
1331
- - **package** omitted → the current package. A short address therefore names
1332
- _this_ package and never falls through to a dependency; reaching another one is
1333
- the fully qualified form's job.
1334
- - **system** omitted → **the system block the link is written under**. Anywhere
1335
- under `sohl:` is `sohl`; anywhere under `hm3:` is `hm3`; **anywhere else** —
1336
- top-level frontmatter, `data:`, and body prose — is `none`. The enclosing block
1337
- decides at any depth, so `sohl.items[3].model` and `sohl.system.body.structure`
1338
- default alike; the field has no say.
1339
-
1340
- **Under `none`, a system-bearing type addresses its documentation.** A note's
1341
- `none` address _is_ its `doc<type>` journal — the Item is the one with a system —
1342
- so a prose `[[affiliation-sirvadar|Sirvadar]]` names the page, which is what
1343
- prose almost always means. A prose link that means the **Item** states the
1344
- system: `[[sohl-affiliation-sirvadar|…]]`.
1345
-
1346
- Only a type whose own document carries a system is redirected this way. A
1347
- `macro` and the map types have documentation journals too, but their own
1348
- documents are core ones already at `none`, so `macro-autoattack` names the Macro
1349
- and `docmacro-autoattack` its journal — two live addresses.
1327
+ An omitted segment is a default, not a wildcard. Every short form expands to one
1328
+ complete Address before lookup.
1329
+
1330
+ - **Package:** the current package. A short address never falls through to a
1331
+ dependency.
1332
+ - **System in `[[link|text]]`:** `note`, for every note type. Use an explicit
1333
+ game system to name its Actor or Item, or `none` to name a systemless target.
1334
+ - **System in `![[link|text]]`:** `none`. An embed names an image or icon asset;
1335
+ it also supplies `image` as the type of a bare shortcode.
1336
+ - **System in frontmatter:** a system block such as `sohl:` or `hm3:` supplies
1337
+ its game system. Other Address fields default to `note`, except fields
1338
+ declared for images, icons or folders, which default to `none`.
1339
+
1340
+ For example, `[[macro-autoattack|Automated Attack]]` names the readable macro
1341
+ note, while `[[none-macro-autoattack|Automated Attack]]` names the Foundry Macro.
1342
+ `![[icon-anvil|Anvil]]` names the systemless icon asset.
1350
1343
 
1351
1344
  **A complete Address is lowercase throughout.** Every segment — package,
1352
1345
  system, type and shortcode alike — contains only lowercase ASCII letters and
@@ -1572,7 +1565,7 @@ A **`Wikilink`** is how body prose names something:
1572
1565
  The brackets, the `#<anchor>` and the `|<text>` label are the Wikilink's own.
1573
1566
  What sits inside them is an `Address` like any other — read by the same grammar,
1574
1567
  written at any of its lengths, and taking each omitted segment from where the
1575
- link is written, which for body prose is this package and system `none`. So
1568
+ link is written, which for body prose is this package and system `note`. So
1576
1569
  nothing about resolution belongs to the bracketed form: a wikilink and a
1577
1570
  frontmatter field ask the same question of the same value, and the findings above
1578
1571
  are the answers either of them gets.
@@ -2054,10 +2047,10 @@ system a note might write. A package shipping for a second system declares
2054
2047
  `system:` on a pack of each class it compiles.
2055
2048
 
2056
2049
  That includes actors. A being producing its Actor and nothing else would be
2057
- the one system-bearing note with no address at `none`, leaving a prose link
2050
+ a system-bearing note with readable content, giving a prose link
2058
2051
  naming it nowhere to land. It carries a documentation journal like every other
2059
2052
  such note, addressed
2060
- `<package>-none-docbeing-<shortcode>` beside the Actor's
2053
+ `<package>-note-being-<shortcode>` beside the Actor's
2061
2054
  `<package>-<system>-being-<shortcode>`.
2062
2055
 
2063
2056
  **A being keeps its prose inline as well.** `system.appearance` and
@@ -4065,7 +4058,7 @@ await CONFIG.SOHL.class.Utility.currentCombatantAttack();
4065
4058
 
4066
4059
  Three rules follow, and each is deliberate. The anchor names the **page**, not
4067
4060
  the heading text, so the heading may be worded freely and
4068
- `[[docmacro-autoattack#script]]` still opens exactly this page. The fence must
4061
+ `[[macro-autoattack#script]]` still opens exactly this page. The fence must
4069
4062
  be **tagged** — an untagged fence is a code sample whose language nobody stated,
4070
4063
  and treating it as executable would make an illustrative snippet the macro. And
4071
4064
  only the **first** tagged fence counts, so a note may document its macro
@@ -4141,8 +4134,8 @@ persists in the pack.
4141
4134
  **Each address names the note's own document.** That is the same rule `pack:`
4142
4135
  follows, so there is one answer and not two. A note that compiles into _two_
4143
4136
  documents — an item and the JournalEntry its prose became — puts the second in a
4144
- bundle only when the bundle names it by its own `doc…` address:
4145
- `miscgear-bowlcer` is the item, `docmiscgear-bowlcer` its description page.
4137
+ bundle when the bundle names its `note` Address:
4138
+ `sohl-miscgear-bowlcer` is the Item, `miscgear-bowlcer` its description page.
4146
4139
 
4147
4140
  **An address that resolves to nothing fails the build.** A `folder` address is
4148
4141
  refused with a message of its own: a folder materialises in every pack holding
@@ -133,9 +133,7 @@ npm install --save-dev @heroiclands/package-build
133
133
  ```
134
134
 
135
135
  That installs `package-build`, the packaging half, and `content-build`, the
136
- content half, into `node_modules/.bin`. It also installs the optional
137
- `heroiclands-content-language-server` for editor navigation. Every build command
138
- below uses one of the first two binaries. Run them through `npx`, or wire them into npm scripts as
136
+ content half, into `node_modules/.bin`. Run them through `npx`, or wire them into npm scripts as
139
137
  [`project-setup.md`](project-setup.md) describes.
140
138
 
141
139
  **How it worked:** `npx package-build --version` prints the installed version.
@@ -79,41 +79,25 @@
79
79
  * to guess which sense was meant. The cost is that `<package>-<type>-<shortcode>`
80
80
  * is **not** a written form, so a link into another package states its system.
81
81
  *
82
- * ## The three rules a complete tuple applies
83
- *
84
- * - **`system` is derived from the type, then defaulted.** An asset type and a
85
- * documentation journal are {@link NO_SYSTEM} because a file and a
86
- * JournalEntry belong to no game system, and the segment is therefore a
87
- * property of the type rather than of where the reference was written.
88
- * Everything else takes the written system, then the position's default.
89
- * - **`type` may not survive unchanged.** A system-bearing type reached at
90
- * `none` is redirected to `doc<type>`, because a note's `none` address *is*
91
- * its documentation journal — the Item is the one with a system.
92
- * - **Acceptability accepts a type's documentation form.** A position that
93
- * accepts `weapongear` accepts `docweapongear`, so a target addressed by its
94
- * documentation journal is acceptable wherever the note itself would be.
82
+ * ## Defaults belong to the citing position
83
+ *
84
+ * Ordinary wikilinks default to `note`, embeds default to `none`, and typed
85
+ * frontmatter fields supply their declared system. The authored type stays
86
+ * the type in the complete tuple. A `doc<type>` qualifier is accepted as an
87
+ * input spelling for a readable note and resolves to `note-<type>`.
95
88
  *
96
89
  * ## The tuple keeps no memory of how it was written
97
90
  *
98
91
  * An abbreviation is a *rendering*, not a different value: `being-kaldor` in a
99
- * `thalorna` note and `thalorna-none-being-kaldor` are the same Address, and
100
- * they parse to the same four fields. So does the pair `weapongear-dgr` in prose
101
- * and `docweapongear-dgr` anywhere, because both name the one documentation
102
- * journal. Nothing here records what the author typed — a tuple that did would
103
- * make identity depend on a spelling, and two notes naming one document would
104
- * compare unequal.
92
+ * `thalorna` note and `thalorna-note-being-kaldor` are the same Address, and
93
+ * they parse to the same four fields. Nothing here records what the author
94
+ * typed — a tuple that did would make identity depend on a spelling.
105
95
  *
106
96
  * The converse matters just as much: **one written string is two Addresses at
107
97
  * two positions**, because the positions supply different defaults.
108
98
  * `skill-wpnc` under `sohl.items` defaults its system from the block and names
109
- * the **Item**; the same string in body prose defaults to `none`, is redirected,
110
- * and names the **page**. Only the resolved tuple can tell them apart.
111
- *
112
- * That is why {@link acceptsType} widens the accepted set rather than reading a
113
- * remembered pre-redirect type off the tuple. The redirect is not reversible
114
- * from the four segments alone: `docmacro-autoattack` addresses a `macro`'s
115
- * journal while `macro-autoattack` addresses the Macro, so `doc`-stripping is a
116
- * question about the type vocabulary, not about the tuple.
99
+ * the Item; the same string in body prose defaults to `note` and names the
100
+ * readable page. Only the resolved tuple can tell them apart.
117
101
  *
118
102
  * ## What this module does not do
119
103
  *
@@ -132,17 +116,15 @@
132
116
  // `engine/systems.mjs` imports nothing but `engine/address-charset.mjs`, so the
133
117
  // direction is toward the leaf and cannot close a cycle.
134
118
  import { isAddressSegment } from "./address-charset.mjs";
135
- import { NO_SYSTEM, assertSystemSegment, isSystemSegment } from "./systems.mjs";
136
- // The asset vocabulary, for the one thing the address grammar asks of it:
137
- // whether a type's `<system>` segment is fixed. `engine/asset-types.mjs` is a
138
- // leaf, so the direction cannot close a cycle.
139
- import { isAssetType } from "./asset-types.mjs";
119
+ import { NO_SYSTEM, NOTE_SYSTEM, assertSystemSegment, isSystemSegment } from "./systems.mjs";
120
+ // Document subtype maps identify the system of a note's own Actor or Item.
140
121
  import { systemOf } from "./document-subtypes.mjs";
141
122
  import { KNOWN_DOCUMENT_SUBTYPE_MAPS } from "./subtype-registry.mjs";
142
123
  // `ids.mjs` is a leaf with no local imports, and `pack-config.mjs` reaches only
143
124
  // the configuration loader and the diagnostics writer — neither reads an
144
125
  // address, so the grammar sits below everything that consumes it.
145
- import { currentType, ITEM_PACK, packForType } from "./ids.mjs";
126
+ import { currentType, ITEM_PACK, JOURNAL_TYPES, packForType } from "./ids.mjs";
127
+ import { HOMEPAGE_TYPE } from "./homepage.mjs";
146
128
  import { loadPackConfig } from "./pack-config.mjs";
147
129
 
148
130
  export { NO_SYSTEM };
@@ -166,32 +148,18 @@ export const ADDRESS_SEPARATOR = "-";
166
148
  */
167
149
  export const CANONICAL_KEY_SEGMENTS = 4;
168
150
 
169
- /**
170
- * The qualifier prefix that addresses a note's **documentation** rather than
171
- * the note's own document: `docskill-wpnc` is the JournalEntry that
172
- * `skill-wpnc`'s prose compiled into.
173
- *
174
- * Every doc-carrying type has a `doc<type>` counterpart, formed by prefix and
175
- * never enumerated, so the prefix itself is the whole of that vocabulary.
176
- *
177
- * @type {string}
178
- */
151
+ /** The accepted `doc<type>` input prefix for readable note links. */
179
152
  export const ITEM_DOC_PREFIX = "doc";
180
153
 
181
154
  /** Package, system and type are one spelling each, so they fold to lowercase. */
182
155
  const norm = (s) => String(s).toLowerCase().trim();
183
156
 
184
157
  /**
185
- * A complete Address: four segments, and nothing else.
186
- *
187
- * `type` is the segment the canonical string carries, after the `doc<type>`
188
- * redirect. There is no fifth field recording the written form, because the
189
- * written form is a rendering rather than part of the value.
158
+ * A complete Address: four segments, with no memory of the authored form.
190
159
  *
191
160
  * @typedef {object} AddressTuple
192
161
  * @property {string} package - The owning **content** package.
193
- * @property {string} system - The system whose document this addresses, or
194
- * `none`.
162
+ * @property {string} system - `note`, `none`, or a game system id.
195
163
  * @property {string} type - The type segment of the canonical Address.
196
164
  * @property {string} shortcode - The note's `shortcode`.
197
165
  */
@@ -218,29 +186,8 @@ const norm = (s) => String(s).toLowerCase().trim();
218
186
  */
219
187
 
220
188
  /**
221
- * Every content type whose **prose compiles into a JournalEntry of its own**,
222
- * addressed by the virtual `doc<type>` qualifier.
223
- *
224
- * Every item type, every actor type, every map type, plus `macro` — a macro
225
- * note's body documents the script the note also compiles into a Macro, which
226
- * is the same shape as an item and its description: one note, two documents,
227
- * the prose living in the journals pack.
228
- *
229
- * **One set, read by the compilers and by the grammar alike.** The journals
230
- * pass decides what to compile from it, the link manifest decides what to
231
- * publish a `doc<type>` entry for, and the parser decides whether `doc<type>`
232
- * is a legal type segment. Held apart, the three drift into a manifest that
233
- * asserts documentation nothing compiled, a compiled entry no consumer can
234
- * address, or an authored `doc<type>` link that stops parsing. It is composed
235
- * exactly once, in `defineConfig`, and read from there — never recomposed at a
236
- * call site.
237
- *
238
- * `doc` notes are absent, for the reason that applies to them alone: a `doc`
239
- * note's single document *is* its prose, so there is no second document for a
240
- * `doc<type>` address to name.
241
- *
242
- * An accessor rather than a hoisted constant, so that importing this module
243
- * needs no configuration.
189
+ * The configured types whose prose compiles into a separate JournalEntry.
190
+ * The journal has a `note` Address with the authored type segment.
244
191
  *
245
192
  * @returns {ReadonlySet<string>} The configured doc-carrying types.
246
193
  */
@@ -265,50 +212,20 @@ export function hasDocEntry(type) {
265
212
  return docEntryTypes().has(String(currentType(type)));
266
213
  }
267
214
 
268
- /**
269
- * Whether a note type's **own** document carries a game system.
270
- *
271
- * True for the types some shipped map compiles into an Item or an Actor; false
272
- * for the core-document types — `doc`, `lore`, `place`, `scenario`, `macro` and
273
- * the map types — whose documents Foundry itself defines and which therefore
274
- * already live at `none`.
275
- *
276
- * It is what the `doc<type>` redirect tests rather than {@link hasDocEntry}: a
277
- * `macro` has a documentation journal *and* a `none` address of its own, so
278
- * redirecting on "has a doc entry" would collapse two live addresses into one
279
- * and a `[[macro-autoattack|]]` would stop naming the Macro.
280
- *
281
- * @param {string} type - The note type.
282
- * @returns {boolean} True when the type compiles into a system document.
283
- */
284
- function isSystemBearing(type) {
285
- return systemOf(type, KNOWN_DOCUMENT_SUBTYPE_MAPS) !== NO_SYSTEM;
215
+ /** The segment of a note's own document. */
216
+ export function ownDocumentSystem(type) {
217
+ return JOURNAL_TYPES.has(String(type)) || type === HOMEPAGE_TYPE ?
218
+ NOTE_SYSTEM
219
+ : systemOf(type, KNOWN_DOCUMENT_SUBTYPE_MAPS);
286
220
  }
287
221
 
288
222
  /**
289
- * Reads a qualifier as the **virtual `doc<type>`** form, or reports that it is
290
- * not one.
291
- *
292
- * A document and its documentation are two documents in two packs, so they need
293
- * two addresses. `skill-wpnc` is the item; `docskill-wpnc` is the JournalEntry
294
- * its prose compiled into, and `docmacro-autoattack` is the same arrangement
295
- * for a macro.
296
- *
297
- * The virtual form exists for a type that carries separate documentation
298
- * ({@link docEntryTypes} — the set the journals compiler and the link manifest
299
- * read too), **or** for one that routes to the items pack. The second clause
300
- * covers the open, unenumerated set: types that compile into items are not
301
- * enumerated anywhere, and a foreign package may publish an item type this
302
- * build has never heard of. Dropping it would silently unlink every `doc<type>`
303
- * address into such a package.
304
- *
305
- * A **real** type of the same name always wins: the virtual reading is only
306
- * consulted for a qualifier no authored note claims.
307
- *
308
- * @param {string} qualifier - The already-normalised type segment.
309
- * @param {Set<string>} types - Every type the content tree contains.
310
- * @returns {string|null} The underlying document type, or `null` when the
311
- * qualifier is not a virtual one.
223
+ * Read a `doc<type>` input qualifier as the underlying authored type.
224
+ * A real type with that spelling takes precedence.
225
+ *
226
+ * @param {string} qualifier - The type segment from the input.
227
+ * @param {Set<string>} types - Types in the content tree.
228
+ * @returns {string|null} The underlying type, or `null`.
312
229
  */
313
230
  export function resolveItemDocType(qualifier, types) {
314
231
  if (types.has(qualifier)) return null; // a real type owns its own name
@@ -445,21 +362,11 @@ export function renderAddress(tuple) {
445
362
  }
446
363
 
447
364
  /**
448
- * Whether the type an Address names is one this position accepts.
449
- *
450
- * The second of the two questions, and separate from the first because the
451
- * accepted **set** is a property of the position while the grammar is not.
452
- *
453
- * **An accepted type carries its documentation form with it.** A position
454
- * accepting `weapongear` accepts `docweapongear`, because the journal that
455
- * documents a weapon is the same thing to a position that wanted a weapon — and
456
- * a prose address of one *is* the journal, so refusing it would refuse the form
457
- * an author most often writes. It is the widening `defineConfig` applies to the
458
- * types a package may claim, applied here to the types a position may take.
365
+ * Whether the canonical type an Address names is accepted at this position.
459
366
  *
460
- * @param {AddressTuple} tuple - A parsed Address.
461
- * @param {Iterable<string>} allowed - The types this position accepts.
462
- * @returns {boolean} True when the Address names one of them.
367
+ * @param {AddressTuple} tuple - A complete Address.
368
+ * @param {Iterable<string>} allowed - Accepted types.
369
+ * @returns {boolean} Whether the type is accepted.
463
370
  */
464
371
  export function acceptsType(tuple, allowed) {
465
372
  const type = norm(tuple?.type ?? "");
@@ -467,7 +374,7 @@ export function acceptsType(tuple, allowed) {
467
374
  for (const one of allowed ?? []) {
468
375
  const want = norm(one);
469
376
  if (!want) continue;
470
- if (type === want || type === `${ITEM_DOC_PREFIX}${want}`) return true;
377
+ if (type === want) return true;
471
378
  }
472
379
  return false;
473
380
  }
@@ -475,9 +382,7 @@ export function acceptsType(tuple, allowed) {
475
382
  /**
476
383
  * Apply the defaults to a partial reading, giving a complete Address.
477
384
  *
478
- * The three rules of the module note, applied in the order they depend on each
479
- * other: the package, then the system the type may fix, then the redirect the
480
- * system may force.
385
+ * Complete a parsed Address using the citing position's package and system.
481
386
  *
482
387
  * @param {{type: string, shortcode: string, package?: string, system?: string,
483
388
  * itemDoc?: boolean}} read - A partial reading, as {@link readQualifier}
@@ -490,27 +395,8 @@ export function completeAddress(read, where = {}) {
490
395
  if (isAddressTuple(read)) return read;
491
396
  const written = read.type;
492
397
  const pkg = read.package ?? where.package;
493
- // A documentation journal is a core document, so it is `none` however it was
494
- // reached, and an **asset** is `none` for a stronger reason: a file belongs
495
- // to no game system, so the segment is a property of the type rather than of
496
- // where the reference was written. Without that, a `sohl:` block naming
497
- // `icon-anvil` would expand to `sohl-sohl-icon-anvil` and resolve to
498
- // nothing, which is exactly where an embedded item's art is written.
499
- // Otherwise the block's system, which body prose reports as `none`.
500
- const system =
501
- read.itemDoc || isAssetType(written) ?
502
- NO_SYSTEM
503
- : (read.system ?? where.system ?? NO_SYSTEM);
504
- // **Under `none`, a system-bearing type addresses its documentation
505
- // journal.** A note's `none` address *is* its `doc<type>` entry — the Item
506
- // is the one with a system — so a prose `[[affiliation-sirvadar|…]]` names
507
- // the page, which is almost always what prose means. A value that means the
508
- // Item states the system and gets it. A `doc<type>` written explicitly is
509
- // `none` wherever it appears, even inside a system block: no game system
510
- // defines a JournalEntry.
511
- const redirected = system === NO_SYSTEM && isSystemBearing(written);
512
- const type = read.itemDoc || redirected ? `${ITEM_DOC_PREFIX}${written}` : written;
513
- return addressTuple({ package: pkg, system, type, shortcode: read.shortcode });
398
+ const system = read.itemDoc ? NOTE_SYSTEM : (read.system ?? where.system ?? NOTE_SYSTEM);
399
+ return addressTuple({ package: pkg, system, type: written, shortcode: read.shortcode });
514
400
  }
515
401
 
516
402
  /**