@heroiclands/package-build 22.13.0 → 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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
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
+
3
9
  ## 22.13.0
4
10
 
5
11
  ### Minor Changes
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
 
@@ -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
@@ -2481,6 +2496,7 @@ Generates a living (or undead, or spirit) being.
2481
2496
  | `templatePriority` | `number` | Template priority, _null_ = not a template |
2482
2497
  | `archetypes` | `Archetype[]` | What sort of character this is. **Always an array** — `[]` where none apply; `null` is an error. |
2483
2498
  | `occupation` | `string` | Name of the character's occupation |
2499
+ | `social` | `Map<string, unknown>` | The being's social profile |
2484
2500
  | `stations` | `Address[]` | Name of the stations the character belongs to |
2485
2501
  | `lore` | `Address[]` | Lore concerning this being — the people it is of, the standing it holds, the law it lives under |
2486
2502
  | `homes` | `Address[]` | Place the being calls home |
@@ -2743,7 +2759,8 @@ no Foundry document: it is a page and nothing else.
2743
2759
  It declares a `shortcode` — conventionally `root` — because that is what a
2744
2760
  link is written with: `[[homepage-root|Text]]` is an ordinary wikilink, and it
2745
2761
  resolves to `/<package>/`. The shortcode names the page in links; its address
2746
- 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
2747
2764
  `description` and a `banner` are read by the theme's hero; any other
2748
2765
  top-level key is passed through to the page as a theme parameter.
2749
2766
 
