Three related day-label bugs, all found via Aug 19's mislabeled Assumption
octave commemoration:
- A sanctoral winner that displaces the only active octave now shows the
octave's real "Nth Day within the Octave of X" phrasing (no rank, since
it's riding along under the winning feast), not a bare feast name.
- An octave's own closing day now titles itself "Octave of X", matching
the real DO's "in octava" vs. "infra octavam" distinction, instead of
"8th Day within the Octave of X".
- A sufficiently-ranked octave can now outrank a privileged temporal
season (e.g. the Immaculate Conception's octave outright winning several
of its days against Advent, live-verified against Divino Afflatu 1954),
via a new shared octaveGoverningPrivilegedDay helper used by both the
day label and the actual office-content resolver. Deliberately excludes
Christmastide, whose own stacked octaves are structurally already that
season's temporal content rather than a foreign add-on.
Updates a Matins test fixture that had unknowingly relied on the
Dec 15 bug (Advent ferial + a lone commemorated saint) and splits it into
a clean ferial case plus a dedicated commemorated-saint case.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Found while starting the December sanctoral pull: Advent's plain
(non-Ember) weekday ferias have real standing in the live Monastic 1617
engine that the existing ordinary-feria category couldn't represent --
a Simplex saint (St. Bibiana, Dec 2) is merely commemorated there rather
than winning outright, while a Semiduplex+ saint (St. Nicholas, Dec 6)
wins with the feria itself commemorated back. Adds a third, weaker
privileged-feria-minor tier (Semiduplex+ threshold, vs. privileged-
feria's Duplex+) rather than reusing either existing tier, mirroring how
privileged-feria/privileged-feria-major were split in March. Lent's own
ordinary ferias likely share this property but are left as-is, unverified
this session.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
A plain Duplex saint was losing to any privileged feria (Ember days
included) since the occurrence engine only let duplex-1-classis+ get
even a commemoration there. Verified this was wrong: St. Gregory the
Great, plain Duplex, outright wins against a Lenten Ember Saturday in
the real Monastic 1617 engine.
Splits TemporalCategory's single privileged-feria into privileged-feria
(the lesser tier — Lent's Ember days, verified — now behaves like an
ordinary Sunday: Duplex+ wins outright) and privileged-feria-major (Ash
Wednesday, Holy Week, the Easter Octave, verified via St. Mark only ever
being commemorated there, never winning — behaves like privileged-
Sunday). Everywhere else the old single category was ambiguous
(Pentecost's own Ember days/Vigil/Octave, the Vigil of Christmas)
defaults to the stricter major tier pending verification.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Corrects and completes the occurrence rules, based on a design discussion
plus one concrete data point: St. Anthony Abbot (plain Duplex) was found
outright winning against an ordinary Sunday in the real Monastic 1617
engine, which the old duplex-1-classis-only threshold got wrong.
- FeastClass gains `vigil`, inserted between `simplex` and `semiduplex` —
one ordering that correctly serves both "does this win against a Sunday"
(vigil behaves like simplex there) and "which of two saints wins a
landing-day collision" (vigil beats simplex, loses to semiduplex).
- LiturgicalDay.occurring (a flat OccurringFeast[] that could only ever
express a losing *sanctoral* candidate) is replaced by `winner:
DayWinner` + `commemorations: Commemoration[]` — a discriminated list
that can hold the temporal day itself, one or more sanctoral entries, or
(not built yet, but the shape already accommodates it) a future octave
kind.
- commemorations.ts: ordinary Sundays let Duplex+ win outright (Sunday
commemorated in return), Semiduplex/Vigil transfer elsewhere (too
substantial a feast to cheapen with a bare commemoration), Simplex stays
and is commemorated. Privileged Sundays never displace; Duplex-majus+
commemorated, everything else transfers.
- collision.ts (new): resolves two sanctoral candidates wanting the same
day (a transfer landing on an already-occupied day, or two native
saints sharing a date) — duplex > semiduplex > vigil > simplex, loser
always commemorated, ties favor the native occupant.
- temporal-id.ts (new): maps any date to one of the 52 real Sunday-collect
ids from the previous commit, so a temporal winner/commemoration can
actually be looked up, not just labeled "temporal" in the abstract.
- index.ts's resolveDay orchestrates all of it, including the actual
Monday/Saturday transfer mechanism. Landing on a privileged feria (the
concrete case: Holy Week, right after Palm Sunday) is explicitly
deferred rather than guessed at — it needs its own Easter-keyed lookup
table, the same way the reference engine handles it.
Added the Vigil of St. Lawrence (Aug 9) as real content specifically to
exercise the backward-transfer rule end-to-end: Aug 9, 2026 is a Sunday,
so the vigil transfers cleanly back to Saturday, verified by a new
integration test alongside the unit-level rule and collision tests.
Replaces the FeastRank placeholder with a real ordered FeastClass (the old
pre-1955 six-level scale) and a new TemporalCategory (privileged/ordinary
Sunday/feria). calendar/commemorations.ts's decideOccurrence encodes the
actual precedence rules as explicit, commented branches per category
rather than the reference engine's own opaque numeric weights — this app
targets one ruleset, so the readability trade-off is worth it. Category
membership (data/calendar/temporal-categories.yml) is a best-effort
reconstruction of the pre-1955 tradition, not sourced from a primary text;
expect corrections.
calendar/feasts.ts resolves real sanctoral candidates, and resolveDay()
wires it all together — occurring/temporalCategory are no longer stubs.
isDoubleOrHigher (hours/antiphon.ts) is now a real comparison instead of
a hardcoded false.
Seeded 7 real saints/feasts, each verified directly against Divinum
Officium's own rank data (not reconstructed from memory) as a small,
growable start: St. Lawrence, the Assumption, St. Augustine, the Nativity
of the BVM, St. Michael, All Saints, the Immaculate Conception.
Client-side-first PWA (Vite/TS, no backend) per the approved plan: day-
navigable shell listing all 8 hours, Prime fully resolves via the
calendar -> psalter -> ordo pipeline, the other 7 hours are registered
but flagged not-implemented. Sanctoral/temporal calendar data uses a
day -> id indirection layer (saints, easter-offsets, fixed-date-calendar)
so reassigning a feast to a different day is a data edit, not a code
change. Docker (Caddy-serving-static) + Gitea CI workflow scaffolded to
match the eec/drip/bookshop operational pattern.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>