From 57acbeefe9b6e91495e43102031202b5e50b4435 Mon Sep 17 00:00:00 2001 From: Will Estes Date: Wed, 2 Sep 2026 07:42:39 -0400 Subject: [PATCH] Author Easter Sunday/Monday/Tuesday's shared proper Matins MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Easter Sunday, Monday, and Tuesday of the Octave share winner {kind: temporal, id: easter-sunday}, and now share one live-verified proper Matins psalmody: Nocturn 1 (Ps 1,2,8,15,23,27 under one antiphon), Nocturn 2 (Ps 29,63,65,75,87,107 under one antiphon), Nocturn 3 (the same 3 canticles already authored for the ordinary-Paschaltide seasonal override, under its own antiphon) — sourced from Pofficium.pl (Monastic Tridentinum 1617, prayMatutinum) against 2026-04-05/06/07. All 3 nocturn-closing versicles are byte-identical to the ones already authored for the Paschaltide override, confirming those are shared season-wide rather than coincidentally reused. The 12 patristic lessons were already authored in an earlier sweep (nocturn-readings/easter-sunday.yml) and needed no new work. Restricted to just these 3 days via the new weekdays field: the Octave's other 4 weekdays (Wed-Sat) share the same winner id but have their own different, not-yet-authored antiphons, tracked as a separate open item in TODO.md. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01X26BkFT3ARbtUGXuBmzUmD --- TODO.md | 70 ++++++++++++++++-- .../matins-sunday-named-nocturn-overrides.yml | 73 +++++++++++++++++++ tests/hours/matins.test.ts | 48 ++++++++++++ 3 files changed, 185 insertions(+), 6 deletions(-) diff --git a/TODO.md b/TODO.md index d868a69..980f5f5 100644 --- a/TODO.md +++ b/TODO.md @@ -19,12 +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): the transfer-cascade piece (a candidate - impeded within Holy Week/Easter Day/the Octave/Low Sunday now correctly skips the whole - span and keeps walking forward, FIFO, until it lands) is built and live-verified — see - the dated log's "Easter octave transfer cascade" entry. Content-wise, Easter Day/the - Octave's own full Matins proper (distinct psalms, not just antiphons) is still not - modeled — a separate, narrower remaining gap, not this one. + - ~~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 + 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. - 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. @@ -52,6 +53,11 @@ 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. @@ -4876,3 +4882,55 @@ Terce/Sext/None antiphons+versicles, Te Deum present. See memory `vu_matins_semiduplex_threshold` for full detail. `npm test` (823 passed), `tsc --noEmit`, and `npm run build` all pass. + +### Easter's own octave — Sunday/Monday/Tuesday proper Matins authored, general temporal-override mechanism added (2026-09-02) + +Closes most of the long-tabled "Easter's own octave" mechanism gap (Priority section, item 1). +User pushed back on an initial overcomplicated read ("psalms with lections" looked like a new +rendering shape at first glance) — turned out, after live-verifying the actual structure, to +be exactly what the user guessed: a temporal override file that "looks weird" only because of +its content (13 psalms under one antiphon per nocturn, not the usual per-pair grouping), not +because it needs new mechanism. Every psalm already gets its own Gloria Patri centrally +(`hours/index.ts`'s `withGloriaPatri`) regardless of hour, and the 12 patristic lessons were +*already* authored (`data/propers/nocturn-readings/easter-sunday.yml`, from the earlier +temporal nocturn-readings sweep) — the only real gap was the psalmody/antiphon/versicle/ +canticle layer. + +**Content**: new `easter-sunday` entry in `matins-sunday-named-nocturn-overrides.yml` — +live-verified against the reference engine (`Pofficium.pl`, `prayMatutinum`, Monastic +Tridentinum 1617, 2026-04-05/06/07) — Nocturn 1: psalms 1,2,8,15,23,27 under one shared +antiphon ("Ego sum qui sum"); Nocturn 2: psalms 29,63,65,75,87,107 under one shared antiphon +("Terra trémuit"); Nocturn 3: the same 3 canticles already authored for the ordinary- +Paschaltide seasonal override (Isa 63:1-5, Osee 6:1-6, Soph 3:8-13), under its own antiphon +("Nolíte expavéscere"). All 3 nocturn-closing versicles are byte-identical to the ones +already authored in `matins-sunday-paschaltide-nocturn-overrides.yml`, confirming those are +shared season-wide rather than coincidentally reused. + +**Mechanism (small, general-purpose, not Easter-specific)**: `hours/matins.ts`'s +`threeNocturns` gate and the Sunday-branch dispatch previously only ever fired for +`day.weekday === 'sunday'` or a sanctoral winner — a *temporal* winner had no path into 3- +nocturn proper content at all, even with a matching `sundayNamedOverrides` entry. Generalized: +a temporal winner with an authored named override is now eligible too (`hasTemporalNamedOverride`), +reusing the exact same `sundayPsalmNocturn`/`sundayCanticleNocturn` render functions the Sunday +and saint-proper branches already share — deliberately built as a reusable feature (user, +2026-09-02: "we want temporal override anyway... there are temporal feasts that could have +their own psalms/lessons"), not a one-off hack. + +**The scoping wrinkle that made this non-trivial**: `winner.id` is `'easter-sunday'` for the +*entire* Octave (all 6 weekdays), but only Sunday/Monday/Tuesday are live-verified to share +this content — Wednesday-Saturday have their own different (Paschal-alleluia-flavored, not yet +authored) antiphons over a differently-shaped psalm set. A naive "any day with this winner.id" +check would have wrongly applied Sunday's content through Saturday. Fixed with a new optional +`weekdays` field on a named-override entry (`easter-sunday: weekdays: [sunday, monday, +tuesday]`) — omitted (as `sunday-after-ascension` still does) means "every day carrying this +temporalId," the common case for a temporalId that already maps to exactly one calendar day. + +**Still open, a separate smaller gap**: Wednesday-Saturday of the Easter Octave — live-verified +(2026-04-08) to carry real Paschal-alleluia antiphons ("Jesum qui crucifíxus est quǽritis, +allelúja...") over a psalm set the reference engine sizes differently from vu's own +already-fixed 9-psalm ferial table (itself deliberate — see "No RB summer contraction" in +CLAUDE.md). 4 more days of antiphon-only content, same shape as the existing Advent/Paschaltide +seasonal-overlay pattern, layered onto vu's own ferial psalm numbers rather than trying to +reproduce the reference engine's differently-sized set — not started. + +`npm test` (828 passed), `tsc --noEmit`, and `npm run build` all pass. diff --git a/src/data/hours/matins-sunday-named-nocturn-overrides.yml b/src/data/hours/matins-sunday-named-nocturn-overrides.yml index 327c520..8b98c22 100644 --- a/src/data/hours/matins-sunday-named-nocturn-overrides.yml +++ b/src/data/hours/matins-sunday-named-nocturn-overrides.yml @@ -7,6 +7,14 @@ # # hours/matins.ts's lookup checks this map first (most specific), ahead of # the season-wide overrides, when `temporalId` matches a key here. +# +# `easter-sunday` (below) is also reused for Easter Monday and Tuesday +# (`hours/matins.ts`'s threeNocturns/Sunday-branch dispatch route any +# temporal winner with a matching entry here through the same rendering as +# a real Sunday, not just `day.weekday === 'sunday'` literally) — the +# reference engine's own content is byte-identical across all three days +# (live-verified 2026-09-02, 2026-04-05/06/07), so one entry covers all +# three rather than three separate near-duplicate ones. # Sunday within the Octave of Ascension (`sunday-after-ascension`) — # live-verified against the local reference-engine CGI instance @@ -32,6 +40,71 @@ # translation of the underlying Mark 16:20 text (a well-known, unambiguous # formula, not a guess), consistent with how every other antiphon in this # file is sourced. +# Easter Sunday itself (and Monday/Tuesday of the Octave, which share this +# entry — see the file header above). Live-verified against the local +# reference-engine CGI instance (`prayMatutinum`, Monastic Tridentinum +# 1617, 2026-09-02, proof dates 2026-04-05/06/07). Unlike every other +# Sunday scheme in this file, each nocturn here uses ONE shared antiphon +# over ALL 6 of its psalms (not one antiphon per psalm, nor per pair) — +# confirmed by the live query: a single "Ant." before the first psalm and +# the same antiphon repeated in full after the last, nothing in between. +# Nocturn III reuses the same 3 canticles as the ordinary-Paschaltide +# override (Isa 63:1-5, Osee 6:1-6, Sophonias 3:8-13 — already authored, +# no new scripture files needed) under its own different antiphon; all 3 +# nocturn-closing versicles are also byte-identical to the ones already +# authored in matins-sunday-paschaltide-nocturn-overrides.yml, confirming +# those are shared season-wide, not coincidentally reused. +# +# The reference engine's own [Rule] for this day actually reads "1 +# nocturn" (the whole night office collapses to a single nocturn of 3 +# lessons on this most privileged of feasts) — not modeled that way here; +# this app's own threeNocturns rule renders every Sunday with 3 nocturns +# unconditionally regardless of the historical single-nocturn rubric for +# this particular feast (same convention already noted in +# nocturn-readings/easter-sunday.yml's own header for the reading side). +# +# Explicitly NOT covered by this entry: Wednesday-Saturday of the Octave +# have their own real Paschal-alleluia antiphons layered over (in the +# reference engine) a differently-sized psalm set — live-verified present +# (2026-04-08, Wednesday) but not authored here. A separate, smaller gap +# (4 more days of antiphon-only content, analogous to the existing Advent/ +# Paschaltide seasonal-overlay shape) — see TODO.md. +easter-sunday: + # winner.id 'easter-sunday' spans the whole Octave (all 6 weekdays), but + # only these 3 calendar days are live-verified to share this content — + # restricting the entry so Wednesday-Saturday keep falling through to + # their own (not yet authored) path instead of wrongly reusing this one. + weekdays: [sunday, monday, tuesday] + nocturn1: + groups: + - psalms: [1, 2, 8, 15, 23, 27] + antiphon: + la: "Ego sum qui sum, * et consílium meum non est cum ímpiis, sed in lege Dómini volúntas mea est, allelúja." + en: "I am who I am, * and my counsel is not with the ungodly, my will is in the law of God, alleluia." + versicle: + v: { la: "Surréxit Dóminus de sepúlcro, allelúja.", en: "The Lord is risen from the sepulchre, alleluia." } + r: { la: "Qui pro nobis pepéndit in ligno, allelúja.", en: "Who for us was hanged on the tree, alleluia." } + nocturn2: + groups: + - psalms: [29, 63, 65, 75, 87, 107] + antiphon: + la: "Terra * trémuit et quiévit, † dum exsúrgeret in judício Deus, allelúja." + en: "The earth * trembled and was still, when God arose to judgment, alleluia." + versicle: + v: { la: "Surréxit Dóminus vere, allelúja.", en: "The Lord is risen indeed, alleluia." } + r: { la: "Et appáruit Simóni, allelúja.", en: "And hath appeared to Simon, alleluia." } + nocturn3: + canticles: + - refs: [{ book: isa, chapter: 63, verses: "1-5" }] + - refs: [{ book: osee, chapter: 6, verses: "1-6" }] + - refs: [{ book: soph, chapter: 3, verses: "8-13" }] + antiphon: + la: "Nolíte expavéscere: * Jesum quǽritis Nazarénum, crucifíxum: surréxit, non est hic, allelúja." + en: "Be not affrighted; * you seek Jesus of Nazareth, who was crucified: he is risen, he is not here, alleluia." + versicle: + v: { la: "Gavísi sunt discípuli, allelúja.", en: "The disciples were glad, alleluia." } + r: { la: "Viso Dómino, allelúja.", en: "When they saw the Lord, alleluia." } + sunday-after-ascension: nocturn1: groups: diff --git a/tests/hours/matins.test.ts b/tests/hours/matins.test.ts index 83fc797..3151224 100644 --- a/tests/hours/matins.test.ts +++ b/tests/hours/matins.test.ts @@ -360,6 +360,54 @@ describe('resolveOrdo("matins", ...) Sunday within the Octave of Ascension (2026 }); }); +describe('resolveOrdo("matins", ...) Easter Sunday/Monday/Tuesday shared proper Matins (2026-09-02 fix)', () => { + // All 6 weekdays of the Easter Octave share winner {kind: 'temporal', id: + // 'easter-sunday'} -- only Sunday/Monday/Tuesday are live-verified to + // share this exact proper content, restricted via the override's own + // `weekdays` field (see matins-sunday-named-nocturn-overrides.yml). + const EASTER_WEEK_2026 = ['2026-04-05', '2026-04-06', '2026-04-07'] as const; + + it.each(EASTER_WEEK_2026)('%s shares the identical 13-psalm proper scheme, 3 nocturns, Te Deum', (date) => { + const day = resolveDay(date); + expect(day.winner).toEqual({ kind: 'temporal', id: 'easter-sunday' }); + const ordo = resolveOrdo('matins', date); + const psalms = ordo.parts.filter((p) => p.kind === 'psalm').map((p) => (p as { psalmNumber: number }).psalmNumber); + expect(psalms).toEqual([3, 94, 1, 2, 8, 15, 23, 27, 29, 63, 65, 75, 87, 107]); + expect(ordo.parts.some((p) => p.kind === 'te-deum')).toBe(true); + + const canticles = ordo.parts.filter((p) => p.kind === 'canticle') as { canticleId: string }[]; + expect(canticles.map((c) => c.canticleId)).toEqual(['isa-63-1-5', 'osee-6-1-6', 'soph-3-8-13']); + + const antiphons = ordo.parts.filter((p) => p.kind === 'antiphon') as { text: { text: Record } }[]; + expect(antiphons[1]?.text.text.la).toBe('Ant. Ego sum qui sum, * et consílium meum non est cum ímpiis, sed in lege Dómini volúntas mea est, allelúja.'); + expect(antiphons.at(-1)?.text.text.la).toBe( + 'Ant. Nolíte expavéscere: * Jesum quǽritis Nazarénum, crucifíxum: surréxit, non est hic, allelúja.', + ); + + const versicles = ordo.parts.filter((p) => p.kind === 'versicle') as { text: { text: Record } }[]; + const [, , nocturn1, nocturn2, nocturn3] = versicles; + expect(nocturn1?.text.text.la).toBe('V. Surréxit Dóminus de sepúlcro, allelúja.\nR. Qui pro nobis pepéndit in ligno, allelúja.'); + expect(nocturn2?.text.text.la).toBe('V. Surréxit Dóminus vere, allelúja.\nR. Et appáruit Simóni, allelúja.'); + 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', () => { + 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. + expect(psalms.slice(2)).toEqual(getPsalmsFor('matins', 'wednesday').map((r) => r.number)); + expect(ordo.parts.some((p) => p.kind === 'te-deum')).toBe(false); + }); + + it('Low Sunday (a different temporalId) is unaffected, still the plain fixed Sunday scheme', () => { + const ordo = resolveOrdo('matins', '2026-04-12'); + const psalms = ordo.parts.filter((p) => p.kind === 'psalm').map((p) => (p as { psalmNumber: number }).psalmNumber); + expect(psalms).toEqual([3, 94, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31]); + }); +}); + 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