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:
@@ -1,7 +1,10 @@
|
|||||||
# Verified against Divinum Officium (Monastic Tridentinum 1617), Lauds live
|
# Verified against Divinum Officium (Monastic Tridentinum 1617), Lauds live
|
||||||
# query. Fixed year-round, same shape as nunc-dimittis.yml — the framing
|
# query. Fixed year-round, same shape as nunc-dimittis.yml — the framing
|
||||||
# antiphon is resolved separately, by day (see hours/resolve-common.ts's
|
# 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
|
id: benedictus
|
||||||
text:
|
text:
|
||||||
la: |
|
la: |
|
||||||
@@ -17,8 +20,6 @@ text:
|
|||||||
Ad dandam sciéntiam salútis plebi ejus: * in remissiónem peccatórum eórum:
|
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:
|
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.
|
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: |
|
en: |
|
||||||
Blessed be the Lord ✠ God of Israel; * because he hath visited and wrought the redemption of his people:
|
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:
|
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:
|
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:
|
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.
|
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:
|
citation:
|
||||||
la: "Luc. 1:68-79"
|
la: "Luc. 1:68-79"
|
||||||
en: "Luke 1:68-79"
|
en: "Luke 1:68-79"
|
||||||
|
|||||||
@@ -4,7 +4,9 @@
|
|||||||
# year-round, same shape as benedictus.yml/nunc-dimittis.yml — the framing
|
# year-round, same shape as benedictus.yml/nunc-dimittis.yml — the framing
|
||||||
# antiphon is resolved separately, by day (see hours/resolve-common.ts's
|
# antiphon is resolved separately, by day (see hours/resolve-common.ts's
|
||||||
# getMagnificatAntiphon), not stored here. Not live-query-verified (see
|
# 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
|
id: magnificat
|
||||||
text:
|
text:
|
||||||
la: |
|
la: |
|
||||||
@@ -18,8 +20,6 @@ text:
|
|||||||
Esuriéntes implévit bonis: * et dívites dimísit inánes.
|
Esuriéntes implévit bonis: * et dívites dimísit inánes.
|
||||||
Suscépit Israël púerum suum: * recordátus misericórdiæ suæ.
|
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.
|
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: |
|
en: |
|
||||||
My soul ✠ doth magnify the Lord.
|
My soul ✠ doth magnify the Lord.
|
||||||
And my spirit hath rejoiced * in God my Saviour.
|
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 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:
|
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.
|
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:
|
status:
|
||||||
la: verified
|
la: verified
|
||||||
en: verified
|
en: verified
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
# Verified against Divinum Officium (Tridentine 1906 run, 2026-08-25 —
|
# Verified against Divinum Officium (Tridentine 1906 run, 2026-08-25 —
|
||||||
# Monastic Compline doesn't have this canticle at all; it's added from the
|
# 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
|
# 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
|
id: nunc-dimittis
|
||||||
text:
|
text:
|
||||||
la: |
|
la: |
|
||||||
@@ -9,15 +12,11 @@ text:
|
|||||||
Quia vidérunt óculi mei * salutáre tuum,
|
Quia vidérunt óculi mei * salutáre tuum,
|
||||||
Quod parásti * ante fáciem ómnium populórum,
|
Quod parásti * ante fáciem ómnium populórum,
|
||||||
Lumen ad revelatiónem géntium, * et glóriam plebis tuæ Israël.
|
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: |
|
en: |
|
||||||
Now ✠ thou dost dismiss thy servant, O Lord, * according to thy word in peace;
|
Now ✠ thou dost dismiss thy servant, O Lord, * according to thy word in peace;
|
||||||
Because my eyes have seen * thy salvation,
|
Because my eyes have seen * thy salvation,
|
||||||
Which thou hast prepared * before the face of all peoples:
|
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.
|
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:
|
citation:
|
||||||
la: "Luc. 2:29-32"
|
la: "Luc. 2:29-32"
|
||||||
en: "Luke 2:29-32"
|
en: "Luke 2:29-32"
|
||||||
|
|||||||
+8
-5
@@ -26,13 +26,16 @@ const registry: Record<HourId, Resolver> = {
|
|||||||
matins: matins.resolveOrdo,
|
matins: matins.resolveOrdo,
|
||||||
};
|
};
|
||||||
|
|
||||||
/** Attaches the closing Gloria Patri to every plain psalm part (recursing
|
/** Attaches the closing Gloria Patri to every plain psalm and canticle part
|
||||||
* into 'nocturn-psalmody' groups), so no per-hour builder has to remember
|
* (recursing into 'nocturn-psalmody' groups), so no per-hour builder has to
|
||||||
* to do it — and so the Sacred Triduum omission (resolveGloriaPatri) is
|
* remember to do it — and so the Sacred Triduum omission
|
||||||
* enforced in exactly one place rather than once per hour file. */
|
* (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[] {
|
function withGloriaPatri(parts: ResolvedPart[], gloriaPatri: ResolvedText | undefined): ResolvedPart[] {
|
||||||
return parts.map((part) => {
|
return parts.map((part) => {
|
||||||
if (part.kind === 'psalm') {
|
if (part.kind === 'psalm' || part.kind === 'canticle') {
|
||||||
return { ...part, gloriaPatri };
|
return { ...part, gloriaPatri };
|
||||||
}
|
}
|
||||||
if (part.kind === 'nocturn-psalmody') {
|
if (part.kind === 'nocturn-psalmody') {
|
||||||
|
|||||||
+9
-17
@@ -1,7 +1,6 @@
|
|||||||
import type { HourDefinition, HourPart, ResolvedOrdo, ResolvedPart, ResolvedText } from './types';
|
import type { HourDefinition, HourPart, ResolvedOrdo, ResolvedPart, ResolvedText } from './types';
|
||||||
import type { LiturgicalDay, Weekday } from '../calendar/types';
|
import type { LiturgicalDay, Weekday } from '../calendar/types';
|
||||||
import { resolveDay, isSundayOrFeast, activeOctavesFor } from '../calendar';
|
import { resolveDay, isSundayOrFeast, activeOctavesFor } from '../calendar';
|
||||||
import { isInTriduum } from '../calendar/temporal';
|
|
||||||
import { getDayLabel } from '../calendar/day-label';
|
import { getDayLabel } from '../calendar/day-label';
|
||||||
import { getTemporalFeastRecord } from '../calendar/temporal-feasts';
|
import { getTemporalFeastRecord } from '../calendar/temporal-feasts';
|
||||||
import { getPsalmVerses } from '../psalter';
|
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 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
|
/** 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
|
* Saturday's split canticle below) into one flowing text block, the same
|
||||||
* shape nunc-dimittis/benedictus already use — Lauds' weekday canticles
|
* shape nunc-dimittis/benedictus already use — Lauds' weekday canticles
|
||||||
* are stored verse-by-verse (see lauds-canticles.ts) purely because that's
|
* are stored verse-by-verse (see lauds-canticles.ts) purely because that's
|
||||||
* how they were transcribed, not because callers need per-verse access.
|
* how they were transcribed, not because callers need per-verse access.
|
||||||
* Appends the fixed Gloria Patri line every canticle (or canticle part)
|
* Does NOT append a closing Gloria Patri — that's attached centrally
|
||||||
* ends with in real practice, same wording as benedictus.yml's own
|
* (hours/index.ts) as its own 'canticle' field, same as every other
|
||||||
* trailing line — omitted during the Sacred Triduum, live-verified against
|
* canticle/psalm, so the Sacred Triduum omission only has to be handled
|
||||||
* both Divino Afflatu 1954 and Tridentine 1906 (Benedictus at Lauds of
|
* in one place. */
|
||||||
* Holy Thursday omits it same as any psalm). */
|
function canticleText(canticleId: string, slice?: [number, number]): ResolvedText {
|
||||||
function canticleText(canticleId: string, slice: [number, number] | undefined, omitGloriaPatri: boolean): ResolvedText {
|
|
||||||
const canticle = getCanticle(canticleId);
|
const canticle = getCanticle(canticleId);
|
||||||
const verses = slice ? canticle.verses.slice(slice[0], slice[1]) : canticle.verses;
|
const verses = slice ? canticle.verses.slice(slice[0], slice[1]) : canticle.verses;
|
||||||
const text: Partial<Record<string, string>> = {};
|
const text: Partial<Record<string, string>> = {};
|
||||||
@@ -118,7 +111,7 @@ function canticleText(canticleId: string, slice: [number, number] | undefined, o
|
|||||||
if (lines.length === 0) {
|
if (lines.length === 0) {
|
||||||
continue;
|
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';
|
let worst: 'verified' | 'draft' | 'missing' = 'verified';
|
||||||
for (const v of verses) {
|
for (const v of verses) {
|
||||||
const s = v.status[lang] ?? 'missing';
|
const s = v.status[lang] ?? 'missing';
|
||||||
@@ -153,7 +146,6 @@ function resolvePsalmody(day: LiturgicalDay): ResolvedPart[] {
|
|||||||
const canticle = getCanticle(wd.canticle.id);
|
const canticle = getCanticle(wd.canticle.id);
|
||||||
const canticleOpening = opening(wd.canticle.antiphon);
|
const canticleOpening = opening(wd.canticle.antiphon);
|
||||||
const canticleClosing = splitNamedAntiphon(verifiedText(wd.canticle.antiphon)).full;
|
const canticleClosing = splitNamedAntiphon(verifiedText(wd.canticle.antiphon)).full;
|
||||||
const omitGloriaPatri = isInTriduum(day.date);
|
|
||||||
if (wd.canticle.split) {
|
if (wd.canticle.split) {
|
||||||
// Said in two pieces, own Gloria Patri each, one shared antiphon
|
// Said in two pieces, own Gloria Patri each, one shared antiphon
|
||||||
// framing both (opening before the first, full repeated only after
|
// framing both (opening before the first, full repeated only after
|
||||||
@@ -162,19 +154,19 @@ function resolvePsalmody(day: LiturgicalDay): ResolvedPart[] {
|
|||||||
kind: 'canticle',
|
kind: 'canticle',
|
||||||
canticleId: canticle.id,
|
canticleId: canticle.id,
|
||||||
antiphon: canticleOpening,
|
antiphon: canticleOpening,
|
||||||
text: canticleText(canticle.id, [0, wd.canticle.split], omitGloriaPatri),
|
text: canticleText(canticle.id, [0, wd.canticle.split]),
|
||||||
});
|
});
|
||||||
parts.push({
|
parts.push({
|
||||||
kind: 'canticle',
|
kind: 'canticle',
|
||||||
canticleId: canticle.id,
|
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 {
|
} else {
|
||||||
parts.push({
|
parts.push({
|
||||||
kind: 'canticle',
|
kind: 'canticle',
|
||||||
canticleId: canticle.id,
|
canticleId: canticle.id,
|
||||||
antiphon: canticleOpening,
|
antiphon: canticleOpening,
|
||||||
text: canticleText(canticle.id, undefined, omitGloriaPatri),
|
text: canticleText(canticle.id),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
parts.push({ kind: 'antiphon', text: canticleClosing });
|
parts.push({ kind: 'antiphon', text: canticleClosing });
|
||||||
|
|||||||
+3
-1
@@ -204,7 +204,9 @@ export type ResolvedPart =
|
|||||||
// Sacred Triduum (calendar/temporal.ts's isInTriduum) — never omit it
|
// Sacred Triduum (calendar/temporal.ts's isInTriduum) — never omit it
|
||||||
// when building a psalm part yourself.
|
// when building a psalm part yourself.
|
||||||
| { kind: 'psalm'; psalmNumber: number; antiphon?: ResolvedText; verses: ResolvedVerse[]; gloriaPatri?: ResolvedText }
|
| { 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 —
|
// A standalone antiphon, said on its own rather than framing a psalm —
|
||||||
// see 'closing-antiphon' above.
|
// see 'closing-antiphon' above.
|
||||||
| { kind: 'antiphon'; text: ResolvedText }
|
| { kind: 'antiphon'; text: ResolvedText }
|
||||||
|
|||||||
@@ -109,6 +109,7 @@ function renderPart(part: ResolvedPart, languages: readonly string[]): string {
|
|||||||
<h3 class="ordo-part-label">${escapeHtml(title)}</h3>
|
<h3 class="ordo-part-label">${escapeHtml(title)}</h3>
|
||||||
${renderCitation(part.text)}
|
${renderCitation(part.text)}
|
||||||
${renderColumns(part.text, languages)}
|
${renderColumns(part.text, languages)}
|
||||||
|
${part.gloriaPatri ? `<div class="ordo-part-gloria-patri">${renderColumns(part.gloriaPatri, languages)}</div>` : ''}
|
||||||
</section>
|
</section>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -78,8 +78,8 @@ describe('resolveOrdo("lauds", ...)', () => {
|
|||||||
expect(part1?.kind === 'canticle' ? part1.text.text.la : undefined).toContain('Audíte, cæli');
|
expect(part1?.kind === 'canticle' ? part1.text.text.la : undefined).toContain('Audíte, cæli');
|
||||||
expect(part2?.kind === 'canticle' ? part2.text.text.la : undefined).toContain('Ignis succénsus est');
|
expect(part2?.kind === 'canticle' ? part2.text.text.la : undefined).toContain('Ignis succénsus est');
|
||||||
// Each part gets its own Gloria Patri.
|
// Each part gets its own Gloria Patri.
|
||||||
expect(part1?.kind === 'canticle' ? part1.text.text.la : undefined).toContain('Glória Patri');
|
expect(part1?.kind === 'canticle' ? part1.gloriaPatri?.text.la : undefined).toContain('Glória Patri');
|
||||||
expect(part2?.kind === 'canticle' ? part2.text.text.la : undefined).toContain('Glória Patri');
|
expect(part2?.kind === 'canticle' ? part2.gloriaPatri?.text.la : undefined).toContain('Glória Patri');
|
||||||
|
|
||||||
const repeatIndex = ordo.parts.indexOf(part2!) + 1;
|
const repeatIndex = ordo.parts.indexOf(part2!) + 1;
|
||||||
const repeat = ordo.parts[repeatIndex];
|
const repeat = ordo.parts[repeatIndex];
|
||||||
|
|||||||
Reference in New Issue
Block a user