@heroiclands/package-build 22.12.3 → 22.13.1

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 (49) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/README.md +2 -2
  3. package/docs/api.md +42 -40
  4. package/docs/commands.md +0 -3
  5. package/docs/content-format.md +94 -84
  6. package/docs/getting-started.md +1 -3
  7. package/engine/address.mjs +39 -153
  8. package/engine/art-fields.mjs +1 -1
  9. package/engine/bundles.mjs +3 -2
  10. package/engine/content-address.mjs +6 -9
  11. package/engine/content-index.mjs +8 -10
  12. package/engine/content-links.mjs +9 -17
  13. package/engine/content-tables.mjs +1 -1
  14. package/engine/folder-notes.mjs +2 -1
  15. package/engine/foundry-entries.mjs +4 -8
  16. package/engine/frontmatter-lint.mjs +2 -2
  17. package/engine/frontmatter.mjs +2 -2
  18. package/engine/homepage.mjs +3 -3
  19. package/engine/item-compiler.mjs +7 -2
  20. package/engine/map-places.mjs +3 -1
  21. package/engine/metadata-index.mjs +23 -6
  22. package/engine/note-addresses.mjs +27 -9
  23. package/engine/note-ids.mjs +7 -2
  24. package/engine/note-vocabulary.mjs +24 -0
  25. package/engine/pack-router.mjs +7 -3
  26. package/engine/place-relations.mjs +4 -4
  27. package/engine/reference-targets.mjs +7 -7
  28. package/engine/shortcode-references.mjs +1 -1
  29. package/engine/site-index.mjs +8 -39
  30. package/engine/sql-tables.mjs +1 -1
  31. package/engine/system-block.mjs +19 -0
  32. package/engine/systems.mjs +4 -1
  33. package/engine/web-wikilinks.mjs +3 -3
  34. package/engine/wikilinks.mjs +31 -106
  35. package/hm3/actors.mjs +7 -2
  36. package/package.json +2 -3
  37. package/sohl/infobox.mjs +2 -2
  38. package/types/engine/address.d.mts +21 -88
  39. package/types/engine/content-address.d.mts +3 -6
  40. package/types/engine/content-links.d.mts +2 -6
  41. package/types/engine/frontmatter.d.mts +1 -1
  42. package/types/engine/homepage.d.mts +2 -2
  43. package/types/engine/note-addresses.d.mts +2 -0
  44. package/types/engine/system-block.d.mts +11 -0
  45. package/types/engine/systems.d.mts +2 -0
  46. package/bin/heroiclands-content-language-server.mjs +0 -8
  47. package/docs/content-language-server.md +0 -25
  48. package/engine/content-language-server.mjs +0 -439
  49. package/types/engine/content-language-server.d.mts +0 -64
package/CHANGELOG.md CHANGED
@@ -1,5 +1,24 @@
1
1
  # @heroiclands/package-build
2
2
 
3
+ ## 22.13.1
4
+
5
+ ### Patch Changes
6
+
7
+ Content authors can keep pinned document identities and compendium placement alongside shared note facts.
8
+
9
+ ## 22.13.0
10
+
11
+ ### Minor Changes
12
+
13
+ **Content links**
14
+
15
+ - Ordinary wikilinks open readable notes by default; image embeds and art fields resolve systemless assets.
16
+ - Readable notes use `note` addresses, while explicit game systems select Actors and Items and `none` selects systemless targets.
17
+
18
+ **Editor navigation**
19
+
20
+ - Install `@heroiclands/content-language-server` for content note search and navigation in an editor.
21
+
3
22
  ## 22.12.3
4
23
 
5
24
  ### 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
@@ -119,28 +119,29 @@ Deterministic document ids, derived by hashing rather than stored, so compile pa
119
119
 
120
120
  The per-system frontmatter block: how one note feeds more than one game system through properties named after that system (`<system>.system`, `<system>.type`, `<system>.img`, `<system>.items` on actors). Resolves a field's value through the block, the shared top level, and a retiring position in that order, and merges an authored `<system>.system` onto a compiler-built one without disturbing what the builder already wrote.
121
121
 
