From 83bf55efdbb736d58757e417451c34f4bd77be74 Mon Sep 17 00:00:00 2001 From: Will Estes Date: Fri, 14 Aug 2026 11:40:31 -0400 Subject: [PATCH] Add reusable Matins building blocks: types, content stores, seed data New ResolvedPart kinds (nocturn-psalmody, te-deum) and lesson fields (nocturn, source, isGospel, responsory) in hours/types.ts. Three new content-store modules: propers/bible-plan.ts (the user's own continuous scripture-reading plan, replacing the historical Nocturn-1 lectionary), propers/nocturn-readings.ts (general feast/temporal-day Nocturn 2-3 readings, extending propers/octave-readings.ts's combine-into-one-reading pattern via a newly-exported resolvePassages), and propers/matins-responsories.ts (a per-book responsory pool, matched loosely rather than by exact citation). Data: Sunday's fixed 12-psalm/3-canticle psalmody (live-verified against the reference engine), the invitatory antiphon, ferial hymn and capitulum, the Te Deum, a seeded Isaiah responsory, two live-transcribed scripture chapters, and the two bible-plan rows needed for this pass' proof content. --- .../hours/bible-plan/advent-3-tuesday.yml | 24 +++ .../bible-plan/post-pentecost-15-sunday.yml | 21 ++ .../hours/matins-responsories-by-book.yml | 38 ++++ src/data/hours/matins-sunday-antiphons.yml | 73 +++++++ .../common/matins-capitulum-ferial.yml | 17 ++ .../propers/common/matins-hymn-ferial.yml | 99 +++++++++ .../common/matins-invitatory-antiphon.yml | 17 ++ src/data/propers/common/te-deum.yml | 125 +++++++++++ .../nocturn-readings/post-pentecost-15.yml | 202 ++++++++++++++++++ src/data/scripture/isa-33.yml | 94 ++++++++ src/data/scripture/sir-36.yml | 43 ++++ src/hours/types.ts | 34 ++- src/propers/bible-plan.ts | 110 ++++++++++ src/propers/matins-responsories.ts | 58 +++++ src/propers/nocturn-readings.ts | 49 +++++ src/propers/octave-readings.ts | 6 +- 16 files changed, 1008 insertions(+), 2 deletions(-) create mode 100644 src/data/hours/bible-plan/advent-3-tuesday.yml create mode 100644 src/data/hours/bible-plan/post-pentecost-15-sunday.yml create mode 100644 src/data/hours/matins-responsories-by-book.yml create mode 100644 src/data/hours/matins-sunday-antiphons.yml create mode 100644 src/data/propers/common/matins-capitulum-ferial.yml create mode 100644 src/data/propers/common/matins-hymn-ferial.yml create mode 100644 src/data/propers/common/matins-invitatory-antiphon.yml create mode 100644 src/data/propers/common/te-deum.yml create mode 100644 src/data/propers/nocturn-readings/post-pentecost-15.yml create mode 100644 src/data/scripture/isa-33.yml create mode 100644 src/data/scripture/sir-36.yml create mode 100644 src/propers/bible-plan.ts create mode 100644 src/propers/matins-responsories.ts create mode 100644 src/propers/nocturn-readings.ts diff --git a/src/data/hours/bible-plan/advent-3-tuesday.yml b/src/data/hours/bible-plan/advent-3-tuesday.yml new file mode 100644 index 0000000..fe26e31 --- /dev/null +++ b/src/data/hours/bible-plan/advent-3-tuesday.yml @@ -0,0 +1,24 @@ +# The user's own continuous scripture-reading plan (source: personal TSV, +# not a historical lectionary — see src/propers/bible-plan.ts's header), +# row "Tuesday of III Advent" -> "Isa 36-37, Wis 14". Two readings, no +# Gospel that day. Each `passages` list becomes one combined Matins +# reading (see hours/matins.ts) — Isa 36-37 is one continuous 2-chapter +# reading, not two separate ones, matching the user's own "recombine +# where the source doesn't genuinely split" instruction (there's no +# source split here at all; this is just how the plan cites a multi- +# chapter passage). +# +# Verse text for these chapters isn't authored in src/data/scripture yet +# (the full Vulgate/Douay-Rheims import is a deferred, separate project) — +# these readings resolve with real citations and honestly `missing` body +# text, which is expected for this proof pass. +temporalId: advent-3 +weekday: tuesday +readings: + - passages: + - { book: isa, chapter: 36 } + - { book: isa, chapter: 37 } + status: { la: missing, en: missing } + - passages: + - { book: sap, chapter: 14 } + status: { la: missing, en: missing } diff --git a/src/data/hours/bible-plan/post-pentecost-15-sunday.yml b/src/data/hours/bible-plan/post-pentecost-15-sunday.yml new file mode 100644 index 0000000..3152cde --- /dev/null +++ b/src/data/hours/bible-plan/post-pentecost-15-sunday.yml @@ -0,0 +1,21 @@ +# The user's own continuous scripture-reading plan, row "14th Sunday +# after Trinity" -> "Tob 1-2, Sir 45" (temporalId post-pentecost-15 = +# "14th Sunday after Trinity" in this app's own Trinity-counted display +# label — Trinity Sunday itself is post-pentecost-01, so "Nth Sunday +# after Trinity" is always post-pentecost-(N+1); see calendar/ +# day-label.ts and calendar/temporal-id.ts). No Gospel reading on this +# Sunday row — confirmed a deliberate editorial choice across the whole +# plan (the user's plan never puts a Gospel reading on a Sunday), not a +# gap. This Sunday's own Nocturn 1 (see hours/matins.ts) uses this data; +# Nocturns 2-3 use the day's own patristic/Gospel content instead (see +# src/propers/nocturn-readings.ts). +temporalId: post-pentecost-15 +weekday: sunday +readings: + - passages: + - { book: tob, chapter: 1 } + - { book: tob, chapter: 2 } + status: { la: missing, en: missing } + - passages: + - { book: sir, chapter: 45 } + status: { la: missing, en: missing } diff --git a/src/data/hours/matins-responsories-by-book.yml b/src/data/hours/matins-responsories-by-book.yml new file mode 100644 index 0000000..61851fa --- /dev/null +++ b/src/data/hours/matins-responsories-by-book.yml @@ -0,0 +1,38 @@ +# A per-book pool of Matins responsories, matched loosely (by scriptural +# book, not exact citation) rather than tied to a specific chapter/verse — +# per the user's own description (2026-08): responsories aren't attuned to +# chapter/verse historically, so a book-level match is the right +# granularity, not a gap to fill in later with exact citations. +# +# Seeded, not a full pool — only the books needed for this build's proof +# content are here (see TODO.md for what's deferred). Cycled in order +# per-day when a Nocturn-1 reading's book has more than one candidate +# responsory available; the day's own responsory count doesn't have to +# match the pool's size. +# +# `isa` sourced from a live query (Monastic Tridentinum 1617, 2026-12-15, +# a plain ferial Tuesday of Advent III — real, un-summer-contracted +# Lectio 1-3 on Isa 30:18-28) — this is genuine historical Advent-season +# Isaiah content, reused here as a loose match for the user's own +# continuous Isaiah reading even though the exact chapters differ (Isa +# 36-37 vs. the source's own Isa 30), consistent with "not attuned to +# chapter/verse." +isa: + - r: + la: "Ægýpte, noli flere, quia Dominátor tuus véniet tibi, ante cujus conspéctum movebúntur abýssi, * Liberáre pópulum suum de manu poténtiæ." + en: "Weep not, O Egypt, for the Ruler cometh unto thee, and the depths shall be moved at His presence. * To deliver His people out of the hand of the mighty." + v: + la: "Ecce véniet Dóminus exercítuum, Deus tuus cum potestáte magna." + en: "Behold, the Lord of hosts, thy God, cometh with great power." + - r: + la: "Prope est ut véniat tempus ejus, et dies ejus non elongabúntur: * Miserébitur Dóminus Jacob, et Israël salvábitur." + en: "Her time is near to come, and her days shall not be prolonged. * The Lord shall have mercy on Jacob, and Israel shall be saved." + v: + la: "Revértere, virgo Israël, revértere ad civitátes tuas." + en: "Turn again, O Virgin of Israel, turn again to thy cities." + - r: + la: "Descéndet Dóminus sicut plúvia in vellus: * Oriétur in diébus ejus justítia, et abundántia pacis." + en: "The Lord shall come down like rain upon a fleece. * In His days shall righteousness flourish, and abundance of peace." + v: + la: "Et adorábunt eum omnes reges, omnes gentes sérvient ei." + en: "All the kings of the earth shall fall down before Him, all nations shall serve Him." diff --git a/src/data/hours/matins-sunday-antiphons.yml b/src/data/hours/matins-sunday-antiphons.yml new file mode 100644 index 0000000..57508c9 --- /dev/null +++ b/src/data/hours/matins-sunday-antiphons.yml @@ -0,0 +1,73 @@ +# Sunday Matins' fixed 12-psalm/3-canticle psalmody (Rule of Benedict ch. +# 18's own Sunday arrangement, distinct from Roman Matins' 9-psalm Sunday +# scheme). Sourced from Divinum Officium's Monastic-labeled block, +# `Latin/Psalterium/Psalmi/Psalmi matutinum.txt`'s `[Daym0]` section +# (English parallel at the same section name in the `English/` tree), and +# live-verified against a real `prayMatutinum` query (Monastic Tridentinum +# 1617, 2026-09-06, an ordinary Sunday with no feast collision) — the +# psalm numbers, canticle citations, and lesson/Te Deum sequence all +# matched exactly. +# +# Unlike the ferial weekday table (psalter-distribution.yml's `matins:`), +# this doesn't vary by weekday — it's the same every Sunday, but the +# antiphon *text* does vary by season even though the psalm numbers don't: +# `[Advm0]` right after `[Daym0]` in the same source file has its own +# Advent-specific antiphon set. Only the plain (non-seasonal) `[Daym0]` set +# is authored here; seasonal variants are a deferred follow-on, not +# required for this build (see TODO.md). +# +# Nocturn 3's 3 canticles are identified via `Psalterium/Monastic +# canticles.txt`'s internal numbering key (266 = Isa 33:2-10, 267 = Isa +# 33:13-18, 268 = Sirach/Ecclesiasticus 36:14-19, cited "sir" per this +# project's own scripture-store convention), confirmed by the live query +# ("Canticum Isaiæ" / "Canticum Isaiae" / "Canticum Ecclesiasticae"). +# Unlike nocturns 1-2 (each psalm pair under its own antiphon), all 3 +# canticles share a single antiphon ("Allelúja, allelúja, allelúja"). + +nocturn1: + groups: + - psalms: [20, 21] + antiphon: + la: "Dómine * in virtúte tua lætábitur rex." + en: "The king rejoices * in Thy strength, O Lord." + - psalms: [22, 23] + antiphon: + la: "Dóminus regit me * et nihil mihi déerit: in loco páscuæ ibi me collocávit." + en: "The Lord ruleth me * and I shall want nothing. He hath set me in a place of pasture." + - psalms: [24, 25] + antiphon: + la: "Óculi mei * semper ad Dóminum." + en: "My eyes * are ever towards the Lord." + versicle: + v: { la: "Memor fui nocte nóminis tui Dómine.", en: "In the night I have remembered thy name, O Lord:" } + r: { la: "Et custodívi legem tuam.", en: "And have kept thy law." } + +nocturn2: + groups: + - psalms: [26, 27] + antiphon: + la: "Dóminus * defénsor vitæ meæ." + en: "The Lord is the defence * of my life." + - psalms: [28, 29] + antiphon: + la: "Adoráte * Dóminum in aula sancta ejus." + en: "Worship the Lord * in His holy courts." + - psalms: [30, 31] + antiphon: + la: "In tua justítia * líbera me, Dómine." + en: "Deliver me * in Thy righteousness." + versicle: + v: { la: "Média nocte surgébam ad confiténdum tibi.", en: "I rose at midnight to give praise to thee." } + r: { la: "Super justítia justificatiónis tuæ.", en: "For the judgments of thy justification." } + +nocturn3: + canticles: + - { book: isa, chapter: 33, verses: "2-10" } + - { book: isa, chapter: 33, verses: "13-18" } + - { book: sir, chapter: 36, verses: "14-19" } + antiphon: + la: "Allelúja, allelúja, allelúja." + en: "Alleluia, alleluia, alleluia." + versicle: + v: { la: "Exaltáre Dómine in virtúte tua.", en: "Be Thou exalted, O Lord, in Thine own strength." } + r: { la: "Cantábimus et psallémus virtútes tuas.", en: "We will sing and praise Thy power." } diff --git a/src/data/propers/common/matins-capitulum-ferial.yml b/src/data/propers/common/matins-capitulum-ferial.yml new file mode 100644 index 0000000..0828d63 --- /dev/null +++ b/src/data/propers/common/matins-capitulum-ferial.yml @@ -0,0 +1,17 @@ +# Live-verified (Monastic Tridentinum 1617, 2026-12-15, a plain ferial +# Tuesday) — closes ferial (1-nocturn) Matins, same fixed per-annum +# chapter regardless of weekday (confirmed by the source's own rubric: +# "in the Ferial Office of either three or one lesson, [this] is always +# said as follows" — i.e. the same closing chapter whether the day got +# its full 3 lessons or the summer-contracted single short reading this +# app doesn't reproduce anyway, see hours/matins.ts). +id: matins-capitulum-ferial +text: + la: "Vigiláte, state in fide, viríliter ágite, et confortámini. Ómnia vestra in caritáte fiant." + en: "Watch ye, stand fast in the faith, do manfully, and be strengthened. Let all your things be done in charity." +status: + la: verified + en: verified +citation: + la: "1 Cor 16:13-14" + en: "1 Cor 16:13-14" diff --git a/src/data/propers/common/matins-hymn-ferial.yml b/src/data/propers/common/matins-hymn-ferial.yml new file mode 100644 index 0000000..fa5f8f1 --- /dev/null +++ b/src/data/propers/common/matins-hymn-ferial.yml @@ -0,0 +1,99 @@ +# Live-verified (Monastic Tridentinum 1617, 2026-12-15, a plain ferial +# Tuesday) — "{ex Psalterio secundum tempora}" in the source, meaning this +# is itself one of several season-variable hymns rather than a true fixed +# year-round text; only this one (evidently the ordinary-time/per-annum +# form) is authored so far. Seasonal variants deferred (see TODO.md), +# same incremental-content pattern as everywhere else in this project. +id: matins-hymn-ferial +text: + la: >- + Somno reféctis ártubus, + + Spreto cubíli, súrgimus: + + Nobis, Pater, canéntibus + + Adésse te depóscimus. + + + Te lingua primum cóncinat, + + Te mentis ardor ámbiat: + + Ut áctuum sequéntium + + Tu, Sancte, sis exórdium. + + + Cedant tenébræ lúmini, + + Et nox diúrno síderi, + + Ut culpa, quam nox íntulit, + + Lucis labáscat múnere. + + + Precámur iídem súpplices, + + Noxas ut omnes ámputes, + + Et ore te canéntium + + Laudéris in perpétuum. + + + Præsta, Pater piíssime, + + Patríque compar Únice, + + Cum Spíritu Paráclito + + Regnans per omne sǽculum. Amen. + en: >- + Our limbs refreshed with slumber now, + + And sloth cast off, in prayer we bow; + + And while we sing thy praises dear, + + O Father, be thou present here. + + + To thee our earliest morning song, + + To thee our hearts full powers belong; + + And thou, O Holy One, prevent + + Each following action and intent. + + + As shades at morning flee away, + + And night before the star of day; + + So each transgression of the night + + Be purged by thee, celestial Light! + + + Cut off, we pray thee, each offense, + + And every lust of thought and sense: + + That by their lips who thee adore + + Thou mayst be praised forevermore. + + + Grant this, O Father ever One + + With Christ, thy sole-begotten Son, + + And Holy Ghost, whom all adore, + + Reigning and blest forevermore. Amen. +status: + la: verified + en: verified diff --git a/src/data/propers/common/matins-invitatory-antiphon.yml b/src/data/propers/common/matins-invitatory-antiphon.yml new file mode 100644 index 0000000..6472b5c --- /dev/null +++ b/src/data/propers/common/matins-invitatory-antiphon.yml @@ -0,0 +1,17 @@ +# Live-verified (Monastic Tridentinum 1617, 2026-09-06 and 2026-12-15). +# Real practice repeats this antiphon as an interspersed refrain between +# verse groups of Psalm 94 (the historical "Venite" invitatory shape) — +# deliberately not modeled that way here, per direct instruction: this app +# frames it like any other psalm antiphon instead, one opening +# (incipit-or-full, same rank rule as everywhere else) and one full +# repeat after, via hours/matins.ts's plain 'psalm' + 'antiphon' parts. +# Fixed year-round for now — real practice does vary this by season/feast +# (`{Antiphona ex Psalterio secundum tempora}` in the source); not yet +# modeled, a deferred follow-on (see TODO.md). +id: matins-invitatory-antiphon +text: + la: "Veníte, * Exsultémus Dómino." + en: "O come, * let us sing unto the Lord." +status: + la: verified + en: verified diff --git a/src/data/propers/common/te-deum.yml b/src/data/propers/common/te-deum.yml new file mode 100644 index 0000000..0c0707a --- /dev/null +++ b/src/data/propers/common/te-deum.yml @@ -0,0 +1,125 @@ +# Live-verified (Monastic Tridentinum 1617, 2026-09-06) — fixed text, sung +# after the last lesson on a 3-nocturn day (Sunday or Duplex+; see +# hours/matins.ts and hours/types.ts's 'te-deum' ResolvedPart). Never said +# on a plain ferial (1-nocturn) day. +id: te-deum +text: + la: >- + Te Deum laudámus: * te Dóminum confitémur. + + Te ætérnum Patrem * omnis terra venerátur. + + Tibi omnes Ángeli, * tibi Cæli, et univérsæ Potestátes: + + Tibi Chérubim et Séraphim * incessábili voce proclámant: + + Sanctus, Sanctus, Sanctus * Dóminus Deus Sábaoth. + + Pleni sunt cæli et terra * majestátis glóriæ tuæ. + + Te gloriósus * Apostolórum chorus, + + Te Prophetárum * laudábilis númerus, + + Te Mártyrum candidátus * laudat exércitus. + + Te per orbem terrárum * sancta confitétur Ecclésia, + + Patrem * imménsæ majestátis; + + Venerándum tuum verum * et únicum Fílium; + + Sanctum quoque * Paráclitum Spíritum. + + Tu Rex glóriæ, * Christe. + + Tu Patris * sempitérnus es Fílius. + + Tu, ad liberándum susceptúrus hóminem: * non horruísti Vírginis úterum. + + Tu, devícto mortis acúleo, * aperuísti credéntibus regna cælórum. + + Tu ad déxteram Dei sedes, * in glória Patris. + + Judex créderis * esse ventúrus. + + Te ergo quǽsumus, tuis fámulis súbveni, * quos pretióso sánguine redemísti. + + Ætérna fac cum Sanctis tuis * in glória numerári. + + Salvum fac pópulum tuum, Dómine, * et bénedic hereditáti tuæ. + + Et rege eos, * et extólle illos usque in ætérnum. + + Per síngulos dies * benedícimus te. + + Et laudámus nomen tuum in sǽculum, * et in sǽculum sǽculi. + + Dignáre, Dómine, die isto * sine peccáto nos custodíre. + + Miserére nostri, Dómine, * miserére nostri. + + Fiat misericórdia tua, Dómine, super nos, * quemádmodum sperávimus in te. + + In te, Dómine, sperávi: * non confúndar in ætérnum. + en: >- + We praise thee, O God, * we acknowledge thee to be the Lord. + + All the earth doth worship thee, * the Father everlasting. + + To thee all Angels cry aloud, * the Heavens, and all the Powers therein. + + To thee Cherubim and Seraphim * continually do cry. + + Holy, Holy, Holy * Lord God of Sabaoth; + + Heaven and earth are full * of the Majesty of thy glory. + + The glorious company of the Apostles * praise thee. + + The goodly fellowship of the Prophets * praise thee. + + The noble army of Martyrs * praise thee. + + The holy Church throughout all the world * doth acknowledge thee; + + The Father, * of an infinite Majesty. + + Thine honourable, true, * and only Son; + + Also the Holy Ghost, * the Comforter. + + Thou art the King of Glory, * O Christ. + + Thou art the everlasting * Son of the Father. + + When thou tookest upon thee to deliver man, * thou didst not abhor the Virgin's womb. + + When thou hadst overcome the sharpness of death, * thou didst open the Kingdom of Heaven to all believers. + + Thou sittest at the right hand of God, * in the glory of the Father. + + We believe that thou shalt come * to be our Judge. + + We therefore pray thee, help thy servants, * whom thou hast redeemed with thy precious Blood. + + Make them to be numbered with thy Saints, * in glory everlasting. + + O Lord, save thy people, * and bless thine heritage. + + Govern them, * and lift them up for ever. + + Day by day * we magnify thee; + + And we worship thy Name * ever, world without end. + + Vouchsafe, O Lord, to keep us * this day without sin. + + O Lord, have mercy upon us, * have mercy upon us. + + O Lord, let thy mercy lighten upon us, * as our trust is in thee. + + O Lord, in thee have I trusted, * let me never be confounded. +status: + la: verified + en: verified diff --git a/src/data/propers/nocturn-readings/post-pentecost-15.yml b/src/data/propers/nocturn-readings/post-pentecost-15.yml new file mode 100644 index 0000000..e28bf8c --- /dev/null +++ b/src/data/propers/nocturn-readings/post-pentecost-15.yml @@ -0,0 +1,202 @@ +# Nocturn 2-3 content for an ordinary Sunday with no saint of its own +# (post-pentecost-15 = "14th Sunday after Trinity" in this app's own +# display labeling — see calendar/day-label.ts). Live-verified against +# Divinum Officium (Monastic Tridentinum 1617, 2026-09-06, a plain Sunday +# with no feast collision — confirmed by cross-checking the same date +# against data/calendar/sanctoral-calendar.yml). +# +# Nocturn 1 for this same Sunday comes from the user's own bible-plan +# instead (data/hours/bible-plan/post-pentecost-15-sunday.yml, Tob 1-2 / +# Sir 45) — historically this Sunday's real Nocturn 1 continuous reading +# is the Book of Job (confirmed live), but the user's plan deliberately +# substitutes its own reading there. Nocturn 2 below is still the real +# historical St. Gregory commentary *on Job*, even though this app's own +# Nocturn 1 no longer reads Job that week — kept as-is (real, valuable +# patristic content proper to the ordinary-time cycle, not tied to a +# specific saint) rather than dropped for no longer lining up thematically +# with Nocturn 1. Flagged, not silently resolved — a real seam between +# the historical Moralia-in-Job commentary cycle and this app's own +# substituted Nocturn 1 plan. +# +# Combined per the user's "recombine where the source doesn't genuinely +# split" instruction: Nocturn 2's 4 lessons (Lectio 5-8) are one +# continuous excerpt from the same work (Moralia, Bk. 9, ch. 2) — no real +# source break between them, so one reading. Nocturn 3's Lectio 9 is +# genuinely two different sources in the one numbered lesson (the Gospel +# pericope itself, then the opening of St. Augustine's homily on it) — +# kept split into two readings here; the homily then continues unbroken +# through Lectio 10-12, recombined into that second reading. +# +# Responsory: this project's own established convention (see +# octave-readings.ts) is "the first one, when several collapse" — applied +# here per resulting reading: Nocturn 2's reading uses Lectio 5's +# responsory; the Gospel reading uses Lectio 9's (the only one that's +# genuinely its own, since Lectio 9 itself split across both new +# readings); the homily reading uses Lectio 10's (the first responsory +# that's unambiguously the homily's own). Known simplification, flagged +# not fixed: Lectio 12's own closing "Duo Seraphim" responsory (with its +# own Gloria Patri, sung specially before the Te Deum) isn't represented +# here at all — this schema only carries one responsory per reading, and +# that grand closing responsory doesn't belong to any single one of these +# readings the way the others do. +id: post-pentecost-15 +readings: + - nocturn: 2 + isGospel: false + source: "St. Gregory the Great, Moralia in Job, Book 9, ch. 2" + text: + la: >- + Vero scio quod ita sit, et quod non justificábitur homo compósitus Deo. Homo quippe Deo non + compósitus justítiam pércipit, compósitus amíttit; quia quisquis se auctóri bonórum cómparat, + bono se, quod accéperat, privat. Qui enim accépta bona sibi árrogat, suis contra Deum donis + pugnat. Unde ergo despéctus erígitur, dignum est ut eréctus inde destruátur. + + + Sanctus autem vir, quia omne virtútis nostræ méritum esse vítium cónspicit, si ab intérno + árbitro distrícte judicétur, recte subjúngit: Si volúerit conténdere cum eo, non póterit + respondére ei unum pro mille. + + + In Scriptúra sancta millenárius númerus pro universitáte solet intélligi. Hinc étenim + Psalmísta ait: Verbi, quod mandávit in mille generatiónes: cum profécto constet, quod ab ipso + mundi exórdio usque ad Redemptiónis advéntum per Evangelístam non ámplius quam septuagínta et + septem propágines numeréntur. Quid ergo in millenário número nisi ad proferéndam novam + sóbolem perfécta univérsitas præscítæ generatiónis exprímitur? Hinc et per Joánnem dícitur: Et + regnábunt cum eo mille annis: quia vidélicet regnum sanctæ Ecclésiæ universitátis perfectióne + solidátur. + + + Quia vero monas décies multiplicáta in denárium dúcitur, denárius per semetípsum ductus in + centenárium dilatátur, qui rursus per denárium ductus in millenárium ténditur; cum ab uno + incípimus, ut ad millenárium veniámus, quid hoc loco uníus appellatióne, nisi bene vivéndi + inítium? quid millenárii númeri amplitúdine, nisi ejúsdem bonæ vitæ perféctio designátur? Cum + Deo autem conténdere, est, non ei tribúere, sed sibi glóriam suæ virtútis arrogáre. Sed + sanctus vir conspíciat, quia et qui summa jam dona percépit, si de accéptis extóllitur, cuncta + quæ accéperat, amíttit. + en: >- + We know that it is so of a truth, and that a man cannot be justified as against God. When God + is put out of the consideration, a man may be considered to be just, but considered as against + God, his righteousness vanisheth away. When a man measureth himself by his relation to Him, + Who is the Author of all good, he doth thereby acknowledge that of himself he hath no good in + him, but hath received from God whatsoever he hath. He that glorifieth himself because of good + which hath been given him, fighteth against God with God's own gifts. It is just therefore + that the grounds upon which he ought to have been humbled, but upon which he hath puffed + himself up, should be used to humble his vain-glory. + + + But an holy man, because he perceiveth that the worth of our own good deeds falleth short, + when he considereth his own spiritual man, justly saith If He will contend with him, he cannot + answer Him one of a thousand. + + + In the Holy Scriptures the numeral a thousand is used to be taken as signifying a + generalization. Thus, the Psalmist saith The word which He commanded to a thousand generations + (Ps. civ. 8), whereas it is notorious that the Evangelist doth not reckon more then + seventy-and-seven generations between the very beginning of the world and the coming of our + Redeemer. What therefore is to be understood here by a thousand? The general ripeness of the + old generation to bring forth a new offspring. Hence also it is said by John And shall reign + with Him a thousand years (Apoc. xx. 6), because the reign of the Holy Church will be over all + mankind made perfect. + + + When times one is ten, and ten times ten is an hundred, and ten times an hundred is a + thousand. Observing therefore this connection between one and a thousand, what are we to + understand by the one (in the text, connected as it is with the thousand whereby we understand + perfection)? Is it not the beginning of a good life, even as the thousand representeth + perfection? The contending with God (which is spoken of in the text) is the non-acknowledgment + of that which is owed to Him, and the vain-glorying instead in our own strength. But an holy + man should see, that even if one had received the gifts of perfection, and were to make them + the grounds of self-glorifying, such an one would thereby lose all that he had received. + responsory: + la: "Indúta est caro mea putrédine, et sórdibus púlveris cutis mea áruit et contrácta est: * Meménto mei, Dómine, quóniam ventus est vita mea.\nV. Dies mei velócius transiérunt quam a texénte tela succíditur, et consúmpti sunt absque ulla spe.\nR. Meménto mei, Dómine, quóniam ventus est vita mea." + en: "My flesh is clothed with worms and clods of dust. My skin is dry and drawn together. * Remember me, O Lord, for my life is wind.\nV. My days are swifter than a weaver's shuttle, and are spent without hope.\nR. Remember me, Lord, for my life is wind." + status: { la: verified, en: verified } + + - nocturn: 3 + isGospel: true + citation: { la: "Luc 7:11-16", en: "Luke 7:11-16" } + # Full text, not the "Et réliqua" incipit Lectio 9 itself uses — the + # live rendering has this same Gospel read out in full a second time, + # right after the Te Deum, closing with its own "Te decet laus" + # versicle rather than the usual "Tu autem, Dómine, miserére nobis" — + # using that fuller form here rather than modeling the two Gospel + # readings (incipit-only at Lectio 9, full after Te Deum) as separate + # parts, a known simplification (see hours/matins.ts). + text: + la: "In illo témpore: Ibat Jesus in civitátem, quæ vocátur Naim: et ibant cum eo discípuli ejus et turba copiósa. Cum autem appropinquáret portæ civitátis, ecce, defúnctus efferebátur fílius únicus matris suæ: et hæc vídua erat, et turba civitátis multa cum illa. Quam cum vidísset Dóminus, misericórdia motus super eam, dixit illi: Noli flere. Et accéssit et tétigit lóculum. Hi autem, qui portábant, stetérunt. Et ait: Adoléscens, tibi dico, surge. Et resédit, qui erat mórtuus, et cœpit loqui. Et dedit illum matri suæ. Accépit autem omnes timor: et magnificábant Deum, dicéntes: Quia Prophéta magnus surréxit in nobis: et quia Deus visitávit plebem suam." + en: "At that time, Jesus went to a town called Naim; and His disciples and a large crowd went with Him. And as He drew near the gate of the town, behold, a dead man was being carried out, the only son of his mother, and she was a widow; and a large gathering from the town was with her. And the Lord, seeing her, had compassion on her, and said to her, Do not weep. And He went up and touched the stretcher; and the bearers stood still. And He said, Young man, I say to you, arise. And he who was dead, sat up, and began to speak. And He gave him to his mother. But fear seized upon all, and they began to glorify God, saying, A great prophet has risen among us, and God has visited His people." + responsory: + la: "Quis mihi tríbuat, ut in inférno prótegas me et abscóndas me, donec pertránseat furor tuus, Dómine, nisi tu, qui solus es Deus? * Et constítuas mihi tempus, in quo recordéris mei?\nV. Numquid sicut dies hóminis dies tui, ut quæras iniquitátem meam; cum sit nemo, qui de manu tua possit erúere.\nR. Et constítuas mihi tempus, in quo recordéris mei?" + en: "O that Thou wouldest hide me in the grave; that Thou wouldest keep me secret, until thy wrath be past, even thine, O Lord, Thou That alone art God; * That Thou wouldest appoint me a set time, and remember me!\nV. Are thy days as the days of man, that Thou inquirest after mine iniquity, and there is none that can deliver out of thine hand.\nR. That Thou wouldest appoint me a set time, and remember me!" + status: { la: verified, en: verified } + + - nocturn: 3 + isGospel: false + source: "St. Augustine, Bishop of Hippo, Sermon 44 on the Words of the Lord" + text: + la: >- + De júvene illo resuscitáto gavísa est mater vídua; de homínibus in spíritu quotídie suscitátis + gaudet mater Ecclésia. Ille quidem mórtuus erat córpore; illi autem mente. Illíus mors + visíbilis visibíliter plangebátur; illórum mors invisíbilis nec quærebátur, nec videbátur. + Quæsívit ille, qui nóverat mórtuos. Ille solus nóverat mórtuos, qui póterat fácere vivos. Nisi + enim ad mórtuos suscitándos venísset, Apóstolus non díceret: Surge, qui dormis, et exsúrge a + mórtuis, et illuminábit te Christus. + + + Tres autem mórtuos invenímus a Dómino resuscitátos visibíliter, míllia invisibíliter. Quot + autem mórtuos visibíliter suscitáverit, quis novit? Non enim ómnia, quæ fecit, scripta sunt. + Joánnes hoc dixit: Multa ália fecit Jesus, quæ si scripta essent, árbitror totum mundum non + posse libros cápere. Multi ergo sunt álii sine dúbio suscitáti, sed non tres frustra + commemoráti. + + + Dóminus enim noster Jesus Christus ea quæ faciébat corporáliter, étiam spiritáliter volébat + intélligi. Neque enim tantum mirácula propter mirácula faciébat; sed ut illa, quæ faciébat, + mira essent vidéntibus, vera essent intelligéntibus. Quemádmodum qui videt lítteras in códice + óptime scripto, et non novit légere, laudat quidem antiquárii manum, admírans ápicum + pulchritúdinem; sed quid sibi velint, quid índicent illi ápices, nescit, et est óculis + laudátor, mente non cógnitor. + + + Alius autem et laudat artifícium, et capit intelléctum: ille útique, qui non solum vidére quod + commúne est ómnibus, potest, sed étiam légere; quod qui non dídicit, non potest. Ita qui + vidérunt Christi mirácula, et non intellexérunt quid sibi vellent, et quid intelligéntibus + quodámmodo innúerent, miráti sunt tantum quia facta sunt; álii vero et facta miráti, et + intellécta assecúti. Tales nos in schola Christi esse debémus. + en: >- + That her son was called again to life was the joy of that widowed mother; that souls of men + are every day called to life is the joy of our Mother the Church. He was dead in body they + have been dead in mind. His death was outward, and was outwardly bewailed; their inward. Death + hath been neither mourned for nor seen. But He hath sought for them, Who hath seen that they + are dead, and He only hath seen that they are dead, Who hath been able to make them alive. If + He had not come to raise the dead, the Apostle had not said: "Awake, thou that sleepest, and + arise from the dead, and Christ shall give thee light." (Eph. v. 14.) + + + We find written how the Lord raised from the dead three persons visibly, but thousands + invisibly. But how many they may have been whom He raised visibly, who knoweth? For all the + things which He did are not written. John saith thus: "There are also many other things which + Jesus did, the which, if they should be written every one, I suppose that even the world + itself could not contain the books that should be written." (John xxi. 25.) There were then, + doubtless, many more raised to life, but it is not meaningless that three are recorded. + + + For our Lord Jesus Christ hath willed that those things which He did carnally, we should + understand also spiritually. He worked not miracles only for the sake of working wonders, but + that His works might be at once wonderful to them that beheld, and true to them that + understand them. Even as one that looketh upon a scroll right fairly written, and knoweth not + how to read therein, praiseth the hand of the old scribe when he seeth the beauty of the + points, but what it saith, what those points mean, he knoweth not, and praiseth by the eye, + without understanding by the mind. + + + Another both praiseth the workmanship, and grasps the meaning: that is, he who can not only + see what is common to all, but can also read; which he who hath not learned cannot do. So they + who saw the miracles of Christ, and understood not what they meant, and what they intimated in + some sort to those who understand, only marvelled that they were done; but others both + marvelled at the deeds, and grasped what they meant. Such ought we to be in the school of + Christ. + responsory: + la: "Numquid supplántat judícium? aut Omnípotens subvértit quod iustum est? † Líbera me, Dómine, et pone me juxta te: * Et cujúsvis manus pugnet contra me.\nV. Apprehénde arma et scutum, et exsúrge in adjutórium mihi.\nR. Et cujúsvis manus pugnet contra me." + en: "Does God pervert judgment, or does the Almighty overthrow that which is just? Deliver me, O Lord, and set me beside you, * And let any man's hand fight against me.\nV. Take hold of arms and shield: and rise up to help me.\nR. And let any man's hand fight against me." + status: { la: verified, en: verified } diff --git a/src/data/scripture/isa-33.yml b/src/data/scripture/isa-33.yml new file mode 100644 index 0000000..05acab6 --- /dev/null +++ b/src/data/scripture/isa-33.yml @@ -0,0 +1,94 @@ +# Isaiah 33 — verses 2-10 and 13-18 transcribed from a live Divinum +# Officium query (Monastic Tridentinum 1617, 2026-09-06, Sunday Matins +# Nocturn III canticles) — see data/hours/matins-sunday-antiphons.yml. +# Only the two verse ranges actually used by that canticle are authored; +# the rest of the chapter is unauthored (empty per src/scripture's own +# "unauthored -> not present" convention). +book: isa +chapter: 33 +verses: + - n: 2 + text: + la: "Dómine, miserére nostri, te enim exspectávimus; esto bráchium nostrum in mane, * et salus nostra in témpore tribulatiónis." + en: "O Lord, have mercy on us: for we have waited for thee: be thou our arm in the morning, and our salvation in the time of trouble." + status: { la: verified, en: verified } + - n: 3 + text: + la: "A voce Angeli fugérunt pópuli, * et ab exaltatióne tua dispérsæ sunt gentes." + en: "At the voice of the angel the people fled, and at the lifting up thyself the nations are scattered." + status: { la: verified, en: verified } + - n: 4 + text: + la: "Et congregabúntur spólia vestra sicut collígitur bruchus, * velut cum fossæ plenæ fúerint de eo." + en: "And your spoils shall be gathered together as the locusts are gathered, as when the ditches are full of them." + status: { la: verified, en: verified } + - n: 5 + text: + la: "Magnificátus est Dóminus, quóniam habitávit in excélso; * implévit Sion judício et justítia." + en: "The Lord is magnified, for he hath dwelt on high: he hath filled Sion with judgment and justice." + status: { la: verified, en: verified } + - n: 6 + text: + la: "Et erit fides in tempóribus tuis: divítiæ salútis sapiéntia: * et sciéntia; timor Dómini ipse est thesáurus ejus." + en: "And there shall be faith in thy times: riches of salvation, wisdom and knowledge: the fear of the Lord is his treasure." + status: { la: verified, en: verified } + - n: 7 + text: + la: "Ecce vidéntes clamábunt foris; * ángeli pacis amáre flebunt." + en: "Behold they that see shall cry without, the angels of peace shall weep bitterly." + status: { la: verified, en: verified } + - n: 8 + text: + la: "Dissipátæ sunt viæ, cessávit tránsiens per sémitam: írritum factum est pactum, * projécit civitátes, non reputávit hómines." + en: "The ways are made desolate, no one passeth by the road, the covenant is made void, he hath rejected the cities, he hath not regarded the men." + status: { la: verified, en: verified } + - n: 9 + text: + la: "Luxit et elánguit terra; * confúsus est Líbanus, et obsórduit." + en: "The land hath mourned, and languished: Libanus is confounded and become foul, and Saron is become as a desert: and Basan and Carmel are shaken." + status: { la: verified, en: verified } + - n: 9 + text: + la: "Et factus est Saron sicut desértum, * et concússa est Basan, et Carmélus." + en: "" + status: { la: verified, en: missing } + - n: 10 + text: + la: "Nunc consúrgam, dicit Dóminus; * nunc exaltábor, nunc sublevábor." + en: "Now will I rise up, saith the Lord: now will I be exalted, now will I lift up myself." + status: { la: verified, en: verified } + - n: 13 + text: + la: "Audíte, qui longe estis, quæ fécerim; * et cognóscite, vicíni, fortitúdinem meam." + en: "Hear, you that are far off, what I have done, and you that are near know my strength." + status: { la: verified, en: verified } + - n: 14 + text: + la: "Contérriti sunt in Sion peccatóres; * possédit tremor hypócritas." + en: "The sinners in Sion are afraid, trembling hath seized upon the hypocrites. Which of you can dwell with devouring fire? which of you shall dwell with everlasting burnings?" + status: { la: verified, en: verified } + - n: 14 + text: + la: "Quis póterit habitáre de vobis cum igne devoránte? * quis habitábit ex vobis cum ardóribus sempitérnis?" + en: "" + status: { la: verified, en: missing } + - n: 15 + text: + la: "Qui ámbulat in justítiis et lóquitur veritátem, qui próicit avarítiam ex calúmnia, * et éxcutit manus suas ab omni múnere," + en: "He that walketh in justices, and speaketh truth, that casteth away avarice by oppression, and shaketh his hands from all bribes, that stoppeth his ears lest he hear blood, and shutteth his eyes that he may see no evil." + status: { la: verified, en: verified } + - n: 16 + text: + la: "Qui obtúrat aures suas ne áudiat sánguinem, * et claudit óculos suos ne vídeat malum." + en: "He shall dwell on high, the fortifications of rocks shall be his highness: bread is given him, his waters are sure." + status: { la: verified, en: verified } + - n: 17 + text: + la: "Iste in excélsis habitábit; muniménta saxórum sublímitas ejus: * panis ei datus est, aquæ ejus fidéles sunt." + en: "His eyes shall see the king in his beauty, they shall see the land far off." + status: { la: verified, en: verified } + - n: 18 + text: + la: "Regem in decóre suo vidébunt óculi ejus, cernent terram de longe. * Cor tuum meditábitur timórem." + en: "Thy heart shall meditate fear: where is the learned? where is he that pondereth the words of the law? where is the teacher of little ones?" + status: { la: verified, en: verified } diff --git a/src/data/scripture/sir-36.yml b/src/data/scripture/sir-36.yml new file mode 100644 index 0000000..955ea72 --- /dev/null +++ b/src/data/scripture/sir-36.yml @@ -0,0 +1,43 @@ +# Sirach (Ecclesiasticus) 36:14-19, transcribed from a live Divinum +# Officium query (Monastic Tridentinum 1617, 2026-09-06, Sunday Matins +# Nocturn III's third canticle) — see +# data/hours/matins-sunday-antiphons.yml. Only this verse range is +# authored; the rest of the chapter is unauthored. +book: sir +chapter: 36 +verses: + - n: 14 + text: + la: "Miserére plebi tuæ, super quam invocátum est nomen tuum: * et Israël quem coæquásti primogénito tuo." + en: "Have mercy on thy people, upon whom thy name is invoked: and upon Israel, whom thou hast raised up to be thy firstborn." + status: { la: verified, en: verified } + - n: 15 + text: + la: "Miserére civitáti sanctificatiónis tuæ, Jerúsalem, * civitáti requiéi tuæ." + en: "Have mercy on Jerusalem, the city which thou hast sanctified, the city of thy rest." + status: { la: verified, en: verified } + - n: 16 + text: + la: "Reple Sion inenarrabílibus verbis tuis, * et glória tua pópulum tuum." + en: "Fill Sion with thy unspeakable words, and thy people with thy glory." + status: { la: verified, en: verified } + - n: 17 + text: + la: "Da testimónium his qui ab inítio creatúræ tuæ sunt, * et súscita prædicatiónes quas locúti sunt in nómine tuo prophétæ prióres." + en: "Give testimony to them that are thy creatures from the beginning, and raise up the prophecies which the former prophets spoke in thy name." + status: { la: verified, en: verified } + - n: 18 + text: + la: "Da mercédem sustinéntibus te, * ut prophétæ tui fidéles inveniántur:" + en: "Reward them that patiently wait for thee, that thy prophets may be found faithful: and hear the prayers of thy servants," + status: { la: verified, en: verified } + - n: 18 + text: + la: "Et exáudi oratiónes servórum tuórum, secúndum benedictiónem Aaron de pópulo tuo: * et dírige nos in viam justítiæ." + en: "" + status: { la: verified, en: missing } + - n: 19 + text: + la: "Et sciant omnes qui hábitant terram, * quia tu es Deus conspéctor sæculórum." + en: "According to the blessing of Aaron over thy people, and direct us into the way of justice, and let all know that dwell upon the earth, that thou art God the beholder of all ages." + status: { la: verified, en: verified } diff --git a/src/hours/types.ts b/src/hours/types.ts index c978a75..56c6f9b 100644 --- a/src/hours/types.ts +++ b/src/hours/types.ts @@ -204,10 +204,42 @@ export type ResolvedPart = // A standalone antiphon, said on its own rather than framing a psalm — // see 'closing-antiphon' above. | { kind: 'antiphon'; text: ResolvedText } + // Matins only. Groups a nocturn's psalms for the UI — each entry is a + // plain 'psalm' ResolvedPart (Sunday/Duplex+ nocturns 1-2 use the fixed + // Ps 20-31 table, nocturn 3 uses 'canticle' entries instead; ferial days + // have a single unnumbered nocturn and don't use this wrapper at all — + // see hours/matins.ts). `nocturn` is 1-3. + | { kind: 'nocturn-psalmody'; nocturn: number; psalms: ResolvedPart[] } + // Matins only. Sung after the last lesson on a 3-nocturn day (Sunday or + // a Duplex+ sanctoral winner) — see hours/matins.ts. Never present on a + // plain ferial (1-nocturn) day. + | { kind: 'te-deum'; text: ResolvedText } // Martyrology and Regula both render as a block of prose text with a // label (a fixed section name for Martyrology, a date-derived chapter // title for Regula) rather than a fixed textRef. - | { kind: 'lesson'; text: ResolvedText; label?: string }; + // `nocturn`/`source` are Matins-only (see hours/matins.ts): `nocturn` + // groups a reading under its 'nocturn-psalmody' sibling for the UI (1-3, + // omitted on a ferial 1-nocturn day); `source` is a human-readable + // attribution ("St. John Chrysostom, Homily 3 on Matthew"; the user's + // own bible-reading plan doesn't carry one). `isGospel` flags a reading + // as a Gospel pericope (from either the user's own continuous-reading + // plan or the day's own proper Gospel+homily — never a Common-of-Saints + // fallback Gospel, which this app deliberately excludes for Matins, see + // hours/matins.ts) rather than a distinct part kind, since a Gospel + // reading is still fundamentally a lesson — just one worth marking. + // `responsory` is the short responsory said after the reading, when one + // was sourced (matched loosely by scriptural book for the user's own + // plan, live-queried directly for patristic/hagiographic readings) — + // absent, not fabricated, when none could be sourced. + | { + kind: 'lesson'; + text: ResolvedText; + label?: string; + nocturn?: number; + source?: string; + isGospel?: boolean; + responsory?: ResolvedText; + }; export interface ResolvedVerse { n: number; diff --git a/src/propers/bible-plan.ts b/src/propers/bible-plan.ts new file mode 100644 index 0000000..3521657 --- /dev/null +++ b/src/propers/bible-plan.ts @@ -0,0 +1,110 @@ +// The user's own continuous scripture-reading plan for Matins Nocturn 1 — +// deliberately not the historical per-day lectionary (see hours/matins.ts's +// header and TODO.md's Matins section): a personal year-round reading plan, +// keyed by the same (temporalId, weekday) pair every other content store in +// this project already uses to identify a day, sourced from a TSV the user +// maintains outside this repo. Replaces the historical Nocturn-1 lesson +// count entirely — a *variable* number of readings per day, no RB summer +// contraction (see calendar/temporal-id.ts's resolveTemporalId for the +// id scheme this keys off). +// +// Only a small, growable subset is authored so far — this is the mechanism +// build, not the full-calendar content pass (~390 rows total in the +// source TSV); see TODO.md for what's deferred. Every entry not yet +// authored here simply resolves to no readings (honest absence, not a +// placeholder) — matins.ts falls back to whatever nocturn-2/3 content +// exists for the day, or renders nothing for Nocturn 1 on a day with +// neither. +import type { LanguageCode, TranslationStatus } from '../psalter/types'; +import { resolvePassages, type ScriptureCitation } from './octave-readings'; +import { getResponsoryForBook } from './matins-responsories'; + +const GOSPEL_BOOKS = new Set(['matt', 'mark', 'luke', 'john']); + +export interface BiblePlanReading { + text: Partial>; + status: Partial>; + citation: Partial>; + /** True when every passage in this reading is from one of the four + * Gospels — the user's own plan deliberately never assigns one on a + * Sunday (confirmed 2026-08, not a gap) — see hours/matins.ts for how + * this combines with the day's own proper Gospel+homily, when one + * exists, rather than replacing it. */ + isGospel: boolean; + /** Matched loosely by the reading's own (first) book against + * data/hours/matins-responsories-by-book.yml — undefined when nothing's + * seeded for that book yet, an honest absence, not a placeholder. */ + responsory?: Partial>; +} + +interface BiblePlanReadingRecord { + passages: ScriptureCitation[]; + /** Set explicitly when the cited passages aren't in src/data/scripture + * yet (the common case until the full Vulgate/Douay-Rheims import + * lands) — mirrors octave-readings.ts's own convention rather than + * inferring "missing" from empty resolved text, since a genuinely short + * authored passage could otherwise look indistinguishable from an + * unauthored one. */ + status?: Partial>; +} + +interface BiblePlanDayRecord { + temporalId: string; + weekday: string; + readings: BiblePlanReadingRecord[]; +} + +function citationLabel(citation: ScriptureCitation): string { + const book = citation.book.charAt(0).toUpperCase() + citation.book.slice(1); + return citation.verses ? `${book} ${citation.chapter}:${citation.verses}` : `${book} ${citation.chapter}`; +} + +function isGospelReading(passages: ScriptureCitation[]): boolean { + return passages.every((p) => GOSPEL_BOOKS.has(p.book)); +} + +function resolveReading(record: BiblePlanReadingRecord, bookIndex: number): BiblePlanReading { + const text = resolvePassages(record.passages); + const status = record.status ?? { + la: text.la ? 'verified' : 'missing', + en: text.en ? 'verified' : 'missing', + }; + const label = record.passages.map(citationLabel).join('; '); + const firstBook = record.passages[0]?.book; + const responsory = firstBook ? getResponsoryForBook(firstBook, bookIndex)?.text : undefined; + return { + text, + status, + citation: { la: label, en: label }, + isGospel: isGospelReading(record.passages), + responsory, + }; +} + +const modules = import.meta.glob<{ default: BiblePlanDayRecord }>('../data/hours/bible-plan/*.yml', { + eager: true, +}); + +const readingsByKey = new Map(); +for (const mod of Object.values(modules)) { + const { temporalId, weekday, readings } = mod.default; + // Cycles independently per book (not per reading) so two same-day + // readings from the same book don't collide on the pool's first entry — + // see matins-responsories.ts's own cycling doc comment. + const seenPerBook = new Map(); + const resolved = readings.map((record) => { + const book = record.passages[0]?.book ?? ''; + const index = seenPerBook.get(book) ?? 0; + seenPerBook.set(book, index + 1); + return resolveReading(record, index); + }); + readingsByKey.set(`${temporalId}-${weekday}`, resolved); +} + +/** Empty array, not undefined, when nothing's authored for this day yet — + * every caller already treats "no readings" as a valid, renderable state + * (see hours/matins.ts), so there's no separate "missing entirely" signal + * to preserve here the way getOctaveReading needs `undefined` for. */ +export function getBiblePlanReadings(temporalId: string, weekday: string): BiblePlanReading[] { + return readingsByKey.get(`${temporalId}-${weekday}`) ?? []; +} diff --git a/src/propers/matins-responsories.ts b/src/propers/matins-responsories.ts new file mode 100644 index 0000000..1d22795 --- /dev/null +++ b/src/propers/matins-responsories.ts @@ -0,0 +1,58 @@ +// The per-book Matins responsory pool — see +// data/hours/matins-responsories-by-book.yml's own header for why matching +// is book-level, not citation-level, and why this is a seeded subset, not +// a full pool. +import type { LanguageCode, TranslationStatus } from '../psalter/types'; + +interface BilingualText { + la?: string; + en?: string; +} + +interface ResponsoryRecord { + r: BilingualText; + v: BilingualText; +} + +/** Shaped like hours/types.ts's ResolvedText, but this module doesn't + * import from hours/ (propers/ is lower in the dependency graph) — callers + * assemble the real ResolvedText themselves. The responsory's versicle is + * folded into `text` as a second line (R./V. read together as one block in + * this app's rendering, same convention as the octave-readings responsory + * field). */ +export interface MatinsResponsory { + text: Partial>; + status: Partial>; +} + +const modules = import.meta.glob<{ default: Record }>( + '../data/hours/matins-responsories-by-book.yml', + { eager: true }, +); + +const poolByBook = new Map(); +for (const mod of Object.values(modules)) { + for (const [book, entries] of Object.entries(mod.default)) { + poolByBook.set(book, entries); + } +} + +function joinResponsory(record: ResponsoryRecord): MatinsResponsory { + const la = record.r.la && record.v.la ? `${record.r.la}\n${record.v.la}` : record.r.la; + const en = record.r.en && record.v.en ? `${record.r.en}\n${record.v.en}` : record.r.en; + return { + text: { la, en }, + status: { la: la ? 'verified' : 'missing', en: en ? 'verified' : 'missing' }, + }; +} + +/** Undefined when no responsory is seeded for this book yet — an honest + * absence (see hours/types.ts's 'lesson' ResolvedPart doc comment), not a + * placeholder. `index` cycles through the pool (e.g. the Nth reading from + * this book this nocturn) via modulo, so a short pool still gives every + * reading *some* responsory rather than only the first one. */ +export function getResponsoryForBook(book: string, index: number): MatinsResponsory | undefined { + const pool = poolByBook.get(book); + if (!pool || pool.length === 0) return undefined; + return joinResponsory(pool[index % pool.length]!); +} diff --git a/src/propers/nocturn-readings.ts b/src/propers/nocturn-readings.ts new file mode 100644 index 0000000..cb2f40b --- /dev/null +++ b/src/propers/nocturn-readings.ts @@ -0,0 +1,49 @@ +// Matins Nocturn 2/3 readings for regular (non-octave-day) Sunday and +// Duplex+ feast days — extends src/propers/octave-readings.ts's own +// "combine several historical lessons into one reading, per real source +// change" pattern to ordinary days, not just the days *within* an octave +// that file already covers. Kept as a separate store (not folded into +// octave-readings.ts) because the id scheme is flat (a saint id or a +// temporal id, not `${octaveId}-octave-day-${n}`) and because a day can +// have several of these active at once — see hours/matins.ts, which +// loops every commemorated saint (calendar/types.ts's +// LiturgicalDay.commemorations) and every calendar/octaves.ts +// activeOctavesFor(date) entry, not just the office winner, per the +// user's own "be generous, not winner-takes-all" instruction (2026-08). +import type { LanguageCode, TranslationStatus } from '../psalter/types'; + +export interface NocturnReading { + nocturn: number; + isGospel: boolean; + /** Attribution, e.g. "St. Gregory the Great, Moralia in Job, Bk. 9" — + * absent for a bare Gospel pericope (the citation itself is enough). */ + source?: string; + citation?: Partial>; + text: Partial>; + responsory?: Partial>; + status: Partial>; +} + +interface NocturnReadingsFile { + id: string; + readings: NocturnReading[]; +} + +const modules = import.meta.glob<{ default: NocturnReadingsFile }>('../data/propers/nocturn-readings/*.yml', { + eager: true, +}); + +const readingsById = new Map(); +for (const mod of Object.values(modules)) { + readingsById.set(mod.default.id, mod.default.readings); +} + +/** Empty array, not undefined — a saint/temporal id with nothing authored + * here yet is the common case during this build's incremental content + * pass (see TODO.md), not an error; callers just get nothing to render + * for that id's own Nocturn 2/3 contribution. `id` is a saint id or a + * temporal id (calendar/temporal-id.ts's resolveTemporalId), matched + * exactly as `resolveOfficeWinner`/`day.commemorations` entries name it. */ +export function getNocturnReadings(id: string): NocturnReading[] { + return readingsById.get(id) ?? []; +} diff --git a/src/propers/octave-readings.ts b/src/propers/octave-readings.ts index 71c5a81..08d0a5f 100644 --- a/src/propers/octave-readings.ts +++ b/src/propers/octave-readings.ts @@ -58,7 +58,11 @@ interface OctaveReadingRecord { status: Partial>; } -function resolvePassages(passages: ScriptureCitation[]): Partial> { +/** Exported for reuse by src/propers/bible-plan.ts and + * src/propers/nocturn-readings.ts, which need the same "pull several + * scripture passages and join them as paragraphs" behavior for Matins + * readings outside the octave-day-keyed store this file owns. */ +export function resolvePassages(passages: ScriptureCitation[]): Partial> { const laParagraphs: string[] = []; const enParagraphs: string[] = []; for (const citation of passages) {