# Project export: Mirror

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: OpenAI Build Week
- Tagline: The AI That Learns From You So You Learn About Yourself
- Devpost: https://devpost.com/software/mirror-7w0ms1
- GitHub: https://github.com/BenDuske/mirror
- Video: https://www.youtube.com/embed/PtUjKrO3dlo?enablejsapi=1&hl=en_US&rel=0&start=&version=3&wmode=transparent
- Team: 2 GitHub contributor(s) — BenDuske (15 commits), Codex (4 commits)

## Devpost submission (written by the team)

### Inspiration

I'm a Lead Controls Operator at a hospital. I run the building automation systems that keep patient floors safe. In that world, "the student got the right answer" isn't good enough. I need to know why they got it right, because when a chilled water plant fails at 2 AM, the person on call has to understand the system, not just recall a checklist. I'm also a Human-Centered AI student at Texas Tech, and I watched classmates use ChatGPT the way you'd use a vending machine: insert prompt, receive answer, feel smart, fail the exam. Every AI education tool on the market pushes explanations at students. They all optimize for the moment the answer appears on screen, and none of them measure the moment the concept actually lands in the student's head. So I asked a stupid, obvious question: what if the AI never gave an answer? What if it only asked questions — really good ones, in the specific pattern a great teacher uses — and the student had to think for themselves? That question wouldn't leave me alone. Mirror is what it turned into.

### What it does

Mirror is a reverse-Socratic learning platform. The student explains a topic to GPT-5.6, and GPT interviews them — probing what they know, what they think they know, and what they've quietly skipped. For students: Join a class, pick a topic, start a session. GPT-5.6 streams Socratic questions that climb Bloom's taxonomy: recall → comprehension → application → analysis It never gives you the answer. If you're wrong, it asks a question that exposes the gap. If you're right, it pushes deeper — "how does that connect to X?", "what breaks if you change Y?" When the session ends, GPT produces a structured comprehension assessment: a ReactFlow knowledge map where each concept node is color-coded by depth (deep / surface / misconception / unexplored), with every node linking back to the exact message where that judgment was made For teachers: A class-wide heatmap showing aggregate comprehension per concept Misconception alerts, grouped by concept, so you can see which wrong beliefs are spreading Drill down into any student's individual map with their Bloom's profile and evidence-linked exchanges The knowledge map isn't just a picture. It's an audit trail from visualization back to evidence.

### How we built it

Solo build, on top of my Human-Centered AI coursework and a full-time controls job. Stack: Frontend: React + Vite + ReactFlow for the interactive knowledge map Backend: Node.js + Express + SQLite (better-sqlite3) AI: OpenAI GPT-5.6 Socratic dialogue via SSE streaming, topic-concept structure generation, and structured JSON assessment Voice: gpt-4o-mini-transcribe and gpt-4o-mini-tts for optional voice sessions (some students explain better out loud than in writing) Auth: JWT + bcrypt, role-based (student/teacher) Built with Codex for scaffolding, component iteration, and prompt engineering The system prompt is the actual product. It went through I don't know how many revisions to reach the version that reliably never gives answers. Early versions collapsed into helpful mode the second a student said, "I don't know." The working version tells GPT to treat "I don't know" as a signal, not a stop sign — reframe smaller, probe an adjacent concept, but never lecture. The comprehension assessment is a structured JSON schema — concept nodes, edges, depth ratings, misconceptions, Bloom's profile, and evidence linked by message index. That last piece is what makes the map trustworthy: click any node, see the exact exchange it came from.

### Challenges we ran into

Getting GPT to refuse to help. This was the hard problem. Language models are trained to be helpful, and Socratic teaching looks unhelpful from the outside. Early versions of the prompt would ask two questions, then cave in and explain. Fixing this required explicit rules ("acknowledgment is one sentence, then the next question — never a paragraph"), examples of the failure mode ("don't do this"), and post hoc validators on the streaming output. Structured output under a streaming dialogue. SSE streaming for the chat, then a separate structured-output call to generate the assessment JSON — with the assessment referencing the streamed conversation by message index. Getting message indices to stay consistent across reconnects was uglier than it sounds. Depth over correctness. Traditional edtech grades right/wrong. Mirror had to grade depth — surface recall vs. transferable understanding — and represent that in a way a teacher could scan in three seconds. That's where the color-coded map came from: yellow-green (surface) vs. solid green (deep) is a distinction teachers already make intuitively; the map makes it visible. Solo scope discipline. I had a list of twelve features I wanted. I shipped six. Cutting adaptive difficulty, longitudinal tracking, and LMS export hurt, but shipping the six that make the core loop work was the right call.

