@heroiclands/package-build 23.8.0 → 23.9.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.
Files changed (57) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/bin/content-commands.mjs +12 -4
  3. package/docs/api.md +328 -28
  4. package/docs/authoring/first-note.md +1 -1
  5. package/docs/authoring/links-and-markup.md +140 -56
  6. package/docs/commands.md +19 -10
  7. package/docs/content-format.md +2 -2
  8. package/docs/diagnostics.md +1 -1
  9. package/docs/guides/packs.md +2 -1
  10. package/docs/guides/reference-pages.md +86 -63
  11. package/docs/reference/format-details.md +269 -123
  12. package/docs/reference/note-types.md +184 -170
  13. package/engine/anchors.mjs +7 -20
  14. package/engine/compile-corpus.mjs +3 -2
  15. package/engine/content-format.yaml +1 -0
  16. package/engine/content-index.mjs +412 -11
  17. package/engine/content-links.mjs +43 -15
  18. package/engine/content-tables.mjs +8 -4
  19. package/engine/culture-reach.mjs +61 -34
  20. package/engine/data-keys.mjs +3 -1
  21. package/engine/dictionary-layout.mjs +151 -19
  22. package/engine/dictionary-rows.mjs +129 -79
  23. package/engine/foundry-entries.mjs +21 -3
  24. package/engine/glossary-entries.mjs +11 -11
  25. package/engine/heading-attributes.mjs +21 -28
  26. package/engine/helpers.mjs +12 -6
  27. package/engine/journals.mjs +4 -1
  28. package/engine/language-data.mjs +53 -0
  29. package/engine/language-terms.mjs +86 -0
  30. package/engine/language-words.mjs +2 -2
  31. package/engine/lexical-sections.mjs +247 -0
  32. package/engine/note-claims.mjs +2 -0
  33. package/engine/note-state.mjs +9 -3
  34. package/engine/note-vocabulary.mjs +7 -6
  35. package/engine/pdf-build.mjs +9 -9
  36. package/engine/site-build.mjs +16 -2
  37. package/engine/sql-tables.mjs +262 -43
  38. package/engine/stub-lint.mjs +6 -2
  39. package/engine/web-wikilinks.mjs +8 -6
  40. package/engine/word-rows.mjs +20 -12
  41. package/engine/word-terms.mjs +15 -6
  42. package/package.json +1 -1
  43. package/types/engine/content-index.d.mts +65 -1
  44. package/types/engine/content-tables.d.mts +2 -1
  45. package/types/engine/culture-reach.d.mts +22 -2
  46. package/types/engine/data-keys.d.mts +7 -0
  47. package/types/engine/dictionary-layout.d.mts +35 -4
  48. package/types/engine/dictionary-rows.d.mts +9 -0
  49. package/types/engine/foundry-entries.d.mts +3 -1
  50. package/types/engine/heading-attributes.d.mts +2 -2
  51. package/types/engine/helpers.d.mts +4 -1
  52. package/types/engine/language-terms.d.mts +7 -0
  53. package/types/engine/lexical-sections.d.mts +95 -0
  54. package/types/engine/note-state.d.mts +2 -1
  55. package/types/engine/site-build.d.mts +4 -1
  56. package/types/engine/sql-tables.d.mts +33 -3
  57. package/types/engine/word-rows.d.mts +9 -7
@@ -334,7 +334,7 @@ A column's heading is its SQL alias, so `AS "Name"` is what a reader sees. A cel
334
334
  | `glossary` | One row per entry a culture or an affiliation writes in `data.glossary`, with the `note` it belongs to and the `language` it resolves to. |
335
335
  | `language_lineage` | One row per language note and each language it inherits words from through `data.family`, itself included at `depth` 0. Join it to `words` for a language's words with inherited ones. |
