Commit Graph

169 Commits

Author SHA1 Message Date
will 0b8529e04b Add June sanctoral content: 6 saints
Deploy / deploy (push) Successful in 1m4s
Continues the full-year sanctoral import: St. Angela Merici, St. Francis
Caracciolo, St. Boniface of Mainz (Bishop and Martyr, Apostle of Germany --
distinct from the earlier, unrelated St. Boniface Martyr already on May 14),
St. Norbert, St. Margaret of Scotland, and St. Aloysius Gonzaga.

Two of the new fixed dates (June 1, June 4) fall on dates used by two
day-label tests pinned to specific Trinity Sunday years; moved those to
years/dates free of the new entries, with explanatory comments.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-17 16:23:05 -04:00
will b4e6ed6aa0 Add May sanctoral content: 10 saints plus the Queenship of the BVM
Deploy / deploy (push) Successful in 1m6s
Continues the full-year sanctoral import (see TODO.md/memory for Jan-Apr):
St. Pius V, St. Antoninus, St. Robert Bellarmine, St. John Baptist de la
Salle, St. Paschal Baylon, St. Venantius, St. Bernardine of Siena, St. John
Baptist de Rossi, St. Augustine of Canterbury, St. Mary Magdalene de Pazzi,
and the Queenship of the BVM (May 31, Duplex II Classis, instituted by Pius
XII's 1954 "Ad cæli Reginam" — full proper content, not a Common fallback).

Adding the Queenship to May 31 surfaces a real collision with Trinity
Sunday in years where Trinity falls on May 31 (2026): since this app
models Trinitytide Sundays as plain ordinary-sunday with no special
first-class standing, a Duplex+ sanctoral feast wins outright there per
the existing precedence rule. Updated the two tests pinned to that date
accordingly, with a third moved to a Trinity Sunday date (2023-06-04)
free of any sanctoral entry to keep testing the octave-vs-anchor-day
interaction in isolation.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-17 16:13:19 -04:00
will ce778d51e3 Add 6 more February saints/feasts previously never added
Deploy / deploy (push) Successful in 1m6s
Purification of the BVM (Feb 2), St. Andrew Corsini (Feb 4), St. John
of Matha (Feb 8), Apparition of the Immaculate BVM at Lourdes (Feb 11),
Ss. Seven Founders of the Servite Order (Feb 12), St. Gabriel of Our
Lady of Sorrows (Feb 27) -- all had real, complete Matins content in
the reference engine and no exclusion comment; simply never reached
during the earlier month-by-month sanctoral pulls. Continuing the same
sweep as January's St. Francis de Sales/St. Martina/St. John Bosco.

Test update (not a regression): St. Andrew Corsini transfers onto Feb 5
in years where Sexagesima falls on his own Feb 4, outranking St. Agatha
when it does -- february-sanctoral.test.ts's proof year for Agatha
moved from 2029 (now a collision year) to 2025 (clean).

npm test (365 tests) and tsc --noEmit both pass.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-17 15:26:00 -04:00
will 80f4f935be Add 4 more fixed Lord's/BVM feasts, and fix Our Lady of the Snows' Nocturn 3
Deploy / deploy (push) Successful in 1m6s
Finding of the Holy Cross (May 3), the Transfiguration (Aug 6), the
Exaltation of the Holy Cross (Sep 14), and Dedication of the Basilica of
the Most Holy Savior (Nov 9, the Lateran) were all excluded under the
same stale "fixed feast of the Lord, not modeled" note that turned out
wrong for Our Lady of the Snows. All four have real, complete content in
the reference engine. Presentation of the BVM (Nov 21) was excluded
under the equivalent "fixed BVM feast" note and is added the same way.

- finding-of-the-holy-cross: fully proprium, own collect/antiphon/Matins
  throughout; now correctly outranks
  ss-alexander-eventius-theodulus-and-juvenal, matching what that
  saint's own file already documented as the live reality.
- transfiguration: same proprium shape. Its subordinate saints (Ss.
  Xystus/Felicissimus/Agapitus) stay excluded -- blocked by the real,
  separate octave/subordination-rendering gap, not by an unmodeled
  Transfiguration.
- exaltation-of-the-holy-cross: reuses the Finding of the Holy Cross's
  content wholesale (ex Sancti/05-03) -- collect/antiphon via a cross-id
  `propers` pointer, Matins nocturn-readings duplicated (no cross-id
  lookup exists for that mechanism).
- dedication-of-the-basilica-of-the-most-holy-savior: reuses the
  existing Common-of-a-Dedication (collect-c8, benedictus-antiphon-
  common-of-a-dedication), real unique Nocturn 2 vita of her own. Her
  secondary, St. Theodore, stays excluded for the same
  octave/subordination reason as above.
- presentation-of-the-bvm: real unique collect of her own; her
  Benedictus antiphon is a substituted Common reference
  (truncate-and-add-alleluia edit specific to this feast), authored as
  her own file.

Also: our-lady-of-the-snows.yml was missing its own Nocturn 3 (she
defers to Commune/C11's own Gospel-incipit-plus-Bede-homily, same as
Presentation of the BVM) -- added.

Test updates (not regressions): three tests had fixed proof dates
colliding with the newly-added feasts, updated to reflect the new,
correct behavior. npm test (365 tests) and tsc --noEmit both pass.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-17 14:48:50 -04:00
will 58816a7d15 Add December Matins readings for SanctiM-track saints, completing the pass
Deploy / deploy (push) Successful in 1m7s
13 of December's 15 SanctiM-track saints authored (st-bibiana, st-nicholas,
st-ambrose, immaculate-conception, st-damasus, st-lucy, st-thomas-apostle,
st-stephen-protomartyr, st-john-apostle, holy-innocents, st-thomas-becket,
st-silvester-i, st-eusebius-of-vercelli); st-barbara and st-melchiades left
unauthored as Common-only. This is the twelfth and final month of the
SanctiM (Monastic 1617)-track saints' Matins readings pass — 129 saints
authored across the whole year.

Adding St. Eusebius of Vercelli's content surfaces a 3rd Matins lesson on
the existing Advent III Tuesday proof date (Dec 15 falls under his
commemoration); updated matins.test.ts accordingly — the "commemorations
get their own reading" design working as intended, not a regression.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-17 07:14:31 -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 974c156255 Add Matins tests; update tests that assumed it was still a stub
tests/hours/matins.test.ts covers both branches (ferial, Sunday)
end-to-end through the real resolveOrdo mechanism against two clean,
live-verified dates — psalm/canticle counts, Te Deum presence, reading
citations/statuses, Gospel flagging and its Common-fallback exclusion,
and responsory matching.

