Commit Graph

19 Commits

Author SHA1 Message Date
will a339068f81 Show a transferred saint's status in the day label
Deploy / deploy (push) Successful in 1m32s
getDayLabel now surfaces the transfer signal added in the previous
commit: a transferred-in winner gets "(Rank, transferred)" instead of
just "(Rank)"; a transferred-in commemoration gets "(transferred)"
appended to its bare name; and the date a saint transferred away from
gets a trailing "St. X (transferred away)" note so a reader isn't left
wondering why an expected saint is missing that day. Deliberately worded
differently on the two sides (plain "transferred" reads as "arrived via
transfer" next to a rank parenthetical; "transferred away" spells out the
direction on the origin date, where there's no rank alongside it to
anchor that reading) and deliberately omits a landing date on the away
note, per direct instruction, since a real landing can chain further than
the single adjacent day resolveDay actually checks (an unmodeled case,
e.g. a transfer running into Holy Week).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013g7AsSvMD9oazR17f3BxLz
2026-08-31 08:53:12 -04:00
will 514300821a Bulk-author nameLa for all 299 remaining saints, close bilingual header backlog
Deploy / deploy (push) Successful in 1m31s
Authors a real Latin nameLa for every saint in src/data/calendar/saints/
except the deliberate example-confessor placeholder (standard
ecclesiastical Latin forms, a lighter sourcing bar than this app's usual
reference-engine verification -- flagged for future spot-check). Fixes a
real mechanism bug found in the process: calendar/index.ts's sanctoral
collision-resolution branch was dropping nameLa for any saint that won
after beating another same-day candidate. Updates ~180 pre-existing test
fixtures across the calendar suite for the new field.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EwsZQMjALCvy7u9tFDWmuQ
2026-08-31 06:44:10 -04:00
will a6261f044a Add nameLa mechanism for bilingual header, proof set of 11 feasts + St. Lawrence
Deploy / deploy (push) Successful in 1m32s
Threads an optional nameLa field through SanctoralIdentity/SaintRecord/
TemporalFeastRecord/ActiveOctave so day-label.ts can render real Latin
proper names instead of always falling back to English. Authors the
proof set: all 11 named temporal feasts plus St. Lawrence (chosen to
exercise the octave-headline and octave-commemoration code paths too).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EwsZQMjALCvy7u9tFDWmuQ
2026-08-31 06:30:48 -04:00
will 5ae1f3a3e0 Fix Vespers status wrongly saying "Second Vespers" when a Sunday anticipates it
Deploy / deploy (push) Successful in 1m29s
getVespersStatusLabel detected First-Vespers anticipation only via
winner.vespersFrom, a tag only ever set for a sanctoral tomorrow-winner
(tagVespersFrom). When Vespers anticipates a Sunday that wins its own
day outright (a temporal winner, e.g. 2026-08-29 anticipating 2026-08-30
since the Decollation of St. John Baptist is below the duplex-2-classis
floor that would let it keep its own evening), the tag never gets set
and the status wrongly read "Second Vespers (of today)" even though the
whole page was showing tomorrow's identity.

Comparing the resolved day's date against the originally-requested date
detects anticipation uniformly regardless of winner kind, so this drops
the vespersFrom dependency entirely.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ME8QNPdEmHSbSx1r6VmDRG
2026-08-30 06:10:52 -04:00
will 95901ef56a Always list the day's actual winner first in getDayLabel
The anchor-day and plain-temporal-fallback branches were listing any
commemorated saint before the winning identity, so a lower-ranked saint
merely riding along as a commemoration (e.g. St. Andrew on Advent I, or
Ss. Tryphon/Respicius/Nympha on an ordinary Sunday) read as if it were
the day's own winner, with the actual winner's rank label dangling at
the end with no name attached. The winner should always lead.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ME8QNPdEmHSbSx1r6VmDRG
2026-08-30 06:10:14 -04:00
will 2f7b421558 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
2026-08-26 15:20:10 -04:00
will 63fd319509 Fix Vespers/Compline header to use evening day, not daytime day
Deploy / deploy (push) Successful in 1m19s
day-nav.ts's header always resolved via resolveDay(date) regardless of
selected hour, so viewing Vespers/Compline on a day whose evening
anticipates tomorrow's First Vespers (e.g. Aug 25, 2026: St. Louis's day,
but St. Zephyrinus claims First Vespers per calendar/vespers.ts) still
showed today's saint in the header while the hour's actual content had
already switched to tomorrow's. Now uses resolveEveningDay for those two
hours specifically.

