@heroiclands/package-build 23.6.1 → 23.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,44 @@
1
1
  # @heroiclands/package-build
2
2
 
3
+ ## 23.7.1
4
+
5
+ ### Patch Changes
6
+
7
+ **Language notes**
8
+
9
+ - A language note can state its sound mutations, group its clusters as common or sparing, give each vowel's front partner, and say where each letter is made.
10
+ - An affix can say what it makes, who a name closing on it is given to, which stem endings bar it and where a second spelling stands, and a zero affix states no form and prints as ∅.
11
+ - A name element can say which kind of name it builds, the word it comes from and its genitive, and a register name can carry its meaning.
12
+ - A language can state how long each class of name runs and the openings and closings only its names keep.
13
+ - It can say which elements each kind of name draws on, what a place name takes from what it is held from, and its near names.
14
+ - A language can also list the forms older than its rules, name its negative particle and say which form its sound changes start from, each set back as a table from an `sql` fence.
15
+
16
+ ## 23.7.0
17
+
18
+ ### Minor Changes
19
+
20
+ **Citations**
21
+
22
+ - A dictionary cites a word from every note that marks it in italics, by its form or a variant, whatever the accents and capitals.
23
+ - A note whose name, alias, rank or office title is the word cites it too.
24
+ - A word's `attested` key is refused: delete it, and mark the word in italics where a note uses it.
25
+
26
+ **Checks**
27
+
28
+ - A word's `frame` must name one of its language's `sounds.frames`, or one of an ancestor's.
29
+ - An `sql` fence set as a table refuses a `key` attribute, which only a dictionary or a glossary uses.
30
+
31
+ ### Patch Changes
32
+
33
+ **Language notes**
34
+
35
+ - A language note can state its register of names, tongues, retired forms, scope, sound changes, name elements, roots, cognates and calling names in frontmatter, each checked where it is written.
36
+ - Its sound rules go there too: letters, word edges, clusters, stress, syllables and harmony, joins, written marks, root frames and the spellings read past.
37
+ - Each of these sets back as a table from an `sql` fence, and the register lists the notes in its scope that carry each name without anyone writing them.
38
+ - A word's gloss can list several senses, which a dictionary numbers, and a word can name its variant forms and carry a note.
39
+ - A dictionary or glossary fence takes `key=only` to print its key alone, so a page with one `key=false` fence per heading states the key once at its head.
40
+ - An affix can state what it replaces at the close of its stem, that it lengthens the stem's last vowel, and its front-vowel form.
41
+
3
42
  ## 23.6.1
4
43
 
5
44
  ### Patch Changes
@@ -309,20 +309,50 @@ A column's heading is its SQL alias, so `AS "Name"` is what a reader sees. A cel
309
309
 
310
310
  ### What a query can read
311
311
 
