Add St. Joseph's suffrage; fix Cross/BVM suffrage gating
Deploy / deploy (push) Successful in 51s

Three things prompted by today's date resolving wrong:

1. St. Joseph's own Lauds suffrage was never modeled at all. Added
   lauds-suffrage-joseph.yml, sourced from Tridentine 1906/1910 (the
   reference engine's "Tridentine - 1906" version) per direct
   instruction -- Monastic 1617's own suffrage set has no Joseph
   suffrage at Lauds, so this one genuinely comes from a different track
   than the other four.

2. The Cross suffrage's actual gating rule, live-verified against a
   plain ferial win, a plain Sunday win, a low-rank saint's own win (even
   bare Simplex), and Marian Saturday: it shows only when the bare
   temporal feria itself is what's being prayed -- the opposite of
   "Sunday or a feast of the Lord." Expressed as `!isSundayOrFeast(day)`
   (the same ferial/festive split Prime's capitulum already uses) plus
   one more exclusion for a named temporal identity like Marian Saturday,
   which isn't Sunday or a sanctoral win either but still isn't a bare
   ferial office.

3. The BVM suffrage is correctly dropped on Marian Saturday (unchanged
   behavior, just now understood and commented correctly): it's specific
   to a day whose own office is already Marian, confirmed via the same
   live source, not a guess.

Also models St. Clare (Aug 12, Simplex under Monastic 1617) and surfaces
active octaves in getDayLabel, since today's date was resolving as a
plain ferial day instead of showing St. Lawrence's octave with St. Clare
commemorated.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-12 06:51:22 -04:00
parent 5f32a3751c
commit fb66f60770
5 changed files with 88 additions and 16 deletions
+7 -2
View File
@@ -272,8 +272,13 @@ real `draft` status went through it.
complete "Let us pray ... Amen." block, not the more compressed real form
(one "Let us pray," only the last collect's doxology).
- Suffrage omission only checks `omitOnDouble` (+ Marian Saturday's own
Cross/BVM filter, + Christ the King's explicit id check). Real seasonal
suppression (e.g. during Advent/Lent) isn't modeled.
Cross/BVM filter, + Christ the King's explicit id check, + the Cross-only
ferial-office check added 2026-08). Real seasonal suppression is still
missing: per the Tridentine 1906/1910 source (`specials.pl`'s
`checksuffragium`), suffrages are omitted *entirely* during Advent,
Christmastide, and Passiontide, and whenever any octave is active — none
of that is modeled, so e.g. an Advent feria currently still shows all
five suffrages when the real office would have none.
- `data/calendar/temporal-categories.yml`'s season→category reconstruction
is from general knowledge of the pre-1955 tradition, not independently
verified against a primary source for every entry (flagged inline where
@@ -0,0 +1,27 @@
# Verified against Divinum Officium (Tridentine 1910, i.e. the version
# labeled "Tridentine - 1906" in the reference engine's own picker), Lauds
# live query, clean ferial day 2026-06-17. Sourced from a different
# rubric track than the other four suffrages (Monastic Tridentinum 1617,
# which doesn't carry a St. Joseph suffrage at Lauds at all) per direct
# instruction -- see lauds-suffrage-cross.yml's header for the set this
# joins, and calendar/lauds.ts's suffrages case for the fuller sourcing
# story and how this one differs from the other four in when it's shown
# (never omitted, only the BVM suffrage is, and only when the day's own
# office is already Marian).
id: lauds-suffrage-joseph
text:
la: |
Ant. Ipse Jesus erat incípiens quasi annórum trigínta ut putabátur fílius Joseph.
V. Os justi meditábitur sapiéntiam.
R. Et lingua ejus loquétur judícium.
Orémus.
Deus, qui ineffábili providéntia beátum Joseph sanctíssimæ Genitrícis tuæ sponsum elígere dignátus es: præsta quǽsumus; ut quem protectórem venerámur in terris, intercessórem habére mereámur in cælis.
en: |
Ant. Jesus himself began to be about thirty years of age, being (as was supposed) the son of Joseph.
V. The mouth of the righteous speaketh wisdom.
R. And his tongue talketh judgment.
Let us pray.
O God, who, in thine unspeakable foreknowledge, didst choose thy blessed servant Joseph to be the husband of thine own most holy Mother; mercifully grant that now that he is in heaven with thee, we who on earth do reverence him for our defender, may worthily be holpen by the succour of his prayers to thee on our behalf.
status:
la: verified
en: verified
+37 -11
View File
@@ -1,7 +1,8 @@
import type { HourDefinition, HourPart, ResolvedOrdo, ResolvedPart, ResolvedText } from './types';
import type { LiturgicalDay, Weekday } from '../calendar/types';
import { resolveDay, isAtLeast } from '../calendar';
import { resolveDay, isAtLeast, isSundayOrFeast } from '../calendar';
import { getDayLabel } from '../calendar/day-label';
import { getTemporalFeastRecord } from '../calendar/temporal-feasts';
import { getPsalmVerses } from '../psalter';
import { getCanticle } from './lauds-canticles';
import { getLaudsPsalmodyOverride, type LaudsPsalmodyOverride } from './lauds-psalmody-overrides';
@@ -200,10 +201,17 @@ function resolveOffice(day: LiturgicalDay): ResolvedPart[] {
];
}
const SUFFRAGES = ['lauds-suffrage-cross', 'lauds-suffrage-bvm', 'lauds-suffrage-apostles', 'lauds-suffrage-peace'];
const SUFFRAGES = [
'lauds-suffrage-cross',
'lauds-suffrage-bvm',
'lauds-suffrage-joseph',
'lauds-suffrage-apostles',
'lauds-suffrage-peace',
];
const SUFFRAGE_LABELS: Record<string, string> = {
'lauds-suffrage-cross': 'Of the Holy Cross',
'lauds-suffrage-bvm': 'Of the Blessed Virgin Mary',
'lauds-suffrage-joseph': 'Of St. Joseph',
'lauds-suffrage-apostles': 'Of the Holy Apostles Peter and Paul',
'lauds-suffrage-peace': 'For Peace',
};
@@ -239,15 +247,33 @@ function resolvePart(part: HourPart, day: LiturgicalDay): ResolvedPart[] {
if (part.omitOnDouble && (isDoubleOrHigher(day.winner) || isChristTheKing)) {
return [];
}
// Confirmed live: Our Lady's Saturday drops both "Of the Holy Cross"
// and "Of the Blessed Virgin Mary" (the latter redundant with the
// day's own theme; the former for reasons the reference engine
// doesn't explain and this doesn't try to re-derive), keeping only
// the Apostles and Peace suffrages.
const ids =
day.winner.kind === 'temporal' && day.winner.id === 'marian-saturday'
? SUFFRAGES.filter((id) => id !== 'lauds-suffrage-cross' && id !== 'lauds-suffrage-bvm')
: SUFFRAGES;
// "Of the Holy Cross" is the real *ferial*-office suffrage, sourced
// from Tridentine 1906/1910 (a different track than the other four,
// which come from Monastic 1617 -- that track has no Cross suffrage
// at Lauds at all) -- live-verified against a plain ferial win, a
// plain Sunday win, a low-rank saint's own win (even bare Simplex),
// and Marian Saturday: Cross shows only when the bare temporal
// feria itself is what's being prayed -- the exact *opposite* of
// "Sunday or a feast of the Lord." In this app's own vocabulary,
// that's `!isSundayOrFeast(day)` (the same ferial/festive split
// Prime's capitulum already keys off) with one more exclusion for a
// named temporal identity like Marian Saturday, which isn't Sunday
// or a sanctoral winner either but still isn't a bare ferial office.
const showCross = !isSundayOrFeast(day) && !(day.winner.kind === 'temporal' && getTemporalFeastRecord(day.winner.id));
// "Of the Blessed Virgin Mary" is omitted specifically when the
// day's own office is already Marian (would be redundant to
// suffrage Mary again on her own day) -- confirmed live on Marian
// Saturday (Simplex-strength, so it reaches this far rather than
// being excluded outright above); the only case this app currently
// has of a day using the Marian common below the suffrage-omitting
// Duplex+ threshold (every actual Marian *feast* modeled so far is
// Duplex-majus+ and already returns above).
const isMarianSaturday = day.winner.kind === 'temporal' && day.winner.id === 'marian-saturday';
const ids = SUFFRAGES.filter((id) => {
if (id === 'lauds-suffrage-cross') return showCross;
if (id === 'lauds-suffrage-bvm') return !isMarianSaturday;
return true;
});
return ids.map((id) => ({
kind: 'preces' as const,
text: resolveCommon(id),
+2 -2
View File
@@ -46,10 +46,10 @@ describe('Lauds psalmody override (duplex-majus+ sanctoral, and Marian Saturday)
expect(collect?.kind === 'prayer' ? collect.text.text.en : undefined).toContain('glorious intercession of the Blessed Mary');
});
it("drops the Holy Cross and Blessed Virgin Mary suffrages on Our Lady's Saturday, keeping Apostles and Peace", () => {
it("drops the Holy Cross and Blessed Virgin Mary suffrages on Our Lady's Saturday (the latter redundant with the day's already-Marian office; the former because Marian Saturday isn't a bare ferial office either), keeping Joseph, Apostles, and Peace", () => {
const ordo = resolveOrdo('lauds', '2026-10-24');
const labels = ordo.parts.filter((p) => p.kind === 'preces' && p.label).map((p) => (p.kind === 'preces' ? p.label : undefined));
expect(labels).toEqual(['Of the Holy Apostles Peter and Paul', 'For Peace', 'Salve Regina']);
expect(labels).toEqual(['Of St. Joseph', 'Of the Holy Apostles Peter and Paul', 'For Peace', 'Salve Regina']);
});
it("substitutes Christ the King's own proper psalmody and office bundle, and omits the suffrages entirely (Duplex I. classis)", () => {
+15 -1
View File
@@ -187,18 +187,32 @@ describe('resolveOrdo("lauds", ...)', () => {
expect(collects.length).toBe(1);
});
it('includes all four Tridentine suffrages, in order, on an ordinary day', () => {
it('includes all five suffrages, in order, on an ordinary ferial day -- Cross from Tridentine 1906/1910, the other four from Monastic 1617', () => {
const ordo = resolveOrdo('lauds', FERIAL_TUESDAY);
const labels = ordo.parts.filter((p) => p.kind === 'preces' && p.label).map((p) => (p.kind === 'preces' ? p.label : undefined));
expect(labels).toEqual([
'Of the Holy Cross',
'Of the Blessed Virgin Mary',
'Of St. Joseph',
'Of the Holy Apostles Peter and Paul',
'For Peace',
'Salve Regina',
]);
});
it('drops "Of the Holy Cross" -- not a bare ferial office -- on a plain Sunday and on a low-rank saint\'s own winning day, unlike an ordinary feria', () => {
// St. Clare (Simplex) doesn't win outright (Lawrence's octave keeps
// her to a commemoration -- see calendar/index.ts's applyOctaves), so
// use St. Lawrence's own vigil day instead, or just check a Sunday.
const sunday = resolveOrdo('lauds', '2026-06-28'); // a plain temporal Sunday, nothing else contesting
const sundayLabels = sunday.parts
.filter((p) => p.kind === 'preces' && p.label)
.map((p) => (p.kind === 'preces' ? p.label : undefined));
expect(sundayLabels).not.toContain('Of the Holy Cross');
expect(sundayLabels).toContain('Of the Blessed Virgin Mary');
expect(sundayLabels).toContain('Of St. Joseph');
});
it('omits the suffrages on a Double-or-higher feast', () => {
const ordo = resolveOrdo('lauds', '2026-08-10'); // St. Lawrence, Duplex II. classis
const crossSuffrage = ordo.parts.find((p) => p.kind === 'preces' && p.label === 'Of the Holy Cross');