diff --git a/CLAUDE.md b/CLAUDE.md deleted file mode 100644 index 21b09ea..0000000 --- a/CLAUDE.md +++ /dev/null @@ -1,199 +0,0 @@ -# CLAUDE.md - -This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. - -## Project Overview - -Bibdle is a daily Bible verse guessing game built with SvelteKit 2 / Svelte 5. Players read a verse and try to guess which book of the Bible it comes from. The game provides feedback hints (Testament match, Section match, Adjacent book, First letter, etc.) similar to Wordle-style games. Progress is stored locally in the browser and a new verse is generated daily. - -You are able to use the Svelte MCP server, where you have access to comprehensive Svelte 5 and SvelteKit documentation. Here's how to use the available tools effectively: - -(Make sure you use the Svelte agent to execute these commands) - -## Available MCP Tools: - -### 1. list-sections - -Use this FIRST to discover all available documentation sections. Returns a structured list with titles, use_cases, and paths. -When asked about Svelte or SvelteKit topics, ALWAYS use this tool at the start of the chat to find relevant sections. - -### 2. get-documentation - -Retrieves full documentation content for specific sections. Accepts single or multiple sections. -After calling the list-sections tool, you MUST analyze the returned documentation sections (especially the use_cases field) and then use the get-documentation tool to fetch ALL documentation sections that are relevant for the user's task. - -### 3. svelte-autofixer - -Analyzes Svelte code and returns issues and suggestions. -You MUST use this tool whenever writing Svelte code before sending it to the user. Keep calling it until no issues or suggestions are returned. - -## Tech Stack - -- **Framework**: SvelteKit 2 with Svelte 5 (uses runes: `$state`, `$derived`, `$effect`, `$props`) -- **Styling**: Tailwind CSS 4 -- **Database**: SQLite (`bun:sqlite`) with Drizzle ORM -- **Auth**: Session-based — email/password (argon2id via `Bun.password`), plus Apple and Google OAuth -- **Bible text**: Local NKJV XML (`EnglishNKJBible.xml`), parsed with `fast-xml-parser`; a Greek 1904 and Swedish 2000 translation are also bundled for alternate modes -- **Deployment**: `@sveltejs/adapter-node` run under Bun, managed by a systemd service (`bibdle.service`) -- **ML** (currently disabled): `@xenova/transformers` verse embeddings for a similarity search route - -The package version is `3.0.0alpha`. - -## Development Commands - -```bash -# Start development server -bun run dev - -# Type checking -bun run check -bun run check:watch - -# Run tests -bun test -bun test --watch -bun test tests/timezone-handling.test.ts # Run a single test file - -# Build for production -bun run build - -# Preview production build -bun run preview - -# Database operations -bun run db:push # Push schema changes directly (avoid in prod) -bun run db:generate # Generate migrations -bun run db:migrate # Run migrations -bun run db:studio # Open Drizzle Studio GUI -``` - -## Critical: Date/Time Handling - -**Bibdle is played by users across many timezones worldwide. The verse shown to a player must always be the verse for the calendar date at *their* location — not the server's timezone, not UTC. A user in Tokyo on Wednesday must see Wednesday's verse, even if the server (or a user in New York) is still on Tuesday.** - -**NEVER use server time or UTC time for user-facing date calculations.** - -- Get today's date client-side: `new Date().toLocaleDateString("en-CA")` → `YYYY-MM-DD` -- Pass the date to the server as a query param or POST body (`localDate`) -- Server-side date arithmetic must use UTC methods on the client-provided date string: `new Date(dateStr + 'T00:00:00Z')` + `setUTCDate`/`getUTCDate` -- `src/routes/+page.ts` has `ssr = false` so the load runs client-side with the true local date -- Never set the user-facing URL to include their date as a parameter. It should always be passed to an API route behind the scenes if needed. - -### Streak Calculation - -A streak counts consecutive calendar days (in the user's local timezone) on which the user completed the puzzle. The rules: - -- The client passes its local date (`localDate`) to the streak API. The server never uses its own clock. -- `/api/streak` walks backwards from `localDate` through the `dailyCompletions` records, counting each completed day. It stops at the first missing day. It's called from the win screen, so today is typically completed. -- `/api/streak-percentile` (which ranks all players) anchors on today-if-played-else-yesterday, so a player's streak isn't zeroed mid-day before they've had a chance to complete today's puzzle. -- A streak of 1 (completed only today, with no prior consecutive days) is **not displayed** — `/api/streak` returns `0` for any streak < 2, and the minimum shown streak is 2. -- All date arithmetic on the server must use UTC methods on the client-provided date string to avoid timezone drift: `new Date(localDate + 'T00:00:00Z')`, then `setUTCDate`/`getUTCDate`. - -## Architecture - -### Database Schema (`src/lib/server/db/schema.ts`) - -- **user**: `id`, `firstName`, `lastName`, `email` (unique), `passwordHash`, `appleId` (unique), `googleId` (unique), `isPrivate` -- **session**: `id` (SHA-256 hash of token), `userId` (FK), `expiresAt` -- **dailyVerses** (table `daily_verses`): cached daily verse — `date` (unique), `bookId`, `verseText`, `reference`, `createdAt` -- **dailyCompletions** (table `daily_completions`): one row per player/date — `anonymousId`, `date`, `guessCount`, `guesses` (JSON array of book IDs; nullable), `completedAt`. Unique on `(anonymousId, date)` to prevent duplicate submissions. - -Sessions expire after 30 days and auto-renew when < 15 days remain. - -**Identity model:** logged-in users' `anonymousId` *is* their `user.id` — `createUser()` inserts the user with `id = anonymousId` so existing stats carry over. Anonymous users get a client-generated UUID stored in `localStorage` (`bibdle-anonymous-id`). - -### Bible Data (`src/lib/types/bible.ts`) - -The `bibleBooks` array contains all 66 Bible books with metadata: -- `testament`: `old` | `new` -- `section`: `Law`, `History`, `Wisdom`, `Major Prophets`, `Minor Prophets`, `Gospels`, `Pauline Epistles`, `General Epistles`, `Apocalyptic` -- `order` (1-66, used for adjacency detection) - -### Daily Verse System - -`src/lib/server/daily-verse.ts` → `getVerseForDate(date)`: returns the cached verse for a date if present, otherwise fetches a random verse from the local XML Bible (`src/lib/server/xml-bible.ts`, wrapped by `src/lib/server/bible-api.ts`) and stores it permanently. The client calls `POST /api/daily-verse` with its local date; `src/routes/+page.server.ts` only loads `user`/`session` (the verse is fetched client-side because `+page.ts` sets `ssr = false`). - -### Game Logic - -Core logic lives in `src/lib/utils/game.ts` and the reactive store `src/lib/stores/game-persistence.svelte.ts`; `src/routes/+page.svelte` wires them into the UI. - -**State Management:** -- `guesses` array stored in `localStorage` keyed by date: `bibdle-guesses-${date}` -- Each `Guess` tracks: `book`, `testamentMatch`, `sectionMatch`, `adjacent`, `firstLetterMatch` -- `evaluateGuess()` includes a special case: numbered Epistles (e.g. "1 John") match on first letter against any other numbered Epistle -- `isWon` derived from whether any guess matches the correct book -- `getGrade()` maps guess count to a letter grade (S+ → C) - -**Hint System, for share grid:** -- ✅ Exact match | 🟩 Section match | 🟧 Testament match | ‼️ Adjacent book | 🟥 No match - -### Authentication System (`src/lib/server/auth.ts`) - -- Token generation: base64url-encoded random bytes; stored as SHA-256 hash in DB. Cookie name: `auth-session`. -- Anonymous users: identified by a client-generated UUID in `localStorage`; stats migrate on sign-up via `migrateAnonymousStats()` (re-attributes `dailyCompletions` rows from the anonymous ID to the new user ID; overlapping dates are dropped). -- Three sign-in methods: email/password (argon2id via `Bun.password`), Apple Sign-In (`src/lib/server/apple-auth.ts`, `appleId` field), and Google Sign-In (`src/lib/server/google-auth.ts`, `googleId` field). The SvelteKit CSRF config trusts `https://appleid.apple.com` for the cross-origin `form_post` callback. - -### Stats & Streak (`src/routes/stats/`, `src/routes/progress/`) - -- `/stats` and `/progress` require auth; the server load returns `requiresAuth: true` for unauthenticated visitors, and the page renders a sign-in modal. -- The current streak is fetched from the server via `GET /api/streak?anonymousId=X&localDate=Y` (the server never uses its own clock). -- Streak walk-back: counts consecutive days backwards from `localDate` through `dailyCompletions`; stops at the first missing day. Single-day streaks are reported as `0` — the minimum displayed streak is 2. -- `/api/streak` walks from `localDate` only (it's called after a win, so today is completed). `/api/streak-percentile`, which ranks all players, anchors on today-if-played-else-yesterday so mid-day streaks aren't zeroed. -- Achievements/milestones are computed server-side in `src/lib/server/milestones.ts`. - -## API Endpoints - -- `POST /api/daily-verse` — Fetch (and cache) the verse for a given `YYYY-MM-DD` date -- `POST /api/submit-completion` — Submit a game result (`anonymousId`, `date`, `guessCount`, `guesses`); returns solve rank, guess rank, total solves, average guesses, ties, percentile. Unique on `(anonymousId, date)`. -- `GET /api/streak?anonymousId=X&localDate=Y` — Current streak for a player -- `GET /api/streak-percentile?streak=N&localDate=Y` — Streak percentile ranking across all players -- `GET /api/stats` — Aggregated stats for the `/global` dashboard -- `GET /api/imposter` — Generate a four-verse imposter-mode round -- `POST /api/similar-verses` — Semantic verse search via embeddings -- `POST /api/send-daily-verse` — Cron-only (bearer `CRON_SECRET`); posts today's verse to the Discord webhook -- `POST /api/dev/seed-history` — Dev seeding helper - -Other endpoints: `GET /feed.xml` (RSS) and `GET /sitemap.xml` (SEO). - -## Key Files - -- `src/routes/+page.svelte` — Main game UI and client-side logic -- `src/routes/+page.server.ts` / `+page.ts` — Server load (user/session) + client load (`ssr: false`, fetches the daily verse) -- `src/routes/+layout.svelte` — App shell, title animation, theme toggle, analytics injection -- `src/routes/imposter/`, `/random`, `/greek-random`, `/similarity` — Alternate game/debug modes -- `src/routes/about/`, `/global/`, `/progress/`, `/stats/`, `/dev/` — Supporting pages -- `src/lib/server/auth.ts` — Session management, password hashing, anonymous→user migration -- `src/lib/server/apple-auth.ts`, `google-auth.ts` — OAuth providers -- `src/lib/server/daily-verse.ts` — Per-date verse caching/lookup -- `src/lib/server/xml-bible.ts` — Local XML Bible parsing (NKJV / Greek / Swedish) -- `src/lib/server/bible-api.ts` — Random verse fetching on top of the XML parser -- `src/lib/server/bible.ts` — Bible book utility functions -- `src/lib/server/milestones.ts` — Achievement/milestone calculation -- `src/lib/utils/game.ts` — Guess evaluation and grading -- `src/lib/utils/share.ts` — Share grid/text generation -- `src/lib/utils/streak.ts`, `stats-client.ts`, `stats.ts` — Client-side streak/stats fetching and formatting -- `src/lib/stores/game-persistence.svelte.ts` — Reactive localStorage-backed game state -- `src/lib/types/bible.ts` — 66-book metadata and TypeScript types -- `src/lib/server/db/schema.ts` — Drizzle ORM schema -- `src/hooks.server.ts` — Session validation hook; (commented-out) embeddings init -- `tests/` — Bun test suites: timezone, game, bible, stats, share, sign-in migration - -## Environment Variables - -See `.env.example`. Required/used variables: - -- `DATABASE_URL` — Path to the SQLite database file (e.g., `prod.db`) -- `PUBLIC_SITE_URL` — Canonical site URL -- `CRON_SECRET` — Bearer token protecting the cron-only `/api/send-daily-verse` endpoint -- `DISCORD_DAILY_WEBHOOK` — Webhook for posting the daily verse to Discord -- `AUTH_SECRET` — Secret for Apple Sign-In -- `APPLE_ID` / `APPLE_TEAM_ID` / `APPLE_KEY_ID` / `APPLE_PRIVATE_KEY` — Apple Sign-In credentials -- `SMTP_USERNAME` / `SMTP_TOKEN` / `SMTP_SERVER` / `SMTP_PORT` — Email (nodemailer) - -## Deployment - -Production uses `@sveltejs/adapter-node` run under Bun via a systemd service. `deploy.sh` pulls latest, installs deps, builds, and restarts `bibdle.service` (which runs `bun --bun build/index.js` on port 5173 with `DATABASE_URL=prod.db`). See `bibdle.service` for the unit file. - -## A Note - -The main developer of this project is still learning a lot about developing full-stack applications. If they ask you to do something, make sure they understand how it will be implemented before proceeding. diff --git a/src/lib/components/WinScreen.svelte b/src/lib/components/WinScreen.svelte index 77b2f00..eca9c21 100644 --- a/src/lib/components/WinScreen.svelte +++ b/src/lib/components/WinScreen.svelte @@ -68,6 +68,7 @@ let copyTracked = $state(false); let showSnippetOption = $state(false); let includeSnippet = $state(false); + let showProgressDropdown = $state(false); let effectiveShareText = $derived( includeSnippet @@ -333,20 +334,35 @@ {#if isLoggedIn}
-
- - 📈 See your progress - -
+ + 📈 See your progress + +
{:else}
-

- Create an account (or sign in) to track your progress -

+ + {#if showProgressDropdown} +

+ Create an account (or sign in) to track your progress +

{/if} + +
+ + + Join the BIBDLE Discord! + + +
+ +
+ + ❤️ Support bibdle's development + + +
+ BIBDLE will be ad-free forever. +
+