# Project export: Veridian

This document was generated by HackStack to give an AI agent context about a hackathon project. Sections are labeled with their provenance; content marked as truncated was cut to keep this document small.

## Project metadata

- Hackathon: TreeHacks 2026
- Tagline: Veridian turns classroom mistakes into structured data, making the learning process visible. Instead of grading outputs, teachers and students alike see thinking, gaps, and growth in real time.
- Devpost: https://devpost.com/software/veridian-3mjsx4
- GitHub: https://github.com/VeridianTH/Veridian
- Demo: https://veridian.fyi/
- Video: https://www.youtube.com/embed/zI34ytYzpI0?enablejsapi=1&hl=en_US&rel=0&start=&version=3&wmode=transparent
- Team: 6 GitHub contributor(s) — Ethan Tamer Farah (136 commits), Claude Opus 4.6 (97 commits), Jonathan Chen (49 commits), ethanharianto (49 commits), davidmaemoto (37 commits), Cursor (13 commits)

## Devpost submission (written by the team)

### Inspiration

Education is entering an AI arms race. Students feel constant pressure to ``keep up.'' Some use AI to skip hard thinking and get answers instantly. Others don’t have the same access and feel like they’re falling behind anyway. Everyone is afraid of being wrong. Teachers are left grading final outputs while the real learning process stays invisible. That’s the fracture Veridian was built for. Classrooms already generate massive learning signal: drafts, crossed-out steps, partial reasoning, misconceptions, and recovery attempts. Today, most of that disappears. Teachers get snapshots. Students get scores. Nobody gets a true diagnostic of how learning happened. Veridian starts with one question: What if every mistake became structured data? We’re building an AI-native classroom runtime where errors are not failures to hide, but the highest-value signal in the room. What Veridian Does Veridian is a full-stack EdTech platform with symmetric teacher and student surfaces. It outperforms existing edtech platforms like Canvas and Google Classroom by unifying the full assignment workflow—creation tech stack, including writing/completion, and submission—into a single AI-powered, data-driven platform Veridian includes AI driven learning insights from student data, seamless digital notebook integrations for easy assignment submission, and AI assisted notetaking autocomplete plus correction within digital homework notebooks. Teacher Side Create classrooms students can join Upload a course corpus per classroom (PDFs, TeX, lecture notes, reference material) Generate auto-formatted assignments delivered directly to students Configure runtime policies (for example: when analysis runs, whether chat is allowed, and how much guidance students receive) Review submissions, mistake patterns, and classroom-level analytics Student Side Work inside a digital canvas for notes and problem-solving Get AI-assisted writing/autocomplete with mistake tracking, when enabled by the teacher Receive real-time mistake analysis during work, based on teacher-defined classroom settings Use Socratic chat guidance when teacher-enabled Operate under teacher-defined runtime policy via resolved_config (the effective classroom configuration applied to each student session) At its core, Veridian converts raw student work into structured mistake intelligence, giving students better feedback loops and giving teachers visibility into learning that used to happen behind closed doors. How We Built It We built Veridian as a mirrored system by design: teacher/backend (Flask) teacher/frontend (Expo React) student/backend (Flask) student/frontend (Expo React) supabase/ (shared schema + migrations) This symmetry is intentional: one platform, two operating surfaces, one source of truth. Backend Flask APIs for teacher and student runtimes JWT auth with strict membership gating (401 / 403) Supabase as the shared persistence layer Config-driven student runtime via GET /assignments/:id, returning teacher-controlled resolved_config Frontend Expo React apps for both teacher and student Shared design tokens and reusable UI primitives (src/components/ui/) Canvas-first student workspace AI analysis trigger modes: auto_idle auto_page_change manual_only passive auto_idle auto_page_change manual_only passive We enforced hard boundaries between: Auth Classroom logic Assignment configuration AI runtime behavior Challenges We Ran Into 1. Runtime policy enforcement at the edge Teacher intent had to survive all the way to the student session. That required strict backend contracts and disciplined frontend state orchestration. 2. Hard role separation We enforced single-role identity (Teacher or Student). It made permissioning safer, but raised the bar for membership and access flows. 3. Multi-layer state sync Assignments, submissions, and AI feedback had to stay coherent across Supabase, Flask APIs, and Expo client state in near real time. 4. Automation vs pedagogy Choosing when AI should intervene (auto_idle) versus when students should drive (manual_only) was a product and learning-design challenge, not just an engineering one. Accomplishments We’re Proud Of AI-assisted handwritten note completion inside the student canvas, so students can keep working without losing momentum AI mistake localization with red-dot markers and suggestion captions that pinpoint exactly where an error occurs for instant correction and review Symmetric architecture that scales without chaos Teacher-governed, config-driven student runtime Fully gated endpoints with role-based access control AI mistake analysis embedded directly in the workflow Repeatable end-to-end classroom demo path Supabase migrations that cleanly separate teacher/student concerns Most importantly: Veridian behaves like infrastructure, not a prototype. What We Learned AI in classrooms must be policy-driven, not always-on. Mistake analysis is strongest inside the workflow, not after the fact. Architectural symmetry lowers team cognitive load and speeds iteration. Strong backend contracts prevent frontend state blowups. In education, teacher trust is the adoption curve. What’s Next For Veridian Longitudinal mistake trajectories across assignments Teacher-side misconception heatmaps Adaptive assignment branching based on error patterns Fine-grained error distribution analytics Agentic reasoning over a student’s full historical mistake profile Institutional deployment model for district and campus rollout Our long-term vision: Veridian becomes the mistake intelligence layer for modern classrooms.

