Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f7ff8fc0ae | |||
| 3c8ccc3cbb | |||
| 290bbac1b5 | |||
| 88e8a5b404 | |||
| 03d39dc911 |
@@ -5192,3 +5192,125 @@ the old, incomplete assertion.
|
||||
|
||||
Full suite re-run after this follow-up: `npm test` (2021 passed, 47 files) and `npm run build`
|
||||
(`tsc --noEmit` + `vite build`) both pass.
|
||||
|
||||
### Ember days' own Benedictus/Magnificat antiphons authored (2026-09-03)
|
||||
|
||||
Follow-on from the 2026-08-22 Ember-day mechanism entry, whose "not authored, logged instead"
|
||||
note called the gap "the Ember-day-specific `[Ant 2]`/`[Ant 3]` Lauds antiphons" — imprecise, and
|
||||
worth correcting here. Live-verified (Monastic Tridentinum 1617) against a clean year for each
|
||||
Ember weekday (Wed 2023-09-20, Fri 2024-09-20, Sat 2025-09-20 for September; Dec 16/18/19 2026
|
||||
for Advent, all winning outright): the day's own Lauds *psalmody* (Ps 50/63/64 + canticle +
|
||||
Laudate, all 5 antiphon slots) stays entirely on the plain ferial weekday default, unchanged —
|
||||
only the **Benedictus antiphon** (and, Wed/Fri only, the **Magnificat antiphon**) is genuinely
|
||||
proper to the Ember day, each labeled "Antiphona ex Proprio de Tempore" live. `hours/resolve-
|
||||
common.ts`'s `getBenedictusAntiphon`/`getMagnificatAntiphon` already look up `${winner.id}-
|
||||
benedictus-antiphon`/`-magnificat-antiphon` unconditionally for any temporal winner (rank-
|
||||
agnostic by design, same as the chapter/hymn bundle) — so this needed no mechanism change at
|
||||
all, just the missing content files, which is why it silently rendered blank rather than
|
||||
tripping any test.
|
||||
|
||||
**Authored**: 6 Benedictus antiphon files (one per Ember weekday × quarter) and 4 Magnificat
|
||||
antiphon files (Wed + Fri only, both quarters). September Wednesday/Friday reuse the identical
|
||||
text for both (`[Ant 3]` is a bare `@:Ant 2` cross-reference in the source, not independently
|
||||
authored — a real fact about the source, not a shortcut taken here); Advent Wednesday's two
|
||||
differ (`Missus est Gábriel` / `Ecce ancílla Dómini`); September Saturday uses the Monastic
|
||||
track's own `TemporaM/093-6.txt` override (`Illumináre... Deus Israël`) per this project's
|
||||
Monastic-1617-first preference, matching the choice already made for this day's Matins
|
||||
responsories.
|
||||
|
||||
**Not authored, correctly**: Magnificat antiphons for either quarter's Ember Saturday — Second
|
||||
Vespers on both is always ceded outright to the following Sunday ("Vespera de sequenti; nihil de
|
||||
præcedenti," live-verified), and the source file itself has no `[Ant 3]` for either Saturday,
|
||||
confirming the omission is real, not a gap. Advent Ember Friday's own Magnificat antiphon *was*
|
||||
still authored despite being currently unreachable — Dec 18 always falls inside the O Antiphons'
|
||||
own unconditional Dec 17-23 window, which wins the Vespers Magnificat outright — per this
|
||||
project's standing "import available real content even if it may not survive the current
|
||||
calendar" default.
|
||||
|
||||
New assertions added to `tests/calendar/ember-days.test.ts` (Benedictus antiphon text/status on
|
||||
all 6 winning-outright dates; Magnificat status on Advent Ember Wednesday, the one case with no
|
||||
overriding mechanism to complicate the assertion). `npm test` (2023 passed, 47 files) and
|
||||
`tsc --noEmit` both pass.
|
||||
|
||||
**Follow-on, same day**: user asked why the Ember days didn't actually show anywhere in the app
|
||||
on 2026-09-16/18/19 despite the above. Real bug, found by resolving `getDayLabel` directly for
|
||||
those dates: `calendar/day-label.ts`'s `collectCommemorations` only ever matched a `temporal`-kind
|
||||
commemoration against `resolveTemporalId(day.date)` (the plain, un-relabeled id for that date) —
|
||||
never against an Ember-relabeled id, so `applyEmberDay`'s own commemoration was silently dropped
|
||||
from the label every time. Worse, even the *winning-outright* case had no name: the final
|
||||
fallback branch calls the generic `temporalLabel(day)` (pure season/weekday arithmetic, no id
|
||||
awareness at all), so an Ember day that won outright rendered as bare "Wednesday in the Nth week
|
||||
after Trinity (Feria)" with zero mention of Ember anywhere.
|
||||
|
||||
Fixed with a small standalone `EMBER_DAY_NAMES`/`emberDayLabel` lookup in `day-label.ts` itself
|
||||
(6 entries, Latin names read straight from each day's own `[Officium]` header) — deliberately
|
||||
*not* added to `temporal-feasts.ts`'s named-feast table, since that table doubles as `resolve-
|
||||
common.ts`'s `isFerialOrVigil` bareness check (`isBareFeria = ... && !getTemporalFeastRecord(...)`)
|
||||
and adding Ember ids there would have silently turned off their own ferial Preces (their `[Rule]`
|
||||
is literally "Preces Feriales"). Wired into both places: `collectCommemorations`'s temporal
|
||||
branch (demoted case) and `getDayLabel`'s final fallback (winning-outright case). New assertions
|
||||
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).
|
||||
|
||||
**Third follow-on, same session**: user then asked why a Sunday's own patristic homily was
|
||||
showing up on a Friday at all. Pulled the live reference engine's own Matins page for St. Joseph
|
||||
of Cupertino's day (Tridentine 1906) to check, rather than reasoning from vu's data alone: real
|
||||
Nocturn III there is exactly 3 lessons — 2 from the winning Duplex saint's own Common homily, plus
|
||||
a single "**Commemoratio Feriæ**" lesson for the displaced Ember day. No Sunday content
|
||||
whatsoever. `hours/matins.ts`'s `nocturnReadingIds` had a `temporalKept` block that pooled the
|
||||
governing Sunday's own id (`temporalId`) *and* its `month-week-N` sibling whenever any temporal
|
||||
commemoration survived on a 3-nocturn day, with no weekday check at all — so a Duplex+ saint
|
||||
winning outright on a plain weekday (not a Sunday) that also happened to carry a temporal
|
||||
commemoration pulled in a whole second homily's worth of unrelated content (here: St. Ambrose's
|
||||
homily + its own Gospel from `post-pentecost-16`, and St. Leo's September-fast sermon from
|
||||
`month-week-093` — two different reference-engine representations of the *same* underlying
|
||||
Sunday, both wrongly pooled at once). Fixed by gating `temporalKept` to `day.weekday === 'sunday'`
|
||||
— the governing Sunday's own content only belongs in the pool when that Sunday is the day actually
|
||||
being celebrated. Live-verified by reloading `/vu/2026-09-18/matins`: both spurious readings are
|
||||
gone. `npm test` (2025 passed) and `tsc --noEmit` both pass.
|
||||
|
||||
Still open, by the user's own explicit request to land this first: Ember Friday's own homily
|
||||
still surfaces as all 3 of its lessons even when merely commemorated, where the live engine caps
|
||||
a commemorated feria at exactly one lesson (its own Lectio 1 only) — same shape of fix as this
|
||||
one, not yet done.
|
||||
|
||||
### Ezekiel spelling fix in the book-incipit heading (2026-09-04)
|
||||
|
||||
User caught `bible-book-incipits.ts`'s `ezek` entry reading "A reading from the Prophecy of
|
||||
**Ezechiel**" — the Vulgate/Douay-Rheims spelling, inconsistent with this app's own generated
|
||||
heading text (which otherwise uses modern English book names, see that file's own header note on
|
||||
"Saint Paul" vs "blessed Paul"). Changed to "Ezekiel". Left every other in-corpus "Ezechiel"
|
||||
alone (scripture verse text, martyrology narrative, homily quotes under `src/data/scripture/`,
|
||||
`src/data/martyrology/`, `src/data/propers/`) — those are quoted Douay-Rheims translation prose,
|
||||
where the traditional spelling is correct as part of the source text, not this app's own
|
||||
generated label.
|
||||
|
||||
@@ -50,6 +50,38 @@ function joinBi(items: Bi[], sep = ' — '): Bi {
|
||||
return { en: items.map((i) => i.en).join(sep), la: items.map((i) => i.la).join(sep) };
|
||||
}
|
||||
|
||||
/**
|
||||
* The 6 Ember-day ids (calendar/ember-days.ts) are deliberately *not* in
|
||||
* temporal-feasts.ts's named-feast table — they're privileged ferias, not
|
||||
* rank-contesting movable feasts (see that file's own doc comment), and
|
||||
* that table's `getTemporalFeastRecord` also doubles as resolve-
|
||||
* common.ts's `isFerialOrVigil` bareness check, so adding them there
|
||||
* would silently turn off their own ferial Preces (their `[Rule]` block
|
||||
* is literally "Preces Feriales"). A small standalone lookup here instead
|
||||
* — Latin names read straight from each day's own `[Officium]` header
|
||||
* (Tempora/093-{3,5,6}.txt, Tempora/Adv3-{3,5,6}.txt).
|
||||
*
|
||||
* Without this, an Ember day never named itself anywhere in the UI: not
|
||||
* as the primary label when it wins outright (fell through to the plain
|
||||
* "Wednesday in the Nth week after Trinity"/"In the Nth week of Advent"
|
||||
* wording with no Ember mention at all), and not as a commemoration when
|
||||
* demoted (collectCommemorations's temporal branch only ever matched
|
||||
* `resolveTemporalId(day.date)`, never an Ember-relabeled id) — live-
|
||||
* verified as a real, user-visible gap, 2026-09-03.
|
||||
*/
|
||||
const EMBER_DAY_NAMES: Record<string, Bi> = {
|
||||
'ember-september-wednesday': bi('Ember Wednesday in September', 'Feria Quarta Quattuor Temporum Septembris'),
|
||||
'ember-september-friday': bi('Ember Friday in September', 'Feria Sexta Quattuor Temporum Septembris'),
|
||||
'ember-september-saturday': bi('Ember Saturday in September', 'Sabbato Quattuor Temporum Septembris'),
|
||||
'ember-advent-wednesday': bi('Ember Wednesday in Advent', 'Feria IV Quattuor Temporum in Adventu'),
|
||||
'ember-advent-friday': bi('Ember Friday in Advent', 'Feria VI Quattuor Temporum in Adventu'),
|
||||
'ember-advent-saturday': bi('Ember Saturday in Advent', 'Sabbato Quattuor Temporum in Adventu'),
|
||||
};
|
||||
|
||||
function emberDayLabel(id: string): Bi | undefined {
|
||||
return EMBER_DAY_NAMES[id];
|
||||
}
|
||||
|
||||
function capitalize(text: string): string {
|
||||
return text.charAt(0).toUpperCase() + text.slice(1);
|
||||
}
|
||||
@@ -458,6 +490,11 @@ function collectCommemorations(
|
||||
} else if (c.kind === 'temporal') {
|
||||
if (c.id === temporalId) {
|
||||
temporal.push(anchorDayName(day, false) ?? temporalLabel(day));
|
||||
} else {
|
||||
const emberName = emberDayLabel(c.id);
|
||||
if (emberName) {
|
||||
temporal.push(emberName);
|
||||
}
|
||||
}
|
||||
} else if (opts.showOctaves && c.id !== opts.excludeOctaveId) {
|
||||
const octave = activeOctaves.find((a) => a.id === c.id);
|
||||
@@ -663,7 +700,7 @@ export function getDayLabel(day: LiturgicalDay): Partial<Record<string, string>>
|
||||
}
|
||||
|
||||
const temporalRank = TEMPORAL_CATEGORY_RANK_LABELS[day.temporalCategory];
|
||||
const label = temporalLabel(day);
|
||||
const label = (day.winner.kind === 'temporal' ? emberDayLabel(day.winner.id) : undefined) ?? temporalLabel(day);
|
||||
const temporal = bi(`${label.en} (${temporalRank.en})`, `${label.la} (${temporalRank.la})`);
|
||||
const { sanctoral } = collectCommemorations(day, { showOctaves: false });
|
||||
return withTransferNote(day, joinBi([temporal, ...sanctoral]));
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
# Verified against Divinum Officium (Monastic Tridentinum 1617), Lauds
|
||||
# live query, 2026-12-18 -- Advent Ember Friday, labeled "Antiphona ex
|
||||
# Proprio de Tempore". Same text as Tempora/Adv3-5.txt's own [Ant 2].
|
||||
id: ember-advent-friday-benedictus-antiphon
|
||||
text:
|
||||
la: "Ex quo facta est * vox salutatiónis tuæ in áuribus meis, exsultávit in gáudio infans in útero meo, allelúja."
|
||||
en: "As soon as the voice of thy salutation * sounded in mine ears, the babe leaped in my womb for joy. Alleluia."
|
||||
status:
|
||||
la: verified
|
||||
en: verified
|
||||
@@ -0,0 +1,17 @@
|
||||
# Real content from Tempora/Adv3-5.txt's own [Ant 3], authored per this
|
||||
# project's "import available content even if it may not survive the
|
||||
# current calendar" default -- but currently unreachable in practice:
|
||||
# Advent Ember Friday always falls on Dec 18 (the Wednesday after the 3rd
|
||||
# Sunday of Advent is always Dec 17-23's own week), which the O Antiphons
|
||||
# mechanism (calendar/index.ts, unconditional date-keyed override, see
|
||||
# TODO.md 2026-08-30) always wins for the Vespers Magnificat antiphon
|
||||
# instead -- live-verified 2026-12-18 renders "O Adonái," not this text.
|
||||
# Kept for completeness/correctness of the id space, same as any other
|
||||
# real proper text this store authors ahead of its own reachability.
|
||||
id: ember-advent-friday-magnificat-antiphon
|
||||
text:
|
||||
la: "Hoc est testimónium, * quod perhíbuit Joánnes: Qui post me venit, ante me factus est."
|
||||
en: "John bare witness of Him, * saying He That cometh after me is preferred before me."
|
||||
status:
|
||||
la: verified
|
||||
en: verified
|
||||
@@ -0,0 +1,18 @@
|
||||
# Verified against Divinum Officium (Monastic Tridentinum 1617), Lauds
|
||||
# live query, 2026-12-19 -- Advent Ember Saturday, labeled "Antiphona ex
|
||||
# Proprio de Tempore". Same text as Tempora/Adv3-6.txt's own plain [Ant
|
||||
# 2] (not the "(rubrica cisterciensis)" alternate below it -- no
|
||||
# Cistercian-specific track modeled in this app).
|
||||
#
|
||||
# No Magnificat antiphon file for this id: Ember Saturday's own Second
|
||||
# Vespers is always ceded to the 4th Sunday of Advent (live-verified
|
||||
# 2026-12-19: "Dominica IV Adventus"), same "Vespera de sequenti" pattern
|
||||
# already confirmed for September's own Ember Saturday -- the source file
|
||||
# itself has no [Ant 3] for this day either.
|
||||
id: ember-advent-saturday-benedictus-antiphon
|
||||
text:
|
||||
la: "Quómodo fiet istud, * Angele Dei, quóniam virum non cognósco? Audi, María Virgo: Spíritus Sanctus supervéniet in te, et virtus Altíssimi obumbrábit tibi."
|
||||
en: "O thou Angel of God, * how shall this be, seeing I know not a man? Hear, O Virgin Mary! the Holy Ghost shall come upon thee, and the power of the Highest shall overshadow thee!"
|
||||
status:
|
||||
la: verified
|
||||
en: verified
|
||||
@@ -0,0 +1,12 @@
|
||||
# Verified against Divinum Officium (Monastic Tridentinum 1617), Lauds
|
||||
# live query, 2026-12-16 -- Advent Ember Wednesday, labeled "Antiphona ex
|
||||
# Proprio de Tempore". Same text as Tempora/Adv3-3.txt's own [Ant 2].
|
||||
# Lauds psalmody itself stays on the plain ferial Wednesday antiphons
|
||||
# throughout (confirmed live), same as September's own Ember Wednesday.
|
||||
id: ember-advent-wednesday-benedictus-antiphon
|
||||
text:
|
||||
la: "Missus est Gábriel * Angelus ad Maríam Vírginem desponsátam Joseph."
|
||||
en: "The angel Gabriel * was sent to Mary, a virgin espoused to Joseph."
|
||||
status:
|
||||
la: verified
|
||||
en: verified
|
||||
@@ -0,0 +1,14 @@
|
||||
# Verified against Divinum Officium (Monastic Tridentinum 1617), Vespers
|
||||
# live query, 2026-12-16 -- Advent Ember Wednesday's own Second Vespers,
|
||||
# labeled "Antiphona ex Proprio de Tempore" (Dec 16 falls just before the
|
||||
# O Antiphons' own Dec 17-23 window, so this really is what's said, unlike
|
||||
# Friday's own sibling file below). Distinct text from the Benedictus
|
||||
# antiphon this time (Tempora/Adv3-3.txt's own separate [Ant 3]), not a
|
||||
# reused cross-reference like September's Wednesday/Friday.
|
||||
id: ember-advent-wednesday-magnificat-antiphon
|
||||
text:
|
||||
la: "Ecce ancílla Dómini: * fiat mihi secúndum verbum tuum."
|
||||
en: "Behold the handmaid * of the Lord; be it unto me according to thy word."
|
||||
status:
|
||||
la: verified
|
||||
en: verified
|
||||
@@ -0,0 +1,15 @@
|
||||
# Verified against Divinum Officium (Monastic Tridentinum 1617), Lauds
|
||||
# live query, 2024-09-20 -- the clean year in range where September Ember
|
||||
# Friday itself wins outright, labeled "Antiphona ex Proprio de Tempore".
|
||||
# Same text as Tempora/093-5.txt's own [Ant 2] (the plain secular
|
||||
# recension, not the "(rubrica cisterciensis)" alternate below it -- no
|
||||
# Cistercian-specific track modeled in this app). Lauds psalmody itself
|
||||
# stays on the plain ferial Friday antiphons throughout, same as
|
||||
# Wednesday's own sibling file.
|
||||
id: ember-september-friday-benedictus-antiphon
|
||||
text:
|
||||
la: "Múlier, * quæ erat in civitáte peccátrix, stans retro secus pedes Dómini, lácrimis cœpit rigáre pedes ejus et capíllis cápitis sui tergébat, et deosculabátur pedes ejus et unguénto ungébat."
|
||||
en: "A woman in the city which was a sinner stood at the Lord's Feet behind Him, * and began to wash His Feet with tears, and did wipe them with the hairs of her head, and kissed His Feet, and anointed them with the ointment."
|
||||
status:
|
||||
la: verified
|
||||
en: verified
|
||||
@@ -0,0 +1,10 @@
|
||||
# Tempora/093-5.txt's [Ant 3] "(rubrica 196)" is a bare "@:Ant 2"
|
||||
# cross-reference, same identical text reused for Vespers as the sibling
|
||||
# -benedictus-antiphon.yml -- see that file's own comment.
|
||||
id: ember-september-friday-magnificat-antiphon
|
||||
text:
|
||||
la: "Múlier, * quæ erat in civitáte peccátrix, stans retro secus pedes Dómini, lácrimis cœpit rigáre pedes ejus et capíllis cápitis sui tergébat, et deosculabátur pedes ejus et unguénto ungébat."
|
||||
en: "A woman in the city which was a sinner stood at the Lord's Feet behind Him, * and began to wash His Feet with tears, and did wipe them with the hairs of her head, and kissed His Feet, and anointed them with the ointment."
|
||||
status:
|
||||
la: verified
|
||||
en: verified
|
||||
@@ -0,0 +1,24 @@
|
||||
# Verified against Divinum Officium (Monastic Tridentinum 1617), Lauds
|
||||
# live query, 2025-09-20 -- the clean year in range where September Ember
|
||||
# Saturday itself wins outright, labeled "Antiphona ex Proprio de
|
||||
# Tempore". Uses the Monastic track's own override text
|
||||
# (Latin/TemporaM/093-6.txt's [Ant 2], "Illumináre... Deus Israël") in
|
||||
# place of the secular Tempora/093-6.txt's plain "Illúmina... in viam
|
||||
# pacis" (no "Deus Israël"), per this project's Monastic-1617-first
|
||||
# structure preference -- same choice already made for this day's Matins
|
||||
# responsories. The English/Tempora/093-6.txt translation already carries
|
||||
# "O Thou the God of Israel," confirming it was translated from the
|
||||
# Monastic Latin, not the secular.
|
||||
#
|
||||
# No Magnificat antiphon file for this id: Ember Saturday's own Second
|
||||
# Vespers is always ceded outright to the following Sunday ("Vespera de
|
||||
# sequenti; nihil de præcedenti," live-verified 2025-09-20) -- the source
|
||||
# file itself has no [Ant 3] for this day, correctly reflecting that the
|
||||
# text would never be said.
|
||||
id: ember-september-saturday-benedictus-antiphon
|
||||
text:
|
||||
la: "Illumináre, Dómine, * his, qui in ténebris sedent: et dírige pedes nostros in viam pacis, Deus Israël."
|
||||
en: "Give light, O Lord, * to them that sit in darkness, and guide our feet into the way of peace, O Thou the God of Israel."
|
||||
status:
|
||||
la: verified
|
||||
en: verified
|
||||
@@ -0,0 +1,17 @@
|
||||
# Verified against Divinum Officium (Monastic Tridentinum 1617), Lauds
|
||||
# live query, 2023-09-20 -- the one clean year in range where September
|
||||
# Ember Wednesday itself wins outright (no competing saint), labeled
|
||||
# "Antiphona ex Proprio de Tempore". Same text as Tempora/093-3.txt's own
|
||||
# [Ant 2] ("Hoc genus...", echoing Lectio3's Gospel line): the app's
|
||||
# Lauds psalmody itself stays entirely on the plain ferial Wednesday
|
||||
# antiphons (Ps 50/63/64 + canticle + Laudate all confirmed unchanged
|
||||
# live) -- only the Benedictus antiphon is genuinely proper to the Ember
|
||||
# day, not the whole psalm-antiphon block TODO.md's 2026-08-22 entry
|
||||
# loosely called "Lauds antiphons."
|
||||
id: ember-september-wednesday-benedictus-antiphon
|
||||
text:
|
||||
la: "Hoc genus * dæmoniórum in nullo potest exíre, nisi in oratióne et jejúnio."
|
||||
en: "This kind of devils * can come forth by nothing but by prayer and fasting."
|
||||
status:
|
||||
la: verified
|
||||
en: verified
|
||||
@@ -0,0 +1,12 @@
|
||||
# Same source as the sibling -benedictus-antiphon.yml: Tempora/093-3.txt's
|
||||
# [Ant 3] is a bare "@:Ant 2" cross-reference, i.e. the identical text
|
||||
# reused for both Lauds and Vespers on this day -- not independently
|
||||
# authored content, but the reference engine's own real choice, not a
|
||||
# simplification on this app's part.
|
||||
id: ember-september-wednesday-magnificat-antiphon
|
||||
text:
|
||||
la: "Hoc genus * dæmoniórum in nullo potest exíre, nisi in oratióne et jejúnio."
|
||||
en: "This kind of devils * can come forth by nothing but by prayer and fasting."
|
||||
status:
|
||||
la: verified
|
||||
en: verified
|
||||
+30
-13
@@ -654,20 +654,37 @@ function nocturnReadingIds(day: LiturgicalDay, temporalId: string, date: string,
|
||||
}
|
||||
// The plain temporalId/month-week content is the *governing Sunday's own*
|
||||
// Nocturn 2/3 patristic material, real content for that Sunday itself —
|
||||
// only pooled on a real 3-nocturn day, and (as before) only when the
|
||||
// day's own occurrence decision (calendar/commemorations.ts's
|
||||
// decideOccurrence) actually retained the temporal identity in some form:
|
||||
// the temporal cycle won outright (day.winner.kind === 'temporal' -- a
|
||||
// plain Sunday, or a named temporal override like Christ the King), or it
|
||||
// survives as a commemoration alongside a sanctoral winner. Excluded:
|
||||
// decideOccurrence's `ordinary-feria` branch, where a real feast --
|
||||
// however low-ranked -- wins with zero commemorations, correctly
|
||||
// suppressing the temporal identity entirely (e.g. St. Bartholomew,
|
||||
// duplex-2-classis, 2026-08-24 -- his own proper reading has no Nocturn
|
||||
// 3 content, and without this gate the leftover 13th-Sunday-after-
|
||||
// Pentecost/month-week content wrongly filled Nocturn 3 instead).
|
||||
// only pooled on a real 3-nocturn day, only on the Sunday itself (see
|
||||
// below), and (as before) only when the day's own occurrence decision
|
||||
// (calendar/commemorations.ts's decideOccurrence) actually retained the
|
||||
// temporal identity in some form: the temporal cycle won outright
|
||||
// (day.winner.kind === 'temporal' -- a plain Sunday, or a named temporal
|
||||
// override like Christ the King), or it survives as a commemoration
|
||||
// alongside a sanctoral winner. Excluded: decideOccurrence's
|
||||
// `ordinary-feria` branch, where a real feast -- however low-ranked --
|
||||
// wins with zero commemorations, correctly suppressing the temporal
|
||||
// identity entirely (e.g. St. Bartholomew, duplex-2-classis, 2026-08-24
|
||||
// -- his own proper reading has no Nocturn 3 content, and without this
|
||||
// gate the leftover 13th-Sunday-after-Pentecost/month-week content
|
||||
// wrongly filled Nocturn 3 instead).
|
||||
//
|
||||
// `day.weekday === 'sunday'` gate added 2026-09-03: live-verified
|
||||
// (Tridentine 1906) that a Duplex+ saint winning outright on a *weekday*
|
||||
// that also happens to carry a temporal commemoration (e.g. St. Joseph
|
||||
// of Cupertino, Duplex, Friday Sept 18) gets no Sunday content at all --
|
||||
// the reference engine's own Nocturn III there is just the saint's own
|
||||
// Common homily (2 lessons) plus a single "Commemoratio Feriæ" lesson
|
||||
// for the displaced feria, never the week's separate governing-Sunday
|
||||
// homily (Ambrose's/Leo's, in that live case). This mechanism previously
|
||||
// fired on *any* 3-nocturn day with a surviving temporal commemoration,
|
||||
// regardless of weekday, wrongly pooling in a whole extra Sunday's worth
|
||||
// of unrelated patristic content on top of the day's own two real
|
||||
// sources. The governing Sunday's own content only belongs in the pool
|
||||
// when that Sunday *is* the day being celebrated.
|
||||
const temporalKept =
|
||||
threeNocturns && (day.winner.kind === 'temporal' || day.commemorations.some((c) => c.kind === 'temporal'));
|
||||
threeNocturns &&
|
||||
day.weekday === 'sunday' &&
|
||||
(day.winner.kind === 'temporal' || day.commemorations.some((c) => c.kind === 'temporal'));
|
||||
if (temporalKept) {
|
||||
ids.add(temporalId);
|
||||
const monthWeek = monthWeekId(date);
|
||||
|
||||
@@ -58,7 +58,7 @@ export const BOOK_INCIPITS: Record<string, { la: string; en: string }> = {
|
||||
jer: { la: 'Léctio Jeremíæ Prophétæ', en: 'A reading from the Prophecy of Jeremias' },
|
||||
lam: { la: 'Léctio Threnórum Jeremíæ Prophétæ', en: 'A reading from the Lamentations of Jeremias the Prophet' },
|
||||
bar: { la: 'Léctio Baruch Prophétæ', en: 'A reading from the Prophecy of Baruch' },
|
||||
ezek: { la: 'Léctio Ezechiélis Prophétæ', en: 'A reading from the Prophecy of Ezechiel' },
|
||||
ezek: { la: 'Léctio Ezechiélis Prophétæ', en: 'A reading from the Prophecy of Ezekiel' },
|
||||
dan: { la: 'Léctio Daniélis Prophétæ', en: 'A reading from the Prophecy of Daniel' },
|
||||
// Minor prophets
|
||||
osee: { la: 'Léctio Osee Prophétæ', en: 'A reading from the Prophecy of Osee' },
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { resolveDay } from '../../src/calendar';
|
||||
import { getDayLabel } from '../../src/calendar/day-label';
|
||||
import { resolveOrdo } from '../../src/hours';
|
||||
import { getDayCollect } from '../../src/hours/resolve-common';
|
||||
|
||||
@@ -53,6 +54,57 @@ describe('Ember days', () => {
|
||||
}
|
||||
});
|
||||
|
||||
it("Lauds' Benedictus antiphon is the Ember day's own proper text on all 6 winning-outright days (2026-08-22 added the mechanism/collect/Matins, but not this -- fixed here)", () => {
|
||||
// Wed/Fri/Sat winning outright for the September quarter, one per
|
||||
// weekday (2026's own dates are all saint-outranked, see above) --
|
||||
// found by scanning forward from 2023, all live-verified against the
|
||||
// reference engine.
|
||||
const cases: [string, string][] = [
|
||||
['2023-09-20', 'Hoc genus'],
|
||||
['2024-09-20', 'Múlier'],
|
||||
['2025-09-20', 'Illumináre, Dómine'],
|
||||
['2026-12-16', 'Missus est Gábriel'],
|
||||
['2026-12-18', 'Ex quo facta est'],
|
||||
['2026-12-19', 'Quómodo fiet istud'],
|
||||
];
|
||||
for (const [date, expectedStart] of cases) {
|
||||
const ordo = resolveOrdo('lauds', date);
|
||||
const idx = ordo.parts.findIndex((p) => p.kind === 'canticle' && p.canticleId === 'benedictus');
|
||||
const antiphon = ordo.parts[idx + 1] as { text: { text: { la?: string }; status: { la?: string; en?: string } } };
|
||||
expect(antiphon.text.status.la).toBe('verified');
|
||||
expect(antiphon.text.status.en).toBe('verified');
|
||||
expect(antiphon.text.text.la).toContain(expectedStart);
|
||||
}
|
||||
});
|
||||
|
||||
it("Vespers' Magnificat antiphon is proper on Ember Wednesday (Sept and Advent) where the reference engine actually renders it, not overridden by a Sunday/O-Antiphon", () => {
|
||||
// September Ember Friday/Saturday and Advent Ember Friday/Saturday
|
||||
// are excluded here: Saturday's Second Vespers is always ceded to the
|
||||
// following Sunday (live-verified), and Advent Ember Friday/Saturday
|
||||
// (always Dec 18/19) are always inside the O Antiphons' own
|
||||
// unconditional Dec 17-23 window -- both real, not gaps.
|
||||
const ordo = resolveOrdo('vespers', '2026-12-16');
|
||||
const idx = ordo.parts.findIndex((p) => p.kind === 'canticle' && p.canticleId === 'magnificat');
|
||||
const antiphon = ordo.parts[idx + 1] as { text: { status: { la?: string; en?: string } } };
|
||||
expect(antiphon.text.status.la).toBe('verified');
|
||||
expect(antiphon.text.status.en).toBe('verified');
|
||||
});
|
||||
|
||||
it("the day label names the Ember day itself, both when it's demoted to a commemoration and when it wins outright -- previously never shown anywhere in the UI (2026-09-03 fix)", () => {
|
||||
// Demoted cases (2026): a saint wins outright, Ember rides along as a
|
||||
// commemoration -- collectCommemorations' temporal branch previously
|
||||
// only matched resolveTemporalId(day.date), never an Ember-relabeled id.
|
||||
expect(getDayLabel(resolveDay('2026-09-16')).en).toContain('Ember Wednesday in September');
|
||||
expect(getDayLabel(resolveDay('2026-09-18')).en).toContain('Ember Friday in September');
|
||||
expect(getDayLabel(resolveDay('2026-09-19')).en).toContain('Ember Saturday in September');
|
||||
// Winning-outright cases: previously fell through to the plain
|
||||
// ordinal weekday label with no Ember mention at all.
|
||||
expect(getDayLabel(resolveDay('2023-09-20')).en).toBe('Ember Wednesday in September (Feria) — Vigil of St. Matthew');
|
||||
expect(getDayLabel(resolveDay('2026-12-16')).en).toBe('Ember Wednesday in Advent (Feria)');
|
||||
expect(getDayLabel(resolveDay('2026-12-18')).en).toBe('Ember Friday in Advent (Feria)');
|
||||
expect(getDayLabel(resolveDay('2026-12-19')).en).toContain('Ember Saturday in Advent (Feria)');
|
||||
});
|
||||
|
||||
it('Matins renders 3 real, distinct, verified lessons+responsories for each of the 6 Ember days', () => {
|
||||
const dates = ['2026-09-16', '2026-09-18', '2026-09-19', '2026-12-16', '2026-12-18', '2026-12-19'];
|
||||
for (const date of dates) {
|
||||
|
||||
@@ -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');
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user