Fix Matins reading-pool ordering/leak bugs and redundant day-label commemoration
- nocturnReadingIds no longer lets a demoted commemoration's temporal id (equal to the plain governing-Sunday temporalId) sneak a Sunday's own homily into an unrelated weekday's pool via the commemorations loop — a second code path than the one a prior fix gated, affecting 18 days/year. - Reading pool order now follows id priority (winner first, then every temporal commemoration, then every sanctoral commemoration) instead of raw nocturn-tag/array order, matching the day label's own temporal-then-sanctoral convention. - gospelReadingPart now falls back to the paired homily's own responsory when the Gospel entry has none of its own, instead of silently dropping it. - Patristic source labels now show "(for <occasion>)" so a reader can tell which occasion a pooled reading belongs to on a multi-reading day. - collectCommemorations no longer restates the plain generic temporal label (e.g. "Saturday in the 15th week after Trinity") once anything else is already being celebrated — general, not Ember-specific. Updated 4 day-label tests that encoded the old behavior. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MKC2QQGLYo2UTiDjDyWEkW
This commit is contained in:
@@ -78,7 +78,7 @@ const EMBER_DAY_NAMES: Record<string, Bi> = {
|
|||||||
'ember-advent-saturday': bi('Ember Saturday in Advent', 'Sabbato Quattuor Temporum in Adventu'),
|
'ember-advent-saturday': bi('Ember Saturday in Advent', 'Sabbato Quattuor Temporum in Adventu'),
|
||||||
};
|
};
|
||||||
|
|
||||||
function emberDayLabel(id: string): Bi | undefined {
|
export function emberDayLabel(id: string): Bi | undefined {
|
||||||
return EMBER_DAY_NAMES[id];
|
return EMBER_DAY_NAMES[id];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -488,9 +488,22 @@ function collectCommemorations(
|
|||||||
sanctoral.push(name);
|
sanctoral.push(name);
|
||||||
}
|
}
|
||||||
} else if (c.kind === 'temporal') {
|
} else if (c.kind === 'temporal') {
|
||||||
if (c.id === temporalId) {
|
// `c.id === temporalId` (the plain governing weekday, e.g. "Saturday
|
||||||
temporal.push(anchorDayName(day, false) ?? temporalLabel(day));
|
// in the 15th week after Trinity") is deliberately never shown here:
|
||||||
} else {
|
// this function only ever runs to list *secondary* identities
|
||||||
|
// alongside an already-displayed primary winner (a plain temporal
|
||||||
|
// day that wins outright never reaches `day.commemorations` at all —
|
||||||
|
// its own name is the header itself, via a different branch of
|
||||||
|
// `getDayLabel`). Once something else is already being celebrated,
|
||||||
|
// restating "this is also just the Nth ordinary weekday" tells the
|
||||||
|
// reader nothing they don't already know from the winner+rank shown
|
||||||
|
// — unlike a *named* temporal identity (an Ember day, a vigil), which
|
||||||
|
// genuinely adds information. Not Ember-specific (a 2026-09-05 first
|
||||||
|
// pass wrongly scoped this to "only when an Ember commemoration is
|
||||||
|
// also present" — user, 2026-09-05: "it has nothing to do with
|
||||||
|
// ember-ness"); this plain generic label is dropped unconditionally
|
||||||
|
// whenever it shows up as a mere commemoration, Ember day or not.
|
||||||
|
if (c.id !== temporalId) {
|
||||||
const emberName = emberDayLabel(c.id);
|
const emberName = emberDayLabel(c.id);
|
||||||
if (emberName) {
|
if (emberName) {
|
||||||
temporal.push(emberName);
|
temporal.push(emberName);
|
||||||
|
|||||||
+108
-22
@@ -71,7 +71,7 @@ import type { ResolvedOrdo, ResolvedPart, ResolvedText, ResolvedVerse } from './
|
|||||||
import type { LiturgicalDay, DayWinner } from '../calendar/types';
|
import type { LiturgicalDay, DayWinner } from '../calendar/types';
|
||||||
import { resolveDay, resolveTemporalId, monthWeekId, activeOctavesFor, resolveActiveOctave, isAtLeast } from '../calendar';
|
import { resolveDay, resolveTemporalId, monthWeekId, activeOctavesFor, resolveActiveOctave, isAtLeast } from '../calendar';
|
||||||
import { isInTriduum } from '../calendar/temporal';
|
import { isInTriduum } from '../calendar/temporal';
|
||||||
import { getDayLabel } from '../calendar/day-label';
|
import { getDayLabel, emberDayLabel } from '../calendar/day-label';
|
||||||
import { getPsalmVerses } from '../psalter';
|
import { getPsalmVerses } from '../psalter';
|
||||||
import { getPsalmsFor, type PsalmRef } from '../psalter/distribution';
|
import { getPsalmsFor, type PsalmRef } from '../psalter/distribution';
|
||||||
import { getScriptureVerses } from '../scripture';
|
import { getScriptureVerses } from '../scripture';
|
||||||
@@ -692,9 +692,37 @@ function nocturnReadingIds(day: LiturgicalDay, temporalId: string, date: string,
|
|||||||
if (day.winner.kind === 'sanctoral' || day.winner.id !== temporalId || threeNocturns) {
|
if (day.winner.kind === 'sanctoral' || day.winner.id !== temporalId || threeNocturns) {
|
||||||
ids.add(day.winner.id);
|
ids.add(day.winner.id);
|
||||||
}
|
}
|
||||||
|
// Two passes, not one: every *temporal* commemoration's id is added
|
||||||
|
// before any *sanctoral* commemoration's, matching `day-label.ts`'s own
|
||||||
|
// `collectCommemorations` convention (`[...temporal, ...sanctoral]`) —
|
||||||
|
// so a reading's pool position agrees with where its own occasion is
|
||||||
|
// named in the day's displayed label. `day.commemorations`' own array
|
||||||
|
// order doesn't already reflect this grouping (live-verified as a real
|
||||||
|
// bug, 2026-09-19: St. Januarius's readings listed the Vigil of St.
|
||||||
|
// Matthew's homily before Ember Saturday's, backwards from the label's
|
||||||
|
// own "... — Ember Saturday in September — Vigil of St. Matthew
|
||||||
|
// (transferred)"), so it must be imposed here rather than assumed.
|
||||||
for (const c of day.commemorations) {
|
for (const c of day.commemorations) {
|
||||||
// Same reasoning as day.winner.id just above.
|
// Same reasoning as day.winner.id just above, but a *commemorated*
|
||||||
if (c.kind === 'sanctoral' || (c.kind === 'temporal' && (c.id !== temporalId || threeNocturns))) ids.add(c.id);
|
// (not winning) temporal identity that equals the plain governing-
|
||||||
|
// Sunday `temporalId` is exactly the "Commemoratio Feriæ" case the
|
||||||
|
// `day.weekday === 'sunday'` gate below exists to guard (see that
|
||||||
|
// gate's own long comment) — a Duplex+ saint winning outright on a
|
||||||
|
// *weekday* (e.g. St. Joseph of Cupertino, Friday Sept 18) still has
|
||||||
|
// `threeNocturns` true, so `c.id !== temporalId || threeNocturns`
|
||||||
|
// alone let the week's separate governing-Sunday homily (Ambrose's,
|
||||||
|
// live-verified 2026-09-05) sneak back into a weekday's pool through
|
||||||
|
// this loop even though the gate below correctly kept it out of the
|
||||||
|
// `temporalKept` block. `c.id !== temporalId` must hold on its own
|
||||||
|
// unless today genuinely *is* that Sunday.
|
||||||
|
if (c.kind === 'temporal' && (c.id !== temporalId || (threeNocturns && day.weekday === 'sunday'))) {
|
||||||
|
ids.add(c.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (const c of day.commemorations) {
|
||||||
|
if (c.kind === 'sanctoral') {
|
||||||
|
ids.add(c.id);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// The plain temporalId/month-week content is the *governing Sunday's own*
|
// The plain temporalId/month-week content is the *governing Sunday's own*
|
||||||
// Nocturn 2/3 patristic material, real content for that Sunday itself —
|
// Nocturn 2/3 patristic material, real content for that Sunday itself —
|
||||||
@@ -753,6 +781,28 @@ function fallbackHagiographicLabel(id: string): string | undefined {
|
|||||||
return name.startsWith('The ') ? `On the ${name.slice(4)}` : `On ${name}`;
|
return name.startsWith('The ') ? `On the ${name.slice(4)}` : `On ${name}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** The human-readable occasion a `nocturn-readings` id stands for — a
|
||||||
|
* saint's or named temporal feast's own display name, or an Ember day's
|
||||||
|
* (which has no `TemporalFeastRecord` of its own, see `emberDayLabel`'s own
|
||||||
|
* doc comment). Used to annotate an authored patristic `source` with *why*
|
||||||
|
* it's here, since a single day can pool several ids' worth of readings
|
||||||
|
* (e.g. a commemorated Ember day's own Gospel homily alongside the winning
|
||||||
|
* saint's own) and a bare "Pope St. Gregory the Great, Homily 33" doesn't
|
||||||
|
* tell a reader which occasion that homily belongs to. */
|
||||||
|
function occasionName(id: string): string | undefined {
|
||||||
|
return getSaintRecord(id)?.name ?? getTemporalFeastRecord(id)?.name ?? emberDayLabel(id)?.en;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Appends `(for <occasion>)` to an authored patristic `source` string,
|
||||||
|
* when the contributing id resolves to a human-readable occasion name —
|
||||||
|
* see `occasionName`. A no-op when `source` is undefined (the hagiographic
|
||||||
|
* fallback case, which already names its own saint). */
|
||||||
|
function withOccasion(source: string | undefined, id: string): string | undefined {
|
||||||
|
if (!source) return source;
|
||||||
|
const occasion = occasionName(id);
|
||||||
|
return occasion ? `${source} (for ${occasion})` : source;
|
||||||
|
}
|
||||||
|
|
||||||
/** `index` is this reading's position among this same id's own
|
/** `index` is this reading's position among this same id's own
|
||||||
* nocturn-readings (not the whole day's pool) — the cycling key for
|
* nocturn-readings (not the whole day's pool) — the cycling key for
|
||||||
* `getResponsoryForCommon` when `r` has no proper `responsory` of its own,
|
* `getResponsoryForCommon` when `r` has no proper `responsory` of its own,
|
||||||
@@ -764,7 +814,7 @@ function nocturnReadingPart(r: NocturnReading, id: string, index: number): Resol
|
|||||||
return {
|
return {
|
||||||
kind: 'lesson',
|
kind: 'lesson',
|
||||||
text: { text: r.text, status: r.status, citation: r.citation },
|
text: { text: r.text, status: r.status, citation: r.citation },
|
||||||
label: r.source ?? fallbackHagiographicLabel(id),
|
label: withOccasion(r.source, id) ?? fallbackHagiographicLabel(id),
|
||||||
responsory: responsoryText ? { text: responsoryText, status: { la: 'verified', en: 'verified' } } : undefined,
|
responsory: responsoryText ? { text: responsoryText, status: { la: 'verified', en: 'verified' } } : undefined,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -779,15 +829,23 @@ function nocturnReadingPart(r: NocturnReading, id: string, index: number): Resol
|
|||||||
* were). */
|
* were). */
|
||||||
function gospelReadingPart(r: NocturnReading, homily: NocturnReading | undefined, id: string, index: number): ResolvedPart {
|
function gospelReadingPart(r: NocturnReading, homily: NocturnReading | undefined, id: string, index: number): ResolvedPart {
|
||||||
const incipit = getGospelIncipitFromCitation(r.citation);
|
const incipit = getGospelIncipitFromCitation(r.citation);
|
||||||
const responsoryText = r.responsory ?? getResponsoryForCommon(getSaintRecord(id)?.common, index);
|
// A responsory authored on the *homily* (the reference engine's own
|
||||||
|
// convention — e.g. post-pentecost-16.yml's Ambrose homily, or a saint's
|
||||||
|
// own Gospel-homily pair) belongs to the pericope+homily as a whole, not
|
||||||
|
// just the bare Gospel text, and must fall back here before the generic
|
||||||
|
// Common-category pool — this file's own header says a governing
|
||||||
|
// Sunday's homily "and its responsory" are pooled together; only `r`'s
|
||||||
|
// own field was ever checked, silently dropping every authored homily
|
||||||
|
// responsory (found 2026-09-05 while live-verifying an unrelated report).
|
||||||
|
const responsoryText = r.responsory ?? homily?.responsory ?? getResponsoryForCommon(getSaintRecord(id)?.common, index);
|
||||||
return {
|
return {
|
||||||
kind: 'gospel',
|
kind: 'gospel',
|
||||||
text: { text: r.text, status: r.status, citation: r.citation },
|
text: { text: r.text, status: r.status, citation: r.citation },
|
||||||
source: r.source,
|
source: withOccasion(r.source, id),
|
||||||
label: incipit ? { la: incipit.la, en: incipit.en } : undefined,
|
label: incipit ? { la: incipit.la, en: incipit.en } : undefined,
|
||||||
responsory: responsoryText ? { text: responsoryText, status: { la: 'verified', en: 'verified' } } : undefined,
|
responsory: responsoryText ? { text: responsoryText, status: { la: 'verified', en: 'verified' } } : undefined,
|
||||||
homily: homily
|
homily: homily
|
||||||
? { source: homily.source, text: { text: homily.text, status: homily.status, citation: homily.citation } }
|
? { source: withOccasion(homily.source, id), text: { text: homily.text, status: homily.status, citation: homily.citation } }
|
||||||
: undefined,
|
: undefined,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -827,18 +885,40 @@ function buildReadingPool(day: LiturgicalDay, temporalId: string, date: string,
|
|||||||
: { kind: 'lesson', text: { text: r.text, status: r.status, citation: r.citation }, responsory, label },
|
: { kind: 'lesson', text: { text: r.text, status: r.status, citation: r.citation }, responsory, label },
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
// Grouped by the reading's own `nocturn` tag (ascending), not by which id
|
|
||||||
// contributed it — a later post-Pentecost Sunday's Nocturn 2 now comes
|
|
||||||
// from a different source (the month-week id) than its Nocturn 3 (its own
|
|
||||||
// temporalId file), and pooling in plain id order would put that Nocturn
|
|
||||||
// 3 content ahead of the Nocturn 2 content supplied by a later-processed
|
|
||||||
// id. Within each nocturn-number group, id order (and each file's own
|
|
||||||
// reading order) is preserved, matching this pool's usual priority rule.
|
|
||||||
// Not hardcoded to [2, 3]: Ember days' own nocturn-readings files use
|
|
||||||
// `nocturn: 1` (their single-nocturn structure), so every tag present
|
|
||||||
// must be handled, not just the usual Sunday/feast pair.
|
|
||||||
const ids = nocturnReadingIds(day, temporalId, date, threeNocturns);
|
const ids = nocturnReadingIds(day, temporalId, date, threeNocturns);
|
||||||
const byNocturn = new Map<number, ResolvedPart[]>();
|
// Ordering is by *id priority* (winner first, then commemorations in
|
||||||
|
// `day.commemorations` order — a winning saint's own content always
|
||||||
|
// precedes a demoted commemoration's, matching the day-label convention
|
||||||
|
// of listing the winner first), with one deliberate exception: the
|
||||||
|
// temporalId itself and `month-week-${monthWeek}` are split halves of
|
||||||
|
// ONE governing Sunday's own Nocturn 2/3 content (see
|
||||||
|
// `nocturnReadingIds`'s `temporalKept` block) — pooling those two in
|
||||||
|
// plain id-priority order would put the temporalId file's own Nocturn 3
|
||||||
|
// content ahead of month-week's Nocturn 2 content, so this pair alone is
|
||||||
|
// grouped by raw `nocturn` tag (ascending) instead. Every other id is its
|
||||||
|
// own atomic block: previously *every* id was grouped globally by raw
|
||||||
|
// `nocturn` tag, which wrongly sorted a winning saint's own content after
|
||||||
|
// a demoted commemoration's whenever the commemoration's file happened to
|
||||||
|
// use a lower tag — e.g. an Ember day's single-nocturn file always tags
|
||||||
|
// `nocturn: 1`, sorting it ahead of a winning Duplex+ saint's own
|
||||||
|
// nocturn:2/3 content even though the saint, not the Ember day, is what's
|
||||||
|
// actually being celebrated (user, 2026-09-05, re: St. Joseph of
|
||||||
|
// Cupertino/Ember Friday, Sept 18). Within a single id's own block,
|
||||||
|
// `nocturn` tag order (== that file's own reading order) is still used to
|
||||||
|
// order its own multiple readings relative to each other.
|
||||||
|
const monthWeek = monthWeekId(date);
|
||||||
|
const splitIds = new Set(threeNocturns && day.weekday === 'sunday' && monthWeek ? [temporalId, `month-week-${monthWeek}`] : []);
|
||||||
|
let splitPriority = -1;
|
||||||
|
const priorityOf = new Map<string, number>();
|
||||||
|
ids.forEach((id, i) => {
|
||||||
|
if (splitIds.has(id)) {
|
||||||
|
if (splitPriority === -1) splitPriority = i;
|
||||||
|
priorityOf.set(id, splitPriority);
|
||||||
|
} else {
|
||||||
|
priorityOf.set(id, i);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const byGroup = new Map<string, ResolvedPart[]>();
|
||||||
// The reference engine's own Tempora files sometimes independently reuse
|
// The reference engine's own Tempora files sometimes independently reuse
|
||||||
// the exact same excerpt across two different Sunday-numbering schemes
|
// the exact same excerpt across two different Sunday-numbering schemes
|
||||||
// (e.g. a Pentecost-numbered Sunday and a calendar-month Sunday landing on
|
// (e.g. a Pentecost-numbered Sunday and a calendar-month Sunday landing on
|
||||||
@@ -856,7 +936,8 @@ function buildReadingPool(day: LiturgicalDay, temporalId: string, date: string,
|
|||||||
const textKey = reading.text.la ?? reading.text.en ?? '';
|
const textKey = reading.text.la ?? reading.text.en ?? '';
|
||||||
if (textKey && seenReadingText.has(textKey)) continue;
|
if (textKey && seenReadingText.has(textKey)) continue;
|
||||||
if (textKey) seenReadingText.add(textKey);
|
if (textKey) seenReadingText.add(textKey);
|
||||||
const bucket = byNocturn.get(reading.nocturn) ?? [];
|
const groupKey = `${priorityOf.get(id)}:${reading.nocturn}`;
|
||||||
|
const bucket = byGroup.get(groupKey) ?? [];
|
||||||
if (reading.isGospel) {
|
if (reading.isGospel) {
|
||||||
// A genuine separate homily on this pericope is the very next
|
// A genuine separate homily on this pericope is the very next
|
||||||
// reading in the same file, in the same nocturn, not itself flagged
|
// reading in the same file, in the same nocturn, not itself flagged
|
||||||
@@ -872,11 +953,16 @@ function buildReadingPool(day: LiturgicalDay, temporalId: string, date: string,
|
|||||||
} else {
|
} else {
|
||||||
bucket.push(nocturnReadingPart(reading, id, commonPoolIndex++));
|
bucket.push(nocturnReadingPart(reading, id, commonPoolIndex++));
|
||||||
}
|
}
|
||||||
byNocturn.set(reading.nocturn, bucket);
|
byGroup.set(groupKey, bucket);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (const nocturnNumber of [...byNocturn.keys()].sort((a, b) => a - b)) {
|
const sortedGroupKeys = [...byGroup.keys()].sort((a, b) => {
|
||||||
parts.push(...byNocturn.get(nocturnNumber)!);
|
const [aPriority, aNocturn] = a.split(':').map(Number);
|
||||||
|
const [bPriority, bNocturn] = b.split(':').map(Number);
|
||||||
|
return aPriority! - bPriority! || aNocturn! - bNocturn!;
|
||||||
|
});
|
||||||
|
for (const key of sortedGroupKeys) {
|
||||||
|
parts.push(...byGroup.get(key)!);
|
||||||
}
|
}
|
||||||
for (const octave of activeOctavesFor(date)) {
|
for (const octave of activeOctavesFor(date)) {
|
||||||
const reading = getOctaveReading(octave.id, octave.dayNumber);
|
const reading = getOctaveReading(octave.id, octave.dayNumber);
|
||||||
|
|||||||
@@ -80,8 +80,12 @@ describe('getDayLabel — ordinal temporal label', () => {
|
|||||||
// for a Duplex II. classis) — live-verified 2026-09-01, the real
|
// for a Duplex II. classis) — live-verified 2026-09-01, the real
|
||||||
// Monastic 1617 engine shows "S. Andreæ Apostoli ~ Duplex II. classis"
|
// Monastic 1617 engine shows "S. Andreæ Apostoli ~ Duplex II. classis"
|
||||||
// there outright, "Tempora: Feria II infra Hebdomadam I Adventus"
|
// there outright, "Tempora: Feria II infra Hebdomadam I Adventus"
|
||||||
// underneath, matching this.
|
// underneath, matching this. The plain "In the 1st week of Advent"
|
||||||
expect(getDayLabel(resolveDay('2025-12-01')).en).toBe('St. Andrew, Apostle (Duplex II Class, transferred) — In the 1st week of Advent');
|
// feria commemoration this day also carries internally (day.commemorations
|
||||||
|
// still has it) is no longer shown in the label (2026-09-05: a plain
|
||||||
|
// temporal commemoration is dropped from the label whenever anything
|
||||||
|
// else is already being celebrated — general, not Ember-specific).
|
||||||
|
expect(getDayLabel(resolveDay('2025-12-01')).en).toBe('St. Andrew, Apostle (Duplex II Class, transferred)');
|
||||||
// Dec 4 (St. Barbara, merely commemorated, not high-ranked enough to
|
// Dec 4 (St. Barbara, merely commemorated, not high-ranked enough to
|
||||||
// win outright) is a clean example of the season-ordinal label itself.
|
// 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
|
||||||
@@ -180,29 +184,29 @@ describe('getDayLabel — resumed post-Epiphany Sunday (overflow years)', () =>
|
|||||||
// one, carry no special first-class standing of their own), so
|
// one, carry no special first-class standing of their own), so
|
||||||
// commemorations.ts's `ordinary-sunday` case's plain Duplex+ threshold
|
// commemorations.ts's `ordinary-sunday` case's plain Duplex+ threshold
|
||||||
// is what lets her win outright rather than being commemorated — a
|
// is what lets her win outright rather than being commemorated — a
|
||||||
// pre-existing rule, not new behavior from adding her. This case
|
// pre-existing rule, not new behavior from adding her. The displaced
|
||||||
// additionally confirms the displaced Sunday is still commemorated in
|
// Sunday is still commemorated internally (day.commemorations), using
|
||||||
// return, using the same fixed 23rd-after-Trinity ordinal, not a raw
|
// the same fixed 23rd-after-Trinity ordinal, not a raw elapsed-week
|
||||||
// elapsed-week count recomputed for 1943.
|
// count recomputed for 1943 — but per the 2026-09-05 label rule, a
|
||||||
expect(getDayLabel(resolveDay('1943-11-21')).en).toBe(
|
// plain temporal commemoration like this one is no longer shown in the
|
||||||
'The Presentation of the Blessed Virgin Mary (Duplex Majus) — The 23rd Sunday after Trinity',
|
// label once something else already has the primary slot.
|
||||||
);
|
expect(getDayLabel(resolveDay('1943-11-21')).en).toBe('The Presentation of the Blessed Virgin Mary (Duplex Majus)');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('getDayLabel — commemorated Sunday/feria under a sanctoral winner', () => {
|
describe('getDayLabel — commemorated Sunday/feria under a sanctoral winner', () => {
|
||||||
it('a saint winning outright on a privileged-feria-minor day still leaves that feria commemorated', () => {
|
it('a saint winning outright on a privileged-feria-minor day still leaves that feria commemorated internally, but the label no longer restates it', () => {
|
||||||
// 2027-12-07, a Tuesday of Advent: St. Ambrose (Duplex) clears
|
// 2027-12-07, a Tuesday of Advent: St. Ambrose (Duplex) clears
|
||||||
// privileged-feria-minor's own Semiduplex+ threshold and wins
|
// privileged-feria-minor's own Semiduplex+ threshold and wins
|
||||||
// outright, but Advent's own feria is still commemorated in return
|
// outright, and Advent's own feria is still commemorated in return
|
||||||
// (`decideOccurrence`'s `privileged-feria-minor` branch) -- live-
|
// internally (`decideOccurrence`'s `privileged-feria-minor` branch) --
|
||||||
// verified shape, same mechanism as the ordinary-Sunday case above,
|
// live-verified shape, same mechanism as the ordinary-Sunday case
|
||||||
// for the feria-tier branch instead.
|
// above, for the feria-tier branch instead. But per the 2026-09-05
|
||||||
// Advent drops the redundant leading weekday word (see the Dec 1 case
|
// label rule, a plain temporal commemoration (a bare "In the Nth week
|
||||||
// above).
|
// of Advent") is dropped from the label once something else already
|
||||||
expect(getDayLabel(resolveDay('2027-12-07')).en).toBe(
|
// has the primary slot -- it isn't Ember-specific, and it applies here
|
||||||
'St. Ambrose, Bishop, Confessor and Doctor of the Church (Duplex) — In the 2nd week of Advent',
|
// too even though this is a feria, not an Ember day.
|
||||||
);
|
expect(getDayLabel(resolveDay('2027-12-07')).en).toBe('St. Ambrose, Bishop, Confessor and Doctor of the Church (Duplex)');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -289,7 +293,7 @@ describe('getDayLabel — active octave', () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('a sanctoral winner on a Sunday still leaves the Sunday commemorated, but neither octave it also happens to overlap -- not an octave name at all', () => {
|
it('a sanctoral winner on a Sunday still leaves the Sunday commemorated internally, but neither octave it also happens to overlap -- not an octave name at all, nor the plain Sunday ordinal any more', () => {
|
||||||
// 2026-08-16 is a Sunday genuinely within both St. Lawrence's and the
|
// 2026-08-16 is a Sunday genuinely within both St. Lawrence's and the
|
||||||
// Assumption's overlapping octave windows -- St. Joachim (Duplex II
|
// Assumption's overlapping octave windows -- St. Joachim (Duplex II
|
||||||
// Class) wins outright per the ordinary-Sunday Duplex+ threshold, not
|
// Class) wins outright per the ordinary-Sunday Duplex+ threshold, not
|
||||||
@@ -297,14 +301,17 @@ describe('getDayLabel — active octave', () => {
|
|||||||
// this case existed). Live-verified against Divino Afflatu 1954: the
|
// this case existed). Live-verified against Divino Afflatu 1954: the
|
||||||
// commemoration line reads only "Dominica XII Post Pentecosten" (this
|
// commemoration line reads only "Dominica XII Post Pentecosten" (this
|
||||||
// app's own Trinity-counted display is one week off from that
|
// app's own Trinity-counted display is one week off from that
|
||||||
// Pentecost-counted number, so "11th ... after Trinity" here) -- no
|
// Pentecost-counted number, so "11th ... after Trinity" internally) --
|
||||||
// octave named at all, even though both are technically active, same
|
// no octave named at all, even though both are technically active,
|
||||||
// as this app's own established `ordinary-feria`-only gate for
|
// same as this app's own established `ordinary-feria`-only gate for
|
||||||
// octave mentions alongside a sanctoral winner.
|
// octave mentions alongside a sanctoral winner. Per the 2026-09-05
|
||||||
|
// label rule, the plain Sunday commemoration itself is now also
|
||||||
|
// dropped from the label (still present in day.commemorations) once
|
||||||
|
// a sanctoral winner already has the primary slot.
|
||||||
const day = resolveDay('2026-08-16');
|
const day = resolveDay('2026-08-16');
|
||||||
expect(day.weekday).toBe('sunday');
|
expect(day.weekday).toBe('sunday');
|
||||||
expect(getDayLabel(day).en).toBe(
|
expect(getDayLabel(day).en).toBe(
|
||||||
'St. Joachim, Confessor, Father of the Blessed Virgin Mary (Duplex II Class) — The 11th Sunday after Trinity',
|
'St. Joachim, Confessor, Father of the Blessed Virgin Mary (Duplex II Class)',
|
||||||
);
|
);
|
||||||
expect(getDayLabel(day).en).not.toContain('Octave');
|
expect(getDayLabel(day).en).not.toContain('Octave');
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user