Fix Matins reading-pool ordering when Nocturn 2/3 come from different sources

buildReadingPool concatenated readings in id-processing order, so a later
post-Pentecost Sunday's Nocturn 3 (from its own temporalId file) could land
ahead of its Nocturn 2 (from the newly-added month-week id) in the pool,
flipping which nocturn slot each landed in. Group by each reading's own
nocturn tag instead, ascending, preserving id/file order within each group.

Also logs three follow-up items to TODO.md: displaying "Te decet laus",
the missing responsory marker convention in nocturn-readings content, and
removing Ps 129/50 from the Lauds/Vespers ferial Preces.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EWzE3kaSZCs79fPBrrE1aF
This commit is contained in:
2026-08-23 08:02:16 -04:00
parent 72a0f8072e
commit ca28f2ef5f
2 changed files with 70 additions and 2 deletions
+51
View File
@@ -32,6 +32,38 @@ across every hour and content type in this app.
needs bilingual voice selection (La/En) worked out, and quality for Latin pronunciation
is an open question. Open, not started — needs its own design pass before content/UI work
begins.
- Matins: "Te decet laus" (the short versicle sung right after the closing Gospel+"Amen",
opening Lauds — Rule of Benedict ch. 13, `data/regula/02-13.yml`) isn't modeled or
displayed anywhere. `post-pentecost-15.yml`'s own header comment already flagged this as
"a known simplification"; the user asked (2026-08-23) for it to actually be shown. Needs a
text/part decided (own `ResolvedPart` kind vs. reusing an existing one like `versicle`)
and where in the Matins ordo it renders (after the Gospel-reading-again-in-full that
currently closes Nocturn 3, before/instead of the existing Benedicamus/Fidelium close —
see memory `vu-matins-closing`). Not started.
- Matins nocturn-readings responsory formatting is inconsistent with octave-readings':
`data/propers/octave-readings/*.yml` responsories are written with explicit `℟.`/`℣.`
marks at the start of each line (e.g. `all-saints-octave-day-6.yml`), but every
`data/propers/nocturn-readings/*.yml` responsory (both the original two proof dates and
everything from the 2026-08-23 temporal-cycle/month-week sweeps) starts straight into the
Latin/English text with no leading marker at all — confirmed by grepping every responsory
`la:` line in that directory, not an isolated miss on one file. User flagged this
(2026-08-23) via "the responsory after the Augustine reading needs to start with the R.;
that R. is missing" — real, but the fix is either (a) prepend `℟.`/`R.` (and `℣.`/`V.`)
to every existing nocturn-readings responsory's text, matching octave-readings' convention
exactly, or (b) inject the marker at render time in `ui/hour-view.ts`'s lesson-responsory
rendering instead of in content, which would fix every file (old and new) in one place but
needs the responsory text's internal `\nV. ...\nR. ...` structure parsed rather than
treated as opaque text. Needs a decision before either bulk-editing ~30+ content files or
touching the renderer — not started.
- Lauds/Vespers ferial Preces (`data/propers/common/lauds-preces-feriales.yml`,
`vespers-preces-feriales.yml`) each embed a full psalm inline within the Preces text
itself — Psalm 129 (De profundis) at Lauds, Psalm 50 (Miserere) at Vespers, per the
reference engine's own `[Preces feriales Vespera] @:Preces feriales Laudes:s/129/50/`
substitution (see each file's own header comment). User asked (2026-08-23) to remove
both psalms from the Preces. Not started — needs deciding whether to just delete the
psalm block (and its own trailing Gloria Patri line) from each file's `text.la`/`text.en`
wholesale, or whether the surrounding V/R litany needs any adjustment once the psalm's
gone.
2. **Minimal-proof-done, bulk content pending** — mechanism is built and trusted (a proof
date/entry already resolves correctly end-to-end); what's left is authoring/importing more
@@ -1500,6 +1532,25 @@ reach (there are none within Aug-Nov — all 20 files are authored); the rest of
cycle (Advent through Eastertide, per the post-Pentecost sweep's own "not done" list above)
remains open and is unrelated to this particular gap.
**Ordering bug found and fixed same day**: pooling a `temporalId`'s own file (Nocturn 3 only,
for a late post-Pentecost Sunday) alongside a separately-sourced `month-week-<id>` file
(Nocturn 2 only) put the Nocturn 3 reading ahead of the Nocturn 2 reading in the pool — the two
sources were being concatenated in `nocturnReadingIds` set order, not grouped by each reading's
own `nocturn` tag, so `distributeIntoNocturns`'s front-light positional slotting (1st pool
reading -> Nocturn 1, 2nd -> Nocturn 2, rest -> Nocturn 3) put the Nocturn-3-tagged reading into
the Nocturn 2 slot and vice versa. Confirmed live on the date that surfaced the whole month-week
gap in the first place (2026-08-23, `post-pentecost-13` + `month-week-084`): St. Gregory's
Moralia commentary (tagged `nocturn: 2`) was showing up after St. Augustine's homily (tagged
`nocturn: 3`) instead of before it. Fixed in `buildReadingPool` (`hours/matins.ts`) by bucketing
every contributing id's readings by `reading.nocturn` and emitting the buckets in ascending
numeric order — not hardcoded to `[2, 3]`, since Ember days' own nocturn-readings files use
`nocturn: 1` for their single-nocturn structure, and an initial `[2, 3]`-only version of this
fix silently dropped those (caught by `tests/calendar/ember-days.test.ts` failing). Within each
bucket, id order (and each file's own reading order) is preserved, matching this pool's usual
priority rule. `npm test` green at 460 tests — no fixture changes needed, since every
previously-authored id's own file already had its readings correctly ordered internally; only
the cross-source case this pass introduced was actually broken.
### Pre-1955-but-post-1617 saints' Matins readings — done (2026-08)
Resolved the former "mechanism gap" above into a concrete, scoped content pass — no new
+19 -2
View File
@@ -394,11 +394,28 @@ function buildReadingPool(day: LiturgicalDay, temporalId: string, date: string):
responsory: r.responsory ? { text: r.responsory, status: { la: 'verified', en: 'verified' } } : undefined,
});
}
for (const id of nocturnReadingIds(day, temporalId, date)) {
// Grouped by the reading's own `nocturn` tag (ascending), not by which id
// contributed it — a later post-Pentecost Sunday's Nocturn 2 now comes
// from a different source (the month-week id) than its Nocturn 3 (its own
// temporalId file), and pooling in plain id order would put that Nocturn
// 3 content ahead of the Nocturn 2 content supplied by a later-processed
// id. Within each nocturn-number group, id order (and each file's own
// reading order) is preserved, matching this pool's usual priority rule.
// Not hardcoded to [2, 3]: Ember days' own nocturn-readings files use
// `nocturn: 1` (their single-nocturn structure), so every tag present
// must be handled, not just the usual Sunday/feast pair.
const ids = nocturnReadingIds(day, temporalId, date);
const byNocturn = new Map<number, ResolvedPart[]>();
for (const id of ids) {
for (const reading of getNocturnReadings(id)) {
parts.push(nocturnReadingPart(reading));
const bucket = byNocturn.get(reading.nocturn) ?? [];
bucket.push(nocturnReadingPart(reading));
byNocturn.set(reading.nocturn, bucket);
}
}
for (const nocturnNumber of [...byNocturn.keys()].sort((a, b) => a - b)) {
parts.push(...byNocturn.get(nocturnNumber)!);
}
for (const octave of activeOctavesFor(date)) {
const reading = getOctaveReading(octave.id, octave.dayNumber);
if (reading) {