Full precedence sweep of temporal-categories.yml, two more real bugs
Deploy / deploy (push) Successful in 1m27s

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
This commit is contained in:
2026-09-01 11:45:04 -04:00
parent 6b66999908
commit 4adbb71ddf
10 changed files with 190 additions and 63 deletions
+52
View File
@@ -4653,3 +4653,55 @@ season-wide overrides, ahead of the plain default.
New regression tests in `tests/hours/matins.test.ts` (`Sunday within the Octave of Ascension`) New regression tests in `tests/hours/matins.test.ts` (`Sunday within the Octave of Ascension`)
assert both the calendar winner (Sunday over the saint) and the full Matins content. `npm test` assert both the calendar winner (Sunday over the saint) and the full Matins content. `npm test`
(821 passed), `tsc --noEmit`, and `npm run build` all pass. (821 passed), `tsc --noEmit`, and `npm run build` all pass.
### Full sweep of `temporal-categories.yml` — two more real bugs found and fixed (2026-09-01)
Follow-up, same day: user pushed back hard on the "not a bug" framing above ("the immaculate
conception transfer is correct... annuncition should transfer") and asked to sweep every
remaining unverified entry in `data/calendar/temporal-categories.yml`, not just the one already
fixed. Found two more real, calendar-wide bugs:
1. **`commemorations.ts`'s `privileged-sunday` case itself was wrong**, not just one season's
tag. It gave any duplex-majus+ candidate a bare commemoration instead of transferring —
never actually verified against a primary source. Live-checked 3 independent cases: the
Immaculate Conception (Duplex I. classis) transfers off Advent II with **no** commemoration
(2024/2030/2041); the Annunciation (also Duplex I. classis) transfers off both Palm Sunday
and Easter Sunday itself the same way (2029/2035/2040/2046). Confirmed by direct instruction
as correct (with a known follow-on: the Annunciation's *real* rule jumps it past the whole
Easter Octave rather than the next open day, which lands inside it — blocked on the
not-yet-built Easter-octave mechanism, noted but not built here). Fixed: `privileged-sunday`
now transfers every rank unconditionally, no exception.
2. **`eastertide`'s own `sunday` tier was backwards** — coded `privileged-sunday`, should be
`ordinary-sunday` for the *ordinary* Sundays after Easter (not Easter Day itself, not within
its Octave, both already separately handled). Live-verified twice: the Finding of the Holy
Cross (Duplex-II-classis, 2026-05-03) and St. Mark (Duplex-II-classis, 2027-04-25) both win
outright over "Dominica IV Post Pascha," with the Sunday merely noted as a `Tempora` line
underneath — exactly `ordinary-sunday`'s rule. Easter Day itself still needs the stronger
tier (confirmed by the Annunciation evidence above), so it's now special-cased directly in
`calendar/temporal.ts`'s `resolveTemporalCategory` (Sundays never consult `offsets`, so this
couldn't be expressed as a plain offset entry the way every other exception in that file is).
**Six test failures from the `privileged-sunday` fix, all genuine fallout, not regressions** —
each test had encoded the old, wrong "commemorated in place" behavior as its expected value:
St. Andrew (Duplex II. classis) no longer commemorated alongside Advent I, but shown
transferred away (landing on Dec 1, where he now wins outright — `privileged-feria-minor`'s
Semiduplex+ threshold easily clears); St. Antoninus similarly now wins outright on his own day
(the real Easter-6 Sunday) instead of being pushed to the next day by the old wrong rule. All
six tests updated to real, live-verified dates/values, not just loosened to pass.
**Remaining entries in the file, not independently live-collision-tested but documented as
such** (each has its own comment now): the Vigil of Pentecost and Vigil of Christmas
(`privileged-feria-major`) are corroborated by their own formal rank in the reference source
("Semiduplex I classis" / "Duplex I classis" respectively) rather than a live saint collision,
which is rare by construction; Pentecost Sunday itself (`privileged-sunday`) is consistent with
the confirmed "great octave Sunday" pattern but has no fixed-date-saint collision to test
against; Corpus Christi/Sacred Heart's `feria` (`privileged-feria-major`) shares the same
"feast of the Lord" reasoning already confirmed elsewhere, and their `sunday` key is flagged as
genuinely unreachable (both are always fixed weekdays, never a Sunday, per
`easter-offsets.yml`'s own `days:` table). Every other `feria` entry is the trivial
`ordinary-feria` default (no rank threshold — any real content wins), already implicitly
exercised correctly across hundreds of ordinary weekdays in the existing test suite.
`npm test` (820 passed — one net fewer than before since two tests merged into one clearer
case), `tsc --noEmit`, and `npm run build` all pass.
+16 -7
View File
@@ -188,13 +188,22 @@ export function decideOccurrence(
}; };
case 'privileged-sunday': case 'privileged-sunday':
if (isAtLeast(sanctoral.rank, 'duplex-majus')) { // A privileged Sunday wants nothing at all, no matter how high the
// Never displaced, but a sufficiently high feast still gets a nod. // candidate ranks — not even the bare commemoration an ordinary
return { winner: temporalWinner, commemorations: [sanctoralCommemoration(sanctoral)] }; // Sunday would allow a Simplex, and no exception for duplex-majus+ —
} // everything transfers. Live-verified 2026-09-01, three independent
// Below duplex-majus, a privileged Sunday wants nothing at all — // dates: the Immaculate Conception (Duplex I. classis) transfers off
// not even the bare commemoration an ordinary Sunday would allow a // Advent II with no commemoration at all (real dates 2024/2030/2041),
// Simplex — so everything here transfers. // and the Annunciation (also Duplex I. classis) transfers off both
// Palm Sunday and Easter Sunday itself the same way (2029/2035/2040/
// 2046) — confirmed by direct instruction as the correct behavior,
// correcting an earlier, never-actually-verified "duplex-majus+
// still gets a nod" guess. (Where a transferred candidate actually
// lands is a separate, still-generic `transferDirectionOf`/next-
// open-day mechanism — the Annunciation's own real rule of jumping
// *past* the whole Easter Octave rather than landing inside it is a
// known follow-on, blocked on the not-yet-built Easter-octave
// mechanism; see TODO.md.)
return { return {
winner: temporalWinner, winner: temporalWinner,
commemorations: [], commemorations: [],
+12 -1
View File
@@ -202,12 +202,23 @@ export function adventEmberDayOffset(isoDate: string): number | undefined {
* the Christmas vigil (Dec 24 can land on a Sunday) — and a Sunday's own * the Christmas vigil (Dec 24 can land on a Sunday) — and a Sunday's own
* privileged/ordinary status should win in that case regardless, so * privileged/ordinary status should win in that case regardless, so
* Sundays are resolved straight from `bySeason` without consulting the * Sundays are resolved straight from `bySeason` without consulting the
* overrides at all. * overrides at all. The one deliberate exception is Easter Sunday itself
* (offset 0): `eastertide`'s own `bySeason` default is `ordinary-sunday`
* (live-verified for the *ordinary* Sundays that follow it — see that
* entry's own comment), but Easter Day needs the stronger
* `privileged-sunday` tier instead (live-verified 2026-09-01: the
* Annunciation, Duplex I. classis, transfers off Easter Day itself in
* years they coincide, the same "transfers, no exception" behavior as
* every other privileged-sunday case) — checked directly here rather
* than via `offsets`, since offsets are never consulted for a Sunday.
*/ */
export function resolveTemporalCategory(isoDate: string, season: Season, weekday: Weekday): TemporalCategory { export function resolveTemporalCategory(isoDate: string, season: Season, weekday: Weekday): TemporalCategory {
const bySeasonEntry = temporalCategories.bySeason[season]; const bySeasonEntry = temporalCategories.bySeason[season];
const base = bySeasonEntry ? (weekday === 'sunday' ? bySeasonEntry.sunday : bySeasonEntry.feria) : 'ordinary-feria'; const base = bySeasonEntry ? (weekday === 'sunday' ? bySeasonEntry.sunday : bySeasonEntry.feria) : 'ordinary-feria';
if (weekday === 'sunday') { if (weekday === 'sunday') {
if (season === 'eastertide' && easterOffsetOf(isoDate) === 0) {
return 'privileged-sunday';
}
return base; return base;
} }
+42 -4
View File
@@ -48,6 +48,19 @@
# fixability but currently fall to the plain `ordinary-feria` default # fixability but currently fall to the plain `ordinary-feria` default
# instead (see the offsetRanges comment below) — unverified, a real gap, # instead (see the offsetRanges comment below) — unverified, a real gap,
# not a guess dressed up as one. # not a guess dressed up as one.
#
# 2026-09-01 full sweep: every `bySeason` entry now live-verified or
# explicitly noted as not (see each entry's own comment below) — found
# and fixed two real bugs in the process (`eastertide`'s and
# `ascensiontide`'s own `sunday` tiers were each backwards), and a third,
# deeper one in `commemorations.ts` itself (`privileged-sunday`'s
# duplex-majus+ candidates were wrongly given a bare commemoration
# instead of transferring like every other rank there). Every `feria`
# entry in `bySeason` besides the ones with their own verification
# comment is the trivial `ordinary-feria` default (no rank threshold at
# all — any real content wins outright) — not worth a dedicated live
# collision test on top of the hundreds of ordinary weekdays already
# exercising it correctly across this app's whole test suite.
bySeason: bySeason:
# Advent's ordinary ferias verified `privileged-feria-minor` during the # Advent's ordinary ferias verified `privileged-feria-minor` during the
# December sanctoral pull (see calendar/types.ts's TemporalCategory doc # December sanctoral pull (see calendar/types.ts's TemporalCategory doc
@@ -81,7 +94,20 @@ bySeason:
septuagesima: { sunday: privileged-sunday, feria: ordinary-feria } septuagesima: { sunday: privileged-sunday, feria: ordinary-feria }
lent: { sunday: privileged-sunday, feria: ordinary-feria } lent: { sunday: privileged-sunday, feria: ordinary-feria }
passiontide: { sunday: privileged-sunday, feria: ordinary-feria } passiontide: { sunday: privileged-sunday, feria: ordinary-feria }
eastertide: { sunday: privileged-sunday, feria: ordinary-feria } # The ordinary Sundays after Easter (i.e. not Easter Day itself, not
# within its Octave — both handled elsewhere) are `ordinary-sunday`,
# NOT `privileged-sunday` — originally miscoded, live-verified
# 2026-09-01, two independent Duplex-II-classis collisions on "Dominica
# IV Post Pascha" (the Finding of the Holy Cross, 2026-05-03; St. Mark,
# 2027-04-25): both win outright, with the Sunday itself only noted as
# a `Tempora` commemoration underneath — exactly `ordinary-sunday`'s
# Duplex+ rule, the reverse of `privileged-sunday`'s (which would keep
# the Sunday primary and transfer the feast entirely, per the
# Ascensiontide/Advent/Palm-Sunday/Easter-Day evidence above and
# below). Easter Day itself (offset 0) and its Octave (offsets 1-6,
# `privileged-feria-major` via offsetRanges below) are unaffected by
# this — only the ordinary Sundays past the Octave use this default.
eastertide: { sunday: ordinary-sunday, feria: ordinary-feria }
# The Sunday within the Octave of Ascension is `privileged-sunday`, not # The Sunday within the Octave of Ascension is `privileged-sunday`, not
# `ordinary-sunday` — live-verified 2026-09-01: the reference engine # `ordinary-sunday` — live-verified 2026-09-01: the reference engine
# titles it "Dominica infra Octavam Ascensionis ~ Semiduplex Dominica # titles it "Dominica infra Octavam Ascensionis ~ Semiduplex Dominica
@@ -92,6 +118,11 @@ bySeason:
# Matches the same "great octave" pattern as eastertide/pentecost above, # Matches the same "great octave" pattern as eastertide/pentecost above,
# both already `privileged-sunday`. # both already `privileged-sunday`.
ascensiontide: { sunday: privileged-sunday, feria: ordinary-feria } ascensiontide: { sunday: privileged-sunday, feria: ordinary-feria }
# Pentecost Sunday itself — no fixed-date saint collision found/
# attempted to independently verify this specific entry, but consistent
# with the confirmed "great octave Sundays are privileged-sunday"
# pattern (Easter Day, Ascension's own octave Sunday) — Pentecost
# outranks both.
pentecost: { sunday: privileged-sunday, feria: ordinary-feria } pentecost: { sunday: privileged-sunday, feria: ordinary-feria }
trinitytide: { sunday: ordinary-sunday, feria: ordinary-feria } trinitytide: { sunday: ordinary-sunday, feria: ordinary-feria }
# Corpus Christi/Sacred Heart are themselves "feasts of the Lord" that # Corpus Christi/Sacred Heart are themselves "feasts of the Lord" that
@@ -99,7 +130,14 @@ bySeason:
# privileged-feria-major (rather than injecting a separate synthetic # privileged-feria-major (rather than injecting a separate synthetic
# FeastClass) since with the small sanctoral calendar this app has, a # FeastClass) since with the small sanctoral calendar this app has, a
# real clash is unlikely and the practical effect (temporal wins) is the # real clash is unlikely and the practical effect (temporal wins) is the
# same either way. # same either way. Not independently live-verified (no fixed-date saint
# collision found/attempted), but same "feast of the Lord" reasoning
# already confirmed for Ash Wednesday/Holy Week/the Easter Octave.
# `sunday` is checked here for shape-consistency with every other
# season entry, but genuinely unreachable in practice — per
# data/calendar/easter-offsets.yml's `days:` table, both are single
# fixed weekdays (Corpus Christi always a Thursday, Sacred Heart always
# the following Friday), never a Sunday.
corpus-christi: { sunday: ordinary-sunday, feria: privileged-feria-major } corpus-christi: { sunday: ordinary-sunday, feria: privileged-feria-major }
sacred-heart: { sunday: ordinary-sunday, feria: privileged-feria-major } sacred-heart: { sunday: ordinary-sunday, feria: privileged-feria-major }
@@ -108,7 +146,7 @@ offsets:
-39: privileged-feria # Lent Ember Wednesday (Wed after 1st Sunday of Lent) — verified: Duplex wins outright here -39: privileged-feria # Lent Ember Wednesday (Wed after 1st Sunday of Lent) — verified: Duplex wins outright here
-37: privileged-feria # Lent Ember Friday -37: privileged-feria # Lent Ember Friday
-36: privileged-feria # Lent Ember Saturday — verified: Duplex wins outright here (St. Gregory the Great) -36: privileged-feria # Lent Ember Saturday — verified: Duplex wins outright here (St. Gregory the Great)
48: privileged-feria-major # Vigil of Pentecost — unverified, defaulted to the stricter tier 48: privileged-feria-major # Vigil of Pentecost — no live saint-collision test found/attempted (rare by construction), but corroborated 2026-09-01 by its own formal rank in the reference source (Sancti/Pentecost Vigil file's own [Rank]: "Semiduplex I classis" — a genuinely first-class day, consistent with the stricter tier)
offsetRanges: offsetRanges:
- { fromOffset: -6, toOffset: -1, category: privileged-feria-major } # ferias of Holy Week - { fromOffset: -6, toOffset: -1, category: privileged-feria-major } # ferias of Holy Week
@@ -129,4 +167,4 @@ offsetRanges:
# existing one, not introduced by this change. # existing one, not introduced by this change.
fixedDates: fixedDates:
"12-24": privileged-feria-major # Vigil of Christmas — unverified, defaulted to the stricter tier "12-24": privileged-feria-major # Vigil of Christmas — no live saint-collision test found/attempted, but corroborated 2026-09-01 by its own formal rank in the reference source (Sancti/12-24.txt's own [Rank]: "Duplex I classis", consistent with the stricter tier)
+16 -13
View File
@@ -34,27 +34,30 @@ describe('April sanctoral pull (first pass)', () => {
]); ]);
}); });
it('a privileged Eastertide Sunday bumps even St. Robert (plain Duplex, not Duplex majus) to transfer forward', () => { it('an ordinary Eastertide Sunday (not Easter Day, not within its Octave) still lets St. Robert (plain Duplex) win outright, same as any weekday', () => {
// 2029: Apr 29 is a privileged Sunday in Eastertide, so Robert can't // 2029: Apr 29 is an ordinary (not privileged) Sunday in Eastertide
// win or be commemorated there — he transfers to Monday and wins the // live-verified 2026-09-01 (`eastertide`'s own `bySeason` entry, see
// collision against St. Peter Martyr instead. // data/calendar/temporal-categories.yml): "S. Roberti Abbatis ~
// Duplex" wins outright there, same as data/calendar/temporal-
// categories.yml's `ordinary-sunday` rule for any other season, with
// the Sunday itself only noted as a `Scriptura` line, not primary.
const sunday = resolveDay('2029-04-29'); const sunday = resolveDay('2029-04-29');
expect(sunday.winner).toEqual({ kind: 'temporal', id: 'easter-5' }); expect(sunday.winner).toEqual({ kind: 'sanctoral', id: 'st-robert', name: 'St. Robert, Abbot', nameLa: 'Sanctus Robertus, Abbas', rank: 'duplex' });
expect(sunday.commemorations).toEqual([ expect(sunday.commemorations).toEqual([
{ kind: 'temporal', id: 'easter-5' },
{ kind: 'sanctoral', id: 'st-catherine-of-siena', name: 'St. Catherine of Siena, Virgin', nameLa: 'Sancta Catharina Senensis, Virgo', rank: 'simplex' }, { kind: 'sanctoral', id: 'st-catherine-of-siena', name: 'St. Catherine of Siena, Virgin', nameLa: 'Sancta Catharina Senensis, Virgo', rank: 'simplex' },
]); ]);
// Apr 30 resolves St. Peter Martyr cleanly on his own day — no
// transferred-Robert collision, since Robert already won on the 29th.
const monday = resolveDay('2029-04-30'); const monday = resolveDay('2029-04-30');
expect(monday.winner).toEqual({ expect(monday.winner).toEqual({
kind: 'sanctoral', kind: 'sanctoral',
id: 'st-robert', id: 'st-peter-martyr',
name: 'St. Robert, Abbot', name: 'St. Peter Martyr, Martyr',
nameLa: 'Sanctus Robertus, Abbas', nameLa: 'Sanctus Petrus Martyr, Martyr',
rank: 'duplex', rank: 'semiduplex',
transferredFrom: '2029-04-29',
}); });
expect(monday.commemorations).toEqual([ expect(monday.commemorations).toEqual([]);
{ kind: 'sanctoral', id: 'st-peter-martyr', name: 'St. Peter Martyr, Martyr', nameLa: 'Sanctus Petrus Martyr, Martyr', rank: 'semiduplex' },
]);
}); });
}); });
+8 -10
View File
@@ -141,18 +141,16 @@ describe('decideOccurrence — ordinary-sunday', () => {
}); });
describe('decideOccurrence — privileged-sunday', () => { describe('decideOccurrence — privileged-sunday', () => {
it('is never displaced; duplex-majus and up are commemorated', () => { it('is never displaced, and never gives even a bare commemoration to any rank — everything transfers forward', () => {
for (const rank of ['duplex-majus', 'duplex-2-classis', 'duplex-1-classis'] as const) { // Live-verified 2026-09-01 (see data/calendar/temporal-categories.yml
// and commemorations.ts's own `privileged-sunday` case comment):
// even a Duplex I. classis feast (the Immaculate Conception on Advent
// II, the Annunciation on Palm/Easter Sunday) transfers with no
// commemoration at all — corrects an earlier, never-actually-verified
// "duplex-majus+ still gets a nod" guess.
for (const rank of ['duplex-majus', 'duplex-2-classis', 'duplex-1-classis', 'duplex', 'semiduplex', 'simplex'] as const) {
const result = decideOccurrence('privileged-sunday', 'advent-1', saint(rank)); const result = decideOccurrence('privileged-sunday', 'advent-1', saint(rank));
expect(result.winner).toEqual({ kind: 'temporal', id: 'advent-1' }); expect(result.winner).toEqual({ kind: 'temporal', id: 'advent-1' });
expect(result.commemorations).toEqual([{ kind: 'sanctoral', id: 'x', name: 'St. Ereden', rank }]);
expect(result.transfer).toBeUndefined();
}
});
it('duplex, semiduplex, and simplex all transfer forward — no bare commemoration here', () => {
for (const rank of ['duplex', 'semiduplex', 'simplex'] as const) {
const result = decideOccurrence('privileged-sunday', 'advent-1', saint(rank));
expect(result.commemorations).toEqual([]); expect(result.commemorations).toEqual([]);
expect(result.transfer).toEqual({ candidate: saint(rank), direction: 'forward' }); expect(result.transfer).toEqual({ candidate: saint(rank), direction: 'forward' });
} }
+19 -9
View File
@@ -66,21 +66,31 @@ describe('getDayLabel — ordinal temporal label', () => {
}); });
it("Advent's own anchor Sunday is already week 1, not a separate anchor-week case", () => { it("Advent's own anchor Sunday is already week 1, not a separate anchor-week case", () => {
// 2025-11-30 is also St. Andrew's own day (Duplex II. classis) — since // 2025-11-30 is also St. Andrew's own day (Duplex II. classis) — a
// the November sanctoral pull, he's correctly commemorated alongside // privileged Sunday transfers *everything*, no rank exception
// Advent I rather than simply absent (confirmed against the live // (live-verified 2026-09-01, correcting an earlier, inaccurate claim
// reference engine, which shows the same pairing), so the label // that he was commemorated in place: the real Monastic 1617 engine
// reflects both, winner (Advent I, so privileged nothing can displace // shows "Transfer: S. Andreæ Apostoli", not a commemoration), so the
// it) first, commemorated saint after. // label shows him as transferred away rather than paired alongside
expect(getDayLabel(resolveDay('2025-11-30')).en).toBe('The 1st Sunday of Advent (Semiduplex) — St. Andrew, Apostle'); // Advent I.
expect(getDayLabel(resolveDay('2025-11-30')).en).toBe('The 1st Sunday of Advent (Semiduplex) — St. Andrew, Apostle (transferred away)');
// Dec 1 is where Andrew's own transfer actually lands (the next open
// day, `privileged-feria-minor`'s Semiduplex+ threshold easily clears
// for a Duplex II. classis) — live-verified 2026-09-01, the real
// Monastic 1617 engine shows "S. Andreæ Apostoli ~ Duplex II. classis"
// there outright, "Tempora: Feria II infra Hebdomadam I Adventus"
// underneath, matching this.
expect(getDayLabel(resolveDay('2025-12-01')).en).toBe('St. Andrew, Apostle (Duplex II Class, transferred) — In the 1st week of Advent');
// Dec 4 (St. Barbara, merely commemorated, not high-ranked enough to
// win outright) is a clean example of the season-ordinal label itself.
// Advent drops the redundant leading weekday word now that the header // Advent drops the redundant leading weekday word now that the header
// always shows the weekday on its own (see temporalLabel's // always shows the weekday on its own (see temporalLabel's
// `dropWeekdayPrefix`). // `dropWeekdayPrefix`).
expect(getDayLabel(resolveDay('2025-12-01')).en).toBe('In the 1st week of Advent (Feria)'); expect(getDayLabel(resolveDay('2025-12-04')).en).toBe('In the 1st week of Advent (Feria) — St. Barbara, Virgin and Martyr');
// Latin: same weekday-drop, and the season-ordinal genitive // Latin: same weekday-drop, and the season-ordinal genitive
// construction ("Hebdomada prima Adventus") needs no extra linking // construction ("Hebdomada prima Adventus") needs no extra linking
// word — 4th-declension "Adventus" has the same genitive form. // word — 4th-declension "Adventus" has the same genitive form.
expect(getDayLabel(resolveDay('2025-12-01')).la).toBe('In hebdomada prima Adventus (Feria)'); expect(getDayLabel(resolveDay('2025-12-04')).la).toBe('In hebdomada prima Adventus (Feria) — Sancta Barbara, Virgo et Martyr');
}); });
it('falls back to weekday + season name for seasons with no ordinal convention modeled', () => { it('falls back to weekday + season name for seasons with no ordinal convention modeled', () => {
+12 -8
View File
@@ -104,17 +104,21 @@ describe('resolveOrdo("compline", ...)', () => {
it('anticipates Advent I: the Saturday evening before switches to the Alma Redemptoris Mater and shows the anticipated day label', () => { it('anticipates Advent I: the Saturday evening before switches to the Alma Redemptoris Mater and shows the anticipated day label', () => {
// Nov 30, 2025 is Advent I Sunday -- also St. Andrew's own day // Nov 30, 2025 is Advent I Sunday -- also St. Andrew's own day
// (Duplex II. classis), correctly commemorated alongside it since the // (Duplex II. classis), but a privileged Sunday transfers him
// November sanctoral pull (see tests/calendar/day-label.test.ts). This // entirely rather than commemorating him in place (live-verified
// evening also anticipates Advent I from Nov 29, itself the Vigil of // 2026-09-01, see tests/calendar/day-label.test.ts). This evening
// St. Andrew -- but the Vigil isn't also shown alongside his own // also anticipates Advent I from Nov 29, itself the Vigil of St.
// feast (calendar/vespers.ts's own isVigilOfTomorrow check, keyed off // Andrew -- calendar/vespers.ts's own isVigilOfTomorrow check only
// each Vigil's explicit `vigilOf` field): showing both would just be // suppresses the Vigil's own commemoration when tomorrow's page
// redundant, the same feast named twice. // *also* shows St. Andrew (which would be redundant); since he no
// longer does (he's transferred away, not commemorated), both pieces
// of information are genuinely distinct and both show.
const eve = resolveOrdo('compline', '2025-11-29'); const eve = resolveOrdo('compline', '2025-11-29');
const last = eve.parts[eve.parts.length - 1]; const last = eve.parts[eve.parts.length - 1];
expect(last?.kind === 'preces' ? last.label : undefined).toBe('Alma Redemptoris Mater'); expect(last?.kind === 'preces' ? last.label : undefined).toBe('Alma Redemptoris Mater');
expect(eve.dayLabel?.en).toBe('The 1st Sunday of Advent (Semiduplex) — St. Andrew, Apostle'); expect(eve.dayLabel?.en).toBe(
'The 1st Sunday of Advent (Semiduplex) — Vigil of St. Andrew (of today) — St. Andrew, Apostle (transferred away)',
);
const dayBefore = resolveOrdo('compline', '2025-11-28'); const dayBefore = resolveOrdo('compline', '2025-11-28');
const lastBefore = dayBefore.parts[dayBefore.parts.length - 1]; const lastBefore = dayBefore.parts[dayBefore.parts.length - 1];
+12 -10
View File
@@ -565,18 +565,20 @@ describe('resolveOrdo("lauds", ...)', () => {
}); });
it('resolves both the winners collect and a commemorated saints collect on a day with a real commemoration', () => { it('resolves both the winners collect and a commemorated saints collect on a day with a real commemoration', () => {
// 2025-11-30: Advent I (the winner) with St. Andrew, Duplex II. classis, // 2029-01-14: post-Epiphany II (the winner, `ordinary-sunday`) with
// commemorated alongside it (see tests/calendar/day-label.test.ts) -- // St. Felix Presbyter, Simplex, commemorated alongside it — live-
// he has only a bare collect authored (no st-andrew-commemoration.yml // verified 2026-09-01. (Not 2025-11-30/St. Andrew as this test used
// Ant+V/R bundle yet), so this renders as a labeled 'preces' block // to: a privileged Sunday transfers *everything*, no rank exception
// (see resolve-common.ts's sanctoralCommemorationPart), not a second // — Andrew doesn't get a bare commemoration there at all, see
// unlabeled 'prayer' the way it used to. // tests/calendar/day-label.test.ts. `ordinary-sunday`'s own Simplex
const ordo = resolveOrdo('lauds', '2025-11-30'); // branch is the real case that still commemorates a saint in place.)
const ordo = resolveOrdo('lauds', '2029-01-14');
const collects = ordo.parts.filter((p) => p.kind === 'prayer'); const collects = ordo.parts.filter((p) => p.kind === 'prayer');
expect(collects.length).toBe(1); expect(collects.length).toBe(1);
const andrewCommemoration = ordo.parts.find((p) => p.kind === 'preces' && p.label?.includes('Andrew')); const felixCommemoration = ordo.parts.find((p) => p.kind === 'preces' && p.label?.includes('Felix'));
expect(andrewCommemoration?.kind === 'preces' ? andrewCommemoration.text.text.en : undefined).toContain( expect(felixCommemoration).toBeDefined();
'Andrew', expect(felixCommemoration?.kind === 'preces' ? felixCommemoration.text.text.en : undefined).toContain(
'palm tree',
); );
}); });
+1 -1
View File
@@ -718,7 +718,7 @@ describe('resolveOrdo("matins", ...) rest-of-year temporal nocturn-readings swee
['2026-04-19', 'easter-3'], ['2026-04-19', 'easter-3'],
['2026-04-27', 'easter-4'], ['2026-04-27', 'easter-4'],
['2026-05-04', 'easter-5'], ['2026-05-04', 'easter-5'],
['2026-05-11', 'easter-6'], ['2026-05-10', 'easter-6 (the real Sunday — not 2026-05-11: since the eastertide-Sunday precedence fix (2026-09-01), St. Antoninus, Duplex, now correctly wins outright on his own day, May 10, rather than transferring onto Monday, so easter-6\'s own pooled readings show up there commemorated, not on the 11th)'],
['2026-05-18', 'sunday-after-ascension'], ['2026-05-18', 'sunday-after-ascension'],
['2026-04-05', 'easter-sunday'], ['2026-04-05', 'easter-sunday'],
['2026-05-24', 'pentecost-sunday'], ['2026-05-24', 'pentecost-sunday'],