@@ -274,7 +274,8 @@ export function collectFolderNotes(notes, pkg) {
274
274
 
275
275
  const { parent, color } = folderFields(fm);
276
276
  const address = folderAddress(pkg, shortcode);
277
- const authoredId = fm.id == null ? "" : String(fm.id).trim();
277
+ const pinnedId = fm.data?.id ?? fm.id;
278
+ const authoredId = pinnedId == null ? "" : String(pinnedId).trim();
278
279
 
279
280
  folders.push({
280
281
  shortcode,
@@ -348,7 +348,7 @@ export function parseValueDesc(raw) {
348
348
  /**
349
349
  * The compendium folder a note names.
350
350
  *
351
- * **There is one spelling.** `packFolder:` is a folder note's **address**
351
+ * `data.packFolder` is a folder note's **address**
352
352
  * (`folder-poisonsandtoxins`), resolved through the address index the whole
353
353
  * build shares. The `folder:` Foundry-id spelling this function once read
354
354
  * beside it, and the per-pack `*-folders.yaml` that id was resolved against,
@@ -372,7 +372,7 @@ export function parseValueDesc(raw) {
372
372
  * two spellings, and there is only one left to be.
373
373
  */
374
374
  export function folderField(fm) {
375
- const asAddress = sohlField(fm, "packFolder", null);
375
+ const asAddress = fm?.sohl?.packFolder ?? fm?.data?.packFolder ?? fm?.packFolder ?? null;
376
376
  if (asAddress != null && asAddress !== "") return { value: asAddress, isAddress: true };
377
377
  return { value: null, isAddress: true };
378
378
  }
@@ -340,8 +340,8 @@ function positionOfType(file) {
340
340
  * The one defaulted value on the page, and it defaults to the package's own
341
341
  * `packageBuild.manifest.title` — the name Foundry already shows for the
342
342
  * package — so a homepage that adds nothing to it need not restate it. An
343
- * authored `title` wins, because a front page is allowed to greet a reader
344
- * differently from a package browser.
343
+ * authored `name.full` wins, because a front page may greet a reader
344
+ * differently from a package browser. A top-level `title` is also readable.
345
345
  *
346
346
  * Falls back to `contentPackage` last, so a package that has no manifest of its
347
347
  * own still yields a titled page rather than a blank heading.
@@ -351,7 +351,7 @@ function positionOfType(file) {
351
351
  * @returns {string} The title.
352
352
  */
353
353
  export function homepageTitle(fm, config) {
354
- const authored = fm?.title;
354
+ const authored = fm?.name?.full ?? fm?.title;
355
355
  if (typeof authored === "string" && authored.trim()) return authored;
356
356
  const manifest = /** @type {Record<string, unknown>|undefined} */ (
357
357
  config?.packageBuild?.manifest
@@ -64,7 +64,12 @@ import { documentSubtype, subtypeRow } from "./document-subtypes.mjs";
64
64
  // The note-level `<system>:` block: `<system>.system` onto the document's
65
65
  // `system` verbatim, and `<system>.img` / `.effects` / `.flags` overriding
66
66
  // their shared top-level forms for this system alone.
67
- import { blockField, blockProperty, claimedPaths, mergeSystemData } from "./system-block.mjs";
67
+ import {
68
+ blockDataProperty,
69
+ blockProperty,
70
+ claimedPaths,
71
+ mergeSystemData,
72
+ } from "./system-block.mjs";
68
73
  // The other direction of the same declaration: a field the *document* writes in
69
74
  // play, which a note may not author and the builder does not emit.
70
75
  import { assertNoDerivedFields } from "./derived-fields.mjs";
@@ -379,7 +384,7 @@ export class SystemItemCompiler extends BasePackCompiler {
379
384
  // one spelling: `packFolder` names a folder note by its address, the
380
385
  // `folder:` id spelling having been retired with the per-pack YAML it
381
386
  // resolved against.
382
- const folder = this.folderResolver(blockField(fm, system, "packFolder", null), {
387
+ const folder = this.folderResolver(blockDataProperty(fm, system, "packFolder", null), {
383
388
  isAddress: true,
384
389
  });
385
390
 
@@ -207,7 +207,9 @@ export function placesFromRecords(records, { contentBase, base }) {
207
207
  folder: record.file?.folder ?? "",
208
208
  package: String(record.package ?? ""),
209
209
  local: true,
210
- hasPackFolder: Object.hasOwn(record, "packFolder"),
210
+ hasPackFolder:
211
+ Object.hasOwn(record.data ?? {}, "packFolder") ||
212
+ Object.hasOwn(record, "packFolder"),
211
213
  });
212
214
  } else if (record.type === "affiliation" && record.subType === "polity") {
213
215
  polities.push({
@@ -105,7 +105,7 @@ function text(value) {
105
105
  */
106
106
  export function noteDocId(fm, { pkg, maps = KNOWN_DOCUMENT_SUBTYPE_MAPS } = {}) {
107
107
  if (!fm || typeof fm !== "object") return undefined;
108
- const pinned = text(fm.id);
108
+ const pinned = text(fm.data?.id) ?? text(fm.id);
109
109
  if (pinned) return pinned;
110
110
  const type = text(fm.type);
111
111
  const shortcode = text(fm.shortcode);
@@ -277,6 +277,29 @@ const TOKEN_ICON = Object.freeze({
277
277
  * @type {readonly DataFieldSpec[]}
278
278
  */
279
279
  export const SHARED_DATA_FIELDS = Object.freeze([
280
+ Object.freeze({
281
+ name: "id",
282
+ ...TEXT,
283
+ describe: "A fixed document identity when the derived identity is unsuitable.",
284
+ }),
285
+ Object.freeze({
286
+ name: "pack",
287
+ ...TEXT,
288
+ describe: "The shared compendium route, with a system block overriding it.",
289
+ }),
290
+ Object.freeze({
291
+ name: "packFolder",
292
+ ...LINK_BY_PACK,
293
+ ref: "folder",
294
+ accepts: ["folder"],
295
+ describe: "The shared compendium folder Address, with a system block overriding it.",
296
+ }),
297
+ Object.freeze({
298
+ name: "harnworld",
299
+ shape: "map",
300
+ kind: "map",
301
+ describe: "HârnWorld source details shared by every system.",
302
+ }),
280
303
  Object.freeze({
281
304
  name: "icon",
282
305
  ...LINK,
@@ -605,6 +628,7 @@ export const NOTE_VOCABULARY = Object.freeze({
605
628
  // package's declared present compute — see `engine/being-age.mjs`.
606
629
  check: checkBeingAge,
607
630
  data: Object.freeze([
631
+ { name: "social", shape: "map", kind: "map", describe: "The being's social profile." },
608
632
  TOKEN_ICON,
609
633
  TEMPLATE_PRIORITY,
610
634
  { name: "archetypes", ...LIST, describe: "Archetypal behaviours the being fits." },
@@ -82,7 +82,7 @@
82
82
 
83
83
  import { NO_PACK, packForType } from "./ids.mjs";
84
84
  import { loadPackConfig } from "./pack-config.mjs";
85
- import { blockProperty, systemBlock } from "./system-block.mjs";
85
+ import { blockDataProperty, systemBlock } from "./system-block.mjs";
86
86
 
87
87
  /**
88
88
  * A note that cannot be routed to a pack. Thrown rather than returned so no
@@ -131,7 +131,9 @@ export { NO_PACK };
131
131
  */
132
132
  export function declaresNoPack(fm, system) {
133
133
  const declared =
134
- system === undefined ? fm?.[PACK_FIELD] : blockProperty(fm, system, PACK_FIELD);
134
+ system === undefined ?
135
+ (fm?.data?.[PACK_FIELD] ?? fm?.[PACK_FIELD])
136
+ : blockDataProperty(fm, system, PACK_FIELD);
135
137
  return declared === NO_PACK;
136
138
  }
137
139
 
@@ -270,7 +272,9 @@ export function createPackRouter(packs) {
270
272
  const inBlock = system === undefined ? undefined : systemBlock(fm, system)?.[PACK_FIELD];
271
273
  const authoredInBlock = inBlock != null && inBlock !== "";
272
274
  let declared =
273
- system === undefined ? fm?.[PACK_FIELD] : blockProperty(fm, system, PACK_FIELD);
275
+ system === undefined ?
276
+ (fm?.data?.[PACK_FIELD] ?? fm?.[PACK_FIELD])
277
+ : blockDataProperty(fm, system, PACK_FIELD);
274
278
  // The declaration names where the note's *own* document goes. A pass
275
279
  // writing a document derived from it — an item's prose becoming a
276
280
  // JournalEntry — is not what the author was addressing.
@@ -361,6 +361,25 @@ export function blockProperty(fm, block, key, defaultValue = undefined) {
361
361
  return shared === undefined || shared === null ? defaultValue : shared;
362
362
  }
363
363
 
364
+ /**
365
+ * A system override followed by a shared `data:` value. The top-level value
366
+ * remains readable while content repositories move their authored fields.
367
+ *
368
+ * @param {object} fm - The note's frontmatter.
369
+ * @param {string} block - The system block key.
370
+ * @param {string} key - The shared property.
371
+ * @param {any} [defaultValue] - Returned when no position declares it.
372
+ * @returns {any} The selected value.
373
+ */
374
+ export function blockDataProperty(fm, block, key, defaultValue = undefined) {
375
+ const declared = systemBlock(fm, block)?.[key];
376
+ if (declared !== undefined && declared !== null) return declared;
377
+ const shared = isMapping(fm?.data) ? fm.data[key] : undefined;
378
+ if (shared !== undefined && shared !== null) return shared;
379
+ const legacy = isMapping(fm) ? fm[key] : undefined;
380
+ return legacy === undefined || legacy === null ? defaultValue : legacy;
381
+ }
382
+
364
383
  /**
365
384
  * The key a field is authored at **inside** a system block — step 2.
366
385
  *
package/hm3/actors.mjs CHANGED
@@ -63,7 +63,12 @@ import {
63
63
  // The note-level `hm3:` block: `hm3.system` onto the document's `system`
64
64
  // verbatim, and `hm3.img` / `hm3.items` / `hm3.effects` / `hm3.flags`
65
65
  // overriding their shared top-level forms for this system alone.
66
- import { blockField, blockProperty, mergeSystemData } from "../engine/system-block.mjs";
66
+ import {
67
+ blockDataProperty,
68
+ blockField,
69
+ blockProperty,
70
+ mergeSystemData,
71
+ } from "../engine/system-block.mjs";
67
72
 
68
73
  /**
69
74
  * Native HM3 facts shared by its actor subtypes.
@@ -324,7 +329,7 @@ export class Hm3Actors extends SystemActorCompiler {
324
329
  // by its address. This pass once read only the Foundry id, so an HM3
325
330
  // tree could not file an actor by address at all — which its own sweep
326
331
  // needs; the id spelling is retired outright.
327
- const folder = this.folderResolver(blockField(fm, block, "packFolder", null), {
332
+ const folder = this.folderResolver(blockDataProperty(fm, block, "packFolder", null), {
328
333
  isAddress: true,
329
334
  });
330
335
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@heroiclands/package-build",
3
- "version": "22.13.0",
3
+ "version": "22.13.1",
4
4
  "description": "Shared toolchain for building and shipping a HeroicLands Foundry VTT package — content compilation, manifest, localization, staging, bundle, release and deployment.",
5
5
  "license": "GPL-3.0-or-later",
6
6
  "type": "module",
@@ -149,7 +149,7 @@ export function parseValueDesc(raw: any): {
149
149
  /**
150
150
  * The compendium folder a note names.
151
151
  *
152
- * **There is one spelling.** `packFolder:` is a folder note's **address**
152
+ * `data.packFolder` is a folder note's **address**
153
153
  * (`folder-poisonsandtoxins`), resolved through the address index the whole
154
154
  * build shares. The `folder:` Foundry-id spelling this function once read
155
155
  * beside it, and the per-pack `*-folders.yaml` that id was resolved against,
@@ -119,8 +119,8 @@ export function checkHomepageCount(found: ReadonlyArray<{
119
119
  * The one defaulted value on the page, and it defaults to the package's own
120
120
  * `packageBuild.manifest.title` — the name Foundry already shows for the
121
121
  * package — so a homepage that adds nothing to it need not restate it. An
122
- * authored `title` wins, because a front page is allowed to greet a reader
123
- * differently from a package browser.
122
+ * authored `name.full` wins, because a front page may greet a reader
123
+ * differently from a package browser. A top-level `title` is also readable.
124
124
  *
125
125
  * Falls back to `contentPackage` last, so a package that has no manifest of its
126
126
  * own still yields a titled page rather than a blank heading.
@@ -93,6 +93,17 @@ export function sharedProperty(fm: object, source: string, defaultValue?: any):
93
93
  * @returns {any} The value.
94
94
  */
95
95
  export function blockProperty(fm: object, block: string, key: string, defaultValue?: any): any;
96
+ /**
97
+ * A system override followed by a shared `data:` value. The top-level value
98
+ * remains readable while content repositories move their authored fields.
99
+ *
100
+ * @param {object} fm - The note's frontmatter.
101
+ * @param {string} block - The system block key.
102
+ * @param {string} key - The shared property.
103
+ * @param {any} [defaultValue] - Returned when no position declares it.
104
+ * @returns {any} The selected value.
105
+ */
106
+ export function blockDataProperty(fm: object, block: string, key: string, defaultValue?: any): any;
96
107
  /**
97
108
  * The key a field is authored at **inside** a system block — step 2.
98
109
  *