@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
@@ -54,7 +54,12 @@ import { metadataFileName, PACKAGEBUILD_PACKAGE } from "./packages.mjs";
54
54
  import { packageBuildRecords } from "./packagebuild-index.mjs";
55
55
 
56
56
  export { metadataFileName };
57
- import { PACKAGE_BASE, readCanonicalKey, resolvePackageUrl } from "./content-address.mjs";
57
+ import {
58
+ PACKAGE_BASE,
59
+ canonicalKey,
60
+ readCanonicalKey,
61
+ resolvePackageUrl,
62
+ } from "./content-address.mjs";
58
63
  import { HOMEPAGE_TYPE } from "./homepage.mjs";
59
64
 
60
65
  /**
@@ -369,11 +374,23 @@ export function loadForeignIndexes(config, localPackages, bases = PACKAGE_BASE)
369
374
  error.position = line >= 0 ? { line: line + 1 } : {};
370
375
  throw error;
371
376
  }
372
- referenceRecords.push(record);
373
- const key = encodeAddresses(record?.address?.canonical);
377
+ let key = encodeAddresses(record?.address?.canonical);
374
378
  if (!key) continue;
375
- const parts = readCanonicalKey(key);
379
+ let parts = readCanonicalKey(key);
376
380
  if (!parts) continue;
381
+ if (
382
+ parts.system === "none" &&
383
+ record.documents &&
384
+ parts.type.startsWith("doc") &&
385
+ record.type === parts.type
386
+ ) {
387
+ record.type = parts.type.slice(3);
388
+ key = canonicalKey(parts.package, "note", record.type, parts.shortcode);
389
+ parts = readCanonicalKey(key);
390
+ record.foundry = { note: record.foundry?.none };
391
+ record.address.canonical = key;
392
+ }
393
+ referenceRecords.push(record);
377
394
  // First writer wins, so two packages claiming one address cannot
378
395
  // make the build depend on the order the cache was read in.
379
396
  if (index.has(key)) continue;
@@ -436,11 +453,11 @@ export function loadForeignIndexes(config, localPackages, bases = PACKAGE_BASE)
436
453
  if (entry.url) {
437
454
  const identity = completeAddress({
438
455
  package: record.package,
439
- system: "none",
456
+ system: "note",
440
457
  type: record.type,
441
458
  shortcode: record.shortcode,
442
459
  });
443
- if (identity.type !== record.type)
460
+ if (key !== renderAddress(identity))
444
461
  pageAliases.push([
445
462
  renderAddress(identity),
446
463
  {
@@ -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
@@ -102,7 +105,7 @@ function text(value) {
102
105
  */
103
106
  export function noteDocId(fm, { pkg, maps = KNOWN_DOCUMENT_SUBTYPE_MAPS } = {}) {
104
107
  if (!fm || typeof fm !== "object") return undefined;
105
- const pinned = text(fm.id);
108
+ const pinned = text(fm.data?.id) ?? text(fm.id);
106
109
  if (pinned) return pinned;
107
110
  const type = text(fm.type);
108
111
  const shortcode = text(fm.shortcode);
@@ -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
  /**
@@ -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.
@@ -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) {
@@ -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
  *
@@ -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,