Prime: real content (psalms, Martyrology, Regula) and ordo corrections
Deploy / deploy (push) Failing after 21s

Pulls in verified content from Divinum Officium rather than placeholders:
17 psalms (2, 6, 7-19, 118, 129), 365 days of the Martyrology, and the full
121-reading Regula cycle, plus the Athanasian Creed.

Ordo corrections driven by review against the real engine output:
- Capitulum and Preces now pick a Sunday/feast vs. ferial form
  (calendar/isSundayOrFeast); ferial Preces said every ferial day by choice.
- Chapter responsory, hymn doxology, and the opening versicle's
  Alleluia/Laus tibi all vary by season via a shared resolver
  (hours/seasonal-propers.ts).
- Real Roman Kalends/Nones/Ides Latin dating (calendar/roman-date.ts,
  verified against 363/365 real Martyrology headings) plus the historical
  "bis sextus" Feb 29 handling, and the Martyrology's Luna (moon-day)
  heading (a ported Golden-Number calculation).
- Fixed responsory structure (was missing its initial full repeat),
  weekday psalm antiphons (opening as incipit-or-full by rank, full
  repeat after the psalms/Creed as its own part, "*" chant mark kept),
  scripture citations on the capitulum/lectio brevis, and V./R. markers
  switched from Unicode symbols to plain text for reliable font rendering.
- Dropped Pretiosa and the dead-commemoration psalm (129) for time;
  trimmed section headings down to the ones that are actually named
  things (Preces, Chapter Office, etc. no longer relabel connective text).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-09 20:03:53 -04:00
parent a5dc6502d7
commit 58958aa847
571 changed files with 21842 additions and 72 deletions
+54 -5
View File
@@ -24,34 +24,83 @@ function renderColumns(resolved: ResolvedText | ResolvedVerse, languages: readon
return `<div class="lang-columns lang-columns-${languages.length}">${cols}</div>`;
}
// Scripture quotations always carry a full citation (book, chapter, verse) —
// shown once, deduplicated across languages when they happen to match.
function renderCitation(text: ResolvedText): string {
if (!text.citation) {
return '';
}
const values = [...new Set(Object.values(text.citation).filter((v): v is string => Boolean(v)))];
return values.length ? `<p class="ordo-part-citation">${escapeHtml(values.join(' / '))}</p>` : '';
}
function renderPart(part: ResolvedPart, languages: readonly string[]): string {
switch (part.kind) {
case 'hymn':
case 'chapter':
case 'responsory':
case 'versicle':
case 'prayer':
return `
<section class="ordo-part ordo-part-${part.kind}">
<h3 class="ordo-part-label">${hourLabel(part.kind)}</h3>
${renderCitation(part.text)}
${renderColumns(part.text, languages)}
</section>
`;
case 'psalm':
// Short liturgical dialogue — reads fine without a jargon label.
case 'responsory':
case 'versicle':
return `
<section class="ordo-part ordo-part-${part.kind}">
${renderColumns(part.text, languages)}
</section>
`;
case 'antiphon':
return `
<section class="ordo-part ordo-part-antiphon">
${renderColumns(part.text, languages)}
</section>
`;
case 'preces':
return `
<section class="ordo-part ordo-part-preces">
${part.label ? `<h3 class="ordo-part-label">${escapeHtml(part.label)}</h3>` : ''}
${renderColumns(part.text, languages)}
</section>
`;
case 'lesson':
return `
<section class="ordo-part ordo-part-lesson">
<h3 class="ordo-part-label">${part.label ? escapeHtml(part.label) : 'Reading'}</h3>
${renderCitation(part.text)}
${renderColumns(part.text, languages)}
</section>
`;
case 'psalm': {
// Always cite the full range, not just the psalm number — a 4-verse
// slice of Psalm 118 is a different citation from the whole psalm.
const first = part.verses[0]?.n;
const last = part.verses[part.verses.length - 1]?.n;
const citation =
first === undefined
? `Psalm ${part.psalmNumber}`
: first === last
? `Psalm ${part.psalmNumber}:${first}`
: `Psalm ${part.psalmNumber}:${first}-${last}`;
return `
<section class="ordo-part ordo-part-psalm">
<h3 class="ordo-part-label">Psalm ${part.psalmNumber}</h3>
${part.antiphon ? renderColumns(part.antiphon, languages) : ''}
<h3 class="ordo-part-label">${citation}</h3>
<ol class="psalm-verses">
${part.verses.map((v) => `<li class="psalm-verse">${renderColumns(v, languages)}</li>`).join('')}
</ol>
</section>
`;
}
case 'canticle':
return `
<section class="ordo-part ordo-part-canticle">
<h3 class="ordo-part-label">Canticle</h3>
${part.antiphon ? renderColumns(part.antiphon, languages) : ''}
<h3 class="ordo-part-label">Canticle</h3>
</section>
`;
}
+16
View File
@@ -261,6 +261,14 @@ button:focus-visible {
margin: 0 0 var(--space-1);
}
.ordo-part-citation {
font-family: var(--font-ui);
font-size: 0.85em;
font-style: italic;
color: var(--color-brass);
margin: 0 0 var(--space-1);
}
.psalm-verses {
list-style: none;
margin: 0;
@@ -290,6 +298,14 @@ button:focus-visible {
}
}
.lang-column {
/* Hymn stanzas, Preces, and other multi-line propers are authored with
meaningful line breaks (poetry lines, one versicle/response per line) —
preserve them instead of letting HTML collapse everything to one run-on
line, while still wrapping normally on narrow screens (unlike `pre`). */
white-space: pre-line;
}
.lang-column[lang='la'] {
font-style: italic;
}