312
- | Relation | What it holds |
313
- | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
314
- | `notes` | Every note except a stub. This is the one to use unless a stub is wanted. |
315
- | `entries` | Every note, stubs included. |
316
- | `events` | One row per event a `lore`, `place` or `affiliation` note writes in `data.events`, with `whenSort` to order them by date. |
317
- | `words` | One row per word a language note writes in `data.words`, with that note as its `language`, its `position` and what it is built from. |
318
- | `affixes` | One row per affix a language note writes in `data.affixes`, with that note as its `language`, its `position`, and each key the affix states: `form`, `gloss`, `attaches`, `examples` and `tones`. |
319
- | `fields` | One row per field a language note writes in `data.fields`, with that note as its `language`, its `position`, and the field's `name` and what it `holds`. |
320
- | `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. |
321
- | `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. |
322
- | `dictionary` | One row per word per language it is listed under, inherited words included, with its `depth`. See [dictionaries and glossaries](#dictionaries-and-glossaries). |
323
- | `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). |
324
- | `market` | The market scale as three columns — `value`, `name`, `trade` — so a table can print `village` beside the number a note wrote. |
325
- | `<package>.notes` | The same relations — `notes`, `entries`, `events`, `words`, `affixes`, `fields`, `glossary`, `language_lineage`, `dictionary` and `terms` — for each package this one declares a dependency on, in a schema named for it. |
312
+ | Relation | What it holds |
313
+ | ------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
314
+ | `notes` | Every note except a stub. This is the one to use unless a stub is wanted. |
315
+ | `entries` | Every note, stubs included. |
316
+ | `events` | One row per event a `lore`, `place` or `affiliation` note writes in `data.events`, with `whenSort` to order them by date. |
317
+ | `words` | One row per word a language note writes in `data.words`, with that note as its `language`, its `position` and what it is built from. |
318
+ | `affixes` | One row per affix a language note writes in `data.affixes`, with that note as its `language`, its `position`, and each key the affix states: `form` (`NULL` on a zero affix), `zero`, `kind`, `given`, `"where"`, `gloss`, `attaches`, `replaces`, `lengthens`, `front`, `examples`, `barredAfter` and `tones`, then `written`, the form as a table prints it, `∅` for a zero affix. |
319
+ | `fields` | One row per field a language note writes in `data.fields`, with that note as its `language`, its `position`, and the field's `name` and what it `holds`. |
320
+ | `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. |
321
+ | `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. |
322
+ | `dictionary` | One row per word per language it is listed under, inherited words included, with its `depth`. See [dictionaries and glossaries](#dictionaries-and-glossaries). |
323
+ | `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). |
324
+ | `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. |
325
+ | `tongues` | One row per tag a language note writes in `data.tongues`: `tag`, `meaning` and `tongue_language`. |
326
+ | `retired` | One row per form a language note writes in `data.retired`: `form`, `instead` as a list, and `why`. |
327
+ | `scope` | One row per value of a language note's `data.scope`: the `kind` it is under and the `value`. |
328
+ | `letters` | One row per entry of a language note's `data.sounds.letters`: `letters` as a list, `kind`, `like`, `place` and `tongue`. |
329
+ | `edges` | One row per entry of a language note's `data.sounds.edges`: the `edge` and its `sounds` as a list. |
330
+ | `clusters` | One row per language note stating `data.sounds.clusters`, with its `max`. |
331
+ | `cluster_groups` | One row per group of a language note's `data.sounds.clusters.groups`: `what`, `clusters` as a list, and `use`. |
332
+ | `stress` | One row per language note stating `data.sounds.stress`, with its `stress`. |
333
+ | `finals` | One row per entry of a language note's `data.sounds.finals`: `final` and `like`. |
334
+ | `syllables` | One row per language note stating `data.sounds.syllables`: each rule a column, the harmony as `harmony_back`, `harmony_front`, `harmony_neutral`, `harmony_neutralAs` and `harmony_partners`, a list of `{ vowel, partner }`. |
335
+ | `joining` | One row per entry of a language note's `data.sounds.joining`: `first`, `second`, `written` and `example`. |
336
+ | `joins` | One row per entry of a language note's `data.sounds.joins`: `meeting`, `rule`, `letters` and `"where"`. |
337
+ | `marks` | One row per entry of a language note's `data.sounds.marks`: `mark`, `letters`, `kind` and `"limit"`. |
338
+ | `skeleton` | One row per entry of a language note's `data.sounds.skeleton`: `second` and `between`. |
339
+ | `frames` | One row per frame of a language note's `data.sounds.frames`: `frame`, `shape`, `class`, `makes` and `examples` as `{ root, form }`. |
340
+ | `read_past` | One row per entry of a language note's `data.sounds.readPast`: `written`, `readAs` and `what`. |
341
+ | `mutations` | One row per entry of a language note's `data.sounds.mutations`: `mutation`, `sound`, `becomes` and `example`. |
342
+ | `sound_changes` | One row per step of each set in a language note's `data.soundChanges`: the set's `position`, `name`, `from_language`, `to_language` and `takes`, then `step_position`, `step`, `change` and `"where"`. |
343
+ | `elements` | One row per element of a language note's `data.elements`: `form`, `variants`, `element_position`, `kind`, `sense`, `examples`, `word` and `genitive`. |
344
+ | `roots` | One row per root of a language note's `data.roots`: `root`, `sense` and `field`. |
345
+ | `cognates` | One row per form of each set in a language note's `data.cognates`: the set's `position`, `gloss` and `proto`, the `form_language` and the `form`. |
346
+ | `calling_names` | One row per closing of a language note's `data.callingNames`: `form`, `doubled`, `usual` and the language's `min_syllables`. |
347
+ | `older` | One row per form of a language note's `data.older`: `form`, `gloss` and `layer`. |
348
+ | `negation` | One row per language note stating `data.negation`: its `particle` and `mutation`. |
349
+ | `name_shapes` | One row per entry of a language note's `data.naming.shapes`: `of`, `syllables_min`, `syllables_max`, `per_vowel_min` and `per_vowel_max`. |
350
+ | `name_edges` | One row per entry of a language note's `data.naming.edges`: `edge`, `sounds` as a list, `vowel` and `of`. |
351
+ | `name_draws` | One row per entry of a language note's `data.naming.draws`: `kind` and `elements` as a list. |
352
+ | `held_from` | One row per entry of a language note's `data.naming.heldFrom`: `source`, `takes`, `clip`, and `genitive` and `elements` as lists. |
353
+ | `near_names` | One row per worked example of a language note's `data.naming.nearName`: `near`, `given` and the language's `min_syllables`. |
354
+ | `market` | The market scale as three columns — `value`, `name`, `trade` — so a table can print `village` beside the number a note wrote. |
355
+ | `<package>.notes` | The same relations — `notes`, `entries`, `events`, `words`, `affixes`, `fields`, `glossary`, `language_lineage`, `dictionary`, `terms`, `register`, `tongues`, `retired`, `scope`, `letters`, `edges`, `clusters`, `stress`, `finals`, `syllables`, `joining`, `joins`, `marks`, `skeleton`, `frames`, `read_past`, `mutations`, `cluster_groups`, `sound_changes`, `elements`, `roots`, `cognates`, `calling_names`, `older`, `negation`, `name_shapes`, `name_edges`, `name_draws`, `held_from` and `near_names` — for each package this one declares a dependency on, in a schema named for it. |
326
356
 
327
357
  Nested frontmatter is addressed exactly as a note writes it: `name.full`, `sohl.weight`, `data.born`. Every Address is a string — the full canonical Address, as in `list_contains(data.parents, 'thalorna-note-place-xerathia')` — whichever package published it. Every note carries a computed `state` column of `full`, `draft` or `stub`. Notes tagged `gm` are absent from the relations on public surfaces.
328
358
 
@@ -439,16 +469,369 @@ ORDER BY position
439
469
 
440
470
  `ORDER BY position` keeps the order the note states them in. The same shape serves `fields`, with `name` and `holds`.
441
471
 
472
+ An affix's `kind` picks one table out of the list. A table of case suffixes in a language with vowel harmony sets the back form beside its `front` form:
473
+
474
+ ````markdown
475
+ ```sql
476
+ SELECT gloss AS "Case", '_' || form || '_' AS "Back", '_' || coalesce(front, form) || '_' AS "Front",
477
+ array_to_string(list_transform(examples, x -> '_' || x || '_'), ', ') AS "Example"
478
+ FROM affixes
479
+ WHERE language = 'thalorna-note-skill-sinalelng' AND kind = 'case'
480
+ ORDER BY position
481
+ ```
482
+ ````
483
+
484
+ A given name's endings say who each is `given` to, and an affix written two ways keeps each form on its own row, the conditioned one saying `"where"` it stands; `string_agg` sets the two back on one line:
485
+
486
+ ````markdown
487
+ ```sql
488
+ SELECT given AS "Given to", string_agg('`' || form || '`', ', ' ORDER BY position) AS "Ending"
489
+ FROM affixes
490
+ WHERE language = 'thalorna-note-skill-nordmalng' AND kind = 'name'
491
+ GROUP BY given ORDER BY min(position)
492
+ ```
493
+
494
+ ```sql
495
+ SELECT gloss AS "Prefix",
496
+ coalesce(max('`' || form || '`') FILTER (WHERE "where" = 'before a stop'), max('`' || form || '`')) AS "Before a stop",
497
+ max('`' || form || '`') FILTER (WHERE "where" IS NULL) AS "Otherwise", kind AS "Kind"
498
+ FROM affixes
499
+ WHERE language = 'thalorna-note-skill-khazarlng' AND kind IN ('aspect', 'voice')
500
+ GROUP BY gloss, kind ORDER BY min(position)
501
+ ```
502
+ ````
503
+
504
+ ### A language's register, sounds and history
505
+
506
+ A language note states its register, tongues, the forms it does not write, scope, letters, sound rules, mutations, sound changes, name elements, roots, cognates, the forms older than its rules and its negation in frontmatter, and each is a relation, so the page sets every table back with a fence. Each row carries the note as its `language`, and a list's entries their `position`, so `WHERE language = '…' ORDER BY position` keeps the order the note states. A list column, such as `sounds` or `instead`, prints its entries joined by commas; `array_to_string` and `list_transform` set them otherwise.
507
+
508
+ The tags the register uses:
509
+
510
+ ````markdown
511
+ ```sql
512
+ SELECT '`' || tag || '`' AS "Tongue", meaning AS "Meaning"
513
+ FROM tongues WHERE language = 'thalorna-note-skill-dunharlng' ORDER BY position
514
+ ```
515
+ ````
516
+
517
+ | Tongue | Meaning |
518
+ | --------- | --------------------------------------------------------------------- |
519
+ | `dunhari` | a Dunhari name |
520
+ | `sovidur` | a Sovidur name, carried by a Khazryn wanderer living among the tribes |
521
+
522
+ The register, each name with the notes in the language's scope that carry it, each linked under the name itself. `notes` is derived, so a name no note carries prints `—`:
523
+
524
+ ````markdown
525
+ ```sql
526
+ SELECT '**' || name || '**' AS "Name",
527
+ coalesce(array_to_string(list_transform(notes, n -> '[[' || n || '|' || name || ']]'), ', '), '—') AS "Notes",
528
+ '`' || tongue || '`' AS "Tongue", coalesce(built_text, '—') AS "Built from"
529
+ FROM register WHERE language = 'thalorna-note-skill-dunharlng' ORDER BY position
530
+ ```
531
+ ````
532
+
533
+ The forms the setting does not write, a form with several replacements joined:
534
+
535
+ ````markdown
536
+ ```sql
537
+ SELECT '`' || form || '`' AS "Form",
538
+ coalesce(array_to_string(list_transform(instead, x -> '`' || x || '`'), ', '), '—') AS "Written instead",
539
+ why AS "Why"
540
+ FROM retired WHERE language = 'thalorna-note-skill-dunharlng' ORDER BY position
541
+ ```
542
+ ````
543
+
544
+ The scope, one row per kind:
545
+
546
+ ````markdown
547
+ ```sql
548
+ SELECT kind AS "Kind", string_agg('`' || value || '`', ' ') AS "Value"
549
+ FROM scope WHERE language = 'thalorna-note-skill-dunharlng' GROUP BY kind ORDER BY kind
550
+ ```
551
+ ````
552
+
553
+ The letters, the word edges and the cluster limit. `like` is an SQL keyword, so it is named in double quotes:
554
+
555
+ ````markdown
556
+ ```sql
557
+ SELECT array_to_string(list_transform(letters, x -> '`' || x || '`'), ', ') AS "Letter",
558
+ kind AS "Kind", "like" AS "Sounds like"
559
+ FROM letters WHERE language = 'thalorna-note-skill-dunharlng' ORDER BY position
560
+ ```
561
+
562
+ ```sql
563
+ SELECT edge AS "Edge", coalesce(nullif(array_to_string(list_transform(sounds, x -> '`' || x || '`'), ', '), ''), '—') AS "Sounds"
564
+ FROM edges WHERE language = 'thalorna-note-skill-dunharlng' ORDER BY position
565
+ ```
566
+
567
+ ```sql
568
+ SELECT 'Most consonants together inside a word' AS "Rule", max AS "Count"
569
+ FROM clusters WHERE language = 'thalorna-note-skill-dunharlng'
570
+ ```
571
+ ````
572
+
573
+ A letter that states its `place` sets a grid of consonants back by manner and place, one `kind` to a row:
574
+
575
+ ````markdown
576
+ ```sql
577
+ SELECT '**' || kind || '**' AS " ",
578
+ coalesce(string_agg(array_to_string(list_transform(letters, x -> '_' || x || '_'), ', '), ', ') FILTER (WHERE place = 'lips'), '') AS "lips",
579
+ coalesce(string_agg(array_to_string(list_transform(letters, x -> '_' || x || '_'), ', '), ', ') FILTER (WHERE place = 'tongue-tip'), '') AS "tongue-tip",
580
+ coalesce(string_agg(array_to_string(list_transform(letters, x -> '_' || x || '_'), ', '), ', ') FILTER (WHERE place = 'back'), '') AS "back"
581
+ FROM letters WHERE language = 'thalorna-note-skill-sinalelng' AND place IS NOT NULL
582
+ GROUP BY kind ORDER BY min(position)
583
+ ```
584
+ ````
585
+
586
+ The groups of clusters that stand inside a word, and the diphthongs by harmony set. A diphthong is a `vowelPairs` entry, and the set it belongs to is the harmony list holding it:
587
+
588
+ ````markdown
589
+ ```sql
590
+ SELECT what AS "Kind", array_to_string(list_transform(clusters, x -> '_' || x || '_'), ', ') AS "Clusters", use AS "Use"
591
+ FROM cluster_groups WHERE language = 'thalorna-note-skill-sinalelng' ORDER BY position
592
+ ```
593
+
594
+ ```sql
595
+ SELECT 'back' AS "Set", array_to_string(list_transform(list_filter(vowelPairs, p -> list_contains(harmony_back, p)), x -> '_' || x || '_'), ', ') AS "Diphthongs"
596
+ FROM syllables WHERE language = 'thalorna-note-skill-sinalelng'
597
+ UNION ALL
598
+ SELECT 'neutral', array_to_string(list_transform(list_filter(vowelPairs, p -> list_contains(harmony_neutral, p)), x -> '_' || x || '_'), ', ')
599
+ FROM syllables WHERE language = 'thalorna-note-skill-sinalelng'
600
+ UNION ALL
601
+ SELECT 'front', array_to_string(list_transform(list_filter(vowelPairs, p -> list_contains(harmony_front, p)), x -> '_' || x || '_'), ', ')
602
+ FROM syllables WHERE language = 'thalorna-note-skill-sinalelng'
603
+ ```
604
+ ````
605
+
606
+ The vowel partners an affix's front form takes. `harmony_partners` is a list of `{ vowel, partner }`, so `unnest` sets it out one row to a vowel:
607
+
608
+ ````markdown
609
+ ```sql
610
+ SELECT '_' || p.vowel || '_' AS "Vowel", '_' || p.partner || '_' AS "In a suffix's front form"
611
+ FROM (SELECT unnest(harmony_partners) AS p FROM syllables WHERE language = 'thalorna-note-skill-sinalelng')
612
+ ```
613
+ ````
614
+
615
+ A mutation, one table per name. `mutation` picks the rows:
616
+
617
+ ````markdown
618
+ ```sql
619
+ SELECT '_' || sound || '_' AS "Radical", '_' || becomes || '_' AS "Worn", coalesce(example, '—') AS "Example"
620
+ FROM mutations WHERE language = 'thalorna-note-skill-sinalelng' AND mutation = 'wearing' ORDER BY position
621
+ ```
622
+ ````
623
+
624
+ The marks and spellings a word is read through:
625
+
626
+ ````markdown
627
+ ```sql
628
+ SELECT '`' || written || '`' AS "Written", coalesce('`' || readAs || '`', '—') AS "Read as", what AS "What it is"
629
+ FROM read_past WHERE language = 'thalorna-note-skill-dunharlng' ORDER BY position
630
+ ```
631
+ ````
632
+
633
+ The sound changes, one table per set. `where` is an SQL keyword, so it is named in double quotes; `name` picks the set:
634
+
635
+ ````markdown
636
+ ```sql
637
+ SELECT step AS "Step", change AS "Change", coalesce("where", '—') AS "Where"
638
+ FROM sound_changes
639
+ WHERE language = 'thalorna-note-skill-helonclng' AND name = 'From Helonic to Byzarian'
640
+ ORDER BY step_position
641
+ ```
642
+ ````
643
+
644
+ The stress rule, the syllable rules and the finals. A rule of `syllables` is a column, so a `Rule | Forms` table is one `SELECT` per row joined with `UNION ALL`:
645
+
646
+ ````markdown
647
+ ```sql
648
+ SELECT 'Stress falls on' AS "Rule", stress AS "Value"
649
+ FROM stress WHERE language = 'thalorna-note-skill-vylarilng'
650
+ ```
651
+
652
+ ```sql
653
+ SELECT 'Codas' AS "Rule", array_to_string(codas, ' ') AS "Forms" FROM syllables WHERE language = 'thalorna-note-skill-sowideslng'
654
+ UNION ALL
655
+ SELECT 'Finals', array_to_string(finals, ' ') FROM syllables WHERE language = 'thalorna-note-skill-sowideslng'
656
+ UNION ALL
657
+ SELECT 'Initial vowel', CASE WHEN initialVowel THEN 'yes' ELSE 'no' END FROM syllables WHERE language = 'thalorna-note-skill-sowideslng'
658
+ ```
659
+
660
+ ```sql
661
+ SELECT '`' || final || '`' AS "Final", "like" AS "Sounds like"
662
+ FROM finals WHERE language = 'thalorna-note-skill-tantsoulng' ORDER BY position
663
+ ```
664
+ ````
665
+
666
+ The joining of vowels, the joins of parts and the marks. `limit` is an SQL keyword, so it is named in double quotes:
667
+
668
+ ````markdown
669
+ ```sql
670
+ SELECT array_to_string(first, ', ') AS "First closes on", array_to_string(second, ', ') AS "Second opens on",
671
+ '`' || written || '`' AS "Written", coalesce(example, '—') AS "Example"
672
+ FROM joining WHERE language = 'thalorna-note-skill-vedyarilng' ORDER BY position
673
+ ```
674
+
675
+ ```sql
676
+ SELECT '`' || meeting || '`' AS "Meeting", '`' || rule || '`' AS "Rule",
677
+ array_to_string(letters, ' ') AS "Letters", coalesce("where", 'anywhere') AS "Where"
678
+ FROM joins WHERE language = 'thalorna-note-skill-khazrilng' ORDER BY position
679
+ ```
680
+
681
+ ```sql
682
+ SELECT mark AS "Mark", array_to_string(letters, ' ') AS "Letters", kind AS "Kind", "limit" AS "Limit"
683
+ FROM marks WHERE language = 'thalorna-note-skill-atarzadilng' ORDER BY position
684
+ ```
685
+ ````
686
+
687
+ A root's vowel and its frames. A frame's form for one root is read out of `examples`:
688
+
689
+ ````markdown
690
+ ```sql
691
+ SELECT array_to_string(second, ' ') AS "Second consonant of the skeleton",
692
+ coalesce(nullif("between", ''), 'nothing') AS "Between the second and third"
693
+ FROM skeleton WHERE language = 'thalorna-note-skill-khazrilng' ORDER BY position
694
+ ```
695
+
696
+ ```sql
697
+ SELECT '**' || frame || '**' AS "Frame", '`' || shape || '`' AS "Shape", array_to_string(class, ', ') AS "Class",
698
+ makes AS "What it makes", list_filter(examples, e -> e.root = 'd-l-k')[1].form AS "On d-l-k"
699
+ FROM frames WHERE language = 'thalorna-note-skill-khazrilng' ORDER BY position
700
+ ```
701
+ ````
702
+
703
+ The calling names, a doubled first syllable named in words:
704
+
705
+ ````markdown
706
+ ```sql
707
+ SELECT coalesce('`' || form || '`', 'the first syllable doubled') AS "Closing", usual AS "Usual for"
708
+ FROM calling_names WHERE language = 'thalorna-note-skill-vedyarilng' ORDER BY position
709
+ ```
710
+ ````
711
+
712
+ The name elements, the roots and the cognates. A cognate set is one row per form, so a table of them pivots on `form_language`, and its reconstructed `proto` form takes its asterisk in the query:
713
+
714
+ ````markdown
715
+ ```sql
716
+ SELECT '`' || form || '`' AS "Element", element_position AS "Stands", sense AS "Sense",
717
+ array_to_string(examples, ', ') AS "Example"
718
+ FROM elements WHERE language = 'thalorna-note-skill-aelwthlng' ORDER BY position
719
+ ```
720
+
721
+ ```sql
722
+ SELECT '`' || root || '`' AS "Skeleton", sense AS "Sense", field AS "Field"
723
+ FROM roots WHERE language = 'thalorna-note-skill-khazrilng' ORDER BY position
724
+ ```
725
+
726
+ ```sql
727
+ SELECT '*' || max(proto) AS "Ancient",
728
+ max(form) FILTER (WHERE form_language = 'thalorna-note-skill-helonclng') AS "Helonic",
729
+ max(form) FILTER (WHERE form_language = 'thalorna-note-skill-byzarnlng') AS "Byzarian",
730
+ gloss AS "Gloss"
731
+ FROM cognates WHERE language = 'thalorna-note-skill-helonclng'
732
+ GROUP BY position, gloss ORDER BY position
733
+ ```
734
+ ````
735
+
736
+ An element's `kind` keeps one set of elements apart from another, and a place's generic gives its `genitive`:
737
+
738
+ ````markdown
739
+ ```sql
740
+ SELECT '`' || form || '`' AS "Generic", sense AS "What it names", coalesce('`' || genitive || '`', '—') AS "In the genitive"
741
+ FROM elements WHERE language = 'thalorna-note-skill-nordmalng' AND kind = 'place' ORDER BY position
742
+ ```
743
+ ````
744
+
745
+ A daughter's inheriting set says what it `takes`, so a parent's table of its daughters is read from `language_lineage`, which follows each daughter's `data.family`, and from that set. An empty label after the `|` prints the daughter's own name:
746
+
747
+ ````markdown
748
+ ```sql
749
+ SELECT '[[' || l.language || '|]]' AS "Daughter", l.depth AS "Generations",
750
+ coalesce(max(s.takes), 'nominative') AS "Takes"
751
+ FROM language_lineage l LEFT JOIN sound_changes s ON s.language = l.language AND s.takes IS NOT NULL
752
+ WHERE l.ancestor = 'thalorna-note-skill-vylarilng' AND l.depth > 0
753
+ GROUP BY l.language, l.depth ORDER BY l.depth, l.language
754
+ ```
755
+ ````
756
+
757
+ A zero affix has no `form`; `written` is the form as a table prints it, `∅` for a zero affix. Sinalë's case table, nominative first:
758
+
759
+ ````markdown
760
+ ```sql
761
+ SELECT gloss AS "Case", '_' || written || '_' AS "Back", '_' || coalesce(front, written) || '_' AS "Front"
762
+ FROM affixes WHERE language = 'thalorna-note-skill-sinalelng' AND kind = 'case' ORDER BY position
763
+ ```
764
+ ````
765
+
766
+ A language's names keep shapes and edges its words do not. A name's openings split by length, and `vowel` adds the row for a name that opens on a vowel; the shapes set a band table back, one row per class of name:
767
+
768
+ ````markdown
769
+ ```sql
770
+ SELECT consonants AS "consonants", openings AS "the openings" FROM (
771
+ SELECT CASE WHEN edge = 'openConsonant' THEN 'one sound' WHEN max(length(s)) = 2 THEN 'two' ELSE 'three' END AS consonants,
772
+ string_agg('`' || s || '`', ' ') AS openings, min(position) * 10 + max(length(s)) AS ord
773
+ FROM (SELECT edge, position, unnest(sounds) AS s FROM name_edges
774
+ WHERE language = 'thalorna-note-skill-nordmalng' AND edge IN ('openConsonant', 'openCluster') AND of = 'name')
775
+ GROUP BY edge, CASE WHEN edge = 'openCluster' THEN length(s) END
776
+ UNION ALL
777
+ SELECT 'none', 'a vowel stands first', 1000 FROM name_edges
778
+ WHERE language = 'thalorna-note-skill-nordmalng' AND edge = 'openConsonant' AND of = 'name' AND vowel
779
+ ) ORDER BY ord
780
+ ```
781
+
782
+ ```sql
783
+ SELECT of AS "class of name", '`' || printf('%.1f', per_vowel_min) || '` to `' || printf('%.1f', per_vowel_max) || '`' AS "consonants per vowel",
784
+ coalesce('`' || syllables_min || '` to `' || syllables_max || '`', '—') AS "syllables"
785
+ FROM name_shapes WHERE language = 'thalorna-note-skill-nordmalng' ORDER BY position
786
+ ```
787
+ ````
788
+
789
+ The sources a place name is held from, and the near names:
790
+
791
+ ````markdown
792
+ ```sql
793
+ SELECT source AS "held from",
794
+ CASE takes WHEN 'stem' THEN 'a name-stem, in any of its three grades'
795
+ WHEN 'element' THEN array_to_string(list_transform(elements, x -> '`' || x || '`'), ', ')
796
+ ELSE (CASE takes WHEN 'godName' THEN 'that god''s name' ELSE 'that founder''s given name' END)
797
+ || coalesce(', clipped to its ' || clip || ' element', '')
798
+ || coalesce(', with a genitive ' || array_to_string(list_transform(genitive, x -> '`' || x || '`'), ' or '), '')
799
+ END AS "what stands first"
800
+ FROM held_from WHERE language = 'thalorna-note-skill-nordmalng' ORDER BY position
801
+ ```
802
+
803
+ ```sql
804
+ SELECT '`' || near || '`' AS "Near name", '`' || given || '`' AS "Given name"
805
+ FROM near_names WHERE language = 'thalorna-note-skill-khelathlng' ORDER BY position
806
+ ```
807
+ ````
808
+
809
+ The forms older than the rules, and the negative particle:
810
+
811
+ ````markdown
812
+ ```sql
813
+ SELECT '_' || form || '_' AS "Form", coalesce(gloss, '—') AS "Gloss", coalesce(layer, '—') AS "Layer"
814
+ FROM older WHERE language = 'thalorna-note-skill-sinalelng' ORDER BY position
815
+ ```
816
+
817
+ ```sql
818
+ SELECT '_' || particle || '_' AS "Particle", coalesce(mutation, '—') AS "The verb takes"
819
+ FROM negation WHERE language = 'thalorna-note-skill-sinalelng'
820
+ ```
821
+ ````
822
+
823
+ See [a language's register, sounds and history](../reference/format-details.md#a-languages-register-sounds-and-history) for every key and what the lint holds it to.
824
+
442
825
  ### Fence attributes
443
826
 
444
827
  A braced list may follow the language on the opening fence. Four attributes are accepted, and anything else is reported by name.
445
828
 
446
- | Attribute | Value | Default | What it does |
447
- | --------------- | ----------------------------------- | ------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
448
- | `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. |
449
- | `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. |
450
- | `key` | `true` or `false` | `true` | Whether a dictionary or a glossary prints its key. `false` leaves it out, for a page that states the key once above several fences. |
451
- | `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). |
829
+ | Attribute | Value | Default | What it does |
830
+ | --------------- | ----------------------------------- | ------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
831
+ | `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. |
832
+ | `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. |
833
+ | `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. |
834
+ | `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). |
452
835
 
