@heroiclands/package-build 22.12.2 → 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.
@@ -24,7 +24,13 @@ export { AddressEntries, encodeAddresses, cloneAddressState } from "./address-va
24
24
 
25
25
  /** Structured positions whose Address is nested inside a declared container. */
26
26
  export const STRUCTURED_ADDRESSES = Object.freeze([
27
- { path: ["packFolder"], shape: "scalar-or-map", type: "folder", accepts: ["folder"] },
27
+ {
28
+ path: ["packFolder"],
29
+ shape: "scalar-or-map",
30
+ type: "folder",
31
+ accepts: ["folder"],
32
+ system: "none",
33
+ },
28
34
  {
29
35
  noteType: "affiliation",
30
36
  path: ["data", "governance", "ranks", "*", "lore"],
@@ -67,6 +73,9 @@ export function addressPositions(fm, context = {}) {
67
73
  shape,
68
74
  type: field.ref,
69
75
  accepts: field.accepts,
76
+ ...(ASSET_TYPE_NAMES.has(field.ref) || field.ref === "folder" ?
77
+ { system: "none" }
78
+ : {}),
70
79
  });
71
80
  }
72
81
  out.push(...STRUCTURED_ADDRESSES.filter((p) => !p.noteType || p.noteType === fm.type));
@@ -79,7 +88,7 @@ export function addressPositions(fm, context = {}) {
79
88
  shape: "scalar-or-map",
80
89
  type: "folder",
81
90
  accepts: ["folder"],
82
- system,
91
+ system: "none",
83
92
  });
84
93
  out.push({
85
94
  path: [system, "items", "*", "model"],
@@ -93,7 +102,7 @@ export function addressPositions(fm, context = {}) {
93
102
  shape: "value",
94
103
  type: slot.type,
95
104
  accepts: slot.accepts,
96
- system,
105
+ system: "none",
97
106
  });
98
107
  const spec = context.systemBlocks?.[system];
99
108
  for (const [index, entry] of (Array.isArray(fm[system]?.items) ?
@@ -121,7 +130,13 @@ export function addressPositions(fm, context = {}) {
121
130
  field.address.holds === "items" ? "list" : (field.address.holds ?? "value"),
122
131
  type: field.address.type,
123
132
  accepts: field.address.accepts,
124
- system,
133
+ system:
134
+ (
135
+ ASSET_TYPE_NAMES.has(field.address.type) ||
136
+ field.address.type === "folder"
137
+ ) ?
138
+ "none"
139
+ : system,
125
140
  });
126
141
  }
127
142
  }
@@ -135,7 +150,10 @@ export function addressPositions(fm, context = {}) {
135
150
  shape: field.address.holds === "items" ? "list" : (field.address.holds ?? "value"),
136
151
  type: field.address.type,
137
152
  accepts: field.address.accepts,
138
- system,
153
+ system:
154
+ ASSET_TYPE_NAMES.has(field.address.type) || field.address.type === "folder" ?
155
+ "none"
156
+ : system,
139
157
  };
140
158
  out.push({ ...position, path: [system, "system", ...field.to.split(".")] });
141
159
  out.push({
@@ -143,7 +161,7 @@ export function addressPositions(fm, context = {}) {
143
161
  path: [system, ...(field.legacyKey ?? field.name).split(".")],
144
162
  });
145
163
  if (field.name?.startsWith("data.") && field.topLevelMeans === undefined)
146
- out.push({ ...position, system: "none", path: field.name.slice(5).split(".") });
164
+ out.push({ ...position, path: field.name.slice(5).split(".") });
147
165
  }
148
166
  }
