Author Easter Sunday/Monday/Tuesday's shared proper Matins
Deploy / deploy (push) Successful in 1m29s

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
This commit is contained in:
2026-09-02 07:42:39 -04:00
parent c2b24ae77d
commit 57acbeefe9
3 changed files with 185 additions and 6 deletions
+64 -6
View File
@@ -19,12 +19,13 @@ across every hour and content type in this app.
1. **Mechanism gaps** — a real design/code piece not built yet, blocking any content pass on
it from starting:
- ~~Easter's own octave~~ — DONE (2026-09-01): the transfer-cascade piece (a candidate
impeded within Holy Week/Easter Day/the Octave/Low Sunday now correctly skips the whole
span and keeps walking forward, FIFO, until it lands) is built and live-verified — see
the dated log's "Easter octave transfer cascade" entry. Content-wise, Easter Day/the
Octave's own full Matins proper (distinct psalms, not just antiphons) is still not
modeled — a separate, narrower remaining gap, not this one.
- ~~Easter's own octave~~ — DONE (2026-09-01/02): the transfer-cascade piece (calendar
precedence within Holy Week/Easter Day/the Octave/Low Sunday) and Easter Sunday/Monday/
Tuesday's own proper Matins (psalms/antiphons/versicles/canticles, sharing one authored
entry via a new general-purpose temporal-override mechanism) are both built and
live-verified — see the dated log's "Easter octave transfer cascade" and "Easter's own
octave" entries. What's left is narrower, moved to the bulk-content section below:
Wednesday-Saturday of the Octave's own (different, not yet authored) antiphons.
- Date picker: `day-nav.ts` currently only steps a day at a time (prev/next); there's no
way to jump to an arbitrary date without walking there one day at a time or editing the
URL. Needs a calendar-grid picker UI — open, not started.
@@ -52,6 +53,11 @@ across every hour and content type in this app.
nocturn-readings sweep" below, including the easter-sunday/pentecost-sunday correction).
Moved out of this "bulk content pending" section since there's no more bulk content left to
author here.
- Wednesday-Saturday of the Easter Octave: real Paschal-alleluia antiphons exist in the
reference engine (live-verified 2026-04-08) over a differently-sized psalm set than vu's
own fixed ferial table — 4 days of antiphon-only content, same shape as the existing
Advent/Paschaltide seasonal-overlay pattern, layered onto vu's own psalm numbers rather
than the reference engine's. See the dated log's "Easter's own octave" entry.
- The bulk Vulgate (Latin) + Douay-Rheims (English) scripture import into
`src/data/scripture/` — long-term project, explicitly deferred; Matins scripture
citations resolve to `missing` text until it lands.
@@ -4876,3 +4882,55 @@ Terce/Sext/None antiphons+versicles, Te Deum present.
See memory `vu_matins_semiduplex_threshold` for full detail.
`npm test` (823 passed), `tsc --noEmit`, and `npm run build` all pass.
### Easter's own octave — Sunday/Monday/Tuesday proper Matins authored, general temporal-override mechanism added (2026-09-02)
Closes most of the long-tabled "Easter's own octave" mechanism gap (Priority section, item 1).
User pushed back on an initial overcomplicated read ("psalms with lections" looked like a new
rendering shape at first glance) — turned out, after live-verifying the actual structure, to
be exactly what the user guessed: a temporal override file that "looks weird" only because of
its content (13 psalms under one antiphon per nocturn, not the usual per-pair grouping), not
because it needs new mechanism. Every psalm already gets its own Gloria Patri centrally
(`hours/index.ts`'s `withGloriaPatri`) regardless of hour, and the 12 patristic lessons were
*already* authored (`data/propers/nocturn-readings/easter-sunday.yml`, from the earlier
temporal nocturn-readings sweep) — the only real gap was the psalmody/antiphon/versicle/
canticle layer.
**Content**: new `easter-sunday` entry in `matins-sunday-named-nocturn-overrides.yml`
live-verified against the reference engine (`Pofficium.pl`, `prayMatutinum`, Monastic
Tridentinum 1617, 2026-04-05/06/07) — Nocturn 1: psalms 1,2,8,15,23,27 under one shared
antiphon ("Ego sum qui sum"); Nocturn 2: psalms 29,63,65,75,87,107 under one shared antiphon
("Terra trémuit"); Nocturn 3: the same 3 canticles already authored for the ordinary-
Paschaltide seasonal override (Isa 63:1-5, Osee 6:1-6, Soph 3:8-13), under its own antiphon
("Nolíte expavéscere"). All 3 nocturn-closing versicles are byte-identical to the ones
already authored in `matins-sunday-paschaltide-nocturn-overrides.yml`, confirming those are
shared season-wide rather than coincidentally reused.
**Mechanism (small, general-purpose, not Easter-specific)**: `hours/matins.ts`'s
`threeNocturns` gate and the Sunday-branch dispatch previously only ever fired for
`day.weekday === 'sunday'` or a sanctoral winner — a *temporal* winner had no path into 3-
nocturn proper content at all, even with a matching `sundayNamedOverrides` entry. Generalized:
a temporal winner with an authored named override is now eligible too (`hasTemporalNamedOverride`),
reusing the exact same `sundayPsalmNocturn`/`sundayCanticleNocturn` render functions the Sunday
and saint-proper branches already share — deliberately built as a reusable feature (user,
2026-09-02: "we want temporal override anyway... there are temporal feasts that could have
their own psalms/lessons"), not a one-off hack.
**The scoping wrinkle that made this non-trivial**: `winner.id` is `'easter-sunday'` for the
*entire* Octave (all 6 weekdays), but only Sunday/Monday/Tuesday are live-verified to share
this content — Wednesday-Saturday have their own different (Paschal-alleluia-flavored, not yet
authored) antiphons over a differently-shaped psalm set. A naive "any day with this winner.id"
check would have wrongly applied Sunday's content through Saturday. Fixed with a new optional
`weekdays` field on a named-override entry (`easter-sunday: weekdays: [sunday, monday,
tuesday]`) — omitted (as `sunday-after-ascension` still does) means "every day carrying this
temporalId," the common case for a temporalId that already maps to exactly one calendar day.
**Still open, a separate smaller gap**: Wednesday-Saturday of the Easter Octave — live-verified
(2026-04-08) to carry real Paschal-alleluia antiphons ("Jesum qui crucifíxus est quǽritis,
allelúja...") over a psalm set the reference engine sizes differently from vu's own
already-fixed 9-psalm ferial table (itself deliberate — see "No RB summer contraction" in
CLAUDE.md). 4 more days of antiphon-only content, same shape as the existing Advent/Paschaltide
seasonal-overlay pattern, layered onto vu's own ferial psalm numbers rather than trying to
reproduce the reference engine's differently-sized set — not started.
`npm test` (828 passed), `tsc --noEmit`, and `npm run build` all pass.
@@ -7,6 +7,14 @@
#
# hours/matins.ts's lookup checks this map first (most specific), ahead of
# the season-wide overrides, when `temporalId` matches a key here.
#
# `easter-sunday` (below) is also reused for Easter Monday and Tuesday
# (`hours/matins.ts`'s threeNocturns/Sunday-branch dispatch route any
# temporal winner with a matching entry here through the same rendering as
# a real Sunday, not just `day.weekday === 'sunday'` literally) — the
# reference engine's own content is byte-identical across all three days
# (live-verified 2026-09-02, 2026-04-05/06/07), so one entry covers all
# three rather than three separate near-duplicate ones.
# Sunday within the Octave of Ascension (`sunday-after-ascension`) —
# live-verified against the local reference-engine CGI instance
@@ -32,6 +40,71 @@
# translation of the underlying Mark 16:20 text (a well-known, unambiguous
# formula, not a guess), consistent with how every other antiphon in this
# file is sourced.
# Easter Sunday itself (and Monday/Tuesday of the Octave, which share this
# entry — see the file header above). Live-verified against the local
# reference-engine CGI instance (`prayMatutinum`, Monastic Tridentinum
# 1617, 2026-09-02, proof dates 2026-04-05/06/07). Unlike every other
# Sunday scheme in this file, each nocturn here uses ONE shared antiphon
# over ALL 6 of its psalms (not one antiphon per psalm, nor per pair) —
# confirmed by the live query: a single "Ant." before the first psalm and
# the same antiphon repeated in full after the last, nothing in between.
# Nocturn III reuses the same 3 canticles as the ordinary-Paschaltide
# override (Isa 63:1-5, Osee 6:1-6, Sophonias 3:8-13 — already authored,
# no new scripture files needed) under its own different antiphon; all 3
# nocturn-closing versicles are also byte-identical to the ones already
# authored in matins-sunday-paschaltide-nocturn-overrides.yml, confirming
# those are shared season-wide, not coincidentally reused.
#
# The reference engine's own [Rule] for this day actually reads "1
# nocturn" (the whole night office collapses to a single nocturn of 3
# lessons on this most privileged of feasts) — not modeled that way here;
# this app's own threeNocturns rule renders every Sunday with 3 nocturns
# unconditionally regardless of the historical single-nocturn rubric for
# this particular feast (same convention already noted in
# nocturn-readings/easter-sunday.yml's own header for the reading side).
#
# Explicitly NOT covered by this entry: Wednesday-Saturday of the Octave
# have their own real Paschal-alleluia antiphons layered over (in the
# reference engine) a differently-sized psalm set — live-verified present
# (2026-04-08, Wednesday) but not authored here. A separate, smaller gap
# (4 more days of antiphon-only content, analogous to the existing Advent/
# Paschaltide seasonal-overlay shape) — see TODO.md.
easter-sunday:
# winner.id 'easter-sunday' spans the whole Octave (all 6 weekdays), but
# only these 3 calendar days are live-verified to share this content —
# restricting the entry so Wednesday-Saturday keep falling through to
# their own (not yet authored) path instead of wrongly reusing this one.
weekdays: [sunday, monday, tuesday]
nocturn1:
groups:
- psalms: [1, 2, 8, 15, 23, 27]
antiphon:
la: "Ego sum qui sum, * et consílium meum non est cum ímpiis, sed in lege Dómini volúntas mea est, allelúja."
en: "I am who I am, * and my counsel is not with the ungodly, my will is in the law of God, alleluia."
versicle:
v: { la: "Surréxit Dóminus de sepúlcro, allelúja.", en: "The Lord is risen from the sepulchre, alleluia." }
r: { la: "Qui pro nobis pepéndit in ligno, allelúja.", en: "Who for us was hanged on the tree, alleluia." }
nocturn2:
groups:
- psalms: [29, 63, 65, 75, 87, 107]
antiphon:
la: "Terra * trémuit et quiévit, † dum exsúrgeret in judício Deus, allelúja."
en: "The earth * trembled and was still, when God arose to judgment, alleluia."
versicle:
v: { la: "Surréxit Dóminus vere, allelúja.", en: "The Lord is risen indeed, alleluia." }
r: { la: "Et appáruit Simóni, allelúja.", en: "And hath appeared to Simon, alleluia." }
nocturn3:
canticles:
- refs: [{ book: isa, chapter: 63, verses: "1-5" }]
- refs: [{ book: osee, chapter: 6, verses: "1-6" }]
- refs: [{ book: soph, chapter: 3, verses: "8-13" }]
antiphon:
la: "Nolíte expavéscere: * Jesum quǽritis Nazarénum, crucifíxum: surréxit, non est hic, allelúja."
en: "Be not affrighted; * you seek Jesus of Nazareth, who was crucified: he is risen, he is not here, alleluia."
versicle:
v: { la: "Gavísi sunt discípuli, allelúja.", en: "The disciples were glad, alleluia." }
r: { la: "Viso Dómino, allelúja.", en: "When they saw the Lord, alleluia." }
sunday-after-ascension:
nocturn1:
groups:
+48
View File
@@ -360,6 +360,54 @@ describe('resolveOrdo("matins", ...) Sunday within the Octave of Ascension (2026
});
});
describe('resolveOrdo("matins", ...) Easter Sunday/Monday/Tuesday shared proper Matins (2026-09-02 fix)', () => {
// All 6 weekdays of the Easter Octave share winner {kind: 'temporal', id:
// 'easter-sunday'} -- only Sunday/Monday/Tuesday are live-verified to
// share this exact proper content, restricted via the override's own
// `weekdays` field (see matins-sunday-named-nocturn-overrides.yml).
const EASTER_WEEK_2026 = ['2026-04-05', '2026-04-06', '2026-04-07'] as const;
it.each(EASTER_WEEK_2026)('%s shares the identical 13-psalm proper scheme, 3 nocturns, Te Deum', (date) => {
const day = resolveDay(date);
expect(day.winner).toEqual({ kind: 'temporal', id: 'easter-sunday' });
const ordo = resolveOrdo('matins', date);
const psalms = ordo.parts.filter((p) => p.kind === 'psalm').map((p) => (p as { psalmNumber: number }).psalmNumber);
expect(psalms).toEqual([3, 94, 1, 2, 8, 15, 23, 27, 29, 63, 65, 75, 87, 107]);
expect(ordo.parts.some((p) => p.kind === 'te-deum')).toBe(true);
const canticles = ordo.parts.filter((p) => p.kind === 'canticle') as { canticleId: string }[];
expect(canticles.map((c) => c.canticleId)).toEqual(['isa-63-1-5', 'osee-6-1-6', 'soph-3-8-13']);
const antiphons = ordo.parts.filter((p) => p.kind === 'antiphon') as { text: { text: Record<string, string> } }[];
expect(antiphons[1]?.text.text.la).toBe('Ant. Ego sum qui sum, * et consílium meum non est cum ímpiis, sed in lege Dómini volúntas mea est, allelúja.');
expect(antiphons.at(-1)?.text.text.la).toBe(
'Ant. Nolíte expavéscere: * Jesum quǽritis Nazarénum, crucifíxum: surréxit, non est hic, allelúja.',
);
const versicles = ordo.parts.filter((p) => p.kind === 'versicle') as { text: { text: Record<string, string> } }[];
const [, , nocturn1, nocturn2, nocturn3] = versicles;
expect(nocturn1?.text.text.la).toBe('V. Surréxit Dóminus de sepúlcro, allelúja.\nR. Qui pro nobis pepéndit in ligno, allelúja.');
expect(nocturn2?.text.text.la).toBe('V. Surréxit Dóminus vere, allelúja.\nR. Et appáruit Simóni, allelúja.');
expect(nocturn3?.text.text.la).toBe('V. Gavísi sunt discípuli, allelúja.\nR. Viso Dómino, allelúja.');
});
it('Wednesday-Saturday of the Octave (same winner.id, excluded by `weekdays`) keep the plain 1-nocturn ferial branch unchanged', () => {
const ordo = resolveOrdo('matins', '2026-04-08'); // Wednesday of Easter week
const day = resolveDay('2026-04-08');
expect(day.winner).toEqual({ kind: 'temporal', id: 'easter-sunday' });
const psalms = ordo.parts.filter((p) => p.kind === 'psalm').map((p) => (p as { psalmNumber: number }).psalmNumber);
// Plain ferial Wednesday table, same as any other Wednesday.
expect(psalms.slice(2)).toEqual(getPsalmsFor('matins', 'wednesday').map((r) => r.number));
expect(ordo.parts.some((p) => p.kind === 'te-deum')).toBe(false);
});
it('Low Sunday (a different temporalId) is unaffected, still the plain fixed Sunday scheme', () => {
const ordo = resolveOrdo('matins', '2026-04-12');
const psalms = ordo.parts.filter((p) => p.kind === 'psalm').map((p) => (p as { psalmNumber: number }).psalmNumber);
expect(psalms).toEqual([3, 94, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31]);
});
});
describe('resolveOrdo("matins", ...) Duplex+ weekday-feast (3-nocturn, non-Sunday) branch', () => {
// 2026-08-17 -- St. Lawrence's own octave closing day ("In Octava S.
// Laurentii Martyris ~ Duplex"), live-verified directly against the