First word of each suggestion starts with the letter being learned
(e.g. "age WALK" for A). Validation requires at least one word to
start with the target letter. Added O/U/N/D/H words to fill gaps.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Dot words: ≤3 letters (was ≤2) — two-letter words like "go/it" were too limiting
- Dash words: ≥4 letters (unchanged in practice)
- Added DOT_WORDS and DASH_WORDS banks (~60 and ~200 words)
- suggestPhrases() generates 5 random valid suggestions per letter
- MnemonicBuilder shows clickable suggestion pills; "↻ new" refreshes them
- Selected suggestion highlights in gold; user can still type their own
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- mailer.js: add requireTLS to force STARTTLS before AUTH on internal relay
- docker-compose.yml: use network_mode:bridge to reach 172.17.0.1:2587 SMTP relay
and switch to ${SMTP_HOST:-172.17.0.1}/${SMTP_PORT:-2587} defaults
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Return 200 ok even if email send fails — token is saved in DB and
magic link is logged to console. Prevents 500 on transient SMTP issues.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- rejectUnauthorized: false (relay cert is for mail.keylinkit.net, not 172.17.0.1)
- connection/greeting/socket timeouts for reliability
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Dockerfile: two-stage build (builder installs all deps + vite build; runtime copies dist + server)
- docker-compose.yml: 127.0.0.1:3004:8080, named volume for SQLite, env var interpolation
- .env.example: all required environment variables documented
- .dockerignore: exclude node_modules, dist, data, .env from build context
- vite.config.ts: outDir → ../client/dist, /api proxy to :3001 for dev
- .gitignore: remove Docker entries (files should be tracked for deployment)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- AdminPage: password gate (sessionStorage), user table, stats banner
- Auto-login using stored sessionStorage password on mount
- App.tsx: route to admin when pathname === '/admin'
- Fix: remove redundant setPassword in auto-login effect (useState already seeds from sessionStorage)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- useMorseAudio: wraps MorseAudioEngine, exposes currentSignal for FlashArea
- useProgress: fetches /api/progress + /api/mnemonics, recordAnswer with local stats
- FlashArea: gold flash on signal, height varies dot/dash
- ScoreBar: level name, score, streak display
- PracticeCard: play button, answer input, correct/incorrect feedback with mnemonic
- GamePage: full practice loop, level-up modal at streak%10, anti-repeat history
- Fix: incorrect attempt recorded only once per challenge (no stat inflation on retries)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- MnemonicBuilder: 12 NOVICE_LETTERS, real-time validatePhrase, save/skip
- OnboardingPage: wraps builder, passes token and onComplete
- App.tsx: post-auth fetch /api/mnemonics, route to onboarding if <12 saved
- Fix: validatePhrase called with letter (not Morse pattern) as second arg
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- useAuth: session token from URL/localStorage, /api/auth/me validation, logout
- LoginPage: email form with POST /api/auth/request, error handling
- App.tsx: use useAuth hook, LoginPage, 'sent' confirmation route
- Fix: auto-route to game covers both 'login' and 'sent' routes when authenticated
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>
Use pendingCancel flag (consumed once) to suppress stop-before-play while
allowing subsequent play() calls to proceed normally. Add afterEach timer
cleanup, re-play test, and behavioral assertion for off().
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Complete Product Requirements Document with full technical specs
- MorseQuest adventure-based branding and visual identity
- All logos, icons, and brand assets ready for development
- Freemium business model ($9.95/year, first year free launch strategy)
- Ready for Keylink IT Forge platform deployment
Phase 1 MVP ready to begin (10-12 week timeline)