## README (from the GitHub repository)

# Veridian - EducAItion

Full EdTech platform: teacher side (classrooms, assignments, corpus, submissions) and student side (canvas, AI mistake analysis, Socratic chat). Shared Supabase.

## Student Runtime Config (PR #4)

- Student assignment behavior is now controlled by teacher `resolved_config` values returned by student backend `GET /assignments/:id`.
- Endpoint contract is auth-protected and membership-gated (`401` unauthenticated, `403` non-member).
- Student app honors:
  - `analysis_trigger`: `auto_idle`, `auto_page_change`, `manual_only`, `passive`
  - `check_button_visible`, `chat_enabled`
  - `dot_threshold`, `max_dots_shown`
  - `notification_style`: `silent`, `toast`, `badge`

## Repo Structure

| Path | Purpose |
|------|---------|
| `teacher/backend/` | Teacher backend (Flask) — classrooms, assignments, corpus |
| `teacher/frontend/` | Teacher frontend (Expo React) — dashboard, assignment creation; Veridian design system (tokens + `src/components/ui/`) |
| `student/backend/` | Student backend (Flask) — mistake analysis, OCR, chat |
| `student/frontend/` | Student frontend (Expo React) — canvas, document, workspace |
| `supabase/` | Shared DB migrations |
| `scripts/` | Migration script, etc. |
| `plans/` | Feature plans |

**Layout:** Symmetric `teacher/` and `student/` each with `backend/` and `frontend/`. Shared Supabase project.

## Prerequisites

Python 3.11+, Node 18+, Expo CLI. One Supabase project (migrations run once — see Supabase Migrations below).

## Quick run (from repo root)

After one-time setup, use the scripts in `scripts/`:

- **Teacher app:** `./scripts/setup-teacher.sh` once, then `./scripts/run-teacher.sh` (or run backend and frontend separately).
- **Student platform:** `./scripts/setup-student.sh` once, then `./scripts/run-student.sh`.

See `scripts/README.md` for all script options.

## Setup

1. **Copy env** (4 apps, 4 `.env` files — each app loads from its own dir):
   - `teacher/backend/.env.example` → `teacher/backend/.env` (set `CORS_ALLOWED_ORIGINS` for your frontend hosts)
   - `teacher/frontend/.env.example` → `teacher/frontend/.env` (set `EXPO_PUBLIC_API_URL=http://localhost:5001` for teacher backend)
   - `student/backend/.env.example` → `student/backend/.env`
   - `student/frontend/.env.example` → `student/frontend/.env` (set `EXPO_PUBLIC_BACKEND_URL` for student backend, e.g. `http://localhost:8000`)
2. **Install deps**: `pip install -r requirements.txt` in `teacher/backend/` and `student/backend/`; `npm install` in `teacher/frontend/` and `student/frontend/`

(Migrations: run once per project via `./scripts/apply_migrations.sh` if needed.)

**Convention**: When adding packages or env vars, update `requirements.txt` (or `package.json`), the relevant `.env.example`, and running docs in the same PR.

## Supabase Migrations

Run in this order (fresh DB):

1. Teacher: `supabase/all_migrations.sql`
2. Teacher: `supabase/migrations/20260214000008_*.sql`, `20260214000009_*.sql`, `20260215000001_*.sql`
3. Student: `student/supabase/migrations/202602140001_*.sql`, `202602140003_*.sql`, `20260214153135_*.sql`, `20260214153124_*.sql`

**Script:** `./scripts/apply_migrations.sh` — set `SUPABASE_DB_URL`, requires `psql`. If teacher tables exist, run steps 2-3 only.

## Full Flows (Repeat Testing)

**Teacher flow:** Create classroom → upload corpus → create assignment → view submissions

```bash
# Terminal 1: Teacher backend (port 5001)
cd teacher/backend && python3 run.py

# Terminal 2: Teacher frontend
cd teacher/frontend && npx expo start
```

**Student flow:** Sample worksheet → canvas → Done → AI analysis → Socratic chat

```bash
# Terminal 1: Student backend (port 8000)
cd student/backend && python3 get_coords.py

# Terminal 2: Student frontend
cd student/frontend && npx expo start
```

**Both sides:** Run all four above. Backend required for full flows; frontend alone works for auth/login UI only.

## Demo flow

Use this sequence for a repeatable demo:

1. **Teacher:** Sign in to the teacher app.
2. **Teacher:** Create a classroom (note or copy the class code).
3. **Teacher:** Create an assignment (add title; optionally due date and assignment file).
4. **Teacher:** (Optional) Upload a corpus file for the classroom.
5. **Student:** Sign in to the student app.
6. **Student:** Join the class using the class code.
7. **Student:** Open the assignment and submit a solution.
8. **Teacher:** (Optional) View submissions for the assignment.

## Conversion Progress WebSocket

- Namespace: `/conversion` on teacher backend.
- Auth required: send teacher JWT token in Socket.IO `auth.token`.
- Subscribe payload: `{ "job_id": "<uuid>" }`.
- For PDF/TEX conversion endpoints, frontend can send `job_id` in multipart form to subscribe before conversion starts.

## Quick Checks

| Test | Command |
|------|---------|
| Teacher backend | `cd teacher/backend && python3 run.py` — hit `/classrooms` with JWT |
| Student backend | `cd student/backend && python3 get_coords.py` — `curl http://localhost:8000/health` |

After migrations: assignments load without config errors; corpus upload works.

## Production Deployment

Frontends deploy to Vercel (veridian.fyi, s.veridian.fyi). Backends on Render: teacher `https://veridian-teach.onrender.com`, student `https://veridian-fi00.onrender.com`. Set `EXPO_PUBLIC_API_URL` / `EXPO_PUBLIC_STUDENT_API_URL` (teacher) and `EXPO_PUBLIC_BACKEND_URL` (student) in Vercel env. See `plans/production-deployment.md`.

## Development

See `AGENTS.md`, `CLAUDE.md` for workflow and conventions. Running docs: `AGENTS.md`, `CLAUDE.md`, `README.md`, `PLAN.md`.


## Detected evidence (automated analysis)

Indexed codebase: 332 recognized source files, 1328 KB.
- Anthropic (technology) — detected in the code
- Flask (technology) — detected in the code
- HTML (language) — detected in the code
- JavaScript (language) — detected in the code
- OpenAI (technology) — detected in the code
- Python (language) — detected in the code
- React (technology) — detected in the code
- SQL (language) — detected in the code
- Supabase (technology) — detected in the code
- TypeScript (language) — detected in the code
- PostgreSQL (technology) — claimed on Devpost, not found in the code
- AI coding agent: Claude Code — evidence: config files committed to the repository; commit authorship or trailers
- AI coding agent: Codex — evidence: config files committed to the repository
- AI coding agent: Cursor — evidence: config files committed to the repository; commit authorship or trailers

## Codebase structure (from repository index)

### Files (120 of 355)

