Files
professor/CLAUDE.md
Aodhan Collins f644937604 Initial commit — AI-powered coding tutor (Professor)
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>
2026-03-04 21:48:34 +00:00

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 phase
  • topic — selected Topic | null
  • task — AI-generated Task | null
  • code — current editor content
  • messagesMessage[] (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.tshooks/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