Compare commits

...

3 Commits

Author SHA1 Message Date
will ee3e155bfa Close out the octave-content TODO item
Deploy / deploy (push) Successful in 1m7s
Christmas Octave's own commemoration blocks are now modeled -- see the
prior two commits. Updates the top-of-file priority summary and the
dated build-log entry to reflect this.
2026-08-17 21:32:11 -04:00
will 6f1e152d7f Render octave commemorations in getDayCollects on stacking days
getDayCollects previously dropped kind: 'octave' commemorations
entirely, so on days where the temporal office keeps its own standing
(the Christmas Octave's own Nativity/Stephen/John/Holy-Innocents stack,
e.g. Dec 30) those octaves contributed nothing but a name in the day
label. Add an octave branch, gated on temporalCategory !== 'ordinary-
feria' -- the same condition resolveOfficeWinner uses -- so an ordinary
octave day's already-substituted primary content never gets a redundant
repeat.
2026-08-17 21:32:05 -04:00
will f60a142bb4 Add Christmas Octave's own commemoration collect content
Ant+V/R+collect "Commemoratio Octavæ" blocks for Christmas, St. Stephen,
St. John, and Holy Innocents, sourced from the reference engine's Sancti/
12-25..28.txt Octava 3 sections -- content for the mechanism change in
the next commit.
2026-08-17 21:31:55 -04:00
7 changed files with 243 additions and 17 deletions
+35 -13
View File
@@ -42,9 +42,9 @@ across every hour and content type in this app.
- The bulk Vulgate (Latin) + Douay-Rheims (English) scripture import into
`src/data/scripture/` — long-term project, explicitly deferred; Matins scripture
citations resolve to `missing` text until it lands.
- The four "…benedícta…" octave-day Ant+V/R+collect blocks (Christmas Octave's own
Nativity/Stephen/John/Holy-Innocents commemorations, e.g. on Dec 30) — not yet modeled;
see "Octave content" below for the real mechanism gap this needs. St. Frances of Rome's/
- The four "Commemoratio Octavæ" Ant+V/R+collect blocks (Christmas Octave's own
Nativity/Stephen/John/Holy-Innocents commemorations, e.g. on Dec 30) are now done —
see "Octave content" below. St. Frances of Rome's/
St. Bibiana's Benedictus antiphons and Ss. Ursula and Companions' were also once flagged
here — the first two turned out already resolved (verified live, see memory
`vu-todo-priority`'s own correction note), the third is a settled non-gap (no unique text
@@ -956,7 +956,7 @@ some by-date saint win there, e.g. St. Mark on Easter V some years, "Our
Lady of the Snows" — not modeled in this app at all — landing on
Post-Pentecost XI whenever the date lines up with Aug 5).
### Octave content — mostly fixed (2026-08), one real gap left
### Octave content — done (2026-08)
Was: `getDayCollects` skipped a `kind: 'octave'` commemoration entirely,
and every other office part (psalmody, hymn, chapter, Benedictus
@@ -978,15 +978,37 @@ bare collect (or a labeled "missing" placeholder) rather than the old
unlabeled one that just read "PRAYER … (translation pending)" with no
indication whose it was.
Real remaining gap: `resolveOfficeWinner` deliberately only kicks in when
`temporalCategory === 'ordinary-feria'` (the octave day has no standing
of its own) — the live-verified counterexample is the Christmas Octave's
own stack (Christmas + Stephen + John + Holy Innocents, e.g. on Dec 30,
`privileged-feria-minor`): the *temporal* day itself keeps the office
there ("Dominica Infra Octavam Nativitatis", `{ex Proprio de Tempore}`),
and each of the four octaves gets its own separate "Commemoratio Octavæ
…" Ant+V/R+collect block instead — not yet modeled; those four octave
commemorations still contribute nothing but a name in the day label.
Remaining gap, now closed (2026-08): `resolveOfficeWinner` deliberately
only kicks in when `temporalCategory === 'ordinary-feria'` (the octave
day has no standing of its own) — the live-verified counterexample was
the Christmas Octave's own stack (Christmas + Stephen + John + Holy
Innocents, e.g. on Dec 30, `privileged-feria-minor`): the *temporal* day
itself keeps the office there ("Dominica Infra Octavam Nativitatis", `{ex
Proprio de Tempore}`), and each of the four octaves gets its own separate
"Commemoratio Octavæ …" Ant+V/R+collect block instead. Fixed by adding a
`kind === 'octave'` branch to `getDayCollects` (gated on
`day.temporalCategory !== 'ordinary-feria'`, the same condition
`resolveOfficeWinner` uses, so an ordinary octave day's already-rendered
primary content never gets a redundant repeat), plus one new
`${id}-octave-commemoration.yml` proper file per octave for the four
Christmas-stack ids (`christmas-day`, `st-stephen-protomartyr`,
`st-john-apostle`, `holy-innocents`), sourced from each Sancti file's own
`[Octava 3]` section in `../divinum-officium-reference` (Second Vespers'
own variant — the reference engine has three slightly different
Octava 1/2/3 texts per octave, roughly Vespers I/Lauds/Vespers II, but
this app's `getDayCollects` renders one shared block regardless of hour,
matching the pre-existing "one Orémus per commemoration, not chained"
simplification already noted above). The Holy Innocents' own English
Octava 3 block turned out to carry a mismatched antiphon in the reference
engine itself (the Common-of-Many-Martyrs "Isti sunt" text, not a
translation of the Latin antiphon printed beside it) — worked around by
sourcing the correct English from that same file's own `[Ant 3]`/
`[Versum 3]` sections instead (same Latin text, correctly paired
translation) — see that file's own comment. Live-verified end-to-end via
`tests/hours/vespers.test.ts`'s `2033-12-30` stacking case (all four
blocks resolve `verified` in both languages, not `missing`). This closes
the octave-content gap listed at the top of this file's "Open work"
section — no octave content work remains open.
## Benedictus antiphons for every sanctoral/temporal entry (in progress)
@@ -0,0 +1,40 @@
# Verified against the reference engine (Monastic Tridentinum 1617,
# ../divinum-officium-reference) -- read directly from Sancti/12-25.txt's
# own [Octava 3] section (Latin/Sancti/12-25.txt:458-468, English/Sancti/
# 12-25.txt:339-348), the non-Cistercian rubric. This is the "Commemoratio
# Octavæ Nativitatis" block said on the days within the Christmas Octave
# where Christmas itself does *not* win the day outright (the Comites
# Christi stacking days, e.g. Dec 26-31) -- see hours/resolve-common.ts's
# getDayCollects (kind: 'octave' branch) and TODO.md's "four benedícta
# octave-day blocks" note. The collect restates Christmas's own [Oratio]
# from the same file (`@:Octava 3` -> `[Oratio]`) with the "Per eúmdem"
# doxology form, matching e.g. easter-sunday-collect.yml's own "Through
# the same..." phrasing for the same macro. The reference engine has
# three Octava variants (1/2/3, roughly First Vespers/Lauds/Second
# Vespers) with different antiphon/versicle text each; this app's
# getDayCollects renders one shared block regardless of hour (matching
# the existing simplification already flagged on getDayCollects' own doc
# comment), so Octava 3 -- Second Vespers' own text -- was picked as the
# single canonical version, for consistency with the same choice made for
# the other three Comites Christi below.
id: christmas-day-octave-commemoration
text:
la: |
Ant. Hódie Christus natus est: hódie Salvátor appáruit: hódie in terra canunt Angeli, lætántur Archángeli: hódie exsúltant justi, dicéntes: Glória in excélsis Deo, allelúja.
V. Notum fecit Dóminus, allelúja.
R. Salutáre suum, allelúja.
Orémus.
Concéde, quǽsumus, omnípotens Deus: ut nos Unigéniti tui nova per carnem Natívitas líberet; quos sub peccáti jugo vetústa sérvitus tenet.
Per eúmdem Dóminum nostrum Jesum Christum Fílium tuum, qui tecum vivit et regnat in unitáte Spíritus Sancti, Deus, per ómnia sǽcula sæculórum.
℟. Amen.
en: |
Ant. This day the Christ is born: this day the Saviour is appeared; this day the Angels sing praise in the earth and the Archangels rejoice; this day the righteous are glad and say: Glory to God in the highest. Alleluia.
V. The Lord hath made known, alleluia.
R. His salvation, alleluia.
Let us pray.
Grant, we beseech thee, almighty God, that the new birth of thy Only-begotten Son as man may set us free, who are held by the old bondage under the yoke of sin.
Through the same Jesus Christ, thy Son, Our Lord, Who liveth and reigneth with thee in the unity of the Holy Ghost, God, world without end.
℟. Amen.
status:
la: verified
en: verified
@@ -0,0 +1,43 @@
# Verified against the reference engine (Monastic Tridentinum 1617,
# ../divinum-officium-reference) -- read directly from Sancti/12-28.txt's
# own [Octava 3] section (Latin/Sancti/12-28.txt:277-286), the non-
# Cistercian rubric -- see christmas-day-octave-commemoration.yml's own
# comment for why Octava 3 (Second Vespers' own text) was picked as the
# single shared version this app renders regardless of hour. Collect
# body + doxology reused verbatim from the already-authored
# holy-innocents-collect.yml (same text, `$Per Dominum` macro).
#
# The English Octava 3 block (English/Sancti/12-28.txt:201-210) turned
# out to carry the wrong antiphon -- "These are they who were not
# defiled with women..." (the Common-of-Many-Martyrs "Isti sunt" text,
# not a translation of the Latin antiphon actually printed alongside it)
# -- a reference-engine data mismatch, not a real variant (same shape as
# the St. Apollinaris/St. Leo II wrong-date bugs TODO.md already
# documents). Used this file's own real English elsewhere instead: [Ant
# 3] (English/Sancti/12-28.txt:198-199) is the correct, matching
# translation of the same Latin antiphon printed at Latin/Sancti/
# 12-28.txt:271-272 (identical Latin to the Octava 3 antiphon). Versicle/
# response likewise taken from this file's own [Versum 3]
# (English/Sancti/12-28.txt:194-196), the correctly paired English for
# the same "Sub throno Dei" V/R the Octava 3 block uses.
id: holy-innocents-octave-commemoration
text:
la: |
Ant. Innocéntes pro Christo infántes occísi sunt, ab iníquo rege lacténtes interfécti sunt: ipsum sequúntur Agnum sine mácula, et dicunt semper: Glória tibi, Dómine.
V. Sub throno Dei omnes Sancti clamant.
R. Víndica sánguinem nostrum, Deus noster.
Orémus.
Deus, cujus hodiérna die præcónium Innocéntes Mártyres non loquéndo, sed moriéndo conféssi sunt: ómnia in nobis vitiórum mala mortífica; ut fidem tuam, quam lingua nostra lóquitur, étiam móribus vita fateátur.
Per Dóminum nostrum Jesum Christum, Fílium tuum: qui tecum vivit et regnat in unitáte Spíritus Sancti, Deus, per ómnia sǽcula sæculórum.
℟. Amen.
en: |
Ant. Innocent babies were slain for Christ, sucklings were killed by a wicked king. Now they follow the Lamb without spot and cry without ceasing, Glory be to thee, O Lord.
V. From beneath the throne of God all the saints cry out.
R. Avenge thou our blood, O our God.
Let us pray.
O God, whose praise the martyred Innocents on this day confessed, not by speaking, but by dying, destroy all the evils of sin in us, that our life also may proclaim in deeds, thy faith which our tongues profess.
Through Jesus Christ, thy Son our Lord, Who liveth and reigneth with thee, in the unity of the Holy Ghost, God, world without end.
℟. Amen.
status:
la: verified
en: verified
@@ -0,0 +1,31 @@
# Verified against the reference engine (Monastic Tridentinum 1617,
# ../divinum-officium-reference) -- read directly from Sancti/12-27.txt's
# own [Octava 3] section (Latin/Sancti/12-27.txt:221-230, English/Sancti/
# 12-27.txt:162-171), the non-Cistercian rubric -- see christmas-day-
# octave-commemoration.yml's own comment for why Octava 3 (Second
# Vespers' own text) was picked as the single shared version this app
# renders regardless of hour. Collect body + doxology reused verbatim
# from the already-authored st-john-apostle-collect.yml (same text,
# `$Per Dominum` macro), rather than the reference engine's own slightly
# differently-worded English paraphrase of the same collect.
id: st-john-apostle-octave-commemoration
text:
la: |
Ant. Exiit sermo inter fratres, quod discípulus ille non móritur; et non dixit Jesus, Non móritur: sed, Sic eum volo manére, donec véniam.
V. Valde honorándus est beátus Joánnes.
R. Qui supra pectus Dómini in cena recúbuit.
Orémus.
Ecclésiam tuam, Dómine, benígnus illústra: ut beáti Joánnis Apóstoli tui et Evangelístæ, illumináta doctrínis, ad dona pervéniat sempitérna.
Per Dóminum nostrum Jesum Christum, Fílium tuum: qui tecum vivit et regnat in unitáte Spíritus Sancti, Deus, per ómnia sǽcula sæculórum.
℟. Amen.
en: |
Ant. A saying went abroad among the brethren, that that disciple should not die; and Jesus did not say that he should not die, but so will I have him remain till I come.
V. Greatly to be honoured is blessed John.
R. Who leaned at supper on the bosom of the Lord.
Let us pray.
Shine upon thy Church, O Lord, in thy goodness, that, enlightened by the teachings of Blessed John, thy Apostle and Evangelist, she may attain to everlasting gifts.
Through Jesus Christ, thy Son our Lord, Who liveth and reigneth with thee, in the unity of the Holy Ghost, God, world without end.
℟. Amen.
status:
la: verified
en: verified
@@ -0,0 +1,32 @@
# Verified against the reference engine (Monastic Tridentinum 1617,
# ../divinum-officium-reference) -- read directly from Sancti/12-26.txt's
# own [Octava 3] section (Latin/Sancti/12-26.txt:249-258, English/Sancti/
# 12-26.txt:164-173), the non-Cistercian rubric -- see christmas-day-
# octave-commemoration.yml's own comment for why Octava 3 (Second
# Vespers' own text) was picked as the single shared version this app
# renders regardless of hour. Collect body + doxology reused verbatim
# from the already-authored st-stephen-protomartyr-collect.yml (same
# text, `$Qui tecum` macro), rather than re-translating -- the reference
# engine's own English for this collect is a close paraphrase of that
# file's already-verified wording.
id: st-stephen-protomartyr-octave-commemoration
text:
la: |
Ant. Sepeliérunt Stéphanum viri timoráti, et fecérunt planctum magnum super eum.
V. Stéphanus vidit cælos apértos.
R. Vidit, et introívit: beátus homo, cui cæli patébant.
Orémus.
Da nobis, quǽsumus, Dómine, imitári quod cólimus: ut discámus et inimícos dilígere; quia ejus natalícia celebrámus, qui novit étiam pro persecutóribus exoráre Dóminum nostrum Jesum Christum Fílium tuum:
Qui tecum vivit et regnat in unitáte Spíritus Sancti, Deus, per ómnia sǽcula sæculórum.
℟. Amen.
en: |
Ant. Devout men buried Stephen, and made great mourning over him.
V. Stephen saw the heavens opened:
R. He saw and entered: blessed man, to whom the heavens were opened.
Let us pray.
Grant us, we beseech thee, O Lord, so to imitate what we revere, that we may learn to love even our enemies; for we celebrate the heavenly birthday of him who knew how to pray for his very persecutors to our Lord Jesus Christ:
Who with thee liveth and reigneth, in the unity of the Holy Spirit, God, world without end.
℟. Amen.
status:
la: verified
en: verified
+34 -4
View File
@@ -462,6 +462,22 @@ function sanctoralCommemorationPart(commemoration: Extract<Commemoration, { kind
return { kind: 'preces', text: { text: {}, status: { la: 'missing', en: 'missing' } }, label };
}
/** An octave commemoration's own rendering the fuller "Commemoratio
* Octavæ ..." Ant+V/R+collect bundle (`${id}-octave-commemoration`, e.g.
* christmas-day-octave-commemoration.yml), same shape and fallback
* pattern as sanctoralCommemorationPart. No per-saint `propers`
* indirection needed: an octave commemoration's id already is the base
* feast/saint id (`christmas-day`, `st-stephen-protomartyr`, ...),
* matching the proper file's own id 1:1. */
function octaveCommemorationPart(commemoration: Extract<Commemoration, { kind: 'octave' }>): ResolvedPart {
const label = `Commemoration of the Octave of ${commemoration.name}`;
const combined = resolveCommon(`${commemoration.id}-octave-commemoration`);
if (combined.status.la !== 'missing' || combined.status.en !== 'missing') {
return { kind: 'preces', text: combined, label };
}
return { kind: 'preces', text: { text: {}, status: { la: 'missing', en: 'missing' } }, label };
}
/**
* The day's own collect, plus one more per commemoration (calendar/
* types.ts's LiturgicalDay.commemorations) Lauds/Vespers say all of
@@ -475,10 +491,22 @@ function sanctoralCommemorationPart(commemoration: Extract<Commemoration, { kind
* Amen." for the single-collect case every other hour uses today, and
* stripping that back out per-collect to chain them properly would need
* text surgery this doesn't attempt so on a commemorated day, each
* collect here renders as its own complete, separate block instead. An
* octave commemoration never contributes anything of its own here once
* resolveOfficeWinner is in play, its content is already the primary
* collect above, so a separate entry would just be a redundant repeat.
* collect here renders as its own complete, separate block instead.
*
* An octave commemoration only renders its own block here when
* `day.temporalCategory !== 'ordinary-feria'` the same gate
* resolveOfficeWinner uses (see its own doc comment). On an *ordinary*
* octave day (e.g. an in-between day of St. Lawrence's own octave),
* resolveOfficeWinner has already substituted that octave's content as
* the primary collect above, so rendering it again here would be a
* redundant repeat (or worse, an honestly-"missing" duplicate for the
* many octaves without their own `-octave-commemoration.yml` authored).
* On a day the temporal identity itself keeps real standing (the
* Christmas Octave's own stacking days, e.g. Dec 26-31, where Christmas
* + Stephen + John + Holy Innocents are each merely commemorated
* alongside the day's own temporal office), resolveOfficeWinner does
* *not* touch the primary collect at all, so this is the only place
* these four octaves' real "Commemoratio Octavæ ..." content surfaces.
*/
export function getDayCollects(day: LiturgicalDay): ResolvedPart[] {
const parts: ResolvedPart[] = [{ kind: 'prayer', text: getDayCollect(day) }];
@@ -487,6 +515,8 @@ export function getDayCollects(day: LiturgicalDay): ResolvedPart[] {
parts.push({ kind: 'prayer', text: toResolvedText(getTemporalProper(`${commemoration.id}-collect`)) });
} else if (commemoration.kind === 'sanctoral') {
parts.push(sanctoralCommemorationPart(commemoration));
} else if (commemoration.kind === 'octave' && day.temporalCategory !== 'ordinary-feria') {
parts.push(octaveCommemorationPart(commemoration));
}
}
return parts;
+28
View File
@@ -157,6 +157,34 @@ describe('resolveOrdo("vespers", ...)', () => {
expect(anne).toBeDefined();
});
it("renders each of the Christmas Octave's own stacked 'Commemoratio Octavæ' blocks on a day the temporal office itself keeps standing", () => {
// 2033-12-30 -- one day past the '2033-12-29' stacking fixture already
// used in tests/calendar/octaves.test.ts (St. Thomas of Canterbury's
// day, itself outside this app's calendar so the day stays plain
// temporal). All four of Christmas/Stephen/John/Holy Innocents' own
// octaves are active and none of them wins the day outright (the
// temporal day itself, "privileged-feria-minor", keeps the office) --
// see hours/resolve-common.ts's getDayCollects, kind === 'octave'
// branch, and its own doc comment for why this is the one case an
// octave commemoration renders its own Ant+V/R+collect block here.
const ordo = resolveOrdo('vespers', '2033-12-30');
const octaveParts = ordo.parts.filter(
(p): p is Extract<typeof p, { kind: 'preces' }> => p.kind === 'preces' && (p.label?.includes('Octave of') ?? false),
);
expect(octaveParts.map((p) => p.label).sort()).toEqual(
[
'Commemoration of the Octave of Christmas',
'Commemoration of the Octave of The Holy Innocents, Martyrs',
'Commemoration of the Octave of St. John, Apostle and Evangelist',
'Commemoration of the Octave of St. Stephen, Protomartyr',
].sort(),
);
for (const part of octaveParts) {
expect(part.text.status.la).toBe('verified');
expect(part.text.status.en).toBe('verified');
}
});
it("renders Advent's seasonal chapter/hymn/versicle instead of the plain weekday default", () => {
// 2025-12-02 is a Tuesday in Advent (St. Bibiana, Simplex, merely
// commemorated — the day's own winner stays temporal either way).