# Professor — LLM Project Reference > This file is loaded automatically by Claude Code at session start. It is not end-user documentation. > For planned features and build order see [ROADMAP.md](ROADMAP.md). ## What the app does AI-powered coding tutor. User picks a topic → Claude generates a task → user writes code in Monaco editor → runs it → gets AI feedback via chat. --- ## Tech Stack | Layer | Choice | |---|---| | Framework | Next.js 16 (App Router, server components minimally used) | | UI | React 19, Tailwind CSS v4 | | Editor | `@monaco-editor/react` (dynamic import, SSR disabled) | | AI | Anthropic SDK (primary) + raw SSE fetch for OpenAI-compatible providers | | Code execution | Wandbox API (Python); iframe srcdoc (HTML/CSS) | | DB | SQLite via Drizzle ORM + better-sqlite3 | | Auth | Custom JWT (jose) + bcryptjs, httpOnly cookie | --- ## Directory Map ``` app/ api/ ai/route.ts # Streaming AI endpoint (task gen, review, chat) execute/route.ts # Code execution proxy → Wandbox models/route.ts # Fetch available models from provider auth/ register/route.ts # POST: create account, set JWT cookie login/route.ts # POST: verify password, set JWT cookie logout/route.ts # POST: clear cookie me/route.ts # GET: verify cookie → return user session/route.ts # GET/PUT: load/save session for logged-in user layout.tsx # Root layout (metadata, fonts) page.tsx # Renders globals.css components/ AppShell.tsx # ← START HERE. Top-level orchestrator. Owns all state, # auth, session sync, and renders everything. TopicSelector.tsx # Full-screen topic picker (shown during 'selecting' phase) ProviderSettings.tsx # Modal: AI provider/model/key config AuthModal.tsx # Modal: Sign In / Create Account editor/ EditorPane.tsx # Left pane: wraps toolbar + editor + output EditorToolbar.tsx # Toolbar: run, submit, auth status, saved indicator CodeEditor.tsx # Monaco editor wrapper OutputPanel.tsx # Execution result display HtmlPreview.tsx # iframe srcdoc preview for HTML topics chat/ ChatPane.tsx # Right pane: wraps task card + message list + input MessageList.tsx # Renders all messages MessageBubble.tsx # Single message (markdown rendered) ChatInput.tsx # Text input for follow-up chat TaskCard.tsx # Displays the AI-generated task description hooks/ useAppState.ts # Central useReducer — AppState + AppAction + reducer useAI.ts # Streaming fetch: generateTask / reviewCode / sendMessage useCodeExecution.ts # Calls /api/execute, dispatches EXECUTE_START/DONE lib/ topics.ts # 10 static topics (TOPICS array + TOPIC_BY_ID map) providers.ts # PROVIDERS, PROVIDER_MAP, load/saveProviderConfig prompts.ts # 3 system prompt builders (task / review / chat) pistonClient.ts # executePython() → Wandbox API (15s timeout) localSession.ts # localStorage save/load/clear for soft session auth.ts # hashPassword, verifyPassword, signToken, verifyToken, # setAuthCookie, clearAuthCookie, getAuthUser db/ schema.ts # Drizzle table definitions: users, saved_sessions index.ts # Drizzle singleton (globalThis pattern for hot reload) types/ index.ts # ALL shared types — read this first when confused about types drizzle.config.ts # Drizzle migration config (SQLite, professor.db) ``` --- ## State Machine ``` AppPhase: selecting → loading_task → ready ↔ executing ↕ reviewing ``` `AppState` (in `hooks/useAppState.ts`): - `phase` — current app phase - `topic` — selected `Topic | null` - `task` — AI-generated `Task | null` - `code` — current editor content - `messages` — `Message[]` (task card, reviews, chat) - `executionResult` — last run output - `streamingContent` — partial AI response being streamed - `isStreaming` — whether AI stream is active - `error` — error string | null Key actions: `SELECT_TOPIC`, `RESTORE_SESSION`, `CODE_CHANGE`, `EXECUTE_DONE`, `STREAM_DONE`, `RESET` --- ## Data Flow ``` AppShell ├── useAppState() → [state, dispatch] ├── useAI() → generateTask / reviewCode / sendMessage │ └── POST /api/ai → streams text back → dispatches STREAM_CHUNK / STREAM_DONE ├── EditorPane │ ├── EditorToolbar (auth status, saved indicator, run/submit buttons) │ ├── CodeEditor (Monaco, dispatches CODE_CHANGE) │ └── useCodeExecution → POST /api/execute → dispatches EXECUTE_DONE └── ChatPane ├── TaskCard (renders state.task) └── MessageList (renders state.messages) ``` --- ## Session Persistence **Soft (always on):** localStorage key `professor_session` stores `{ topicId, task, code, messages, executionResult }`. Saved on every state change, restored on mount via `RESTORE_SESSION` action. **Hard (account required):** Same data synced to `saved_sessions` table in SQLite, debounced 1s. Auth via httpOnly JWT cookie (`professor_auth`). On login, DB session overrides localStorage. Restore sequence in `AppShell.tsx`: 1. Mount → restore from localStorage immediately 2. Mount → `GET /api/auth/me` → if logged in, `GET /api/session` → dispatch `RESTORE_SESSION` (overrides local) --- ## AI Provider System Configured at runtime via `ProviderSettings` modal. Config stored in `localStorage` under `professor_provider_config`. Supported providers (`lib/providers.ts`): `anthropic`, `openrouter`, `lmstudio`, `ollama` `/api/ai/route.ts` branches on `providerConfig.provider`: - Anthropic → uses `@anthropic-ai/sdk` with streaming - Others → raw SSE fetch to OpenAI-compatible endpoint --- ## Key Patterns & Conventions - **All types in one file:** `types/index.ts`. Check here before creating new types. - **No external state library** — everything is `useReducer` in `AppShell` via `useAppState`. - **Prop drilling** (intentional, no Context) — `AppShell → EditorPane → EditorToolbar` and `AppShell → ChatPane`. - **Modal pattern:** Fixed overlay div, `onClick` on backdrop closes. See `ProviderSettings.tsx` or `AuthModal.tsx` as reference. - **localStorage pattern:** Always guard with `if (typeof window === 'undefined') return`. See `lib/providers.ts`. - **API routes are all public** except `/api/session` (requires JWT cookie). - **Streaming:** AI responses stream as plain text (`text/plain`). Client accumulates via `STREAM_CHUNK` and finalises with `STREAM_DONE`. - **Monaco:** Always `next/dynamic({ ssr: false })`. - **HTML topics** skip `/api/execute` entirely — rendered client-side in an iframe. --- ## Environment Variables ``` ANTHROPIC_API_KEY=... # Fallback key for Anthropic provider (required) JWT_SECRET=... # 32+ char secret for JWT signing (required) ``` --- ## Common Tasks — Where to Start | Task | File(s) to read first | |---|---| | Add a new topic | `lib/topics.ts` | | Change AI prompt behaviour | `lib/prompts.ts` | | Add a new app phase / state | `types/index.ts` → `hooks/useAppState.ts` | | Add a new modal | Copy `components/AuthModal.tsx` or `components/ProviderSettings.tsx` | | Add a new API route | Mirror existing routes in `app/api/` | | Change execution behaviour | `hooks/useCodeExecution.ts` + `app/api/execute/route.ts` | | Modify session data saved | `lib/localSession.ts` + `app/api/session/route.ts` + `db/schema.ts` | | Add a new provider | `lib/providers.ts` + `types/index.ts` (`ProviderId`) + `app/api/ai/route.ts` | --- ## Dev Commands ```bash npm run dev # Start dev server npm run db:push # Apply schema changes to professor.db (Drizzle) npx tsc --noEmit # Type check ```