Also adds an explicit "First Vespers (of tomorrow)" / "Second Vespers (of
today)" status line to the Vespers header, via new
getVespersStatusLabel(), so the anticipation/commemoration result is
visible rather than only inferable from which saint's name shows up.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014axryJBrRswYh2niA7WCUc
2026-08-26 07:02:21 -04:00
will e7bbc7c9f5 Add bilingual day-label headers with Latin weekday/season/ordinal vocabulary
Builds every day label as a {en, la} pair (weekdays, ranks, season/ordinal
phrasing authored in Latin; proper names without an authored Latin form
fall back to their English string) and merges the date+day-label into one
header in the day-nav bar.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-19 05:27:44 -04:00
will f43f030a6a Fix ordinary-Sunday precedence and unify getDayLabel's commemoration rendering
Two related fixes surfaced while chasing why St. Gregory Thaumaturgus
appeared to be misdated on Nov 18:

- calendar/commemorations.ts's `ordinary-sunday` case was transferring a
  Semiduplex (or lower) saint off the Sunday instead of commemorating it
  in place — an earlier, unverified guess. Live-verified against both
  Tridentine 1906 and Divino Afflatu 1954 (St. Gregory Thaumaturgus, St.
  Clement, St. Apollinaris, St. Thomas Becket, all real cases): the saint
  stays and is commemorated on the Sunday itself, same as Simplex, never
  pushed to the next open day. This is what was actually moving Gregory
  onto Nov 18 — not a data error. `applyChristmasOctaveSunday`'s own
  Dec 26-29 special case needed a matching adjustment (per its own
  documented intent, the displaced saint there is deliberately *not*
  commemorated in place, since he reappears in full on Dec 30 instead).

- getDayLabel had five branches, each hand-assembling its own
  filter/format logic for which commemorations to show — which is why
  the octave phrasing, the closing-day title, and a missing Sunday
  commemoration turned into three separate bugs earlier instead of one.
  Replaced with a single shared `collectCommemorations` used by every
  branch. This also exposed that the sanctoral-winner branch never
  showed any commemorated saint at all, and every other branch only
  showed the *first* one (`.find()`), silently dropping real collisions
  — both now show every commemorated saint, matching this app's own
  generous-commemoration design.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-18 10:59:50 -04:00