```
.cursor/worktrees.json
.github/workflows/claude-code-review.yml
.github/workflows/claude.yml
.github/workflows/validate.yml
.gitignore
AGENTS.md
CLAUDE.md
handwriting/__init__.py
handwriting/ocr.py
handwriting/test_latex_ocr.py
IMPLEMENTATION_SUMMARY.md
mistakes.json
PLAN.md
plans/context-powered-analysis-and-chat.md
plans/debug-next.md
plans/demo-sprint-stream-d.md
plans/hide-note-autocomplete-accept.md
plans/high-school-algebra-sample-worksheet.md
plans/pdf-preview-fallback-fix.md
plans/pr1-anthropic-thinking-sdk-fix.md
plans/production-chat-rate-limiting.md
plans/production-deployment.md
plans/production-polish.md
plans/structured-assignment-problems-authoring.md
plans/submissions-and-students-improvements.md
plans/teacher-config-contract-parity.md
plans/teacher-live-monitoring-dashboard.md
plans/tokens-motion.md
plans/wave3-cross-cutting.md
plans/web-compatible-alerts.md
README.md
scripts/apply_migration_010.py
scripts/apply_migrations.sh
scripts/check_config_schema_sync.sh
scripts/README.md
scripts/run-student-backend.sh
scripts/run-student-frontend.sh
scripts/run-student.sh
scripts/run-teacher-backend.sh
scripts/run-teacher-frontend.sh
scripts/run-teacher.sh
scripts/setup-student.sh
scripts/setup-teacher.sh
scripts/verify_schema.py
scripts/verify.sh
student/.github/workflows/claude-code-review.yml
student/.github/workflows/claude.yml
student/AGENTS.md
student/backend/.env.example
student/backend/anthropic_guard.py
student/backend/artifact_service.py
student/backend/assignment_service.py
student/backend/auth_middleware.py
student/backend/chat_service.py
student/backend/chat.py
student/backend/classroom_service.py
student/backend/config_schema.py
student/backend/context_loader.py
student/backend/get_coords.py
student/backend/mistake_analysis/__init__.py
student/backend/mistake_analysis/client.py
student/backend/mistake_analysis/constants.py
student/backend/mistake_analysis/helpers.py
student/backend/mistake_analysis/prompts.py
student/backend/mistake_preamble.tex
student/backend/mistake_profile.py
student/backend/pyproject.toml
student/backend/requirements.txt
student/backend/result_service.py
student/backend/scripts/test_math_to_latex.py
student/backend/src/math_screenshot_to_latex/__init__.py
student/backend/src/math_screenshot_to_latex/client.py
student/backend/src/math_screenshot_to_latex/models.py
student/backend/supabase_service.py
student/backend/test_pipeline.py
student/backend/tests/test_anthropic_guard.py
student/backend/tests/test_assignment_config_contract.py
student/backend/tests/test_chat_thinking_params.py
student/backend/tests/test_config_schema.py
student/backend/tests/test_mistake_analysis_thinking_params.py
student/backend/uv.lock
student/backend/websocket_service.py
student/frontend/.env.example
student/frontend/.gitignore
student/frontend/app.json
student/frontend/app/_layout.tsx
student/frontend/app/(tabs)/_layout.tsx
student/frontend/app/(tabs)/index.tsx
student/frontend/app/(tabs)/notes.tsx
student/frontend/app/assignments/[classroomId].tsx
student/frontend/app/document/[id].tsx
student/frontend/app/note/[id].tsx
student/frontend/app/sign-in.tsx
student/frontend/app/sign-up.tsx
student/frontend/assets/pdf-viewer.html
student/frontend/components/BackendHint.tsx
student/frontend/components/CenteredMessage.tsx
student/frontend/components/ChatPanel.tsx
student/frontend/components/DrawingCanvas.tsx
student/frontend/components/ErrorBoundary.tsx
student/frontend/components/forest/ForestBackground.tsx
student/frontend/components/forest/index.ts
student/frontend/components/forest/LeafAccent.tsx
student/frontend/components/forest/TreeIcon.tsx
student/frontend/components/InkCanvas.tsx
student/frontend/components/InlineLatex.tsx
student/frontend/components/MistakeOverlay.tsx
student/frontend/components/motion/MotionView.tsx
student/frontend/components/motion/MotionView.web.tsx
student/frontend/components/notifications/StatusBadge.tsx
student/frontend/components/notifications/Toast.tsx
student/frontend/components/notifications/ToastHost.tsx
student/frontend/components/ProblemHeader.tsx
student/frontend/components/SampleAlgebraContent.tsx
student/frontend/components/ShortcutHint.tsx
student/frontend/components/SuggestionGhost.tsx
student/frontend/components/ToolBar.tsx
student/frontend/components/ui/Button.tsx
student/frontend/components/ui/Card.tsx
student/frontend/components/ui/EmptyState.tsx
[235 more files omitted for size]
```

### Dependencies

- student/backend/pyproject.toml: anthropic@>=0.79.0, flask@>=3.0.0, flask-cors@>=6.0.0, openai@>=1.0.0, pillow@>=10.0.0, python-dotenv@>=1.0.0, supabase@>=2.12.0
- student/backend/requirements.txt: anthropic@>=0.79.0, certifi@>=2024.0.0, cryptography@>=3.0.0, eventlet@>=0.36.0, flask@>=3.0.0, flask-cors@>=6.0.0, flask-socketio@>=5.3.0, gunicorn@>=23.0.0, openai@>=1.0.0, pillow@>=10.0.0, PyJWT@>=2.0.0, PyPDF2@>=3.0.0, python-dotenv@>=1.0.0, supabase@>=2.12.0
- student/frontend/package.json: @expo/vector-icons@^15.0.3, @react-native-async-storage/async-storage@2.2.0, @react-navigation/bottom-tabs@^7.4.0, @react-navigation/elements@^2.6.3, @react-navigation/native@^7.1.8, @supabase/supabase-js@^2.95.3, @types/react@~19.1.0, eslint@^9.25.0, eslint-config-expo@~10.0.0, expo@~54.0.33, expo-constants@~18.0.13, expo-document-picker@~14.0.8, expo-file-system@~19.0.21, expo-font@~14.0.11, expo-haptics@~15.0.8, expo-linear-gradient@~15.0.8, expo-linking@~8.0.11, expo-router@~6.0.23, expo-splash-screen@~31.0.13, expo-status-bar@~3.0.9, expo-system-ui@~6.0.9, framer-motion@^12.34.0, react@19.1.0, react-dom@19.1.0, react-native@0.81.5, react-native-gesture-handler@~2.28.0, react-native-reanimated@~4.1.1, react-native-safe-area-context@~5.6.0, react-native-screens@~4.16.0, react-native-svg@15.12.1, react-native-view-shot@4.0.3, react-native-web@~0.21.0, react-native-webview@13.15.0, react-native-worklets@0.5.1, socket.io-client@^4.8.1, typescript@~5.9.2
- teacher/backend/requirements.txt: anthropic@>=0.79.0, certifi@>=2024.0.0, cryptography@>=44.0.0, eventlet@>=0.36.0, flask@==3.1.0, flask-cors@==6.0.2, flask-socketio@==5.4.1, gunicorn@>=23.0.0, PyJWT@==2.10.1, PyMuPDF@==1.26.4, python-dotenv@==1.0.1, python-socketio@==5.12.0, supabase@==2.13.0
- teacher/frontend/package.json: @expo-google-fonts/dancing-script@^0.4.2, @expo-google-fonts/dm-sans@^0.4.2, @expo/vector-icons@^15.0.3, @react-native-async-storage/async-storage@2.2.0, @react-native-community/datetimepicker@8.4.4, @react-navigation/native@^7.1.28, @react-navigation/native-stack@^7.12.0, @supabase/supabase-js@^2.95.3, @testing-library/react@^16.0.1, @types/jest@^29.5.14, @types/react@~19.1.0, eslint@^9.0.0, eslint-config-expo@~10.0.0, expo@~54.0.33, expo-clipboard@~8.0.3, expo-constants@~18.0.0, expo-document-picker@~14.0.8, expo-file-system@~19.0.21, expo-font@~14.0.11, expo-linear-gradient@^15.0.8, expo-linking@~8.0.11, expo-status-bar@~3.0.9, framer-motion@^12.34.0, jest@^29.7.0, jest-environment-jsdom@^29.7.0, react@19.1.0, react-dom@19.1.0, react-native@0.81.5, react-native-safe-area-context@~5.6.0, react-native-screens@~4.16.0, react-native-svg@15.12.1, react-native-view-shot@4.0.3, react-native-web@^0.21.0, react-native-webview@13.15.0, react-test-renderer@19.1.0, socket.io-client@^4.8.3, ts-jest@^29.2.5, typescript@~5.9.2