### Accomplishments we're proud of

The refusal actually holds. In demo sessions, GPT-5.6 has remained in Socratic mode across 20+ exchanges without collapsing into explanation mode. The constraint is the feature. Evidence-linking works. Every node on the knowledge map is one click from the exact message that produced it. That transforms the map from "interesting visualization" into "defensible assessment" — a teacher can justify a rating to a parent by showing the transcript. Bloom's progression is visible in the transcripts. You can literally watch a student climb from recall to application inside a single session. That's the moment I knew this wasn't just a hackathon demo — it was measuring something real. The class heatmap surfaces group-level misconceptions. Teachers have never had this. It's the aggregate view of "which wrong ideas are spreading in my class right now." I shipped it solo, on nights and weekends, while working a critical-infrastructure job.

### What we learned

Students learn more when the AI refuses to help. The constraint isn't a limitation — it's the pedagogy. Every "I don't know" that GPT-5.6 doesn't rescue is a moment when the student actually has to think. Structured output beats free text for education. A JSON schema with typed fields (depth, misconception, evidence_message_idx) is dramatically more useful than a paragraph summary — for visualization, for aggregation, and for teacher trust. Evidence is what turns AI assessment from a toy into a tool. The message-index link between assessment and transcript is the single change that made teachers I demoed to lean forward instead of nodding politely. My controls-operator instincts transferred. In critical infrastructure, you don't trust a sensor reading you can't trace to a physical point. Same rule here: don't trust an assessment you can't trace to an exchange. Formally, Mirror measures comprehension along two axes: $$ \text{Understanding}(c) = f(\text{Bloom-level}(c),\ \text{Depth}(c)) $$ where $c$ is a concept node, $\text{Bloom-level} \in {1,2,3,4}$ (recall → analysis), and $\text{Depth} \in {\text{unexplored}, \text{misconception}, \text{surface}, \text{deep}}$. The right answer on Bloom-1 with surface depth is not the same as the right answer on Bloom-4 with deep depth — and Mirror is the first tool I've used that treats them differently.

### What's next

Longitudinal tracking — how does a student's knowledge map evolve across three sessions on the same topic? That's the real learning curve, not the test score. Adaptive difficulty — GPT-5.6 adjusts questioning based on the student's demonstrated Bloom's level in real time, so strong students get pushed and struggling students get scaffolded. Multi-modal Socratic sessions — diagram drawing, equation input, and voice — so students can explain how their brains actually work. LMS integration — knowledge maps as gradebook entries with the evidence trail intact. Longer-term: publish the pedagogy. The refusal pattern, the Bloom's climb, the evidence-linked assessment — those generalize beyond any one app. I want the paper to exist so other builders can steal the idea and improve education faster than I can alone. Mirror is the AI that learns from you — so you learn about yourself.

## README (from the GitHub repository)

# Mirror — The AI That Learns From You

![Mirror](assets/branding/mirror-hero-banner.png)

> Every AI education tool explains TO the student. Mirror reverses the paradigm: the student explains TO the AI.

Mirror is a Socratic learning platform where GPT acts as an interviewer — probing depth, following threads, and surfacing misconceptions the student didn't know they had. The result is a live **knowledge map** that shows what a student truly understands, not just what they can parrot back.

