From efb1717e2780cf0e02b06d122ba497ab779d60f0 Mon Sep 17 00:00:00 2001 From: Will Estes Date: Sun, 30 Aug 2026 23:21:03 -0400 Subject: [PATCH] Add test coverage and TODO.md log for the full temporal-cycle sweep Extends tests/hours/matins.test.ts's proof-date table to cover all 28 newly-authored temporal ids end-to-end, including Easter and Pentecost Sunday. Documents the full sweep in TODO.md, including the easter-sunday/pentecost-sunday misdiagnosis and correction so the "1 nocturn in the source" vs. "no content" distinction doesn't get re-litigated later. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01ME8QNPdEmHSbSx1r6VmDRG --- TODO.md | 85 ++++++++++++++++++++++++++++++++++++-- tests/hours/matins.test.ts | 82 +++++++++++++++++++++++++++++++++++- 2 files changed, 161 insertions(+), 6 deletions(-) diff --git a/TODO.md b/TODO.md index 26ec6d1..2d6fe3d 100644 --- a/TODO.md +++ b/TODO.md @@ -41,10 +41,11 @@ across every hour and content type in this app. - Matins sanctoral-side readings: the per-book Matins responsory pool has only one book seeded (`isa`) — needs expanding. Bible-plan TSV content also isn't yet in a test table (see "Bible-plan TSV — bulk conversion done" below). - - Matins temporal-cycle readings: only the post-Pentecost season (`post-pentecost-01` - through `-24`) is authored (see "Temporal-cycle nocturn-readings sweep" below) — the rest - of the year (Advent, Christmastide, post-Epiphany, Septuagesima through Lent/Passiontide, - Eastertide) has no Nocturn 2/3 content yet. + - ~~Matins temporal-cycle readings~~ — DONE (2026-08): all 52 temporal ids across the whole + year now have authored Nocturn 2/3 content, no remaining gaps (see "Temporal-cycle + nocturn-readings sweep" below, including the easter-sunday/pentecost-sunday correction). + Moved out of this "bulk content pending" section since there's no more bulk content left to + author here. - The bulk Vulgate (Latin) + Douay-Rheims (English) scripture import into `src/data/scripture/` — long-term project, explicitly deferred; Matins scripture citations resolve to `missing` text until it lands. @@ -1530,6 +1531,82 @@ changes). so a future reader doesn't go looking for `post-pentecost-13-tuesday.yml`-style files that were never meant to exist. +### Temporal-cycle nocturn-readings sweep — rest of the year done (2026-08) + +Extended the post-Pentecost sweep above to the other 26 temporal ids — Advent (`advent-1..4`), +`christmas-octave-sunday`, post-Epiphany (`post-epiphany-1..6`), Septuagesima through Lent +(`septuagesima`, `sexagesima`, `quinquagesima`, `lent-1..4`), Passiontide/Holy Week +(`passion-sunday`, `palm-sunday`), and Eastertide (`easter-octave`, `easter-3..6`, +`sunday-after-ascension`) — using the reference engine's own per-season Tempora files +(`Adv1-0.txt`..`Adv4-0.txt`, `Nat1-0.txt`, `Epi1-0a.txt`/`Epi2-0.txt`..`Epi6-0.txt`, +`Quadp1-0.txt`..`Quadp3-0.txt`, `Quad1-0.txt`..`Quad6-0.txt`, `Pasc0-0.txt`..`Pasc7-0.txt`). +24 of 26 ids got real, sourced Nocturn 2/3 content on the first pass; the remaining 2 temporal +ids in this season range (`easter-sunday`, `pentecost-sunday`) were initially misdiagnosed as +genuine source gaps (see below) — corrected the same day, both now authored too. + +- **`post-epiphany-1`'s real source is `Epi1-0a.txt`, not `Epi1-0.txt`**: the reference + engine reassigns `Epi1-0.txt` to the later (1893/1921) feast of the Holy Family, which this + project doesn't model (see the temporal-cycle survey note on Holy Family as a still-open + pre-1955 gap, elsewhere in this file). `Epi1-0b.txt` (a thin `Dominica I post Epiphaniam` + wrapper cross-referencing `Epi1-0a` for its own Nocturn 1) confirmed `Epi1-0a.txt` carries the + real older "Dominica infra Octavam Epiphaniæ" office underneath. That file also has a real + structural gap of its own: its `[Rule]` reads "Initia cum responsory" and it carries no + `[Responsory4]`-`[Responsory9]` tags at all — its Nocturn 2/3 responsories come from a + continuous weekly "Historia" cycle not transcribed per-Sunday in the static files (same class + of gap as the post-Pentecost season's un-sourced Nocturn 2 weeks). `post-epiphany-1.yml`'s + two readings are authored without a `responsory` field for this reason, not with an invented + one. +- **`sunday-after-ascension`'s source file (`Pasc6-0.txt`) has no responsories of its own + either** (only `[Responsory8]`, proper to a single lesson) — its own `[Rank]`/`[Rule]` say + "ex Tempora/Pasc5-4" (Ascension Thursday itself), meaning the whole office, responsories + included, is inherited from that day. Resolved by pulling the representative responsories + directly from `Pasc5-4.txt` rather than leaving them unauthored. +- **`easter-sunday` (`Pasc0-0.txt`) and `pentecost-sunday` (`Pasc7-0.txt`) were initially + (wrongly) called source-level gaps, then corrected same day.** Both files' own `[Rule]` really + does read "1 nocturn" (their whole night office collapses to 3 lessons, straight to Te Deum) — + true, and unlike every other Sunday in this sweep. But "1 nocturn instead of 3" is not the same + claim as "no Nocturn 2/3 content exists": `Lectio1` in both files opens with a bare Gospel + incipit ("Et réliqua") immediately followed by a full patristic homily (Easter: Pope St. + Gregory the Great, Homily 21 on the Gospels, on Mark 16:1-7; Pentecost: the same, Homily 30, on + John 14:23-31) continuing through Lectio2-3 — real, substantial, translatable content, just + compressed into one nocturn rather than split across three the way an ordinary Sunday's 9 + lessons are. `hours/matins.ts` already supports single-nocturn source content tagged + `nocturn: 1` (the Ember days' own nocturn-readings files do exactly this), and this app's own + `threeNocturns` rule (every Sunday renders 3 nocturns regardless of the historical rubric for + this particular feast) reslots the pool across 3 rendered nocturns anyway — the `nocturn: 1` + tag only affects pool-ordering relative to other same-day contributors, not final placement. + Authored `easter-sunday.yml`/`pentecost-sunday.yml` the same way as every other Nocturn-3-style + entry in this sweep: incipit dropped, Lectio1's homily portion combined with Lectio2-3 into one + reading, Responsory1 (the only nocturn-proper responsory transcribed in either source — + Responsory3 doesn't exist, the office runs straight to the Te Deum after Responsory2's Gloria + repeat). +- **Cross-reference reuse continues the post-Pentecost pattern**: several Sundays' + `[Responsory4]`/`[Responsory7]` tags are themselves cross-references to a different file + (e.g. `post-epiphany-3` through `-6` all point back to `post-epiphany-2`'s own Responsory4/7; + `easter-6` points back to `easter-5`'s Pasc4-0 responsories) rather than fresh text — resolved + and transcribed directly, same as before, not left as unresolved pointers. +- **The "homily begins inside Lectio7 itself" pattern, not just at Lectio8, held for nearly + every Sunday in this pass** (not only the few post-Pentecost weeks that showed it) — every + file's Nocturn 3 combines Lectio7's homily portion (dropping only the bare Gospel incipit) + with Lectio8-9, using Lectio7's own responsory to represent the combined reading. + +Live-verified via `npx tsc --noEmit`, `npm run build`, and `npm test` (806 tests, up from 777 — +a "rest-of-year temporal nocturn-readings sweep" describe block in `tests/hours/matins.test.ts` +covering all 28 ids end-to-end, `easter-sunday`/`pentecost-sunday` included; two pre-existing +tests in the ferial-branch describe block also needed a small filter update, since Advent I's +own ferial dates now legitimately pool in `advent-1.yml`'s patristic content alongside the +user's bible-plan readings). No fixture collisions (pure content addition under already-existing +ids, no calendar changes) except one real interaction the new content surfaced: `2026-02-23`, +initially picked as a `lent-1` proof date, turned out to actually be the Vigil of St. Matthias +transferred onto that Sunday and winning outright — swapped for `2031-03-03`, a clean `lent-1` +date. + +**Temporal-cycle nocturn-readings sweep, all 52 ids, is now fully complete — no remaining gaps.** +The lesson from the easter-sunday/pentecost-sunday misdiagnosis: "the source's own `[Rule]` says +1 nocturn" is a claim about nocturn *count*, not about whether content exists — always check +whether `[Lectio1]`-`[Lectio3]` (or whichever lessons exist) carry real prose before calling a +short nocturn structure a content gap. + ### Calendar-month/week nocturn-readings import — done (2026-08) Follow-up to the post-Pentecost sweep above: the user asked about the "Nocturn 3 only, from diff --git a/tests/hours/matins.test.ts b/tests/hours/matins.test.ts index 0ad8781..bd1ea07 100644 --- a/tests/hours/matins.test.ts +++ b/tests/hours/matins.test.ts @@ -71,7 +71,14 @@ describe('resolveOrdo("matins", ...) ferial (1-nocturn) branch', () => { }); it("resolves the user's own bible-plan readings for the day, not the historical lectionary", () => { - const lessons = ordo.parts.filter((p) => p.kind === 'lesson'); + // Filtered to unlabeled lessons only: since the temporal-cycle + // nocturn-readings sweep authored advent-1.yml (2026-08), this same + // date's Nocturn 2/3 pool also gains two labeled (patristic) lesson + // entries alongside the user's own bible-plan readings — a real, + // separate content source this test isn't about (see + // hours/matins.ts's own nocturnReadingPart, which is the only lesson + // constructor that sets `label`). + const lessons = ordo.parts.filter((p) => p.kind === 'lesson' && !p.label); // Plain 2 bible-plan readings on this particular clean ferial (no // commemorated saint here) — see the next test for the 3-reading case. expect(lessons).toHaveLength(2); @@ -91,7 +98,9 @@ describe('resolveOrdo("matins", ...) ferial (1-nocturn) branch', () => { // Semiduplex threshold). 2025-12-02, a Tuesday, keeps the same 9-psalm // ferial shape as the other tests in this block. const commemoratedOrdo = resolveOrdo('matins', '2025-12-02'); - const lessons = commemoratedOrdo.parts.filter((p) => p.kind === 'lesson'); + // Same unlabeled-lesson filter as the test above — advent-1.yml's + // patristic content also pools into this date. + const lessons = commemoratedOrdo.parts.filter((p) => p.kind === 'lesson' && !p.label); expect(lessons).toHaveLength(3); const citations = lessons.map((l) => (l as { text: { citation?: { en?: string } } }).text.citation?.en); expect(citations).toEqual(['Isa 6; Isa 7', 'Sap 2', undefined]); @@ -495,6 +504,75 @@ describe('resolveOrdo("matins", ...) post-Pentecost season nocturn-readings swee }); }); +// Temporal-cycle nocturn-readings sweep, rest of the year (2026-08): the +// same sweep extended past the post-Pentecost season to the other 26 +// temporal ids (Advent through Eastertide), sourced from the reference +// engine's own Tempora/-0.txt files (Adv1-0, Nat1-0, Epi1-0a - +// post-epiphany-1's actual source, since Epi1-0.txt itself is the later +// Holy Family override, not this Sunday - Quadp1-3-0, Quad1-6-0, Pasc0-7-0). +// Two ids are deliberate, source-confirmed gaps, not oversights: +// easter-sunday and pentecost-sunday both keep the ancient single-nocturn, +// three-lesson form in the source (no Lectio4-9 at all), so neither has a +// nocturn-readings file. See TODO.md and memory +// vu-temporal-nocturn-sweep-progress for the full sweep record. +describe('resolveOrdo("matins", ...) rest-of-year temporal nocturn-readings sweep (2026-08)', () => { + function lessonStatuses(date: string) { + const ordo = resolveOrdo('matins', date); + return ordo.parts + .filter((p) => p.kind === 'lesson') + .map((p) => (p as { text: { status: Record } }).text.status); + } + + it.each([ + ['2028-12-04', 'advent-1'], + ['2026-12-07', 'advent-2'], + ['2026-12-14', 'advent-3'], + ['2026-12-21', 'advent-4'], + ['2026-01-05', 'christmas-octave-sunday'], + ['2026-01-12', 'post-epiphany-1'], + ['2026-01-19', 'post-epiphany-2'], + ['2026-01-26', 'post-epiphany-3'], + ['2027-11-01', 'post-epiphany-4 (a resumed post-Pentecost-overflow Sunday, not a January date)'], + ['2026-11-09', 'post-epiphany-5 (resumed)'], + ['2026-11-16', 'post-epiphany-6 (resumed)'], + ['2026-02-02', 'septuagesima'], + ['2026-02-09', 'sexagesima'], + ['2026-02-16', 'quinquagesima'], + ['2031-03-03', 'lent-1 (not 2026-02-23: that date is really the Vigil of St. Matthias, transferred onto the Sunday and winning outright)'], + ['2026-03-02', 'lent-2'], + ['2026-03-09', 'lent-3'], + ['2026-03-16', 'lent-4'], + ['2026-03-23', 'passion-sunday'], + ['2027-03-22', 'palm-sunday'], + ['2026-04-13', 'easter-octave'], + ['2026-04-20', 'easter-3'], + ['2026-04-27', 'easter-4'], + ['2026-05-04', 'easter-5'], + ['2026-05-11', 'easter-6'], + ['2026-05-18', 'sunday-after-ascension'], + ['2026-04-05', 'easter-sunday'], + ['2026-05-24', 'pentecost-sunday'], + ])('%s (%s) resolves at least one non-missing lesson', (date) => { + const statuses = lessonStatuses(date); + expect(statuses.length).toBeGreaterThan(0); + expect(statuses.some((s) => s.la !== 'missing' && s.en !== 'missing')).toBe(true); + }); + + it('every rest-of-year temporal id has an authored nocturn-readings file', async () => { + const { getNocturnReadings } = await import('../../src/propers/nocturn-readings'); + const ids = [ + 'advent-1', 'advent-2', 'advent-3', 'advent-4', 'christmas-octave-sunday', + 'post-epiphany-1', 'post-epiphany-2', 'post-epiphany-3', 'post-epiphany-4', 'post-epiphany-5', 'post-epiphany-6', + 'septuagesima', 'sexagesima', 'quinquagesima', 'lent-1', 'lent-2', 'lent-3', 'lent-4', + 'passion-sunday', 'palm-sunday', 'easter-octave', 'easter-3', 'easter-4', 'easter-5', 'easter-6', + 'sunday-after-ascension', 'easter-sunday', 'pentecost-sunday', + ]; + for (const id of ids) { + expect(getNocturnReadings(id).length, id).toBeGreaterThan(0); + } + }); +}); + // The calendar-month/week Nocturn 2 import (2026-08): the later // post-Pentecost Sundays' real Nocturn 2 is keyed by civil calendar // month/week (calendar/month-week-id.ts's monthWeekId), not by