Track a transferred saint's origin date on DayWinner and Commemoration

Adds transferredFrom to both DayWinner's sanctoral variant and
Commemoration's sanctoral variant, set in calendar/index.ts's
applyIncomingTransfer whenever a transferred-in candidate actually lands
(wins outright) or ends up merely commemorated (lost a collision, or fell
below the landing day's own threshold) — previously this signal was
discarded after resolveDay used it once to decide the winner, so nothing
downstream could tell a transferred feast apart from a native one.

Also adds transferredAway to LiturgicalDay, set from a date's own native
occurrence when its candidate couldn't be kept there at all, so a UI can
note "this office moved elsewhere" without asserting a specific landing
date (which a later, unmodeled multi-hop chain — e.g. into Holy Week —
could get wrong).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013g7AsSvMD9oazR17f3BxLz
This commit is contained in:
2026-08-31 08:53:02 -04:00
parent cb60039ab2
commit db9c844caf
6 changed files with 74 additions and 11 deletions
+26
View File
@@ -161,6 +161,12 @@ export type DayWinner =
// First Vespers is being anticipated this evening (i.e. this feast
// belongs to *tomorrow*, but is winning tonight's Vespers/Compline).
vespersFrom?: 'firstVespersOfTomorrow';
// Set by calendar/index.ts's resolveDay/applyIncomingTransfer when
// this winner is only here because its own native date (an adjacent
// ISO date) couldn't hold it (calendar/commemorations.ts's
// `decideOccurrence` `transfer` signal) — the ISO date it transferred
// *from*. Absent for a feast winning on its own native date.
transferredFrom?: string;
} & SanctoralIdentity);
/**
@@ -184,6 +190,14 @@ export type Commemoration =
// commemoration (a real collision, an octave day, etc.), which
// carries no such qualifier.
vespersNote?: 'today' | 'tomorrow';
// Set by calendar/index.ts's applyIncomingTransfer, same meaning as
// `DayWinner`'s own `transferredFrom` — this saint is only
// commemorated here (rather than absent) because a transfer landed
// on this date and lost a collision, or fell below the landing
// day's own outright-winning threshold, not because it's natively
// due for commemoration here. Absent for an ordinary same-day
// commemoration.
transferredFrom?: string;
} & SanctoralIdentity)
| { kind: 'octave'; id: string; name: string; nameLa?: string };
@@ -199,4 +213,16 @@ export interface LiturgicalDay {
winner: DayWinner;
/** Everything else commemorated alongside the winner — see the doc comment on Commemoration. */
commemorations: Commemoration[];
/** Set when this date's own native sanctoral candidate couldn't be kept
* here at all (calendar/commemorations.ts's `decideOccurrence` `transfer`
* signal) and moved to a later/earlier date instead. No landing date is
* recorded here — `resolveDay` only ever checks the immediate adjacent
* date, but a real landing can chain further than that (an unmodeled
* case noted in `applyIncomingTransfer`, e.g. a transfer running into
* Holy Week), so naming a specific "to" date risked asserting a wrong
* one; this exists purely so the display can say "not an omission, this
* office moved elsewhere" without claiming to know where. Distinct from
* — and not implied by — `Commemoration`, since a transferred-away
* candidate gets no commemoration on its own native date at all. */
transferredAway?: { candidate: SanctoralIdentity };
}