Build Matins' real dynamic ordo, wire into the UI

hours/matins.ts no longer a stub: resolves a full ordo assembled
programmatically per day rather than a static parts list, since the real
structure (1 nocturn ferial vs. 3 on a Sunday or Duplex+ feast, a
variable number of readings) doesn't fit the pattern every other hour
uses. Several deliberate departures from the historical office, all per
direct instruction (see TODO.md's own writeup): Nocturn 1 always draws
from the user's bible-plan rather than the historical lectionary and
never contracts for "summer"; split historical lessons are recombined
except where the source genuinely changes; Nocturn 3 is gated at
Duplex-and-higher plus every Sunday; every commemorated saint and active
octave contributes its own reading, not just the office winner; a Gospel
reading is sourced only from the user's plan or the day's own proper
content, never a Common-of-Saints fallback; the Invitatory is framed like
an ordinary antiphoned psalm rather than the historical repeating
refrain.

ui/hour-view.ts renders the two new part kinds (nocturn-psalmody,
te-deum) and the extended lesson shape (responsory, Gospel flag).
This commit is contained in:
2026-08-14 11:40:43 -04:00
parent 83bf55efdb
commit bedbf577ea
2 changed files with 281 additions and 8 deletions
+21 -2
View File
@@ -69,10 +69,11 @@ function renderPart(part: ResolvedPart, languages: readonly string[]): string {
`;
case 'lesson':
return `
<section class="ordo-part ordo-part-lesson">
<h3 class="ordo-part-label">${part.label ? escapeHtml(part.label) : 'Reading'}</h3>
<section class="ordo-part ordo-part-lesson${part.isGospel ? ' ordo-part-lesson-gospel' : ''}">
<h3 class="ordo-part-label">${part.isGospel ? 'Gospel' : part.label ? escapeHtml(part.label) : 'Reading'}</h3>
${renderCitation(part.text)}
${renderColumns(part.text, languages)}
${part.responsory ? `<div class="ordo-part-responsory">${renderColumns(part.responsory, languages)}</div>` : ''}
</section>
`;
case 'psalm': {
@@ -110,6 +111,24 @@ function renderPart(part: ResolvedPart, languages: readonly string[]): string {
</section>
`;
}
// Matins only. Not currently emitted by hours/matins.ts (its psalms
// render as a flat sequence of plain 'psalm'/'antiphon'/'versicle'
// parts instead) — handled here for the ResolvedPart union's sake,
// ready for whenever nocturn-grouped UI treatment is wanted.
case 'nocturn-psalmody':
return `
<section class="ordo-part ordo-part-nocturn-psalmody">
<h3 class="ordo-part-label">Nocturn ${part.nocturn}</h3>
${part.psalms.map((p) => renderPart(p, languages)).join('')}
</section>
`;
case 'te-deum':
return `
<section class="ordo-part ordo-part-te-deum">
<h3 class="ordo-part-label">Te Deum</h3>
${renderColumns(part.text, languages)}
</section>
`;
}
}