@heroiclands/package-build 22.12.3 → 22.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -88,7 +88,7 @@ export function artSlot(key) {
88
88
  * not parse; or `not-accepted` when it parses to a type outside `accepts`.
89
89
  */
90
90
  export function artTarget(value, defaultType, accepts, vocabulary = {}) {
91
- const tuple = parseAddress(value, { ...vocabulary, type: defaultType });
91
+ const tuple = parseAddress(value, { ...vocabulary, system: ASSET_SYSTEM, type: defaultType });
92
92
  if (tuple.reason) return tuple;
93
93
  if (accepts && !acceptsType(tuple, accepts)) {
94
94
  return { reason: "not-accepted", type: tuple.type };
@@ -71,7 +71,7 @@ import {
71
71
  import { contentPackage } from "./content-package.mjs";
72
72
  import { FOLDER_TYPE } from "./folder-notes.mjs";
73
73
  import { HOMEPAGE_TYPE } from "./homepage.mjs";
74
- import { itemDocEntryId } from "./item-docs.mjs";
74
+ import { hasDocEntry, itemDocEntryId } from "./item-docs.mjs";
75
75
  import { folderField, renderFoundryMarkdown, resolveName } from "./helpers.mjs";
76
76
  import { packForType } from "./ids.mjs";
77
77
  import { readQualifier } from "./wikilinks.mjs";
@@ -251,8 +251,9 @@ export class Bundles extends BasePackCompiler {
251
251
 
252
252
  const baseType =
253
253
  isAddressTuple(read) ? resolveItemDocType(read.type, index.types) : undefined;
254
- const itemDoc = read.itemDoc || Boolean(baseType);
255
254
  const type = baseType ?? read.type;
255
+ const itemDoc =
256
+ read.itemDoc || Boolean(baseType) || (read.system === "note" && hasDocEntry(type));
256
257
  const note = index.byShortcode.get(`${type}/${read.shortcode}`);
257
258
  if (!note) {
258
259
  throw new Error(
@@ -45,7 +45,7 @@ import { DEFAULT_ADDRESS_SCHEME } from "../content-config.mjs";
45
45
  // The system vocabulary is the `<system>` segment's own registry, and
46
46
  // `engine/systems.mjs` imports nothing but `engine/address-charset.mjs`, so
47
47
  // the direction is toward the leaf and cannot close a cycle.
48
- import { NO_SYSTEM, isSystemSegment } from "./systems.mjs";
48
+ import { NO_SYSTEM, NOTE_SYSTEM, isSystemId } from "./systems.mjs";
49
49
 
50
50
  // The address grammar, which sits below this module: a page URL is derived from
51
51
  // an address, so the rule for reading and writing one is stated there and
@@ -171,12 +171,9 @@ export function packageAddress(fm) {
171
171
  *
172
172
  * The **enclosing system block** decides, at any depth within it, and nothing
173
173
  * else does: `sohl.items[3].model` and `sohl.system.body.structure` are both
174
- * `sohl` because both sit under `sohl:`. Everywhere else is {@link NO_SYSTEM} —
175
- * top-level frontmatter, the shared `data:` container, and body prose, which has
176
- * no key path at all and passes `undefined`.
177
- *
178
- * It is the block rather than the field, so an `Address` field needs no opinion
179
- * about systems and no per-field table has to be kept in step with the schema.
174
+ * `sohl` because both sit under `sohl:`. Everywhere else takes
175
+ * {@link NOTE_SYSTEM}; fields declared for assets or folders supply their own
176
+ * `none` default at the field parser.
180
177
  *
181
178
  * The first segment must **be** a declared system, not merely look like one:
182
179
  * `sohlish.items` is a key called `sohlish`, and `notes.sohl.thing` names no
@@ -187,9 +184,9 @@ export function packageAddress(fm) {
187
184
  * @returns {string} The system id, or `none`.
188
185
  */
189
186
  export function blockSystem(keyPath) {
190
- if (typeof keyPath !== "string" || !keyPath) return NO_SYSTEM;
187
+ if (typeof keyPath !== "string" || !keyPath) return NOTE_SYSTEM;
191
188
  const first = keyPath.split(".")[0].trim().toLowerCase();
192
- return isSystemSegment(first) && first !== NO_SYSTEM ? first : NO_SYSTEM;
189
+ return isSystemId(first) ? first : NOTE_SYSTEM;
193
190
  }
194
191
 
195
192
  /**
@@ -102,12 +102,13 @@ import unidecode from "unidecode";
102
102
  import { metadataFileName } from "./metadata-index.mjs";
103
103
  import { collectAssetRecords } from "./asset-index.mjs";
104
104
  import { addressSlug, canonicalKey } from "./content-address.mjs";
105
+ import { ownDocumentSystem } from "./address.mjs";
106
+ import { NOTE_SYSTEM } from "./systems.mjs";
105
107
  // One reader for a note's anchors, shared with the link checker and with the
106
108
  // builds that emit a link. Re-exported because this is where callers
107
109
  // have always addressed it.
108
110
  import { collectAnchors } from "./anchors.mjs";
109
- import { subtypeRow, NO_SYSTEM, systemOf } from "./document-subtypes.mjs";
110
- import { KNOWN_DOCUMENT_SUBTYPE_MAPS } from "./note-claims.mjs";
111
+ import { NO_SYSTEM } from "./document-subtypes.mjs";
111
112
 
112
113
  /**
113
114
  * The `<system>` a note belongs to when it belongs to none.
@@ -204,7 +205,7 @@ export function noteAddress(frontmatter, contentPackage) {
204
205
  slug,
205
206
  canonical: canonicalKey(
206
207
  contentPackage,
207
- systemOf(frontmatter.type, KNOWN_DOCUMENT_SUBTYPE_MAPS),
208
+ ownDocumentSystem(frontmatter.type),
208
209
  frontmatter.type,
209
210
  frontmatter.shortcode,
210
211
  ),
@@ -504,10 +505,7 @@ export function buildIndexRecord({
504
505
  link: address ? `${address.slug}#${a.slug}` : null,
505
506
  }))
506
507
  ),
507
- foundry: foundryBlock(
508
- entries?.own,
509
- systemOf(frontmatter?.type, KNOWN_DOCUMENT_SUBTYPE_MAPS),
510
- ),
508
+ foundry: foundryBlock(entries?.own, ownDocumentSystem(frontmatter?.type)),
511
509
  // Forward link to the note's documentation journal, which is its
512
510
  // own record. Named rather than nested, because the journal is a
513
511
  // separate document with its own address — see `buildDocRecord`.
@@ -536,7 +534,7 @@ export function buildIndexRecord({
536
534
  *
537
535
  * An item note compiles into two documents — the item, and a JournalEntry
538
536
  * holding its prose — and the second is a document in its own right: its own
539
- * canonical address (`doc<type>/<shortcode>`), its own UUID, its own pages.
537
+ * canonical `note` address with the authored type, its own UUID and pages.
540
538
  * So it gets its own record, and resolving `docaffliction/blkdth` is the same
541
539
  * lookup as resolving anything else. Nested inside the item's record it would
542
540
  * be the one address in the index reachable only by knowing to look somewhere
@@ -572,7 +570,7 @@ function buildDocRecord({ frontmatter, address, entry, file, contentPackage, anc
572
570
  return /** @type {Record<string, any>} */ (
573
571
  sortKeysDeep({
574
572
  package: contentPackage,
575
- type: `doc${frontmatter.type}`,
573
+ type: frontmatter.type,
576
574
  shortcode: frontmatter.shortcode,
577
575
  name: frontmatter.name,
578
576
  // The journal's own `_id`, taken from the entry rather than
@@ -586,7 +584,7 @@ function buildDocRecord({ frontmatter, address, entry, file, contentPackage, anc
586
584
  // the forward link on that record, so either end reaches the other.
587
585
  documents: address.canonical,
588
586
  anchors: made ? anchors : null,
589
- foundry: foundryBlock(entry), // a journal: no system key
587
+ foundry: foundryBlock(entry, NOTE_SYSTEM),
590
588
  file,
591
589
  })
592
590
  );
@@ -76,8 +76,8 @@ import { ASSET_TYPE_NAMES } from "./asset-types.mjs";
76
76
  import { resolveEmbeds } from "./content-embeds.mjs";
77
77
  import { foundryAddressProblem, servesFoundry } from "./pathnames.mjs";
78
78
  import { hasDocEntry } from "./item-docs.mjs";
79
- import { NO_SYSTEM, systemOf } from "./document-subtypes.mjs";
80
- import { KNOWN_DOCUMENT_SUBTYPE_MAPS } from "./note-claims.mjs";
79
+ import { ownDocumentSystem } from "./address.mjs";
80
+ import { NOTE_SYSTEM } from "./systems.mjs";
81
81
  import { loadPackConfig } from "./pack-config.mjs";
82
82
  import { searchableFrontmatter } from "./note-package.mjs";
83
83
  import {
@@ -138,12 +138,8 @@ export function anchorsOf(body) {
138
138
  * before, since the walk read the file and it then read it again for the raw
139
139
  * text.
140
140
  *
141
- * The index mirrors what both builds construct, including the two addresses a
142
- * doc-carrying note answers to: `type/shortcode` for the document, and
143
- * `doc<type>/shortcode` for the JournalEntry its prose compiles into. Once a
144
- * manifest publishes `doc<type>` entries that prefix is a *known type*, and the
145
- * virtual reading that used to answer for it no longer fires — a real type owns
146
- * its own name — so the note is indexed under both.
141
+ * The index holds each compiled document under its complete Address. Readable
142
+ * content uses `note`, and system documents use their declared system.
147
143
  *
148
144
  * @param {string} contentBase - Root of the content tree.
149
145
  * @param {object} [opts]
@@ -250,24 +246,20 @@ export function buildLinkIndex(
250
246
  // once for the whole build.
251
247
  const canonical =
252
248
  encodeAddresses(record.address?.canonical) ??
253
- canonicalKey(pkg, systemOf(type, KNOWN_DOCUMENT_SUBTYPE_MAPS), type, fm.shortcode);
249
+ canonicalKey(pkg, ownDocumentSystem(type), type, fm.shortcode);
254
250
  // A note with no address publishes no page, so its identity is
255
251
  // filed where a link cannot reach it and a reference still can.
256
252
  const into = record.address ? byKey : byStub;
257
253
  into.set(canonical, note);
258
254
  if (hasDocEntry(type)) {
259
- // A documentation journal is `none`: no game system defines a
260
- // JournalEntry, and one note has one of them however many
261
- // system blocks it carries.
262
- into.set(canonicalKey(pkg, NO_SYSTEM, `doc${type}`, fm.shortcode), note);
255
+ // The prose has one `note` Address across all system blocks.
256
+ into.set(canonicalKey(pkg, NOTE_SYSTEM, type, fm.shortcode), note);
263
257
  }
264
258
  }
265
259
  }
266
260
 
267
261
  // The types a link may name, which are the ones notes declare. A
268
- // documentation journal's `doc<type>` is deliberately not among them: it is
269
- // virtual, and `readQualifier` resolves it from the base type rather than
270
- // from a type any tree declares.
262
+ // The `doc<type>` input spelling is an alias, not a published type.
271
263
  //
272
264
  // The **asset** types join unconditionally, whether or not this tree holds
273
265
  // a file of each. They are a closed vocabulary rather than a census of what
@@ -423,7 +415,7 @@ export function buildLinkIndex(
423
415
  for (const [all, rawInner] of matchAllOutsideCode(body, new RegExp(WIKILINK.source, "g"))) {
424
416
  const parsed = readWikilink(rawInner, {
425
417
  package: pkg,
426
- system: "none",
418
+ system: "note",
427
419
  types,
428
420
  packages,
429
421
  noIndexPackages,
@@ -1041,7 +1041,7 @@ export function renderContentTable(spec, rows, linkable, self) {
1041
1041
  const label = text.replace(/\\?\|/g, "/");
1042
1042
  const target = completeAddress(
1043
1043
  { type: doc.fm.type, shortcode: doc.fm.shortcode },
1044
- { package: doc.fm.package ?? contentPackage(), system: "none" },
1044
+ { package: doc.fm.package ?? contentPackage(), system: "note" },
1045
1045
  );
1046
1046
  return `[[${renderAddress(target)}\\|${label}]]`;
1047
1047
  }),
@@ -60,7 +60,8 @@ import path from "node:path";
60
60
 
61
61
  import { noteAddressContext } from "./note-addresses.mjs";
62
62
  import { canonicalKey, packageAddress } from "./content-address.mjs";
63
- import { NO_SYSTEM, systemOf } from "./document-subtypes.mjs";
63
+ import { ownDocumentSystem } from "./address.mjs";
64
+ import { NOTE_SYSTEM } from "./systems.mjs";
64
65
  import {
65
66
  KNOWN_DOCUMENT_SUBTYPE_MAPS,
66
67
  NEVER_PACKED_TYPES,
@@ -150,12 +151,7 @@ export function anchorsOf(entryUuid, entryId, body, name) {
150
151
  */
151
152
  export function entriesForNote(fm, name, address, body, ctx) {
152
153
  const { contentPackage, foundryPackageId, packRouter } = ctx;
153
- const key = canonicalKey(
154
- contentPackage,
155
- systemOf(fm.type, KNOWN_DOCUMENT_SUBTYPE_MAPS),
156
- fm.type,
157
- fm.shortcode,
158
- );
154
+ const key = canonicalKey(contentPackage, ownDocumentSystem(fm.type), fm.type, fm.shortcode);
159
155
  // `buildManifest` records `packageRelative(url, base)`, so the pair it is
160
156
  // given has to round-trip. The address is already package-relative, so the
161
157
  // honest pair is the address under a base of `"/"` — which strips straight
@@ -244,7 +240,7 @@ export function entriesForNote(fm, name, address, body, ctx) {
244
240
  // `NO_SYSTEM`, whatever the item is: a documentation journal is a
245
241
  // JournalEntry, which no game system defines, and there is one of them
246
242
  // however many system blocks the note carries.
247
- const docKey = canonicalKey(contentPackage, NO_SYSTEM, `doc${fm.type}`, fm.shortcode);
243
+ const docKey = canonicalKey(contentPackage, NOTE_SYSTEM, fm.type, fm.shortcode);
248
244
  const docEntryId = fm.id ? itemDocEntryId(fm.id) : undefined;
249
245
  const docUuid = uuidFor("doc", docEntryId);
250
246
  return [
@@ -1109,7 +1109,7 @@ export function lintNote(
1109
1109
  const fm = note.fm ?? {};
1110
1110
  const referenceContext = {
1111
1111
  package: index?.contentPackage,
1112
- system: "none",
1112
+ system: "note",
1113
1113
  ...addressContext,
1114
1114
  types: new Set([
1115
1115
  ...Object.keys(vocabulary ?? {}),
@@ -1773,7 +1773,7 @@ export function lintFrontmatter(
1773
1773
  index: references ? index : undefined,
1774
1774
  addressContext: {
1775
1775
  package: index.contentPackage,
1776
- system: "none",
1776
+ system: "note",
1777
1777
  types: index.types,
1778
1778
  },
1779
1779
  ...(systems ? { systems } : {}),
@@ -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
@@ -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,