aboutsummaryrefslogtreecommitdiff
path: root/docs/superpowers/plans
diff options
context:
space:
mode:
Diffstat (limited to 'docs/superpowers/plans')
-rw-r--r--docs/superpowers/plans/2026-07-23-lectio-go-rewrite.md90
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.