149
167
  for (const position of [...out]) {
@@ -171,7 +189,7 @@ export function decodeNoteAddresses(fm, context) {
171
189
  const defaults = {
172
190
  ...context,
173
191
  types,
174
- system: position.system ?? context.system ?? "none",
192
+ system: position.system ?? context.system ?? "note",
175
193
  type: position.type,
176
194
  };
177
195
  const fail = (value, path, description) => {
@@ -277,7 +295,7 @@ export function noteAddressContext(config) {
277
295
  }
278
296
  return {
279
297
  package: config.contentPackage,
280
- system: "none",
298
+ system: "note",
281
299
  systemBlocks,
282
300
  };
283
301
  }
@@ -313,7 +331,7 @@ export function decodeIndexAddresses(record, context) {
313
331
  written,
314
332
  {
315
333
  ...context,
316
- system: "none",
334
+ system: "note",
317
335
  types: new Set([...Object.keys(NOTE_VOCABULARY), ...ASSET_TYPE_NAMES]),
318
336
  },
319
337
  { declared: true },
@@ -44,6 +44,9 @@ import { documentId } from "./content-address.mjs";
44
44
  import { systemOf } from "./document-subtypes.mjs";
45
45
  import { contentPackage } from "./content-package.mjs";
46
46
  import { KNOWN_DOCUMENT_SUBTYPE_MAPS } from "./subtype-registry.mjs";
47
+ import { JOURNAL_TYPES } from "./ids.mjs";
48
+ import { NOTE_SYSTEM } from "./systems.mjs";
49
+ import { HOMEPAGE_TYPE } from "./homepage.mjs";
47
50
  // The folder id's derivation, taken from the pass that owns it rather than
48
51
  // restated here — see the `folder` branch below. `folder-notes.mjs` reaches
49
52
  // only `content-address`, `address-charset`, `ids` and `retired-fields`, none
@@ -112,7 +115,9 @@ export function noteDocId(fm, { pkg, maps = KNOWN_DOCUMENT_SUBTYPE_MAPS } = {})
112
115
  // the two must answer alike about the same note or the divergence this
113
116
  // branch closes reopens under a capitalised `type: Folder`.
114
117
  if (type.toLowerCase() === FOLDER_TYPE) return folderDocId(owner, shortcode);
115
- return documentId(owner, systemOf(type, maps), type, shortcode);
118
+ const system =
119
+ JOURNAL_TYPES.has(type) || type === HOMEPAGE_TYPE ? NOTE_SYSTEM : systemOf(type, maps);
120
+ return documentId(owner, system, type, shortcode);
116
121
  }
117
122
 
118
123
  /**
@@ -67,7 +67,7 @@ import { encodeAddresses } from "./address-values.mjs";
67
67
 
68
68
  import { positionOfFrontmatterPath } from "./diagnostics.mjs";
69
69
  import { acceptsType, parseAddress, renderAddress } from "./address.mjs";
70
- import { NO_SYSTEM } from "./document-subtypes.mjs";
70
+ import { NOTE_SYSTEM } from "./systems.mjs";
71
71
 
72
72
  /* --------------------------------------------------------------------- */
73
73
  /* The closed sets */
@@ -232,7 +232,7 @@ function addressDefaults(index) {
232
232
  packages: index?.packages,
233
233
  noIndexPackages: index?.noIndexPackages,
234
234
  package: index?.contentPackage,
235
- system: NO_SYSTEM,
235
+ system: NOTE_SYSTEM,
236
236
  type: RELATION_TYPE,
237
237
  };
238
238
  }
@@ -356,7 +356,7 @@ function checkRelation(note, { field, index }) {
356
356
  here.package && self ?
357
357
  renderAddress({
358
358
  package: here.package,
359
- system: NO_SYSTEM,
359
+ system: NOTE_SYSTEM,
360
360
  type: RELATION_TYPE,
361
361
  shortcode: self,
362
362
  })
@@ -425,7 +425,7 @@ function checkRelation(note, { field, index }) {
425
425
  "to",
426
426
  `${label(i, "to")} must name a place — the other place's \`shortcode\`, or ` +
427
427
  `its address as \`${RELATION_TYPE}-<shortcode>\` or ` +
428
- `\`<package>-${NO_SYSTEM}-${RELATION_TYPE}-<shortcode>\` for a place in ` +
428
+ `\`<package>-${NOTE_SYSTEM}-${RELATION_TYPE}-<shortcode>\` for a place in ` +
429
429
  `another package — but reads ${JSON.stringify(encodeAddresses(to))}`,
430
430
  );
431
431
  } else if (!acceptsType(read, [RELATION_TYPE])) {
@@ -4,9 +4,10 @@
4
4
  * SPDX-License-Identifier: GPL-3.0-or-later
5
5
  */
6
6
 
7
- import { completeAddress, renderAddress } from "./address.mjs";
8
- import { systemOf } from "./document-subtypes.mjs";
9
- import { KNOWN_DOCUMENT_SUBTYPE_MAPS } from "./subtype-registry.mjs";
7
+ import { completeAddress, ownDocumentSystem, renderAddress } from "./address.mjs";
8
+ import { NOTE_SYSTEM } from "./systems.mjs";
9
+ import { JOURNAL_TYPES } from "./ids.mjs";
10
+ import { hasDocEntry } from "./item-docs.mjs";
10
11
 
11
12
  /**
12
13
  * Names and published destinations for reference presentation.
@@ -21,10 +22,9 @@ export function buildReferenceTargets(records, published = new Map()) {
21
22
  for (const record of records) {
22
23
  if (!record.package || !record.type || !record.shortcode) continue;
23
24
  const value = { name: record.name?.full ?? record.name, subType: record.subType };
24
- for (const system of new Set([
25
- systemOf(record.type, KNOWN_DOCUMENT_SUBTYPE_MAPS),
26
- "none",
27
- ])) {
25
+ const systems = new Set([ownDocumentSystem(record.type)]);
26
+ if (JOURNAL_TYPES.has(record.type) || hasDocEntry(record.type)) systems.add(NOTE_SYSTEM);
27
+ for (const system of systems) {
28
28
  const address = completeAddress({
29
29
  package: record.package,
30
30
  system,
@@ -52,7 +52,7 @@ export function resolveShortcodeReference(indexes, code, hint) {
52
52
  const { address: native, value } = matches.values().next().value;
53
53
  const address = completeAddress({
54
54
  package: native.package,
55
- system: "none",
55
+ system: "note",
56
56
  type: native.type,
57
57
  shortcode: native.shortcode,
58
58
  });
@@ -61,12 +61,9 @@ import {
61
61
  parseAddress,
62
62
  renderAddress,
63
63
  isAddressTuple,
64
- completeAddress,
64
+ ownDocumentSystem,
65
65
  } from "./address.mjs";
66
- import { NO_SYSTEM } from "./systems.mjs";
67
- import { systemOf } from "./document-subtypes.mjs";
68
- import { KNOWN_DOCUMENT_SUBTYPE_MAPS } from "./note-claims.mjs";
69
- import { hasDocEntry } from "./item-docs.mjs";
66
+ import { NOTE_SYSTEM } from "./systems.mjs";
70
67
  import { contentPackage } from "./content-package.mjs";
71
68
  // The declared tag vocabulary, which is where `draft` is stated.
72
69
  import { isDraftNote } from "./note-vocabulary.mjs";
@@ -241,42 +238,14 @@ export function buildSiteIndex(
241
238
  // resolves it and records it as `pkg`. Never read out of
242
239
  // frontmatter: `package:` is retired.
243
240
  index.set(
244
- canonicalKey(
245
- e.pkg ?? ownPackage,
246
- systemOf(type, KNOWN_DOCUMENT_SUBTYPE_MAPS),
247
- type,
248
- shortcode,
249
- ),
241
+ canonicalKey(e.pkg ?? ownPackage, ownDocumentSystem(type), type, shortcode),
250
242
  value,
251
243
  );
252
244
  if (e.pkg) packages.add(e.pkg);
253
- // In Foundry an item and its documentation are two documents, so
254
- // `skill/wpnc` and `docskill/wpnc` are two UUIDs. Here the
255
- // item note renders as one page which *is* its documentation, so
256
- // the two qualifiers alias one URL and an anchor on either is an
257
- // ordinary in-page anchor. One authored link, correct in both
258
- // builds — restricted to the types that actually have an item doc,
259
- // so a qualifier the packs would reject is reported broken here too.
260
- const proseAddress = completeAddress({
261
- package: e.pkg ?? ownPackage,
262
- system: NO_SYSTEM,
263
- type,
264
- shortcode,
265
- });
266
- if (proseAddress.type !== type || hasDocEntry(type)) {
267
- contentTypes.add(`doc${type}`);
268
- index.set(`doc${type}/${shortcode}`.toLowerCase(), value);
269
- // The canonical documentation address too, so the page answers
270
- // to the address a bare prose link expands to: body
271
- // prose is under no system block, so it defaults to `none`, and
272
- // a system-bearing type's `none` address is its `doc<type>`
273
- // one. In Foundry that names a second document; here it names
274
- // this same page, which is what makes one authored link correct
275
- // in both builds.
276
- index.set(
277
- canonicalKey(e.pkg ?? ownPackage, NO_SYSTEM, `doc${type}`, shortcode),
278
- value,
279
- );
245
+ // A web page is the readable `note` target for every type.
246
+ if (ownDocumentSystem(type) !== NOTE_SYSTEM) {
247
+ // The same page answers to its readable Address.
248
+ index.set(canonicalKey(e.pkg ?? ownPackage, NOTE_SYSTEM, type, shortcode), value);
280
249
  }
281
250
  }
282
251
  }
@@ -375,7 +344,7 @@ export function resolveInfoboxRef(siteIndex, ref, hint) {
375
344
  }
376
345
  const context = {
377
346
  package: siteIndex.contentPackage ?? contentPackage(),
378
- system: "none",
347
+ system: "note",
379
348
  types: siteIndex.contentTypes,
380
349
  packages: siteIndex.packages,
381
350
  };
@@ -253,7 +253,7 @@ export async function openNotesDatabase(records, { dir, dependencies = [], addre
253
253
  addressContext: addressContext ?? {
254
254
  package: records.find((record) => record.package)?.package,
255
255
  types: new Set(Object.keys(NOTE_VOCABULARY)),
256
- system: "none",
256
+ system: "note",
257
257
  },
258
258
  stubsExcluded,
259
259
  async query(sql) {
@@ -62,6 +62,9 @@ import { ADDRESS_SEGMENT_PATTERN, isAddressSegment } from "./address-charset.mjs
62
62
  */
63
63
  export const NO_SYSTEM = "none";
64
64
 
65
+ /** The Address segment for a note's readable content. */
66
+ export const NOTE_SYSTEM = "note";
67
+
65
68
  /**
66
69
  * Every game system this toolchain compiles for.
67
70
  *
@@ -86,7 +89,7 @@ export const SYSTEM_IDS = Object.freeze(new Set(["sohl", "hm3"]));
86
89
  *
87
90
  * @type {ReadonlySet<string>}
88
91
  */
89
- export const SYSTEM_SEGMENTS = Object.freeze(new Set([...SYSTEM_IDS, NO_SYSTEM]));
92
+ export const SYSTEM_SEGMENTS = Object.freeze(new Set([...SYSTEM_IDS, NO_SYSTEM, NOTE_SYSTEM]));
90
93
 
91
94
  /**
92
95
  * Whether a value names a game system this toolchain knows.
@@ -48,7 +48,7 @@ import { replaceOutsideCode } from "./code-fences.mjs";
48
48
  // address is matched the way a vendored manifest publishes it — by the
49
49
  // segments the target supplies, with the system wildcarded unless stated.
50
50
  import { canonicalKey, expandAddress, readCanonicalKey } from "./content-address.mjs";
51
- import { NO_SYSTEM } from "./systems.mjs";
51
+ import { NOTE_SYSTEM } from "./systems.mjs";
52
52
  // The one rule about a link's shape both builds share: it carries a label, and
53
53
  // {@link unlabelledLinkMessage} is the one place that says so.
54
54
  import { unlabelledLinkMessage } from "./wikilink-syntax.mjs";
@@ -102,7 +102,7 @@ function lookupRead(index, read, contentPackage) {
102
102
  // It replaced a system-blind short key for the unqualified form, which
103
103
  // could be silently overwritten by a second note of the same
104
104
  // `(type, shortcode)` under another system.
105
- const key = expandAddress(read, { package: contentPackage, system: NO_SYSTEM });
105
+ const key = expandAddress(read, { package: contentPackage, system: NOTE_SYSTEM });
106
106
  const found = index.get(key);
107
107
  if (found || read.package) return found;
108
108
 
@@ -393,7 +393,7 @@ export function resolveWebWikilinks(body, ctx) {
393
393
  return replaceOutsideCode(embedded.markdown, WIKILINK, (all, rawInner) => {
394
394
  const parsed = readWikilink(rawInner, {
395
395
  package: ctx.contentPackage,
396
- system: "none",
396
+ system: "note",
397
397
  types: ctx.contentTypes ?? new Set(),
398
398
  packages: ctx.packages,
399
399
  noIndexPackages: ctx.noIndexPackages,
@@ -12,74 +12,17 @@
12
12
  */
13
13
 
14
14
  /**
15
- * Wikilink resolution for the pack compilers.
15
+ * Resolve body wikilinks for Foundry packs.
16
16
  *
17
- * Content notes link to one another with wikilinks rather than file paths:
17
+ * `[[type-shortcode|Text]]` defaults to the readable `note` Address. A stated
18
+ * game system names an Actor or Item; `none` names a systemless document.
19
+ * Journal links can carry a heading anchor. Actor, Item and Macro links open
20
+ * their sheets, so their anchors do not select a page.
18
21
  *
19
- * `[[type-shortcode|Text]]` a document of that type
20
- * `[[type-shortcode|]]` the same, showing the target's current name
21
- * `[[type-shortcode#slug|T]]` a section (see below)
22
- * `[[#slug|Text]]` a section of the source note itself
23
- * `[[doctype-shortcode|T]]` an item's *documentation* (see below)
22
+ * `doc<type>` is accepted as an input alias for the readable note Address.
23
+ * A link with an empty label displays the target's current name.
24
24
  *
25
- * **Every link is an address, and every address carries a label**. A
26
- * link written without one addresses nothing and is reported — see
27
- * {@link unlabelledLinkMessage}, which states the rule for both builds. The
28
- * bare `[[Alias]]` form and the index it was looked up in are retired.
29
- *
30
- * The qualifier is the note's **type**, which with its shortcode is the system's
31
- * logical identity: `(type, shortcode)` is unique by rule (see the Shortcode
32
- * Integrity doc). It is deliberately not the note's directory — shortcodes are
33
- * unique per type, not per directory, so a directory qualifier would add nothing
34
- * to the address while breaking every inbound link the moment a note is refiled.
35
- *
36
- * Nothing narrower than `(type, shortcode)` is consulted — a note's directory
37
- * and its `category` play no part in resolution — and nothing wider: a note's
38
- * *name* is not an address, so two notes of a type may share a display name
39
- * ("Gear" as a rules page and as a user guide page) with nothing to disambiguate.
40
- *
41
- * At compile time each becomes a Foundry UUID enricher, routed to the pack that
42
- * the target's type compiles into (see {@link packForType}):
43
- *
44
- * `@UUID[Compendium.sohl.items.Item.<id>]{Text}`
45
- * `@UUID[Compendium.sohl.journals.JournalEntry.<id>.JournalEntryPage.<anchorId>]{Text}`
46
- *
47
- * **Every address is computed once, when the target is indexed** — see
48
- * {@link buildWikilinkIndex} — and a link is resolved by looking that value up.
49
- * Nothing here concatenates a prefix at the point of use.
50
- *
51
- * Section links address a **JournalEntryPage**, because Foundry UUIDs cannot
52
- * target a position inside a page. A heading carrying `{#slug}` therefore starts
53
- * its own page, whose id is {@link anchorPageId} — derived from the note id and
54
- * the slug so that the link and the page agree without any shared state.
55
- *
56
- * **An anchor on an Item, an Actor or a Macro is a no-op** and is dropped. What
57
- * such a link does is open that document's **sheet** — not its documentation —
58
- * and a sheet has no sections to address. Only a JournalEntry link opens a
59
- * journal, at its first page or at the page an anchor names. An item's pages are
60
- * reached through its `doc<type>` counterpart, below.
61
- *
62
- * **A document and its documentation are two documents.** An item note
63
- * compiles into an item — and, separately, its prose compiles into a
64
- * JournalEntry in the journals pack (see
65
- * {@link sohl.utils.packs.itemDocEntryId}); a macro note works the same way.
66
- * `skill/wpnc` addresses the skill; the **virtual qualifier**
67
- * `docskill/wpnc` addresses that skill's documentation, and
68
- * `docskill/wpnc#crafting` a page within it. `docmacro/autoattack#script`
69
- * reaches a macro's source. Every doc-carrying type has a `doc<type>`
70
- * counterpart, formed by prefix and never enumerated; see
71
- * {@link resolveItemDocType}. Without it a section link to an item note
72
- * produced a UUID against the *items* pack, which cannot hold a
73
- * JournalEntryPage, and dead-ended.
74
- *
75
- * **The two builds read the qualifier differently, by design.** In Foundry the
76
- * item and its documentation are separate documents in separate packs, so the
77
- * two qualifiers resolve to two different UUIDs. On the knowledgebase the item
78
- * note renders as a single page which *is* its documentation, so `doc<type>` and
79
- * `<type>` are aliases for the same URL and an anchor on either is an ordinary
80
- * in-page anchor. One authored link, correct in both.
81
- *
82
- * Plain ESM with no Foundry and no filesystem access, so it is unit-testable.
25
+ * @module
83
26
  */
84
27
 
85
28
  import { resolveShortcodeReference } from "./shortcode-references.mjs";
@@ -100,12 +43,13 @@ import {
100
43
  renderAddress,
101
44
  isAddressTuple,
102
45
  completeAddress,
46
+ ownDocumentSystem,
103
47
  } from "./address.mjs";
104
48
  import { ASSET_TYPE_NAMES } from "./asset-types.mjs";
105
- import { NO_SYSTEM } from "./systems.mjs";
49
+ import { NOTE_SYSTEM } from "./systems.mjs";
106
50
  import { systemOf } from "./document-subtypes.mjs";
107
51
  import { KNOWN_DOCUMENT_SUBTYPE_MAPS } from "./subtype-registry.mjs";
108
- import { itemDocEntryId } from "./item-docs.mjs";
52
+ import { hasDocEntry, itemDocEntryId } from "./item-docs.mjs";
109
53
  import { replaceOutsideCode } from "./code-fences.mjs";
110
54
  // The syntax lives in `./wikilink-syntax.mjs`, so the web resolver and this
111
55
  // one cannot disagree about what counts as a link.
@@ -228,10 +172,8 @@ export function buildWikilinkIndex(
228
172
  // type and a UUID carries the pack name, so the address
229
173
  // cannot be derived from the type alone.
230
174
  uuid: compendiumUuid(packageId, d.type, d.id, d.pack),
231
- // An item's prose compiles into a separate JournalEntry,
232
- // addressed by the virtual `doc<type>` qualifier. Its id
233
- // is derived from the item's, so its address is knowable
234
- // here too.
175
+ // The readable note has a separate JournalEntry whose id
176
+ // is derived from the item's id.
235
177
  docUuid: compendiumUuid(packageId, "doc", itemDocEntryId(d.id), d.docPack),
236
178
  },
237
179
  );
@@ -249,19 +191,8 @@ export function buildWikilinkIndex(
249
191
  for (const v of foreignByKey.values()) {
250
192
  if (v.type) foreignTypes.push(norm(v.type));
251
193
  }
252
- // A manifest publishes `doc<type>` addresses, but `doc<type>` is a
253
- // *virtual* qualifier formed by prefix — never a real type. Admitting it
254
- // here would make it one, and a real type owns its own name, so the
255
- // virtual reading would stop firing and every `[[docskill-wpnc]]` would
256
- // resolve nowhere. The virtual form still reaches a foreign documentation
257
- // entry: it reads as `skill` + `itemDoc`, which the manifest lookup then
258
- // asks for as `docskill`.
259
- //
260
- // Real types are admitted **first**, in a pass of their own, so the test
261
- // below sees the complete set. Done in one pass it would depend on
262
- // manifest iteration order wherever the base type is published only by a
263
- // foreign package — `docmacro` admitted or not according to whether
264
- // `macro` happened to come first.
194
+ // A legacy index may contain `doc<type>` records. Keep the authored type
195
+ // vocabulary separate so the input alias can resolve to its base type.
265
196
  for (const t of foreignTypes) {
266
197
  if (!t.startsWith(ITEM_DOC_PREFIX)) types.add(t);
267
198
  }
@@ -334,7 +265,8 @@ function findForeign(index, read) {
334
265
  */
335
266
  function foreignHits(index, read) {
336
267
  if (!read || read.reason || !index.foreign?.size) return [];
337
- const wanted = norm(read.itemDoc ? `doc${read.type}` : read.type);
268
+ const tuple = completeAddress(read, { package: index.contentPackage, system: NOTE_SYSTEM });
269
+ const wanted = norm(tuple.type);
338
270
  const shortcode = norm(read.shortcode);
339
271
 
340
272
  // **An omitted package means this package**, so a short form
@@ -348,7 +280,7 @@ function foreignHits(index, read) {
348
280
  // than a filter. That is what makes a cross-package `ambiguous` impossible:
349
281
  // one key, one entry.
350
282
  if (!read.package) return [];
351
- const wantedSystem = norm(read.system ?? NO_SYSTEM);
283
+ const wantedSystem = norm(tuple.system);
352
284
  const hits = [];
353
285
  for (const [key, entry] of index.foreign) {
354
286
  const parts = readCanonicalKey(key);
@@ -460,7 +392,7 @@ export function convertWikilinks(markdown, { type, id, pack, docPack, index }) {
460
392
  const out = replaceOutsideCode(markdown, WIKILINK, (all, rawInner, offset) => {
461
393
  const parsed = readWikilink(rawInner, {
462
394
  package: index.contentPackage,
463
- system: "none",
395
+ system: "note",
464
396
  types: index.types,
465
397
  packages: index.packages,
466
398
  noIndexPackages: index.noIndexPackages,
@@ -516,20 +448,13 @@ export function convertWikilinks(markdown, { type, id, pack, docPack, index }) {
516
448
  });
517
449
  return unresolvedLink(text || target, target);
518
450
  }
519
- // An omitted system defaults from where the link is written,
520
- // and a body is under no system block, so it is `none`. Under
521
- // `none` a system-bearing type addresses its *documentation* — a
522
- // note's `none` address IS its `doc<type>` entry — which is what a
523
- // prose link almost always means. Stating the system is how prose
524
- // reaches the Item instead.
525
- //
526
- // Only a type whose *own* document carries a system is redirected.
527
- // A `macro` and the map types have documentation journals too, but
528
- // their own documents are core ones already at `none`, so
529
- // `macro-autoattack` names the Macro and `docmacro-autoattack` its
530
- // journal — two live addresses the redirect would collapse.
451
+ // Ordinary wikilinks default to readable note content.
531
452
  const baseType = resolveItemDocType(qualified.type, index.types);
532
- itemDoc = Boolean(baseType);
453
+ const address = completeAddress(qualified, {
454
+ package: index.contentPackage,
455
+ system: NOTE_SYSTEM,
456
+ });
457
+ itemDoc = address.system === NOTE_SYSTEM && hasDocEntry(baseType ?? qualified.type);
533
458
  if (qualified.package === index.contentPackage)
534
459
  doc = index.byShortcode.get(`${baseType ?? qualified.type}/${qualified.shortcode}`);
535
460
  }
@@ -615,7 +540,7 @@ export function convertWikilinks(markdown, { type, id, pack, docPack, index }) {
615
540
  // *sheet*, which has no sections, so the anchor has nothing to address
616
541
  // and is dropped. Forging a JournalEntryPage id onto a document that
617
542
  // can never hold one is what made such links dead-end; an
618
- // item's pages are addressed through its `doc<type>` counterpart.
543
+ // an item's pages are addressed through its `note` Address.
619
544
  // A `#section` the target declares no heading for. Checked here, and
620
545
  // not only by `content-build links`, because this is the build that
621
546
  // *emits* the link: `anchorPageId` will hash any slug into a page id,
@@ -672,7 +597,7 @@ export function resolveReference(index, ref, hint) {
672
597
  );
673
598
  const tuple = parseAddress(ref, {
674
599
  package: index.contentPackage,
675
- system: "none",
600
+ system: "note",
676
601
  types: index.types,
677
602
  packages: index.packages,
678
603
  noIndexPackages: index.noIndexPackages,
@@ -699,15 +624,15 @@ export function resolveReference(index, ref, hint) {
699
624
  if (foreign) return { ...foreign, address: tuple };
700
625
  return undefined;
701
626
  }
702
- const base = resolveItemDocType(tuple.type, index.types);
703
- const type = base ?? tuple.type;
704
- if (tuple.system !== (base ? NO_SYSTEM : systemOf(type, KNOWN_DOCUMENT_SUBTYPE_MAPS)))
627
+ const type = tuple.type;
628
+ const documentation = tuple.system === NOTE_SYSTEM && hasDocEntry(type);
629
+ if (tuple.system !== (documentation ? NOTE_SYSTEM : ownDocumentSystem(type)))
705
630
  return undefined;
706
631
  const local = index.byShortcode?.get(`${type}/${tuple.shortcode}`);
707
632
  if (!local) return undefined;
708
633
  const addresses = index.uuidByDoc?.get(local);
709
634
  const uuid =
710
- base ?
635
+ documentation ?
711
636
  Object.hasOwn(local, "documentationUuid") ?
712
637
  local.documentationUuid
713
638
  : addresses?.docUuid
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@heroiclands/package-build",
3
- "version": "22.12.2",
3
+ "version": "22.13.0",
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",
package/sohl/infobox.mjs CHANGED
@@ -270,7 +270,7 @@ function itemPresentation(item, resolve, block) {
270
270
  if (!item.shortcode) return undefined;
271
271
  if (!item.address)
272
272
  return resolve?.(item.shortcode, { kind: "shortcode", type: item.type, system: block });
273
- const documentation = completeAddress({ ...item.address, system: "none" });
273
+ const documentation = completeAddress({ ...item.address, system: "note" });
274
274
  const found = resolve?.(documentation, { type: documentation.type });
275
275
  const native =
276
276
  !found?.name || !found?.subType ? resolve?.(item.address, { type: item.type }) : undefined;
@@ -556,7 +556,7 @@ export function affiliationSections(fm, ctx) {
556
556
  const entries = value.map((skill) => {
557
557
  const native = ctx.resolve?.(skill, { type: "skill", system: "sohl" });
558
558
  const docAddress =
559
- isAddressTuple(skill) ? completeAddress({ ...skill, system: "none" }) : undefined;
559
+ isAddressTuple(skill) ? completeAddress({ ...skill, system: "note" }) : undefined;
560
560
  const doc = docAddress ? ctx.resolve?.(docAddress, { type: docAddress.type }) : undefined;
561
561
  return {
562
562
  text: doc?.name || native?.name || humanizeValue(skill),