Model Ascension, Corpus Christi, and the Sacred Heart as temporal feasts
Deploy / deploy (push) Successful in 1m23s
Deploy / deploy (push) Successful in 1m23s
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
This commit is contained in:
@@ -75,10 +75,14 @@ describe('getDayLabel — ordinal temporal label', () => {
|
||||
|
||||
it('falls back to weekday + season name for seasons with no ordinal convention modeled', () => {
|
||||
// 2026-05-15 is now St. John Baptist de la Salle's own fixed day
|
||||
// (Duplex, added per the full-year sanctoral import) -- moved to
|
||||
// May 21, still within Ascensiontide 2026 (May 14-23) and still
|
||||
// free of any sanctoral entry.
|
||||
expect(getDayLabel(resolveDay('2026-05-21')).en).toContain('in Ascensiontide');
|
||||
// (Duplex, added per the full-year sanctoral import); May 21 is now
|
||||
// Ascension's own octave closing day (Ascension now modeled as a
|
||||
// real TemporalFeastRecord with an octave, Easter+39 through +46 —
|
||||
// see TODO.md's 2026-08-26 entry), and May 22/23 both carry a fixed
|
||||
// saint of their own -- 2028-06-03 (Easter+48, the day before
|
||||
// Pentecost) is the nearest genuinely free Ascensiontide day: past
|
||||
// Ascension's own octave, no fixed saint, still short of Pentecost.
|
||||
expect(getDayLabel(resolveDay('2028-06-03')).en).toContain('in Ascensiontide');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -339,12 +343,20 @@ describe('getDayLabel — named temporal feast rank', () => {
|
||||
});
|
||||
|
||||
describe('getDayLabel — feast name combination', () => {
|
||||
// 2026-06-15 (the "2nd week after Trinity" Monday, Easter+71) used to
|
||||
// be free of any octave; now it's day 4 of Sacred Heart's own octave
|
||||
// (Easter+68 through +75, added 2026-08-26 -- see TODO.md), and that
|
||||
// offset range is fixed relative to Easter every year, not just this
|
||||
// one, so no year-swap avoids it for "2nd week after Trinity"
|
||||
// specifically. Moved to the "3rd week after Trinity" Monday
|
||||
// (Easter+78) instead, clear of Ascension's (+39..+46) and the
|
||||
// Corpus Christi/Sacred Heart combined (+60..+75) octave ranges.
|
||||
const base: LiturgicalDay = {
|
||||
date: '2026-06-15',
|
||||
date: '2026-06-22',
|
||||
weekday: 'monday',
|
||||
season: 'trinitytide',
|
||||
temporalCategory: 'ordinary-feria',
|
||||
winner: { kind: 'temporal', id: 'post-pentecost-02' },
|
||||
winner: { kind: 'temporal', id: 'post-pentecost-03' },
|
||||
commemorations: [],
|
||||
};
|
||||
|
||||
@@ -361,10 +373,10 @@ describe('getDayLabel — feast name combination', () => {
|
||||
...base,
|
||||
commemorations: [{ kind: 'sanctoral', id: 'x', name: 'St. Ereden', rank: 'duplex-2-classis' }],
|
||||
};
|
||||
expect(getDayLabel(day).en).toBe('St. Ereden — Monday in the 2nd week after Trinity (Feria)');
|
||||
expect(getDayLabel(day).en).toBe('St. Ereden — Monday in the 3rd week after Trinity (Feria)');
|
||||
});
|
||||
|
||||
it('shows just the temporal label when nothing is commemorated at all', () => {
|
||||
expect(getDayLabel(base).en).toBe('Monday in the 2nd week after Trinity (Feria)');
|
||||
expect(getDayLabel(base).en).toBe('Monday in the 3rd week after Trinity (Feria)');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -10,8 +10,12 @@ describe('resolveEveningDay', () => {
|
||||
});
|
||||
|
||||
it('does not anticipate between two plain ferias', () => {
|
||||
const day = resolveEveningDay('2026-06-16'); // Tuesday, ordinary trinitytide
|
||||
expect(day.date).toBe('2026-06-16');
|
||||
// 2033-09-06: Tuesday, ordinary trinitytide, genuinely free of any
|
||||
// saint or active octave on both this date and the next (2026-06-16,
|
||||
// used here previously, fell inside Sacred Heart's own octave once
|
||||
// that was modeled 2026-08-26 -- see TODO.md).
|
||||
const day = resolveEveningDay('2033-09-06');
|
||||
expect(day.date).toBe('2033-09-06');
|
||||
});
|
||||
|
||||
it('anticipates Easter from Holy Saturday', () => {
|
||||
|
||||
@@ -6,7 +6,7 @@ import { resolveOrdo } from '../../src/hours';
|
||||
// why these particular dates.
|
||||
const FERIAL_SUNDAY = '2026-06-21';
|
||||
const FERIAL_MONDAY = '2026-06-08'; // not 08-31 -- St. Raymond Nonnatus's own day (common-of-a-confessor)
|
||||
const FERIAL_TUESDAY = '2026-06-16';
|
||||
const FERIAL_TUESDAY = '2033-09-06'; // not 2026-06-16 -- that fell inside Sacred Heart's own octave once modeled 2026-08-26, see TODO.md
|
||||
const LAWRENCE_OCTAVE_DAY = '2026-08-11'; // Semiduplex -- exercises isDoubleOrHigher's false branch differently than a plain feria only in rank, not in psalms (Commune override not modeled, see below)
|
||||
|
||||
describe('resolveOrdo("lauds", ...)', () => {
|
||||
@@ -170,11 +170,17 @@ describe('resolveOrdo("lauds", ...)', () => {
|
||||
expect(hymn?.kind === 'hymn' ? hymn.text.text.la : undefined).toContain('Martyr Dei, qui únicum');
|
||||
});
|
||||
|
||||
it("uses Common-of-a-Confessor-Bishop's real office bundle on St. Ubald's own day, not the ferial default", () => {
|
||||
// St. Ubald (May 16, Semiduplex, common-of-a-confessor-bishop) has no
|
||||
// proper Lauds content of his own -- see vespers-hymn-common-of-a-
|
||||
// confessor-bishop.yml's header.
|
||||
const ordo = resolveOrdo('lauds', '2026-05-16');
|
||||
it("uses Common-of-a-Confessor-Bishop's real office bundle on St. Robert Bellarmine's own day, not the ferial default", () => {
|
||||
// Was St. Ubald (May 16, Semiduplex, common-of-a-confessor-bishop) --
|
||||
// moved to St. Robert Bellarmine (May 13, Duplex, same Common) once
|
||||
// Ascension's own octave was modeled (2026-08-26, see TODO.md):
|
||||
// Ubald now loses his own day outright to the octave (`octave.wins:
|
||||
// duplex`, and Semiduplex Ubald falls short of it), so his own
|
||||
// Common-of-a-Confessor-Bishop content no longer surfaces there at
|
||||
// all. Bellarmine sits safely before the octave's own window (May
|
||||
// 14-21) and has no proper Lauds content of his own either -- see
|
||||
// vespers-hymn-common-of-a-confessor-bishop.yml's header.
|
||||
const ordo = resolveOrdo('lauds', '2026-05-13');
|
||||
const chapter = ordo.parts.find((p) => p.kind === 'chapter');
|
||||
expect(chapter?.kind === 'chapter' ? chapter.text.citation?.en : undefined).toBe('Sir 44:16-17');
|
||||
const hymn = ordo.parts.find((p) => p.kind === 'hymn');
|
||||
|
||||
+41
-19
@@ -129,10 +129,11 @@ describe('resolveOrdo("vespers", ...)', () => {
|
||||
});
|
||||
|
||||
it('falls back to the classic weekday-default Magnificat antiphon on a bare ferial', () => {
|
||||
// 2026-06-16 is a Tuesday, plain ordinary trinitytide, no saint, no
|
||||
// 2033-09-06 is a Tuesday, plain ordinary trinitytide, no saint, no
|
||||
// active octave (same date already used by calendar/vespers.test.ts's
|
||||
// "does not anticipate between two plain ferias").
|
||||
const ordo = resolveOrdo('vespers', '2026-06-16');
|
||||
// "does not anticipate between two plain ferias" -- see that test's
|
||||
// own comment for why this moved off 2026-06-16).
|
||||
const ordo = resolveOrdo('vespers', '2033-09-06');
|
||||
const magnificatIndex = ordo.parts.findIndex((p) => p.kind === 'canticle' && p.canticleId === 'magnificat');
|
||||
expect(magnificatIndex).toBeGreaterThanOrEqual(0);
|
||||
const magnificat = ordo.parts[magnificatIndex];
|
||||
@@ -250,15 +251,24 @@ describe('resolveOrdo("vespers", ...)', () => {
|
||||
}
|
||||
});
|
||||
|
||||
it("uses Common-of-a-Confessor-Bishop's real office bundle for St. Ubald's First Vespers", () => {
|
||||
// St. Ubald (May 16, Semiduplex, common-of-a-confessor-bishop) claims
|
||||
// First Vespers over May 15's own evening (no stronger rival that
|
||||
// year). Live-verified against Divinum Officium (Monastic Tridentinum
|
||||
// 1617) votive=C4 -- see vespers-hymn-common-of-a-confessor-bishop.yml's
|
||||
// header.
|
||||
it("no longer gives St. Ubald First Vespers now that Ascension's own octave is modeled", () => {
|
||||
// Historically (live-verified against Divinum Officium, Monastic
|
||||
// Tridentinum 1617, votive=C4) St. Ubald (May 16, Semiduplex,
|
||||
// common-of-a-confessor-bishop) claims First Vespers over May 15's
|
||||
// own evening -- a real Order-III-octave permissiveness this app's
|
||||
// own `ascension.yml` (added 2026-08-26) doesn't reproduce: its
|
||||
// `octave.wins: duplex` is deliberately borrowed from Pentecost's own
|
||||
// (Order I) threshold rather than modeled per privilege-order, a
|
||||
// known, accepted conflict with this specific live-verified case
|
||||
// (see ascension.yml's own comment). Semiduplex Ubald now loses to
|
||||
// the octave outright on his own day (May 16), so May 15's evening is
|
||||
// just St. John Baptist de la Salle's (Duplex, wins May 15 itself
|
||||
// outright) own kept Second Vespers -- his own Common-of-a-Confessor
|
||||
// (not Bishop) bundle, not Ubald's Confessor-Bishop one -- with
|
||||
// Ascension's octave merely commemorated alongside him.
|
||||
const ordo = resolveOrdo('vespers', '2026-05-15');
|
||||
const chapter = ordo.parts.find((p) => p.kind === 'chapter');
|
||||
expect(chapter?.kind === 'chapter' ? chapter.text.citation?.en : undefined).toBe('Sir 44:16-17');
|
||||
expect(chapter?.kind === 'chapter' ? chapter.text.citation?.en : undefined).toBe('Sir 31:8-9');
|
||||
const hymn = ordo.parts.find((p) => p.kind === 'hymn');
|
||||
expect(hymn?.kind === 'hymn' ? hymn.text.text.la : undefined).toContain('Iste Conféssor Dómini sacrátus');
|
||||
});
|
||||
@@ -322,16 +332,28 @@ describe('resolveOrdo("vespers", ...)', () => {
|
||||
expect(hymn?.kind === 'hymn' ? hymn.text.text.la : undefined).toContain('Exsúltet cælum láudibus');
|
||||
});
|
||||
|
||||
it("uses common-of-a-widow's real office bundle for St. Margaret of Scotland's First Vespers", () => {
|
||||
it("no longer gives St. Margaret of Scotland First Vespers now that Corpus Christi's own octave is modeled", () => {
|
||||
// St. Margaret of Scotland (Jun 10, Semiduplex, common-of-a-widow --
|
||||
// live-verified 2026-08-26 to be real content identical to Common-
|
||||
// of-a-Holy-Woman's, see lauds-capitulum-common-of-a-holy-woman.yml's
|
||||
// header) claims First Vespers over Jun 9's own evening.
|
||||
// content itself live-verified 2026-08-26 to be real, identical to
|
||||
// Common-of-a-Holy-Woman's, see lauds-capitulum-common-of-a-holy-
|
||||
// woman.yml's header) used to claim First Vespers over Jun 9's own
|
||||
// evening. Now inside Corpus Christi's own octave (Jun 4-11, added
|
||||
// 2026-08-26, `octave.wins: duplex` -- same deliberate, accepted
|
||||
// conflict with a real Order-III-octave permissiveness as
|
||||
// ascension.yml's own St. Ubald case, see corpus-christi.yml's own
|
||||
// comment), Semiduplex Margaret loses her own day (Jun 10) outright
|
||||
// to the octave -- and Jun 9 itself is *also* inside the same
|
||||
// octave, already having lost its own Simplex saint (Ss. Primus and
|
||||
// Felicianus) to it, so neither day has any sanctoral winner left to
|
||||
// anticipate. Jun 9's evening falls all the way through to the plain
|
||||
// ferial Vespers default (Trinity week's own governing chapter/hymn),
|
||||
// with both Primus & Felicianus and Corpus Christi's octave merely
|
||||
// commemorated, not any widow-specific content at all.
|
||||
const ordo = resolveOrdo('vespers', '2026-06-09');
|
||||
const chapter = ordo.parts.find((p) => p.kind === 'chapter');
|
||||
expect(chapter?.kind === 'chapter' ? chapter.text.citation?.en : undefined).toBe('Prov 31:10-11');
|
||||
expect(chapter?.kind === 'chapter' ? chapter.text.citation?.en : undefined).toBe('2 Cor 1:3-4');
|
||||
const hymn = ordo.parts.find((p) => p.kind === 'hymn');
|
||||
expect(hymn?.kind === 'hymn' ? hymn.text.text.la : undefined).toContain('Fortem viríli péctore');
|
||||
expect(hymn?.kind === 'hymn' ? hymn.text.text.la : undefined).toContain('Tellúris ingens Cónditor');
|
||||
});
|
||||
|
||||
it("uses common-of-the-bvm's real office bundle for Our Lady of Mount Carmel's First Vespers", () => {
|
||||
@@ -409,9 +431,9 @@ describe('resolveOrdo("vespers", ...)', () => {
|
||||
});
|
||||
|
||||
it('includes the Kyrie + Our Father lead-in right after the Magnificat, before the day collect (either form)', () => {
|
||||
// 2026-06-16 is a Tuesday, plain ordinary trinitytide -- same bare
|
||||
// 2033-09-06 is a Tuesday, plain ordinary trinitytide -- same bare
|
||||
// ferial already used above for the Magnificat weekday-default test.
|
||||
const ordo = resolveOrdo('vespers', '2026-06-16');
|
||||
const ordo = resolveOrdo('vespers', '2033-09-06');
|
||||
const litanyIndex = ordo.parts.findIndex((p) => p.kind === 'preces' && p.text.text.la?.includes('Kýrie, eléison'));
|
||||
const magnificatIndex = ordo.parts.findIndex((p) => p.kind === 'canticle' && p.canticleId === 'magnificat');
|
||||
const collectIndex = ordo.parts.findIndex((p) => p.kind === 'prayer');
|
||||
@@ -424,7 +446,7 @@ describe('resolveOrdo("vespers", ...)', () => {
|
||||
// dropped from this app's own text (2026-08) — 'Ego dixi' is the next
|
||||
// versicle after where the psalm used to sit, still unique to the
|
||||
// fuller ferial form vs. the short litany.
|
||||
const ordo = resolveOrdo('vespers', '2026-06-16');
|
||||
const ordo = resolveOrdo('vespers', '2033-09-06');
|
||||
const preces = ordo.parts.find((p) => p.kind === 'preces' && p.text.text.la?.includes('Kýrie, eléison'));
|
||||
expect(preces?.kind === 'preces' ? preces.text.text.la : undefined).toContain('Ego dixi: Dómine, miserére mei');
|
||||
expect(preces?.kind === 'preces' ? preces.text.text.en : undefined).toContain('I said: Lord, be merciful');
|
||||
|
||||
Reference in New Issue
Block a user