122
- | Export | Signature | Returns | Use it when |
123
- | --------------------------- | --------------------------------------------- | -------------------------------------- | --------------------------------------------------------------------------------------------- |
124
- | `SYSTEM_DATA_KEY` | `const SYSTEM_DATA_KEY` | — | naming the key inside a system block whose contents are the system's own vocabulary |
125
- | `BLOCK_DOCUMENT_PROPERTIES` | `const BLOCK_DOCUMENT_PROPERTIES` | — | mapping a system-block key to the document property it becomes |
126
- | `BLOCK_DIRECTIVES` | `const BLOCK_DIRECTIVES` | — | recognizing a build directive (e.g. `pack`) that names no document property |
127
- | `SYSTEM_BLOCK_KEYS` | `const SYSTEM_BLOCK_KEYS` | — | enumerating every key any system block may carry |
128
- | `setPath` | `setPath(target, dotted, value)` | `object` — `target`, for chaining | writing a value at a dotted path in a document's `system` block |
129
- | `systemBlock` | `systemBlock(fm, block)` | `Record<string, unknown> \| undefined` | reading one system's whole block off a note, or nothing when it is absent or malformed |
130
- | `carriesSystemBlock` | `carriesSystemBlock(fm, block)` | `boolean` | deciding whether a pack that declares a system should compile a note at all |
131
- | `systemData` | `systemData(fm, block)` | `Record<string, unknown>` | reading a system block's `system` sub-block, `{}` when absent |
132
- | `blockField` | `blockField(fm, block, key, defaultValue)` | the value | reading a key from one system's block, falling back to the top level |
133
- | `sharedProperty` | `sharedProperty(fm, source, defaultValue)` | the value | reading a shared top-level property by a possibly-dotted path, blind to every system block |
134
- | `blockProperty` | `blockProperty(fm, block, key, defaultValue)` | the value | reading a property a system block may override, else the shared top-level one |
135
- | `legacyKeyOf` | `legacyKeyOf(field)` | `string \| undefined` | finding the key a field is authored at inside a system block |
136
- | `retiredTopLevelKey` | `retiredTopLevelKey(field)` | `string \| undefined` | finding the bare top-level key a `data:`-sourced field is being swept off |
137
- | `authoredKey` | `authoredKey(name)` | `string` | reading a shared source as the key an author writes, without the `data:` container holding it |
138
- | `resolveFieldValue` | `resolveFieldValue(field, fm, ...)` | `{value, from}` | resolving one declared field against a note, in the declared source order |
139
- | `systemDataPaths` | `systemDataPaths(data, prefix)` | `string[]` | listing every path a note authors under `<system>.system`, containers included |
140
- | `undeclaredPaths` | `undeclaredPaths(data, declared, prefix)` | `string[]` | finding authored paths a system's published schema does not declare |
141
- | `unknownBlockKeys` | `unknownBlockKeys(fm, block, ...)` | `string[]` | finding keys directly under a system block that neither this format nor the system recognizes |
142
- | `claimedPaths` | `claimedPaths(fields)` | `Set<string>` | listing the exact `system` paths a field declaration writes, for `mergeSystemData` |
143
- | `mergeSystemData` | `mergeSystemData(built, fm, ...)` | `object` — `built`, for chaining | merging a note's `<system>.system` verbatim onto a built `system` block |
122
+ | Export | Signature | Returns | Use it when |
123
+ | --------------------------- | ------------------------------------------------- | -------------------------------------- | --------------------------------------------------------------------------------------------- |
124
+ | `SYSTEM_DATA_KEY` | `const SYSTEM_DATA_KEY` | — | naming the key inside a system block whose contents are the system's own vocabulary |
125
+ | `BLOCK_DOCUMENT_PROPERTIES` | `const BLOCK_DOCUMENT_PROPERTIES` | — | mapping a system-block key to the document property it becomes |
126
+ | `BLOCK_DIRECTIVES` | `const BLOCK_DIRECTIVES` | — | recognizing a build directive (e.g. `pack`) that names no document property |
127
+ | `SYSTEM_BLOCK_KEYS` | `const SYSTEM_BLOCK_KEYS` | — | enumerating every key any system block may carry |
128
+ | `setPath` | `setPath(target, dotted, value)` | `object` — `target`, for chaining | writing a value at a dotted path in a document's `system` block |
129
+ | `systemBlock` | `systemBlock(fm, block)` | `Record<string, unknown> \| undefined` | reading one system's whole block off a note, or nothing when it is absent or malformed |
130
+ | `carriesSystemBlock` | `carriesSystemBlock(fm, block)` | `boolean` | deciding whether a pack that declares a system should compile a note at all |
131
+ | `systemData` | `systemData(fm, block)` | `Record<string, unknown>` | reading a system block's `system` sub-block, `{}` when absent |
132
+ | `blockField` | `blockField(fm, block, key, defaultValue)` | the value | reading a key from one system's block, falling back to the top level |
133
+ | `sharedProperty` | `sharedProperty(fm, source, defaultValue)` | the value | reading a shared top-level property by a possibly-dotted path, blind to every system block |
134
+ | `blockProperty` | `blockProperty(fm, block, key, defaultValue)` | the value | reading a property a system block may override, else the shared top-level one |
135
+ | `blockDataProperty` | `blockDataProperty(fm, block, key, defaultValue)` | the value | reading a system override, then shared `data`, then a top-level value |
136
+ | `legacyKeyOf` | `legacyKeyOf(field)` | `string \| undefined` | finding the key a field is authored at inside a system block |
137
+ | `retiredTopLevelKey` | `retiredTopLevelKey(field)` | `string \| undefined` | finding the bare top-level key a `data:`-sourced field is being swept off |
138
+ | `authoredKey` | `authoredKey(name)` | `string` | reading a shared source as the key an author writes, without the `data:` container holding it |
139
+ | `resolveFieldValue` | `resolveFieldValue(field, fm, ...)` | `{value, from}` | resolving one declared field against a note, in the declared source order |
140
+ | `systemDataPaths` | `systemDataPaths(data, prefix)` | `string[]` | listing every path a note authors under `<system>.system`, containers included |
141
+ | `undeclaredPaths` | `undeclaredPaths(data, declared, prefix)` | `string[]` | finding authored paths a system's published schema does not declare |
142
+ | `unknownBlockKeys` | `unknownBlockKeys(fm, block, ...)` | `string[]` | finding keys directly under a system block that neither this format nor the system recognizes |
143
+ | `claimedPaths` | `claimedPaths(fields)` | `Set<string>` | listing the exact `system` paths a field declaration writes, for `mergeSystemData` |
144
+ | `mergeSystemData` | `mergeSystemData(built, fm, ...)` | `object` — `built`, for chaining | merging a note's `<system>.system` verbatim onto a built `system` block |
144
145
 