### Recent commits (newest first)

- Merge pull request #106 from VeridianTH/codex/hide-note-autocomplete-accept
- Merge pull request #105 from VeridianTH/feat/resizable-chat-panel
- fix: resolve merge conflict with main (keep Platform.OS check, drop completedLineKeys)
- fix: resolve merge conflict with main (keep usePanelResize, drop InlineLatex)
- Merge pull request #107 from VeridianTH/ui-changes
- Merge pull request #108 from VeridianTH/deploymentv6
- new deployment
- UI OVERHAUL pt. 2
- UI OVERHAUL
- fix: scope iframe height messages to correct bubble via e.source
- fix autocomplete
- fix: use ResizeObserver for LaTeX bubble height on web
- fix: auto-size LaTeX bubbles on web instead of hardcoded 60px
- feat: resizable chat panel with drag handle and persisted height
- Merge pull request #104 from VeridianTH/deploymentv5
- second trigger
- Merge pull request #103 from VeridianTH/deploymentv5
- trigger
- Merge pull request #102 from VeridianTH/deploymentv4
- frontend deployment

## Key source files (fetched from GitHub, selected and truncated for size)

### AGENTS.md

```markdown
---
description: 
alwaysApply: true
---
# 🚨 MANDATORY WORKFLOW — READ FIRST

STOP. Before writing any code, follow this checklist:

## For New Features

Write a plan to plans/{feature-name}.md with:
What will be built and why
Which files will be created/modified
Breakdown into small PRs
Open questions, success criteria
Wait for plan approval before proceeding
 Create a feature branch from main
 Spawn sub-agents to do the actual coding (Codex orchestrates, doesn't code directly)
 Review sub-agent output for correctness, logic, integration, and style (see review checklist below)
 Create PR — plan file becomes the PR description basis
 Comment @codex and @claude review to trigger code review (see prompt template below)
 Address Codex feedback — fix issues, push updates, then comment @codex and @claude review again
 Update this file's roadmap checkboxes when work completes

 Integrate the updates and relevant documentation you made here into PLAN.md, then delete the now redundant plans/feature made in this process.
 **Running docs**: Keep `AGENTS.md`, `CLAUDE.md`, `README.md`, `PLAN.md` in sync with the codebase. Update them in the same PR when features, architecture, or conventions change.

**Deps and env**: When adding/removing Python packages or env variables, update in the same PR: `requirements.txt` (or `package.json`), the relevant `.env.example`, and running docs (README Setup, etc.).

## For Bug Fixes / Small Changes

Skip the plan if it's truly trivial (< 10 lines, obvious fix)
Still use sub-agents for code changes

## After Completing Work

 Document any decisions or dead ends in PR description
 **Update running docs when applicable** — `AGENTS.md`, `CLAUDE.md`, `README.md`, `PLAN.md`. When you add features, change architecture, or modify conventions, update the relevant doc(s). Include doc updates in the same PR as the code change.
 **Deps/env changes** — Update `requirements.txt` (or `package.json`), `.env.example`, and running docs in the same PR.

## Code Style Rules

These apply to ALL code written by Codex or sub-agents:

Follow best industry coding standards: streamlined, efficient code. Lean development — skip comprehensive test suites and extensive documentation. Tests only when they prevent regressions on tricky logic (e.g., edge cases in parsing, state machines). Optimize for shipping speed, clarity, and maintainability.
Type hints on all function signatures
Minimal comments — code should be self-documenting
No verbose logging — code runs quietly unless there's an error (errors to stderr)
Precision over generalization — no aliases, wrappers, or flexibility that isn't needed. Use exact formats.
Smart file splitting — driver files orchestrate, modules do one thing

## Code Philosophy

Optimize for reading, not writing. Code is read 10x more than it's written.
The core heuristic: "Would a tired engineer at 2am understand this immediately?" If not, simplify.

### Function Design

~20 lines max unless genuinely cleaner as one unit (e
[truncated — 3374 more characters]
```

