Files
Curio/CLAUDE.md
T
arnaudne 4e38b5a791 feat: initial commit — Curio mastery tutor
Full Next.js App Router application with:
- AI-graded lesson checkpoints (BKT/Elo mastery tracking)
- Auth.js v5: credentials, Google, GitHub, generic OIDC
- Anonymous-session-first with migrate-on-signin
- Admin panel: users, blueprints, reports, site settings
- Password reset + email verification (nodemailer/SMTP)
- Site config: require_auth + signups_enabled flags
- server-only guards on all DB/generation/verification modules
- PostgreSQL 16 + pgvector, Redis cache, Drizzle ORM
- 271 unit tests (Vitest), golden-eval harness, Playwright e2e stubs

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-21 22:25:43 +02:00

7.7 KiB
Raw Blame History

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 (T0T2) + 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 (~6268ch) 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

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.