Add Vespers' ferial Preces, sourced from Tridentine 1906/1910

Monastic 1617 has no ferial Preces at Vespers, same gap Lauds already
had — the fuller "Preces feriales Vespera" is byte-for-byte the Lauds
ferial Preces with Psalm 129 swapped for Psalm 50 in the source, live-
verified against the same clean Advent feria already used for Lauds.
The short Sunday/feast form turns out to be identical at both hours too,
so it's reused directly rather than duplicated.

Factors the shared ferial-or-vigil predicate out of hours/lauds.ts into
resolve-common.ts's isFerialOrVigil so both hours' -preces cases resolve
it the same way instead of keeping two copies in sync by hand.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-13 21:30:01 -04:00
parent ce954fea9a
commit 4d88568c42
12 changed files with 242 additions and 20 deletions
+27 -1
View File
@@ -3,7 +3,8 @@ import type { Commemoration, DayWinner, LiturgicalDay, Weekday } from '../calend
import type { ProperText } from '../propers';
import { getCommonProper, getTemporalProper } from '../propers';
import { getSaintRecord } from '../calendar/feasts';
import { resolveActiveOctave } from '../calendar';
import { resolveActiveOctave, activeOctavesFor } from '../calendar';
import { getTemporalFeastRecord } from '../calendar/temporal-feasts';
import { splitAntiphon } from './antiphon';
import vespersMagnificatAntiphonsData from '../data/hours/vespers-magnificat-antiphons.yml';
@@ -105,6 +106,31 @@ export function isSundayOrFeastOffice(day: LiturgicalDay): boolean {
return day.weekday === 'sunday' || resolveOfficeWinner(day).kind === 'sanctoral';
}
/**
* A day/hour is "ferial or vigil" — the predicate both Lauds' and Vespers'
* ferial Preces (`lauds-preces`/`vespers-preces`) key off to pick the
* fuller Tridentine 1906/1910 litany over the short Sunday/feast one.
* Deliberately not `isSundayOrFeast`/`isSundayOrFeastOffice` — those treat
* any sanctoral winner, Vigils included, as a "feast." Here a Vigil needs
* to land on the ferial side instead (per direct instruction), so it's
* checked for explicitly: a Vigil-ranked sanctoral winner, or a bare
* temporal winner that isn't Sunday and isn't a named temporal identity
* like Marian Saturday or Christ the King (which aren't Sunday or a
* sanctoral winner either, but still aren't a bare ferial office — same
* reasoning as Lauds' own Cross-suffrage gate). Also excludes any day
* within an active octave — live-verified (Tridentine 1910, day 3 of St.
* Lawrence's own octave, both Lauds and Vespers): "Preces Feriales
* {omittitur}", same exclusion the suffrages already have, for the same
* reason — an octave day isn't a bare ferial office even though nothing
* else is winning it outright.
*/
export function isFerialOrVigil(day: LiturgicalDay): boolean {
const isVigil = day.winner.kind === 'sanctoral' && day.winner.rank === 'vigil';
const isBareFeria = day.winner.kind === 'temporal' && !getTemporalFeastRecord(day.winner.id);
const isWithinAnOctave = activeOctavesFor(day.date).length > 0;
return day.weekday !== 'sunday' && !isWithinAnOctave && (isVigil || isBareFeria);
}
/**
* The id to look up for today's office winner's (resolveOfficeWinner)
* minor-hour content (antiphon + chapter) — `${hourId}-antiphon-${id}`/