Skip to main content

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-word manifest; site-level public/manifest.json untouched.
  • 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 = yesPWA-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.json is brand-level: name: "ChurchWiseAI", start_url: "/", display: standalone, navy theme_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 / capacitor in package.json; no service worker; no scoped living-word manifest.
  • 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-chat is a server-side Claude Haiku stream (/api/living-word/npc-chat). Offline, the client fetch never reaches the server, so the route's own error_fallback will 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-word from 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:

  1. Installable. Visiting /living-word in a PWA-capable browser surfaces the browser's native install affordance; installing adds a "Living Word" icon whose start_url opens directly into the game (/living-word), in standalone display (no browser chrome), with the correct name, icon, and theme color.
  2. 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.
  3. 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.
  4. 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.
  5. Reconnect. On regaining network, degraded surfaces recover without a manual reload (cloud save syncs, NPC chat works again).
  6. 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).
  7. No regression to the website. The root public/manifest.json and the rest of churchwiseai.com install behavior are unchanged; the scoped game manifest does not hijack the site-level install.
  8. 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:
    1. manifest served + correct start_url/scope/icons (/living-word route);
    2. service worker registers and reaches activated;
    3. offline boot + offline play of one full encounter (Playwright context.setOffline(true));
    4. 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);
    5. reconnect recovery.
  • Lighthouse PWA / installability check on the preview.
  • Confirm public/manifest.json and site-level install are unchanged (no regression).
  • Append a line to DECISION_LOG.md; if any knowledge/ doc references touched files, update it in the same PR (rule #16). No product_knowledge change 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).