┌─────────────────────────────────────────────────────────────┐
│ FRONTEND (React + Vite) │
│ http://localhost:3001 │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌────────────────────────────────────────────────────┐ │
│ │ Practice Page Component │ │
│ │ /src/routes/practice.tsx │ │
│ ├────────────────────────────────────────────────────┤ │
│ │ │ │
│ │ const { data } = useProblems('binary-decimal', 5) │ │
│ │ const validate = useValidateProblem() │ │
│ │ const hint = useGetHint(problemId) │ │
│ │ const session = useUserSession() │ │
│ │ │ │
│ └──────────────────┬───────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌────────────────────────────────────────────────────┐ │
│ │ React Query Hooks + API Client │ │
│ │ /src/lib/api-client.ts │ │
│ ├────────────────────────────────────────────────────┤ │
│ │ │ │
│ │ ❌ GET /problems?type=X&difficulty=Y │ │
│ │ ❌ POST /problems/:id/validate │ │
│ │ ❌ GET /problems/:id/hint │ │
│ │ ❌ GET /session │ │
│ │ ❌ PUT /users/:id/progress │ │
│ │ │ │
│ └──────────────────┬───────────────────────────────────┘ │
│ │ │
└─────────────────────┼─────────────────────────────────────────┘
│
│ ❌ MISMATCH
│
┌─────────────────────▼─────────────────────────────────────────┐
│ BACKEND (Cloudflare Worker) │
│ http://localhost:3002 │
├────────────────────────────────────────────────────────────────┤
│ │
│ ┌────────────────────────────────────────────────────┐ │
│ │ Hono Framework + Drizzle ORM │ │
│ │ /binary-math-api/src/index.ts │ │
│ ├────────────────────────────────────────────────────┤ │
│ │ │ │
│ │ ✅ GET /api/problems/:type/:difficulty │ │
│ │ ✅ POST /api/validate │ │
│ │ ⊘ NOT IMPLEMENTED: hints │ │
│ │ ✅ POST /api/sessions │ │
│ │ ✅ GET /api/sessions/:id │ │
│ │ ⊘ NOT IMPLEMENTED: /users/:id/progress │ │
│ │ │ │
│ └──────────────────┬───────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌────────────────────────────────────────────────────┐ │
│ │ Turso Database (LibSQL) │ │
│ ├────────────────────────────────────────────────────┤ │
│ │ │ │
│ │ ❌ TURSO_URL: undefined │ │
│ │ ❌ TURSO_AUTH_TOKEN: undefined │ │
│ │ │ │
│ │ Tables: │ │
│ │ - problems │ │
│ │ - sessions │ │
│ │ - validationAttempts │ │
│ │ - userStats │ │
│ │ │ │
│ └────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ FRONTEND (React + Vite) │
│ http://localhost:3001 │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌────────────────────────────────────────────────────┐ │
│ │ Practice Page Component │ │
│ │ /src/routes/practice.tsx │ │
│ ├────────────────────────────────────────────────────┤ │
│ │ │ │
│ │ const { data } = useProblems('binary-decimal', 5) │ │
│ │ validate.mutate({ │ │
│ │ problemId, userAnswer, userId, timeSpent │ │
│ │ }) │ │
│ │ │ │
│ └──────────────────┬───────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌────────────────────────────────────────────────────┐ │
│ │ React Query Hooks + API Client │ │
│ │ /src/lib/api-client.ts │ │
│ ├────────────────────────────────────────────────────┤ │
│ │ │ │
│ │ ✅ GET /api/problems/:type/:difficulty │ │
│ │ ✅ POST /api/validate │ │
│ │ { problemId, userId, userAnswer, timeSpent } │ │
│ │ ✅ POST /api/sessions { userId } │ │
│ │ ✅ GET /api/sessions/:id │ │
│ │ │ │
│ │ Response Unwrapper: │ │
│ │ { success, data, pagination } → data │ │
│ │ │ │
│ └──────────────────┬───────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌────────────────────────────────────────────────────┐ │
│ │ Zustand Store (/src/store/app-store.ts) │ │
│ ├────────────────────────────────────────────────────┤ │
│ │ │ │
│ │ Synced from Backend: │ │
│ │ - XP (from validation response) │ │
│ │ - Streak (from validation response) │ │
│ │ - Session data (from session endpoints) │ │
│ │ │ │
│ │ Persisted Locally: │ │
│ │ - User preferences │ │
│ │ - Settings │ │
│ │ │ │
│ └────────────────────────────────────────────────────┘ │
│ │
└─────────────────────┼─────────────────────────────────────────┘
│
│ ✅ CORS: *
│ ✅ Retry: 3x with backoff
│ ✅ Cache: 5min (problems)
│
┌─────────────────────▼─────────────────────────────────────────┐
│ BACKEND (Cloudflare Worker) │
│ http://localhost:3002 │
├────────────────────────────────────────────────────────────────┤
│ │
│ ┌────────────────────────────────────────────────────┐ │
│ │ Hono Framework Router │ │
│ │ /binary-math-api/src/index.ts │ │
│ ├────────────────────────────────────────────────────┤ │
│ │ │ │
│ │ app.route('/api', problemsRouter) │ │
│ │ app.route('/api', validationRouter) │ │
│ │ app.route('/api', sessionsRouter) │ │
│ │ │ │
│ └──────────────────┬───────────────────────────────────┘ │
│ │ │
│ ┌─────────────────┴──────────────────┐ │
│ │ │ │
│ ▼ ▼ │
│ ┌──────────────────┐ ┌──────────────────────────┐ │
│ │ Problems Router │ │ Validation Router │ │
│ │ /routes/ │ │ /routes/validation.ts │ │
│ │ problems.ts │ ├──────────────────────────┤ │
│ ├──────────────────┤ │ │ │
│ │ │ │ Calculate XP: │ │
│ │ GET /:type/:diff │ │ - Base XP by difficulty │ │
│ │ → Problems[] │ │ - Time multiplier │ │
│ │ │ │ - Streak bonus │ │
│ │ Response: │ │ │ │
│ │ { success, data, │ │ Update userStats: │ │
│ │ pagination } │ │ - Total attempts │ │
│ │ │ │ - Correct attempts │ │
│ └──────────────────┘ │ - Current streak │ │
│ │ │ │
│ ┌──────────────────┐ └──────────────────────────┘ │
│ │ Sessions Router │ │
│ │ /routes/ │ │
│ │ sessions.ts │ │
│ ├──────────────────┤ │
│ │ │ │
│ │ POST / │ │
│ │ GET /:id │ │
│ │ PUT /:id │ │
│ │ │ │
│ └──────────────────┘ │
│ │ │
│ ▼ │
│ ┌────────────────────────────────────────────────────┐ │
│ │ Turso Database (LibSQL) │ │
│ ├────────────────────────────────────────────────────┤ │
│ │ │ │
│ │ ✅ TURSO_URL: libsql://xxx.turso.io │ │
│ │ ✅ TURSO_AUTH_TOKEN: eyJhb*** │ │
│ │ │ │
│ │ Tables: │ │
│ │ ┌──────────────┬─────────────┬──────────────┐ │ │
│ │ │ problems │ sessions │ userStats │ │ │
│ │ ├──────────────┼─────────────┼──────────────┤ │ │
│ │ │ id │ id │ userId │ │ │
│ │ │ type │ userId │ currentStreak│ │ │
│ │ │ difficulty │ startedAt │ totalAttempts│ │ │
│ │ │ question │ totalXpEarn │ correctAtte… │ │ │
│ │ │ answer │ streak │ lastAttempt… │ │ │
│ │ │ hints │ status │ │ │ │
│ │ └──────────────┴─────────────┴──────────────┘ │ │
│ │ │ │
│ └────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
1. User starts practice
│
├─→ Frontend: useProblems('binary-decimal', 5)
│ └─→ Backend: GET /api/problems/binary-decimal/5
│ └─→ Database: SELECT * FROM problems WHERE type=... AND difficulty=...
│ └─→ Backend: { success: true, data: [...], pagination: {...} }
│ └─→ Frontend: Unwrap → Problem[]
│
2. Frontend creates session
│
├─→ Frontend: createSession(userId)
│ └─→ Backend: POST /api/sessions { userId }
│ └─→ Database: INSERT INTO sessions (...)
│ └─→ Backend: { success: true, data: Session }
│ └─→ Frontend: Store session.id
│
3. User answers problem (takes 15 seconds)
│
├─→ Frontend: validate.mutate({
│ problemId, userId, userAnswer, timeSpent: 15
│ })
│ └─→ Backend: POST /api/validate { ... }
│ ├─→ Database: SELECT * FROM problems WHERE id=problemId
│ ├─→ Calculate: isCorrect = (userAnswer === problem.answer)
│ ├─→ Calculate: xp = baseXp * timeMultiplier * difficulty
│ ├─→ Database: INSERT INTO validationAttempts (...)
│ ├─→ Database: UPDATE userStats SET totalAttempts++, streak++
│ └─→ Backend: {
│ success: true,
│ correct: true,
│ explanation: "...",
│ xpEarned: 30,
│ xpMultiplier: 2.0,
│ newStreak: 5
│ }
│ └─→ Frontend: Update Zustand store
│ ├─→ updateXP(30)
│ └─→ incrementStreak() → 5
│
4. Next problem...
┌───────────────────────────────────────────────────────┐
│ Request: GET /api/problems/binary-decimal/5 │
└──────────────────┬────────────────────────────────────┘
│
▼
┌─────────────────────┐
│ apiFetch() wrapper │
└──────────┬───────────┘
│
▼
┌─────────────────────┐
│ fetch() native API │
└──────────┬───────────┘
│
┌──────────┴──────────┐
│ │
✅ 200 OK ❌ 4xx/5xx Error
│ │
▼ ▼
Parse JSON Throw APIError
│ { status, statusText }
▼ │
Validate Zod │
│ │
▼ │
Return data │
│
┌────────────────┘
│
▼
┌─────────────────────┐
│ retryRequest() │
├─────────────────────┤
│ │
│ 4xx? → NO RETRY │
│ 5xx? → RETRY 3x │
│ Timeout? → RETRY │
│ │
│ Delay: 1s → 2s → 4s│
│ (exponential back)│
│ │
└──────────┬───────────┘
│
▼
┌─────────────────────┐
│ React Query │
├─────────────────────┤
│ │
│ isError: true │
│ error: APIError │
│ │
│ Component shows: │
│ - Toast message │
│ - Error UI │
│ - Retry button │
│ │
└─────────────────────┘
useProblems():
Query Key: ['problems', type, difficulty]
staleTime: 5 minutes
┌─────────────────────────────────────────┐
│ 0min: Fetch from API ──────────┐ │
│ │ │
│ 1min: Return cached data ─────┤ │
│ 2min: Return cached data ─────┤ FRESH │
│ 3min: Return cached data ─────┤ │
│ 4min: Return cached data ─────┤ │
│ 5min: Return cached data ─────┘ │
│ │
│ 5min+: Fetch from API (stale) ─────────┤
│ STALE │
└─────────────────────────────────────────┘
useValidateProblem():
Mutation: No caching
Invalidates: ['problems'] on success
useUserSession():
Query Key: ['session']
staleTime: 10 minutes
(Consider reducing to 1-2 min for active sessions)
Full details: /Users/bhunt/development/claude/binary/INTEGRATION_TEST_REPORT.md