### CLAUDE.md

```markdown
# CLAUDE.md

**Running docs**: `AGENTS.md`, `CLAUDE.md`, `README.md`, `PLAN.md`. Update when features, architecture, or conventions change. When adding packages or env vars: update `requirements.txt` (or `package.json`), `.env.example`, and running docs in the same PR.

## Project Overview

Math Mistake Analysis Platform — full monorepo (teacher + student). Shared Supabase.

- **Teacher**: `teacher/backend/`, `teacher/frontend/` — Flask + React. Classrooms, assignments, corpus, submissions.
- **Student**: `student/backend/`, `student/frontend/` — Flask + Expo. Canvas, mistake analysis, Socratic chat.
- **Student assignment contract**: `GET /assignments/<id>` on student backend is auth + classroom-membership protected and returns merged `resolved_config` used by the student frontend runtime.
- **Assignment problems**: Stored as JSONB array `problems` on the `assignments` table. Each problem: `{ num: int, statement_tex: string }`. Teacher creates/edits via ProblemEditor UI. Student frontend renders one problem per page.
- **Teacher conversion progress**: Socket.IO namespace `/conversion` requires teacher JWT auth (`auth.token`) and job subscription by UUID (`subscribe` with `job_id`).

## Workflow

Push directly to main. No PR review process.

## Key Directories

| Path | Purpose |
|------|---------|
| `teacher/backend/` | Teacher Flask app (assignments, classrooms, corpus, convert) |
| `teacher/frontend/` | Teacher React app (screens, hooks, components). Veridian design system: `src/constants/` (palette, typography, spacing), `src/components/ui/` (Button, Card, Input, Badge, EmptyState, Skeleton, ScreenContainer, Section, Row, ErrorState, TabBar), `src/components/forest/` (TreeIcon, LeafAccent, ForestBackground). Primary: #16A34A. Forest theme: forestCanopy, forestLeaf, forestBark, TreeIcon for empty states. App root is wrapped in `ErrorBoundary` (fallback: message + Retry). |
| `student/backend/` | Student Flask (mistake analysis, OCR, chat) |
| `student/backend/get_coords.py` | Main student API server |
| `student/backend/mistake_analysis/` | LLM mistake detection (Claude) |
| `student/backend/src/math_screenshot_to_latex/` | Math OCR (OpenAI) |
| `student/frontend/` | Student Expo app (canvas, document, workspace) |
| `supabase/` | Shared migrations |

## Design tokens (Veridian)

**Where tokens live:** `teacher/frontend/src/constants/` — canonical source. Student frontend uses the same Veridian values in `student/frontend/constants/` (palette, typography, spacing, motion, theme).

| File | Exports | Use for |
|------|---------|--------|
| `palette.ts` | `palette`, `radius`, `elevation` | Colors (primary #16A34A, surface, card, text, error, success, forestCanopy, forestLeaf, forestBark, forestMist, forestGradientStart/End), radii (button, card, organic 20, etc.), elevation shadows |
| `typography.ts` | `typography`, `fontFamily` | Font families (DM Sans, Dancing Script wordmark), sizes and line heights (display, h1, h2, body, caption, button) |
[truncated — 4820 more characters]
```

### teacher/backend/requirements.txt

```
flask==3.1.0
flask-socketio==5.4.1
python-socketio==5.12.0
supabase==2.13.0
anthropic>=0.79.0
certifi>=2024.0.0
eventlet>=0.36.0
gunicorn>=23.0.0
python-dotenv==1.0.1
PyJWT==2.10.1
cryptography>=44.0.0
flask-cors==6.0.2
PyMuPDF==1.26.4

```

### student/backend/requirements.txt

```
anthropic>=0.79.0
certifi>=2024.0.0
gunicorn>=23.0.0
cryptography>=3.0.0
eventlet>=0.36.0
flask>=3.0.0
flask-cors>=6.0.0
flask-socketio>=5.3.0
openai>=1.0.0
pillow>=10.0.0
PyJWT>=2.0.0
PyPDF2>=3.0.0
python-dotenv>=1.0.0
supabase>=2.12.0

```

### student/backend/pyproject.toml

```
[project]
name = "veridian-student"
version = "0.1.0"
description = "AI education system backend"
requires-python = ">=3.10"
dependencies = [
    "anthropic>=0.79.0",
    "flask>=3.0.0",
    "flask-cors>=6.0.0",
    "openai>=1.0.0",
    "pillow>=10.0.0",
    "python-dotenv>=1.0.0",
    "supabase>=2.12.0",
]

[tool.setuptools.packages.find]
where = ["src"]

```