453
836
  ````markdown
454
837
  ```sql {allow-empty=true section-level=3}
@@ -476,7 +859,7 @@ See [SQL details](../reference/format-details.md#content-tables) for query optio
476
859
 
477
860
  ### Dictionaries and glossaries
478
861
 
479
- 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 and the notes it is cited from. **`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.
862
+ 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.
480
863
 
481
864
  **The query decides what appears**, and any note may carry either fence. These are the common ones:
482
865
 
@@ -511,17 +894,28 @@ SELECT * FROM terms WHERE list_contains(cultures, 'thalorna-note-lore-provenzian
511
894
  ```
512
895
  ````
513
896
 
514
- A page that sets several fences, one per heading, states its key once and sets `key=false` on each fence, so the abbreviations are not repeated under every heading:
897
+ 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:
515
898
 
516
899
  ````markdown
517
- ```sql {layout=dictionary key=false section-level=4}
900
+ ## Words {#words}
901
+
902
+ ```sql {layout=dictionary key=only}
518
903
  SELECT * FROM dictionary WHERE language = 'thalorna-note-skill-vylarilng' AND depth = 0
519
904
  ```
905
+
906
+ ### Love and its conduct
907
+
908
+ ```sql {layout=dictionary key=false section-level=4}
909
+ SELECT d.* FROM dictionary d JOIN words w ON w.language = d.language AND w.form = d.name
910
+ WHERE d.language = 'thalorna-note-skill-vylarilng' AND w.field = 'Love and its conduct'
911
+ ```
520
912
  ````
521
913
 
914
+ `key`, whatever its value, sets a dictionary's or a glossary's key, so it is refused on a fence set as a table.
915
+
522
916
  Either layout prints, in order:
523
917
 
524
- - **a key**, once, unless the fence sets `key=false`, 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;
918
+ - **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;
525
919
  - **a heading per letter**, at `section-level`, accents taken off and apostrophes skipped, so `'ostal` files under O and `Écuyer` under E;
526
920
  - **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.
527
921
 
@@ -143,6 +143,6 @@ Places, affiliations, lore and beings may name their culture in `data.culture`,
143
143
 
144
144
  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).
145
145
 
146
- 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, with its source, the pieces it is built from and the notes it is attested in, beside `data.classModifiers`, `data.fields` and `data.affixes`. 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`. See [a language's words](reference/format-details.md#a-languages-words), [glossary terms](reference/format-details.md#glossary-terms) and [dictionaries and glossaries](reference/format-details.md#dictionaries-and-glossaries).
146
+ 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).
147
147
 
148
148
  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).