@heroiclands/package-build 25.5.4 → 25.5.5

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 +22 -0
  2. package/README.md +7 -0
  3. package/bin/content-commands.mjs +32 -8
  4. package/docs/api.md +57 -16
  5. package/docs/authoring/links-and-markup.md +51 -3
  6. package/docs/commands.md +60 -17
  7. package/docs/configuration.md +3 -2
  8. package/docs/content-format.md +10 -0
  9. package/docs/diagnostics.md +17 -0
  10. package/docs/guides/module-project.md +11 -3
  11. package/docs/guides/site.md +12 -2
  12. package/docs/guides/writing-in-invented-scripts.md +5 -2
  13. package/docs/reference/format-details.md +61 -11
  14. package/docs/reference/note-types.md +32 -32
  15. package/e2e.mjs +1 -1
  16. package/engine/base-compiler.mjs +2 -0
  17. package/engine/compile-corpus.mjs +6 -2
  18. package/engine/content-charset.mjs +1 -1
  19. package/engine/content-render.mjs +14 -2
  20. package/engine/content-scripts.mjs +44 -6
  21. package/engine/content-session.mjs +11 -7
  22. package/engine/content-spans.mjs +3 -4
  23. package/engine/content-verse.mjs +52 -10
  24. package/engine/core-report.mjs +27 -18
  25. package/engine/language-names.mjs +53 -23
  26. package/engine/markdownlint-run.mjs +1 -1
  27. package/engine/markup-validation.mjs +2 -0
  28. package/engine/note-vocabulary.mjs +10 -5
  29. package/engine/npc-generate.mjs +52 -7
  30. package/engine/page-breaks.mjs +115 -0
  31. package/engine/pdf-build.mjs +12 -4
  32. package/engine/pdf-render.mjs +10 -1
  33. package/engine/preview-index.mjs +4 -2
  34. package/engine/site-build.mjs +12 -3
  35. package/engine/site-preview.mjs +3 -0
  36. package/engine/site-render.mjs +18 -4
  37. package/engine/site-root.mjs +10 -1
  38. package/engine/site-serve-http.mjs +151 -0
  39. package/engine/site-serve.mjs +196 -126
  40. package/engine/verse-metre.mjs +50 -5
  41. package/engine/verse-regions.mjs +16 -0
  42. package/package.json +3 -3
  43. package/types/e2e.d.mts +1 -1
  44. package/types/engine/content-scripts.d.mts +9 -1
  45. package/types/engine/content-verse.d.mts +11 -2
  46. package/types/engine/core-report.d.mts +6 -2
  47. package/types/engine/language-names.d.mts +18 -6
  48. package/types/engine/npc-generate.d.mts +3 -0
  49. package/types/engine/page-breaks.d.mts +30 -0
  50. package/types/engine/pdf-render.d.mts +2 -0
  51. package/types/engine/site-build.d.mts +3 -1
  52. package/types/engine/site-render.d.mts +6 -2
  53. package/types/engine/site-root.d.mts +3 -1
  54. package/types/engine/site-serve-http.d.mts +1 -0
  55. package/types/engine/site-serve.d.mts +12 -6
  56. package/types/engine/verse-metre.d.mts +2 -1
  57. package/types/engine/verse-regions.d.mts +8 -0
package/CHANGELOG.md CHANGED
@@ -1,5 +1,27 @@
1
1
  # @heroiclands/package-build
2
2
 
3
+ ## 25.5.5
4
+
5
+ **Languages and scripts**
6
+
7
+ - Ch'alish P'ilam Ceremonial text passes character checks and is recognized as native script alongside Common.
8
+ - Core vocabulary reports name the languages left unmeasured, including when no language opts into coverage.
9
+ - Generated NPC names capitalize their name words for English prose while keeping connecting words as written.
10
+ - Distinct source names keep their authored spelling under the culture's language code, except when the selected pattern draws English words.
11
+ - The language reference explains that Roman inventories list language additions and refuse shared punctuation and whitespace in the list.
12
+ - A language can supply one list of given names for everyone; NPC generation draws from it for every gender.
13
+
14
+ **Verse**
15
+
16
+ - Metre checks allow multi-syllable function words to go unstressed, so lines can scan without placing a beat on every particle or pronoun.
17
+ - Verse inside blockquotes and nested lists keeps its line breaks, stanza boundaries and indentation, with findings located at the authored text.
18
+ - Verse metre checks count a linked name's visible words without adding beats from its address.
19
+
20
+ **Rendering and sites**
21
+
22
+ - A book can break the page at a chosen point with `--- {.page-break}`.
23
+ - Website previews start immediately, keep the last successful pages available during rebuilds, and report progress while building pages and search.
24
+
3
25
  ## 25.5.4
4
26
 
5
27
  **Builds and commands**
package/README.md CHANGED
@@ -53,6 +53,13 @@ repository’s setup installs the two canonical shell hooks; it installs no
53
53
  pre-push hook. Run `npm test` and the other checks directly, or use
54
54
  `package-build ci` for a container run.
55
55
 
