Bible Quest origin — an annotated walkthrough

Status: 🟩 COMPLETE Last updated: 2026-06-21 Plain-English tagline: The actual story of how St Mark’s Bible Quest got built end-to-end, phase by phase, with every encyclopedia entry that explains a piece of it. Half case study, half stress-test of the encyclopedia.


What this is

A case study: the real story of how St Mark’s Bible Quest got built, with links to the encyclopedia entries that explain each piece. It works two ways:

  • If you’re reading the encyclopedia and want to see how it all comes together in a real project — start here.
  • If you’re starting your own project and want a reference for the order of operations — this is the playbook in narrative form.

The project: St Mark’s Bible Quest is a Bible-study webapp for kids at St Mark’s church. Daily passages, quizzes, streaks, badges, levels, leaderboards, memory verse practice, word search, treasure hunts, team challenges, themed days, weekly prizes — 17 features + bonus features, shipped in roughly 10 days of focused work, by a non-programmer (George) collaborating with Claude Code.

Live URL: https://stmarkbible.com

This walkthrough covers the build from kickoff through the Reading Plans feature (May 7 to May 16, 2026), plus the domain-registration milestone in May 17. Iteration since then has continued; for the authoritative current state, see PROGRESS.md inside the project.


Phase 0 — Picking the stack and tools (May 7)

The first session was almost entirely about choices. Not code — choices.

The stack we landed on:

This is the stack the encyclopedia now treats as default for any new webapp. It exists because of this project. The reason is brutally pragmatic: this stack is the fastest path from zero to shipping a real webapp for a solo developer (or a non-coder driving Claude Code).

Encyclopedia entries that explain this phase:

Lesson surfaced: picking the stack before any code is written takes one session and saves weeks. Defer decisions until the moment they bite, then they cost hours.


Phase 1 — Local environment + scaffolding (May 7)

Same day. Installed Node.js, Git, VS Code. Set PowerShell execution policy. Created the project with npx create-next-app@latest. Initialized git. Pushed to GitHub. Created the Vercel project. Live URL minted: https://st-marks-bible-quest.vercel.app/.

The whole flow — from “nothing installed” to “default Next.js page live on the internet” — was about 90 minutes.

Encyclopedia entries that explain this phase:

Lesson surfaced: the single gotcha that ate the most time was PATH refresh in background PowerShell sessions. Documented in project_tech_stack memory and in PowerShell vs Bash — read it before your first long-running command in a fresh PowerShell session.


Phase 2 — Database + auth (May 7–8)

Supabase project created. Schema designed: 6 core tables (users, passages, questions, submissions, reading_logs, points_log). Migration 20260507_001_initial_schema.sql written and run in the Supabase SQL Editor.

A critical decision early: custom code-based auth, NOT Supabase Auth. Kids each get an 8-character access code in the format XXXX-XXXX. Login = type the code. Server validates, issues a session cookie. A sessions table tracks active logins.

Why custom auth?

  1. Kids don’t have email addresses to receive magic links
  2. Server-side sessions are simpler than JWTs for this use case

Encyclopedia entries that explain this phase:

Gotcha surfaced in real time: raw-SQL Supabase migrations need GRANT statements or service_role hits “permission denied” (Postgres error 42501). Fix lives in 20260507_002_grant_role_privileges.sql. The pattern is now a documented gotcha — push invariants into the database, but remember the database still needs to grant access to the role that’s using it.


Phase 3 — MVP build (May 8–9)

About 36 hours of elapsed time. Shipped:

  • /admin/kids — create kids with auto-generated access codes (8 chars, format XXXX-XXXX, retry on collision)
  • /admin/passages — admin enters today’s daily Bible passage with reading_points
  • /admin/passages/[id]/questions — write quiz questions per passage (MCQ / true-false / fill-in-blank)
  • /home — kid sees today’s passage, clicks “Mark as Read” for points
  • /quiz/[passageId] — interactive one-question-at-a-time quiz with per-answer feedback
  • /leaderboard — weekly point standings

Encyclopedia entries that explain this phase:

Critical gotcha — timezones. Vercel runs in UTC. Admin is in Australia/Sydney. A passage dated 2026-05-09 (admin local) would be invisible to kids until midnight UTC (10am Sydney). Fix: lib/datetime.ts exports todayInProjectTimezone() which uses Intl.DateTimeFormat("en-CA", { timeZone }) to return YYYY-MM-DD in PROJECT_TIMEZONE. The whole project’s date logic now flows through this helper.

Critical gotcha — security pattern. The quiz correct_answer field is only sent to the client for questions the kid has already submitted. Otherwise kids could “View Page Source” to see future answers. The server enforces this at the data-fetch layer.

Encyclopedia entries on the gotchas:


Phase 4 — Phase 2 features: gamification (May 9)

In a single intense day, shipped 5 gamification features:

  1. Streaks with milestone bonuses (at 3, 7, 14, 30, 60, 100, 200, 365 days)
  2. Badges — 13 achievements seeded; idempotent awarder that runs on every relevant action
  3. Levels — 4 tiers (Disciple → Apostle → Scholar → Sage) derived from lifetime points
  4. Dark mode with cookie persistence and CSS-variable theming
  5. Custom avatars — 12 icons × 8 colors per kid

The badge system is worth highlighting: lib/badges.ts exports checkAndAwardBadges(supabase, userId) which:

  • Pre-fetches all relevant data in parallel via Promise.all
  • Computes derived metrics (reads count, perfect quizzes, etc.)
  • Iterates a predicate map keyed by badge slug
  • Awards any missing badges + their points_value bonus rows in points_log
  • Idempotent — safe to call from anywhere

