Next.js 16, React 19, Monaco editor, Anthropic SDK, multi-provider AI, Wandbox Python execution, iframe HTML preview, SQLite auth + session persistence. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
7.9 KiB
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.
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 <AppShell />
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 phasetopic— selectedTopic | nulltask— AI-generatedTask | nullcode— current editor contentmessages—Message[](task card, reviews, chat)executionResult— last run outputstreamingContent— partial AI response being streamedisStreaming— whether AI stream is activeerror— 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:
- Mount → restore from localStorage immediately
- Mount →
GET /api/auth/me→ if logged in,GET /api/session→ dispatchRESTORE_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/sdkwith 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
useReducerinAppShellviauseAppState. - Prop drilling (intentional, no Context) —
AppShell → EditorPane → EditorToolbarandAppShell → ChatPane. - Modal pattern: Fixed overlay div,
onClickon backdrop closes. SeeProviderSettings.tsxorAuthModal.tsxas reference. - localStorage pattern: Always guard with
if (typeof window === 'undefined') return. Seelib/providers.ts. - API routes are all public except
/api/session(requires JWT cookie). - Streaming: AI responses stream as plain text (
text/plain). Client accumulates viaSTREAM_CHUNKand finalises withSTREAM_DONE. - Monaco: Always
next/dynamic({ ssr: false }). - HTML topics skip
/api/executeentirely — 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
npm run dev # Start dev server
npm run db:push # Apply schema changes to professor.db (Drizzle)
npx tsc --noEmit # Type check