tests/hours/prime.test.ts and tests/ui/shell.test.ts both had assertions
that Matins was still pending — replaced with assertions that it now
renders real content, mirroring the equivalent Compline test.
2026-08-14 11:40:51 -04:00
will 53582f1ded Add per-feast Vespers office overrides for all 30 duplex-majus+ ids
Same eligibility rule as Lauds' psalmody override (a duplex-majus+
saint, or a named temporal feast), factored out to
resolve-common.ts's getOfficeOverrideId since Vespers has no
psalmody-override table of its own to piggyback eligibility on.

Authored via one live command=prayVespera query per id, reusing the
same clean per-saint dates already established for each one's Lauds
office-bundle override. The capitulum turned out to be byte-identical
between Lauds and Vespers for every one of the 30 ids checked, a real
Monastic-rite fact rather than a coincidence — so no
vespers-capitulum-<id>.yml files exist at all; the chapter falls
through to the already-authored lauds-capitulum-<id> directly instead
of duplicating identical content.

St. Scholastica's Vespers responsory, hymn, and versicle needed a hand
translation (same gap she has at Lauds/Terce/Sext/None — the reference
engine has no English for any of her proper content). Two more
one-line English gaps in the shared Immaculate Conception/Nativity of
the BVM responsory were filled with the standard Douay-Rheims/Ave
Maria wording.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-13 21:31:59 -04:00
will 4d88568c42 Add Vespers' ferial Preces, sourced from Tridentine 1906/1910
Monastic 1617 has no ferial Preces at Vespers, same gap Lauds already
had — the fuller "Preces feriales Vespera" is byte-for-byte the Lauds
ferial Preces with Psalm 129 swapped for Psalm 50 in the source, live-
verified against the same clean Advent feria already used for Lauds.
The short Sunday/feast form turns out to be identical at both hours too,
so it's reused directly rather than duplicated.

Factors the shared ferial-or-vigil predicate out of hours/lauds.ts into
resolve-common.ts's isFerialOrVigil so both hours' -preces cases resolve
it the same way instead of keeping two copies in sync by hand.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-13 21:30:01 -04:00
will f14d00bd73 Port seasonal office content to Lauds, closing the same pre-existing gap
Deploy / deploy (push) Successful in 56s
Lauds had the identical issue Vespers started with: resolveOffice only
ever rendered the plain weekday chapter/responsory/hymn/versicle,
year-round, with no Advent/Lent/Passiontide/Paschaltide override —
same source file (Major Special.txt) as Vespers', just never wired up.
Ports the same seasonalOfficeSuffix-based precedence (per-feast/octave
override -> season -> weekday default) and the matching seasonal
content, transcribed the same way as Vespers' own (Monastic hymn
variants applied from the source's substitution notation against each
base Roman text). Paschaltide's chapter is byte-identical to Vespers'
own — the source aliases "[Pasch Vespera] = @:Pasch Laudes" — so both
hours' data files intentionally carry the same text rather than one
importing the other's id.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-13 20:15:29 -04:00
will de83786793 Build the Vespers ordo: mechanism, weekday content, seasonal content
Wires resolveOrdo('vespers', ...) end to end: opening versicle,
weekday psalmody (4 groups, handling the joined Ps 115+116 and the
split Ps 138/143 via a PsalmRef-like shape), the chapter/responsory/
hymn/versicle office bundle, Magnificat, day-collects — reusing
resolveEveningDay so First/Second Vespers precedence and the cross-day
commemoration (both added in the previous commit) apply automatically
— and a closing that reuses Lauds' own conclusio, since no distinct
"Vespera" version of that exact text turned up in the source.

getMagnificatAntiphon mirrors getBenedictusAntiphon's saint/temporal-id
lookup order, but also falls back to real content Benedictus has no
equivalent of: the classic ferial set that quotes the Magnificat's own
text in sequence across the week, with the correct Monastic-rubric
variant picked for Tue/Thu/Fri/Sat (two of those needed a fresh
translation where the Monastic Latin diverges from the given English
in content, not just wording).

Also includes Vespers' own seasonal content (Advent/Lent/Passiontide/
Paschaltide chapter+responsory+hymn+versicle), transcribed from the
same source blocks as the weekday content, with the Monastic-specific
hymn variants hand-applied from the source's substitution notation
against each base Roman text (one transcription slip caught and fixed
mid-pass: the Passiontide hymn's closing lines had been copied from
the Roman text instead of the Monastic one actually being
transcribed). Passiontide's own responsory/hymn correctly omit the
Gloria Patri, matching real rubric and the source data as given.

All weekday/seasonal content marked `draft`, not `verified` — read
directly from the reference source, not cross-checked against a live
rendering per day the way the psalmody was.

Six other hours' resolvePart switches needed the three new HourPart
kinds ('vespers-psalmody', 'vespers-office', 'magnificat') added to
their exhaustive not-used lists so they still typecheck; three
pre-existing tests assumed Vespers was still "coming soon" and are
updated to exercise Matins (still the genuine stub) instead.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-13 20:14:19 -04:00
will 979bbe6aa4 Model cross-day Vespers commemorations, fix First Vespers threshold
Deploy / deploy (push) Successful in 1m1s
Whichever of today/tomorrow doesn't govern a given evening's Vespers
can still be commemorated there, unless it's Simplex (or a bare
temporal winner with no standing of its own) — live-verified in both
directions against the real Monastic 1617 engine: Ss. Cyriacus/Largus/
Smaragdus (Semiduplex) commemorated when Sunday claims First Vespers
over them; St. Anne commemorated even though St. James (Duplex II.
classis) keeps his own Second Vespers outright; St. Donatus (Simplex)
gets nothing ("nihil de præcedenti") in the same situation Cyriacus/
etc. did get a commemoration. The isMajorFixedFeastOfTheLord override
(Christmas/Epiphany/Candlemas/Easter/Ascension/Corpus Christi/Sacred
Heart) deliberately doesn't inherit this yet — no live data on whether
those suppress it the way privileged-sunday/privileged-feria-major do
during the day.

Also fixes hasFirstVespers's threshold, found wrong while gathering
the above evidence: it required duplex-majus+, but Ss. Cyriacus/etc.
(plain Semiduplex) actually claims First Vespers over St. Donatus in
the live engine. Lowered to semiduplex, matching the sibling
privileged-feria-minor threshold already used just below it.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-13 18:59:16 -04:00
will 3aee8eca2f Bulk-import the full Psalter from Divinum Officium's Psalmorum text
Rather than continuing to author psalms one at a time as each hour
needs them, pull all 150 from the reference corpus directly (Latin +
English line up 1:1 for every psalm) — Vespers alone needed ~25 new
numbers, and Matins will eventually need most of the rest anyway.
Also completes 001.yml, which turned out to still be a 2-verse
placeholder predating this project's "verified" convention.