145
146
  ### `engine.codeFences`
146
147
 
@@ -596,16 +597,17 @@ The map from each place, drawn for the site: `content-build site` draws the map
596
597
 
597
598
  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
599
 
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 |
600
+ | Export | Signature | Returns | Use it when |
601
+ | ---------------------- | -------------------------------------- | ------------------------------- | -------------------------------------------------------------------------------------- |
602
+ | `NO_SYSTEM` | `const NO_SYSTEM` | — | naming a systemless asset or Foundry document |
603
+ | `NOTE_SYSTEM` | `const NOTE_SYSTEM` | — | naming readable note content across web, book and Foundry |
604
+ | `SYSTEM_IDS` | `const SYSTEM_IDS` | — | enumerating every game system this toolchain compiles for |
605
+ | `SYSTEM_SEGMENTS` | `const SYSTEM_SEGMENTS` | — | enumerating the game systems, `note` and `none` |
606
+ | `isSystemId` | `isSystemId(value)` | `boolean` | checking whether a value names a game system this toolchain knows (`none` is rejected) |
607
+ | `isSystemSegment` | `isSystemSegment(value)` | `boolean` | checking whether a value is something the `<system>` address segment may hold |
608
+ | `unknownSystemMessage` | `unknownSystemMessage(value, where)` | `string` | building the message for a caller that wrote an unknown system |
609
+ | `assertSystemSegment` | `assertSystemSegment(value, where)` | `string` — the value, unchanged | refusing a value the `<system>` segment may not hold, validating inline |
610
+ | `assertSystemCharset` | `assertSystemCharset(segments, where)` | throws | refusing a registry declaration whose id could not be an address segment |
609
611
 
