LiturgyGen

Weekly reports

Newest at the top. Never edit an old one.


Week of 2026-10-06

Done. Week 3’s two deferred security items, which the week-2 checklist had named as “they become real the day the API sits on a public URL”. It did, so they did.

helmet is in, with the Content-Security-Policy written out rather than left at the default or switched off: styleSrc allows 'unsafe-inline' because Tailwind injects a stylesheet at run time, scriptSrc does not, and connect-src is derived from SUPABASE_URL instead of hardcoded. It has a test — server/test/csp.test.js — because the way a too-tight policy fails is the worst kind: the browser blocks every sign-in request before it leaves the page, fetch rejects with “Failed to fetch”, and nothing reaches a server log at all. There is no trace to debug from, so the policy gets an assertion instead. express-rate-limit is on /api at 600 requests a minute, applied after the gate so a signed-in office is counted separately from anonymous traffic at the door, and off under NODE_ENV=test.

The phone layout. The navbar’s four tabs move to a fixed bottom bar below 768px and each gains a mark; below 640px a day tile sheds everything that is not the date and the liturgical colour. The two bars are now one pair of custom properties, --app-header and --app-nav, because the day book has to clear both and the heights were previously literals written out in two files — a top-[60px] that was a silent promise about a number in App.jsx.

SECURITY-CHECKLIST.md was written against the announcement’s four sections, with the evidence command for every row.

What broke, and what it cost. Nothing broke. What went wrong is that the documentation stopped tracking the code, in both directions, and neither direction was visible from inside the repository:

Also. The demo video is recorded, and it is 6:13 against a 5:00 maximum. The cue sheet targeted 4:50, so the overrun is 1:23 and it is spread across all twelve slides rather than concentrated in one — the signature of reading slides aloud instead of talking over them. The existing cut list is worth about 1:05, so it does not close the gap on its own; docs/05 now says what else has to go and what must survive whatever happens, which is the demo, the code walk-through and the AI disclosure. An earlier attempt to measure the file reported a corrupt mvhd atom with timescale=0; the atom was fine and the parse offset was wrong.

Migrating the API to Vercel was investigated to kill the ~50s cold start and rejected: four separate pieces of process-level state make it impossible — the batch job queue in a Map, the SSE progress stream, the USCCB grace cookie, and the calendar cache. Each serverless invocation gets a fresh process, and the 10–15 minute batch runtime exceeds the function limit regardless. The cold start has four fixes that are not a migration, and none is chosen yet.

Checked, and left as it is. The week-of-09-22 report ended “Next. … Then the REST pass”, and the REST pass never happened. Looked at properly this week: server/src/routes/ is 904 lines over eight routers, resource-oriented, with GET/POST/PUT/DELETE doing what their names say and nested resources where there are nested resources (/api/settings/schedule/:date, /api/readings/:date/override). Six endpoints are action-shaped rather than resource-shaped — POST /api/readings/check, /api/calendar/expand, /api/potf/parse, /api/potf/:id/duplicate, /api/generate/preview and /api/batch/:id/cancel.

The week-2 report called those “remote procedure calls wearing HTTP”, and on reflection that was the wrong thing to be embarrassed about. “Check these eighteen dates and tell me which ones are missing a psalm response” is not a resource; nor is “cancel this job”. Forcing them into PUT /api/checks/{id} would invent a resource that nothing stores in order to satisfy a shape. The genuine defect the same paragraph named — routes/settings.js building a WHERE clause by string concatenation — is fixed, and that was the row worth acting on. So: no REST pass, and the reason is written down rather than the promise being quietly dropped.

Open. The Supabase Site URL still points at localhost:3000. --color-gold-edge still fails contrast as a graphic, and the reason it has not been a one-line fix is now written down: the token is doing two jobs and needs splitting. The prayer-book importer is still unrun against a real book. docs/assets/phone.png predates both the entry spread and the bottom tab bar, so it shows a layout that no longer exists. The video needs re-recording. 152 tests (138 server — 137 pass, 1 skipped — and 14 client).

Added 2026-10-11, same week. The main README was brought back in line with the code, and three of its claims had gone stale rather than merely thin: the header still sent readers to What I would do next for an API that has been on Render for a week, Architecture still ended “the API and the database are not hosted yet”, and What I would do next still promised the REST pass that this same report had already looked at and declined. The README also undercounted the action-shaped endpoints as four against the six counted here, and its AI-usage footer claimed “six worked examples … four cases where it was wrong” against the eight and ten actually in AI-USAGE.md — a duplicated paragraph that contradicted the corrected one three lines above it. The phone layout, helmet and the rate limit were in the code, in docs/02, docs/03 and this report, and nowhere in the README at all; they are now.

