# CDACC Learning Plan AI

An AI-powered curriculum planning tool for Kenyan TVET trainers. Generates, reviews, and approves CDACC/CBET-aligned learning and session plans using GPT-4o.

## Run & Operate

- `pnpm --filter @workspace/api-server run dev` — run the API server (port 8080)
- `pnpm --filter @workspace/cdacc-app run dev` — run the React frontend (port 18585)
- `pnpm run typecheck` — full typecheck across all packages
- `pnpm run build` — typecheck + build all packages
- `pnpm --filter @workspace/api-spec run codegen` — regenerate API hooks and Zod schemas from the OpenAPI spec
- `pnpm --filter @workspace/db run push` — push DB schema changes (dev only)
- Required env: `DATABASE_URL` — Postgres connection string

## Stack

- pnpm workspaces, Node.js 24, TypeScript 5.9
- Frontend: React + Vite + Tailwind v4 + shadcn/ui + Wouter routing
- API: Express 5 + @clerk/express for auth
- Auth: Clerk (whitelabel, provisioned via setupClerkWhitelabelAuth())
- DB: PostgreSQL + Drizzle ORM
- AI: OpenAI GPT-4o via @workspace/integrations-openai-ai-server
- Validation: Zod (zod/v4), drizzle-zod
- API codegen: Orval (from OpenAPI spec at lib/api-spec/openapi.yaml)
- Build: esbuild (CJS bundle)

## Where things live

- `artifacts/cdacc-app/` — React + Vite frontend, routes on `/`
- `artifacts/api-server/` — Express API server, routes on `/api`
- `lib/api-spec/openapi.yaml` — source-of-truth for all API contracts
- `lib/api-client-react/` — generated React Query hooks
- `lib/api-zod/` — generated Zod validation schemas
- `lib/db/` — Drizzle ORM schema and database client
- `lib/integrations-openai-ai-server/` — OpenAI client wrapper

## Architecture decisions

- Contract-first API: OpenAPI spec → Orval → typed React Query hooks + Zod schemas
- All backend routes validate inputs/outputs using Zod schemas from lib/api-zod
- Clerk auth proxied through `/api/__clerk` — VITE_CLERK_PROXY_URL=/api/__clerk
- AI generation uses GPT-4o with JSON mode; existing unlocked sessions are replaced on regeneration
- Documents support RAG: extractedText field is prepended to AI prompts when documentIds are passed

## Product

- Landing page for unauthenticated users → Clerk sign-up/sign-in
- Dashboard: stats cards (total/approved/pending/draft plans) + recent plans list
- Plans: list, create, view detail, AI generate sessions, per-session editing/regeneration
- Session table: inline editing of all CDACC columns (week/session/topic/outcomes/activities/resources/assessment/criteria)
- Approval workflow: Trainer → HOD → IQA, with comments
- Document library: register documents for RAG context in AI generation
- Templates: view plan templates
- Settings: user profile management

## User preferences

- Use `getAuth(req)` from `@clerk/express` (not `req.auth`) for all auth checks in route handlers
- Document table uses `title` (not `fileName`), `filePath` (not `storageUrl`)
- Never use `console.log` in server code — use `req.log` or `logger`
- Hook signatures for list endpoints: `useListPlans(params?, options?)` — params before options
- Approval stage enum: `trainer_review | hod_review | iqa_review | final_approval`
- Approval status enum: `pending | approved | rejected | revision_requested`

## Gotchas

- Always run `pnpm --filter @workspace/api-spec run codegen` after changing openapi.yaml
- Run `pnpm --filter @workspace/db run push` after changing lib/db/src/schema.ts
- `publishableKeyFromHost` is in `@clerk/shared/keys` (server-side) — not available in browser bundles
- Frontend: use `import.meta.env.VITE_CLERK_PUBLISHABLE_KEY` directly
- Plans route maps: `trainer_approved → hod_review`, `hod_approved → iqa_review`, `iqa_approved → approved`

## Pointers

- See the `pnpm-workspace` skill for workspace structure, TypeScript setup, and package details
- See `.local/skills/clerk-auth` for Clerk auth setup and customization
