@heroiclands/package-build 23.12.5 → 23.12.6

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,15 @@
1
1
  # @heroiclands/package-build
2
2
 
3
+ ## 23.12.6
4
+
5
+ ### Patch Changes
6
+
7
+ A package can name a default calendar with `defaultCalendar` in its configuration, and it replaces the world note's calendar as the one dates fall back to.
8
+
9
+ A note's dates print in its own `data.calendar`, which any note may now set, else its culture's, else for a place its parents', else the default. A place whose parents keep different calendars is a build error until it names its own.
10
+
11
+ Chronologies and the other event sections print every date in the calendar of the page showing them, and `dateformat` with no calendar named uses the note's.
12
+
3
13
  ## 23.12.5
4
14
 
5
15
  ### Patch Changes
@@ -571,6 +571,10 @@ export function publishesContentPages(config) {
571
571
  * resolved against it, so the build never
572
572
  * depends on the working directory.
573
573
  * @property {string} [currentDate] Setting-wide neutral current date; defaults to "720.1".
574
+ * @property {string} [defaultCalendar] Shortcode of the `lore` note with
575
+ * `subType: calendar` that prints the
576
+ * dates of a note no other rule gives a
577
+ * calendar.
574
578
  * @property {string} contentPackage Content package name — the address
575
579
  * namespace every note in this
576
580
  * repository is published under.
@@ -646,6 +650,8 @@ export function publishesContentPages(config) {
646
650
  *
647
651
  * @typedef {object} ContentBuildConfig
648
652
  * @property {string} currentDate Setting-wide neutral current date.
653
+ * @property {string|null} defaultCalendar The calendar of last resort's
654
+ * shortcode, `null` when none is named.
649
655
  * @property {string} rootDir
650
656
  * @property {string} contentPackage
651
657
  * @property {string|null} foundryPackage `null` for a `documentation`
@@ -730,6 +736,7 @@ const CONFIG_KEYS = [
730
736
  "rootDir",
731
737
  "contentPackage",
732
738
  "currentDate",
739
+ "defaultCalendar",
733
740
  "foundryPackage",
734
741
  "homepage",
735
742
  "authors",
@@ -912,6 +919,24 @@ function fail(field, problem) {
912
919
  throw Object.assign(new TypeError(`package-build config: \`${field}\` ${problem}.`), { field });
913
920
  }
914
921
 
922
+ /**
923
+ * The configured `defaultCalendar`: a calendar note's shortcode, or `null`.
924
+ * Whether it names a calendar note is checked against the content tree when
925
+ * the content index is built.
926
+ *
927
+ * @param {unknown} value - The configured value.
928
+ * @returns {string|null} The shortcode.
929
+ */
930
+ function normalizeDefaultCalendar(value) {
931
+ if (value === undefined || value === null) return null;
932
+ if (typeof value !== "string" || !isAddressSegment(value))
933
+ fail(
934
+ "defaultCalendar",
935
+ "must be the shortcode of a calendar note — lowercase letters and digits",
936
+ );
937
+ return /** @type {string} */ (value);
938
+ }
939
+
915
940
  /**
916
941
  * @param {object} object
917
942
  * @param {readonly string[]} allowed
@@ -2973,6 +2998,7 @@ export function defineConfig(config) {
2973
2998
  fail("currentDate", error.message);
2974
2999
  }
2975
3000
  })(),
3001
+ defaultCalendar: normalizeDefaultCalendar(input.defaultCalendar),
2976
3002
  contentPackage: requireContentPackage(input.contentPackage, docEntryTypes, {
2977
3003
  rootDir,
2978
3004
  packageKind,
package/docs/api.md CHANGED
@@ -723,7 +723,7 @@ resolved value: the culture a place or affiliation inherits through
723
723
 
724
724
  ### `engine.markdownExpressions`
725
725
 
726
- `renderMarkdownExpressions(body, { fm, dates, sqlResults, figures, file, bodyLine })` expands scalar frontmatter references and registered Handlebars helpers outside Markdown code. The `dateformat` helper uses `formatDateInCalendar`; `gt`, `gte`, `lt`, `lte`, `eq`, `and`, `or`, and `not` evaluate explicit comparisons. The `sql` helper reads a prepared scalar result, which must contain one row and one column. The `ref` helper links to a captioned item by its anchor — `"#thorn"` on the same note — in `number` (the default, and admitted explicitly), `full` or `title` form; `figures` is a `Map` of this note's own figures, by id, to `{label, caption, hasCaption}`, and an address naming another note is reported as unresolved rather than looked up. `sqlQueriesInMarkdown` discovers SQL calls, including nested calls; `prepareInlineSqlExpressions` evaluates them through the same DuckDB connection as SQL tables. `EXPRESSION_HELPERS` is the one list of helpers: the engine registers from it and the authoring document is checked against it, so each name there carries the parameters it takes and a sentence on what it does. The renderer returns `{ markdown, findings }`, with source positions for invalid expressions. Site, Foundry and PDF passes invoke it after generated tables expand and before links resolve. Hugo shortcodes remain literal for the site renderer.
726
+ `renderMarkdownExpressions(body, { fm, dates, sqlResults, figures, file, bodyLine })` expands scalar frontmatter references and registered Handlebars helpers outside Markdown code. The `dateformat` helper uses `formatDateInCalendar`, in the calendar it names or else the note's resolved calendar, which it reads from `dates.noteCalendars` — the map a reckoning context built over content-index records carries; `gt`, `gte`, `lt`, `lte`, `eq`, `and`, `or`, and `not` evaluate explicit comparisons. The `sql` helper reads a prepared scalar result, which must contain one row and one column. The `ref` helper links to a captioned item by its anchor — `"#thorn"` on the same note — in `number` (the default, and admitted explicitly), `full` or `title` form; `figures` is a `Map` of this note's own figures, by id, to `{label, caption, hasCaption}`, and an address naming another note is reported as unresolved rather than looked up. `sqlQueriesInMarkdown` discovers SQL calls, including nested calls; `prepareInlineSqlExpressions` evaluates them through the same DuckDB connection as SQL tables. `EXPRESSION_HELPERS` is the one list of helpers: the engine registers from it and the authoring document is checked against it, so each name there carries the parameters it takes and a sentence on what it does. The renderer returns `{ markdown, findings }`, with source positions for invalid expressions. Site, Foundry and PDF passes invoke it after generated tables expand and before links resolve. Hugo shortcodes remain literal for the site renderer.
727
727
 
728
728
  | Export | Signature | Returns | Use it when |
729
729
  | --------------------------- | ------------------------------------------ | ---------------------- | ------------------------------------------------ |
@@ -736,10 +736,13 @@ resolved value: the culture a place or affiliation inherits through
736
736
 
737
737
  The corpus registry anchors each calendar's year 1, day 1 to its canonical `data.epoch` and maps every declared era to that calendar. Exactly one `start: null` era covers earlier years and counts backwards; exactly one `start: 1` era begins the forward count. Other starts are distinct positive calendar years. Numeric order determines bounds, irrespective of array order. Duplicate starts and markers, missing required eras, and invalid epochs are findings. Short qualifiers are ambiguous when they name eras in several packages; a full Address selects one.
738
738
 
739
- | Export | Signature | Returns | Use it when |
740
- | ------------------------- | --------------------------------------------- | ---------------------------------------- | ----------------------------------------------------------- |
741
- | `resolveReckoningMarkers` | `resolveReckoningMarkers(index, daysPerYear)` | `{eras, markers, findings}` | resolving authored eras, their markers, and their bounds |
742
- | `reckoningContext` | `reckoningContext(index)` | `{eras, markers, daysPerYear, findings}` | sharing one resolved date context across checks on a corpus |
739
+ | Export | Signature | Returns | Use it when |
740
+ | ------------------------- | --------------------------------------------- | ------------------------------------------------------- | ----------------------------------------------------------------------------------------------------- |
741
+ | `resolveReckoningMarkers` | `resolveReckoningMarkers(index, daysPerYear)` | `{eras, markers, findings}` | resolving authored eras, their markers, and their bounds |
742
+ | `reckoningContext` | `reckoningContext(index)` | `{eras, markers, daysPerYear, findings, noteCalendars}` | sharing one resolved date context across checks on a corpus |
743
+ | `noteCalendarMap` | `noteCalendarMap(notes)` | `Map<string, string>` | reading each record's `resolvedCalendar` by note, as `reckoningContext` carries it in `noteCalendars` |
744
+ | `noteCalendarKey` | `noteCalendarKey(fm)` | `string \| undefined` | finding a note's entry in `noteCalendars` by its type and shortcode |
745
+ | `calendarAddress` | `calendarAddress(value)` | `string \| undefined` | reading a record's `resolvedCalendar`, tuple or string, as its canonical Address |
743
746
 
744
747
  ### `engine.calendarNotes`
745
748
 
@@ -84,7 +84,35 @@ package-build dateto commoncal 326.114 --format long
84
84
 
85
85
  The CLI `datefrom` command requires a day and reads the default (`std`, or first) pattern. `dateto` prints the era covering the canonical day. A clock time makes a date exact; a year or day without one spans the stated interval. `data.epoch` and `data.moon.newOn` require a day. The [detailed date rules](../reference/format-details.md#dates-and-calendars) describe precision and bounds.
86
86
 
87
- In prose, `{{dateformat "commoncal" data.born}}` prints a date in the chosen calendar. `{{dateformat data.calendar data.born}}` uses the note's calendar Address. A third argument chooses another named pattern. A being or place can set `data.calendar`; its infobox uses that calendar for dates.
87
+ In prose, `{{dateformat "commoncal" data.born}}` prints a date in the chosen calendar, and `{{dateformat data.born}}` prints it in the note's own calendar. A last argument chooses another named pattern: `{{dateformat "commoncal" data.born "long"}}`, or `{{dateformat data.born "long"}}`.
88
+
89
+ ## Choose the calendar a note's dates print in
90
+
91
+ Every note reads its dates in one calendar. The infobox's birth and death dates, `dateformat` with no calendar named, and every date in the event sections the build adds to the note's page all print in it. It is the first of:
92
+
93
+ 1. the note's own `data.calendar`, which any note may set and which always wins;
94
+ 2. its culture's calendar — the `data.calendar` of the culture note its `data.culture` names;
95
+ 3. for a place, its parents' calendar — the calendar each place in its `data.parents` reads in, by these same rules;
96
+ 4. the package's `defaultCalendar`, set in the [configuration](../configuration.md#defaultcalendar);
97
+ 5. otherwise none, and a date prints as it was written.
98
+
99
+ ```yaml
100
+ # package-build.config.yaml
101
+ defaultCalendar: commoncal
102
+ ```
103
+
104
+ ```yaml
105
+ # A town in a province that keeps the Vylarian count
106
+ type: place
107
+ subType: settlement
108
+ shortcode: brenford
109
+ data:
110
+ parents: [vylaria]
111
+ ```
112
+
113
+ Brenford names no calendar and no culture, so it reads in Vylaria's. A place whose parents read in **different** calendars is a build error at its `data.parents`, naming the place and each calendar; give the place its own `data.calendar` to settle it. A parent, or a culture, that only reaches the default counts for nothing, so a place among parents that all fall through to `defaultCalendar` takes the default without an error.
114
+
115
+ An event section prints every date in the calendar of the page showing it, whichever note the event is written on: Brenford's chronology reads in Vylaria's count even where an event comes from a history kept in the common calendar. The [format reference](../reference/format-details.md#a-notes-calendar) gives the rules in full.
88
116
 
89
117
  ## Write a recurring date
90
118
 
@@ -1342,7 +1342,7 @@ The first call lists every page tagged `key-concept` alphabetically by title. Th
1342
1342
  An expression in `{{ }}` is replaced by a value, inline in a sentence. The note's own frontmatter is the context, so a field is read by the path a note writes it at.
1343
1343
 
1344
1344
  ```markdown
1345
- {{name.full}} was born on {{dateformat data.calendar data.born}}.
1345
+ {{name.full}} was born on {{dateformat data.born}}.
1346
1346
  There are {{words (sql "SELECT COUNT(*) FROM notes WHERE type = 'being'")}} beings.
1347
1347
  ```
1348
1348
 
@@ -1373,14 +1373,14 @@ There are {{words (sql "SELECT COUNT(*) FROM notes WHERE type = 'being'")}} bein
1373
1373
  | `words` | `number` | The number spelled out for running prose — `1200` becomes one thousand two hundred. Whole numbers only; a decimal is a finding, and `digits` takes one. |
1374
1374
  | `digits` | `number` | The number as a grouped numeral — `1200` becomes 1,200, and a fractional part is kept. |
1375
1375
  | `sql` | `"query"` | The single value a query returns, for use inside a sentence. The query is a quoted string, the same SQL a table fence takes. |
1376
- | `dateformat` | `calendar date ["format"]` | A date written in a calendar. The calendar is an Address or its shortcode, the date is the value a note carries, and the optional third argument names an output pattern. |
1376
+ | `dateformat` | `[calendar] date ["format"]` | A date written in a calendar. The calendar is an Address or its shortcode; left out, it is the note's own calendar — its `data.calendar`, else its culture's, else for a place its parents', else the configured `defaultCalendar` — and a note with none prints the date as written. The date is the value a note carries, and the optional last argument names an output pattern. |
1377
1377
  | `ref` | `address [form="number"\|"full"\|"title"] \| address field="when"\|"until"\|"kind"\|"summary"\|"name"` | A link to a captioned item, by its anchor — `"#thorn"` on this note, `"note-address#thorn"` on another. `form` is `number` (the default, and admitted explicitly), rendering "Figure 13"; `full`, the number and the caption; or `title`, the caption alone. Always renders as a link to the figure, and a link inside the caption contributes only its label text. The number rendered is the one the figure carries on the surface doing the rendering, whichever note names it. With `field` in place of `form`, the address names an event — `"place-ironfells#sack"`, `"#sack"` on this note, or `"lore-founding"` for a note holding one event — and the reference prints that event's `when`, `until`, `kind`, `summary` or `name` as plain text, never a link; a date prints as the note format prints any date. |
1378
1378
 
1379
1379
  <!-- expression-helpers:end -->
1380
1380
 
1381
1381
  A helper that is given the wrong kind of value reports it rather than printing something wrong: `words` refuses a decimal, the four comparisons refuse anything that is not a finite number, and `dateformat` refuses a date it cannot resolve in the calendar it was given.
1382
1382
 
1383
- See [date rules](dates-and-calendars.md) for what `dateformat` accepts and the patterns its third argument names.
1383
+ See [date rules](dates-and-calendars.md) for what `dateformat` accepts, the calendar it uses when none is named, and the patterns its last argument names.
1384
1384
 
1385
1385
  ### An event's date, inline
1386
1386
 
@@ -54,7 +54,7 @@ around its own evaluation.
54
54
 
55
55
  ### Quick reference
56
56
 
57
- 24 top-level keys. `rootDir` is not one of them — a data configuration never
57
+ 25 top-level keys. `rootDir` is not one of them — a data configuration never
58
58
  writes it — and is documented under [Derived values](#derived-values) instead,
59
59
  alongside `foundryPackage`, `homepage`, `author` and `itemBuilders`, whose
60
60
  data-configuration behaviour is also derivation rather than ordinary
@@ -63,6 +63,7 @@ authoring.
63
63
  | Key | Type | Required | Default |
64
64
  | ------------------------------------------- | -------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------- | ------------------------- |
65
65
  | [`currentDate`](#currentdate) | quoted neutral date string | no | `"720.1"` |
66
+ | [`defaultCalendar`](#defaultcalendar) | calendar note shortcode | no | — |
66
67
  | [`contentPackage`](#contentpackage) | string | yes | — |
67
68
  | [`foundryPackage`](#foundrypackage) | string | yes (`.mjs` only — derived in YAML); refused in a `documentation` package | — |
68
69
  | [`homepage`](#homepage) | string | no (`.mjs` only — derived in YAML) | `null` |
@@ -88,7 +89,7 @@ authoring.
88
89
 
89
90
  Any key outside this list is refused:
90
91
 
91
- > `` `<key>` is not a recognized option (expected one of: rootDir, contentPackage, currentDate, foundryPackage, homepage, authors, packageKind, stats, itemBuilders, paths, skipDirectories, forbidGeneratedArt, icons, packs, docs, site, pdf, compatibility, relationships, systems, requiresSystem, packageBuild, publish, changelog). ``
92
+ > `` `<key>` is not a recognized option (expected one of: rootDir, contentPackage, currentDate, defaultCalendar, foundryPackage, homepage, authors, packageKind, stats, itemBuilders, paths, skipDirectories, forbidGeneratedArt, icons, packs, docs, site, pdf, compatibility, relationships, systems, requiresSystem, packageBuild, publish, changelog). ``
92
93
 
93
94
  (`rootDir` appears in that list because it is a key `defineConfig` itself
94
95
  accepts — an `.mjs` configuration authors it directly. A YAML configuration
@@ -195,7 +196,7 @@ build writes its `name` as `params.author`. `null` when `authors` is empty.
195
196
  as an unknown key, at the key's own line, because `authors` is the one list a
196
197
  package names its people in:
197
198
 
198
- > ``package-build config: `author` is not a recognized option (expected one of: rootDir, contentPackage, currentDate, foundryPackage, homepage, authors, packageKind, stats, itemBuilders, paths, skipDirectories, forbidGeneratedArt, icons, packs, docs, site, pdf, compatibility, relationships, systems, requiresSystem, packageBuild, publish, changelog).``
199
+ > ``package-build config: `author` is not a recognized option (expected one of: rootDir, contentPackage, currentDate, defaultCalendar, foundryPackage, homepage, authors, packageKind, stats, itemBuilders, paths, skipDirectories, forbidGeneratedArt, icons, packs, docs, site, pdf, compatibility, relationships, systems, requiresSystem, packageBuild, publish, changelog).``
199
200
 
200
201
  `package.json`'s own `author` is npm's and is not read.
201
202
 
@@ -313,6 +314,29 @@ day. Named calendars, `unknown`, numeric YAML values and malformed dates are
313
314
  configuration errors located at `currentDate`. Birth dates can still use named
314
315
  calendars when the output has the relevant calendar context.
315
316
 
317
+ ### `defaultCalendar`
318
+
319
+ **Type:** string · optional · no default.
320
+
321
+ The shortcode of the `lore` note with `subType: calendar` that prints the dates
322
+ of every note no other rule gives a calendar. A note reads its dates in its own
323
+ `data.calendar`, else its culture's, else — for a place — its parents', and
324
+ only then in this one; with no `defaultCalendar` and nothing else to go on, a
325
+ date prints as it was written. The
326
+ [format reference](reference/format-details.md#a-notes-calendar) gives the
327
+ rules in full.
328
+
329
+ ```yaml
330
+ contentPackage: thalorna
331
+ defaultCalendar: commoncal
332
+ ```
333
+
334
+ A value that is not a lowercase alphanumeric shortcode is a configuration
335
+ error at load. A shortcode that names no calendar note in the package is a
336
+ build error located at `defaultCalendar`:
337
+
338
+ > `defaultCalendar commonkal names no calendar note in thalorna; name the shortcode of a lore note with subType: calendar`
339
+
316
340
  ### `contentPackage`
317
341
 
318
342
  **Type:** string · **Required** · no default.
@@ -275,7 +275,7 @@ Five keys, and each answers a question the build cannot answer for itself:
275
275
  Everything omitted has a default, and the defaults are the conventional
276
276
  HeroicLands layout: the content tree at `assets/content`, the compiled packs at
277
277
  `build/stage/packs`, the intermediates and caches elsewhere under `build/`.
278
- [`configuration.md`](../configuration.md) documents all eighteen top-level keys,
278
+ [`configuration.md`](../configuration.md) documents every top-level key,
279
279
  what each refusal message means, and the five values that are derived rather
280
280
  than authored.
281
281
 
@@ -432,17 +432,55 @@ A named date uses `datefrom <calendar> <date>`, where the final text follows the
432
432
 
433
433
  `package-build datefrom <calendar> <date>` reads the default calendar pattern and prints a canonical day. `package-build dateto <calendar> <canonical-date>` prints the day in the covering era; `--format <name>` chooses another named output pattern. The [authoring guide](../authoring/dates-and-calendars.md) gives a complete example.
434
434
 
435
+ ##### A note's calendar
436
+
437
+ A note's dates print in one calendar, the first of:
438
+
439
+ 1. its own `data.calendar`, which every note type accepts, and which always
440
+ wins;
441
+ 2. its culture's calendar: the culture note its `data.culture` names, resolved
442
+ by these same rules — in effect that culture's own `data.calendar`;
443
+ 3. for a `place`, its parents' calendar: each note its `data.parents` names,
444
+ resolved by these same rules;
445
+ 4. the package's [`defaultCalendar`](../configuration.md#defaultcalendar);
446
+ 5. otherwise none, and a date prints as it was written.
447
+
448
+ Steps 2 and 3 take only a calendar that some note states. A culture or a
449
+ parent that itself falls through to `defaultCalendar` contributes nothing, so a
450
+ place whose parents all fall through takes the default. Parents that resolve to
451
+ **two or more distinct calendars** are a build error at the place's
452
+ `data.parents`, naming the place and each calendar with the parent it came
453
+ through:
454
+
455
+ ```text
456
+ Places/Edge.md:6:3: error: place edge: data.parents resolve to different calendars (thalorna-note-lore-commoncal via north, thalorna-note-lore-vrcal via south); state data.calendar
457
+ ```
458
+
459
+ Stating `data.calendar` on the place settles it. A cycle through
460
+ `data.parents` or `data.culture` contributes nothing, and the chain goes on to
461
+ `defaultCalendar`. A culture or parent in another package contributes the
462
+ `data.calendar` its published entry states, and nothing else.
463
+
464
+ The calendar decides what a reader sees wherever the note prints a date: the
465
+ infobox's `born` and `died`, `dateformat` with no calendar named, the `events`
466
+ relation's `whenText`, and every date in the [event views](#event-views) the
467
+ note's page shows.
468
+
435
469
  Markdown prose accepts inline `{{...}}` expressions. A property path reads
436
470
  the note's frontmatter, and `dateformat` converts a date to the reckoning used by a
437
- specified calendar. The first argument names a calendar note by shortcode or
438
- Address, or one of its eras by `<calendar>.<era>`. Use a full Address when a
439
- short name is ambiguous. The second argument is a canonical date or a
440
- frontmatter property holding a date. An optional third argument names an output
441
- format from the calendar note. An explicit era that does not cover the date is an error.
471
+ calendar. The first argument names a calendar note by shortcode or Address, or
472
+ one of its eras by `<calendar>.<era>`; left out, the calendar is
473
+ [the note's own](#a-notes-calendar), and a note with none prints the date as
474
+ written. Use a full Address when a short name is ambiguous. The date is a
475
+ canonical date or a frontmatter property holding a date. An optional last
476
+ argument names an output format from the calendar note. With two arguments,
477
+ the first is the calendar unless it is a date that names no calendar, so
478
+ `{{dateformat data.born "long"}}` prints the note's date in its own calendar's
479
+ `long` format. An explicit era that does not cover the date is an error.
442
480
 
443
481
  ```markdown
444
482
  {{name.full}} was born on {{dateformat "vrcal" data.born}}.
445
- {{dateformat data.calendar data.born}}
483
+ {{dateformat data.born}}
446
484
  The event fell on {{dateformat "vrcal" "300.25"}}.
447
485
  {{and (gt 3 5) (lt 4 2)}}
448
486
  {{sql "SELECT COUNT(*) FROM notes WHERE type = 'being'"}}
@@ -870,6 +908,7 @@ noticed:
870
908
  | `seeAlso` | list of `{ address, gloss? }` | Deliberate cross-references in reading order, shown on the site, in journals and in books. |
871
909
  | `seeAlso[].address` | a note `Address` | Required; the whole note to read next. |
872
910
  | `seeAlso[].gloss` | `string` | An optional short phrase saying why to follow the reference. |
911
+ | `calendar` | `Address` | The calendar note this note's dates print in — see [a note's calendar](#a-notes-calendar). |
873
912
  | `id` | `string` | A pinned document identity. |
874
913
  | `pack` | `string` | The shared compendium route. |
875
914
  | `packFolder` | `Address` or pack map | The shared compendium folder Address. |
@@ -2972,14 +3011,22 @@ ORDER BY whenSort
2972
3011
  ```
2973
3012
  ````
2974
3013
 
2975
- | Column | What it holds |
2976
- | ------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
2977
- | `note` | The canonical Address of the note the event is written on. |
2978
- | `address` | The event's own Address: `note#id` where the event has an `id`, the note's Address alone where it has none. |
2979
- | `id`, `kind`, `depth`, … | Every key of an event, each its own column under its own name — `id`, `kind`, `depth`, `when`, `until`, `recurs`, `summary`, `standing`, `names`, `where`, `who`, `follows`, `accounts`, `unresolved`, `sources`, `stated`. |
2980
- | `whenSort`, `untilSort` | The date's position on the canonical axis: the canonical year, with a day as its fraction of the year where the calendar states the year's length. `ORDER BY whenSort` is date order. |
2981
- | `whenText`, `untilText` | The date as a reader sees it, printed as `dateformat` prints it in the calendar the owning note names in `data.calendar`, else the one the world note names, else in the era the date itself names; `~` marks an approximate date. A date that does not resolve keeps the text its note wrote. |
2982
- | `whenYear`, `untilYear` | The canonical year alone, for a filter such as `whenYear BETWEEN 200 AND 299`. |
3014
+ | Column | What it holds |
3015
+ | ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
3016
+ | `note` | The canonical Address of the note the event is written on. |
3017
+ | `address` | The event's own Address: `note#id` where the event has an `id`, the note's Address alone where it has none. |
3018
+ | `id`, `kind`, `depth`, … | Every key of an event, each its own column under its own name — `id`, `kind`, `depth`, `when`, `until`, `recurs`, `summary`, `standing`, `names`, `where`, `who`, `follows`, `accounts`, `unresolved`, `sources`, `stated`. |
3019
+ | `whenSort`, `untilSort` | The date's position on the canonical axis: the canonical year, with a day as its fraction of the year where the calendar states the year's length. `ORDER BY whenSort` is date order. |
3020
+ | `whenText`, `untilText` | The date as a reader of the owning note sees it, printed as `dateformat` prints it in [the owning note's calendar](#a-notes-calendar), else in the era the date itself names; `~` marks an approximate date. A date that does not resolve keeps the text its note wrote. |
3021
+ | `whenIn`, `untilIn` | The date in every calendar: a struct keyed by each calendar note's canonical Address, holding the date as `dateformat` prints it there. `whenIn['thalorna-note-lore-commoncal']` reads it in that calendar; a date that calendar cannot print reads as in `whenText`. |
3022
+ | `whenYear`, `untilYear` | The canonical year alone, for a filter such as `whenYear BETWEEN 200 AND 299`. |
3023
+
3024
+ A fence chooses which calendar its dates print in. `whenText` prints each
3025
+ row in the calendar of the note that owns the event, so a table gathering the
3026
+ events of notes with different calendars reads in more than one count;
3027
+ `whenIn['<calendar Address>']` prints every row in one. The generated [event
3028
+ views](#event-views) do the second, in the calendar of the page showing the
3029
+ view.
2983
3030
 
2984
3031
  `when` and `until` read as the text the note wrote, `~` included. The sort
2985
3032
  keys and years are positions on the canonical axis, which counts a year `0`
@@ -3777,7 +3824,7 @@ The build appends sections to the end of a note's body that list the events conc
3777
3824
  | Accounts | `accounts` | an `affiliation`, a `place`, a `lore` note of `subType: culture` | each `accounts[]` entry the note gives, with the event it concerns | When, Event, What they say, Agrees |
3778
3825
  | What followed | `followed` | a note holding events | events whose `follows[].event` names one of this note's events, with how | When, What followed, How |
3779
3826
 
3780
- - **Every view's first column is When**, the event's date written in the setting's calendar — the `events` relation's `whenText` column. An event whose date does not resolve shows the text its note wrote.
3827
+ - **Every view's first column is When**, every row's date written in [the calendar of the page showing the view](#a-notes-calendar), whichever note owns the event, so one table reads in one count. The fence reads `whenIn['<calendar Address>']` for that calendar; on a page with no calendar it reads `whenText`, each date in its owning note's calendar. An event whose date does not resolve shows the text its note wrote.
3781
3828
  - **A chronology's Felt here column appears only where some row is felt in the place.** A place whose rows are all events that happened there, or world events, has the When and What happened columns alone.
3782
3829
  - **A chronology is always set across the full page width in a PDF book**, whatever its columns.
3783
3830
  - **The views follow everything the author wrote**, in the order of the [generated sections](#generated-sections).
@@ -4080,7 +4127,6 @@ See [a being's subtype describes the subject](#a-beings-subtype-describes-the-su
4080
4127
  | `gender` | `male \| female \| nonbinary \| none \| other` | Gender of the being — see [a being's look](#a-beings-look) |
4081
4128
  | `species` | `Address` | Being's species (lore) |
4082
4129
  | `born` | `date \| unknown` | When the being was born; absent, it was never born |
4083
- | `calendar` | `Address` | Calendar note that prints the being's dates |
4084
4130
  | `died` | `date \| unknown` | When the being died; absent, it is alive |
4085
4131
  | `age` | `34 \| ~34` | Age in years, stated only to override what `born` says; `~` marks an estimate |
4086
4132
  | `ageYears` | `number` | Written by the compiler beside an `age` estimate — the `~` stripped; a note never authors this |
@@ -6982,7 +7028,6 @@ level with no background prints nothing.
6982
7028
  | `data` property | Values | Description |
6983
7029
  | --------------------------------- | -------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------- |
6984
7030
  | `events` | event entries — see [Events](#events) | What happened to or at this subject: dated, attributed events |
6985
- | `calendar` | `Address` | Calendar note that prints this place's dates |
6986
7031
  | `demonym` | `string` | What a person from this place is called — a Vylarian |
6987
7032
  | `purpose` | `placeCharacter` tag | The reason a settlement, site, or structure exists |
6988
7033
  | `lore` | `Address[]` | Lore concerning this place — its peoples, its law, its calendar, its history |
@@ -32,14 +32,15 @@ These are the only keys a note writes at its top level, in the order the formatt
32
32
 
33
33
  These fields are accepted by every note type. A field's value can still be irrelevant to a particular output; the build reports that where it can. `data.icon` is not shared: it is listed under each type whose document carries art.
34
34
 
35
- | Field | Shape | Meaning |
36
- | ----------------- | ----------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- |
37
- | `data.seeAlso` | list of `{ address, gloss? }` | Deliberate cross-references in reading order, with optional glosses; shown on the site, in journals and in books. |
38
- | `data.id` | string | A fixed document identity when the derived identity is unsuitable. |
39
- | `data.pack` | string | The shared compendium route, with a system block overriding it. |
40
- | `data.packFolder` | an Address, or a map of Addresses keyed by pack | The shared compendium folder Address, with a system block overriding it. |
41
- | `data.harnworld` | `{ realm?, ritual? }` | HârnWorld source details shared by every system — `realm` and `ritual`. |
42
- | `data.banner` | an Address | The page's hero image — an `image` address, cut to 1792×768, or `none` to decline a hero image. Reaches no compiled document. |
35
+ | Field | Shape | Meaning |
36
+ | ----------------- | ----------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
37
+ | `data.seeAlso` | list of `{ address, gloss? }` | Deliberate cross-references in reading order, with optional glosses; shown on the site, in journals and in books. |
38
+ | `data.calendar` | an Address | The calendar this note's dates print in, as a calendar lore note. Absent, the note takes its culture's calendar, then — for a place — its parents', then the configured `defaultCalendar`. |
39
+ | `data.id` | string | A fixed document identity when the derived identity is unsuitable. |
40
+ | `data.pack` | string | The shared compendium route, with a system block overriding it. |
41
+ | `data.packFolder` | an Address, or a map of Addresses keyed by pack | The shared compendium folder Address, with a system block overriding it. |
42
+ | `data.harnworld` | `{ realm?, ritual? }` | HârnWorld source details shared by every system — `realm` and `ritual`. |
43
+ | `data.banner` | an Address | The page's hero image — an `image` address, cut to 1792×768, or `none` to decline a hero image. Reaches no compiled document. |
43
44
 
44
45
  **Inner keys** — a key a field's value does not declare is an error at its own line and column.
45
46
 
@@ -71,7 +72,6 @@ These fields are accepted by every note type. A field's value can still be irrel
71
72
  | `data.gender` | string | One of `female`, `male`, `nonbinary`, `none` or `other`. `none` says the being has no gender; an absent field says its gender is unrecorded. See [a being's look](format-details.md#a-beings-look) for what each word means. |
72
73
  | `data.species` | an Address | The being's species, as a lore note. |
73
74
  | `data.born` | string | When the being was born — a date, or `unknown` where the birth is unrecorded. Absent, the being was never born. |
74
- | `data.calendar` | an Address | Calendar that displays this being's dates. |
75
75
  | `data.died` | string | When the being died — a date, or `unknown` where the death is unrecorded. Absent, the being is alive. |
76
76
  | `data.age` | string | Age in years, stated only to override what `born` says — `34`, or `~34` for an estimate. Unstated beside a dated `born` it is computed; unstated beside an unknown or absent `born` the age is unknown. |
77
77
  | `data.ageYears` | number | Written by the compiler beside an `age` estimate — the `~` stripped, the magnitude alone. A note never authors this. |
@@ -867,7 +867,6 @@ Lore records in-world knowledge. A `culture` describes a people; a `custom` desc
867
867
  | Field | Shape | Meaning |
868
868
  | -------------------------------------- | ----------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
869
869
  | `data.events` | list of event entries | What happened to or at this subject — each entry one dated, attributed event: a founding, a war, a fall, or an occasion that recurs. |
870
- | `data.calendar` | an Address | Calendar that displays this place's dates. |
871
870
  | `data.demonym` | string | What a person from this place is called — a Vylarian. |
872
871
  | `data.purpose` | string | The reason this settlement, site, or structure exists, selected from its placeCharacter tags. |
873
872
  | `data.lore` | list of Addresses | Lore concerning this place — its peoples, its law, its calendar, its history. |
@@ -15,7 +15,6 @@ types:
15
15
  - gender
16
16
  - species
17
17
  - born
18
- - calendar
19
18
  - died
20
19
  - age
21
20
  - ageYears
@@ -315,7 +314,6 @@ types:
315
314
  place:
316
315
  data:
317
316
  - events
318
- - calendar
319
317
  - demonym
320
318
  - purpose
321
319
  - lore
@@ -86,6 +86,7 @@
86
86
  */
87
87
 
88
88
  import { isAddressSegment } from "./address-charset.mjs";
89
+ import { configFileIn, resolveNoteCalendars } from "./note-calendars.mjs";
89
90
  import { cultureInheritance, reportCultureParents } from "./culture-parents.mjs";
90
91
  import {
91
92
  formatDiagnostic,
@@ -162,7 +163,7 @@ import { resolveNoteId } from "./note-ids.mjs";
162
163
  // The `package:` refusal, so a note declaring one is reported where it is
163
164
  // rather than as a bare abort.
164
165
  import { assertNoDeclaredPackage } from "./note-package.mjs";
165
- import { loadPackConfig } from "./pack-config.mjs";
166
+ import { CONFIG_FILENAMES, loadPackConfig } from "./pack-config.mjs";
166
167
  // The record accessors, which live apart so that a module the compilers load
167
168
  // can read a record without importing this one and closing a cycle.
168
169
  // Re-exported because this is where callers have always addressed them.
@@ -1062,6 +1063,10 @@ export function resolveCultures(
1062
1063
  * reader receives each as a finding, and the emitter throws once, listing
1063
1064
  * every one.
1064
1065
  * @param {string} [options.currentDate="720.1"] - Setting-wide neutral current date.
1066
+ * @param {string} [options.defaultCalendar] - The calendar of last resort, a
1067
+ * calendar note's shortcode; see {@link module:engine/note-calendars}.
1068
+ * @param {string} [options.configFile] - The configuration file, where an
1069
+ * unknown `defaultCalendar` is reported.
1065
1070
  * @param {(key: string) => ({culture?: unknown}|undefined)} [options.foreignRecord] -
1066
1071
  * Another package's entry for a canonical address, read for a parent that
1067
1072
  * package publishes; see {@link resolveCultures}.
@@ -1079,6 +1084,8 @@ export function collectContentIndex(
1079
1084
  {
1080
1085
  contentPackage,
1081
1086
  currentDate,
1087
+ defaultCalendar,
1088
+ configFile,
1082
1089
  skipDirectories,
1083
1090
  assetsBase,
1084
1091
  manifest,
@@ -1234,7 +1241,17 @@ export function collectContentIndex(
1234
1241
 
1235
1242
  // Every note record exists and every Address in it is a tuple, so a parent
1236
1243
  // is found by its canonical address. Before the sort, which reads nothing
1237
- // the pass writes.
1244
+ // the pass writes. Calendars first: they read the culture a note states,
1245
+ // which the culture pass then replaces with the one it resolves.
1246
+ resolveNoteCalendars(records, {
1247
+ contentPackage,
1248
+ defaultCalendar,
1249
+ configFile,
1250
+ contentBase,
1251
+ foreignRecord,
1252
+ problems,
1253
+ parsed,
1254
+ });
1238
1255
  resolveCultures(records, { contentPackage, contentBase, foreignRecord, problems, parsed });
1239
1256
  const cultureNotes = records
1240
1257
  .filter((record) => isNoteRecord(record) && record.type === "lore")
@@ -1459,6 +1476,8 @@ export function indexRecordsFor({
1459
1476
  return collectContentIndex(tree, {
1460
1477
  contentPackage: resolved.contentPackage,
1461
1478
  currentDate: resolved.currentDate,
1479
+ defaultCalendar: resolved.defaultCalendar ?? undefined,
1480
+ configFile: configFileIn(resolved.rootDir, CONFIG_FILENAMES),
1462
1481
  addressContext: {
1463
1482
  ...noteAddressContext(resolved),
1464
1483
  declaresAddress: dependencyAddresses(resolved),
@@ -239,6 +239,9 @@ export function buildLinkIndex(
239
239
  const body = splitFrontmatter(raw)?.body ?? raw;
240
240
  const type = record.type.toLowerCase();
241
241
  const note = { file: absPath, rel, fm, body, raw, type };
242
+ // The calendar the index resolved, which the reckoning context built
243
+ // over these notes hands to every date printed for this note.
244
+ if (record.resolvedCalendar) note.resolvedCalendar = record.resolvedCalendar;
242
245
  const hierarchyProblems = cultureRecordProblems(record);
243
246
  note.inheritedCultureFields = cultureInheritedFields(record);
244
247
  if (hierarchyProblems)