No email required — players pick male/female/mystery, get assigned
a humorous period-appropriate operator name (e.g. "Dusty Clicksworth",
"Ada Dottsworth", "The Phantom Sender"), can reroll, then hit
"Begin Transmitting!" for instant play. Email/magic link moved to
a collapsible section for cross-device access only.
- server: POST /api/auth/guest creates guest profile + session
- server/db: profiles.email now nullable, is_guest column added,
migration recreates table for existing installs
- client: names.ts with 15 names per gender category
- client: LoginPage redesigned — gender picker → name display → play
- client: useAuth gains setAuth() for direct session injection
- 55 server + 44 client tests pass
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- server.js: Node HTTP server with all API routes, static file serving, SPA fallback
- Path traversal protection on static file serving
- Async handler wrapped with .catch() to prevent unhandled rejections
- readBody: size limit (1MB) + error handler
- letter validation: single [A-Z] char check on progress/mnemonics routes
- phrase length limit (500 chars) on POST /api/mnemonics
- requireAdmin reads ADMIN_PASSWORD at request time for testability
- logout uses session.token from requireAuth (not re-read from header)
- 51 server tests passing (auth, progress, admin routes + edge cases)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Remove module-level `let db` singleton; all query functions now close
over the local `instance` from their `initDb` call, preventing
cross-contamination when multiple callers each invoke `initDb`
- Add REFERENCES … ON DELETE CASCADE to profile_id in magic_tokens,
sessions, user_progress, user_mnemonics, and letter_stats so that
the already-enabled foreign_keys pragma is actually enforced by DDL
- recordAnswer: normalise letter to uppercase before storing stats so
lowercase 'a' and uppercase 'A' are never tracked separately
- updateLevel: call getOrCreateProgress first to avoid a silent no-op
UPDATE when no progress row exists yet
- saveMnemonic: replace SELECT-then-INSERT with an atomic UPSERT
(INSERT … ON CONFLICT DO UPDATE) and detect first-save by checking
whether created_at equals the current timestamp
- getSession: re-fetch the row after updating last_seen so the returned
object reflects the freshly written timestamp
- db.test.js: add tests for findProfileById, findProfileById (null),
updateProfileSeen, updateLevel (normal + no-row-yet), getAdminUsers,
and getAdminStats (21 tests total, all pass)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Implements Task 4 (TDD): wrote failing tests first, then the full SQLite
database layer covering profiles, magic tokens, sessions, user progress,
mnemonics, and letter stats. All 14 tests pass.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>