Author Benedictus antiphon + P/T/S/N Commons for 66 propers:null saints
Deploy / deploy (push) Successful in 56s
Deploy / deploy (push) Successful in 56s
Continues the 94-saint batch this collects pass unblocked. Raw Commune/*.txt fields don't reliably match what the engine actually renders per hour -- same lesson as the collects pass, confirmed again: no CommuneM/*.txt file defines explicit per-hour antiphon fields for any category this batch needed, and the already-authored common-of-a-confessor-bishop content (from the original 29-saint pass) doesn't match Commune/C4.txt's own raw [Ant 1] either, since it was live-query-sourced rather than read from the file directly. Fixed by querying web/cgi-bin/horas/officium.pl directly (local Perl, not a network fetch) for one representative saint per Common category, with every representative's title verified against an accent-insensitive name-stem match before its content is trusted -- caught a stale-date bug this way: St. Emerentiana's stored clean-year comment actually rendered St. Hilary of Poitiers's office that year. Cross-querying multiple saints per nominal SaintRecord.common category found the real liturgical groupings are coarser (Martyr-Bishop and Martyr; Abbot, Confessor-not-Bishop, and Confessor; Virgin-Martyr, Virgin, and Holy Woman all render identical minor-hour text) but not uniformly so (Pope-Martyrs render distinct allelúja-toned text from plain Several-Martyrs; the Dedication of the Basilicas of Ss. Peter and Paul draws from the Dedication Common, not its nominal common-of-an-apostle at all). Net: 7 new shared Common categories plus the pre-existing common-of-a-confessor-bishop (which also needed a new Benedictus-antiphon file of its own) cover 66 of the 94 saints. New mechanism: SaintRecord.benedictusCommon, with getBenedictusAntiphon falling back to it the same way getDayCollect already falls back to collectCommon -- no name-substitution needed here, since every Common category in current use renders name-free Benedictus-antiphon text. Also fixes a real bug this surfaced: getMinorHourOverrideId gated all Prime/Terce/Sext/None lookup behind the same duplex-majus+ threshold Lauds' own psalmody override uses, on the assumption that only a strong feast carries its own minor-hour content -- live-querying proved that wrong (a winning Semiduplex saint still renders its own antiphons, not the ferial default). Lauds' psalmody override itself stays majus+-gated correctly, since it's a much bigger per-saint commitment this batch doesn't add a Common fallback for. Ss. Ursula and Companions deliberately left without a minor-hour Common: her collect template has no other calendar member to verify a shared text against, and she can never be queried directly (a genuinely unwinnable stub) -- left honestly unauthored rather than guessed. The other 26 of the 94 (own proper collect, from the collects batch) still need their own individually-authored Benedictus antiphon, same shape as the original 109-saint pass -- not done in this pass. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
+35
-18
@@ -3,7 +3,7 @@ import type { Commemoration, DayWinner, LiturgicalDay } from '../calendar/types'
|
||||
import type { ProperText } from '../propers';
|
||||
import { getCommonProper, getTemporalProper } from '../propers';
|
||||
import { getSaintRecord } from '../calendar/feasts';
|
||||
import { resolveActiveOctave, isAtLeast } from '../calendar';
|
||||
import { resolveActiveOctave } from '../calendar';
|
||||
import { splitAntiphon } from './antiphon';
|
||||
|
||||
function toResolvedText(proper: ProperText): ResolvedText {
|
||||
@@ -93,30 +93,39 @@ export function isSundayOrFeastOffice(day: LiturgicalDay): boolean {
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether today's office winner (resolveOfficeWinner) is a strong enough
|
||||
* feast to carry its own proper minor-hour content (antiphon + chapter)
|
||||
* — duplex-majus+, the same threshold Lauds' own psalmody override uses
|
||||
* (hours/lauds.ts's getPsalmodyOverrideFor), kept in sync deliberately:
|
||||
* as each remaining duplex-majus+ saint gets a Lauds override authored,
|
||||
* the same id is what Prime/Terce/Sext/None would look for too, so it's
|
||||
* one shared backlog (see TODO.md), not per-hour lists that can drift.
|
||||
* The id to look up for today's office winner's (resolveOfficeWinner)
|
||||
* minor-hour content (antiphon + chapter) — `${hourId}-antiphon-${id}`/
|
||||
* `${hourId}-capitulum-${id}` first (a per-saint proper file, only ever
|
||||
* authored for duplex-majus+ saints in practice), then that saint's
|
||||
* `minorHoursCommon` fallback (see resolveMinorHourAntiphon/Chapter
|
||||
* below), tried regardless of rank: live-querying the reference engine
|
||||
* directly (2026-08, the 94 `propers: null` saints' P/T/S/N pass)
|
||||
* confirmed a winning Simplex/Semiduplex saint's own minor hours *do*
|
||||
* carry proper/Common antiphons, not the plain ferial default — a real
|
||||
* liturgical fact, not an app gap. Any sanctoral winner is therefore
|
||||
* eligible here; it's `resolveMinorHourAntiphon`/`resolveMinorHourChapter`
|
||||
* that decide, per hour, whether anything's actually been authored for
|
||||
* that id (falling through to the weekday default when not). Until
|
||||
* 2026-08 this was gated at duplex-majus+ (the same threshold Lauds'
|
||||
* own *psalmody* override uses, hours/lauds.ts's getPsalmodyOverrideFor)
|
||||
* — that threshold remains correct for the *psalmody* override
|
||||
* specifically (a much bigger authored-content commitment, still
|
||||
* majus+-only), but was wrongly reused here as if it were a general
|
||||
* "is this saint's office strong enough" rule.
|
||||
* Also honors ALWAYS_OVERRIDE_TEMPORAL_IDS the same way
|
||||
* getPsalmodyOverrideFor does — a named temporal feast (`christ-the-
|
||||
* king`, `christmas-octave-sunday`) is eligible regardless of rank, since
|
||||
* it has no FeastClass to compare against duplex-majus in the first
|
||||
* place. Missing until 2026-08: this function only checked the sanctoral
|
||||
* branch, so a temporal id in that set got a Lauds psalmody override but
|
||||
* silently no Prime/Terce/Sext/None one, however much was authored for
|
||||
* it — a real gap, not by design.
|
||||
* Returns the id to look up (`${hourId}-antiphon-${id}` etc.), or
|
||||
* `undefined` when nothing eligible is happening today.
|
||||
* it has no FeastClass to compare against in the first place.
|
||||
* Returns the id to look up, or `undefined` when nothing sanctoral or
|
||||
* named-temporal is happening today (a plain temporal day just stays on
|
||||
* its own weekday default, same as always).
|
||||
*/
|
||||
export function getMinorHourOverrideId(day: LiturgicalDay): string | undefined {
|
||||
const winner = resolveOfficeWinner(day);
|
||||
if (winner.kind === 'temporal' && ALWAYS_OVERRIDE_TEMPORAL_IDS.has(winner.id)) {
|
||||
return winner.id;
|
||||
}
|
||||
if (winner.kind === 'sanctoral' && isAtLeast(winner.rank, 'duplex-majus')) {
|
||||
if (winner.kind === 'sanctoral') {
|
||||
return winner.id;
|
||||
}
|
||||
return undefined;
|
||||
@@ -329,7 +338,9 @@ export function getDayCollects(day: LiturgicalDay): ResolvedPart[] {
|
||||
* pull, sourced from each saint's own raw [Ant 1] — see e.g.
|
||||
* st-lawrence-antiphon.yml) for a sanctoral winner, `${id}-benedictus-
|
||||
* antiphon` (not authored yet for any temporal id — resolves "missing",
|
||||
* same pending convention as everywhere else) for a temporal one. Uses
|
||||
* same pending convention as everywhere else) for a temporal one, then
|
||||
* `saint.benedictusCommon` (`benedictus-antiphon-${benedictusCommon}.yml`,
|
||||
* see SaintRecord's own doc comment) when neither exists. Uses
|
||||
* resolveOfficeWinner, not the raw `day.winner` — on an octave day this
|
||||
* is the octave's own feast's antiphon (live-verified: St. Lawrence's
|
||||
* "In cratícula", not the commemorated St. Clare's, and not the plain
|
||||
@@ -343,7 +354,13 @@ export function getBenedictusAntiphon(day: LiturgicalDay): ResolvedText {
|
||||
}
|
||||
const saint = getSaintRecord(winner.id);
|
||||
if (saint?.propers) {
|
||||
return resolveCommon(`${saint.propers}-antiphon`);
|
||||
const proper = resolveCommon(`${saint.propers}-antiphon`);
|
||||
if (proper.status.la !== 'missing' || proper.status.en !== 'missing') {
|
||||
return proper;
|
||||
}
|
||||
}
|
||||
if (saint?.benedictusCommon) {
|
||||
return resolveCommon(`benedictus-antiphon-${saint.benedictusCommon}`);
|
||||
}
|
||||
return { text: {}, status: { la: 'missing', en: 'missing' } };
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user