From 04a86865511e6e6ab35c471753b6b70b73ad8261 Mon Sep 17 00:00:00 2001 From: Will Estes Date: Sun, 30 Aug 2026 18:12:12 -0400 Subject: [PATCH] Finish the last 5 vigils: weekday-keyed commemoration antiphons Corrected an earlier "no source exists" claim by live-querying real commemorated instances of the Vigils of St. Lawrence and the Assumption across six different weekdays. Their source files' own [Rule] block ("Versum Feria") turns out to be a real, sourced mechanism, not an absence: a mere commemoration doesn't reuse the vigil's own antiphon at all -- it borrows the plain ferial day's antiphon/versicle instead, and that antiphon quotes the Benedictus canticle in weekday sequence (same shape vespers-magnificat-antiphons.yml already uses for the Magnificat). The versicle is a single fixed text. Sunday has no entry: a Vigil transfers off an ordinary Sunday rather than ever reaching a commemoration there, confirmed live as well as already true in this app's own precedence rules. sanctoralCommemorationPart now takes weekday and resolves this dynamically for common-of-a-vigil saints, rather than needing a static per-saint file. All 218 saints with an authored collect now have a real commemoration antiphon. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01ME8QNPdEmHSbSx1r6VmDRG --- TODO.md | 17 +++-- .../lauds-vigil-commemoration-antiphons.yml | 63 +++++++++++++++++++ src/hours/resolve-common.ts | 58 ++++++++++++++++- tests/hours/vigil-commemoration.test.ts | 43 +++++++++++++ 4 files changed, 172 insertions(+), 9 deletions(-) create mode 100644 src/data/hours/lauds-vigil-commemoration-antiphons.yml create mode 100644 tests/hours/vigil-commemoration.test.ts diff --git a/TODO.md b/TODO.md index cfa2625..43fef18 100644 --- a/TODO.md +++ b/TODO.md @@ -64,12 +64,17 @@ across every hour and content type in this app. Holy Cross/Most Precious Blood/Transfiguration, given their own real `[Ant 2]`/`[Versum 2]` straight from their Sancti files). **213 of ~218 real saints now covered** (`tests/propers/commemoration-sweep.test.ts`, 213 cases); every entry not independently - live-verified is marked `status: draft`. Only 5 remain, genuinely open: the Vigils - (`common-of-a-vigil` — All Saints, Ss. Simon and Jude, St. John the Baptist, St. Lawrence, - the Assumption) — no dedicated "Commune Vigiliarum" antiphon set exists in the reference - engine at all (only `C1v`, Vigil of Apostles specifically, itself empty/inherited), and - `FeastClass: 'vigil'` is weak enough that these rarely reach the commemoration code path in - the first place — real, but low-priority. + live-verified is marked `status: draft`. **DONE, full 218/218**: the remaining 5 Vigils + (`common-of-a-vigil`) turned out not to need a static template at all — pushed back on the + earlier "no source exists" claim by live-querying real commemorated instances directly, which + found their own source file's `[Rule]` ("Versum Feria") is a real, sourced mechanism: the + antiphon quotes the Benedictus canticle in weekday sequence (confirmed against 6 real + weekdays, same shape `vespers-magnificat-antiphons.yml` already uses for the Magnificat), the + versicle is a single fixed text, and Sunday genuinely has no entry (a Vigil transfers off an + ordinary Sunday rather than ever reaching a commemoration there — confirmed live too). + Implemented as a small weekday-aware code path in `sanctoralCommemorationPart` (now takes + `weekday`) rather than a per-saint file — see `data/hours/lauds-vigil-commemoration- + antiphons.yml` and `tests/hours/vigil-commemoration.test.ts`. - Vespers Magnificat antiphon: after the 2026-08-30 sweep plus the same-day O-Antiphon follow-up (see "O Antiphons (Dec 17-23) added" below), only two narrow gaps remain — Advent 4's own Sunday landing on Dec 24 specifically (outside the O-Antiphon range), and diff --git a/src/data/hours/lauds-vigil-commemoration-antiphons.yml b/src/data/hours/lauds-vigil-commemoration-antiphons.yml new file mode 100644 index 0000000..60ca239 --- /dev/null +++ b/src/data/hours/lauds-vigil-commemoration-antiphons.yml @@ -0,0 +1,63 @@ +# A mere commemoration of a Vigil (`common: common-of-a-vigil` — St. +# Lawrence's, the Assumption's, All Saints', Ss. Simon and Jude's, St. +# John the Baptist's) does NOT reuse that vigil's own antiphon (the one +# used when it wins outright, e.g. vigil-of-st-lawrence-antiphon.yml) — +# its own source file's [Rule] block says "Versum Feria" (borrow the +# plain ferial day's own versicle/antiphon instead), confirmed live +# (Monastic Tridentinum 1617): the Vigil of St. Lawrence's commemoration +# on a real Monday (2027-08-09) and the Vigil of the Assumption's on six +# different real weekdays (2026 Fri/2027 Sat/2028 Mon/2029 Tue/2030 +# Wed/2031 Thu) all gave the exact same result for their weekday — the +# antiphon quotes the Benedictus canticle's own text in weekday sequence +# (same "quote the framing canticle in order through the week" pattern +# vespers-magnificat-antiphons.yml already uses for Vespers), same text +# regardless of *which* vigil. Sunday has no entry: a Vigil (`FeastClass: +# 'vigil'`, weaker than Semiduplex) never actually reaches a commemoration +# on an ordinary Sunday in this app's own precedence rules either — it +# transfers instead (calendar/commemorations.ts's `ordinary-sunday` +# branch) — confirmed live too (2033-08-14, a real Sunday: the Vigil of +# the Assumption doesn't appear at all, only the Lawrence-octave day is +# commemorated). +# +# English: the reference engine's own English files have no translation +# for this specific commemoration antiphon at all (checked across every +# weekday) -- these are direct translations, informed by the Benedictus +# canticle's own already-verified English (propers/common/benedictus.yml) +# for vocabulary consistency, but not independently sourced -- `status` +# marks them `draft` for that reason. + +monday: + antiphon: + la: "Benedíctus Dóminus, Deus Israël." + en: "Blessed be the Lord God of Israel." + status: { la: verified, en: draft } + +tuesday: + antiphon: + la: "Eréxit nobis * Dóminus cornu salútis in domo David púeri sui." + en: "The Lord hath raised up for us * a horn of salvation in the house of David his servant." + status: { la: verified, en: draft } + +wednesday: + antiphon: + la: "De manu ómnium qui odérunt nos, líbera nos Dómine." + en: "From the hand of all that hate us, deliver us, O Lord." + status: { la: verified, en: draft } + +thursday: + antiphon: + la: "In sanctitáte serviámus Dómino, et liberábit nos ab inimícis nostris." + en: "Let us serve the Lord in holiness, and he will deliver us from our enemies." + status: { la: verified, en: draft } + +friday: + antiphon: + la: "Per víscera misericórdiæ Dei nostri visitávit nos Óriens ex alto." + en: "Through the tender mercy of our God, the Dayspring from on high hath visited us." + status: { la: verified, en: draft } + +saturday: + antiphon: + la: "Illumináre, Dómine, his qui in ténebris sedent: et dírige pedes nostros in viam pacis, Deus Israël." + en: "Give light, O Lord, to them that sit in darkness: and direct our feet into the way of peace, O God of Israel." + status: { la: verified, en: draft } diff --git a/src/hours/resolve-common.ts b/src/hours/resolve-common.ts index b78ca9a..173e2cd 100644 --- a/src/hours/resolve-common.ts +++ b/src/hours/resolve-common.ts @@ -10,6 +10,7 @@ import { splitAntiphon, isDoubleOrHigher } from './antiphon'; import { getLaudsSaintOverride } from './lauds-psalmody-overrides'; import vespersMagnificatAntiphonsData from '../data/hours/vespers-magnificat-antiphons.yml'; import vespersOAntiphonsData from '../data/hours/vespers-o-antiphons.yml'; +import laudsVigilCommemorationAntiphonsData from '../data/hours/lauds-vigil-commemoration-antiphons.yml'; type BilingualText = Partial>; type TranslationStatus = 'verified' | 'draft' | 'missing'; @@ -26,6 +27,10 @@ const vespersMagnificatAntiphons = vespersMagnificatAntiphonsData as Partial< // these seven can't live in a per-Advent-week temporal-proper file the // way every other week's Magnificat antiphon does. const vespersOAntiphons = vespersOAntiphonsData as Partial>; +// Monday-Saturday only — see lauds-vigil-commemoration-antiphons.yml's own +// header for why Sunday has no entry (a Vigil never reaches a +// commemoration on an ordinary Sunday in the first place). +const vigilCommemorationAntiphons = laudsVigilCommemorationAntiphonsData as Partial>; function toResolvedText(proper: ProperText): ResolvedText { return { text: proper.text, status: proper.status, citation: proper.citation }; @@ -597,6 +602,41 @@ export function getDayCollect(day: LiturgicalDay): ResolvedText { return { text: {}, status: { la: 'missing', en: 'missing' } }; } +/** A Vigil (`common: common-of-a-vigil`) doesn't reuse its own antiphon + * when merely commemorated — its own source file's `[Rule]` says "Versum + * Feria" (borrow the plain ferial day's own antiphon/versicle instead), + * confirmed live across six different real weekdays (see + * data/hours/lauds-vigil-commemoration-antiphons.yml's own header for the + * dates checked) — the antiphon quotes the Benedictus canticle in weekday + * sequence (same pattern vespers-magnificat-antiphons.yml already uses + * for the Magnificat), same text regardless of *which* vigil, and the + * versicle is a single fixed text (`lauds-versicle-monday.yml`'s own, + * confirmed identical even on a live Saturday, so genuinely constant, not + * itself weekday-keyed despite superficially matching most weekdays' + * plain ferial versicle). Sunday has no entry — a Vigil never reaches a + * commemoration on an ordinary Sunday in this app's own precedence rules + * either (calendar/commemorations.ts's `ordinary-sunday` branch + * transfers it instead), confirmed live too. */ +function vigilCommemorationAntiphon(weekday: Weekday): ResolvedText | undefined { + const entry = vigilCommemorationAntiphons[weekday]; + return entry ? (entry.status ? { text: entry.antiphon, status: entry.status } : verifiedText(entry.antiphon)) : undefined; +} + +function combineCommemorationBundle(antiphon: ResolvedText, versicle: ResolvedText, collect: ResolvedText): ResolvedText { + const text: Partial> = {}; + const status: Partial> = {}; + for (const lang of new Set([...Object.keys(antiphon.text), ...Object.keys(versicle.text), ...Object.keys(collect.text)])) { + const parts = [antiphon, versicle, collect].map((t) => t.text[lang]).filter((t): t is string => !!t); + if (parts.length === 0) continue; + text[lang] = `Ant. ${parts[0] ?? ''}\n${parts[1] ?? ''}\n${parts[2] ?? ''}`.trim(); + const statuses = [antiphon.status[lang], versicle.status[lang], collect.status[lang]].filter( + (s): s is 'verified' | 'draft' | 'missing' => !!s, + ); + status[lang] = statuses.reduce((worst, s) => (STATUS_RANK[s] > STATUS_RANK[worst] ? s : worst), 'verified' as const); + } + return { text, status }; +} + /** A sanctoral commemoration's own rendering: the fuller Ant+V/R+collect * bundle (`${propers}-commemoration`, e.g. st-clare-commemoration.yml) * when authored — live-verified this is the real shape a commemoration @@ -605,8 +645,13 @@ export function getDayCollect(day: LiturgicalDay): ResolvedText { * replaces) when only that's been authored, and to an honestly labeled * "missing" block when neither has. Always labeled ("Commemoration of St. * X") so an unauthored one reads as "this saint's commemoration isn't - * written up yet," not as a mystery blank prayer. */ -function sanctoralCommemorationPart(commemoration: Extract): ResolvedPart { + * written up yet," not as a mystery blank prayer. A Vigil is a special + * case within this same fallback chain: see vigilCommemorationAntiphon's + * own doc comment. */ +function sanctoralCommemorationPart( + commemoration: Extract, + weekday: Weekday, +): ResolvedPart { const label = `Commemoration of ${commemoration.name}`; const saint = getSaintRecord(commemoration.id); if (saint?.propers) { @@ -616,6 +661,13 @@ function sanctoralCommemorationPart(commemoration: Extract { + it('2027-08-09 (Monday): Vigil of St. Lawrence commemorated under Ss. Cyriacus etc., Monday\'s own Benedictus-verse antiphon', () => { + const lauds = resolveOrdo('lauds', '2027-08-09'); + const commem = lauds.parts.find((p) => p.kind === 'preces' && p.label?.includes('Vigil of St. Lawrence')); + expect(commem?.kind === 'preces' ? commem.text.text.la : undefined).toContain('Benedíctus Dóminus, Deus Israël.'); + expect(commem?.kind === 'preces' ? commem.text.text.la : undefined).toContain('Repléti sumus mane misericórdia tua'); + expect(commem?.kind === 'preces' ? commem.text.text.la : undefined).toContain('Adésto, Dómine'); // his own real collect, unchanged + expect(commem?.kind === 'preces' ? commem.text.status.la : undefined).toBe('verified'); + }); + + it('2029-08-14 (Tuesday) vs. 2030-08-14 (Wednesday): the same Vigil of the Assumption gets a different antiphon on a different weekday, same fixed versicle', () => { + const tuesday = resolveOrdo('lauds', '2029-08-14'); + const wednesday = resolveOrdo('lauds', '2030-08-14'); + const tueCommem = tuesday.parts.find((p) => p.kind === 'preces' && p.label?.includes('Vigil of the Assumption')); + const wedCommem = wednesday.parts.find((p) => p.kind === 'preces' && p.label?.includes('Vigil of the Assumption')); + const tueText = tueCommem?.kind === 'preces' ? tueCommem.text.text.la : undefined; + const wedText = wedCommem?.kind === 'preces' ? wedCommem.text.text.la : undefined; + expect(tueText).toContain('Eréxit nobis'); + expect(wedText).toContain('De manu ómnium qui odérunt nos'); + expect(tueText).toContain('Repléti sumus mane misericórdia tua'); + expect(wedText).toContain('Repléti sumus mane misericórdia tua'); + }); + + it('2033-08-14 (a real Sunday): the Vigil is never even commemorated — a Vigil transfers off an ordinary Sunday, matching the live reference exactly', () => { + const lauds = resolveOrdo('lauds', '2033-08-14'); + const commem = lauds.parts.find((p) => p.kind === 'preces' && p.label?.includes('Vigil of the Assumption')); + expect(commem).toBeUndefined(); + }); +});