610
612
  ### `engine.calendars`
611
613
 
@@ -675,7 +677,7 @@ Where a content note publishes on the web. One rule, in one place, because two b
675
677
  | `addressSlug` | `function addressSlug(fm)` | {string} The address segment, e.g. | The single path segment a note is addressed by: `type-shortcode`. |
676
678
  | `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
679
  | `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. |
680
+ | `blockSystem` | `function blockSystem(keyPath)` | {string} The game system id, or `note`. | Which system a frontmatter key path is written under. |
679
681
  | `expandAddress` | `function expandAddress(read, where)` | {string} The canonical `package-system-type-shortcode`. | Expand a written address to the one canonical address it names. |
680
682
  | `CANONICAL_KEY_SEGMENTS` | `const CANONICAL_KEY_SEGMENTS` | — | How many segments a canonical key has, and therefore how many the reader below counts. |
681
683
  | `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 +1064,12 @@ Shared helpers for the pack compilers in `packages/content-build/`. The HeroicLa
1062
1064
 
1063
1065
  **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
1066
 
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. |
1067
+ | Export | Signature | Returns | Use it when |
1068
+ | ---------------- | --------------------------------------------------------------- | ------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------- |
1069
+ | `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. |
1070
+ | `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. |
1071
+ | `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. |
1072
+ | `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
1073
 
1072
1074
  - `itemTypes` — re-exported: Every content type that compiles into an item — and therefore into an item doc.
1073
1075
 
@@ -1077,7 +1079,7 @@ Wikilink resolution for the pack compilers. Content notes link to one another wi
1077
1079
 
1078
1080
  | Export | Signature | Returns | Use it when |
1079
1081
  | -------------------- | ----------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
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. |
1082
+ | `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
1083
  | `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
1084
  | `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
1085
  | `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.
@@ -308,6 +308,12 @@ finding that names the key you meant; the same misspelling at top level would
308
308
  silently become a theme parameter, which is exactly why these cannot live
309
309
  together.
310
310
 
311
+ Every note type may put a pinned document id in `data.id`, a shared compendium
312
+ route in `data.pack`, a shared folder Address in `data.packFolder`, and source
313
+ details in `data.harnworld`. A system block's `pack` or `packFolder` overrides
314
+ the shared value for that system. A `being` may also describe its social profile
315
+ in `data.social`.
316
+
311
317
  **Tags are open, except the ones that classify.** `tags:` shares the top level's
312
318
  openness: a tag naming a theme or a region — `underworld`,
313
319
  `byzaria`, `riverlands` — is the author's own and this build has no opinion about it.
@@ -569,8 +575,8 @@ type.
569
575
  | ----------------------- | ------------------------- | ---------------------------- |
570
576
  | `name.full` | `name` | `name` |
571
577
  | `data.icon` | `img` | `img` |
572
- | `id` | `_id` | `_id` |
573
- | `packFolder` | `folder` | `folder` |
578
+ | `data.id` | `_id` | `_id` |
579
+ | `data.packFolder` | `folder` | `folder` |
574
580
  | `shortcode` | `system.shortcode` | NA |
575
581
  | `data.templatePriority` | `system.templatePriority` | `flags.hm3.templatePriority` |
576
582
  | `actionDefs` | `system.actionDefs` | NA |
@@ -657,16 +663,20 @@ refused at all four slots: a sound is not art, and a value naming one — `icon:
657
663
  audio-thunder`, say — is an error naming the set the slot accepts, never a
658
664
  silent fall back to the note's default art.
659
665
 
660
- **Two of them are legal on _every_ note**, whatever its type. `data:` is a
666
+ **These properties are legal on _every_ note**, whatever its type. `data:` is a
661
667
  closed container and the per-type vocabularies below are the only lists there
662
668
  are, so a key legal everywhere is stated once here rather than repeated in
663
669
  twenty-five tables where the one that was mistyped would be the one nobody
664
670
  noticed:
665
671
 
666
- | shared `data` property | Values | Description |
667
- | ---------------------- | --------- | -------------------------------------------------------------------- |
668
- | `icon` | `Address` | The document's profile art — an `icon` address, resolved into `img`. |
669
- | `banner` | `Address` | The page's hero image — an `image` address. |
672
+ | shared `data` property | Values | Description |
673
+ | ---------------------- | ---------------------- | -------------------------------------------------------------------- |
674
+ | `id` | `string` | A pinned document identity. |
675
+ | `pack` | `string` | The shared compendium route. |
676
+ | `packFolder` | `Address` or pack map | The shared compendium folder Address. |
677
+ | `harnworld` | `Map<string, unknown>` | HârnWorld source details. |
678
+ | `icon` | `Address` | The document's profile art — an `icon` address, resolved into `img`. |
679
+ | `banner` | `Address` | The page's hero image — an `image` address. |
670
680
 
671
681
  `icon` is legal everywhere because most types compile into a document that
672
682
  carries one; where a type's passes emit none, the lint says so and the value is
@@ -847,10 +857,11 @@ on any type, and the frontmatter lint never reports it.
847
857
 
848
858
  #### The pack a note compiles into
849
859
 
850
- `pack` names which configured compendium receives the note's document.
860
+ `data.pack` names which configured compendium receives the note's document.
851
861
 
852
862
  ```yaml