### teacher/frontend/package.json

```
{
  "name": "frontend",
  "version": "1.0.0",
  "main": "index.ts",
  "scripts": {
    "start": "expo start",
    "android": "expo start --android",
    "ios": "expo start --ios",
    "web": "expo start --web",
    "lint": "expo lint",
    "test": "jest",
    "test:watch": "jest --watch"
  },
  "dependencies": {
    "@expo-google-fonts/dancing-script": "^0.4.2",
    "@expo-google-fonts/dm-sans": "^0.4.2",
    "@expo/vector-icons": "^15.0.3",
    "@react-native-async-storage/async-storage": "2.2.0",
    "@react-native-community/datetimepicker": "8.4.4",
    "@react-navigation/native": "^7.1.28",
    "@react-navigation/native-stack": "^7.12.0",
    "@supabase/supabase-js": "^2.95.3",
    "expo": "~54.0.33",
    "expo-clipboard": "~8.0.3",
    "expo-constants": "~18.0.0",
    "expo-document-picker": "~14.0.8",
    "expo-file-system": "~19.0.21",
    "expo-font": "~14.0.11",
    "expo-linear-gradient": "^15.0.8",
    "expo-linking": "~8.0.11",
    "expo-status-bar": "~3.0.9",
    "framer-motion": "^12.34.0",
    "react": "19.1.0",
    "react-dom": "19.1.0",
    "react-native": "0.81.5",
    "react-native-safe-area-context": "~5.6.0",
    "react-native-screens": "~4.16.0",
    "react-native-svg": "15.12.1",
    "react-native-view-shot": "4.0.3",
    "react-native-web": "^0.21.0",
    "react-native-webview": "13.15.0",
    "socket.io-client": "^4.8.3"
  },
  "devDependencies": {
    "@testing-library/react": "^16.0.1",
    "@types/jest": "^29.5.14",
    "@types/react": "~19.1.0",
    "eslint": "^9.0.0",
    "eslint-config-expo": "~10.0.0",
    "jest": "^29.7.0",
    "jest-environment-jsdom": "^29.7.0",
    "react-test-renderer": "19.1.0",
    "ts-jest": "^29.2.5",
    "typescript": "~5.9.2"
  },
  "private": true
}

```

### student/frontend/package.json

```
{
  "name": "veridian-learning",
  "main": "expo-router/entry",
  "version": "1.0.0",
  "scripts": {
    "start": "expo start",
    "start:tunnel": "expo start --tunnel",
    "start:localhost": "expo start --localhost",
    "ios:tunnel": "expo start --tunnel --ios",
    "ios:localhost": "REACT_NATIVE_PACKAGER_HOSTNAME=127.0.0.1 expo start --localhost --ios",
    "test:capture-server": "node scripts/test-capture-server.js",
    "server:claude": "node scripts/capture-to-claude-server.js",
    "server:gemini": "node scripts/capture-to-gemini-server.js",
    "server:openrouter": "node scripts/capture-to-openrouter-server.js",
    "reset-project": "node ./scripts/reset-project.js",
    "android": "expo start --android",
    "ios": "expo start --ios",
    "web": "expo start --web",
    "lint": "expo lint"
  },
  "dependencies": {
    "@expo/vector-icons": "^15.0.3",
    "@react-native-async-storage/async-storage": "2.2.0",
    "@react-navigation/bottom-tabs": "^7.4.0",
    "@react-navigation/elements": "^2.6.3",
    "@react-navigation/native": "^7.1.8",
    "@supabase/supabase-js": "^2.95.3",
    "expo": "~54.0.33",
    "expo-constants": "~18.0.13",
    "expo-document-picker": "~14.0.8",
    "expo-file-system": "~19.0.21",
    "expo-font": "~14.0.11",
    "expo-haptics": "~15.0.8",
    "expo-linear-gradient": "~15.0.8",
    "expo-linking": "~8.0.11",
    "expo-router": "~6.0.23",
    "expo-splash-screen": "~31.0.13",
    "expo-status-bar": "~3.0.9",
    "expo-system-ui": "~6.0.9",
    "framer-motion": "^12.34.0",
    "react": "19.1.0",
    "react-dom": "19.1.0",
    "react-native": "0.81.5",
    "react-native-gesture-handler": "~2.28.0",
    "react-native-reanimated": "~4.1.1",
    "react-native-safe-area-context": "~5.6.0",
    "react-native-screens": "~4.16.0",
    "react-native-svg": "15.12.1",
    "react-native-view-shot": "4.0.3",
    "react-native-web": "~0.21.0",
    "react-native-webview": "13.15.0",
    "react-native-worklets": "0.5.1",
    "socket.io-client": "^4.8.1"
  },
  "devDependencies": {
    "@types/react": "~19.1.0",
    "eslint": "^9.25.0",
    "eslint-config-expo": "~10.0.0",
    "typescript": "~5.9.2"
  },
  "private": true
}

```

