Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f4d4eb3738 | |||
| 9da5a74301 |
@@ -4838,3 +4838,41 @@ category of move as this app's existing late-canonization rank-deflation convent
|
||||
the other direction — bringing his own count to 6.
|
||||
|
||||
`npm test` (821 passed), `tsc --noEmit`, and `npm run build` all pass.
|
||||
|
||||
### Matins ferial nocturn: mid-nocturn versicle removed, a real live-verified bug (2026-09-02)
|
||||
|
||||
User noticed the 1-nocturn ferial branch had a V./R. versicle stuck mid-list as if marking a
|
||||
nocturn boundary — structurally incoherent for a single continuous nocturn.
|
||||
|
||||
`data/hours/matins-ferial-antiphons.yml`'s per-weekday `groups` carried a `versicle` field
|
||||
ported from a mid-list V./R. line embedded in the source `Psalmi matutinum.txt`
|
||||
(`[Daym1_]`-`[Daym6_]`), rendered partway through the single continuous 1-nocturn ferial day.
|
||||
Live-verified against the reference engine (`Pofficium.pl`, `command=prayMatutinum`,
|
||||
`version=Monastic Tridentinum 1617`) for Wednesday and Thursday: the real office runs straight
|
||||
from the last psalm to the Capitulum, no versicle anywhere in the psalmody — that source line
|
||||
is inert there, never actually emitted. Removed the field from all 6 weekdays and the
|
||||
rendering code (`ferialAntiphonedNocturn`); the earlier "versicle placement" reasoning in that
|
||||
file's header was a documented-but-unverified assumption, not real content.
|
||||
|
||||
See memory `vu_ferial_matins_antiphons` for full detail.
|
||||
|
||||
`npm test`, `tsc --noEmit`, and `npm run build` all pass.
|
||||
|
||||
### Matins: 3-nocturn threshold lowered from Duplex to Semiduplex (2026-09-02)
|
||||
|
||||
Same session, a separate fix: the 3-nocturn gate (`hours/matins.ts`'s `threeNocturns`) was
|
||||
floored at Duplex (`isDoubleOrHigher`). User's own design call, prompted by noticing today's
|
||||
Matins (St. Stephen of Hungary, Semiduplex) wrongly rendering as 1-nocturn: Semiduplex and
|
||||
Duplex should share the same 3-nocturn/3-psalms-each shape (distinct from Sunday/Duplex-majus
|
||||
+'s 6/6/3-plus-canticles scheme). Lowered the floor with a new, separate check
|
||||
(`isAtLeast(winner.rank, 'semiduplex')`) rather than touching `isDoubleOrHigher` itself, which
|
||||
drives an unrelated antiphon-doubling rule elsewhere and must stay floored at Duplex. No new
|
||||
content authoring was needed — `ferialPsalmodyThreeNocturns` already existed and already
|
||||
chunks the ferial 9-psalm pool into 3/3/3 as the fallback for a plain-Duplex winner with no
|
||||
override authored; Semiduplex winners now fall into that same already-working branch. Proved
|
||||
via St. Stephen of Hungary (2026-09-02, Common of a Confessor): 3 real nocturns, Common's own
|
||||
Terce/Sext/None antiphons+versicles, Te Deum present.
|
||||
|
||||
See memory `vu_matins_semiduplex_threshold` for full detail.
|
||||
|
||||
`npm test` (823 passed), `tsc --noEmit`, and `npm run build` all pass.
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
# Antiphons + versicles for the plain ferial weekday Matins nocturn (no
|
||||
# Sunday, no Duplex+ override) — previously rendered with bare psalms and
|
||||
# no antiphon at all (a genuine oversight, not a documented design choice
|
||||
# like `ferialPsalmodyThreeNocturns`'s antiphon-less fallback).
|
||||
# Antiphons for the plain ferial weekday Matins nocturn (below Semiduplex,
|
||||
# see hours/matins.ts's threeNocturns threshold) — previously rendered
|
||||
# with bare psalms and no antiphon at all (a genuine oversight, not a
|
||||
# documented design choice like `ferialPsalmodyThreeNocturns`'s
|
||||
# antiphon-less fallback).
|
||||
#
|
||||
# `psalter-distribution.yml`'s own `matins:` table is NOT the historical
|
||||
# per-weekday psalm set — its own header documents a deliberate "editorial
|
||||
@@ -43,16 +44,17 @@
|
||||
# psalm, just spread over two verse ranges); any cross-psalm pair is now
|
||||
# split per the "One antiphon per psalm" note above.
|
||||
#
|
||||
# Versicle placement: each weekday draws its 9 psalms from 1-2 different
|
||||
# original source days (see each weekday's own comment below for exactly
|
||||
# which), but only one versicle fits per plain single-nocturn weekday —
|
||||
# taken from whichever source day contributes the majority of that
|
||||
# weekday's psalms, placed at the same relative position it held in that
|
||||
# source day's own sequence (immediately after whichever group of this
|
||||
# weekday's psalms was originally its next-door neighbor, once the
|
||||
# psalms that moved out from between them are accounted for). None of the
|
||||
# cross-psalm pairs split by the 2026-09-01 change carried a versicle of
|
||||
# their own, so no versicle needed repositioning.
|
||||
# No mid-nocturn versicle (fixed 2026-09-02): the source `Psalmi
|
||||
# matutinum.txt` files (`[Daym1_]`-`[Daym6_]`) embed a V./R. line partway
|
||||
# through each weekday's psalm list, and an earlier version of this file
|
||||
# ported it as a real `versicle` field on the group where it happened to
|
||||
# fall, rendered mid-nocturn as if marking a nocturn boundary. Live-verified
|
||||
# against the reference engine (`Pofficium.pl`, `command=prayMatutinum`,
|
||||
# `version=Monastic Tridentinum 1617`) for both Wednesday and Thursday: the
|
||||
# real single-nocturn ferial office runs straight from the last psalm to
|
||||
# the Capitulum, with no versicle anywhere in the psalmody — that embedded
|
||||
# line is inert in the source and was never actually emitted by the engine.
|
||||
# Removed; do not reintroduce it without a live query proving otherwise.
|
||||
#
|
||||
# Not live-verified against a running query (Monastic ferial ordinary time
|
||||
# isn't independently re-derivable that way once psalter-distribution.yml's
|
||||
@@ -62,8 +64,8 @@
|
||||
|
||||
monday:
|
||||
# All 9 psalms are Daym1_'s own (Monastic Matins Feria II) — no reslicing
|
||||
# across weekday boundaries here, so both antiphon grouping and the
|
||||
# versicle sit exactly where Daym1_ itself has them.
|
||||
# across weekday boundaries here, so the antiphon grouping sits exactly
|
||||
# where Daym1_ itself has it.
|
||||
groups:
|
||||
- psalms: [{ number: 32 }]
|
||||
antiphon:
|
||||
@@ -83,9 +85,6 @@ monday:
|
||||
antiphon:
|
||||
la: "Revéla * Dómino viam meam."
|
||||
en: "Commit my way * to the Lord."
|
||||
versicle:
|
||||
v: { la: "Dómine, in cælo misericórdia tua.", en: "O Lord, thy mercy is in heaven," }
|
||||
r: { la: "Et véritas tua usque ad nubes.", en: "And thy truth reacheth even to the clouds." }
|
||||
- psalms: [{ number: 37 }]
|
||||
antiphon:
|
||||
la: "Ne in ira tua * árguas me Dómine."
|
||||
@@ -107,11 +106,7 @@ monday:
|
||||
|
||||
tuesday:
|
||||
# 43-44 are Monday's own tail (Daym1_); 47-58 are Daym2_'s own opening (6
|
||||
# of 9 psalms — the majority, hence Daym2_'s own versicle here); 59 is
|
||||
# Wednesday's own opening (Daym3_). The versicle sits right after 49,
|
||||
# exactly where Daym2_ itself has it (between the "49,51" and "52,53"
|
||||
# groups — 51/52/53 all moved elsewhere in the reslice, but 49 and 54
|
||||
# remain each other's nearest neighbor once they're gone).
|
||||
# of 9 psalms — the majority); 59 is Wednesday's own opening (Daym3_).
|
||||
groups:
|
||||
- psalms: [{ number: 43 }]
|
||||
# New (2026-09-01): the source's shared antiphon for this pair
|
||||
@@ -138,9 +133,6 @@ tuesday:
|
||||
antiphon:
|
||||
la: "Deus deórum * Dóminus locútus est."
|
||||
en: "The God of gods * the Lord hath spoken."
|
||||
versicle:
|
||||
v: { la: "Ímmola Deo sacrifícium laudis.", en: "Offer to God the sacrifice of praise." }
|
||||
r: { la: "Et redde Altíssimo vota tua.", en: "And pay thy vows to the most High." }
|
||||
- psalms: [{ number: 54 }]
|
||||
antiphon:
|
||||
la: "Inténde in me * et exáudi me."
|
||||
@@ -162,9 +154,7 @@ tuesday:
|
||||
|
||||
wednesday:
|
||||
# 65-72 are Daym3_'s own (7 of 9 — the majority); 73 is Thursday's own
|
||||
# opening (Daym4). The versicle sits between the "67ab" and "68ab"
|
||||
# groups, exactly where Daym3_ itself has it — nothing moved out from
|
||||
# between those two groups in the reslice, so the position is unchanged.
|
||||
# opening (Daym4).
|
||||
groups:
|
||||
- psalms: [{ number: 65 }]
|
||||
antiphon:
|
||||
@@ -174,9 +164,6 @@ wednesday:
|
||||
antiphon:
|
||||
la: "In ecclésiis, * benedícite Dóminum."
|
||||
en: "In the churches * bless ye the Lord."
|
||||
versicle:
|
||||
v: { la: "Benedícite, gentes, Deum nostrum.", en: "O bless our God, ye people." }
|
||||
r: { la: "Et audítam fácite vocem laudis ejus.", en: "And make the voice of his praise to be heard." }
|
||||
- psalms: [{ number: 68, verses: "2-16" }, { number: 68, verses: "17-37" }]
|
||||
antiphon:
|
||||
la: "Quǽrite Dóminum * et vivet ánima vestra."
|
||||
@@ -204,9 +191,7 @@ wednesday:
|
||||
|
||||
thursday:
|
||||
# All 9 psalms are Daym4's own — no reslicing across weekday boundaries
|
||||
# here either, so grouping and versicle position are both unchanged from
|
||||
# the source (the versicle sits between the "78" and "79,80" groups,
|
||||
# exactly as Daym4 itself has it).
|
||||
# here either, so the grouping is unchanged from the source.
|
||||
groups:
|
||||
- psalms: [{ number: 76 }]
|
||||
antiphon:
|
||||
@@ -220,9 +205,6 @@ thursday:
|
||||
antiphon:
|
||||
la: "Propítius esto * peccátis nostris, Dómine."
|
||||
en: "Forgive us our sins * O Lord."
|
||||
versicle:
|
||||
v: { la: "Deus, in sancto via tua.", en: "Thy way, O God, is in the holy place." }
|
||||
r: { la: "Quis Deus magnus sicut Deus noster.", en: "Who is the great God like our God?" }
|
||||
- psalms: [{ number: 79 }]
|
||||
# New (2026-09-01): no real antiphon found elsewhere in the corpus
|
||||
# for Ps 79 alone; composed from its own opening verse (79:2).
|
||||
@@ -254,10 +236,7 @@ friday:
|
||||
# elsewhere, so it's kept here on 93 alone; likewise 95's antiphon
|
||||
# originally also covered 96, which the redistribution's own documented
|
||||
# hand-adjustment moved to Saturday to keep both halves of Ps 103
|
||||
# together there); 101-103 are Daym6_'s own opening (4 of 9). The
|
||||
# versicle sits between the "93" and "95" groups, exactly where Daym5_
|
||||
# itself has it (originally between "92/93" and "95/96" — 96 leaving for
|
||||
# Saturday doesn't touch this boundary).
|
||||
# together there); 101-103 are Daym6_'s own opening (4 of 9).
|
||||
groups:
|
||||
- psalms: [{ number: 85 }]
|
||||
antiphon:
|
||||
@@ -271,9 +250,6 @@ friday:
|
||||
antiphon:
|
||||
la: "Exaltáre, * qui júdicas terram."
|
||||
en: "Lift up thyself, * thou that judgest the earth."
|
||||
versicle:
|
||||
v: { la: "Intret orátio mea in conspéctu tuo, Dómine.", en: "Let my prayer come before Thee, O Lord." }
|
||||
r: { la: "Inclína aurem tuam ad precem meam.", en: "Incline Thine ear unto my cry." }
|
||||
- psalms: [{ number: 95 }]
|
||||
antiphon:
|
||||
la: "Cantáte Dómino, * et benedícite nómini ejus."
|
||||
@@ -297,9 +273,7 @@ saturday:
|
||||
# Ps 96 is Daym5_'s own tail (moved here by the redistribution's own
|
||||
# documented hand-adjustment, swapped out of Friday to let both halves
|
||||
# of Ps 103 land there instead); 104-108 are Daym6_'s own tail (8 of 9 —
|
||||
# the majority, hence Daym6_'s own versicle here). The versicle sits
|
||||
# between the "104ab" and "105ab" groups, exactly where Daym6_ itself
|
||||
# has it — nothing moved out from between those two groups.
|
||||
# the majority).
|
||||
groups:
|
||||
- psalms: [{ number: 96 }]
|
||||
antiphon:
|
||||
@@ -309,9 +283,6 @@ saturday:
|
||||
antiphon:
|
||||
la: "Lætétur cor * quæréntium Dóminum."
|
||||
en: "Let the heart of them rejoice, * that seek the Lord."
|
||||
versicle:
|
||||
v: { la: "Dómine, exáudi oratiónem meam.", en: "O Lord, hear my prayer." }
|
||||
r: { la: "Et clamor meus ad te véniat.", en: "And let my cry come unto thee." }
|
||||
- psalms: [{ number: 105, verses: "1-31" }, { number: 105, verses: "32-48" }]
|
||||
antiphon:
|
||||
la: "Vísita nos, * Dómine, in salutári tuo."
|
||||
|
||||
+39
-33
@@ -3,8 +3,8 @@
|
||||
// Builds its ordo programmatically per day rather than resolving a static
|
||||
// `data/hours/matins.yml` parts list the way every other hour does, because
|
||||
// the real shape (1 nocturn on a plain ferial day vs. 3 on a Sunday or a
|
||||
// Duplex+ feast, with a *variable* number of readings) doesn't fit that
|
||||
// pattern.
|
||||
// Semiduplex+ feast, with a *variable* number of readings) doesn't fit
|
||||
// that pattern.
|
||||
//
|
||||
// Critical framing (see memory `vu-not-a-reconstruction` / `vu-matins-
|
||||
// design`, and TODO.md's own Matins section): this is NOT a historical
|
||||
@@ -76,7 +76,7 @@ import { getPsalmVerses } from '../psalter';
|
||||
import { getPsalmsFor, type PsalmRef } from '../psalter/distribution';
|
||||
import { getScriptureVerses } from '../scripture';
|
||||
import { getOpeningVersicleId } from './opening-versicle';
|
||||
import { isDoubleOrHigher, applyFlexaMark } from './antiphon';
|
||||
import { applyFlexaMark } from './antiphon';
|
||||
import {
|
||||
resolveCommon,
|
||||
getDayCollect,
|
||||
@@ -208,7 +208,6 @@ function sundayNocturnFor(nocturnKey: 'nocturn1' | 'nocturn2' | 'nocturn3', day:
|
||||
interface FerialGroup {
|
||||
psalms: PsalmRef[];
|
||||
antiphon: BilingualText;
|
||||
versicle?: { v: BilingualText; r: BilingualText };
|
||||
}
|
||||
interface FerialNocturn {
|
||||
groups: FerialGroup[];
|
||||
@@ -434,12 +433,14 @@ function resolveMatinsHymn(day: LiturgicalDay): ResolvedText {
|
||||
|
||||
/** The plain ferial weekday nocturn's real antiphoned psalmody (see
|
||||
* data/hours/matins-ferial-antiphons.yml's own header for how each
|
||||
* weekday's groups/versicle were reconciled against psalter-
|
||||
* distribution.yml's own "editorial redistribution" of the historical
|
||||
* per-weekday psalm set). Every psalm-group carries its own antiphon
|
||||
* (incipit-or-full opening, same rank rule as Sunday's own nocturns), and
|
||||
* exactly one group per weekday carries a versicle+responsory, rendered
|
||||
* as one combined V./R. block via `versicleText`. */
|
||||
* weekday's groups were reconciled against psalter-distribution.yml's own
|
||||
* "editorial redistribution" of the historical per-weekday psalm set).
|
||||
* Every psalm-group carries its own antiphon (incipit-or-full opening,
|
||||
* same rank rule as Sunday's own nocturns). No versicle: an earlier
|
||||
* version of this ported a mid-list V./R. line embedded in the source
|
||||
* `Psalmi matutinum.txt`, but that line is live-verified inert there — the
|
||||
* real single-nocturn ferial office runs straight from the last psalm to
|
||||
* the Capitulum (fixed 2026-09-02, see the data file's own header). */
|
||||
function ferialAntiphonedNocturn(day: LiturgicalDay): ResolvedPart[] {
|
||||
const nocturn = ferialAntiphons[day.weekday as Exclude<Weekday, 'sunday'>];
|
||||
const winner = resolveOfficeWinner(day);
|
||||
@@ -459,22 +460,20 @@ function ferialAntiphonedNocturn(day: LiturgicalDay): ResolvedPart[] {
|
||||
});
|
||||
});
|
||||
parts.push({ kind: 'antiphon', text: full });
|
||||
if (group.versicle) {
|
||||
parts.push({ kind: 'versicle', text: versicleText(group.versicle) });
|
||||
}
|
||||
}
|
||||
return parts;
|
||||
}
|
||||
|
||||
/** Fallback for a Duplex+ weekday feast with no matins-psalmody-overrides
|
||||
/** Fallback for a Semiduplex+ weekday feast with no matins-psalmody-overrides
|
||||
* entry authored for its winner yet: the plain ferial weekday table,
|
||||
* chunked into 3 nocturns instead of 1. Bare (no antiphons, no canticles)
|
||||
* when `common` isn't given — genuinely nothing authored at all for this
|
||||
* winner, still used as-is for a duplex-majus+ winner with no Common
|
||||
* category either.
|
||||
*
|
||||
* When `common`/`commonId` *are* given (a duplex-rank winner whose Common
|
||||
* category exists — user, 2026-08), each nocturn borrows that category's
|
||||
* When `common`/`commonId` *are* given (a Semiduplex- or Duplex-rank
|
||||
* winner whose Common category exists — user, 2026-08, threshold lowered
|
||||
* to Semiduplex 2026-09-02), each nocturn borrows that category's
|
||||
* own `nocturn{N}.versicle`: the ferial table only ever authors one
|
||||
* versicle for the whole (normally 1-nocturn) day, so a 3-nocturn split
|
||||
* leaves 2 of the 3 nocturns with none at all — Common's own 3 real
|
||||
@@ -548,8 +547,8 @@ function withSingleAntiphon(refs: PsalmRef[], antiphonText: ResolvedText, winner
|
||||
* gathered for `day` — the office winner (if sanctoral), every
|
||||
* commemorated saint (a transferred-in feast already appears as `day.winner`
|
||||
* once `resolveDay` has applied the transfer, so it needs no separate
|
||||
* lookup here), and — only on a real 3-nocturn day (Sunday, or a Duplex+
|
||||
* feast) — the plain temporal id itself (that Sunday's own Moralia-in-
|
||||
* lookup here), and — only on a real 3-nocturn day (Sunday, or a
|
||||
* Semiduplex+ feast) — the plain temporal id itself (that Sunday's own Moralia-in-
|
||||
* Job-style commentary) plus, for dates from the 1st Sunday of August
|
||||
* through the eve of Advent, the calendar-month/week id (`month-week-<id>`,
|
||||
* calendar/month-week-id.ts's monthWeekId): the real historical Nocturn 2
|
||||
@@ -735,12 +734,17 @@ export function resolveOrdo(date: string): ResolvedOrdo {
|
||||
const day = resolveDay(date);
|
||||
const winner = resolveOfficeWinner(day);
|
||||
const temporalId = resolveTemporalId(date);
|
||||
// Every Sunday, unconditionally, or a Duplex+ sanctoral winner — the
|
||||
// user's own chosen threshold (2026-08), not gated on whether any
|
||||
// content is actually authored yet, same "eligible, not content-gated"
|
||||
// convention every other per-feast override in this app already uses
|
||||
// (see hours/resolve-common.ts's getOfficeOverrideId).
|
||||
const threeNocturns = day.weekday === 'sunday' || isDoubleOrHigher(winner);
|
||||
// Every Sunday, unconditionally, or a Semiduplex+ sanctoral winner — the
|
||||
// user's own chosen threshold (2026-08, lowered from Duplex to
|
||||
// Semiduplex 2026-09-02), not gated on whether any content is actually
|
||||
// authored yet, same "eligible, not content-gated" convention every
|
||||
// other per-feast override in this app already uses (see
|
||||
// hours/resolve-common.ts's getOfficeOverrideId). Deliberately a
|
||||
// separate check from `isDoubleOrHigher` (antiphon.ts) — that function
|
||||
// drives the unrelated antiphon-doubling rule (full vs. incipit antiphon
|
||||
// before the psalms) and stays floored at Duplex; the two thresholds
|
||||
// only coincided before this change by accident, not by shared meaning.
|
||||
const threeNocturns = day.weekday === 'sunday' || (winner.kind === 'sanctoral' && isAtLeast(winner.rank, 'semiduplex'));
|
||||
const pool = buildReadingPool(day, temporalId, date, threeNocturns);
|
||||
const [nocturn1Readings, nocturn2Readings, nocturn3Readings] = distributeIntoNocturns(pool, threeNocturns ? 3 : 1);
|
||||
|
||||
@@ -776,19 +780,21 @@ export function resolveOrdo(date: string): ResolvedOrdo {
|
||||
parts.push({ kind: 'te-deum', text: resolveCommon('te-deum') });
|
||||
parts.push({ kind: 'versicle', text: resolveCommon('te-decet-laus') });
|
||||
} else if (threeNocturns) {
|
||||
// A Duplex+ weekday feast — genuinely different psalmody from a real
|
||||
// Sunday's, not the same content reused.
|
||||
// A Semiduplex+ weekday feast — genuinely different psalmody from a
|
||||
// real Sunday's, not the same content reused.
|
||||
// - Saint-specific proper: use its own full scheme, any rank.
|
||||
// - No proper, rank duplex-majus+: Common's own full scheme (own
|
||||
// psalm numbers, canticles) if authored; else the fully bare
|
||||
// ferialPsalmodyThreeNocturns stand-in. Unaffected by the
|
||||
// duplex-and-below carve-out below (user, 2026-08).
|
||||
// - No proper, rank exactly duplex (the floor of 3-nocturn
|
||||
// eligibility): ferial psalm numbers, not Common's own — but
|
||||
// Common's per-nocturn versicle, and each nocturn's own Terce/
|
||||
// Sext/None antiphon for that category, if authored (see
|
||||
// ferialPsalmodyThreeNocturns's own doc comment for why those
|
||||
// three, not Common's own Matins Nocturn antiphons).
|
||||
// below-duplex-majus carve-out below (user, 2026-08).
|
||||
// - No proper, rank Semiduplex or Duplex (below the floor of the
|
||||
// duplex-majus+ tier, but at or above the floor of 3-nocturn
|
||||
// eligibility itself, lowered to Semiduplex 2026-09-02): ferial
|
||||
// psalm numbers, not Common's own — but Common's per-nocturn
|
||||
// versicle, and each nocturn's own Terce/Sext/None antiphon for
|
||||
// that category, if authored (see ferialPsalmodyThreeNocturns's
|
||||
// own doc comment for why those three, not Common's own Matins
|
||||
// Nocturn antiphons).
|
||||
const properOverride = winner.kind === 'sanctoral' ? getMatinsSaintOverride(winner.id) : undefined;
|
||||
const isAboveDuplex = winner.kind === 'sanctoral' && isAtLeast(winner.rank, 'duplex-majus');
|
||||
const commonOverride = winner.kind === 'sanctoral' && !properOverride ? getMatinsCommonOverride(winner.id) : undefined;
|
||||
|
||||
@@ -48,7 +48,7 @@ describe('resolveOrdo("matins", ...) ferial (1-nocturn) branch', () => {
|
||||
expect(ordo.parts.some((p) => p.kind === 'versicle' && p.text.text.la?.includes('Te decet laus'))).toBe(false);
|
||||
});
|
||||
|
||||
it("gives every nocturn psalm its own antiphon and includes Tuesday's own versicle (data/hours/matins-ferial-antiphons.yml)", () => {
|
||||
it('gives every nocturn psalm its own antiphon and has no mid-nocturn versicle (data/hours/matins-ferial-antiphons.yml)', () => {
|
||||
// The nocturn's own psalms start at index 5 (after the two opening versicles/Ps3/heading/Ps94).
|
||||
const nocturnPsalms = ordo.parts.slice(5).filter((p) => p.kind === 'psalm') as {
|
||||
psalmNumber: number;
|
||||
@@ -65,11 +65,11 @@ describe('resolveOrdo("matins", ...) ferial (1-nocturn) branch', () => {
|
||||
expect(p.antiphon.text.en).toBeTruthy();
|
||||
}
|
||||
}
|
||||
// No versicle anywhere in the psalmody (fixed 2026-09-02) — the
|
||||
// source's embedded mid-list V./R. line is live-verified inert; the
|
||||
// real single-nocturn ferial office runs straight to the Capitulum.
|
||||
const nocturnVersicles = ordo.parts.slice(5).filter((p) => p.kind === 'versicle');
|
||||
expect(nocturnVersicles).toHaveLength(1);
|
||||
const versicleText = (nocturnVersicles[0] as { text: { text: Record<string, string> } }).text.text;
|
||||
expect(versicleText.la).toContain('Ímmola Deo sacrifícium laudis');
|
||||
expect(versicleText.la).toContain('Et redde Altíssimo vota tua');
|
||||
expect(nocturnVersicles).toHaveLength(0);
|
||||
});
|
||||
|
||||
it("resolves the user's own bible-plan readings for the day, not the historical lectionary", () => {
|
||||
@@ -527,6 +527,49 @@ describe('resolveOrdo("matins", ...) Duplex+ weekday-feast (3-nocturn, non-Sunda
|
||||
});
|
||||
});
|
||||
|
||||
describe('resolveOrdo("matins", ...) Semiduplex weekday-feast (3-nocturn threshold lowered 2026-09-02)', () => {
|
||||
// St. Stephen of Hungary (Semiduplex, Common of a Confessor Not a
|
||||
// Bishop, no proper Matins antiphon of his own) -- 2026-09-02, a
|
||||
// Wednesday. Threshold was Duplex-or-higher before this fix, so a
|
||||
// Semiduplex winner used to fall all the way through to the plain
|
||||
// 1-nocturn ferial branch; the user's own design intent (2026-09-02) is
|
||||
// that Semiduplex and Duplex share the same 3-nocturn/3-psalms-each
|
||||
// shape, distinct from the 6/6/3-plus-canticles Sunday/Duplex-majus+
|
||||
// scheme. Reuses the same already-working ferialPsalmodyThreeNocturns
|
||||
// branch a plain-Duplex winner gets (see the describe block above) --
|
||||
// no new content authoring was needed for this fix.
|
||||
const ordo = resolveOrdo('matins', '2026-09-02');
|
||||
|
||||
it('splits the ferial 9-psalm pool into 3 nocturns of 3 each, plus a Te Deum, not the 1-nocturn ferial branch', () => {
|
||||
const psalms = ordo.parts.filter((p) => p.kind === 'psalm').map((p) => (p as { psalmNumber: number }).psalmNumber);
|
||||
expect(psalms).toEqual([3, 94, 65, 67, 67, 68, 68, 70, 71, 72, 73]);
|
||||
expect(ordo.parts.some((p) => p.kind === 'te-deum')).toBe(true);
|
||||
// No canticles -- reserved for Sunday/Duplex-majus+, not this tier.
|
||||
expect(ordo.parts.some((p) => p.kind === 'canticle')).toBe(false);
|
||||
});
|
||||
|
||||
it("uses Common-of-a-Confessor's own Terce/Sext/None antiphons and versicles, one distinct pair per nocturn", () => {
|
||||
const psalmParts = ordo.parts.filter((p) => p.kind === 'psalm') as { psalmNumber: number; antiphon?: { text: Record<string, string>; status: Record<string, string> } }[];
|
||||
const nocturnPsalms = psalmParts.filter((p) => ![3, 94].includes(p.psalmNumber));
|
||||
expect(nocturnPsalms[0]?.antiphon?.text.la).toContain('Euge, serve bone');
|
||||
expect(nocturnPsalms[3]?.antiphon?.text.la).toContain('Fidélis servus');
|
||||
expect(nocturnPsalms[6]?.antiphon?.text.la).toContain('Serve bone');
|
||||
for (const i of [0, 3, 6]) {
|
||||
expect(nocturnPsalms[i]?.antiphon?.status.la).toBe('verified');
|
||||
expect(nocturnPsalms[i]?.antiphon?.status.en).toBe('verified');
|
||||
}
|
||||
const versicles = ordo.parts.filter((p) => p.kind === 'versicle') as { text: { text: Record<string, string> } }[];
|
||||
// The 3 nocturn-closing versicles (excluding the opening pair and the
|
||||
// final Te decet laus) are Common-of-a-Confessor's own, one per
|
||||
// nocturn -- not a stray mid-psalmody line.
|
||||
const nocturnVersicles = versicles.slice(2, 5);
|
||||
expect(nocturnVersicles).toHaveLength(3);
|
||||
expect(nocturnVersicles[0]?.text.text.la).toContain('Amávit eum Dóminus');
|
||||
expect(nocturnVersicles[1]?.text.text.la).toContain('Os justi meditábitur');
|
||||
expect(nocturnVersicles[2]?.text.text.la).toContain('Lex Dei ejus in corde ipsíus');
|
||||
});
|
||||
});
|
||||
|
||||
describe('resolveOrdo("matins", ...) second Duplex+ weekday-feast proof — St. Andrew, Common of an Apostle', () => {
|
||||
// 2026-11-30 -- St. Andrew's own day outright ("S. Andreæ Apostoli",
|
||||
// not transferred that year: Nov 30 falls on a Monday, not a Sunday),
|
||||
|
||||
Reference in New Issue
Block a user