Give each day of Easter's octave its own real content
Deploy / deploy (push) Successful in 1m56s

Easter Monday-Saturday previously all inherited Easter Sunday's single
collect and single-nocturn Matins content via the shared `easter-sunday`
temporal id, even though the reference engine gives each of the six days
its own real proper (own title, rank, collect, Gospel+homily). Adds six
new temporal-feasts records (anchor `{kind: easter, offset: 1..6}`, same
mechanism as the Passiontide batch), six new collects transcribed from
each day's own Office Oratio, and six new nocturn-readings files with
each day's own Gospel pericope (from the Missal) and patristic homily.

Also fixes two mechanism gaps this surfaced: hours/matins.ts's 3-nocturn/
Sunday-style gate was keyed by `winner.id` (now per-weekday) instead of
`temporalId` (still shared for the whole octave, which is what actually
supplies the content); and the Wednesday-Saturday ferial antiphon
override was keyed by the old shared `easter-sunday` id.

Fact-checked two adjacent assumptions along the way: commemorations stay
fully suppressed Monday-Saturday (the user's own 2026-09-01 "no
commemoration from Palm Sunday through Low Sunday" instruction already
covers this span, unchanged), and the correct per-day names are "Die N
infra Octavam Paschæ" (Mon-Fri) / "Sabbato in Albis" (Sat only) rather
than a blanket "Hebdomada in Albis" for every day.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W8cttEhwbN3f6hiHQHqCae
This commit is contained in:
2026-09-06 08:38:49 -04:00
parent deebc51f81
commit 7482df1777
24 changed files with 1056 additions and 30 deletions
+13 -2
View File
@@ -34,8 +34,19 @@ describe('April sanctoral pull (first pass)', () => {
// candidate in 2030's own backlog, so he lands the 1st day after Low
// Sunday (Apr 28 that year) outright.
const native = resolveDay('2030-04-25');
expect(native.winner).toEqual({ kind: 'temporal', id: 'easter-sunday' });
expect(native.commemorations).toEqual([]);
// 2030-04-25 is Easter+4 (Thursday of Easter week); `winner.id` is
// this weekday's own real temporal-feasts id since 2026-09-06 (see
// calendar/temporal-feasts/easter-thursday.yml), not the shared
// `easter-sunday` governing-week id. The override mechanism pushes a
// `{kind:'temporal', id:'easter-sunday'}` commemoration for the coarse
// id it replaces, same shape every other movable feast (Ascension,
// Corpus Christi, the Passiontide days) already does; harmless, since
// day-label.ts's collectCommemorations deliberately drops any
// commemoration whose id equals the plain governing weekday (see
// tests/calendar/transfer.test.ts's own Holy Monday case for the same
// shape).
expect(native.winner).toEqual({ kind: 'temporal', id: 'easter-thursday' });
expect(native.commemorations).toEqual([{ kind: 'temporal', id: 'easter-sunday' }]);
expect(native.transferredAway?.candidate.id).toBe('st-mark');
const landing = resolveDay('2030-04-29');
+18 -8
View File
@@ -376,15 +376,20 @@ 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).
// Live-verified to share this exact proper content: Sunday/Monday/
// Tuesday, restricted via the override's own `weekdays` field (see
// matins-sunday-named-nocturn-overrides.yml), keyed by `temporalId`
// (`resolveTemporalId`, always 'easter-sunday' across the whole
// octave) -- independent of `day.winner`, which since 2026-09-06 is
// each weekday's own real temporal-feasts record (`easter-monday`,
// `easter-tuesday`, ...), not the shared governing-Sunday id (see
// calendar/temporal-feasts/easter-monday.yml and siblings).
const EASTER_WEEK_2026 = ['2026-04-05', '2026-04-06', '2026-04-07'] as const;
const EASTER_WEEK_2026_WINNER_IDS = ['easter-sunday', 'easter-monday', 'easter-tuesday'] 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' });
expect(day.winner).toEqual({ kind: 'temporal', id: EASTER_WEEK_2026_WINNER_IDS[EASTER_WEEK_2026.indexOf(date)] });
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]);
@@ -406,10 +411,10 @@ describe('resolveOrdo("matins", ...) Easter Sunday/Monday/Tuesday shared proper
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 the Sunday-side `weekdays`) stay in the plain 1-nocturn ferial branch, same psalm numbers, no Te Deum', () => {
it('Wednesday-Saturday of the Octave (own weekday winner.id, excluded by the Sunday-side `weekdays`) stay in the plain 1-nocturn ferial branch, same psalm numbers, no Te Deum', () => {
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' });
expect(day.winner).toEqual({ kind: 'temporal', id: 'easter-wednesday' });
const psalms = ordo.parts.filter((p) => p.kind === 'psalm').map((p) => (p as { psalmNumber: number }).psalmNumber);
// Plain ferial Wednesday psalm numbers, same as any other Wednesday --
// only the antiphon layer differs (see the ferial-named-override
@@ -429,11 +434,16 @@ describe('resolveOrdo("matins", ...) Easter Octave Wednesday-Saturday ferial nam
// All 4 weekdays share the exact same 2 antiphons verbatim (live-
// verified 2026-04-08/09/10/11) -- own psalm numbers per weekday
// unchanged, split evenly across the weekday's own psalm-group count.
// `day.winner` is each weekday's own real temporal-feasts id since
// 2026-09-06 (see the describe block above) -- the antiphon override
// itself is unaffected, keyed by `temporalId` (`resolveTemporalId`,
// still 'easter-sunday' for the whole octave), not by `day.winner`.
const EASTER_OCTAVE_WEEKDAYS = ['2026-04-08', '2026-04-09', '2026-04-10', '2026-04-11'] as const;
const EASTER_OCTAVE_WEEKDAY_WINNER_IDS = ['easter-wednesday', 'easter-thursday', 'easter-friday', 'easter-saturday'] as const;
it.each(EASTER_OCTAVE_WEEKDAYS)('%s gets the same 2 real antiphons, own weekday psalm numbers unchanged', (date) => {
const day = resolveDay(date);
expect(day.winner).toEqual({ kind: 'temporal', id: 'easter-sunday' });
expect(day.winner).toEqual({ kind: 'temporal', id: EASTER_OCTAVE_WEEKDAY_WINNER_IDS[EASTER_OCTAVE_WEEKDAYS.indexOf(date)] });
const ordo = resolveOrdo('matins', date);
// Ps 3 + 94 (opening/invitatory) plus this weekday's own real ferial table.
const psalms = ordo.parts.filter((p) => p.kind === 'psalm').map((p) => (p as { psalmNumber: number }).psalmNumber);