8.6 KiB
CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Project Overview
MOPC (Monaco Ocean Protection Challenge) — a secure jury voting platform for managing multi-round project selection. Jury members evaluate ocean conservation projects through configurable competitions with rounds (intake, filtering, evaluation, submission, mentoring, live finals, deliberation). Domain: monaco-opc.com.
Common Commands
# Development
npm run dev # Next.js dev (Turbopack)
npm run build # Production build (always test before push)
npm run typecheck # tsc --noEmit
# Database
npx prisma generate # Regenerate client after schema changes
npx prisma migrate dev # Create + apply migration
npx prisma studio # GUI database browser
npm run db:seed # Run seed (tsx prisma/seed.ts)
# Testing (vitest, not jest)
npx vitest # Run all tests (watch mode)
npx vitest run # Run all tests once
npx vitest run tests/unit/round-engine.test.ts # Single file
npx vitest run -t 'test name' # Single test by name
# Code Quality
npm run lint # ESLint
npm run format # Prettier
# Docker (production)
docker compose -f docker/docker-compose.yml up -d
docker compose -f docker/docker-compose.dev.yml up -d # Dev stack
Tech Stack
Next.js 15 (App Router) | TypeScript 5 (strict) | Tailwind CSS 4 | shadcn/ui | tRPC 11 (superjson) | Prisma 6 | PostgreSQL 16 | NextAuth 5 (Auth.js) | Vitest 4 | OpenAI SDK 6 | MinIO (S3) | Nodemailer + Poste.io
Architecture
Data Flow: Prisma → tRPC → React
End-to-end type safety: prisma/schema.prisma defines models → src/server/routers/*.ts expose tRPC procedures with Zod validation → src/lib/trpc/client.ts provides typed React hooks → components call trpc.domain.procedure.useQuery().
tRPC Middleware Hierarchy (src/server/trpc.ts)
All role-based access is enforced via procedure types:
| Procedure | Roles Allowed |
|---|---|
publicProcedure |
Anyone (no auth) |
protectedProcedure |
Any authenticated user |
adminProcedure |
SUPER_ADMIN, PROGRAM_ADMIN |
superAdminProcedure |
SUPER_ADMIN only |
juryProcedure |
JURY_MEMBER only |
mentorProcedure |
SUPER_ADMIN, PROGRAM_ADMIN, MENTOR |
observerProcedure |
SUPER_ADMIN, PROGRAM_ADMIN, OBSERVER |
awardMasterProcedure |
SUPER_ADMIN, PROGRAM_ADMIN, AWARD_MASTER |
audienceProcedure |
Any authenticated user |
Route Groups (Next.js App Router)
src/app/(auth)/— Public auth pages (login, verify, accept-invite, onboarding)src/app/(admin)/— Admin dashboard, competition management, round configsrc/app/(jury)/— Jury evaluation interface, round assignments, live votingsrc/app/(applicant)/— Applicant dashboard, competition progress, document uploadssrc/app/(mentor)/— Mentor workspace
Competition System
The core domain model. A Competition represents a complete evaluation cycle:
Competition → has many Rounds (ordered)
- Competition (
src/server/routers/competition.ts): Top-level competition config with program link, status, and settings - Round (
RoundType: INTAKE | FILTERING | EVALUATION | SUBMISSION | MENTORING | LIVE_FINAL | DELIBERATION): Each competition has ordered rounds with type-specific config inconfigJson
Key models:
JuryGroup— Named jury panel with chair/member/observer rolesAdvancementRule— Auto-advance, score threshold, top-N, admin selection between roundsSubmissionWindow— File submission deadlines per roundAssignmentPolicy/AssignmentIntent— Governance layer for jury assignmentProjectRoundState— Per-project state within each roundDeliberationSession/DeliberationVote/ResultLock— Structured deliberation and result finalization
Key services:
src/server/services/round-engine.ts— State machine for round transitionssrc/server/services/round-assignment.ts— Jury assignment generation with policy enforcementsrc/server/services/submission-manager.ts— File submission + filtering with duplicate detectionsrc/server/services/deliberation.ts— Deliberation session management and vote tallyingsrc/server/services/result-lock.ts— Result finalization and unlock governancesrc/server/services/live-control.ts— Live ceremony cursor managementsrc/server/services/competition-context.ts— Cross-cutting competition context resolver
Competition types: src/types/competition.ts, src/types/competition-configs.ts
AI Services (src/server/services/ai-*.ts)
All AI calls anonymize data before sending to OpenAI. Services:
ai-filtering.ts— AI-powered project screening with rubricai-assignment.ts— GPT-suggested jury-project matchingai-evaluation-summary.ts— Strengths/weaknesses synthesis from evaluationsai-tagging.ts— Auto-tagging projectsai-award-eligibility.ts— Award eligibility assessmentai-shortlist.ts— AI-powered shortlist recommendationsanonymization.ts— Strips PII before AI calls
Auth System
NextAuth v5 with two providers: Email (magic links) and Credentials (password + invite token). Failed login tracking with 5-attempt lockout (15 min). Session includes user.role for RBAC.
Docker Entrypoint (docker/docker-entrypoint.sh)
On container start: retry prisma migrate deploy → prisma generate → auto-seed if User table is empty → node server.js. A docker compose down -v && docker compose up -d will run all migrations from scratch and seed.
Testing Infrastructure
- Framework: Vitest 4 with
fileParallelism: falseandpool: 'forks'(tests run sequentially) - Setup:
tests/setup.tsprovidesprismaclient (usesDATABASE_URL_TESTorDATABASE_URL),createTestContext(user),createCaller(router, user) - Factories:
tests/helpers.tshascreateTestUser(),createTestProgram(),createTestCompetition(),createTestRound(),uid()helper, andcleanupTestData() - Pattern: Create data with factories → build caller with
createCaller(routerModule, user)→ call procedures → assert → cleanup inafterAll
User Roles
SUPER_ADMIN | PROGRAM_ADMIN | JURY_MEMBER | OBSERVER | MENTOR | APPLICANT | AWARD_MASTER | AUDIENCE
Coding Standards
- TypeScript: Strict mode,
typeoverinterface, preferunknownoverany - Files: kebab-case. Components: PascalCase. DB models: PascalCase in Prisma
- React: Server Components by default,
'use client'only when needed - Styling: Tailwind utility classes, mobile-first (
md:,lg:breakpoints), shadcn/ui as base - tRPC: Group by domain (
trpc.competition.create()), Zod input validation,TRPCErrorfor errors - Brand colors: Primary Red
#de0f1e, Dark Blue#053d57, White#fefefe, Teal#557f8c - Typography: Montserrat (600/700 headings, 300/400 body)
Key Constraints
- Jury can only see assigned projects (enforced at query level)
- Voting windows are strict — submissions blocked outside active window
- All admin actions are audited via
DecisionAuditLog - Files accessed via MinIO pre-signed URLs only (no public bucket)
- COI declaration required before evaluation (blocking dialog)
- Smart assignment skips COI-declared jurors, applies geo-diversity penalty and familiarity bonus
- Cron endpoints protected by
CRON_SECRETheader - Round notifications never throw — all errors caught and logged
Security
- CSRF: tRPC uses
application/json(triggers CORS preflight). Do NOT add permissive CORS headers. - Rate limiting: 100 req/min tRPC, 10 req/min auth, 5-attempt lockout
- AI privacy: All data anonymized before OpenAI calls
Seed Data
prisma/seed.ts imports from docs/Candidatures 2026 *.csv with special handling for non-breaking spaces (U+00A0) around French guillemets. Includes ALL CSV rows (no filtering/dedup) — duplicate detection happens in submission-manager.ts at runtime.
Environment Variables
Required: DATABASE_URL, NEXTAUTH_URL, NEXTAUTH_SECRET, MINIO_ENDPOINT, MINIO_ACCESS_KEY, MINIO_SECRET_KEY, MINIO_BUCKET, SMTP_HOST, SMTP_PORT, SMTP_USER, SMTP_PASS, EMAIL_FROM, OPENAI_API_KEY, CRON_SECRET
External services (pre-existing on VPS): MinIO :9000, Poste.io :587, Nginx reverse proxy with SSL.
Git
Remote: code.monaco-opc.com/MOPC/MOPC-Portal. Branch: main. Always npm run build before pushing.