Annotate plain-Sunday-sourced Matins readings with their occasion

occasionName() only recognized saints, named temporal feasts, and Ember
days, so any reading pooled under a plain governing-Sunday id
(post-pentecost-15, month-week-092, etc.) -- the most common id shape
in the whole pool -- silently got no "(for ...)" annotation. This is
why Sept 6's Gregory the Great homily and its paired Augustine Gospel
homily showed up with no indication of why they were present. Falls
back to the same getDayLabel() the day's own heading already uses.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T6rG4RTNqLFYcyjbCGkPFs
This commit is contained in:
2026-09-05 14:07:27 -04:00
parent 28b29c71d2
commit ff94000f48
+35 -18
View File
@@ -782,24 +782,35 @@ function fallbackHagiographicLabel(id: string): string | undefined {
} }
/** The human-readable occasion a `nocturn-readings` id stands for — a /** 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 * saint's or named temporal feast's own display name, an Ember day's (which
* (which has no `TemporalFeastRecord` of its own, see `emberDayLabel`'s own * has no `TemporalFeastRecord` of its own, see `emberDayLabel`'s own doc
* doc comment). Used to annotate an authored patristic `source` with *why* * comment), or — the most common case in the whole pool — a plain governing
* it's here, since a single day can pool several ids' worth of readings * Sunday's own temporal-cycle id (`post-pentecost-15`) or its
* (e.g. a commemorated Ember day's own Gospel homily alongside the winning * `month-week-<NNN>` half (see `nocturnReadingIds`'s `temporalKept` block),
* saint's own) and a bare "Pope St. Gregory the Great, Homily 33" doesn't * which falls back to the same `getDayLabel` the day's own heading uses
* tell a reader which occasion that homily belongs to. */ * (e.g. "The 14th Sunday after Trinity"). Used to annotate an authored
function occasionName(id: string): string | undefined { * patristic `source` with *why* it's here, since a single day can pool
return getSaintRecord(id)?.name ?? getTemporalFeastRecord(id)?.name ?? emberDayLabel(id)?.en; * 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, day: LiturgicalDay): string | undefined {
const named = getSaintRecord(id)?.name ?? getTemporalFeastRecord(id)?.name ?? emberDayLabel(id)?.en;
if (named) return named;
const monthWeek = monthWeekId(day.date);
if (id === resolveTemporalId(day.date) || (monthWeek && id === `month-week-${monthWeek}`)) {
return getDayLabel(day).en;
}
return undefined;
} }
/** Appends `(for <occasion>)` to an authored patristic `source` string, /** Appends `(for <occasion>)` to an authored patristic `source` string,
* when the contributing id resolves to a human-readable occasion name — * when the contributing id resolves to a human-readable occasion name —
* see `occasionName`. A no-op when `source` is undefined (the hagiographic * see `occasionName`. A no-op when `source` is undefined (the hagiographic
* fallback case, which already names its own saint). */ * fallback case, which already names its own saint). */
function withOccasion(source: string | undefined, id: string): string | undefined { function withOccasion(source: string | undefined, id: string, day: LiturgicalDay): string | undefined {
if (!source) return source; if (!source) return source;
const occasion = occasionName(id); const occasion = occasionName(id, day);
return occasion ? `${source} (for ${occasion})` : source; return occasion ? `${source} (for ${occasion})` : source;
} }
@@ -809,12 +820,12 @@ function withOccasion(source: string | undefined, id: string): string | undefine
* same convention as the per-book pool. Common-category fallback only * same convention as the per-book pool. Common-category fallback only
* applies to a real saint id (`getSaintRecord(id)?.common`) — a temporal or * applies to a real saint id (`getSaintRecord(id)?.common`) — a temporal or
* octave id has no Commune category to fall back to. */ * octave id has no Commune category to fall back to. */
function nocturnReadingPart(r: NocturnReading, id: string, index: number): ResolvedPart { function nocturnReadingPart(r: NocturnReading, id: string, index: number, day: LiturgicalDay): ResolvedPart {
const responsoryText = r.responsory ?? getResponsoryForCommon(getSaintRecord(id)?.common, index); const responsoryText = r.responsory ?? getResponsoryForCommon(getSaintRecord(id)?.common, index);
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: withOccasion(r.source, id) ?? fallbackHagiographicLabel(id), label: withOccasion(r.source, id, day) ?? fallbackHagiographicLabel(id),
responsory: responsoryText ? { text: responsoryText, status: { la: 'verified', en: 'verified' } } : undefined, responsory: responsoryText ? { text: responsoryText, status: { la: 'verified', en: 'verified' } } : undefined,
}; };
} }
@@ -827,7 +838,13 @@ function nocturnReadingPart(r: NocturnReading, id: string, index: number): Resol
* ResolvedPart can't be split across two nocturns by `distributeIntoNocturns`, * ResolvedPart can't be split across two nocturns by `distributeIntoNocturns`,
* whereas two adjacent pool entries could be (and, before this, sometimes * whereas two adjacent pool entries could be (and, before this, sometimes
* 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,
day: LiturgicalDay,
): ResolvedPart {
const incipit = getGospelIncipitFromCitation(r.citation); const incipit = getGospelIncipitFromCitation(r.citation);
// A responsory authored on the *homily* (the reference engine's own // 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 // convention — e.g. post-pentecost-16.yml's Ambrose homily, or a saint's
@@ -841,11 +858,11 @@ function gospelReadingPart(r: NocturnReading, homily: NocturnReading | undefined
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: withOccasion(r.source, id), source: withOccasion(r.source, id, day),
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: withOccasion(homily.source, id), text: { text: homily.text, status: homily.status, citation: homily.citation } } ? { source: withOccasion(homily.source, id, day), text: { text: homily.text, status: homily.status, citation: homily.citation } }
: undefined, : undefined,
}; };
} }
@@ -948,10 +965,10 @@ function buildReadingPool(day: LiturgicalDay, temporalId: string, date: string,
// apart by distributeIntoNocturns. // apart by distributeIntoNocturns.
const next = readings[i + 1]; const next = readings[i + 1];
const homily = next && !next.isGospel && next.nocturn === reading.nocturn ? next : undefined; const homily = next && !next.isGospel && next.nocturn === reading.nocturn ? next : undefined;
bucket.push(gospelReadingPart(reading, homily, id, commonPoolIndex++)); bucket.push(gospelReadingPart(reading, homily, id, commonPoolIndex++, day));
if (homily) i++; if (homily) i++;
} else { } else {
bucket.push(nocturnReadingPart(reading, id, commonPoolIndex++)); bucket.push(nocturnReadingPart(reading, id, commonPoolIndex++, day));
} }
byGroup.set(groupKey, bucket); byGroup.set(groupKey, bucket);
} }