Encyclopedia entries that explain this phase:

Lesson surfaced: when adding a feature that retroactively rewards prior activity (badges, in this case), also call the awarder on page loads — not just from action handlers. Otherwise existing users have to perform fresh actions to backfill. This is now baked into the playbook.


Phase 5 — Phase 3 features: content + mini-games (May 9)

Same day, more features:

  1. Reflection journal per passage
  2. Audio reading — URL field; renders inline <audio> for .mp3/.m4a/etc., falls back to “open in new tab” button for YouVersion links
  3. Memory verse challenge — Read / Blanks / First-letters / Lock-in modes with Levenshtein-based similarity scoring
  4. Bible Character Match — daily card-flip game, seeded RNG so every kid gets the same daily challenge
  5. Phone wallpapernext/og ImageResponse generates a 1080×1920 PNG of the current memory verse
  6. Treasure hunt — Bible-reference clues with a ~280-alias parser
  7. Word search — 12×12 grid built from a passage’s distinctive words

Encyclopedia entries that explain this phase:

Lesson surfaced: YouVersion / bible.com URLs are HTML pages, not audio files — the <audio> element can’t play them. The right move is detecting URL type via regex on path extension and falling back to a “listen along (opens new tab)” link. Sometimes the right answer is a graceful fallback, not a fix.


Phase 6 — Phase 4 features: social (May 10)

5 social features in one day:

  1. Team challenges — kids assigned to teams; leaderboard with team standings
  2. High-five reactions — one per kid per recipient per day (CHECK + UNIQUE enforced in the DB)
  3. Discussion of the week — admin posts a question, kids respond, admin features the best
  4. Themed days — 7 daily themes (Mystery Monday, Trivia Tuesday, etc.)
  5. Real-world prizes — weekly prize announced + winner selected

The high_fives table has elegant constraints:

CHECK (from_user_id <> to_user_id)
UNIQUE (from_user_id, to_user_id, date_sent)

These two lines enforce “you can’t high-five yourself” and “max one high-five per recipient per day” — without any application code involved.

Encyclopedia entries that explain this phase:

Lesson surfaced: push as much invariant enforcement into the database as possible. CHECK and UNIQUE are constant-time guarantees the application code can rely on. Adding the same logic in JavaScript would mean races, edge cases, and bugs.


Phase 7 — Reading Plans + ongoing iteration (May 14 onward)

The big post-MVP feature: Reading Plans. Admin creates a plan (“Read the Gospel of Matthew over 28 days”); the app generates the daily passages on its own, with optional AI quiz generation per passage. Frequency cadence (daily / weekdays-only / weekly+day), skip-dates, chain configs (auto-advance to the next book in a preset chain like “Whole NT”), end-of-book behaviors.

This phase brought in serious LLM work — the AI quiz generation pipeline:

  • Cached system prompt for cost savings
  • Per-passage Claude calls
  • Idempotency via a row-level “already generated?” check
  • 4-way concurrency batching for initial passage text fetches

Encyclopedia entries that explain this phase:

Domain registration (May 17): bought stmarkbible.com through Vercel Domains. One-click setup; SSL auto-issued by Let’s Encrypt via Vercel. Live at https://www.stmarkbible.com.

Encyclopedia entries on the domain step:


Lessons that apply to any project

Distilling the lessons surfaced across these phases — these are now baked into the encyclopedia’s cross-project playbook:

  1. Pick the stack before any code. One session of decisions saves weeks of regret.
  2. Run npm run build before every push. Production is stricter than dev — TypeScript errors that npm run dev skips will fail Vercel.
  3. Pin the Vercel function region to match the database region. Cross-Pacific queries are slow for no reason.
  4. Use service_role only server-side. Never expose it; never put it behind NEXT_PUBLIC_.
  5. Enable RLS on every table containing user data. The default Supabase posture.
  6. Push invariants into the database when you can. CHECK and UNIQUE are free; application-level checks are flaky.
  7. Custom auth is fine for non-standard cases. Kids without emails, internal tools, niche use cases — server-side sessions are simpler than fighting a third-party auth system.
  8. Use UTC at the storage layer; convert at the boundary. All dates stored as UTC, all reads use a todayInProjectTimezone() helper.
  9. Make retroactive features run on page load too. Action-only awarders miss existing users.
  10. Detect URL types before assuming format. YouVersion looks like an audio link but isn’t.
  11. Idempotency everywhere it makes sense. Badge awards, point ledger entries, plan generation — all idempotent.
  12. Update PROGRESS.md when you ship something. Future-you (and future-Claude) will thank you.

What this walkthrough surfaced about the encyclopedia

A case study like this is also a quality test. The result: every concept this project needed has an encyclopedia entry. No glaring gaps surfaced during the walkthrough. The 16 sections + 12 how-tos + glossary cover the full journey.

The walkthrough also confirms that the cross-linking pattern works — almost every paragraph above could link to 3-5 entries, and they all exist as 🟩 COMPLETE.

If you’re reading this and a specific concept feels under-explained when you click through — that’s the gap worth filling, and worth telling Claude about so the entry can be improved.


Where to go from here


Sources

  • St Mark’s Bible Quest live site: https://stmarkbible.com
  • In-repo PROGRESS.md and AGENT_GUIDE.md at C:\Users\georg\st-marks-bible-quest — authoritative source of project history
  • Memory files: project_progress_log.md, project_tech_stack.md, project_bible_study_app.md — the chronological log this walkthrough was built from