**Built for [OpenAI Build Week](https://openai.devpost.com/) — Education Track**

## How It Works

1. **Teacher** creates a class and assigns a topic (e.g., "Mitosis vs. Meiosis")
2. **Student** joins the class and starts a Mirror session
3. **GPT** asks Socratic questions — never giving answers, only probing understanding
4. After the dialogue, GPT produces a **structured comprehension assessment**:
   - **Knowledge Map**: interactive node graph showing depth of understanding per concept
   - **Bloom's Profile**: how much of the student's understanding is recall vs. comprehension vs. application vs. analysis
   - **Misconception Detection**: specific wrong beliefs surfaced during the dialogue
5. **Teacher** sees a **class-wide heatmap** — which concepts the class understands, common misconceptions, and per-student drill-down

## Key Design Decision

The AI **never gives the answer**. Learning happens in the student's head, not in the AI's output. Mirror creates a *mirror* — reflecting the student's own understanding back at them so they can see their own gaps.

## Tech Stack

| Layer | Tech |
|-------|------|
| Frontend | React + Vite |
| Knowledge Map | ReactFlow (interactive node graph) |
| Backend | Node.js + Express |
| AI Engine | OpenAI GPT-4.1 API |
| Database | SQLite (better-sqlite3) |
| Auth | JWT + bcrypt (role-based: student/teacher) |
| Built with | Codex |

## Setup

### Prerequisites
- Node.js 18+
- OpenAI API key

### Backend
```bash
cd backend
npm install
cp .env.example .env
# Add your OPENAI_API_KEY to .env
npm run seed   # Load demo data
npm run dev    # Start on :3001
```

### Frontend
```bash
cd frontend
npm install
npm run dev    # Start on :5173, proxies /api to :3001
```

### Demo Accounts (after seeding)
| Role | Email | Password |
|------|-------|----------|
| Teacher | teacher@demo.com | demo1234 |
| Student | alex@demo.com | demo1234 |
| Student | jordan@demo.com | demo1234 |
| Student | maya@demo.com | demo1234 |
| Student | sam@demo.com | demo1234 |

Class join code: `BIO101`

## Features

### For Students
- **Socratic Chat** — streaming AI dialogue that probes your understanding
- **Knowledge Map** — interactive visualization of what you know (green = deep, yellow = surface, red = misconception, gray = unexplored)
- **Bloom's Profile** — see where your understanding sits on the taxonomy
- **Click any concept node** to see the exact exchange where it was assessed

### For Teachers
- **Class Heatmap** — aggregate comprehension bars per concept across all students
- **Misconception Alerts** — grouped by concept, showing which students hold which wrong beliefs
- **Per-Student Maps** — drill into any student's knowledge graph
- **Topic Assignment** — create topics with learning objectives; GPT generates the expected concept structure
- **Bloom's Profile per Student** — see if students are stuck at recall or pushing into analysis

## How Codex Was Used

1. **Scaffolding** — project initialization, routing, database schema, auth boilerplate
2. **UI Components** — chat interface, dashboard layouts, knowledge map visualization
3. **API Endpoints** — CRUD for users, classes, topics, sessions
4. **System Prompt Iteration** — designing and refining the Socratic engine behavior
5. **Seed Data** — generating realistic demo dialogues and assessments

## How GPT-4.1 Is Used

- **Socratic Engine** — real-time streaming dialogue that follows Bloom's taxonomy progression, never gives answers, uses the student's own words in follow-ups
- **Concept Structure Generation** — teacher provides learning objectives, GPT generates the expected concept graph
- **Comprehension Assessment** — after each session, GPT produces a structured JSON assessment with per-concept depth ratings, misconception detection, Bloom's profile, and evidence linked to specific message indices

## Architecture

```
Frontend (React + Vite)
├── Auth (Login/Signup with role selection)
├── Student Dashboard (classes, sessions, progress)
├── Teacher Dashboard (classes, join codes, analytics)
├── Socratic Chat (streaming SSE, markdown rendering)
├── Knowledge Map (ReactFlow, click-to-inspect)
├── Class Heatmap (aggregate bars, misconception alerts)
└── Student Map View (teacher's per-student drill-down)

Backend (Node.js + Express)
├── Auth (JWT, bcrypt, role middleware)
├── Classes (create, join codes, roster)
├── Topics (create with AI concept generation)
├── Sessions (create, streaming chat, complete with assessment)
└── Knowledge Maps (per-student, per-class heatmap, misconceptions)

Database: SQLite (users, classes, topics, sessions, knowledge_maps)
```

## Branding & Assets

Brand assets live in [`assets/branding/`](assets/branding/):

| Asset | File | Use |
|-------|------|-----|
| Hero banner (16:9) | `assets/branding/mirror-hero-banner.png` | Devpost cover, README header, slides |
| App icon / logo (1:1) | `assets/branding/mirror-icon.png` | Logo, Devpost thumbnail, source for favicons |

Favicons are generated from the app icon and served from [`frontend/public/`](frontend/public/): `favicon.ico`, `favicon-16x16.png`, `favicon-32x32.png`, `favicon-48x48.png`, `apple-touch-icon.png` (180px), plus `mirror-192.png` / `mirror-512.png` for PWA. They're wired into `frontend/index.html`.

## License

MIT

## Author

Benjamin Duske — [LinkedIn](https://linkedin.com/in/benjamin-duske-ai)


## Detected evidence (automated analysis)

Indexed codebase: 42 recognized source files, 178 KB.
- CSS (language) — detected in the code
- Express (technology) — detected in the code
- HTML (language) — detected in the code
- JavaScript (language) — detected in the code
- OpenAI (technology) — detected in the code
- React (technology) — detected in the code
- Node.js (technology) — claimed on Devpost, not found in the code
- AI coding agent: Codex — evidence: commit authorship or trailers

## Codebase structure (from repository index)

### Files (51 of 51)

```
.gitignore
ARCHITECTURE.md
backend/.env.example
backend/package.json
backend/src/config.js
backend/src/crypto.js
backend/src/db.js
backend/src/middleware/auth.js
backend/src/notify.js
backend/src/openai.js
backend/src/routes/auth.js
backend/src/routes/classes.js
backend/src/routes/knowledge.js
backend/src/routes/messages.js
backend/src/routes/sessions.js
backend/src/routes/settings.js
backend/src/routes/topics.js
backend/src/routes/voice.js
backend/src/seed.js
backend/src/server.js
BUILD-PLAN.md
CODEX-PROMPT-gpt56.md
DEMO-SCRIPT.md
DEVPOST-SUBMISSION.md
Dockerfile
frontend/.gitignore
frontend/.oxlintrc.json
frontend/index.html
frontend/package.json
frontend/src/api/client.js
frontend/src/App.css
frontend/src/App.jsx
frontend/src/context/AuthContext.jsx
frontend/src/index.css
frontend/src/main.jsx
frontend/src/pages/ClassDetail.jsx
frontend/src/pages/ClassHeatmap.jsx
frontend/src/pages/Inbox.jsx
frontend/src/pages/KnowledgeMap.jsx
frontend/src/pages/Login.jsx
frontend/src/pages/Settings.jsx
frontend/src/pages/Signup.jsx
frontend/src/pages/SocraticChat.jsx
frontend/src/pages/StudentDashboard.jsx
frontend/src/pages/StudentMapView.jsx
frontend/src/pages/TeacherDashboard.jsx
frontend/vite.config.js
MIRROR-OVERVIEW.md
package.json
railway.json
README.md
```

### Dependencies

- backend/package.json: bcryptjs@^3.0.3, better-sqlite3@^12.11.1, cors@^2.8.6, dotenv@^17.4.2, express@^5.2.1, jsonwebtoken@^9.0.3, nodemailer@^9.0.3, openai@^6.46.0, uuid@^14.0.1
- frontend/package.json: @types/react@^19.2.17, @types/react-dom@^19.2.3, @vitejs/plugin-react@^6.0.3, @xyflow/react@^12.11.2, axios@^1.18.1, d3@^7.9.0, oxlint@^1.71.0, react@^19.2.7, react-dom@^19.2.7, react-markdown@^10.1.0, react-router-dom@^7.18.1, reactflow@^11.11.4, vite@^8.1.1

### Recent commits (newest first)

- Add project overview, GPT-5.6 migration prompt, and oxlint config
- Harden assessment JSON parsing against model prose leakage
- Teachers can rename, archive, and delete classes
- Ignore SQLite WAL/SHM sidecar files and demo assets
- In-app messaging with class broadcasts, DMs, and reply threads
- Voice I/O in student chat: mic input and read-aloud toggle
- Add per-user BYOK for OpenAI keys with encrypted storage
- Teachers can edit and delete topics with cascade safeguard
- Auto-grow student chat textarea (2–15 rows, then scroll)
- Add Mirror brand assets and multi-format favicons
- Centralize model config for GPT-5.6
- Add Devpost submission copy
- Polish: Mirror favicon and page title
- Add architecture diagram and design decisions
- Initial commit — Mirror: Socratic learning platform

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

### CODEX-PROMPT-gpt56.md

```markdown
# Codex prompt — switch Mirror backend to GPT-5.6

Paste this into your open Codex terminal (gpt-5.6 with high reasoning). It's scoped, uses env-var config so we can swap models later, and includes a live-verification step.

---

You are editing the Mirror hackathon project at `/home/ben-duske/projects/mirror`.

**Goal.** Move the backend off the hard-coded `gpt-4.1` and onto **GPT-5.6** everywhere the OpenAI client is called, via a single configurable constant. Then run a live end-to-end verification that a fresh Mirror session actually round-trips through the new model.

**Constraints.**
- Do NOT change routing, prompts, streaming, or response-shape logic — only the model identifier and how it's resolved.
- Add `OPENAI_MODEL` to `backend/.env` with default value `gpt-5.6` (exact string; if `gpt-5.6` returns a 404 from the OpenAI API in your live check, iterate ONCE by listing available models via `curl -s https://api.openai.com/v1/models -H "Authorization: Bearer $OPENAI_API_KEY"` and pick the closest GPT-5.6 identifier — write the chosen string back into `.env` and this doc's "resolved model" line).
- Introduce a single import point (e.g. `backend/src/config.js` exporting `MODEL = process.env.OPENAI_MODEL || 'gpt-5.6'`) and replace every literal `'gpt-4.1'` with that import.
- Update `backend/.env.example` too.

**Files to touch (found by grep — verify current state before editing).**
- `backend/src/routes/topics.js:19`
- `backend/src/routes/sessions.js:91`
- `backend/src/routes/sessions.js:142`
- `backend/src/routes/sessions.js:198`
- `backend/.env`, `backend/.env.example`
- new: `backend/src/config.js`

**Live verification (must run and pass — no faking).**
1. `cd backend && npm run dev` in background; wait for `Mirror API running`.
2. `curl` login as `alex@demo.com` / `demo1234`, grab JWT.
3. Create a session on the existing "Mitosis vs. Meiosis" topic in class BIO101, POST one student message ("cells make more cells"), and stream/collect the assistant reply.
4. Assert: HTTP 200, non-empty assistant reply, no 404/400 model errors in the backend log. Print the resolved model string and the first ~200 chars of the assistant reply.
5. Kill the dev server. Restore any test DB rows created if the seeded data would look polluted.

**Deliverables.** A short summary listing: (a) the resolved model string, (b) the diff (files changed with line counts), (c) the pass/fail of each verification step. Do NOT commit; leave changes in the working tree so Ben can review.

```

### DEMO-SCRIPT.md

```markdown
# Mirror — Demo Video Script (~2:30)

## Opening (0:00–0:15)
**Screen:** Mirror logo / landing page
**Voiceover:** "Every AI education tool explains TO the student. Mirror flips that — the student explains to the AI. The result: a live map of what they actually understand."

## The Problem (0:15–0:30)
**Screen:** Quick montage — ChatGPT giving a perfect answer, student copying it
**Voiceover:** "Current AI tools are answer machines. Students get perfect explanations and feel like they understand — until the exam. Mirror fixes this by making GPT an interviewer, not a lecturer."

## Student Flow (0:30–1:30)
**Screen:** Log in as alex@demo.com

1. **(0:30)** Dashboard → join Biology 101 → pick "Mitosis vs. Meiosis"
2. **(0:40)** Session starts — GPT asks: "Can you walk me through what happens during mitosis?"
3. **(0:50)** Type a partial answer — show GPT probing deeper, never giving the answer
4. **(1:00)** After 4–5 exchanges, click "Finish & See Map"
5. **(1:10)** Knowledge Map appears — green nodes (deep), yellow (surface), red (misconception)
6. **(1:20)** Click a node → see the exact exchange where GPT assessed it
7. **(1:25)** Bloom's Profile bars — recall vs. comprehension vs. application vs. analysis

**Voiceover:** "GPT follows Bloom's taxonomy — starting with recall, pushing into application and analysis. It never gives the answer. Every node on this map links back to a specific moment in the conversation."

## Teacher Flow (1:30–2:10)
**Screen:** Log in as teacher@demo.com

1. **(1:30)** Teacher Dashboard → Biology 101 → "Mitosis vs. Meiosis" → View Heatmap
2. **(1:40)** Class Heatmap — aggregate bars per concept. Point out one concept where most students are red/yellow
3. **(1:50)** Misconception Alerts — "3 students think meiosis produces identical cells"
4. **(2:00)** Click a student → full knowledge map with Bloom's profile

**Voiceover:** "Teachers see the whole class at once. Which concepts are strong, which are weak, and exactly which misconceptions are spreading. No more guessing what students actually know."

## Tech & Closing (2:10–2:30)
**Screen:** Architecture diagram or code snippets
**Voiceover:** "Built with GPT-4.1 for Socratic dialogue and structured comprehension assessment. React + Node.js + SQLite. Every assessment is a structured JSON — concept nodes, depth ratings, Bloom's profile, misconception detection, with evidence linked to specific message indices. Mirror doesn't test knowledge. It reveals it."

**End card:** "Mirror — Built for OpenAI Build Week / Education Track / Benjamin Duske"

```

### Dockerfile

```
FROM node:22-slim

WORKDIR /app

COPY backend/package*.json backend/
RUN cd backend && npm install --production

COPY frontend/package*.json frontend/
RUN cd frontend && npm install

COPY backend/ backend/
COPY frontend/ frontend/

RUN cd frontend && npm run build

EXPOSE 3001
ENV NODE_ENV=production
CMD ["node", "backend/src/server.js"]

```

### package.json

```
{
  "name": "mirror",
  "version": "1.0.0",
  "description": "Mirror — The AI That Learns From You",
  "scripts": {
    "install:all": "cd backend && npm install && cd ../frontend && npm install",
    "build": "cd frontend && npm run build",
    "start": "cd backend && npm start",
    "seed": "cd backend && npm run seed",
    "dev": "concurrently \"cd backend && npm run dev\" \"cd frontend && npm run dev\""
  },
  "author": "Benjamin Duske",
  "license": "MIT"
}

```

### backend/package.json

```
{
  "name": "backend",
  "version": "1.0.0",
  "description": "",
  "main": "src/server.js",
  "scripts": {
    "dev": "node --watch src/server.js",
    "start": "node src/server.js",
    "seed": "node src/seed.js"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "type": "commonjs",
  "dependencies": {
    "bcryptjs": "^3.0.3",
    "better-sqlite3": "^12.11.1",
    "cors": "^2.8.6",
    "dotenv": "^17.4.2",
    "express": "^5.2.1",
    "jsonwebtoken": "^9.0.3",
    "nodemailer": "^9.0.3",
    "openai": "^6.46.0",
    "uuid": "^14.0.1"
  },
  "allowScripts": {
    "better-sqlite3@12.11.1": true
  }
}

```

### frontend/package.json

```
{
  "name": "frontend",
  "private": true,
  "version": "0.0.0",
  "type": "module",
  "scripts": {
    "dev": "vite",
    "build": "vite build",
    "lint": "oxlint",
    "preview": "vite preview"
  },
  "dependencies": {
    "@xyflow/react": "^12.11.2",
    "axios": "^1.18.1",
    "d3": "^7.9.0",
    "react": "^19.2.7",
    "react-dom": "^19.2.7",
    "react-markdown": "^10.1.0",
    "react-router-dom": "^7.18.1",
    "reactflow": "^11.11.4"
  },
  "devDependencies": {
    "@types/react": "^19.2.17",
    "@types/react-dom": "^19.2.3",
    "@vitejs/plugin-react": "^6.0.3",
    "oxlint": "^1.71.0",
    "vite": "^8.1.1"
  }
}

```

### frontend/src/main.jsx

```javascript
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import './index.css'
import App from './App.jsx'

createRoot(document.getElementById('root')).render(
  <StrictMode>
    <App />
  </StrictMode>,
)

```

### backend/src/server.js

```javascript
require('dotenv').config();

const express = require('express');
const cors = require('cors');

const authRoutes = require('./routes/auth');
const classRoutes = require('./routes/classes');
const topicRoutes = require('./routes/topics');
const sessionRoutes = require('./routes/sessions');
const knowledgeRoutes = require('./routes/knowledge');
const settingsRoutes = require('./routes/settings');
const voiceRoutes = require('./routes/voice');
const messageRoutes = require('./routes/messages');

const path = require('path');

const app = express();
const PORT = process.env.PORT || 3001;

app.use(cors());
app.use(express.json({ limit: '30mb' }));

app.get('/api/health', (req, res) => {
  res.json({ status: 'ok', name: 'Mirror API' });
});

app.use('/api/auth', authRoutes);
app.use('/api/classes', classRoutes);
app.use('/api/topics', topicRoutes);
app.use('/api/sessions', sessionRoutes);
app.use('/api/knowledge', knowledgeRoutes);
app.use('/api/settings', settingsRoutes);
app.use('/api/voice', voiceRoutes);
app.use('/api/messages', messageRoutes);

const clientDist = path.join(__dirname, '../../frontend/dist');
app.use(express.static(clientDist));
app.get('{*path}', (req, res) => {
  res.sendFile(path.join(clientDist, 'index.html'));
});

app.listen(PORT, () => {
  console.log(`Mirror API running on http://localhost:${PORT}`);
});

```

### frontend/src/App.jsx

```javascript
import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom';
import { AuthProvider, useAuth } from './context/AuthContext';
import Login from './pages/Login';
import Signup from './pages/Signup';
import StudentDashboard from './pages/StudentDashboard';
import TeacherDashboard from './pages/TeacherDashboard';
import ClassDetail from './pages/ClassDetail';
import SocraticChat from './pages/SocraticChat';
import KnowledgeMap from './pages/KnowledgeMap';
import ClassHeatmap from './pages/ClassHeatmap';
import StudentMapView from './pages/StudentMapView';
import Settings from './pages/Settings';
import Inbox from './pages/Inbox';
import './App.css';

function ProtectedRoute({ children }) {
  const { user, loading } = useAuth();
  if (loading) return <div className="loading">Loading...</div>;
  if (!user) return <Navigate to="/login" />;
  return children;
}

function AppRoutes() {
  const { user, loading } = useAuth();
  if (loading) return <div className="loading">Loading...</div>;

  return (
    <Routes>
      <Route path="/login" element={user ? <Navigate to={user.role === 'teacher' ? '/dashboard' : '/student'} /> : <Login />} />
      <Route path="/signup" element={user ? <Navigate to={user.role === 'teacher' ? '/dashboard' : '/student'} /> : <Signup />} />
      <Route path="/dashboard" element={<ProtectedRoute><TeacherDashboard /></ProtectedRoute>} />
      <Route path="/student" element={<ProtectedRoute><StudentDashboard /></ProtectedRoute>} />
      <Route path="/class/:classId" element={<ProtectedRoute><ClassDetail /></ProtectedRoute>} />
      <Route path="/session/:sessionId" element={<ProtectedRoute><SocraticChat /></ProtectedRoute>} />
      <Route path="/map/:mapId" element={<ProtectedRoute><KnowledgeMap /></ProtectedRoute>} />
      <Route path="/heatmap/:classId/:topicId" element={<ProtectedRoute><ClassHeatmap /></ProtectedRoute>} />
      <Route path="/student-map/:studentId/:topicId" element={<ProtectedRoute><StudentMapView /></ProtectedRoute>} />
      <Route path="/settings" element={<ProtectedRoute><Settings /></ProtectedRoute>} />
      <Route path="/inbox" element={<ProtectedRoute><Inbox /></ProtectedRoute>} />
      <Route path="*" element={<Navigate to="/login" />} />
    </Routes>
  );
}

export default function App() {
  return (
    <BrowserRouter>
      <AuthProvider>
        <AppRoutes />
      </AuthProvider>
    </BrowserRouter>
  );
}

```

### frontend/vite.config.js

```javascript
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'

export default defineConfig({
  plugins: [react()],
  server: {
    proxy: {
      '/api': 'http://localhost:3001',
    },
  },
})

```

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