# CLAUDE.md — Curio Repo context for Claude Code. Read this every session. Keep it current: if a convention changes, update this file in the same commit. Full product/architecture/design reference: `docs/curio-specification.md`. ## What Curio is (one paragraph) Curio is a curiosity-led **mastery tutor**. A learner states intent → gets a Journey of bounded ~10-minute **Lessons** → each Lesson is short readable AI-generated explanation segments punctuated by **production checkpoints** (predict / explain-back / solve) → the AI **diagnoses** the learner's free-text response against pre-verified rubrics and a per-concept misconception library, gives targeted feedback, and gates advancement on demonstrated mastery. The moat is **diagnosis** (inferring *what* a learner misunderstands and *why*), not content generation. ## Decided stack — single language (TypeScript), do not substitute without asking - **App:** Next.js (App Router) + TypeScript — RSC reading surface + Route Handlers (API) + native streaming/SSE - **LLM:** Vercel AI SDK (provider-agnostic, streaming, schema-constrained structured output). Generator and grader configured to **different** models/providers. - **Contracts:** Zod — shared across API I/O, LLM outputs, DB-derived types - **DB:** PostgreSQL 16 + pgvector (single store, source of truth) - **ORM/migrations:** Drizzle ORM + drizzle-kit - **Cache/buffer:** Redis — a cache/buffer in *front* of Postgres, never load-bearing for availability - **Background jobs:** Route Handlers + the `job` ledger table for the slice; **BullMQ** (Redis) when scale arrives — jobs spend money, so they must be **idempotent** with durable Postgres state - **Styling/motion:** Tailwind + CSS custom properties (design tokens); motion via CSS transitions + **View Transitions API** (no animation library); honor `prefers-reduced-motion` - **Observability:** Langfuse (or OpenTelemetry) for LLM call tracing - **Rate limiting:** edge limiter (e.g. Upstash) on the cold-start generation endpoint - **Tests:** Vitest (unit, LLM mocked) + golden-eval runner (real-model, CI) + Playwright (e2e) - **Local infra:** docker-compose (postgres+pgvector, redis) ## Repo structure ``` curio/ CLAUDE.md docker-compose.yml docs/curio-specification.md src/ app/ # Next.js App Router: reading surface + api/ route handlers components/ lib/ llm/ # provider-agnostic client (Vercel AI SDK) + versioned prompt registry generation/ # RAG-grounded content generation verification/ # content cascade (T0–T2) + response grading memory/ # mastery (BKT/Elo) + spaced repetition intent/ # intent normalization + blueprint lookup db/ # drizzle schema, queries, migrations schemas/ # zod contracts (shared) styles/ # design tokens jobs/ # BullMQ workers (deferred until P3) tests/golden/ # frozen eval sets: content verification + response grading e2e/ # playwright ``` ## Architectural invariants — violating these is a bug 1. **All LLM access goes through `src/lib/llm/client.ts`.** Never call a provider/AI-SDK model directly elsewhere. Generator and grader pull their model from config; they must differ. 2. **Prompts live in the versioned registry** (`src/lib/llm/prompts/`), never inline. Each has an id + version. 3. **Contracts are Zod schemas** in `src/schemas/`, shared by the LLM layer, the API, and the UI. LLM structured output is validated against them. 4. **The serve path reads from the Redis buffer; jobs generate.** No synchronous content generation in request handlers except the documented cold-start gate. Postgres is the source of truth — a Redis outage degrades latency, not availability. 5. **Generated content stores provenance** (`source_chunk_ids`) so verification and grading are *grounded* against sources, not free-judged. 6. **Grading is constrained.** The grader evaluates against a pre-verified rubric + reference answer + misconception library. Verdict ∈ {`mastered`,`partial`,`misconception`,`uncertain`}. It **must** support `uncertain` (abstain → reveal). **Bias against false-fail** — telling a correct learner they're wrong is the worst error. 7. **Verification is a cascade.** T0 structural (free) → T1 cheap-model → T2 strong-model (adversarial, at blueprint promotion). Expensive checks amortized once per blueprint, never per serve. 8. **Paid background jobs are idempotent** (idempotency key + durable `job` state). A retry must never regenerate-and-rebill. 9. **Per-session token budget is enforced in code.** The ~10-min Lesson bound is a real cap. 10. **Content is versioned** by `(intent_key, content_version, model_version)`; invalidation is lazy. ## Design invariants (see spec §7) - **Thesis:** a calm, editorial reading surface that comes alive only when the learner responds. Not gamified, not a dashboard, not a chat-bubble app. - **Signature:** diagnosis-as-**marginalia** — feedback threaded against the learner's own words, pointing at the `evidence_span` that tripped them. Spend the design's boldness here; keep everything else quiet. - **Motion** is meaning, not decoration: reserved for the diagnosis moment (a brief "considering" beat → feedback eases in). CSS/View Transitions only. - **Reading is the product:** humanist reading serif, generous measure (~62–68ch) and leading; display face used with restraint; tokens centralized in `src/styles/`. - **Quality floor (non-negotiable):** responsive to mobile, visible keyboard focus, reduced-motion respected, WCAG-AA contrast (including every diagnosis-state color). - **Before building any UI, run the frontend-design two-pass process** (plan tokens → critique for genericness → build). Avoid AI-default looks (cream+serif+terracotta, near-black+acid accent, broadsheet hairlines). - Copy is design material: warm, specific, plain, on the learner's side; never gamified or condescending. ## Commands ```bash docker compose up -d # postgres+pgvector, redis pnpm install # or npm pnpm db:generate && pnpm db:migrate pnpm dev # Next.js pnpm test # vitest (unit; LLM mocked) pnpm test:golden # real-model eval harness (CI-gated) pnpm test:e2e # playwright pnpm lint && pnpm format # eslint + prettier ``` ## Testing rules - Write/extend tests **with** each change; run the suite before declaring a task done. - **Mock the LLM in unit tests** (deterministic fixtures). Real-model calls only in the golden-eval suite. - Golden evals (`tests/golden/`) are the bar for the moat: response-grading accuracy (track **false-fail rate**) + content-verification accuracy. Don't regress them; add cases on new failure modes. - Migrations: never edit an applied drizzle migration; add a new one. ## Out of scope — do NOT build unless explicitly asked Gamification (XP/badges/leaderboards), infinite feed / social, multi-provider fallback orchestration, knowledge graph, voice, PDF ingestion, accounts (MVP is anonymous-session-first). If a task seems to require one of these, stop and ask. ## Working agreement - **Plan before coding** for anything non-trivial; surface architecture- or design-affecting choices before implementing. - Small, focused commits per task. One ticket = one logical change. - Keep Route Handlers thin; logic lives in `src/lib/`. - Run the frontend-design planning pass before UI tickets. - If you change a convention here, update this file in the same commit. - When unsure between two reasonable designs, ask rather than guess — especially on the grading/verification path and the diagnosis UI.