Re-measured while there: 28 of 58 commits carry the personal author email, not the 25 of 53 recorded on 2026-10-09. Three commits made today through the GitHub web interface account for the whole difference — committed after the row warning that browser edits do exactly that was written. The trap documented last week is not theoretical, and docs/06 and SECURITY-CHECKLIST.md now say the local git config fixes local commits only. Test counts re-run and unchanged at 138 server and 14 client.

Needs a decision, not a document. The demo video’s Google Drive link was committed to the public README today. docs/05 and docs/README.md both said it was kept private on purpose, because the video shows a face and says a name. Both now flag the contradiction instead of asserting the old rule; which way it resolves is a call about the face and the name, not about the docs.


Week of 2026-09-29

Done. Accounts. LiturgyGen serves more than one parish now: sign-in is a screen inside the app built on Supabase Auth, organizations and memberships joined the schema, and every table a parish owns carries an org_id that every query names. readings_cache deliberately does not — the readings for a date are the same everywhere, so one office’s fetch warms the cache for all of them. Row Level Security is the second lock, ten policies in one Supabase migration.

The signed-out screens were rebuilt as one spread, an ink cover leaf and a paper leaf, and the app got a mark of its own — an open missal with a gold ribbon — replacing the chapel’s seal, which belongs to one parish and not to the software. The landing page at / is the same spread rendered without React.

A prayer-book importer went in: upload PDFs or photographs of a General Intercessions book, and the pages are read and offered as drafts for approval. It is written and not yet run against a real book, and it is marked that way everywhere it is mentioned.

What broke, and what it cost. Three regressions came in with the accounts work and all three were invisible — no failing test, nothing in a log:

Both of the first two now have tests written specifically because they left no trace: server/test/boot.test.js drives the four requests the client fires on sign-in over real HTTP, and client/test/stream.test.js asserts the client never reaches for EventSource. I checked each by putting the bug back.

Also. The office’s 362 transcribed prayers reached the host — not by committing the books, which the licence forbids, but by loading one parish and cloning it inside the database. Confirmed: all three parishes hold 362 real prayers across seven seasons, where before they held 19 placeholders each and nothing else.

Open. The Supabase Site URL still points at localhost:3000, so a fresh sign-up’s confirmation link goes nowhere — two dashboard fields. One advisory left standing and written up rather than quietly fixed. The importer is still untested. 152 tests pass (138 server, 14 client).


Week of 2026-09-22

Done. The application is feature-complete and runs end to end on a laptop and on an office machine: calendar, day book with live missalette preview, readings with the USCCB scraper and the Evangelizo fallback, the Prayers of the Faithful cascade, batch generation, the Word builder, and the Windows installer. 92 tests pass (82 server, 10 client).

This week the class template was merged in and the repository restructured around it: server/server.js as the entry point, server/db/ with the PostgreSQL pool, schema and seed, /healthz and /readyz, CORS with named origins, .env.example in three places, the GitHub Pages workflow, and client/src/api/ split into one interface with two implementations so the Pages build has something to show before the API is deployed. Screenshots of every screen are in docs/assets/.

Stuck. Nothing is blocking, but two things are named rather than solved. The database is still SQLite: docs/07-postgres-migration-map.md lists all twenty call sites and what each becomes, and none of them are converted yet. And the API is not RESTful — /api/readings/check, /api/calendar/expand, /api/potf/parse and /api/batch/:id/cancel are remote procedure calls wearing HTTP, and routes/settings.js:43 builds a WHERE clause by string concatenation. Validated input today, but it is the wrong shape.

Hours. About 14. Roughly 5 on the restructure, 4 on the demo-mode split and its seed generator, 2 on screenshots and docs, 3 reading the migration surface.

Next. The PostgreSQL migration, in the order the map sets out: settings first, then the readings overrides, then the schedule, then the POTF service, with the cascade last. Then the REST pass.


Week of 2026-09-15

Done. Planning, and the bulk of the build. Proposal, wireframes at two widths, and the design system, all generated from the real token values rather than drawn separately and left to drift. The monorepo, Vite with an API proxy, and CI on Node 20.10 and 22.

Stuck. USCCB’s HTML is not consistent: days with optional readings put the “or” block inside the section above it rather than beside it, so the first parser produced a reading made of two different books stuck together. Captured the edge-case pages as fixtures. Also spent longer than expected deciding how to handle the General Intercessions volumes, which are published and copyrighted — landed on keeping the transcriptions in a git-ignored folder and shipping placeholder prayers in the public build.

Hours. About 20.

Next. Finish the services, the client, and the desktop packaging.