# Daily Word — Project Summary & Maintenance Guide

**A Rock RMS Bible reading plan application for Good News Church**
Built August 2026 · Current version: **app v5.2 · endpoint save-plan v2**

---

## 1. What it is

Daily Word is a Rock-native Bible reading plan app on the church's public site
(goodnewsde.com). A signed-in person builds a 365-day reading plan from
templates (M'Cheyne, Whole Bible, New Testament) or custom book/frequency
selections, then reads or listens daily: a clean reader with API.Bible text, an
audio player with word-level follow-along highlighting, check-offs, a catch-up
list, a year calendar, a printable full plan, and a year-complete celebration.

**Guiding philosophy (Sam's ruling):** the celebrated metric is **days in the
Word this calendar year** — plan-independent and gap-forgiving. Streaks exist
to build habits, but the number the church celebrates is total days showing
up, which can never be lost or reset. "A new year is a new chance to move
deeper into God's Word."

---

## 2. Where everything lives

| Piece | Location |
|---|---|
| Lava Application | slug `daily-word`, hosted on the public site (goodnewsde.com); same Rock instance as manage.goodnewsde.com |
| App code | one Lava file pasted into the **Lava Application Content** block (all CSS/JS inline inside `{% raw %}`) |
| Save endpoint | `POST /api/v2/lava-app/1/daily-word/save-plan` (the `1` is a permanent API literal, not an app id) |
| Endpoint settings | Sql command enabled · **Application View security** (the endpoint's own Security rows are NOT consulted) |
| App security | daily-word Lava Application → Security → **ViewExecute → Allow → All Authenticated Users** (set 2026-08-03; without it, regular members get 401 on save) |
| Bible content | API.Bible — text + audio; key `ihqFe6Ulyut2QBDL8qqXX` inlined in the app file (`DW_BIBLE_KEY`) by explicit decision |
| Data | one Person attribute blob (`DailyWordPlan`) + seven summary attributes (§4) |
| Fonts / design | Newsreader + Work Sans; forest/sage/cream palette (#145C45, #f5f0e8, #d4c5a9); all CSS scoped under `#dw-root` |

**Client → endpoint contract (v5.0+):**
`POST { "plan": "<json string>", "streak": <int> }` with header
`X-Helix-CSRF-Protection: true` and `keepalive: true`.
Response: `{ "ok": true, "readDates": [ ...merged ISO dates... ] }` — the
client adopts the merged ledger (never a shorter set than it holds).

---

## 3. The plan blob (DailyWordPlan JSON)

```json
{
  "selections": [
    { "name": "Genesis onward", "baseName": "M'Cheyne 1",
      "books": ["Genesis", "..."], "freq": 1,
      "sched": [ { "mode": "steady", "from": 0 },
                 { "mode": "passes", "from": 214 } ] }
  ],
  "startDate": "2026-07-31",
  "checks": { "d12s0": true },
  "viewDay": 12, "view": "today",
  "readerFont": 18, "follow": true, "speed": 1,
  "readDates": ["2026-08-01", "2026-08-02"],
  "ledgerSeeded": true,
  "sharePublicly": true
}
```

Key semantics:

- **`checks`** — keyed `d{dayIndex}s{selectionIndex}`. Day-keyed, so display
  reordering (canonical Bible order) never moves anyone's progress.
- **`sched`** — segmented cadence history (v5.1). Each `{mode, from}` entry
  plans *remaining chapters over the rest of the year* in that mode, truncated
  where the next switch re-plans. Switching cadence NEVER reschedules the
  past. `mode` is `"steady"` (even spread) or `"passes"` (bunched: contiguous
  read-throughs with even rests; degrades to steady when chapters × freq
  > 365). Legacy plans may carry `cadence: "passes"` instead — treated as a
  single segment from day 0.
- **`readDates`** — the "days in the Word" ledger: ISO date recorded once
  whenever the person checks anything off. Append-only by design: unchecking
  never removes a date; plan rebuilds and year resets never touch it; the
  endpoint unions it server-side so a stale tab can never erase a day.
- **`ledgerSeeded`** — one-time back-fill flag (v4.15): on first load, checked
  plan-days convert to calendar dates and seed the ledger, then never again.
- **`sharePublicly`** — consent checkbox state; **defaults ON** (undefined →
  true, seeded like `follow`), but an explicit `false` survives restore.

---

## 4. Person attributes (all 8)

All written by the save endpoint as Rock (SQL upsert with
`IsPersistedValueDirty = 1`). Attribute **Edit security: nobody** — the
endpoint writes on the person's behalf; no one edits these directly.

| Key | Field Type | View security | Meaning |
|---|---|---|---|
| `DailyWordPlan` | Memo | All Authenticated (app reads it client-side) | the whole state blob (§3) |
| `DailyWordShare` | Boolean | staff only | consent to share streak/history with the church. **Attribute Default Value: blank** — non-users must not count as consenting; only a save writes True/False |
| `DailyWordStreak` | Integer | staff only | current streak at last save (client-computed; weakest report signal — a returner drops to 1 on comeback day) |
| `DailyWordBestStreak` | Integer | staff only | high-water mark, **monotonic server-side** — never lowered |
| `DailyWordDaysRead` | Integer | staff only | lifetime ledger count (days with *any* reading) |
| `DailyWordDaysReadYear` | Integer | staff only | ledger days in the current calendar year; self-replaces on first save of a new year |
| `DailyWordDaysReadYearOf` | Integer | staff only | which year the above belongs to — pair in dataviews (`YearOf = 2027`) to avoid stale prior-year totals in January |
| `DailyWordLastRead` | Date | staff only | most recent ledger date (last day with *any* reading — slightly more generous than "fully-completed day"; right for shepherding) |

**Report guidance:** these update only when a person saves — a lapsed reader's
values freeze. Anchor every pastoral dataview on `DailyWordLastRead`
(e.g. "LastRead > 14 days ago AND DaysRead ≥ 5" for follow-up;
"Share = True AND DaysReadYear ≥ N" for celebration).

---

## 5. Version history

### v1.x — Setup + Today (the port begins)
- **v1.0** — Prototype core ported verbatim from the Claude Design file
  ("Reading Plan Builder v5 polish"): plan builder (templates, presets,
  books, frequency) + Today view with check-offs. Reader/audio deferred.
  `DailyWordPlan` attribute + `save-plan` endpoint (SQL upsert, CSRF header).
- **v1.1** — Removed prototype's footer band; transient save toast; sticky
  action bar clearance; single-book picker; restored `editSt()` (without it
  every chip/book add was a no-op).
- **v1.2** — All CSS scoped under `#dw-root` + element reset so the site
  theme can't bleed in; M'Cheyne tracks named by anchor books.
- **v1.3** — Stylesheet rebuilt verbatim from the Good News design tokens.

### v2.x — Reader and audio (the hard part)
- **v2.0** — Day reader + glass audio dock; text and audio from API.Bible.
- **v2.1** — Button reset fix. **v2.2** — dock could never appear (two gating
  bugs). **v2.3** — sound fix; autoplay unlock via silent-primer inside the
  click gesture. **v2.4** — primer bounce fixed. **v2.5–v2.6** — audio finally
  played by streaming the presigned URL **directly** to the audio element
  (never fetch-as-blob). **v2.7** — whole-verse highlighting (all spans of a
  verse), polish. **v2.8** — follow-along drift: timing model rebuilt (verses
  share duration minus intro; pause weighting; pace calibration from mid-book
  chapters). **v2.9–v3.1** — new-book chapter-1 offsets measured, not guessed.

### v3.x — Reader correctness and resilience
- **v3.2** — Read pill scroll fix. **v3.3** — Rock WebForms wraps the page in
  one form: every button `type="button"` + preventDefault (page-reload bug).
- **v3.4** — "Verse-2 mystery" solved by the `DW timing` console log:
  API text begins mid-verse-list; `repairVerseIds()` attributes leading
  orphan spans to verse 1. Instrumentation habit born here.
- **v3.5** — Play starts at your passage. **v3.6** — localStorage API cache
  (`dw2:` — text 30d, discovery 7d, presigned URLs never cached, quota
  eviction) because api.bible rate limits are real.
- **v3.7** — Cue-on-skip (navigation while paused only moves the pointer).
  **v3.8** — × means dismiss. **v3.9** — queue tap navigates.

### v4.x — The full product
- **v4.0** — Calendar (12 month grids, day states, coral today ring,
  tap→day) + Full plan (365 rows) restored — the two prototype views cut at
  v1.0. **v4.1–v4.3** — landscape multi-column print attempts (superseded).
- **v4.4** — **Print remade from scratch**: browser multicol printing
  duplicates/splits rows at fragment boundaries, so the app self-paginates —
  `printPlan()` measures rows, `packPrintRows()` (pure, tested) packs explicit
  3-column landscape pages; print hides everything except `#dw-print`
  (kills the theme's header page). **v4.5** — date nowrap; references glued
  internally with nbsp (no more "Rom / 10" orphans). **v4.6** — columns pack
  with ~0.5in slack (screen-measured heights run short of print).
- **v4.7–v4.9** — Mark-read moved to each passage's foot, centered, named
  ("Mark Genesis 1–2 read"), rule removed.
- **v4.10** — **Pass-aligned cadence** (opt-in per stream): Acts ×6 becomes
  six contiguous 28-day read-throughs with even rests.
- **v4.11** — **Canonical Bible order** (Gen→Rev) for every day's readings
  across all views; **day progress bar** on Today.
- **v4.12** — Share button (later removed — see v4.23).
- **v4.13** — **Days-in-the-Word ledger** (`readDates`): the celebrated,
  plan-independent, gap-forgiving number. Today stats: Days this year ·
  Day streak · Chapters today. **v4.14** — highlight glyph clipping fixed
  (vertical-only shadows). **v4.15** — one-time ledger back-fill from check
  history. **v4.16–v4.17** — cadence labels became **steady / bunched**
  (Sam's words) with helper. **v4.18–v4.22** — Edit-plan layout polish;
  helper became a hover tooltip on the chip (desktop only — no hover on
  touch). **v4.23** — **Share button removed** (a misread): the real need was
  a consent checkbox — "I'm happy to share my reading streak and history with
  Good News" — mapped to `DailyWordShare`. **v4.24** — fresh users start with
  an **empty plan** (no pre-loaded sections); guarded Start button.
  **v4.25** — save diagnostics (toast shows HTTP status / auth hint) +
  `keepalive: true` for iOS backgrounding. **v4.26** — consent label reworded.
  **v4.27** — consent **defaults ON**; explicit no survives restore.
  **v4.28** → renumbered **v5.0**.

### v5.x — The pastoral data layer
- **v5.0** — **Endpoint v2 two-file deploy.** Save body carries the streak;
  server unions `readDates`, derives counts from the merged set (metrics can
  only grow), keeps BestStreak monotonic, upserts all 8 attributes (missing
  attributes are skipped harmlessly), returns the merged ledger; client
  adopts it. Verified live 2026-08-03: all attributes populated.
- **v5.1** — **Cadence switches govern the future only.** Toggling
  steady/bunched had re-anchored the whole year at day 1, rewriting the past
  and flooding catch-up. Cadence is now segmented (`sched` history); the
  past is byte-identical across switches; the year still lands exactly.
- **v5.2** — **Year Complete.** From day 366 (unclamped `rawDayIndex` —
  `todayIndex` caps at 364 for scheduling), Today becomes the completion
  screen: plan date range, four computed metrics (days in the Word windowed
  to the plan's dates, best streak, chapters read, % of plan), closing line
  "A new year is a new chance to move deeper into God's Word," and Begin a
  new year (resets checks/start date; **ledger, consent, reader prefs carry
  forward** — enforced by test) / Adjust my plan first.

**Also deployed:** `save-plan v2` (endpoint, pairs with app v5.0+).
**M'Cheyne note:** measured 2026-08-03 — the template already produces
exactly 4 readings every day, zero empty track-days, opening Gen 1 / Ezr 1 /
Mt 1 / Ac 1 and closing 2 Chr 36 / Mal 4 / Jn 21 / Rev 22. The only delta
from the literal 1842 table is *which* 139 days carry two-chapter entries
(ours arithmetic, M'Cheyne's hand-chosen). Sam accepted as-is; the literal
table remains a possible someday data-entry upgrade (~1,460 cells, public
domain).

---

## 6. Critical maintenance knowledge

### The Lava comment trap (caused blank renders — memorize this)
Rock Lava treats `/-` as opening a multi-line comment and `//-` as a
single-line comment **anywhere in the template** — inside CSS, JS comments,
prose — because Lava renders before the browser sees anything. An unclosed
`/-` silently swallows the rest of the file: output stops mid-word, HTTP 200,
no error. **Before pasting anything into Rock, grep for `/-` and `//-` and
expect zero hits.** Watch for: "and/or", `grid-column: 1/-1`, negative array
indices in comments. Also: quoting a live Lava tag inside a JS comment
triggers real parsing. All JS/CSS lives inside `{% raw %}…{% endraw %}` and
the raw region must contain zero `{{`, `}}`, `{%`.

### The deployment workflow (non-negotiable)
1. **Never edit in place** — every change is a new version number; bump the
   header comment AND the `console.log("Daily Word vX.Y ready")` stamp.
2. Preflight the **assembled** file (not just sources): `/-` count zero, raw
   region clean, version stamp present. Verify content greps in the
   **published output file** — two stale publishes shipped this project when
   a failed edit's `&&` chain still copied an old file.
3. After pasting into Rock, the browser console stamp is the source of truth
   for which version is actually live.
4. In edit scripts, every string replacement gets an `assert old in text` —
   three silent no-op replacements this project came from targets with
   trailing whitespace.

### Rock behaviors learned the hard way
- **Rock returns 401 for authorization denials too**, not just missing auth.
  A signed-in member failing endpoint security gets 401. Rock Admin overrides
  ALL Lava Application security — admin-account testing can never catch a
  security gap; test with a regular member account.
- **Helix "Application View security"** means the endpoint's own Security
  rows are decorative; the *application's* ViewExecute action is the one
  check. Always confirm the endpoint's security mode before touching grants.
- Security-grant changes take effect immediately (no restart).
- **iOS Safari aborts in-flight requests on backgrounding** — exactly when a
  debounced save fires. `keepalive: true` on the save fetch is the fix.
- Whole-blob saves are last-write-wins; the ledger survives stale-tab
  overwrites **only because the endpoint unions it**. If new append-only data
  is ever added to the blob, give it the same server-side union treatment.
- Raw SQL attribute writes don't flush Rock's attribute cache; page loads may
  serve a stale blob briefly. The client-side union adoption papers over
  this for the ledger.
- `todayIndex` clamps at 364 (for scheduling); use `rawDayIndex` for "is the
  plan over" logic.
- Field types: Rock's picker says **Integer** (not "Number"); endpoint
  security verb in the dialog is **Execute** (internally "ExecuteView").

### Testing setup (Node, no Rock required)
Working dir `/home/claude/dw/` (session-scoped; sources also embedded in the
published .lava files):
- `_core_subset.js` (scheduling), `_app_layer.js`, `_reader_layer.js`,
  `_style.css` — assembled by a Python heredoc into `daily-word.lava`.
- `_dom.js` harness: element shim, localStorage, gesture-gated Audio,
  dynamic getElementById, **`setTimeout` is a no-op** (async tests must wait
  in microtask ticks), **Node 22's global `navigator` is getter-only**
  (mock via `Object.defineProperty(globalThis, "navigator", …)`).
- Suites: `_t.js` / `_t2.js` (sync) / `_t3.js` (serialized promise chain).
  **140 tests green at v5.2.** Rebuild `_headless.js` after any layer edit.
- Behavioral rules proven repeatedly: instrument, don't guess (console logs
  and user-printed PDFs found what reasoning missed); fixture trap — freq-1
  streams have empty day-0.

---

## 7. Open items at handoff

**Blocking congregation launch**
1. **API.Bible Pro upgrade (~$29/mo, 150k calls)** — free tier is 5,000
   calls/MONTH; ~18 daily listeners exhaust it. The one loud failure.
2. **Soft-launch week** with the worship team — every real tester found bugs
   no test could (Sam's son found the security blocker with one save).

**Open product decisions**
3. **Streak semantics** — reading-days (current) vs calendar-days. Now stored
   in Rock via the endpoint, and bunched plans create long scheduled-nothing
   stretches that make the difference visible. Provenance note: the current
   behavior was inherited from the AI-built prototype; no human ever chose it.
4. **M'Cheyne + single-NT-book overlap edge** — `addSelection` de-dup can
   carve sections surprisingly when mixing templates; low priority.
5. **Invitation copy** — three drafts rejected; direction unresolved (the
   weekly-announcements pipeline is the suggested route).
6. Optional label precision: template description could note "(daily
   groupings adapted)" vs the literal 1842 table.

**Consent posture (on the record)**
The share checkbox is pre-checked (Sam's call, appropriate for sharing *with
church staff*). If its use ever extends to public celebration (slides,
bulletin), re-read the label against the actual use; flipping the default is
one line.

---

## 8. File inventory (this conversation's outputs)

- `daily-word-v5.2.lava.txt` — **current app** (paste into the block)
- `save-plan-v2.lava.txt` — **current endpoint** (paste into save-plan)
- `daily-word-v4.x/v5.x…` — prior versions retained for rollback
- `DAILY-WORD-PROJECT.md` — this document

Full build history transcripts (with every version's assembly and debugging):
`2026-08-02 daily-word-rock-build` (v1.0–v4.5) and
`2026-08-03 daily-word-build-v2` (v4.6–v5.2).
