# Project export: Feynman

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: UC Berkeley AI Hackathon 2026
- Tagline: Feynman learns how you think. Build a living knowledge graph of what you study and master it by teaching it back.
- Devpost: https://devpost.com/software/feynman-s6h3ke
- GitHub: https://github.com/mattdiep15/feynman
- Demo: https://feynman-pi.vercel.app/
- Video: https://www.youtube.com/embed/veUY0hJSdsw?enablejsapi=1&hl=en_US&rel=0&start=&version=3&wmode=transparent
- Team: 2 GitHub contributor(s) — Matt (75 commits), Claude Opus 4.8 (1M context) (58 commits)

## Devpost submission (written by the team)

### Overview

Build your knowledge graph. Master it by teaching it back.

### Inspiration

I've always been searching for more effective ways to learn. I've tried pomodoro, blurting, and plenty of other methods, but none of them felt as effective as one idea I kept coming back to: teaching is the most effective way to learn. When you have to explain something to someone else, you find out very quickly what you actually know and what concepts you need to spend more time on. That led me to the Feynman technique. If you can't explain a concept in plain language, you don't deeply understand it. The moment you try to teach something out loud, every gap in your knowledge surfaces immediately. It's hard to fake your way through an explanation. I'd also always wanted to visualize the connections between topics and actually see which areas I needed to build up before tackling harder concepts. So I combined the two ideas into one tool. Something that makes you explain to demonstrate mastery, listens to how you learn, and visualizes the entire process. What does Feynman do? Feynman is a voice-first learning agent. You paste your notes, give it an initial framework of what you want to learn, and it builds a living knowledge graph where every node is a concept colored by how well you understand it. When you're ready to study, pick a concept, hit record, and explain it out loud. Feynman transcribes your explanation, searches your knowledge graph for related concepts, and passes everything to Claude. Claude scores what you got right, what you missed, and what misconceptions crept in. Deepgram speaks the feedback back to you. The node re-colors instantly. The more you use it, the smarter it gets. Feynman tracks your misconceptions, your progress, and how you like to be taught across sessions. It adapts to your learning style. How I built it Frontend: Built with Next.js. The neuron map is rendered using react-force-graph-2d, with nodes colored by mastery state. Untouched concepts appear hollow. The more you study a concept, the more it fills in. Voice: Deepgram STT (nova-3) transcribes everything you say. Deepgram TTS speaks the feedback back. The whole experience is a conversation. You talk, it responds. Reasoning: Claude reads your notes and pulls out the key concepts and how they connect. When you converse with Feyman, it evaluates your explanation against what you've actually studied; it grounds itself in your knowledge graph. Embeddings: Voyage AI embeds every concept and spoken transcript into vectors, making it possible to find concepts that are related in meaning across your entire neuron map. Memory: Redis Cloud with Search and Query is where everything lives. When you speak, your transcript is embedded and KNN-searched against your knowledge graph to find the most relevant concepts. Mastery scores, misconceptions, and learning preferences all persist across sessions in long-term memory. It's the agent's brain. Challenges I ran into Redis vector search setup. Getting Redis vector search working was one of the bigger hurdles. Not all Redis tiers include the Search and Query module, which took time to figure out. On top of that, embeddings have to be passed as a Float32Buffer, not a raw JS array. Passing the wrong type returns nothing silently, which made it fairly annoying to debug. react-force-graph-2d in Next.js. The library uses browser APIs that don't exist on the server, which crashes Next.js on startup. It needs to be dynamically imported with ssr: false, otherwise the app just white-screens with no useful error message. Consistent JSON from Claude. The evaluation prompt needs to return structured JSON every time or the frontend breaks. Getting Claude to stay consistent while still producing useful feedback took many iterations. Voyage AI embeddings. Anthropic has no native embeddings endpoint. I assumed it worked like other providers, but their documentation points to Voyage AI as the recommended provider, so I went with that. Accomplishments that I'm proud of The core loop working end-to-end is what I'm most proud of. You speak, Feynman retrieves your knowledge graph from Redis, Claude evaluates against that context, Deepgram speaks the feedback back, and the node re-colors. The whole interaction feels responsive and highly curated to you. I'm also proud of building out the memory system and figuring out how to have the agent actually learn from your responses over time. Getting the relationship between concepts to inform how Claude evaluates you, and having that context persist and grow across sessions, was one of the more rewarding features to get working. What I learned This was my first project connecting multiple API endpoints together and working with Redis, and I felt like I was ambitious with the scope. Getting all the pieces talking to each other reliably, managing latency between Deepgram STT and Claude, keeping API keys server-side, took far more time and thinking than I expected. I also learned that an idea like the Feynman technique is structured really well for an AI interaction loop. A naturally structured process with clear inputs and outputs gives you a lot to work with, and plugging the right APIs into each step of that loop is what brought the ideas into a working POC. Feynman's Future Cross-brain transfer learning. When a new concept enters one brain, vector search across all brains finds semantically similar mastered concepts. Claude draws bridge edges with analogies like "you already understand exponential growth from math. Compound interest is the same idea applied to money." Concept dependency graph. Prerequisite relationships between concepts, so Feynman can tell you "your understanding of compound interest is held back by a shaky grasp of principal. Reinforce that concept first." Deeper learning profile. The system already detects misconception patterns and confidence gaps across sessions. The next step is surfacing these as a visible Learning Profile node on the graph, showing you a map of not just what you know but how you think. Spaced repetition. The next step is implementing a forgetting curve per concept, where mastery slowly decays over time if a concept goes unreviewed, and scheduling teachback sessions around that decay, to keep concepts fresh.

