Commit Graph

23 Commits

Author SHA1 Message Date
will 449472d5aa Add Matins hymn Common-of-Saints tier and a real invitatory-antiphon mechanism
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
2026-08-24 07:14:06 -04:00
will 8f55ec6931 Centralize antiphon opening-doubling logic across all hours
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
2026-08-24 07:13:38 -04:00
will f8de318b7f Fix Matins nocturn readings leaking suppressed Sunday content
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
2026-08-24 07:11:09 -04:00
will 42dd4dc04b Add Te decet laus to Matins, drop Ps 129/50 from ferial Preces
Deploy / deploy (push) Successful in 1m24s
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
2026-08-23 09:03:04 -04:00
will ca28f2ef5f Fix Matins reading-pool ordering when Nocturn 2/3 come from different sources
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
2026-08-23 08:02:16 -04:00
will 72a0f8072e Import calendar-month/week Matins Nocturn 2 content for Aug-Nov
Deploy / deploy (push) Successful in 1m24s
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
2026-08-23 07:45:37 -04:00
will 7858692fa7 Wire the flexa mark into every hour's psalm-antiphon pairing
Deploy / deploy (push) Successful in 1m24s
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
2026-08-23 06:30:58 -04:00
will 9fb9cb5e24 Add Ember-day mechanism (privileged feria, not a rank contest) + real content
Deploy / deploy (push) Successful in 1m17s
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
2026-08-22 07:27:52 -04:00
will 5e79fb4edf Generalize non-Sunday movable feasts into one Easter-offset resolver
Deploy / deploy (push) Successful in 1m21s
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
2026-08-22 06:33:15 -04:00
will cf4edfae2b Fix Matins hymn to actually keep the Assumption's octave hymn all week
Deploy / deploy (push) Successful in 1m31s
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
2026-08-22 06:00:42 -04:00
will 224f34908c Relocate Immaculate Heart of Mary off Aug 22 to Easter+69
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
2026-08-22 05:59:33 -04:00
will 025d0e3a08 Fix Sunday Matins nocturn versicles dropping their R. line
Deploy / deploy (push) Successful in 1m13s
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.
2026-08-21 09:31:09 -04:00
will 675b644c68 Add antiphons and versicle to plain ferial weekday Matins nocturns
Deploy / deploy (push) Successful in 1m13s
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.
2026-08-21 09:27:13 -04:00
will 62c74539f8 Add Matins hymn override mechanism
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
2026-08-21 07:56:06 -04:00
will 15929a6e50 Add Matins' missing closing (Benedicamus Domino / Fidelium animae)
Deploy / deploy (push) Successful in 1m7s
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>
2026-08-19 09:24:54 -04:00
will e93fc4032a Drop Matins' opening (versicle, Ps 3, Invitatory, hymn) during the Sacred Triduum
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.
2026-08-19 06:18:07 -04:00
will a9f673fef9 Label Matins nocturns and the Invitatory in the rendered ordo
Deploy / deploy (push) Successful in 1m17s
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>
2026-08-19 05:28:44 -04:00
will ef71d9c5bc Distribute Matins readings front-light across nocturns, not evenly
Deploy / deploy (push) Successful in 1m7s
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>
2026-08-18 13:06:57 -04:00
will 0a8bcef859 Wire the Matins psalmody-override category tier into real resolution
Deploy / deploy (push) Successful in 1m5s
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>
2026-08-18 06:52:08 -04:00
will 3e58f2258d Add Matins psalmody overrides for a Duplex+ weekday feast
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.
2026-08-18 06:11:52 -04:00
will 2cc856ed92 Rework Matins readings into a pooled, distributed model
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>
2026-08-16 07:06:23 -04:00
will bedbf577ea Build Matins' real dynamic ordo, wire into the UI
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).
2026-08-14 11:40:43 -04:00
will a5dc6502d7 Milestone 0/1: shell, calendar/psalter/hours scaffold, Prime
Deploy / deploy (push) Failing after 1m8s
Client-side-first PWA (Vite/TS, no backend) per the approved plan: day-
navigable shell listing all 8 hours, Prime fully resolves via the
calendar -> psalter -> ordo pipeline, the other 7 hours are registered
but flagged not-implemented. Sanctoral/temporal calendar data uses a
day -> id indirection layer (saints, easter-offsets, fixed-date-calendar)
so reassigning a feast to a different day is a data edit, not a code
change. Docker (Caddy-serving-static) + Gitea CI workflow scaffolded to
match the eec/drip/bookshop operational pattern.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-09 15:20:06 -04:00