853
- pack: items-hm3
863
+ data:
864
+ pack: items-hm3
854
865
  ```
855
866
 
856
867
  It is deliberately close to the retired `package:` and deliberately not the same
@@ -867,7 +878,8 @@ system: each block the note carries sends its own document where that block
867
878
  says, and a note carrying two blocks may place them in two packs.
868
879
 
869
880
  ```yaml
870
- pack: items-sohl
881
+ data:
882
+ pack: items-sohl
871
883
  hm3:
872
884
  pack: items-hm3
873
885
  ```
@@ -898,10 +910,11 @@ and in no compendium — a page of developer documentation, a page about the
898
910
  package itself:
899
911
 
900
912
  ```yaml
913
+ shortcode: architecture
901
914
  type: doc
902
915
  subType: concept
903
- shortcode: architecture
904
- pack: none
916
+ data:
917
+ pack: none
905
918
  ```
906
919
 
907
920
  A link to such a note resolves everywhere the note is addressable. On the web
@@ -1046,16 +1059,17 @@ none. It is the same principle that turned `folder: ONXsqZAIZr2qzxTb` into
1046
1059
  `packFolder: <address>` above: an opaque derived identity does not belong in
1047
1060
  authored content.
1048
1061
 
1049
- **An authored `id` still wins**, and that is how a document's identity is
1062
+ **An authored `data.id` wins**, and that is how a document's identity is
1050
1063
  **pinned**:
1051
1064
 
1052
1065
  ```yaml
1053
- type: miscgear
1054
1066
  shortcode: bowlcer
1055
- id: plaiQQm2T5zVK5mO # pinned: this document keeps this id
1067
+ type: miscgear
1068
+ data:
1069
+ id: plaiQQm2T5zVK5mO # pinned: this document keeps this id
1056
1070
  ```
1057
1071
 
1058
- A blank `id:` is not a pin — it is a deleted value with the key left behind, and
1072
+ A blank `data.id:` is not a pin — it is a deleted value with the key left behind, and
1059
1073
  is treated as absent.
1060
1074
 
1061
1075
  **A rename moves the id, and that is the trade.** The address carries the
@@ -1144,7 +1158,8 @@ of the same entry is a warning; the declaration still works.
1144
1158
  A note says which folder of its pack it lands in:
1145
1159
 
1146
1160
  ```yaml
1147
- packFolder: poisonsandtoxins # a folder note's address
1161
+ data:
1162
+ packFolder: poisonsandtoxins # a folder note's address
1148
1163
  ```
1149
1164
 
1150
1165
  **`packFolder` is a folder note's address** — an ordinary address, resolved the
@@ -1268,85 +1283,78 @@ below.
1268
1283
  <package>-<system>-<note_type>-<shortcode>
1269
1284
  ```