## README (from the GitHub repository)

# UC Berkeley AI Hackathon 2026 Submission

<div align="center">
  <img src="media/logo.png" alt="Feynman" width="180"/>
</div>

<p align="center"><i>Build your knowledge graph. Master it by teaching it back.</i></p>

# Feynman
Feynman is a **voice-first learning agent**. You paste your notes, give it an initial framework of what you want to learn, and it builds a living **knowledge graph** where every node is a concept colored by how well you understand it. Pick a concept, hit record, and explain it out loud — Feynman scores what you got right, what you missed, and what misconceptions crept in, then re-colors the node instantly.

<p align="center">
  <a href="https://feynman-pi.vercel.app/"><b>🚀 Try it out</b></a>
  &nbsp;·&nbsp;
  <a href="https://devpost.com/software/feynman-s6h3ke"><b>📝 Devpost</b></a>
  &nbsp;·&nbsp;
  <a href="https://docs.google.com/presentation/d/1Phhx9FkcMEhtW2xGULLAx6HPz6B2Rzj15hy87e_OIMo/edit?usp=sharing"><b>📄 Slides</b></a>
  &nbsp;·&nbsp;
  <a href="https://www.youtube.com/watch?v=veUY0hJSdsw"><b>🎥 Demo</b></a>
</p>

---

## App Overview

<table>
  <tr>
    <td align="center">
      <img src="media/start_screen.png" alt="Start" width="320"/>
      <br/>
      <b>Start</b>
      <br/>
      <sub>Land, drop in your notes, and start learning</sub>
    </td>
    <td align="center">
      <img src="media/converse.png" alt="Converse" width="320"/>
      <br/>
      <b>Converse</b>
      <br/>
      <sub>Explain a concept out loud; get scored, grounded feedback</sub>
    </td>
  </tr>
  <tr>
    <td align="center">
      <img src="media/neuron_map.png" alt="Neuron Map" width="320"/>
      <br/>
      <b>Neuron Map</b>
      <br/>
      <sub>Your knowledge as a graph, nodes colored by mastery</sub>
    </td>
    <td align="center">
      <img src="media/progress.png" alt="Progress" width="320"/>
      <br/>
      <b>Progress</b>
      <br/>
      <sub>Track mastery per concept across every session</sub>
    </td>
  </tr>
</table>

---