will 3f07d6e8d3 Fix octave day labels: correct commemoration phrasing, closing-day title, and privileged-season precedence
Deploy / deploy (push) Successful in 1m4s
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>
2026-08-18 08:49:51 -04:00
will 427ba6756a Show rank in the day label for every winner kind, not just sanctoral
Deploy / deploy (push) Successful in 1m3s
Rank display (added in 1fc4bd7) only fired for a plain sanctoral
winner. Every other case -- an active octave day, a named temporal
feast, a season anchor day (Easter, Trinity Sunday, Epiphany), or the
plain ordinal Sunday/feria fallback -- showed no rank at all, which is
what made today's octave day (Aug 18, day 4 of the Assumption's
octave) show no rank. Octave days use the octave's own already-computed
effective rank; anchor days get a fixed Duplex I Class (Ash Wednesday
excepted -- a privileged feria has no duplex-scale rank); the plain
fallback derives a Semiduplex/Feria label from the day's own
temporalCategory, since Sundays/ferias aren't on the duplex scale at
all.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-18 07:41:57 -04:00
will 1fc4bd7160 Fix octave-tie precedence, day-label commemorations, and rank display
A tie between an occurring saint's rank and an active octave now favors
the octave only on its own elevated closing day (live-verified: St.
Hyacinth vs. St. Lawrence's own Aug 17 closing day), not on an ordinary
octave day, where a tied saint still wins as before -- confirmed against
two already-tested counterexamples (St. Thomas of Canterbury, St.
Nicholas of Tolentino) that a blanket tie-flip would have broken.

getDayLabel previously dropped every commemoration whenever the day's
winner was a plain saint, and dropped every non-headline active octave
even when an octave itself won -- both fixed. Rank is now shown after
the day's own winner's name (previously not shown anywhere in the UI).

Also authored assumption-octave-day-3.yml, a real content gap (Aug 17,
day 3 of her octave) surfaced while fixing the above.
2026-08-18 06:11:37 -04:00
will a7901b3c61 Implement the resumed-post-Epiphany-Sunday temporal-id algorithm
Deploy / deploy (push) Successful in 1m6s
Post-Pentecost overflow years (early Easter, fewer than 6 Sundays after
Epiphany fit before Septuagesima) previously clamped every excess Sunday
to post-pentecost-24 instead of resuming the skipped post-Epiphany
Sundays' own content, per the traditional rubric. Ported directly from
the reference engine's own DivinumOfficium/Date.pm getweek().

Also adds the Epiphany VI Saturday-before-Septuagesima commemoration
(only when Epiphany V was the last Sunday to actually occur), and fixes
day-label.ts's on-screen display to track the same resumed-Sunday
content instead of a raw elapsed-week count — including a correction so
the fixed final Sunday of the year always reads "23rd Sunday after
Trinity", not a per-year-varying number.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-16 20:07:04 -04:00
will 397f31f4fc Implement octave-vs-octave precedence, and fix a real day-label bug it surfaced
Deploy / deploy (push) Successful in 52s
When more than one octave is active on a day with no temporal standing of
its own (St. Lawrence's and the Assumption's genuinely overlap every Aug
16-17), which one governs the day's content/label is now decided by rank
comparison instead of "whichever started first" (an accident of
insertion order). Per direct instruction:

- Highest effective rank wins outright; every other active octave still
  gets commemorated, same as a single octave already outranking a weak
  rival saint.
- A rank tie goes to whichever octave started more recently -- day 1 of
  a new octave needs to be fully present, the whole point of it
  starting. No real tied-rank case exists yet to verify this
  empirically, unlike everything else here -- documented in TODO.md as
  a stated decision, not a live finding.

New OctaveConfig.closingDayRank (default duplex): an octave's own final
day ("in Octava") is elevated above its ordinary in-between rank --
live-verified as a real, general pattern (both St. Lawrence's Aug 17 and
the Assumption's Aug 22 show as Duplex, above their otherwise-Semiduplex
ordinary days), not a one-off. This elevation is *why* Lawrence's octave
beats the Assumption's on their one real overlap day despite the
Assumption being the far higher-ranked feast overall. Feeds both the
existing rival-saint threshold and the new octave-vs-octave comparison.

calendar/octaves.ts gains resolveActiveOctave (+ pickWinningOctave, the
comparison itself factored out for direct unit testing against synthetic
data, since no real tied-rank overlap exists to test against yet).
hours/resolve-common.ts's resolveOfficeWinner and calendar/day-label.ts
both now call it instead of each keeping their own "activeOctavesFor(...)
[0]" logic.

That consolidation surfaced a real, independent bug: getDayLabel never
checked temporalCategory at all before choosing an octave name, unlike
resolveOfficeWinner -- found while testing the real Aug 16 overlap (a
Sunday that year, where the temporal Sunday has standing and should win
outright). Live-verified counterexample: the Christmas Octave's own
stack (Dec 30) was wrongly labeled "3rd Day within the Octave of The Holy
Innocents" instead of the correct plain temporal label -- the real title
never names any of the four stacked octaves there. Fixed by sharing the
exact same ordinary-feria gate resolveOfficeWinner already had, so the
two can no longer disagree.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-12 09:38:06 -04:00
will 5f32a3751c Model St. Clare and surface active octaves in the day label
Deploy / deploy (push) Successful in 52s
Today (Aug 12, within St. Lawrence's octave) was resolving as a plain
ferial day: St. Clare wasn't modeled in the calendar at all, and even
once an octave commemoration exists, getDayLabel only ever looked for a
sanctoral commemoration, never an octave one, so the label fell all the
way through to the generic ordinal week label.

Adds St. Clare (Simplex under Monastic 1617, correctly losing to
Lawrence's octave and only commemorated -- live-verified against three
clean years). Extends getDayLabel to show the active octave itself as
the day's primary identity when nothing else has won outright (e.g.
"3rd Day within the Octave of St. Lawrence, Martyr -- St. Clare,
Virgin"), matching what the live reference engine actually titles these
days, rather than falling back to an anonymous ordinal week label.

Also fixes a regression this surfaced: Trinity Sunday is technically
also day 8 of Pentecost's own octave, and the naive octave-first check
was shadowing its own anchor-day name ("8th Day within the Octave of
Pentecost" instead of "Trinity Sunday"). A season's own named anchor day
now always outranks an active octave label.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-12 06:39:48 -04:00
will 747a078580 calendar: model Our Lady's Saturday as a real occurrence outcome
Deploy / deploy (push) Successful in 49s
On a free Saturday (nothing privileged already claims it, no active
octave), the day's own identity is now "Our Lady's Saturday" rather
than an anonymous ordinary-feria -- mirrors privileged-feria-minor
exactly, per direct instruction: Semiduplex-or-higher still wins
outright (Marian Saturday doesn't apply at all); below that (Simplex,
and Vigil, both under semiduplex on the FeastClass scale) loses and is
commemorated instead. Layered additively after decideOccurrence, same
shape as applyOctaves -- never touches decideOccurrence's own rules.

Needed a real winner identity (not just the plain temporal feria id)
so day-label/antiphon/collect sourcing can recognize it -- added a
temporal-feasts record purely for that, and taught getDayLabel to
check it for any named temporal winner (a small, free improvement for
Christmas/Pentecost's own labels too, previously unhandled).

hours: build the duplex-majus+ Lauds psalmody override (per-feast)

lauds-psalmody no longer unconditionally uses the plain weekday
default: a duplex-majus-or-higher sanctoral winner, or Our Lady's
Saturday (unconditional, no rank threshold -- it isn't competing with
the weekday default the way a saint is), now substitutes its own
proper psalm groups/antiphons/canticle. Per-feast, not per-Common, per
direct instruction, even though most duplex-majus+ saints across the
year don't have one authored yet and fall back to the plain weekday
default until they do.

Content for the worked example (St. Lawrence's own day) surfaced a
real bug while sourcing it: st-lawrence-antiphon.yml had been read
from the Roman/secular rite's own [Ant 1], not Monastic 1617's actual
Benedictus antiphon for that day -- corrected from a fresh live query.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-11 14:02:55 -04:00
will 37ac31c3f2 calendar: occurrence engine v2 — real rank thresholds, transfers, collisions
Deploy / deploy (push) Successful in 39s
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.
2026-08-10 12:00:01 -04:00
will ad87d8dd18 calendar: move Sunday-governance arithmetic from day-label.ts into temporal.ts
Deploy / deploy (push) Successful in 37s
sundayOnOrBefore and firstSundayStrictlyAfter are calendar facts ("which
Sunday does this feria belong to"), not presentation logic — day-label.ts
was the only consumer so far, but a future temporal-propers content
resolver (PropersRef's still-unused source: 'temporal' branch) will need
the exact same arithmetic, just keyed for content ids instead of display
strings. Moving it now, while the shape is still small and well
understood, rather than duplicating it later.

day-label.ts keeps the actual display config (which anchor, "Trinity" vs
"Pentecost" wording) — that part stays presentation-only on purpose, since
it's also where a future counting-convention setting would hook in.

No behavior change: 88/88 tests still pass.
2026-08-10 07:30:21 -04:00
will 226fff2ce6 ui: show the "day being celebrated" label
Deploy / deploy (push) Successful in 38s
Adds a real label under each hour's heading — "Monday in the 10th week
after Trinity", "The 1st Sunday of Advent" — computed from whichever
LiturgicalDay the hour actually resolved against, which can differ from
the nav date for Compline's evening anticipation.

calendar/day-label.ts combines two things: an ordinal week-within-season
label (pure date arithmetic on the season anchors from calendar/temporal.ts
and calendar/easter.ts — Trinity-counted, not Divinum Officium's own
Pentecost-counted convention; meant to become configurable later via the
same day->id indirection already used for the sanctoral calendar, not
hardcoded forever), and a feast name from calendar/commemorations.ts's
occurrence decision — shown alone if the feast displaces the day outright,
prefixed onto the temporal label if merely commemorated, or omitted
entirely if nothing's occurring.
2026-08-10 07:07:10 -04:00