Living Word — PWA / Installable App (Offline) — Acceptance Spec
Status: MERGED + LIVE on prod 2026-06-05 (PR #757 squash-merged; churchwiseai.com/living-word installable + offline; manifest route confirmed serving on the production host).
Deployed-preview Playwright 5/5 green (e2e/living-word/pwa-offline.spec.ts): manifest,
SW active + scoped to /living-word, SW isolation from the site root, offline degrade +
reconnect, offline boot serves the precached shell. A trailing-slash scope bug (/living-word/
excluded the /living-word page → offline boot failed) was caught by the preview test and fixed
(scope /living-word). Known follow-on: 11.2 MB precache (globs all .next/static) — shrink via
code-splitting later. Android TWA + iOS Capacitor deferred (D7).
Wave: 4 (Reach & retention) — Track A, the first Wave-4 item
Author: orchestrator, 2026-06-05
Rule #17 gate: SATISFIED — founder approved the spec via the D1–D7 rulings below.
Resolved decisions (founder, 2026-06-05)
- D1 = yes — scoped
/living-wordmanifest; site-levelpublic/manifest.jsonuntouched. - D2 = (a) — NPC chat offline shows a canned in-character line, input disabled.
- D3 = (a) — daily puzzle out of scope this PR (offline: "connect to play today's puzzle").
- D4 = (a) — browser-native install only; no custom prompt.
- D5 = (a) — silent update on next launch (no refresh toast).
- D6 = drop — offline lead-gen telemetry dropped, not queued.
- D7 = yes — PWA-only this wave; Android TWA + iOS Capacitor deferred to own specs.
- Lower-stakes defaults accepted: name "Living Word", reuse brand icons, navy/cream theme, Serwist.
1. Goal
Make /living-word installable to the home screen and playable offline as a
Progressive Web App, so a child, a homeschool parent, or a Sunday-school teacher on
spotty church wifi can open "Living Word" like a native app and play the narrative game
with no connection. This is the highest-reach, lowest-risk Wave-4 lever (no theology-review
bottleneck) and it is the foundation the later Android TWA + iOS Capacitor wrappers sit on.
This spec covers PWA-first only. Android TWA and iOS Capacitor are explicitly deferred to their own specs/PRs (see §7, decision D7), per the roadmap's phased plan.
Brand-doorway invariant (unchanged): the game must remain valuable with zero CTA clicked; the PWA must not introduce any popup, nag, or kid-targeted install prompt mid-game (honors the existing living-word lead-gen anti-goals).
2. Current state (verified 2026-06-05)
public/manifest.jsonis brand-level:name: "ChurchWiseAI",start_url: "/",display: standalone, navytheme_color #1B365D, favicon icons (32/192/256/512). Installing today gives you the whole marketing site, not the game.- No PWA infra: no
serwist/next-pwa/workbox/capacitorinpackage.json; no service worker; no scopedliving-wordmanifest. - Audio is 100% synthesized (Web Audio oscillators, zero asset files) — already offline-friendly, no audio files to cache.
- Guest play needs no auth. Supabase magic-link auth (
AuthBar.tsx) is only for cloud save; the game is fully playable as an anonymous guest with localStorage progress. npc-chatis a server-side Claude Haiku stream (/api/living-word/npc-chat). Offline, the clientfetchnever reaches the server, so the route's ownerror_fallbackwill NOT fire — the offline fallback must be handled client-side before/around the fetch.- Existing reusable icon assets:
favicon-192.png,favicon-512.png,apple-touch-icon.png,icon.svg. No game-specific icon yet.
3. Scope: what must work offline vs. degrade gracefully
The PWA is offline-capable, not offline-only. The contract:
MUST work fully offline (precached)
- Boot
/living-wordfrom the home-screen icon to a playable game with no white screen (Apple Guideline 4.2 de-risk for the later iOS wrapper). - The full narrative game: all scenes, the Explorer verb system, Word Cards, relics, the Legacy Chain, scene-completion flow.
- All bundled quiz + Scripture + scene JSON read from cache (the reviewed encounter questions, lens questions, lesson-plan/scope text already in the bundle — NOT the 1,774 mechanical-corpus questions, which are out of the player pool anyway).
- localStorage progress save/load (already local).
Degrades gracefully when offline (clear, in-voice notice — never a raw error)
- NPC free-form chat (
npc-chat) — needs the Claude stream. See D2. - Cloud save / sign-in — needs network + Supabase. Offline: keep playing as guest on localStorage; show a quiet "you're offline — progress is saved on this device" note where sign-in would appear; sync on reconnect (existing cloud-save behavior, just don't error).
- Classroom co-op banner / join / live-play — all need the network. Offline: the co-op banner simply doesn't render (it already returns null for non-classroom devices); join shows "connect to the internet to join your class."
- Daily puzzle (
/living-word/daily) — server-day-deterministic. See D3 (in/out of scope). - Lead-gen telemetry — fire-and-forget; offline events are dropped or queued (D6).
Out of scope for this spec
- Code-splitting the 451KB monolith (perf, separate track).
- Android/iOS store wrappers (deferred — D7).
- Push notifications / haptics (belong to the native-wrapper specs).
4. Acceptance criteria (expected outputs)
A reviewer (or Playwright on the deployed preview) can verify each:
- Installable. Visiting
/living-wordin a PWA-capable browser surfaces the browser's native install affordance; installing adds a "Living Word" icon whosestart_urlopens directly into the game (/living-word), instandalonedisplay (no browser chrome), with the correct name, icon, and theme color. - Offline boot. With the network disabled after first visit, relaunching the installed app boots to a playable game — no white screen, no "no internet" browser page.
- Offline play. Offline, a player can move, enter a scene, trigger an encounter, answer a bundled quiz question, and complete a scene beat — identical to online for these paths.
- Graceful degrade. Offline, every network-dependent surface (NPC chat, sign-in, classroom, daily) shows an in-voice, non-error notice per §3 — never a stack trace, raw fetch error, infinite spinner, or silent dead button.
- Reconnect. On regaining network, degraded surfaces recover without a manual reload (cloud save syncs, NPC chat works again).
- Update flow. When a new version deploys, the service worker updates without trapping the user on a stale shell forever (see D5 — silent-on-next-launch vs. prompt).
- No regression to the website. The root
public/manifest.jsonand the rest of churchwiseai.com install behavior are unchanged; the scoped game manifest does not hijack the site-level install. - No new install nag. No custom install popup mid-game; if any custom prompt exists it only appears at an earned-trust moment to the right person (D4).
5. Founder decisions (need rulings before build)
D1 — Install scope. Confirm the PWA installs scoped to /living-word (icon opens the
game directly), as a second, game-specific manifest, leaving the site-level
public/manifest.json untouched. (Proposed: yes.)
D2 — NPC chat offline behavior. When the Scribe/NPC free-form chat is opened offline:
- (a) Canned in-character line — the NPC says something true-to-character like "The wind carries no words to me just now — we'll talk again when you're back online," and the input is disabled; OR
- (b) Plain notice — a simple "NPC conversation needs an internet connection" banner, chat input disabled. (Proposed: (a) — stays in the game's voice, no raw error. The pre-scripted dialogue and quiz encounters all still work offline regardless; this only affects the free-form Claude chat.)
D3 — Daily puzzle offline. The daily puzzle is server-day-deterministic. Options:
- (a) Out of scope — daily simply shows "connect to play today's puzzle" offline; OR
- (b) Cache today's puzzle so a player who loaded it online can finish it offline. (Proposed: (a) for this PR — keeps scope tight; (b) is a small follow-on.)
D4 — Install prompt UX. Options:
- (a) Rely on the browser's native install affordance only (no custom UI); OR
- (b) Add a subtle, dismissible "Add to Home Screen" hint at an earned-trust moment (e.g., after completing a scene), never mid-game, never aimed at a child. (Proposed: (a) for v1 — zero nag risk, ships fastest; revisit (b) after install metrics.)
D5 — Update flow when a new version ships. Options:
- (a) Silent update on next launch (new SW activates when all tabs close); simplest, user may run one session behind; OR
- (b) "New version available — refresh" toast. (Proposed: (a) — simplest, least disruptive for a game; (b) is a small add later.)
D6 — Offline lead-gen telemetry. Drop offline events, or queue + flush on reconnect? (Proposed: drop — telemetry is fire-and-forget and best-effort; queuing adds complexity for marginal value.)
D7 — Confirm PWA-only this wave. Android TWA and iOS Capacitor are deferred to their own specs/PRs after PWA ships and Lighthouse PWA ≥ the install bar. (Proposed: yes.)
Lower-stakes defaults (documented, not blocking — flag if you disagree):
- App name: "Living Word", short_name "Living Word".
- Icon: reuse existing brand icons for v1 (
favicon-192/512,apple-touch-icon); a game-specific icon is a nice-to-have follow-on. - Theme color: brand navy
#1B365D, background cream#FEFCF8(matches the site). - Service worker library: Serwist (actively maintained; the roadmap explicitly rules out
the abandoned
next-pwa).
6. Verification approach (per CLAUDE.md rules 10, 12, 20)
- Playwright on the deployed Vercel preview (not localhost), artifact saved:
- manifest served + correct
start_url/scope/icons (/living-wordroute); - service worker registers and reaches
activated; - offline boot + offline play of one full encounter (Playwright
context.setOffline(true)); - each degraded surface shows its in-voice notice offline (assert the notice text/behavior,
not just DOM presence — per
feedback_verify_behavior_not_dom_presence); - reconnect recovery.
- manifest served + correct
- Lighthouse PWA / installability check on the preview.
- Confirm
public/manifest.jsonand site-level install are unchanged (no regression). - Append a line to
DECISION_LOG.md; if anyknowledge/doc references touched files, update it in the same PR (rule #16). Noproduct_knowledgechange expected (no pricing/feature/policy).
7. Deferred follow-ons (tracked, not built here)
- Android TWA (PWABuilder → Bubblewrap,
assetlinks.json, Lighthouse PWA ≥80) — own spec. - iOS Capacitor (static-bundle the route into
webDir, native magic-link/OTP auth rework, splash/haptics) — own spec; the real engineering work. - Code-splitting scene data + lazy modals to cut the 451KB cold-start parse — perf track.
- Game-specific app icon + maskable icon set.
- Cache-today's-daily-puzzle (if D3=a).