Ps 94 (the Invitatory) is included too, with its source file's
repeating "$ant" antiphon macro dropped — this app's Matins doesn't
need the interleaved-antiphon structure the real Invitatorium uses, so
it's stored as plain continuous verses like every other psalm (a
deliberate simplification, not an oversight). Its English is the
traditional Coverdale/BCP "Venite" translation rather than this
store's usual Douay-Rheims register, so it's marked `draft` pending a
deliberate reword later.

prime.test.ts's status-surfacing test relied on Ps 1 being incomplete;
rewritten against Ps 94's real verified/draft split and Ps 151 (off
the real 1-150 range) for the missing-fallback case instead.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-13 18:59:01 -04:00
will 1a1b296c5f Model the Paschaltide alleluia suffix on Common-category antiphons
Deploy / deploy (push) Successful in 56s
Live-querying settled the "sometimes 1, sometimes 2 allelúja" question:
categories without their own dedicated Paschaltide source-text chain
(Confessor-Bishop/Doctor, Abbot, Virgin, ...) get a mechanical single
suffix appended at render time, not a rank/octave-dependent count.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-13 14:19:32 -04:00
will 75b731221e Close out P/T/S/N: St. Benedict's/All Saints Benedictine's own content, St. Michael redirect bug
Deploy / deploy (push) Successful in 57s
The last 2 duplex-majus+ saints held back from the original 29-saint
P/T/S/N pass (for lacking a collect at the time) get the same
individual live-query treatment as their peers: real, unique proper
text for both (St. Benedict's own Prime antiphon is the opening line of
St. Gregory the Great's Life of St. Benedict; All Saints of the
Benedictine Order draws on Matt 13:43, Luke 6:22-23, and a well-known
All Saints antiphon) -- 14 new files, English translated throughout
since the source rendered none for either.

A final calendar-wide sweep (every saint's resolveMinorHourAntiphon/
resolveMinorHourChapter) turned up one more real gap, in the mechanism
itself rather than missing data: St. Michael's propers field points at
Apparition of St. Michael's own id (the two feasts are word-for-word
identical), and getDayCollect/getBenedictusAntiphon both explicitly
follow propers when resolving content -- but getMinorHourOverrideId
doesn't; it keys off the day's own winner id directly, so
resolveMinorHourAntiphon was looking for a file that could never exist.
Fixed the same way as Finding of St. Stephen: minorHoursCommon points
directly at his delegate's already-authored proper files.

Every real saint on this app's calendar now has real P/T/S/N content.
The only remaining absence is Ss. Ursula and Companions (no way to
verify a category of one) and the example-confessor test placeholder,
which was never meant to have any.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-13 11:34:21 -04:00
will 37dac8b476 Sweep minorHoursCommon (P/T/S/N) across the whole calendar, not just the 94-saint batch
Deploy / deploy (push) Successful in 56s
The original 29-saint P/T/S/N pass only ever covered duplex-majus+
saints; every below-majus saint from the *original*, pre-94-batch
109-saint pass had never had Prime/Terce/Sext/None attempted at all --
same gap already found and fixed for the 94-batch's own below-majus
saints, now closed for the rest of the calendar too.

Reused the Kalendaria-table-validated fileref approach rather than a
blind Sancti/MM-DD.txt read -- a real near-miss caught this early: St.
Apollonia's Sancti/02-09.txt is currently St. Cyril of Alexandria's (a
19th-century addition to that filename slot); blind reading would have
silently attributed his Doctor-of-the-Church Common to her. Also fixed
a regex bug the same pass turned up: the "vide"/"ex CXX" pattern
matched the bare word "Commune" (no number) and reading-specific
sub-references like "ex C4 in 2 loco" -- found via St. Clement, whose
Monastic-specific [Rule] override has neither a clean "vide CXX" nor a
number after "Commune."

90 below-majus saints swept, ~82 wired directly into the categories
already established in the previous commits -- no new content needed,
every C-number variant found already falls inside an already-verified
family.

A real bug this pass caught and fixed in its own output: Ss. Ursula and
Companions (deliberately left minorHoursCommon-unwired) got swept up
and wrongly wired, since she has collectCommon set despite propers:
null and being a genuine commemoration-only stub -- the Kalendaria
lookup silently defaulted to St. Hilarion's own primary slot at her
shared date. Caught by cross-checking every swept saint's propers field
for this exact pattern; reverted.

A real new category found: Doctors of the Church are not one thing.
6 of the 7 saints whose own [Rule] says "vide C4a" (Athanasius,
Augustine, Basil the Great, Hilary of Poitiers, John Chrysostom, Leo I)
render P/T/S/N byte-identical to the already-verified
common-of-a-confessor-bishop -- wired directly. St. Gregory the Great is
the one genuine exception: his own live-rendered P/T/S/N is real, unique
proper text (his own election, his Liber Regulae Pastoralis, the
dove-and-quill legend, the "non Angli sed Angeli" episode) -- 7 new
files, English translated throughout since the source rendered none.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-13 11:24:53 -04:00
will 130b0f983d Extend the source-file fallback to Frances of Rome/Bibiana, fix a real Agatha/Lucy gap
Deploy / deploy (push) Successful in 56s
Follow-up to the previous commit: applies the same "read the fileref
directly, no live query required" fix to St. Frances of Rome and St.
Bibiana, whose own filerefs simply hadn't been derived yet (they predate
the Kalendaria-table work this session built). Both resolve to
common-of-a-virgin, same as several of the 15 fixed previously.

Also swept every saint on the calendar (not just this batch's own
lineage) for any other still-missing collect or Benedictus antiphon,
which turned up a real, older gap: St. Agatha and St. Lucy's own file
comments already documented "resolves honestly missing... despite
propers being set" -- written before collectCommon existed as a
mechanism at all. Agatha's own [Oratio] is a name-substituted reference
confirmed identical to the already-existing collect-c6.yml template, so
collectCommon was simply wired in. Lucy's own [Oratio] references a
different Common formula unedited, with her own [Name] section supplying
the whole descriptive phrase a plain name field would otherwise need the
template for -- written as her own st-lucy-collect.yml rather than a new
single-use template, since she's the only current user of that unedited
form.

St. Clare showed up in the same sweep but is a false positive: her real
content lives in a separate commemoration-bundle file, used via a
different code path the sweep's synthetic-winner methodology doesn't
exercise -- already fully authored, no gap.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-13 10:56:10 -04:00
will ea15e909df Resolve 15 "unwinnable" stubs' Benedictus antiphons from source directly
Deploy / deploy (push) Successful in 55s
Direct feedback on the previous commit's framing: "unwinnable" was
describing an artifact of Divinum Officium's own single-winner
precedence model, not a real content gap -- this app already renders
commemorated saints on their own terms and doesn't need a saint to win
a live query to have real content. Fixed by reading each of the 15
saints' own already-validated fileref directly (the exact same fileref
their collect was already sourced from during the Kalendaria-table
pull) for its [Ant 1] section, falling back to whatever Common its own
[Rule]/[Rank] "vide"/"ex CXX" reference names when there isn't one --
the same proper-then-Common shape getDayCollect/collectCommon already
uses, just applied to the Benedictus antiphon too.

