diff --git a/TODO.md b/TODO.md index d6944dc..d2a00e6 100644 --- a/TODO.md +++ b/TODO.md @@ -271,14 +271,20 @@ real `draft` status went through it. - `getDayCollects`: each collect in a multi-collect day renders as its own complete "Let us pray ... Amen." block, not the more compressed real form (one "Let us pray," only the last collect's doxology). -- Suffrage omission only checks `omitOnDouble` (+ Marian Saturday's own - Cross/BVM filter, + Christ the King's explicit id check, + the Cross-only - ferial-office check added 2026-08). Real seasonal suppression is still - missing: per the Tridentine 1906/1910 source (`specials.pl`'s - `checksuffragium`), suffrages are omitted *entirely* during Advent, - Christmastide, and Passiontide, and whenever any octave is active — none - of that is modeled, so e.g. an Advent feria currently still shows all - five suffrages when the real office would have none. +- Suffrage omission (`lauds.ts`'s `suffrages` case) now checks: Double-or- + higher rank, Christ the King, Advent/Christmastide/Passiontide season, + any active octave (however low its own rank), Marian Saturday's own + Cross/BVM filter, and the Cross-only ferial-office check — all + confirmed directly against **Monastic Tridentinum 1617** itself, not + just the Tridentine 1906/1910 track the suffrage content and the + Joseph/Cross-gating logic were sourced from (checked Advent 2026-12-03, + Christmastide 2026-12-30, Passiontide 2026-03-23, an active octave via + St. Lawrence's own Aug 11-12, and ordinary Lent 2026-02-20 specifically + *not* excluded, to confirm it's Passiontide-only, not all of Lent). + Still not modeled: the Paschaltide *substitution* (real practice + replaces the whole suffrage set with a single alleluia-form "Suffragium + Paschale" during Eastertide rather than omitting or showing the + ordinary set) — flagged, not built. - `data/calendar/temporal-categories.yml`'s season→category reconstruction is from general knowledge of the pre-1955 tradition, not independently verified against a primary source for every entry (flagged inline where diff --git a/src/hours/lauds.ts b/src/hours/lauds.ts index 49d8db9..50ba88b 100644 --- a/src/hours/lauds.ts +++ b/src/hours/lauds.ts @@ -1,6 +1,6 @@ import type { HourDefinition, HourPart, ResolvedOrdo, ResolvedPart, ResolvedText } from './types'; import type { LiturgicalDay, Weekday } from '../calendar/types'; -import { resolveDay, isAtLeast, isSundayOrFeast } from '../calendar'; +import { resolveDay, isAtLeast, isSundayOrFeast, activeOctavesFor } from '../calendar'; import { getDayLabel } from '../calendar/day-label'; import { getTemporalFeastRecord } from '../calendar/temporal-feasts'; import { getPsalmVerses } from '../psalter'; @@ -244,7 +244,19 @@ function resolvePart(part: HourPart, day: LiturgicalDay): ResolvedPart[] { // "Suffragium{omittitur}" — the whole set drops, same as any other // Double-or-higher day. const isChristTheKing = day.winner.kind === 'temporal' && day.winner.id === 'christ-the-king'; - if (part.omitOnDouble && (isDoubleOrHigher(day.winner) || isChristTheKing)) { + // Confirmed live against Monastic Tridentinum 1617 directly (this + // isn't a Tridentine-1906-only quirk -- the same exclusions hold + // under the app's own primary rubric track): suffrages are omitted + // *entirely* during Advent, Christmastide, and Passiontide (not all + // of Lent -- ordinary Lent ferias still get them, checked directly + // against 2026-02-20; only the last two weeks), and on any day + // within an active octave, however low that octave's own rank is + // -- checked directly against day 3 of St. Lawrence's own + // (Semiduplex) octave, which still omits them despite being well + // below the Duplex+ rank threshold below. + const isSeasonallyExcluded = day.season === 'advent' || day.season === 'christmastide' || day.season === 'passiontide'; + const isWithinAnOctave = activeOctavesFor(day.date).length > 0; + if (part.omitOnDouble && (isDoubleOrHigher(day.winner) || isChristTheKing || isSeasonallyExcluded || isWithinAnOctave)) { return []; } // "Of the Holy Cross" is the real *ferial*-office suffrage, sourced diff --git a/tests/hours/lauds.test.ts b/tests/hours/lauds.test.ts index 89a3d69..d6f6163 100644 --- a/tests/hours/lauds.test.ts +++ b/tests/hours/lauds.test.ts @@ -219,6 +219,26 @@ describe('resolveOrdo("lauds", ...)', () => { expect(crossSuffrage).toBeUndefined(); }); + it('omits the suffrages entirely during Advent, Christmastide, and Passiontide -- confirmed against Monastic Tridentinum 1617 directly, not just Tridentine 1906/1910', () => { + const advent = resolveOrdo('lauds', '2026-12-03'); // plain Advent feria + const christmastide = resolveOrdo('lauds', '2026-12-30'); // plain day within the Christmas Octave + const passiontide = resolveOrdo('lauds', '2026-03-23'); // Monday of Passion Week + for (const ordo of [advent, christmastide, passiontide]) { + expect(ordo.parts.some((p) => p.kind === 'preces' && p.label === 'Of the Holy Cross')).toBe(false); + expect(ordo.parts.some((p) => p.kind === 'preces' && p.label === 'For Peace')).toBe(false); + } + }); + + it('shows the suffrages on an ordinary Lent feria (not Passiontide) -- only the last two weeks of Lent are excluded, not all of it', () => { + const ordo = resolveOrdo('lauds', '2026-02-20'); // Friday after Ash Wednesday, week 1 of Lent + expect(ordo.parts.some((p) => p.kind === 'preces' && p.label === 'For Peace')).toBe(true); + }); + + it('omits the suffrages entirely on any day within an active octave, however low the octave\'s own rank is', () => { + const ordo = resolveOrdo('lauds', LAWRENCE_OCTAVE_DAY); // day 2 of St. Lawrence's own (Semiduplex) octave + expect(ordo.parts.some((p) => p.kind === 'preces' && p.label === 'For Peace')).toBe(false); + }); + it('closes with the Conclusio, the closing versicle ("may the Lord grant us his peace... and life everlasting"), and the Marian antiphon -- without spelling out the silent Our Father', () => { const ordo = resolveOrdo('lauds', FERIAL_TUESDAY); const closingVersicle = ordo.parts.find(