Add name-template substitution for Common-of-a-Holy-Woman's invitatory antiphon
Deploy / deploy (push) Successful in 1m20s

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 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012AVXS5iTrk3c2anSYVGwxS
This commit is contained in:
2026-08-27 09:26:22 -04:00
parent f55cd46272
commit 366ada1b26
8 changed files with 74 additions and 8 deletions
+13 -5
View File
@@ -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
+13
View File
@@ -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;
+6
View File
@@ -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"]
+6
View File
@@ -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"]
@@ -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"]
@@ -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
+9 -2
View File
@@ -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);
+1 -1
View File
@@ -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<Record<string, string>> = {};
for (const [lang, t] of Object.entries(text.text)) {
if (!t) {