14 of the 15 had no [Ant 1] of their own at all -- clean Common
fallback, and every one of their C-numbers falls inside a family
already live-query-verified in the previous commit, so
minorHoursCommon/benedictusCommon were wired directly with full
confidence: St. Marcus, St. Felix (Presbyter), St. Hermes, St.
Saturninus, St. Prisca, St. Thecla, St. Catherine of Siena, Ss.
Machabees, Ss. Marius/Martha/Audifax/Abachum, Ss. Twelve Brothers, Ss.
Alexander/Eventius/Theodulus/Juvenal, Ss. Felix/Simplicius/
Faustinus/Beatrix, Ss. Nabor and Felix. St. Felicitas needed one new
category (C7), but it turned out to be a cross-reference resolving to
text byte-identical to the already-verified common-of-a-virgin.

The 1 remaining (Ss. Euphemia, Lucy, and Geminian) does have her own
real [Ant 1] -- but marked draft, not verified, on principle: St.
Alexius's own file also has a genuine [Ant 1] that his actual
live-rendered Benedictus antiphon doesn't use (common-of-an-abbot's
text instead), so a raw [Ant 1] read alone isn't sufficient proof, and
for a saint that can never be live-checked at all that uncertainty
can't be resolved either way -- used the text anyway, honestly flagged
rather than claimed as confirmed.