1270
1285
 
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.
1286
+ The final segment is the shortcode, preceded by the note type, the system and
1287
+ the package. Each segment has one position. The system says which kind of target
1288
+ is named:
1275
1289
 
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.
1290
+ | system | target |
1291
+ | ------------- | --------------------------------------------------------------------------- |
1292
+ | `note` | Readable note content: a web page, a book entry, or a Foundry JournalEntry. |
1293
+ | `none` | A systemless target such as an asset, Folder, Macro or Scene. |
1294
+ | `sohl`, `hm3` | A game system's Actor or Item document. |
1295
+
1296
+ A note can publish more than one Address. A `being` note, for example, publishes
1297
+ `thalorna-note-being-bctrncml` for its readable content and
1298
+ `thalorna-sohl-being-bctrncml` for its SoHL Actor. An item note follows the same
1299
+ pattern for its page and Item. The type segment remains the authored note type
1300
+ in each Address.
1279
1301
 
1280
1302
  #### Shorter forms
1281
1303
 
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:
1304
+ Authors may omit segments from the left. The complete Address states all four:
1285
1305
 
1286
1306
  | form | expands to |
1287
1307
  | ------------------------------- | -------------------------------------------------------- |
1288
1308
  | `package-system-type-shortcode` | itself |
1289
1309
  | `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 |
1310
+ | `type-shortcode` | `<this package>-<this position's system>-type-shortcode` |
1311
+ | `shortcode` | as above, with the type from the field's declaration |
1292
1312
 
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.
1313
+ There is no `package-type-shortcode` form. A reference to another package states
1314
+ its system as well as its package.
1297
1315
 
1298
1316
  #### Authored references and generated references
1299
1317
 
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]`.
1318
+ Authored notes may use any permitted suffix. Builds leave source bytes unchanged.
1319
+ Generated content indexes, SQL records, site frontmatter and generated
1320
+ Wikilinks contain complete Addresses. For example, `data.parents: [north]` in
1321
+ `thalorna` emits `data.parents: [thalorna-note-place-north]`.
1305
1322
 
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.
1323
+ SQL queries compare Address-valued fields to complete Addresses. A note's
1324
+ `shortcode` is a distinct identity component, so it is not equal to an Address
1325
+ stored in `data.parents`, `data.domains` or a governance rank's `lore`. Use the
1326
+ target's complete identity for that comparison. A stub has no published page
1327
+ address; its note identity still contains the package, system, type and shortcode.
1312
1328
 
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.
1329
+ The toolchain holds parsed Addresses as complete tuples internally. Address-keyed
1330
+ relation maps hold typed targets and standing values. Only generated output
1331
+ serializes these properties to Address strings. Runtime Shortcodes, Foundry
1332
+ UUIDs, file paths and URLs keep their own representations.
1317
1333
 
1318
1334
  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.
1335
+ dependency index. `content-build lint` reports a missing target at the value or
1336
+ map key that names it. A stub counts as a target for data even though it
1337
+ publishes no page. Art slots use their own missing-art warning and document
1338
+ fallback.
1323
1339
 
1324
1340
  #### An omitted segment defaults from where the link is written
1325
1341
 
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.
1342
+ An omitted segment is a default, not a wildcard. Every short form expands to one
1343
+ complete Address before lookup.
1344
+
1345
+ - **Package:** the current package. A short address never falls through to a
1346
+ dependency.
1347
+ - **System in `[[link|text]]`:** `note`, for every note type. Use an explicit
1348
+ game system to name its Actor or Item, or `none` to name a systemless target.
1349
+ - **System in `![[link|text]]`:** `none`. An embed names an image or icon asset;
1350
+ it also supplies `image` as the type of a bare shortcode.
1351
+ - **System in frontmatter:** a system block such as `sohl:` or `hm3:` supplies
1352
+ its game system. Other Address fields default to `note`, except fields
1353
+ declared for images, icons or folders, which default to `none`.
1354
+
1355
+ For example, `[[macro-autoattack|Automated Attack]]` names the readable macro
1356
+ note, while `[[none-macro-autoattack|Automated Attack]]` names the Foundry Macro.
1357
+ `![[icon-anvil|Anvil]]` names the systemless icon asset.
1350
1358
 
1351
1359
  **A complete Address is lowercase throughout.** Every segment — package,
1352
1360
  system, type and shortcode alike — contains only lowercase ASCII letters and
@@ -1572,7 +1580,7 @@ A **`Wikilink`** is how body prose names something:
1572
1580
  The brackets, the `#<anchor>` and the `|<text>` label are the Wikilink's own.
