Give temporal commemorations at Lauds/Vespers a real Ant+V/R+collect bundle
getDayCollects previously rendered a commemorated Sunday/privileged feria (calendar/commemorations.ts's ordinary-sunday/privileged-sunday/ privileged-feria-minor branches) as a bare, unlabeled collect -- no antiphon, no versicle, no indication it was even a commemoration. Only sanctoral commemorations had gotten the fuller bundle treatment (2026-08-30 pass). Adds temporalCommemorationPart, reusing the temporal day's own benedictus/magnificat antiphon and weekday-keyed versicle (the same content it would use had it won outright), falling back to a labeled bare collect or honest "missing" block when no antiphon is authored for that id. Verified live for Nov 1, 2026 (All Saints on a Sunday): both Lauds and Vespers now show "Commemoration of The 22nd Sunday after Trinity" with real antiphon and versicle instead of a silent bare prayer. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LGsATZvfnpQ81HQuoF5JuL
This commit is contained in:
+1
-1
@@ -373,7 +373,7 @@ function resolvePart(part: HourPart, day: LiturgicalDay): ResolvedPart[] {
|
|||||||
case 'preces':
|
case 'preces':
|
||||||
return [{ kind: 'preces', text: resolveCommon(part.textRef.id), label: part.label }];
|
return [{ kind: 'preces', text: resolveCommon(part.textRef.id), label: part.label }];
|
||||||
case 'day-collects':
|
case 'day-collects':
|
||||||
return getDayCollects(day);
|
return getDayCollects(day, 'lauds');
|
||||||
case 'suffrages':
|
case 'suffrages':
|
||||||
return resolveSuffrages(day, part.omitOnDouble, 'lauds');
|
return resolveSuffrages(day, part.omitOnDouble, 'lauds');
|
||||||
case 'versicle':
|
case 'versicle':
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { getSaintRecord } from '../calendar/feasts';
|
|||||||
import { resolveActiveOctave, activeOctavesFor, octaveGoverningPrivilegedDay, isAtLeast, isSundayOrFeast } from '../calendar';
|
import { resolveActiveOctave, activeOctavesFor, octaveGoverningPrivilegedDay, isAtLeast, isSundayOrFeast } from '../calendar';
|
||||||
import { getTemporalFeastRecord } from '../calendar/temporal-feasts';
|
import { getTemporalFeastRecord } from '../calendar/temporal-feasts';
|
||||||
import { isInTriduum } from '../calendar/temporal';
|
import { isInTriduum } from '../calendar/temporal';
|
||||||
|
import { temporalLabel } from '../calendar/day-label';
|
||||||
import { splitAntiphon, isDoubleOrHigher } from './antiphon';
|
import { splitAntiphon, isDoubleOrHigher } from './antiphon';
|
||||||
import { getLaudsSaintOverride } from './lauds-psalmody-overrides';
|
import { getLaudsSaintOverride } from './lauds-psalmody-overrides';
|
||||||
import vespersMagnificatAntiphonsData from '../data/hours/vespers-magnificat-antiphons.yml';
|
import vespersMagnificatAntiphonsData from '../data/hours/vespers-magnificat-antiphons.yml';
|
||||||
@@ -720,6 +721,44 @@ function octaveCommemorationPart(commemoration: Extract<Commemoration, { kind: '
|
|||||||
return { kind: 'preces', text: { text: {}, status: { la: 'missing', en: 'missing' } }, label };
|
return { kind: 'preces', text: { text: {}, status: { la: 'missing', en: 'missing' } }, label };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** A temporal commemoration's own rendering — same fuller Ant+V/R+collect
|
||||||
|
* shape as sanctoralCommemorationPart, for when a Sunday or privileged
|
||||||
|
* feria with real standing of its own is commemorated in return after
|
||||||
|
* losing outright to a Duplex+ sanctoral winner (calendar/commemorations.ts's
|
||||||
|
* `ordinary-sunday`/`privileged-sunday`/`privileged-feria-minor` branches).
|
||||||
|
* There's no separate `${id}-commemoration` proper-file convention for
|
||||||
|
* temporal ids the way there is for saints, so this reuses the exact
|
||||||
|
* antiphon/versicle the temporal day would have used had it won outright:
|
||||||
|
* `${id}-benedictus-antiphon`/`${id}-magnificat-antiphon` (the same lookup
|
||||||
|
* getBenedictusAntiphon/getMagnificatAntiphon use for a real temporal
|
||||||
|
* winner) plus the plain weekday-keyed versicle every ferial/Sunday day
|
||||||
|
* already falls back to when no override bundle applies. */
|
||||||
|
function temporalCommemorationPart(
|
||||||
|
commemoration: Extract<Commemoration, { kind: 'temporal' }>,
|
||||||
|
day: LiturgicalDay,
|
||||||
|
hour: 'lauds' | 'vespers',
|
||||||
|
): ResolvedPart {
|
||||||
|
const label = `Commemoration of ${temporalLabel(day).en}`;
|
||||||
|
const collect = toResolvedText(getTemporalProper(`${commemoration.id}-collect`));
|
||||||
|
const antiphon = toResolvedText(
|
||||||
|
getTemporalProper(`${commemoration.id}-${hour === 'lauds' ? 'benedictus' : 'magnificat'}-antiphon`),
|
||||||
|
);
|
||||||
|
if (antiphon.status.la !== 'missing' || antiphon.status.en !== 'missing') {
|
||||||
|
const versicleId =
|
||||||
|
hour === 'lauds'
|
||||||
|
? `lauds-versicle-${day.weekday}`
|
||||||
|
: day.weekday === 'saturday'
|
||||||
|
? 'vespers-versicle-saturday'
|
||||||
|
: 'vespers-versicle-sunday-ferial';
|
||||||
|
const versicle = resolveCommon(versicleId);
|
||||||
|
return { kind: 'preces', text: combineCommemorationBundle(antiphon, versicle, collect), label };
|
||||||
|
}
|
||||||
|
if (collect.status.la !== 'missing' || collect.status.en !== 'missing') {
|
||||||
|
return { kind: 'preces', text: collect, label };
|
||||||
|
}
|
||||||
|
return { kind: 'preces', text: { text: {}, status: { la: 'missing', en: 'missing' } }, label };
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The day's own collect, plus one more per commemoration (calendar/
|
* The day's own collect, plus one more per commemoration (calendar/
|
||||||
* types.ts's LiturgicalDay.commemorations) — Lauds/Vespers say all of
|
* types.ts's LiturgicalDay.commemorations) — Lauds/Vespers say all of
|
||||||
@@ -753,7 +792,7 @@ function octaveCommemorationPart(commemoration: Extract<Commemoration, { kind: '
|
|||||||
* octave is skipped and every active one renders here — the only place
|
* octave is skipped and every active one renders here — the only place
|
||||||
* those four octaves' real "Commemoratio Octavæ ..." content surfaces.
|
* those four octaves' real "Commemoratio Octavæ ..." content surfaces.
|
||||||
*/
|
*/
|
||||||
export function getDayCollects(day: LiturgicalDay): ResolvedPart[] {
|
export function getDayCollects(day: LiturgicalDay, hour: 'lauds' | 'vespers'): ResolvedPart[] {
|
||||||
const parts: ResolvedPart[] = [{ kind: 'prayer', text: getDayCollect(day) }];
|
const parts: ResolvedPart[] = [{ kind: 'prayer', text: getDayCollect(day) }];
|
||||||
// Mirrors resolveOfficeWinner's own gate: an octave only actually
|
// Mirrors resolveOfficeWinner's own gate: an octave only actually
|
||||||
// supplied the primary collect when the day's winner itself wasn't a
|
// supplied the primary collect when the day's winner itself wasn't a
|
||||||
@@ -767,7 +806,7 @@ export function getDayCollects(day: LiturgicalDay): ResolvedPart[] {
|
|||||||
: undefined;
|
: undefined;
|
||||||
for (const commemoration of day.commemorations) {
|
for (const commemoration of day.commemorations) {
|
||||||
if (commemoration.kind === 'temporal') {
|
if (commemoration.kind === 'temporal') {
|
||||||
parts.push({ kind: 'prayer', text: toResolvedText(getTemporalProper(`${commemoration.id}-collect`)) });
|
parts.push(temporalCommemorationPart(commemoration, day, hour));
|
||||||
} else if (commemoration.kind === 'sanctoral') {
|
} else if (commemoration.kind === 'sanctoral') {
|
||||||
parts.push(sanctoralCommemorationPart(commemoration, day.weekday));
|
parts.push(sanctoralCommemorationPart(commemoration, day.weekday));
|
||||||
} else if (commemoration.kind === 'octave' && commemoration.id !== substitutedOctaveId) {
|
} else if (commemoration.kind === 'octave' && commemoration.id !== substitutedOctaveId) {
|
||||||
|
|||||||
@@ -286,7 +286,7 @@ function resolvePart(part: HourPart, day: LiturgicalDay, weekday: Weekday): Reso
|
|||||||
return [{ kind: 'preces', text: resolveCommon(id) }];
|
return [{ kind: 'preces', text: resolveCommon(id) }];
|
||||||
}
|
}
|
||||||
case 'day-collects':
|
case 'day-collects':
|
||||||
return getDayCollects(day);
|
return getDayCollects(day, 'vespers');
|
||||||
case 'suffrages':
|
case 'suffrages':
|
||||||
return resolveSuffrages(day, part.omitOnDouble, 'vespers');
|
return resolveSuffrages(day, part.omitOnDouble, 'vespers');
|
||||||
case 'preces':
|
case 'preces':
|
||||||
|
|||||||
@@ -27,21 +27,24 @@ describe('Advent 4 Sunday landing on Dec 24 (the Vigil of Christmas)', () => {
|
|||||||
expect(day.commemorations).toEqual([{ kind: 'temporal', id: 'advent-4' }]);
|
expect(day.commemorations).toEqual([{ kind: 'temporal', id: 'advent-4' }]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("Lauds carries both collects (Vigil's own, then Advent 4's commemorated) and the Vigil's own proper antiphons/Benedictus antiphon", () => {
|
it("Lauds carries the Vigil's own collect plus Advent 4's commemorated Ant+V/R+collect bundle, and the Vigil's own proper antiphons/Benedictus antiphon", () => {
|
||||||
const lauds = resolveOrdo('lauds', '2023-12-24');
|
const lauds = resolveOrdo('lauds', '2023-12-24');
|
||||||
const collects = lauds.parts.filter((p) => p.kind === 'prayer');
|
const ownCollect = lauds.parts.find((p) => p.kind === 'prayer');
|
||||||
expect(collects).toHaveLength(2);
|
expect(ownCollect?.kind).toBe('prayer');
|
||||||
for (const c of collects) {
|
if (ownCollect?.kind === 'prayer') {
|
||||||
if (c.kind !== 'prayer') continue;
|
expect(ownCollect.text.status.la).toBe('verified');
|
||||||
expect(c.text.status.la).toBe('verified');
|
expect(ownCollect.text.status.en).toBe('verified');
|
||||||
expect(c.text.status.en).toBe('verified');
|
expect(ownCollect.text.text.la).toContain('Deus, qui nos redemptiónis nostræ');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Advent 4's own real standing gets the fuller Ant+V/R+collect
|
||||||
|
// commemoration bundle (see resolve-common.ts's temporalCommemorationPart),
|
||||||
|
// not a bare collect -- same treatment a sanctoral commemoration gets.
|
||||||
|
const commemoration = lauds.parts.find((p) => p.kind === 'preces' && p.label?.startsWith('Commemoration of'));
|
||||||
|
expect(commemoration?.kind).toBe('preces');
|
||||||
|
if (commemoration?.kind === 'preces') {
|
||||||
|
expect(commemoration.text.text.la).toContain('Excita, quǽsumus, Dómine');
|
||||||
}
|
}
|
||||||
expect(collects[0]!.kind === 'prayer' ? collects[0]!.text.text.la : undefined).toContain(
|
|
||||||
'Deus, qui nos redemptiónis nostræ',
|
|
||||||
);
|
|
||||||
expect(collects[1]!.kind === 'prayer' ? collects[1]!.text.text.la : undefined).toContain(
|
|
||||||
'Excita, quǽsumus, Dómine',
|
|
||||||
);
|
|
||||||
|
|
||||||
const benedictus = lauds.parts.find((p) => p.kind === 'canticle' && p.canticleId === 'benedictus');
|
const benedictus = lauds.parts.find((p) => p.kind === 'canticle' && p.canticleId === 'benedictus');
|
||||||
expect(benedictus?.kind).toBe('canticle');
|
expect(benedictus?.kind).toBe('canticle');
|
||||||
|
|||||||
@@ -98,18 +98,19 @@ describe('Lauds psalmody override (duplex-majus+ sanctoral, and Marian Saturday)
|
|||||||
'He hath made us',
|
'He hath made us',
|
||||||
);
|
);
|
||||||
|
|
||||||
// One extra collect (the commemorated Sunday's own, still an
|
// The day's own single collect (Christ the King's) plus two labeled
|
||||||
// unlabeled 'prayer' -- a temporal commemoration, not a sanctoral
|
// 'preces' commemoration bundles: the commemorated Sunday's own (a
|
||||||
// one) plus the Simplex saint already commemorated under that Sunday
|
// temporal commemoration, now the fuller Ant+V/R+collect bundle since
|
||||||
// before Christ the King displaced it too, now a labeled 'preces'
|
// post-pentecost-22 has a real Benedictus antiphon authored -- see
|
||||||
// block rather than a third bare 'prayer' -- see
|
// resolve-common.ts's temporalCommemorationPart) and the Simplex saint
|
||||||
// tests/calendar/christ-the-king.test.ts.
|
// already commemorated under that Sunday before Christ the King
|
||||||
|
// displaced it too -- see tests/calendar/christ-the-king.test.ts.
|
||||||
const collects = ordo.parts.filter((p) => p.kind === 'prayer');
|
const collects = ordo.parts.filter((p) => p.kind === 'prayer');
|
||||||
expect(collects.length).toBe(2);
|
expect(collects.length).toBe(1);
|
||||||
const sanctoralCommemoration = ordo.parts.find(
|
const commemorations = ordo.parts.filter(
|
||||||
(p) => p.kind === 'preces' && p.label?.startsWith('Commemoration of'),
|
(p) => p.kind === 'preces' && p.label?.startsWith('Commemoration of'),
|
||||||
);
|
);
|
||||||
expect(sanctoralCommemoration).toBeDefined();
|
expect(commemorations.length).toBe(2);
|
||||||
|
|
||||||
const SUFFRAGE_LABELS = new Set([
|
const SUFFRAGE_LABELS = new Set([
|
||||||
'Of the Holy Cross',
|
'Of the Holy Cross',
|
||||||
|
|||||||
Reference in New Issue
Block a user