aboutsummaryrefslogtreecommitdiff
path: root/docs/superpowers/plans
diff options
context:
space:
mode:
authorLukasz Kasprzak <lukas@labunix.xyz>2026-07-27 23:24:13 +0200
committerLukasz Kasprzak <lukas@labunix.xyz>2026-07-27 23:24:13 +0200
commit5d765444f315ee512c5ceb9bcf83730be8112b60 (patch)
tree71128529a6126f1af69244e671cd68c75c4f853e /docs/superpowers/plans
parent9fb2aadc1d9b96cd05b1b1d9f54a7ee955e1815a (diff)
downloadlectio-5d765444f315ee512c5ceb9bcf83730be8112b60.tar.gz
lectio-5d765444f315ee512c5ceb9bcf83730be8112b60.zip
docs: implementation plan for OF readings (Sundays + solemnities)
3 TDD tasks: OF resolver + embedded table (seed), niedziela generator (2025-2027, All:true harvest), render verification + coverage sweep. Keyed by <slug>-<cycle>. Verified caldata.Universal(), readings.Options.All (gospel-only default), and the OF slugs against current code.
Diffstat (limited to 'docs/superpowers/plans')
-rw-r--r--docs/superpowers/plans/2026-07-27-lectio-of-readings.md205
1 files changed, 205 insertions, 0 deletions
diff --git a/docs/superpowers/plans/2026-07-27-lectio-of-readings.md b/docs/superpowers/plans/2026-07-27-lectio-of-readings.md
new file mode 100644
index 0000000..0e53bb3
--- /dev/null
+++ b/docs/superpowers/plans/2026-07-27-lectio-of-readings.md
@@ -0,0 +1,205 @@
+# OF Readings (Sundays + Solemnities) Implementation Plan
+
+> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
+
+**Goal:** Serve OF Sunday & solemnity readings offline — an embedded `of-lectionary.ini` keyed by `<slug>-<cycle>`, generated from niedziela over 2025-2027, resolved via an OF branch in `caldata.Readings`, rendered from the public-domain corpora (Polish → Latin).
+
+**Architecture:** Mirror the EF readings pipeline. `caldata` embeds `of-lectionary.ini` (parsed once into `ofTempReadings`); `TemporalReadings` dispatches EF→`efTempReadings` / OF→`ofTempReadings`; `Readings` looks the OF table up by `Observed.Slug + "-" + SundayCycle`. A `//go:build ignore` generator produces the table from niedziela. Rendering is unchanged (`readingLine`).
+
+**Spec:** `docs/superpowers/specs/2026-07-27-lectio-of-readings-design.md`
+
+## Global Constraints
+
+- **Scope: Sundays + solemnities only.** Weekdays (2-year cycle) and sanctoral memorials are OUT.
+- **Keying:** `<Observed.Slug>-<SundayCycle>` (e.g. `ordinary-sunday-10-A`, `christ-the-king-B`, `assumption-A`). Uniform for Sundays and all solemnities (`SundayCycle` is populated on every day — verified).
+- **Citations only**, English-canonical (normalized via `bible.ToEnglishRef`); text rendered from `drb`/`vul`; Polish → Latin fallback (unchanged `readingLine`).
+- **EF path unchanged**; the niedziela daily-view scraper unchanged; OF readings simply start resolving where they were empty.
+- **`internal/calendar` untouched.** Readings live in `caldata` (imports `calendar` + `ini`; the `//go:build ignore` generator may import more).
+- Every non-generator task ends `gofmt -l` clean and `go test ./...` green.
+
+**Verified facts (use as written):**
+- `caldata.parseLectionary(data []byte) map[string][]calendar.Reading` reads INI `[slug]` sections, fields `first`/`psalm`/`second`/`gospel` → `[]calendar.Reading{Part,Citation}`.
+- `var efTempReadings = parseLectionary(tridentineLectionary)`; `func TemporalReadings(form, slug string) []calendar.Reading { if form=="old" { return efTempReadings[slug] } … }`.
+- `caldata.Readings(sel, layers, date, day)` today: inline `day.Observed.Masses` readings → `TemporalReadings(sel.Form, day.Observed.Slug)` → EF-only weekday fallback → nil.
+- OF `LiturgicalDay` carries `SundayCycle` ("A"/"B"/"C") on every day; slugs are `ordinary-sunday-10`, `lent-sunday-3`, `easter-sunday`, `christ-the-king`, `baptism-of-the-lord`, `trinity-sunday`, `palm-sunday`, plus sanctoral solemnity names.
+- `readings.Load(cfg config.Config, opts readings.Options) ([]liturgy.Section, liturgy.DayInfo, error)`; `liturgy.Section{Heading, Subtitle, Citation, PartID string}`; PartIDs `pierwsze_czytanie`/`psalm`/`drugie_czytanie`/`ewangelia`.
+- `bible.ToEnglishRef(plRef, system string) (string, error)` converts a Polish citation to English-canonical; `scripts/genlect.go` has `cleanCite` for source glitches.
+
+---
+
+### Task 1: OF resolver + embedded table (with a seed)
+
+**Files:**
+- Create: `internal/caldata/of-lectionary.ini` (small hand-authored seed)
+- Modify: `internal/caldata/caldata.go` (embed + `ofTempReadings` + `TemporalReadings` dispatch), `internal/caldata/readings.go` (OF key)
+- Test: `internal/caldata/readings_test.go` (add OF cases), `internal/caldata/of_test.go`
+
+**Interfaces:**
+- Produces: OF-aware `TemporalReadings(form, slug)`; `Readings` resolving OF by slug+cycle.
+
+- [ ] **Step 1: Seed** `internal/caldata/of-lectionary.ini` with a few STABLE, well-known cycle-A Sundays/solemnities (the generator replaces this file in Task 2; these keys will still be present after, so Task-1 tests keep passing):
+
+```ini
+; OF (Ordinary Form) Sunday & solemnity lectionary, keyed by
+; <computed-temporal-slug>-<SundayCycle>. Citations English-canonical.
+; SEED — replaced by scripts/genlect-of.go (niedziela, 2025-2027).
+
+[easter-sunday-A]
+first = Acts 10:34,37-43
+psalm = Ps 118:1-2,16-17,22-23
+second = Col 3:1-4
+gospel = John 20:1-9
+
+[christ-the-king-A]
+first = Ezek 34:11-12,15-17
+psalm = Ps 23:1-2,2-3,5-6
+second = 1 Cor 15:20-26,28
+gospel = Matt 25:31-46
+```
+
+- [ ] **Step 2: Write the failing test** `internal/caldata/of_test.go`:
+
+```go
+package caldata
+
+import (
+ "testing"
+ "time"
+
+ "github.com/lukaszkasprzak/lectio/internal/calendar"
+)
+
+func TestOFReadingsByCycle(t *testing.T) {
+ // A synthetic OF day keyed christ-the-king-A must resolve to Matt 25.
+ day := calendar.LiturgicalDay{
+ Observed: calendar.Celebration{Slug: "christ-the-king", Layer: "temporal"},
+ SundayCycle: "A",
+ Weekday: time.Sunday,
+ }
+ sel := calendar.Selection{Form: "new"}
+ rs := Readings(sel, nil, time.Date(2026, 11, 22, 0, 0, 0, 0, time.UTC), day)
+ var gospel string
+ for _, r := range rs {
+ if r.Part == "gospel" {
+ gospel = r.Citation
+ }
+ }
+ if gospel == "" || gospel[:4] != "Matt" {
+ t.Fatalf("christ-the-king-A gospel = %q, want Matt…", gospel)
+ }
+}
+
+func TestOFWeekdayEmpty(t *testing.T) {
+ day := calendar.LiturgicalDay{
+ Observed: calendar.Celebration{Slug: "ordinary-weekday", Layer: "temporal"},
+ Weekday: time.Wednesday, // no SundayCycle key -> deferred phase -> empty
+ }
+ if rs := Readings(calendar.Selection{Form: "new"}, nil, time.Now().UTC(), day); len(rs) != 0 {
+ t.Fatalf("weekday should be empty, got %v", rs)
+ }
+}
+```
+
+- [ ] **Step 3: Run it, verify it fails** — `go test ./internal/caldata/ -run TestOF` → FAIL.
+
+- [ ] **Step 4: Embed + parse** in `internal/caldata/caldata.go` (next to the tridentine block):
+
+```go
+//go:embed of-lectionary.ini
+var ofLectionary []byte
+
+// ofTempReadings maps an OF <slug>-<cycle> key to its readings (parsed once).
+var ofTempReadings = parseLectionary(ofLectionary)
+```
+
+- [ ] **Step 5: Dispatch in `TemporalReadings`** — for the OF, use `ofTempReadings`:
+
+```go
+func TemporalReadings(form, slug string) []calendar.Reading {
+ if form == "old" {
+ return efTempReadings[slug]
+ }
+ return ofTempReadings[slug]
+}
+```
+
+- [ ] **Step 6: OF key in `Readings`** — replace the single temporal lookup with a form-aware key:
+
+```go
+ // temporal table: EF by slug, OF by slug+cycle.
+ key := day.Observed.Slug
+ if sel.Form != "old" && day.SundayCycle != "" {
+ key = day.Observed.Slug + "-" + day.SundayCycle
+ }
+ if r := TemporalReadings(sel.Form, key); len(r) > 0 {
+ return r
+ }
+```
+
+(Leave the EF weekday fallback block below it untouched.)
+
+- [ ] **Step 7: Run tests** — `go test ./internal/caldata/` PASS; also `go test ./...` (EF unchanged) PASS.
+
+- [ ] **Step 8: gofmt + commit** — `git commit -m "feat(caldata): OF Sunday/solemnity readings by slug+cycle (seed table)"`
+
+---
+
+### Task 2: Generator + full lectionary table
+
+**Files:**
+- Create: `scripts/genlect-of.go` (`//go:build ignore`)
+- Regenerate: `internal/caldata/of-lectionary.ini`
+
+**Interfaces:** consumes `calendar.Compute`, `readings.Load`, `bible.ToEnglishRef`.
+
+- [ ] **Step 1: Write** `scripts/genlect-of.go` (`//go:build ignore`; `go run scripts/genlect-of.go`), modeled on `scripts/genlect.go`:
+ - `sel := calendar.DefaultSelection(); sel.Form = "new"`; layers `[]calendar.Layer{caldata.Universal()}`.
+ - Iterate every day 2025-01-01 .. 2027-12-31. Compute the day. KEEP it if `date.Weekday()==time.Sunday` OR `day.Observed.Rank` is a solemnity/feast-of-the-Lord (`RankSolemnity`/`RankFeast` with proper readings). Skip ordinary weekdays.
+ - Key = `day.Observed.Slug + "-" + day.SundayCycle`. Skip if already seen.
+ - Fetch citations: build a modern-lectionary `config.Config` (lectionary "new"), `readings.Load(cfg, readings.Options{Date: date.Format("2006-01-02"), All: true})` — **`All: true` is REQUIRED** (the default keeps only the gospel); for each returned `liturgy.Section`, map `PartID` → part (`pierwsze_czytanie`→first, `psalm`→psalm, `drugie_czytanie`→second, `ewangelia`→gospel); normalize `section.Citation` via `bible.ToEnglishRef(cit, system)` (psalm→"drb", else default/"") + reuse a `cleanCite` copy from genlect.go. Skip parts whose citation is empty or fails to normalize (log them).
+ - Store `map[key]map[part]citation`; require at least first+gospel to keep the entry.
+ - Write sorted into `internal/caldata/of-lectionary.ini` (same header/format as the seed). Log: entries written, and every kept day whose first or gospel was missing/unparseable (visible gaps, never silent).
+
+- [ ] **Step 2: Run the generator** — `go run scripts/genlect-of.go` (network; ~200 fetches). Confirm it writes `of-lectionary.ini` with a plausible entry count (roughly (52 Sundays × 3 cycles) + solemnities ≈ 170-200 entries) and review the logged gaps.
+
+- [ ] **Step 3: Build + coverage check** — `go build ./...`; `go test ./internal/caldata/` (Task-1 tests still pass against the regenerated table — `christ-the-king-A` gospel is still Matt 25). Spot-check via a throwaway: `Readings` for a 2026 cycle-A Sunday resolves first+gospel.
+
+- [ ] **Step 4: gofmt + commit** — `gofmt -w scripts/genlect-of.go`; `git add scripts/genlect-of.go internal/caldata/of-lectionary.ini`; `git commit -m "feat(caldata): generate OF Sunday/solemnity lectionary from niedziela (2025-2027)"`
+
+---
+
+### Task 3: Render verification + coverage sweep
+
+**Files:**
+- Test: `internal/cli/liturgy_test.go` (add an OF render case)
+
+**Interfaces:** consumes the built resolver + table.
+
+- [ ] **Step 1: Write the test** in `internal/cli/liturgy_test.go`: with an EN modern config, `dayReadings`/`runLiturgy` for a 2026 cycle-A Sunday (e.g. `2026-06-07`, Ordinary Sunday 10) returns a non-empty first and gospel; assert the gospel citation is non-empty. (Use the same helpers the existing liturgy tests use.)
+
+- [ ] **Step 2: Run it** — `go test ./internal/cli/` PASS.
+
+- [ ] **Step 3: Manual acceptance** (build a binary, temp configs — never touch the real config):
+ - EN: `lectio 2026-06-07 -L` shows a first reading + gospel with text (drb).
+ - PL: a `traditional_lang=pl`/`ui_language=pl` modern config renders the Latin fallback note + Latin text.
+ - Coverage sweep: every Sunday of 2026 (cycle A) resolves a non-empty first + gospel; print any gaps.
+
+- [ ] **Step 4: gofmt + commit** — `git commit -m "test(cli): OF Sunday readings render offline (en text, pl Latin fallback)"`
+
+---
+
+## Wrap-up
+
+- [ ] `go test ./... && gofmt -l internal/ scripts/ cmd/`.
+- [ ] Bump `config.Version` (0.35.0 → 0.36.0).
+- [ ] NOTICE/README: credit niedziela.pl for the OF lectionary citations; note OF Sunday/solemnity readings now render offline (weekdays pending).
+- [ ] Update memory `lectio-selfcontained-calendar-epic.md`: OF Sunday+solemnity readings shipped; the self-contained epic's four sub-projects are complete (weekdays/sanctoral = follow-on phases).
+- [ ] Use **superpowers:finishing-a-development-branch** to merge/install.
+- [ ] Manual: the JSON/iCal API for a 2026 Sunday now carries `readings` (they flow through `caldata.Readings` automatically).
+
+## Self-Review Notes
+
+- Spec coverage: table+resolver (T1), generator (T2), render+sweep (T3). Keying `<slug>-<cycle>`, Polish→Latin, EF-unchanged all mapped.
+- Regeneration safety: Task-1 tests assert stable facts (`christ-the-king-A` → Matt 25) that survive Task 2 overwriting `of-lectionary.ini`; the weekday-empty test needs no table data.
+- Confirm-before-coding: read the exact `Readings` body and `readings.Load`/`Options` shape (T2) from current code; confirm `caldata.Universal()` is the OF base-layer constructor (it is used by the EF `Tridentine()` sibling).
+- The generator is network-dependent and `//go:build ignore` (not unit-tested); its output is validated by Task 1's tests + Task 3's sweep. Log gaps, never drop silently.