1573
1581
  What sits inside them is an `Address` like any other — read by the same grammar,
1574
1582
  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
1583
+ link is written, which for body prose is this package and system `note`. So
1576
1584
  nothing about resolution belongs to the bracketed form: a wikilink and a
1577
1585
  frontmatter field ask the same question of the same value, and the findings above
1578
1586
  are the answers either of them gets.
@@ -2054,10 +2062,10 @@ system a note might write. A package shipping for a second system declares
2054
2062
  `system:` on a pack of each class it compiles.
2055
2063
 
2056
2064
  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
2065
+ a system-bearing note with readable content, giving a prose link
2058
2066
  naming it nowhere to land. It carries a documentation journal like every other
2059
2067
  such note, addressed
2060
- `<package>-none-docbeing-<shortcode>` beside the Actor's
2068
+ `<package>-note-being-<shortcode>` beside the Actor's
2061
2069
  `<package>-<system>-being-<shortcode>`.
2062
2070
 
2063
2071
  **A being keeps its prose inline as well.** `system.appearance` and
@@ -2488,6 +2496,7 @@ Generates a living (or undead, or spirit) being.
2488
2496
  | `templatePriority` | `number` | Template priority, _null_ = not a template |
2489
2497
  | `archetypes` | `Archetype[]` | What sort of character this is. **Always an array** — `[]` where none apply; `null` is an error. |
2490
2498
  | `occupation` | `string` | Name of the character's occupation |
2499
+ | `social` | `Map<string, unknown>` | The being's social profile |
2491
2500
  | `stations` | `Address[]` | Name of the stations the character belongs to |
2492
2501
  | `lore` | `Address[]` | Lore concerning this being — the people it is of, the standing it holds, the law it lives under |
2493
2502
  | `homes` | `Address[]` | Place the being calls home |
@@ -2750,7 +2759,8 @@ no Foundry document: it is a page and nothing else.
2750
2759
  It declares a `shortcode` — conventionally `root` — because that is what a
2751
2760
  link is written with: `[[homepage-root|Text]]` is an ordinary wikilink, and it
2752
2761
  resolves to `/<package>/`. The shortcode names the page in links; its address
2753
- is the package root. `title` defaults to `packageBuild.manifest.title`; a
2762
+ is the package root. The page heading reads `name.full` and defaults to
2763
+ `packageBuild.manifest.title`; a
2754
2764
  `description` and a `banner` are read by the theme's hero; any other
2755
2765
  top-level key is passed through to the page as a theme parameter.
2756
2766
 
@@ -4065,7 +4075,7 @@ await CONFIG.SOHL.class.Utility.currentCombatantAttack();
4065
4075
 
4066
4076
  Three rules follow, and each is deliberate. The anchor names the **page**, not
4067
4077
  the heading text, so the heading may be worded freely and
4068
- `[[docmacro-autoattack#script]]` still opens exactly this page. The fence must
4078
+ `[[macro-autoattack#script]]` still opens exactly this page. The fence must
4069
4079
  be **tagged** — an untagged fence is a code sample whose language nobody stated,
4070
4080
  and treating it as executable would make an illustrative snippet the macro. And
4071
4081
  only the **first** tagged fence counts, so a note may document its macro
@@ -4141,8 +4151,8 @@ persists in the pack.
4141
4151
  **Each address names the note's own document.** That is the same rule `pack:`
4142
4152
  follows, so there is one answer and not two. A note that compiles into _two_
4143
4153
  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.
4154
+ bundle when the bundle names its `note` Address:
4155
+ `sohl-miscgear-bowlcer` is the Item, `miscgear-bowlcer` its description page.
4146
4156
 
4147
4157
  **An address that resolves to nothing fails the build.** A `folder` address is
4148
4158
  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.