Centralize antiphon opening-doubling logic across all hours

The same idiom (splitNamedAntiphon + isDoubleOrHigher(...) ? full :
incipit) was copy-pasted at 11 call sites across 8 hour files. Add one
shared openingAntiphon(antiphon, winner) helper in resolve-common.ts
and use it everywhere a psalm/canticle antiphon's opening form is
decided. Pure dedup, no behavior change.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01F25189JqjXddUhU9hM9nUS
This commit is contained in:
2026-08-24 07:13:38 -04:00
parent f8de318b7f
commit 8f55ec6931
9 changed files with 55 additions and 36 deletions
+11 -7
View File
@@ -78,6 +78,7 @@ import {
seasonalOfficeSuffix,
verifiedText,
splitNamedAntiphon,
openingAntiphon,
} from './resolve-common';
import { getBiblePlanReadings } from '../propers/bible-plan';
import { getNocturnReadings, type NocturnReading } from '../propers/nocturn-readings';
@@ -187,8 +188,9 @@ function invitatoryParts(day: LiturgicalDay): ResolvedPart[] {
function sundayPsalmNocturn(group: SundayNocturn, day: LiturgicalDay): ResolvedPart[] {
const parts: ResolvedPart[] = [];
for (const g of group.groups ?? []) {
const { incipit, full } = splitNamedAntiphon(verifiedText(g.antiphon));
const opening = isDoubleOrHigher(resolveOfficeWinner(day)) ? full : incipit;
const antiphonText = verifiedText(g.antiphon);
const opening = openingAntiphon(antiphonText, resolveOfficeWinner(day));
const { full } = splitNamedAntiphon(antiphonText);
g.psalms.forEach((n, i) => {
parts.push(psalmPartWithAntiphon(n, i === 0 ? opening : undefined));
});
@@ -201,8 +203,9 @@ function sundayPsalmNocturn(group: SundayNocturn, day: LiturgicalDay): ResolvedP
/** Nocturn 3's 3 fixed OT canticles under one shared antiphon — see
* data/hours/matins-sunday-antiphons.yml's own header. */
function sundayCanticleNocturn(group: SundayNocturn, day: LiturgicalDay): ResolvedPart[] {
const { incipit, full } = splitNamedAntiphon(verifiedText(group.antiphon ?? {}));
const opening = isDoubleOrHigher(resolveOfficeWinner(day)) ? full : incipit;
const antiphonText = verifiedText(group.antiphon ?? {});
const opening = openingAntiphon(antiphonText, resolveOfficeWinner(day));
const { full } = splitNamedAntiphon(antiphonText);
const parts: ResolvedPart[] = (group.canticles ?? []).map((c, i) => {
const verses = c.refs.flatMap((ref) => getScriptureVerses(ref.book, ref.chapter, ref.verses));
const text: BilingualText = {
@@ -285,11 +288,12 @@ function ferialPsalmody(day: LiturgicalDay): ResolvedPart[] {
* as one combined V./R. block via `versicleText`. */
function ferialAntiphonedNocturn(day: LiturgicalDay): ResolvedPart[] {
const nocturn = ferialAntiphons[day.weekday as Exclude<Weekday, 'sunday'>];
const isDouble = isDoubleOrHigher(resolveOfficeWinner(day));
const winner = resolveOfficeWinner(day);
const parts: ResolvedPart[] = [];
for (const group of nocturn.groups) {
const { incipit, full } = splitNamedAntiphon(verifiedText(group.antiphon));
const opening = isDouble ? full : incipit;
const antiphonText = verifiedText(group.antiphon);
const opening = openingAntiphon(antiphonText, winner);
const { full } = splitNamedAntiphon(antiphonText);
group.psalms.forEach((ref, i) => {
const rawVerses = getPsalmVerses(ref.number, ref.verses).map((v): ResolvedVerse => ({ n: v.n, text: v.text, status: v.status }));
const { verses, antiphon } = applyFlexaMark(rawVerses, i === 0 ? opening : undefined);