56
+ The repository's `vitest.config.ts` limits both `npm test` and `npm run
57
+ test:watch` to two workers, with Vitest's isolated fork pool. CI runs the same
58
+ `npm test` command. This bounds concurrent test files on the shared runner,
59
+ including files that start CLI subprocesses, load SQL databases, or emit
60
+ TypeScript declarations. Keep this bound when reproducing CI failures locally;
61
+ a percentage of the host's CPUs does not reserve resources for other jobs.
62
+
56
63
  ## Documentation
57
64
 
58
65
  The documentation lives in the Markdown files in this repository. Start with [the documentation home](docs/index.md). This toolchain does not build its own site or book.
@@ -1289,7 +1289,12 @@ function lintCommand() {
1289
1289
  // Text marked with a language, a font or a map, checked against
1290
1290
  // the languages this package declares, the faces it installed
1291
1291
  // and the maps its notes declare.
1292
- const runs = runContext(config, records);
1292
+ const runs = runContext(config, records, {
1293
+ referenceName: (target) => {
1294
+ const hit = index.resolveAddress(target) ?? index.manifestHit(target);
1295
+ return hit?.fm?.name?.full ?? hit?.name;
1296
+ },
1297
+ });
1293
1298
  const loaderLocations = new Set(
1294
1299
  (runs.encodingFindings ?? []).map((one) =>
1295
1300
  JSON.stringify([one.file, one.line, one.column, one.message]),
@@ -2346,7 +2351,11 @@ function siteBuildCommand() {
2346
2351
  const config = argv[CONTENT_CONFIG] ?? loadPackConfig();
2347
2352
  const root = path.resolve(config.rootDir, argv.out ?? DEPLOY_ROOT);
2348
2353
  const out = siteDirectory(config, root);
2349
- const result = await buildPackageSite({ config, out });
2354
+ const result = await buildPackageSite({
2355
+ config,
2356
+ out,
2357
+ progress: (message) => log.info(message),
2358
+ });
2350
2359
  // A note in error is written as its page shows it, and the
2351
2360
  // build fails without indexing a site that is not to be
2352
2361
  // deployed.
@@ -2374,6 +2383,7 @@ function siteBuildCommand() {
2374
2383
  base: siteBase(config),
2375
2384
  out: root,
2376
2385
  search: config.site.search,
2386
+ progress: (message) => log.info(message),
2377
2387
  });
2378
2388
  if (search) {
2379
2389
  log.info(
@@ -2429,8 +2439,10 @@ function siteServeCommand() {
2429
2439
  host: argv.host,
2430
2440
  port: argv.port,
2431
2441
  report: (event) => {
2432
- if (event.kind === "error") reportFailure(event.error);
2433
- else if (event.kind === "failed") {
2442
+ if (event.kind === "progress") log.info(event.message);
2443
+ else if (event.kind === "error") {
2444
+ if (!event.fatal) reportFailure(event.error);
2445
+ } else if (event.kind === "failed") {
2434
2446
  if (event.built) reportSiteBuild(event.built);
2435
2447
  for (const finding of event.findings ?? []) emitDiagnostic(finding);
2436
2448
  } else {
@@ -2444,11 +2456,22 @@ function siteServeCommand() {
2444
2456
  },
2445
2457
  });
2446
2458
  log.info(`serving ${served.url} — interrupt to stop`);
2447
- await new Promise((resolve) => {
2448
- process.once("SIGINT", resolve);
2449
- process.once("SIGTERM", resolve);
2459
+ let interrupt;
2460
+ const interrupted = new Promise((resolve) => {
2461
+ interrupt = resolve;
2462
+ process.once("SIGINT", interrupt);
2463
+ process.once("SIGTERM", interrupt);
2450
2464
  });
2451
- await served.close();
2465
+ try {
2466
+ await Promise.race([interrupted, served.done]);
2467
+ } catch (err) {
2468
+ reportFailure(err);
2469
+ process.exitCode = 1;
2470
+ } finally {
2471
+ process.removeListener("SIGINT", interrupt);
2472
+ process.removeListener("SIGTERM", interrupt);
2473
+ await served.close();
2474
+ }
2452
2475
  } catch (err) {
2453
2476
  reportFailure(err);
2454
2477
  process.exitCode = 1;
@@ -3467,6 +3490,7 @@ function newNpc(config, argv) {
3467
3490
  profile,
3468
3491
  names,
3469
3492
  language: languageKey,
3493
+ languageCode: code,
3470
3494
  seed: argv.seed,
3471
3495
  currentDate: config.currentDate,
3472
3496
  dateContext: reckoningContext(index),
package/docs/api.md CHANGED
@@ -1329,12 +1329,30 @@ content tree can also compile into Foundry compendium packs.
1329
1329
  | `renderPages` | `function renderPages(pages, options)` | `{{written: number, byKind: Record<string, number>, tableErrors: object[], wikiErrors: object[]}}` | Renders every page. |
1330
1330
  | `resolveSitePass` | `function resolveSitePass(name, options)` | `{{beforeLinks?: Function}}` The bundle. | Resolves `site.pass` to its bundle. |
1331
1331
  | `sitePageDecorator` | `sitePageDecorator(config, index)` | Function | Add the site's infobox and being metadata to a rendered page. |
1332
- | `buildSite` | `buildSite({ config, sqlTables })` | `{{gates: object, stats: object\|null, tableErrors: object[], wikiErrors: object[], mapFindings: object[], manifests: object\|null, site?: object}}` | Collects, checks and renders a content tree's pages, and reports what it found; returns each page for the site's renderer. |
1332
+ | `buildSite` | `buildSite({ config, sqlTables, progress })` | `{{gates: object, stats: object\|null, tableErrors: object[], wikiErrors: object[], mapFindings: object[], manifests: object\|null, site?: object}}` | Collects, checks and renders a content tree's pages, and reports what it found; returns each page for the site's renderer. |
1333
1333
 
1334
1334
  `siteGates` takes `{ config, records, sqlTables? }`. Pass the prepared
1335
1335
  `sqlTables` map so anchors emitted by automatic dictionary sections are present in the
1336
1336
  index for checking page links.
1337
1337
 
1338
+ `buildSite`, `engine/site-render.mjs`'s `buildPackageSite` and `writeSite`, and
1339
+ `engine/site-root.mjs`'s `writeSiteRoot` accept an optional `progress(message)`
1340
+ callback for phase summaries. It receives prose, independently of findings.
1341
+
1342
+ `engine/site-serve.mjs`'s `serveSite({ config, out, root?, host?, port?, search?,
1343
+ year?, report? })` resolves with `{ url, settled, close, done }` when HTTP listens,
1344
+ before the initial build. Await `settled()` to wait for that build and queued
1345
+ edits, and `close()` to stop watching and serving. `done` resolves on close;
1346
+ an unexpected HTTP failure rejects it and stops the watchers. Its `report` callback
1347
+ receives `progress` events with a `message`, alongside `built`, `page`,
1348
+ `theme`, `failed` and `error` events; a fatal HTTP error has `fatal: true`.
1349
+ Failed builds are reported through that
1350
+ callback and leave the previous output in service. HTTP runs in a separate
1351
+ thread so synchronous rendering does not stall requests; the resolved
1352
+ configuration and incremental render state stay in the calling thread. Every
1353
+ update is prepared in a separate output tree, then installed between HTTP
1354
+ requests so readers receive complete files.
1355
+
1338
1356
  ### `engine.contentRender`
1339
1357
 
1340
1358
  `createRenderer(target, options?)` builds the one markdown-it configuration every target renders through. `target` is `"foundry"`, `"web"`, `"preview"` or `"book"`. The renderer holds the configured parser (`md`), the names of the plugins installed (`plugins`), and three functions: `prepare` writes the constructs markdown-it has no syntax for — marked text, captioned items, named blocks, verse and alerts — as HTML between blank lines; `render` prepares and renders to HTML; `renderInline` renders one inline run. The `book` target parses only, and its Typst stage reads the tokens.
@@ -1362,6 +1380,16 @@ complete source. Reference-only positions contribute no work shape. A wholly
1362
1380
  reference-only body carries its references in `model.notes`, rendered once
1363
1381
  outside native-font wrappers without a verse row or label.
1364
1382
 
1383
+ Verse regions retain the authored `body` and its absolute `bodyStart` and
1384
+ `bodyEnd`. Their `logicalBody` excludes Markdown list and blockquote prefixes;
1385
+ `bodyOffsets` maps each logical UTF-16 position, including the exclusive end,
1386
+ back to an authored body offset. `parseVerse(logicalBody, {sourceOffsets:
1387
+ bodyOffsets, footnotes})` returns the whole model in authored body coordinates:
1388
+ line bounds and character offsets, stanza and canto bounds, boundaries, layout
1389
+ ranges and findings all use that map. Omitted prefixes are layout ranges, so
1390
+ Roman and native projections exclude them. Add `bodyStart` to a model offset to
1391
+ locate it in the complete source. LF and CRLF retain their authored lengths.
1392
+
1365
1393
  | Export | Signature | Returns | Use it when |
1366
1394
  | ---------------- | ---------------------------------- | ------------------------------------------------------------ | ---------------------------------------------------------------------------------------- |
1367
1395
  | `createRenderer` | `createRenderer(target, options?)` | `{ target, md, plugins, prepare, render, renderInline }` | Rendering a note body for one target with the rules every target shares. |
@@ -1446,17 +1474,17 @@ The charset authored content is held to, so a book can choose its face. The pack
1446
1474
 
1447
1475
  ### `engine.romanCharacters`
1448
1476
 
1449
- Exact permitted Roman source characters for configured languages, before script
1450
- encoding. Inventories come from `data.roman` on each language's own note.
1477
+ A configured language's Roman additions to the shared punctuation standard,
1478
+ before script encoding. Inventories come from `data.roman` on each language's own note.
1451
1479
 
1452
- | Export | Signature | Returns | Use it when |
1453
- | ------------------------- | -------------------------------------------- | ----------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
1454
- | `isRomanWhitespace` | `isRomanWhitespace(char)` | `boolean` | recognizing standard spaces and ASCII tab or line whitespace |
1455
- | `romanInventoryProblems` | `romanInventoryProblems(value)` | `string[]` | checking NFC, repeated characters and forbidden whitespace or controls in a literal inventory |
1456
- | `romanInventoriesOf` | `romanInventoriesOf(records, languages)` | `Map<string, RomanInventory>` | looking up a configured language's own inventory by name or BCP 47 tag |
1457
- | `romanTextProblems` | `romanTextProblems(text, inventory, stream)` | `{offset, message}[]` | validating standard Roman punctuation and language additions with exact UTF-16 offsets; pass a `stream` state to continue sentences and quotations from earlier runs |
1458
- | `romanPunctuationState` | `romanPunctuationState()` | `{sentenceStart, quotes}` | starting a stream of runs whose sentences and quotations continue from one run to the next |
1459
- | `romanUnclosedQuotations` | `romanUnclosedQuotations(quotes)` | `{offset, message}[]` | reporting the quotations still open where a stream of runs ends |
1480
+ | Export | Signature | Returns | Use it when |
1481
+ | ------------------------- | -------------------------------------------- | ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
1482
+ | `isRomanWhitespace` | `isRomanWhitespace(char)` | `boolean` | recognizing standard spaces and ASCII tab or line whitespace |
1483
+ | `romanInventoryProblems` | `romanInventoryProblems(value)` | `string[]` | checking NFC, repeats and forbidden whitespace, controls, standard punctuation, literal markers, semicolons, colons, English double quotes and setting-script characters |
1484
+ | `romanInventoriesOf` | `romanInventoriesOf(records, languages)` | `Map<string, RomanInventory>` | looking up a configured language's own inventory by name or BCP 47 tag |
1485
+ | `romanTextProblems` | `romanTextProblems(text, inventory, stream)` | `{offset, message}[]` | validating standard Roman punctuation and language additions with exact UTF-16 offsets; pass a `stream` state to continue sentences and quotations from earlier runs |
1486
+ | `romanPunctuationState` | `romanPunctuationState()` | `{sentenceStart, quotes}` | starting a stream of runs whose sentences and quotations continue from one run to the next |
1487
+ | `romanUnclosedQuotations` | `romanUnclosedQuotations(quotes)` | `{offset, message}[]` | reporting the quotations still open where a stream of runs ends |
1460
1488
 
1461
1489
  ### `engine.pathCharset`
1462
1490
 
@@ -2573,6 +2601,16 @@ points derive their own inputs. Generation and pack compilation accept
2573
2601
  `reportPreparation` (default true); a coordinating caller may set it false
2574
2602
  only after reporting the shared source findings itself.
2575
2603
 
2604
+ `engine/content-scripts` exposes `runContext(config, records?, {referenceName}?)`.
2605
+ It returns the language, encoding and metre context used by run scanning.
2606
+ `referenceName(target)` supplies the rendering or validation index's current
2607
+ name for an Address, including dependency targets, or `undefined` when none
2608
+ resolves. This lookup takes precedence over the local-record fallback and
2609
+ belongs only to the returned context; shared cached lexical data does not
2610
+ share a lookup between page or audience views. A metre line whose empty link
2611
+ label has no resolved spoken text stays unscanned, while the link validator
2612
+ reports invalid targets independently.
2613
+
2576
2614
  `buildLinkIndex` and `buildContentLinkIndex` accept an optional `foreign` view
2577
2615
  from the same session; their diagnostic and routed resolver contracts remain
2578
2616
  distinct. `collectAnchors`, `markupAnchorFindings`, `markupFindings`, journal
@@ -2922,10 +2960,13 @@ const inferred = encode(data, null, "say", { enc: "aelwsemaronenc" });
2922
2960
 
2923
2961
  Both functions take `(data, language, text, options?)` and return
2924
2962
  `{text, findings}`; encoding with a language also returns source-positioned
2925
- `pieces`. A language key selects its encoding, face and lexical inputs.
2926
- `options.enc` and `options.font` override the encoding and face; `options.verse`
2927
- asks the encoder to interpret verse structure. With `language: null`, `enc` is
2928
- required and only the encoding's rows apply.
2963
+ `pieces`. `options.enc` selects a consumer-authored encoding document, which
2964
+ supplies its language and font. An explicit language must agree with that
2965
+ document; a null or omitted language infers it and uses its lexical inputs.
2966
+ `options.font` must agree with the encoding's font; `options.verse` asks the
2967
+ encoder to interpret verse structure. An omitted `enc`, or `enc: "default"`,
2968
+ preserves the Roman text. Bare conversion without lexical inputs uses the
2969
+ pure `encodeText` and `decodeText` functions in `engine/encoding.mjs`.
2929
2970
 
2930
2971
  Language conversion uses the same pure `encodeWithLanguage` and
2931
2972
  `decodeWithLanguage` functions as the build. Recorded native writings and
@@ -2933,7 +2974,7 @@ parts precede encoding rows, and decoding can recover an unmarked word from
2933
2974
  the lexicon. An unknown word gives a positioned finding. A bare refusal
2934
2975
  returns empty text and an error finding. Findings carry `severity`, `message`,
2935
2976
  `offset` (UTF-16), and one-based Unicode-code-point `line` and `column`.
2936
- Invalid data, unknown languages and missing encoding selections throw an
2977
+ Invalid data, unknown languages and unknown encoding selections throw an
2937
2978
  error before conversion. `validateData(data)` returns the validated data or
2938
2979
  throws; it opens no files.
2939
2980
 
@@ -237,6 +237,30 @@ A captioned item inside the section is withheld with it: in Foundry the item's o
237
237
 
238
238
  The special Actor anchors `{#appearance}` and `{#dossier}` feed the same authored sections to each system's Actor fields, and take an H1 only — not the lower-level heading a journal page may otherwise start from. The section runs to the next H1, nested headings included. An anchor declared on a lower heading is still found, and is a build error rather than a quietly empty field: move the heading to the top level, or drop the anchor if the section is meant to stay unaddressed prose.
239
239
 
240
+ ## Book page breaks
241
+
242
+ Write `--- {.page-break}` on its own line between blocks, with blank lines around it:
243
+
244
+ ```markdown
245
+ The first passage ends here.
246
+
247
+ --- {.page-break}
248
+
249
+ The next passage starts on a fresh book page.
250
+ ```
251
+
252
+ The book starts a fresh page without drawing a rule. If the marker already
253
+ falls at the top of a page, it adds no blank page. The website, Foundry and
254
+ both editor preview views omit the marker. Plain `---`, `***` and `___` keep
255
+ their ordinary horizontal rules. Code blocks and verse divs keep the marker
256
+ as literal text.
257
+
258
+ Only `---` accepts `.page-break`; `*** {.page-break}` and `___ {.page-break}`
259
+ are findings. Other classes, ids and attributes on an attributed `---` line
260
+ are findings too. A page break stands between blocks at the top level of a
261
+ note: putting one inside a list, block quote, alert, secret block or fenced
262
+ div is a finding at the marker's line and column.
263
+
240
264
  ## Captions and numbered references
241
265
 
242
266
  Write a caption on its own line **before** the item it describes, with a blank line before and after the caption. `:` produces an unnumbered caption; `:@` produces a numbered caption. One or more spaces separate the marker from its text. An optional attribute block at the end accepts an identifier, classes and named attributes:
@@ -424,6 +448,20 @@ Periods inside the first inline span can set indentation; periods in later spans
424
448
  are literal. Tabs inside a verse div are errors. The div body is opaque to Markdown block syntax:
425
449
  lines beginning `- `, `1. `, `# `, `> ` or four spaces stay verse text.
426
450
 
451
+ A verse div may sit inside a blockquote, a list item, or nested combinations of
452
+ both. Write the surrounding container prefix on each line, including blank
453
+ stanza lines. Container markers stay outside the verse text; logical lines,
454
+ indentation and stanza boundaries follow the same rules as a standalone div.
455
+ Findings point to the authored line and column, including its container prefix.
456
+
457
+ ```markdown
458
+ > ::: {.verse}
459
+ > One/..Two
460
+ >
461
+ > Three
462
+ > :::
463
+ ```
464
+
427
465
  These two bodies have the same lines and indentation:
428
466
 
429
467
  ```markdown
@@ -491,13 +529,23 @@ language's stress rules; it reports an error at the line:
491
529
  Speaker labels and stage directions (`.speaker` and `.direction`) are presentation
492
530
  only: their text is not scanned, and a line containing only those spans is skipped.
493
531
 
532
+ Wikilinks contribute only their visible labels to the beat count. An empty label
533
+ (`[[place-ashkarad|]]`) contributes the indexed target's name, including a name
534
+ from a dependency. Address segments, anchors and link attributes contribute no
535
+ beats. If an empty label has no resolved spoken text, the line is not scanned;
536
+ the link check reports invalid targets separately. Empty same-page caption
537
+ references depend on page or book numbering and leave the line unscanned when
538
+ that display text is unavailable.
539
+
494
540
  Beats are counted this way:
495
541
 
496
542
  - **Syllables** are vowel groups. A vowel is a letter whose base is `a`, `e`,
497
543
  `i`, `o` or `u`, and a pair the language lists in
498
544
  `data.sounds.syllables.vowelPairs` is one syllable.
499
- - **Every word of two or more syllables has one beat**, on its stressed
500
- syllable. The language's stress rule places it: `two` for a word of two
545
+ - **A word of two or more syllables has one beat**, on its stressed
546
+ syllable, but may go unstressed when every lexicon class is a function class:
547
+ any base other than `n`, `v`, `adj`, `num` or `name`. A word with any content
548
+ class, or no lexicon entry, keeps its beat. The language's stress rule places it: `two` for a word of two
501
549
  syllables, `longer` for a word of three or more, and a vowel carrying the
502
550
  rule's `mark` is stressed wherever it sits.
503
551
  - **A one-syllable noun, verb, adjective, numeral or name has a beat.** A
@@ -506,7 +554,7 @@ Beats are counted this way:
506
554
  through `lang=` as it is for the word check, including words inherited from
507
555
  its family. All entries for a spelling in the nearest lexicon supply their
508
556
  classes. A form the
509
- language's own rules derive takes its word's class. A word whose entries differ in class is a
557
+ language's own rules derive takes its word's class. A one-syllable word with both content and function classes has a
510
558
  beat that may be left out.
511
559
  - **A one-syllable word the lexicon does not hold is a content word** and has a
512
560
  beat. The word check reports the word separately.
package/docs/commands.md CHANGED
@@ -2255,11 +2255,12 @@ dependencies; exact Item UUID validation remains part of compilation. Reads
2255
2255
  the content tree named by `root`, defaulting to
2256
2256
  `paths.content`; writes nothing.
2257
2257
 
2258
- Every configured language note declares `data.roman`, its exact NFC Roman
2259
- source characters. Lint checks language-tagged text before mapping, using the
2258
+ Every configured language note declares `data.roman`, the NFC Roman characters
2259
+ the language adds to the shared standard: letters in accepted cases, accents,
2260
+ catch signs and digits. Lint checks language-tagged text before mapping, using the
2260
2261
  same inventory for a configured name and its BCP 47 tag. Visible linked labels
2261
2262
  are checked; destinations, attributes and verse structure are excluded. An
2262
- explicit empty inventory declares no written characters. External tags without
2263
+ explicit empty inventory declares no additions. External tags without
2263
2264
  a language-note association retain their ordinary behavior. Standalone
2264
2265
  `package-build markdown` does not load these inventories.
2265
2266
 
@@ -2698,6 +2699,11 @@ links, tables, expressions, generated sections, maps — and rendered by the
2698
2699
  same renderer the Foundry journals and the editor preview use, then drawn by
2699
2700
  the site theme's layouts.
2700
2701
 
2702
+ Progress goes to stdout as each phase starts: indexing content, rendering
2703
+ pages, writing pages, and building the search index. Rendering and writing
2704
+ include page counts; the search summary gives the number indexed. Diagnostics
2705
+ remain on stderr in `file:line:column: severity: message` form.
2706
+
2701
2707
  **A site is its homepage and its pages.** The `type: homepage` note is the
2702
2708
  package's front page at `/<contentPackage>/`; every other note is one page at
2703
2709
  `/<contentPackage>/<type>-<shortcode>/`. Nothing is generated between them
@@ -2770,8 +2776,12 @@ findings of severity `warning` included.
2770
2776
 
2771
2777
  ```
2772
2778
  $ package-build site build
2779
+ […] indexing content
2780
+ […] rendering 4306 page(s)
2781
+ […] writing 4306 page(s)
2773
2782
  package-build.config.yaml:38:24: warning: the page-not-found links to /thalorna/polity/, which the site does not publish, so the link is left out
2774
2783
  […] wrote 4306 page(s) to build/site/thalorna
2784
+ […] building the search index
2775
2785
  […] indexed 4307 page(s) for search into build/site/thalorna/pagefind
2776
2786
  ```
2777
2787
 
@@ -2794,13 +2804,25 @@ package-build site serve [--port <port>] [--host <address>] [--out <dir>]
2794
2804
 
2795
2805
  **DESCRIPTION**
2796
2806
 
2797
- Builds the site as `package-build site build` does, indexes it for search once,
2798
- and serves the deployed directory over HTTP, so every page is at the address it
2807
+ Starts HTTP and prints the listening address before the first build begins.
2808
+ Builds the site as `package-build site build` does and indexes it for search,
2809
+ while serving the deployed directory, so every page is at the address it
2799
2810
  is deployed at: `http://127.0.0.1:1313/<contentPackage>/`, or the path
2800
2811
  `site.base` states. An address the site does not publish is answered with its
2801
2812
  `404.html`. A search index `site.mergeIndexes` names is not served, since it is
2802
2813
  another site's; the site's own index is.
2803
2814
 
2815
+ Existing output stays available while a whole build runs. Incremental page and
2816
+ theme updates also prepare their files separately before replacing the served
2817
+ tree, so requests receive complete pages and styles. The new pages and
2818
+ search index replace it together after a successful build. A failed render,
2819
+ font delivery or search build leaves the previous site available. Without
2820
+ existing output, requests receive HTTP 503 with a message pointing to the
2821
+ terminal until a build succeeds; reload the page after the build completes.
2822
+ The build's indexing, rendering, writing and search phases print to stdout,
2823
+ with page counts where available. Diagnostics keep their located format on
2824
+ stderr. Whole rebuilds also refresh the search index.
2825
+
2804
2826
  The content tree and the theme are watched. A change to a note's body or
2805
2827
  frontmatter writes that note's page again; a note whose name, type or
2806
2828
  shortcode changed, a new or removed note, a note holding an `sql` table, and
@@ -2825,15 +2847,21 @@ running. Interrupt it to stop.
2825
2847
 
2826
2848
  **EXIT STATUS**
2827
2849
 
2828
- 1 when the site's settings or its cached menu cannot be read. Otherwise 0 once
2829
- interrupted.
2850
+ 1 when the site's settings or its cached menu cannot be read, or HTTP cannot
2851
+ start or stops unexpectedly. Build failures are reported while the server keeps running. Otherwise
2852
+ 0 once interrupted.
2830
2853
 
2831
2854
  **EXAMPLES**
2832
2855
 
2833
2856
  ```
2834
2857
  $ package-build site serve
2835
- […] built the site
2836
2858
  […] serving http://127.0.0.1:1313/thalorna/ — interrupt to stop
2859
+ […] indexing content
2860
+ […] rendering 4306 page(s)
2861
+ […] writing 4306 page(s)
2862
+ […] building the search index
2863
+ […] indexed 4307 page(s) for search
2864
+ […] built the site
2837
2865
  […] wrote build/site/thalorna/place-vedyara/index.html
2838
2866
  ```
2839
2867
 
@@ -3389,17 +3417,20 @@ reads the words of its `data.family` chain as its own. The languages are listed
3389
3417
  by name; each block gives the number missing, then the missing concepts under
3390
3418
  their groups in list order, each group with its count, each concept as its `id`
3391
3419
  and `gloss` separated by a tab. A language with no concept missing is reported as
3392
- covering them all. Writes nothing.
3420
+ covering them all. The default report ends with `Not covered (no data.core): …`,
3421
+ naming language notes without `data.core: true` in name order, when any exist.
3422
+ An explicit `--language` measures only the requested language and prints no
3423
+ skip line. Writes nothing.
3393
3424
 
3394
3425
  A missing concept is a fact to work through, never a finding, so the report does
3395
3426
  not change the exit status.
3396
3427
 
3397
3428
  **OPTIONS**
3398
3429
 
3399
- | Option | Type | Default | Description |
3400
- | ------------ | ------- | ------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
3401
- | `--language` | string | — | Report this language alone, by its name, its shortcode or its reference, whether or not it states `data.core`. |
3402
- | `--json` | boolean | `false` | Print `{ concepts, languages }` instead, each language `{ name, language, core, total, covered, missing }` and each missing group `{ group, count, total, concepts }`. |
3430
+ | Option | Type | Default | Description |
3431
+ | ------------ | ------- | ------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
3432
+ | `--language` | string | — | Report this language alone, by its name, its shortcode or its reference, whether or not it states `data.core`. |
3433
+ | `--json` | boolean | `false` | Print `{ concepts, notCovered, languages }` instead; `notCovered` lists unmeasured language names (empty with `--language`), each language `{ name, language, core, total, covered, missing }` and each missing group `{ group, count, total, concepts }`. |
3403
3434
 
3404
3435
  **EXIT STATUS**
3405
3436
 
@@ -3505,7 +3536,7 @@ exactly as given, at every depth, and every field it leaves out is generated:
3505
3536
 
3506
3537
  | Field | Generated value |
3507
3538
  | ------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- |
3508
- | `name.full` | Drawn from the culture's language's name lists by one of its patterns for `data.gender`, the given name from the list for that gender. |
3539
+ | `name.full` | Drawn by a pattern for `data.gender`; given names use the shared or gendered list, and drawn words start with capitals. |
3509
3540
  | `shortcode` | The name transliterated to lowercase letters and digits and cut to twelve characters; a counter replaces its tail where a being holds it. |
3510
3541
  | `type`, `subType`, `tags` | `being`, `npc`, `[generated, draft]`. |
3511
3542
  | `data.icon`, `data.species`, `data.packFolder` | The commonest value among the package's npcs of the same culture, else among all its npcs; left out where none states one. |
@@ -3549,13 +3580,25 @@ cycle. See [culture parents](reference/format-details.md#culture-parents-and-inh
3549
3580
 
3550
3581
  A profile or name lists with problems are refused with a finding at each.
3551
3582
 
3583
+ Generated `name.full` is the English display form: each word drawn from a name
3584
+ list starts with a capital, while its remaining letters and the pattern's
3585
+ connecting words keep their authored case. A caseless tongue can therefore
3586
+ supply `lumawesa` and `simi manik` to produce `Lumawesa of the Simi Manik`.
3587
+ The distinct source form keeps its authored spelling under the culture's language code,
3588
+ such as `name: {full: Lumawesa of the Simi Manik, teyu: [lumawesa of the simi manik]}`.
3589
+ If the selected pattern draws any English `{en.*}` part, the command omits
3590
+ the generated native alias. An unused English pattern does not affect a name
3591
+ drawn from another pattern.
3592
+ Supplied names and language forms keep their exact spelling and case.
3593
+
3552
3594
  A spec that states `name.full` keeps it, and the name must fit the language's
3553
- lists: one of the patterns for `data.gender`, with every placeholder filled by
3595
+ source or English display lists: one of the patterns for `data.gender`, with every placeholder filled by
3554
3596
  a name of its list, the given name from the list for `data.gender`. A name that does not fit is an
3555
3597
  error naming the part that fails and the language it was checked against; from
3556
3598
  a `--specfile`, the error is located at the name in the file. Where the
3557
- gender has no given list of its own, the given name is drawn from, and
3558
- checked against, every given list together.
3599
+ language states one shared `given` list, every gender draws from and is checked
3600
+ against it. With a gender map, a gender with no list of its own draws from and
3601
+ is checked against every given list together.
3559
3602
 
3560
3603
  Two top-level spec keys are section bodies rather than frontmatter.
3561
3604
  `appearance` is the Appearance section's markdown, written verbatim with its
@@ -2307,8 +2307,9 @@ the source directory:
2307
2307
 
2308
2308
  Optional path for a browser converter's JSON data, relative to
2309
2309
  `packageBuild.stageDir`. `package-build manifest` writes it during the module
2310
- build from the same published encodings and language vocabulary that render
2311
- native text. The path must stay within the stage, end in `.json`, and name an
2310
+ build from the same consumer-authored encoding notes, selected fonts' glyph
2311
+ metadata and language vocabulary that render native text. The path must stay
2312
+ within the stage, end in `.json`, and name an
2312
2313
  artifact other than `module.json`, `system.json` or `package.json`. Absolute
2313
2314
  paths and parent traversal are refused. Omission writes no converter file.
2314
2315
 
@@ -27,10 +27,20 @@ Start with [your first note](authoring/first-note.md). It explains the smallest
27
27
 
28
28
  A nonempty note body creates one system-agnostic Foundry JournalEntry. Content before the first H1 becomes an introduction page; every H1 starts another JournalEntryPage. A lower-level heading with an explicit `{#anchor}` starts a page too, so a link can address it. Each page name is its heading's visible text: spans, links and emphasis contribute their text without their markup. The same prose becomes a web page and can be selected for a book. The content index records the note and its frontmatter for queries and navigation.
29
29
 
30
+ A top-level `--- {.page-break}` line between blank lines starts a fresh book page
31
+ without drawing a rule, and adds no blank page when it already falls at a page
32
+ top. Foundry, the website and both editor preview views omit it. Plain thematic
33
+ breaks remain rules; code and verse keep the marker literally. Other
34
+ attributes, star or underscore markers, and markers inside containers are
35
+ findings. See [book page breaks](authoring/links-and-markup.md#book-page-breaks).
36
+
30
37
  Leading `:` captions describe the next supported block; `:@` captions also number it. The optional type accepts `figure`, `table`, `verse`, `code`, `prose`, and `example`. Captions support identifiers, classes and named attributes; unsupported following blocks are errors. See [captions and numbered references](authoring/links-and-markup.md#captions-and-numbered-references).
31
38
 
32
39
  A `.verse` div (`::: {.verse ...}` or `:::verse {...}`) sets poetry or prose. `/` ends a logical line; source newlines
33
40
  otherwise join. Blank lines or `//` end stanzas, and a standalone `--- {.canto}` after a blank line ends a canto.
41
+ Blockquotes and list items may contain verse divs, including nested combinations.
42
+ Their container prefixes are excluded from verse text, while findings retain
43
+ authored line and column positions with either LF or CRLF endings.
34
44
  Leading periods indent a logical line, including after `/` within a source line;
35
45
  a period on a joined continuation line is text. `\/` writes a literal slash.
36
46
  A concrete `enc=` on a div sets the original once in two presentations, script on the left
@@ -297,6 +297,23 @@ Like a culture conflict, each fails the index and every command that reads it
297
297
  without collecting problems. The rules are under
298
298
  [a note's language](reference/format-details.md#a-notes-language).
299
299
 
300
+ ## Book page-break findings
301
+
302
+ The optional [book page break](authoring/links-and-markup.md#book-page-breaks)
303
+ uses only `--- {.page-break}` between blocks at the note's top level.
304
+ Other classes, ids or attributes on an attributed `---` line receive findings
305
+ at their opening brace. A `.page-break` on `***` or `___` receives a finding
306
+ at the marker. A marker inside a list, block quote, alert, secret block or
307
+ fenced div receives `a page break stands between blocks at the top level of a
308
+ note`, at that marker's line and column. Missing surrounding blank lines
309
+ receive the same placement finding. Code blocks and complete verse divs are
310
+ literal and receive no page-break findings.
311
+
312
+ The author lint, pack compiler, site build, both preview views and book source
313
+ generation report these errors from the same scanner. A valid marker is
314
+ omitted on screens and emits a weak page break in the book, avoiding an extra
315
+ blank page at a page top.
316
+
300
317
  ## A worked case: versions of a work
301
318
 
302
319
  [Versions of a work](authoring/links-and-markup.md#versions-and-stanza-labels)
@@ -716,13 +716,21 @@ packageBuild:
716
716
  ```
717
717
 
718
718
  `package-build manifest` writes the JSON beneath the build stage, so the
719
- release includes the data alongside the module's other files. It embeds each
720
- language's published encoding and the lexical inputs used by the build.
719
+ release includes the data alongside the module's other files. It embeds the
720
+ consumer's authored encoding definitions, the selected fonts' glyph-code
721
+ snapshots, and the language names and lexical inputs used by the build.
722
+ Conversion rules belong to the consumer's encoding notes; fonts supply the
723
+ glyphs and their metadata.
721
724
 
722
725
  Bundle `@heroiclands/package-build/convert` into the module's browser code,
723
726
  fetch `modules/<module-id>/data/converter.json` once, and pass the parsed data
724
727
  to `encode` or `decode`. A picker can list `Object.entries(data.languages)`
725
- using each entry's `name`; each entry already states its encoding and face.
728
+ using each entry's `name`. Offer the encoding documents in `data.encodings`
729
+ whose `data.language` matches the selected language, and pass the selected
730
+ document Address as `options.enc`. That document supplies its font; language
731
+ entries carry no default encoding or face. Omitting `options.enc`, or choosing
732
+ `default`, preserves Roman text. The JSON includes all conversion inputs, so
733
+ the browser fetches no separate encoding or font metadata files.
726
734
  Show the returned findings alongside the text when a word cannot be recovered.
727
735
  See the [API](../api.md#browser-conversion) and
728
736
  [data format](../reference/format-details.md#converter-data-format).
@@ -302,12 +302,22 @@ index inside it. Nothing else renders the pages: the body of each is the same
302
302
  HTML the Foundry journals and the editor preview read, drawn into the site
303
303
  theme's layouts and minified.
304
304
 
305
+ The terminal reports indexing, rendering, writing and search indexing as they
306
+ start, including page counts where available. Progress is on stdout;
307
+ diagnostics retain their file, line and column on stderr.
308
+
305
309
  ```bash
306
310
  package-build site serve
307
311
  ```
308
312
 
309
- `package-build site serve` builds the site the same way and serves it at
310
- `http://127.0.0.1:1313/<contentPackage>/`. Edit a note and its page is written
313
+ `package-build site serve` prints its address as soon as HTTP starts, before
314
+ building the site: `http://127.0.0.1:1313/<contentPackage>/`. Existing output
315
+ remains available during the build. Successful whole builds replace the pages
316
+ and search index together; failed builds leave the last successful output in
317
+ place. A first run with no output answers HTTP 503 until a build succeeds, with
318
+ a message pointing to progress and diagnostics in the terminal.
319
+
320
+ Edit a note and its page is written
311
321
  again and the browser reloads; edit the theme and every page is drawn again.
312
322
  A change that moves, renames or adds a note builds the whole site again. See
313
323
  [`site serve`](../commands.md#package-build-site-serve) for the whole table.
@@ -618,11 +618,14 @@ that the language lists in `data.sounds.syllables.vowelPairs` is one syllable.
618
618
 
619
619
  **The beat convention, in plain words:**
620
620
 
621
- - Every word of two or more syllables has one beat, on its stressed syllable.
621
+ - A word of two or more syllables has one beat, on its stressed syllable,
622
+ but may go unstressed when every lexicon class is a function class: any base
623
+ other than `n`, `v`, `adj`, `num` or `name`. A word with any content class,
624
+ or no lexicon entry, keeps its beat.
622
625
  - A one-syllable noun, verb, adjective, numeral or name has a beat. A
623
626
  one-syllable particle, preposition, conjunction, adverb or pronoun has none.
624
627
  The lexicon entry's class decides, and a form that the language's rules derive
625
- takes its word's class. A word whose entries differ in class is a beat that
628
+ takes its word's class. A one-syllable word with both content and function classes has a beat that
626
629
  may be left out.
627
630
  - A one-syllable word the lexicon does not hold is a content word and has a
628
631
  beat; the word check reports the word separately.