will f43f030a6a Fix ordinary-Sunday precedence and unify getDayLabel's commemoration rendering
Two related fixes surfaced while chasing why St. Gregory Thaumaturgus
appeared to be misdated on Nov 18:

- calendar/commemorations.ts's `ordinary-sunday` case was transferring a
  Semiduplex (or lower) saint off the Sunday instead of commemorating it
  in place — an earlier, unverified guess. Live-verified against both
  Tridentine 1906 and Divino Afflatu 1954 (St. Gregory Thaumaturgus, St.
  Clement, St. Apollinaris, St. Thomas Becket, all real cases): the saint
  stays and is commemorated on the Sunday itself, same as Simplex, never
  pushed to the next open day. This is what was actually moving Gregory
  onto Nov 18 — not a data error. `applyChristmasOctaveSunday`'s own
  Dec 26-29 special case needed a matching adjustment (per its own
  documented intent, the displaced saint there is deliberately *not*
  commemorated in place, since he reappears in full on Dec 30 instead).

- getDayLabel had five branches, each hand-assembling its own
  filter/format logic for which commemorations to show — which is why
  the octave phrasing, the closing-day title, and a missing Sunday
  commemoration turned into three separate bugs earlier instead of one.
  Replaced with a single shared `collectCommemorations` used by every
  branch. This also exposed that the sanctoral-winner branch never
  showed any commemorated saint at all, and every other branch only
  showed the *first* one (`.find()`), silently dropping real collisions
  — both now show every commemorated saint, matching this app's own
  generous-commemoration design.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-18 10:59:50 -04:00
2026-08-16 06:30:00 -04:00

vu

A static, offline-first PWA for praying the Divine Office — Matins, Lauds, Prime, Terce, Sext, None, Vespers, and Compline — in bilingual Latin/English, for any given date.

This isn't a reconstruction of any single historical breviary. It's a personal design that draws on several historical sources without reproducing any one of them exactly: hour structure chiefly from the Monastic Tridentinum (1617), precedence/rubrics from Divino Afflatu, and a calendar merged from both the Tridentine 1906 and Divino Afflatu calendars, with more generous handling of commemorations and octaves than any one historical rite used. See CLAUDE.md (gitignored, not shipped in this repo) or the project's own notes for the full rationale if you're extending it.

There's no backend and no database — everything is a pre-built static bundle, and the app is designed to keep working fully offline once it's been loaded once.

Development

Requires Node.js (developed against Node 20+).

npm install
npm run dev        # start the Vite dev server
npm run build       # type-check (tsc --noEmit) and produce a production build in dist/
npm run preview     # serve the production build locally
npm test            # run the test suite (vitest)

Run a single test file or a subset by name:

npx vitest run tests/hours/vespers.test.ts
npx vitest run -t "some test name"

Running in production

The app is entirely static once built, so "running it" just means serving dist/ — any static file host works, as long as it falls back to index.html for unknown paths (the app does its own client-side routing for URLs like /2026-08-09/prime).

Docker

A Dockerfile builds the static bundle with Node and serves it with Caddy:

docker build -t vu .
docker run -p 8080:80 vu

Or with Docker Compose (docker-compose.yml is included):

docker compose up -d --build

This serves the app on http://localhost:8080. Put a reverse proxy/TLS terminator in front of it for a real deployment; the container itself just listens on port 80 internally and has no configuration, secrets, or external dependencies.

Without Docker

Any static file server works — build the app, then serve dist/ with SPA-style fallback routing. For example, with Caddy directly:

npm run build
caddy file-server --root dist --listen :8080

(For a real deployment you'll want a fallback rule so unknown paths resolve to index.html — see the Caddyfile in this repo for the pattern.)

S
Description
Divine Office / Liturgy of the Hours app (pre-1955-leaning calendar, modified Benedictine psalter, whole-Bible reading plan)
Readme 14 MiB
Languages
TypeScript 99%
CSS 0.9%