aboutsummaryrefslogtreecommitdiff
path: root/docs/superpowers/plans/2026-07-27-lectio-calendar-customization.md
blob: 1b4f725dd5a746f7fb0678621809a10b542b95d6 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
# Calendar Customization (User Layer Files) Implementation Plan — Sub-project #2

> REQUIRED SUB-SKILL: superpowers:executing-plans. Design settled in brainstorming; format documented in the #1 spec.

**Goal:** Let users add/override the liturgical calendar with hand-edited INI layer files (`~/.config/lectio/calendars/*.ini`), stacked over the universal base via a config `use = a, b, c`, and provide `--cal-new`/`--cal-check` tooling.

**Architecture:** Reuse the engine's existing ordered-layer merge (`calendar.Compute(date, sel, layers)`). `caldata` gains an exported layer parser + a `Stack(dir, use)` that returns `[Universal(), ...user layers]`. `config` gains a `Use []string` field and `CalendarsDir()`. The CLI wires the stack into `--liturgy` and adds scaffold/lint flags.

**Tech Stack:** Go stdlib + internal/{calendar,ini,caldata,config,cli}.

## Global Constraints
- `calendar` stays stdlib-pure. `caldata` may import calendar + ini (+ os/filepath for file loading).
- Layers are matched by **filename stem**: `use = krakow` loads `krakow.ini`. The `[layer]` header is metadata.
- Merge order = universal first, then `use` order (later wins), per the engine.
- A missing/invalid user layer warns and is skipped — it never breaks `--liturgy`.
- Formats: INI only (`use` is a comma list).

---

### Task 1: Export layer parser + user-layer loading (`caldata`)
**Files:** Modify `internal/caldata/caldata.go`; Test `internal/caldata/stack_test.go`
**Produces:** `func ParseLayer(data []byte) (calendar.Layer, error)` (renamed from `parse`); `func LoadLayer(path, id string) (calendar.Layer, error)`; `func Stack(dir string, use []string) ([]calendar.Layer, []error)`.

- [ ] Rename `parse` → `ParseLayer` (exported); `Universal()` calls it.
- [ ] Add `LoadLayer(path, id)`: read file, `ParseLayer`, set `Layer.ID = id` if the file omitted it.
- [ ] Add `Stack(dir, use)`: `[]calendar.Layer{Universal()}` + for each id in `use`, `LoadLayer(filepath.Join(dir, id+".ini"), id)`; collect per-id errors (skip the failed layer).
- [ ] Test: write two temp layer files; `Stack(dir, []string{"a","b"})` returns 3 layers in order; a missing id yields an error but the rest load.

### Task 2: config `Use` + `CalendarsDir()`
**Files:** Modify `internal/config/config.go`; Test `internal/config/config_test.go`
**Produces:** `Config.Use []string`; `func CalendarsDir() (string, error)`; INI `use` key (top-level) read + written + documented in `configHeader`.

- [ ] Add `Use []string` field (toml:"-"; INI-only). `applyScalar` case `"use": cfg.Use = ini.List(val)`. `renderConfigINI` writes `use = <joined>`. Add a `use` line to `configHeader`.
- [ ] `CalendarsDir()`: `filepath.Join(filepath.Dir(configPath), "calendars")`.
- [ ] Test: INI with `use = poland, krakow` loads `Use == ["poland","krakow"]`; round-trips through Save.

### Task 3: Wire the stack into `--liturgy`
**Files:** Modify `internal/cli/liturgy.go`; Test `internal/cli/liturgy_test.go`
**Consumes:** `caldata.Stack`, `config.CalendarsDir`, `cfg.Use`.

- [ ] In `runLiturgy`, build `layers` via `caldata.Stack(dir, cfg.Use)`; print a stderr warning per load error; pass `layers` to `Compute`.
- [ ] Test: a temp calendars dir with a `local.ini` adding a solemnity on a ferial date + `use = local` → `runLiturgy` shows that solemnity as observed.

### Task 4: `--cal-new NAME` scaffold + `--cal-check NAME` lint
**Files:** Create `internal/cli/callayer.go`; Modify `internal/cli/cli.go` (flags + dispatch); Test `internal/cli/callayer_test.go`
**Produces:** `func runCalNew(cfg, name, stdout, stderr) int`; `func runCalCheck(cfg, name, stdout, stderr) int`.

- [ ] `--cal-new NAME`: write `<CalendarsDir>/NAME.ini` (a commented `[layer]` header + one example celebration); refuse to overwrite an existing file.
- [ ] `--cal-check NAME`: `LoadLayer` the file; for each celebration, verify the date resolves and rank parses (non-ferial); report problems or "ok".
- [ ] Flags `--cal-new`/`--cal-check` (string) in `Run`, dispatched after config load; add to help text.
- [ ] Tests: `runCalNew` creates a parseable file; a second call refuses; `runCalCheck` flags a bad date and passes a good file.

### Task 5: Version bump + verification
- [ ] `config.Version` → `0.27.0`; `go build/vet/test ./...`; purity check unchanged.