Cut the intro/priority blurb, dropped the epilogue prose after
"none currently open" (that detail lives in the dated log below),
stripped New Features down to just the features, and removed the
Known Simplifications list entirely since it isn't open work — that
detail already lives as comments in temporal-categories.yml.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LGsATZvfnpQ81HQuoF5JuL
The 2026-08-28 note blocking Lauds' Common-of-a-Dedication file was
based on a mis-targeted live query (plain ferial Thursday's own output,
not actually votive=C8) rather than a real content gap. Fix the
Vespers file's header comment and close out the corresponding TODO.md
backlog entries with a dated log entry.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LGsATZvfnpQ81HQuoF5JuL
Live-verified the real scheme against Divinum Officium (Monastic
Tridentinum 1617, votive=C8) across all 7 weekdays: proper antiphons on
Ps 50/5/35, plus a canticle slot whose antiphon is fixed to this Common
but whose underlying text follows the plain ferial weekday rotation
(all 7 already authored). Required making
LaudsPsalmodyOverride.canticle.id optional and having resolvePsalmody
splice in the day's own ferial canticle when a category omits it.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LGsATZvfnpQ81HQuoF5JuL
When a Gospel+homily pair is folded into one pool entry, the homily's
own text was never registered in seenReadingText -- only the Gospel
pericope's text was. Not live for any date today, but the same
structural mistake already fixed once for two ids pooling the same
Gregory the Great excerpt: a later pooled id whose solo lesson happens
to share a homily's exact text would render as a visible duplicate.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T6rG4RTNqLFYcyjbCGkPFs
The octave-reading branch never called withOccasion/occasionName at
all, so an active octave's own reading always showed a bare source
even when pooled alongside other content on the same day -- the same
missing-annotation gap just fixed for plain-Sunday and saint/temporal
readings, in a fourth branch that predates that helper. octave-readings'
source is bilingual ({la, en}), unlike nocturn-readings' plain string,
so the occasion suffix is appended to both language slots directly
rather than routed through withOccasion itself.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T6rG4RTNqLFYcyjbCGkPFs
occasionName() only recognized saints, named temporal feasts, and Ember
days, so any reading pooled under a plain governing-Sunday id
(post-pentecost-15, month-week-092, etc.) -- the most common id shape
in the whole pool -- silently got no "(for ...)" annotation. This is
why Sept 6's Gregory the Great homily and its paired Augustine Gospel
homily showed up with no indication of why they were present. Falls
back to the same getDayLabel() the day's own heading already uses.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T6rG4RTNqLFYcyjbCGkPFs
Unlike the ~110 files fixed previously (a homily present with no
paired Gospel), these 6 had no Nocturn 3 at all: nativity-bvm.yml
(Matt 1:1-16 + St. Jerome), ss-cletus-and-marcellinus.yml/
ss-soter-and-caius.yml (Common-of-Popes, Matt 16:13-19 + Leo's Sermon
2 on his own Election, reused from st-martin-i.yml), ss-cornelius-and-
cyprian.yml (Common-of-Martyrs, Luke 21:9-19 + Gregory's Homily 35),
ss-seven-brothers-rufina-and-secunda.yml (a proper override, Matt
12:46-50 + Gregory's Homily 3), and st-anacletus.yml (an alternate
"rubrica 1570 aut rubrica 1617" variant carrying real content the
file's modern-rubric default lacks entirely -- Luke 14:26-33 +
Gregory's Homily 37, English composed directly from the Latin where
no reference-engine translation lines up with this Lectio split).
npm test (2029 passed) and tsc --noEmit both pass; live-verified via
resolveOrdo on all 6 saints' real dates.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MKC2QQGLYo2UTiDjDyWEkW
~110 of 355 nocturn-readings files had a patristic homily with no
paired Gospel-pericope text -- the homily discussing a Gospel that was
never itself transcribed into the file. Closed in two waves (the first
pass's source-string regex missed many real cases where the citation
lived only in a header comment or wasn't named at all), each entry
sourced from divinum-officium-reference's own Missale files (following
Sancti/Commune redirects where a feast reuses another's Gospel) and
verified per file rather than assumed -- several candidates turned out
to be genuine Nocturn 2 content or Common-of-saints cross-references
with no proper Gospel of their own, correctly left untouched.
Includes both live-reported gaps: St. Joseph of Cupertino's own Bk. II
Homily 38 (Matt 22:1-14) and the Vigil of St. Matthew's Ambrose homily
(Luke 5:27-32).
TODO.md logs the closed item plus a new, distinct follow-up: a handful
of files with no Nocturn 3 content authored at all (not just an
unpaired Gospel), found along the way.
npm test (2029 passed) and tsc --noEmit both pass.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MKC2QQGLYo2UTiDjDyWEkW
- nocturnReadingIds no longer lets a demoted commemoration's temporal id
(equal to the plain governing-Sunday temporalId) sneak a Sunday's own
homily into an unrelated weekday's pool via the commemorations loop —
a second code path than the one a prior fix gated, affecting 18
days/year.
- Reading pool order now follows id priority (winner first, then every
temporal commemoration, then every sanctoral commemoration) instead of
raw nocturn-tag/array order, matching the day label's own
temporal-then-sanctoral convention.
- gospelReadingPart now falls back to the paired homily's own responsory
when the Gospel entry has none of its own, instead of silently
dropping it.
- Patristic source labels now show "(for <occasion>)" so a reader can
tell which occasion a pooled reading belongs to on a multi-reading day.
- collectCommemorations no longer restates the plain generic temporal
label (e.g. "Saturday in the 15th week after Trinity") once anything
else is already being celebrated — general, not Ember-specific.
Updated 4 day-label tests that encoded the old behavior.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MKC2QQGLYo2UTiDjDyWEkW
post-pentecost-15 and month-week-092 land on the same Sunday
(2026-09-06) and independently reuse the identical Gregory the Great
Moralia in Job excerpt -- a real duplication in the reference engine's
own Tempora files, not a vu authoring error. buildReadingPool pooled
both ids and rendered the reading twice; now dedupes by text.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MKC2QQGLYo2UTiDjDyWEkW
No liturgical precedent exists for this book; the verse's antithesis
("the hand of our God is upon all them that seek him in goodness: and
his power and strength, and wrath upon all them that forsake him") is
reordered so the hopeful clause repeats after the V., not the wrath
clause. Closes the per-book pool at 68/72 -- the last 4 have no
sourceable material at all.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ALE7phKTAnmRWvWMU9dZa7
Nehemiah was wrongly bundled in with ezra's "ambiguous, not acted on"
reasoning and left unseeded. It actually has a real, unambiguous
citation in the Missa (Ember Wednesday of September's Lectio/Communio,
Neh 8:1-10) that a prior pass never checked. R. from v.3, V. from v.9,
both from that accented Missa Lectio.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ALE7phKTAnmRWvWMU9dZa7
User-directed: compose Ruth's own responsory from Ruth 1:16-17 ("thy
people shall be my people, and thy God my God") on the verse's own
merit. Unlike deut/rom/bar, this text has no liturgical precedent
anywhere in the reference source (no Nuptial Mass file even exists
there) -- flagged that distinction before proceeding; user confirmed
explicitly.
Since data/scripture/ruth-1.yml (the usual bulk import) carries no
accents, the Latin was hand-accented rather than transcribed, but
cross-checked word-by-word against already-accented occurrences of
the same words elsewhere in the corpus rather than guessed freehand.
66 of 72 bible-plan books now seeded. npm test (2029 passed) and
tsc --noEmit pass; live-verified through getBiblePlanReadings.
Canonical-key audit re-run, clean.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AGjUyhUZJaSjiniEmnLdak
Vulgate "Esdras" numbering is genuinely ambiguous: 1/2 Esdras is
canonical Ezra/Nehemiah, but there's also a separate non-canonical
Greek "Esdras A" (the Vulgate appendix's own 3 Esdras, sometimes
itself called "1 Esdras" in Septuagint/Orthodox numbering) -- a
different text this project has never digitized. The reference
engine's own citation never disambiguates, so this can't be
confirmed, but it cleanly explains why the Pasc7-2.txt Introit's
"Esdr 2:36-37" citation doesn't match our canonical Ezra text.
Practical outcome unchanged (ezra stays unseeded), now documented
with a clear reason rather than an open mystery.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AGjUyhUZJaSjiniEmnLdak
User pointed to missa/Latin/Tempora/Quad6-6.txt -- the Mass propers, a
directory tree this whole responsory search had never once checked
(everything so far searched horas/, the Divine Office proper). The
Easter Vigil Mass's own fourth prophecy ([Proph_Baruch3], Bar 3:9-38,
"Audi, Israel, mandata vitae") is genuine, real, accented liturgical
Latin -- just never part of Matins at all.
Composed bar the same way as deut/rom: built from that prophecy's own
climactic close ("Hic est Deus noster... Post haec in terris visus
est, et cum hominibus conversatus est" -- read at the Vigil because
early exegetes saw "seen upon earth, and conversed with men" as a
prophecy of the Incarnation).
Extended the Missa check to the other 6 already-absent books too --
genuinely zero citations anywhere. Ezra turned up one thin, ambiguous
Missa Introit citation that doesn't match its own cited verse; left
unseeded rather than force it.
65 of 72 bible-plan books now seeded -- this closes the per-book
responsory pool. npm test (2029 passed) and tsc --noEmit pass;
live-verified through getBiblePlanReadings. Canonical-key audit
re-run, clean.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AGjUyhUZJaSjiniEmnLdak
Composed rom from Rom 13:11-12 ("the night is past, the day is at
hand... let us cast off the works of darkness, and put on the armour
of light") -- the standing year-round ferial Lauds Capitulum, also
used at Advent I's own Capitulum. Genuine accented Vulgate/
Douay-Rheims text, never historically assembled into its own Matins
responsory, same approach as deut. User-confirmed before adding.
64 of 72 bible-plan books now seeded -- every book actually used in
the bible-plan has a real responsory, whether transcribed (62) or
composed from genuine already-liturgical text (deut, rom). The
remaining 8 have no citation anywhere in the source at all; there's
nothing further to check.
npm test (2029 passed) and tsc --noEmit pass; live-verified through
getBiblePlanReadings. Canonical-key audit re-run, clean.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AGjUyhUZJaSjiniEmnLdak
User found a lead reading a patristic homily (English/Sancti/01-02.txt)
that quotes Deut 32:11's eagle image. Deuteronomy has no historical
Matins responsory anywhere in the reference source, so unlike every
other entry in this pool, this one is composed rather than
transcribed: a real R./V. pairing built from the Canticle of Moses'
own genuine verses (Latin/Psalterium/Psalmorum/Psalm226.txt, sung at
Lauds), verified against data/scripture/deut-32.yml to confirm the
citation is standard verse 11 and not the breviary's own internal
chant-verse-numbering (32:15-16) for the same text. Documented plainly
in the data file's own header as vu's composition, not presented as
historically attested.
63 of 72 bible-plan books now seeded. npm test (2029 passed) and
tsc --noEmit pass; live-verified through getBiblePlanReadings.
Canonical-key audit re-run, clean.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AGjUyhUZJaSjiniEmnLdak
Advent's own reading plan (advent-1-sunday through advent-4-saturday,
28 rows for a full 4-week Advent) is always longer than real Advent
runs. Advent's last real day is always Dec 23 (Dec 24 is the Vigil of
Christmas's own temporal id), while Advent 1 Sunday lands anywhere
from Nov 27 to Dec 3 -- even the longest possible Advent is one row
short of the full plan, and the shortest is a whole week short. Every
year, at least the plan's last row never reaches its own natural date.
Added adventCarryoverReadings to propers/bible-plan.ts: whatever
doesn't land on its own natural date now carries forward one reading
per day starting Christmas Day, pooled alongside whatever
Christmastide's own calendar-dated rows already have -- never
replacing them. This is the one place in bible-plan.ts that needs
real calendar-year awareness (calendar/temporal.ts's adventStart).
4 new tests covering the shortest (2028) and a long (2027) Advent,
pooling behavior, and the Dec-24 boundary. npm test (2029 passed) and
tsc --noEmit pass.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AGjUyhUZJaSjiniEmnLdak
User pushback on Canticles being "confirmed absent" -- right instinct
to check the Commune files, which redirected productively: the same
Lectio-citation survey run against Sancti (individual saint/feast
files) rather than just Tempora found real, genuine content:
- cant: the Visitation (July 2) reads straight through the Song of
Songs, blending it with the Visitation narrative itself.
- lev: the Purification of the BVM's own Leviticus purification-law
reading is the feast's actual reason for being.
- num: the Exaltation of the Holy Cross's bronze-serpent/Cross
typology (Num 21:6-9).
Re-checked the other 9 already-"confirmed absent" books against
Sancti too -- still genuinely absent, no alternate abbreviation
missed.
62 of 72 bible-plan books now seeded -- the real final count. Only
Romans and 9 books with no citation anywhere (Tempora or Sancti)
remain. npm test (2025 passed) and tsc --noEmit pass; live-verified
through getBiblePlanReadings. Canonical-key audit re-run, clean.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AGjUyhUZJaSjiniEmnLdak
Asked to translate the 15 remaining "real Latin, no English" books.
None needed translation: the whole post-Epiphany reading season
(Galatians/Ephesians/Philippians/Colossians/1-2 Timothy/Titus/
Philemon/2 Thessalonians) turns out to have no distinct responsory
content of its own -- every feria file references 2 Corinthians' own
week (already bilingual) by weekday position. Same for 2 Peter/2-3
John/Jude, which reference Ascension Day's own responsories (act).
Only Romans genuinely has nothing: every citing file borrows an
unrelated feast's own proper responsory (Circumcision, Christmas Day,
a January saint), never anything Romans-specific. Left unseeded --
using one would repeat the Sacred Heart/Jeremiah mismatch this pool
already avoids elsewhere.
59 of 72 books now seeded -- every book with any usable Latin content
in the source at all. The remaining 13 (Romans plus 12 books with no
citation anywhere) are the real, final gap.
Ran the full canonical-key audit again; confirmed clean. npm test
(2025 passed) and tsc --noEmit pass; live-verified through
getBiblePlanReadings.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AGjUyhUZJaSjiniEmnLdak
getResponsoryForBook's joinResponsory concatenated the r/v text with a
bare newline and no "R."/"V." labels at all, unlike every other
responsory source in the app (octave-readings' own responsory field,
nocturn-readings' inline responsory, and the new per-Common-category
pool) which all include the full "R. ...\n* ...\nV. ...\nR. ..."
convention. User caught this looking at the rendered Judith entry.
Fixed by computing the repeat line from the existing "*" marker
already present in the pool's r text. npm test (2025 passed) and
tsc --noEmit pass.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AGjUyhUZJaSjiniEmnLdak
Confirmed Leviticus/Numbers/Deuteronomy genuinely absent from Tempora
(precise awk-based survey, not a loose grep). Added 1cor (Maundy
Thursday's own Institution-narrative Nocturn 3), 2cor (Epiphany II
Sunday), 1pet and 1joan (Paschaltide Sunday sets already partly used
for jac/jas).
Caught a repeat of the same canonical-key mistake from the previous
commit: the jas entry should have been keyed jac (BOOK_ALIASES maps
jas->jac). Fixed, and this time audited every single key in the file
programmatically against BOOK_ALIASES rather than just the keys
touched this pass -- confirmed clean.
45 of 72 books now seeded. npm test (2025 passed) and tsc --noEmit
pass; live-verified through getBiblePlanReadings.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AGjUyhUZJaSjiniEmnLdak
Surveyed Tempora precisely with an awk one-liner reading the line after
every [LectioN] tag (two lines deep for Gospel citations) rather than a
loose grep-for-book-prefix, which had already produced one false
positive this session.
Added act (Ascension Day's own Matins), marc (Easter Sunday's own),
heb (Good Friday's own Tenebrae), apoc (Easter III Sunday), jas
(Easter IV Sunday), and luc/matt/joan (Advent I/II/III Sundays, one
entry each). 41 of 72 bible-plan books now seeded.
Confirmed no English exists anywhere in the reference engine for
Galatians/Ephesians/Philippians/Colossians/Titus/Philemon or Romans --
real Latin exists, no bilingual source; left unseeded.
npm test (2025 passed) and tsc --noEmit pass; live-verified through
the real getBiblePlanReadings call chain (not a direct
getResponsoryForBook call, per the previous commit's lesson).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AGjUyhUZJaSjiniEmnLdak
bible-plan.ts normalizes a reading's book through canonicalBook()
(scripture/index.ts's BOOK_ALIASES map) before calling
getResponsoryForBook -- so the pool must be keyed by the canonical
form, not the bible-plan YAML's own raw book code. 12 of the 33 keys
seeded earlier today used the raw form and would have silently
resolved to undefined in the real app despite passing an initial
"live verification" that called getResponsoryForBook directly
(bypassing the normalization the real call site always applies).
Renamed: 1macc->1mach, 2macc->2mach, 1sam->1reg, 2sam->2reg,
1kgs->3reg, 2kgs->4reg, jdt->jdth, zeph->soph, obad->abd, jonah->jon,
hos->osee, mic->mich.
Re-verified through the actual getBiblePlanReadings call chain this
time (2026-06-04, post-pentecost-01/Thursday, "1sam 9-10" now
resolves a real responsory). npm test (2025 passed) and
tsc --noEmit pass.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AGjUyhUZJaSjiniEmnLdak
User caught jdt (Judith) listed as unseeded despite having real
responsory content directly visible in the reference source -- and
already flagged present in this same session's own citation dump,
never followed up on. Self-audit after that catch found job had also
been extracted earlier this session but never committed to the file.
Added jdt, esth, job, eccl, hab, joel, zeph, obad, jonah, mal, hos, mic,
nah, and lam (Maundy Thursday's own Tenebrae responsories). 33 of 72
bible-plan books now seeded, up from 19.
npm test (2025 passed) and tsc --noEmit pass; live-verified via
getResponsoryForBook resolving real text for every new key.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AGjUyhUZJaSjiniEmnLdak
"~66" was an eyeballed guess from a sort|uniq -c list, never actually
counted -- and coincidentally echoes the 66-book Protestant canon count,
a bad number to leave uncorrected in a Catholic office project's own
docs. The real count (grep -c distinct `book:` values across
data/hours/bible-plan/*.yml) is 72.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AGjUyhUZJaSjiniEmnLdak
The per-book pool extraction script only matched a bare [ResponsoryN]
tag, missing the reference engine's underscore-suffixed [ResponsoryN_]
template convention that holds the real text (a bare tag often just
@:-references it, alongside a Cistercian-rubric variant). This wrongly
made 1sam/2sam/1kgs/2kgs/2macc look untranslated in English.
Fixed the script and hand-followed @Tempora/X:ResponsoryN cross-file
references to add 1sam, 2sam, 1kgs, 2kgs, 2macc, zach, amos, and agg --
19 of ~66 bible-plan books now seeded (up from 11).
npm test (2025 passed) and tsc --noEmit pass; live-verified via
getResponsoryForBook resolving real text for every new key at every
pool index.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AGjUyhUZJaSjiniEmnLdak
Added exod, prov, sir, sap, tob, ezek, dan, 1macc (11 of ~66 total now
seeded), each sourced from a genuine, non-overridden reference-engine
Tempora file with real bilingual Responsory text. sir/sap and dan reuse
prov's/ezek's own entries respectively, confirmed by the reference
engine's own week files @-referencing that same pool -- real evidence
this app's loose book-level responsory match is historically authentic,
not invented.
Skipped 1sam (macro-only responsory text, not worth resolving for one
book) and 2sam/1kgs/2kgs/2macc/zach/amos/agg (real Latin but no
reference-engine English translation at all).
npm test (2025 passed) and tsc --noEmit pass; live-verified via
getResponsoryForBook resolving real text for every new key.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AGjUyhUZJaSjiniEmnLdak
The per-book responsory pool (Nocturn 1 scripture readings) only had `isa`
seeded; the much bigger, previously untracked gap was that 272 of 355
nocturn-readings/*.yml files (Nocturn 2/3 patristic readings) had no
responsory at all. Added a sibling per-Common-category pool, sourced from
the reference engine's static Commune text files, covering all 19 of vu's
real `common:` category ids (several confirmed aliases of the same
underlying file). Wired into matins.ts's reading assembly as a fallback
when a reading has no proper responsory of its own. Also expanded the
per-book pool with `gen` and `jer`.
Live-verified end-to-end (St. Francis de Sales, Ss. Cornelius/Cyprian, Ss.
Euphemia/Lucy/Geminianus). npm test (2025 passed) and tsc --noEmit pass.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AGjUyhUZJaSjiniEmnLdak
Throughout Paschaltide (Eastertide/Ascensiontide/Pentecost), two
things happen wherever they'd otherwise show: the ordinary
5-suffrage set (Cross/BVM/Joseph/Apostles/Peace) collapses into one
alleluia-form "Suffragium Paschale" (Lauds and Vespers each have
their own distinct antiphon here, unlike the ordinary suffrages,
which share identical text at both hours), and the ferial-Preces
litany drops entirely rather than being replaced. Live-verified
against the reference source's own [Suffragium] macro directly,
byte-identical between Monastic Tridentinum 1617 and Tridentine 1906.
Exported isPaschaltide(season) from resolve-common.ts (wraps the
pre-existing PASCHALTIDE_SEASONS set). resolveSuffrages gained a
third hourId parameter to pick the right antiphon; the Preces
omission is a small new check gated on the same isFerialOrVigil
predicate that already selects the ferial litany form.
Authored lauds-suffrage-paschale.yml and vespers-suffrage-
paschale.yml -- English on the Vespers antiphon is this project's own
translation (the reference engine leaves it untranslated) and marked
draft; everything else verified.
Closes the last item in TODO.md's "Mechanism gaps" priority tier.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AGjUyhUZJaSjiniEmnLdak
Same shape as the earlier Lauds capitulum/responsory/versicle fix
this session: real content, just never live-query-verified. Live-
verified Vespers' whole bundle (capitulum/responsory/hymn/versicle,
including the hymn's own claimed Monastic word-swap) against a plain
Lenten Wednesday (2026-03-04). Lauds' own hymn and both hours'
doxology-per-annum files were already seen live earlier the same
session (checking lauds-hymn-saturday.yml's real content against a
Lenten Ember Saturday) -- byte-identical, just needed the status
flipped. No more draft Lent content anywhere in this app.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AGjUyhUZJaSjiniEmnLdak
Trinity Sunday wasn't modeled at all: getDayLabel had a purely
cosmetic "Trinity Sunday" label overlay for the Sunday after
Pentecost, but resolveTemporalId just fell back to post-pentecost-01
(ordinary-sunday category), so any Duplex+ saint assigned that date
(e.g. the Queenship of the BVM, fixed May 31) won outright, real
Trinity Sunday content never appearing anywhere.
Added a real temporal-feasts/trinity-sunday.yml record (Easter+56,
Duplex II. classis, live-verified). Per direct instruction,
unconditional: true (same shape as christ-the-king.yml) -- a
principal Sunday shouldn't lose a same-rank fixed-date collision, and
the one real collision case (Queenship of the BVM) isn't
independently verifiable against Monastic 1617 at all (that calendar
doesn't carry the Queenship feast -- May 31 there is St. Petronilla
instead).
Authored the full office and registered trinity-sunday in
ALWAYS_OVERRIDE_TEMPORAL_IDS -- the same generic mechanism already
built for the Pentecost-cluster fix, no new code needed: collect,
Benedictus/Magnificat antiphons, Matins hymn ("Summæ Deus cleméntiæ"),
Lauds/Vespers chapter (shared, Rom 11:33) and hymn (shared, "O lux
beáta Trínitas", already the pre-Urban VIII text), each hour's own
distinct responsory/versicle -- all live-verified 2026-05-31.
Fixed 3 pre-existing tests written around the old cosmetic-label
behavior, two of which had never-verified guesses baked in as if
correct: day-label.test.ts's "names the anchor day itself" test and
its "outranks an active octave" test (was asserting "Duplex I Class",
a guess -- the real live-verified rank is Duplex II);
transfer.test.ts's St. Felix I test.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AGjUyhUZJaSjiniEmnLdak
Not actually homeless, contrary to an earlier session's claim: these
3 files already had the real content (Isa 58:1 / "Ipse liberávit me"
/ "Ángelis suis"), just marked draft since never live-verified.
Live-verified against both a Lenten Ember Saturday (2026-02-28) and a
plain Lenten Wednesday (2026-03-04) -- identical, confirming this is
genuinely season-wide content, not weekday-varying.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AGjUyhUZJaSjiniEmnLdak
Same fix as ember-september-friday.yml (already done): each file's
Lectio1-3 turned out to be one continuous, unbroken homily (Bede,
Gregory, or Ambrose's Commentary on Luke for the two Advent cases,
cross-referenced from the Annunciation's own Common and confirmed
contiguous by reading the source file directly), split only to fill
the fixed 3-lesson slot count. Recombined ember-september-wednesday/
-saturday and ember-advent-wednesday/-friday/-saturday into single
readings, keeping each one's first lesson's own responsory.
All 6 Ember days now recombined -- no remaining split-for-no-reason
readings in this family. Updated ember-days.test.ts's lesson-count
table accordingly.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AGjUyhUZJaSjiniEmnLdak
Same gap and same generic mechanism fix as Ascension/Pentecost --
just needed the ids added to ALWAYS_OVERRIDE_TEMPORAL_IDS plus real
content. Both feasts have 3 genuinely different hymns, one per hour:
Corpus Christi's own are widely known outside this context too
("Sacris solémniis" at Matins, "Verbum supérnum pródiens" at Lauds
whose last 2 stanzas are "O Salutaris Hostia", "Pange, lingua,
gloriósi Córporis mystérium" at Vespers whose last 2 stanzas are
"Tantum Ergo").
Sacred Heart needed one source-track correction: Monastic Tridentinum
1617 doesn't observe this feast on this date at all (shows St.
Barnabas instead) -- used Divino Afflatu 1954 instead, this project's
own stated rubric track. Also live-verified a genuine structural
difference: Sacred Heart's own Lauds/Vespers have no responsory at
all in the source (straight chapter to hymn) -- not an authoring gap.
Fixed lauds.ts's and vespers.ts's own resolveOffice to omit the
responsory part entirely when its status is missing in both
languages, instead of always rendering an empty section.
Authored matins-hymn-{corpus-christi,sacred-heart}.yml,
lauds-{capitulum,responsory,hymn,versicle}-corpus-christi.yml,
vespers-{responsory,hymn,versicle}-corpus-christi.yml,
lauds-{capitulum,hymn,versicle}-sacred-heart.yml, and
vespers-{hymn,versicle}-sacred-heart.yml -- all live-verified,
2026-06-04 and 2026-06-12.
Fixed 2 pre-existing tests whose own dates now land inside these
newly-modeled octaves and were asserting the old fall-through
behavior as if correct: lauds.test.ts's FERIAL_MONDAY (moved off
2026-06-08) and vespers.test.ts's St. Margaret of Scotland test
(2026-06-09) -- its own title already anticipated this fix, just had
the wrong expected values until now.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AGjUyhUZJaSjiniEmnLdak
A separate, pre-existing gap: getDayCollect already looked up
ascension-collect via resolveOfficeWinner (the same mechanism this
session's earlier fix already covers), but the content file itself
never existed. Live-verified against Divinum Officium (Monastic
Tridentinum 1617), 2026-05-14.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AGjUyhUZJaSjiniEmnLdak
Same gap as Ascension (pentecost-sunday is a temporal-feast id, not a
saint record) -- the mechanism fix from that commit is generic, so
this only needed the id added to ALWAYS_OVERRIDE_TEMPORAL_IDS plus
real content.
Unlike Ascension, Pentecost has 3 genuinely different hymns, one per
hour: "Jam Christus astra ascénderat" (Matins), "Beáta nobis gáudia"
(Lauds), "Veni Creátor Spíritus" (Vespers), each with its own real,
distinct chapter/responsory/versicle at Lauds vs. Vespers.
Authored matins-hymn-pentecost-sunday.yml, lauds-{capitulum,
responsory,hymn,versicle}-pentecost-sunday.yml, and
vespers-{responsory,hymn,versicle}-pentecost-sunday.yml (Vespers
reuses Lauds' chapter via the existing cross-hour fallback) -- all
live-verified against Divinum Officium (Monastic Tridentinum 1617),
2026-05-24. Confirmed the whole octave end-to-end via resolveOrdo,
including a real winning saint within the window (St. Augustine of
Canterbury) keeping his own hymn at Lauds/Vespers unaffected.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AGjUyhUZJaSjiniEmnLdak
`ascension` is a temporal-feast id, not a saint record, so
resolveOfficeWinner's octave-synthesis (which already handles
saint-based octaves like Assumption's/St. Lawrence's) never
recognized it -- Ascension Day itself and its whole octave silently
rendered the generic Paschaltide hymn at every hour.
Fixed the mechanism: added `ascension` to ALWAYS_OVERRIDE_TEMPORAL_IDS
(covers the feast day itself), and taught resolveOfficeWinner to also
synthesize a {kind:'temporal'} winner from an active octave with no
saint record but a real temporal-feast record (covers ordinary-feria
octave days; a real winning saint within the octave still correctly
keeps priority).
A third gap surfaced testing this: octaveGoverningPrivilegedDay's
correct-by-design "a privileged Sunday never accepts an octave
override" rule is live-verified wrong specifically for "Dominica
infra Octavam Ascensionis" (keeps the octave's own hymn/responsory,
just with its own distinct proper chapter). Rather than loosen that
shared gate for every octave, gave resolveLaudsHymn/resolveVespersHymn
a narrow, hymn-only intercept guarded to saint-less octaves, checked
before the season/weekday default.
Authored matins-hymn-ascension.yml, lauds-{capitulum,responsory,hymn,
versicle}-ascension.yml, and vespers-{responsory,hymn,versicle}-
ascension.yml (Vespers reuses Lauds' chapter via the existing
cross-hour fallback) -- all live-verified against Divinum Officium
(Monastic Tridentinum 1617), 2026-05-14.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AGjUyhUZJaSjiniEmnLdak
The "genuinely more complex, not-yet-understood two-hymn mechanism"
flagged for Sunday turned out simple: Day0 Hymnus ("Nocte surgéntes")
is just Sunday's plain per-annum hymn, and Day0 Hymnus1 ("Primo
diérum ómnium") is Septuagesima-tide's own separate seasonal hymn --
the same shape Advent/Lent/Passiontide/Paschaltide each already have.
Live-verified these 4 don't vary by weekday within their season (a
Lenten Sunday and a Lenten feria render identically). None of the 6
were authored at all before this -- every Sunday and every season day
not otherwise overridden by a winning saint/octave silently fell
through to Monday's own ferial placeholder text.
Authored matins-hymn-{sunday,septuagesima,advent,lent,passiontide,
paschaltide}.yml, all live-verified, self-contained with their own
real closing doxology baked in. Septuagesima is checked directly by
day.season in resolveMatinsHymn itself, deliberately not folded into
the shared seasonalOfficeSuffix (Lauds/Vespers have no matching
content yet -- resolveOfficeBundle there is all-or-nothing per feast,
so adding it would silently blank their chapter/hymn instead of
falling through to their current weekday default).
Known, flagged simplification: the generic Paschaltide hymn wrongly
applies during the Ascension octave, whose own real hymn isn't
authored anywhere yet -- a same-shape gap Lauds already has, not a
new regression. Updated 2 FERIAL_DATE (a Tuesday in Advent) hymn
assertions that had been asserting Tuesday's own ferial text --
Advent's own seasonal hymn correctly outranks the weekday cycle now.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AGjUyhUZJaSjiniEmnLdak
Design (direct instruction): Marian Saturday is its own rank tier,
between simplex and vigil in vu's FeastClass scale -- beats a plain
Simplex saint (commemorated in return), loses cleanly with zero trace
to anything Vigil-or-higher. Deliberately not the full historical
Officium Sabbatorum B.M.V. ("no little office"): ferial psalmody is
kept everywhere, only the antiphons swap to Marian text; Matins and
Lauds each get a real proper Marian hymn; the Marian collect applies
whenever the day wins.
The calendar-side mechanism (calendar/index.ts's applyMarianSaturday)
and a collect/Benedictus antiphon already existed from an earlier
session -- authored what was still missing: matins-hymn-marian-
saturday.yml (Matins had nothing at all) and lauds-{capitulum,
responsory,hymn,versicle}-marian-saturday.yml.
Also found and fixed 3 real bugs from that earlier, unverified pass:
Lauds' own hymn ("O gloriósa Dómina") was sitting under the plain
ferial lauds-hymn-saturday id instead of its own marian-saturday id;
that id's capitulum/responsory/versicle siblings were live-verified
from a different, privileged Lenten Ember Saturday, not a genuine
per-annum ferial Saturday -- corrected to the real content ("Auróra
jam spargit polum" / Rom 13:12-13 / "Sana ánimam meam" / "Repléti
sumus mane misericórdia tua", live-verified against a clean September
Ember Saturday); and the Lauds psalmody override used the real
historical proper psalm set (92/99/62) instead of the ferial one,
rolled back per direct instruction to plain ferial psalms (50, 142,
the split Canticle of Moses) with only the antiphons swapped.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AGjUyhUZJaSjiniEmnLdak
matins-hymn-ferial.yml turned out to already be Monday's own hymn
("Somno reféctis ártubus"), mislabeled as a single fixed year-round
text and used unconditionally for every weekday -- the source
(Psalterium/Special/Matutinum Special.txt) genuinely varies this hymn
Monday through Saturday, the classic "week of hymns". Caught first on
Friday ("Tu Trinitátis Únitas" expected, Monday's text shown), then
again on a real Ember Saturday ("Summæ Deus cleméntiæ" expected).
Authored matins-hymn-ferial-{tuesday,wednesday,thursday,friday,
saturday}.yml (Monastic 1617 text, live-verified against the CGI
instance directly, including Friday's own real M-track word-swaps) and
wired resolveMatinsHymn's final fallback tier to pick by day.weekday.
Sunday is left on the existing placeholder, documented as a real,
more complex not-yet-understood gap (a two-hymn source split).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AGjUyhUZJaSjiniEmnLdak
Lectio1-3 in ember-september-friday.yml were one continuous, unbroken
Gregory homily on Luke 7:36-50, split only to fill the fixed 3-lesson
Nocturn slot count -- the "source doesn't genuinely split" case this
store's own established default (post-pentecost-15.yml) already
recombines elsewhere. User caught this live on /2026-09-18/matins
showing 3 identically-labeled lessons in a row. Keeps Lectio1's own
responsory per the "first one, when several collapse" convention.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AGjUyhUZJaSjiniEmnLdak
The ezek incipit's English heading read "...Prophecy of Ezechiel" --
the Vulgate/Douay-Rheims spelling, inconsistent with this app's own
generated heading text elsewhere. Left every other in-corpus
"Ezechiel" alone (quoted Douay-Rheims verse/martyrology/homily prose,
where the traditional spelling is correct as source text).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AGjUyhUZJaSjiniEmnLdak
nocturnReadingIds pooled the governing Sunday's own nocturn-readings
id (temporalId) and its month-week-N sibling whenever any temporal
commemoration survived on a 3-nocturn day, with no weekday check --
so a Duplex+ saint winning outright on a plain weekday that also
carried a temporal commemoration pulled in a whole second homily's
worth of unrelated Sunday content. Live-verified against the
reference engine (Tridentine 1906, St. Joseph of Cupertino's day):
real Nocturn III there is just the winning saint's own Common homily
plus a single "Commemoratio Feriae" lesson -- no Sunday content at
all. Gates temporalKept to day.weekday === 'sunday'.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AGjUyhUZJaSjiniEmnLdak
bible-plan.ts's *.yml import uses English-style book abbreviations
("jdt", "1kgs", "luke", ...) for ~30 books where that diverges from
this store's canonical form ("jdth", "3reg", "luc", ...).
scripture/index.ts's own BOOK_ALIASES already normalized this for
verse-text lookup, but the incipit (bible-book-incipits.ts) and
responsory (matins-responsories.ts) lookups used the raw abbreviation
directly, silently missing on any canonically-keyed book and falling
back to a bare "Reading" heading. The 3 Gospel aliases self-healed via
a separate citation-regex fallback that only exists for Gospels; every
other aliased book (Judith, Kings, Samuel, Esdras, Osee, Jonas, Josue,
Micheas, Abdias, Judges, the Johannine epistles, James, Jude, Philemon,
Titus, Zacharias, Sophonias) had no such fallback. Exports
canonicalBook from scripture/index.ts and routes both lookups through
it -- one shared normalization instead of two independently-aliased
ones that can drift again.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AGjUyhUZJaSjiniEmnLdak
calendar/day-label.ts never surfaced an Ember day's own identity
anywhere: collectCommemorations only matched a temporal commemoration
against resolveTemporalId(day.date), never an Ember-relabeled id, and
the winning-outright fallback used the generic ordinal weekday label
with no id awareness at all. Adds a standalone emberDayLabel lookup,
deliberately kept out of temporal-feasts.ts since that table doubles
as isFerialOrVigil's bareness check and would have silently turned
off Ember days' own ferial Preces.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AGjUyhUZJaSjiniEmnLdak
Live-verified (Monastic Tridentinum 1617) that an Ember day's Lauds
psalmody stays on the plain ferial default throughout -- only the
Benedictus antiphon (and, Wednesday/Friday only, the Magnificat
antiphon) is genuinely proper to the day. Adds the 6 Benedictus +
4 Magnificat antiphon files (Ember Saturday's Second Vespers is
always ceded to the following Sunday on both quarters, so no
Magnificat content exists to author there).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AGjUyhUZJaSjiniEmnLdak
Closes the gap this whole sweep started from: Nocturn 3 on
post-pentecost-13/14 showed St. Augustine's homily with no Gospel
citation or text above it. Fixes all remaining post-pentecost-NN ids
(01-14, 16-24; -15 already had this from the original mechanism proof)
the same way as the rest of the sweep, plus Trinity Sunday's second
Nocturn 3 pair — the "Commemoration of the First Sunday after
Pentecost," whose Gospel (Luke 6:36-42) has no live date to source from
since Trinity always occupies that slot, so it's sourced from the
reference engine's preserved pre-displacement Mass propers file
instead. Also fixes a stray-space formatting glitch in
post-pentecost-19's existing homily text, found while sourcing.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AGjUyhUZJaSjiniEmnLdak
Same gap and fix as the Advent/Christmas and post-Epiphany sweeps, for
easter-octave, easter-3 through -6, and sunday-after-ascension. Also
fixes stray leading "v." verse-marker artifacts left over in three of
the existing English homily texts (easter-4/5/6), found while sourcing
the paired Gospel text alongside them.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AGjUyhUZJaSjiniEmnLdak
Same gap as the Advent/Christmas sweep: Nocturn 3's patristic homily
appeared with no Gospel pericope before it. Authors the full text for
post-epiphany-1 through -6, septuagesima/sexagesima/quinquagesima,
lent-1 through -4, passion-sunday, and palm-sunday.
Palm Sunday needed a different source than the rest: its Missale
[Evangelium] tag is the Passion narrative (Matt 26-27) proclaimed at
Mass, not the entry-into-Jerusalem pericope (Matt 21:1-9) its own
Nocturn 3 homily is actually about — that shorter Gospel is instead
read at the Blessing of Palms, sourced from there.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AGjUyhUZJaSjiniEmnLdak
Nocturn 3 previously dropped the bare "Et réliqua" Gospel incipit and
kept only the patristic homily, so the Gospel citation and text never
appeared. Authors the full pericope before the homily for advent-1
through advent-4 and christmas-octave-sunday, sourced from the
reference engine's Missale Gospel files. Authoring Advent 4's Gospel
surfaces a genuine second Matins Gospel on the rare Dec-24-as-Advent-4
collision (2023/2028), so the affected test now expects both readings
instead of one.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AGjUyhUZJaSjiniEmnLdak
A saint's vita reading (Nocturn 2, no patristic `source` attribution)
fell through to the UI's generic "Reading" heading. Add
fallbackHagiographicLabel, which looks up the contributing id's own
saint/temporal-feast record and produces "On St. Pius X, Pope and
Confessor" style labels instead — same plain-string convention as an
authored `source`.
Updates two matins.test.ts assertions that used label shape (string
vs object) as a proxy for "not a bible-plan reading" — no longer
reliable now that vita readings also carry a string label, so they
key off citation presence instead.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AGjUyhUZJaSjiniEmnLdak
Bible-plan Gospel entries in buildReadingPool never had an authored
label, so they fell back to the UI's generic "Gospel" heading. Reuse
getGospelIncipitFromCitation (already used for proper nocturn-reading
Gospels) to derive the real "A reading from the Holy Gospel
according to..." incipit from the citation when no label was
authored.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AGjUyhUZJaSjiniEmnLdak
Closes the "unexamined gap" open-work item and records both passes (the
source-marker audit, then the deliberate broadening) as dated build-log
entries, including the extraction-script bug caught and reverted before
anything was committed.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AGjUyhUZJaSjiniEmnLdak
Lauds' Monastic source never carries the "*" substitution marker anywhere
(audited), so this is purely the same deliberate broadening as the Vespers
commit: every plain-default hymn (Sunday, Mon-Sat, Advent, Lent,
Passiontide, Paschaltide) now routes its closing doxology through
getHymnDoxologyId/appendDoxology instead of a fixed ending, split into
matching per-annum companion files that reproduce the original text
whenever no season/feast override applies.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AGjUyhUZJaSjiniEmnLdak
Sunday's hymn ("Lucis Creator") is the one case whose Monastic source text
carries a real "*" seasonal-substitution marker (live-checked); every other
key (Mon-Sat, Advent, Lent, Passiontide, Paschaltide) is wired the same way
anyway as a deliberate broadening beyond the source, at the user's request
("the doxology is a neat way to stay tuned in to the season... we're doing a
remix, not a copy"). Each hymn's own natural ending is split into a matching
per-annum companion file, so a season/feast with no override reproduces the
original text exactly; only Nat/Epi/Pasch/Asc/Pent/Corp/Heart and the two
feast/date overrides from the previous commit actually change anything.
Real visible effect: hymns shown during Christmastide/Epiphanytide/Corpus
Christi/Sacred Heart (none of which get a distinct seasonal hymn, so these
were always the plain weekday defaults) now pick up the right doxology
instead of a generic ending; Ascensiontide and the Pentecost octave now
diverge from the rest of Paschaltide's doxology mid-season.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AGjUyhUZJaSjiniEmnLdak
The plain ferial hymn's Monastic source (Day1-6 Hymnus,
Psalterium/Special/Matutinum Special.txt) carries a real "*" seasonal-
substitution marker, live-checked against the reference engine. Splits the
hymn's own natural doxology ending into a new per-annum companion file and
swaps it via getHymnDoxologyId/appendDoxology, same pattern already used by
Prime and Compline.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AGjUyhUZJaSjiniEmnLdak
getHymnDoxologyId now takes the whole LiturgicalDay (needed for a date-window
check) and checks a feast override for a temporal winner's id too, not just
sanctoral — matching Divinum Officium's own Common-of-the-BVM `Doxology=Nat`
rule, which is why Marian Saturdays get the Nativity doxology regardless of
season. Also ports the source's Aug 16-22 (Assumption octave) hardcoded
Nativity-doxology window, confirmed live not excluded for the Monastic/
Tridentine 1617 track.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AGjUyhUZJaSjiniEmnLdak
Bible-plan readings now carry a generated bilingual incipit label
(bible-book-incipits.ts) instead of falling through to the UI's generic
"Reading" heading. Gospel pericopes (whether from the user's own plan
or nocturn-readings.ts's proper content) get the same treatment via a
new label field on the 'gospel' ResolvedPart, recovering the book from
the reading's own citation when no raw book code is available.
Updates 3 matins.test.ts assertions that used "no label" as a stand-in
for "this is a bible-plan reading" — no longer true now that these
readings carry a real label, so they now key off the label's shape
(string = patristic attribution, object = generated incipit) instead.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AGjUyhUZJaSjiniEmnLdak
Maps every book abbreviation under src/data/scripture to its bilingual
liturgical incipit (e.g. "Léctio libri Sapiéntiæ" / "A reading from the
book of Wisdom"), sourced from the reference engine's own headers where
available. English uses "Saint Paul"/"Saint James"/etc. rather than
"blessed" per common modern usage; Latin keeps the traditional "beáti".
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AGjUyhUZJaSjiniEmnLdak
Multi-chapter readings (e.g. Job 34-37, authored as one YAML entry per
chapter) were rendering as "Job 34; Job 35; Job 36; Job 37" instead of
the compact "Job 34-37". Verse-qualified and non-consecutive citations
are unaffected.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AGjUyhUZJaSjiniEmnLdak
Every other post-Pentecost week's own file already attaches "Duo
Séraphim" (the closing responsory before Te Deum) to the hour's *last*
reading, overriding the usual "first one, when several collapse"
convention for exactly this responsory. post-pentecost-15 was the only
week not following that pattern -- its last (homily) reading kept
Lectio 10's own responsory instead. Swapped it in, matching the other
23 weeks; removes the now-resolved "needs an hour-level responsory
slot" note from TODO.md, which was chasing a problem that didn't
actually exist.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Je2mXNHoXmi1DS3xEL67rH
Duo Seraphim: the old note claimed it "isn't modeled" as a blanket
statement, but it's actually authored as a regular reading's own
responsory in 23 of 24 post-Pentecost weeks -- only post-pentecost-15
lacks it (its 3 readings don't include the one it historically closes).
Rewrote to state the real scope and moved to Mechanism gaps: the actual
gap is the schema having no hour-level closing-responsory slot, not a
missing content item.
temporal-categories.yml: the old note said the whole season->category
mapping was unverified, but the 2026-09-01 sweep (see that file's own
header) already fully live-verified `bySeason`. What's still open is
narrower -- a few privileged-period tier classifications (Ash Wednesday/
Holy Week's major tier on prior knowledge, Vigil of Christmas/Pentecost
Ember days defaulted as a guess, Pentecost Ember days' precedence
flagged in-file as a real unverified gap).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Je2mXNHoXmi1DS3xEL67rH
- Removed the getDayCollects multi-collect-rendering note entirely.
- Moved the suffrage/ferial-Preces Paschaltide-substitution gap into
Mechanism gaps, trimmed to just the actual gap (the alleluia-form
"Suffragium Paschale" swap isn't modeled) rather than the prior
verification-status framing.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Je2mXNHoXmi1DS3xEL67rH
While auditing the Lauds/Vespers Common-category psalmody note for
staleness, found 3 saints (St. Pius X, St. Leo II, St. Pius V) use
common: common-of-a-confessor-pope, which had no alias in either
category file -- unlike common-of-a-confessor-doctor, which already
does. Live-verified (votive=C4 vs C4b, both Vespers and Lauds,
Monastic Tridentinum 1617): psalms/antiphons are byte-identical to
Confessor-Bishop's, only the Magnificat antiphon and collect differ
(both already handled per-saint, not through this table).
Note: this doesn't change any currently-visible content -- all 3
saints are ranked duplex/semiduplex, below the duplex-majus+ threshold
this psalmody-override tier is gated at, so they correctly fall to the
plain ferial default regardless. It closes a real data-completeness
gap for whenever a duplex-majus+ Confessor Pope is added.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Je2mXNHoXmi1DS3xEL67rH
- Removed the bogus St. Michael Sep 29 note from section 3 (user: "it's
bogus").
- Removed the ferial-Preces note from section 3 -- it documents a design
decision, not a simplification; moved to CLAUDE.md's "Not a
reconstruction" list and a new memory entry instead.
- Moved the hymn-doxology note from section 3 to section 1 (mechanism
gaps) and broadened its scope: checked which hours actually wire in
the existing getHymnDoxologyId/appendDoxology mechanism (only Prime
and Compline do), confirmed Terce/Sext/None are genuinely fine as
documented, and found Lauds/Vespers/Matins are all equally
unexamined -- not just the "5 of 30 Vespers hymns" the old note
undersold this as.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Je2mXNHoXmi1DS3xEL67rH
The dated log at the bottom already carries full detail for each; the
Open Work section should list only what's actually open, not linger on
struck-through DONE bullets (Easter's own octave, bilingual header
nameLa, Matins temporal-cycle readings, bulk scripture import).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Je2mXNHoXmi1DS3xEL67rH
33 chapters from the first-pass "Unbound Bible" import each had one lone
missing English verse (matt-12:43's in-file "Transfer interrupted!"
download-corruption artifact among them). Rather than assume these were
genuine source gaps, checked every one against drbo.org's per-verse
text: 33 of 34 turned out to be real, present verses the old scrape had
simply dropped, now filled in.
The remaining 10 are confirmed genuine: drbo.org's own Douay-Rheims
numbering for these chapters stops one verse short of the Vulgate's own
count too (checked each chapter's actual max verse number there) -- a
real versification difference, not a scrape artifact, so these stay
honestly missing.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Je2mXNHoXmi1DS3xEL67rH
The "Unbound Bible" source used for the previous pass had real defects
in 12 chapters (Douay-Rheims verses shifted/merged under the wrong
number, e.g. "Isaias 25:1" actually holding 25:11's text) and was
missing Daniel 4-14/Esther 11-16 outright.
Found a better source: drbo.org's combined "DR + LV" page per chapter
keeps each verse's Latin and English in the same HTML block, making
the earlier misalignment structurally impossible. Fetched and parsed
all 28 real chapters from it (bar-2; isa-5/25; joan-15; rom-9/11;
sir-10/19/23/41/51; dan-4..14; esth-11..16), spot-checked several
against known-correct text, and confirmed sir-52 isn't a real chapter
at all (Sirach only has 51 -- the old source's "52" was bogus).
Store now at 1185 chapter files with zero remaining gaps against every
book/chapter citation the app actually uses. Repoints
scripture.test.ts's "unauthored chapter" case from dan-14 (now
authored) to gen-51 (Genesis only has 50 chapters).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Je2mXNHoXmi1DS3xEL67rH
Found 137 chapters missing from src/data/scripture/ despite the recent
bulk import, scattered oddly across ~30 otherwise-covered books. Located
the original "Unbound Bible" source archives (douay_rheims.zip/latinv.zip)
the earlier import must have used but never preserved in-repo, and
re-parsed directly from them.
118 new chapters authored, plus the 3 hand-authored short books (Jude,
Obadiah, Philemon) replaced with source-derived text — small wording
differences from the hand-typed drafts confirm going back to source was
worth it. Store now at 1157 chapter files.
Deliberately left unauthored rather than risk bad content:
- Daniel 4-14 and Esther 11-16: this digitization's Latin side only has
Daniel 1-3 and Esther 1-10 at all (English has the rest).
- 12 chapters where the Douay-Rheims side has a corrupted, misnumbered
block (e.g. isa-25's tagged verse 1 actually carries verse 11's text)
rather than simply missing content -- importing under the source's own
wrong verse numbers would silently attach mismatched text to real
citations, worse than leaving them missing.
Widened bulk-import.test.ts's invariant to tolerate up to 2 genuinely
isolated missing-English verses per chapter (extends the pre-existing
isa-33/sir-36 pointing-split-half-verse allowance to also cover true
source gaps, e.g. an in-file "Transfer interrupted!" download-corruption
artifact at matt-12:43), while still failing loudly on anything
resembling the corrupted-block pattern above.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Je2mXNHoXmi1DS3xEL67rH
Three single-chapter books referenced by bible-plan.ts's readings had no
corresponding file in src/data/scripture/ at all, unlike everything else
covered by the Vulgate/Douay-Rheims bulk import — apparently dropped by
whatever produced the source. Authored by hand (Clementine Vulgate /
Douay-Rheims 1899 American Edition) and wired via new BOOK_ALIASES
entries (jude->jud, obad->abd, philem->phlm) so bible-plan's existing
citations keep their familiar English display form.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Je2mXNHoXmi1DS3xEL67rH
resolvePassages() prepended each verse's number ("1 Et factum est...")
when joining scripture verses into continuous reading text — visible
today in Job's Matins reading. Not wanted; join the verse text alone.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Je2mXNHoXmi1DS3xEL67rH
resolveReading() in bible-plan.ts preferred each YAML row's hardcoded
status: { la: missing, en: missing } over computing status from the
actually-resolved text. That field was accurate before the Vulgate/
Douay-Rheims bulk import but was never cleared once real text landed,
so every Matins bible-plan reading kept showing as untranslated
regardless. Strip the stale field from all 368 files so status falls
through to the real compute-from-resolved-text path.
Also alias book abbreviations in scripture/index.ts's lookup: the
bulk-imported store uses Vulgate-numbering codes (sap, jac, joan,
1reg..4reg, 1par/2par, 1esdr/2esdr, ...) matching octave-readings.ts's
existing convention, while bible-plan.ts's TSV import used common
English abbreviations (kgs, sam, chr, john, jas, luke, mark, ...) for
the ~30 books where the two conventions diverge. Alias at the lookup
layer so citations keep their familiar English display form.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Je2mXNHoXmi1DS3xEL67rH
Adds ~1017 chapter YAML files under src/data/scripture/ carrying verified
bilingual (Latin Vulgate / Douay-Rheims English) verse text, closing out
the bulk scripture import TODO.md has tracked as pending bulk-content
work. Daniel 14 (the Greek deuterocanonical addition) has no counterpart
in the Latin source and stays unauthored.
Raises the PWA precache size cap (8 -> 24 MiB) to fit the larger bundle,
repoints scripture.test.ts's "unauthored chapter" case from Genesis 1
(now authored) to Daniel 14, and adds bulk-import.test.ts asserting every
imported chapter has verified, gap-free, ordered verse text in both
languages -- tolerating the pre-existing isa-33/sir-36 pointing-split
half-verses, whose English intentionally isn't split to match.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Je2mXNHoXmi1DS3xEL67rH
All 4 weekdays share the exact same 2 antiphons verbatim ("Jesum qui
crucifíxus est quǽritis, allelúja..." / "Veníte et vidéte locum ubi
pósitus erat Dóminus, allelúja, allelúja."), live-verified against
Pofficium.pl (Monastic Tridentinum 1617, prayMatutinum) across
2026-04-08/09/10/11 — despite each day drawing on a different Gospel
reading in the nocturn-readings pool, the antiphon layer doesn't vary
by day.
Layered onto vu's own already-fixed per-weekday ferial psalm numbers
rather than the reference engine's own differently-sized (Rule of
Benedict summer-contracted) psalm set for these days, consistent with
this project's deliberate rejection of that contraction.
This closes "Easter's own octave" fully: calendar precedence
(2026-09-01), Sunday/Monday/Tuesday's shared proper psalmody, and now
Wednesday-Saturday's antiphons (both 2026-09-02).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01X26BkFT3ARbtUGXuBmzUmD
Ferial-side counterpart to the Sunday-branch temporal-override
mechanism added earlier today: hours/matins.ts's ferialAntiphonedNocturn
now checks a new matins-ferial-named-antiphon-overrides.yml store first,
before falling back to the plain per-weekday default. An override's
antiphon list is distributed evenly across the weekday's own
already-fixed psalm-group count rather than tied to specific psalm
numbers, since one entry is meant to be reused across several
different weekdays (each with a different psalm set) instead of
authored per-day. Same optional weekdays field as the Sunday-side
override restricts which days of a multi-day temporalId span actually
share the content.
General-purpose, not scoped to any one feast — content added next.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01X26BkFT3ARbtUGXuBmzUmD
Easter Sunday, Monday, and Tuesday of the Octave share winner {kind:
temporal, id: easter-sunday}, and now share one live-verified proper
Matins psalmody: Nocturn 1 (Ps 1,2,8,15,23,27 under one antiphon),
Nocturn 2 (Ps 29,63,65,75,87,107 under one antiphon), Nocturn 3 (the
same 3 canticles already authored for the ordinary-Paschaltide
seasonal override, under its own antiphon) — sourced from
Pofficium.pl (Monastic Tridentinum 1617, prayMatutinum) against
2026-04-05/06/07. All 3 nocturn-closing versicles are byte-identical
to the ones already authored for the Paschaltide override, confirming
those are shared season-wide rather than coincidentally reused. The
12 patristic lessons were already authored in an earlier sweep
(nocturn-readings/easter-sunday.yml) and needed no new work.
Restricted to just these 3 days via the new weekdays field: the
Octave's other 4 weekdays (Wed-Sat) share the same winner id but have
their own different, not-yet-authored antiphons, tracked as a
separate open item in TODO.md.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01X26BkFT3ARbtUGXuBmzUmD
hours/matins.ts's threeNocturns gate and Sunday-branch dispatch only
ever fired for a literal Sunday or a sanctoral winner — a temporal
winner (e.g. Easter Monday/Tuesday, sharing Easter Sunday's own
winner id) had no path into 3-nocturn proper content at all, even
with a matching named override authored for it.
Generalizes hours/matins-sunday-named-nocturn-overrides.yml's lookup
so a temporal winner with an authored entry is eligible too, reusing
the same sundayPsalmNocturn/sundayCanticleNocturn render functions
the Sunday and saint-proper branches already share. A new optional
weekdays field lets one entry restrict itself to specific days of a
temporalId that spans more calendar days than actually share its
content (needed by the Easter content this unblocks, added next).
Built as a general-purpose feature, not scoped to Easter specifically
— any temporal feast that wants its own proper psalms/lessons can now
reach this path by adding a named-override entry.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01X26BkFT3ARbtUGXuBmzUmD
Semiduplex and Duplex should share the same 3-nocturn/3-psalms-each
shape (distinct from Sunday/Duplex-majus+'s 6/6/3-plus-canticles
scheme) — the previous Duplex floor meant a Semiduplex winner like
St. Stephen of Hungary wrongly fell all the way through to the
1-nocturn ferial branch.
New threshold is a separate check from isDoubleOrHigher, which drives
the unrelated antiphon-doubling rule elsewhere and stays floored at
Duplex — the two thresholds only coincided by accident before this
change. No new content authoring was needed: ferialPsalmodyThreeNocturns
already chunks the ferial 9-psalm pool into 3/3/3 as the fallback for
a plain-Duplex winner with no override authored, and Semiduplex
winners now route into that same already-working branch.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01X26BkFT3ARbtUGXuBmzUmD
The 1-nocturn ferial branch was rendering a V./R. versicle stuck
mid-list, as if marking a nocturn boundary that doesn't exist in a
single continuous nocturn. Live-verified against the reference engine
(Pofficium.pl, Monastic Tridentinum 1617) for Wednesday and Thursday:
the real office runs straight from the last psalm to the Capitulum,
with no versicle anywhere in the psalmody. The embedded mid-list V./R.
line in the source Psalmi matutinum.txt is inert there and was never
actually emitted by the engine — matins-ferial-antiphons.yml's
versicle field was a documented-but-unverified assumption, not real
content.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01X26BkFT3ARbtUGXuBmzUmD
Several psalm files carried leftover inline markers like (6a), (17a),
(32) from the source text (e.g. Ps 67:2-19). vu doesn't number verses
inline anywhere else, so these were artifacts, not content.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015yHHNsbzD8jLkkmVuJpAcC
Passion week's ferias (Mon-Sat before Palm Sunday) fell through to
passiontide's bare ordinary-feria default, letting any real feast win
outright regardless of rank. Live-verified against the reference engine:
swept every date that can land in this window across a century of Easter
dates and found zero sub-Duplex winners -- every real winner is Duplex+.
This was letting vu's own low-ranked calendar saints (e.g. Ss. Tiburtius/
Valerian/Maximus, St. Anicetus) trivially win there and wrongly restore
the responsory's closing Gloria on what should stay a penitential day.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015yHHNsbzD8jLkkmVuJpAcC
The Monastic-rite versicle "V. Dómine, lábia mea apéries. R. Et os
meum annuntiábit laudem tuam" was entirely absent from Matins' opening
block — only the shared "Deus in adjutórium" versicle was modeled.
Authored per the reference engine's "iij" rubric (recited three times,
after Deus in adjutórium, before Psalm 3), with the English rendered
as imperative ("O Lord, open Thou my lips") rather than future
indicative.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015yHHNsbzD8jLkkmVuJpAcC
Replaced the multi-day cascading queue with the actual intended design:
only candidates at or above duplex-majus are worth carrying out of the
Palm-Sunday-through-Low-Sunday span at all - below that, a candidate
simply lapses for the year. Of those that clear the floor, the Nth day
after Low Sunday celebrates the Nth one (native-date order), winning
outright with no rank fight, demoting whatever's natively there to a
commemoration - one per day, positional, not a free-day search.
Caught a real bug while re-verifying: the ordinary single-hop transfer
check was independently leaking Low Sunday's own signal into the next
day, letting a below-floor candidate bypass the new gate. Fixed by
excluding the whole span from that path.
Inventoried the only duplex-majus+ candidates that can ever fall in the
reachable window (St. Benedict, St. Gabriel the Archangel, the
Annunciation, St. Mark, Ss. Philip & James), then bumped St. Patrick's
own rank to duplex-majus by direct instruction, adding him as the 6th.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017XMSokiTD2Qc5vPP2YQu3Q
The Easter octave cascade built last commit reused the existing
"duplex-majus+ still gets a bare commemoration" branch unchanged,
trusting its own citation (St. Mark within the Easter Octave). That
citation was a misreading. Live-verified: the Annunciation (Duplex I.
classis) transfers with zero commemoration on Holy Thursday itself, and
Chair of St. Peter at Antioch does the same on Ash Wednesday - the
exception was wrong everywhere this category is used, not just Holy
Week. privileged-feria-major now matches privileged-sunday exactly:
unconditional transfer, no exceptions, any rank.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017XMSokiTD2Qc5vPP2YQu3Q
Closes the tabled "Easter's own octave" mechanism gap. resolveDay only
ever checked yesterday/tomorrow for an inbound transfer - a single hop -
so anything impeded in Holy Week just vanished rather than reappearing
after the Octave. Live-verified the real behavior first (2033 proof
year): a candidate skips the whole privileged span and keeps walking
forward, FIFO by native date, until it lands; a transferred-in candidate
never gets the "still gets a nod" commemoration a native occurrence of
the same rank would along the way (confirmed with the Annunciation, zero
commemoration anywhere in the span). Also fixed Low Sunday itself, which
needed the same privileged-sunday treatment as Easter Day.
Built resolveForwardTransferLanding: a bounded queue simulation that
reuses applyIncomingTransfer unchanged per hop - its existing
privileged-feria-major no-op was already the right single-hop refusal,
it just needed a real cascade around it instead of a dead end.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017XMSokiTD2Qc5vPP2YQu3Q
Following the Ascensiontide fix, swept every remaining unverified Sunday/
feria precedence tier against the live reference engine:
- commemorations.ts's privileged-sunday rule was itself wrong: a
duplex-majus+ candidate was given a bare commemoration instead of
transferring, never actually verified. Live-checked the Immaculate
Conception (transfers off Advent II) and the Annunciation (transfers
off Palm/Easter Sunday) - confirmed correct by direct instruction.
privileged-sunday now transfers every rank unconditionally.
- eastertide's own Sunday tier was backwards (privileged-sunday instead
of ordinary-sunday) for the ordinary Sundays after Easter - live-
verified twice (Finding of the Holy Cross, St. Mark both win outright).
Easter Day itself still needs the stronger tier, now special-cased
directly since Sundays never consult the offset table.
Six existing tests updated to reflect real, live-verified behavior - each
had the old wrong "commemorated in place" assumption baked into its
expected value. Remaining unverified entries (Vigil of Pentecost/
Christmas, Corpus Christi/Sacred Heart) documented as such rather than
silently assumed correct.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017XMSokiTD2Qc5vPP2YQu3Q
The Sunday within the Octave of Ascension was letting a Duplex saint win
outright (ascensiontide's Sunday was miscategorized ordinary-sunday
instead of privileged-sunday, the only "great octave" season not already
so classed). Live-verified the reference engine keeps the Sunday primary
and transfers the saint instead. Fixed the category, then authored the
Sunday's own wholly proper Matins (a different psalm scheme entirely, not
a seasonal overlay) via a new per-temporalId override layer.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017XMSokiTD2Qc5vPP2YQu3Q
Same seasonal-overlay shape as Advent, live-verified against the reference
engine across 5 Paschaltide Sundays. Generalized sundayNocturnFor to a
season-keyed lookup covering both Advent and Paschaltide.
Confirmed two related non-gaps while investigating: Christmastide/post-
Epiphany Sundays have no seasonal block in the source, and a Duplex+ saint
winning outright on a Sunday deliberately still uses the fixed Sunday
psalmody per this app's own documented design (readings still surface the
saint via the separate pool) — neither needs a fix.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017XMSokiTD2Qc5vPP2YQu3Q
Nocturns I-II had no seasonal override mechanism at all, and Nocturn III's
own Septuagesima-Palm override didn't cover Advent — live-verified against
the reference engine that Advent has its own seasonal antiphons/versicles
across all three nocturns, identical on all 4 Advent Sundays.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017XMSokiTD2Qc5vPP2YQu3Q
It was hardcoded to "Allelúja, allelúja, allelúja" year-round. Each of
the 9 Sundays from Septuagesima through Palm Sunday has its own
Gospel-tied antiphon instead, live-verified against Monastic Tridentinum
1617; Lent 1-4 also swap in their own canticles, and Lent 1-4 plus
Passion/Palm Sunday swap in their own versicle. New
matins-sunday-nocturn3-overrides.yml, keyed by temporalId and merged
over the base nocturn3 group by hours/matins.ts's new
sundayNocturn3For — only the real-Sunday branch uses it.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017XMSokiTD2Qc5vPP2YQu3Q
Records the sourcing detail for the one-antiphon-per-psalm change in
the dated build log, and adds a mechanism-gap entry for Sunday
Matins' Nocturn 3 antiphon: hardcoded to "Allelúja" year-round but
confirmed live against Monastic Tridentinum 1617 to need its own
proper antiphon on each of the 9 Sundays from Septuagesima through
Palm Sunday — collected but not yet authored.
Every cross-psalm pair previously shared one antiphon under just the
first psalm, which read inconsistently once psalter-distribution.yml's
reslice left some pairs intact and split others. Each pair is now two
single-psalm groups: the psalm whose own words the shared antiphon
actually quoted keeps it, and its former partner gets a real antiphon
borrowed from elsewhere in the reference corpus where one exists, else
one composed fresh from its own opening verse.
The rest-of-year temporal nocturn-readings sweep was testing the Monday
after each named Sunday, relying on the now-removed leak to prove content
reachability for dates with no separately-authored ferial content -
repointed to the actual Sunday date. Updated the 2026-08-24 suppression
test's ferial case to assert the leak no longer happens, and added a
regression test pinning the original 2026-09-01 bug report.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017XMSokiTD2Qc5vPP2YQu3Q
A plain feria within an ordinary week was pooling that week's Sunday's own
patristic homily and responsory verbatim into its single nocturn, because
day.winner.id (added unconditionally) equals the Sunday's own temporalId on
an ordinary day, bypassing the existing suppression gate. Both the
day.winner.id/commemoration shortcut and the plain temporalId/month-week
pool are now gated on threeNocturns, so a ferial day only draws on its own
day's content.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017XMSokiTD2Qc5vPP2YQu3Q
Kyrie eleison and Pater Noster now render every day regardless of rank
(minor-hour-kyrie-pater); the further Preces V/R versicle pairs are
ferial-only, dropped on Sunday and any sanctoral winner.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017XMSokiTD2Qc5vPP2YQu3Q
september-sanctoral.test.ts: Sept 1 now resolves to a plain temporal
day with no commemorations; the Raymond Nonnatus transfer-collision
case now wins uncontested since the native Sept 1 saints are gone.
vespers.test.ts: the semiduplex-keeps-its-own-Vespers-against-a-
lower-ranked-tomorrow regression test used Raymond Nonnatus vs. St.
Giles as its example pair; re-pointed to St. Agatha (semiduplex) vs.
St. Dorothea (simplex), a different adjacent pair that exercises the
same concurrentia rule.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017XMSokiTD2Qc5vPP2YQu3Q
User decision to carry no saints on Sept 1. Data files for both
saints (and their propers/commemoration bundles) are left in place,
restorable by re-adding the sanctoral-calendar.yml list entry, same
pattern used when St. Hermes was dropped from 8/28.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017XMSokiTD2Qc5vPP2YQu3Q
A Vigil never has a fixed proper Magnificat antiphon in the real rubric
-- its Rule block ("ex C1v; ... Laudes 2") always borrows whatever the
current ferial weekday's own antiphon is, live-verified across 4
different years (2025-2028) at the Vigil of St. Andrew's own First
Vespers. getMagnificatAntiphon now special-cases saint.rank === 'vigil'
to fall through to the same weekday-default table the plain temporal
branch already used, rather than trying (and missing) a fixed Common
lookup.
Closes the 11th and last Common-category Magnificat antiphon gap; see
TODO.md for the full writeup, including how the first pass wrongly
closed this one as "not a gap" before being asked to double check.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014QEe9oTbn1UjQVAYcCoW1a
getMagnificatAntiphon's Common-category fallback
(magnificat-antiphon-${benedictusCommon}) had never been authored for
any of the 11 real Common categories saints fall back to -- only the
temporal-cycle side of this gap had been closed previously. Sourced
each from its Commune file's own [Ant 3] (Second Vespers), live-verified
against Ss. Placid and Companions, St. Blaise, and St. John Gualbert's
real evenings.
common-of-a-vigil (the 11th category) is handled separately, in code --
see the next commit.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014QEe9oTbn1UjQVAYcCoW1a
benedictus-antiphon-common-of-a-confessor.yml and -the-bvm.yml were each
reading their Commune source file's [Ant 1] (the Common's First Vespers
antiphon), not [Ant 2] (the real Lauds antiphon) -- discovered while
sourcing the sibling Magnificat antiphon set. Live-verified wrong via St.
Alexius's real Lauds query (renders "Euge, serve bone", not "Similabo
eum").
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014QEe9oTbn1UjQVAYcCoW1a
keepsOwnSecondVespers used a fixed duplex-2-classis floor on today's rank
alone, so a lower-ranked but still-real sanctoral winner (e.g. Simplex)
tomorrow could still steal First Vespers from a higher-ranked-but-below-the-
floor winner today (semiduplex St. Raymond Nonnatus losing to simplex St.
Giles on 2026-08-31). The real "De Concurrentia Officii" rubric compares the
two days' ranks directly, with ties going to tomorrow — replace the floor
with that comparison when both sides have a real sanctoral winner, keeping
the floor only as a fallback against a plain Sunday.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014QEe9oTbn1UjQVAYcCoW1a
Her Aug 31 commemoration (e.g. under St. Raymond Nonnatus) was falling
back to a bare collect with no Ant./V./R. since she was authored after
the earlier commemoration-bundle sweep and never got her own.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QC6jHsj75xcsTkapqJR6fD
Unicode ℣/℟ (U+2123/U+211F) were mixed unpredictably with plain V./R.
across ~550 proper YAML files, sometimes within the same file. Screen
readers can't announce the unicode glyphs. Replace all instances with
plain text.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QC6jHsj75xcsTkapqJR6fD
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
Adds transferredFrom to both DayWinner's sanctoral variant and
Commemoration's sanctoral variant, set in calendar/index.ts's
applyIncomingTransfer whenever a transferred-in candidate actually lands
(wins outright) or ends up merely commemorated (lost a collision, or fell
below the landing day's own threshold) — previously this signal was
discarded after resolveDay used it once to decide the winner, so nothing
downstream could tell a transferred feast apart from a native one.
Also adds transferredAway to LiturgicalDay, set from a date's own native
occurrence when its candidate couldn't be kept there at all, so a UI can
note "this office moved elsewhere" without asserting a specific landing
date (which a later, unmodeled multi-hop chain — e.g. into Holy Week —
could get wrong).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013g7AsSvMD9oazR17f3BxLz
Raymond's rank deflation (a1ed3d2) changes cascading behavior in years
where Aug 31 is a Sunday: he now loses to the Sunday outright (needs
duplex+ to win, per Divino Afflatu's ordinary-sunday threshold) and
transfers forward onto Sept 1, winning the collision there over St.
Giles. Move the original clean-year Giles test to 2026 and add a new
test documenting the 2025 transfer/collision case explicitly.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013g7AsSvMD9oazR17f3BxLz
Canonized 1657, well past the ~1600 late-canonization threshold; per
project policy his source Duplex rank gets deflated one tier, same
treatment already applied to St. Rose of Lima.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013g7AsSvMD9oazR17f3BxLz
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
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
Claimed Semiduplex wins outright over the Advent feria with the feria
commemorated in return -- true when written, but no longer true after
the later octave-wins-class rework. Dec 11 falls inside the Immaculate
Conception octave, whose elevated wins:duplex threshold now outranks a
plain Semiduplex, so Damasus is actually demoted to a commemoration
and the Advent feria keeps the day. No behavior change, found and
confirmed live while authoring St. Juan Diego's neighboring entry.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EwsZQMjALCvy7u9tFDWmuQ
Sourced from the reference engine's national-calendar file. Own proper
collect, Ant 1, and Matins Nocturn 2 narrative reading (Juan Diego's
apparition account), same scope as the Our Lady of Lourdes feast --
Common of the BVM (C11) carries the rest. No canonization-deflation
question here (an apparition title, cultus confirmed 1754/1910). At
Duplex she ties the Immaculate Conception octave's threshold on a
non-closing day, which favors the occurring saint, so she wins Dec 12
outright.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EwsZQMjALCvy7u9tFDWmuQ
Sourced from the reference engine's national-calendar file (canonized
2002, outside both Tridentine 1906 and Divino Afflatu 1954). Source
rank "Duplex optional" has no FeastClass slot; mapped to Duplex then
deflated one tier to Semiduplex per the late-canonization rule. Live-
verified he's actually a commemoration, not the day's winner: Dec 9
falls inside the Immaculate Conception octave, whose elevated
wins:duplex threshold outranks a plain Semiduplex.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EwsZQMjALCvy7u9tFDWmuQ
She was previously excluded on a mistaken "post-1617, out of scope"
reading that conflated vu's hour-structure track (Monastic 1617) with
its calendar/saints track (Tridentine 1906 + Divino Afflatu 1954,
which has included her since her 1671 canonization). Her source rank
of Duplex is deliberately deflated to semiduplex, correcting for the
rank creep that made "new canonization" and "Duplex" a near-automatic
pairing from the Baroque period on. She wins Aug 30 outright over Ss.
Felix and Adauctus, who keep their existing commemoration.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EwsZQMjALCvy7u9tFDWmuQ
Extends tests/hours/matins.test.ts's proof-date table to cover all 28
newly-authored temporal ids end-to-end, including Easter and Pentecost
Sunday. Documents the full sweep in TODO.md, including the
easter-sunday/pentecost-sunday misdiagnosis and correction so the
"1 nocturn in the source" vs. "no content" distinction doesn't get
re-litigated later.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ME8QNPdEmHSbSx1r6VmDRG
Both feasts' source keeps the ancient single-nocturn, 3-lesson Matins
form (unlike every other Sunday in the surrounding sweep), which was
initially mistaken for "no content exists" and left unauthored. Their
Lectio1-3 actually carry a real Gospel incipit plus a full Pope St.
Gregory the Great homily each (Homily 21 on Mark 16:1-7 for Easter,
Homily 30 on John 14:23-31 for Pentecost) — a nocturn-count difference,
not a content gap. Authored as nocturn:1 content, same as this app's
existing Ember-day single-nocturn files; this app's own threeNocturns
rule still renders 3 nocturns for these Sundays regardless.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ME8QNPdEmHSbSx1r6VmDRG
Extends the post-Pentecost nocturn-readings sweep to the other 26 temporal
ids spanning the rest of the liturgical year — Advent, Christmastide,
post-Epiphany, Septuagesima through Lent, Passiontide/Holy Week, and
Eastertide — transcribed directly from the reference engine's own
per-season Tempora source files.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ME8QNPdEmHSbSx1r6VmDRG
Was inline-editing "DONE" notes into the top-of-file priority list
instead of following TODO.md's own convention: completed work moves out
of "Open work" entirely and into a dated ### entry at the bottom. Fixes
that for everything done today that never got a proper log entry --
the Vigil of Christmas content, the Advent IV/Dec 24 Sunday collision
resolution, the full sanctoral commemoration bundle sweep (218/218),
and closing out the last two Vespers Magnificat "gaps" as not gaps.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ME8QNPdEmHSbSx1r6VmDRG
Live-checked both instead of leaving them as unauthored: the Vigil of
Christmas never has its own Vespers in the real rite (Dec 23 evening is
plain Advent-4 ferial Vespers, not First Vespers of the Vigil; Dec 24
evening is always Christmas's own), and marian-saturday's Commune file
has no Vespers antiphon of its own either, because a Saturday's Vespers
is always structurally ceded to the following Sunday. Neither is missing
content to author -- there's no historical text for either case.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ME8QNPdEmHSbSx1r6VmDRG
Corrected an earlier "no source exists" claim by live-querying real
commemorated instances of the Vigils of St. Lawrence and the Assumption
across six different weekdays. Their source files' own [Rule] block
("Versum Feria") turns out to be a real, sourced mechanism, not an
absence: a mere commemoration doesn't reuse the vigil's own antiphon at
all -- it borrows the plain ferial day's antiphon/versicle instead, and
that antiphon quotes the Benedictus canticle in weekday sequence (same
shape vespers-magnificat-antiphons.yml already uses for the Magnificat).
The versicle is a single fixed text. Sunday has no entry: a Vigil
transfers off an ordinary Sunday rather than ever reaching a
commemoration there, confirmed live as well as already true in this
app's own precedence rules.
sanctoralCommemorationPart now takes weekday and resolves this
dynamically for common-of-a-vigil saints, rather than needing a static
per-saint file. All 218 saints with an authored collect now have a real
commemoration antiphon.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ME8QNPdEmHSbSx1r6VmDRG
Follow-up to the earlier bulk pass. Sourced the remaining category
templates: BVM feasts (13, reused the already-verified BVM suffrage
versicle plus each feast's own antiphon), angels (4, versicle inferred
from a saint file's own "structural category" note), Confessor-Doctor/
-Pope (7, resolved to C4a/C5a/C4b -- each inheriting its base category's
Ant2/Versum2 unchanged, no new content needed), several confessors and
apostles-plural (2, reusing already-sourced category versicles), and 5
fully-proper feasts (All Saints x2, Finding of the Holy Cross, Most
Precious Blood, Transfiguration, Our Lady of Ransom) given their own
real Ant2/Versum2 straight from their Sancti files.
213 of ~218 real saints now covered. Only the 5 Vigils remain open --
no dedicated Commune template exists for that category in the reference
engine, and FeastClass 'vigil' rarely reaches the commemoration code
path anyway.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ME8QNPdEmHSbSx1r6VmDRG
Found that 217 of 220 saints with an authored collect had no
${propers}-commemoration.yml bundle, so sanctoralCommemorationPart was
silently falling back to a bare collect with no antiphon whenever that
saint was merely commemorated rather than winning outright (user-reported
for St. Raymond Nonnatus and the Decollation of St. John the Baptist).
Bulk-fixed: reused each saint's own proper antiphon where authored, else
the standard de-Communi [Ant 2]/[Versum 2] for that saint's own Common
category. Confirmed via three already live-verified commemorations
(Ss. Felix and Adauctus, St. Clare, St. Sabina) that [Ant 2]/[Versum 2]
are the real tags used for a mere commemoration, not [Ant 1]/[Nocturn 2
Versum] as first assumed -- corrected St. Raymond Nonnatus's file
accordingly. 182 of ~218 real saints now covered; entries not
independently live-verified are marked draft. Remaining ~36 (BVM feasts,
angels, confessor-doctors/popes, vigils) tracked in TODO.md.
New tests/propers/commemoration-sweep.test.ts (182 cases) asserts every
bundle has a real antiphon+versicle+collect, never bare/missing.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ME8QNPdEmHSbSx1r6VmDRG
Same bare-collect-only gap as Ss. Felix and Adauctus: both saints were
falling back to a plain collect with no antiphon when merely commemorated
(today's and yesterday's Vespers respectively). Raymond has no proper
antiphon of his own, so uses the standard Common-of-a-Confessor-Not-Bishop
text; the Decollation reuses his own already-authored proper Lauds
antiphon, paired with the generic Common-of-a-Martyr versicle. Both
live/reference-verified.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ME8QNPdEmHSbSx1r6VmDRG
Their commemoration was falling back to a bare collect (only
ss-felix-and-adauctus-collect.yml existed, no combined Ant+V/R bundle),
so it rendered with no antiphon at Lauds when they're commemorated on a
Sunday (today, 2026-08-30). Live-verified against that exact date.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ME8QNPdEmHSbSx1r6VmDRG
When Advent 4's own Sunday lands on Dec 24, the Vigil of Christmas's
identity (rank, collect, Lauds antiphons, Matins Gospel nocturn) now
governs the day, matching the reference engine's own rule that a
privileged Sunday never displaces it. Unlike the reference engine's own
"no commemoratio" for this exact collision, Advent 4 is deliberately
still commemorated — the Sundays of Advent have real standing of their
own. Matins' three-nocturn Sunday psalmody is untouched either way,
since it's driven by weekday, not by which id wins.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ME8QNPdEmHSbSx1r6VmDRG
Non-Sunday Dec 24 previously just inherited Advent 4's ferial collect and
antiphons with nothing of its own. Import the Vigil's real proper content
from the reference engine (Duplex I classis): its own collect, all 5 Lauds
antiphons, the Benedictus antiphon, and Matins' Gospel (Matt 1:18-21) +
St. Jerome homily, wired via a new `vigil-of-christmas` temporal id scoped
to non-Sunday Dec 24 only. Advent IV landing on Dec 24 itself is untouched,
left as its own separate design question.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ME8QNPdEmHSbSx1r6VmDRG
advent-4's own remaining Magnificat-antiphon gap turned out to be a
mechanism problem, not missing content: Dec 17-23 always carries one of
the seven "O Antiphons" at Vespers, keyed to the calendar date itself,
not to whichever Advent week (advent-3 or advent-4, varies by year)
happens to govern that date -- a single per-week file was structurally
the wrong shape.
Added vespers-o-antiphons.yml (MM-DD keyed) and a new lookup tier in
getMagnificatAntiphon, checked unconditionally ahead of both the
per-week temporal file and a real sanctoral winner's own antiphon --
direct instruction: the O Antiphon wins this one text slot on these
seven dates regardless of who otherwise wins the day (e.g. St. Thomas,
Dec 21, still keeps every other part of his own office). Two of the
seven (O Clavis David/O Oriens, Dec 20-21) couldn't be live-verified --
every reference-engine track routes those two dates through St.
Thomas's Vigil/feast instead, which this app's own vigil-of-st-thomas.yml
already documents diverging from -- so they're marked draft rather than
verified.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ME8QNPdEmHSbSx1r6VmDRG
Every temporal id had its Lauds (Benedictus) antiphon authored, but only
3 of 57 had the Vespers (Magnificat) counterpart -- a whole-calendar
gap exposed by 2026-08-29's Vespers rendering with no Magnificat
antiphon at all. Sourced from the reference engine's static Tempora/
Sancti files, same convention and per-id reference dates already
established for the Benedictus antiphons.
advent-4 and marian-saturday are the only two ids still missing one
(neither resolves from the static files alone) -- follow-up commit.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ME8QNPdEmHSbSx1r6VmDRG
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
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
She was rendering as a bare collect (propers: null had no
sanctoral-commemoration fallback), and even that collect used
collect-c6's Virgin-and-Martyr text -- she was a matron, not a virgin.
Live-verified against Divino Afflatu: added collect-c7 (the Common's
own non-Virgin-Martyr template) and a full antiphon/versicle/collect
commemoration bundle.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018sPkfGLiq58pmmDoBWH5UX
Both Canticle of Moses entries carried a book-name parenthetical in
their display name, redundant with (and now, since the last commit,
duplicated verbatim by) the citation shown right underneath. Only one
canticle is ever displayed per day, so there's nothing to disambiguate
in the UI -- trim to plain "Canticle of Moses" for both.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018sPkfGLiq58pmmDoBWH5UX
canticleText() always returned the whole canticle's stored citation
("Deut 32:1-65"), even when only slicing out half its verses -- both
Saturday Lauds pieces showed the same full range regardless of which
half was actually being said. Derive the citation from the slice's own
first/last verse numbers instead, only when slicing; a full canticle's
stored citation is left as-is, since it already matches what's shown
and a formula can't reproduce an irregular one like
canticum-trium-puerorum's "Dan 3:57-88,56" (a non-contiguous liturgical
splice).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018sPkfGLiq58pmmDoBWH5UX
The user-facing citation field carried an explanatory aside
("liturgical division; split across two Lauds slots") instead of a
plain reference. Move that explanation into the file's own header
comment (which already existed) and leave citation as just "Deut
32:1-65".
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018sPkfGLiq58pmmDoBWH5UX
Confirms 2026-08-29 Lauds and 2026-08-28 First Vespers both keep the
ferial psalm numbers (Duplex, below duplex-majus) while still layering
on the feast's own proper antiphons -- and that an override group with
no matching ferial slot (Puellae saltanti / Arguebat) is dropped, not
substituted in.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018sPkfGLiq58pmmDoBWH5UX
Mirrors the Lauds fix: a proper override's psalm numbers stay gated at
duplex-majus+, while its antiphons still layer onto the ferial weekday's
own numbers below that rank, paired by group index.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018sPkfGLiq58pmmDoBWH5UX
A feast's own proper psalm-group antiphons should surface at any rank
(f3cf30c), but the actual psalm numbers sung should only substitute at
duplex-majus+, per real Tridentine/Monastic rubric — a lower-rank feast
keeps the ferial psalm cycle and only gets proper text layered onto it.
Below duplex-majus, merge the proper override's antiphons onto the
ferial weekday's own psalm numbers/canticle, paired by group index.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018sPkfGLiq58pmmDoBWH5UX
Live-verified against Divinum Officium (Tridentine 1906/1910 and
Divino Afflatu 1954, Sancti/08-29.txt) — the feast's own 5-antiphon
set ([Ant Vespera], reused verbatim at Lauds as [Ant Laudes]), now
wired through both the newly rank-agnostic Lauds/Vespers proper tier
and the new minor-hour derivation mechanism. Vespers gets only the
first four (mapped onto vu's Monastic-1617-structured four-psalm
Vespers, matching common-of-a-martyr.yml's own 109/111/112/115
baseline) — the reference's own fifth antiphon is a secular five-
psalm-Vespers detail that doesn't apply here, and is used only at
Lauds' Laudate psalm instead.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VFGHb4XMe6Wya4pGpPhEEi
resolveMinorHourAntiphon fell straight from a (rarely-authored)
dedicated per-hour proper file to the saint's generic Common-category
antiphon, skipping the real proper text a saint may already have as
its 5-antiphon Lauds/Vespers set. The reference engine's own
getanthoras() mechanism derives exactly these four minor-hour
antiphons from that same 5-antiphon set by fixed index (Prime/Terce/
Sext/None <- antiphons 1/2/3/5) whenever no dedicated per-hour proper
exists; vu had no equivalent, so any minor hour fell back to the
generic Common antiphon even when the feast's own text was already
sitting right there in its Lauds override.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VFGHb4XMe6Wya4pGpPhEEi
getPsalmodyOverrideFor bundled "which psalm numbers are said" and
"which antiphons are used" into one lookup, both gated behind the same
duplex-majus+ rank threshold. A saint's own authored proper antiphon
text should never be gated behind rank — only the weaker, generic
Common-category substitute has any business being rank-gated. Splits
the lookup into two independent tiers: the feast's own proper (any
rank, via the new getPsalmodyProperOverrideId) tried first, then the
existing duplex-majus+ Common-category fallback — mirroring the split
getMinorHourOverrideId already made for the chapter/responsory/hymn/
versicle bundle.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VFGHb4XMe6Wya4pGpPhEEi
renderCitation deduplicated a text's citation values across every
stored language, not just the ones actually being rendered. Once a
citation's La/En abbreviations genuinely differ (e.g. "Jac 1:12" vs.
"Jas 1:12", or "Sap. 10:10" vs. "Wis. 10:10"), both survived the
dedupe and got joined together regardless of view mode, leaking the
other language's abbreviation into a single-language display.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VFGHb4XMe6Wya4pGpPhEEi
sanctoralCommemorationPart() only ever checked a commemorated saint's
own propers, so a saint like St. Sabina — no propers of her own, but
a real collectCommon + collectName template fallback, verified text
and all — rendered as an outright "missing" commemoration. The day's
primary-collect resolver (getDayCollect) already had this same
collectCommon/collectName fallback tier; this brings the
commemoration path in line with it.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VFGHb4XMe6Wya4pGpPhEEi
canticleText() built the resolved text/status from the weekday
canticle's verses but never copied the source YAML's own citation
field across, so every Lauds canticle rendered with no book/chapter/
verse citation at all despite the data already carrying one.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VFGHb4XMe6Wya4pGpPhEEi
nativity-of-st-john-the-baptist.yml's `common` field (common-of-an-apostle)
was the same mistaken placeholder as the Beheading's, but this feast
needed a different fix: the reference engine assigns it no Commune at
all -- every [Rule] line in Sancti/06-24.txt and SanctiM/06-24.txt points
to proper text, never a Commune category -- so there's no better-fitting
id to swap in either.
Authored the real proper content that was still falling through to the
apostle Common, live-verified against the local Divinum Officium CGI
instance (Monastic Tridentinum 1617):
- Matins invitatory antiphon ("Regem Præcursóris Dóminum")
- Matins hymn ("Antra desérti téneris," with the Monastic doxology
substitution already used by this feast's own Lauds/Vespers hymns)
- Full Matins Nocturn 1-3 psalmody + nocturn versicles (Nocturn 1/2 reuse
St. Lawrence's own shared psalm-number pool per "Psalmi Dominica";
Nocturn 3 reuses St. Lawrence's own canticle scripture files verbatim,
since the citations are identical)
- Vespers psalmody (only 4 psalm slots at this Monastic rank, not 5 as a
naive reading of the raw Ant Vespera list would suggest)
- Nocturn 2/3 responsories on the existing nocturn-readings file
`common: common-of-an-apostle` is left in place as a fully inert
placeholder (a non-optional field with no better id available) with an
updated comment explaining why.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WvnBMq7HGfXRyMsVLu2v4D
decollation-of-st-john-baptist.yml's `common` field was set to
common-of-an-apostle -- a mistaken placeholder (he isn't an apostle) that
leaked into the Aug 29 Matins invitatory and the Lauds/Vespers office
bundle. Corrected to common-of-a-martyr, matching the file's own already-
correct `minorHoursCommon` field.
Also corrected a stale comment in the feast's own nocturn-readings file
claiming neither Nocturn 2 nor 3 has a proper responsory: Nocturn 2's own
closing responsory is genuine proper text (not a Commune reference like
Nocturn 3's), and is now authored.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WvnBMq7HGfXRyMsVLu2v4D
compline-preces.yml was already the secular Tridentine 1906/1910 text
word-for-word (verified live against both tracks), so no text change
was needed there. The actual gap: both Monastic and secular tracks drop
Compline's Preces to nothing on a Double-or-higher, unlike Vespers/Lauds
which fall back to a short form. Per explicit request, this is now a
deliberate vu-specific deviation from both tracks: on a Double, Compline
substitutes the same short Kyrie/Pater-Noster litany Vespers already
falls back to on Sundays/feasts, instead of showing nothing.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AHXJAWVAabCKX1JgmDh4Rn
Records which of the 24 SaintRecord.common categories are now covered
(authored or aliased), which one real content gap remains
(common-of-a-dedication's Lauds canticle text), and why
common-of-a-vigil isn't a gap at all (structurally unreachable by rank).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AHXJAWVAabCKX1JgmDh4Rn
Covers common-of-an-angel (both hours, via Guardian Angels' First
Vespers/own day), common-of-a-dedication (Vespers, via its own day),
and the common-of-an-evangelist/several-women-martyrs aliases (one
date-based test via St. Mark where a real date is reachable, direct
unit tests where every carrier saint is below this override's rank
threshold or shadowed by an existing per-feast override).
Two pre-existing Vespers test dates had to move again for the same
reason as the previous session's fix: newly-correct category coverage
retroactively changed what a "clean ferial"/"own kept evening" date
actually resolves to. 2026-09-28 (anticipates St. Michael's own First
Vespers) moved to 2026-10-12; Guardian Angels' own day (Oct 2) moved to
Oct 1 (her First Vespers) once tracing resolveEveningDay showed she
doesn't keep her own Second Vespers there.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AHXJAWVAabCKX1JgmDh4Rn
St. Mark's and St. Luke's own reference-engine rule files say "ex C1a",
byte-identical to plain C1 (Apostles) -- an Evangelist-not-Apostle
reuses the Apostles' own scheme, not a separate one.
Ss. Perpetua and Felicity's own file says "vide C7b"; C7b returns the
same four psalm numbers and group/canticle/Laudate antiphons as plain
C7 (Holy Woman) -- its only real difference is the Magnificat/Benedictus
antiphon, handled by a separate, already-existing mechanism, not this
psalmody table. (First-draft duplicate category files for this id were
written and then deleted once this was caught by diffing line-by-line
against the live output.)
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AHXJAWVAabCKX1JgmDh4Rn
Genuinely its own scheme (votive=C8) -- unlike every other Common,
Vespers' numbers (109/110/111/147) don't match any other category's
pattern. Lauds' own version is left unauthored: its OT canticle
("Canticum Ieremiae") has never been transcribed anywhere in this app,
so that file isn't written rather than guessed at (see TODO.md).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AHXJAWVAabCKX1JgmDh4Rn
No dedicated numbered Common exists for Angels in the reference engine;
St. Michael's own rule says plain "Psalmi Dominica" with his own proper
antiphons, which Guardian Angels (no proper of her own) reuses
byte-for-byte at both hours. Authored as a reusable category so any
future proper-less Angel addition benefits automatically.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AHXJAWVAabCKX1JgmDh4Rn
Vespers psalms on Mon-Sat were silently following the anticipated
First-Vespers day's weekday instead of the actual calendar date's, since
resolvePsalmody reused resolveEveningDay's result (correct for
propers/office, which legitimately borrow tomorrow's identity, but wrong
for the ferial psalm cycle, which has no per-feast override of its own).
Also adds a duplex-majus+ Common-category psalmody override to both
Lauds and Vespers, mirroring Matins' existing saint/category mechanism.
Lauds already had a per-feast-only override; Vespers had none. 8 of 24
Common categories authored for both hours, live-verified against the
reference engine.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AHXJAWVAabCKX1JgmDh4Rn
Replaces the lesson+isGospel flag with a dedicated 'gospel' ResolvedPart
carrying an optional nested homily. Previously a Gospel pericope and its
patristic homily were two separate pool entries, which the front-light
1/1/rest nocturn slicer could split into different nocturns purely by
index. buildReadingPool now folds an isGospel:true reading and its
immediately-following same-nocturn homily into a single pool entry, so
the pairing is structural rather than a convention the slicer has to
honor. hour-view.ts renders the pair as one visually grouped section.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D1LqodVbQdQVoTGPz9sSw4
When tonight anticipates tomorrow's First Vespers, resolvePsalmody was
keying off the borrowed day's weekday instead of today's actual calendar
weekday, silently shifting the fixed 6-day psalm rotation. The psalm
cycle has no per-feast override and must stay on today's own place in
the rotation regardless of whose office governs the evening's propers.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D1LqodVbQdQVoTGPz9sSw4
The Matins bullets repeated a lot of already-done detail inline; that
detail already lives in its own dated build-log sections. Left concise
pointers to those sections and kept only the actual remaining gap.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D1LqodVbQdQVoTGPz9sSw4
The priority-list stub pointed at a separate list of the same name near
the end of the file; consolidated into one concise section under item 3
and tightened the wording. Renamed the now-emptied end-of-file heading
to "Recently closed items" to accurately describe the done entries still
parked there.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D1LqodVbQdQVoTGPz9sSw4
Matins psalmody overrides (all 19 categories), the Commemoratio Octavae/
fixed Lord's-BVM feast exclusions, and the Lauds/Vespers bundle were
completely finished with no open remainder, but still sat in the
"bulk content pending" section. Gave them their own dated done sections
so that section only lists genuinely partial work.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D1LqodVbQdQVoTGPz9sSw4
St. Hermes removal, psalm-list accessibility fix, Te decet laus, and the
ferial Preces psalm drops were all done but still listed under the open
Mechanism gaps section; moved to their own dated done entries so that
section only holds actually-open work.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D1LqodVbQdQVoTGPz9sSw4
The Te decet laus entry flagged the historical Gospel-read-again-in-full
step as unmodeled; that's moot now that Nocturn 3 will display the Gospel
pericope alongside its patristic homily.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D1LqodVbQdQVoTGPz9sSw4
The pairing of a Nocturn 3 Gospel pericope with its patristic homily
should render as two distinct, visually-distinguished parts rather than
one concatenated lesson; bible-plan Gospel readings stay pericope-only.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D1LqodVbQdQVoTGPz9sSw4
Screen readers announced "list, N items" for continuous liturgical text
that isn't conceptually a list; styling is class-based so no CSS changes
were needed.
When already viewing today, clicking Today now snaps the selected hour
to whatever hour would be default for the current wall-clock time
(same as a fresh visit to /vu); when viewing another date it still just
jumps to today while preserving the current hour, as before. The link's
href is built from the same todayTargetHour() decision as the click
handler, so right-click -> open in new tab lands on the same target
instead of following a stale href.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CbHcmjnqQPJZ1i2yoebmp5
He always lost to St. Augustine and, per the generous-commemoration
design, only ever surfaced as a commemoration, so removing him from
the date list suppresses him entirely. st-hermes.yml and its collect
are left in place, restorable by re-adding the list entry.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SVigobgkuEayoGdEiubBdt
This Duplex I classis feast (instituted by Pius IX, 1847) didn't exist
anywhere in vu's calendar. Dated per the older, pre-1955 reckoning --
Wednesday after the 2nd Sunday after Easter (Easter+17) -- rather than the
1913 Pius X reform's later placement on the 3rd Sunday after Easter
itself, per direct instruction. Mechanism: one new temporal-feast record
plus a new EASTER_OFFSET_STARTS table entry (the only code change --
everything else is the existing generic anchor-driven resolution).
Authored the feast day's own Matins reading and its full 7-day octave
(days 2/3/4/6/7/8; day 5, the Sunday, has no override in the source at
all, correctly left unauthored).
Fixed one real collateral effect: an existing fixed-year calendar test
(St. Pudentiana vs. St. Peter Celestine, 2038-05-19) broke because that
date now falls on this octave's own closing day, tying Celestine's rank
against the octave's threshold -- the app's own documented tie-break
correctly favors the octave that year. Moved the proof date to 2033.
Also bumped the PWA precache file-size cap (6 MiB -> 8 MiB) since this
content pull pushed the main bundle past the previous limit.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PhysogLP9eCeKS7JEwRi6y
St. Stephen, St. John the Apostle, and Holy Innocents each have an 8-day
octave overlapping Christmas week, but their intervening days (2-7) carry
no distinct Lectio content in the source -- every suffix file checked at
Dec 27-31 is a bare @Sancti/MM-DD alias, correctly left unauthored, same
pattern as the fixed-date octaves. Only each octave's own closing "In
Octava" day (Jan 2/3/4 respectively) carries real content: St. Augustine's
sermon on Stephen + St. Jerome on Matt 23:34-35; St. Augustine's Tractate
36 on John + St. John Chrysostom on John 21:20-24; St. Augustine's first
sermon for Childermas + St. John Chrysostom on Matt 2:13-18.
This closes the octave-readings backlog entirely -- every fixed-date,
movable-feast, and Christmas-week saint octave has now been audited by
calendar date against the source.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PhysogLP9eCeKS7JEwRi6y
Ascension, Corpus Christi, and Sacred Heart octave content lives in the
reference engine's Tempora/ directory, keyed by liturgical offset from
Easter rather than calendar date. All three octaves' 7 non-feast days
(day 2 through the closing day) turned out to have genuine, distinct
patristic content -- unlike the fixed-date octaves, none were bare
vide-only days. 21 files authored across the three octaves.
Found and fixed one duplication along the way: each octave's own Sunday
day turned out to duplicate content already authored under a different id
in the earlier post-Pentecost temporal-cycle sweep, since a numbered
post-Pentecost Sunday and the Sunday within Corpus Christi's or Sacred
Heart's octave are structurally the same calendar day every year. Both
duplicate files (corpus-christi-octave-day-4, sacred-heart-octave-day-3)
were dropped after confirming the reading still surfaces via the existing
temporal id.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PhysogLP9eCeKS7JEwRi6y
Re-auditing the two prior sanctoral bulk passes (pre-1955 Tridentine track
and SanctiM Monastic-1617 track) turned up one saint missed by both:
St. Felix I (May 30), authored the same single-lesson shape as
st-pontian.yml. This closes the saint-level side of the Matins
patristic/hagiographic reading backlog.
Separately, auditing octave-day content by calendar date (the reference
engine keys it that way, not by octave-day-number) found two real gaps in
already-partially-authored fixed-date octaves: Immaculate Conception's own
closing day (Dec 15, Pius IX bull + St. Epiphanius homily) and Nativity of
the BVM's day 3 (Sep 10, St. Jerome on Matthew's genealogy -- no usable
reference English, own translation used, draft). Every other apparent gap
in these octaves was confirmed a correctly-empty vide-only day, not a
further gap.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PhysogLP9eCeKS7JEwRi6y
Two content/mechanism gaps flagged by the user: St. Hermes (8/28) always
loses to St. Augustine and only ever surfaces as an unwanted commemoration
(fix is a one-line sanctoral-calendar.yml edit, data files untouched); and
psalm verses render as a real <ol>/<li> list, which screen readers announce
disruptively per-verse for content that isn't conceptually a list.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XdfjdTvfwgxyaC8BUTEvWy
Closes the "Matins psalmody overrides" open-work item entirely -- all
20 Common categories (17 authored/aliased in the category sweep, plus
proper saint-level content for the 5 angel feasts and St. Scholastica)
now resolve real Matins psalmody instead of the plain ferial fallback.
Records the fourth pass's secular-track method for Widow/Several Women
Martyrs.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PhysogLP9eCeKS7JEwRi6y
Closes the last 2 of 20 Matins psalmody-override Common categories. The
Monastic-1617 track alone was a genuine dead end here -- no real saint
in the Monastic corpus ever invokes this Common, so there was no live
Monastic rendering to verify a hand-resolution against. Resolved per
explicit user authorization to source secular-track content directly,
consistent with this project's existing blended-source design: combined
a direct read of CommuneM/C7.txt's own psalm-substitution rule with one
live secular query (version=Tridentine - 1906) against St. Bridget of
Sweden to supply the actual antiphon text.
Nocturn 1/2 end up near-identical to common-of-a-virgin-martyr.yml (one
antiphon substituted, since Virgin-Martyr's own text literally calls her
a Virgin) -- too close to alias, not close enough to merge, same
reasoning as common-of-a-confessor vs. common-of-a-confessor-bishop.
Nocturn 3 is genuinely Widow-specific (Parable of the Pearl of Great
Price); its English has no source counterpart and is self-translated,
marked draft-quality.
common-of-several-women-martyrs aliased here: its own Monastic template
whole-file-inherits the same base with no genuine Matins-nocturn
override anywhere in the traced redirect chain.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PhysogLP9eCeKS7JEwRi6y
Records the Virgin/Confessor-Doctor/Confessor-Pope closures via the
source-file-first method, St. Scholastica's proper-content case, and
corrects a prior pass's wrong claim that Ss. Perpetua and Felicity are
"only ever Commemoratio" -- their own source file shows rank Duplex.
Widow and Several Women Martyrs remain blocked for a sharper reason:
CommuneM/C7a.txt and C7b.txt are synthetic patchworks that no real
saint anywhere in the Monastic corpus ever actually invokes, so there's
no live rendering to verify a hand-resolution against.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PhysogLP9eCeKS7JEwRi6y
Read the reference engine's CommuneM/*.txt templates directly (C6a,
C4a, C4b), rather than trial-and-error live queries, to confirm each
whole-file-inherits its parent Common's Matins nocturn psalmody with no
override. Live-verified byte-identical against St. Scholastica (Virgin
-> Virgin-Martyr) and St. Ambrose (Confessor-Doctor -> Confessor-Bishop);
Confessor-Pope -> Confessor-Bishop aliased on the source redirect chain
alone, since none of vu's three Pope-Confessor saints exist on the
Monastic-1617 calendar at all (matching the convention their own files
already use for every other hour).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PhysogLP9eCeKS7JEwRi6y
Her psalm numbers and all three nocturn versicles match Common-of-a-
Virgin(-Martyr) exactly, but her page is headed "ex Proprio Sanctorum"
and her antiphons genuinely narrate her own rainstorm-miracle episode --
real proper content, not Common text, so she gets her own saint-level
file rather than being left to the Common fallback.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PhysogLP9eCeKS7JEwRi6y
lauds-capitulum-st-gabriel-archangel.yml carried Apparition of St.
Michael's own capitulum ("Significávit Deus...") under Gabriel's id --
found in passing while authoring his Matins psalmody. His real
capitulum, read directly from the reference engine's Sancti/03-24.txt
([Capitulum Laudes]), is Daniel's own account of the angel's visitation
(Dan 9:21-22), genuinely proper to Gabriel, not shared with Apparition
despite sharing the same hymn. No separate vespers-capitulum file exists
for this id, so the fix covers both hours via vespersCapitulumForOverride.
Updated tests/hours/lauds.test.ts: removed Gabriel from the shared-
chapter assertion loop (Guardian Angels/Raphael still legitimately share
Apparition's chapter, confirmed via their own saint files' explicit
source cross-references) and added a dedicated test for his own chapter.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PhysogLP9eCeKS7JEwRi6y
Closes the Angel item out of the priority list, corrects the prior
pass's wrong claim that Guardian Angels is gated behind a
Monastic Divino 1930 rubric (verified via a resolveOrdo smoke test with
zero missing parts under plain Monastic 1617), and flags a separate
found-in-passing bug: lauds-capitulum-st-gabriel-archangel.yml carries
Apparition's capitulum under Gabriel's id -- left for its own pass.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PhysogLP9eCeKS7JEwRi6y
Common-of-an-Angel doesn't exist in the reference engine -- every angel
feast has genuine proper Matins content of its own, so these are
saint-level overrides, not a Common category. Transcribed directly from
the reference engine's source files (Sancti/05-08 etc.) after a live-CGI
attempt mis-extracted a mid-nocturn antiphon change.
St. Michael needed his own file: the psalmody-override lookup keys off
the day's resolved winner id, not the `propers` field, and his own
Matins antiphons/lessons turn out to genuinely differ from Apparition's
despite his calendar file's "word-for-word identical" claim (true only
of the collect).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PhysogLP9eCeKS7JEwRi6y
Records the Holy-Woman/Several-Confessors/Evangelist closures, the
Angel non-gap finding (no such Common exists in the reference engine --
every angel feast has genuine proper content instead, a different task),
and sharper blockers for the remaining Virgin/Widow/Confessor-Doctor/
Confessor-Pope/Several-Women-Martyrs categories.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PhysogLP9eCeKS7JEwRi6y
Live-verified byte-identical against Ss. John of God and Camillus de
Lellis (Several Confessors -> Common-of-a-Confessor) and St. Luke
(Evangelist -> Common-of-an-Apostle) -- genuine full redirects, matching
the reference engine's own votive-index adjacency (C1/C1a, C5/C5c).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PhysogLP9eCeKS7JEwRi6y
Live-verified against St. Anne (2026-07-26): Nocturns 1/2 and Nocturn
3's canticles/versicle match Common-of-a-Virgin-Martyr exactly, but the
Nocturn 3 antiphon itself differs (pearl-of-great-price vs. bridegroom
imagery), so it gets its own file rather than an alias.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PhysogLP9eCeKS7JEwRi6y
Records the nine-category/three-alias progress, the votive-parameter
dead end for Matins nocturn psalmody, and the specific blockers found
for each of the eight still-open Common categories.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PhysogLP9eCeKS7JEwRi6y
Live-verified byte-identical psalms, antiphons, and versicles against
St. Maurus/St. Gall (Abbot), St. Francis of Assisi/St. Jerome
(Confessor-not-Bishop), and the Octave of Ss. Peter and Paul
(Apostles-plural) — genuine full redirects, not new content. Swaps the
matins.test.ts ferial-fallback proof off St. Anthony Abbot (no longer
unauthored) onto St. Monica (Widow, still unauthored).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PhysogLP9eCeKS7JEwRi6y
Live-verified against Divinum Officium (Monastic Tridentinum 1617) via
clean representative saints with no proper content of their own:
St. Justina (Virgin-Martyr), Our Lady of the Snows (BVM), and the
Dedication of the Lateran Basilica.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PhysogLP9eCeKS7JEwRi6y
Douay-Rheims's "physicians" is a literal rendering of the Vulgate's
"medici," itself a mistranslation of Hebrew Rephaim (the shades of
Sheol). Replace with "shades" for clarity, matching modern
translations (RSV/NRSV/NABRE).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018AThxkXu7pbPP7dLBkUpLj
Commune/C7's own invitatory antiphon ("Laudémus Deum nostrum * In
confessióne beátæ N..") needs a per-saint genitive-singular name, unlike
every other Common invitatory antiphon currently authored. Exports
resolve-common.ts's existing substituteName helper (previously only used
by getDayCollect for collectCommon/collectName) and wires it into
resolveMatinsInvitatoryText via a new SaintRecord.invitatoryName field
-- kept separate from collectName since the two templates' {N}
placeholders don't always need the same grammatical case. Fills in real
declined names for the 3 saints using common-of-a-holy-woman (Anne,
Martha, Mary Magdalene).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012AVXS5iTrk3c2anSYVGwxS
resolveMatinsInvitatoryText's Common-category tier existed but only had
Common-of-an-Apostle authored, so every other Common category fell all
the way through to the plain ferial invitatory antiphon regardless of
rank (e.g. St. Joseph Calasanctius, 2026-08-27, Confessor). Adds the
Confessor family (5 duplicated ids, matching the hymn's own precedent),
Martyr family (2 ids + Several Martyrs), Virgin family (2 ids), and
Dedication — all read directly from the reference engine's own
Commune/C-number [Invit] lines. Holy Women (C7) needs a name-template
mechanism this project doesn't have yet for invitatory antiphons, and
General/All Saints (C9) turned out to be a mislabeled backlog item (C9 is
actually the Office of the Dead) — both logged in TODO.md instead.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012AVXS5iTrk3c2anSYVGwxS
common-of-an-abbot's own P/T/S/N antiphon files already claimed to cover
common-of-a-confessor and common-of-a-confessor-not-bishop too ("an Abbot
is himself a Confessor"), but the matching duplicate files were never
authored — so the ~36 saints using either of those two ids (bare
common-of-a-confessor is the more common of the two) got no minor-hour
antiphon at all. Adds the 8 missing files, copying the abbot file's
already-verified text, matching this category's existing duplicate-not-
alias convention (see matins-hymn-common-of-a-confessor.yml vs.
matins-hymn-common-of-a-confessor-not-bishop.yml).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012AVXS5iTrk3c2anSYVGwxS
Nocturn count stays rank-based (readings only), but psalm content for an
exactly-Duplex sanctoral winner with no proper now always uses that
weekday's real ferial psalms instead of Common's own psalm numbers (or a
bare mechanical chunk) — so the psalter actually gets prayed rather than
repeating the same dozen Common psalms. Each nocturn's antiphon comes from
the saint's own minorHoursCommon Terce/Sext/None text (real, distinct per
nocturn, already proven non-psalm-specific), with Common's own Matins
versicle per nocturn; falls back to bare only where that content isn't
authored. Duplex-majus+ winners are unaffected.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012AVXS5iTrk3c2anSYVGwxS
The top-of-file priority summary still said 10 Common categories were
open, but the dated build log below it already showed all 11 done and
the Matins hymn gap closed to match, both from the prior session -- the
summary had drifted out of sync. Also logs today's Circumcision hymn fix.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014axryJBrRswYh2niA7WCUc
Closes the last content gap flagged when Circumcision was added: its
rubric borrows Christmas Day's propers wholesale, but Christmas Day
itself has no hymn authored anywhere in vu yet. Lauds reuses the
already-verified Christmas Octave Sunday text (byte-identical in the
source); Vespers/Matins use the Monastic "Christe Redemptor omnium",
a genuinely separate hymn from the secular "Jesu, Redemptor omnium",
with a fresh draft English translation since none exists in the
reference engine for that variant.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014axryJBrRswYh2niA7WCUc
Historically a floating Sunday (Jan 2-5, fixed to Jan 2 when no Sunday
falls there); per direct instruction this is instead fixed to Jan 2
every year for simplicity. Adds the feast's collect, Magnificat/
Benedictus antiphons, full Lauds psalmody, capitulum, the three-part
"Jesu dulcis memoria" hymn cycle (Vespers/Matins/Lauds), the Invitatory
antiphon, versicles, and two Matins nocturn readings (both St. Bernard
of Clairvaux), sourced from Tempora/Nat2-0.txt. Reuses the `fixed-date`
anchor kind added in the prior commit.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014axryJBrRswYh2niA7WCUc
Jan 1 previously fell through to generic Christmastide-feria content with
no identity of its own, despite being Christmas's own Octave Day. Adds a
`fixed-date` anchor kind to calendar/movable-feasts.ts's MovableAnchor
(the existing easter/nth-sunday kinds can't express "always Jan 1"),
plus the feast's collect, Magnificat/Benedictus antiphons, full Lauds
psalmody, capitulum, versicle, and two Matins nocturn readings (Leo the
Great, Ambrose), sourced from Sancti/01-01.txt.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014axryJBrRswYh2niA7WCUc
Adds pre-1955 TemporalFeastRecords (rank duplex-1-classis, real octaves)
for the three "feasts of the Lord" that were previously only referenced
as bare date offsets: Corpus Christi/Sacred Heart's own days already
couldn't lose to a sanctoral candidate, but with no record the day fell
back to the generic Trinity-week feria collect and never displayed its
real name; Ascension had no protection at all against an ordinary saint
winning it outright. Sacred Heart's own record carries the
forbidsSuccessorCommemoration flag added in the prior commit, refusing to
commemorate Most Precious Blood on the rare late-Easter date they fall
adjacent.
octave.wins is set to duplex on all three, matching Pentecost's own
verified threshold, despite a known conflict with a previously
live-verified case (St. Ubald winning First Vespers within Ascension's
own octave) -- kept per direct instruction; the affected tests (Ubald,
St. Margaret of Scotland) were updated to assert the new behavior, and
the tradeoff is documented in ascension.yml and TODO.md. A handful of
unrelated test fixtures that happened to land inside these new octave
windows were moved to nearby clean dates.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014axryJBrRswYh2niA7WCUc
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
resolveMatinsHymn (hours/matins.ts) is a separate mechanism from
resolveOfficeBundle, with its own fallback chain (proper -> active
octave -> Common via SaintRecord.common -> season -> ferial). Only the
Confessor family and Apostle had a matins-hymn-<id>.yml before this
pass; everything else fell through to season/ferial at Matins
specifically, even after the Lauds/Vespers fixes.
Live-verified and authored: common-of-several-martyrs ("Ætérna Christi
múnera, Et Mártyrum victórias"), common-of-an-abbot and
common-of-several-confessors (= Confessor-Not-Bishop's hymn, same label
finding as Lauds/Vespers), common-of-a-virgin-martyr/-virgin ("Vírginis
Proles"), common-of-a-holy-woman/-widow (a genuinely shorter closing-two-
stanzas form, "Hujus obténtu, Deus alme"), Guardian Angels/St.
Michael/St. Gabriel/St. Raphael (reused from St. Michael's Apparition,
confirmed via SanctiM/05-08.txt's own cross-reference), common-of-the-
bvm ("Quem terra, pontus, ǽthera"), Finding/Exaltation of the Holy Cross
("Pange, lingua, gloriósi," the Nativity-themed other half of the same
poem as Lauds' own hymn), and the Transfiguration (byte-identical to its
own Vespers hymn).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014axryJBrRswYh2niA7WCUc
Last of the 12-saint individual-Proprio list this session. Live-verified
against Divinum Officium (Monastic Tridentinum 1617), 2025-11-13 (clean
year): own chapter (Isa 8:18, "Ecce ego et púeri mei") and own hymn
("Salvéte, cedri Líbani," same text both hours); responsory and versicle
byte-identical to Common-of-Several-Martyrs's own already-authored
content.
This closes the individual-saint Proprio pass: 10 real saints/feasts
imported, 2 flagged stubs (Ss. Alexander etc., Ss. Machabees) confirmed
to need no new content. Combined with the 10 Common categories and the
Guardian-Angels/Archangels fix earlier this session, resolveOfficeBundle
now has real, live-verified Lauds/Vespers content for every gap the
original audit found.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014axryJBrRswYh2niA7WCUc
Live-verified against Divinum Officium (Monastic Tridentinum 1617),
2026-08-06 (fixed date). Fully proper feast: own chapter (Phil 3:20-21),
own distinct responsory/hymn/versicle at each hour ("Amor Jesu
dulcíssime" Lauds, "Quicúmque Christum quǽritis" Vespers), with real
Alleluias baked into the responsories.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014axryJBrRswYh2niA7WCUc
Fully proper feast, live-verified against Divinum Officium (Monastic
Tridentinum 1617), 2027-05-03 for the Finding (its own 2026 date
collides with a privileged Sunday) and 2026-09-14 for the Exaltation.
Own chapter (Phil 2:5-7), own distinct responsory/hymn/versicle at each
hour -- Lauds hymn "Lustris sex qui jam peráctis," Vespers hymn "Vexílla
Regis pródeunt," both with real Alleluias baked into their own text
(confirmed permanent via the Vespers hymn's own "In hoc Pascháli gáudio"
line, not seasonal contamination).
Authored under two ids (finding-of-the-holy-cross and proper-to-the-
finding-of-the-holy-cross): Exaltation's own saint file sets common:
proper-to-the-finding-of-the-holy-cross, a different id than its propers
field (a separate day-collect/antiphon lookup) -- without the second
copy, resolveOfficeBundle fell through to the ferial default for her.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014axryJBrRswYh2niA7WCUc
Live-verified against Divinum Officium (Monastic Tridentinum 1617),
2026-02-02 (fixed date, always winnable): only the chapter is genuinely
her own (Mal 3:1, "Ecce ego mitto Angelum meum"). Responsory, both
hymns, and both versicles are all byte-identical to Common-of-the-BVM's
own already-authored content -- duplicated under her own id.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014axryJBrRswYh2niA7WCUc
Live-verified against Divinum Officium (Monastic Tridentinum 1617),
2026-03-25 (fixed date, always winnable). Own chapter (Isa 7:14-15,
"Ecce virgo concípiet") and own versicle ("Ave, María, grátia plena")
at both hours, own Vespers responsory ("Ángelus Dómini * Nuntiávit
Maríæ," the opening of the Angelus). Lauds responsory and both hours'
hymns turn out byte-identical to Common-of-the-BVM's own already-
authored content -- reused verbatim.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014axryJBrRswYh2niA7WCUc
Live-verified against Divinum Officium (Monastic Tridentinum 1617),
2029-01-21: byte-identical to St. Agatha's own override (Sir 51:1-3).
Confirmed a real, saint-specific shared override between these two
ancient Roman virgin martyrs specifically, not a general Virgin-Martyr
rank-tier variant -- St. Lucy (also a Canon-of-the-Mass virgin martyr)
uses the plain Common chapter on her own clean day. Responsory, hymn,
and versicle reuse Common-of-a-Virgin-Martyr's own content.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014axryJBrRswYh2niA7WCUc
Live-verified against Divinum Officium (Monastic Tridentinum 1617),
2026-02-05: only the chapter is genuinely her own (Sir 51:1-3,
"Confitébor tibi, Dómine, Rex"). Responsory, hymn, and versicle are all
byte-identical to Common-of-a-Virgin-Martyr's own already-authored
content -- duplicated under her own id.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014axryJBrRswYh2niA7WCUc
Live-verified against Divinum Officium (Monastic Tridentinum 1617),
2035-06-30 (clean year): only the chapter is genuinely his own (2 Tim
4:7-8, "Bonum certámen certávi" -- Paul's own "I have fought the good
fight"). Responsory, hymn, and versicle are all byte-identical to
Common-of-an-Apostle's own already-authored content at Lauds -- reused
under his own id, since resolveOfficeBundle resolves a whole bundle per
id, not per part. Vespers content inferred by the same pattern (his real
Vespers is permanently masked by the Octave of St. John the Baptist's
Nativity closing the same day every year checked, 2026-2030 -- not
modeled in this app), not independently verified.
Updates one existing test whose date (2026-06-30) now correctly shows
his own proper chapter winning over the plain Common fallback.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014axryJBrRswYh2niA7WCUc
Live-verified against Divinum Officium (Monastic Tridentinum 1617),
2033-03-21 (clean year), cross-checked directly against the source's own
Monastic-specific file (SanctiM/03-21.txt), which carries a full proper
office for his Transitus despite the plain secular Sancti file having
none.
Own chapter (Eccli 50:6-7, "Ecce Confessor magnus") and own hymns at
both hours ("Inter ætérnas súperum corónas" Lauds, "Láudibus cives
résonent canóris" Vespers, recounting Subiaco and the founding of Monte
Cassino). Lauds responsory/versicle and Vespers versicle turn out
byte-identical to Common-of-a-Confessor-Bishop's own -- authored
explicitly under his own id rather than left to fall through, since his
actual common (common-of-an-abbot) resolves to a different Confessor-
Not-Bishop responsory that would be wrong here. Vespers has its own
proper responsory invoking him by name ("Sancte Pater Benedícte").
Neither hymn (nor the chapter) has an English rendering in the reference
engine -- own translations, marked draft.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014axryJBrRswYh2niA7WCUc
Live-verified against Divinum Officium (Monastic Tridentinum 1617),
2033-03-12 (clean year, per his own saint file's comment). Own chapter
(Eccli 50:1, "Ecce sacérdos magnus... suffúlsit domum") and own hymns at
both hours ("Mella cor obdulcántia" Lauds, "Anglórum jam Apóstolus"
Vespers, the latter recounting his sending Augustine of Canterbury and
the "non Angli sed Angeli" legend); responsory and versicle turn out
byte-identical to Common-of-a-Confessor-Bishop's own, reused verbatim.
Neither hymn has an English rendering in the reference engine at all --
own original translations, marked draft.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014axryJBrRswYh2niA7WCUc
User-reported: 2026-08-24 Vespers (St. Bartholomew, Duplex II. classis,
keeps his own evening) should have commemorated 2026-08-25's St. Louis
(Simplex), but didn't. This was the exact untested asymmetry the old
commemorationOf's own doc comment flagged as "inferred by symmetry, not
independently live-verified." Live-verifying now shows the two
directions genuinely differ: a Simplex predecessor losing to tomorrow's
First Vespers is correctly excluded (Aug 7 Donatus case, "nihil de
præcedenti"), but a Simplex successor displaced because today keeps its
own Vespers IS still commemorated (Aug 24->25, "commemoratio de
sequenti").
Split the one shared commemorationOf into
commemorationOfDisplacedPredecessor (keeps the Simplex exclusion) and
commemorationOfDisplacedSuccessor (drops it).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014axryJBrRswYh2niA7WCUc
Finished the "ferial/other" audit bucket via each saint's own source
file. Vigils (9 saints) are not a gap at all: every vigil's own [Rule]
explicitly says "Responsory Feria"/"Versum Feria" -- deliberate rubric,
matching this app's existing isFerialOrVigil convention. Even the "ex
C1v" tag on the 6 apostle vigils resolves to the same plain ferial text
when live-queried, confirming no override applies to the Lauds/Vespers
bundle for any vigil.
Common-of-the-BVM (5 feasts) is a real category: each source file says
plain "ex C11"/"vide C11". Live-verified votive=C11: chapter Sir 24:14,
Lauds hymn "O gloriósa Dómina" (the same pre-Urban-VIII text as the
Annunciation's own separate proper), Vespers hymn "Ave maris stella,"
same responsory at both hours, distinct versicles per hour.
Surfaced one masked collision: the Annunciation now resolves through
this Common instead of the ferial default (real improvement, still
interim until its own proper content is imported). Moved the
Passiontide-seasonal-office Vespers test off its date (Mar 25) onto the
clean Mar 30.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014axryJBrRswYh2niA7WCUc
Read straight from the source (Sancti/02-12.txt, Ss. Seven Holy Founders
of the Servite Order -- the only saint in this category): its own [Rule]
says plain "vide C5," no votive number of its own. Duplicated the same 7
files under common-of-several-confessors.
This closes every Common category this session's audit found with real
users -- resolveOfficeBundle now has real Lauds/Vespers content for
every category, not just the two it started with.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014axryJBrRswYh2niA7WCUc
Methodology upgrade this session: read each saint's own Sancti/MM-DD.txt
[Rank]/[Rule] field directly instead of hunting for a per-saint clean
year to live-query -- it states the real Common assignment (or a direct
proper-content inheritance) with zero calendar dependency.
Guardian Angels/St. Gabriel/St. Raphael the Archangels' own files say
"vide C4," but that's a Missal/Mass-propers designation -- their real
Monastic-breviary file (SanctiM/10-02.txt) cross-references St. Michael's
Apparition (Sancti/05-08, already fully authored in this app) directly
for the chapter/responsory at every hour. No common-of-an-angel category
exists in the source at all. Duplicated St. Michael's Apparition's 7
Lauds/Vespers files under each of the three saints' own ids (proper
content, not a Common fallback), live-verified on their own real 2026
days.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014axryJBrRswYh2niA7WCUc
Both genuinely distinct from Virgin(-Martyr) -- different chapter
Prov 31:10-11 "Mulíerem fortem," different hymn "Fortem viríli péctore"
-- but share that category's exact responsory pattern and versicle
byte-for-byte in both hours. Live-verified via St. Anne's own real day
(Jul 26). St. Monica's real day (May 4) confirms common-of-a-widow
shares the identical content with common-of-a-holy-woman, so the same 7
files were duplicated under that id too -- first content authored for
either category at all.
Proof: St. Anne's and St. Monica's own days for Lauds, St. Margaret of
Scotland's real First-Vespers anticipation (Jun 9 -> 10) for Vespers.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014axryJBrRswYh2niA7WCUc
common-of-a-confessor-doctor (4 saints) is another "same content,
different id" case: live-verified votive=C4a byte-identical to plain
votive=C4 (Common-of-a-Confessor-Bishop), so duplicated verbatim rather
than authored fresh, matching the already-existing matins-hymn-common-
of-a-confessor-doctor.yml precedent.
common-of-an-apostle (3 saints/feasts) is genuinely distinct: chapter
Eph 2:19-20, hymn "Exsúltet cælum láudibus" (byte-identical Lauds/
Vespers), shared versicle "Annuntiavérunt ópera Dei." Also duplicated
under the separate common-of-apostles (plural) id used by the Octave of
Ss. Peter and Paul.
Proofs: St. John Damascene (own day) + St. Robert Bellarmine (First
Vespers) for Confessor-Doctor; Commemoration of St. Paul (own day) + the
Beheading of St. John the Baptist (First Vespers) for Apostle.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014axryJBrRswYh2niA7WCUc
Live-verified against Divinum Officium (Monastic Tridentinum 1617)
votive=C6: chapter 2 Cor 10:17-18, hymn "Jesu, coróna Vírginum" (byte-
identical Lauds/Vespers), shared versicle "Diffúsa est grátia in lábiis
tuis" -- only the two responsories differ between the hours. Cross-
checked plain common-of-a-virgin (St. Praxedes) renders the same real
content, matching the already-existing benedictus-antiphon-common-of-a-
virgin.yml precedent -- duplicated the same 7 files under that id too.
Proof: St. Apollonia's own clean year (2029-02-09, her 2026 date collides
with St. John of Matha) for Lauds, St. Praxedes's real First-Vespers
anticipation for Vespers.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014axryJBrRswYh2niA7WCUc
Same finding as common-of-a-confessor: live-verified (St. Maurus, Jan 15;
St. Odo, Feb 7) that Common-of-an-Abbot's real Lauds/Vespers content is
identical to Common-of-a-Confessor-Not-Bishop's -- an Abbot isn't
necessarily a bishop, so the traditional rite reuses the plain Confessor
common for them. Duplicated the same 7 files under this id, matching the
existing benedictus-antiphon-common-of-an-abbot.yml precedent. Proof
tests on St. Maurus (Duplex II. classis, own Lauds + real First-Vespers
anticipation Jan 14 -> 15).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014axryJBrRswYh2niA7WCUc
Fixed a real audit-methodology bug first: the bulk gap-audit was sampling
each saint's own date via prayVespera, which reflects whatever saint
claims First Vespers that evening (often a neighboring day's), not the
sampled saint's own category. Redid it via prayLaudes (no anticipation).
That surfaced the real finding: common-of-a-confessor (37 saints, this
app's largest remaining gap) isn't a distinct liturgical Common -- it's
common-of-a-confessor-not-bishop under a different label. St. Joseph's
and St. Thomas Aquinas's chapter is byte-identical to the already-
authored Sir 31:8-9 text, and several of the 37 saints' own file comments
already say "vide C5" (Confessor Not a Bishop) directly. Not relabeled
across the 37 saint files, since matins-hymn-common-of-a-confessor.yml
and benedictus-antiphon-common-of-a-confessor.yml already establish this
plain id as the real, working one for those saints' Matins/Benedictus
content -- duplicated the Lauds/Vespers bundle under this id too, to
match that existing convention.
Updates two existing tests whose dates collided with this fix: the
FERIAL_MONDAY constant (moved off St. Raymond Nonnatus's own day) and a
new Vespers proof on St. Joseph's First Vespers.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014axryJBrRswYh2niA7WCUc
Third Common category (17 real users) with no Lauds/Vespers bundle
authored. Live-verified against Divinum Officium (Monastic Tridentinum
1617) votive=C4: chapter Sir 44:16-17 "Ecce sacérdos magnus," Lauds hymn
"Jesu Redémptor ómnium," shared versicle "Justum dedúxit Dóminus per vias
rectas." Found the Vespers hymn is byte-identical to Common-of-a-
Confessor-Not-Bishop's own ("Iste Conféssor Dómini sacrátus") even though
the chapter, both responsories, and the Lauds hymn all genuinely differ
between the two categories -- reused that already-authored text verbatim
rather than re-deriving it.
No real Duplex-II-classis+ saint exists in this category, so the Vespers
proof uses St. Ubald's real First-Vespers anticipation instead of a
votive-only proof. Updates one existing test (2026-04-20, Paschaltide
seasonal office) whose date turned out to also anticipate St. Anselm's
First Vespers, previously masked by this same gap.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014axryJBrRswYh2niA7WCUc
Next-largest remaining Common category (22 real users) with no Lauds/
Vespers bundle authored, same fix pattern as Confessor-Not-Bishop and
Martyr(-Bishop). Live-verified against Divinum Officium (Monastic
Tridentinum 1617) votive=C3: chapter Sap 3:1-3, Lauds hymn "Rex glorióse
Mártyrum," Vespers hymn "Sanctórum méritis ínclyta gáudia," shared
versicle "Exsultábunt Sancti in glória." Cross-checked votive=C3/C3a/C3b
byte-identical, confirming the Missal-collect split (plain-martyrs vs.
Pope-martyrs) doesn't carry through to this Office bundle. Proof tested
on Ss. Placid and Companions (Duplex II. classis, real un-outranked
Second Vespers).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014axryJBrRswYh2niA7WCUc
St. Zephyrinus (Aug 26, common-of-a-martyr-bishop, Simplex) claims First
Vespers over St. Louis's Aug 25, but that evening was showing the plain
ferial hymn instead of a real Common-of-a-Martyr hymn: no content had
been authored yet for this Common category's Lauds/Vespers bundle, so
resolveOfficeBundle's chapter lookup came back missing and the whole
bundle fell through to the ferial default.
Live-verified against Divinum Officium (Monastic Tridentinum 1617)
votive=C2b: chapter Jac 1:12, Lauds hymn "Martyr Dei, qui unicum," Vespers
hymn "Deus tuorum militum," shared versicle "Justus ut palma florebit."
Used the votive-office view rather than a real saint's own day for the
same structural reason as the earlier Confessor-Not-Bishop fix: every
real Common-of-a-Martyr-Bishop saint either has proper content of his own
(St. Lawrence, St. Stephen) or never reaches the rank needed to keep his
own Vespers (St. Zephyrinus, St. Callistus). Cross-checked votive=C2/C2a/
C2b/C2-1 all render identical text, so the same 7 files were duplicated
under the plain common-of-a-martyr id too (St. Hadrian, St. Hermes, St.
Fidelis of Sigmaringen).
Updates one existing Vespers test whose date (2026-03-03) turned out to
also anticipate St. Lucius I's First Vespers, previously masked by this
same content gap; adds two new proof tests and a TODO.md entry.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014axryJBrRswYh2niA7WCUc
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
Day navigation (prev/today/next) and the language toggle were plain
buttons wired to click handlers only; switched them to real <a href>
elements (built via router's new exported urlFor) so right-click,
middle-click, and open-in-new-tab work, with a click handler that
still SPA-routes on a plain left-click and lets modified clicks fall
through to normal browser navigation.
Language selection is now reflected in the URL as a ?lang= query
param (parsed/pushed by router.ts, omitted for the default single-
English case to keep that URL clean), so a shared link preserves the
viewer's chosen language(s).
hour-list.ts now groups the 8 hours into three traditional rows
(night office, day hours, evening) instead of one flat wrapped list.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014axryJBrRswYh2niA7WCUc
hasFirstVespers() required Semiduplex-or-higher, so a bare Simplex
feast could never anticipate the evening before it. Live-verified
this was too strict: St. Zephyrinus (Aug 26, plain Simplex) claims
First Vespers over St. Louis (Aug 25, also Simplex) in the reference
engine -- "Vespera de sequenti; nihil de præcedenti." A Simplex feast
has no Second Vespers of its own, but it does still get First Vespers;
there's no rank floor at all, only keepsOwnSecondVespers' separate
question of whether the preceding day outranks the claim.
This also surfaces St. Zephyrinus's Magnificat antiphon, which now
actually gets used (St. Louis, previously shown by the old bug, is
permanently boxed out of ever governing real Vespers in this merged
calendar -- Bartholomew outranks him the evening before, Zephyrinus
the evening after, every year -- so his own Magnificat antiphon was
never authored). Added st-zephyrinus-magnificat-antiphon.yml, live-
verified bilingual text ("Iste Sanctus...").
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014axryJBrRswYh2niA7WCUc
Vespers had no suffrages (Holy Cross, BVM, Joseph, Apostles, Peace)
at all -- types.ts even documented it as Lauds-only. Live-verified
against the reference engine (Monastic Tridentinum 1617): the
"Suffragium" block after the day's collect(s) renders byte-identical
after both Lauds and Vespers, so this was an honest content gap, not
a deliberate omission.
Extracted the suffrage ids/labels and full omission logic (Double-or-
higher, Christ the King, Advent/Christmastide/Passiontide, active
octaves, Cross/Marian-Saturday special cases) out of lauds.ts into a
shared resolveSuffrages() in resolve-common.ts, and wired it into
vespers.ts/vespers.yml the same way Lauds already uses it.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014axryJBrRswYh2niA7WCUc
Real Monastic 1617 has no Preces at the Little Hours, and that was
previously documented as intentional. Per this project's "not a
reconstruction" design, Prime was recently given Preces in the secular
Tridentine form as a deliberate deviation from Monastic — do the same
here: the reference engine's ferial minor-hour Preces (Kyrie, Pater
Noster, then "Domine Deus virtutum, converte nos..." V/R pairs) is
authored as a single shared common id and wired into all three hours
after the chapter. By explicit choice this is said every day regardless
of rank, mirroring Prime's own "every ferial day" simplification, rather
than importing the reference engine's Sunday/feast form, whose sourcing
(a V/R block reused from Compline) looks like a data quirk rather than
genuine minor-hour content.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PXySSbbUh1EhdN1RnAoCaP
Every capitulum-*.yml under the three Little Hours combined the chapter,
"R. Deo grátias"/"Thanks be to God", and the trailing V./R. pair into one
flat single-line string. The .lang-column CSS already renders on
white-space: pre-line (as little-hour-conclusio.yml's block-scalar text
already demonstrated), so the run-together rendering was purely a missing
line-break bug in the source YAML, not a CSS or resolver issue.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PXySSbbUh1EhdN1RnAoCaP
isSundayOrFeastOffice treats any sanctoral winner, Vigils included, as a
"feast," so a Vigil day was getting the Sunday/feast Preces. Add a
by-day-kind 'vigilIsFerial' flag, set only on Prime's Preces part (its
capitulum keeps the old behavior), so a Vigil-ranked winner now routes
to the ferial Preces text instead.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HWN869GrMFHqrer9fdCBbF
Record that showing the ferial Preces on every ferial day (rather than
only Advent/Lent/vigils/Ember, per the historical rubric) is a
deliberate, currently-unaddressed backlog item, not an oversight.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HWN869GrMFHqrer9fdCBbF
Monastic Prime's own Dominicales Preces drop the "Et ego ad te, Domine,
clamavi..." versicle block and shorten the closing "Miserere nostri,
Domine... Fiat misericordia tua..." set. Prefer the secular Tridentine
form instead (live-verified against Divinum Officium's "Tridentine -
1906" version), keeping "beato Patri nostro Benedicto" in the Confiteor
by explicit choice even though the real secular Confiteor omits it.
The Sunday/ferial by-day-kind split stays as-is; the ferial form
(prime-preces-feriales.yml) is unchanged and continues to show on every
ferial day by deliberate choice, more generously than the historical
rubric.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HWN869GrMFHqrer9fdCBbF
Proof content for the new Common-fallback mechanism, live-verified
against Divinum Officium (Monastic Tridentinum 1617): Lauds chapter/
responsory/hymn/versicle ("Jesu coróna célsior"), cross-checked
byte-identical between St. Louis's and St. Nicholas of Tolentino's own
days. Vespers bundle (hymn "Iste Conféssor Dómini sacrátus", reusing
the already-authored Matins hymn translation) sourced via the
reference engine's votive=C5 view instead of a real saint's own Second
Vespers, since every saint in this Common category has their own
Second Vespers permanently outranked by an adjacent fixed feast.
TODO.md records the fuller sourcing detail and the remaining open
Common categories.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HWN869GrMFHqrer9fdCBbF
resolveOffice in both hours only substituted a feast's own proper
chapter/responsory/hymn/versicle, gated at duplex-majus+ (piggybacking
eligibility on Lauds' psalmody-override table). A simplex/semiduplex
feast with no proper content of its own dropped straight to the plain
ferial default, skipping their Common entirely (caught via St. Louis,
Simplex, common-of-a-confessor-not-bishop).
Add resolve-common.ts's resolveOfficeBundle: tries the winner's own
proper bundle first, then their Common's bundle, eligibility tested
rank-agnostically via the existing getMinorHourOverrideId (any
sanctoral winner) rather than duplex-majus+ — matching the rule
already used for the minor hours. Applied to both Lauds and Vespers.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HWN869GrMFHqrer9fdCBbF
English used the same id-derived Latin title (e.g. "Canticum Ezechiae")
for every language. Now English uses the authored name from
lauds-canticles.ts when one exists, falling back to the derived title
for names already conventional untranslated in English (Benedictus,
Magnificat, Nunc Dimittis) and Matins' scripture-ref-derived ids.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HWN869GrMFHqrer9fdCBbF
The English text had a static ‡ baked in, colliding with the
dynamically-computed flex mark on Tuesday/Thursday Lauds when the
Laudate antiphon's incipit matches the first four words of verse 1.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HWN869GrMFHqrer9fdCBbF
Live-verified against Ss. Fabian and Sebastian's clean date (2029-01-20,
Monastic Tridentinum 1617). Genuinely distinct psalm scheme from
common-of-a-martyr (single martyr), not a duplicate. Adds Wisdom 3:1-6
to the scripture store for Nocturn 3's canticles. Swaps the matins.test.ts
ferial-fallback proof to St. Anthony Abbot now that Fabian/Sebastian
resolve through this new category instead.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VpKT73TSqRNnE2CSD8GpUY
Reuses the existing lauds-short-litany proper (Kyrie eleison x3 + Pater
noster) rather than a Matins-named duplicate, same reuse discipline
Vespers already applies. Unlike Lauds/Vespers's own preces, this fires
every day with no ferial/Sunday swap and is never omitted.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VpKT73TSqRNnE2CSD8GpUY
Combines it with the existing John 21 reading on Saturday. Also brings
the plan back in line with matins.ts's own documented invariant that
the user's reading plan never assigns a Gospel reading on a Sunday.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019jHSimu99UDKh1pRRd11uV
Marks the Confessors (C4) hymn gap from the 2026-08-24 entry as done
and logs the Monastic-vs-secular textual-variant finding as a general
lesson about not trusting reused reference-engine translations.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019jHSimu99UDKh1pRRd11uV
Fixes the ferial-hymn fallback for St. Louis and 82 other confessor
saints across all 5 confessor common ids, using the pre-Urban VIII
(Monastic-track "sacrátus") textual variant with a fresh English
translation rather than the reference engine's mismatched reused one.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019jHSimu99UDKh1pRRd11uV
Documents the full set of changes from this session: the
nocturnReadingIds precedence bug fix, the new Matins hymn Common-tier
and invitatory-antiphon mechanism (Common-of-an-Apostle content
authored, other Commons/seasons logged as backlog), the antiphon-
doubling dedup, and the Terce/Sext/None hymn dedup — including why the
Lauds/Vespers office-bundle resolvers were deliberately left alone
(they turned out not to be byte-identical: different override-
eligibility semantics) and why Matins's own octave hymn tier isn't
redundant with resolveOfficeWinner's octave-folding.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01F25189JqjXddUhU9hM9nUS
Terce, Sext, and None each had a byte-identical 'hymn' case
(resolveCommon(part.textRef.id), no doxology mechanism, no override/
season tiering). Replace all three with a single shared
resolveSelfContainedHymn helper in resolve-common.ts.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01F25189JqjXddUhU9hM9nUS
resolveMatinsHymn gained a Common-category tier (override -> octave ->
Common category -> season -> ferial), reusing matins-psalmody-
overrides.ts's own getSaintRecord(id)?.common lookup pattern. The
invitatory antiphon (invitatoryParts), previously one hardcoded fixed
text for every day of the year with only full-vs-incipit doubling
varying by rank, now goes through a real resolveMatinsInvitatoryText
(override -> Common category -> season -> ferial, no octave tier) before
the doubling step.
Content authored this pass: Common-of-an-Apostle only, both hymn
(matins-hymn-common-of-an-apostle.yml, "Aeterna Christi munera") and
invitatory antiphon (matins-invitatory-common-of-an-apostle.yml, "Regem
Apostolorum Dominum"), both read directly from the reference engine's
Commune/C1.txt. Live-verified: St. Bartholomew (2026-08-24) and St.
Andrew (2026-11-30) both now resolve to this Common's text.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01F25189JqjXddUhU9hM9nUS
The same idiom (splitNamedAntiphon + isDoubleOrHigher(...) ? full :
incipit) was copy-pasted at 11 call sites across 8 hour files. Add one
shared openingAntiphon(antiphon, winner) helper in resolve-common.ts
and use it everywhere a psalm/canticle antiphon's opening form is
decided. Pure dedup, no behavior change.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01F25189JqjXddUhU9hM9nUS
nocturnReadingIds unconditionally pooled the plain temporal-cycle and
month-week reading ids into every day's nocturn-reading pool, even when
a real feast wins the day outright with zero commemorations
(decideOccurrence's `ordinary-feria` branch, which correctly suppresses
the temporal identity entirely). St. Bartholomew (duplex-2-classis,
2026-08-24, a Monday) has no Nocturn 3 content of his own, so the
leftover 13th-Sunday-after-Pentecost and month-week readings wrongly
filled his Nocturn 3.
Gate the pooling on whether the day's own occurrence decision actually
retained the temporal identity (day.winner.kind === 'temporal', or a
`temporal`-kind entry in day.commemorations) — mirrors getDayCollects's
existing pattern for the same question.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01F25189JqjXddUhU9hM9nUS
Matins now sings "Te decet laus" (Psalterium/Common/Prayers.txt's [Te
decet]) immediately after the Te Deum on every 3-nocturn day, matching
Rule of Benedict ch. 11's close of Vigils. New common text
te-decet-laus.yml, wired into both matins.ts branches that render a Te
Deum; absent on ferial days along with it.
Also drops Psalm 129 (De profundis) from the Lauds ferial Preces and
Psalm 50 (Miserere) from the Vespers ferial Preces, per direct
instruction — both were embedded inline in the live-transcribed text.
Updated the affected fixture tests to key off a versicle that survives
the removal instead of the now-absent psalm text.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EWzE3kaSZCs79fPBrrE1aF
All 53 nocturn-readings files with a responsory field started straight
into the text with no leading R., unlike octave-readings' own R./V.
convention. Fixed in content per direct instruction (not injected at
render time), matching each file's own existing plain V./R. style rather
than switching to octave-readings' R./V. glyphs.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EWzE3kaSZCs79fPBrrE1aF
buildReadingPool concatenated readings in id-processing order, so a later
post-Pentecost Sunday's Nocturn 3 (from its own temporalId file) could land
ahead of its Nocturn 2 (from the newly-added month-week id) in the pool,
flipping which nocturn slot each landed in. Group by each reading's own
nocturn tag instead, ascending, preserving id/file order within each group.
Also logs three follow-up items to TODO.md: displaying "Te decet laus",
the missing responsory marker convention in nocturn-readings content, and
removing Ps 129/50 from the Lauds/Vespers ferial Preces.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EWzE3kaSZCs79fPBrrE1aF
The later post-Pentecost Sundays' real Nocturn 2 patristic content isn't
keyed by Pentecost offset in the reference engine — it's keyed by civil
calendar month/week (the old "Scriptura occurrens" cycle), which shifts
relative to post-pentecost-NN every year depending on Easter's date. Port
Date.pm's monthday() algorithm (pre-1955/Tridentine rubrics) as a new,
date-driven id, author all 20 August-November month-week files from the
reference engine's Tempora/0MN-0.txt sources, and pool them in
hours/matins.ts as a third, independent reading source alongside the
existing temporalId lookup.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EWzE3kaSZCs79fPBrrE1aF
Closes the gap the user found on 2026-08-23 (post-pentecost-13's Matins had
no patristic reading): nocturn-readings/ had content for only one temporal
id (post-pentecost-15, the original mechanism proof date). Since
resolveTemporalId maps every day in a liturgical week to the same id, one
file per id covers the whole week — authored all 23 remaining
post-pentecost-NN ids (01-14, 16-24) from the reference engine's own
Tempora/PentNN-0.txt files. Trinity Sunday (01) keeps its own full proper
office including a displaced-Sunday commemoration; 02-11 have real Nocturn
2+3 content; 12 onward are Nocturn-3-only (homily, Gospel incipit skipped
per convention), reusing the same two responsories the source itself
cross-references week to week.
Rest of the temporal cycle (Advent/Christmas/Epiphany/Septuagesima/Lent/
Easter) is a separate follow-up pass, tracked in TODO.md.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EWzE3kaSZCs79fPBrrE1aF
Every psalm's opening antiphon (Lauds, Prime, Terce, Sext, None,
Vespers, Compline's callers, Matins) now runs through applyFlexaMark,
marking the boundary in both the displayed antiphon and the psalm's
first verse — e.g. today's Sunday Matins Nocturn 1: "Ant. The king
rejoices ‡" / "The king rejoices ‡ in thy strength, O Lord...".
Live-verified against the reference engine's own rendering for Ps 20.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UCvvgFLrhXNVFUThz6kanw
Ports Divinum Officium's getantcross/depunct (horas.pl): finds where a
displayed antiphon's words stop matching a psalm's first verse, so the
mark can later show where the antiphon leaves off and the psalm's own
text resumes. Not yet wired into any hour builder.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UCvvgFLrhXNVFUThz6kanw
Researched whether September/Advent Ember days belong in the
movable-feasts.ts table alongside Christ the King/Immaculate Heart of
Mary. Live-verified against the reference engine they don't: Ember days
are privileged ferias (a saint can still win outright against them), not
named feasts contesting the day by rank.
Two smaller pieces instead: calendar/temporal.ts's septemberEmberDayOffset
(Sunday nearest Sept 14, same "nearest" arithmetic adventStart already
used, factored into a shared nearestSunday helper) feeds a new
resolveTemporalCategory check giving those 3 dates privileged-feria-minor
(live-verified correct); calendar/ember-days.ts's applyEmberDay relabels
the day's own temporal id to the Ember day's own (so its real content is
found) when the feria itself wins, or adds a commemoration when a saint
does. Advent Ember days needed no precedence change -- Advent's own
season default already covers them.
Found and fixed a real bug along the way: matins.ts's nocturnReadingIds
only ever pulled a commemorated *sanctoral* id's own readings, never a
commemorated *temporal* one's -- so a commemorated Ember day (the common
case) would never have surfaced its own content. Same root cause as the
day.winner.id gap the IHM relocation fixed earlier, just hiding in the
commemorations loop instead.
Authored all 6 days' real collect + 3 Matins readings each, transcribed
directly from the reference engine (September: Tempora/093-{3,5,6}.txt;
Advent: Tempora/Adv3-{3,5,6}.txt, whose lessons are themselves a
cross-reference to the Annunciation's own Common, followed and
transcribed from there). Kept as 3 separate readings per day rather than
the usual combine-into-one default, since each carries its own
genuinely distinct proper responsory.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VZSAgRi4QE4XRTqVto93zA
Extends TemporalFeastRecord's date anchor from a single easterOffset
number into a MovableAnchor union: 'easter' (unchanged), plus
'nth-sunday-of-month' (nth: -1 for "last") and 'nth-sunday-of-advent' --
both new anchor kinds resolve to "Nth Sunday counting from an anchor
date," just with a different anchor (first-of-month vs. Advent's own
start, which is itself always a Sunday). Adds calendar/temporal.ts's
sundayOnOrAfter to support the month case.
Migrates christ-the-king.yml onto the new nth-sunday-of-month anchor
(month: 10, nth: -1) with a new `unconditional: true` flag replacing its
old always-wins-no-rank-check bespoke function -- retiring
applyChristTheKing and its lastSundayOfOctober helper from
calendar/index.ts entirely. Down to two bespoke override functions left
(marian-saturday's date-less fallback, christmas-octave-sunday's
fixed-date-range Sunday search), both structurally different from the
Nth-Sunday-from-anchor shape this table now covers.
No behavior change -- the full existing Christ the King test suite
passes unchanged through the new generic path.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VZSAgRi4QE4XRTqVto93zA
The bespoke applyImmaculateHeart function (following the applyMarianSaturday/
applyChristTheKing precedent of one hand-written override function per
feast) doesn't scale: several more feasts are planned that all reduce to
"weekday N days from Easter Sunday" (Ember/Rogation days, more
Sacred-Heart-family Marian devotions, St. Joseph's pre-1955 Eastertide
feast, Lenten Friday Passion devotions), and one-off functions invite
subtle ordering bugs -- applyImmaculateHeart had to be sequenced after
applyMarianSaturday specifically or it would have been silently clobbered.
Replaced with calendar/movable-feasts.ts's generic applyMovableFeasts,
which scans calendar/temporal-feasts.ts's existing per-feast YAML records
for a new optional `easterOffset` field. Adding the next movable feast is
now a new data/calendar/temporal-feasts/<id>.yml file with `rank` and
`easterOffset` set, not a new TypeScript function. Same rank-compared,
commemorate-the-loser semantics as before, just centralized instead of
duplicated per feast.
christ-the-king, marian-saturday, and christmas-octave-sunday stay as
their own functions -- they're structurally different rules (a fixed
month-position search, a date-less fallback default, and a fixed-date-
range Sunday search, respectively), not Easter offsets, so force-fitting
them into this table wouldn't actually simplify anything.
No behavior change -- the existing Immaculate Heart of Mary test suite
passes unchanged through the new generic path.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VZSAgRi4QE4XRTqVto93zA
resolveMatinsHymn's own doc comment already claimed the octave "keeps
the feast's own proper hymn all week," but the code never implemented
it -- it only matched when the day's own winner literally was
`assumption` (Aug 15 itself). Every other octave day (16, 17, 19, 20,
21, none of whom has a Matins hymn of their own authored) fell straight
through to the plain ferial hymn instead.
Adds a real octave-fallback tier between the existing per-feast override
and the seasonal tier. A deliberate departure from the reference engine,
which doesn't do this either (Monastic 1617's octave days have no
[Hymnus Matutinum] override at all) -- consistent with how this project
already treats octave readings/commemorations more generously than any
one source track.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VZSAgRi4QE4XRTqVto93zA
Fixed Aug 22 collided outright with the Assumption's own octave-closing
day (both this project's blended calendar tracks are kept deliberately,
so one shouldn't permanently suppress the other). Moved IHM to the
Saturday after the Feast of the Sacred Heart -- its real diocesan date
from 1914 until Pius XII's 1944 fixed-date decree, and also the date the
1969 reform returned to.
Required real new mechanism, not just a data move: a new bespoke
calendar/index.ts override (applyImmaculateHeart, following the existing
applyMarianSaturday/applyChristTheKing precedent), a move from the
sanctoral saints store to the temporal-feasts store (different propers
lookup entirely), and a real bug fix in matins.ts's nocturnReadingIds,
which only ever picked up a *sanctoral* winner's own reading file.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VZSAgRi4QE4XRTqVto93zA
sundayPsalmNocturn/sundayCanticleNocturn only ever rendered a
versicle's V. line, silently missing the responsory for Sunday Matins
and every Duplex+ weekday feast using matins-psalmody-overrides. Both
now share the versicleText helper introduced for the ferial nocturn
antiphons, which combines V./R. into one block.
Every non-Sunday, non-Duplex+ Matins nocturn rendered bare psalms with
no antiphon at all. Sourced each psalm's antiphon from Divinum
Officium's Monastic Matins psalter and regrouped to fit
psalter-distribution.yml's own already-established (and non-historical)
weekday psalm redistribution, since the source's own per-weekday
antiphon pairings don't survive that reslice intact.
Every octave-reading yml's source field held only the Latin
attribution copied verbatim from the breviary text; add the matching
English translation now that the field carries both languages.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L3wxvz3mPXiHxkPB5JpnmD
The heading above each Matins octave reading was a bare Latin string
(e.g. "Sermo sancti Bernárdi Abbátis") rendered as-is in both the
Latin and English columns, since the app shows both languages side
by side rather than toggling. Widen OctaveReadingText.source and the
lesson part's label to carry per-language text, and render it as its
own lang-columns row like the body text beneath it.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L3wxvz3mPXiHxkPB5JpnmD
"Surge! Jam terris fera bruma cessit" / "Arise! the cold blasts from
earth have receded", read from the reference engine's Sancti/08-15.txt,
used throughout the Assumption's octave via that override mechanism.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L3wxvz3mPXiHxkPB5JpnmD
Matins always rendered the plain ferial hymn, unlike Lauds/Vespers which
already check a duplex-majus+ override then a seasonal default first.
Surfaced by the Assumption's octave, which keeps the feast's own proper
hymn all week rather than falling back to ferial.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L3wxvz3mPXiHxkPB5JpnmD
Assembled from already-verified Assumption pieces (Benedictus
antiphon, Vespers V/R, post-1950-dogma collect) since the octave
commemoration block this app's own generous-commemorations design
calls for has no direct reference-engine source to copy.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L3wxvz3mPXiHxkPB5JpnmD
The substitutedOctaveId gate only checked temporalCategory, not who
actually won the day, so a saint winning outright within an active
octave (e.g. St. Bernard vs. the Assumption octave, Aug 20) silently
lost that octave's own commemoration block at Lauds/Vespers. Now
mirrors resolveOfficeWinner's real gate (day.winner.kind and
ALWAYS_OVERRIDE_TEMPORAL_IDS).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L3wxvz3mPXiHxkPB5JpnmD
Monastic 1617 itself is inconsistent: the feast/closing octave day
render the old pre-dogma "Famulórum tuórum..." collect, but the
interior octave days (16/18/19/21) render the newer "Omnípotens
sempitérne Deus, qui Immaculátam..." collect from the 1954 Divino
Afflatu revision -- a data cross-reference slip in the reference
engine, not a deliberate rubric. vu already used one collect uniformly
across the whole octave rather than replicating the split; switch that
shared collect to the newer text per user preference.
Lauds/Vespers were falling back to a bare collect for her commemoration
since only the collect had been authored. Live-verified the antiphon
and versicle against the reference engine (Divino Afflatu - 1954,
1955-08-21, the nearest year her feast actually fell on a Sunday and
was commemorated rather than won).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L3wxvz3mPXiHxkPB5JpnmD
Two new mechanism-gap items: an arbitrary-date calendar picker (day-nav
currently only steps one day at a time) and a "read it to me" audio
interface via the Web Speech API for long texts.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L3wxvz3mPXiHxkPB5JpnmD
Visiting the app with no hour in the URL previously always showed
Prime for today, since selectedHour was hardcoded in AppState. Now it
picks the hour whose band contains the visitor's local clock time.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L3wxvz3mPXiHxkPB5JpnmD
HOUR_IDS drove the nav's render order but was declared arbitrarily;
reorder to Matins, Lauds, Prime, Terce, Sext, None, Vespers, Compline
so the nav reads left-to-right the way the office actually unfolds.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Matches the reference app's row layout instead of a vertical sidebar
list, and switches from buttons to anchors since hours are real
routes — plain click still SPA-navigates, modifier/middle clicks
fall through to native browser behavior (open in new tab, etc.).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Matins is hand-assembled in TS rather than driven by a data/hours/*.yml
parts list, so it never got the closing wired in that every other hour
has. Live-verified against the reference engine's Monastic Tridentinum
1617 Matutinum "Conclusio" block that Matins' closing text is
byte-identical to lauds-conclusio — reusing it the same way
Vespers already does.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Same `wins: duplex` override as the Assumption's and Pentecost's
octaves, applied to two more common-class octaves. Accepted side
effect: St. Damasus (Dec 11) and St. Nicholas of Tolentino (Sep 10),
both Semiduplex, no longer tie-and-win an ordinary octave day here --
they're now commemorated instead of winning outright.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Demote St. John Eudes (Aug 19) from Duplex to Semiduplex -- too minor
a confessor to keep outranking the Assumption's octave. To make that
demotion actually cede the day, raise the Assumption's octave to
`wins: duplex` (same pattern already used by Pentecost's octave), so an
ordinary-day tie no longer automatically favors the occurring saint.
Promote St. Thomas of Canterbury (Dec 29) to Duplex so he keeps
winning against the Christmas octave stack now that ordinary-day ties
are no longer a given.
Also add a closing-day tie-break to octave-vs-octave precedence
(pickWinningOctave): when two active octaves tie in rank, the one on
its own closing day now wins the label contest, ahead of the existing
"more recently started" tie-break. Needed because Assumption's day 3
and St. Lawrence's own closing day (Aug 17) now tie at Duplex, and the
closing day should still govern that date's label.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
A different, wider rule than the psalm/canticle Gloria (Triduum-only):
responsories drop their Gloria for the whole of Passiontide (Passion
Sunday through Holy Saturday), except on a Sancti feast's own day —
live-verified (St. Joseph, Duplex I, 2027-03-19, landing in Passiontide
that year, keeps it; an ordinary ferial day in the same window omits it,
at both Compline and Prime).
Every responsory's stored text differs, Gloria-bearing vs Gloria-free,
by exactly one fixed-wording line, so resolve-common.ts's new
resolveResponsory filters that line out rather than requiring separate
per-season files or a data migration — mechanism first, per the
project's own stated content-authoring order. Wired into the four hours
that have a real responsory (Compline, Prime, Lauds, Vespers); Lauds/
Vespers' existing hand-authored Passiontide-proper responsories were
already Gloria-free and are unaffected.
Tenebrae's rubric replaces Matins' usual opening with a silently-said
Pater/Ave/Credo, live-verified against both Monastic Tridentinum 1617
and Divino Afflatu 1954 (Holy Thursday: Invitatorium and Hymnus both
"omittitur", no Ps 3 or versicle either). Since this app already never
renders that silent block on ordinary days either (data/hours/prime.yml),
the fix is a pure omission straight into Nocturn 1, using the same
isInTriduum window added for the Gloria Patri work.
Benedictus, Magnificat, and Nunc Dimittis had the Gloria Patri baked
verbatim into their propers YAML with no Triduum suppression; Matins'
Nocturn 3 OT canticles had none at all. Give the 'canticle' ResolvedPart
the same centrally-attached gloriaPatri field psalms already got, strip
the baked-in text, and drop Lauds' now-redundant local append/Triduum
check — live-verified that canticles follow the exact same Triduum-only
omission as psalms (e.g. Benedictus at Lauds of Holy Thursday).
Psalms across every hour rendered with no closing Gloria Patri at all.
Add it centrally (hours/index.ts) rather than per-hour, using a new
calendar/temporal.ts isInTriduum helper so it's correctly dropped from
Maundy Thursday through Holy Saturday and nowhere else — live-verified
against both of this app's actual source tracks (Divino Afflatu 1954
and Tridentine 1906), not just the modern Rubrics 1960 default. Also
fixes Lauds' existing but Triduum-blind canticle Gloria Patri append.
Adds a section-heading ResolvedPart kind and emits "Invitatory" and
"Nocturn I/II/III" headings in matins.ts, so the hour's structural
boundaries are visible instead of rendering as one undifferentiated
stream of parts. Ferial (1-nocturn) days get no Nocturn heading, matching
their existing single-unnumbered-nocturn design.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
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>
Nocturn 1 and 2 each get exactly one reading; Nocturn 3 absorbs the
rest of the pool, however large. A pool of only one reading goes to
Nocturn 3 rather than Nocturn 1.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Keeps Simplex commemorating in place, but a Semiduplex feast on an
ordinary Sunday goes back to transferring off to the next open day —
this app's own design choice, not a correction against a reference
engine. The transfer mechanism is already generic: this file's `decideOccurrence`
only signals "couldn't hold onto this candidate" for the day itself;
calendar/index.ts's resolveDay is what independently checks each
neighboring day's own native occurrence for an incoming transfer and
claims it — no separate lookback logic needed.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
At Vespers/Compline, a day's own Vigil losing tonight to tomorrow's First
Vespers was showing up as a redundant extra commemoration alongside that
same feast (e.g. Nov 29's Compline showing both "St. Andrew" and "Vigil
of St. Andrew" for Advent I's anticipated evening).
Added an explicit `vigilOf` field to each Vigil's own saint record
(several Vigil ids don't map cleanly to their feast's id by stripping a
`vigil-of-` prefix — `vigil-of-the-assumption` vs `assumption`,
`vigil-of-st-james` vs `st-james-the-greater` — so this needs to be
explicit rather than guessed from the id or date adjacency).
calendar/vespers.ts's evening-anticipation merge now skips adding the
Vigil commemoration when tomorrow's winner or its own commemorations
already include that same feast.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
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>
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>
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>
TemporalFeastRecord had no rank field at all, so Christmas/Pentecost/
Christ the King/Marian Saturday had nothing for the day label to show.
Christmas, Pentecost, and Christ the King are Duplex I Class (the
latter's own file comment already said so); Marian Saturday is a
Simplex-strength votive commemoration.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Documents the Francis of Paola / Basil the Great live-verification,
the identical-apart-from-versicles finding, the Duplex+ saints checked
for proper-antiphon eligibility, and the M1617-kalendar wrinkle found
along the way (a SanctiM file existing doesn't mean the saint is
actually on the Monastic Tridentinum 1617 calendar).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Two new Common-tier proofs (Francis of Paola, Basil the Great) confirm
the resolver picks up both new categories with verified antiphon text.
The old ferial-fallback proof (St. John Bosco, Common of a Confessor)
is now superseded since that category is authored -- swapped for Ss.
Fabian and Sebastian (Common of Several Martyrs, still unauthored).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Live-verified against Divinum Officium's Monastic Tridentinum 1617 CGI
instance: St. Francis of Paola (2030-04-02, "vide C5") for the plain
Confessor scheme, St. Basil the Great (2035-06-14) for the Bishop
scheme. The two turned out word-for-word identical in psalm numbers
and antiphon text across all three nocturns, differing only in their
three nocturn-closing versicles -- kept as separate full files since
the mechanism has no partial-override support.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
getMatinsPsalmodyOverride is now a genuine 3-tier lookup: proper
(saints/) -> Common (categories/, joined via the winner's own
SaintRecord.common) -> the plain ferial fallback, reversing the earlier
"category files are documentation-only" decision now that two
categories are trusted. Category files gain an `aliases` field;
common-of-a-martyr.yml lists common-of-a-martyr-bishop, since the
reference engine's own Matins psalmody redirects that Common outright
to plain Martyr — so its three Duplex+ saints (Ignatius, Callistus,
Boniface, none with proper text of his own) get real Common-tier
content with no new authoring.
tests/hours/matins.test.ts's old ferial-fallback proof (St. Ignatius)
is rewritten as a Common-tier proof; a fresh ferial-fallback proof
(St. John Bosco, an unauthored Confessor category) replaces it.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Two categories live-verified: common-of-a-martyr (from St. Ignatius of
Antioch, whose own office is entirely Common — also confirms
Common-of-a-Martyr-Bishop redirects to this same scheme, and that none
of its 3 Duplex+ saints has a proper antiphon of his own) and
common-of-an-apostle (from St. Bartholomew). St. Andrew authored as the
second saint-level proof, sharing the apostle category's psalm numbers
in every slot with his own proper antiphon text.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Sparse transcriptions (only the cited verses) for St. Andrew's Matins
Nocturn III canticles, live-verified against the reference engine.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Split the store into categories/*.yml (generic Common-category psalm-
number/versicle/canticle scheme) and saints/*.yml (per-saint proper
antiphon overrides, unchanged shape) per the mechanism's own flagged
next step. St. Lawrence's file moves into saints/ with a category
cross-reference; resolution behavior (per-saint lookup, ferial
fallback when unauthored) is unchanged.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
matins.ts's three-nocturn gate correctly governed nocturn count
(Sunday, or Duplex+) but wrongly reused the literal Sunday psalmody as
content for every three-nocturn day, including a weekday feast. Live-
verified (Monastic Tridentinum 1617) that a Duplex+ weekday feast has
its own genuinely different psalmody -- confirmed per-Common-category,
not per-saint, by cross-checking St. Lawrence against St. Ignatius of
Antioch (a different Martyr sub-category, nearly identical psalm
numbers, different antiphons).
New mechanism (matins-psalmody-overrides.ts, mirroring the Lauds one
but keyed per-category) with St. Lawrence authored as the live-verified
proof; falls back to the plain ferial weekday table, redistributed into
3 nocturns, for every other Duplex+ id until its own category's content
is authored. Also generalizes the canticle shape to support a citation
spanning multiple scripture chapters under one heading (Lawrence's own
Nocturn 3), and adds the new Sirach/Jeremiah chapters his canticles cite.
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.
Christmas Octave's own commemoration blocks are now modeled -- see the
prior two commits. Updates the top-of-file priority summary and the
dated build-log entry to reflect this.
getDayCollects previously dropped kind: 'octave' commemorations
entirely, so on days where the temporal office keeps its own standing
(the Christmas Octave's own Nativity/Stephen/John/Holy-Innocents stack,
e.g. Dec 30) those octaves contributed nothing but a name in the day
label. Add an octave branch, gated on temporalCategory !== 'ordinary-
feria' -- the same condition resolveOfficeWinner uses -- so an ordinary
octave day's already-substituted primary content never gets a redundant
repeat.
Ant+V/R+collect "Commemoratio Octavæ" blocks for Christmas, St. Stephen,
St. John, and Holy Innocents, sourced from the reference engine's Sancti/
12-25..28.txt Octava 3 sections -- content for the mechanism change in
the next commit.
Own real Oratio and Nocturn 2-3 content (Duplex majus). This was the
only real gap left in December -- the rest of the month was already
complete from the earlier pre-1955 sanctoral pull. No test collisions:
Dec 3 was genuinely open.
This closes out the full-year sanctoral sweep (January-December).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
St. Charles Borromeo (11-04), the Octave Day of All Saints (11-08,
own real Cyprian/Augustine Nocturn 2-3 content, collect reused from
All Saints via cross-id propers), St. Albert the Great (11-15),
St. Gertrude (11-16), St. Felix of Valois (11-20). All Souls' Day
(11-02) deliberately skipped -- a Requiem office, not a normal
saint's office, a genuine mechanism gap.
Several new fixed-date Duplex+ saints displaced existing test
fixtures that had assumed particular dates were clean/ferial/
plain-Sunday: prime.test.ts's MONDAY constant, weekday.test.ts's
clean-Sunday case, and day-label.test.ts's post-Epiphany-overflow
and open-octave-day cases. Fixed by finding genuinely clean
alternate dates/years, never by weakening assertions.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Continues the full-year sanctoral import: the Guardian Angels (Duplex
majus, full proper content), St. Thérèse of Lisieux, St. Bruno, St.
Bridget of Sweden, St. Francis Borgia, the Maternity of the BVM (Duplex
II Classis, instituted 1931 for the 1500th anniversary of the Council
of Ephesus, full proper content), St. Edward the Confessor, St. Teresa
of Avila (distinct from St. Thérèse of Lisieux -- two different
Carmelite saints sharing a name), St. Margaret Mary Alacoque, St. Peter
of Alcantara, St. John Cantius, and St. Raphael the Archangel (own
full proper content, matching st-gabriel-archangel.yml's established
pattern for a Sancti file that redirects wholesale to another date's
file but still carries its own real content).
St. Raphael's new fixed day (Oct 24, Duplex majus) collided with the
"clean, empty Saturday" fixture used by three tests for Marian
Saturday/Lauds-psalmody-override behavior; moved all three to 2026-07-04,
verified genuinely free of any sanctoral entry (not even a commemorated
Simplex).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Continues the full-year sanctoral import: St. Stephen of Hungary, St.
Pius X, St. Lawrence Justinian, Ss. Protus and Hyacinth, the Holy Name
of Mary, the Seven Sorrows of the BVM (Duplex II Classis, own full
proper content except Nocturn 2, a real cross-reference to Passion
Friday's own temporal content), the Impression of the Stigmata of St.
Francis, St. Joseph of Cupertino, Our Lady of Ransom, and St.
Wenceslaus.
Ss. Protus and Hyacinth (Sep 11) were previously excluded under a
stale "real secondary saint blocked by the Nativity's own octave, same
as St. Clare in August" note -- wrong, same class of error already
corrected for St. Tiburtius/Susanna in August: the octave-window
exclusion was never a real rule. Sep 15 (day 8 of the Nativity's own
octave) is now also the Seven Sorrows' own fixed day (Duplex II
Classis), which wins outright with the octave still commemorated
alongside her -- updated september-sanctoral.test.ts's own "excluded
secondary saints" case accordingly, splitting it into the still-valid
Sep 9 (Gorgonius) half and a new Sep 15 case reflecting the real
winner.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Continues the full-year sanctoral import: Ss. Tiburtius and Susanna, Ss.
Hippolytus and Cassian, the Vigil of the Assumption, St. Joachim (Duplex
II Classis, own full proper content), St. Hyacinth, St. John Eudes, St.
Jane Frances de Chantal, the Immaculate Heart of the BVM (Duplex II
Classis, instituted 1942, full proper content), St. Joseph Calasanctius,
and St. Raymond Nonnatus.
Several of the new fixed dates (Aug 16, 17, 21, 22) fall within or
adjacent to St. Lawrence's and the Assumption's overlapping octave
windows and now win outright there under the ordinary-feria/ordinary-
sunday precedence rules, displacing what several existing tests
expected to be bare octave commemorations or ferial fallbacks. Updated:
- august-sanctoral.test.ts: Aug 22 now correctly shows the Immaculate
Heart of Mary as the winner, with the Assumption's octave still
commemorated alongside her.
- day-label.test.ts: the two octave-priority tests pinned to Aug 17
(now St. Hyacinth's own day) moved their "day 8 alone" case to All
Saints' own octave (Nov 8, still open); the two-octaves-overlap
comparison can no longer be demonstrated via any real date in this
app since every day where St. Lawrence's and the Assumption's octaves
genuinely overlap is now real sanctoral content -- the underlying
`pickWinningOctave` mechanism itself stays covered by
tests/calendar/octaves.test.ts's own synthetic-data unit test.
- vespers.test.ts: the Thursday Ps 138 split test moved off Aug 20,
whose own first Vespers is now claimed by St. Jane Frances de
Chantal's fixed day (Aug 21).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Continues the full-year sanctoral import: the Most Precious Blood of Our
Lord (Duplex I Classis, full proper office, elevated by Pius XI for the
1933 Redemption jubilee), the Visitation of the BVM (Duplex II Classis,
full proper office), St. Leo II, St. Anthony Mary Zaccaria, the Octave
Day of Ss. Peter and Paul (own real content, not a bare continuation),
Ss. Cyril and Methodius, St. Elizabeth of Portugal, St. Henry, Our Lady
of Mount Carmel, St. Vincent de Paul, and St. Ignatius of Loyola.
Also fixes a real pre-existing bug surfaced while authoring St. Leo II:
an earlier June sanctoral pull had mapped him to "06-28" (his own vita
text mentions his burial "upon the 28th day of June," but his actual
liturgical feast, per the reference engine's Sancti/07-03.txt, is July
3) with no saints/st-leo-ii.yml ever authored for it -- a wrong-date
stub, same class of artifact as the pre-existing St. Apollinaris
wrong-date bug already documented in july-sanctoral.test.ts. Removed
the stale June 28 entry now that he's correctly authored on July 3.
Two day-label tests pinned to Trinity-Sunday-relative dates that now
collide with new fixed feasts (Jul 6: Octave Day of Ss. Peter and Paul)
were moved to collision-free years/dates, and the July stub test
covering "pure octave-continuation days" was updated to a date (July 4)
that's still genuinely unmapped, since most of that range turned out to
have real content after all.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
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>
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>
St. Isidore of Seville, St. Vincent Ferrer, St. Hermenegild, St. Anselm,
St. Fidelis of Sigmaringen, St. Peter Canisius -- April complete.
St. Peter Canisius is the second common-of-a-confessor-doctor saint
(after St. John Damascene in March), same benedictusCommon reuse
pattern. St. Hugh, Abbot (Apr 1) checked and correctly skipped: his own
file is Commemoratio-rank with only a collect, no Lectio content at
all to source.
npm test (365 tests) and tsc --noEmit both pass.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
St. John Capistrano -- March complete
Finishes March's sweep of previously-never-added sanctoral content.
The Annunciation (Mar 25) was one of the two feasts literally named in
this app's original stale "fixed BVM feast, excluded per the usual
convention" note (alongside Purification, already fixed).
St. John Damascene is the first common-of-a-confessor-doctor saint;
his own Commune/C5a points its own Benedictus antiphon back at C4a's,
so he reuses the already-authored
benedictus-antiphon-common-of-a-confessor-bishop.yml directly rather
than duplicating it.
npm test (365 tests) and tsc --noEmit both pass.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
of Jerusalem (March)
Continuing the sweep of previously-never-added sanctoral content. New
shared Common templates: collect-c7b.yml and
benedictus-antiphon-common-of-several-women-martyrs.yml (Commune/C7b,
"several women Martyrs, not Virgins" -- Perpetua and Felicity's own
Common category, not authored before now).
St. Gabriel Archangel (Mar 24), the Annunciation (Mar 25), St. John
Damascene (Mar 27), and St. John Capistrano (Mar 28) are still open --
substantial feasts left for a follow-up pass.
npm test (365 tests) and tsc --noEmit both pass.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
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>
Per direct instruction to import all available sanctoral content
regardless of rubric-era markers: these three had real, complete Matins
content in the reference engine and were simply never added during the
earlier month-by-month sanctoral pulls (no exclusion comment, no
placeholder -- just never reached). First of a broader sweep across the
whole calendar.
New shared Common template: benedictus-antiphon-common-of-a-confessor.yml
(Commune/C5's own Ant 1) -- the first common-of-a-confessor saint that
needed one; st-alexius.yml (the only prior user of this category) had
used a Cistercian-specific alternate instead.
npm test (365 tests) and tsc --noEmit both pass.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
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>
Previously excluded entirely under a stale "fixed BVM feasts aren't
modeled" note -- wrong: this app already models three other fixed BVM
feasts (assumption, immaculate-conception, nativity-bvm), and this one
has real, complete content too. Sancti/08-05.txt's untagged rank block
gives her Duplex Majus with real Nocturn 2/3 content (the
miraculous-snowfall founding legend of St. Mary Major); SanctiM/08-05.txt
confirms she's on the Monastic 1617 track too, reslicing the same
Lectio4-6.
Unlike the other three BVM feasts, she has no proper collect or
Benedictus antiphon of her own (her Rule says "ex C11" -- Common of
Feasts of the BVM) -- collect-c11.yml and
benedictus-antiphon-common-of-the-bvm.yml are real, reusable Common
templates, wired via collectCommon/benedictusCommon like any other
Common-only saint, not a private per-saint duplicate.
Live-checked via direct resolution: she wins outright on 2026-08-05 and
2027-08-05, with antiphon, collect, and Matins reading all resolving
verified. npm test (365 tests) and tsc --noEmit both pass.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The Latin text already named the dedication of St. Mary Major ("Romæ,
in Exquiliis, Dedicatio Basilicae sanctae Mariae ad Nives") but the
English translation silently dropped the corresponding sentence,
jumping straight to the day's other martyrs. Restored from the
reference engine's own English/Martyrologium/08-05.txt.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
st-telesphorus, st-hyginus (Jan 5/11, octave-absorbed — their day's Nocturn
2/3 is given over to a Nativity/Epiphany homily rather than their own vita)
and vigil-of-st-john-the-baptist, vigil-of-st-lawrence (Vigils whose single
3-lesson nocturn was misread as "no content") were left unauthored earlier
in the SanctiM pass on the theory that non-proper-to-the-saint text
shouldn't be sourced. Per direct correction: this app pools every available
reading first and figures out display later, so real sourceable text
belongs in the pool even when it isn't proper to that particular saint —
flagged as such in each file's own header, not disguised as the saint's
own vita. vigil-of-all-saints was re-checked under the same policy and
confirmed still genuinely contentless (a bare @Commune rubric).
npm test (365 tests) and tsc --noEmit both pass.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
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>
14 of November's 16 SanctiM-track saints authored, including All
Saints of the Benedictine Order -- a genuinely Monastic-only feast
with both rich content and (unusually for this pass) an available
English translation. St. Martin I is another off-date case like St.
Paul the Hermit, and two more saints (St. Emilian, St. Odo) turn out
Monastic-only-but-Common-only, extending October's wrinkle.
9 of October's 13 SanctiM-track saints authored. Found a new wrinkle:
St. Justina and St. Gall are genuinely Monastic-only (no Tridentine
file at all) but their own SanctiM files turn out to be Common-only
with no proper Lectio content -- being Monastic-only doesn't guarantee
real text to transcribe.
All 9 of September's SanctiM-track saints authored -- the first full
month in this pass with nothing left unsourced. Includes the
Dedication of St. Michael the Archangel, whose SanctiM file borrows
structure from his separate May 8 Apparition file but still resolves
its actual readings back to this day's own Tridentine content.
13 of August's 16 SanctiM-track saints authored, including the Duplex
I class feasts of St. Lawrence and the Assumption of the B.V.M. The
Assumption's presence confirms this app's calendar already maps fixed
BVM feasts, despite an older note in the parent workspace CLAUDE.md
suggesting otherwise -- flagged in memory as stale documentation to
verify against, not trust blindly. 3 saints left unsourced (Common-only
or Nocturn-1-only vigil content).
13 of July's 18 SanctiM-track saints authored (5 left unsourced,
Common-only). Discovered the Monastic calendar keeps a full Octave of
St. Benedict (Jul 11-18), which explains several dates' SanctiM files
not matching their expected saint. Also hit a translation-misalignment
bug on St. Bonaventure's English source (paragraph boundaries don't
line up with the Latin), worked around with an original translation.
12 of June's 13 SanctiM-track saints authored, including the major
Duplex I class feast of Ss. Peter and Paul. One left unsourced (the
Vigil of St. John the Baptist has only a Nocturn 1 scripture reading
in the source, nothing proper to author for Nocturn 2/3). Found a new
wrinkle: St. William Abbot has real vita content in both tracks,
essentially the same story independently transcribed — sourced from
the Tridentine file since only it has an English translation.
12 of May's 13 SanctiM-track saints authored (1 left unsourced,
Common-only). Resolves an old loose end from the pre-1955 pass: St.
Romanus Abbot does have a real SanctiM file after all, it's just
Common-only with no proper Lectio content, which looked like a missing
file until actually opened. Also the first isGospel:true case since
January (Ss. Philip and James's Nocturn 3 has a full Gospel pericope,
not a bare incipit).
All 9 of April's SanctiM-track saints authored. Found a new wrinkle:
St. Catherine of Siena and St. Peter Martyr sit on swapped dates
between this app's calendar and the general Tridentine one (Apr 29/30
each way), not just an off-by-a-few-days shift like earlier cases.
Also hit a reference-engine data bug on St. George (English prose
filed under a Latin heading) and another genuinely Monastic-only saint
(St. Robert of Molesme) needing an original translation.
All 6 of March's SanctiM-track saints authored. St. Benedict's own
patronal feast turns out to be the richest case in this pass so far:
its Nocturn 3 is genuinely Monastic-only content (a St. Peter Damian
sermon) absent from the Tridentine track entirely, unlike every other
saint done so far where the SanctiM file just reslices the same
Tridentine text. Also caught a case (St. Gregory the Great) where a
saint's SanctiM file carries content copied verbatim from an unrelated
feast rather than anything proper to the saint himself.
8 of February's 10 SanctiM-track saints authored (2 left unsourced —
Common-only, no proper text in the source). Two more turned out to be
missed pre-1955-pool candidates rather than genuine SanctiM cases
(their date's SanctiM file belongs to a different saint sharing the
date). Also settles a convention for bare Gospel incipits followed by
a real homily: author the homily alone as isGospel:false rather than
including the incipit line under isGospel:true.
Starts the bulk-content pass complementary to the pre-1955 saints pass:
11 of January's 13 SanctiM-track saints authored (2 left unsourced —
their Nocturn 2/3 content is entirely octave homiletic material, no
proper vita text in either track). Most SanctiM files turn out to be
thin rule overrides pointing back at the same Sancti/ text; only
St. Maurus (Monastic-only, no Sancti file) needed direct SanctiM
sourcing, with an original translation for its missing English source.
Removes the resolved "mechanism gap" entry (it turned out to need no
new mechanism, just sourcing), adds a dated build-log entry with the
sourcing method and the critical "plain Sancti/MM-DD.txt file is often
the wrong saint" wrinkle, and repoints the remaining backlog at the
larger Monastic-1617-track saint pool.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Ss. Tryphon and Companions, St. Gregory Thaumaturgus, St. Catherine of
Alexandria, and the Vigil of St. Andrew. This completes the
pre-1955-but-post-1617 sanctoral pass: 43 saints authored across the
year, 9 confirmed Common-only with nothing to source (see TODO.md).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
St. Giles, St. Nicholas of Tolentino, Ss. Cornelius and Cyprian, Ss.
Euphemia/Lucy/Geminian, St. Januarius, the Vigil of St. Matthew, and
Ss. Cyprian and Justina.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The Finding of St. Stephen, St. Donatus, St. Louis, St. Zephyrinus, and
Ss. Felix and Adauctus.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Ss. Basilides/Cyrinus/Nabor/Nazarius, St. Anthony of Padua, Ss. Gervase
and Protase, St. Silverius, and St. Paulinus of Nola.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
St. Monica, St. Stanislaus, the Apparition of St. Michael, and St.
Eleutherius. St. Monica's Nocturn 3 Gospel+homily reuses the
already-verified Luke 7:11-16 text from post-pentecost-15.yml, the same
pericope her own day's lessons cross-reference.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Sts. Marcellus I, Paul the Hermit, Marius/Martha/Audifax/Abachum,
Emerentiana, Timothy, Polycarp, and Agnes (second feast) — sourced from
the reference engine's Tridentine/Divino Afflatu track since none of
these saints existed in the Monastic 1617 calendar this app otherwise
follows.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
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>
The user's own reading plan isn't sourced from a historical breviary, so
"not yet spot-verified against a reference" wasn't a real gap for this
content, unlike sanctoral/temporal data. Also confirms Jn 17 on Sunday
after Ascension as a deliberate, remembered exception.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Converts all ~369 non-blank rows of the user's own scripture-reading
plan TSV into data/hours/bible-plan/*.yml, mapping each row's day label
to this app's (temporalId, weekday) or Dec25-Jan13 calendarDate scheme.
Fixes a few source TSV typos in the process (chapter-range and
trailing-colon citations); status stays explicitly `missing` pending the
separate Vulgate/Douay-Rheims import.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Replaces the fixed Nocturn 1 = plan / Nocturns 2-3 = patristic slotting
with one ordered pool (scripture plan, winner/commemorations/temporal id,
active octaves) sliced evenly across however many nocturns the day has.
Also makes the bible-plan store dual-keyed so the Dec 25 - Jan 13 stretch
can key off a fixed calendar date alongside the usual (temporalId,
weekday) key. Mechanism only — content authoring is still 2 proof dates.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Lets someone outside Reground's infra run vu standalone: builds the image and exposes it on localhost:8080, independent of the reground.org-specific compose config that joins an external network with no published port.
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.
hours/matins.ts no longer a stub: resolves a full ordo assembled
programmatically per day rather than a static parts list, since the real
structure (1 nocturn ferial vs. 3 on a Sunday or Duplex+ feast, a
variable number of readings) doesn't fit the pattern every other hour
uses. Several deliberate departures from the historical office, all per
direct instruction (see TODO.md's own writeup): Nocturn 1 always draws
from the user's bible-plan rather than the historical lectionary and
never contracts for "summer"; split historical lessons are recombined
except where the source genuinely changes; Nocturn 3 is gated at
Duplex-and-higher plus every Sunday; every commemorated saint and active
octave contributes its own reading, not just the office winner; a Gospel
reading is sourced only from the user's plan or the day's own proper
content, never a Common-of-Saints fallback; the Invitatory is framed like
an ordinary antiphoned psalm rather than the historical repeating
refrain.
ui/hour-view.ts renders the two new part kinds (nocturn-psalmody,
te-deum) and the extended lesson shape (responsory, Gospel flag).
New ResolvedPart kinds (nocturn-psalmody, te-deum) and lesson fields
(nocturn, source, isGospel, responsory) in hours/types.ts. Three new
content-store modules: propers/bible-plan.ts (the user's own continuous
scripture-reading plan, replacing the historical Nocturn-1 lectionary),
propers/nocturn-readings.ts (general feast/temporal-day Nocturn 2-3
readings, extending propers/octave-readings.ts's combine-into-one-reading
pattern via a newly-exported resolvePassages), and
propers/matins-responsories.ts (a per-book responsory pool, matched
loosely rather than by exact citation).
Data: Sunday's fixed 12-psalm/3-canticle psalmody (live-verified against
the reference engine), the invitatory antiphon, ferial hymn and
capitulum, the Te Deum, a seeded Isaiah responsory, two live-transcribed
scripture chapters, and the two bible-plan rows needed for this pass'
proof content.
Records what shipped in the previous two commits, plus the known gaps
(5 hymns with a seasonally-variable doxology not modeled, same class
as the Paschaltide alleluia suffix elsewhere in this file).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
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>
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>
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>
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>
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>
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>
Sourced from Divinum Officium's explicit "[Monastic Vespera]" block
(Latin+English Psalmorum), not the ambiguous unlabeled Day/Daya tables
earlier in the same file — confirmed by cross-checking Monday's psalms
against the already live-verified Lauds antiphons, which the unlabeled
tables both got wrong. Only 4 psalms a day (Rule of Benedict ch. 17),
with two real wrinkles: Ps 115+116 joined under one shared antiphon on
Monday, and Ps 138/143 each split across two verse-ranges under one
shared antiphon (Thu/Fri) — modeled by letting a group's `psalms` list
take `{number, verses}` entries alongside plain numbers.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
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>
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>
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>
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>
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>
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>
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>
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>
Completes the Lauds content backlog: every saint with Prime/Terce/Sext/
None content already authored this session now also has its own Lauds
psalmody override (3 psalm antiphons + Canticle of the Three Young Men +
Laudate antiphon) and office bundle (capitulum/responsory/hymn/versicle),
live-verified against Divinum Officium (Monastic Tridentinum 1617).
Combined with the 4 already done (st-lawrence, marian-saturday,
christ-the-king, christmas-octave-sunday), the psalmody-override and
office-bundle backlogs are both now fully closed for every non-`propers:
null` duplex-majus+ id.
Confirmed structurally, not assumed: the 5 Lauds antiphons are the same
pool Prime/Terce/Sext/None draw from (Prime=1, Terce=2, Sext=3, None=5,
skipping the canticle's own 4th).
St. Scholastica again needed self-translation (no English exists in the
source for her Lauds content either) -- four of five antiphons and her
capitulum reuse the English already verified for her Terce/Sext/None
content; the canticle antiphon, versicle, and 6-stanza hymn are new
translations. Surfaced a real schema gap along the way: the Lauds
psalmody-override type has no per-language status field the way the
Prime/Terce/Sext/None mechanism does, so her draft English can't carry
the same "unverified" UI marker there -- flagged in TODO.md, not fixed.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
christmas-octave-sunday previously had only its Benedictus antiphon
authored -- no Lauds psalmody override or office bundle, no Prime/Terce/
Sext/None content at all. Adds the full set: Lauds psalmody (3 psalms +
Canticle of the Three Young Men + Laudate, all distinct antiphons),
Lauds capitulum/versicle/hymn ("A solis ortus cardine"), and Prime/Terce/
Sext/None antiphons+chapters (Terce/Sext/None only, matching Gal
4:1-2/4:4-5/4:7 -- the same reading the raw Monastic Tridentinum 1617
source file gives for this Sunday).
Real code gap found and fixed along the way: getMinorHourOverrideId
(hours/resolve-common.ts) only checked the sanctoral branch of
resolveOfficeWinner's result, so a named temporal feast in
ALWAYS_OVERRIDE_TEMPORAL_IDS (marian-saturday, christ-the-king) got a
Lauds psalmody override but silently never a Prime/Terce/Sext/None one,
however much was authored for it. Fixed by mirroring
getPsalmodyOverrideFor's own temporal-id branch (hours/lauds.ts), and
christmas-octave-sunday added to that set.
Content sourced from a "Divino Afflatu 1954" live query rather than
Monastic Tridentinum 1617, since 1617 has no real identity of its own for
this Sunday under its own calendar rules on most Dec 26-29 dates (only
the collect, already authored separately, happened to be pulled from a
date where 1617 did render it) -- consistent with this app's stated
precedent of following D.A. for calendar/precedence even while keeping
1617's shape for the hours themselves elsewhere.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Confirms and formalizes what TODO.md had flagged as an open question:
the reference engine's own source has explicit Paschaltide variants
(Commune/C1p.txt, C2p.txt, C3p.txt -- "tempore Paschali") for exactly 3
of its 12 Common categories -- Apostles, a Martyr-Bishop, Several
Martyrs -- a real liturgical restriction of Alleluia/victory imagery to
apostles and martyrs, not a coincidence.
resolveMinorHourAntiphon/resolveMinorHourChapter now try a
`${minorHoursCommon}-paschaltide` variant first whenever day.season is
eastertide/ascensiontide/pentecost (the same three-season window as
Compline's own Regina Caeli table, since "tempore paschali" runs through
the Pentecost octave in both), falling back to the plain Common when no
variant is authored.
common-of-apostles-paschaltide (7 files) ported directly from the
reference engine's own Commune/C1p.txt source files rather than
live-queried, then cross-checked against st-mark's already-authored,
live-queried content -- identical except one antiphon's asterisk, which
the source places one word earlier; corrected to match. st-mark and
st-john-before-the-latin-gate's duplicate per-saint files removed
entirely; ss-philip-and-james's chapter files removed (its antiphons
stay proper).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
A systematic diff across all 29 duplex-majus+ saints' P/T/S/N files found
real shared-Common groups beyond the pairs noticed in passing while
authoring them. Extracted into shared data/propers/common/{hour}-antiphon-
common-of-*.yml / {hour}-capitulum-common-of-*.yml files and wired via a
new SaintRecord.minorHoursCommon field, with resolveMinorHourAntiphon/
resolveMinorHourChapter falling back to it when a saint has no proper file
of their own. Deliberately a separate field from the existing (previously
unused) `common` field: the minor hours don't always draw from the same
Common category as the saint's overall classification -- both Chair of
St. Peter feasts are `common: common-of-an-apostle` by rank, but their
P/T/S/N content is Common of a Confessor Bishop (Peter considered as
bishop of that see).
Four groups extracted (87 duplicate files collapsed to 21 shared ones),
each confirmed byte-identical via diff first: common-of-apostles (8
saints, all 4 hours), common-of-a-confessor-bishop (Chair of Peter pair
share antiphons+chapters, Martin of Tours shares only chapters), common-
of-the-bvm (Nativity BVM + Immaculate Conception's Sext/None/Terce
chapters -- the Assumption's own stay proper, different reading + proper
versicles), and common-of-st-peter (St. Peter ad Vincula + Ss. Peter and
Paul, not a real Common category, just genuinely identical Petrine
content).
Deliberately left un-extracted: St. Mark, St. John Before the Latin Gate,
and Ss. Philip and James share identical text that live-checking shows is
Paschaltide-seasonal (all three landed within the Easter octave weeks on
their queried dates, despite disagreeing `common` classifications) rather
than a fixed per-saint-category Common -- folding it into the same
mechanism would misrepresent season-contingent content as timeless. Left
as individually-authored files; flagged in TODO.md as needing a real
Paschaltide-awareness mechanism this app doesn't have yet for any hour.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Completes the P/T/S/N minor-hour override backlog started with St. Lawrence:
every duplex-majus+ saint not blocked by a missing collect (propers: null)
now has its own antiphon (all four hours) and chapter (Terce/Sext/None) text,
live-verified against Divinum Officium (Monastic Tridentinum 1617).
Two saints needed corrections to the reference engine's own data rather than
straight extraction: St. Scholastica's minor hours have no English rendering
in the source at all (English/SanctiM only covers Matins for her), so the
English is translated here directly and marked draft; the Immaculate
Conception's antiphons render with diacritics stripped relative to the
word-for-word-parallel Nativity BVM antiphon, so accentuation is restored
to match and marked draft. Several saints share identical Common of Apostles
or Common of the BVM content, confirmed via diff before reuse.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
4 authored fresh (Nicholas, the Immaculate Conception, Damasus, Thomas
of Canterbury), 1 corrected (Lucy -- confirmed sourced from the Roman
rite's raw [Ant 1] rather than a real Monastic 1617 rendering, same
pattern as every month so far). Thomas Apostle, Stephen Protomartyr,
John Apostle, and Holy Innocents already matched the live query as
found -- December's own late feasts (the Christmas Octave content)
turned out much less prone to the Roman/Monastic divergence than
earlier months, presumably because they're old and universal enough
that the two rites never actually diverged on them.
St. Bibiana left honestly missing: Simplex rank, Dec 2 falls within
Advent every tested year 2026-2029, same structural block as St.
Frances of Rome in March -- she can never win her own day outright
under this app's own calendar rules.
This closes out the full sweep: all 109 sanctoral entries with a
propers id have now been checked against a live Monastic query, twelve
months, one at a time. TODO.md has the final tally.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
4 authored fresh (All Saints, Clement I, Catherine of Alexandria,
Silvester Abbot), 2 corrected (Cecilia, Andrew -- each confirmed
sourced from the Roman rite's raw [Ant 1] rather than a real Monastic
1617 rendering, same pattern as every month so far). Martin of Tours
already matched the live query as found, no change needed.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
All 7 authored fresh: Francis of Assisi, Callistus I, Luke, Ss.
Chrysanthus and Daria, the Vigil of Ss. Simon and Jude, Ss. Simon and
Jude, the Vigil of All Saints.
Luke and Simon/Jude both needed the same register fix as Matthias/
Barnabas/James the Greater/Bartholomew/Matthew (Matt 19:27ff, same
verse): the source's own English existed but read jarringly modern
next to everything else, reworded to match and marked draft.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
4 authored fresh (Matthew, Ss. Cyprian and Justina, Ss. Cosmas and
Damian, Jerome), 1 corrected (the Nativity of the BVM -- confirmed
sourced from the Roman rite's raw [Ant 1] rather than a real Monastic
1617 rendering, same pattern as every month so far).
Matthew needed the same register fix as Matthias/Barnabas/James the
Greater/Bartholomew (Matt 19:27ff, same verse): the source's own
English existed but read jarringly modern next to everything else,
reworded to match and marked draft.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
9 authored fresh (Dominic, Ss. Cyriacus/Largus/Smaragdus, Assumption,
Bernard, Bartholomew, Louis, Zephyrinus, Augustine), 1 corrected
(Peter's Chains -- confirmed sourced from the Roman rite's raw [Ant 1]
rather than a real Monastic 1617 rendering, same pattern as every
month so far). Two already matched the live query as found (Finding
of St. Stephen, the Decollation of St. John the Baptist) and needed no
change.
Bartholomew needed the same register fix as Matthias/Barnabas/James
the Greater (Matt 19:27ff, same verse): the source's own English
existed but read jarringly modern next to everything else, reworded
to match and marked draft.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
8 authored fresh (the Seven Brothers/Rufina/Secunda, Symphorosa and
Her Seven Sons, Margaret of Antioch, Apollinaris, James the Greater,
Anne, Nazarius/Celsus/Victor I/Innocent I, Abdon and Sennen), 1
corrected (Mary Magdalene -- confirmed sourced from the Roman rite's
raw [Ant 1] rather than a real Monastic 1617 rendering, same pattern
as every month so far).
James the Greater needed the same register fix as Matthias/Barnabas
(Matt 19:27ff, same verse): the source's own English existed but read
jarringly modern next to everything else, reworded to match and
marked draft.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
11 authored fresh (Ss. Marcellinus/Peter/Erasmus, Ss. Primus and
Felicianus, Barnabas, Anthony of Padua, Basil the Great, Ss. Vitus/
Modestus/Crescentia, Ss. Mark and Marcellian, Ss. Gervase and Protase,
Paulinus of Nola, William Abbot, Leo II), 5 corrected (Ss. Basilides/
Cyrinus/Nabor/Nazarius, the Nativity of St. John the Baptist, Ss. John
and Paul, Ss. Peter and Paul, Commemoration of St. Paul -- each
confirmed sourced from the Roman rite's raw [Ant 1] rather than a real
Monastic 1617 rendering, same pattern as every month so far).
Barnabas needed the same register fix as Matthias (Matt 19:27ff, same
verse): the source's own English existed but read jarringly modern
next to everything else, reworded to match and marked draft.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
10 authored fresh (Athanasius, Monica, Stanislaus, Ss. Nereus/
Achilleus/Domitilla/Pancras, Boniface, Ubald, Peter Celestine, Urban I,
Eleutherius, John I), 3 corrected (Ss. Philip and James, John Before
the Latin Gate, the Apparition of St. Michael -- each confirmed
sourced from the Roman rite's raw [Ant 1] rather than a real Monastic
1617 rendering, same pattern as every month so far).
Urban I, Eleutherius, and John I (all late May) needed 2038 to find a
year not swallowed by Ascensiontide/Pentecost/Trinity/Corpus Christi
in some combination -- the latest year needed yet.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
All 8 authored fresh: Francis of Paola, Leo I, Ss. Tiburtius/Valerian/
Maximus, George, Mark, Vitalis, Robert, Peter Martyr. Several share
one Eastertide Common-of-Several-Martyrs antiphon word for word
(Tiburtius/Valerian/Maximus, George, Vitalis, Peter Martyr) -- each
confirmed independently via its own live query rather than assumed
from the others.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
4 authored fresh (Lucius I, Thomas Aquinas, the Forty Holy Martyrs,
Gregory the Great), 1 corrected (Joseph -- confirmed sourced from the
Roman rite's raw [Ant 1] rather than a real Monastic 1617 rendering,
same pattern as January/February's corrections).
Gregory the Great's antiphon (the "non Angli sed Angeli" episode) had
no English in the source at all -- translated fresh, marked draft.
St. Frances of Rome left honestly missing: Simplex rank, and her day
(March 9) falls within Lent in every year tested 2026-2035, so she can
never win outright under this app's own privileged-feria-major rule --
there's no live-rendered antiphon to source, ever, not just this year.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
5 authored fresh (Dorothea, Apollonia, Scholastica, Valentine,
Matthias), 2 corrected (Agatha, Chair of St. Peter at Antioch -- both
confirmed sourced from the Roman rite's raw [Ant 1] rather than a real
Monastic 1617 rendering, same pattern as January's corrections).
Two needed translation/rewording, not just transcription: Scholastica
had no English at all in the source (translated fresh, marked draft);
Matthias's own English existed but was jarringly modern next to every
other antiphon's archaic register, so reworded to match (the
underlying verse's standard Douay-Rheims wording) and marked draft
rather than presented as a straight source transcription.
Also noted in TODO.md, not fixed: splitNamedAntiphon always marks its
output verified regardless of input status, so a draft antiphon
currently renders as if fully verified wherever it's actually
displayed -- a pre-existing issue across every hour, only now surfaced
by content with a real draft status going through it.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Confirmed against the raw [Officium] file directly: Jan 14 is Hilary
(Latin/Sancti/01-14.txt), Jan 23 is St. Raymond of Peñafort
(Latin/Sancti/01-23.txt) -- not Hilary at all. The original Jan-23
placement wasn't fabricated, just misled by its own source: 2029 is a
real year where Jan 14 falls on a Sunday and every day through the
22nd is already occupied by another feast, so Hilary genuinely
transfers all the way to the 23rd that one year -- the app had
mistaken that transfer-year artifact for his real, fixed date.
His collect text is unaffected (a feast's collect doesn't change based
on which day it's said), re-confirmed identical on his real date.
Benedictus antiphon pulled fresh, completing January's antiphon pass
(9/9).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
All 9 January saints with a propers id: 5 authored fresh (Marcellus I,
Paul the Hermit, Fabian & Sebastian, Vincent & Anastasius, John
Chrysostom), 3 corrected (Chair of St. Peter at Rome, Agnes,
Conversion of St. Paul -- each confirmed sourced from the Roman rite's
raw [Ant 1] rather than a real Monastic 1617 rendering, same mistake
as St. Lawrence's earlier fix), 1 left honestly missing (Hilary of
Poitiers -- this app's calendar has him Jan 23, but Divinum Officium's
own calendar has him Jan 14; flagged in TODO.md rather than guessed
at). See TODO.md for the full remaining scope and the method this
settled on after automated matching proved unreliable on real edge
cases (shared dates, saints the reference engine doesn't carry at
all).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Was scattered across code comments and session summaries with no
single place to check what's actually left open -- consolidated here,
with concrete lists (which duplex-majus+ saints still need a Lauds
psalmody override, which already-authored Benedictus antiphons are
unverified against Monastic 1617 vs. the Roman rite they may have
actually been sourced from) rather than vague pointers.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
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>
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>
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>
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>
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>
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>
Every fixed and weekday-variable text Lauds needs beyond the psalms/
canticles themselves: per-weekday antiphons (data/hours/lauds-
antiphons.yml, reconstructed with an embedded '*' from the engine's
separately-rendered incipit/full lines), the weekday capitulum/
responsory/hymn/versicle bundle (shared verbatim Mon-Fri for the
capitulum, confirmed against the live engine), the fixed Benedictus
text, the Kyrie/Pater-noster short litany, the four Tridentine
suffrages, the conclusio, and the closing versicle.
Saturday's hymn is left honestly `missing`: every ordinary-time
Saturday is either Marian-Sabbato-overridden or falls in a privileged
season whose hymn is itself swapped for the season's own, so there's
no achievable date showing a genuinely per-annum, unprivileged
Saturday Lauds hymn to source it from.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Ps 5, 35, 42, 50, 56, 62, 63, 64, 66, 75, 87, 89, 91, 117, 142, 148-150
weren't needed by any hour built so far. The 7 weekday OT canticles
(Isaiah, Ezechias, Anna, Moses x2, Habacuc, the Three Young Men) get
their own small store (src/hours/lauds-canticles.ts), separate from the
Psalter, since they use their own liturgical verse-numbering per
canticle rather than the Psalter's 1-150 numbering.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
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>
Corrects the earlier "no Matins content exists" conclusion (previous
commit): that was true of the live prayMatutinum query specifically,
scoped to "Monastic Tridentinum 1617" -- which naturally selects nothing,
since the dogma wasn't defined until 1854. The reference project's raw
"bmv"-suffixed variant files (12-09bmv.txt through 12-14bmv.txt) do carry
real content: excerpts from Pope Pius IX's dogmatic bull "Ineffabilis
Deus" joined with a different Marian homily each day (Sophronius,
Bernard, Tarasius). Included per explicit user direction -- this
project's octave content is deliberately more generous than the strict
1617 monastic rubric alone would select.
Days 4 (Dec 11) and 6 (Dec 13) genuinely have no reading of their own in
the source (confirmed directly in the raw files, not assumed) -- both
already belong to another saint's own feast (Damasus, Lucy) anyway. Day
8 has no "bmv" variant file at all.
Also fixes two more source typos found while transcribing ("Himselfcan"
-> "Himself can", "Onlybegotten" -> "Only-begotten").
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Christmas's stacking cluster (Dec 26-31): only day 6 (Dec 30) has its own
Matins reading -- the one day nothing else wins outright, confirmed via
its own Invitatory. St. Stephen, St. John, and the Holy Innocents' own
octaves (also active there, stacking) have no separate reading content
anywhere in this range -- every other day belongs entirely to whichever
saint natively wins it, same as Immaculate Conception's octave (no
Matins content at all beyond its own day, confirmed and left
unauthored -- checked, not skipped).
Pentecost's octave: days 2-7 all have real content, confirmed via a
consistent distinctive Invitatory throughout, including its own Ember
Saturday (day 7) -- Pentecost's own content governs Matins there despite
St. Felix I's stub-level commemoration that same day (see
tests/calendar/transfer.test.ts). Day 8 (Trinity Sunday) has none of its
own -- Trinity's own distinct office governs that day's actual Matins
content, so no reading is authored for it despite the octave mechanism
still commemorating Pentecost there. Two readings are direct Scripture
(Acts 19:1-9, Joel 2:23-28) with no source English rendered; used the
standard Douay-Rheims wording (verified, not draft) rather than the
project's own patristic-prose translations, matching how every other
Scripture citation in this project is sourced.
Also fixes two more instances of the recurring "dropped space after
capital I" source typo ("Icame" -> "I came").
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
All Saints: all 7 days (2-8) have real content -- a continuous reading
from the book of Ezechiel read straight through the whole octave,
confirmed via each day's own distinctive Invitatory antiphon, present
even on Nov 2 (so this app's own All Souls' Day exclusion doesn't
conflict with anything here). Also fixes two more recurring "dropped
space after capital I" source typos ("Iwill" -> "I will") found by
grepping the accumulated readings so far, retroactively correcting
St. Lawrence's day 8 and the Assumption's day 6.
vite.config.ts: raises workbox's maximumFileSizeToCacheInBytes -- the
growing body of eagerly-bundled octave-reading text (by design, for
offline use) pushed the main chunk past the default 2 MiB precache cap,
which was failing the production build.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Assumption: days 2, 4-8 (day 3, Aug 17, is entirely overshadowed by St.
Lawrence's own octave-concluding day, a higher-rank Duplex that day --
no separate Assumption reading exists there). Also fixes a source typo
("Isaw her" -> "I saw her") in the shared responsory, retroactively
correcting day 2's file from the previous commit.
Nativity of the BVM: days 2, 4, 5, 6, 8 (day 3, Sep 10, belongs to St.
Nicholas of Tolentino's monastic-specific reassignment; day 7, Sep 14, is
the Exaltation of the Holy Cross -- confirmed via each day's own
Invitatory antiphon, not assumed). Day 5 had no source English at all;
translated per this session's discussion (status: draft). Also fixes a
second source typo ("Gorgonitis" -> "Gorgonius" in a commemoration
heading).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
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>
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>
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>
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>
16 saints, plus a propers-slug update for the pre-existing Immaculate
Conception entry. Also extends the Advent temporal-category fix
(previous commit) to Christmastide: St. Thomas of Canterbury, only
Semiduplex, wins outright within the Christmas Octave in the real
Monastic 1617 engine, which the stronger privileged-feria tier would
have wrongly forbidden -- Christmastide now shares Advent's
privileged-feria-minor tier instead. Every real winner this month falls
within one of these two newly-corrected seasons.
This completes the first pass of the pre-1955 sanctoral calendar pull
across all twelve months.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Found while starting the December sanctoral pull: Advent's plain
(non-Ember) weekday ferias have real standing in the live Monastic 1617
engine that the existing ordinary-feria category couldn't represent --
a Simplex saint (St. Bibiana, Dec 2) is merely commemorated there rather
than winning outright, while a Semiduplex+ saint (St. Nicholas, Dec 6)
wins with the feria itself commemorated back. Adds a third, weaker
privileged-feria-minor tier (Semiduplex+ threshold, vs. privileged-
feria's Duplex+) rather than reusing either existing tier, mirroring how
privileged-feria/privileged-feria-major were split in March. Lent's own
ordinary ferias likely share this property but are left as-is, unverified
this session.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
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>
19 saints (14 real winners + 5 stubs), plus a propers-slug update for the
pre-existing All Saints entry. Three monastic-calendar-specific
reassignments this month, the most of any month so far (St. Emilian
displacing St. Martin I Pope, who moves to a different date; St. Odo
demoting St. Pontian; St. Silvester Abbot displacing St. Peter of
Alexandria), plus All Saints of the Benedictine Order, a monastic-only
addition at Duplex II. classis. New exclusion category: All Souls' Day
(Nov 2) is a fundamentally different liturgical genre -- the Office of
the Dead -- out of scope the same way the Requiem rite isn't modeled
anywhere else in this app.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
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>
16 saints (14 real winners + 2 stubs). Three monastic-calendar-specific
finds this month: Ss. Placid and Companions (Oct 5, St. Benedict's own
disciple, Duplex II. classis -- absent from the base Tridentine table
entirely), St. Justina (Oct 7, reassigns what the base table gives to
St. Marcus, Pope), and St. Gall (Oct 16, founder of St. Gallen, also
absent from the base table). No octave-gap exclusions this month.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Records the explicit choice to keep the pre-existing Sep 29 mapping as a
deliberate simplification rather than removing it, per user decision.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
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>
14 saints (12 real winners + 2 stubs), plus a propers-slug update for the
pre-existing Nativity of the BVM entry. Two monastic-calendar-specific
finds: St. Nicholas of Tolentino (Sep 10) overrides what the base
Tridentine table treats as a plain Nativity-octave continuation, and St.
Januarius (Sep 19) is a monastic-only addition absent from the base table
entirely. Also flags (not yet acted on) that the pre-existing Sep 29 St.
Michael mapping doesn't appear to hold in this pull's actual target
track -- see the sanctoral-calendar.yml comment for detail.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
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>
18 saints (14 real winners + 4 stubs), plus propers-slug updates for the
4 pre-existing August entries. Notable this month: several dates have a
real named secondary saint permanently subordinate to an unmodeled fixed
octave (Lawrence's own, the Assumption's) or the fixed Transfiguration --
excluded entirely rather than guessed at, since this app can't yet
represent the actual blocking primary (a known, deferred octave gap).
Most notable casualty: St. Clare (Aug 12).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
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>
23 saints for July (18 real + 5 stubs). Caught and corrected a real
mistake in this pull's own methodology: an earlier live-query pass
recorded St. Apollinaris under July 24 based on a year where he'd
actually transferred there (his true date, the 23rd, was a Sunday that
year) — cross-checking against the raw calendar text file
(1570.txt) caught it before it went in. His real date is the 23rd, with
St. Liborius as his (permanently unwinnable) secondary; July 24 is
instead the Vigil of St. James with St. Christina as its own secondary.
Five stubs this month are all genuinely unwinnable (fixed-date
competitors) — the most of any month so far.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Real collect text for all 16 June saints that have one, plus a real
antiphon for 4 of them and a fifth (Ss. Peter and Paul) that deliberately
reuses the Chair-of-Peter-at-Antioch antiphon text, matching the source's
own cross-reference — same extraction method as prior months.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
18 saints for June, all winning outright on a clean per-annum date — no
stubs this month, including two normally blocked by the generic
"Sanctae Mariae Sabbato" Saturday votive, which just needed a
non-Saturday year instead.
Three distinct Pauline feasts now exist in this pull (Conversion of St.
Paul, Jan 25; Ss. Peter and Paul, June 29; Commemoration of St. Paul,
June 30) plus a second St. Leo (Leo II, June 28, distinct from Leo I in
April). The Nativity of St. John the Baptist (Duplex I. classis, the
highest rank pulled so far) is kept in the sanctoral store despite the
very high rank, same as the Conversion of St. Paul/Chair-of-Peter
feasts — it's about the saint himself, not a feast of the Lord/BVM.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
June's sanctoral pull assigned Ss. Vitus, Modestus, and Crescentia to
2026-06-15 — the same date prime.test.ts's MONDAY constant used, which
correctly flips Prime's capitulum/Preces from ferial to feast form now
that isSundayOrFeast sees a real sanctoral winner there. Moved to an
still-unpulled October Monday instead; also fixed a stale comment that
had drifted from the constant's actual value.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Real collect text for all 13 May saints that have one, plus a real
antiphon for the 3 that have one of their own (Ss. Philip and James, St.
John Before the Latin Gate, the Apparition of St. Michael) — same
extraction method as prior months. Two saints (St. Peter Celestine, St.
Urban I) have an [Ant 1] in the source that turned out to just be a
cross-reference to the generic Common template, not real unique text, so
no antiphon file was authored for them.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
18 saints for May (16 real + 2 stubs). Both stubs this month are
genuinely unwinnable in the real historical calendar in any year (their
competing observances are fixed-date, not movable) — flagged explicitly
in their own files as distinct from the usual "just an artifact of the
year checked" caveat.
A second, distinct feast of St. Michael (his Apparition, May 8) is kept
as its own record separate from st-michael.yml's Sept 29 dedication
feast, same precedent as the two Chair-of-Peter feasts.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Found via real data (the May sanctoral pull): a transfer landing on a day
whose own native winner was temporal blindly took over as winner,
regardless of that day's own precedence category — so a transferred-in
Simplex saint (St. Felix I, impeded by Pentecost's Ember Saturday) was
overwriting Trinity Sunday itself instead of being merely commemorated,
the way a native Simplex candidate on an ordinary Sunday already
correctly is.
Fixes applyIncomingTransfer to run the arriving candidate through
decideOccurrence using the receiving day's own temporalCategory, the same
rules a native occurrence there would use, instead of assuming an empty
temporal slot means automatic victory.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Real collect text for all 8 April saints that have one — same extraction
method as prior months. One correction to St. Robert's own English
translation in the source, which left his name in Latin genitive form
("blessed Robérti") rather than translating it — fixed, flagged in the
file as a transcription artifact, not a deliberate style choice.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
12 saints for April (11 real + St. Catherine of Siena as a stub), all
winning outright on a clean per-annum date.
Two real methodological findings this month:
- St. Mark (Apr 25, Duplex II. classis) directly confirms the
privileged-feria-major behavior from the previous fix: he only ever
gets commemorated within the Easter Octave, never wins there.
- St. Robert/St. Catherine of Siena (Apr 29) is a genuine, year-stable
monastic-calendar reassignment — this app's own base Latin source data
files that date's Roman-calendar content under a different saint (Peter
Martyr) entirely, which a naive raw-file lookup by MM-DD would have
missed; the live per-date query resolved it correctly regardless, and
is what these records are actually built from. Worth remembering for
future months.
New common category: common-of-an-evangelist (St. Mark, an Evangelist
but not an Apostle).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
A plain Duplex saint was losing to any privileged feria (Ember days
included) since the occurrence engine only let duplex-1-classis+ get
even a commemoration there. Verified this was wrong: St. Gregory the
Great, plain Duplex, outright wins against a Lenten Ember Saturday in
the real Monastic 1617 engine.
Splits TemporalCategory's single privileged-feria into privileged-feria
(the lesser tier — Lent's Ember days, verified — now behaves like an
ordinary Sunday: Duplex+ wins outright) and privileged-feria-major (Ash
Wednesday, Holy Week, the Easter Octave, verified via St. Mark only ever
being commemorated there, never winning — behaves like privileged-
Sunday). Everywhere else the old single category was ambiguous
(Pentecost's own Ember days/Vigil/Octave, the Vigil of Christmas)
defaults to the stricter major tier pending verification.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Real collect text for all 6 March saints that have one, plus St. Joseph's
own antiphon — same extraction method as January/February.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
7 saints for March, all winning outright on a clean per-annum date. Notably
St. Benedict himself (Mar 21, Duplex I. classis, the founder — "S. P. N."
in the source) turns out to have no unique Oratio/Antiphon at all in this
date's source file, only Common-of-an-Abbot cross-references — flagged in
his own file to check his second (July) feast when that month comes up.
Two Benedictine-oblate saints (Frances of Rome, Gregory the Great) are
marked "O. N." (Ordinis Nostri) in the source — real significance for a
monastic calendar. St. Frances also needed a new common category,
common-of-a-widow (she's neither virgin nor martyr).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Real collect text for the 6 February saints that have one, plus a real
antiphon for the 2 that have one of their own — same extraction method
as January (fully-rendered live-engine output, not the raw source files'
own shorthand endings).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
13 saints for February (12 real + the Vigil of St. Matthias), all
verified winning outright on a clean per-annum date — no secondary/
commemorated stubs needed this month, unlike January.
Two new scope exclusions found this month, beyond the usual octave/
temporal-continuation and fixed Lord/BVM feast ones: the generic
"Sanctae Mariae Sabbato" Saturday votive (not a real saint) and Feb 29's
leap-year redirect of the Vigil of St. Matthias (a real but not-yet-
modeled leap-day date shift).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Real collect text for the 9 January saints that have one, plus a real
antiphon for the 3 that have one of their own (as opposed to falling back
to a generic Common-of-Saints text) — read directly from the reference
engine's fully-rendered output, not reconstructed from the raw source
files' own shorthand endings ($Per Dominum, $Qui vivis, etc.).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
20 saints for January, verified per-day against Divinum Officium (Monastic
Tridentinum 1617): 13 that actually win their day outright (rank pulled
from a live per-date query, since the static 1570/M1617 calendar text
files turned out unreliable once monastic-specific overrides apply) and
7 stubs for saints that only ever get commemorated, with an explicitly
flagged, unverified simplex rank guess pending correction.
Deliberately out of scope: pure octave-continuation days (In Octava S.
Stephani, the Epiphany-octave ferias) and fixed-date feasts of the
Lord/BMV (Circumcision, Epiphany) — sanctoral only, not temporal.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Three separate resolver files (terce.ts/sext.ts/none.ts), not a shared
little-hour module — they're structurally identical but kept independent
by choice. Ordo verified against Divinum Officium (Monastic Tridentinum
1617): opening versicle, self-contained hymn, one antiphon framing three
psalms (no closing repeat, unlike Prime), a capitulum with its own
embedded short versicle/response, the day's own collect, and a closing
identical across all three hours.
Also fills in psalter-distribution.yml's previously-missing Sunday/
Monday/Tuesday psalm entries (Ps 118 sections for Sunday/Monday; Tuesday
turns out to share Wednesday/Thursday's native gradual-psalm set, not a
distinct one). Friday/Saturday's antiphons are derived from each day's
first psalm rather than reusing the native gradual antiphons, since
those two days already use a redistributed, non-native psalm set.
hours/types.ts gains a 'day-collect' part kind — the first ordo to use
the day's own Proper collect rather than a fixed text.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Fixed, self-contained text (including each hymn's own final doxology
stanza) — unlike Prime's/Compline's hymns, these don't change by season,
so there's no appendDoxology split. Verified against Divinum Officium
(Monastic Tridentinum 1617).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Text for every psalm src/data/psalter-distribution.yml's terce/sext/none
tables reference but hadn't been authored yet — the native Gradual set
(119-127) plus the psalms used by the Friday/Saturday redistribution.
Verified Latin/English against Divinum Officium (Monastic Tridentinum
1617).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Terce/Sext/None's Oratio is the day's own Proper-of-season/-saints
collect, not a fixed text — getDayCollect() resolves it from the
LiturgicalDay's winner (temporal id or sanctoral saint's propers),
falling through to an honest "missing" status for saints that don't
have authored propers yet.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Corrects and completes the occurrence rules, based on a design discussion
plus one concrete data point: St. Anthony Abbot (plain Duplex) was found
outright winning against an ordinary Sunday in the real Monastic 1617
engine, which the old duplex-1-classis-only threshold got wrong.
- FeastClass gains `vigil`, inserted between `simplex` and `semiduplex` —
one ordering that correctly serves both "does this win against a Sunday"
(vigil behaves like simplex there) and "which of two saints wins a
landing-day collision" (vigil beats simplex, loses to semiduplex).
- LiturgicalDay.occurring (a flat OccurringFeast[] that could only ever
express a losing *sanctoral* candidate) is replaced by `winner:
DayWinner` + `commemorations: Commemoration[]` — a discriminated list
that can hold the temporal day itself, one or more sanctoral entries, or
(not built yet, but the shape already accommodates it) a future octave
kind.
- commemorations.ts: ordinary Sundays let Duplex+ win outright (Sunday
commemorated in return), Semiduplex/Vigil transfer elsewhere (too
substantial a feast to cheapen with a bare commemoration), Simplex stays
and is commemorated. Privileged Sundays never displace; Duplex-majus+
commemorated, everything else transfers.
- collision.ts (new): resolves two sanctoral candidates wanting the same
day (a transfer landing on an already-occupied day, or two native
saints sharing a date) — duplex > semiduplex > vigil > simplex, loser
always commemorated, ties favor the native occupant.
- temporal-id.ts (new): maps any date to one of the 52 real Sunday-collect
ids from the previous commit, so a temporal winner/commemoration can
actually be looked up, not just labeled "temporal" in the abstract.
- index.ts's resolveDay orchestrates all of it, including the actual
Monday/Saturday transfer mechanism. Landing on a privileged feria (the
concrete case: Holy Week, right after Palm Sunday) is explicitly
deferred rather than guessed at — it needs its own Easter-keyed lookup
table, the same way the reference engine handles it.
Added the Vigil of St. Lawrence (Aug 9) as real content specifically to
exercise the backward-transfer rule end-to-end: Aug 9, 2026 is a Sunday,
so the vigil transfers cleanly back to Saturday, verified by a new
integration test alongside the unit-level rule and collision tests.
New data/propers/temporal/*.yml and propers/index.ts's getTemporalProper,
mirroring the existing common-propers store. Populated with all 52
distinct Sunday collects for the full liturgical year (Advent 1-4, the
Christmas-octave Sunday, Epiphany 1-6, Septuagesima-tide, Lent 1-4 +
Passion + Palm Sunday, the 8 Paschal-time Sundays through Pentecost, and
post-Pentecost 1-24), each verified against the real Monastic Tridentinum
1617 engine.
Keyed Pentecost/offset-based ("post-pentecost-N-collect"), not
Trinity-counted — that's a display choice for calendar/day-label.ts, kept
independent of this content's storage identity. 8 of the 52 needed to be
read directly from the reference engine's source files plus its standard
closing-formula macros, since a real saint outranks those particular
Sundays on every candidate year checked and a live date query would only
ever return the saint's own collect.
Non-Sunday Monastic Matins is 12 psalms/day; ferial Terce/Sext/None always
repeats the same 9 (119-127) regardless of weekday. Worked out an editorial
redistribution in conversation: cut non-Sunday Matins to 9/day, and hand
the freed capacity to Friday's and Saturday's Terce/Sext/None instead of
discarding it, keeping Wednesday/Thursday's 119-127 as-is and leaving
Sunday/Monday/Tuesday untouched entirely.
No code reads `matins`/`terce`/`sext`/`none` from this table yet — those
hours don't exist until milestone 3 (T/S/N) and milestone 5 (Matins).
Recording the decision now, with the method spelled out in the comment,
so it survives until then rather than needing to be re-derived. Verified
against a real Monastic Tridentinum 1617 run throughout, not reconstructed
from memory.
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.
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.
Compline (and eventually Vespers) now resolves off whichever day's
identity actually governs tonight's office, not always today's — real
practice anticipates a higher-ranking tomorrow (Alma Redemptoris Mater
starting the Saturday evening before Advent I, Ave Regina Caelorum
starting the eve of Candlemas, Regina Caeli starting at the Easter
Vigil), which the previous date-only resolution missed entirely.
calendar/vespers.ts's resolveEveningDay compares today's and tomorrow's
precedence (reusing commemorations.ts's FeastClass machinery) to decide.
One deliberate absolute exception, caught by a failing test: a short,
explicit list of supreme fixed/movable feasts (Easter, Christmas,
Epiphany, Candlemas, Ascension, Corpus Christi, Sacred Heart) always wins
tomorrow's Vespers before checking whether today would otherwise keep its
own — without it, Holy Saturday's privileged-feria status incorrectly
blocked Easter's anticipation, the textbook case this whole thing exists
to get right.
hours/compline.ts now resolves its whole ordo through resolveEveningDay
instead of resolveDay.
Replaces the FeastRank placeholder with a real ordered FeastClass (the old
pre-1955 six-level scale) and a new TemporalCategory (privileged/ordinary
Sunday/feria). calendar/commemorations.ts's decideOccurrence encodes the
actual precedence rules as explicit, commented branches per category
rather than the reference engine's own opaque numeric weights — this app
targets one ruleset, so the readability trade-off is worth it. Category
membership (data/calendar/temporal-categories.yml) is a best-effort
reconstruction of the pre-1955 tradition, not sourced from a primary text;
expect corrections.
calendar/feasts.ts resolves real sanctoral candidates, and resolveDay()
wires it all together — occurring/temporalCategory are no longer stubs.
isDoubleOrHigher (hours/antiphon.ts) is now a real comparison instead of
a hardcoded false.
Seeded 7 real saints/feasts, each verified directly against Divinum
Officium's own rank data (not reconstructed from memory) as a small,
growable start: St. Lawrence, the Assumption, St. Augustine, the Nativity
of the BVM, St. Michael, All Saints, the Immaculate Conception.
Ordo is Monastic 1617 as a base, with deliberate Tridentine 1906
additions: the "In manus tuas" short responsory, the Nunc Dimittis
with its "Salva nos" antiphon, the fuller Preces (Monastic's own is
shorter), and Lent's "Christe, qui lux es et dies" hymn replacement
(English translation marked draft, pending a real historical one).
Psalms 4, 90, 133 are fixed daily with real verified text, unlike
Prime's weekday-rotated psalms.
The closing Marian antiphon now picks Alma Redemptoris Mater / Ave
Regina Caelorum / Regina Caeli / Salve Regina by real season, using
the calendar work from the previous commit — except Ave Regina
Caelorum's own window (Candlemas through the day before Maundy
Thursday) doesn't align to any single season value, so that one case
checks the real date directly instead of going through the by-season
table.
resolve-common.ts extracts the resolve/status/doxology/antiphon
helpers Prime already had into something Compline can share; a few
propers files lose their "prime-" prefix now that both hours use them.
resolveDay() no longer hardcodes season: 'trinitytide'. Easter is
computed via the Anonymous Gregorian algorithm (verified against 16
known reference dates spanning 1583-2100); Advent/Christmastide/
Epiphanytide follow the Christmas-anchored rules (including Advent's
"Sunday nearest Nov 30"); Septuagesima through Trinitytide come from
easter-offsets.yml, reshaped from single named anchor points into real
ranges plus single-day overrides for Corpus Christi and Sacred Heart.
occurring feasts (calendar/feasts.ts) are still a stub — the sanctoral
calendar and Double-vs-not ranking are a separate, larger project.
2026-08-10 05:27:12 -04:00
4337 changed files with 317771 additions and 400 deletions
# No [Ant 1] of its own in the source (09-01cc.txt) -- [Rule]/[Rank] point to
# Commune/C3 instead, the same C-number family already live-query-verified
# for common-of-several-martyrs.yml (see that section of TODO.md).
minorHoursCommon:common-of-several-martyrs
benedictusCommon:common-of-several-martyrs
Some files were not shown because too many files have changed in this diff
Show More
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.