3.7 KiB
CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Commands
pnpm dev # Start dev server
pnpm build # Production build
pnpm lint # ESLint
pnpm typecheck # tsc --noEmit
pnpm test # Vitest unit tests (run once)
pnpm test:e2e # Playwright E2E (requires dev server running)
pnpm db:generate # Regenerate Prisma client after schema changes
pnpm db:push # Sync schema to DB (dev, no migration files)
pnpm db:migrate # Apply migrations (production)
pnpm db:seed # Seed with sample data
pnpm db:studio # Open Prisma Studio
Package manager: pnpm.
Coding conventions
Server-first by default
This codebase maximises server-side rendering and minimises client-side JavaScript:
- All pages are server components by default. Never add
"use client"to a page file. - Data fetching always happens server-side in
src/lib/server-data.ts— neverfetch()from the client, never call Prisma from a client component. - All mutations use server actions (
src/actions/) — never create new API routes for mutations. The only remaining API routes are the export endpoints and auth (see below). "use client"is only added to components that genuinely need browser APIs or React state (forms, interactive widgets). Keep the surface area small.- Auth checks happen in every server action via
const session = await auth()before touching the DB.
Data flow pattern
- Server page calls
src/lib/server-data.tsfunctions (which callauth()+ Prisma internally) - Page passes serialized data as props to client components in
src/components/ - Client components call server actions (
src/actions/) for mutations - Server actions call
revalidatePath()to trigger cache invalidation
Auth
src/auth.ts — NextAuth v5 with Credentials provider (email + bcrypt password). JWT strategy with id and role added to the token. Two roles: evaluator (default) and admin.
src/middleware.ts — Protects all routes. Admin routes redirect non-admins. Auth routes redirect logged-in users to /dashboard.
Access control
src/lib/evaluation-access.ts — canAccessEvaluation() is the single source of truth. An evaluation is accessible if: user is admin, user is the evaluator, evaluation is shared with the user (EvaluationShare), or isPublic is true (read-only).
Database
SQLite in dev (DATABASE_URL=file:./dev.db), swap to Postgres for production. Schema lives in prisma/schema.prisma. Key relations:
Evaluation→Template→TemplateDimension[]Evaluation→DimensionScore[](one per dimension,@@unique([evaluationId, dimensionId]))Evaluation→EvaluationShare[](many users)Evaluation→AuditLog[]
TemplateDimension.suggestedQuestions is stored as a JSON string (array). TemplateDimension.rubric is stored as a "1:X;2:Y;..." string. Both are parsed client-side.
API routes (remaining)
Only exports and auth use API routes:
GET /api/export/csv?id=andGET /api/export/pdf?id=— usesrc/lib/export-utils.tsPOST /api/ai/suggest-followups— stub, returns deterministic suggestions; replace with real LLM call if neededPOST /api/auth/signup— user registration
Key types
src/types/next-auth.d.ts extends Session.user with id and role. Always use session.user.id (never session.user.email) as the user identifier in server actions.
JWT staleness
session.user.name comes from the JWT token frozen at login time. If a page needs the user's current name (or any other mutable profile field), query Prisma directly using session.user.id — do not rely on the session object for those values.