Extend closing Gloria Patri to canticles, centralize instead of baking it in

Benedictus, Magnificat, and Nunc Dimittis had the Gloria Patri baked
verbatim into their propers YAML with no Triduum suppression; Matins'
Nocturn 3 OT canticles had none at all. Give the 'canticle' ResolvedPart
the same centrally-attached gloriaPatri field psalms already got, strip
the baked-in text, and drop Lauds' now-redundant local append/Triduum
check — live-verified that canticles follow the exact same Triduum-only
omission as psalms (e.g. Benedictus at Lauds of Holy Thursday).
This commit is contained in:
2026-08-19 06:06:07 -04:00
parent b230f3f1e8
commit 7ffb9afea2
8 changed files with 34 additions and 40 deletions
+4 -5
View File
@@ -1,7 +1,10 @@
# Verified against Divinum Officium (Monastic Tridentinum 1617), Lauds live
# query. Fixed year-round, same shape as nunc-dimittis.yml — the framing
# antiphon is resolved separately, by day (see hours/resolve-common.ts's
# getBenedictusAntiphon), not stored here.
# getBenedictusAntiphon), not stored here. The closing Gloria Patri isn't
# stored here either — it's attached centrally (hours/index.ts) so it can
# be omitted during the Sacred Triduum (live-verified: Divino Afflatu 1954
# and Tridentine 1906 both omit it at Lauds of Holy Thursday).
id: benedictus
text:
la: |
@@ -17,8 +20,6 @@ text:
Ad dandam sciéntiam salútis plebi ejus: * in remissiónem peccatórum eórum:
Per víscera misericórdiæ Dei nostri: * in quibus visitávit nos, óriens ex alto:
Illumináre his, qui in ténebris, et in umbra mortis sedent: * ad dirigéndos pedes nostros in viam pacis.
V. Glória Patri, et Fílio, * et Spirítui Sancto.
R. Sicut erat in princípio, et nunc, et semper, * et in sǽcula sæculórum. Amen.
en: |
Blessed be the Lord ✠ God of Israel; * because he hath visited and wrought the redemption of his people:
And hath raised up an horn of salvation to us, * in the house of David his servant:
@@ -32,8 +33,6 @@ text:
To give knowledge of salvation to his people, * unto the remission of their sins:
Through the bowels of the mercy of our God, * in which the Orient from on high hath visited us:
To enlighten them that sit in darkness, and in the shadow of death: * to direct our feet into the way of peace.
V. Glory be to the Father, and to the Son, * and to the Holy Ghost.
R. As it was in the beginning, is now, * and ever shall be, world without end. Amen.
citation:
la: "Luc. 1:68-79"
en: "Luke 1:68-79"
+3 -5
View File
@@ -4,7 +4,9 @@
# year-round, same shape as benedictus.yml/nunc-dimittis.yml — the framing
# antiphon is resolved separately, by day (see hours/resolve-common.ts's
# getMagnificatAntiphon), not stored here. Not live-query-verified (see
# TODO.md) — read directly from source.
# TODO.md) — read directly from source. The closing Gloria Patri isn't
# stored here either — it's attached centrally (hours/index.ts) so it can
# be omitted during the Sacred Triduum, same as every other canticle/psalm.
id: magnificat
text:
la: |
@@ -18,8 +20,6 @@ text:
Esuriéntes implévit bonis: * et dívites dimísit inánes.
Suscépit Israël púerum suum: * recordátus misericórdiæ suæ.
Sicut locútus est ad patres nostros: * Ábraham, et sémini ejus in sǽcula.
V. Glória Patri, et Fílio, * et Spirítui Sancto.
R. Sicut erat in princípio, et nunc, et semper, * et in sǽcula sæculórum. Amen.
en: |
My soul ✠ doth magnify the Lord.
And my spirit hath rejoiced * in God my Saviour.
@@ -31,8 +31,6 @@ text:
He hath filled the hungry with good things; * and the rich he hath sent empty away.
He hath received Israel his servant, * being mindful of his mercy:
As he spoke to our fathers, * to Abraham and to his seed for ever.
V. Glory be to the Father, and to the Son, * and to the Holy Ghost.
R. As it was in the beginning, is now, * and ever shall be, world without end. Amen.
status:
la: verified
en: verified
+4 -5
View File
@@ -1,7 +1,10 @@
# Verified against Divinum Officium (Tridentine 1906 run, 2026-08-25 —
# Monastic Compline doesn't have this canticle at all; it's added from the
# Tridentine 1906 form by explicit choice). The antiphon that frames it
# ("Salva nos") is a separate proper — see nunc-dimittis-antiphon.yml.
# ("Salva nos") is a separate proper — see nunc-dimittis-antiphon.yml. The
# closing Gloria Patri isn't stored here either — it's attached centrally
# (hours/index.ts) so it can be omitted during the Sacred Triduum, same as
# every other canticle/psalm.
id: nunc-dimittis
text:
la: |
@@ -9,15 +12,11 @@ text:
Quia vidérunt óculi mei * salutáre tuum,
Quod parásti * ante fáciem ómnium populórum,
Lumen ad revelatiónem géntium, * et glóriam plebis tuæ Israël.
V. Glória Patri, et Fílio, * et Spirítui Sancto.
R. Sicut erat in princípio, et nunc, et semper, * et in sǽcula sæculórum. Amen.
en: |
Now ✠ thou dost dismiss thy servant, O Lord, * according to thy word in peace;
Because my eyes have seen * thy salvation,
Which thou hast prepared * before the face of all peoples:
A light to the revelation of the Gentiles, * and the glory of thy people Israel.
V. Glory be to the Father, and to the Son, * and to the Holy Ghost.
R. As it was in the beginning, is now, * and ever shall be, world without end. Amen.
citation:
la: "Luc. 2:29-32"
en: "Luke 2:29-32"
+8 -5
View File
@@ -26,13 +26,16 @@ const registry: Record<HourId, Resolver> = {
matins: matins.resolveOrdo,
};
/** Attaches the closing Gloria Patri to every plain psalm part (recursing
* into 'nocturn-psalmody' groups), so no per-hour builder has to remember
* to do it — and so the Sacred Triduum omission (resolveGloriaPatri) is
* enforced in exactly one place rather than once per hour file. */
/** Attaches the closing Gloria Patri to every plain psalm and canticle part
* (recursing into 'nocturn-psalmody' groups), so no per-hour builder has to
* remember to do it — and so the Sacred Triduum omission
* (resolveGloriaPatri) is enforced in exactly one place rather than once
* per hour file. Live-verified (Divino Afflatu 1954, Tridentine 1906) that
* canticles (e.g. Benedictus at Lauds of Holy Thursday) follow the exact
* same Triduum-only omission as plain psalms — not a separate rule. */
function withGloriaPatri(parts: ResolvedPart[], gloriaPatri: ResolvedText | undefined): ResolvedPart[] {
return parts.map((part) => {
if (part.kind === 'psalm') {
if (part.kind === 'psalm' || part.kind === 'canticle') {
return { ...part, gloriaPatri };
}
if (part.kind === 'nocturn-psalmody') {
+9 -17
View File
@@ -1,7 +1,6 @@
import type { HourDefinition, HourPart, ResolvedOrdo, ResolvedPart, ResolvedText } from './types';
import type { LiturgicalDay, Weekday } from '../calendar/types';
import { resolveDay, isSundayOrFeast, activeOctavesFor } from '../calendar';
import { isInTriduum } from '../calendar/temporal';
import { getDayLabel } from '../calendar/day-label';
import { getTemporalFeastRecord } from '../calendar/temporal-feasts';
import { getPsalmVerses } from '../psalter';
@@ -93,22 +92,16 @@ function psalmParts(numbers: number[], antiphon: BilingualText, opening: Resolve
const STATUS_RANK = { verified: 0, draft: 1, missing: 2 } as const;
const GLORIA_PATRI = {
la: 'V. Glória Patri, et Fílio, * et Spirítui Sancto.\nR. Sicut erat in princípio, et nunc, et semper, * et in sǽcula sæculórum. Amen.',
en: 'V. Glory be to the Father, and to the Son, * and to the Holy Ghost.\nR. As it was in the beginning, is now, * and ever shall be, world without end. Amen.',
};
/** Joins a canticle's verse array (or a `[start, end)` slice of it — see
* Saturday's split canticle below) into one flowing text block, the same
* shape nunc-dimittis/benedictus already use — Lauds' weekday canticles
* are stored verse-by-verse (see lauds-canticles.ts) purely because that's
* how they were transcribed, not because callers need per-verse access.
* Appends the fixed Gloria Patri line every canticle (or canticle part)
* ends with in real practice, same wording as benedictus.yml's own
* trailing line — omitted during the Sacred Triduum, live-verified against
* both Divino Afflatu 1954 and Tridentine 1906 (Benedictus at Lauds of
* Holy Thursday omits it same as any psalm). */
function canticleText(canticleId: string, slice: [number, number] | undefined, omitGloriaPatri: boolean): ResolvedText {
* Does NOT append a closing Gloria Patri — that's attached centrally
* (hours/index.ts) as its own 'canticle' field, same as every other
* canticle/psalm, so the Sacred Triduum omission only has to be handled
* in one place. */
function canticleText(canticleId: string, slice?: [number, number]): ResolvedText {
const canticle = getCanticle(canticleId);
const verses = slice ? canticle.verses.slice(slice[0], slice[1]) : canticle.verses;
const text: Partial<Record<string, string>> = {};
@@ -118,7 +111,7 @@ function canticleText(canticleId: string, slice: [number, number] | undefined, o
if (lines.length === 0) {
continue;
}
text[lang] = omitGloriaPatri ? lines.join('\n') : `${lines.join('\n')}\n${GLORIA_PATRI[lang]}`;
text[lang] = lines.join('\n');
let worst: 'verified' | 'draft' | 'missing' = 'verified';
for (const v of verses) {
const s = v.status[lang] ?? 'missing';
@@ -153,7 +146,6 @@ function resolvePsalmody(day: LiturgicalDay): ResolvedPart[] {
const canticle = getCanticle(wd.canticle.id);
const canticleOpening = opening(wd.canticle.antiphon);
const canticleClosing = splitNamedAntiphon(verifiedText(wd.canticle.antiphon)).full;
const omitGloriaPatri = isInTriduum(day.date);
if (wd.canticle.split) {
// Said in two pieces, own Gloria Patri each, one shared antiphon
// framing both (opening before the first, full repeated only after
@@ -162,19 +154,19 @@ function resolvePsalmody(day: LiturgicalDay): ResolvedPart[] {
kind: 'canticle',
canticleId: canticle.id,
antiphon: canticleOpening,
text: canticleText(canticle.id, [0, wd.canticle.split], omitGloriaPatri),
text: canticleText(canticle.id, [0, wd.canticle.split]),
});
parts.push({
kind: 'canticle',
canticleId: canticle.id,
text: canticleText(canticle.id, [wd.canticle.split, canticle.verses.length], omitGloriaPatri),
text: canticleText(canticle.id, [wd.canticle.split, canticle.verses.length]),
});
} else {
parts.push({
kind: 'canticle',
canticleId: canticle.id,
antiphon: canticleOpening,
text: canticleText(canticle.id, undefined, omitGloriaPatri),
text: canticleText(canticle.id),
});
}
parts.push({ kind: 'antiphon', text: canticleClosing });
+3 -1
View File
@@ -204,7 +204,9 @@ export type ResolvedPart =
// Sacred Triduum (calendar/temporal.ts's isInTriduum) — never omit it
// when building a psalm part yourself.
| { kind: 'psalm'; psalmNumber: number; antiphon?: ResolvedText; verses: ResolvedVerse[]; gloriaPatri?: ResolvedText }
| { kind: 'canticle'; canticleId: string; text: ResolvedText; antiphon?: ResolvedText }
// `gloriaPatri` filled in centrally, same as the 'psalm' kind above —
// see that field's doc comment.
| { kind: 'canticle'; canticleId: string; text: ResolvedText; antiphon?: ResolvedText; gloriaPatri?: ResolvedText }
// A standalone antiphon, said on its own rather than framing a psalm —
// see 'closing-antiphon' above.
| { kind: 'antiphon'; text: ResolvedText }
+1
View File
@@ -109,6 +109,7 @@ function renderPart(part: ResolvedPart, languages: readonly string[]): string {
<h3 class="ordo-part-label">${escapeHtml(title)}</h3>
${renderCitation(part.text)}
${renderColumns(part.text, languages)}
${part.gloriaPatri ? `<div class="ordo-part-gloria-patri">${renderColumns(part.gloriaPatri, languages)}</div>` : ''}
</section>
`;
}