## Inspiration

I've always been searching for more effective ways to learn. I've tried pomodoro, blurting, and plenty of other methods, but none of them felt as effective as one idea I kept coming back to: teaching is the most effective way to learn. When you have to explain something to someone else, you find out very quickly what you actually know and what concepts you need to spend more time on.

That led me to the Feynman technique. If you can't explain a concept in plain language, you don't deeply understand it. The moment you try to teach something out loud, every gap in your knowledge surfaces immediately. It's hard to fake your way through an explanation.

I'd also always wanted to visualize the connections between topics and actually see which areas I needed to build up before tackling harder concepts. So I combined the two ideas into one tool — something that makes you explain to demonstrate mastery, listens to how you learn, and visualizes the entire process.

---

## What does Feynman do?

Feynman is a voice-first learning agent. You paste your notes, give it an initial framework of what you want to learn, and it builds a living knowledge graph where every node is a concept colored by how well you understand it.

When you're ready to study, pick a concept, hit record, and explain it out loud. Feynman transcribes your explanation, searches your knowledge graph for related concepts, and passes everything to Claude. Claude scores what you got right, what you missed, and what misconceptions crept in. Deepgram speaks the feedback back to you. The node re-colors instantly.

The more you use it, the smarter it gets. Feynman tracks your misconceptions, your progress, and how you like to be taught across sessions. It adapts to your learning style.

---

## How I built it

**Frontend:** Built with Next.js. The neuron map is rendered using react-force-graph-2d, with nodes colored by mastery state. Untouched concepts appear hollow. The more you study a concept, the more it fills in.

**Voice:** Deepgram STT (nova-3) transcribes everything you say. Deepgram TTS speaks the feedback back. The whole experience is a conversation. You talk, it responds.

**Reasoning:** Claude reads your notes and pulls out the key concepts and how they connect. When you converse with Feynman, it evaluates your explanation against what you've actually studied; it grounds itself in your knowledge graph.

**Embeddings:** Voyage AI embeds every concept and spoken transcript into vectors, making it possible to find concepts that are related in meaning across your entire neuron map.

**Memory:** Redis Cloud with Search and Query is where everything lives. When you speak, your transcript is embedded and KNN-searched against your knowledge graph to find the most relevant concepts. Mastery scores, misconceptions, and learning preferences all persist across sessions in long-term memory. It's the agent's brain.

> 📖 For setup, env vars, API routes, and the full architecture, see **[DEVELOPMENT.md](./DEVELOPMENT.md)**.

---

## Challenges I ran into

**Redis vector search setup.** Getting Redis vector search working was one of the bigger hurdles. Not all Redis tiers include the Search and Query module, which took time to figure out. On top of that, embeddings have to be passed as a Float32Buffer, not a raw JS array. Passing the wrong type returns nothing silently, which made it fairly annoying to debug.

**react-force-graph-2d in Next.js.** The library uses browser APIs that don't exist on the server, which crashes Next.js on startup. It needs to be dynamically imported with `ssr: false`, otherwise the app just white-screens with no useful error message.

**Consistent JSON from Claude.** The evaluation prompt needs to return structured JSON every time or the frontend breaks. Getting Claude to stay consistent while still producing useful feedback took many iterations.

**Voyage AI embeddings.** Anthropic has no native embeddings endpoint. I assumed it worked like other providers, but their documentation points to Voyage AI as the recommended provider, so I went with that.

---

## Accomplishments that I'm proud of

The core loop working end-to-end is what I'm most proud of. You speak, Feynman retrieves your knowledge graph from Redis, Claude evaluates against that context, Deepgram speaks the feedback back, and the node re-colors. The whole interaction feels responsive and highly curated to you.

I'm also proud of building out the memory system and figuring out how to have the agent actually learn from your responses over time. Getting the relationship between concepts to inform how Claude evaluates you, and having that context persist and grow across sessions, was one of the more rewarding features to get working.

---

## What I learned

