From 366ada1b26f4a0901630ad30110eae26fdb58200 Mon Sep 17 00:00:00 2001 From: Will Estes Date: Thu, 27 Aug 2026 09:26:22 -0400 Subject: [PATCH] Add name-template substitution for Common-of-a-Holy-Woman's invitatory antiphon MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Commune/C7's own invitatory antiphon ("Laudémus Deum nostrum * In confessióne beátæ N..") needs a per-saint genitive-singular name, unlike every other Common invitatory antiphon currently authored. Exports resolve-common.ts's existing substituteName helper (previously only used by getDayCollect for collectCommon/collectName) and wires it into resolveMatinsInvitatoryText via a new SaintRecord.invitatoryName field -- kept separate from collectName since the two templates' {N} placeholders don't always need the same grammatical case. Fills in real declined names for the 3 saints using common-of-a-holy-woman (Anne, Martha, Mary Magdalene). Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_012AVXS5iTrk3c2anSYVGwxS --- TODO.md | 18 +++++++++++++----- src/calendar/feasts.ts | 13 +++++++++++++ src/data/calendar/saints/st-anne.yml | 6 ++++++ src/data/calendar/saints/st-martha.yml | 6 ++++++ .../calendar/saints/st-mary-magdalene.yml | 7 +++++++ ...tins-invitatory-common-of-a-holy-woman.yml | 19 +++++++++++++++++++ src/hours/matins.ts | 11 +++++++++-- src/hours/resolve-common.ts | 2 +- 8 files changed, 74 insertions(+), 8 deletions(-) create mode 100644 src/data/propers/common/matins-invitatory-common-of-a-holy-woman.yml diff --git a/TODO.md b/TODO.md index cb6b550..f74c2e5 100644 --- a/TODO.md +++ b/TODO.md @@ -2765,13 +2765,21 @@ a-dedication.yml` (Commune/C8's own `[Invit]`, "Domum Dei decet sanctitúdo"). D St. Joseph Calasanctius (2026-08-27, Confessor, duplex) falling all the way to the plain ferial invitatory antiphon. +**Holy Women (C7) invitatory antiphon authored, new name-template mechanism (2026-08-27)**: +`matins-invitatory-common-of-a-holy-woman.yml` ("Laudémus Deum nostrum * In confessióne beátæ +{N}..", Commune/C7.txt's own `[Invit]`) is the first Common invitatory antiphon needing a +per-saint name substitution. Reused `hours/resolve-common.ts`'s existing `substituteName` +helper (previously only called from `getDayCollect` for `collectCommon`/`collectName`) — now +exported and wired into `hours/matins.ts`'s `resolveMatinsInvitatoryText` too, via a new +`SaintRecord.invitatoryName` field (a separate field from `collectName` since the invitatory's +own `{N}` position needs its own grammatical case — genitive singular here — not necessarily +`collectName`'s case, though for these 3 saints it happens to be the same declension). All 3 +`common-of-a-holy-woman` saints given real genitive-singular Latin names (St. Anne → "Annæ", +St. Martha → "Marthæ", reusing her existing `collectName` value; St. Mary Magdalene → "Maríæ +Magdalénæ") and live-traced end to end. + **Still not authored, logged as explicit backlog**, same incremental-content pattern as everywhere else in this project: -- Holy Women (C7) invitatory antiphon — Commune/C7.txt's own `[Invit]` ("Laudémus Deum nostrum - * In confessióne beátæ N..") is a name-template text (a literal "N." placeholder, like - `collectCommon`'s `{N}` substitution elsewhere) — this project's invitatory-antiphon files - have no substitution mechanism built yet, so authoring this one needs that groundwork first, - not just a transcription. Only 3 saints use `common-of-a-holy-woman`. - General/All Saints (C9) invitatory antiphon — re-investigated 2026-08-27: the reference engine's own `Commune/C9.txt` is actually the Office of the Dead ("Regem, cui ómnia vivunt"), not a General/All-Saints common — this backlog item's own C-number was mislabeled at the time diff --git a/src/calendar/feasts.ts b/src/calendar/feasts.ts index e0ffe14..0fbe593 100644 --- a/src/calendar/feasts.ts +++ b/src/calendar/feasts.ts @@ -75,6 +75,19 @@ export interface SaintRecord { * independently per saint, not assumed identical). */ benedictusCommon?: string; + /** + * The `{N}` substitution value(s) for the Matins invitatory antiphon's + * own Common template (`hours/matins.ts`'s `resolveMatinsInvitatoryText`, + * via `hours/resolve-common.ts`'s `substituteName` — the same helper + * `getDayCollect` already uses for `collectCommon`/`collectName`, just a + * separate field here since the invitatory's own `{N}` position needs + * its own grammatical case, not necessarily the same one `collectName` + * was declined for). Currently only needed for Common-of-a-Holy-Woman + * (`Commune/C7.txt`'s own `[Invit]`, "Laudémus Deum nostrum * In + * confessióne beátæ N.." — genitive singular, e.g. "Marthæ") — every + * other Common category in use has name-free invitatory text. Absent + * for saints whose Common invitatory antiphon needs no substitution. */ + invitatoryName?: { la: string[]; en: string[] }; /** See calendar/types.ts's OctaveConfig doc comment. Absent for the vast * majority of saints — only ones with a real octave declare it. */ octave?: OctaveConfig; diff --git a/src/data/calendar/saints/st-anne.yml b/src/data/calendar/saints/st-anne.yml index da08801..066d86d 100644 --- a/src/data/calendar/saints/st-anne.yml +++ b/src/data/calendar/saints/st-anne.yml @@ -13,3 +13,9 @@ propers: "st-anne" # already live-query-verified for common-of-a-virgin.yml (see TODO.md's # 'minor hour content gaps' section). minorHoursCommon: common-of-a-virgin +# Common-of-a-Holy-Woman's own Matins invitatory antiphon needs a +# genitive-singular {N} ("beátæ N..", matins-invitatory-common-of-a- +# holy-woman.yml) -- standard Latin 1st-declension genitive of "Anna". +invitatoryName: + la: ["Annæ"] + en: ["Anne"] diff --git a/src/data/calendar/saints/st-martha.yml b/src/data/calendar/saints/st-martha.yml index bd0a82f..a3c0189 100644 --- a/src/data/calendar/saints/st-martha.yml +++ b/src/data/calendar/saints/st-martha.yml @@ -21,3 +21,9 @@ collectName: en: ["Martha"] minorHoursCommon: common-of-a-virgin benedictusCommon: common-of-a-virgin +# Same genitive form as collectName above -- collect-c6a's own "beátæ {N} +# Vírginis tuæ" and the Matins invitatory's "beátæ N.." are both this +# same genitive-singular position. +invitatoryName: + la: ["Marthæ"] + en: ["Martha"] diff --git a/src/data/calendar/saints/st-mary-magdalene.yml b/src/data/calendar/saints/st-mary-magdalene.yml index 831e375..360012e 100644 --- a/src/data/calendar/saints/st-mary-magdalene.yml +++ b/src/data/calendar/saints/st-mary-magdalene.yml @@ -14,3 +14,10 @@ propers: "st-mary-magdalene" # already live-query-verified for common-of-a-virgin.yml (see TODO.md's # 'minor hour content gaps' section). minorHoursCommon: common-of-a-virgin +# Common-of-a-Holy-Woman's own Matins invitatory antiphon needs a +# genitive-singular {N} ("beátæ N..", matins-invitatory-common-of-a- +# holy-woman.yml) -- standard Latin genitive of "Maria Magdalena", both +# parts declined. +invitatoryName: + la: ["Maríæ Magdalénæ"] + en: ["Mary Magdalene"] diff --git a/src/data/propers/common/matins-invitatory-common-of-a-holy-woman.yml b/src/data/propers/common/matins-invitatory-common-of-a-holy-woman.yml new file mode 100644 index 0000000..aec80a8 --- /dev/null +++ b/src/data/propers/common/matins-invitatory-common-of-a-holy-woman.yml @@ -0,0 +1,19 @@ +# The Common-of-a-Holy-Woman's own invitatory antiphon, read from the +# reference engine's own Commune/C7.txt [Invit] line (both languages) -- +# a name-template text, not a fixed one like every other Common category's +# invitatory antiphon in this app: the literal `{N}` here stands for the +# winning saint's own name in genitive-singular Latin ("beátæ N.." = +# "of blessed N."), substituted at read time by +# hours/matins.ts's resolveMatinsInvitatoryText via hours/resolve- +# common.ts's substituteName (SaintRecord.invitatoryName) -- the same +# mechanism getDayCollect already uses for collectCommon/collectName, +# just for this separate template family. Any winning holy woman with no +# proper invitatory antiphon of her own authored falls to this Common +# tier. +id: matins-invitatory-common-of-a-holy-woman +text: + la: "Laudémus Deum nostrum * In confessióne beátæ {N}.." + en: "Let us praise our God * for the confession of blessed {N}." +status: + la: verified + en: verified diff --git a/src/hours/matins.ts b/src/hours/matins.ts index 3321aaa..a49ae87 100644 --- a/src/hours/matins.ts +++ b/src/hours/matins.ts @@ -79,6 +79,7 @@ import { verifiedText, splitNamedAntiphon, openingAntiphon, + substituteName, } from './resolve-common'; import { getSaintRecord } from '../calendar/feasts'; import { getBiblePlanReadings } from '../propers/bible-plan'; @@ -193,11 +194,17 @@ function resolveMatinsInvitatoryText(day: LiturgicalDay): ResolvedText { } } const winner = resolveOfficeWinner(day); - const commonId = winner.kind === 'sanctoral' ? getSaintRecord(winner.id)?.common : undefined; + const saint = winner.kind === 'sanctoral' ? getSaintRecord(winner.id) : undefined; + const commonId = saint?.common; if (commonId) { const common = resolveCommon(`matins-invitatory-${commonId}`); if (common.status.la !== 'missing' || common.status.en !== 'missing') { - return common; + // Common-of-a-Holy-Woman's own invitatory antiphon is a name + // template ("beátæ N..") -- the only Common invitatory antiphon + // currently in use that needs one (see matins-invitatory-common- + // of-a-holy-woman.yml's own header). Same substituteName idiom + // getDayCollect already uses for collectCommon/collectName. + return saint?.invitatoryName ? substituteName(common, saint.invitatoryName) : common; } } const seasonSuffix = seasonalOfficeSuffix(day.season); diff --git a/src/hours/resolve-common.ts b/src/hours/resolve-common.ts index 34e761e..84edaec 100644 --- a/src/hours/resolve-common.ts +++ b/src/hours/resolve-common.ts @@ -471,7 +471,7 @@ export function resolveOfficeBundle( * saint who shares it. Positional: the template's `{N}` occurrences fill * left-to-right from `names`, in document order — most templates have * one, a few (two co-named saints commemorated together) have two. */ -function substituteName(text: ResolvedText, name: { la: string[]; en: string[] }): ResolvedText { +export function substituteName(text: ResolvedText, name: { la: string[]; en: string[] }): ResolvedText { const substituted: Partial> = {}; for (const [lang, t] of Object.entries(text.text)) { if (!t) {