diff options
Diffstat (limited to 'docs/superpowers/plans')
| -rw-r--r-- | docs/superpowers/plans/2026-07-23-lectio-go-rewrite.md | 90 |
1 files changed, 90 insertions, 0 deletions
diff --git a/docs/superpowers/plans/2026-07-23-lectio-go-rewrite.md b/docs/superpowers/plans/2026-07-23-lectio-go-rewrite.md index b4a82d2..a3ab615 100644 --- a/docs/superpowers/plans/2026-07-23-lectio-go-rewrite.md +++ b/docs/superpowers/plans/2026-07-23-lectio-go-rewrite.md @@ -1511,6 +1511,96 @@ renders vernacular only, with a one-line note that Latin is unavailable. `cli`: add global `--lectionary`/`--lang` flags overriding config; everything else is unchanged because both sources yield `[]Section`. +## Addendum B: lectio-web (HTMX web UI with selectable themes) + +A third binary `lectio-web` — an hledger-web-style local server. Build after the +base plan + Addendum A. Consumes `readings.Load` + `render.GatherVersion`; imports +the domain packages, never the reverse. + +### Amendment to Task 10 (config) — web fields +Add to `Config`: `WebTheme string \`toml:"web_theme"\``, `WebPort int \`toml:"web_port"\``. +Defaults: `WebTheme "light"`, `WebPort 0`. Validate `WebTheme` ∈ +`{light,dark,sepia,parchment,nord}`. Add the two lines to the embedded seed +(after `offline`): `web_theme = "light"` and `web_port = 0` with the spec's +comments. (Do this as a small follow-up commit to internal/config.) + +### Task B1: internal/web — HTML render + embedded themes + +**Files:** Create `internal/web/render.go`, `internal/web/render_test.go`, +`internal/web/templates/*.html`, `internal/web/static/themes/{light,dark,sepia,parchment,nord}.css`, +`internal/web/static/htmx.min.js` (download the pinned release). + +**Interfaces:** +- Consumes `liturgy.Section`, `render.GatherVersion`, `render.OfflineVersions`. +- Produces: `web.RenderReadings(secs []liturgy.Section, versions []string, lectionary string) template.HTML` + (the reading pane: per section, a heading + one column per version built from + `render.GatherVersion(v, sec, lectionary)`; verse-number / heading / citation / + refrain wrapped in CSS-class spans so themes restyle them), and + `web.Themes() []string` (the five theme names). Embed templates + CSS + htmx via `go:embed`. + +- [ ] **Step 1: Write the failing test** +```go +func TestRenderReadings(t *testing.T) { + secs := []liturgy.Section{{Heading: "Ewangelia (J 20, 1. 11-18)", PartID: "ewangelia"}} + html := string(RenderReadings(secs, []string{"wuj"}, "new")) + if !strings.Contains(html, "Ewangelia") || !strings.Contains(html, "class=") { + t.Errorf("reading pane missing heading/classes: %q", html[:min(200, len(html))]) + } +} +func TestThemesEmbedded(t *testing.T) { + for _, name := range Themes() { + if b, err := themeCSS(name); err != nil || len(b) == 0 { + t.Errorf("theme %s not embedded", name) + } + } +} +``` +- [ ] **Step 2:** `go test ./internal/web/` → FAIL. +- [ ] **Step 3:** Implement render.go: `//go:embed templates static` FS; parse templates once; `RenderReadings` builds the pane by calling `render.GatherVersion` per (section, version) and feeding a `templates/readings.html` fragment; wrap heading/citation/verse-number/refrain in `<span class="...">`. `Themes()` returns the five names; `themeCSS(name)` reads `static/themes/<name>.css` from the embed FS (error on unknown). Write five real theme CSS files (each defines the colour-role classes + page background/foreground; light/dark/sepia/parchment/nord distinct). Download the pinned `htmx.min.js` into `static/`. +- [ ] **Step 4:** `go test ./internal/web/` → PASS. +- [ ] **Step 5:** Commit `web: HTML render + embedded themes`. + +### Task B2: internal/web — server + handlers + cmd/lectio-web + +**Files:** Create `internal/web/server.go`, `internal/web/server_test.go`, `cmd/lectio-web/main.go`. + +**Interfaces:** +- Consumes `config.Config`, `readings.Load`, `bible.Lookup`, B1's render. +- Produces: `web.NewServer(cfg config.Config) http.Handler`; `web.Run(cfg) error` + (pick port = cfg.WebPort or a free one, start, open the browser). + +- [ ] **Step 1: Write the failing test** (httptest against the handler, no real browser) +```go +func TestIndexAndPartial(t *testing.T) { + liturgy.SetBaseURL(fixtureServerURL(t) + "/liturgia/%s/Ewangelia") // reuse the T8 hook + srv := NewServer(config.Default()) + // GET /?date=2026-07-22&v=wuj -> 200, contains a reading + the theme <link> + htmx script + rec := httptest.NewRecorder() + srv.ServeHTTP(rec, httptest.NewRequest("GET", "/?date=2026-07-22&v=wuj", nil)) + if rec.Code != 200 || !strings.Contains(rec.Body.String(), "htmx") { t.Fatalf("index: %d", rec.Code) } + // GET /readings (HTMX partial) -> 200, fragment only (no <html>) + rec2 := httptest.NewRecorder() + srv.ServeHTTP(rec2, httptest.NewRequest("GET", "/readings?date=2026-07-22&v=wuj", nil)) + if rec2.Code != 200 || strings.Contains(rec2.Body.String(), "<html") { t.Fatalf("partial not a fragment") } + // GET /lookup?ref=J+20:1&v=wuj -> contains the verse + rec3 := httptest.NewRecorder() + srv.ServeHTTP(rec3, httptest.NewRequest("GET", "/lookup?ref=J+20:1&v=wuj", nil)) + if !strings.Contains(rec3.Body.String(), "20:1") { t.Fatalf("lookup missing verse") } + // GET /theme.css?name=sepia -> text/css + rec4 := httptest.NewRecorder() + srv.ServeHTTP(rec4, httptest.NewRequest("GET", "/theme.css?name=sepia", nil)) + if rec4.Code != 200 || !strings.Contains(rec4.Header().Get("Content-Type"), "css") { t.Fatalf("theme.css") } +} +``` +- [ ] **Step 2:** `go test ./internal/web/` → FAIL. +- [ ] **Step 3:** Implement server.go: routes — `/` full page (date/lectionary/version/all/theme controls with `hx-get="/readings"` targeting the pane; the passage-lookup form `hx-get="/lookup"`; theme `<link id=theme href="/theme.css?name=…">` + a theme `<select>` that swaps it; embedded `/static/htmx.min.js`), `/readings` HTMX partial (calls `readings.Load` with query params, offline→OfflineVersions, returns `RenderReadings`), `/lookup` (bible.Lookup for the typed ref across the picked versions → HTML fragment), `/theme.css?name=` (serves `themeCSS`, `Content-Type: text/css`), `/static/` (embedded). `Run(cfg)` listens on cfg.WebPort or `:0`, prints the URL, best-effort opens the browser (`xdg-open`/`open`/`start`), serves. `cmd/lectio-web/main.go`: `config.Load()` → `web.Run(cfg)`. +- [ ] **Step 4:** `go test ./internal/web/` → PASS; `go build ./cmd/lectio-web`; manual: run it, open the browser, click date/version/theme, try the lookup box. +- [ ] **Step 5:** Commit `web: HTMX server + lectio-web binary`. + +### Amendment to Task 1/14 (Makefile, .gitignore, README) +Add `lectio-web` to the Makefile `build`/`install`/`cross` targets and `.gitignore`; +document `lectio-web`, its keys/controls, and the themes in the README. + ## Self-Review Notes - Spec coverage: two binaries (T1,12,13,15-via-14), embedded corpora (T1,4), lookup (T4,5), aliases incl. `J`→John (T3), citation conversion + psalm systems (T2,6), fetch/parse (T7,8), cache HTML+JSON (T8), sigla harvest + offline (T9), config incl. `offline` (T10), render + dedup + pl→wuj (T11), subcommands (T12), colored reader TUI (T13), Makefile/cross/README (T1,14). All spec sections map to a task. |
