aboutsummaryrefslogtreecommitdiff
path: root/docs/superpowers/specs/2026-07-23-lectio-go-rewrite-design.md
diff options
context:
space:
mode:
authorŁukasz <lukasz@arcofasiagroup.com>2026-07-23 11:50:16 +0200
committerŁukasz <lukasz@arcofasiagroup.com>2026-07-23 11:50:16 +0200
commit6d31f48ee0603b847223722ee0179dda08cb529c (patch)
tree2dd2fd22d81fc1217e27138b3858ab8b268bd070 /docs/superpowers/specs/2026-07-23-lectio-go-rewrite-design.md
downloadlectio-6d31f48ee0603b847223722ee0179dda08cb529c.tar.gz
lectio-6d31f48ee0603b847223722ee0179dda08cb529c.zip
Design spec: lectio Go rewrite (CLI + colored TUI, self-contained)
Forked from the Python daily-reading tool. Two binaries (lectio, lectio-ui) over shared internal packages, modelled on bread-calc: Bubble Tea + Lipgloss TUI, go:embed for the four Bible corpora (self-contained, no external tools), subcommand CLI, TOML config, colored reader TUI, psalm versification ported as-is.
Diffstat (limited to 'docs/superpowers/specs/2026-07-23-lectio-go-rewrite-design.md')
-rw-r--r--docs/superpowers/specs/2026-07-23-lectio-go-rewrite-design.md269
1 files changed, 269 insertions, 0 deletions
diff --git a/docs/superpowers/specs/2026-07-23-lectio-go-rewrite-design.md b/docs/superpowers/specs/2026-07-23-lectio-go-rewrite-design.md
new file mode 100644
index 0000000..9cc8cb3
--- /dev/null
+++ b/docs/superpowers/specs/2026-07-23-lectio-go-rewrite-design.md
@@ -0,0 +1,269 @@
+# lectio — design spec
+
+Date: 2026-07-23
+Status: approved (brainstorm), pre-plan
+
+## Goal
+
+Reimplement the Python `daily-reading` tool (`ewangelia.py`) as a self-contained
+Go project with two binaries:
+
+- **`lectio`** — terminal CLI (subcommand-based).
+- **`lectio-ui`** — TUI reader (Bubble Tea), modelled on the user's `bread-calc`.
+
+It fetches the daily Catholic liturgy readings from niezbednik.niedziela.pl and
+shows them in Polish plus four other versions (Wujek Polish, Vulgate Latin,
+Greek, Douay-Rheims English), with correct psalm versification across them.
+
+This is a **fork**: the Python `daily-reading` project is left untouched and
+keeps working. `lectio` is a fresh project.
+
+## Non-goals
+
+- Offline lectionary computation (computing the day's citations without the
+ site) remains the separate `offline_readings` effort. `lectio` fetches the
+ site like `ewangelia.py`; when an offline engine exists it slots in behind
+ `internal/liturgy` without changing consumers.
+- No new translations beyond the five already supported.
+
+## Key decisions
+
+| Decision | Choice |
+|---|---|
+| Language / stack | Go; Bubble Tea + Lipgloss (TUI), go-toml/v2 (config). Match bread-calc. |
+| Binaries | `lectio` (CLI), `lectio-ui` (TUI). |
+| Self-contained | Embed all four Bible corpora via `go:embed`; verse lookup in Go. No external `vul`/`grb`/`wuj`/`drb` tools. |
+| Daily data | Still fetched from niedziela.pl (source of citations + Polish text); cached on disk. |
+| CLI shape | Subcommands (hand-rolled dispatch, no cobra). |
+| TUI shape | Reader + version-switch: full day in one pane, `tab` cycles versions, arrows change date. |
+| Config | `~/.config/lectio/config.toml`, auto-seeded, flags override. |
+| Versification | Port `psalm_versify.py` behaviour exactly. |
+
+## Project layout
+
+Module: `github.com/lukaszkasprzak/lectio`. Location: `~/git/projects/lectio`.
+
+```
+cmd/lectio/main.go -> cli.Run(args, stdin, stdout, stderr) int
+cmd/lectio-ui/main.go -> load config+data, tui.New(...), tea.NewProgram(...).Run()
+internal/liturgy/ fetch + parse niedziela.pl into []Section
+internal/bible/ embedded corpora + reference lookup + book aliases
+internal/psalter/ psalm versification (port of psalm_versify.py)
+internal/render/ text rendering: compare columns, section text (CLI)
+internal/config/ TOML config load + auto-seed
+internal/cli/ subcommand dispatch
+internal/tui/ Bubble Tea reader
+Makefile, README.md, LICENSE, go.mod
+```
+
+Each `internal/*` package has one clear purpose, a small exported surface, and
+is unit-testable in isolation. CLI and TUI are thin front-ends over the same
+core (liturgy + bible + psalter + render).
+
+## Data architecture
+
+### Embedded corpora
+`internal/bible` embeds four TSV files with `go:embed`:
+
+- `wuj.tsv` — Biblia Wujka (Polish), from `offline_readings/corpus/wuj.tsv`.
+- `drb.tsv` — Douay-Rheims (English), from `offline_readings/drb/drb.tsv`.
+- `vul.tsv` — Vulgate (Latin), extracted from the installed `vul` tool
+ (`sed '1,/^#EOF$/d' $(command -v vul) | tar xzf - -O vul.tsv`).
+- `grb.tsv` — Greek, extracted from the installed `grb` tool the same way.
+
+All four share the 6-column format `Book | Abbrev | BookNum | Chapter | Verse | Text`
+(the kjv-family format). Total ~15 MB, embedded into the binary.
+
+### Reference lookup (replaces the awk engine + shell-out)
+`internal/bible` reimplements the reference grammar in Go. It must support the
+subset the tool actually feeds it:
+
+- Book match: canonical English name (`John`), English prefix (`Joh`), and the
+ Polish/English alias table (`J`, `Łk`, `1 Kor`, `Jana`, `Mdr`, ...) resolved
+ to a canonical book. Longest alias wins; exact before prefix (fixing the old
+ `J`→Joshua bug).
+- Chapter+verse forms: `Book C:V`, `Book C:V-V` (range), `Book C:V,V,...`
+ (list). Mixed lists like `John 20:1,11-18` are split into single-group
+ queries and merged (port of `split_ref`).
+- Returns `[]Verse{Chapter, Verse, Text}` in reference order; reports the groups
+ a corpus lacked (for the "brak w …" note).
+
+### Daily readings
+`internal/liturgy`:
+
+- `Fetch(date, refresh) (html, error)` — GET
+ `https://niezbednik.niedziela.pl/liturgia/{date}/Ewangelia` with a browser
+ User-Agent; cache to `~/.cache/lectio/{date}.html` (honor `XDG_CACHE_HOME`).
+ Cache only fully-published pages.
+- `Parse(html) ([]Section, error)` — pick the **new** lectionary tab
+ (`tabnowy0all`), fall back to `tabstary0all` with a warning; extract each
+ section's heading, subtitle, citation, and paragraphs. Fail loudly if the tab
+ is present but empty or absent (layout change).
+- `Section{Heading, Subtitle, Citation, Paragraphs}`.
+
+## Versions and psalm systems
+
+Ported from `ewangelia.py` constants:
+
+```
+versions: pl, wuj, vul, grb, drb (canonical order; = config `versions` default)
+labels: pl="Polski (niedziela.pl)" wuj="Wujek (pol.)" vul="Wulgata (lac.)"
+ grb="Grecki" drb="Douay-Rheims (ang.)"
+bible tools: wuj, vul, grb, drb (pl comes from the fetched paragraphs)
+psalm system: vul/grb/wuj -> "vulgate" drb -> "drb"
+```
+
+`lectio show VERSION` accepts any of the five (pl renders the fetched paragraphs;
+the others render looked-up verses).
+
+`pl` renders the fetched Polish paragraphs (dropping the "Słowa Ewangelii"
+incipit, and de-duplicating a repeated responsorial refrain). The four bible
+versions render verses looked up from the embedded corpora, with the citation
+converted per that version's psalm system.
+
+## Reference conversion & psalm versification
+
+`internal/bible` (conversion) + `internal/psalter` (versification) port
+`to_english_ref` / `_psalm_ref` / `psalm_versify.py`:
+
+- Strip a leading `por.` (compare marker) and a trailing `(R.: ...)`
+ responsorial refrain from the citation.
+- Convert the Polish citation to English style and normalise verse groups
+ (`Mt 7, 1-5` -> `Mat 7:1-5`; disjoint groups preserved as a comma list).
+- Psalms: the lectionary cites `Ps H (V)`. The Vulgate versions use chapter `V`,
+ verses unchanged. `drb` uses chapter `H` and shifts each verse by the
+ title-fold count `k` for that psalm: `drb_verse = lectionary_verse - k`
+ (clamped ≥ 1). `k` comes from the `DRB_TITLE_FOLD` table (1 or 2 title lines;
+ 0 for untitled), derived by aligning the Wujek/DRB corpora. ~140 psalms exact,
+ ~10 approximate (documented in the package).
+
+## CLI surface (`internal/cli`)
+
+Hand-rolled subcommand dispatch. `lectio` with no args = `lectio today`.
+
+```
+lectio today [--all] [--raw] [--width N] [--refresh]
+lectio date D [--all] [--raw] [--width N] [--refresh] # D = YYYY-MM-DD
+lectio compare LIST [--date D] [--all] [--width N] [--refresh]
+ LIST = comma versions (default: config `versions`)
+lectio show VERSION [--date D] [--all] [--refresh] # one version's text
+lectio --version | lectio -v
+lectio help | lectio -h | lectio <cmd> -h
+```
+
+- `--all` shows every reading (1st/2nd, psalm, acclamation, gospel); default is
+ the gospel only, unless config `all = true`.
+- `--raw` drops banner/headings for piping.
+- `--refresh` bypasses the cache.
+- Flags override config. Exit codes: 0 ok, 1 runtime error (fetch/parse), 2
+ usage error.
+
+## TUI (`internal/tui`)
+
+Bubble Tea Elm architecture, per the approved mockup (reader + version-switch):
+
+- One scrolling pane showing the whole day's readings for the **active version**.
+- Header: date + active version label. Footer: keybar.
+- Keys: `tab`/`shift+tab` cycle versions (order = config `versions`, starting at
+ `default_version`); `←/→` change date (async re-fetch with a loading state);
+ `j/k` and `space`/`b` scroll; `g/G` top/bottom; `r` refresh; `q`/`ctrl+c` quit.
+- Async fetch via `tea.Cmd` returning a `readingsMsg` or `errMsg`; a spinner or
+ "ładowanie…" line while in flight.
+- Lipgloss styling, theme-neutral (works on light/dark terminals); width-aware
+ wrapping from `tea.WindowSizeMsg`.
+- `Model{ config, cache, date, version, sections, scroll, width, loading, err }`.
+
+The TUI shows one version at a time (not columns); the CLI `compare` is where
+side-by-side lives.
+
+### Colour scheme
+Colours distinguish the parts of a reading. Each role is a named Lipgloss style
+in one place (`internal/tui`), using `lipgloss.AdaptiveColor` so it reads on both
+light and dark terminals; a `NO_COLOR` env / non-TTY output degrades to plain.
+
+| Role | Style |
+|---|---|
+| Section heading (`1. czytanie`, `Ewangelia`) | bold, accent colour |
+| Citation (`Pnp 8, 6-7`) | dim / muted, next to the heading |
+| Verse number (`20:1`) | distinct muted colour, separated from text |
+| Verse text | default foreground |
+| Responsorial refrain (psalm) | italic / secondary colour |
+| Header (date + active version) | accent background or bold |
+| Footer keybar | dim |
+
+Exact hues are finalised in planning, but the role → style mapping above is
+fixed. The CLI stays plain text (colour is a TUI concern).
+
+## Config (`internal/config`)
+
+`Config` struct loaded via go-toml. Resolution: `LECTIO_CONFIG` env →
+`~/.config/lectio/config.toml` (auto-seeded from an embedded default on first
+run, honoring `XDG_CONFIG_HOME`) → built-in defaults. Flags override.
+
+```toml
+schema_version = 1
+versions = ["pl", "wuj", "vul", "grb", "drb"] # compare set + TUI cycle order
+default_version = "pl" # TUI start / `lectio show` default
+width = 0 # CLI wrap width; 0 = detect terminal
+all = false # default to all readings (true) or just the gospel (false)
+```
+
+Unknown versions in config are rejected with a clear error. Invalid TOML falls
+back to defaults with a stderr warning.
+
+## Porting map (Python → Go)
+
+| Python (`ewangelia.py` / `psalm_versify.py`) | Go |
+|---|---|
+| `fetch`, `parse_sections`, `html_to_lines`, `extract_reference` | `internal/liturgy` |
+| awk engine, `run_bible_tool`, `split_ref` | `internal/bible` (lookup) |
+| `POLISH_TO_EN`, `to_english_ref`, `_psalm_ref` | `internal/bible` (aliases + conversion) |
+| `psalm_versify.py` (`DRB_TITLE_FOLD`, `drb_verse`, chapter map) | `internal/psalter` |
+| `gather_version`, `run_bible_section`, `render_compare`, `render_section`, refrain dedup | `internal/render` |
+| `VERSION_LABELS`, `PSALM_SYSTEM`, `COMPARE_CODES`, `BIBLE_TOOLS` | `internal/bible` / `internal/render` constants |
+| `argparse` main | `internal/cli` |
+
+## Error handling
+
+- Network failure / unpublished date: clear stderr message, exit 1 (CLI) or an
+ error line in the TUI (stay usable, let the user change date).
+- Layout change (tab missing/empty): explicit "site layout may have changed"
+ error, exit 1.
+- A corpus lacking a passage (e.g. deuterocanonical book absent from a version):
+ per-section note, not a crash; other versions still render.
+- Unknown version code: usage error, exit 2.
+
+## Testing
+
+- **bible**: table tests for book matching (incl. `J`→John, `Łk`→Luke, `1 Kor`),
+ reference grammar (ranges, lists, split groups), and known verses across all
+ four corpora (Gen 1:1, John 20:1, a deuterocanonical, a psalm).
+- **psalter**: unit tests for `drb_verse` on titled/untitled/2-line-title psalms.
+- **liturgy.Parse**: fixture tests against a few cached HTML pages (a normal day,
+ a split-reading feast, an unpublished date), asserting sections/citations.
+- **render**: golden-text tests for a compare block and a section, including the
+ refrain dedup and psalm-number divergence.
+- **config**: load/seed/override tests with a temp `XDG_CONFIG_HOME`.
+- `go vet ./...` clean; `gofmt`-formatted.
+
+## Build & install
+
+Makefile cloned from bread-calc:
+
+```
+make build -> ./lectio and ./lectio-ui
+make install -> $(PREFIX)/bin (default ~/.local/bin)
+make cross -> dist/ for linux/darwin/windows amd64+arm64
+make test | vet | fmt | clean
+```
+
+`.gitignore`: `/lectio`, `/lectio-ui`, `dist/`, `*.test`, coverage, cache.
+
+## Open items (decide during planning)
+
+- Exact Lipgloss hues for the colour roles (mapping is fixed in the TUI section;
+ only the specific colours are open).
+- Whether `lectio show` and the TUI share a single "gather one version" function
+ (they should).
+- Whether to vendor the extracted `vul.tsv`/`grb.tsv` into the repo or fetch them
+ at build time (lean: vendor them under `internal/bible/`, like wuj/drb).