Label Vespers cross-day commemorations and add feast-side privilege
A commemoration that only exists because Vespers/Compline crossed a day boundary now renders as "(of today)"/"(of tomorrow)" in English and the real Divino Afflatu "de præcedenti"/"de sequenti" in Latin, instead of showing silently with no indication it's a cross-day artifact. Also adds forbidsSuccessorCommemoration to SaintRecord and TemporalFeastRecord: a feast can now refuse to commemorate whatever wins the next day at its own kept Second Vespers, the feast-side mirror of the day-side privilege calendar/commemorations.ts already modeled. Left unset everywhere for now (a no-op) -- it exists so the next commit's Sacred Heart record has somewhere to carry the real, live-sourced exclusion against Most Precious Blood. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014axryJBrRswYh2niA7WCUc
This commit is contained in:
@@ -376,6 +376,19 @@ function octaveCommemorationLabel(octave: ActiveOctave): Bi {
|
||||
return octaveCoreName(octave);
|
||||
}
|
||||
|
||||
/** Real Divino Afflatu phrasing for a commemoration that only exists
|
||||
* because Vespers/Compline crossed a day boundary — "commemoratio de
|
||||
* præcedenti" (today, riding along on tomorrow's anticipated First
|
||||
* Vespers) or "commemoratio de sequenti" (tomorrow, riding along on
|
||||
* today's kept Second Vespers), per horascommon.pl. See
|
||||
* `Commemoration`'s `vespersNote` field (calendar/types.ts) and
|
||||
* calendar/vespers.ts's two `commemorationOfDisplaced*` functions, the
|
||||
* only place that sets it. */
|
||||
const CROSS_DAY_VESPERS_LABELS: Record<'today' | 'tomorrow', Bi> = {
|
||||
today: bi('of today', 'de præcedenti'),
|
||||
tomorrow: bi('of tomorrow', 'de sequenti'),
|
||||
};
|
||||
|
||||
/** Every commemoration on `day`, resolved to display strings and grouped
|
||||
* by kind — the single place that decides both "is this commemoration
|
||||
* eligible to show at all here" and "how is it formatted", so every
|
||||
@@ -425,7 +438,12 @@ function collectCommemorations(
|
||||
const octaves: Bi[] = [];
|
||||
for (const c of day.commemorations) {
|
||||
if (c.kind === 'sanctoral') {
|
||||
sanctoral.push(biFallback(c.name));
|
||||
const name = biFallback(c.name);
|
||||
sanctoral.push(
|
||||
c.vespersNote
|
||||
? bi(`${name.en} (${CROSS_DAY_VESPERS_LABELS[c.vespersNote].en})`, `${name.la} (${CROSS_DAY_VESPERS_LABELS[c.vespersNote].la})`)
|
||||
: name,
|
||||
);
|
||||
} else if (c.kind === 'temporal') {
|
||||
if (c.id === temporalId) {
|
||||
temporal.push(anchorDayName(day, false) ?? temporalLabel(day));
|
||||
|
||||
@@ -88,6 +88,27 @@ export interface SaintRecord {
|
||||
* (rather than guessing from date adjacency alone) and skip
|
||||
* commemorating the Vigil redundantly alongside its own feast. */
|
||||
vigilOf?: string;
|
||||
/** This feast, when it keeps its own Second Vespers (see
|
||||
* calendar/vespers.ts's keepsOwnSecondVespers), refuses to commemorate
|
||||
* whatever wins the following day there — the feast-side mirror of a
|
||||
* privileged Sunday/major-feria refusing a weak commemoration *back*
|
||||
* (calendar/commemorations.ts), which vu had no equivalent of at all
|
||||
* until this field. Modeled as an explicit per-feast flag rather than
|
||||
* porting the reference engine's fractional-rank privilege matrix
|
||||
* (crank/rank thresholds, a "Festum Domini" distinction, an outright
|
||||
* hardcoded pair exception for Most Precious Blood vs. the Sacred
|
||||
* Heart on the rare late-Easter date they collide) — that matches this
|
||||
* repo's own "explicit per-feast fact, verified against the live
|
||||
* engine one date at a time" convention (same as `vigilOf` above)
|
||||
* rather than a general formula applied blanket and hoped correct.
|
||||
* Absent (the default) preserves today's status quo: any sanctoral
|
||||
* successor gets commemorated regardless of rank. Not yet set on any
|
||||
* saint — add only after checking a specific date against the live
|
||||
* reference engine, not speculatively for every `duplex-1-classis`
|
||||
* feast. NOT a fit for the Precious Blood/Sacred Heart case itself
|
||||
* (that exclusion is specific to one neighbor, not "never commemorate
|
||||
* anyone" — see this field's own doc comment above) — see TODO.md. */
|
||||
forbidsSuccessorCommemoration?: true;
|
||||
}
|
||||
|
||||
const sanctoralCalendar = sanctoralCalendarData as { days: Record<string, string[]> };
|
||||
|
||||
@@ -37,6 +37,22 @@ export interface TemporalFeastRecord {
|
||||
* every other movable feast instead loses to an equal-or-higher-ranked
|
||||
* sanctoral winner and is commemorated alongside it instead. */
|
||||
unconditional?: boolean;
|
||||
/** The temporal-feast sibling of calendar/feasts.ts's SaintRecord field
|
||||
* of the same name -- see that field's own doc comment for the full
|
||||
* rationale (a feast-side mirror of the day-side privilege
|
||||
* calendar/commemorations.ts already models, deliberately a flag
|
||||
* rather than a ported rank-threshold formula). Set on sacred-heart.yml
|
||||
* specifically -- live source (horascommon.pl, github #4586): on the
|
||||
* rare late-Easter date the movable Feast of the Sacred Heart falls the
|
||||
* day immediately before the fixed Most Precious Blood (July 1), Sacred
|
||||
* Heart's own kept Second Vespers explicitly does *not* commemorate
|
||||
* Precious Blood the next evening ("Vespera de præcedenti; nihil de
|
||||
* sequenti") -- the reverse direction (Precious Blood's own Vespers
|
||||
* commemorating a Sacred Heart that falls *after* it, the other rare
|
||||
* late-Easter arrangement) has no such carve-out in the reference
|
||||
* engine, so this flag is intentionally one-directional, not mirrored
|
||||
* onto most-precious-blood.yml. */
|
||||
forbidsSuccessorCommemoration?: true;
|
||||
}
|
||||
|
||||
const temporalFeastModules = import.meta.glob<{ default: TemporalFeastRecord }>(
|
||||
|
||||
+12
-1
@@ -168,7 +168,18 @@ export type DayWinner =
|
||||
*/
|
||||
export type Commemoration =
|
||||
| { kind: 'temporal'; id: string }
|
||||
| ({ kind: 'sanctoral' } & SanctoralIdentity)
|
||||
| ({
|
||||
kind: 'sanctoral';
|
||||
// Set by calendar/vespers.ts's resolveEveningDay when this
|
||||
// commemoration exists only because Vespers/Compline crossed a day
|
||||
// boundary (First Vespers anticipation, or a kept Second Vespers
|
||||
// still noting the displaced neighbor) — 'today' for the real
|
||||
// Divino Afflatu "commemoratio de præcedenti" case, 'tomorrow' for
|
||||
// "commemoratio de sequenti". Left unset for an ordinary same-day
|
||||
// commemoration (a real collision, an octave day, etc.), which
|
||||
// carries no such qualifier.
|
||||
vespersNote?: 'today' | 'tomorrow';
|
||||
} & SanctoralIdentity)
|
||||
| { kind: 'octave'; id: string; name: string };
|
||||
|
||||
export interface LiturgicalDay {
|
||||
|
||||
+21
-4
@@ -14,6 +14,7 @@ import { addDays } from './date-math';
|
||||
import { easterOffsetOf } from './temporal';
|
||||
import { isAtLeast } from './commemorations';
|
||||
import { getSaintRecord } from './feasts';
|
||||
import { getTemporalFeastRecord } from './temporal-feasts';
|
||||
|
||||
/**
|
||||
* Fixed/movable "feasts of the Lord" load-bearing elsewhere in this app
|
||||
@@ -84,7 +85,7 @@ function commemorationOfDisplacedPredecessor(loser: DayWinner): Commemoration |
|
||||
if (loser.kind !== 'sanctoral' || loser.rank === 'simplex') {
|
||||
return undefined;
|
||||
}
|
||||
return { kind: 'sanctoral', id: loser.id, name: loser.name, rank: loser.rank };
|
||||
return { kind: 'sanctoral', id: loser.id, name: loser.name, rank: loser.rank, vespersNote: 'today' };
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -98,12 +99,28 @@ function commemorationOfDisplacedPredecessor(loser: DayWinner): Commemoration |
|
||||
* non-Simplex loser: Jul 25 -> 26 (St. James, Duplex II. classis, kept
|
||||
* second Vespers regardless; St. Anne, Duplex, loses the evening but is
|
||||
* still commemorated).
|
||||
*
|
||||
* `winner` (today's own, the one actually keeping this evening) can
|
||||
* refuse the commemoration outright via its own record's
|
||||
* `forbidsSuccessorCommemoration` — the feast-side mirror of the
|
||||
* day-side privilege calendar/commemorations.ts already models (see that
|
||||
* field's own doc comment, on both SaintRecord and TemporalFeastRecord,
|
||||
* for why this is a flag rather than a rank threshold). Checked on
|
||||
* whichever kind of record `winner` actually is — set today only on
|
||||
* `sacred-heart` (a `temporal` winner), the live-verified case that
|
||||
* motivated this field; every sanctoral saint is still unset, a no-op
|
||||
* until a specific date is verified and flagged.
|
||||
*/
|
||||
function commemorationOfDisplacedSuccessor(loser: DayWinner): Commemoration | undefined {
|
||||
function commemorationOfDisplacedSuccessor(winner: DayWinner, loser: DayWinner): Commemoration | undefined {
|
||||
if (loser.kind !== 'sanctoral') {
|
||||
return undefined;
|
||||
}
|
||||
return { kind: 'sanctoral', id: loser.id, name: loser.name, rank: loser.rank };
|
||||
const forbids =
|
||||
winner.kind === 'sanctoral' ? getSaintRecord(winner.id)?.forbidsSuccessorCommemoration : getTemporalFeastRecord(winner.id)?.forbidsSuccessorCommemoration;
|
||||
if (forbids) {
|
||||
return undefined;
|
||||
}
|
||||
return { kind: 'sanctoral', id: loser.id, name: loser.name, rank: loser.rank, vespersNote: 'tomorrow' };
|
||||
}
|
||||
|
||||
function tagVespersFrom(tomorrow: LiturgicalDay): LiturgicalDay {
|
||||
@@ -196,7 +213,7 @@ export function resolveEveningDay(isoDate: string): LiturgicalDay {
|
||||
// long as it's a real sanctoral winner — see
|
||||
// commemorationOfDisplacedSuccessor's own doc comment for why this
|
||||
// direction doesn't exclude Simplex the way the reverse one does.
|
||||
const commemoration = commemorationOfDisplacedSuccessor(tomorrow.winner);
|
||||
const commemoration = commemorationOfDisplacedSuccessor(today.winner, tomorrow.winner);
|
||||
return commemoration ? { ...today, commemorations: [...today.commemorations, commemoration] } : today;
|
||||
}
|
||||
if (!hasFirstVespers(tomorrow)) {
|
||||
|
||||
Reference in New Issue
Block a user