Give Advent Sundays their own Matins Nocturn I-III content
Deploy / deploy (push) Successful in 1m27s

Nocturns I-II had no seasonal override mechanism at all, and Nocturn III's
own Septuagesima-Palm override didn't cover Advent — live-verified against
the reference engine that Advent has its own seasonal antiphons/versicles
across all three nocturns, identical on all 4 Advent Sundays.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017XMSokiTD2Qc5vPP2YQu3Q
This commit is contained in:
2026-09-01 09:07:46 -04:00
parent 20b1401919
commit c3e540f6c2
7 changed files with 347 additions and 3 deletions
+33 -3
View File
@@ -95,6 +95,7 @@ import { getOctaveReading } from '../propers/octave-readings';
import { getMatinsSaintOverride, getMatinsCommonOverride, type MatinsPsalmodyScheme } from './matins-psalmody-overrides';
import matinsSundayAntiphonsData from '../data/hours/matins-sunday-antiphons.yml';
import matinsSundayNocturn3OverridesData from '../data/hours/matins-sunday-nocturn3-overrides.yml';
import matinsSundayAdventOverridesData from '../data/hours/matins-sunday-advent-nocturn-overrides.yml';
import matinsFerialAntiphonsData from '../data/hours/matins-ferial-antiphons.yml';
import type { Weekday } from '../calendar/types';
@@ -145,6 +146,35 @@ function sundayNocturn3For(temporalId: string | undefined): SundayNocturn {
};
}
// Advent's own Nocturns I-III override, applied uniformly across all 4
// Advent Sundays (unlike sundayNocturn3Overrides above, which is keyed
// per-distinct-Sunday for Septuagesima-Palm) — see
// data/hours/matins-sunday-advent-nocturn-overrides.yml's own header.
interface MatinsSundayAdventOverrides {
nocturn1: Partial<SundayNocturn>;
nocturn2: Partial<SundayNocturn>;
nocturn3: Partial<SundayNocturn>;
}
const sundayAdventOverrides = matinsSundayAdventOverridesData as unknown as MatinsSundayAdventOverrides;
/** Sunday Nocturn I/II/III content for a given day — Advent's own
* season-wide override (all three nocturns) takes precedence over the
* plain-season default; Nocturn III additionally checks the per-Sunday
* Septuagesima-Palm override (`sundayNocturn3For`) when Advent doesn't
* apply (the two never overlap). */
function sundayNocturnFor(nocturnKey: 'nocturn1' | 'nocturn2' | 'nocturn3', day: LiturgicalDay, temporalId: string | undefined): SundayNocturn {
const base = nocturnKey === 'nocturn3' ? sundayNocturn3For(temporalId) : sundayAntiphons[nocturnKey];
if (day.season !== 'advent') return base;
const override = sundayAdventOverrides[nocturnKey];
if (!override) return base;
return {
groups: override.groups ?? base.groups,
canticles: override.canticles ?? base.canticles,
antiphon: override.antiphon ?? base.antiphon,
versicle: override.versicle ?? base.versicle,
};
}
interface FerialGroup {
psalms: PsalmRef[];
antiphon: BilingualText;
@@ -704,13 +734,13 @@ export function resolveOrdo(date: string): ResolvedOrdo {
if (threeNocturns && day.weekday === 'sunday') {
parts.push({ kind: 'section-heading', label: `Nocturn ${NOCTURN_NUMERAL[0]}` });
parts.push(...sundayPsalmNocturn(sundayAntiphons.nocturn1, day));
parts.push(...sundayPsalmNocturn(sundayNocturnFor('nocturn1', day, temporalId), day));
parts.push(...(nocturn1Readings ?? []));
parts.push({ kind: 'section-heading', label: `Nocturn ${NOCTURN_NUMERAL[1]}` });
parts.push(...sundayPsalmNocturn(sundayAntiphons.nocturn2, day));
parts.push(...sundayPsalmNocturn(sundayNocturnFor('nocturn2', day, temporalId), day));
parts.push(...(nocturn2Readings ?? []));
parts.push({ kind: 'section-heading', label: `Nocturn ${NOCTURN_NUMERAL[2]}` });
parts.push(...sundayCanticleNocturn(sundayNocturn3For(temporalId), day));
parts.push(...sundayCanticleNocturn(sundayNocturnFor('nocturn3', day, temporalId), day));
parts.push(...(nocturn3Readings ?? []));
parts.push({ kind: 'te-deum', text: resolveCommon('te-deum') });
parts.push({ kind: 'versicle', text: resolveCommon('te-decet-laus') });