diff --git a/TODO.md b/TODO.md index 980f5f5..b1b2d6c 100644 --- a/TODO.md +++ b/TODO.md @@ -19,13 +19,13 @@ across every hour and content type in this app. 1. **Mechanism gaps** — a real design/code piece not built yet, blocking any content pass on it from starting: - - ~~Easter's own octave~~ — DONE (2026-09-01/02): the transfer-cascade piece (calendar - precedence within Holy Week/Easter Day/the Octave/Low Sunday) and Easter Sunday/Monday/ - Tuesday's own proper Matins (psalms/antiphons/versicles/canticles, sharing one authored - entry via a new general-purpose temporal-override mechanism) are both built and + - ~~Easter's own octave~~ — DONE (2026-09-01/02): calendar precedence (the transfer-cascade + piece within Holy Week/Easter Day/the Octave/Low Sunday), Easter Sunday/Monday/Tuesday's + own proper Matins, and Wednesday-Saturday's own antiphons are all built, authored, and live-verified — see the dated log's "Easter octave transfer cascade" and "Easter's own - octave" entries. What's left is narrower, moved to the bulk-content section below: - Wednesday-Saturday of the Octave's own (different, not yet authored) antiphons. + octave" entries (two general-purpose mechanisms came out of this: a Sunday-branch and a + ferial-branch temporal-named-override, both reusable by any future temporal feast wanting + its own proper psalms/lessons, not scoped to Easter specifically). - Date picker: `day-nav.ts` currently only steps a day at a time (prev/next); there's no way to jump to an arbitrary date without walking there one day at a time or editing the URL. Needs a calendar-grid picker UI — open, not started. @@ -53,11 +53,6 @@ across every hour and content type in this app. 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. - - Wednesday-Saturday of the Easter Octave: real Paschal-alleluia antiphons exist in the - reference engine (live-verified 2026-04-08) over a differently-sized psalm set than vu's - own fixed ferial table — 4 days of antiphon-only content, same shape as the existing - Advent/Paschaltide seasonal-overlay pattern, layered onto vu's own psalm numbers rather - than the reference engine's. See the dated log's "Easter's own octave" entry. - 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. @@ -4934,3 +4929,28 @@ seasonal-overlay pattern, layered onto vu's own ferial psalm numbers rather than reproduce the reference engine's differently-sized set — not started. `npm test` (828 passed), `tsc --noEmit`, and `npm run build` all pass. + +### Easter's own octave — Wednesday-Saturday's antiphons done too, fully closing the item (2026-09-02, same day) + +Closes the remaining open piece from the entry above. Live-verified: all 4 weekdays +(Wednesday-Saturday) share the exact same 2 antiphons verbatim — not per-day content, despite +each drawing on a different Gospel reading in the nocturn-readings pool ("Jesum qui crucifíxus +est quǽritis, allelúja..." / "Veníte et vidéte locum ubi pósitus erat Dóminus, allelúja, +allelúja.", Pofficium.pl, prayMatutinum, Monastic Tridentinum 1617, 2026-04-08/09/10/11). + +New general-purpose mechanism, the ferial-side counterpart to the Sunday-named-override built +earlier the same day: `data/hours/matins-ferial-named-antiphon-overrides.yml` (a temporal id's +own antiphon list, optionally restricted to specific weekdays — same `weekdays` idiom as the +Sunday-side override) + `hours/matins.ts`'s `ferialNamedAntiphonedNocturn`, checked first in +`ferialAntiphonedNocturn` ahead of the plain per-weekday default. The antiphon list is +distributed evenly across the weekday's own already-fixed psalm-group count (not tied to +specific psalm numbers), since one entry is meant to be reused across several different +weekdays rather than authored per-day — psalm numbers themselves are unchanged from the plain +ferial default (vu's own fixed table, not the reference engine's differently-sized Paschaltide- +contracted one). + +With this, "Easter's own octave" is fully closed: calendar precedence (2026-09-01), Sunday/ +Monday/Tuesday's shared proper psalmody (2026-09-02), and now Wednesday-Saturday's antiphons +(2026-09-02, same day). See memory `vu_easter_octave_proper_matins` for the full writeup. + +`npm test` (832 passed), `tsc --noEmit`, and `npm run build` all pass. diff --git a/src/data/hours/matins-ferial-named-antiphon-overrides.yml b/src/data/hours/matins-ferial-named-antiphon-overrides.yml new file mode 100644 index 0000000..97b76fa --- /dev/null +++ b/src/data/hours/matins-ferial-named-antiphon-overrides.yml @@ -0,0 +1,37 @@ +# Ferial (1-nocturn) Matins antiphon override for a specific temporal id +# whose plain per-weekday antiphons (matins-ferial-antiphons.yml) don't +# apply — the ferial-side counterpart to matins-sunday-named-nocturn- +# overrides.yml's `weekdays` field/general mechanism, added for the same +# reason: some temporal feasts have their own real antiphon content that +# should override the plain weekday default without a new part kind or +# psalm-set change. +# +# `antiphons` is an ordered list, distributed as evenly as possible across +# the target weekday's own already-fixed psalm-group count (from +# matins-ferial-antiphons.yml) — NOT tied to specific psalm numbers, since +# one entry here is meant to be reused across several different weekdays +# (each with its own different psalm set) rather than authored per-day. +# `hours/matins.ts`'s `ferialNamedAntiphonedNocturn` does the splitting. +# +# Easter Octave, Wednesday-Saturday (`easter-sunday`, restricted via +# `weekdays` the same way the Sunday-side override restricts itself to +# just Sunday/Monday/Tuesday of the same span): live-verified (Pofficium.pl, +# prayMatutinum, Monastic Tridentinum 1617, 2026-04-08/09/10/11) that all 4 +# weekdays share the exact same 2 antiphons verbatim — not per-day content, +# despite each having a different Gospel reading in the nocturn-readings +# pool. The reference engine itself renders these 2 antiphons over its own +# 2-nocturn, differently-sized psalm set (the historical Rule-of-Benedict +# Paschaltide contraction) — not reproduced here, since this app already +# deliberately rejects that contraction (see CLAUDE.md's "No RB summer +# contraction"). Instead, layered onto vu's own already-fixed per-weekday +# 9-psalm ferial pool, split into 2 roughly-even halves by psalm-group +# count (a design choice, not a transcription — the reference's own +# antiphon/psalm-group boundary doesn't map onto vu's differently-sized +# pool at all). +easter-sunday: + weekdays: [wednesday, thursday, friday, saturday] + antiphons: + - la: "Jesum qui crucifíxus est quǽritis, allelúja; non est hic: surréxit enim, sicut dixit vobis, allelúja." + en: "You seek Jesus who was crucified, alleluia; he is not here, for he is risen, as he said, alleluia." + - la: "Veníte et vidéte locum ubi pósitus erat Dóminus, allelúja, allelúja." + en: "Come, see the place where the Lord was laid, alleluia, alleluia." diff --git a/tests/hours/matins.test.ts b/tests/hours/matins.test.ts index 3151224..16038a7 100644 --- a/tests/hours/matins.test.ts +++ b/tests/hours/matins.test.ts @@ -391,12 +391,14 @@ describe('resolveOrdo("matins", ...) Easter Sunday/Monday/Tuesday shared proper expect(nocturn3?.text.text.la).toBe('V. Gavísi sunt discípuli, allelúja.\nR. Viso Dómino, allelúja.'); }); - it('Wednesday-Saturday of the Octave (same winner.id, excluded by `weekdays`) keep the plain 1-nocturn ferial branch unchanged', () => { + it('Wednesday-Saturday of the Octave (same winner.id, excluded by the Sunday-side `weekdays`) stay in the plain 1-nocturn ferial branch, same psalm numbers, no Te Deum', () => { const ordo = resolveOrdo('matins', '2026-04-08'); // Wednesday of Easter week const day = resolveDay('2026-04-08'); expect(day.winner).toEqual({ kind: 'temporal', id: 'easter-sunday' }); const psalms = ordo.parts.filter((p) => p.kind === 'psalm').map((p) => (p as { psalmNumber: number }).psalmNumber); - // Plain ferial Wednesday table, same as any other Wednesday. + // Plain ferial Wednesday psalm numbers, same as any other Wednesday -- + // only the antiphon layer differs (see the ferial-named-override + // describe block below), not the psalmody itself. expect(psalms.slice(2)).toEqual(getPsalmsFor('matins', 'wednesday').map((r) => r.number)); expect(ordo.parts.some((p) => p.kind === 'te-deum')).toBe(false); }); @@ -408,6 +410,37 @@ describe('resolveOrdo("matins", ...) Easter Sunday/Monday/Tuesday shared proper }); }); +describe('resolveOrdo("matins", ...) Easter Octave Wednesday-Saturday ferial named antiphon override (2026-09-02 fix)', () => { + // All 4 weekdays share the exact same 2 antiphons verbatim (live- + // verified 2026-04-08/09/10/11) -- own psalm numbers per weekday + // unchanged, split evenly across the weekday's own psalm-group count. + const EASTER_OCTAVE_WEEKDAYS = ['2026-04-08', '2026-04-09', '2026-04-10', '2026-04-11'] as const; + + it.each(EASTER_OCTAVE_WEEKDAYS)('%s gets the same 2 real antiphons, own weekday psalm numbers unchanged', (date) => { + const day = resolveDay(date); + expect(day.winner).toEqual({ kind: 'temporal', id: 'easter-sunday' }); + const ordo = resolveOrdo('matins', date); + // Ps 3 + 94 (opening/invitatory) plus this weekday's own real ferial table. + const psalms = ordo.parts.filter((p) => p.kind === 'psalm').map((p) => (p as { psalmNumber: number }).psalmNumber); + expect(psalms.slice(2)).toEqual(getPsalmsFor('matins', day.weekday as Exclude).map((r) => r.number)); + + // Only the 2 named-override antiphons appear in the nocturn (skip the + // Invitatory's own antiphon, index 0). + const antiphons = ordo.parts.filter((p) => p.kind === 'antiphon') as { text: { text: Record } }[]; + const [, first, second] = antiphons; + expect(first?.text.text.la).toBe( + 'Ant. Jesum qui crucifíxus est quǽritis, allelúja; non est hic: surréxit enim, sicut dixit vobis, allelúja.', + ); + expect(second?.text.text.la).toBe('Ant. Veníte et vidéte locum ubi pósitus erat Dóminus, allelúja, allelúja.'); + expect(antiphons).toHaveLength(3); + + // No Te Deum -- still the 1-nocturn ferial branch, not promoted to 3. + expect(ordo.parts.some((p) => p.kind === 'te-deum')).toBe(false); + // No stray versicle in the psalmody (same fix as the plain ferial branch). + expect(ordo.parts.slice(5).some((p) => p.kind === 'versicle')).toBe(false); + }); +}); + describe('resolveOrdo("matins", ...) Duplex+ weekday-feast (3-nocturn, non-Sunday) branch', () => { // 2026-08-17 -- St. Lawrence's own octave closing day ("In Octava S. // Laurentii Martyris ~ Duplex"), live-verified directly against the