Lower Matins 3-nocturn threshold from Duplex to Semiduplex
Deploy / deploy (push) Successful in 1m31s

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) — the previous Duplex floor meant a Semiduplex winner like
St. Stephen of Hungary wrongly fell all the way through to the
1-nocturn ferial branch.

New threshold is a separate check from isDoubleOrHigher, which drives
the unrelated antiphon-doubling rule elsewhere and stays floored at
Duplex — the two thresholds only coincided by accident before this
change. No new content authoring was needed: ferialPsalmodyThreeNocturns
already chunks the ferial 9-psalm pool into 3/3/3 as the fallback for
a plain-Duplex winner with no override authored, and Semiduplex
winners now route into that same already-working branch.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01X26BkFT3ARbtUGXuBmzUmD
This commit is contained in:
2026-09-02 07:07:12 -04:00
parent 9da5a74301
commit f4d4eb3738
3 changed files with 89 additions and 20 deletions
+43
View File
@@ -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),