### teacher/frontend/index.ts

```typescript
import { registerRootComponent } from 'expo';

import App from './App';

// registerRootComponent calls AppRegistry.registerComponent('main', () => App);
// It also ensures that whether you load the app in Expo Go or in a native build,
// the environment is set up appropriately
registerRootComponent(App);

```

### teacher/frontend/App.tsx

```typescript
import React from "react";
import { Platform, View } from "react-native";
import { useFonts } from "expo-font";
import {
  DMSans_400Regular,
  DMSans_500Medium,
  DMSans_600SemiBold,
  DMSans_700Bold,
} from "@expo-google-fonts/dm-sans";
import { DancingScript_600SemiBold } from "@expo-google-fonts/dancing-script";
import { AuthProvider } from "./src/stores/auth";
import { ToastProvider } from "./src/contexts/ToastContext";
import { RootNavigator } from "./src/navigation";
import { ErrorBoundary } from "./src/components/ErrorBoundary";
import { ForestBackground } from "./src/components/forest";

export default function App() {
  const [fontsLoaded] = useFonts({
    DMSans_400Regular,
    DMSans_500Medium,
    DMSans_600SemiBold,
    DMSans_700Bold,
    DancingScript_600SemiBold,
  });

  if (!fontsLoaded && Platform.OS !== "web") {
    return null;
  }

  return (
    <ErrorBoundary>
      <AuthProvider>
        <ToastProvider>
          <View style={{ flex: 1 }}>
            <ForestBackground />
            <View style={{ flex: 1, backgroundColor: "transparent" }} pointerEvents="box-none">
              <RootNavigator />
            </View>
          </View>
        </ToastProvider>
      </AuthProvider>
    </ErrorBoundary>
  );
}

```

### student/frontend/app/_layout.tsx

```typescript
import { DefaultTheme, ThemeProvider } from '@react-navigation/native';
import { useFonts } from 'expo-font';
import { Stack, useRouter, useSegments } from 'expo-router';
import { useEffect } from 'react';
import { ActivityIndicator, StyleSheet, View } from 'react-native';
import { StatusBar } from 'expo-status-bar';
import { GestureHandlerRootView } from 'react-native-gesture-handler';
import 'react-native-reanimated';

import { ErrorBoundary } from '@/components/ErrorBoundary';
import { ForestBackground } from '@/components/forest';
import { palette } from '@/constants/palette';
import { useAuth } from '@/hooks/useAuth';
import { BackendHint } from '@/components/BackendHint';

export const unstable_settings = {
  anchor: '(tabs)',
};

const transparentTheme = {
  ...DefaultTheme,
  colors: {
    ...DefaultTheme.colors,
    background: "transparent",
    card: "transparent",
  },
};

function useProtectedRoute(userId: string | null, loading: boolean) {
  const segments = useSegments();
  const router = useRouter();

  useEffect(() => {
    if (loading) return;
    const onAuthScreen = segments[0] === 'sign-in' || segments[0] === 'sign-up';
    if (!userId && !onAuthScreen) {
      router.replace('/sign-in');
    } else if (userId && onAuthScreen) {
      router.replace('/(tabs)');
    }
  }, [userId, loading, segments, router]);
}

export default function RootLayout() {
  const [fontsLoaded] = useFonts({
    Caveat: require('@/assets/fonts/Caveat-Regular.ttf'),
  });
  const { userId, loading: authLoading } = useAuth();

  useProtectedRoute(userId, authLoading || !fontsLoaded);

  if (authLoading || !fontsLoaded) {
    return (
      <View style={styles.loading}>
        <ActivityIndicator size="large" color={palette.primary} />
      </View>
    );
  }

  return (
    <ErrorBoundary>
      <ThemeProvider value={transparentTheme}>
        <GestureHandlerRootView style={styles.root}>
          <ForestBackground />
          <View style={styles.content}>
            <BackendHint />
            <Stack
              screenOptions={{
                contentStyle: { backgroundColor: "transparent" },
              }}
            >
              <Stack.Screen name="(tabs)" options={{ headerShown: false }} />
              <Stack.Screen
                name="assignments/[classroomId]"
                options={{ headerShown: false }}
              />
              <Stack.Screen name="document/[id]" options={{ headerShown: false }} />
              <Stack.Screen name="note/[id]" options={{ headerShown: false }} />
              <Stack.Screen name="sign-in" options={{ title: 'Sign in', headerShown: false }} />
              <Stack.Screen name="sign-up" options={{ headerShown: false }} />
            </Stack>
            <StatusBar style="auto" />
          </View>
        </GestureHandlerRootView>
      </ThemeProvider>
    </ErrorBoundary>
  );
}

const styles = StyleSheet.create({
  loading: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    backgroundColor: palette.surface,
  },
  root: {
    flex: 1,
  },
  content: {
    flex: 1,
    backgroundColor: "transparent",
  },
});

```

[319 more indexed source files omitted to keep this export small. The full file list is in the Codebase structure section above.]