From a2d082e6080be56cb86acd4b04420fcf4ea70a35 Mon Sep 17 00:00:00 2001 From: Will Estes Date: Sat, 29 Aug 2026 05:29:56 -0400 Subject: [PATCH] Keep a short litany + Our Father at Compline on a Double, don't omit compline-preces.yml was already the secular Tridentine 1906/1910 text word-for-word (verified live against both tracks), so no text change was needed there. The actual gap: both Monastic and secular tracks drop Compline's Preces to nothing on a Double-or-higher, unlike Vespers/Lauds which fall back to a short form. Per explicit request, this is now a deliberate vu-specific deviation from both tracks: on a Double, Compline substitutes the same short Kyrie/Pater-Noster litany Vespers already falls back to on Sundays/feasts, instead of showing nothing. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01AHXJAWVAabCKX1JgmDh4Rn --- TODO.md | 22 ++++++++++++++++++++++ src/data/hours/compline.yml | 15 +++++++++++---- src/hours/compline.ts | 17 ++++++++++++----- src/hours/types.ts | 12 +++++++++--- tests/hours/compline.test.ts | 17 +++++++++++++++++ 5 files changed, 71 insertions(+), 12 deletions(-) diff --git a/TODO.md b/TODO.md index 3fd0983..bd7c328 100644 --- a/TODO.md +++ b/TODO.md @@ -4074,3 +4074,25 @@ day) had to move to Oct 1 (her First Vespers) once tracing `resolveEveningDay` s doesn't keep her own Second Vespers (falls short of the `duplex-2-classis` floor) and Oct 3 (St. Therese, plain Duplex, below this override's own threshold) claims her evening instead. `npm test` (542 passed), `tsc --noEmit`, and `npm run build` all pass. + +### Compline Preces on a Double no longer vanish entirely — done (2026-08-28) + +User request: incorporate the secular track's Compline Preces, but always keep a short litany + +Our Father said, even on a Double. Checked first rather than assuming a text change was needed: +`compline-preces.yml`'s content is already word-for-word the secular Tridentine 1906/1910 text +(the reference engine treats "1888/1906/1910" as one combined version label, same rubrics) — it +was already the fuller, non-Monastic form, live-verified against both tracks. The real gap was +structural: `compline.ts`'s `'preces'` case dropped the whole part to `[]` on a Double-or-higher +(`omitOnDouble`), matching real practice in *both* Monastic and secular tracks (Compline's +Preces are never substituted with anything shorter in the reference engine, secular or not — +unlike Vespers/Lauds, which do fall back to a short litany). + +Per the user's explicit request, this is now a deliberate vu-specific deviation from both +tracks: `compline.ts`'s `'preces'` case substitutes `lauds-short-litany` (the same short +Kyrie/Pater-Noster litany Vespers' own `vespers-preces` case already falls back to on Sundays/ +feasts) instead of omitting, whenever `omitOnDouble` and `isDoubleOrHigher` both hold. `types.ts`'s +`omitOnDouble` doc comment and `compline.yml`'s own header updated to describe the substitution +rather than a drop. Regression test added (`tests/hours/compline.test.ts`, 2026-08-15, the +Assumption, Duplex I. classis) confirming the short-form text (Kyrie/Pater/"Dómine, exáudi") is +present and the fuller exchanges ("Benedíctus es, Dómine.../Miserére nostri...") are absent. +`npm test` (543 passed), `tsc --noEmit`, and `npm run build` all pass. diff --git a/src/data/hours/compline.yml b/src/data/hours/compline.yml index 77fb477..f43c99b 100644 --- a/src/data/hours/compline.yml +++ b/src/data/hours/compline.yml @@ -7,12 +7,19 @@ # - The "In manus tuas" short responsory, right after the capitulum. # - The Nunc Dimittis, framed by its "Salva nos" antiphon, right after # the responsory's versicle. -# - The fuller Tridentine Preces, in place of Monastic's own shorter form -# (which drops the "Benedíctus es, Dómine..." triple exchange and the -# "Miserére nostri, Dómine.../Fiat misericórdia..." exchange — both +# - The fuller Tridentine Preces (Tridentine 1906/1910 -- both share the +# same rubrics/calendar-era text per the reference engine's own combined +# "1888/1906/1910" version label), in place of Monastic's own shorter +# form (which drops the "Benedíctus es, Dómine..." triple exchange and +# the "Miserére nostri, Dómine.../Fiat misericórdia..." exchange — both # marked "(sed rubrica ^Monastic omittitur)" in the source). Compline # has no separate ferial/Sunday Preces text the way Prime does — one -# form, gated only by omitOnDouble (see calendar/isDoubleOrHigher). +# form, gated by omitOnDouble (see calendar/isDoubleOrHigher) -- but by +# deliberate choice (not drawn from either track, which both drop +# Compline's Preces to nothing on a Double) that gate substitutes the +# plain Kyrie/Pater-Noster short litany rather than omitting outright, +# so a short litany + Our Father is always said. See hours/compline.ts's +# 'preces' case. # - Lent's distinct hymn replacement ("Christe, qui lux es et dies") — # see hours/compline.ts's 'hymn' case for a caveat on how confident to # be that this actually triggers automatically in real practice. diff --git a/src/hours/compline.ts b/src/hours/compline.ts index d0d7963..0054542 100644 --- a/src/hours/compline.ts +++ b/src/hours/compline.ts @@ -41,11 +41,18 @@ function resolvePart(part: HourPart, day: LiturgicalDay): ResolvedPart[] { return [{ kind: part.kind, text: resolveCommon(part.textRef.id) }]; case 'responsory': return [{ kind: 'responsory', text: resolveResponsory(resolveCommon(part.textRef.id), day) }]; - case 'preces': - if (part.omitOnDouble && isDoubleOrHigher(day.winner)) { - return []; - } - return [{ kind: 'preces', text: resolveCommon(part.textRef.id), label: part.label }]; + case 'preces': { + // On a Double-or-higher, the full secular Preces (compline-preces — + // already the fuller Tridentine 1906/1910 text, see compline.yml's + // own header) steps aside for the plain Kyrie/Pater-Noster short + // litany (lauds-short-litany, already reused by Vespers' own + // 'vespers-preces' case the same way) rather than vanishing + // entirely — a deliberate choice to always keep a short litany + + // Our Father, even though real practice (both Monastic and + // secular) drops Compline's Preces to nothing on a Double. + const id = part.omitOnDouble && isDoubleOrHigher(day.winner) ? 'lauds-short-litany' : part.textRef.id; + return [{ kind: 'preces', text: resolveCommon(id), label: part.label }]; + } case 'lesson': return [{ kind: 'lesson', text: resolveCommon(part.textRef.id), label: part.label }]; case 'opening-versicle': diff --git a/src/hours/types.ts b/src/hours/types.ts index f2bb2e0..20bc3cb 100644 --- a/src/hours/types.ts +++ b/src/hours/types.ts @@ -34,9 +34,15 @@ export type HourPart = // `label`, when given, is shown as a heading (e.g. "Preces", "Pretiosa") — // omitted for parts that are just connective tissue around a reading // (a blessing, a closing dialogue) that don't read as their own section. - // `omitOnDouble` drops the whole part on a Double-or-higher feast (real - // Preces are suppressed above a certain rank) — see calendar/ - // isDoubleOrHigher for how far that's actually wired up today. + // `omitOnDouble` marks a part that's suppressed above a certain rank on + // real Double-or-higher feasts (see calendar/isDoubleOrHigher) — its own + // resolver decides what (if anything) replaces it. Compline's own + // 'preces' part is the only current user: it substitutes the short + // Kyrie/Pater litany (see hours/compline.ts's 'preces' case) rather than + // dropping to nothing, a deliberate vu-specific choice (real practice, in + // both Monastic and secular tracks, drops Compline's Preces to nothing + // on a Double, with no substitute) — the flag still means "not the full + // text here," just not "nothing here." | { kind: 'preces'; textRef: PropersRef; label?: string; omitOnDouble?: true } | { kind: 'psalm'; psalmNumber: number; verses?: string; antiphonRef?: PropersRef } | { kind: 'canticle'; canticleId: string; textRef: PropersRef; antiphonRef?: PropersRef } diff --git a/tests/hours/compline.test.ts b/tests/hours/compline.test.ts index 9d337bc..6db02d4 100644 --- a/tests/hours/compline.test.ts +++ b/tests/hours/compline.test.ts @@ -77,6 +77,23 @@ describe('resolveOrdo("compline", ...)', () => { expect(preces && preces.kind === 'preces' ? preces.text.text.la : undefined).toContain('Miserére nostri'); }); + it('substitutes the short Kyrie/Pater litany for the fuller Preces on a Double-or-higher, rather than omitting it entirely', () => { + // 2026-08-15, the Assumption (Duplex I. classis), keeps her own + // evening outright. Real practice (both Monastic and secular tracks) + // drops Compline's Preces to nothing on a Double -- vu deliberately + // substitutes the plain short litany instead, so a short litany + + // Our Father is always said. See hours/compline.ts's 'preces' case + // and data/hours/compline.yml's own header. + const ordo = resolveOrdo('compline', '2026-08-15'); + const preces = ordo.parts.find((p) => p.kind === 'preces' && p.text.text.la?.includes('Kýrie, eléison')); + expect(preces).toBeDefined(); + const la = preces && preces.kind === 'preces' ? preces.text.text.la : undefined; + expect(la).toContain('Pater noster'); + expect(la).toContain('Dómine, exáudi oratiónem meam'); + expect(la).not.toContain('Benedíctus es, Dómine'); + expect(la).not.toContain('Miserére nostri'); + }); + it('closes with the Salve Regina (the per-annum default Marian antiphon), labeled by name', () => { const ordo = resolveOrdo('compline', MONDAY); const last = ordo.parts[ordo.parts.length - 1];