Last updated: 2026-04-07 21:05:42 +08
This file provides guidance to Codex (ChatGPT) when working with code in this repository.
STEM Learning Platform with GenAI - A production-ready educational platform where teachers transform class materials into structured Course Blueprints that power student activities (AI chat, quizzes, flashcards, homework help, exam review).
Stack: Next.js 16 (App Router), TypeScript, Supabase (PostgreSQL, Auth, Storage, RLS), Tailwind CSS 4, Vitest
pnpm install # Install all dependencies (from monorepo root)
pnpm dev # Run Next.js dev server (uses --webpack; required for Next.js 16 + React 19)
pnpm build # Build for production (--webpack required; turbopack not yet stable)
pnpm start # Run production server
pnpm lint # Run ESLint
pnpm test # Run Vitest unit tests
pnpm test:watch # Run Vitest in watch mode
pnpm export:mermaid-png # Export Mermaid diagrams in ARCHITECTURE.md as PNG images
# Python backend
pip install -r backend/requirements.txt # Install Python deps
uvicorn app.main:app --app-dir backend --host 0.0.0.0 --port 8001 --reload # Run backend locally
python3 -m unittest discover -s backend/tests -p 'test_*.py' # Run Python testsRun a single Vitest unit test file:
pnpm vitest run path/to/testfile.test.tsE2E tests live in tests/ and run against a deployed URL (default: Vercel preview/production).
# First-time setup
pnpm exec playwright install # Install browser binaries
cp tests/.env.example tests/.env # Fill in credentials (gitignored)
# Run all E2E tests
npx playwright test --config tests/playwright.config.ts
# Run a single spec
npx playwright test --config tests/playwright.config.ts tests/e2e/teacher-nav.spec.ts
# Open HTML report after a run
npx playwright show-report tests/results/html-reportRequired env vars in tests/.env:
| Variable | Description |
|---|---|
E2E_BASE_URL |
Target URL (defaults to Vercel deployment) |
E2E_TEACHER_EMAIL / E2E_TEACHER_PASSWORD |
Teacher test account |
E2E_STUDENT_EMAIL / E2E_STUDENT_PASSWORD |
Student test account (optional) |
E2E_JOIN_CODE |
Valid class join code (optional; skips join-class test if absent) |
- This repository has two configured remotes for push/fetch:
originandorg. - When pushing a branch, push to both remotes so they stay synchronized.
- Recommended commands:
git push origin HEAD
git push org HEAD- Treat this file and the reference docs under
./.codex/as living project guidance. - Every time
AGENTS.mdis updated, write the current date and time immediately under the# AGENTS.mdheading. - Periodically review and refresh the companion docs under
./.codex/, especiallyMEMORY.md,architecture.md,testing.md,ui-patterns.md,guest-mode.md, and any other project guidance files added later. - When a task changes architecture, workflow, testing approach, UI conventions, guest mode behavior, or other durable project knowledge, update the relevant
./.codex/doc in the same session whenever practical. - Prefer adding a visible
Last updatednote near the top of each./.codex/reference doc when it is refreshed so future sessions can quickly judge freshness.
./.codex/MEMORY.md- high-level memory index, active work, and cross-links to deeper docs./.codex/architecture.md- monorepo structure, system boundaries, deployment/build notes, and key paths./.codex/testing.md- test commands, mocking patterns, and testing caveats./.codex/ui-patterns.md- UI primitives, tokens, motion conventions, and recent UI decisions./.codex/guest-mode.md- guest-mode product decisions, constraints, and implementation direction- Check these docs before making broad changes, and keep them aligned with the source code and
AGENTS.md.
- Do not use one-line-only commit messages for substantive work in this repository.
- Prefer a verbose commit message with a clear subject line plus a body that explains the problem, the scope of the change, and any important implementation notes or follow-up context.
- If a helper skill suggests a terse commit message, follow this repository rule instead and expand the commit message appropriately.
- GitHub PR summaries should follow the spirit of the
yeetworkflow but be clearer, more structured, and more detailed than the default terse form. - For larger PRs, write a detailed PR body with clear sections covering the user-visible problem, root cause, key changes, risks, validation, and any rollout or follow-up notes.
cd web/
# Check Vercel version
npx vercel --version
# Check logged-in user
npx vercel whoami
# Deploy to production
npx --yes vercel --yes --prod
# Deploy to preview (staging)
npx vercel
# View deployment logs
npx vercel logs ai-stem-learning-platform-group-8
# Inspect a deployment
npx vercel inspect <deployment-url># Link to Supabase project
supabase link --project-ref <project-ref>
# Apply migrations
supabase db push
# Create new migration
supabase migration new migration_name
# Start local Supabase instance
supabase start
# View Supabase logs
supabase functions logs <function-name>Apply migrations via MCP (if configured):
# Use the supabase MCP tool to execute SQL
mcp__supabase__execute_sql --sql "SELECT 1"| Purpose | Path |
|---|---|
| Supabase browser client | web/src/lib/supabase/client.ts |
| Supabase server client | web/src/lib/supabase/server.ts |
| AI adapter entry (Next.js → Python) | web/src/lib/ai/python-backend.ts |
| Server actions (root) | web/src/app/actions.ts |
| Activity access/assignments | web/src/lib/activities/ |
| Analytics/class insights | backend/app/analytics.py |
| Global styles & design tokens | web/src/app/globals.css |
| Auth session helpers (server-side) | web/src/lib/auth/session.ts |
| Auth URL/redirect helpers | web/src/lib/auth/ui.ts |
| Auth surface (modal + page) | web/src/components/auth/AuthSurface.tsx |
| Guest mode config + utilities | web/src/lib/guest/ |
| Supabase email templates | supabase/templates/ |
| E2E test specs | tests/e2e/ |
| Playwright config | tests/playwright.config.ts |
Monorepo Structure:
web/- Next.js application with App Routerbackend/- Python FastAPI service for AI provider orchestrationsupabase/- Database migrations and Supabase configurationtests/- Playwright E2E test suite (runs against deployed URL)
Key Boundaries:
- Web App: UI, role-based routing, client-side workflows
- API Layer: Server actions and API routes for all data writes
- AI Orchestrator: Provider adapters (OpenAI, Gemini, OpenRouter), prompt templates, safety checks
- Data Layer: Supabase with Row Level Security (RLS) policies
- Apply migrations via Supabase CLI or dashboard SQL editor
- Baseline schema:
supabase/migrations/0001_init.sql - Incremental migrations in
supabase/migrations/
- Copy
web/.env.exampletoweb/.env.local - Required variables:
NEXT_PUBLIC_SUPABASE_URLNEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEYSUPABASE_SECRET_KEYNEXT_PUBLIC_SITE_URL(e.g.http://localhost:3000; used for auth email redirect links)- At least one AI provider key:
OPENAI_API_KEY,GEMINI_API_KEY, orOPENROUTER_API_KEY PYTHON_BACKEND_URL(default:http://localhost:8001for local dev)PYTHON_BACKEND_API_KEY(set a shared value in both web and backend, or explicitly enablePYTHON_BACKEND_ALLOW_UNAUTHENTICATED_REQUESTS=truefor local-only dev)
- Optional variables:
NEXT_PUBLIC_GUEST_MODE_ENABLED— settrueto enable the guest entry flow (off by default)
Blueprint Lifecycle: Draft → Overview (Approved) → Published (read-only, student-facing)
AI Provider Policy: Pluggable adapter interface supporting OpenAI, Gemini, OpenRouter. Configuration is environment-driven. Providers can be swapped without changing feature logic.
Python Backend: All AI generation (blueprints, quiz, flashcards, chat, embeddings) routes through the FastAPI backend/ service. Next.js server actions call web/src/lib/ai/python-*.ts adapters, which proxy to the backend. Never call AI providers directly from Next.js. Response envelope is always { ok, data, error, meta }.
Canvas / Generative Layout: backend/app/canvas.py supports AI-driven layout generation for the student chat view and teacher insights panel. Layouts are generated per-session using the blueprint as context.
Class Analytics: backend/app/analytics.py + migration 0013_add_class_insights_snapshots.sql persist aggregated class intelligence snapshots for the teacher dashboard.
Auth Surface: A single AuthSurface component handles all auth flows (sign-in, sign-up, forgot-password). It renders as a modal on the home page (triggered by ?auth=sign-in / ?auth=sign-up query params) or as a page at /login and /register. The HomeAuthDialog wraps the modal variant. Auth helpers live in web/src/lib/auth/ui.ts (getAuthHref, buildRedirectUrl) and session.ts (getAuthContext).
Guest Mode: Toggled by NEXT_PUBLIC_GUEST_MODE_ENABLED=true. Guests sign in via Supabase Anon Auth and get a sandboxed session (32h max, 8h inactivity). Guest entry is created via /guest/enter and enforced through the global guest_session_quota RPC flow rather than per-IP tracking. Current defaults are 60 active guest sessions globally and 20 new guest sessions per hour; guest AI defaults are 50 chat, 10 quiz, 10 flashcards, 5 blueprint, and 15 embedding operations with 20 concurrent AI requests. Guest mode is enforced at the DB layer via RLS and quota migrations (0015–0023). Guest config lives in web/src/lib/guest/.
Security: RLS enforced on all tables, input validation on every API route and server action, file uploads are size-limited and content-type checked. AI context restricted to approved materials and blueprint.
- Shared UI primitives live in
web/src/components/uiand should be preferred over ad-hoc page-local controls. - Utility helpers and variant merging:
web/src/lib/utils.ts(cn)class-variance-authoritypatterns for variant-driven components
- Icons should be consumed from
web/src/components/icons/index.tsx(Lucide registry) rather than inline SVG in pages/components, except approved exceptions (brand mark and semantic diagrams). - Motion should use:
- global provider:
web/src/components/providers/motion-provider.tsx - reusable variants/transitions:
web/src/lib/motion/presets.ts
- global provider:
- Preserve semantic warm tokens in
web/src/app/globals.css; avoid introducing hardcoded color classes where token utilities exist.
- Store all implementation plans, session trackers, and working notes under
.claude/plans/— not indocs/or the repo root. .claude/is gitignored-safe for local-only files and avoids branch noise for plan files that don't belong in PR diffs.
- Email/password auth only;
profiles.account_typeis immutable (teacher or student) - Guest sessions use Supabase Anon Auth — not email/password. They are sandboxed and expire automatically.
- Material ingestion is queue-driven on Supabase (
pgmq+ Edge Function worker) - Chat uses long-session context engineering with memory compaction
- All AI outputs are saved before use and are editable/auditable by teachers
- Branded confirmation email template lives in
supabase/templates/confirmation.html(must be applied in Supabase Dashboard → Auth → Email Templates)
- Edge Function Secrets: When using AI providers (like
OPENROUTER_*) in Supabase Edge Functions, secrets must be set in Edge Function Secrets (in Supabase Dashboard → Edge Functions → Secrets), not in the Vault. Edge Functions cannot access Vault secrets. - Vercel + Supabase Integration: While the Vercel + Supabase integration plugin allows Vercel to access Supabase secrets, the reverse is not true—Supabase Edge Functions cannot access secrets stored in Vercel. All secrets required by Edge Functions must be configured directly in Supabase.
- httpx trust_env: All
httpx.Client(...)calls in the Python backend must includetrust_env=Falseto avoid picking up proxy env vars in production; omitting it causes silent connection failures in certain deploy environments. - Cursor pagination validation: Cursor tokens passed to PostgREST must be validated as UUID + ISO-8601 format before string interpolation to prevent injection. See
backend/app/chat_workspace.py. - Message timestamp ordering: When persisting user + assistant message pairs, offset assistant timestamp by 1ms to guarantee correct ordering in queries that sort by
created_at. - Supabase Storage iframe embedding: Supabase Storage serves files with
X-Frame-Optionsheaders that block cross-origin iframe embedding. Never use signed URLs directly as iframesrc. Instead, fetch the file as a blob viafetch(signedUrl), create a same-origin blob URL withURL.createObjectURL(blob), and use that. Always clean up withURL.revokeObjectURL()in both the dialog close handler and auseEffectcleanup. SeeMaterialActionsMenu.tsxfor the reference implementation. - CSS keyframes + Tailwind v4 centering: Tailwind v4 applies centering via the standalone CSS
translateproperty (not insidetransform). Keyframetransformvalues must therefore contain only scale/Y-offset; never embed-50% -50%centering offsets inside a@keyframesblock — doing so fights the framework and breaks dialog positioning.