336
336
  | `dictionary` | One row per word per language it is listed under, inherited words included, with its `depth`. See [dictionaries and glossaries](#dictionaries-and-glossaries). |
337
- | `terms` | One row per glossary entry, rank or office per note that owns it, with the `cultures` it reaches. See [dictionaries and glossaries](#dictionaries-and-glossaries). |
337
+ | `terms` | One row per glossary entry, rank or office per note that owns it, with its `cultures` associations. See [dictionaries and glossaries](#dictionaries-and-glossaries). |
338
338
  | `register` | One row per name a language note writes in `data.register`, with its `tongue`, `kind`, `gloss` and `why`, its pieces as `built_text`, and the `notes` carrying the name, derived from the index. |
339
339
  | `tongues` | One row per tag a language note writes in `data.tongues`: `tag`, `meaning` and `tongue_language`. |
340
340
  | `retired` | One row per form a language note writes in `data.retired`: `form`, `instead` as a list, and `why`. |
@@ -840,12 +840,12 @@ See [a language's register, sounds and history](../reference/format-details.md#a
840
840
 
841
841
  A braced list may follow the language on the opening fence. Four attributes are accepted, and anything else is reported by name.
842
842
 
843
- | Attribute | Value | Default | What it does |
844
- | --------------- | ----------------------------------- | ------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
845
- | `allow-empty` | `true` or `false` | `false` | Whether a query selecting no rows is allowed. A zero-row result is a finding otherwise, because a table that silently prints nothing is the commonest way a query goes wrong unnoticed. |
846
- | `section-level` | an integer from 1 through 6 | `2` | The heading level given to each group a `_section` column creates, and to each letter of a dictionary or a glossary. |
847
- | `key` | `true`, `false` or `only` | `true` | Whether a dictionary or a glossary prints its key. `false` leaves it out; `only` prints the key for the fence's rows and no letter or entry, so a page with one `key=false` fence per heading states its key once. Refused on a table. |
848
- | `layout` | `table`, `dictionary` or `glossary` | `table` | How the result is set: as a table, as a dictionary of words, or as a glossary of terms — see [dictionaries and glossaries](#dictionaries-and-glossaries). |
843
+ | Attribute | Value | Default | What it does |
844
+ | --------------- | ----------------------------------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
845
+ | `allow-empty` | `true` or `false` | `false` | Whether a query selecting no rows is allowed. A zero-row result is a finding otherwise, because a table that silently prints nothing is the commonest way a query goes wrong unnoticed. |
846
+ | `section-level` | an integer from 1 through 6 | `2` | The heading level given to each group a `_section` column creates, and to each letter of a dictionary or a glossary. |
847
+ | `key` | `true`, `false` or `only` | `true` | Whether a dictionary or a glossary prints its key. `false` omits the key and any dictionary verb table; `only` prints the key for the fence's rows and no table, letter or entry. Refused on a table. |
848
+ | `layout` | `table`, `dictionary` or `glossary` | `table` | How the result is set: as a table, as a dictionary of words, or as a glossary of terms — see [dictionaries and glossaries](#dictionaries-and-glossaries). |
849
849
 
850
850
  ````markdown
851
851
  ```sql {allow-empty=true section-level=3}
@@ -873,81 +873,165 @@ See [SQL details](../reference/format-details.md#content-tables) for query optio
873
873
 
874
874
  ### Dictionaries and glossaries
875
875
 
876
- Two layouts set a result as paragraphs under letter headings rather than as a table. **`layout=dictionary` sets words**, read from the `dictionary` relation: each with its class, its etymology, its senses, numbered where a word's `gloss` lists several, and the notes it is cited from — every note whose prose marks the word in italics. **`layout=glossary` sets terms**, read from the `terms` relation: a culture's or an affiliation's own glossary entries and an affiliation's ranks and offices, each sense tagged with the body or culture it comes from. Each needs the `name`, `senses` and `usage` columns, and a query that leaves one out is reported, naming the column.
876
+ The build supplies a note's dictionary or glossary from its frontmatter on
877
+ every surface: the site, the Foundry journal and the book.
877
878
 
878
- **The query decides what appears**, and any note may carry either fence. These are the common ones:
879
+ - **A language note with nonempty `data.words` receives a Dictionary section.** It
880
+ contains the language's full dictionary as one alphabetical list, with
881
+ letter headings, each word's field beside it and the key printed once. A
882
+ language with absent or empty `data.words` receives no Dictionary section.
883
+ - **A culture or affiliation with nonempty `data.glossary` receives a Glossary
884
+ section.** It contains only the entries that owner declares. Ranks and
885
+ offices stand in the affiliation's own sections; they enter its glossary
886
+ only when an explicit glossary entry names them. A note with absent or
887
+ empty `data.glossary` receives no Glossary section.
879
888
 
880
- | What | Layout | Query |
881
- | ----------------------------------------- | ------------ | --------------------------------------------------------------------------------- |
882
- | A language's dictionary | `dictionary` | `SELECT * FROM dictionary WHERE language = '<the language's Address>'` |
883
- | A language's common words, on any page | `dictionary` | `SELECT * FROM dictionary WHERE language = '<the language's Address>' AND common` |
884
- | A culture's glossary | `glossary` | `SELECT * FROM terms WHERE list_contains(cultures, '<the culture's Address>')` |
885
- | An affiliation's glossary | `glossary` | `SELECT * FROM terms WHERE owner = '<the affiliation's Address>'` |
886
- | The setting's _Dictionary of the Tongues_ | `dictionary` | `SELECT * EXCLUDE (common) FROM dictionary WHERE depth = 0` |
887
- | The setting's _Glossary_ | `glossary` | `SELECT * EXCLUDE (common) FROM terms` |
889
+ The sections follow the note's authored text. An existing heading with
890
+ `{#dictionary}` or `{#lexicon}` receives the dictionary after its authored
891
+ text; `{#glossary}` does the same for a glossary. The heading and its
892
+ introduction stay in place, with one generated list under it:
888
893
 
889
- A language's dictionary lists the words it inherits through `data.family` beside its own; `depth = 0` lists each word once, under the language that states it. A culture's glossary holds the culture's own terms and the ranks, offices and glossary terms of every affiliation that reaches it. The two setting pages leave out `common`, so they mark no entry: which words are common is a culture's judgement.
890
-
891
- The most common Vylari words, on the Vylarian culture's page:
894
+ ```markdown
895
+ ## Lexicon {#lexicon}
896
+
897
+ The tongue of Provenzia, whose _solmère_ is its own word.
898
+ ```
899
+
900
+ A dictionary belongs to a language; a glossary belongs to one culture or
901
+ affiliation. A glossary entry belongs for one of three reasons: a common word
902
+ in the owner's core vocabulary, a dictionary word with a context meaning,
903
+ or a phrase, compound or term of art outside the dictionary with its own
904
+ gloss. The [worked culture glossary](../reference/format-details.md#glossary-terms)
905
+ selects `solmère` as a common word, gives `amère` a context meaning and glosses
906
+ `blood-field` as a term outside the dictionary. Its frontmatter supplies the
907
+ whole default Glossary section. There are no setting-wide dictionary or
908
+ glossary pages.
909
+
910
+ A dictionary noun prints its gender from its existing class and its plural
911
+ where one is available: **marendos** _n. m., pl. marendoi_. An explicit
912
+ word plural wins; otherwise the language's optional `grammar.plural` rule
913
+ replaces the first matching literal ending. The language's optional
914
+ `grammar.verbs` pattern prints once as a small head table, after the key and
915
+ before the letters. Empty verb endings print as "unchanged". Grammar is
916
+ never inherited through `family`, and absent grammar leaves the dictionary
917
+ unchanged. The [worked noun and verb example](../reference/format-details.md#optional-noun-and-verb-forms)
918
+ shows both declarations. Only supplied shapes are checked; words are never
919
+ checked against the rule, and no language must state grammar.
920
+
921
+ An entry naming a word of its resolved language without a `definition`
922
+ carries the word's dictionary senses and class, with a link to the word's
923
+ entry on its language page. An entry with a `definition` carries that meaning
924
+ instead, retaining its word link and class when it names a dictionary word.
925
+ Ranks, offices and definitions carry their source body or culture beside
926
+ their senses. Common choices belong to each owner: a culture and an
927
+ affiliation using the same language choose independently, and a word may be
928
+ common in one glossary and absent from another. Dictionaries mark nothing
929
+ common.
930
+
931
+ Public dictionaries omit citations to GM-only notes. `links` checks generated
932
+ tables in the source note's audience: public notes use public rows, and GM
933
+ notes retain GM citations. An authored link from a public note to a GM-only
934
+ note is still reported.
935
+
936
+ #### Custom views
937
+
938
+ An `sql` fence with `layout=dictionary` or `layout=glossary` sets a custom view
939
+ of the same relations. A dictionary view reads `dictionary`; a glossary view
940
+ reads `terms`. Each needs the `name`, `senses` and `usage` columns; a query
941
+ that leaves one out is reported, naming the column.
942
+
943
+ The optional `plural` and `verbs` columns supply noun plurals and the head
944
+ table in a custom dictionary view; `SELECT *` includes both. A projection
945
+ that omits these columns omits that presentation, as omitting `class` omits
946
+ word classes. With the default or `key=true`, each selected language's pattern
947
+ appears once per dictionary result, even when no abbreviation key is needed.
948
+ `key=false` omits both the key and the verb table from a fragment;
949
+ `key=only` prints only the abbreviation key. Separate head-enabled fences
950
+ each print their own tables.
951
+
952
+ For a custom list of one culture's common entries:
892
953
 
893
954
  ````markdown
894
955
  ## Words Everyone Knows {#common-words}
895
956
 
896
- ```sql {layout=dictionary section-level=3}
897
- SELECT * FROM dictionary WHERE language = 'thalorna-note-skill-vylarilng' AND common
898
- ```
899
- ````
900
-
901
- A culture's glossary:
902
-
903
- ````markdown
904
- ## Glossary {#glossary}
905
-
906
957
  ```sql {layout=glossary section-level=3}
907
- SELECT * FROM terms WHERE list_contains(cultures, 'thalorna-note-lore-provenzianclt')
958
+ SELECT * FROM terms WHERE owner = 'thalorna-note-lore-provenzianclt' AND common
908
959
  ```
909
960
  ````
910
961
 
911
- A page that sets several fences, one per heading, states its key once: a `key=only` fence at the head over all the rows the page sets, then `key=false` on each fence below it, so the abbreviations are not repeated under every heading. The `key=only` fence prints the key for its rows and no letter heading and no entry:
962
+ A custom dictionary view can select one field from the language's dictionary:
912
963
 
913
964
  ````markdown
914
- ## Words {#words}
915
-
916
- ```sql {layout=dictionary key=only}
917
- SELECT * FROM dictionary WHERE language = 'thalorna-note-skill-vylarilng' AND depth = 0
918
- ```
919
-
920
- ### Love and its conduct
965
+ ## Love and its conduct
921
966
 
922
- ```sql {layout=dictionary key=false section-level=4}
923
- SELECT d.* FROM dictionary d JOIN words w ON w.language = d.language AND w.form = d.name
924
- WHERE d.language = 'thalorna-note-skill-vylarilng' AND w.field = 'Love and its conduct'
967
+ ```sql {layout=dictionary section-level=3}
968
+ SELECT * FROM dictionary
969
+ WHERE language = 'thalorna-note-skill-provnzlng' AND field = 'Love and its conduct'
925
970
  ```
926
971
  ````
927
972
 
928
- `key`, whatever its value, sets a dictionary's or a glossary's key, so it is refused on a fence set as a table.
973
+ A custom view with several fences can print its key once: a `key=only` fence
974
+ over all selected rows, then `key=false` on each fence below it. The
975
+ `key=only` fence prints only the abbreviation key. `key`, whatever its value,
976
+ is refused on a fence set as a table. The automatic Dictionary section
977
+ prints its own key once for the full alphabetical list.
929
978
 
930
979
  Either layout prints, in order:
931
980
 
932
- - **a key**, once, unless the fence sets `key=false`, and nothing else where it sets `key=only`, listing only the abbreviations the result uses — a language's tag, and for words a class or a modifier — each with what it stands for, and the common mark where an entry carries it;
933
- - **a heading per letter**, at `section-level`, accents taken off and apostrophes skipped, so `'ostal` files under O and `Écuyer` under E;
934
- - **one paragraph per entry**: `•` for a common entry, the headword in bold, its pronunciation between slashes, its language tag in italics, the senses, and after a dash the notes it is cited from, as links.
981
+ - **a key**, once, unless the fence sets `key=false`, and nothing else where
982
+ it sets `key=only`, listing the abbreviations the result uses — a language's
983
+ tag, a word's class or a modifier — each with its expansion; a glossary also
984
+ explains the common mark where an entry carries it;
985
+ - **a heading per letter**, at `section-level`, accents taken off and
986
+ apostrophes skipped, so `'ostal` files under O and `Écuyer` under E;
987
+ - **one paragraph per entry**: a glossary's `•` for a common entry, the
988
+ headword in bold, its pronunciation between slashes, its language tag and
989
+ word class in italics where present, its senses, and after a dash the notes
990
+ it is cited from, as links.
935
991
 
936
- A word also carries its class beside the tag and its etymology in brackets:
992
+ A dictionary entry gives its field in italic parentheses and its etymology
993
+ in brackets:
937
994
 
938
995
  ```markdown
939
- • **solmère** /sol-MEH-reh/ _n. m._ [fr. Élav. _sólmerë_] Longing; the ache for a time that is gone. — [[thalorna-note-lore-lastshrine|The Song of the Last Shrine]]
996
+ **solmère** /sol-MEH-reh/ _n. m._ (_Love and its conduct_) [fr. Élav. _sólmerë_] Longing; the ache for a person, a place or a time that is gone. — [[thalorna-note-lore-lastshrine|The Song of the Last Shrine]]
940
997
  ```
941
998
 
942
- A term carries no class, and each of its senses names its source. A glossary sets the rows sharing a headword and a language as one entry, numbering their senses in the order the rows arrive and printing a sense several bodies give once, with all of them:
999
+ A glossary entry naming a dictionary word uses the word's senses when
1000
+ `definition` is absent; a list of dictionary glosses supplies every sense.
1001
+ Those senses carry no body label. An entry with a `definition` uses that
1002
+ meaning and labels it with the owner, retaining its word link and class
1003
+ where it names a dictionary word. On the owner's own page, a sense attributed
1004
+ only to that owner omits the repeated label; other bodies and shared senses
1005
+ keep their labels. A glossary sets the rows sharing a headword
1006
+ and a language as one entry, numbering their senses in query order and
1007
+ printing a sense several bodies give once, with all of them:
943
1008
 
944
1009
  ```markdown
945
- **Sworn Knight** **1** (_Duchy of Aubrac_) Holds land of the duke. **2** (_Kingdom of Provenzia_) Holds land of the crown. **3** (_Provenzia_) A knight bound by oath to a lord. — [[thalorna-note-affiliation-duchy|Duchy of Aubrac]], [[thalorna-note-affiliation-kingdom|Kingdom of Provenzia]], [[thalorna-note-lore-provenzianclt|Provenzia]]
946
- ```
947
-
948
- The entries are ordered by the layout, not by the query: by letter, then by `name_sort`. **The language tag prints where the entry's language is not the page's own**: a language note's own language, a culture's `data.language`, or the language an affiliation's glossary resolves to. It is the language note's `data.abbreviation`, or its full name where it states none. The etymology never repeats the entry's own language: a borrowed word prints `[fr. Élav. _sólmerë_]`, a built word its pieces each with its gloss, `[_am-_ "love" + _-ère_ "one who"]`, an inherited word its older form, `[*_hur_]` or `[Hel. *_thalass_]`, and a root word its root and pattern. A citation of a note with no published page prints as its name.
949
-
950
- A culture's or an affiliation's `data.glossary` shapes both: an entry without a definition marks a word or a rank common, gives it a sort key or a respelling, and lets in a rank whose title is a single word; an entry with a definition replaces the senses of the owner's own rank or office, or is a term of the owner's own. The [format reference](../reference/format-details.md#dictionaries-and-glossaries) gives every column and rule.
1010
+ **Sworn Knight** **1** (_Duchy of Aubrac_) Holds land of the duke. **2** (_Kingdom of Provenzia_) Holds land of the crown. **3** A knight bound by oath to a lord. — [[thalorna-note-affiliation-duchy|Duchy of Aubrac]], [[thalorna-note-affiliation-kingdom|Kingdom of Provenzia]], [[thalorna-note-lore-provenzianclt|Provenzia]]
1011
+ ```
1012
+
1013
+ The layout orders entries by letter, then by `name_sort`. **The language tag
1014
+ prints where the entry's language is not the page's own**: a language note's
1015
+ own language, a culture's `data.language`, or the language an affiliation's
1016
+ glossary resolves to. It is the language note's `data.abbreviation`, or its
1017
+ full name where unstated. Etymology never repeats the entry's own language:
1018
+ a borrowed word prints `[fr. Élav. _sólmerë_]`, a built word its pieces each
1019
+ with its gloss, `[_am-_ "love" + _-ère_ "one who"]`, an inherited word its
1020
+ older form, `[*_hur_]` or `[Hel. *_thalass_]`, and a root word its root and
1021
+ pattern. A citation of a note with no published page prints as its name.
1022
+
1023
+ A built piece can name a language element by its `form` or one of its
1024
+ `variants`; its `sense` prints beside the piece. An affix can also be named by
1025
+ its `front` form. Words and affixes take precedence over elements in the same
1026
+ language, and the language is searched before its ancestors. Exact spellings
1027
+ are checked before ignoring joining hyphens; case and accents are preserved.
1028
+
1029
+ A glossary entry may give its own sort key or respelling. A reference to a
1030
+ rank or office inherits its descriptions and admits a title of one word; its
1031
+ `common` value belongs to the entry's owner. A definition replaces the senses
1032
+ of the owner's own rank or office, or supplies an entry of the owner's own.
1033
+ The [format reference](../reference/format-details.md#dictionaries-and-glossaries)
1034
+ gives every column and rule.
951
1035
 
952
1036
  ## Page lists
953
1037
 
@@ -987,7 +1071,7 @@ That list names the `lore` notes tagged `key-concept`, grouped by type and alpha
987
1071
 
988
1072
  **A tag no page carries is a finding** at the fence's own line, naming the tag it looked for. A misspelled tag is the whole of what goes wrong here, and a heading with an empty list under it is how one goes unnoticed. Write `{allow-empty=true}` where a group that is genuinely empty is intended; the directive then leaves nothing behind rather than an empty list.
989
1073
 
990
- A page list names this package's own pages. A **stub** — a note with an empty body — publishes no page, so it joins no list. A note tagged `gm` is listed in Foundry and left out of the website and the book, exactly as it is left out of a content table's relations.
1074
+ A page list names this package's own pages. A **stub** — a note with neither an authored body nor automatic lexical content — publishes no page, so it joins no list. A note tagged `gm` is listed in Foundry and left out of the website and the book, exactly as it is left out of a content table's relations.
991
1075
 
992
1076
  See [page-list details](../reference/format-details.md#page-lists) for the directive's full behaviour.
993
1077
 
package/docs/commands.md CHANGED
@@ -2182,6 +2182,9 @@ wikilink at all. Also reports a vendored foreign-package content index that
2182
2182
  has drifted out of reach, naming `package-build deps fetch` as the fix. Resolves
2183
2183
  address-valued SoHL system fields declared by the installed schema against this
2184
2184
  package and its declared dependencies.
2185
+ An anchored reference to a dependency's readable note must name a prose anchor
2186
+ in its fetched content index. A missing section is an error even when the page
2187
+ address resolves. Event ids are not prose sections and cannot be wikilink targets.
2185
2188
  Reads the content tree named by `root`, defaulting to `paths.content`, and
2186
2189
  the cached content indexes of any declared dependency; writes nothing.
2187
2190
 
@@ -2342,16 +2345,22 @@ package-build content-index [root] [--out <dir>]
2342
2345
 
2343
2346
  **DESCRIPTION**
2344
2347
 
2345
- Emits this package's note index as JSON Lines — one record per note,
2346
- carrying its full frontmatter plus its place in the tree. Every build
2347
- already walks the tree and parses every note's frontmatter and then throws
2348
- the result away; this publishes that walk so anything outside a build can
2349
- query the content without paying for a second walk. Its own command rather
2350
- than only a build step, because the point is that anyone can regenerate it
2351
- at will — the artifact costs a frontmatter parse, not a build, which is
2352
- also what lets it stay uncommitted. Reads the content tree named by `root`,
2353
- defaulting to `paths.content`; writes `<contentPackage>-metadata.jsonl`
2354
- into `--out`, defaulting to the configured `paths.contentIndex`.
2348
+ Emits this package's note index as JSON Lines, carrying full frontmatter,
2349
+ tree positions, prose and event anchors, and document targets. Reads the
2350
+ content tree named by `root`, defaulting to `paths.content`; writes
2351
+ `<contentPackage>-metadata.jsonl`, `<contentPackage>-dictionary.jsonl` and
2352
+ `<contentPackage>-terms.jsonl` into `--out`, defaulting to the configured
2353
+ `paths.contentIndex`.
2354
+
2355
+ The command prepares SQL tables, page lists, automatic sections and scalar
2356
+ expressions before publishing their prose targets. Each source uses its own
2357
+ audience for prose declarations: public notes use public providers and usage,
2358
+ and GM notes use the complete corpus. Native target maps use the compendium's
2359
+ complete SQL corpus and expose only declared prose slugs present there, with
2360
+ their actual native page targets. Readable item documentation targets its
2361
+ documentation journal. An empty published prose-anchor list declares no prose targets,
2362
+ including for pages without a Foundry document. Preparation errors leave all
2363
+ three output files untouched.
2355
2364
 
2356
2365
  **OPTIONS**
2357
2366
 
@@ -9,7 +9,7 @@ subType: userguide
9
9
 
10
10
  A content package begins with Markdown notes under `assets/content/`. Each note has YAML frontmatter describing its identity and data, followed by prose. Folder names beneath `assets/content/` are for authors; `type`, `subType`, and Addresses determine what the build creates.
11
11
 
12
- Every typed note requires a nonempty body, including drafts, folders and homepages. An empty or whitespace-only body is an error. A nonempty body with fewer than 25 prose words warns unless tagged `draft`; the tag never exempts an empty body. A `folder` note needs only a nonempty body: any content passes, with or without `draft`.
12
+ Nonempty `data.words` on a language note (`type: skill`, `subType: language`), or `data.glossary` on a culture or affiliation, supplies page content without an authored body. Every other typed note requires a nonempty body, including drafts, folders and homepages. An empty or whitespace-only body is otherwise an error. A nonempty body with fewer than 25 prose words warns unless tagged `draft`; the tag never exempts an empty body. A `folder` note needs only a nonempty body: any content passes, with or without `draft`.
13
13
 
14
14
  Start with [your first note](authoring/first-note.md). It explains the smallest valid note and shows the JournalEntry, web page, book entry, and content-index record made from it. Then use the chapters below in the order your note needs them.
15
15
 
@@ -166,6 +166,6 @@ Places, affiliations, lore and beings may name their culture in `data.culture`,
166
166
 
167
167
  An affiliation may name the trade, temple or order tongue it keeps in its own `data.language`. An affiliation that reaches no culture is a warning, in a tree holding a culture note. A language needs no culture and no affiliation. See [type: affiliation](reference/format-details.md#type-affiliation).
168
168
 
169
- A language note (`type: skill`, `subType: language`) states its words in frontmatter: `data.words` states each word as a map of its form, class, field and gloss — one sense or a list of them, which a dictionary numbers — with its source, its variant forms, the pieces it is built from and a note, beside `data.classModifiers`, `data.fields` and `data.affixes`, each affix with the `kind` it makes, for a given name's ending who the name is `given` to, and the stem closes it is barred after; a zero affix states `zero: true` and no form. The notes a word is cited from are derived, not written: a note cites a word by marking it in italics in its prose, matched by form or a variant with diacritics and case set aside. The language of a word is the note it sits on, and none of it reaches the compiled Item. A daughter language names its parent in `data.family` and reads the parent's words as its own; the `language_lineage` relation joins a language to its ancestors. Classes come from the [master word classes](reference/format-details.md#word-classes). A culture or an affiliation states its own glossary terms in `data.glossary`, and every entry resolves to a language — its own `language`, else the affiliation's, its parents' or its culture's, else the culture's — or is an error at the entry. The `words`, `affixes`, `fields`, `glossary` and `language_lineage` relations hold them for `sql` fences, the `dictionary` relation lists each word under every language that has it, inherited words included, and the `terms` relation each glossary entry, rank and office with the cultures it reaches; a fence sets them with `layout=dictionary` or `layout=glossary`. A language note names the tag a dictionary prints for it in `data.abbreviation`. Every fact a check reads about a language's names, sounds and history is frontmatter too: `data.tongues` and `data.register` (every name the setting gives in the language's reach, the notes carrying each derived from the index), `data.retired`, `data.scope`, `data.sounds` (letters with their place, word edges from a closed list, the cluster limit and the groups of clusters inside a word, the stress rule, syllable finals, syllable rules and vowel harmony with each vowel's front partner, the joining of parts, written marks, a root's skeleton vowel and frames, the spellings read past, and named mutations such as wearing and voicing), `data.soundChanges` (named sets of ordered steps), `data.elements` (each standing `opening`, `closing`, `either` or `link`, for one kind of name, with the word it is the stem of and its genitive), `data.roots`, `data.cognates` (with a reconstructed `proto` form), `data.callingNames`, `data.older` (the forms older than the rules), `data.negation` (the negative particle and the mutation it brings) and `data.naming` (how long each class of name runs, the edges names keep that words do not, the element kinds each kind of name draws on, what a place name takes from what it is held from, and the near name), each a relation — `sounds` as `letters`, `edges`, `clusters`, `cluster_groups`, `stress`, `finals`, `syllables`, `joining`, `joins`, `marks`, `skeleton`, `frames`, `read_past` and `mutations`, `soundChanges` as `sound_changes`, `callingNames` as `calling_names` — that a fence sets back on the page. A word's citations also come from a note whose name, alias or rank or office title spells it whole. See [a language's words](reference/format-details.md#a-languages-words), [a language's register, sounds and history](reference/format-details.md#a-languages-register-sounds-and-history), [glossary terms](reference/format-details.md#glossary-terms) and [dictionaries and glossaries](reference/format-details.md#dictionaries-and-glossaries).
169
+ A language note (`type: skill`, `subType: language`) states its words in frontmatter: `data.words` states each word as a map of its form, class, field and gloss — one sense or a list of them, which a dictionary numbers — with its source, its variant forms, the pieces it is built from and a note, beside `data.classModifiers`, `data.fields` and `data.affixes`, each affix with the `kind` it makes, for a given name's ending who the name is `given` to, and the stem closes it is barred after; a zero affix states `zero: true` and no form. The notes a word is cited from are derived, not written: a note cites a word by marking it in italics in its prose, matched by form or a variant with diacritics and case set aside. The language of a word is the note it sits on, and none of it reaches the compiled Item. A daughter language names its parent in `data.family` and reads the parent's words as its own; the `language_lineage` relation joins a language to its ancestors. Classes come from the [master word classes](reference/format-details.md#word-classes). A culture or an affiliation states its own glossary entries in `data.glossary`: common words in its core vocabulary, dictionary words with a context meaning, or phrases, compounds and terms of art outside the dictionary with their own gloss. An entry naming a word of its resolved language takes the word's senses and class and links to its language entry; a `definition` supplies the entry's meaning instead. Common marks belong to the glossary's owner. A language note with nonempty `data.words` receives its full Dictionary section automatically: one alphabetical list with field tags and one key. A culture or affiliation with nonempty `data.glossary` receives only its explicitly declared entries in its Glossary section; ranks and offices stand in the affiliation's own sections. A note without the relevant frontmatter receives no such section. All surfaces show these sections after the authored text, or under an existing `{#dictionary}`, `{#lexicon}` or `{#glossary}` heading after its introduction. There are no setting-wide dictionary or glossary pages. Every entry resolves to a language — its own `language`, else the affiliation's, its parents' or its culture's, else the culture's — or is an error at the entry. The `words`, `affixes`, `fields`, `glossary` and `language_lineage` relations hold them for `sql` fences, the `dictionary` relation lists each word under every language that has it, inherited words included, and the `terms` relation each glossary entry, rank and office with its culture associations; a fence sets custom views with `layout=dictionary` or `layout=glossary`. A language note names the tag a dictionary prints for it in `data.abbreviation`. Every fact a check reads about a language's names, sounds and history is frontmatter too: `data.tongues` and `data.register` (every name the setting gives in the language's reach, the notes carrying each derived from the index), `data.retired`, `data.scope`, `data.sounds` (letters with their place, word edges from a closed list, the cluster limit and the groups of clusters inside a word, the stress rule, syllable finals, syllable rules and vowel harmony with each vowel's front partner, the joining of parts, written marks, a root's skeleton vowel and frames, the spellings read past, and named mutations such as wearing and voicing), `data.soundChanges` (named sets of ordered steps), `data.elements` (each standing `opening`, `closing`, `either` or `link`, for one kind of name, with the word it is the stem of and its genitive), `data.roots`, `data.cognates` (with a reconstructed `proto` form), `data.callingNames`, `data.older` (the forms older than the rules), `data.negation` (the negative particle and the mutation it brings) and `data.naming` (how long each class of name runs, the edges names keep that words do not, the element kinds each kind of name draws on, what a place name takes from what it is held from, and the near name), each a relation — `sounds` as `letters`, `edges`, `clusters`, `cluster_groups`, `stress`, `finals`, `syllables`, `joining`, `joins`, `marks`, `skeleton`, `frames`, `read_past` and `mutations`, `soundChanges` as `sound_changes`, `callingNames` as `calling_names` — that a fence sets back on the page. A word's citations also come from a note whose name, alias or rank or office title spells it whole. See [a language's words](reference/format-details.md#a-languages-words), [a language's register, sounds and history](reference/format-details.md#a-languages-register-sounds-and-history), [glossary terms](reference/format-details.md#glossary-terms) and [dictionaries and glossaries](reference/format-details.md#dictionaries-and-glossaries).
170
170
 
171
171
  Blockquote alerts use `[!NOTE]`, `[!TIP]`, `[!IMPORTANT]`, `[!WARNING]`, or `[!]`, with optional attributes after the marker. They replace the former info and warn div fences; secret blocks remain supported. See [alerts](authoring/links-and-markup.md#alerts).
@@ -170,7 +170,7 @@ package-build.config.yaml:382:64: error: package-build config: `site.sections.be
170
170
 
171
171
  ## A worked case: the three states of a note
172
172
 
173
- A note with an empty body is a **stub**, and the diagnostics about one show each
173
+ A place note with an empty body is a **stub**, and the diagnostics about one show each
174
174
  rule above in turn.
175
175
 
176
176
  An empty body must be deliberate, so a stub that says nothing about itself is an
@@ -20,7 +20,8 @@ Start with one pack for each document class the notes produce. An Item, Actor,
20
20
  Macro, Scene, or Adventure note can also produce a JournalEntry for its prose,
21
21
  so a package with those notes generally needs a JournalEntry pack. A missing
22
22
  pack for a document a note actually produces is a finding on that note. An
23
- empty body produces no prose journal and needs no pack for it.
23
+ empty body without automatic Dictionary or Glossary content produces no prose
24
+ journal and needs no pack for it.
24
25
 
25
26
  ```yaml
26
27
  packs:
@@ -62,10 +62,10 @@ takes.
62
62
 
63
63
  `data.pack: none` is appropriate when the page belongs on the site and in the
64
64
  index but does not need a Foundry compendium entry. A documentation package
65
- has no packs at all. A page with an empty body is a stub: it remains in the
66
- index for data references, but publishes no page address or anchors. Write a
67
- description for a stub and leave off the `draft` tag; use `draft` for a page
68
- with prose still in progress. The [stub rules](../reference/format-details.md#a-stub-is-a-note-with-an-empty-body)
65
+ has no packs at all. A page with neither an authored body nor automatic
66
+ Dictionary or Glossary content is a stub: it remains in the index for data
67
+ references, but publishes no page address or anchors. Supply content before
68
+ publishing; use `draft` for prose still in progress. The [stub rules](../reference/format-details.md#a-stub-is-a-note-with-an-empty-body)
69
69
  and [pack routing](packs.md) cover these cases.
70
70
 
71
71
  ## Link to the intended target
@@ -165,92 +165,115 @@ reconstructing them from filenames.
165
165
 
166
166
  ## Set words as a dictionary and terms as a glossary
167
167
 
168
- A language's words and a people's terms are each one `sql` fence, and nothing
169
- is copied into the note: the words come from the language notes' `data.words`,
170
- the terms from the `data.glossary` entries of cultures and affiliations and
171
- from the affiliations' ranks and offices, and a section changes when they do.
172
- `layout=dictionary` sets rows of the `dictionary` relation and
173
- `layout=glossary` rows of the `terms` relation. The query decides what
174
- appears, so any note may carry either.
168
+ A language note's `data.words` supplies its Dictionary section automatically
169
+ on the site, in the Foundry journal and in the book. The dictionary is the
170
+ language's full word list, including inherited words, in one alphabetical
171
+ sequence with letter headings, field tags and one key. A language with absent
172
+ or empty `data.words` receives no Dictionary section.
175
173
 
176
- A language note lists its words, with the words it inherits from its parent
177
- language:
174
+ The frontmatter states the words:
178
175
 
179
- ````markdown
180
- # Dictionary {#dictionary}
181
-
182
- ```sql {layout=dictionary section-level=2}
183
- SELECT * FROM dictionary WHERE language = 'thalorna-note-skill-provnzlng'
176
+ ```yaml
177
+ shortcode: provnzlng
178
+ name: { full: Provenzal Language }
179
+ type: skill
180
+ subType: language
181
+ data:
182
+ classModifiers: [f, m]
183
+ fields:
184
+ - { name: Love and its conduct, holds: "Longing, courtship and the courts of love." }
185
+ words:
186
+ - {
187
+ form: solmère,
188
+ class: n m,
189
+ pronunciation: sol-MEH-reh,
190
+ field: Love and its conduct,
191
+ gloss: "longing; the ache for a person, a place or a time that is gone",
192
+ }
193
+ - {
194
+ form: amère,
195
+ class: n f,
196
+ field: Love and its conduct,
197
+ gloss: ["a beloved", "a sweetheart, in the songs"],
198
+ }
184
199
  ```
185
- ````
186
200
 
187
- A culture note lists the words of its tongue a player is expected to know, and
188
- its glossary: its own terms and the ranks, offices and terms of every
189
- affiliation that reaches it.
201
+ A culture or affiliation's `data.glossary` supplies its Glossary section. It
202
+ contains only that owner's declared entries, so each culture and affiliation
203
+ using a language chooses its own common words and context meanings. Ranks
204
+ and offices stand in the affiliation's own sections; an explicit glossary
205
+ entry can name one, but a glossary does not draw in the whole ladder or list
206
+ of offices. A note with absent or empty `data.glossary` receives no Glossary
207
+ section.
190
208
 
191
- ````markdown
192
- # Words Everyone Knows {#common-words}
209
+ The [worked culture glossary](../reference/format-details.md#glossary-terms)
210
+ uses all three reasons for an entry:
193
211
 
194
- ```sql {layout=dictionary section-level=2}
195
- SELECT * FROM dictionary WHERE language = 'thalorna-note-skill-provnzlng' AND common
212
+ ```yaml
213
+ data:
214
+ language: skill-provnzlng
215
+ glossary:
216
+ - { name: solmère, common: true }
217
+ - { name: amère, definition: "a sweetheart, in the songs" }
218
+ - {
219
+ name: blood-field,
220
+ definition: "ground where a battle tore the boundary with the spirit world, and it did not close",
221
+ }
196
222
  ```
197
223
 
198
- # Glossary {#glossary}
199
-
200
- ```sql {layout=glossary section-level=2}
201
- SELECT * FROM terms WHERE list_contains(cultures, 'thalorna-note-lore-provenzianclt')
202
- ```
203
- ````
224
+ `solmère` is part of this culture's core vocabulary: the words a player needs
225
+ to adventure there. With no `definition`, it carries the senses and class of
226
+ the dictionary word and links to that word's entry. `amère` supplies a context
227
+ meaning, which takes the place of its dictionary senses while retaining the
228
+ word link and class. `blood-field` supplies a gloss for a term outside the
229
+ dictionary. Common marks belong to the glossary's owner; dictionaries mark
230
+ nothing common. There are no setting-wide dictionary or glossary pages.
204
231
 
205
- The glossary renders as a key, a heading per letter and one paragraph per
206
- term, each sense tagged with the body or culture it comes from and each term
207
- in another tongue tagged with it:
232
+ The generated sections follow the note's authored text. A heading with
233
+ `{#dictionary}` or `{#lexicon}` receives the Dictionary list, and a heading
234
+ with `{#glossary}` receives the Glossary list, after the authored text in
235
+ that section. This preserves an introduction or loanword discussion under
236
+ an anchored heading:
208
237
 
209
238
  ```markdown
210
- **Key:** _Lit._ Liturgic Language; • a common word, which a player is expected to know by heart.
239
+ # Lexicon {#lexicon}
211
240
 
212
- ## K
213
-
214
- **Knight-Captain** **1** (_Kingdom of Provenzia_) A knight who leads a company. **2** (_Kingdom of Provenzia_) Commands the guard in the field. — [[thalorna-note-affiliation-kingdom|Kingdom of Provenzia]]
215
-
216
- ## O
217
-
218
- **Ordained Priest** _Lit._ (_Church of the Rite_) Ordained to serve a parish. — [[thalorna-note-affiliation-church|Church of the Rite]]
241
+ The tongue of Provenzia, whose _solmère_ is its own word.
219
242
  ```
220
243
 
221
- The culture shapes both through its own `data.glossary`.
222
- `{name: solmère, common: true, pronunciation: sol-MEH-reh}` marks a word of its
223
- tongue common, so the first fence lists it, and gives it a respelling;
224
- `{name: Subject, common: true}` marks a rank and lets in a title of one word,
225
- which no glossary holds unless an entry names it; and
226
- `{name: blood-field, definition: …}` is a term of the culture's own.
244
+ The list is generated once under that heading. Without such a heading, the
245
+ build supplies **Dictionary** or **Glossary** at the end of the note.
227
246
 
228
- An affiliation lists its own terms with `WHERE owner = '<its Address>'`. The
229
- setting's two pages list everything, each word once and each term under every
230
- owner that holds it:
247
+ ### Custom views
231
248
 
232
- ````markdown
233
- ```sql {layout=dictionary}
234
- SELECT * EXCLUDE (common) FROM dictionary WHERE depth = 0
235
- ```
236
- ````
249
+ An `sql` fence with `layout=dictionary` or `layout=glossary` can add a custom
250
+ view of the same `dictionary` and `terms` relations. This one selects only a
251
+ culture's common entries:
237
252
 
238
253
  ````markdown
239
- ```sql {layout=glossary}
240
- SELECT * EXCLUDE (common) FROM terms
254
+ # Words Everyone Knows {#common-words}
255
+
256
+ ```sql {layout=glossary section-level=2}
257
+ SELECT * FROM terms WHERE owner = 'thalorna-note-lore-provenzianclt' AND common
241
258
  ```
242
259
  ````
243
260
 
244
- `package-build content-index` also writes both relations beside the index, for
245
- reading with `jq`:
261
+ The `terms` relation also holds ranks and offices for custom queries. The
262
+ default Glossary section selects only rows arising from its owner's explicit
263
+ `data.glossary` entries, including an entry naming a rank or office. The
264
+ [layout guide](../authoring/links-and-markup.md#dictionaries-and-glossaries)
265
+ describes query options and rendering.
266
+
267
+ `package-build content-index` writes both relations beside the index. To
268
+ read a culture's own entries with `jq`:
246
269
 
247
270
  ```bash
248
- jq -r 'select(.cultures | index("thalorna-note-lore-provenzianclt")) | .name' \
271
+ jq -r 'select(.owner == "thalorna-note-lore-provenzianclt") | .name' \
249
272
  build/content-index/thalorna-terms.jsonl
250
273
  ```
251
274
 
252
275
  The [format reference](../reference/format-details.md#dictionaries-and-glossaries)
253
- gives every column and every rule.
276
+ gives every column and rule.
254
277
 
255
278
  ## Resolve another package
256
279