St. Frances of Rome and St. Bibiana (the older, pre-Kalendaria-table
109-saint pass) are good candidates for this same fix but weren't
touched here, since their own fileref was never derived by this
session's work.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-13 09:38:12 -04:00
will 9faa7168bc Author Benedictus antiphons for the remaining 26 propers:null saints
Deploy / deploy (push) Successful in 57s
Completes the 94-saint batch's Benedictus-antiphon coverage. A saint
with a genuine proper collect doesn't imply a genuine proper antiphon,
same finding as St. Martin of Tours's mixed proper/shared split from
the original 29-saint pass: of the 26, 15 are genuinely unwinnable
stubs (already documented from the collects pass -- always
commemorated, never the day's own title, so nothing to live-query) and
left honestly missing, matching St. Frances of Rome/St. Bibiana's
precedent from the original 109-saint pass; 7 of the remaining 11
turned out to render byte-identical to an already-authored Common
category (benedictusCommon wired directly, no new content) -- including
Ss. Dionysius and Companions, whose own `common` field says
common-of-a-martyr-bishop but whose live-verified minor-hour text
actually matches common-of-several-martyrs, another case (like Chair of
St. Peter earlier) where the two fields genuinely diverge. The other 4
have real unique text of their own: All Saints of the Benedictine
Order, St. Agnes (secunda), St. Benedict (no English in the source,
translated and marked draft, same gap as St. Scholastica's), and the
Vigil of St. John the Baptist (deliberately echoing the Benedictus
canticle's own opening words).

P/T/S/N for these 26 not attempted here -- a further layer on top, same
as how the original 109 got Benedictus antiphons long before their own
later, separate P/T/S/N pass.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-13 09:14:05 -04:00
will ffaa797940 Author Benedictus antiphon + P/T/S/N Commons for 66 propers:null saints
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>
2026-08-13 08:58:13 -04:00
will 6bb4c6a80f Author day collects for all 94 propers:null saints via Kalendaria tables
Deploy / deploy (push) Successful in 59s
Blind per-date source-file reading (Sancti/SanctiM MM-DD.txt) turned out
unreliable for collect authorship: those files are era-merged, carrying
centuries of accumulated saints behind rubric-conditional gating, so a
naive read often returned a saint canonized long after 1617 instead of
the Monastic-Tridentine-era one actually on this calendar (St. Didacus
instead of All Saints of the Benedictine Order, St. Rose of Lima instead
of Ss. Felix and Adauctus, etc).

Fixed by pulling from web/www/Tabulae/Kalendaria/'s own era-specific
calendar tables (1570.txt Tridentine base + M1617.txt's Monastic diffs)
instead, cross-validated against the pre-1955/Divino Afflatu chain
(1888->1906->1939->1954) as a corroborating signal. Cross-referencing all
205 existing saint records against this table confirmed 204/205 were
already correctly placed -- the earlier live-query verification work was
sound, only blind file-reading was flawed -- so no sanctoral-calendar.yml
corrections were needed, just the collect content itself.

Two collect shapes came out of the 94: 34 proper collects (own YAML file,
Latin verified against source, English translated+draft where the source
lacked it) and 60 resolved via a new Common-collect templating mechanism
-- SaintRecord.collectCommon/collectName plus substituteName() in
resolve-common.ts, filling a template's {N} placeholders positionally
from the saint's own declined name, across 21 new common/collect-c*.yml
templates named after the source's own C-numbering.

St. Michael's Sep 29 propers resolved via its whole-file @Sancti/05-08
inheritance (Apparition of St. Michael, May 8). St. Martha's plural-
template default was overridden to the singular collect-c6a since she's
one person, not a pair.

Also fixes a latent bug this surfaced: getDayCollect looked up
`${collectCommon}-collect`, but collect-c*.yml files are id'd as
`collect-c2` etc with no such suffix -- caught by a smoke test where
every one of the 93 non-Michael saints resolved as missing.

4 pre-existing tests updated: they asserted specific saints (St.
Benedict, St. Romanus Abbot, St. Anthony Abbot) had no authored collect,
which is no longer true; resolve-common.test.ts's generic "missing" case
now uses a synthetic day/winner since no real unauthored saint remains
as an example.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-13 08:19:28 -04:00
will 2658a2b94e Fix splitNamedAntiphon silently upgrading draft text to verified
Deploy / deploy (push) Successful in 53s
Every antiphon that goes through the incipit/full split -- which is
every antiphon across every hour -- lost its real status the moment it
passed through here: splitNamedAntiphon hardcoded `verified` on its
output via verifiedText(), regardless of what the source's actual
status was. Concretely, this meant St. Scholastica's antiphon (I
translated it myself, no English existed in the source), Matthias's and
several other apostles' reworded English, and Gregory the Great's
translated antiphon all silently rendered as if fully verified --
losing the "unverified draft text" UI marker (src/ui/styles.css's
.text-draft, a dashed underline + tooltip) that's supposed to
distinguish "copied straight from the source" from "Claude's own
rendering."

Fixed by having splitNamedAntiphon accept a real ResolvedText and carry
its per-language status through unchanged to both outputs, instead of
silently upgrading everything to verified. Callers whose antiphon never
had a status field to begin with (plain weekday-default antiphons and
Lauds psalmody overrides -- neither type tracks status at all, always
implicitly verified by the source file's own live-checked convention)
now wrap with verifiedText() explicitly at the call site, rather than
that assumption being buried inside splitNamedAntiphon itself.
resolveMinorHourAntiphon (Prime/Terce/Sext/None's override lookup) now
returns a full ResolvedText for the same reason.

Verified end-to-end with St. Scholastica's real draft-English antiphon,
both via a direct unit test and visually in the browser (the dashed
underline now shows correctly under her Benedictus antiphon).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-12 11:34:21 -04:00
will ea29e5ee45 Add Dec 26-30's own Sunday rule -- a deliberate remix, not a live-verified fix
Deploy / deploy (push) Successful in 54s
Per direct instruction, and explicitly framed as this project's own
departure from Monastic 1617 itself: within Dec 26-30 inclusive (exactly
one real Sunday falls somewhere in that window every year), whichever
date is the actual Sunday always wins as Nat1-0 outright, with no rank
check at all -- even Stephen/John/Holy Innocents (Duplex II. classis)
get fully displaced, not just commemorated. The displaced saint isn't
commemorated in place either; he reappears in full as Dec 30's own
winner instead (the block's one date with no fixed saint of its own).
Rank never matters for this rule -- confirmed with Becket (Semiduplex)
transferring the same way as the Duplex-II-classis saints.

This deliberately diverges both from what Monastic 1617 itself does on
Dec 26-29 (live-verified two commits ago: the saint wins there, Sunday
merely commemorated) and from the general privileged-sunday transfer
rule elsewhere (which only transfers ranks below duplex-majus and
commemorates everything else in place) -- this window transfers every
rank and commemorates nothing in place, a genuinely new, third
behavior specific to this 5-day block.

Implemented as calendar/index.ts's applyChristmasOctaveSunday, a
narrowly-scoped layer in the same spirit as applyMarianSaturday/
applyChristTheKing, rather than by changing the shared
decideOccurrence/transfer machinery every other Sunday relies on. Dec 31
and everything outside Dec 26-30 is untouched -- St. Silvester still
beats the Sunday there via the plain, unchanged ordinary-sunday rule.

Verified against all five possible Sunday positions within the window
(Dec 26/27/28/29/30) plus the "nothing displaces anything" control case,
each with its own dedicated test in the new
tests/calendar/christmas-octave-sunday.test.ts. Updates two existing
tests whose assertions were correct under the old rule and are now
superseded specifically within this window (Stephen/Innocents no longer
win their own Sunday-collision dates there) while confirming the
underlying ordinary-sunday rule is still live and real outside it (Dec
31/St. Silvester).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-12 10:59:34 -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 5f4cf33776 Add per-feast overrides to Prime/Terce/Sext/None; fill Lauds' own Lawrence office bundle
Deploy / deploy (push) Successful in 51s
Prime/Terce/Sext/None had no per-feast override mechanism at all before
this -- their psalm antiphon always fell back to the plain weekday
default and their chapter was always a single fixed per-annum/per-weekday
text, regardless of what was actually being celebrated, even on a
saint's own actual feast day (not just during an octave). Confirmed live
(Monastic Tridentinum 1617) that St. Lawrence has his own real antiphon
and chapter at every one of these hours, both on his own day and
throughout his octave.

New shared mechanism in hours/resolve-common.ts: getMinorHourOverrideId
(duplex-majus+ threshold, kept in sync with Lauds' own so it's one
shared backlog), resolveMinorHourAntiphon, resolveMinorHourChapter --
all resolveOfficeWinner-aware, so an octave day gets the same override
as the feast's own actual day. Prime additionally needed
isSundayOrFeastOffice (its capitulum/Preces Sunday-vs-ferial choice)
made octave-aware -- live-verified Prime's capitulum stays the
Sunday/feast form throughout the octave too, so no new file was needed
there, just reaching the existing one correctly.

Also fills in Lauds' own still-missing Lawrence office bundle
(capitulum/responsory/hymn/versicle) -- flagged in TODO.md as worth
fixing "even before tackling the other 32" and still not done until now.

Authored real content for St. Lawrence across all five hours from live
queries. The same ~31-saint backlog (TODO.md) now applies uniformly
across Lauds and the four hours fixed here.

Also documents a real, found-not-fixed gap: when two octaves overlap
with no temporal standing to yield to (St. Lawrence's and the
Assumption's genuinely overlap every Aug 16-17), this app currently
picks whichever started first, which happens to match the live engine
on Aug 17 (Lawrence's own elevated final "Octave Day" outranks the
Assumption's ordinary octave-day strength) but only by coincidence, not
a real rank comparison -- proposed design written up in TODO.md pending
a decision on scope.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-12 08:05:08 -04:00
will 143244203d Fix octave-day office content: use the octave's own feast, not the plain ferial
Deploy / deploy (push) Successful in 52s
Real, multi-part bug found by looking closely at today's rendering (day 3
of St. Lawrence's octave, St. Clare commemorated): the psalmody
antiphons, hymn, chapter/responsory/versicle, Benedictus antiphon, and
day collect were all keyed off `day.winner` directly, which stays the
plain temporal identity (post-pentecost-11) throughout an octave --
St. Lawrence's own already-authored psalmody override and collect never
engaged on any day but his actual feast day. Live-verified (Tridentine
1910, 2026-08-12) that the whole office on an ordinary octave day
actually comes from the octave's own feast ("{ex Commune aut Festo}" /
"{ex Proprio Sanctorum}"), with a *weaker* commemorated saint (Clare)
getting a separate Ant+V/R+collect block of her own alongside it.

Fixed with a new resolveOfficeWinner(day) in hours/resolve-common.ts,
used everywhere getDayCollect/getDayCollects/getBenedictusAntiphon/
getPsalmodyOverrideFor used to read day.winner directly. Deliberately
gated on temporalCategory === 'ordinary-feria' (the day has no standing
of its own) rather than "any active octave" -- the Christmas Octave's
own stacked octaves are the live-verified counterexample where the
*temporal* day keeps the office instead, each octave becoming its own
separate commemoration block (not yet modeled, flagged in TODO.md).

Also fixes the ferial Preces gate (added in the previous commit) to
exclude active octaves the same way the suffrages already do --
live-verified "Preces Feriales{omittitur}" on the same date.

getDayCollects now renders a sanctoral commemoration as a real
Ant+V/R+collect bundle when authored (${propers}-commemoration.yml),
not just a bare, unlabeled collect -- the old rendering was indeed the
confusing "PRAYER / (translation pending)" block spotted today, which
was St. Clare's placeholder with no indication whose it was or why.
Authored her real commemoration bundle and standalone collect from the
same live query rather than leaving it pending.

One correction to a guess floated mid-session: the Benedictus antiphon
does NOT go to the commemorated saint (Clare) -- live-verified it stays
with the octave's own feast (Lawrence's "In cratícula"), same as
everything else in the primary office; only the separate commemoration
block is Clare's.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-12 07:44:39 -04:00
will 9c9811a9ed Add Lauds' ferial Preces (Tridentine 1906/1910), shown on ferial/vigil days
Deploy / deploy (push) Successful in 53s
Monastic 1617 has no Preces feriales at Lauds at all -- content sourced
from Tridentine 1906/1910 per direct instruction, same track as the
Cross suffrage and St. Joseph's suffrage. New lauds-preces-feriales.yml
(Kyrie, Pater noster, the full versicle litany, Psalm 129, closing
versicles) transcribed from a live query against a clean Advent feria,
matching what actually rendered rather than the underlying template's
own conditional markup (the Pope/Bishop and benefactors' versicles are
both dropped live despite being present in the template; the King/nation
versicle pair is kept despite the template's own rubric note suggesting
otherwise).

New `lauds-preces` part kind (hours/types.ts) picks between this and the
existing short Sunday/feast litany (lauds-short-litany.yml, unchanged
content) on a ferial-or-vigil day. Deliberately not the shared
`isSundayOrFeast` predicate, which treats a Vigil as a "feast" -- here a
Vigil needs to land on the ferial side instead, per direct instruction.
Also deliberately wider than the real 1906/1910 rubric itself, which
restricts the fuller form to Advent/Lent/Ember days specifically -- shown
on *every* ferial or vigil day instead, mirroring the identical explicit
choice already made for Prime's own ferial Preces.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-12 07:12:27 -04:00
will 2f8f744cd1 Omit Lauds suffrages entirely during Advent/Christmastide/Passiontide and any active octave
Deploy / deploy (push) Successful in 54s
Checked directly against Monastic Tridentinum 1617 (not just the
Tridentine 1906/1910 track the suffrage content itself comes from) --
this omission isn't a rubric-track-specific quirk, it holds under this
app's own primary source too:

- Advent, Christmastide, and Passiontide ferias: suffrages omitted
  entirely. Confirmed ordinary Lent (weeks 1-4, not Passiontide) is NOT
  excluded -- only the last two weeks are.
- Any day within an active octave: suffrages omitted entirely, however
  low that octave's own rank is -- confirmed against day 2/3 of St.
  Lawrence's own Semiduplex octave, well below the existing Duplex+
  threshold.

Today (within Lawrence's octave) was showing all five suffrages before
this fix; the real office has none but the Marian antiphon.

Still not modeled, flagged in TODO.md: Paschaltide's own substitution
(a single alleluia-form "Suffragium Paschale" replaces the whole set
during Eastertide, rather than the ordinary set showing or being
omitted) -- a separate, larger feature.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-12 06:59:17 -04:00
will fb66f60770 Add St. Joseph's suffrage; fix Cross/BVM suffrage gating
Deploy / deploy (push) Successful in 51s
Three things prompted by today's date resolving wrong:

1. St. Joseph's own Lauds suffrage was never modeled at all. Added
   lauds-suffrage-joseph.yml, sourced from Tridentine 1906/1910 (the
   reference engine's "Tridentine - 1906" version) per direct
   instruction -- Monastic 1617's own suffrage set has no Joseph
   suffrage at Lauds, so this one genuinely comes from a different track
   than the other four.

2. The Cross suffrage's actual gating rule, live-verified against a
   plain ferial win, a plain Sunday win, a low-rank saint's own win (even
   bare Simplex), and Marian Saturday: it shows only when the bare
   temporal feria itself is what's being prayed -- the opposite of
   "Sunday or a feast of the Lord." Expressed as `!isSundayOrFeast(day)`
   (the same ferial/festive split Prime's capitulum already uses) plus
   one more exclusion for a named temporal identity like Marian Saturday,
   which isn't Sunday or a sanctoral win either but still isn't a bare
   ferial office.

3. The BVM suffrage is correctly dropped on Marian Saturday (unchanged
   behavior, just now understood and commented correctly): it's specific
   to a day whose own office is already Marian, confirmed via the same
   live source, not a guess.

Also models St. Clare (Aug 12, Simplex under Monastic 1617) and surfaces
active octaves in getDayLabel, since today's date was resolving as a
plain ferial day instead of showing St. Lawrence's octave with St. Clare
commemorated.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-12 06:51:22 -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 ff81a6fe52 Fix Christmas Octave Sunday's occurrence precedence (was backwards)
Deploy / deploy (push) Successful in 51s
christmastide's Sunday was coded privileged-sunday (Advent's own tier),
which live-checking against the reference engine shows is wrong: on
every one of the three real collision dates (St. Stephen/John/Holy
Innocents, all Duplex-II-classis, falling on the Sunday within the
Octave) the saint wins outright and the Sunday is merely commemorated --
the reverse of what privileged-sunday would produce (Sunday stays
primary, saint just gets a nod). St. Silvester (plain Duplex, Dec 31)
confirms the same live. Reclassified to ordinary-sunday, which matches
all four checked cases: Duplex+ wins outright (Stephen/John/Innocents/
Silvester), Semiduplex still transfers off the Sunday rather than
winning (St. Thomas Becket, unaffected by the fix either way), and a day
with no saint at all resolves to the Sunday cleanly.

Found while pulling christmas-octave-sunday's own Benedictus antiphon
from the reference engine's dedicated Tempora/Nat1-0 proper and noticing
this app's own occurrence resolution didn't match what the live engine
showed for the Sunday's real collision dates.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-12 06:30:46 -04:00
will 73672853ef Author christmas-octave-sunday's own Benedictus antiphon
Deploy / deploy (push) Successful in 52s
Corrects the previous conclusion (that this id had no clean sourceable
date at all). The reference engine has a dedicated proper for this
Sunday, Tempora/Nat1-0, distinct from the individual octave-day saints'
own pages -- most years the Sunday loses outright to a Duplex-II-classis
octave saint (St. Stephen/St. John/Holy Innocents) and only earns a bare
commemoration, which is what the earlier search kept finding, but
2029-12-30 is a year where the Sunday wins outright with no
commemoration at all, so that's the date pulled from.

All 52 temporal ids (54 counting marian-saturday/christ-the-king) now
have an authored Benedictus antiphon, closing out the temporal half of
the "every sanctoral and temporal entry needs its antiphons" task for
real.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-12 06:26:25 -04:00
will 5a76f2198c Author Benedictus antiphons for all 51 remaining temporal ids
Deploy / deploy (push) Successful in 50s
Pulls the Lauds Benedictus antiphon (Latin + English) for every
temporal-id proper except christmas-octave-sunday, completing the
"every sanctoral and temporal entry needs its antiphons" task
(sanctoral side finished in prior commits this session).

Dates were found programmatically: a Node script iterated resolveDay()
over full years to find, for each temporal id, the first date this
app's own calendar logic resolves to it, then each candidate date was
cross-checked against a live Divinum Officium (Monastic Tridentinum
1617) query to confirm the title has no saint collision before pulling
content. Four ids (easter-5, easter-6, post-pentecost-11,
post-pentecost-14) needed a second, alternate date after the first
candidate turned out to collide with a saint modeled in the live
reference engine but not in this app (St. Mark, St. Athanasius, Our
Lady of the Snows).

christmas-octave-sunday is deliberately left unauthored: every date it
can resolve to collides with a specific named octave-day feast in the
real calendar, so there's no clean generic content to source -- see
TODO.md.

Also updates tests/hours/lauds.test.ts: the old "falls back to missing
on a plain temporal day" test used a date that now has real content
(post-pentecost-11), so it's replaced with a test confirming that
content resolves correctly plus a new test specifically covering the
christmas-octave-sunday exception.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-11 21:50:55 -04:00
will 2247e8c27f calendar: exempt Vigils from Our Lady's Saturday's demotion
Deploy / deploy (push) Successful in 49s
Refined per direct correction: Our Lady's Saturday is really only a
Simplex-strength standing, not a privileged-feria-minor-shaped one --
a Vigil keeps forcing its own primacy on a Saturday the same way it
already does any other day (see calendar/commemorations.ts's own
transferDirectionOf special-casing for Vigils elsewhere). Only a
Simplex saint alone now loses to it; a Vigil, along with anything
Semiduplex-or-higher, wins outright and Marian Saturday doesn't apply.

Un-does the two test-date moves this same regression prompted last
time (Vigil of St. Lawrence, Vigil of St. John Baptist) -- both dates'
original years work again now that the fix is right.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-11 14:20:13 -04:00
will 0404642614 calendar: add Christ the King, last Sunday of October
Deploy / deploy (push) Successful in 51s
A 1925 addition, so nowhere in the Monastic Tridentinum 1617 base this
project otherwise targets -- content sourced from "Monastic Divino
1930" instead, the Monastic-lineage version of the era that actually
carries it. Always wins outright (Duplex I. classis, confirmed live),
commemorating whichever Sunday after Pentecost it displaces -- same
additive-layering shape as applyOctaves/applyMarianSaturday, run last
so nested commemorations (e.g. a Simplex saint already commemorated
under that Sunday) survive underneath it.

Extended the Lauds psalmody-override mechanism (both the psalm groups
and, newly, the chapter/responsory/hymn/versicle bundle) to recognize
named temporal winners as unconditionally override-eligible, not just
duplex-majus+ sanctoral ones -- generalizing what had been a Marian-
Saturday-only special case. Suffrages needed their own explicit check
too: isDoubleOrHigher only ever looks at a sanctoral rank, and a named
temporal winner has no FeastClass to check at all.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-11 14:13:22 -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 4548042b65 hours: split Saturday's Deuteronomy canticle back into its own two parts
Deploy / deploy (push) Successful in 48s
Per direct instruction: RB 13 says this canticle "in duas glorias" --
two pieces, each closing with its own Gloria Patri, framed by one
shared antiphon (opening before the first, full only after the
second) -- not joined into one continuous block the way this was
first built. canticleText() now takes an optional verse slice so
Saturday can ask for two.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-11 13:46:20 -04:00
will 73f9c1aebe hours: use the Marian Sabbato hymn as Saturday's own Lauds default
Deploy / deploy (push) Successful in 47s
By explicit choice, not the honest-missing placeholder from before: a
genuinely per-annum, unprivileged Saturday isn't achievable (every one
is either Marian-Sabbato-overridden or in a privileged season with its
own swapped hymn), so Sanctae Mariae Sabbato's own hymn ("O gloriosa
Domina") becomes Saturday's default rather than leaving a gap.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-11 11:57:57 -04:00
will 5830e4cfd2 hours: build Lauds
Deploy / deploy (push) Successful in 48s
Opening versicle; Ps 66 (fixed, no antiphon at all); the weekday-
variable psalm groups (Ps 50 plus 1-2 more, framed by their own
antiphon each, wholesale-substituted by Sunday's Ps 50+117-share-one-
antiphon shape); the weekday OT canticle; the Laudate psalms (148-150)
under one more shared antiphon; the weekday chapter/responsory/hymn/
versicle bundle; the Benedictus (framed by a day-resolved antiphon,
same incipit-or-full/full-repeat shape as Compline's Nunc Dimittis);
the Kyrie/Pater-noster short litany; the day's collect(s) — winner
plus one per commemoration, via the new getDayCollects; the four
Tridentine suffrages (omitted on a Double-or-higher feast); the
conclusio; the closing versicle (the silent Our Father that precedes
it isn't re-rendered, its text having already appeared once at the
short litany); and the closing Marian antiphon, reusing Compline's own
mechanism exactly.

Known, deliberate gap, not yet fixed: the psalmody doesn't model the
Commune/feast override real practice applies on a Semiduplex-or-higher
day (substituting an entirely different psalm+canticle set) — every
day currently gets the plain ferial weekday distribution regardless of
who wins. That's a separate, larger propers-authoring task (a full
Common-of-Saints psalm set per Common).

Also a simplification in getDayCollects: each collect in a multi-
collect day renders as its own complete "Let us pray ... Amen." block,
rather than the more compressed real form (one "Let us pray," only the
last collect's doxology).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-11 11:45:25 -04:00
will 49bd7f7858 scripture: add a dedicated non-Psalm scripture store, migrate pure-scripture readings
Deploy / deploy (push) Successful in 46s
Adds src/scripture/ (types.ts, index.ts), a verse-addressable store mirroring
psalter/index.ts's own pattern exactly -- getScriptureVerses(book, chapter,
range) -- for non-Psalm scripture (Wisdom, Canticles, Ezekiel, Acts, Joel,
etc.). Kept as its own module, not folded into the Psalter, per 2026-08
discussion ("the psalms kind of work differently even if the logic is the
same, they do need their own store").

Octave readings can now declare `passages: [{ book, chapter, verses }]`
instead of embedding text -- each entry becomes its own paragraph, letting a
reading built from several verse ranges keep its original paragraph breaks.
getOctaveReading resolves either shape (embedded text or passages) into the
same OctaveReadingText callers already expect, so nothing downstream needs
to know which was used. Scope, per discussion: only readings that are
Scripture start to finish get this treatment -- prose that merely quotes a
verse or two (most of the patristic sermons pulled so far) keeps its
quotations embedded inline, as before.

Migrates the 9 already-committed octave readings that are pure Scripture
(Wisdom 4:7-20, Canticles 1 and 8, seven different Ezekiel excerpts across
All Saints' octave -- one of which, day 7, genuinely spans two chapters --
and Acts 19/Joel 2 for Pentecost's octave) to the new passages notation,
verified to resolve to character-identical text as before.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-11 10:06:10 -04:00
will e35183bfac propers: add octave Matins readings, starting with St. Lawrence's
Deploy / deploy (push) Successful in 46s
Adds propers/octave-readings.ts: a day-indexed lookup
(getOctaveReading(octaveId, dayNumber), id convention
"${octaveId}-octave-day-${dayNumber}") since readings genuinely vary day
to day within a single octave (confirmed: the Assumption's own octave
gives different patristic excerpts on day 2 vs. day 5) -- the singular
OctaveConfig.readingId field from the previous commit didn't fit that
reality and is removed.

Sourced via a different query command (prayMatutinum, not prayTertia)
than every other proper this pull has pulled -- the raw per-date
Sancti/MM-DD.txt files for octave-continuation days are occupied by
unrelated later-calendar saints (same trap as everywhere else), but the
real content lives in MM-DDoct.txt/MM-DDbmv.txt-style variant files the
live engine resolves correctly.

Per project convention (2026-08 discussion): the historical 3 (or 9-12,
on a day's own higher-rank office) Lectios are collapsed into one
continuous reading rather than kept broken up, and only the first
responsory is kept (which one to use when several collapse into one is
still an open question). A caught bug along the way: several readings
have no English translation in the source at all -- its own English page
silently falls back to showing the Latin text under the "Reading"
header, which an early pass mistakenly stored as a duplicate before the
mismatch was caught; now left honestly missing instead.

St. Lawrence's octave: real readings for days 2-5 and 8 (day 8, "In
Octava S. Laurentii," is a fuller distinct office); days 6-7 (Aug 15-16)
belong entirely to the Assumption's feast and octave instead -- day 7's
query, redirected, became assumption-octave-day-2's reading. The other
five octaves (Assumption's remaining days, Nativity BVM, Immaculate
Conception, All Saints, the Christmas trio, Pentecost) are follow-up
work, not done in this pass.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-11 08:46:58 -04:00
will eec76a1e4e calendar: enable the four remaining real octaves
Deploy / deploy (push) Successful in 44s
Assumption (Aug 15-22), Nativity of the BVM (Sep 8-15), Immaculate
Conception (Dec 8-15), and All Saints (Nov 1-8) now declare octave:
{ enabled: true }, same generic mechanism as St. Lawrence and the
Christmas trio. Every octave this pull has identified is now modeled;
Matins reading content is a separate, larger follow-up (see conversation)
-- these still just commemorate at L/V-equivalent level for now.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-11 08:20:47 -04:00
will da3b6e893e calendar: model octaves as a generic, data-driven mechanism
Deploy / deploy (push) Successful in 46s
Adds calendar/octaves.ts: a lookback over the past week collecting every
octave (sanctoral or temporal) still active on a date, stacking multiple
at once (Christmas + St. Stephen + St. John + Holy Innocents all
commemorated together within the Christmas Octave). Declared via an
optional `octave` field on a saint's own record or a new small
TemporalFeastRecord (calendar/temporal-feasts.ts) for temporal ids like
Christmas/Pentecost that didn't have a metadata record before -- data-
driven per user design discussion, with `{ enabled: true }` alone using
sensible defaults (8 days, semiduplex threshold) so a minimal declaration
works without authored content.

Wired into resolveDay as a post-processing layer: doesn't change how a
single day's own precedence contest is decided, just adds commemorations
for active octaves and occasionally overrides the winner when the
occurring saint doesn't clear the strictest active octave's threshold.

Populated so far: St. Lawrence's own octave (the one that repeatedly cost
real saints their spot in August), the three Comites Christi octaves
(Stephen/John/Innocents -- Thomas of Canterbury deliberately excluded,
per discussion), and Pentecost's (duplex threshold, user-specified).
Pentecost's octave offsets are also removed from temporal-categories.yml's
privileged-feria-major classification, letting a real candidate reach the
new octave layer instead of being transferred away first -- with the
side effect that Pentecost's own Ember Saturday no longer forces a
transfer (a sub-threshold saint is now commemorated in place instead, see
tests/calendar/transfer.test.ts's updated case). Assumption, Nativity
BVM, Immaculate Conception, and All Saints' own octaves are not yet
populated with octave data -- deliberately deferred to a follow-up pass.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-11 07:23:43 -04:00
will c0413a44dc propers: add December's collect and antiphon text
Deploy / deploy (push) Successful in 45s
10 collects, 5 antiphons -- including backfilling the pre-existing
Immaculate Conception entry. St. Damasus's collect is Latin-only (no
English translation exists in the reference source at all); a few
others (All Saints, Bibiana, Immaculate Conception) needed their English
read directly from the raw source file after the live query's English
side either omitted the Oratio translation or rendered the wrong Marian
feast's text -- caught by cross-checking, not assumed.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-11 06:34:45 -04:00
will 08f94823f7 calendar: give Advent's ordinary ferias their own precedence tier
Deploy / deploy (push) Successful in 44s
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>
2026-08-11 06:23:35 -04:00
will 7cc6adfb55 propers: add November's collect and antiphon text
Deploy / deploy (push) Successful in 44s
9 collects, 3 antiphons -- including backfilling the pre-existing All
Saints entry. Also updates two pre-existing tests whose shared date
(2025-11-30, Advent I Sunday) now correctly shows St. Andrew commemorated
alongside it, confirmed against the live reference engine.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-11 06:18:12 -04:00
will 14227c7afe propers: add October's collect text
Deploy / deploy (push) Successful in 46s
7 collects. Also moves two pre-existing tests' dates off Oct 4-5, which
now carry real sanctoral content (St. Francis, Ss. Placid and
Companions) that changes their governing behavior -- same class of
collision as previous months.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-11 06:07:51 -04:00
will 3376036779 propers: add September's collect and antiphon text
Deploy / deploy (push) Successful in 43s
6 collects, 1 antiphon -- including backfilling the pre-existing Nativity
of the BVM entry now that the pulling methodology exists.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-10 21:20:44 -04:00
will a492be961b propers: add August's collect and antiphon text
Deploy / deploy (push) Successful in 44s
11 collects, 4 antiphons -- including backfilling the three pre-existing
August entries (Lawrence, Assumption, Augustine) now that the pulling
methodology exists. Also updates three pre-existing tests whose dates
collided with this month's new real content: the vigil-of-St.-Lawrence
backward-transfer case now runs into a real feast at its landing day
instead of an empty one (confirmed correct against the live reference
engine, including the surviving commemoration on the impeded Sunday
itself), and two other tests moved off Aug 9/10 to dates unaffected by
the new data.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-10 21:11:56 -04:00
will c03d6a227d propers: add July's collect and antiphon text
Deploy / deploy (push) Successful in 42s
Real collect text for all 9 July saints that have one, plus St. Mary
Magdalene's own antiphon — same extraction method as prior months.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-10 18:38:50 -04:00