From 290bbac1b5862f690fbb743288d87717d1490dba Mon Sep 17 00:00:00 2001 From: Will Estes Date: Fri, 4 Sep 2026 07:12:55 -0400 Subject: [PATCH] Route bible-plan incipit/responsory lookup through the book-alias table bible-plan.ts's *.yml import uses English-style book abbreviations ("jdt", "1kgs", "luke", ...) for ~30 books where that diverges from this store's canonical form ("jdth", "3reg", "luc", ...). scripture/index.ts's own BOOK_ALIASES already normalized this for verse-text lookup, but the incipit (bible-book-incipits.ts) and responsory (matins-responsories.ts) lookups used the raw abbreviation directly, silently missing on any canonically-keyed book and falling back to a bare "Reading" heading. The 3 Gospel aliases self-healed via a separate citation-regex fallback that only exists for Gospels; every other aliased book (Judith, Kings, Samuel, Esdras, Osee, Jonas, Josue, Micheas, Abdias, Judges, the Johannine epistles, James, Jude, Philemon, Titus, Zacharias, Sophonias) had no such fallback. Exports canonicalBook from scripture/index.ts and routes both lookups through it -- one shared normalization instead of two independently-aliased ones that can drift again. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01AGjUyhUZJaSjiniEmnLdak --- TODO.md | 30 ++++++++++++++++++++++++++++++ src/propers/bible-plan.ts | 6 ++++-- src/scripture/index.ts | 13 +++++++++++++ tests/propers/bible-plan.test.ts | 21 +++++++++++++++++++++ 4 files changed, 68 insertions(+), 2 deletions(-) diff --git a/TODO.md b/TODO.md index 5eaf390..b5c5ed6 100644 --- a/TODO.md +++ b/TODO.md @@ -5252,3 +5252,33 @@ branch (demoted case) and `getDayLabel`'s final fallback (winning-outright case) in `tests/calendar/ember-days.test.ts` cover both. `npm test` (2024 passed) and `tsc --noEmit` both pass. +**Second follow-on, same session**: user then flagged `/vu/2026-09-18/matins` (Ember Friday, +commemorated) showing the Judith reading under a bare "Reading" heading. Real root cause, and a +much broader bug than just Judith: `data/hours/bible-plan/*.yml`'s ~390-row TSV import uses +English-style book abbreviations (`jdt`, `1kgs`, `luke`, ...) for the ~30 books where that +diverges from this store's canonical Vulgate-numbering form (`jdth`, `3reg`, `luc`, ...) — +`scripture/index.ts`'s own `BOOK_ALIASES` already normalizes this for verse-*text* lookup, but +`bible-plan.ts`'s incipit (`bible-book-incipits.ts`) and responsory (`matins-responsories.ts`) +lookups both used the raw, unaliased abbreviation, silently missing on any canonically-keyed +book and falling back to the generic "Reading" heading. The 3 Gospel aliases (`luke`/`mark`/ +`john`) happened to self-heal through a second, independent citation-regex fallback that only +exists for Gospels (`matins.ts`'s `getGospelIncipitFromCitation`) — every other aliased book +(Judith, both Kings pairs, both Samuel books, Esdras, Osee, Jonas, Josue, Micheas, Abdias, +Judges, the 3 Johannine epistles, James, Jude, Philemon, Titus, Zacharias, Sophonias) had no such +fallback and was silently broken wherever it happened to be the first book of a reading. Fixed by +exporting `canonicalBook` from `scripture/index.ts` (the same substitution `getScriptureChapter` +already applies internally) and routing both `bible-plan.ts` lookups through it — one shared +normalization, not two independently-aliased lookups that can drift again. New test in +`tests/propers/bible-plan.test.ts`. `npm test` (2025 passed) and `tsc --noEmit` both pass. + +Separately, the user also asked about seeing 3 lessons in a row all headed "Pope St. Gregory the +Great, Homily 33 on the Gospels, on Luke 7:36-50" (Ember Friday's own 3-lesson split of one +homily, each with its own distinct text/responsory — see the 2026-08-22 Ember-day entry above). +Confirmed this is correct content, not a duplication bug, but also confirmed it's *not* actually +how the reference source itself labels it: `Tempora/093-5.txt` only carries the attribution once, +on `[Lectio1]` — `[Lectio2]`/`[Lectio3]` have no repeated header at all, continuing silently under +the same nocturn. This app's own authoring convention repeats the full `source` string identically +on every split lesson, and does so consistently across the *entire* nocturn-readings corpus, not +just Ember days — flagged for the user as a real presentational rough edge, not fixed yet pending +their call on scope (a corpus-wide UI/authoring change, not a narrow one-file fix). + diff --git a/src/propers/bible-plan.ts b/src/propers/bible-plan.ts index eaf5144..fc4e031 100644 --- a/src/propers/bible-plan.ts +++ b/src/propers/bible-plan.ts @@ -21,6 +21,7 @@ import type { LanguageCode, TranslationStatus } from '../psalter/types'; import { resolvePassages, type ScriptureCitation } from './octave-readings'; import { getResponsoryForBook } from './matins-responsories'; import { getBookIncipit } from './bible-book-incipits'; +import { canonicalBook } from '../scripture'; const GOSPEL_BOOKS = new Set(['matt', 'mark', 'luke', 'john']); @@ -115,8 +116,9 @@ function resolveReading(record: BiblePlanReadingRecord, bookIndex: number): Bibl }; const citationLabelText = formatCitationLabel(record.passages); const firstBook = record.passages[0]?.book; - const responsory = firstBook ? getResponsoryForBook(firstBook, bookIndex)?.text : undefined; - const incipit = firstBook ? getBookIncipit(firstBook) : undefined; + const canonicalFirstBook = firstBook ? canonicalBook(firstBook) : undefined; + const responsory = canonicalFirstBook ? getResponsoryForBook(canonicalFirstBook, bookIndex)?.text : undefined; + const incipit = canonicalFirstBook ? getBookIncipit(canonicalFirstBook) : undefined; return { text, status, diff --git a/src/scripture/index.ts b/src/scripture/index.ts index bd2e454..ae7395c 100644 --- a/src/scripture/index.ts +++ b/src/scripture/index.ts @@ -58,6 +58,19 @@ export function getScriptureChapter(book: string, chapter: number): ScriptureCha return chapters.get(`${BOOK_ALIASES[book] ?? book}-${chapter}`); } +/** `book` normalized to this store's own canonical abbreviation — the + * same substitution `getScriptureChapter` applies internally, exposed for + * callers (bible-plan.ts's incipit/responsory lookups) that key off a + * *different* store keyed by the canonical form only (bible-book- + * incipits.ts, matins-responsories.ts) and would otherwise silently miss + * on any of `BOOK_ALIASES`' ~30 entries — live case: "jdt" (Judith) + * resolves its verse text fine but fell through bible-book-incipits.ts's + * `jdth`-keyed lookup, rendering as a bare "Reading" heading instead of + * "A reading from the book of Judith" (2026-09-03). */ +export function canonicalBook(book: string): string { + return BOOK_ALIASES[book] ?? book; +} + /** * @param verseRange e.g. "2-19" — inclusive. Whole (authored) chapter when * omitted. Unauthored book/chapter combinations resolve to an empty array diff --git a/tests/propers/bible-plan.test.ts b/tests/propers/bible-plan.test.ts index 6bb61ec..eff917f 100644 --- a/tests/propers/bible-plan.test.ts +++ b/tests/propers/bible-plan.test.ts @@ -17,3 +17,24 @@ describe('getBiblePlanReadings citation labels', () => { expect(readings[2]?.citation.la).toBe('Luke 18:1-23'); }); }); + +// A reading's `label` (the "Léctio libri..." incipit) previously looked up +// bible-book-incipits.ts's canonical-abbreviation-only table using the raw +// book abbreviation from data/hours/bible-plan/*.yml -- which, for ~30 +// books, is one of scripture/index.ts's own English-style aliases (e.g. +// "jdt" for Judith), not the canonical form ("jdth") that table is keyed +// by. The alias resolved verse *text* fine (getScriptureChapter already +// consulted the alias table) but silently missed the label lookup, +// rendering as the generic "Reading" heading instead -- live case: Sept +// 18 2026's own Ember Friday commemoration, `post-pentecost-16-friday`'s +// first reading (Judith 11-13). Fixed 2026-09-03 by routing both the +// incipit and responsory lookups through scripture/index.ts's own +// `canonicalBook`, the same normalization the text lookup already used. +describe('getBiblePlanReadings label uses the same book-alias normalization as verse-text lookup', () => { + it('an aliased book ("jdt" for Judith) still gets its real incipit label, not the generic fallback', () => { + const readings = getBiblePlanReadings('post-pentecost-16', 'friday', '2026-09-18'); + expect(readings[0]?.citation.la).toBe('Jdt 11-13'); + expect(readings[0]?.label?.en).toBe('A reading from the book of Judith'); + expect(readings[0]?.label?.la).toBe('Léctio libri Judith'); + }); +});