This was my first project connecting multiple API endpoints together and working with Redis, and I felt like I was ambitious with the scope. Getting all the pieces talking to each other reliably, managing latency between Deepgram STT and Claude, keeping API keys server-side, took far more time and thinking than I expected.

I also learned that an idea like the Feynman technique is structured really well for an AI interaction loop. A naturally structured process with clear inputs and outputs gives you a lot to work with, and plugging the right APIs into each step of that loop is what brought the ideas into a working POC.

---

## Feynman's Future

**Cross-brain transfer learning.** When a new concept enters one brain, vector search across all brains finds semantically similar mastered concepts. Claude draws bridge edges with analogies like "you already understand exponential growth from math. Compound interest is the same idea applied to money."

**Concept dependency graph.** Prerequisite relationships between concepts, so Feynman can tell you "your understanding of compound interest is held back by a shaky grasp of principal. Reinforce that concept first."

**Deeper learning profile.** The

[README truncated for size]

## Detected evidence (automated analysis)

Indexed codebase: 105 recognized source files, 460 KB.
- Anthropic (technology) — detected in the code
- CSS (language) — detected in the code
- HTML (language) — detected in the code
- Next.js (technology) — detected in the code
- React (technology) — detected in the code
- Redis (technology) — detected in the code
- TypeScript (language) — detected in the code
- Vercel (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

## Codebase structure (from repository index)

### Files (111 of 111)

```
code/.env.local.example
code/.gitignore
code/app/api/brains/route.ts
code/app/api/commit/route.ts
code/app/api/deepgram-token/route.ts
code/app/api/evaluate/route.ts
code/app/api/extract/route.ts
code/app/api/graph/route.ts
code/app/api/overview/route.ts
code/app/api/refresher/route.ts
code/app/api/reset/route.ts
code/app/api/speak/route.ts
code/app/api/suggest/route.ts
code/app/api/transcribe/route.ts
code/app/globals.css
code/app/layout.tsx
code/app/page.tsx
code/CLAUDE.md
code/components/AgentAvatar.tsx
code/components/brainIcons.tsx
code/components/BrainOverview.tsx
code/components/Converse.tsx
code/components/Feynman.tsx
code/components/HowItWorks.tsx
code/components/IconEditModal.tsx
code/components/IconPicker.tsx
code/components/LandingGraph.tsx
code/components/LandingPage.tsx
code/components/Logo.tsx
code/components/Modal.tsx
code/components/NeuronMap.tsx
code/components/NewBrainModal.tsx
code/components/NotesPanel.tsx
code/components/ProgressTab.tsx
code/components/SegmentedControl.tsx
code/components/SettingsTab.tsx
code/components/Sidebar.tsx
code/components/tabDefs.tsx
code/components/Toggle.tsx
code/context/SettingsContext.tsx
code/data/notes_personal_finance.txt
code/docs/draft/brand_components_prompt.md
code/docs/draft/dependent_nodes_prompt.md
code/docs/draft/example_conversation.md
code/docs/draft/feynman_ui_mockup_v2.html
code/docs/draft/landing.md
code/docs/draft/proposal_refined.md
code/docs/draft/proposal_v2.md
code/docs/draft/proposal_v3.md
code/docs/draft/proposal_v4.md
code/docs/draft/proposal.md
code/docs/draft/settings.md
code/docs/features_v2.md
code/docs/features_v3.md
code/docs/features_v4.md
code/docs/multibrain_plan.md
code/docs/proposal_agent_ready.md
code/docs/ui_prompt.md
code/lib/brainIcons.ts
code/lib/brains.ts
code/lib/claude.ts
code/lib/constants.ts
code/lib/deepgram.ts
code/lib/embed.ts
code/lib/evaluate.ts
code/lib/extract.ts
code/lib/graph.ts
code/lib/json.ts
code/lib/mastery.ts
code/lib/memory.ts
code/lib/nodeState.ts
code/lib/overview.ts
code/lib/redis.ts
code/lib/retrieve.ts
code/lib/sampleNotes.ts
code/lib/score.ts
code/lib/settings.ts
code/lib/suggest.ts
code/next.config.mjs
code/package.json
code/README.md
code/RUNBOOK.md
code/tests/api-brains.test.ts
code/tests/api-commit.test.ts
code/tests/api-evaluate.test.ts
code/tests/api-extract.test.ts
code/tests/api-graph.test.ts
code/tests/api-overview.test.ts
code/tests/api-refresher.test.ts
code/tests/api-reset.test.ts
code/tests/api-speak.test.ts
code/tests/api-suggest.test.ts
code/tests/api-transcribe.test.ts
code/tests/brains.test.ts
code/tests/claude.test.ts
code/tests/embed.test.ts
code/tests/evaluate.test.ts
code/tests/extract.test.ts
code/tests/graph.test.ts
code/tests/json.test.ts
code/tests/mastery.test.ts
code/tests/memory.test.ts
code/tests/nodeState.test.ts
code/tests/overview.test.ts
code/tests/redis.test.ts
code/tests/retrieve.test.ts
code/tests/score.test.ts
code/tests/suggest.test.ts
code/tsconfig.json
code/vitest.config.ts
README.md
```

### Dependencies

- code/package.json: @anthropic-ai/sdk@^0.32.1, @deepgram/sdk@^3.9.0, @types/node@^22.10.0, @types/react@^19.0.0, @types/react-dom@^19.0.0, lucide-react@^1.21.0, next@^15.1.0, react@^19.0.0, react-dom@^19.0.0, react-force-graph-2d@^1.27.0, redis@^4.7.0, typescript@^5.7.0, vitest@^2.1.8, voyageai@^0.0.4

### Recent commits (newest first)

- docs: update README.md
- Updated README
- fix: README
- chore: repo clean up and documentation
- tune(ui): set light-mode label halo to 3px (between 2 and prior 4)
- fix(tabs): gate brain tabs on the actual sidebar selection
- refine(ui): white node labels + no halo in dark mode; gate brain tabs
- feat(settings): make Text size scale the whole app UI, not graph nodes
- tune(settings): widen Text size range so small vs large is obvious
- fix(settings): apply Text size to overview brain labels
- feat(settings): add Text size control; thinner neuron-map label halo
- chore: docs + cleanup
- refine(ui): bigger neuron hint, darker sidebar icons, upright landing labels
- feat(brains): lucide brain icons + editable per-brain icon; fix overview pile-up
- refine(overview+map+sidebar): circles, hover fix, home overview, rename
- refine(overview+map): flat field, solid lobes, click-to-open, fade handoff
- fix(landing): single-settle spacing + centered framing (no explode)
- fix(layout): suppress html hydration warning from pre-hydration theme script
- feat(v4): multibrain overview redesign (R3)
- feat(v4): Deepgram real-time STT + reactive voice element (R5)

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

### code/CLAUDE.md

```markdown
# CLAUDE.md

Behavioral guidelines to reduce common LLM coding mistakes. Merge with project-specific instructions as needed.

**Tradeoff:** These guidelines bias toward caution over speed. For trivial tasks, use judgment.

## 1. Think Before Coding

**Don't assume. Don't hide confusion. Surface tradeoffs.**

Before implementing:
- State your assumptions explicitly. If uncertain, ask.
- If multiple interpretations exist, present them - don't pick silently.
- If a simpler approach exists, say so. Push back when warranted.
- If something is unclear, stop. Name what's confusing. Ask.

## 2. Simplicity First

**Minimum code that solves the problem. Nothing speculative.**

- No features beyond what was asked.
- No abstractions for single-use code.
- No "flexibility" or "configurability" that wasn't requested.
- No error handling for impossible scenarios.
- If you write 200 lines and it could be 50, rewrite it.

Ask yourself: "Would a senior engineer say this is overcomplicated?" If yes, simplify.

## 3. Surgical Changes

**Touch only what you must. Clean up only your own mess.**

When editing existing code:
- Don't "improve" adjacent code, comments, or formatting.
- Don't refactor things that aren't broken.
- Match existing style, even if you'd do it differently.
- If you notice unrelated dead code, mention it - don't delete it.

When your changes create orphans:
- Remove imports/variables/functions that YOUR changes made unused.
- Don't remove pre-existing dead code unless asked.

The test: Every changed line should trace directly to the user's request.

## 4. Goal-Driven Execution

**Define success criteria. Loop until verified.**

Transform tasks into verifiable goals:
- "Add validation" → "Write tests for invalid inputs, then make them pass"
- "Fix the bug" → "Write a test that reproduces it, then make it pass"
- "Refactor X" → "Ensure tests pass before and after"

For multi-step tasks, state a brief plan:
```
1. [Step] → verify: [check]
2. [Step] → verify: [check]
3. [Step] → verify: [check]
```

Strong success criteria let you loop independently. Weak criteria ("make it work") require constant clarification.

---

**These guidelines are working if:** fewer unnecessary changes in diffs, fewer rewrites due to overcomplication, and clarifying questions come before implementation rather than after mistakes.

```

### code/RUNBOOK.md

```markdown
# Runbook

Operational guide for running, verifying, and troubleshooting Feynman. For the
project overview and architecture, see [README.md](./README.md).

---

## 0. Pre-flight (one-time)

| Check | How | Expected |
|---|---|---|
| Node version | `node -v` | v18+ (built on v22) |
| Deps installed | `npm install` | exits 0, `node_modules/` present |
| Secrets present | `cat .env.local` (don't share output) | 4 vars filled, no placeholders |
| Redis has Search | Redis Cloud console → your DB → **Capabilities** | **Search & Query** = enabled |

`.env.local` (copy from `.env.local.example`):
```bash
ANTHROPIC_API_KEY=sk-ant-...
DEEPGRAM_API_KEY=...
VOYAGE_API_KEY=pa-...
REDIS_URL=rediss://default:<password>@<host>:<port>   # password is inside the URL; no separate key
```

> ⚠️ If **Search & Query** is not enabled on the Redis database, `FT.*` commands
> fail and vector search silently returns nothing. This is the #1 setup gotcha.
> Vanilla Redis and Upstash do not support it — use Redis Cloud.

---

## 1. Run the test suite (no keys needed)

External SDKs (Claude, Deepgram, Voyage, Redis) are mocked, so this works
offline and without credentials. Run it first to confirm the code is intact.

```bash
npm test
```
**Expected:** `Test Files 15 passed`, `Tests 49 passed`.

Watch mode while developing: `npm run test:watch`.

---

## 2. Type-check / production build (no keys needed)

```bash
npm run build
```
**Expected:** `✓ Compiled successfully`, a route table listing all six
`/api/*` routes, exit 0. Clients are lazily initialized, so the build does not
need live keys.

---

## 3. Start the dev server (needs all 4 secrets)

```bash
npm run dev          # http://localhost:3000
```
On the **first request**, the app connects to Redis and creates the
`idx:concepts` vector index (idempotent — safe on every cold start).

### Verify the stack layer by layer

**3a. Redis connection + index.** Trigger any route (just load the page, which
calls `GET /api/graph`). Then check the index exists:
```bash
redis-cli -u "$REDIS_URL" FT.INFO idx:concepts        # should print index schema
```
On an empty brain the page loads with an empty graph — that's correct.

**3b. Extract (Notes → Graph).** In the UI: **Load sample notes → Build graph**.
Gray (untested) nodes should appear within a few seconds. Verify storage:
```bash
redis-cli -u "$REDIS_URL" KEYS 'concept:demo:finance:*'      # one key per concept
redis-cli -u "$REDIS_URL" ZRANGE mastery:demo:finance 0 -1   # concept ids
redis-cli -u "$REDIS_URL" HGET concept:demo:finance:compound_interest name
# do NOT HGETALL — the embedding field is a binary Buffer and will look like garbage
```
Or via curl:
```bash
curl -s localhost:3000/api/graph | head -c 400
```

**3c. Voice loop (the core).** Click a node (e.g. **Compound Interest**) →
**Record explanation** → speak → **Stop & submit**. Expected sequence in the
panel: `Transcribing… → Evaluating… → Speaking…`, then a score, written
feedback, and the node recolors (gray→amb
[truncated — 3367 more characters]
```

### code/package.json

```
{
  "name": "feynman",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "lint": "next lint",
    "test": "vitest run",
    "test:watch": "vitest"
  },
  "dependencies": {
    "@anthropic-ai/sdk": "^0.32.1",
    "@deepgram/sdk": "^3.9.0",
    "lucide-react": "^1.21.0",
    "next": "^15.1.0",
    "react": "^19.0.0",
    "react-dom": "^19.0.0",
    "react-force-graph-2d": "^1.27.0",
    "redis": "^4.7.0",
    "voyageai": "^0.0.4"
  },
  "devDependencies": {
    "@types/node": "^22.10.0",
    "@types/react": "^19.0.0",
    "@types/react-dom": "^19.0.0",
    "typescript": "^5.7.0",
    "vitest": "^2.1.8"
  }
}

```

### code/app/page.tsx

```typescript
'use client';

import { useState } from 'react';
import Feynman from '@/components/Feynman';
import LandingPage from '@/components/LandingPage';

export default function Home() {
  const [started, setStarted] = useState(false);
  const [landingMounted, setLandingMounted] = useState(true);

  const handleStart = () => {
    setStarted(true);
    // Unmount the decorative landing graph once the transition completes to free
    // up its render loop.
    setTimeout(() => setLandingMounted(false), 700);
  };

  return (
    <>
      {landingMounted && (
        <div className={`landing${started ? ' hidden' : ''}`}>
          <LandingPage onStart={handleStart} />
        </div>
      )}
      <div className={`app-wrapper${started ? ' visible' : ''}`}>
        <Feynman />
      </div>
    </>
  );
}

```

### code/app/layout.tsx

```typescript
import type { ReactNode } from 'react';
import './globals.css';
import { SettingsProvider } from '@/context/SettingsContext';

export const metadata = {
  title: 'Feynman',
  description: 'A voice-first learning agent that builds a living knowledge graph.',
  icons: { icon: '/favicon.svg' },
};

// Apply persisted theme + font + text scale before paint to avoid a flash of default settings.
const THEME_SCRIPT = `(function(){try{var s=JSON.parse(localStorage.getItem('feynman-settings')||'{}');document.documentElement.setAttribute('data-theme',s.theme||'light');var f={system:"system-ui, -apple-system, sans-serif",inter:"'Inter', sans-serif",jetbrains:"'JetBrains Mono', monospace",playfair:"'Playfair Display', serif","dm-sans":"'DM Sans', sans-serif"}[s.font||'system'];if(f)document.documentElement.style.setProperty('--font-app',f);var z={small:0.9,medium:1,large:1.15}[s.textSize||'medium'];if(z)document.documentElement.style.fontSize=(z*100)+'%';}catch(e){}})();`;

export default function RootLayout({ children }: { children: ReactNode }) {
  return (
    // THEME_SCRIPT mutates data-theme/--font-app on <html> before hydration, so the
    // live element intentionally differs from the server markup — suppress the
    // (html-element-scoped) hydration warning for exactly that reason.
    <html lang="en" data-theme="light" suppressHydrationWarning>
      <head>
        <link rel="preconnect" href="https://fonts.googleapis.com" />
        <link rel="preconnect" href="https://fonts.gstatic.com" crossOrigin="anonymous" />
        <link
          href="https://fonts.googleapis.com/css2?family=DM+Sans:wght@400;500;700&family=Inter:wght@400;500;700&family=JetBrains+Mono:wght@400;500;700&family=Playfair+Display:wght@400;500;700&display=swap"
          rel="stylesheet"
        />
        <script dangerouslySetInnerHTML={{ __html: THEME_SCRIPT }} />
      </head>
      <body>
        <SettingsProvider>{children}</SettingsProvider>
      </body>
    </html>
  );
}

```

### code/app/api/transcribe/route.ts

```typescript
// Feature 3b — Transcribe. POST raw audio blob → Deepgram STT → { transcript }.
import { NextResponse } from 'next/server';
import { transcribe } from '@/lib/deepgram';

export async function POST(req: Request) {
  const audio = Buffer.from(await req.arrayBuffer());
  if (!audio.length) {
    return NextResponse.json({ error: 'empty audio' }, { status: 400 });
  }
  const transcript = await transcribe(audio);
  return NextResponse.json({ transcript });
}

```

### code/app/api/speak/route.ts

```typescript
// Feature 3e — Speak. POST { text } → Deepgram TTS → audio/mpeg stream.
import { NextResponse } from 'next/server';
import { synthesize } from '@/lib/deepgram';

export async function POST(req: Request) {
  const body = await req.json().catch(() => null);
  const text = body?.text;
  if (!text || typeof text !== 'string') {
    return NextResponse.json({ error: 'text (string) required' }, { status: 400 });
  }
  const stream = await synthesize(text);
  return new Response(stream, { headers: { 'content-type': 'audio/mpeg' } });
}

```

### code/app/api/deepgram-token/route.ts

```typescript
// R5 — mint a short-lived, scoped Deepgram key for the browser's live STT
// WebSocket. The master key stays server-side; this returns a 60s key the client
// uses to authenticate `wss://api.deepgram.com/v1/listen`.
import { NextResponse } from 'next/server';
import { createScopedKey } from '@/lib/deepgram';

export async function POST() {
  try {
    const { key, expiresIn } = await createScopedKey(60);
    return NextResponse.json({ key, expiresIn });
  } catch (e) {
    // Non-fatal: the client falls back to prerecorded /api/transcribe.
    console.error('deepgram-token error', e);
    return NextResponse.json({ error: 'could not mint token' }, { status: 502 });
  }
}

```

### code/app/api/reset/route.ts

```typescript
// Reset — wipe one brain's data for a clean demo (and drop it from the
// registry). Leaves the idx:concepts index intact (it re-applies to new
// concept:* hashes). brainId comes from the body; defaults to the default brain.
import { NextResponse } from 'next/server';
import { getRedis } from '@/lib/redis';
import { USER_ID } from '@/lib/constants';
import { resolveBrainId, deleteBrain } from '@/lib/brains';

export async function POST(req: Request) {
  const body = await req.json().catch(() => null);
  const brainId = resolveBrainId(body?.brainId);
  const redis = await getRedis();
  const deleted = await deleteBrain(redis, USER_ID, brainId);
  return NextResponse.json({ ok: true, deleted });
}

```

### code/app/api/refresher/route.ts

```typescript
// Refresher Mode — the weakest concepts to review next (nearly free via ZSET).
import { NextResponse } from 'next/server';
import { getRedis } from '@/lib/redis';
import { USER_ID } from '@/lib/constants';
import { resolveBrainId, masteryKey, conceptKey } from '@/lib/brains';
import { statusFromScore } from '@/lib/mastery';

export async function GET(req: Request) {
  const brainId = resolveBrainId(new URL(req.url).searchParams.get('brainId'));
  const redis = await getRedis();
  // ascending score → weakest first; 0..3 = weakest 4
  const ids = await redis.zRange(masteryKey(USER_ID, brainId), 0, 3);

  const concepts = await Promise.all(
    ids.map(async (id) => {
      const [name, masteryScore, status] = await redis.hmGet(conceptKey(USER_ID, brainId, id), [
        'name',
        'masteryScore',
        'status',
      ]);
      const score = Number(masteryScore ?? 0) || 0;
      return { id, name: name ?? id, masteryScore: score, status: status ?? statusFromScore(score) };
    }),
  );

  return NextResponse.json({ concepts });
}

```

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