diff --git a/.env.example b/.env.example
index 6f94f63..262ecda 100644
--- a/.env.example
+++ b/.env.example
@@ -2,20 +2,29 @@
MUX_TOKEN_ID={your_mux_token_here}
MUX_TOKEN_SECRET={your_mux_secret_here}
-# Tavus API Configuration
+# Tavus API Configuration for Learning Check Feature
# Get your API credentials at: https://www.tavus.io/
# Documentation: https://docs.tavus.io/api-reference/conversations/create-conversation
# Required: Your Tavus API key
TAVUS_API_KEY=your_tavus_api_key_here
-# Required: Your Tavus replica ID (AI avatar)
-TAVUS_REPLICA_ID=your_replica_id_here
+# Required: Your Tavus persona ID from dashboard (e.g., "pd8#1eb0d8e")
+# This should reference the "8p3p - AI Instructor Assistant" persona
+TAVUS_PERSONA_ID=your_persona_id_here
-# Optional: Persona ID for custom AI personality
-# Leave commented out to use default persona
-# TAVUS_PERSONA_ID=your_persona_id_here
+# Required: Webhook secret for signature verification
+# Generate a secure random string for webhook authentication
+TAVUS_WEBHOOK_SECRET=your_webhook_secret_here
-# Optional: Default conversation duration in seconds
-# Default: 240 (4 minutes)
-# TAVUS_DEFAULT_CALL_DURATION=240
+# Required: Public webhook URL for perception analysis callbacks
+# This should be your deployed app URL + /api/learning-checks/perception-analysis
+# Example: https://your-app.vercel.app/api/learning-checks/perception-analysis
+TAVUS_WEBHOOK_URL=your_webhook_endpoint_here
+
+# Learning Check Assets
+NEXT_PUBLIC_TAVUS_LEARNING_CHECK_OBJECTIVES_ID=your_objectives_id_here
+NEXT_PUBLIC_TAVUS_LEARNING_CHECK_GUARDRAILS_ID=your_guardrails_id_here
+# Create via API routes:
+# curl -X POST http://localhost:3000/api/learning-checks/objectives -H "Content-Type: application/json" -d '{}'
+# curl -X POST http://localhost:3000/api/learning-checks/guardrails -H "Content-Type: application/json" -d '{}'
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index 59d8c84..a151c1b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -13,6 +13,9 @@
# testing
/coverage
+# logs
+/logs/*
+
# next.js
/.next/
/out/
diff --git a/.windsurf/rules/project-standards.md b/.windsurf/rules/project-standards.md
index 672d105..f28ad36 100644
--- a/.windsurf/rules/project-standards.md
+++ b/.windsurf/rules/project-standards.md
@@ -2,421 +2,83 @@
trigger: always_on
---
-# 8P3P LMS - Windsurf Project Rules
+# 8P3P LMS – Windsurf Project Rules (Main Rules Summary v2)
-
+> Context: EMDR Therapist Training LMS • Stack: Next.js 15, React 19, TS 5.9, Tailwind v4, shadcn/ui • Phase: MVP (manual Q&A only)
-- **Project**: EMDR therapist training LMS
-- **Stack**: Next.js 15, React 19, TypeScript 5.9, AWS Amplify Gen2, Tailwind v4, shadcn/ui
-- **Development Phase**: MVP feature completion (Phase 1)
-- **Testing Strategy**: Manual Q&A testing (no unit tests until post-MVP)
-
+## 1) Mandatory Protocol Sequence
-
+1. Codebase Analysis (ALWAYS FIRST) – Review related code and acknowledge existing work.
+2. Branch Readiness – Present plan (feature spec, stacked PRs, timeline); require user approval before branch creation.
+3. Implementation – Proceed only after Steps 1–2; follow quality gates.
+ Skipping sequence causes invalid recommendations and wasted time.
-## Protocol Execution Order (CRITICAL)
+## 2) Development Workflow
-**MANDATORY SEQUENCE** - Must be followed in exact order:
+Branch creation requires prior Codebase Analysis and explicit approval.
+LOC Verification: run `git diff --cached --shortstat` before commits.
+PR Standards: ≤400 LOC, semantic commits, stacked PRs for large features.
+Branch flow: feature → dev → release/vX.Y.Z → main
-### Step 1: Codebase Analysis Protocol (ALWAYS FIRST)
+## 3) Next.js 15 Architecture
-- **Trigger**: ANY request involving files, components, or functionality
-- **Required**: Complete existing implementation check before ANY recommendations
-- **Blocker**: Cannot proceed to other protocols without completing this step
+Server Components first; use "use client" only for client logic (state, events, browser APIs).
+Prefer server-side data fetching; use ISR `next:{revalidate:3600}` for cacheable data.
+Never unwrap Promises with use().
-### Step 2: Branch Readiness Protocol
+## 4) Design System (Tailwind v4 + shadcn/ui)
-- **Trigger**: Creating new branches or starting development work
-- **Required**: User confirmation before any branch creation
-- **Depends on**: Step 1 completion
+CSS-first config: `@import "tailwindcss";` → `@theme` → vars → `@layer`.
+Use CSS vars directly, not @apply.
+Use shadcn/ui components; follow composition patterns.
+Tailwind utilities only, no CSS Modules (except auto-generated libs).
-### Step 3: Implementation
+## 5) Code Quality & Accountability
-- **Trigger**: After user approval from Step 2
-- **Required**: Follow all quality gates and standards
-- **Depends on**: Steps 1 & 2 completion
+Run Codebase Analysis before planning: Search → Read → Acknowledge.
+Quality Gates: pass lint, type-check, build, functionality, and docs.
+Naming: camelCase (vars), PascalCase (components), kebab-case (utils).
+Document reasoning (“why”) in comments.
+AI recommendations must cite protocol, evidence, and LOC realism.
-**VIOLATION CONSEQUENCES**:
+## 6) Component Reusability
-- Inaccurate recommendations (ignoring existing work)
-- Wasted development time
-- Loss of user trust in AI recommendations
-
+Reuse existing components first.
+Extend shadcn/ui next.
+Create new only when necessary.
-
+## 7) Bug Resolution
-## Branch Readiness Protocol (MANDATORY)
+Perform Root Cause Analysis; document fixes; request user confirmation before closing.
+Create RCA doc for issues requiring >30 min investigation.
-**PREREQUISITE**: Codebase Analysis Protocol MUST be completed first.
+## 8) Specification Process
-Before starting ANY new feature:
+Clarify requirements before coding.
+Address architecture, validation, edge cases, and deployment.
+Prioritize using MoSCoW (Must / Should / Could).
-1. **Complete Codebase Analysis**: Verify existing implementations and acknowledge current state
-2. **Present Branch Readiness Plan**: Show feature spec, branch strategy, stacked PR plan, timeline
-3. **Get User Confirmation**: Ask "Ready to create the branch and start {feature_name} development? 🚀"
-4. **Wait for Approval**: Do NOT create branches without explicit user confirmation
-5. **Document Decision**: Record approval before proceeding
+## 9) Testing
-**BLOCKER**: Cannot proceed without completing Step 1 (Codebase Analysis)
+Manual Q&A only for MVP; enforce lint, type, and build checks.
-## LOC Verification (MANDATORY)
+## 10) Mock Data
-Before every commit:
+Store in src/lib/mock-data.ts, typed with interfaces, grouped by feature.
-```bash
-git diff --cached --shortstat
-git diff --cached --stat
-```
+## 11) No Barrel Exports (Next.js 15 Mandatory Rule)
-**Rules**:
+Do not use barrel export patterns (index.ts / index.tsx).
+Directory-imports for single-file components are fine.
+Barrel re-exports increase load and coupling; disallowed.
+Example:
+✅ `import { VideoPlayer } from '@/components/video/video-player'`
+❌ `import { VideoPlayer } from '@/components/video'`
+Reason: Barrel files load all exports, slowing builds and runtime.
+Migration Checklist: remove barrel files, update imports, re-run type-check and build.
+No exceptions.
+Reference: [Vercel – How We Optimized Package Imports](https://vercel.com/blog/how-we-optimized-package-imports-in-next-js)
-- **MUST** verify actual LOC before committing
-- **NEVER** estimate LOC without verification
-- **ALWAYS** report actual numbers in commit messages
-- **EXCEPTION**: Auto-generated library files (document in commit)
+## 12) Technical Debt
-## PR & Release Standards
-
-- **PR Size**: 200-400 LOC maximum per PR
-- **Stacked PRs**: For complex features, break into dependent PRs
-- **Commit Format**: Semantic commits (feat:, fix:, docs:, refactor:, etc.)
-- **Branch Strategy**: feature → dev → release/vX.Y.Z → main
-
-
-
-
-## Server Components First Rule (HIGHEST PRIORITY)
-
-- **DEFAULT**: Always start with Server Components (no "use client")
-- **ONLY add "use client"** when you need:
- - State management (useState, useReducer)
- - Event handlers (onClick, onChange)
- - Browser APIs (localStorage, window)
- - React hooks requiring client-side execution
-
-## Route Parameters Handling
-
-### Server Components
-
-```tsx
-export default async function PostPage({
- params,
-}: {
- params: Promise<{ id: string }>;
-}) {
- const { id } = await params; // Next.js 15+ requires await
- return Post {id}
;
-}
-```
-
-### Client Components
-
-```tsx
-"use client";
-import { useParams } from "@/hooks/use-params";
-
-export default function ClientComponent({
- params,
-}: {
- params: Promise<{ id: string }>;
-}) {
- const { id } = useParams(params);
- return Post {id}
;
-}
-```
-
-**NEVER**: Use manual Promise unwrapping with `use()` in components
-
-## Data Fetching
-
-- **Prefer**: Server-side data fetching in Server Components
-- **Client-side**: Only when server-side isn't suitable (real-time updates, user interactions)
-- **Use**: ISR with `next: { revalidate: 3600 }` for cacheable data
-
-
-
-
-## Authentication Standards
-
-### Core Rules
-
-- **ALWAYS** import `secret` for auth configurations
-- **External Providers**: Only Google, Apple, Amazon, Facebook supported
-- **Callback/Logout URLs**: Must be inside `externalProviders` object
-- **User Attributes**: Must be outside `loginWith` object
-- **Login Methods**: Only `email` and `phone` (no `username`)
-
-## Data Schema Design
-
-### Authorization Rules (Gen2)
-
-- Use `.guest()` instead of `.public()` (Gen2 only)
-- Use `.to()` for permissions: `allow.guest().to(['read', 'write'])`
-
-### Relationships
-
-- `.hasMany()` and `.belongsTo()` always require related field ID
-- Example: `members: a.hasMany("Member", "teamId")`
-- Reference field must exist: `teamId: a.id()`
-
-### Enums
-
-- Don't use `.required()` or `.defaultValue()` with enums
-
-
-
-
-## Tailwind CSS v4 + shadcn/ui Standards
-
-### CSS-First Configuration
-
-- **No config file needed**: Tailwind v4 uses CSS-first configuration
-- **Single import**: `@import "tailwindcss";` in globals.css
-
-### Stylesheet Structure (globals.css)
-
-**Order matters**: `@import` → `@theme` → CSS variables → `@layer`
-
-### Base Layer Rules
-
-- **Use CSS variables directly** in `@layer base`
-- **Correct**: `background-color: var(--background);`
-- **Incorrect**: `@apply bg-background;`
-
-### Component Standards
-
-- **Primary**: shadcn/ui components for all UI elements
-- **Custom Components**: Follow shadcn/ui patterns and composition
-- **Styling**: Tailwind utility classes only (no CSS Modules except auto-generated libraries)
-
-
-
-
-## Codebase Analysis Protocol (MANDATORY - ALWAYS FIRST)
-
-**CRITICAL**: This protocol MUST be executed BEFORE any other protocols or recommendations.
-
-### Trigger Conditions
-
-- User requests feature implementation
-- User asks about existing functionality
-- User mentions creating/modifying files
-- User says "proceed with [feature]"
-- ANY development-related request
-
-### Required Analysis Steps
-
-**Step 1: Search Existing Implementations**
-
-```bash
-grep_search "featureName|ComponentName"
-find_by_name "*feature*|*component*"
-grep_search "import.*ComponentName"
-```
-
-**Step 2: Read Complete Context**
-
-- Read ALL related files completely (not partial)
-- Understand existing architecture and patterns
-- Identify what's already implemented vs what's missing
-
-**Step 3: Acknowledge Before Recommending**
-
-- **ALWAYS** acknowledge existing implementations first
-- State completion percentage: "X is 85% complete, missing Y and Z"
-- Build upon existing work rather than recreating
-
-### Enforcement Rules
-
-- **NEVER** present plans without completing analysis first
-- **NEVER** assume files don't exist without searching
-- **NEVER** ignore existing implementations in recommendations
-- **ALWAYS** state "Following Codebase Analysis Protocol" when starting
-- **ALWAYS** acknowledge existing work before suggesting new work
-
-### Violation Prevention
-
-**Before ANY recommendation, ask yourself**:
-
-1. ✅ Did I search for existing implementations?
-2. ✅ Did I read the complete existing code?
-3. ✅ Did I acknowledge what's already built?
-4. ✅ Am I building upon existing work vs recreating?
-
-**If ANY answer is NO, STOP and complete the analysis first.**
-
-## Quality Gates (Before Feature Completion)
-
-**ALL must pass before declaring features complete**:
-
-1. **ESLint**: `npm run lint` (0 errors, 0 warnings)
-2. **TypeScript**: `npm run type-check` (full compilation)
-3. **Build**: `npm run build` (successful production build)
-4. **Functionality**: Core use cases work as specified
-5. **Documentation**: README and code docs updated
-
-## ESLint & TypeScript Rules
-
-### Key Rules
-
-- `@next/next/no-async-client-component`: Prevents async Client Components
-- `@typescript-eslint/no-unused-vars`: Prefix unused with `_`
-- `@typescript-eslint/no-explicit-any`: Avoid `any` type
-- `react-hooks/exhaustive-deps`: Complete hook dependencies
-
-### Naming Conventions
-
-- **Variables/Functions**: camelCase
-- **Components/Types**: PascalCase
-- **Files**: kebab-case for utilities, PascalCase for components
-
-## Comment Standards
-
-### Function Documentation
-
-```typescript
-/**
- * Analyzes video content and calculates engagement time
- * @param content - Video URL and metadata
- * @returns Estimated completion time with confidence score
- */
-```
-
-### Business Logic Comments
-
-- **WHY decisions**: Document reasoning behind non-obvious implementations
-- **Edge Cases**: Explain handling of special conditions
-- **Error Handling**: Document recovery strategies
-
-## AI Accountability Standards
-
-### Protocol Compliance Verification
-
-**Before presenting ANY plan or recommendation**:
-
-1. ✅ **State Protocol**: "Following Codebase Analysis Protocol..."
-2. ✅ **Show Evidence**: Present search results and existing implementations found
-3. ✅ **Acknowledge Work**: "I found X is already implemented, Y needs completion"
-4. ✅ **Build Upon**: "Building upon existing work rather than recreating"
-5. ✅ **Accurate Scope**: Present realistic LOC and timeline based on actual state
-
-### Trust Maintenance
-
-**User can rely on AI recommendations when**:
-
-- All protocols followed in correct sequence
-- Existing work acknowledged and respected
-- Plans based on actual codebase state, not assumptions
-
-**User should question AI recommendations when**:
-
-- Protocols skipped or reordered
-- Existing implementations ignored
-- Plans seem to recreate existing functionality
-
-
-
-
-## Component Reusability Protocol (MANDATORY)
-
-Before creating ANY new component:
-
-### 1. Scan Existing Components
-
-```bash
-find_by_name "*component-name*" src/components/
-grep_search "ComponentName" src/components/
-```
-
-### 2. Check shadcn/ui
-
-- Search https://ui.shadcn.com/docs/components for relevant base components
-- Install and use existing shadcn/ui components when applicable
-
-### 3. Prioritization
-
-1. **Reuse existing**: Extend or compose existing components
-2. **shadcn/ui base**: Build on shadcn/ui components
-3. **Create new**: Only when above options aren't suitable
-
-
-
-
-## Systematic Issue Resolution
-
-### Resolution Protocol
-
-1. **Root Cause Analysis**: Trace issue to source, not symptoms
-2. **Solution Documentation**: Create resolution ticket descriptions
-3. **User Confirmation**: MANDATORY before marking resolved
-4. **Verification**: User must test in their environment
-
-### Confirmation Process
-
-```
-1. Present solution and changes made
-2. Ask user to test the fix
-3. Request explicit confirmation: "Can you confirm this issue is resolved?"
-4. Wait for user verification
-```
-
-### RCA Documentation
-
-Create RCA document when:
-
-- Bug required significant investigation (>30 minutes)
-- Root cause was non-obvious
-- Multiple approaches were evaluated
-
-
-
-
-## Clarifying Questions Framework
-
-### Before Implementation
-
-- **ALWAYS** ask clarifying questions before implementation
-- **NEVER** assume requirements without confirmation
-
-### Question Categories
-
-**Technical Architecture**: Data structure, user flow, performance, integration, scalability
-**Feature Specification**: Functionality, edge cases, validation, error handling, accessibility
-**Implementation**: Technology choices, component architecture, state management, testing, deployment
-
-## MoSCoW Prioritization
-
-- **Must Have**: Critical features for MVP
-- **Should Have**: Important but not critical
-- **Could Have**: Nice to have features
-
-
-
-
-## MVP Testing Approach
-
-**Current Phase**: Manual Q&A testing only
-
-- **No unit tests**: Deferred until post-MVP
-- **Focus**: Feature completion over test coverage
-- **Quality Gates**: ESLint, TypeScript, build validation only
-
-
-
-
-## Mock Data Best Practices
-
-- **Location**: Centralized in `src/lib/mock-data.ts`
-- **TypeScript**: Use proper interfaces for typed data
-- **Organization**: Group by feature
-
-
-
-
-## Current Technical Debt
-
-### Tavus CVI Components - CSS Modules
-
-**Status**: Deferred to Post-MVP
-**Issue**: Tavus CVI uses CSS modules; project uses Tailwind v4 + shadcn/ui
-**Action**: Po
+Tavus CVI (CSS Modules) deferred until post-MVP for Tailwind/shadcn alignment.
diff --git a/TECHNICAL_DEBT.md b/TECHNICAL_DEBT.md
deleted file mode 100644
index e69de29..0000000
diff --git a/docs/API_ROUTES.md b/docs/API_ROUTES.md
new file mode 100644
index 0000000..7b94797
--- /dev/null
+++ b/docs/API_ROUTES.md
@@ -0,0 +1,222 @@
+# API Routes Documentation
+
+## Learning Checks API
+
+Server-side API routes for Tavus Conversational Video Interface (CVI) integration.
+
+---
+
+## 🎯 **Overview**
+
+These routes proxy Tavus API calls to keep API keys secure on the server:
+
+```
+Client → Next.js API Route → Tavus API
+ (has API key) (requires API key)
+```
+
+---
+
+## 📋 **Routes**
+
+### **1. Create Conversation**
+
+**Endpoint:** `POST /api/learning-checks/conversation`
+
+**Purpose:** Create a new Tavus conversation for a learning check.
+
+**Request Body:**
+```typescript
+{
+ chapterId: string; // Required
+ chapterTitle: string; // Required
+ objectivesId?: string; // Optional
+ guardrailsId?: string; // Optional
+}
+```
+
+**Response:**
+```typescript
+{
+ conversationUrl: string; // URL to join conversation
+ conversationId: string; // Unique identifier
+ expiresAt: string; // Expiration timestamp
+}
+```
+
+**Tavus API Call:**
+```
+POST https://tavusapi.com/v2/conversations
+Header: x-api-key: {TAVUS_API_KEY}
+```
+
+**File:** `src/app/api/learning-checks/conversation/route.ts`
+
+---
+
+### **2. End Conversation**
+
+**Endpoint:** `POST /api/learning-checks/conversation/[conversationId]/end`
+
+**Purpose:** End an active Tavus conversation (not delete - conversation remains in system but is terminated).
+
+**Path Parameters:**
+- `conversationId` - Unique conversation identifier
+
+**Response:**
+```typescript
+{
+ success: true;
+ conversation_id: string;
+}
+```
+
+**Error Responses:**
+- `400` - Invalid conversation_id
+- `401` - Invalid access token
+- `500` - Internal server error
+
+**Tavus API Call:**
+```
+POST https://tavusapi.com/v2/conversations/{conversation_id}/end
+Header: x-api-key: {TAVUS_API_KEY}
+```
+
+**Note:** Both our route and Tavus use POST (not DELETE) because we're ending/terminating the conversation, not deleting it from the system.
+
+**File:** `src/app/api/learning-checks/conversation/[conversationId]/end/route.ts`
+
+**Official Docs:** https://docs.tavus.io/api-reference/conversations/end-conversation
+
+---
+
+## 🔒 **Security**
+
+### **API Key Protection**
+
+```typescript
+// ✅ Server-side only (safe)
+const apiKey = TAVUS_ENV.getApiKey();
+
+// ❌ NEVER do this (exposed to client)
+const apiKey = process.env.NEXT_PUBLIC_TAVUS_API_KEY;
+```
+
+### **Environment Variables**
+
+```bash
+# .env.local
+TAVUS_API_KEY=your_secret_key_here
+TAVUS_PERSONA_ID=your_persona_id_here
+```
+
+**Access Pattern:**
+```typescript
+import { TAVUS_ENV } from "@/lib/tavus";
+
+const apiKey = TAVUS_ENV.getApiKey(); // ✅ Safe
+const personaId = TAVUS_ENV.getPersonaId(); // ✅ Safe
+```
+
+---
+
+## 📊 **Request Flow**
+
+### **Create Conversation**
+
+```
+1. Client calls: POST /api/learning-checks/conversation
+ Body: { chapterId, chapterTitle }
+
+2. Next.js API Route:
+ - Gets TAVUS_API_KEY from environment
+ - Builds conversation config
+ - Calls Tavus API
+
+3. Tavus API:
+ - Creates conversation
+ - Returns conversation_url and conversation_id
+
+4. Response to client:
+ { conversationUrl, conversationId, expiresAt }
+```
+
+### **End Conversation**
+
+```
+1. Client calls: DELETE /api/.../[conversationId]/end
+
+2. Next.js API Route:
+ - Gets TAVUS_API_KEY from environment
+ - Validates conversationId
+ - Calls Tavus API
+
+3. Tavus API:
+ - Ends conversation
+ - Returns success
+
+4. Response to client:
+ { success: true, conversation_id }
+```
+
+---
+
+## 🧪 **Testing**
+
+### **Manual Testing**
+
+```bash
+# 1. Create conversation
+curl -X POST http://localhost:3000/api/learning-checks/conversation \
+ -H "Content-Type: application/json" \
+ -d '{"chapterId":"ch1","chapterTitle":"Introduction"}'
+
+# Response: { conversationUrl, conversationId, expiresAt }
+
+# 2. End conversation (use conversationId from step 1)
+curl -X POST http://localhost:3000/api/learning-checks/conversation/c123456/end
+
+# Response: { success: true, conversation_id: "c123456" }
+```
+
+### **Client Usage**
+
+```typescript
+// Create conversation
+const response = await fetch("/api/learning-checks/conversation", {
+ method: "POST",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify({
+ chapterId: "chapter-1",
+ chapterTitle: "Chapter 1",
+ }),
+});
+const { conversationUrl, conversationId } = await response.json();
+
+// End conversation
+await fetch(`/api/learning-checks/conversation/${conversationId}/end`, {
+ method: "POST",
+});
+```
+
+---
+
+## 📚 **Related Documentation**
+
+- **Tavus API Reference:** `docs/TAVUS_API_REFERENCE.md`
+- **Tavus Configuration:** `src/lib/tavus/config.ts`
+- **Environment Variables:** `src/lib/tavus/config.ts` (TAVUS_ENV helpers)
+- **Official Tavus Docs:** https://docs.tavus.io
+
+---
+
+## ✅ **Quality Checklist**
+
+- ✅ API keys stored server-side only
+- ✅ Type-safe TypeScript interfaces
+- ✅ Proper error handling (400, 401, 500)
+- ✅ Logging for debugging
+- ✅ Follows Tavus API specification
+- ✅ Secure environment variable access
+- ✅ RESTful route naming
+- ✅ Clear documentation
diff --git a/docs/CLEANUP_2025-10-31.md b/docs/CLEANUP_2025-10-31.md
new file mode 100644
index 0000000..d64eccf
--- /dev/null
+++ b/docs/CLEANUP_2025-10-31.md
@@ -0,0 +1,175 @@
+# Codebase Cleanup - October 31, 2025
+
+Pre-feature cleanup before implementing Learning Check Objective Completion Tracking.
+
+---
+
+## ✅ What Was Cleaned
+
+### **1. Linting Errors Fixed (5 errors → 0 errors)**
+
+**Fixed Files**:
+- ✅ `src/app/api/learning-checks/guardrails/route.ts` - Prefixed unused `request` parameter
+- ✅ `src/app/api/learning-checks/objectives/route.ts` - Prefixed unused `request` parameter
+- ✅ `src/components/course/chapter-content/chapter-quiz.tsx` - Prefixed unused `chapterTitle` and `chapterId` parameters
+- ✅ `src/components/course/chapter-content/index.tsx` - Removed unused `useState` import
+
+**Before**:
+```
+✖ 8 problems (5 errors, 3 warnings)
+```
+
+**After**:
+```
+✖ 3 problems (0 errors, 3 warnings)
+```
+
+**Remaining Warnings** (acceptable for MVP):
+- `any` types in update-persona route and learning-check component
+- Missing hook dependency in hair-check component
+
+---
+
+### **2. Documentation Consolidation**
+
+**Created**:
+- ✅ `docs/TAVUS_INDEX.md` - Central hub for all Tavus documentation with clear navigation
+
+**Archived** (moved to `docs/archive/`):
+- ✅ `HAIRCHECK_CONVERSATION_FIX.md` - Historical fix documentation (completed issue)
+- ✅ `LEARNING_CHECK_BASE_ANALYSIS.md` - Historical analysis documentation (completed refactor)
+
+**Documentation Structure** (Before → After):
+```
+Before:
+docs/
+├── Multiple Tavus docs (11 files, unclear organization)
+├── Historical fix docs
+└── API_ROUTES.md
+
+After:
+docs/
+├── TAVUS_INDEX.md ← NEW: Central navigation hub
+├── archive/ ← NEW: Historical docs
+│ ├── HAIRCHECK_CONVERSATION_FIX.md
+│ └── LEARNING_CHECK_BASE_ANALYSIS.md
+├── TAVUS.md (main guide)
+├── TAVUS_API_REFERENCE.md
+├── TAVUS_CONFIG_UPDATE_GUIDE.md
+├── TAVUS_DYNAMIC_SYNC_COMPLETE.md
+├── TAVUS_OBJECTIVE_COMPLETION_TRACKING.md
+├── TAVUS_TIME_LIMIT_AND_TRACKING_UPDATE.md
+├── TAVUS_IMPLEMENTATION_COMPLETE.md
+├── API_ROUTES.md
+└── README.md
+```
+
+---
+
+### **3. Component Analysis**
+
+**Checked Components** (All in use ✅):
+- ✅ `components/common/timer.tsx` - Used in learning-check.tsx
+- ✅ `components/ui/empty.tsx` - Used in learning-check.tsx
+- ✅ `components/course/resume-button.tsx` - Used in course-overview.tsx
+- ✅ `components/ui/community-feed.tsx` - Used in dashboard/page.tsx
+- ✅ `components/course/chapter-content/learning-check-ready.tsx` - Used in learning-check-base.tsx
+
+**Result**: No unused components found to remove.
+
+---
+
+## 📊 Impact Summary
+
+### **Code Quality**
+- Reduced ESLint errors from 5 to 0
+- Cleaner codebase with proper unused parameter handling
+- Better TypeScript hygiene
+
+### **Documentation**
+- Centralized Tavus documentation navigation
+- Clearer documentation structure
+- Historical docs archived for reference
+- Easier onboarding for new developers
+
+### **Maintenance**
+- Easier to find relevant documentation
+- Clear separation of active vs. historical docs
+- Better organization for future feature development
+
+---
+
+## 🎯 Benefits for Next Feature
+
+**For Objective Completion Tracking Implementation**:
+1. ✅ Clean linting baseline (0 errors)
+2. ✅ Organized documentation structure
+3. ✅ Clear Tavus documentation index
+4. ✅ No unused components cluttering codebase
+5. ✅ Easy to reference existing Tavus implementations
+
+---
+
+## 📝 Cleanup Checklist
+
+- [x] Fix all linting errors
+- [x] Remove unused imports
+- [x] Prefix unused parameters with underscore
+- [x] Create documentation index
+- [x] Archive historical documentation
+- [x] Verify all components are in use
+- [x] Run full linting check
+- [x] Verify type-checking passes
+
+---
+
+## 🔍 Verification
+
+**Linting**:
+```bash
+npm run lint
+# Result: 0 errors, 3 warnings (acceptable)
+```
+
+**Type Checking**:
+```bash
+npm run type-check
+# Result: No errors
+```
+
+**Build**:
+```bash
+npm run build
+# Status: Not run (optional for cleanup)
+```
+
+---
+
+## 📚 Related Documentation
+
+- [TAVUS Index](./TAVUS_INDEX.md) - New central documentation hub
+- [Objective Completion Tracking Spec](../specs/features/learning-check/objective-completion-tracking.md) - Next feature to implement
+
+---
+
+## 🎉 Summary
+
+**Before Cleanup**:
+- 5 linting errors
+- 11 unorganized documentation files
+- Historical docs mixed with active docs
+- Unclear documentation navigation
+
+**After Cleanup**:
+- 0 linting errors ✅
+- Organized documentation with central index ✅
+- Historical docs archived ✅
+- Clear navigation structure ✅
+- Ready for new feature development ✅
+
+**Status**: Codebase is clean and ready for Objective Completion Tracking implementation!
+
+---
+
+**Cleanup Date**: October 31, 2025
+**Next Task**: Implement Learning Check Objective Completion Tracking webhook endpoint
diff --git a/docs/README.md b/docs/README.md
new file mode 100644
index 0000000..bbfb973
--- /dev/null
+++ b/docs/README.md
@@ -0,0 +1,34 @@
+# Documentation
+
+## 📚 Available Guides
+
+### **Tavus Integration**
+
+- **[TAVUS.md](./TAVUS.md)** - Complete developer guide
+ - Setup instructions
+ - Configuration (constants vs env vars)
+ - Creating personas
+ - Troubleshooting
+
+- **[TAVUS_API_REFERENCE.md](./TAVUS_API_REFERENCE.md)** - Complete API reference
+ - All Tavus API endpoints
+ - Request/response examples
+ - Layer configurations
+ - Quick reference
+
+---
+
+## 🚀 Quick Start
+
+1. **Setup Tavus**: Follow [TAVUS.md](./TAVUS.md#setup)
+2. **Create Assets**: Create objectives and guardrails
+3. **Create Persona**: Run `./scripts/create-persona.sh`
+4. **Start Coding**: Import from `@/lib/tavus`
+
+---
+
+## 💡 Need Help?
+
+- **Setup issues**: See [TAVUS.md - Troubleshooting](./TAVUS.md#troubleshooting)
+- **API questions**: See [TAVUS_API_REFERENCE.md](./TAVUS_API_REFERENCE.md)
+- **Architecture**: See `src/lib/tavus/README.md`
diff --git a/docs/TAVUS.md b/docs/TAVUS.md
new file mode 100644
index 0000000..b5f823c
--- /dev/null
+++ b/docs/TAVUS.md
@@ -0,0 +1,480 @@
+# Tavus Integration Guide
+
+Complete guide for integrating Tavus Conversational Video Interface (CVI) in the 8P3P LMS.
+
+**Quick Links:**
+- [Setup](#setup) - Get started quickly
+- [Configuration](#configuration) - Constants and environment variables
+- [Creating Personas](#creating-personas) - Persona creation workflow
+- [API Reference](./TAVUS_API_REFERENCE.md) - Complete API documentation
+
+---
+
+## 📚 Table of Contents
+
+1. [Overview](#overview)
+2. [Setup](#setup)
+3. [Configuration](#configuration)
+4. [Creating Personas](#creating-personas)
+5. [Troubleshooting](#troubleshooting)
+
+---
+
+## Overview
+
+### What is Tavus?
+
+Tavus provides AI-powered conversational video interfaces for real-time learning support. Our Learning Check feature uses:
+
+- **Objectives** - Structured assessment sequence (recall → application → self-explanation)
+- **Guardrails** - Behavioral boundaries (quiz protection, time management, scope)
+- **Personas** - AI instructor personality and behavior
+- **Context** - Chapter-specific information and learning objectives
+
+### Architecture
+
+```
+src/lib/tavus/
+├── config.ts # All Tavus configurations
+├── index.ts # Centralized exports
+└── README.md # Architecture documentation
+
+src/app/api/learning-checks/
+├── objectives/ # Create objectives
+├── guardrails/ # Create guardrails
+├── conversation/ # Start conversations
+└── update-persona/ # Update persona
+
+scripts/
+└── create-persona.sh # Persona creation script
+```
+
+---
+
+## Setup
+
+### 1. Environment Variables
+
+Copy `.env.example` to `.env.local` and configure:
+
+```bash
+# Required: Tavus API credentials
+TAVUS_API_KEY=your_tavus_api_key_here
+TAVUS_PERSONA_ID=your_persona_id_here
+
+# Optional: Webhook configuration
+TAVUS_WEBHOOK_SECRET=your_webhook_secret_here
+TAVUS_WEBHOOK_URL=https://your-domain.com/api/webhooks/tavus
+
+# Learning Check Assets (create these in step 3)
+NEXT_PUBLIC_TAVUS_LEARNING_CHECK_OBJECTIVES_ID=o_your_id
+NEXT_PUBLIC_TAVUS_LEARNING_CHECK_GUARDRAILS_ID=g_your_id
+```
+
+**Optional Overrides** (defaults in `src/lib/tavus/config.ts`):
+```bash
+# TAVUS_LEARNING_CHECK_DURATION=180 # Default: 180 seconds (3 min)
+# TAVUS_MAX_CONCURRENT_SESSIONS=10 # Default: 10
+```
+
+### 2. Start Development Server
+
+```bash
+npm run dev
+```
+
+Server should be running on `http://localhost:3000`
+
+### 3. Create Objectives and Guardrails
+
+**Option A: Using API Routes** (Recommended)
+
+```bash
+# Create objectives
+curl -X POST http://localhost:3000/api/learning-checks/objectives \
+ -H "Content-Type: application/json" \
+ -d '{}'
+
+# Response: {"objectives_id": "o_abc123"}
+
+# Create guardrails
+curl -X POST http://localhost:3000/api/learning-checks/guardrails \
+ -H "Content-Type: application/json" \
+ -d '{}'
+
+# Response: {"guardrails_id": "g_xyz789"}
+```
+
+**Option B: Using Tavus Platform**
+
+1. Go to https://platform.tavus.io/conversations/builder
+2. Create objectives and guardrails using visual builder
+3. Copy the IDs
+
+**Add IDs to `.env.local`:**
+```bash
+NEXT_PUBLIC_TAVUS_LEARNING_CHECK_OBJECTIVES_ID=o_abc123
+NEXT_PUBLIC_TAVUS_LEARNING_CHECK_GUARDRAILS_ID=g_xyz789
+```
+
+### 4. Restart Server
+
+```bash
+# Stop server (Ctrl+C) and restart
+npm run dev
+```
+
+✅ **Setup Complete!** Your Learning Check feature is now configured.
+
+---
+
+## Configuration
+
+### Constants vs Environment Variables
+
+**Rule of Thumb:**
+- **Constants** → Same across all environments (in code)
+- **Environment Variables** → Different per environment or secrets (in `.env.local`)
+
+### Constants (`src/lib/tavus/config.ts`)
+
+Application-level values that don't change:
+
+```typescript
+import { TAVUS_DEFAULTS } from '@/lib/tavus';
+
+TAVUS_DEFAULTS.API_BASE_URL // "https://tavusapi.com/v2"
+TAVUS_DEFAULTS.LEARNING_CHECK_DURATION // 180 seconds (3 minutes)
+TAVUS_DEFAULTS.MAX_CONCURRENT_SESSIONS // 10
+TAVUS_DEFAULTS.ENGAGEMENT_THRESHOLD // 90
+TAVUS_DEFAULTS.CONVERSATION_TIMEOUT // 60 seconds
+TAVUS_DEFAULTS.TEST_MODE // false
+```
+
+### Environment Variables (Type-Safe Access)
+
+Use `TAVUS_ENV` helpers instead of `process.env`:
+
+```typescript
+import { TAVUS_ENV, TAVUS_DEFAULTS } from '@/lib/tavus';
+
+// ✅ Type-safe with fallback to default
+const duration = TAVUS_ENV.getLearningCheckDuration();
+// Returns: env value OR TAVUS_DEFAULTS.LEARNING_CHECK_DURATION
+
+// ✅ Type-safe secret access
+const apiKey = TAVUS_ENV.getApiKey();
+// Returns: string | undefined
+
+// ✅ All available helpers
+TAVUS_ENV.getApiKey() // API key
+TAVUS_ENV.getPersonaId() // Persona ID
+TAVUS_ENV.getLearningCheckDuration() // Duration with fallback
+TAVUS_ENV.getMaxConcurrentSessions() // Max sessions with fallback
+TAVUS_ENV.getWebhookSecret() // Webhook secret
+TAVUS_ENV.getWebhookUrl() // Webhook URL
+TAVUS_ENV.getObjectivesId() // Objectives ID
+TAVUS_ENV.getGuardrailsId() // Guardrails ID
+```
+
+### Usage in API Routes
+
+```typescript
+import { TAVUS_ENV, TAVUS_DEFAULTS } from '@/lib/tavus';
+
+export async function POST(request: NextRequest) {
+ // Get secrets (required)
+ const apiKey = TAVUS_ENV.getApiKey();
+ const personaId = TAVUS_ENV.getPersonaId();
+
+ if (!apiKey || !personaId) {
+ return NextResponse.json(
+ { error: 'Missing Tavus configuration' },
+ { status: 500 }
+ );
+ }
+
+ // Use constants
+ const apiUrl = TAVUS_DEFAULTS.API_BASE_URL;
+ const testMode = TAVUS_DEFAULTS.TEST_MODE;
+
+ // Get optional values with fallbacks
+ const duration = TAVUS_ENV.getLearningCheckDuration();
+
+ const response = await fetch(`${apiUrl}/conversations`, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ 'x-api-key': apiKey,
+ },
+ body: JSON.stringify({
+ persona_id: personaId,
+ test_mode: testMode,
+ max_call_duration: duration,
+ }),
+ });
+}
+```
+
+### What Goes Where?
+
+| Value | Constant? | Env Var? | Why |
+|-------|-----------|----------|-----|
+| API Base URL | ✅ | ❌ | Same for everyone |
+| Session Duration | ✅ | ✅ (optional) | Default constant, can override |
+| API Key | ❌ | ✅ | Secret, different per env |
+| Persona ID | ❌ | ✅ | Different per env |
+| Test Mode | ✅ | ❌ | Application-level setting |
+| Webhook URL | ❌ | ✅ | Different per deployment |
+
+---
+
+## Creating Personas
+
+### Quick Start
+
+```bash
+# Make script executable (first time only)
+chmod +x scripts/create-persona.sh
+
+# Create persona (reads objectives/guardrails from .env.local)
+./scripts/create-persona.sh
+
+# Or specify IDs manually
+./scripts/create-persona.sh \
+ --objectives-id o_abc123 \
+ --guardrails-id g_xyz789
+```
+
+### What Gets Created
+
+The script creates a persona with:
+
+#### **Core Settings**
+- **Name**: "8p3p - AI Instructor Assistant"
+- **Pipeline Mode**: Full (recommended)
+- **System Prompt**: Knowledgeable, supportive tutor personality
+- **Context**: 3-minute learning check conversation guidelines
+
+#### **Layers Configuration**
+
+**Perception (Raven-0)**
+- Ambient awareness queries for engagement tracking
+- Visual analysis of learner attention and comprehension
+
+**STT (Tavus-advanced)**
+- High pause sensitivity
+- Medium interrupt sensitivity
+- Smart turn detection
+
+**LLM (Tavus-llama)**
+- Optimized for conversations
+- Speculative inference enabled
+
+**TTS (Sonic-2)**
+- Natural voice synthesis
+- Emotion control
+
+### Complete Workflow
+
+#### For a Brand New Account
+
+**1. Create Objectives and Guardrails First**
+```bash
+npm run dev
+
+# Create objectives
+curl -X POST http://localhost:3000/api/learning-checks/objectives \
+ -H "Content-Type: application/json" \
+ -d '{}'
+
+# Create guardrails
+curl -X POST http://localhost:3000/api/learning-checks/guardrails \
+ -H "Content-Type: application/json" \
+ -d '{}'
+
+# Add IDs to .env.local
+```
+
+**2. Create Persona**
+```bash
+# Script automatically reads IDs from .env.local
+./scripts/create-persona.sh
+
+# Or specify manually
+./scripts/create-persona.sh \
+ --objectives-id o_abc123 \
+ --guardrails-id g_xyz789
+```
+
+**3. Update Environment**
+```bash
+# Add persona ID to .env.local
+TAVUS_PERSONA_ID=p_new_persona_id
+```
+
+**4. Restart Server**
+```bash
+npm run dev
+```
+
+### Updating an Existing Persona
+
+If you already have a persona and want to update it with objectives/guardrails:
+
+```bash
+# Use the update-persona API route
+curl -X PATCH http://localhost:3000/api/learning-checks/update-persona \
+ -H "Content-Type: application/json" \
+ -d '{
+ "persona_id": "p_your_persona_id",
+ "objectives_id": "o_your_objectives_id",
+ "guardrails_id": "g_your_guardrails_id"
+ }'
+```
+
+### Saved Configuration
+
+After creation, the script saves the full response to:
+```
+docs/persona-created-YYYYMMDD-HHMMSS.json
+```
+
+This file contains the complete persona configuration for reference.
+
+---
+
+## Troubleshooting
+
+### Missing API Key
+
+```bash
+# Check your API key
+echo $TAVUS_API_KEY
+
+# Or check .env.local
+grep TAVUS_API_KEY .env.local
+```
+
+**Solution**: Add `TAVUS_API_KEY` to `.env.local`
+
+### Persona Created but Missing Objectives/Guardrails
+
+```bash
+# Create objectives and guardrails
+curl -X POST http://localhost:3000/api/learning-checks/objectives \
+ -H "Content-Type: application/json" \
+ -d '{}'
+
+curl -X POST http://localhost:3000/api/learning-checks/guardrails \
+ -H "Content-Type: application/json" \
+ -d '{}'
+
+# Update the persona
+curl -X PATCH http://localhost:3000/api/learning-checks/update-persona \
+ -H "Content-Type: application/json" \
+ -d '{
+ "persona_id": "p_your_id",
+ "objectives_id": "o_your_id",
+ "guardrails_id": "g_your_id"
+ }'
+```
+
+### Need to Recreate Persona
+
+```bash
+# Just run the script again
+./scripts/create-persona.sh
+
+# It creates a NEW persona (doesn't update existing)
+# Update TAVUS_PERSONA_ID in .env.local with the new ID
+```
+
+### Script Permission Denied
+
+```bash
+chmod +x scripts/create-persona.sh
+```
+
+### Environment Variables Not Loading
+
+```bash
+# Restart your development server
+npm run dev
+```
+
+Environment variables are loaded at server startup.
+
+### Conversation Not Starting
+
+**Check:**
+1. ✅ `TAVUS_API_KEY` is set
+2. ✅ `TAVUS_PERSONA_ID` is set
+3. ✅ `NEXT_PUBLIC_TAVUS_LEARNING_CHECK_OBJECTIVES_ID` is set
+4. ✅ `NEXT_PUBLIC_TAVUS_LEARNING_CHECK_GUARDRAILS_ID` is set
+5. ✅ Server is running on `http://localhost:3000`
+
+**Verify in console:**
+```bash
+# Check all Tavus env vars
+grep TAVUS .env.local
+```
+
+---
+
+## 📚 Additional Resources
+
+- **API Reference**: [TAVUS_API_REFERENCE.md](./TAVUS_API_REFERENCE.md)
+- **Architecture**: `src/lib/tavus/README.md`
+- **Tavus Docs**: https://docs.tavus.io
+- **Tavus Platform**: https://platform.tavus.io
+
+---
+
+## 🎯 Quick Reference
+
+### Common Commands
+
+```bash
+# Create objectives
+curl -X POST http://localhost:3000/api/learning-checks/objectives \
+ -H "Content-Type: application/json" -d '{}'
+
+# Create guardrails
+curl -X POST http://localhost:3000/api/learning-checks/guardrails \
+ -H "Content-Type: application/json" -d '{}'
+
+# Create persona
+./scripts/create-persona.sh
+
+# Update persona
+curl -X PATCH http://localhost:3000/api/learning-checks/update-persona \
+ -H "Content-Type: application/json" \
+ -d '{"persona_id": "p_id", "objectives_id": "o_id", "guardrails_id": "g_id"}'
+```
+
+### Import Examples
+
+```typescript
+// Import configurations
+import {
+ LEARNING_CHECK_OBJECTIVES,
+ LEARNING_CHECK_GUARDRAILS,
+ PERSONA_CONFIG,
+ buildChapterContext,
+ buildGreeting,
+ TAVUS_DEFAULTS,
+ TAVUS_ENV,
+} from '@/lib/tavus';
+
+// Use in code
+const apiKey = TAVUS_ENV.getApiKey();
+const duration = TAVUS_ENV.getLearningCheckDuration();
+const apiUrl = TAVUS_DEFAULTS.API_BASE_URL;
+const context = buildChapterContext('ch1', 'Introduction to EMDR');
+const greeting = buildGreeting('Introduction to EMDR');
+```
+
+---
+
+**Last Updated**: October 30, 2025
diff --git a/docs/TAVUS_API_REFERENCE.md b/docs/TAVUS_API_REFERENCE.md
new file mode 100644
index 0000000..b89c0e1
--- /dev/null
+++ b/docs/TAVUS_API_REFERENCE.md
@@ -0,0 +1,606 @@
+# Tavus API Reference
+
+Complete reference documentation for Tavus Conversational Video Interface (CVI) APIs.
+
+**Source**: https://docs.tavus.io/llms-full.txt
+**Last Updated**: October 30, 2025
+
+---
+
+## 📚 **Table of Contents**
+
+1. [Personas](#personas)
+2. [Objectives](#objectives)
+3. [Guardrails](#guardrails)
+4. [Conversations](#conversations)
+5. [Replicas](#replicas)
+6. [Documents (Knowledge Base)](#documents)
+7. [Layers Configuration](#layers-configuration)
+
+---
+
+## 🎭 **Personas**
+
+### **Create Persona**
+```http
+POST /v2/personas
+```
+
+Creates and customizes a digital replica's behavior and capabilities for CVI.
+
+**Core Components:**
+- **Replica** - Audio/visual appearance
+- **Context** - Contextual information for LLM
+- **System Prompt** - System-level instructions for LLM
+- **Layers** - Perception, STT, LLM, TTS configuration
+
+**Required Fields** (full pipeline mode):
+- `system_prompt` - Required for full pipeline mode
+
+**Example:**
+```bash
+curl --request POST \
+ --url https://tavusapi.com/v2/personas \
+ --header 'Content-Type: application/json' \
+ --header 'x-api-key: ' \
+ --data '{
+ "persona_name": "AI Instructor",
+ "system_prompt": "You are a knowledgeable tutor...",
+ "context": "You are having a conversation with a student...",
+ "default_replica_id": "r12345",
+ "objectives_id": "o12345",
+ "guardrails_id": "g12345",
+ "pipeline_mode": "full"
+ }'
+```
+
+---
+
+### **Patch Persona**
+```http
+PATCH /v2/personas/{persona_id}
+```
+
+Updates a persona using JSON Patch (RFC 6902).
+
+**Supported Operations:**
+- `add` - Add new field
+- `remove` - Remove field
+- `replace` - Replace field value
+- `copy` - Copy field value
+- `move` - Move field
+- `test` - Test field value
+
+**Example:**
+```bash
+curl --request PATCH \
+ --url https://tavusapi.com/v2/personas/{persona_id} \
+ --header 'Content-Type: application/json' \
+ --header 'x-api-key: ' \
+ --data '[
+ {"op": "replace", "path": "/system_prompt", "value": "Updated prompt"},
+ {"op": "replace", "path": "/context", "value": "Updated context"},
+ {"op": "add", "path": "/objectives_id", "value": "o12345"},
+ {"op": "add", "path": "/guardrails_id", "value": "g12345"}
+ ]'
+```
+
+**Important Notes:**
+- Ensure `path` matches current persona schema
+- For `remove` operation, `value` parameter not required
+- Can modify **any field** within the persona
+
+---
+
+### **Get Persona**
+```http
+GET /v2/personas/{persona_id}
+```
+
+Returns a single persona by its unique identifier.
+
+---
+
+### **List Personas**
+```http
+GET /v2/personas
+```
+
+Returns all personas created by the account.
+
+---
+
+### **Delete Persona**
+```http
+DELETE /v2/personas/{persona_id}
+```
+
+Deletes a persona by its unique identifier.
+
+---
+
+## 🎯 **Objectives**
+
+Objectives are goal-oriented instructions that define desired outcomes and conversation flow.
+
+### **Key Concepts**
+- Work alongside system prompt for structured conversations
+- Best for purposeful conversations (sales, education, customer journeys)
+- Provide flexible approach while maintaining natural interactions
+
+### **Create Objectives**
+```http
+POST /v2/objectives
+```
+
+**Example:**
+```bash
+curl --request POST \
+ --url https://tavusapi.com/v2/objectives \
+ --header 'Content-Type: application/json' \
+ --header 'x-api-key: ' \
+ --data '{
+ "data": [
+ {
+ "objective_name": "recall_assessment",
+ "objective_prompt": "Ask recall questions about key concepts",
+ "confirmation_mode": "auto",
+ "modality": "verbal",
+ "next_required_objectives": ["application_assessment"]
+ }
+ ]
+ }'
+```
+
+### **Parameters**
+- `objective_name` - Unique identifier for the objective
+- `objective_prompt` - Instructions for the objective
+- `confirmation_mode` - `auto` or `manual`
+- `modality` - `verbal` or `visual`
+- `next_required_objectives` - Array of next objectives
+- `next_conditional_objectives` - Conditional branching
+- `output_variables` - Optional variables to extract
+- `callback_url` - Optional webhook URL
+
+### **Attaching to Persona**
+
+**During Creation:**
+```bash
+curl --request POST \
+ --url https://tavusapi.com/v2/personas \
+ --data '{"objectives_id": "o12345"}'
+```
+
+**By Editing:**
+```bash
+curl --request PATCH \
+ --url https://tavusapi.com/v2/personas/{persona_id} \
+ --data '[{"op": "add", "path": "/objectives_id", "value": "o12345"}]'
+```
+
+### **Best Practices**
+- ✅ Plan entire workflow before creating objectives
+- ✅ Think through possible participant answers
+- ✅ Ensure system prompt doesn't conflict with objectives
+- ✅ Create branching structure for different paths
+
+---
+
+## 🛡️ **Guardrails**
+
+Guardrails provide strict behavioral guidelines enforced throughout conversations.
+
+### **Key Concepts**
+- Act as safety layer alongside system prompt
+- Enforce rules, restrictions, and behavioral patterns
+- Prevent unwanted topics or responses
+- Complement persona's intended functionality
+
+### **Create Guardrails**
+```http
+POST /v2/guardrails
+```
+
+**Example:**
+```bash
+curl --request POST \
+ --url https://tavusapi.com/v2/guardrails \
+ --header 'Content-Type: application/json' \
+ --header 'x-api-key: ' \
+ --data '{
+ "name": "Learning Check Guardrails",
+ "data": [
+ {
+ "guardrail_name": "quiz_protection",
+ "guardrail_prompt": "Never reveal quiz answers",
+ "modality": "verbal"
+ },
+ {
+ "guardrail_name": "time_management",
+ "guardrail_prompt": "Keep responses brief (1-2 sentences)",
+ "modality": "verbal"
+ }
+ ]
+ }'
+```
+
+### **Parameters**
+- `guardrails_name` - Unique identifier
+- `guardrails_prompt` - Strict behavioral rule
+- `modality` - `verbal` or `visual`
+- `callback_url` - Optional webhook URL
+
+### **Attaching to Persona**
+
+**During Creation:**
+```bash
+curl --request POST \
+ --url https://tavusapi.com/v2/personas \
+ --data '{"guardrails_id": "g12345"}'
+```
+
+**By Editing:**
+```bash
+curl --request PATCH \
+ --url https://tavusapi.com/v2/personas/{persona_id} \
+ --data '[{"op": "add", "path": "/guardrails_id", "value": "g12345"}]'
+```
+
+### **Best Practices**
+- ✅ Be specific about restricted topics/behaviors
+- ✅ Consider edge cases and creative prompting
+- ✅ Ensure guardrails complement system prompt
+- ✅ Test with various scenarios
+- ✅ Create specific guardrails for different contexts
+
+---
+
+## 💬 **Conversations**
+
+### **Create Conversation**
+```http
+POST /v2/conversations
+```
+
+**Example:**
+```bash
+curl --request POST \
+ --url https://tavusapi.com/v2/conversations \
+ --header 'Content-Type: application/json' \
+ --header 'x-api-key: ' \
+ --data '{
+ "persona_id": "p12345",
+ "conversational_context": "Chapter-specific context...",
+ "custom_greeting": "Hi! Ready to discuss this chapter?",
+ "conversation_name": "Learning Check: Chapter 1",
+ "objectives_id": "o12345",
+ "guardrails_id": "g12345",
+ "test_mode": true
+ }'
+```
+
+**Response:**
+```json
+{
+ "conversation_url": "https://...",
+ "conversation_id": "c12345",
+ "expires_at": "2025-10-30T12:00:00Z"
+}
+```
+
+---
+
+### **End Conversation**
+```http
+POST /v2/conversations/{conversation_id}/end
+```
+
+Terminates an active conversation.
+
+---
+
+### **Get Conversation**
+```http
+GET /v2/conversations/{conversation_id}
+```
+
+Returns conversation details and status.
+
+---
+
+### **List Conversations**
+```http
+GET /v2/conversations
+```
+
+Returns all conversations for the account.
+
+---
+
+### **Delete Conversation**
+```http
+DELETE /v2/conversations/{conversation_id}
+```
+
+Deletes a conversation by ID.
+
+---
+
+## 🎨 **Replicas**
+
+### **Create Replica**
+```http
+POST /v2/replicas
+```
+
+Creates a new replica using `phoenix-3` model (default).
+
+**Required Parameters:**
+
+**Personal Replica:**
+- `train_video_url` - Publicly accessible URL
+- `consent_video_url` - Publicly accessible URL
+
+**Non-Human Replica:**
+- `train_video_url` - Publicly accessible URL
+
+**Example:**
+```bash
+curl --request POST \
+ --url https://tavusapi.com/v2/replicas \
+ --header 'Content-Type: application/json' \
+ --header 'x-api-key: ' \
+ --data '{
+ "replica_name": "AI Instructor",
+ "train_video_url": "https://...",
+ "consent_video_url": "https://...",
+ "model_name": "phoenix-3"
+ }'
+```
+
+---
+
+### **Get Replica**
+```http
+GET /v2/replicas/{replica_id}
+```
+
+Returns replica details including `training_progress` and `status`.
+
+---
+
+### **List Replicas**
+```http
+GET /v2/replicas
+```
+
+Returns all replicas for the account.
+
+---
+
+### **Delete Replica**
+```http
+DELETE /v2/replicas/{replica_id}
+```
+
+Deletes a replica (cannot be used in conversations after deletion).
+
+---
+
+## 📚 **Documents (Knowledge Base)**
+
+### **Create Document**
+```http
+POST /v2/documents
+```
+
+Adds documents to the knowledge base for persona reference.
+
+---
+
+### **Get Document**
+```http
+GET /v2/documents/{document_id}
+```
+
+Returns document details.
+
+---
+
+### **List Documents**
+```http
+GET /v2/documents
+```
+
+Returns all documents.
+
+---
+
+### **Update Document**
+```http
+PATCH /v2/documents/{document_id}
+```
+
+Updates document content or metadata.
+
+---
+
+### **Delete Document**
+```http
+DELETE /v2/documents/{document_id}
+```
+
+Removes document from knowledge base.
+
+---
+
+## ⚙️ **Layers Configuration**
+
+### **Perception Layer (Raven)**
+
+Multimodal vision and understanding.
+
+```json
+{
+ "perception": {
+ "perception_model": "raven-0",
+ "ambient_awareness_queries": [
+ "How engaged is the learner?",
+ "Are there signs of confusion?"
+ ],
+ "perception_analysis_queries": [],
+ "perception_tool_prompt": "",
+ "perception_tools": []
+ }
+}
+```
+
+**Parameters:**
+- `perception_model` - Model version (e.g., `raven-0`)
+- `ambient_awareness_queries` - Real-time visual analysis questions
+- `perception_analysis_queries` - End-of-call analysis questions
+- `perception_tool_prompt` - Tool usage instructions
+- `perception_tools` - Array of tool definitions
+
+---
+
+### **STT Layer (Sparrow)**
+
+Speech-to-text and turn-taking.
+
+```json
+{
+ "stt": {
+ "stt_engine": "tavus-advanced",
+ "participant_pause_sensitivity": "high",
+ "participant_interrupt_sensitivity": "medium",
+ "smart_turn_detection": true,
+ "hotwords": ""
+ }
+}
+```
+
+**Parameters:**
+- `stt_engine` - Engine choice (`tavus-advanced`, etc.)
+- `participant_pause_sensitivity` - `low`, `medium`, `high`
+- `participant_interrupt_sensitivity` - `low`, `medium`, `high`
+- `smart_turn_detection` - Boolean for turn-taking model
+- `hotwords` - Comma-separated keywords for better recognition
+
+---
+
+### **LLM Layer**
+
+Language model configuration.
+
+```json
+{
+ "llm": {
+ "model": "tavus-llama",
+ "speculative_inference": true,
+ "tools": [],
+ "headers": {},
+ "extra_body": {},
+ "base_url": "",
+ "api_key": ""
+ }
+}
+```
+
+**Tavus-Hosted Models:**
+- `tavus-llama` - Default, optimized for conversations
+- `tavus-gpt-4o` - GPT-4 powered
+
+**Custom LLM:**
+- Set `base_url`, `api_key`, and `model` for external LLMs
+
+**Parameters:**
+- `model` - Model identifier
+- `speculative_inference` - Boolean for faster responses
+- `tools` - Array of tool definitions
+- `base_url` - Custom LLM endpoint (optional)
+- `api_key` - Custom LLM API key (optional)
+
+---
+
+### **TTS Layer**
+
+Text-to-speech configuration.
+
+```json
+{
+ "tts": {
+ "tts_model_name": "sonic-2",
+ "tts_engine": "",
+ "api_key": "",
+ "external_voice_id": "",
+ "tts_emotion_control": null,
+ "voice_settings": {}
+ }
+}
+```
+
+**Parameters:**
+- `tts_model_name` - Model version (e.g., `sonic-2`)
+- `tts_engine` - Engine choice (optional)
+- `external_voice_id` - Custom voice ID (optional)
+- `tts_emotion_control` - Boolean for emotion control
+- `voice_settings` - Custom voice parameters
+
+---
+
+## 🔗 **Related Documentation**
+
+- **Tavus Full Docs**: https://docs.tavus.io/llms-full.txt
+- **Developer Guide**: `docs/TAVUS.md`
+- **Our Config**: `src/lib/tavus/config.ts`
+- **Architecture**: `src/lib/tavus/README.md`
+
+---
+
+## 💡 **Quick Reference**
+
+### **Common Operations**
+
+**Create Complete Persona:**
+```bash
+# 1. Create objectives
+POST /v2/objectives
+
+# 2. Create guardrails
+POST /v2/guardrails
+
+# 3. Create persona with IDs
+POST /v2/personas
+{
+ "objectives_id": "o12345",
+ "guardrails_id": "g12345"
+}
+```
+
+**Update Existing Persona:**
+```bash
+PATCH /v2/personas/{persona_id}
+[
+ {"op": "add", "path": "/objectives_id", "value": "o12345"},
+ {"op": "add", "path": "/guardrails_id", "value": "g12345"}
+]
+```
+
+**Start Conversation:**
+```bash
+POST /v2/conversations
+{
+ "persona_id": "p12345",
+ "conversational_context": "...",
+ "custom_greeting": "..."
+}
+```
+
+---
+
+## 📞 **Support**
+
+- **Documentation**: https://docs.tavus.io
+- **API Reference**: https://docs.tavus.io/api-reference
+- **Platform**: https://platform.tavus.io
diff --git a/docs/TAVUS_CONFIG_UPDATE_GUIDE.md b/docs/TAVUS_CONFIG_UPDATE_GUIDE.md
new file mode 100644
index 0000000..88795e2
--- /dev/null
+++ b/docs/TAVUS_CONFIG_UPDATE_GUIDE.md
@@ -0,0 +1,245 @@
+# Tavus Configuration Update Guide
+
+**Single Source of Truth**: Edit `src/lib/tavus/config.ts` → Run script → Done! ✨
+
+The scripts automatically extract your configuration from TypeScript and sync it to Tavus.
+
+---
+
+## 📋 Prerequisites
+
+Ensure your `.env.local` has the following:
+
+```bash
+TAVUS_API_KEY=your_api_key_here
+NEXT_PUBLIC_TAVUS_LEARNING_CHECK_GUARDRAILS_ID=your_guardrails_id
+NEXT_PUBLIC_TAVUS_LEARNING_CHECK_OBJECTIVES_ID=your_objectives_id
+```
+
+---
+
+## 🚀 Quick Start
+
+### Update Everything (Recommended)
+
+After editing both guardrails and objectives in `config.ts`:
+
+```bash
+./scripts/update-tavus-config.sh
+```
+
+This will update both guardrails and objectives in sequence.
+
+---
+
+## 📝 Individual Updates
+
+### Update Guardrails Only
+
+1. Edit `LEARNING_CHECK_GUARDRAILS` in `src/lib/tavus/config.ts`
+2. Run the update script:
+
+```bash
+./scripts/update-tavus-guardrails.sh
+```
+
+### Update Objectives Only
+
+1. Edit `LEARNING_CHECK_OBJECTIVES` in `src/lib/tavus/config.ts`
+2. Run the update script:
+
+```bash
+./scripts/update-tavus-objectives.sh
+```
+
+---
+
+## 🔧 How It Works
+
+The scripts use a Node.js helper (`extract-tavus-config.mjs`) that:
+
+1. **Reads** `src/lib/tavus/config.ts`
+2. **Parses** the TypeScript constants (`LEARNING_CHECK_OBJECTIVES` and `LEARNING_CHECK_GUARDRAILS`)
+3. **Extracts** the `data` arrays
+4. **Converts** to JSON
+5. **Sends** to Tavus API via PATCH request
+
+**You never edit the scripts** - they automatically stay in sync with your `config.ts` file!
+
+---
+
+## 🎯 What Gets Updated
+
+### Guardrails
+
+The script updates all 4 guardrails:
+- ✅ `quiz_answer_protection` - Prevents revealing quiz answers
+- ✅ `time_management` - Enforces brief responses
+- ✅ `content_scope` - Keeps conversation on chapter topics
+- ✅ `encouraging_tone` - Maintains supportive tone
+
+### Objectives
+
+The script updates all 3 objectives in the assessment flow:
+- ✅ `recall_assessment` - Tests memory of fundamentals
+- ✅ `application_assessment` - Tests application in scenarios
+- ✅ `self_explanation_assessment` - Tests deeper understanding
+
+---
+
+## 🔍 Verification
+
+After running the update scripts, you'll see:
+
+```bash
+✅ Guardrails updated successfully!
+✅ Objectives updated successfully!
+```
+
+The scripts will also display the Tavus API response for verification.
+
+---
+
+## ⚠️ Troubleshooting
+
+### Error: "TAVUS_API_KEY not found"
+
+**Solution**: Add `TAVUS_API_KEY` to your `.env.local` file.
+
+### Error: "GUARDRAILS_ID not found"
+
+**Solution**: Add `NEXT_PUBLIC_TAVUS_LEARNING_CHECK_GUARDRAILS_ID` to `.env.local`.
+
+### Error: "OBJECTIVES_ID not found"
+
+**Solution**: Add `NEXT_PUBLIC_TAVUS_LEARNING_CHECK_OBJECTIVES_ID` to `.env.local`.
+
+### HTTP Status 400 or 404
+
+**Problem**: Invalid guardrails/objectives ID or resource doesn't exist.
+
+**Solution**:
+1. Verify the IDs in `.env.local` match your Tavus dashboard
+2. Create the resources first if they don't exist (see [TAVUS_SETUP.md](./TAVUS_SETUP.md))
+
+### HTTP Status 401
+
+**Problem**: Invalid API key.
+
+**Solution**: Verify your `TAVUS_API_KEY` is correct in `.env.local`.
+
+---
+
+## 🔄 Workflow
+
+### Typical Development Flow
+
+```bash
+# 1. Edit guardrails/objectives in config.ts
+vim src/lib/tavus/config.ts
+
+# 2. Update Tavus with new configuration
+./scripts/update-tavus-config.sh
+
+# 3. Test in development
+npm run dev
+# Navigate to a learning check section
+
+# 4. Verify the AI behavior reflects your changes
+```
+
+---
+
+## 📚 Configuration Reference
+
+### Guardrail Structure
+
+```typescript
+{
+ guardrail_name: "unique_name",
+ guardrail_prompt: "The strict rule to enforce",
+ modality: "verbal" | "visual"
+}
+```
+
+### Objective Structure
+
+```typescript
+{
+ objective_name: "unique_name",
+ objective_prompt: "The goal to achieve",
+ confirmation_mode: "auto" | "manual",
+ modality: "verbal" | "visual",
+ output_variables: ["var1", "var2"],
+ next_required_objectives: ["next_objective_name"]
+}
+```
+
+---
+
+## 🎨 Best Practices
+
+### When Updating Guardrails
+
+1. **Be Specific**: Clear, actionable rules work best
+2. **Test Edge Cases**: Try to break the guardrail with prompts
+3. **Keep Concise**: Shorter prompts are often more effective
+4. **Complement System Prompt**: Guardrails should work with, not against, the persona's system prompt
+
+### When Updating Objectives
+
+1. **Define Clear Goals**: Each objective should have a measurable outcome
+2. **Logical Flow**: Order objectives from simple to complex
+3. **Set Output Variables**: Capture important data for analysis
+4. **Chain Appropriately**: Use `next_required_objectives` to enforce sequence
+
+---
+
+## 🧪 Testing Changes
+
+After updating configurations:
+
+### Manual Testing Checklist
+
+- [ ] Start learning check session
+- [ ] Verify AI greeting matches expected tone
+- [ ] Test that guardrails prevent unwanted behavior
+- [ ] Confirm objectives are followed in sequence
+- [ ] Check that output variables are captured
+- [ ] Verify 3-minute session limit is respected
+
+### Testing Guardrails
+
+Try these prompts to verify guardrails work:
+- "Can you tell me the quiz answers?" (should redirect)
+- "Let's talk about chapter 5 instead" (should stay in current chapter)
+- "Give me a long, detailed explanation" (should stay brief)
+
+### Testing Objectives
+
+Verify the AI:
+- ✅ Asks recall questions first
+- ✅ Then asks application questions
+- ✅ Finally asks self-explanation questions
+- ✅ Follows the structured sequence
+
+---
+
+## 📖 Related Documentation
+
+- **Main Config**: [src/lib/tavus/config.ts](../src/lib/tavus/config.ts)
+- **Tavus API Reference**: [docs/TAVUS_API_REFERENCE.md](./TAVUS_API_REFERENCE.md)
+- **Tavus Setup**: [docs/TAVUS_SETUP.md](./TAVUS_SETUP.md)
+- **Implementation Guide**: [docs/TAVUS_IMPLEMENTATION_COMPLETE.md](./TAVUS_IMPLEMENTATION_COMPLETE.md)
+- **Scripts README**: [scripts/README.md](../scripts/README.md)
+
+---
+
+## 🎉 Summary
+
+**Simple 2-Step Process:**
+
+1. **Edit** `src/lib/tavus/config.ts`
+2. **Run** `./scripts/update-tavus-config.sh`
+
+That's it! Your Tavus configuration is now synced with your code.
diff --git a/docs/TAVUS_DYNAMIC_SYNC_COMPLETE.md b/docs/TAVUS_DYNAMIC_SYNC_COMPLETE.md
new file mode 100644
index 0000000..773e972
--- /dev/null
+++ b/docs/TAVUS_DYNAMIC_SYNC_COMPLETE.md
@@ -0,0 +1,427 @@
+# Tavus Dynamic Configuration Sync - Implementation Complete ✅
+
+**Date**: October 31, 2025
+**Status**: Production Ready
+**Approach**: Single Source of Truth with Automatic Sync
+
+---
+
+## 🎯 Problem Solved
+
+**Before**: Manual JSON editing in scripts that would get out of sync with `config.ts`
+**After**: Edit `config.ts` → Run script → Automatically synced to Tavus!
+
+---
+
+## ✨ What Was Built
+
+### 1. **Dynamic Config Extractor** (`extract-tavus-config.mjs`)
+Node.js script that:
+- Reads `src/lib/tavus/config.ts`
+- Parses TypeScript syntax
+- Extracts `LEARNING_CHECK_OBJECTIVES.data` and `LEARNING_CHECK_GUARDRAILS.data`
+- Outputs clean JSON for API consumption
+
+### 2. **Smart Update Scripts**
+Three bash scripts that automatically sync your config:
+- `update-tavus-objectives.sh` - Syncs objectives
+- `update-tavus-guardrails.sh` - Syncs guardrails
+- `update-tavus-config.sh` - Syncs both (recommended)
+
+### 3. **macOS Compatibility**
+Fixed all scripts to work on macOS by:
+- Replacing `head -n -1` with `sed '$d'` (BSD compatible)
+- Using proper bash variable handling
+- Testing on macOS environment
+
+---
+
+## 🚀 How to Use
+
+### Simple Workflow
+
+```bash
+# 1. Edit your configuration
+vim src/lib/tavus/config.ts
+
+# 2. Update Tavus
+./scripts/update-tavus-config.sh
+
+# That's it! ✨
+```
+
+### What It Does
+
+```
+src/lib/tavus/config.ts
+ ↓
+extract-tavus-config.mjs (parses TypeScript)
+ ↓
+update-tavus-*.sh (sends to Tavus API)
+ ↓
+Tavus API Updated ✅
+```
+
+---
+
+## 📊 Example Output
+
+```bash
+╔═══════════════════════════════════════════╗
+║ Tavus Configuration Update (All) ║
+╚═══════════════════════════════════════════╝
+
+━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+🛡️ Tavus Guardrails Update Script
+
+Guardrails ID: g7771e9a453db
+
+📖 Reading guardrails from src/lib/tavus/config.ts...
+✓ Guardrails loaded successfully
+
+📤 Sending PATCH request to Tavus...
+
+✅ Guardrails updated successfully!
+
+━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+🎯 Tavus Objectives Update Script
+
+Objectives ID: o5d8f3a912cd
+
+📖 Reading objectives from src/lib/tavus/config.ts...
+✓ Objectives loaded successfully
+
+📤 Sending PATCH request to Tavus...
+
+✅ Objectives updated successfully!
+
+━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+✨ All Tavus configurations updated successfully!
+```
+
+---
+
+## 🔧 Technical Implementation
+
+### Parser Logic
+
+```javascript
+// Reads TypeScript file
+const configContent = readFileSync('src/lib/tavus/config.ts', 'utf-8');
+
+// Extracts constant using regex
+const regex = /export const LEARNING_CHECK_OBJECTIVES\s*=\s*({[\s\S]*?^};)/m;
+const match = content.match(regex);
+
+// Parses to JavaScript object
+const objectives = new Function(`return ${objectivesStr}`)();
+
+// Outputs data array as JSON
+console.log(JSON.stringify(objectives.data, null, 2));
+```
+
+### API Integration
+
+```bash
+# Extract from TypeScript
+OBJECTIVES_DATA=$(node extract-tavus-config.mjs objectives)
+
+# Create JSON Patch payload
+PATCH_DATA=$(cat < {
+ setLoading(true);
+
+ // Create conversation via API
+ const response = await fetch("/api/learning-checks/conversation", {
+ method: "POST",
+ body: JSON.stringify({ chapterId, chapterTitle })
+ });
+
+ const data = await response.json();
+ // data = { conversationUrl, conversationId, expiresAt }
+
+ setConversation(data);
+ setScreen("call"); // Switch to Conversation component
+ setLoading(false);
+};
+
+// Conversation component auto-joins when conversationUrl is provided
+
+```
+
+---
+
+## 🔍 Implementation Details
+
+### API Request Structure
+
+**Create Conversation:**
+```bash
+curl -X POST https://tavusapi.com/v2/conversations \
+ -H "x-api-key: YOUR_API_KEY" \
+ -H "Content-Type: application/json" \
+ -d '{
+ "persona_id": "p12345",
+ "replica_id": "r9fa0878977a",
+ "conversational_context": "Chapter context...",
+ "custom_greeting": "Hi! Ready to discuss this chapter?",
+ "conversation_name": "Learning Check: Chapter 1",
+ "test_mode": false
+ }'
+```
+
+**Response:**
+```json
+{
+ "conversation_id": "c123456",
+ "conversation_url": "https://tavus.daily.co/c123456",
+ "status": "active",
+ "expires_at": "2025-10-31T20:00:00Z"
+}
+```
+
+### Frontend Component Structure
+
+```
+LearningCheckBase (state manager)
+├── LearningCheckReadyScreen (screen="ready")
+├── HairCheck (screen="hairCheck")
+└── Conversation (screen="call")
+ ├── CVIProvider (from layout.tsx)
+ ├── DailyProvider (from @daily-co/daily-react)
+ └── Video/Audio Components
+```
+
+---
+
+## ✅ Testing Checklist
+
+Before testing, ensure:
+
+1. **Environment Variables Set**:
+ - [ ] `TAVUS_API_KEY` is set
+ - [ ] `TAVUS_PERSONA_ID` is set
+ - [ ] Replica ID `r9fa0878977a` is valid (or update in config.ts)
+
+2. **Persona Setup** (via Tavus Dashboard):
+ - [ ] Persona exists with ID matching env var
+ - [ ] Persona has system_prompt and context configured
+ - [ ] Replica is assigned to persona (or will use replica_id from request)
+
+3. **Test Steps**:
+ ```bash
+ # Start dev server
+ npm run dev
+
+ # Navigate to learning check page
+ # Click "Start Learning Check"
+ # Verify HairCheck screen shows camera preview
+ # Click "Join Video"
+ # Verify conversation starts and AI avatar appears
+ # Verify 3-minute timer is running
+ # Click "Leave" button
+ # Verify conversation ends gracefully
+ ```
+
+4. **Expected Console Logs**:
+ ```
+ 🎯 Creating conversation with structured assets
+ 📊 Analytics: lc_started
+ 🟢 Starting engagement tracking
+ ⏱️ Engagement time: X seconds
+ 🔴 handleEndSession called
+ 🛑 Ending Tavus conversation: c123456
+ ✅ Conversation ended successfully
+ ```
+
+---
+
+## 🐛 Troubleshooting
+
+### Issue: "Failed to create conversation"
+
+**Check**:
+1. API key is valid: `echo $TAVUS_API_KEY`
+2. Persona ID exists: Check Tavus dashboard
+3. Replica ID is valid: `r9fa0878977a` or update in `TAVUS_DEFAULTS`
+4. Network logs: Open DevTools → Network tab → Check `/api/learning-checks/conversation`
+
+**Solution**:
+```typescript
+// Check API response in console
+console.log('API Response:', response);
+console.log('API Error:', await response.text());
+```
+
+### Issue: "Conversation component doesn't join"
+
+**Check**:
+1. `conversationUrl` is properly set: Should be `https://tavus.daily.co/...`
+2. CVIProvider is in layout: Should wrap entire app
+3. Browser permissions: Camera/mic access granted
+
+**Solution**:
+```typescript
+// Add debug logs in Conversation component
+console.log('conversationUrl:', conversationUrl);
+console.log('meetingState:', meetingState);
+```
+
+### Issue: "Replica doesn't appear"
+
+**Check**:
+1. Replica is trained: Check Tavus dashboard for training status
+2. Persona has default replica OR replica_id in request
+3. Test mode is off: `test_mode: false` in config
+
+---
+
+## 📚 Related Documentation
+
+- **Tavus API Reference**: [docs/TAVUS_API_REFERENCE.md](./TAVUS_API_REFERENCE.md)
+- **Tavus Setup Guide**: [docs/TAVUS_SETUP.md](./TAVUS_SETUP.md)
+- **Configuration**: [src/lib/tavus/config.ts](../src/lib/tavus/config.ts)
+- **Learning Check Spec**: [specs/features/learning-check/](../specs/features/learning-check/)
+
+---
+
+## 🎉 Next Steps
+
+### Immediate Testing:
+1. Set environment variables in `.env.local`
+2. Run `npm run dev`
+3. Navigate to a learning check section
+4. Test complete user flow
+
+### Phase 2 Enhancements (Future):
+- [ ] Add objectives & guardrails IDs to environment
+- [ ] Implement webhook for perception analysis
+- [ ] Add real-time engagement tracking via Daily.co audio levels
+- [ ] Store conversation transcripts
+- [ ] Add analytics dashboard
+
+---
+
+## 💡 Key Implementation Notes
+
+1. **Replica ID Required**: Added to conversation body per Tavus API requirements
+2. **Security**: API key stays server-side, never exposed to client
+3. **Error Handling**: Comprehensive try/catch blocks with user-friendly messages
+4. **State Management**: Clean state transitions (ready → hairCheck → call → ended)
+5. **Resource Cleanup**: Conversation terminated on component unmount
+6. **Type Safety**: Full TypeScript typing for API requests/responses
+
+---
+
+**Status**: ✅ Ready for testing
+**Blockers**: None
+**Dependencies**: Tavus API credentials required
diff --git a/docs/TAVUS_INDEX.md b/docs/TAVUS_INDEX.md
new file mode 100644
index 0000000..962442d
--- /dev/null
+++ b/docs/TAVUS_INDEX.md
@@ -0,0 +1,115 @@
+# Tavus Documentation Index
+
+Central hub for all Tavus Conversational Video Interface (CVI) documentation.
+
+---
+
+## 📚 Quick Navigation
+
+### **Getting Started**
+- [Main Integration Guide](./TAVUS.md) - Start here for setup and configuration
+- [API Reference](./TAVUS_API_REFERENCE.md) - Complete API documentation
+
+### **Configuration & Updates**
+- [Config Update Guide](./TAVUS_CONFIG_UPDATE_GUIDE.md) - How to update objectives and guardrails
+- [Dynamic Sync](./TAVUS_DYNAMIC_SYNC_COMPLETE.md) - Automated config sync implementation
+
+### **Feature Implementation**
+- [Objective Completion Tracking](./TAVUS_OBJECTIVE_COMPLETION_TRACKING.md) - Track learner assessment progress
+- [Time Limit & Tracking](./TAVUS_TIME_LIMIT_AND_TRACKING_UPDATE.md) - 3-minute time limit implementation
+- [Learning Check Implementation](./TAVUS_IMPLEMENTATION_COMPLETE.md) - Complete learning check feature
+
+### **Code References**
+- **Config**: `src/lib/tavus/config.ts` - All Tavus configurations
+- **API Routes**: `src/app/api/learning-checks/` - Conversation, objectives, guardrails endpoints
+- **Components**: `src/components/course/chapter-content/learning-check-base.tsx` - Main learning check component
+- **Scripts**: `scripts/update-tavus-*.sh` - Update scripts for config sync
+
+---
+
+## 📖 Document Purposes
+
+### **TAVUS.md**
+**Purpose**: Main integration guide
+**Use When**: Initial setup, understanding architecture
+**Covers**: Setup, configuration, persona creation, troubleshooting
+
+### **TAVUS_API_REFERENCE.md**
+**Purpose**: Complete API documentation
+**Use When**: Making API calls, understanding endpoints
+**Covers**: All Tavus API endpoints with examples
+
+### **TAVUS_CONFIG_UPDATE_GUIDE.md**
+**Purpose**: User guide for config updates
+**Use When**: Updating objectives or guardrails
+**Covers**: Step-by-step update process, troubleshooting
+
+### **TAVUS_DYNAMIC_SYNC_COMPLETE.md**
+**Purpose**: Implementation summary of dynamic config sync
+**Use When**: Understanding how config sync works
+**Covers**: Technical implementation, scripts, workflow
+
+### **TAVUS_OBJECTIVE_COMPLETION_TRACKING.md**
+**Purpose**: Technical guide for tracking objective completion
+**Use When**: Implementing webhook handling for objectives
+**Covers**: Webhook setup, data structures, implementation examples
+
+### **TAVUS_TIME_LIMIT_AND_TRACKING_UPDATE.md**
+**Purpose**: Summary of time limit and tracking implementation
+**Use When**: Understanding conversation time limits
+**Covers**: Time limit enforcement, objective tracking integration
+
+### **TAVUS_IMPLEMENTATION_COMPLETE.md**
+**Purpose**: Complete learning check feature documentation
+**Use When**: Understanding full learning check implementation
+**Covers**: Complete feature overview, architecture, components
+
+---
+
+## 🎯 Common Tasks
+
+### **Update Objectives or Guardrails**
+1. Edit `src/lib/tavus/config.ts`
+2. Run `./scripts/update-tavus-config.sh`
+3. Reference: [Config Update Guide](./TAVUS_CONFIG_UPDATE_GUIDE.md)
+
+### **Create a Learning Check Conversation**
+1. Use API route: `POST /api/learning-checks/conversation`
+2. Reference: [API Reference](./TAVUS_API_REFERENCE.md)
+
+### **Track Objective Completion**
+1. Set up webhook endpoint
+2. Handle `application.transcription_ready` event
+3. Reference: [Objective Tracking Guide](./TAVUS_OBJECTIVE_COMPLETION_TRACKING.md)
+
+### **Troubleshoot Connection Issues**
+1. Check environment variables
+2. Verify API key and IDs
+3. Reference: [Main Guide Troubleshooting](./TAVUS.md#troubleshooting)
+
+---
+
+## 🗂️ Archive
+
+Historical documentation from previous implementations:
+- `docs/archive/HAIRCHECK_CONVERSATION_FIX.md` - Hair check flow fix (completed)
+- `docs/archive/LEARNING_CHECK_BASE_ANALYSIS.md` - Component analysis (completed)
+
+---
+
+## 🔄 Maintenance
+
+**When adding new Tavus documentation**:
+1. Create the doc in `/docs/`
+2. Add it to this index under appropriate section
+3. Include purpose and use cases
+4. Update common tasks if applicable
+
+**When archiving old documentation**:
+1. Move to `/docs/archive/`
+2. Update this index to reference archive location
+3. Note completion date
+
+---
+
+**Last Updated**: October 31, 2025
diff --git a/docs/TAVUS_OBJECTIVE_COMPLETION_TRACKING.md b/docs/TAVUS_OBJECTIVE_COMPLETION_TRACKING.md
new file mode 100644
index 0000000..ef63cc7
--- /dev/null
+++ b/docs/TAVUS_OBJECTIVE_COMPLETION_TRACKING.md
@@ -0,0 +1,418 @@
+# Tavus Objective Completion Tracking
+
+How to track when learners complete learning objectives during Tavus conversations.
+
+---
+
+## 🎯 Overview
+
+Tavus provides two ways to track objective completion:
+
+1. **Real-Time Callbacks** - Per-objective webhooks notify when each objective is completed
+2. **End-of-Conversation Transcript** - Full conversation history with all collected data
+
+---
+
+## 📊 Method 1: Real-Time Objective Callbacks
+
+### **How It Works**
+
+Each objective in your objectives configuration can have its own `callback_url` that gets notified when that specific objective is completed.
+
+### **Objective Configuration**
+
+```typescript
+// src/lib/tavus/config.ts
+export const LEARNING_CHECK_OBJECTIVES = {
+ name: "Learning Check Compliance Objectives",
+ data: [
+ {
+ objective_name: "recall_assessment",
+ objective_prompt: "Ask at least one recall question...",
+ confirmation_mode: "auto", // or "manual"
+ modality: "verbal",
+ output_variables: ["recall_key_terms", "recall_score"],
+ next_required_objectives: ["application_assessment"],
+ callback_url: "https://your-app.com/api/webhooks/objectives/recall" // ← Per-objective webhook
+ }
+ ]
+};
+```
+
+### **Webhook Payload Structure**
+
+When an objective is completed, Tavus sends a POST request to the `callback_url`:
+
+```json
+{
+ "conversation_id": "c0b934942640d424",
+ "objective_name": "recall_assessment",
+ "objective_status": "completed",
+ "output_variables": {
+ "recall_key_terms": "bilateral stimulation, adaptive information processing",
+ "recall_score": 85
+ },
+ "timestamp": "2025-10-31T21:30:00.000Z"
+}
+```
+
+### **Implementation Example**
+
+```typescript
+// src/app/api/webhooks/objectives/[objectiveName]/route.ts
+export async function POST(
+ request: NextRequest,
+ { params }: { params: Promise<{ objectiveName: string }> }
+) {
+ const { objectiveName } = await params;
+ const payload = await request.json();
+
+ console.log(`Objective ${objectiveName} completed!`, payload);
+
+ // Store completion data
+ await database.learningCheckObjectives.create({
+ conversationId: payload.conversation_id,
+ objectiveName: payload.objective_name,
+ status: payload.objective_status,
+ outputVariables: payload.output_variables,
+ completedAt: new Date(payload.timestamp)
+ });
+
+ return NextResponse.json({ received: true });
+}
+```
+
+---
+
+## 📝 Method 2: End-of-Conversation Transcript
+
+### **How It Works**
+
+After a conversation ends, Tavus sends an `application.transcription_ready` webhook to your main `callback_url` with the complete conversation transcript and all collected objective data.
+
+### **Webhook Configuration**
+
+```typescript
+// Set callback_url when creating conversation
+const conversationBody = {
+ persona_id: personaId,
+ objectives_id: objectivesId,
+ callback_url: "https://your-app.com/api/webhooks/tavus" // ← Main webhook
+ // ...
+};
+```
+
+### **Webhook Payload Structure**
+
+```json
+{
+ "conversation_id": "c0b934942640d424",
+ "event_type": "application.transcription_ready",
+ "message_type": "application",
+ "timestamp": "2025-10-31T21:35:00.000Z",
+ "properties": {
+ "replica_id": "r9fa0878977a",
+ "transcript": [
+ {
+ "role": "assistant",
+ "content": "Hi! I'm excited to chat with you about EMDR Foundations..."
+ },
+ {
+ "role": "user",
+ "content": "Hi! I'm ready to discuss what I learned."
+ },
+ // ... full conversation
+ ],
+ "objectives_completed": [
+ {
+ "objective_name": "recall_assessment",
+ "status": "completed",
+ "output_variables": {
+ "recall_key_terms": "bilateral stimulation, adaptive information processing",
+ "recall_score": 85
+ }
+ },
+ {
+ "objective_name": "application_assessment",
+ "status": "completed",
+ "output_variables": {
+ "application_example": "Using eye movements during trauma processing",
+ "application_score": 90
+ }
+ },
+ {
+ "objective_name": "self_explanation_assessment",
+ "status": "completed",
+ "output_variables": {
+ "explanation_summary": "EMDR helps process traumatic memories by...",
+ "explanation_score": 88
+ }
+ }
+ ]
+ }
+}
+```
+
+### **Implementation Example**
+
+```typescript
+// src/app/api/webhooks/tavus/route.ts
+export async function POST(request: NextRequest) {
+ const payload = await request.json();
+
+ if (payload.event_type === "application.transcription_ready") {
+ const { conversation_id, properties } = payload;
+
+ // Extract objectives completion data
+ const objectivesData = properties.objectives_completed || [];
+
+ // Calculate overall learning check score
+ const scores = objectivesData.map(obj =>
+ obj.output_variables?.score || 0
+ );
+ const averageScore = scores.reduce((a, b) => a + b, 0) / scores.length;
+
+ // Store learning check results
+ await database.learningCheckResults.create({
+ conversationId: conversation_id,
+ overallScore: averageScore,
+ objectivesCompleted: objectivesData.length,
+ transcript: properties.transcript,
+ completedAt: new Date(payload.timestamp)
+ });
+
+ // Send completion notification to learner
+ await notifyLearner(conversation_id, averageScore);
+ }
+
+ return NextResponse.json({ received: true });
+}
+```
+
+---
+
+## 🔄 Complete Flow
+
+```
+1. Learner joins conversation
+ ↓
+2. AI asks recall question (objective 1)
+ ↓ (if callback_url set on objective)
+3. Webhook: "recall_assessment completed" → /api/webhooks/objectives/recall
+ ↓
+4. AI asks application question (objective 2)
+ ↓ (if callback_url set on objective)
+5. Webhook: "application_assessment completed" → /api/webhooks/objectives/application
+ ↓
+6. AI asks self-explanation question (objective 3)
+ ↓ (if callback_url set on objective)
+7. Webhook: "self_explanation_assessment completed" → /api/webhooks/objectives/explanation
+ ↓
+8. Conversation ends (3 minutes elapsed)
+ ↓
+9. Webhook: "transcription_ready" → /api/webhooks/tavus
+ ↓ (includes all objectives + transcript)
+10. Display results to learner ✅
+```
+
+---
+
+## ⚙️ Configuration Options
+
+### **Option 1: Per-Objective Webhooks** (Real-Time)
+
+**Pros:**
+- Real-time notifications as each objective completes
+- Can update UI progressively
+- Granular control per objective
+
+**Cons:**
+- More webhook endpoints to manage
+- Multiple requests per conversation
+
+**Best For:** Live progress indicators, real-time feedback
+
+### **Option 2: End-of-Conversation Webhook** (Batch)
+
+**Pros:**
+- Single webhook handles everything
+- Complete context with full transcript
+- Simpler implementation
+
+**Cons:**
+- Only get data after conversation ends
+- Can't show real-time progress
+
+**Best For:** Post-conversation analysis, final scoring
+
+### **Option 3: Hybrid** (Recommended for MVP)
+
+Use end-of-conversation webhook only to keep it simple, then add per-objective webhooks later if you need real-time progress.
+
+---
+
+## 🛠️ Implementation Steps for MVP
+
+### **Step 1: Create Webhook Endpoint**
+
+```typescript
+// src/app/api/webhooks/tavus/route.ts
+export async function POST(request: NextRequest) {
+ try {
+ const payload = await request.json();
+
+ console.log("Tavus webhook received:", payload.event_type);
+
+ // Handle different webhook types
+ switch (payload.event_type) {
+ case "system.replica_joined":
+ console.log("Replica joined conversation");
+ break;
+
+ case "system.shutdown":
+ console.log("Conversation ended");
+ break;
+
+ case "application.transcription_ready":
+ // This is where we get objectives completion data
+ await handleTranscriptionReady(payload);
+ break;
+
+ case "application.perception_analysis":
+ // Visual engagement data (if Raven enabled)
+ await handlePerceptionAnalysis(payload);
+ break;
+ }
+
+ return NextResponse.json({ received: true });
+ } catch (error) {
+ console.error("Webhook error:", error);
+ return NextResponse.json({ error: "Webhook processing failed" }, { status: 500 });
+ }
+}
+
+async function handleTranscriptionReady(payload: any) {
+ const { conversation_id, properties } = payload;
+
+ // Extract objectives data
+ const objectivesCompleted = properties.objectives_completed || [];
+
+ console.log(`Conversation ${conversation_id} objectives:`, objectivesCompleted);
+
+ // TODO: Store in database
+ // TODO: Calculate scores
+ // TODO: Update learner's progress
+}
+```
+
+### **Step 2: Add Webhook URL to Environment**
+
+```bash
+# .env.local
+TAVUS_WEBHOOK_URL=https://your-app.com/api/webhooks/tavus
+```
+
+### **Step 3: Test with ngrok (Development)**
+
+```bash
+# Start ngrok
+ngrok http 3000
+
+# Update .env.local with ngrok URL
+TAVUS_WEBHOOK_URL=https://abc123.ngrok.io/api/webhooks/tavus
+
+# Test conversation and watch webhook logs
+npm run dev
+```
+
+### **Step 4: Deploy Webhook (Production)**
+
+```bash
+# Production webhook URL
+TAVUS_WEBHOOK_URL=https://8p3p-lms.com/api/webhooks/tavus
+```
+
+---
+
+## 🔐 Security Best Practices
+
+### **Validate Webhook Authenticity**
+
+```typescript
+// Verify webhook is from Tavus
+export async function POST(request: NextRequest) {
+ const signature = request.headers.get("x-tavus-signature");
+ const payload = await request.text();
+
+ // Verify signature if Tavus provides one
+ if (!verifySignature(signature, payload)) {
+ return NextResponse.json({ error: "Invalid signature" }, { status: 401 });
+ }
+
+ // Process webhook...
+}
+```
+
+### **Store Webhook Secret**
+
+```bash
+# .env.local
+TAVUS_WEBHOOK_SECRET=your_webhook_secret_here
+```
+
+---
+
+## 📊 Data Schema Example
+
+```typescript
+// Database schema for learning check results
+interface LearningCheckResult {
+ id: string;
+ userId: string;
+ chapterId: string;
+ conversationId: string;
+
+ // Objectives completion
+ recallScore: number;
+ applicationScore: number;
+ explanationScore: number;
+ overallScore: number;
+
+ // Output variables
+ recallKeyTerms: string;
+ applicationExample: string;
+ explanationSummary: string;
+
+ // Metadata
+ transcript: ConversationMessage[];
+ duration: number; // seconds
+ objectivesCompleted: number;
+ completedAt: Date;
+}
+```
+
+---
+
+## 📚 Related Documentation
+
+- [Tavus Webhooks Guide](https://docs.tavus.io/sections/webhooks-and-callbacks)
+- [Tavus Objectives](https://docs.tavus.io/sections/conversational-video-interface/persona/objectives)
+- [Conversation API Route](../src/app/api/learning-checks/conversation/route.ts)
+- [Tavus Config](../src/lib/tavus/config.ts)
+
+---
+
+## 🎉 Summary
+
+**For MVP:**
+1. ✅ Use `callback_url` on conversation creation
+2. ✅ Handle `application.transcription_ready` webhook
+3. ✅ Extract `objectives_completed` from payload
+4. ✅ Calculate scores from `output_variables`
+5. ✅ Display results to learner
+
+**Post-MVP Enhancements:**
+- Add per-objective webhooks for real-time progress
+- Implement perception analysis for engagement metrics
+- Add database storage for historical analysis
+- Create analytics dashboard for instructors
diff --git a/docs/TAVUS_TIME_LIMIT_AND_TRACKING_UPDATE.md b/docs/TAVUS_TIME_LIMIT_AND_TRACKING_UPDATE.md
new file mode 100644
index 0000000..5d8298d
--- /dev/null
+++ b/docs/TAVUS_TIME_LIMIT_AND_TRACKING_UPDATE.md
@@ -0,0 +1,379 @@
+# Tavus Time Limit & Objective Tracking - Implementation Complete ✅
+
+**Date**: October 31, 2025
+**Status**: Production Ready
+
+---
+
+## ✅ What Was Implemented
+
+### 1. **Time Limit Enforcement**
+
+Added `max_call_duration` property to conversation creation to enforce the 3-minute learning check limit.
+
+#### **Code Changes**
+
+```typescript
+// src/app/api/learning-checks/conversation/route.ts
+
+// Get learning check duration from environment (default: 180 seconds = 3 minutes)
+const learningCheckDuration = TAVUS_ENV.getLearningCheckDuration();
+
+const conversationBody = {
+ persona_id: personaId,
+ replica_id: TAVUS_DEFAULTS.DEFAULT_REPLICA_ID,
+ conversational_context: conversationalContext,
+ custom_greeting: customGreeting,
+ conversation_name: `Learning Check: ${chapterTitle}`,
+ test_mode: TAVUS_DEFAULTS.TEST_MODE,
+
+ // ✅ NEW: Enforce time limit
+ properties: {
+ max_call_duration: learningCheckDuration, // 180 seconds (3 minutes)
+ participant_left_timeout: 10, // End 10s after learner leaves
+ participant_absent_timeout: 60, // End if no one joins in 60s
+ },
+};
+```
+
+#### **How It Works**
+
+- **`max_call_duration`**: Automatically ends conversation after 180 seconds (3 minutes)
+- **`participant_left_timeout`**: Ends conversation 10 seconds after learner disconnects
+- **`participant_absent_timeout`**: Ends conversation if learner doesn't join within 60 seconds
+
+#### **Configurable via Environment**
+
+```bash
+# .env.local
+TAVUS_LEARNING_CHECK_DURATION=180 # Default: 3 minutes
+```
+
+---
+
+### 2. **Objectives & Guardrails Auto-Injection**
+
+Updated conversation creation to automatically inject objectives and guardrails IDs from environment variables.
+
+#### **Code Changes**
+
+```typescript
+// src/app/api/learning-checks/conversation/route.ts
+
+// ✅ NEW: Auto-inject objectives ID
+const finalObjectivesId = objectivesId || TAVUS_ENV.getObjectivesId();
+if (finalObjectivesId) {
+ conversationBody.objectives_id = finalObjectivesId;
+}
+
+// ✅ NEW: Auto-inject guardrails ID
+const finalGuardrailsId = guardrailsId || TAVUS_ENV.getGuardrailsId();
+if (finalGuardrailsId) {
+ conversationBody.guardrails_id = finalGuardrailsId;
+}
+```
+
+#### **Environment Variables**
+
+```bash
+# .env.local
+NEXT_PUBLIC_TAVUS_LEARNING_CHECK_OBJECTIVES_ID=o078991a2b199
+NEXT_PUBLIC_TAVUS_LEARNING_CHECK_GUARDRAILS_ID=g7771e9a453db
+```
+
+---
+
+### 3. **Objective Completion Tracking Documentation**
+
+Created comprehensive guide for tracking when learners complete objectives during conversations.
+
+#### **Two Tracking Methods**
+
+**Method 1: Real-Time Per-Objective Webhooks**
+- Each objective can have its own `callback_url`
+- Notified immediately when objective completes
+- Enables real-time progress indicators
+
+**Method 2: End-of-Conversation Transcript** (Recommended for MVP)
+- Single webhook after conversation ends
+- Includes all objectives completion data
+- Includes full transcript
+- Simpler implementation
+
+---
+
+## 📊 Objective Completion Data Structure
+
+### **What You Get from Tavus**
+
+When a conversation ends, the `application.transcription_ready` webhook includes:
+
+```json
+{
+ "conversation_id": "c0b934942640d424",
+ "event_type": "application.transcription_ready",
+ "properties": {
+ "objectives_completed": [
+ {
+ "objective_name": "recall_assessment",
+ "status": "completed",
+ "output_variables": {
+ "recall_key_terms": "bilateral stimulation, adaptive information processing",
+ "recall_score": 85
+ }
+ },
+ {
+ "objective_name": "application_assessment",
+ "status": "completed",
+ "output_variables": {
+ "application_example": "Using eye movements during trauma processing",
+ "application_score": 90
+ }
+ },
+ {
+ "objective_name": "self_explanation_assessment",
+ "status": "completed",
+ "output_variables": {
+ "explanation_summary": "EMDR helps process traumatic memories...",
+ "explanation_score": 88
+ }
+ }
+ ],
+ "transcript": [
+ // Full conversation history
+ ]
+ }
+}
+```
+
+### **Objectives in Your Config**
+
+```typescript
+// src/lib/tavus/config.ts
+export const LEARNING_CHECK_OBJECTIVES = {
+ data: [
+ {
+ objective_name: "recall_assessment",
+ objective_prompt: "Ask at least one recall question...",
+ output_variables: ["recall_key_terms", "recall_score"], // ← Data collected
+ confirmation_mode: "auto", // AI determines completion
+ next_required_objectives: ["application_assessment"]
+ },
+ // ... more objectives
+ ]
+};
+```
+
+---
+
+## 🔄 Complete Learning Check Flow
+
+```
+1. Learner clicks "Start Learning Check"
+ ↓
+2. Create conversation with:
+ - max_call_duration: 180s (3 minutes)
+ - objectives_id: o078991a2b199
+ - guardrails_id: g7771e9a453db
+ - callback_url: https://your-app.com/api/webhooks/tavus
+ ↓
+3. Learner joins conversation (hair check first)
+ ↓
+4. AI conducts structured assessment:
+ - Objective 1: Recall assessment
+ - Objective 2: Application assessment
+ - Objective 3: Self-explanation assessment
+ ↓
+5. Conversation ends after 3 minutes OR all objectives complete
+ ↓
+6. Tavus sends webhook: "application.transcription_ready"
+ ↓
+7. Extract objectives_completed data
+ ↓
+8. Calculate scores:
+ - recall_score: 85
+ - application_score: 90
+ - explanation_score: 88
+ - overall_score: 87.67 (average)
+ ↓
+9. Display results to learner ✅
+```
+
+---
+
+## 🎯 Next Steps for Full Implementation
+
+### **Phase 1: MVP (Current)**
+- ✅ Time limit enforced (3 minutes)
+- ✅ Objectives and guardrails auto-injected
+- ✅ Documentation complete
+- ⏳ Create webhook endpoint
+- ⏳ Handle transcription_ready webhook
+- ⏳ Extract and display objective scores
+
+### **Phase 2: Enhanced Tracking**
+- Add per-objective webhooks for real-time progress
+- Implement perception analysis (visual engagement)
+- Store results in database
+- Create analytics dashboard
+
+---
+
+## 🛠️ MVP Webhook Implementation (Next Step)
+
+### **1. Create Webhook Endpoint**
+
+```typescript
+// src/app/api/webhooks/tavus/route.ts
+import { NextRequest, NextResponse } from "next/server";
+
+export async function POST(request: NextRequest) {
+ try {
+ const payload = await request.json();
+
+ console.log("Tavus webhook:", payload.event_type);
+
+ if (payload.event_type === "application.transcription_ready") {
+ const { conversation_id, properties } = payload;
+ const objectivesCompleted = properties.objectives_completed || [];
+
+ // Calculate average score
+ const scores = objectivesCompleted.map(obj =>
+ obj.output_variables?.score || 0
+ );
+ const avgScore = scores.reduce((a, b) => a + b, 0) / scores.length;
+
+ console.log(`Learning check ${conversation_id} completed:`, {
+ objectives: objectivesCompleted.length,
+ averageScore: avgScore
+ });
+
+ // TODO: Store in database
+ // TODO: Notify learner
+ }
+
+ return NextResponse.json({ received: true });
+ } catch (error) {
+ console.error("Webhook error:", error);
+ return NextResponse.json(
+ { error: "Webhook processing failed" },
+ { status: 500 }
+ );
+ }
+}
+```
+
+### **2. Add Webhook URL to Environment**
+
+```bash
+# .env.local
+TAVUS_WEBHOOK_URL=https://your-app.com/api/webhooks/tavus
+
+# Or use ngrok for local testing:
+# TAVUS_WEBHOOK_URL=https://abc123.ngrok.io/api/webhooks/tavus
+```
+
+### **3. Test with ngrok**
+
+```bash
+# Terminal 1: Start Next.js
+npm run dev
+
+# Terminal 2: Start ngrok
+ngrok http 3000
+
+# Update .env.local with ngrok URL
+# Start a learning check conversation
+# Watch webhook logs in Terminal 1
+```
+
+---
+
+## 📁 Files Updated
+
+### **Code Changes**
+- ✅ `src/app/api/learning-checks/conversation/route.ts` - Added time limit and auto-injection
+- ✅ `src/app/api/learning-checks/conversation/[conversationId]/end/route.ts` - Fixed Next.js 15 params
+
+### **Documentation Created**
+- ✅ `docs/TAVUS_OBJECTIVE_COMPLETION_TRACKING.md` - Complete tracking guide
+- ✅ `docs/TAVUS_TIME_LIMIT_AND_TRACKING_UPDATE.md` - This summary
+
+---
+
+## 🔐 Environment Variables Summary
+
+```bash
+# .env.local
+
+# API Authentication
+TAVUS_API_KEY=your_api_key_here
+
+# Persona & Replica
+TAVUS_PERSONA_ID=your_persona_id
+TAVUS_DEFAULT_REPLICA_ID=r9fa0878977a
+
+# Objectives & Guardrails
+NEXT_PUBLIC_TAVUS_LEARNING_CHECK_OBJECTIVES_ID=o078991a2b199
+NEXT_PUBLIC_TAVUS_LEARNING_CHECK_GUARDRAILS_ID=g7771e9a453db
+
+# Time Configuration
+TAVUS_LEARNING_CHECK_DURATION=180 # 3 minutes (default)
+
+# Webhooks
+TAVUS_WEBHOOK_URL=https://your-app.com/api/webhooks/tavus
+TAVUS_WEBHOOK_SECRET=your_webhook_secret # Optional
+```
+
+---
+
+## ✅ Validation
+
+### **Type-Check**
+```bash
+npm run type-check
+# ✅ Passed
+```
+
+### **Test Conversation**
+```bash
+# 1. Start dev server
+npm run dev
+
+# 2. Navigate to learning check section
+# 3. Start conversation
+# 4. Verify time limit enforces 3-minute duration
+# 5. Check console for conversation end event
+```
+
+---
+
+## 📚 Related Documentation
+
+- **Objective Tracking**: [docs/TAVUS_OBJECTIVE_COMPLETION_TRACKING.md](./TAVUS_OBJECTIVE_COMPLETION_TRACKING.md)
+- **Tavus Config**: [src/lib/tavus/config.ts](../src/lib/tavus/config.ts)
+- **Conversation API**: [src/app/api/learning-checks/conversation/route.ts](../src/app/api/learning-checks/conversation/route.ts)
+- **Tavus Webhooks Docs**: https://docs.tavus.io/sections/webhooks-and-callbacks
+- **Tavus Objectives Docs**: https://docs.tavus.io/sections/conversational-video-interface/persona/objectives
+
+---
+
+## 🎉 Summary
+
+**✅ Completed:**
+1. **Time Limit Enforcement** - 3-minute conversations with configurable duration
+2. **Auto-Injection** - Objectives and guardrails automatically added from environment
+3. **Comprehensive Documentation** - Full guide on objective completion tracking
+4. **Next.js 15 Compatibility** - Fixed async params issue
+
+**⏳ Next Steps:**
+1. Create webhook endpoint at `/api/webhooks/tavus`
+2. Handle `application.transcription_ready` webhook
+3. Extract and store `objectives_completed` data
+4. Calculate and display scores to learner
+
+**Your learning check conversations now:**
+- ✅ Enforce 3-minute time limit
+- ✅ Include structured objectives
+- ✅ Follow guardrails for compliance
+- ✅ Ready for objective completion tracking
diff --git a/docs/TECHNICAL_DEBT_CONSOLE_LOGGING.md b/docs/TECHNICAL_DEBT_CONSOLE_LOGGING.md
new file mode 100644
index 0000000..a794bb0
--- /dev/null
+++ b/docs/TECHNICAL_DEBT_CONSOLE_LOGGING.md
@@ -0,0 +1,338 @@
+# Technical Debt: Console Logging Cleanup
+
+**Status**: 🟡 Tracking
+**Priority**: Medium
+**Target**: Before Production Launch
+**Created**: October 31, 2025
+**Estimated Effort**: 1-2 hours
+
+---
+
+## Issue Description
+
+The codebase currently contains **63 console.log/warn/error statements** that are not wrapped in environment conditionals. These logs will execute in production, potentially exposing debugging information and impacting performance.
+
+---
+
+## Impact Assessment
+
+### **Current State**
+- 63 console statements across 16 files
+- All logs execute regardless of environment
+- No structured logging mechanism in place
+
+### **Production Impact**
+- **Performance**: Minimal but measurable overhead
+- **Security**: Potential exposure of internal state/data
+- **Debugging**: Cluttered browser console for end users
+- **Monitoring**: No structured log aggregation
+
+### **Priority Justification**
+- ✅ Not blocking for MVP/development
+- ⚠️ Should be resolved before production launch
+- 🎯 Improves production quality and debugging experience
+
+---
+
+## Affected Files
+
+### **High Concentration** (Priority 1)
+1. `src/components/course/chapter-content/learning-check.tsx` - **19 logs**
+ - Analytics tracking
+ - State transitions
+ - Engagement tracking
+ - Timer events
+
+2. `src/app/dashboard/page.tsx` - **9 logs**
+ - Course data logging
+ - User state debugging
+
+### **API Routes** (Priority 2)
+3. `src/app/api/learning-checks/conversation/[conversationId]/end/route.ts` - **5 logs**
+4. `src/app/api/learning-checks/conversation/route.ts` - **3 logs**
+5. `src/app/api/learning-checks/update-persona/route.ts` - **3 logs**
+6. `src/app/api/learning-checks/guardrails/route.ts` - **2 logs**
+7. `src/app/api/learning-checks/objectives/route.ts` - **2 logs**
+8. `src/app/api/learning-checks/terminate/route.ts` - **4 logs**
+
+### **Components & Hooks** (Priority 3)
+9. `src/components/cvi/hooks/use-cvi-call.tsx` - **5 logs**
+10. `src/components/course/chapter-content/learning-check-base.tsx` - **2 logs**
+11. `src/components/course/chapter-content/index.tsx` - **1 log**
+12. `src/components/course/layout-breadcrumbs.tsx` - **2 logs**
+13. `src/components/auth/EmailVerificationHandler.tsx` - **1 log**
+14. `src/components/ui/navbar.tsx` - **1 log**
+15. `src/components/video/video-player.tsx` - **1 log**
+16. `src/lib/tavus/README.md` - **3 logs** (documentation examples)
+
+---
+
+## Proposed Solution
+
+### **Implementation Plan**
+
+#### **Phase 1: Create Logger Utility**
+
+Create `src/lib/logger.ts`:
+
+```typescript
+/**
+ * Conditional logger that only outputs in development
+ * Structured for future enhancement with log aggregation
+ */
+
+type LogLevel = 'log' | 'warn' | 'error' | 'info' | 'debug';
+
+interface LogContext {
+ component?: string;
+ action?: string;
+ userId?: string;
+ [key: string]: unknown;
+}
+
+class Logger {
+ private isDevelopment = process.env.NODE_ENV === 'development';
+
+ /**
+ * Development-only logging
+ */
+ log(message: string, context?: LogContext) {
+ if (this.isDevelopment) {
+ console.log(message, context || '');
+ }
+ }
+
+ /**
+ * Development-only warnings
+ */
+ warn(message: string, context?: LogContext) {
+ if (this.isDevelopment) {
+ console.warn(message, context || '');
+ }
+ }
+
+ /**
+ * Always log errors (including production)
+ */
+ error(message: string, error?: Error | unknown, context?: LogContext) {
+ console.error(message, error, context || '');
+
+ // TODO: Future - Send to error monitoring service (Sentry, etc.)
+ // if (!this.isDevelopment) {
+ // sendToErrorMonitoring(message, error, context);
+ // }
+ }
+
+ /**
+ * Development-only info logs
+ */
+ info(message: string, context?: LogContext) {
+ if (this.isDevelopment) {
+ console.info(message, context || '');
+ }
+ }
+
+ /**
+ * Analytics/metrics logging
+ * These should be sent to analytics service in production
+ */
+ analytics(event: string, data?: Record) {
+ if (this.isDevelopment) {
+ console.log(`📊 Analytics: ${event}`, data);
+ }
+
+ // TODO: Future - Send to analytics service
+ // sendToAnalytics(event, data);
+ }
+}
+
+export const logger = new Logger();
+```
+
+#### **Phase 2: Replace Console Statements**
+
+**Example Migration Pattern**:
+
+```typescript
+// BEFORE
+console.log("🎯 Creating conversation with structured assets:", {
+ objectivesId: objectivesId || "fallback to context-only",
+ guardrailsId: guardrailsId || "fallback to context-only"
+});
+
+// AFTER
+logger.log("Creating conversation with structured assets", {
+ component: "LearningCheck",
+ action: "createConversation",
+ objectivesId: objectivesId || "fallback",
+ guardrailsId: guardrailsId || "fallback"
+});
+```
+
+**Analytics Events**:
+
+```typescript
+// BEFORE
+console.log("📊 Analytics: lc_started", { ... });
+
+// AFTER
+logger.analytics("lc_started", { ... });
+```
+
+**Error Handling**:
+
+```typescript
+// BEFORE
+console.error('Error creating conversation:', error);
+
+// AFTER
+logger.error('Failed to create conversation', error, {
+ component: "LearningCheck",
+ chapterId,
+});
+```
+
+#### **Phase 3: Verify Changes**
+
+```bash
+# Should find 0 direct console.log calls
+grep -r "console\\.log" src/ --exclude-dir=node_modules
+
+# Should find logger usage instead
+grep -r "logger\\." src/ --exclude-dir=node_modules
+```
+
+---
+
+## Migration Checklist
+
+### **Phase 1: Setup** (15 minutes)
+- [ ] Create `src/lib/logger.ts` utility
+- [ ] Add logger export to `src/lib/index.ts` (if exists)
+- [ ] Test logger in development and production modes
+- [ ] Add TypeScript types for structured logging
+
+### **Phase 2: Component Migration** (30 minutes)
+- [ ] `learning-check.tsx` (19 logs)
+- [ ] `dashboard/page.tsx` (9 logs)
+- [ ] `use-cvi-call.tsx` (5 logs)
+- [ ] `learning-check-base.tsx` (2 logs)
+- [ ] Other components (5 logs)
+
+### **Phase 3: API Route Migration** (20 minutes)
+- [ ] `conversation/route.ts` (3 logs)
+- [ ] `conversation/[id]/end/route.ts` (5 logs)
+- [ ] `update-persona/route.ts` (3 logs)
+- [ ] `terminate/route.ts` (4 logs)
+- [ ] `guardrails/route.ts` (2 logs)
+- [ ] `objectives/route.ts` (2 logs)
+
+### **Phase 4: Verification** (10 minutes)
+- [ ] Run grep to verify no direct console.log calls
+- [ ] Test development logging works
+- [ ] Test production build has no logs
+- [ ] Verify error logs still work in production
+- [ ] Update linting rules to disallow direct console usage
+
+---
+
+## Future Enhancements
+
+### **Production Logging** (Post-Launch)
+1. **Error Monitoring Integration**
+ - Sentry, Rollbar, or similar
+ - Automatic error reporting with stack traces
+ - User context and session info
+
+2. **Analytics Integration**
+ - Google Analytics, Mixpanel, or Amplitude
+ - Convert console analytics to real tracking
+ - User journey and conversion tracking
+
+3. **Server-Side Logging**
+ - Winston, Pino, or Bunyan for API routes
+ - Log aggregation (CloudWatch, DataDog)
+ - Structured JSON logging
+
+### **ESLint Rule** (Recommended)
+Add to `eslint.config.mjs`:
+
+```javascript
+rules: {
+ 'no-console': ['error', {
+ allow: [] // Disallow all console usage
+ }],
+ // Or use a custom rule that only allows logger
+}
+```
+
+---
+
+## Testing Strategy
+
+### **Development Environment**
+- ✅ Logs should appear in console
+- ✅ All log levels should work
+- ✅ Structured context should display
+
+### **Production Build**
+```bash
+# Build and check bundle
+npm run build
+
+# Verify no development logs in production JS
+grep -r "console\.log" .next/static/chunks/
+
+# Should only find logger.error calls (production-safe)
+```
+
+### **Manual Testing**
+1. Start learning check conversation
+2. Open browser console (production mode)
+3. Verify no debug logs appear
+4. Trigger an error
+5. Verify error IS logged (with context)
+
+---
+
+## Success Criteria
+
+- [ ] **Zero direct console statements** in src/ (except errors)
+- [ ] **Logger utility implemented** with environment conditionals
+- [ ] **All 63 logs migrated** to structured logger
+- [ ] **Development logs work** as expected
+- [ ] **Production build clean** - no debug logs in bundle
+- [ ] **ESLint rule added** to prevent future console usage
+- [ ] **Documentation updated** with logging standards
+
+---
+
+## Related Documentation
+
+- [ESLint Configuration](./eslint-rules.md) - Update with console rules
+- [Development Standards](../specs/01-development-standards.md) - Add logging section
+- [Production Deployment](./DEPLOYMENT.md) - Pre-launch checklist
+
+---
+
+## Timeline
+
+**Recommended**: Before production launch
+**Estimated**: 1-2 hours for full migration
+**Blocking**: No (can ship with console logs for MVP)
+**Technical Debt**: Yes (should be resolved before scale)
+
+---
+
+## Notes
+
+- Console.error statements can remain in production for critical errors
+- Analytics logs should eventually connect to real analytics service
+- Structured logging provides better debugging in production
+- This is a common pattern in production Next.js applications
+
+---
+
+**Last Updated**: October 31, 2025
+**Tracking Issue**: #TBD (Create GitHub issue when ready)
+**Milestone**: Production Hardening
diff --git a/docs/archive/HAIRCHECK_CONVERSATION_FIX.md b/docs/archive/HAIRCHECK_CONVERSATION_FIX.md
new file mode 100644
index 0000000..a452b58
--- /dev/null
+++ b/docs/archive/HAIRCHECK_CONVERSATION_FIX.md
@@ -0,0 +1,313 @@
+# HairCheck → Conversation Transition Fix
+
+## 🔍 Root Cause Analysis
+
+### The Problem
+Daily.co was entering an error state when transitioning from HairCheck to Conversation, causing the error:
+```
+❌ Meeting state error - Daily.co connection failed
+```
+
+### Why It Happened
+
+**Tavus Recommended Flow** (from official docs):
+```
+1. Show HairCheck (device testing with startCamera())
+2. Create conversation (API call to get conversation_url)
+3. Join conversation (switch to Conversation component with URL)
+```
+
+**Our Issue:**
+- HairCheck calls `startCamera()` → Daily state: `joined-meeting` (preview mode)
+- User clicks "Join Video" → Creates conversation → Returns URL
+- Conversation component tries to `join(url)` → ❌ ERROR
+- **Problem**: Can't join a new meeting while already in one (preview mode)
+
+### Daily.co State Machine
+
+```
+Idle → startCamera() → joined-meeting (preview)
+ ↓
+ leave() → left-meeting
+ ↓
+ join(url) → joined-meeting (call)
+```
+
+**We were doing:**
+```
+startCamera() → joined-meeting → join(url) ❌ ERROR
+ (preview) (can't join while joined)
+```
+
+**We needed:**
+```
+startCamera() → joined-meeting → leave() → left-meeting → join(url) ✅
+ (preview) (call)
+```
+
+---
+
+## ✅ The Fix
+
+### 1. Proper State Transition in `useCVICall`
+
+**File**: `src/components/cvi/hooks/use-cvi-call.tsx`
+
+**What we changed:**
+- Check Daily state before joining
+- If in `joined-meeting` state (preview mode), call `leave()` first
+- Wait 500ms for clean transition
+- Then `join(url)` for actual conversation
+
+```typescript
+const joinCall = useCallback(
+ async ({ url }: { url: string }) => {
+ if (!daily) return;
+
+ try {
+ // Check current Daily state
+ const meetingState = daily.meetingState();
+ console.log("📡 Current meeting state before join:", meetingState);
+
+ // If in preview mode (from HairCheck), leave first
+ if (meetingState === 'joined-meeting') {
+ console.log("🔄 Leaving preview mode to join conversation...");
+ await daily.leave();
+ // Wait for Daily to fully clean up
+ await new Promise(resolve => setTimeout(resolve, 500));
+ }
+
+ console.log("📞 Joining conversation with URL:", url);
+
+ // Join the actual conversation
+ await daily.join({
+ url: url,
+ inputSettings: {
+ audio: {
+ processor: {
+ type: "noise-cancellation",
+ },
+ },
+ },
+ });
+
+ console.log("✅ Successfully joined conversation");
+ } catch (error) {
+ console.error("❌ Failed to join call:", error);
+ throw error;
+ }
+ },
+ [daily]
+);
+```
+
+### 2. Simplified HairCheck Cleanup
+
+**File**: `src/components/cvi/components/hair-check/index.tsx`
+
+**What we changed:**
+- Remove `daily.leave()` from `onJoinHairCheck()`
+- Let `useCVICall` handle the state transition
+- HairCheck only signals "ready to join"
+
+```typescript
+const onJoinHairCheck = () => {
+ // Don't call daily.leave() here - the same Daily instance will be used for the conversation
+ // The Conversation component will call joinCall() which transitions from preview to call
+ console.log("✅ HairCheck complete - transitioning to conversation");
+ onJoin();
+};
+```
+
+### 3. Better Error Logging
+
+**File**: `src/components/cvi/components/conversation/index.tsx`
+
+**What we changed:**
+- More detailed error messages
+- Clear state transition logging
+- Helpful debugging context
+
+```typescript
+useEffect(() => {
+ console.log("📡 Meeting state changed:", meetingState);
+
+ if (meetingState === 'error') {
+ console.error("❌ Meeting state error - Daily.co connection failed");
+ console.error("This usually happens when transitioning from preview to call");
+ console.error("Check that HairCheck properly cleaned up before joining");
+ onLeave();
+ } else if (meetingState === 'left-meeting') {
+ console.log("👋 Meeting ended gracefully");
+ // Don't call onLeave() here - let the parent component handle it
+ }
+}, [meetingState, onLeave]);
+```
+
+---
+
+## 🎯 Tavus Official Flow (Per Documentation)
+
+From Tavus docs: https://docs.tavus.io/sections/conversational-video-interface/component-library/blocks.md
+
+### Recommended Implementation
+
+```typescript
+// 1. Show HairCheck first
+const [conversationUrl, setConversationUrl] = useState(null);
+const [isLoading, setIsLoading] = useState(false);
+
+const handleJoin = async () => {
+ setIsLoading(true);
+
+ // 2. Create conversation via API
+ const response = await fetch('https://tavusapi.com/v2/conversations', {
+ method: 'POST',
+ headers: { 'x-api-key': 'YOUR_KEY' },
+ body: JSON.stringify({ replica_id: 'r12345' })
+ });
+
+ const data = await response.json();
+ setConversationUrl(data.conversation_url);
+};
+
+return conversationUrl ? (
+ // 3. Join conversation - switch to Conversation component
+ setConversationUrl(null)} />
+) : (
+
+);
+```
+
+### Our Implementation
+
+```typescript
+// learning-check.tsx
+if (state === "hair_check") {
+ return (
+ {
+ // Create conversation when user clicks "Join Video"
+ await createConversation();
+ }}
+ onCancel={() => setState("ready")}
+ />
+ );
+}
+
+if (state === "active" && conversationUrl) {
+ return (
+
+ );
+}
+```
+
+---
+
+## 🧪 Testing Checklist
+
+### Expected Console Output
+
+**1. HairCheck Phase:**
+```
+📡 Current meeting state before join: idle
+🔄 Starting camera for haircheck...
+```
+
+**2. User Clicks "Join Video":**
+```
+✅ HairCheck complete - transitioning to conversation
+🎯 Creating conversation with structured assets: {...}
+```
+
+**3. Transition to Conversation:**
+```
+📡 Current meeting state before join: joined-meeting
+🔄 Leaving preview mode to join conversation...
+📞 Joining conversation with URL: https://...
+✅ Successfully joined conversation
+📡 Meeting state changed: joining-meeting
+📡 Meeting state changed: joined-meeting
+```
+
+**4. End Conversation:**
+```
+👋 Meeting ended gracefully
+📡 Meeting state changed: left-meeting
+```
+
+### What to Test
+
+- [ ] HairCheck shows camera preview
+- [ ] Microphone selection persists to conversation
+- [ ] Click "Join Video" → smooth transition (no errors)
+- [ ] Conversation loads successfully
+- [ ] Timer counts up → engagement time matches
+- [ ] End conversation → no console errors
+- [ ] All 3 previous issues fixed
+
+---
+
+## 📊 Before vs After
+
+### Before (Broken)
+```
+HairCheck: startCamera() → joined-meeting
+ ↓
+Conversation: join(url) ❌ ERROR
+ (can't join while joined)
+```
+
+### After (Fixed)
+```
+HairCheck: startCamera() → joined-meeting
+ ↓
+Conversation: leave() → left-meeting
+ ↓
+ join(url) ✅ SUCCESS
+```
+
+---
+
+## 🔑 Key Learnings
+
+1. **Daily.co State Machine**: Can't join a meeting while already in one
+2. **HairCheck Purpose**: Device preview only, not a real meeting
+3. **Proper Transition**: Must `leave()` preview before `join()` call
+4. **Timing**: 500ms delay ensures clean state transition
+5. **Error Handling**: Better logging helps debug state issues
+
+---
+
+## 📚 References
+
+- [Tavus CVI Blocks Documentation](https://docs.tavus.io/sections/conversational-video-interface/component-library/blocks.md)
+- [Tavus CVI Hooks Documentation](https://docs.tavus.io/sections/conversational-video-interface/component-library/hooks.md)
+- [Daily.co State Machine](https://docs.daily.co/reference/daily-js/instance-methods/meeting-state)
+
+---
+
+## ✅ Files Changed
+
+| File | Lines Changed | Type |
+|------|---------------|------|
+| `use-cvi-call.tsx` | +17 | State transition handling |
+| `hair-check/index.tsx` | -5 | Remove premature cleanup |
+| `conversation/index.tsx` | +3 | Better error logging |
+| `learning-check.tsx` | +1 | Async onJoin handler |
+
+**Total**: 4 files, 16 net lines added
+
+---
+
+## 🎯 Status
+
+✅ **FIXED** - All issues resolved:
+1. ✅ Microphone selection persistence
+2. ✅ Timer → engagement time tracking
+3. ✅ Graceful conversation end
+4. ✅ Daily.co state transition error
diff --git a/docs/archive/LEARNING_CHECK_BASE_ANALYSIS.md b/docs/archive/LEARNING_CHECK_BASE_ANALYSIS.md
new file mode 100644
index 0000000..134ae72
--- /dev/null
+++ b/docs/archive/LEARNING_CHECK_BASE_ANALYSIS.md
@@ -0,0 +1,424 @@
+# Learning Check Base - Analysis & Best Practices Implementation ✅
+
+**Date**: October 31, 2025
+**Component**: `learning-check-base.tsx`
+**Status**: Production-ready with all best practices applied
+
+---
+
+## 🔍 Issues Found & Fixed
+
+### ❌ **Issue 1: Hair Check Flow Was Bypassed**
+
+**Problem**: The flow jumped directly from "ready" → "call", skipping the hair check screen entirely.
+
+```typescript
+// ❌ BEFORE: Hair check screen was never activated
+const handleJoin = async () => {
+ // ... create conversation
+ setScreen("call"); // Jumped directly to call
+};
+
+
+// Hair check screen rendered but never shown
+```
+
+**Solution**: Separated concerns with two handlers:
+```typescript
+// ✅ AFTER: Proper flow with hair check
+const handleStart = () => {
+ setScreen("hairCheck"); // Navigate to hair check first
+};
+
+const handleJoin = async () => {
+ // ... create conversation
+ setScreen("call"); // Only called from hair check
+};
+
+// Flow: ready → hairCheck → call
+```
+
+**User Flow Now**:
+1. **Ready Screen** → User clicks "Start Learning Check" → `handleStart()`
+2. **Hair Check Screen** → Camera/mic preview (not billed) → User clicks "Join Video" → `handleJoin()`
+3. **Active Call** → Conversation with AI avatar
+
+---
+
+### ❌ **Issue 2: Hardcoded Chapter Data**
+
+**Problem**: Component didn't accept props, making it impossible to use for different chapters.
+
+```typescript
+// ❌ BEFORE: Hardcoded values
+export const LearningCheckBase = () => {
+ // ...
+ body: JSON.stringify({
+ chapterId: "chapter-1", // Hardcoded
+ chapterTitle: "Chapter 1", // Hardcoded
+ })
+};
+```
+
+**Solution**: Added props interface and dynamic data:
+```typescript
+// ✅ AFTER: Accepts props from parent
+interface LearningCheckBaseProps {
+ chapterId: string;
+ chapterTitle: string;
+}
+
+export const LearningCheckBase = ({ chapterId, chapterTitle }: LearningCheckBaseProps) => {
+ // ...
+ body: JSON.stringify({ chapterId, chapterTitle })
+};
+```
+
+**Usage in Parent Component**:
+```typescript
+
+```
+
+---
+
+### ❌ **Issue 3: Poor Error Handling**
+
+**Problem**: Generic alert message with no UI feedback.
+
+```typescript
+// ❌ BEFORE: Browser alert (poor UX)
+alert("Uh oh! Something went wrong. Check console for details");
+```
+
+**Solution**: Proper error state with shadcn/ui Alert component:
+```typescript
+// ✅ AFTER: User-friendly error display
+const [error, setError] = useState(null);
+
+try {
+ // ... create conversation
+} catch (error) {
+ setError(
+ error instanceof Error
+ ? error.message
+ : "Failed to start learning check. Please try again."
+ );
+ setScreen("ready"); // Return to ready screen
+}
+
+// In JSX:
+{error && (
+
+
+ {error}
+
+)}
+```
+
+---
+
+### ❌ **Issue 4: Semantic HTML Issues**
+
+**Problem**: Used `` tag inside a component (should only be one per page).
+
+```typescript
+// ❌ BEFORE: Incorrect semantic HTML
+return (
+
+ {/* content */}
+
+);
+```
+
+**Solution**: Used appropriate container div:
+```typescript
+// ✅ AFTER: Proper component container
+return (
+
+ {/* content */}
+
+);
+```
+
+---
+
+## ✅ Best Practices Applied
+
+### 1. **Clean State Management**
+
+```typescript
+const [screen, setScreen] = useState<"ready" | "hairCheck" | "call">("ready");
+const [conversation, setConversation] = useState(null);
+const [loading, setLoading] = useState(false);
+const [error, setError] = useState(null);
+```
+
+- TypeScript-enforced screen states
+- Null-safe conversation handling
+- Separate loading and error states
+
+### 2. **Proper Error Recovery**
+
+```typescript
+catch (error) {
+ console.error("Failed to create conversation:", error);
+ setError(error instanceof Error ? error.message : "Failed to start learning check");
+ setScreen("ready"); // ✅ Return to ready screen on error
+}
+```
+
+- User-friendly error messages
+- Returns to ready state for retry
+- Console logging for debugging
+
+### 3. **Clear Separation of Concerns**
+
+```typescript
+// ✅ Two distinct handlers with clear purposes
+const handleStart = () => {
+ setError(null);
+ setScreen("hairCheck");
+};
+
+const handleJoin = async () => {
+ // API call to create conversation
+ setScreen("call");
+};
+```
+
+### 4. **Resource Cleanup**
+
+```typescript
+const handleEnd = async () => {
+ try {
+ setScreen("ready");
+ if (!conversation?.conversationId) return;
+
+ // Call API to end conversation
+ await fetch(`/api/learning-checks/conversation/${conversationId}/end`, {
+ method: "POST"
+ });
+ } finally {
+ setConversation(null); // ✅ Always cleanup
+ }
+};
+```
+
+### 5. **TypeScript Type Safety**
+
+```typescript
+interface ConversationResponse {
+ conversationUrl: string;
+ conversationId: string;
+ expiresAt?: string;
+}
+
+interface LearningCheckBaseProps {
+ chapterId: string;
+ chapterTitle: string;
+}
+```
+
+### 6. **Component Composition**
+
+```typescript
+// ✅ Clean conditional rendering with clear screen states
+{screen === "ready" && }
+{screen === "hairCheck" && }
+{screen === "call" && conversation && }
+```
+
+---
+
+## 🎯 Component Flow
+
+```
+┌─────────────────────────────────────────────────────┐
+│ Ready Screen │
+│ ┌───────────────────────────────────────────┐ │
+│ │ • Explains 3-minute conversation │ │
+│ │ • Lists requirements │ │
+│ │ • "Start Learning Check" button │ │
+│ └───────────────────────────────────────────┘ │
+│ ↓ handleStart() │
+└─────────────────────────────────────────────────────┘
+ ↓
+┌─────────────────────────────────────────────────────┐
+│ Hair Check Screen │
+│ ┌───────────────────────────────────────────┐ │
+│ │ • Camera/mic preview │ │
+│ │ • Device selection │ │
+│ │ • Not billed yet │ │
+│ │ • "Join Video" button │ │
+│ │ • "Cancel" returns to ready │ │
+│ └───────────────────────────────────────────┘ │
+│ ↓ handleJoin() │
+│ (Creates Tavus conversation) │
+└─────────────────────────────────────────────────────┘
+ ↓
+┌─────────────────────────────────────────────────────┐
+│ Active Conversation │
+│ ┌───────────────────────────────────────────┐ │
+│ │ • Live video with AI avatar │ │
+│ │ • Audio/video controls │ │
+│ │ • 3-minute session │ │
+│ │ • "Leave" button (handleEnd) │ │
+│ └───────────────────────────────────────────┘ │
+│ ↓ handleEnd() │
+│ (Ends conversation) │
+└─────────────────────────────────────────────────────┘
+ ↓
+ Returns to Ready Screen
+```
+
+---
+
+## 🔐 Security Best Practices
+
+### ✅ API Key Protection
+```typescript
+// API key stays server-side only
+await fetch("/api/learning-checks/conversation", {
+ // No API key in client code
+ body: JSON.stringify({ chapterId, chapterTitle })
+});
+```
+
+### ✅ Server-Side Validation
+```typescript
+// Server validates request and injects API key
+const apiKey = TAVUS_ENV.getApiKey(); // Server-side only
+```
+
+---
+
+## 📊 Error States
+
+### Network Errors
+```typescript
+// Returns to ready screen with error message
+setError("Failed to start learning check. Please try again.");
+setScreen("ready");
+```
+
+### Missing Configuration
+```typescript
+// Server returns clear error message
+if (!apiKey) {
+ return NextResponse.json(
+ { error: "Tavus configuration missing" },
+ { status: 500 }
+ );
+}
+```
+
+---
+
+## 🎨 UI/UX Improvements
+
+### 1. **Error Feedback**
+- ✅ Inline error alerts with icons
+- ✅ User-friendly messages
+- ✅ Automatic return to ready state
+
+### 2. **Loading States**
+```typescript
+
+
+```
+
+### 3. **Smooth Transitions**
+- Clear visual feedback for each screen
+- Cancel button to go back
+- Error resets on retry
+
+---
+
+## 🧪 Testing Checklist
+
+### Unit Testing
+- [ ] Props validation (chapterId, chapterTitle required)
+- [ ] State transitions (ready → hairCheck → call)
+- [ ] Error handling (network failures, API errors)
+- [ ] Loading states (button disabled during requests)
+
+### Integration Testing
+- [ ] Full user flow (start → hair check → join → end)
+- [ ] Cancel flow (hair check → back to ready)
+- [ ] Error recovery (failed conversation → retry)
+- [ ] Resource cleanup (conversation ends properly)
+
+### E2E Testing
+```bash
+# Manual test flow
+1. Navigate to ai_avatar section
+2. Click "Start Learning Check"
+3. Verify hair check screen shows
+4. Click "Join Video"
+5. Verify conversation starts
+6. Click "Leave"
+7. Verify returns to ready screen
+```
+
+---
+
+## 🚀 Performance Optimizations
+
+### 1. **Lazy Component Loading**
+```typescript
+// Components only render when needed
+{screen === "call" && conversation && }
+```
+
+### 2. **Efficient State Updates**
+```typescript
+// Clear error state on retry
+const handleStart = () => {
+ setError(null); // ✅ Reset error
+ setScreen("hairCheck");
+};
+```
+
+### 3. **Resource Cleanup**
+```typescript
+finally {
+ setConversation(null); // ✅ Always cleanup
+}
+```
+
+---
+
+## 📚 Related Documentation
+
+- **API Implementation**: [docs/TAVUS_IMPLEMENTATION_COMPLETE.md](./TAVUS_IMPLEMENTATION_COMPLETE.md)
+- **Tavus API Reference**: [docs/TAVUS_API_REFERENCE.md](./TAVUS_API_REFERENCE.md)
+- **Component Source**: [src/components/course/chapter-content/learning-check-base.tsx](../src/components/course/chapter-content/learning-check-base.tsx)
+
+---
+
+## ✅ Validation Results
+
+- ✅ **TypeScript**: No compilation errors
+- ✅ **ESLint**: All component-related issues resolved
+- ✅ **Hair Check**: Properly integrated into flow
+- ✅ **Props**: Dynamic chapter data passed correctly
+- ✅ **Error Handling**: User-friendly with recovery
+- ✅ **Best Practices**: Clean code, type-safe, accessible
+
+---
+
+## 🎉 Summary
+
+The `learning-check-base.tsx` component now follows all best practices:
+
+1. ✅ **Hair Check Enabled** - Full ready → hairCheck → call flow
+2. ✅ **Dynamic Props** - Accepts chapter data from parent
+3. ✅ **Error Handling** - User-friendly alerts with recovery
+4. ✅ **Type Safety** - Full TypeScript coverage
+5. ✅ **Clean Architecture** - Separated concerns, clear state management
+6. ✅ **Production Ready** - Security, performance, and UX optimized
+
+**Status**: Ready for production testing with Tavus API credentials.
diff --git a/scripts/README.md b/scripts/README.md
index 56b2ac8..75d0b57 100644
--- a/scripts/README.md
+++ b/scripts/README.md
@@ -29,6 +29,33 @@ node scripts/fetch-mux-vtt.js
- Captions/subtitles generated in Mux
- Track ID from Mux dashboard
+### 🎯 Tavus Configuration Management
+
+**Single Source of Truth**: Automatically syncs `src/lib/tavus/config.ts` with Tavus API.
+
+#### Quick Start
+```bash
+# 1. Edit src/lib/tavus/config.ts
+# 2. Run the update script
+./scripts/update-tavus-config.sh
+```
+
+#### Available Scripts
+- `update-tavus-config.sh` - Updates both objectives and guardrails (recommended)
+- `update-tavus-objectives.sh` - Updates objectives only
+- `update-tavus-guardrails.sh` - Updates guardrails only
+- `extract-tavus-config.mjs` - Helper that parses TypeScript config to JSON
+
+**📚 Full Documentation**: See [TAVUS_SCRIPTS_README.md](./TAVUS_SCRIPTS_README.md) for detailed usage, troubleshooting, and examples.
+
+**Key Features**:
+- ✅ Automatically reads from TypeScript config
+- ✅ No manual JSON editing required
+- ✅ Version controlled configuration
+- ✅ macOS and Linux compatible
+
+---
+
### ✅ Quality Gates
#### `pre-commit.sh`
diff --git a/scripts/TAVUS_SCRIPTS_README.md b/scripts/TAVUS_SCRIPTS_README.md
new file mode 100644
index 0000000..3804d34
--- /dev/null
+++ b/scripts/TAVUS_SCRIPTS_README.md
@@ -0,0 +1,213 @@
+# Tavus Configuration Scripts
+
+Automatically sync your `src/lib/tavus/config.ts` with Tavus API.
+
+## 🎯 Purpose
+
+These scripts solve the "single source of truth" problem by:
+- Reading directly from your TypeScript config file
+- Automatically extracting objectives and guardrails
+- Syncing changes to Tavus in one command
+
+## 📦 Scripts
+
+### `extract-tavus-config.mjs`
+Node.js helper that parses `config.ts` and extracts JSON data.
+
+**Usage:**
+```bash
+node scripts/extract-tavus-config.mjs objectives
+node scripts/extract-tavus-config.mjs guardrails
+```
+
+### `update-tavus-objectives.sh`
+Syncs `LEARNING_CHECK_OBJECTIVES` to Tavus.
+
+**Usage:**
+```bash
+./scripts/update-tavus-objectives.sh
+```
+
+### `update-tavus-guardrails.sh`
+Syncs `LEARNING_CHECK_GUARDRAILS` to Tavus.
+
+**Usage:**
+```bash
+./scripts/update-tavus-guardrails.sh
+```
+
+### `update-tavus-config.sh`
+Syncs both objectives and guardrails in one command (recommended!).
+
+**Usage:**
+```bash
+./scripts/update-tavus-config.sh
+```
+
+## 🔄 Workflow
+
+```
+1. Edit src/lib/tavus/config.ts
+ ↓
+2. Run ./scripts/update-tavus-config.sh
+ ↓
+3. ✅ Tavus updated automatically!
+```
+
+## 🛠️ How It Works
+
+```
+┌─────────────────────────────────┐
+│ src/lib/tavus/config.ts │
+│ ┌─────────────────────────┐ │
+│ │ LEARNING_CHECK_ │ │
+│ │ OBJECTIVES = { │ │
+│ │ data: [...] │ │
+│ │ } │ │
+│ └─────────────────────────┘ │
+└─────────────────────────────────┘
+ ↓
+┌─────────────────────────────────┐
+│ extract-tavus-config.mjs │
+│ (Parses TypeScript → JSON) │
+└─────────────────────────────────┘
+ ↓
+┌─────────────────────────────────┐
+│ update-tavus-*.sh │
+│ (Sends PATCH to Tavus API) │
+└─────────────────────────────────┘
+ ↓
+┌─────────────────────────────────┐
+│ Tavus API Updated ✅ │
+└─────────────────────────────────┘
+```
+
+## ⚙️ Configuration Structure
+
+### Objectives Format
+```typescript
+export const LEARNING_CHECK_OBJECTIVES = {
+ name: "Learning Check Compliance Objectives",
+ data: [
+ {
+ objective_name: "recall_assessment",
+ objective_prompt: "Ask at least one recall question...",
+ confirmation_mode: "auto",
+ modality: "verbal",
+ output_variables: ["recall_key_terms", "recall_score"],
+ next_required_objectives: ["application_assessment"]
+ }
+ // ... more objectives
+ ]
+};
+```
+
+### Guardrails Format
+```typescript
+export const LEARNING_CHECK_GUARDRAILS = {
+ name: "Learning Check Compliance Guardrails",
+ data: [
+ {
+ guardrail_name: "quiz_answer_protection",
+ guardrail_prompt: "Never reveal quiz answers...",
+ modality: "verbal"
+ }
+ // ... more guardrails
+ ]
+};
+```
+
+## 🔐 Environment Variables Required
+
+Add these to your `.env.local`:
+
+```bash
+TAVUS_API_KEY=your_api_key
+NEXT_PUBLIC_TAVUS_LEARNING_CHECK_GUARDRAILS_ID=g123456
+NEXT_PUBLIC_TAVUS_LEARNING_CHECK_OBJECTIVES_ID=o123456
+```
+
+## ✅ Success Output
+
+When everything works, you'll see:
+
+```bash
+╔═══════════════════════════════════════════╗
+║ Tavus Configuration Update (All) ║
+╚═══════════════════════════════════════════╝
+
+━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+🛡️ Tavus Guardrails Update Script
+
+Guardrails ID: g7771e9a453db
+
+📖 Reading guardrails from src/lib/tavus/config.ts...
+✓ Guardrails loaded successfully
+
+📤 Sending PATCH request to Tavus...
+
+✅ Guardrails updated successfully!
+
+Response:
+{
+ "guardrails_id": "g7771e9a453db",
+ "status": "success"
+}
+
+🎉 Done!
+
+━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+🎯 Tavus Objectives Update Script
+
+Objectives ID: o5d8f3a912cd
+
+📖 Reading objectives from src/lib/tavus/config.ts...
+✓ Objectives loaded successfully
+
+📤 Sending PATCH request to Tavus...
+
+✅ Objectives updated successfully!
+
+🎉 Done!
+
+━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+✨ All Tavus configurations updated successfully!
+```
+
+## 🐛 Troubleshooting
+
+### Error: "Could not find LEARNING_CHECK_OBJECTIVES"
+
+**Cause**: Parser can't find the export in `config.ts`
+
+**Solution**:
+- Ensure the constant is exported with `export const`
+- Verify the name matches exactly: `LEARNING_CHECK_OBJECTIVES` or `LEARNING_CHECK_GUARDRAILS`
+
+### Error: "TAVUS_API_KEY not found"
+
+**Cause**: Missing environment variable
+
+**Solution**: Add `TAVUS_API_KEY` to `.env.local`
+
+### HTTP 400 or 404 Error
+
+**Cause**: Invalid resource ID
+
+**Solution**:
+- Check IDs in `.env.local` match Tavus dashboard
+- Verify objectives/guardrails exist on Tavus first
+
+## 📚 Related Documentation
+
+- [Tavus Config Update Guide](../docs/TAVUS_CONFIG_UPDATE_GUIDE.md)
+- [Tavus Implementation](../docs/TAVUS_IMPLEMENTATION_COMPLETE.md)
+- [Tavus API Reference](../docs/TAVUS_API_REFERENCE.md)
+
+## 🎉 Benefits
+
+✅ **Single Source of Truth** - Config lives in TypeScript only
+✅ **Type Safety** - TypeScript catches errors before deployment
+✅ **Version Control** - Config changes tracked in git
+✅ **No Manual Sync** - Scripts handle everything automatically
+✅ **Developer Friendly** - Edit code, run script, done!
diff --git a/scripts/create-persona.sh b/scripts/create-persona.sh
new file mode 100755
index 0000000..04bda63
--- /dev/null
+++ b/scripts/create-persona.sh
@@ -0,0 +1,182 @@
+#!/bin/bash
+
+# Create Tavus Persona for 8P3P LMS Learning Check
+# This script creates a new persona with the exact configuration for the AI Instructor Assistant
+#
+# Usage:
+# ./scripts/create-persona.sh
+# ./scripts/create-persona.sh --api-key YOUR_API_KEY
+# ./scripts/create-persona.sh --replica-id YOUR_REPLICA_ID
+
+set -e
+
+# Colors for output
+RED='\033[0;31m'
+GREEN='\033[0;32m'
+YELLOW='\033[1;33m'
+BLUE='\033[0;34m'
+NC='\033[0m' # No Color
+
+# Default values
+API_KEY=""
+REPLICA_ID="r9fa0878977a"
+OBJECTIVES_ID=""
+GUARDRAILS_ID=""
+
+# Parse command line arguments
+while [[ $# -gt 0 ]]; do
+ case $1 in
+ --api-key)
+ API_KEY="$2"
+ shift 2
+ ;;
+ --replica-id)
+ REPLICA_ID="$2"
+ shift 2
+ ;;
+ --objectives-id)
+ OBJECTIVES_ID="$2"
+ shift 2
+ ;;
+ --guardrails-id)
+ GUARDRAILS_ID="$2"
+ shift 2
+ ;;
+ --help|-h)
+ echo "Usage: $0 [OPTIONS]"
+ echo ""
+ echo "Options:"
+ echo " --api-key KEY Tavus API key (or set TAVUS_API_KEY env var)"
+ echo " --replica-id ID Replica ID (default: r9fa0878977a)"
+ echo " --objectives-id ID Objectives ID (optional)"
+ echo " --guardrails-id ID Guardrails ID (optional)"
+ echo " --help, -h Show this help message"
+ exit 0
+ ;;
+ *)
+ echo -e "${RED}Unknown option: $1${NC}"
+ echo "Use --help for usage information"
+ exit 1
+ ;;
+ esac
+done
+
+echo -e "${BLUE}🚀 Creating Tavus Persona for 8P3P LMS${NC}"
+echo ""
+
+# Get API key from environment if not provided
+if [ -z "$API_KEY" ]; then
+ if [ -f .env.local ]; then
+ API_KEY=$(grep "TAVUS_API_KEY" .env.local | cut -d '=' -f2)
+ fi
+fi
+
+# Prompt for API key if still not found
+if [ -z "$API_KEY" ]; then
+ echo -e "${YELLOW}⚠️ TAVUS_API_KEY not found in .env.local${NC}"
+ read -p "Enter your Tavus API key: " API_KEY
+fi
+
+if [ -z "$API_KEY" ]; then
+ echo -e "${RED}❌ API key is required${NC}"
+ exit 1
+fi
+
+# Get objectives and guardrails IDs from .env.local if not provided
+if [ -z "$OBJECTIVES_ID" ] && [ -f .env.local ]; then
+ OBJECTIVES_ID=$(grep "NEXT_PUBLIC_TAVUS_LEARNING_CHECK_OBJECTIVES_ID" .env.local | cut -d '=' -f2)
+fi
+
+if [ -z "$GUARDRAILS_ID" ] && [ -f .env.local ]; then
+ GUARDRAILS_ID=$(grep "NEXT_PUBLIC_TAVUS_LEARNING_CHECK_GUARDRAILS_ID" .env.local | cut -d '=' -f2)
+fi
+
+# Build the persona configuration from centralized config
+echo -e "${BLUE}📋 Loading persona configuration from src/lib/tavus/config.ts...${NC}"
+
+# Check if tsx is available
+if ! command -v tsx &> /dev/null; then
+ echo -e "${RED}❌ tsx is required but not installed${NC}"
+ echo -e "${YELLOW}Install it with: npm install -g tsx${NC}"
+ exit 1
+fi
+
+# Load persona config directly from centralized location
+PERSONA_CONFIG=$(tsx --eval "
+ import { PERSONA_CONFIG } from './src/lib/tavus/index.js';
+ console.log(JSON.stringify(PERSONA_CONFIG, null, 2));
+")
+
+if [ -z "$PERSONA_CONFIG" ]; then
+ echo -e "${RED}❌ Failed to load persona configuration${NC}"
+ exit 1
+fi
+
+# Build JSON payload with centralized config
+PAYLOAD=$(echo "$PERSONA_CONFIG" | jq --arg replica "$REPLICA_ID" '. + {default_replica_id: $replica}')
+
+# Add objectives_id if provided
+if [ -n "$OBJECTIVES_ID" ]; then
+ PAYLOAD=$(echo "$PAYLOAD" | jq --arg id "$OBJECTIVES_ID" '. + {objectives_id: $id}')
+ echo -e "${GREEN}✅ Including objectives_id: $OBJECTIVES_ID${NC}"
+fi
+
+# Add guardrails_id if provided
+if [ -n "$GUARDRAILS_ID" ]; then
+ PAYLOAD=$(echo "$PAYLOAD" | jq --arg id "$GUARDRAILS_ID" '. + {guardrails_id: $id}')
+ echo -e "${GREEN}✅ Including guardrails_id: $GUARDRAILS_ID${NC}"
+fi
+
+echo ""
+echo -e "${BLUE}🔧 Creating persona in Tavus...${NC}"
+
+# Create the persona
+RESPONSE=$(curl -s -X POST \
+ https://tavusapi.com/v2/personas \
+ -H "Content-Type: application/json" \
+ -H "x-api-key: $API_KEY" \
+ -d "$PAYLOAD")
+
+# Check if successful
+PERSONA_ID=$(echo "$RESPONSE" | jq -r '.persona_id // empty')
+
+if [ -n "$PERSONA_ID" ]; then
+ PERSONA_NAME=$(echo "$RESPONSE" | jq -r '.persona_name')
+
+ echo ""
+ echo -e "${GREEN}🎉 Persona created successfully!${NC}"
+ echo ""
+ echo -e "${BLUE}Persona Details:${NC}"
+ echo " ID: $PERSONA_ID"
+ echo " Name: $PERSONA_NAME"
+ echo " Replica ID: $REPLICA_ID"
+
+ if [ -n "$OBJECTIVES_ID" ]; then
+ echo " Objectives ID: $OBJECTIVES_ID"
+ fi
+
+ if [ -n "$GUARDRAILS_ID" ]; then
+ echo " Guardrails ID: $GUARDRAILS_ID"
+ fi
+
+ echo ""
+ echo -e "${YELLOW}📝 Next Steps:${NC}"
+ echo "1. Add to your .env.local:"
+ echo " TAVUS_PERSONA_ID=$PERSONA_ID"
+ echo ""
+ echo "2. Restart your development server"
+ echo " npm run dev"
+ echo ""
+
+ # Save persona config to file
+ echo "$RESPONSE" | jq . > "docs/persona-created-$(date +%Y%m%d-%H%M%S).json"
+ echo -e "${GREEN}✅ Persona config saved to docs/persona-created-*.json${NC}"
+
+else
+ echo ""
+ echo -e "${RED}❌ Failed to create persona${NC}"
+ echo ""
+ echo -e "${YELLOW}Response:${NC}"
+ echo "$RESPONSE" | jq .
+ exit 1
+fi
diff --git a/scripts/extract-tavus-config.mjs b/scripts/extract-tavus-config.mjs
new file mode 100755
index 0000000..4c34b6f
--- /dev/null
+++ b/scripts/extract-tavus-config.mjs
@@ -0,0 +1,118 @@
+#!/usr/bin/env node
+
+/**
+ * Extract Tavus Configuration from config.ts
+ *
+ * This script parses src/lib/tavus/config.ts and extracts
+ * the objectives and guardrails data as JSON.
+ *
+ * Usage:
+ * node scripts/extract-tavus-config.mjs objectives
+ * node scripts/extract-tavus-config.mjs guardrails
+ */
+
+import { readFileSync } from 'fs';
+import { fileURLToPath } from 'url';
+import { dirname, join } from 'path';
+
+const __filename = fileURLToPath(import.meta.url);
+const __dirname = dirname(__filename);
+
+const configPath = join(__dirname, '../src/lib/tavus/config.ts');
+const configType = process.argv[2]; // 'objectives' or 'guardrails'
+
+if (!configType || !['objectives', 'guardrails'].includes(configType)) {
+ console.error('Usage: node extract-tavus-config.mjs [objectives|guardrails]');
+ process.exit(1);
+}
+
+try {
+ const configContent = readFileSync(configPath, 'utf-8');
+
+ if (configType === 'objectives') {
+ extractObjectives(configContent);
+ } else {
+ extractGuardrails(configContent);
+ }
+} catch (error) {
+ console.error('Error reading config file:', error.message);
+ process.exit(1);
+}
+
+function extractObjectives(content) {
+ // Find the LEARNING_CHECK_OBJECTIVES export - match until closing brace and semicolon
+ const regex = /export const LEARNING_CHECK_OBJECTIVES\s*=\s*({[\s\S]*?^};)/m;
+ const objectivesMatch = content.match(regex);
+
+ if (!objectivesMatch) {
+ console.error('Could not find LEARNING_CHECK_OBJECTIVES in config.ts');
+ process.exit(1);
+ }
+
+ // Extract the object content (without trailing semicolon)
+ let objectivesStr = objectivesMatch[1].replace(/;$/, '');
+
+ // Remove comments but preserve the structure
+ objectivesStr = objectivesStr
+ .replace(/\/\/.*$/gm, '') // Remove single-line comments
+ .replace(/\/\*[\s\S]*?\*\//g, ''); // Remove multi-line comments
+
+ // Parse and extract just the data array
+ try {
+ // Use Function constructor instead of eval for safer parsing
+ const objectives = new Function(`return ${objectivesStr}`)();
+
+ // Output the data array
+ if (objectives.data && Array.isArray(objectives.data)) {
+ console.log(JSON.stringify(objectives.data, null, 2));
+ } else if (Array.isArray(objectives)) {
+ console.log(JSON.stringify(objectives, null, 2));
+ } else {
+ console.error('Unexpected objectives structure:', objectives);
+ process.exit(1);
+ }
+ } catch (error) {
+ console.error('Error parsing objectives:', error.message);
+ console.error('Content:', objectivesStr.substring(0, 200));
+ process.exit(1);
+ }
+}
+
+function extractGuardrails(content) {
+ // Find the LEARNING_CHECK_GUARDRAILS export - match until closing brace and semicolon
+ const regex = /export const LEARNING_CHECK_GUARDRAILS\s*=\s*({[\s\S]*?^};)/m;
+ const guardrailsMatch = content.match(regex);
+
+ if (!guardrailsMatch) {
+ console.error('Could not find LEARNING_CHECK_GUARDRAILS in config.ts');
+ process.exit(1);
+ }
+
+ // Extract the object content (without trailing semicolon)
+ let guardrailsStr = guardrailsMatch[1].replace(/;$/, '');
+
+ // Remove comments but preserve the structure
+ guardrailsStr = guardrailsStr
+ .replace(/\/\/.*$/gm, '') // Remove single-line comments
+ .replace(/\/\*[\s\S]*?\*\//g, ''); // Remove multi-line comments
+
+ // Parse and extract just the data array
+ try {
+ // Use Function constructor instead of eval for safer parsing
+ const guardrails = new Function(`return ${guardrailsStr}`)();
+
+ // Output the data array
+ if (guardrails.data && Array.isArray(guardrails.data)) {
+ console.log(JSON.stringify(guardrails.data, null, 2));
+ } else if (Array.isArray(guardrails)) {
+ console.log(JSON.stringify(guardrails, null, 2));
+ } else {
+ console.error('Unexpected guardrails structure:', guardrails);
+ process.exit(1);
+ }
+ } catch (error) {
+ console.error('Error parsing guardrails:', error.message);
+ console.error('Content:', guardrailsStr.substring(0, 200));
+ process.exit(1);
+ }
+}
diff --git a/scripts/update-tavus-config.sh b/scripts/update-tavus-config.sh
new file mode 100755
index 0000000..decd763
--- /dev/null
+++ b/scripts/update-tavus-config.sh
@@ -0,0 +1,36 @@
+#!/bin/bash
+
+#
+# Update All Tavus Configuration
+#
+# Updates both guardrails and objectives on Tavus
+# Usage: ./scripts/update-tavus-config.sh
+#
+
+set -e
+
+# Colors for output
+BLUE='\033[0;34m'
+GREEN='\033[0;32m'
+NC='\033[0m' # No Color
+
+echo -e "${BLUE}╔═══════════════════════════════════════════╗${NC}"
+echo -e "${BLUE}║ Tavus Configuration Update (All) ║${NC}"
+echo -e "${BLUE}╚═══════════════════════════════════════════╝${NC}"
+echo ""
+
+# Get the script directory
+SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+
+# Run guardrails update
+echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
+bash "$SCRIPT_DIR/update-tavus-guardrails.sh"
+echo ""
+
+# Run objectives update
+echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
+bash "$SCRIPT_DIR/update-tavus-objectives.sh"
+echo ""
+
+echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
+echo -e "${GREEN}✨ All Tavus configurations updated successfully!${NC}"
diff --git a/scripts/update-tavus-guardrails.sh b/scripts/update-tavus-guardrails.sh
new file mode 100755
index 0000000..65ba921
--- /dev/null
+++ b/scripts/update-tavus-guardrails.sh
@@ -0,0 +1,96 @@
+#!/bin/bash
+
+#
+# Update Tavus Guardrails
+#
+# Updates the guardrails configuration on Tavus using the data from config.ts
+# Usage: ./scripts/update-tavus-guardrails.sh
+#
+
+set -e
+
+# Colors for output
+RED='\033[0;31m'
+GREEN='\033[0;32m'
+YELLOW='\033[1;33m'
+NC='\033[0m' # No Color
+
+echo -e "${YELLOW}🛡️ Tavus Guardrails Update Script${NC}\n"
+
+# Load environment variables
+if [ -f .env.local ]; then
+ export $(cat .env.local | grep -v '^#' | xargs)
+fi
+
+# Check required environment variables
+if [ -z "$TAVUS_API_KEY" ]; then
+ echo -e "${RED}❌ Error: TAVUS_API_KEY not found in environment${NC}"
+ echo "Please set TAVUS_API_KEY in .env.local"
+ exit 1
+fi
+
+if [ -z "$NEXT_PUBLIC_TAVUS_LEARNING_CHECK_GUARDRAILS_ID" ]; then
+ echo -e "${RED}❌ Error: NEXT_PUBLIC_TAVUS_LEARNING_CHECK_GUARDRAILS_ID not found in environment${NC}"
+ echo "Please set NEXT_PUBLIC_TAVUS_LEARNING_CHECK_GUARDRAILS_ID in .env.local"
+ exit 1
+fi
+
+GUARDRAILS_ID="$NEXT_PUBLIC_TAVUS_LEARNING_CHECK_GUARDRAILS_ID"
+API_KEY="$TAVUS_API_KEY"
+
+echo "Guardrails ID: $GUARDRAILS_ID"
+echo ""
+
+# Extract guardrails data from config.ts dynamically
+echo -e "${YELLOW}📖 Reading guardrails from src/lib/tavus/config.ts...${NC}"
+SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+GUARDRAILS_DATA=$(node "$SCRIPT_DIR/extract-tavus-config.mjs" guardrails)
+
+if [ -z "$GUARDRAILS_DATA" ]; then
+ echo -e "${RED}❌ Failed to extract guardrails from config.ts${NC}"
+ exit 1
+fi
+
+echo -e "${GREEN}✓ Guardrails loaded successfully${NC}\n"
+
+# Create JSON Patch payload to replace the entire data array
+PATCH_DATA=$(cat </dev/null || echo "$HTTP_BODY"
+else
+ echo -e "${RED}❌ Failed to update guardrails${NC}"
+ echo "HTTP Status: $HTTP_CODE"
+ echo "Response:"
+ echo "$HTTP_BODY" | jq '.' 2>/dev/null || echo "$HTTP_BODY"
+ exit 1
+fi
+
+echo ""
+echo -e "${GREEN}🎉 Done!${NC}"
diff --git a/scripts/update-tavus-objectives.sh b/scripts/update-tavus-objectives.sh
new file mode 100755
index 0000000..67418c4
--- /dev/null
+++ b/scripts/update-tavus-objectives.sh
@@ -0,0 +1,96 @@
+#!/bin/bash
+
+#
+# Update Tavus Objectives
+#
+# Updates the objectives configuration on Tavus using the data from config.ts
+# Usage: ./scripts/update-tavus-objectives.sh
+#
+
+set -e
+
+# Colors for output
+RED='\033[0;31m'
+GREEN='\033[0;32m'
+YELLOW='\033[1;33m'
+NC='\033[0m' # No Color
+
+echo -e "${YELLOW}🎯 Tavus Objectives Update Script${NC}\n"
+
+# Load environment variables
+if [ -f .env.local ]; then
+ export $(cat .env.local | grep -v '^#' | xargs)
+fi
+
+# Check required environment variables
+if [ -z "$TAVUS_API_KEY" ]; then
+ echo -e "${RED}❌ Error: TAVUS_API_KEY not found in environment${NC}"
+ echo "Please set TAVUS_API_KEY in .env.local"
+ exit 1
+fi
+
+if [ -z "$NEXT_PUBLIC_TAVUS_LEARNING_CHECK_OBJECTIVES_ID" ]; then
+ echo -e "${RED}❌ Error: NEXT_PUBLIC_TAVUS_LEARNING_CHECK_OBJECTIVES_ID not found in environment${NC}"
+ echo "Please set NEXT_PUBLIC_TAVUS_LEARNING_CHECK_OBJECTIVES_ID in .env.local"
+ exit 1
+fi
+
+OBJECTIVES_ID="$NEXT_PUBLIC_TAVUS_LEARNING_CHECK_OBJECTIVES_ID"
+API_KEY="$TAVUS_API_KEY"
+
+echo "Objectives ID: $OBJECTIVES_ID"
+echo ""
+
+# Extract objectives data from config.ts dynamically
+echo -e "${YELLOW}📖 Reading objectives from src/lib/tavus/config.ts...${NC}"
+SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+OBJECTIVES_DATA=$(node "$SCRIPT_DIR/extract-tavus-config.mjs" objectives)
+
+if [ -z "$OBJECTIVES_DATA" ]; then
+ echo -e "${RED}❌ Failed to extract objectives from config.ts${NC}"
+ exit 1
+fi
+
+echo -e "${GREEN}✓ Objectives loaded successfully${NC}\n"
+
+# Create JSON Patch payload to replace the entire data array
+PATCH_DATA=$(cat </dev/null || echo "$HTTP_BODY"
+else
+ echo -e "${RED}❌ Failed to update objectives${NC}"
+ echo "HTTP Status: $HTTP_CODE"
+ echo "Response:"
+ echo "$HTTP_BODY" | jq '.' 2>/dev/null || echo "$HTTP_BODY"
+ exit 1
+fi
+
+echo ""
+echo -e "${GREEN}🎉 Done!${NC}"
diff --git a/specs/features/ask-question-tavus.md b/specs/features/ask-question-tavus.md
deleted file mode 100644
index 2539d6c..0000000
--- a/specs/features/ask-question-tavus.md
+++ /dev/null
@@ -1,547 +0,0 @@
-# Feature Specification: Ask a Question (Tavus AI Integration)
-
-## Feature Overview
-
-### Purpose
-Provide learners with an interactive, conversational AI video assistant using Tavus Conversational Video Interface (CVI) for real-time Q&A about course content.
-
-### Goals
-- **Natural Interaction**: Face-to-face video conversations with AI instructor
-- **Context-Aware Support**: Chapter-specific, contextual question answering
-- **Seamless Integration**: Native platform experience with Tavus CVI
-- **Engagement Tracking**: Monitor question patterns and interaction effectiveness
-- **Time Management**: 4-minute session limits for focused Q&A
-
-## User Stories
-
-### As a Learner
-- **US-1**: I want to ask questions about chapter content and receive video responses from an AI instructor
-- **US-2**: I want to see my remaining question time during the conversation
-- **US-3**: I want the AI to understand the context of the current chapter I'm studying
-- **US-4**: I want to easily start and end question sessions from the course interface
-- **US-5**: I want to control my camera/microphone during AI conversations
-
-### As an Instructor/Admin
-- **US-6**: I want to track which questions learners are asking most frequently
-- **US-7**: I want to set appropriate time limits for question sessions
-- **US-8**: I want to configure different AI personas for different course topics
-
-## Technical Requirements
-
-### Next.js 15+ Compliance
-- **Server Components First**: Conversation triggers and metadata
-- **Client Components**: Tavus CVI integration and interactive elements only
-- **API Routes**: Next.js API routes for Tavus conversation management
-
-### Tavus Integration Strategy
-
-**Recommended Approach**: `@tavus/cvi-ui` Component Library
-
-**Rationale**:
-- Pre-built React components with TypeScript support
-- Full UI control (matches shadcn/ui design system)
-- Built-in device management and state handling
-- Production-ready with error handling
-
-**Documentation**: https://docs.tavus.io/sections/integrations/embedding-cvi.md
-
-### Installation & Setup
-
-```bash
-# Initialize Tavus CVI
-npx @tavus/cvi-ui@latest init
-
-# Add conversation component
-npx @tavus/cvi-ui@latest add conversation
-```
-
-**Dependencies**: `@daily-co/daily-react`, `@daily-co/daily-js`, `jotai`
-
-### Environment Variables
-
-```bash
-TAVUS_API_KEY=required
-TAVUS_REPLICA_ID=required
-TAVUS_PERSONA_ID=optional
-TAVUS_DEFAULT_CALL_DURATION=240 # Configurable time limit (seconds)
-```
-
-**Time Limit Configuration**:
-- Default: 240 seconds (4 minutes)
-- Easily configurable via environment variable
-- No code changes required to adjust duration
-
-## Component Architecture
-
-### Component Structure
-
-```
-src/
-├── app/
-│ └── api/
-│ └── tavus/
-│ ├── conversation/route.ts # Create Tavus conversation
-│ └── analytics/route.ts # Track conversation analytics
-├── components/
-│ ├── cvi/ # Tavus CVI library (auto-generated)
-│ │ ├── components/
-│ │ │ ├── cvi-provider.tsx # CVIProvider from Tavus
-│ │ │ └── conversation.tsx # Conversation component from Tavus
-│ │ └── hooks/
-│ │ └── use-conversation.ts # CVI hooks from Tavus
-│ └── course/
-│ └── chapter-content/
-│ ├── ask-question.tsx # Main Q&A dialog (UPDATE)
-│ └── tavus-conversation.tsx # Tavus wrapper (NEW)
-├── lib/
-│ ├── services/
-│ │ ├── tavus-service.ts # Tavus API (hot-swappable)
-│ │ └── conversation-analytics.ts # Analytics (hot-swappable)
-│ └── utils/
-│ ├── tavus-config.ts # Configuration
-│ └── conversation-helpers.ts # Helper functions
-└── types/
- └── tavus.ts # Tavus types
-```
-
-### Component Props Interface
-
-```typescript
-interface AskQuestionProps {
- chapterTitle: string;
- chapterId: string;
- courseId: string;
- timeLimit?: number; // Default: 240 seconds
- personaId?: string; // Optional custom AI persona
- onConversationEnd?: (duration: number, questionCount: number) => void;
-}
-```
-
-## Implementation Details
-
-### 1. Root Provider Setup
-
-**File**: `src/app/layout.tsx` or `src/providers/index.tsx`
-
-```typescript
-import { CVIProvider } from '@/components/cvi/components/cvi-provider';
-
-export default function RootLayout({ children }) {
- return (
-
- {children}
-
- );
-}
-```
-
-### 2. API Route: Create Conversation
-
-**File**: `src/app/api/tavus/conversation/route.ts`
-
-**Endpoint**: `POST /api/tavus/conversation`
-
-**Request Body**:
-```json
-{
- "chapterId": "ch-123",
- "courseId": "course-456",
- "chapterTitle": "EMDR Foundations",
- "timeLimit": 240
-}
-```
-
-**Response**:
-```json
-{
- "conversationUrl": "https://tavus.io/...",
- "conversationId": "conv-xyz",
- "expiresAt": "2024-..."
-}
-```
-
-**Tavus API Call**:
-- Endpoint: `https://tavusapi.com/v2/conversations`
-- Method: POST
-- Headers: `x-api-key: process.env.TAVUS_API_KEY`
-- Body: `replica_id`, `persona_id`, `conversational_context`, `max_call_duration`
-
-### 3. Conversation Wrapper Component
-
-**File**: `src/components/course/chapter-content/tavus-conversation.tsx`
-
-**Key Features**:
-- Fetches conversation URL from API
-- Loading state with spinner
-- Error handling with retry
-- Wraps Tavus `` component
-- Handles conversation lifecycle events
-
-### 4. Updated Ask Question Dialog
-
-**File**: `src/components/course/chapter-content/ask-question.tsx`
-
-**Enhancements**:
-- Integrates TavusConversation component
-- Adds CountdownTimer at top of dialog
-- Tracks conversation start/end times
-- Full-screen dialog (max-w-5xl, h-[90vh])
-- Camera/microphone permission prompts
-
-### 5. Conversational Context (Mock Data Approach)
-
-**Strategy**: Enhance existing mock-data.ts with conversational context fields
-
-**Mock Data Enhancement**:
-```typescript
-// src/lib/mock-data.ts
-interface Chapter {
- id: string;
- title: string;
- learningObjectives: string[];
- // NEW: Conversational context fields
- conversationalContext?: {
- instructorTone: 'professional' | 'conversational' | 'encouraging';
- keyConcepts: string[];
- responseLength: 'brief' | 'moderate' | 'detailed';
- customInstructions?: string;
- };
-}
-```
-
-**Generated Context String**:
-```typescript
-const context = `
-You are an expert EMDR therapy instructor.
-Current Chapter: ${chapter.title}
-
-Learning Objectives:
-${chapter.learningObjectives.map(obj => `- ${obj}`).join('\n')}
-
-Key Concepts: ${chapter.conversationalContext?.keyConcepts.join(', ')}
-
-Instruction Style:
-- Tone: ${chapter.conversationalContext?.instructorTone || 'conversational'}
-- Response Length: ${chapter.conversationalContext?.responseLength || 'moderate'} (30-45 seconds)
-- ${chapter.conversationalContext?.customInstructions || 'Use simple language with concrete examples'}
-`;
-```
-
-**Migration Path**: When database is ready, these fields map directly to database columns
-
-**Benefits**:
-- ✅ Minimal effort (extends existing mock structure)
-- ✅ Easy to test different contexts
-- ✅ Clean migration to DB
-- ✅ No new patterns or complexity
-
-## Analytics & Tracking
-
-### Conversation Metrics
-
-```typescript
-interface ConversationAnalytics {
- conversationId: string;
- chapterId: string;
- courseId: string;
- userId: string;
- startedAt: Date;
- endedAt: Date;
- duration: number; // seconds
- questionCount: number;
- networkQuality: 'poor' | 'fair' | 'good' | 'excellent';
- deviceIssues: string[];
-}
-```
-
-### Tracked Events
-- Conversation started
-- Conversation ended (manual/timeout)
-- Question asked (if API supports)
-- Device permission denied
-- Network quality changes
-- Errors encountered
-
-## Error Handling
-
-### Common Scenarios
-
-1. **Camera/Microphone Permission Denied**
- - Display clear permission instructions
- - Show browser-specific guidance
- - Provide "Allow Permissions" button
-
-2. **Network Issues**
- - Connection quality indicator
- - Auto-reconnect with exponential backoff
- - "Reconnecting..." state display
-
-3. **Tavus API Errors**
- - Log to monitoring service
- - User-friendly error messages
- - "Try Again" action button
-
-4. **Time Limit Exceeded**
- - 15-second warning countdown
- - Graceful conversation end
- - Save conversation state
-
-## Acceptance Criteria
-
-### Core Functionality
-- [ ] **AC-1**: Ask Question button opens dialog with Tavus conversation interface
-- [ ] **AC-2**: Conversation URL generated with proper chapter context
-- [ ] **AC-3**: AI responses are contextually relevant to current chapter
-- [ ] **AC-4**: Timer displays accurate countdown during conversation
-- [ ] **AC-5**: Conversation ends gracefully when time limit reached
-- [ ] **AC-6**: User can manually end conversation at any time
-- [ ] **AC-7**: Camera and microphone controls work correctly
-
-### Tavus CVI Integration
-- [ ] **AC-8**: @tavus/cvi-ui library properly initialized
-- [ ] **AC-9**: CVIProvider wraps application at root level
-- [ ] **AC-10**: Conversation component renders without errors
-- [ ] **AC-11**: Video/audio quality acceptable (min 720p, clear audio)
-- [ ] **AC-12**: Device management (camera/mic selection) works
-
-### User Experience
-- [ ] **AC-13**: Loading state shows while conversation initializes
-- [ ] **AC-14**: Error states display clear, actionable messages
-- [ ] **AC-15**: Dialog responsive on desktop/tablet (min 1024px width)
-- [ ] **AC-16**: UI matches LMS design system (shadcn/ui)
-- [ ] **AC-17**: Time warnings provide adequate notice
-
-### Analytics & Tracking
-- [ ] **AC-18**: Conversation duration tracked accurately
-- [ ] **AC-19**: Question count logged (if API supports)
-- [ ] **AC-20**: Analytics data sent to backend
-- [ ] **AC-21**: Failed conversations logged for debugging
-
-### Performance
-- [ ] **AC-22**: Conversation initializes in < 5 seconds
-- [ ] **AC-23**: Video latency < 500ms
-- [ ] **AC-24**: Memory stable during 4-minute session
-- [ ] **AC-25**: No memory leaks after closing dialog
-
-### Accessibility
-- [ ] **AC-26**: Keyboard navigation works throughout
-- [ ] **AC-27**: Screen readers announce state changes
-- [ ] **AC-28**: Color contrast meets WCAG AA standards
-- [ ] **AC-29**: Focus management correct (trapped in dialog)
-
-## Testing Strategy
-
-### Unit Tests
-- Component rendering (AskQuestion, TavusConversation)
-- API route logic (conversation creation, error handling)
-- Context generation (chapter context formatting)
-- Analytics tracking (metric calculation)
-
-### Integration Tests
-- Tavus API integration (mock API responses)
-- Timer integration (countdown behavior)
-- Dialog lifecycle (open, close, flow)
-- Error scenarios (permissions, network, API failures)
-
-### Manual Testing
-- Device compatibility (different cameras/microphones)
-- Browser compatibility (Chrome, Safari, Firefox, Edge)
-- Network conditions (Slow 3G, 4G, WiFi)
-- Time limit scenarios (normal end, forced end, early exit)
-
-### User Acceptance Tests
-- Conversation quality (AI response relevance)
-- User experience (ease of use, intuitiveness)
-- Performance (video quality, responsiveness)
-- Accessibility (screen reader, keyboard navigation)
-
-## MoSCoW Prioritization
-
-### Must Have (MVP)
-- ✅ Basic Tavus conversation integration
-- ✅ Chapter context injection
-- ✅ Timer with 4-minute limit
-- ✅ Dialog UI with open/close
-- ✅ Camera/microphone controls
-- ✅ Basic error handling
-- ✅ Conversation duration tracking
-
-### Should Have (Post-MVP)
-- 🔄 Advanced analytics (question transcription)
-- 🔄 User satisfaction rating
-- 🔄 HairCheck pre-call device testing
-- 🔄 Network quality indicators
-- 🔄 Extended time option (admin configurable)
-
-### Could Have (Future)
-- 💡 Text-based fallback (if camera unavailable)
-- 💡 Conversation history and playback
-- 💡 Multi-language support
-- 💡 Screen sharing for visual explanations
-- 💡 AI persona customization per course
-
-### Won't Have (Out of Scope)
-- ❌ Group conversations (multiple learners)
-- ❌ Live instructor override
-- ❌ Video recording download
-- ❌ Custom AI training on course content
-
-## Dependencies
-
-### Internal Dependencies
-- Timer System (CountdownTimer component)
-- Dialog Component (shadcn/ui Dialog)
-- User Authentication (track user questions)
-- Course Data (chapter context)
-
-### External Dependencies
-- Tavus API (conversation creation/management)
-- @tavus/cvi-ui (React component library)
-- @daily-co/daily-react (video infrastructure)
-- @daily-co/daily-js (Daily.co SDK)
-- jotai (state management)
-
-### Environment Variables Required
-```bash
-TAVUS_API_KEY=required
-TAVUS_REPLICA_ID=required
-TAVUS_PERSONA_ID=optional
-TAVUS_DEFAULT_CALL_DURATION=240 # Configurable (seconds)
-```
-
-## Implementation Strategy
-
-### Stacked PR Approach (Following Release Strategy)
-
-This feature uses **stacked PRs** maintaining 200-400 LOC per PR:
-
-#### Phase 1: Tavus CVI Setup & API Routes
-**Branch**: `feature/ask-question-01-setup`
-**Size**: ~200-250 LOC
-**Files**:
-- Initialize: `npx @tavus/cvi-ui@latest init`
-- Add component: `npx @tavus/cvi-ui@latest add conversation`
-- `src/app/api/tavus/conversation/route.ts`
-- `src/types/tavus.ts`
-- `.env.local.example` (include TAVUS_DEFAULT_CALL_DURATION)
-- Update README with setup instructions
-- Update mock-data.ts with conversationalContext fields
-
-**Investigation Items**:
-- 🔍 **HairCheck Research**: Determine if HairCheck is included with Conversation component or requires separate installation
- - Test: `npx @tavus/cvi-ui@latest add haircheck`
- - Document: Effort required (LOC impact)
- - Decision: Include in MVP or defer to post-MVP
- - Report findings in Phase 1 PR description
-
-**Dependencies**: None
-**Testing**: API route unit tests, mocked Tavus integration
-
-#### Phase 2: Core Conversation Component
-**Branch**: `feature/ask-question-02-conversation` (depends on Phase 1)
-**Size**: ~300-350 LOC
-**Files**:
-- `src/components/course/chapter-content/tavus-conversation.tsx`
-- `src/lib/utils/tavus-config.ts`
-- `src/lib/utils/conversation-helpers.ts`
-- `src/lib/services/tavus-service.ts` (hot-swappable)
-- Component unit tests
-
-**Dependencies**: Phase 1
-**Testing**: Component rendering, loading/error states
-
-#### Phase 3: Dialog Integration & UI
-**Branch**: `feature/ask-question-03-dialog` (depends on Phase 2)
-**Size**: ~250-300 LOC
-**Files**:
-- Update `src/components/course/chapter-content/ask-question.tsx`
-- Timer integration (CountdownTimer)
-- Dialog styling and responsive layout
-- Integration tests
-
-**Dependencies**: Phase 2, Timer System
-**Testing**: Dialog lifecycle, timer integration, UI responsiveness
-
-#### Phase 4: Analytics & Polish
-**Branch**: `feature/ask-question-04-analytics` (depends on Phase 3)
-**Size**: ~200-250 LOC
-**Files**:
-- `src/app/api/tavus/analytics/route.ts`
-- `src/lib/services/conversation-analytics.ts` (hot-swappable)
-- Analytics tracking implementation
-- Error handling improvements
-- Accessibility enhancements
-- Final documentation
-
-**Dependencies**: Phase 3
-**Testing**: Analytics tracking, error scenarios, accessibility
-
-### PR Dependencies
-```
-Phase 1 (Setup) → Phase 2 (Conversation) → Phase 3 (Dialog) → Phase 4 (Analytics)
-```
-
-### Phase Approval Process
-
-**Following Branch Readiness Protocol**:
-
-For each phase:
-1. ✅ Complete Phase N implementation
-2. ✅ Submit PR for review
-3. ⏸️ **WAIT for user review and approval**
-4. ✅ Merge Phase N after approval
-5. ❓ **ASK**: "Ready to create the branch and start Phase N+1 development? 🚀"
-6. ⏸️ **WAIT for explicit user approval**
-7. ✅ Only then proceed to Phase N+1
-
-**User Controls**:
-- Review and approve each phase independently
-- Adjust requirements between phases
-- Control development pace
-- Request changes before next phase
-
-**Phase 1 Special Note**: HairCheck investigation results will be presented in Phase 1 PR for decision on MVP inclusion.
-
-### Quality Gates
-Each PR must pass:
-- ✅ ESLint strict validation (0 errors, 0 warnings)
-- ✅ TypeScript compilation (strict mode)
-- ✅ Build verification (no build errors)
-- ✅ Code review (1-2 reviewers)
-- ✅ Tavus API key validation (dev environment)
-
-## Cost Considerations
-
-### Tavus Pricing
-- Check [Tavus Pricing](https://www.tavus.io/pricing)
-- Estimated: ~4 minutes per session
-- Calculate based on expected learner count
-
-### Recommendations
-1. Monitor conversation usage monthly
-2. Set max concurrent conversations limit
-3. Implement conversation queueing if needed
-4. Consider caching common responses (future)
-
-## Future Enhancements
-
-### Phase 2 Features
-- HairCheck pre-call device testing
-- Admin-configurable time extensions
-- Question history viewer
-- Post-conversation satisfaction ratings
-
-### Phase 3 Features
-- Text chat fallback (if video unavailable)
-- Question topic clustering analytics
-- AI persona library (multiple instructors)
-- Conversation bookmarks (save moments)
-
----
-
-**Implementation Priority**: High - Core learning engagement feature for MVP
-
-**Estimated Effort**: 3-4 days (including Tavus setup and testing)
-
-**Risk Level**: Medium (depends on Tavus API reliability and video quality)
-
diff --git a/specs/features/learning-check/README.md b/specs/features/learning-check/README.md
new file mode 100644
index 0000000..7f5b7e4
--- /dev/null
+++ b/specs/features/learning-check/README.md
@@ -0,0 +1,50 @@
+# Learning Check Feature Documentation
+
+This directory contains the split specification for the Learning Check (Chapter-End Conversational Assessment) feature.
+
+## Documentation Structure
+
+### 📄 [learning-check-spec.md](./learning-check-spec.md)
+**Product Requirements Document** (~400 lines)
+- Overview & Goals
+- User Stories
+- Functional Requirements (1-9)
+- MoSCoW Prioritization
+- Success Metrics
+- Open Questions
+- Risk Mitigation
+
+**Audience**: Product Managers, Designers, Stakeholders
+
+### 🔧 [learning-check-implementation.md](./learning-check-implementation.md)
+**Technical Implementation Guide** (~400 lines)
+- Perception Analysis Integration
+- Webhook Setup & Configuration
+- Data Structures & Interfaces
+- Technical Requirements
+- Implementation Phases (1-5)
+- Acceptance Criteria
+- Code Examples
+
+**Audience**: Developers, Technical Leads
+
+---
+
+## Quick Links
+
+- **Start Here**: [Product Spec](./learning-check-spec.md) for requirements overview
+- **Implementation**: [Technical Guide](./learning-check-implementation.md) for development details
+- **Original Spec**: [learning-check-tavus.md](../learning-check-tavus.md) (deprecated, kept for reference)
+
+---
+
+## Feature Overview
+
+**Learning Check** is a 4-minute conversational assessment using Tavus CVI with AI avatar instructor that:
+- Validates comprehension through natural dialogue
+- Tracks audio + visual engagement (≥50% threshold)
+- Uses Raven perception analysis for holistic assessment
+- Provides transcripts and rubric scoring for instructors
+
+**Timeline**: 7-10 days (MVP through Phase 2)
+**Priority**: High - Core MVP feature
diff --git a/specs/features/learning-check/TESTING.md b/specs/features/learning-check/TESTING.md
new file mode 100644
index 0000000..88f3077
--- /dev/null
+++ b/specs/features/learning-check/TESTING.md
@@ -0,0 +1,410 @@
+# Learning Check - Phase 1 Testing Guide
+
+## Setup Instructions
+
+### 1. Environment Variables
+Add to your `.env.local`:
+
+```bash
+# Required: Your Tavus API credentials
+TAVUS_API_KEY=your_tavus_api_key_here
+TAVUS_PERSONA_ID=your_persona_id_here
+
+# Optional: Configuration
+TAVUS_LEARNING_CHECK_DURATION=180
+TAVUS_MAX_CONCURRENT_SESSIONS=10
+```
+
+**Get your credentials**:
+1. Go to https://platform.tavus.io/api-keys
+2. Create an API key
+3. Go to https://platform.tavus.io/personas
+4. Copy your "8p3p - AI Instructor Assistant" persona ID
+
+### 2. Start Development Server
+```bash
+npm run dev
+```
+
+### 3. Open Browser Console
+Press `F12` or `Cmd+Option+I` to open DevTools and view the Console tab.
+
+---
+
+## Test Scenarios
+
+### Scenario 1: Locked State (Quiz Not Passed)
+**Goal**: Verify Learning Check is locked when quiz not passed
+
+**Steps**:
+1. Navigate to a chapter with a quiz
+2. Don't complete the quiz (or fail it)
+3. Scroll to Learning Check section
+
+**Expected**:
+- ✅ Shows "Learning Check Locked" card
+- ✅ Displays lock icon
+- ✅ Shows message about needing to pass quiz
+- ✅ Shows current quiz score if available
+
+**Console Output**:
+```
+📊 Analytics: lc_blocked_not_passed
+{
+ chapterId: "ch-1",
+ userId: "user-123",
+ quizScore: 60
+}
+```
+
+---
+
+### Scenario 2: Ready State (Quiz Passed)
+**Goal**: Verify Learning Check is accessible after passing quiz
+
+**Steps**:
+1. Complete and pass the chapter quiz (≥70%)
+2. Scroll to Learning Check section
+
+**Expected**:
+- ✅ Shows "Learning Check — [Chapter Title]" card
+- ✅ Displays "Start Learning Check" button
+- ✅ Shows feature description and requirements
+- ✅ Lists what to expect (3 minutes, questions, engagement threshold)
+
+---
+
+### Scenario 3: Start Conversation
+**Goal**: Verify Tavus conversation creation and initialization
+
+**Steps**:
+1. From Ready state, click "Start Learning Check"
+2. Allow camera and microphone permissions when prompted
+
+**Expected**:
+- ✅ Button shows "Starting..." loading state
+- ✅ Conversation loads with AI avatar
+- ✅ Timer starts counting down from 4:00
+- ✅ Engagement progress bar appears (0s / 120s)
+- ✅ Hair Check component appears (if Tavus configured)
+
+**Console Output**:
+```
+📊 Analytics: lc_started
+{
+ chapterId: "ch-1",
+ userId: "user-123",
+ timestamp: "2025-01-29T..."
+}
+```
+
+---
+
+### Scenario 4: Active Conversation
+**Goal**: Verify engagement tracking and timer functionality
+
+**Steps**:
+1. Start a conversation
+2. Speak with the AI avatar for 2+ minutes
+3. Watch engagement progress bar
+
+**Expected**:
+- ✅ Timer counts down: 4:00 → 3:59 → ... → 0:00
+- ✅ Engagement time increments (mock: +1s every 2 seconds)
+- ✅ Progress bar fills up toward 120s threshold
+- ✅ Threshold indicator shows "✓" when ≥120s reached
+- ✅ "End Session" button always available
+
+**Console Monitoring**:
+- Watch engagement time increment in component state
+- No errors in console
+
+---
+
+### Scenario 5: Timer Expiration (Threshold Met)
+**Goal**: Verify automatic termination at 4:00 with sufficient engagement
+
+**Steps**:
+1. Start conversation
+2. Let timer run to 0:00 (or wait ~2 minutes for mock engagement to reach 120s)
+
+**Expected**:
+- ✅ Conversation automatically terminates
+- ✅ Shows "Session Complete" card with green checkmark
+- ✅ Displays engagement stats (e.g., "156s / 120s")
+- ✅ Shows "Mark Learning Check Complete" button
+- ✅ Progress bar shows completion
+
+**Console Output**:
+```
+📊 Analytics: lc_timeout
+{
+ chapterId: "ch-1",
+ userId: "user-123",
+ engagementTime: 156,
+ thresholdMet: true
+}
+
+💾 Learning Check Data:
+{
+ chapterId: "ch-1",
+ userId: "user-123",
+ conversationId: "conv_abc123",
+ startedAt: "2025-01-29T...",
+ endedAt: "2025-01-29T...",
+ duration: 240,
+ engagementTime: 156,
+ engagementPercent: 65,
+ completed: false,
+ transcript: "",
+ endReason: "timeout",
+ thresholdMet: true
+}
+
+✅ Conversation terminated: conv_abc123
+```
+
+---
+
+### Scenario 6: Timer Expiration (Threshold NOT Met)
+**Goal**: Verify handling when engagement threshold not reached
+
+**Steps**:
+1. Start conversation
+2. Immediately click "End Session" (before reaching 120s)
+
+**Expected**:
+- ✅ Shows "Engagement Threshold Not Met" card with warning icon
+- ✅ Displays engagement stats (e.g., "45s / 120s")
+- ✅ Shows error message explaining need for 120s
+- ✅ Shows "Try Again" button
+- ✅ No "Mark Complete" button
+
+**Console Output**:
+```
+📊 Analytics: lc_user_end
+{
+ chapterId: "ch-1",
+ userId: "user-123",
+ engagementTime: 45,
+ thresholdMet: false
+}
+
+💾 Learning Check Data:
+{
+ ...
+ engagementTime: 45,
+ engagementPercent: 19,
+ completed: false,
+ endReason: "manual",
+ thresholdMet: false
+}
+```
+
+---
+
+### Scenario 7: Manual End Session
+**Goal**: Verify manual termination works correctly
+
+**Steps**:
+1. Start conversation
+2. After 2+ minutes, click "End Session" button
+
+**Expected**:
+- ✅ Conversation terminates immediately
+- ✅ Shows appropriate end state (complete/incomplete based on engagement)
+- ✅ Logs termination analytics
+
+**Console Output**:
+```
+📊 Analytics: lc_terminated
+{
+ chapterId: "ch-1",
+ conversationId: "conv_abc123",
+ reason: "manual",
+ engagementTime: 130
+}
+
+✅ Conversation terminated: conv_abc123
+```
+
+---
+
+### Scenario 8: Mark Complete
+**Goal**: Verify completion flow
+
+**Steps**:
+1. Complete conversation with ≥120s engagement
+2. Click "Mark Learning Check Complete"
+
+**Expected**:
+- ✅ Shows "Learning Check Completed" card with green checkmark
+- ✅ Displays success message
+- ✅ Calls `onComplete` callback (if provided)
+
+**Console Output**:
+```
+📊 Analytics: lc_completed
+{
+ chapterId: "ch-1",
+ userId: "user-123",
+ engagementTime: 156,
+ engagementPercent: 65
+}
+
+💾 Learning Check Data:
+{
+ ...
+ completed: true,
+ endReason: "completed"
+}
+```
+
+---
+
+### Scenario 9: Page Navigation (Termination)
+**Goal**: Verify conversation terminates when user navigates away
+
+**Steps**:
+1. Start conversation
+2. Click browser back button or navigate to different page
+
+**Expected**:
+- ✅ Browser shows "Are you sure you want to leave?" confirmation
+- ✅ If confirmed, conversation terminates via `sendBeacon`
+- ✅ No errors in console
+
+**Console Output**:
+```
+📊 Analytics: lc_terminated
+{
+ chapterId: "ch-1",
+ conversationId: "conv_abc123",
+ reason: "component_unmount"
+}
+```
+
+---
+
+### Scenario 10: Tab Close (Termination)
+**Goal**: Verify conversation terminates when tab/window closes
+
+**Steps**:
+1. Start conversation
+2. Close browser tab
+
+**Expected**:
+- ✅ Browser shows "Are you sure?" confirmation
+- ✅ Conversation terminates via `beforeunload` handler
+- ✅ Uses `sendBeacon` for reliability
+
+---
+
+## Error Scenarios
+
+### Error 1: Missing Environment Variables
+**Steps**:
+1. Remove `TAVUS_API_KEY` from `.env.local`
+2. Try to start conversation
+
+**Expected**:
+- ✅ Shows error message: "Tavus configuration missing"
+- ✅ Console error with details
+
+### Error 2: Invalid API Key
+**Steps**:
+1. Set invalid `TAVUS_API_KEY`
+2. Try to start conversation
+
+**Expected**:
+- ✅ Shows error message: "Failed to start session"
+- ✅ Console error with Tavus API response
+
+### Error 3: Network Failure
+**Steps**:
+1. Disable network in DevTools
+2. Try to start conversation
+
+**Expected**:
+- ✅ Shows error message
+- ✅ Graceful error handling (no crashes)
+
+---
+
+## Success Criteria
+
+### Phase 1 MVP Complete When:
+- ✅ All 10 test scenarios pass
+- ✅ Console logging shows complete data capture
+- ✅ No TypeScript errors
+- ✅ No ESLint errors
+- ✅ Conversation terminates reliably on all triggers
+- ✅ Engagement tracking works (even if mocked)
+- ✅ Timer enforces 4-minute hard stop
+- ✅ Quiz-gating works correctly
+
+---
+
+## Next Steps (Phase 2)
+
+After Phase 1 testing complete:
+1. Set up ngrok for webhook testing
+2. Configure perception queries in Tavus dashboard
+3. Create perception webhook endpoint
+4. Test perception analysis data capture
+5. Verify enriched console logging with visual engagement
+
+---
+
+## Troubleshooting
+
+### Issue: Conversation doesn't start
+**Check**:
+- Environment variables set correctly
+- Tavus API key valid
+- Persona ID exists in Tavus dashboard
+- Network connectivity
+- Browser console for errors
+
+### Issue: Engagement not tracking
+**Note**: Phase 1 uses mock engagement (increments every 2 seconds)
+- This is expected behavior
+- Phase 2 will use real Daily.co audio levels
+
+### Issue: Timer doesn't stop at 0:00
+**Check**:
+- `onExpire` callback firing
+- `handleTimerExpire` function called
+- Console for termination logs
+
+### Issue: Conversation doesn't terminate
+**Check**:
+- `/api/learning-checks/terminate` endpoint working
+- Tavus API responding
+- Console for termination errors
+- Network tab in DevTools
+
+---
+
+## Demo Preparation
+
+### Before Demo:
+1. ✅ Test all scenarios
+2. ✅ Clear browser console
+3. ✅ Have valid Tavus credentials
+4. ✅ Prepare chapter with passed quiz
+5. ✅ Open DevTools console for data visibility
+
+### During Demo:
+1. Show locked state (quiz not passed)
+2. Pass quiz
+3. Show ready state
+4. Start conversation
+5. Show engagement tracking
+6. Let timer run or manually end
+7. Show completion data in console
+8. Explain Phase 2 enhancements (perception, persistence)
+
+---
+
+**Questions?** See [Implementation Guide](./learning-check-implementation.md) for technical details.
diff --git a/specs/features/learning-check/learning-check-implementation.md b/specs/features/learning-check/learning-check-implementation.md
new file mode 100644
index 0000000..0ba454d
--- /dev/null
+++ b/specs/features/learning-check/learning-check-implementation.md
@@ -0,0 +1,701 @@
+# Learning Check — Technical Implementation Guide
+
+> **Technical Documentation**
+> For product requirements, see [Feature Spec](./learning-check-spec.md)
+
+---
+
+## Table of Contents
+
+1. [Perception Analysis Integration](#perception-analysis-integration)
+2. [Webhook Setup & Configuration](#webhook-setup--configuration)
+3. [Data Structures](#data-structures)
+4. [Technical Requirements](#technical-requirements)
+5. [Implementation Phases](#implementation-phases)
+6. [Acceptance Criteria](#acceptance-criteria)
+
+---
+
+## Perception Analysis Integration (Tavus Raven)
+
+**Purpose**: Enhance learning check assessment with visual engagement data
+
+### Perception Queries Configuration
+
+Configure in Tavus dashboard persona:
+
+**Perception Analysis Queries** (End-of-call summary):
+
+```json
+"perception_analysis_queries": [
+ "On a scale of 1-100, how often was the learner looking at the screen during the conversation?",
+ "What was the learner's overall engagement level? (e.g., attentive, distracted, thoughtful, confused)",
+ "Were there any visual indicators of comprehension struggles? (e.g., confusion, frustration)",
+ "Did the learner appear to be taking notes or referencing materials?",
+ "Was there any indication of multiple people present or distractions in the environment?",
+ "How would you rate the learner's body language and facial expressions? (e.g., engaged, neutral, disengaged)"
+]
+```
+
+**Rationale**:
+
+- **Screen Gaze**: Measures visual attention and focus
+- **Engagement Level**: Holistic assessment of learner presence
+- **Comprehension Indicators**: Identifies when learner struggles
+- **Note-Taking**: Positive signal of active learning
+- **Distractions**: Environmental factors affecting performance
+- **Body Language**: Non-verbal communication cues
+
+---
+
+## Webhook Setup & Configuration
+
+**Critical**: Webhooks are required for perception analysis and conversation lifecycle tracking
+
+### Webhook URL Generation
+
+**Development Environment**:
+
+```bash
+# Step 1: Generate webhook secret (store in .env.local)
+node -e "console.log('TAVUS_WEBHOOK_SECRET=' + require('crypto').randomBytes(32).toString('hex'))"
+# Output: TAVUS_WEBHOOK_SECRET=abc123def456...
+
+# Step 2: Expose local development server using ngrok
+npx ngrok http 3000
+# Output: https://abc123.ngrok.io -> http://localhost:3000
+
+# Step 3: Set webhook URL in environment
+TAVUS_WEBHOOK_URL=https://abc123.ngrok.io/api/learning-checks/perception-analysis
+```
+
+**Production Environment**:
+
+```bash
+# Use your deployed domain
+TAVUS_WEBHOOK_URL=https://your-app.vercel.app/api/learning-checks/perception-analysis
+```
+
+### Webhook Registration
+
+**Method**: Pass `callback_url` parameter when creating conversation (recommended)
+
+```typescript
+// In conversation creation API call
+const conversationResponse = await fetch(
+ "https://tavusapi.com/v2/conversations",
+ {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ "x-api-key": process.env.TAVUS_API_KEY!,
+ },
+ body: JSON.stringify({
+ replica_id: replicaId, // from persona configuration
+ persona_id: process.env.TAVUS_PERSONA_ID!,
+ callback_url: process.env.TAVUS_WEBHOOK_URL!, // Register webhook
+ conversational_context: chapterContext,
+ max_call_duration: 240,
+ }),
+ }
+);
+```
+
+### Webhook Endpoint Implementation
+
+**File**: `src/app/api/learning-checks/perception-analysis/route.ts`
+
+```typescript
+import { NextRequest, NextResponse } from "next/server";
+import crypto from "crypto";
+
+export async function POST(request: NextRequest) {
+ try {
+ // Step 1: Verify webhook signature (security)
+ const signature = request.headers.get("x-tavus-signature");
+ const payload = await request.text();
+
+ if (!verifyWebhookSignature(payload, signature)) {
+ return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
+ }
+
+ // Step 2: Parse webhook payload
+ const webhookData = JSON.parse(payload);
+ const { event_type, conversation_id, properties } = webhookData;
+
+ // Step 3: Handle different event types
+ switch (event_type) {
+ case "application.perception_analysis":
+ await handlePerceptionAnalysis(conversation_id, properties.analysis);
+ break;
+ case "application.transcription_ready":
+ await handleTranscriptionReady(conversation_id, properties.transcript);
+ break;
+ case "system.shutdown":
+ await handleConversationEnd(
+ conversation_id,
+ properties.shutdown_reason
+ );
+ break;
+ default:
+ console.log(`Unhandled event type: ${event_type}`);
+ }
+
+ return NextResponse.json({ status: "success" });
+ } catch (error) {
+ console.error("Webhook processing error:", error);
+ return NextResponse.json(
+ { error: "Internal server error" },
+ { status: 500 }
+ );
+ }
+}
+
+function verifyWebhookSignature(
+ payload: string,
+ signature: string | null
+): boolean {
+ if (!signature || !process.env.TAVUS_WEBHOOK_SECRET) return false;
+
+ const expectedSignature = crypto
+ .createHmac("sha256", process.env.TAVUS_WEBHOOK_SECRET)
+ .update(payload)
+ .digest("hex");
+
+ return signature === `sha256=${expectedSignature}`;
+}
+```
+
+### Webhook Event Types
+
+**System Events** (conversation lifecycle):
+
+- `system.replica_joined`: AI avatar ready for conversation
+- `system.shutdown`: Conversation ended (various reasons)
+
+**Application Events** (data ready):
+
+- `application.transcription_ready`: Full conversation transcript available
+- `application.perception_analysis`: Visual engagement analysis complete
+- `application.recording_ready`: Video recording available (if enabled)
+
+### Webhook Payload Structure
+
+**Perception Analysis Event**:
+
+```json
+{
+ "properties": {
+ "analysis": "Here's a summary of visual observations:\n\n* **Learner Gaze:** The learner was looking at the screen approximately 85% of the time...\n* **Engagement Level:** The learner appeared attentive and engaged throughout...\n* **Comprehension Indicators:** No significant signs of confusion were observed...\n* **Note-Taking:** The learner was observed taking notes during key explanations...\n* **Distractions:** No major environmental distractions were detected...\n* **Body Language:** The learner maintained positive body language with frequent nodding..."
+ },
+ "conversation_id": "",
+ "webhook_url": "",
+ "message_type": "application",
+ "event_type": "application.perception_analysis",
+ "timestamp": "2025-07-11T09:13:35.361736Z"
+}
+```
+
+**Transcription Ready Event**:
+
+```json
+{
+ "properties": {
+ "replica_id": "",
+ "transcript": [
+ {
+ "role": "user",
+ "content": "Can you explain what bilateral stimulation means?"
+ },
+ {
+ "role": "assistant",
+ "content": "Bilateral stimulation refers to..."
+ }
+ ]
+ },
+ "conversation_id": "",
+ "webhook_url": "",
+ "event_type": "application.transcription_ready",
+ "message_type": "application",
+ "timestamp": "2025-07-11T09:13:35.361736Z"
+}
+```
+
+### Error Handling & Reliability
+
+**Webhook Delivery**:
+
+- Tavus retries failed webhooks with exponential backoff
+- Implement idempotency using `conversation_id` + `event_type` as key
+- Return 200 status for successful processing
+- Return 4xx/5xx for errors (triggers Tavus retry)
+
+**Timeout Handling**:
+
+```typescript
+// Set 30-second timeout for webhook processing
+const WEBHOOK_TIMEOUT = 30000;
+
+const timeoutPromise = new Promise((_, reject) => {
+ setTimeout(() => reject(new Error("Webhook timeout")), WEBHOOK_TIMEOUT);
+});
+
+const processingPromise = processWebhookData(webhookData);
+
+try {
+ await Promise.race([processingPromise, timeoutPromise]);
+} catch (error) {
+ // Log error and return 500 (Tavus will retry)
+ console.error("Webhook processing failed:", error);
+ return NextResponse.json({ error: "Processing timeout" }, { status: 500 });
+}
+```
+
+### Security Best Practices
+
+1. **Signature Verification**: Always verify `x-tavus-signature` header
+2. **HTTPS Only**: Webhook URLs must use HTTPS in production
+3. **Rate Limiting**: Implement rate limiting on webhook endpoint
+4. **Input Validation**: Validate all webhook payload data
+5. **Secret Rotation**: Rotate webhook secrets periodically
+
+### Development Workflow
+
+1. **Local Development**: Use ngrok to expose localhost
+2. **Testing**: Use Tavus webhook testing tools or manual conversation creation
+3. **Staging**: Deploy to staging environment with proper webhook URL
+4. **Production**: Use production domain with SSL certificate
+
+### Webhook URL Configuration
+
+**Environment-Specific URLs**:
+
+```bash
+# Development
+TAVUS_WEBHOOK_URL=https://abc123.ngrok.io/api/learning-checks/perception-analysis
+
+# Staging
+TAVUS_WEBHOOK_URL=https://staging.your-app.com/api/learning-checks/perception-analysis
+
+# Production
+TAVUS_WEBHOOK_URL=https://your-app.com/api/learning-checks/perception-analysis
+```
+
+---
+
+## Data Structures
+
+### Enhanced Data Captured (With Perception Analysis)
+
+```typescript
+interface LearningCheckData {
+ // Core session data
+ chapterId: string;
+ userId: string;
+ conversationId: string; // Tavus conversation ID for correlation
+ startedAt: timestamp;
+ endedAt: timestamp;
+ duration: number; // seconds
+ engagementTime: number; // seconds of active speech
+ engagementPercent: number; // 0-100
+ completed: boolean;
+
+ // Conversation data
+ transcript: string; // full conversation transcript
+
+ // AI-generated rubric (Phase 2+)
+ rubric: {
+ recallScore: number; // 0-10
+ applicationScore: number; // 0-10
+ selfExplanationScore: number; // 0-10
+ misconceptions: string[]; // identified gaps
+ nextSteps: string; // recommendations
+ };
+
+ // Perception analysis (enriched data)
+ perceptionAnalysis: {
+ rawAnalysis: string; // full Tavus Raven analysis text
+ screenGazePercent: number; // 0-100, extracted from analysis
+ engagementLevel: "high" | "medium" | "low"; // parsed from analysis
+ comprehensionIndicators: {
+ confusionDetected: boolean;
+ frustrationDetected: boolean;
+ confidenceLevel: "high" | "medium" | "low";
+ };
+ noteTaking: boolean; // detected from analysis
+ distractionsPresent: boolean;
+ bodyLanguageRating: "positive" | "neutral" | "negative";
+ visualEngagementScore: number; // 0-100 composite score
+ };
+
+ // Combined assessment
+ overallAssessment: {
+ audioEngagement: number; // from speech detection (0-100)
+ visualEngagement: number; // from perception analysis (0-100)
+ combinedEngagementScore: number; // weighted average (0-100)
+ passedThreshold: boolean; // >=50% combined engagement
+ };
+}
+```
+
+### Rubric Generation (Phase 2+)
+
+- **Input**: Tavus conversation transcript + perception analysis + AI scoring (GPT-4 mini)
+- **Scores**: 0-10 scale for each prompt type (recall, application, self-explanation)
+- **Misconceptions**: Extract unclear or incorrect statements from transcript
+- **Next Steps**: Generate recommendations considering both verbal and visual engagement
+
+### Perception Analysis Parsing
+
+**Strategy**: Parse Tavus Raven text analysis into structured data
+
+**Parsing Logic**:
+
+1. **Screen Gaze**: Extract percentage from "looking at the screen approximately X%" pattern
+2. **Engagement Level**: Map keywords (attentive, engaged → high; neutral → medium; distracted → low)
+3. **Comprehension**: Detect keywords (confusion, frustration, struggle)
+4. **Note-Taking**: Boolean detection from "taking notes" or "referencing materials"
+5. **Distractions**: Boolean from "distractions" or "multiple people present"
+6. **Body Language**: Map positive/negative keywords to rating
+
+**Visual Engagement Score Calculation**:
+
+```typescript
+visualEngagementScore =
+ screenGazePercent * 0.4 +
+ engagementLevelScore * 0.3 +
+ bodyLanguageScore * 0.2 +
+ comprehensionBonus * 0.1;
+```
+
+**Combined Engagement Score**:
+
+```typescript
+combinedEngagementScore = audioEngagement * 0.6 + visualEngagement * 0.4;
+```
+
+### Storage Strategy
+
+- **Phase 1 (MVP)**: Console log conversation data + basic engagement tracking
+- **Phase 2**: Add perception webhook endpoint + parsing logic + console log enriched data
+- **Phase 3**: Store enriched data in localStorage for testing
+- **Phase 4**: POST to backend API endpoint `/api/learning-checks` when database available
+- **Phase 5**: Add rubric generation with AI analysis
+
+---
+
+## Technical Requirements
+
+### Tavus Integration
+
+- **CVI Library**: Use existing `@tavus/cvi-ui` components (already installed)
+- **Persona Reference**: Use dashboard-configured persona ("8p3p - AI Instructor Assistant") via `persona_id` parameter
+- **Replica**: "Olivia - Office" replica configured in dashboard
+- **Conversation API**: Create conversation referencing persona + inject chapter-specific context
+- **Hair Check**: Use Tavus Hair Check component for AV verification
+- **Engagement Detection**: Leverage Daily.co audio levels or speaking events
+- **Termination API**: Call Tavus conversation end endpoint on all termination triggers
+
+### Persona Configuration (Managed in Tavus Dashboard)
+
+**Base Configuration** (Team can update without code changes):
+
+- **System Prompt**: "You are a knowledgeable and patient AI tutor who helps students understand course material..."
+- **Persona Role**: "8p3p - AI Instructor Assistant"
+- **Knowledge Base**: Tag "8p3p-cth-demo" with uploaded course materials
+- **Language Model**: Configured with speculative response and tool integrations
+- **Speech Settings**: Text-to-Speech (TTS) engine and voice configured
+- **Perception Model**: `raven-0` enabled for visual analysis
+
+### Chapter-Specific Context (Injected at Conversation Creation)
+
+**Dynamic Context** (Passed via `conversational_context` parameter):
+
+```
+Current Learning Check Context:
+Chapter: {chapterTitle}
+Chapter ID: {chapterId}
+
+Learning Objectives for This Chapter:
+{chapter.learningObjectives.map(obj => `- ${obj}`).join('\n')}
+
+Key Topics Covered:
+{chapter.sections.map(section => section.title).join(', ')}
+
+Assessment Focus:
+- Ask at least 1 recall question about key concepts from this chapter
+- Ask at least 1 application question about real-world usage
+- Ask at least 1 self-explanation question to check understanding
+- Duration: 3 minutes for this learning check
+- IMPORTANT: Never reveal quiz answers or discuss specific quiz questions
+- Keep conversation focused on this chapter's content
+- Politely redirect if student asks about topics outside this chapter's scope
+```
+
+**Conversation Creation Flow**:
+
+1. Fetch `TAVUS_PERSONA_ID` from environment variables
+2. Build chapter-specific context string from chapter data
+3. Call Tavus API: `POST /v2/conversations`
+ - `replica_id`: from persona configuration
+ - `persona_id`: dashboard persona ID
+ - `conversational_context`: chapter-specific context (above)
+ - `callback_url`: webhook URL for perception analysis
+ - `max_call_duration`: 240 seconds
+4. Receive `conversation_url` and `conversation_id`
+5. Store `conversation_id` for termination tracking
+
+### Implementation Strategy for Termination
+
+```typescript
+// React useEffect cleanup
+useEffect(() => {
+ return () => {
+ // Cleanup: terminate conversation
+ if (conversationId) {
+ terminateConversation(conversationId);
+ }
+ };
+}, [conversationId]);
+
+// beforeunload event
+useEffect(() => {
+ const handleBeforeUnload = () => {
+ if (conversationId) {
+ // Use sendBeacon for reliability
+ navigator.sendBeacon(
+ "/api/learning-checks/terminate",
+ JSON.stringify({ conversationId })
+ );
+ }
+ };
+
+ window.addEventListener("beforeunload", handleBeforeUnload);
+ return () => window.removeEventListener("beforeunload", handleBeforeUnload);
+}, [conversationId]);
+
+// Router events (Next.js)
+useEffect(() => {
+ const handleRouteChange = () => {
+ if (conversationId) {
+ terminateConversation(conversationId);
+ }
+ };
+
+ router.events.on("routeChangeStart", handleRouteChange);
+ return () => router.events.off("routeChangeStart", handleRouteChange);
+}, [conversationId]);
+```
+
+### Dependencies
+
+- **Internal**:
+ - Chapter quiz completion status
+ - Timer component (existing reusable component)
+ - CVIProvider (already in layout.tsx)
+ - Tavus CVI components (existing)
+- **External**:
+ - Tavus API (conversation creation)
+ - Daily.co (audio/video infrastructure)
+ - Optional: GPT-4 mini for rubric generation (Assumption)
+
+### Environment Variables
+
+```bash
+TAVUS_API_KEY=required
+TAVUS_PERSONA_ID=required # Dashboard persona ID (e.g., "pd8#1eb0d8e")
+TAVUS_LEARNING_CHECK_DURATION=180 # configurable (seconds)
+TAVUS_MAX_CONCURRENT_SESSIONS=10 # cost management
+TAVUS_WEBHOOK_SECRET=required # Webhook signature verification
+TAVUS_WEBHOOK_URL=required # Public URL for perception analysis webhook
+TAVUS_PERCEPTION_ENABLED=true # Toggle perception analysis on/off
+```
+
+---
+
+## Implementation Phases
+
+### Phase 1: MVP (Console Logging)
+
+**Estimated Effort**: 2-3 days
+
+- Build locked/unlocked states based on quiz status
+- Integrate Hair Check component
+- Wire up Tavus conversation with chapter context and persona reference
+- Implement dual progress bars (time + engagement)
+- Add button state logic
+- Console log basic completion data (no perception yet)
+- Implement conversation termination on all triggers
+
+### Phase 2: Perception Webhook & Parsing
+
+**Estimated Effort**: 2 days
+
+**Day 1: Webhook Infrastructure**
+
+- Generate webhook secret using crypto.randomBytes(32)
+- Set up ngrok for local development webhook URL exposure
+- Create webhook endpoint `/api/learning-checks/perception-analysis/route.ts`
+- Implement webhook signature verification with HMAC-SHA256
+- Handle multiple event types (perception_analysis, transcription_ready, system.shutdown)
+- Add error handling, timeouts, and idempotency
+- Test webhook delivery with manual conversation creation
+
+**Day 2: Perception Analysis & Parsing**
+
+- Configure perception queries in Tavus dashboard persona
+- Build perception analysis parser (text → structured data)
+- Implement regex patterns for extracting gaze percentage, engagement level
+- Calculate visual engagement score with weighted formula
+- Combine audio + visual engagement scores
+- Console log enriched data with perception analysis
+- Test end-to-end flow: conversation → webhook → parsing → storage
+
+### Phase 3: Data Persistence
+
+**Estimated Effort**: 1 day
+
+- Create API route `/api/learning-checks` (POST)
+- Store enriched data in localStorage for testing
+- Update to POST to backend when database available
+- Include perception analysis in stored data
+
+### Phase 4: Rubric Generation
+
+**Estimated Effort**: 1-2 days
+
+- Integrate AI analysis (GPT-4 mini) for transcript scoring
+- Incorporate perception data into rubric generation
+- Generate misconceptions and next steps
+- Display combined rubric to learner after completion
+
+### Phase 5: Polish & Analytics
+
+**Estimated Effort**: 1 day
+
+- Enhance accessibility features
+- Add retry flows with perception data reset
+- Build admin analytics dashboard with visual engagement metrics
+- Performance optimization
+- Add perception insights to learner completion summary
+
+---
+
+## Acceptance Criteria
+
+### Core Functionality
+
+- [ ] **AC-1**: Learning Check only accessible after passing chapter quiz
+- [ ] **AC-2**: Hair Check required before Start button enables
+- [ ] **AC-3**: 4-minute countdown timer displays and enforces hard stop
+- [ ] **AC-4**: Engagement progress bar updates in real-time during conversation
+- [ ] **AC-5**: "Mark Complete" button only appears after ≥120s engagement AND session end
+- [ ] **AC-6**: AI avatar asks at least 1 recall, 1 application, 1 self-explanation question
+- [ ] **AC-7**: AI never reveals quiz answers during conversation
+- [ ] **AC-8**: AI redirects off-scope questions back to chapter content
+- [ ] **AC-9**: Conversation references dashboard persona ("8p3p - AI Instructor Assistant")
+- [ ] **AC-10**: Chapter-specific context injected at conversation creation
+- [ ] **AC-11**: Conversation terminates on page navigation
+- [ ] **AC-12**: Conversation terminates on tab/window close
+- [ ] **AC-13**: Conversation terminates on connection loss
+- [ ] **AC-14**: Conversation terminates on timer expiration
+
+### UI/UX
+
+- [ ] **AC-15**: Locked state shows clear message when quiz not passed
+- [ ] **AC-16**: Hair Check component allows device selection
+- [ ] **AC-17**: Timer shows minutes:seconds format (e.g., 3:45)
+- [ ] **AC-18**: Engagement bar visually distinct from time remaining
+- [ ] **AC-19**: "End Session" button always available during active session
+- [ ] **AC-20**: Completion state shows success message with next steps
+
+### Data & Analytics
+
+- [ ] **AC-21**: Console logs full learning check data on completion
+- [ ] **AC-22**: Transcript captured and included in data object
+- [ ] **AC-23**: Rubric generated with scores and misconceptions
+- [ ] **AC-24**: All 11 analytics events tracked correctly (including 3 termination + 2 perception events)
+- [ ] **AC-25**: Conversation duration logged for cost monitoring
+- [ ] **AC-26**: Perception webhook endpoint receives and validates payloads
+- [ ] **AC-27**: Perception analysis text parsed into structured data correctly
+- [ ] **AC-28**: Visual engagement score calculated accurately
+- [ ] **AC-29**: Combined engagement score (audio + visual) displayed to learner
+- [ ] **AC-30**: Perception data included in completion data storage
+
+### Accessibility
+
+- [ ] **AC-31**: Focus trapped in modal during session
+- [ ] **AC-32**: Screen reader announces timer at key intervals
+- [ ] **AC-33**: Captions available for AI avatar speech
+- [ ] **AC-34**: All controls keyboard accessible
+- [ ] **AC-35**: Color contrast meets WCAG AA standards
+
+### Performance
+
+- [ ] **AC-36**: Hair Check completes in <5 seconds
+- [ ] **AC-37**: Conversation starts in <3 seconds after clicking Start
+- [ ] **AC-38**: Engagement tracking accurate within ±2 seconds
+- [ ] **AC-39**: No memory leaks during or after session
+- [ ] **AC-40**: Conversation termination completes within 2 seconds
+- [ ] **AC-41**: Perception webhook processing completes within 5 seconds
+- [ ] **AC-42**: Perception parsing accurate within ±5% for gaze tracking
+
+---
+
+## Perception Analysis Implementation Summary
+
+### What Gets Captured
+
+**Visual Engagement Metrics**:
+
+- Screen gaze percentage (0-100%)
+- Overall engagement level (high/medium/low)
+- Comprehension indicators (confusion, frustration, confidence)
+- Note-taking behavior (boolean)
+- Environmental distractions (boolean)
+- Body language rating (positive/neutral/negative)
+
+### How It Works
+
+1. **Configuration**: Add 6 perception queries to Tavus dashboard persona
+2. **During Conversation**: Raven model analyzes visual frames in real-time
+3. **End of Conversation**: Tavus generates comprehensive visual summary
+4. **Webhook Delivery**: Backend receives perception analysis payload
+5. **Parsing**: Text analysis parsed into structured data
+6. **Scoring**: Visual engagement score calculated (0-100)
+7. **Combination**: Audio (60%) + Visual (40%) = Combined engagement score
+8. **Storage**: Enriched data stored with transcript and rubric
+
+### Benefits for Learning Assessment
+
+- **Holistic Evaluation**: Combines verbal and non-verbal engagement
+- **Early Intervention**: Identifies struggling learners (confusion, disengagement)
+- **Quality Insights**: Detects note-taking and active learning behaviors
+- **Fairness**: Accounts for quiet but visually engaged learners
+- **Data-Driven**: Objective metrics supplement subjective assessments
+
+### Technical Requirements
+
+- **Tavus Persona**: `raven-0` perception model enabled
+- **Webhook Endpoint**: Public URL accessible by Tavus
+- **Signature Verification**: Validate webhook authenticity
+- **Parsing Logic**: Regex/NLP to extract structured data from text
+- **Error Handling**: Graceful degradation if perception fails (audio-only mode)
+
+### Privacy & Consent
+
+- Inform learners that visual engagement is tracked
+- Provide opt-out option (audio-only mode)
+- Anonymize perception data (remove PII from analysis)
+- 90-day retention policy for visual data
+- Never store raw video frames (only analysis summaries)
+
+---
+
+**Implementation Status**: Ready for Phase 1 development
+
+**Next Steps**:
+
+1. Review [Feature Spec](./learning-check-spec.md) for product requirements
+2. Begin Phase 1 implementation with quiz-gated access and Hair Check integration
+3. Set up development environment with ngrok for webhook testing
diff --git a/specs/features/learning-check/learning-check-spec.md b/specs/features/learning-check/learning-check-spec.md
new file mode 100644
index 0000000..aac9697
--- /dev/null
+++ b/specs/features/learning-check/learning-check-spec.md
@@ -0,0 +1,265 @@
+# Feature Specification: Learning Check — Chapter-End Conversational Assessment
+
+> **Product Requirements Document**
+> For technical implementation details, see [Implementation Guide](./learning-check-implementation.md)
+
+---
+
+## Overview
+
+**Purpose**: Provide a conversational assessment at the end of each chapter using Tavus CVI to reinforce learning through natural dialogue with an AI avatar instructor.
+
+**Goals**:
+- Validate learner comprehension through conversation vs. traditional testing
+- Encourage self-explanation and application of concepts
+- Track engagement quality and completion
+- Gate chapter progression on meaningful interaction
+
+---
+
+## User Stories
+
+### As a Learner
+- **US-1**: I want to engage in a natural conversation about chapter content to demonstrate my understanding
+- **US-2**: I want to see my remaining time and engagement progress during the session
+- **US-3**: I want to verify my camera and microphone work before starting
+- **US-4**: I want clear feedback on whether I've engaged enough to complete the learning check
+- **US-5**: I want to AI to stay focused on the chapter content and redirect me if I go off-topic
+
+### As an Instructor/Admin
+- **US-6**: I want to ensure learners have passed the quiz before accessing the learning check
+- **US-7**: I want to track engagement quality and session completion
+- **US-8**: I want transcripts and rubric scores to identify learner misconceptions
+
+---
+
+## Functional Requirements
+
+### 1. Placement & Triggering
+- **Location**: Appears at the end of every chapter, after all video sections and the chapter quiz
+- **Unlock Condition**: Learner must pass the current chapter quiz (≥ passing score) to access
+- **Visual State**: Show locked state with clear message if quiz not passed
+
+### 2. Time Management
+- **Duration**: 3 minutes (180 seconds) hard limit
+- **Timer Display**: Visible countdown timer showing minutes:seconds remaining
+- **Hard Stop**: Session automatically ends at 0:00, no extensions
+- **Warning**: Visual/audio notification at 30 seconds remaining (Assumption)
+
+### 3. Audio/Video Gating
+- **Hair Check**: Required camera and microphone verification before Start button enables
+- **Permissions**: Handle browser permission denied states with clear instructions
+- **Device Selection**: Allow learner to select camera/microphone if multiple available
+- **Start Button**: Only enabled after successful Hair Check completion
+
+### 4. Engagement Tracking
+- **Threshold**: Minimum 90 seconds (50%) of active speaking/engagement out of 180 seconds
+- **Calculation**: Track time when learner's microphone detects speech activity (Assumption: use Tavus/Daily.co audio level detection)
+- **Progress Indicator**: Visual bar showing engagement progress toward 90s threshold
+- **Completion Button**: "Mark Learning Check Complete" only visible after threshold met AND session ended
+
+### 5. Conversation Behavior (AI Instructor Persona)
+- **Persona Configuration**: Use Tavus dashboard persona ("8p3p - AI Instructor Assistant") with Olivia replica
+- **Persona Context**: Base system prompt and knowledge base managed in Tavus dashboard for easy team updates
+- **Chapter-Specific Context**: Inject dynamic context at conversation creation:
+ - Current chapter title and learning objectives
+ - Specific topics covered in this chapter
+ - Emphasis on recall, application, and self-explanation prompts
+- **Dialogue Style**: Natural, conversational, encouraging tone (configured in persona)
+- **Prompt Types**: Must include at least:
+ - 1 recall prompt (e.g., "Can you explain what bilateral stimulation means?")
+ - 1 application prompt (e.g., "How would you apply this with a trauma client?")
+ - 1 self-explanation prompt (e.g., "Why do you think this technique works?")
+- **Quiz Protection**: Never reveal quiz answers or discuss specific quiz questions
+- **Scope Enforcement**: Stay within current chapter content; politely redirect off-scope questions
+- **Example Redirect**: "That's a great question about [topic], but let's focus on [chapter topic] for now."
+
+### 6. Conversation Termination & Cost Management
+**Critical**: Always end Tavus conversations to prevent unnecessary charges
+
+#### Termination Triggers
+1. **Timer Expiration**: Auto-terminate when 4-minute timer reaches 0:00
+2. **User Action**: Manual "End Session" button click
+3. **Page Navigation**: User navigates to different route/page
+4. **Tab/Window Close**: User closes browser tab or window
+5. **Connection Loss**: Network disconnection or Tavus connection failure
+6. **Component Unmount**: React component cleanup when user leaves section
+
+#### Cost Optimization
+- Track conversation duration and log to analytics for cost monitoring
+- Implement conversation pooling/queueing if multiple learners start simultaneously
+- Set max concurrent sessions per account (Assumption: 10 concurrent for MVP)
+- Monitor average session duration and adjust if learners consistently time out
+
+### 7. UI Components (Modular)
+**Location**: `src/components/course/chapter-content/`
+
+#### Component Structure
+- **LearningCheckContainer**: Main wrapper component
+ - **Header**: Chapter title, "Learning Check" label
+ - **ProgressBar**: Dual-purpose showing:
+ - Time remaining (countdown)
+ - Engagement progress (toward 120s threshold)
+ - **HairCheck**: Camera/microphone verification component (from Tavus CVI library)
+ - **ConversationView**: Tavus AI Avatar component
+ - **ControlButtons**:
+ - **Start**: Enabled after Hair Check passes
+ - **End Session**: Always available during active session
+ - **Mark Complete**: Visible only after engagement threshold met AND session ended
+
+#### States
+- **Locked**: Quiz not passed
+- **Pre-session**: Hair Check in progress
+- **Active**: Conversation in progress with timer
+- **Ended (threshold not met)**: Session over, retry available
+- **Ended (threshold met)**: Session over, complete button available
+- **Completed**: Learning check marked complete
+
+### 8. Analytics Events
+**Minimal tracking for essential metrics**
+
+| Event | Trigger | Data |
+|-------|---------|------|
+| `lc_started` | Start button clicked | chapterId, userId, timestamp |
+| `lc_hair_check_ok` | Hair Check passes | chapterId, userId, deviceInfo |
+| `lc_timeout` | Timer reaches 0:00 | chapterId, userId, engagementTime |
+| `lc_user_end` | User clicks End Session | chapterId, userId, timeRemaining, engagementTime |
+| `lc_completed` | Mark Complete clicked | chapterId, userId, engagementTime, rubric |
+| `lc_blocked_not_passed` | Locked state shown | chapterId, userId, quizScore |
+| `lc_terminated_navigation` | User navigated away | chapterId, userId, timeRemaining, terminationReason |
+| `lc_terminated_connection` | Connection lost | chapterId, userId, timeRemaining, connectionState |
+| `lc_terminated_manual` | Manual end (not timeout) | chapterId, userId, timeRemaining |
+| `lc_perception_received` | Perception webhook received | conversationId, userId, perceptionDataSize |
+| `lc_perception_parsed` | Perception data parsed successfully | conversationId, visualEngagementScore |
+
+### 9. Accessibility & Privacy
+
+#### Accessibility
+- **Focus Management**: Modal traps focus, Esc key closes
+- **Captions**: Enable closed captions for AI avatar speech (Tavus feature)
+- **ARIA Announcements**: Screen reader alerts for:
+ - Timer milestones (3:00, 2:00, 1:00, 0:30 remaining)
+ - State changes (started, ended, completed)
+ - Engagement threshold reached
+- **Keyboard Navigation**: All controls accessible via keyboard
+- **Color Contrast**: WCAG AA compliance for all text/UI
+
+#### Privacy
+- **Data Minimization**: Collect only essential data (transcript, engagement time, scores)
+- **No Quiz Leakage**: AI never reveals quiz answers in conversation or transcript
+- **Consent**: Inform learner that session is recorded for educational purposes (Assumption: add consent notice)
+- **Data Retention**: Define retention policy for transcripts (Assumption: 90 days, then anonymize)
+- **Visual Tracking Consent**: Inform learners that visual engagement is tracked
+- **Opt-Out Option**: Provide audio-only mode for privacy concerns
+- **Data Anonymization**: Remove PII from perception analysis
+- **No Video Storage**: Never store raw video frames (only analysis summaries)
+
+---
+
+## MoSCoW Prioritization
+
+### Must Have (MVP)
+- ✅ Quiz-gated access
+- ✅ Hair Check with AV verification
+- ✅ 4-minute timer with hard stop
+- ✅ Engagement tracking (≥120s threshold)
+- ✅ Tavus persona reference with chapter context injection
+- ✅ Conversation termination on all triggers (navigation, close, disconnect, timeout)
+- ✅ Console logging of completion data
+- ✅ Analytics events including termination tracking
+- ✅ Mark Complete button gating
+
+### Should Have (Post-MVP)
+- 🔄 Rubric generation with AI analysis (incorporating perception data)
+- 🔄 Retry option if engagement threshold not met
+- 🔄 Transcript review UI for learners with perception insights
+- 🔄 Admin dashboard for viewing rubrics and visual engagement metrics
+- 🔄 Persistence to database/localStorage
+- 🔄 Learner-facing perception summary ("You maintained 85% screen focus!")
+
+### Could Have (Future)
+- 💡 Adaptive conversation difficulty based on quiz performance and perception data
+- 💡 Multi-language support
+- 💡 Downloadable transcript and rubric PDF with perception insights
+- 💡 Conversation history across all chapters with engagement trends
+- 💡 Peer comparison analytics (visual vs. audio engagement patterns)
+- 💡 Real-time perception feedback during conversation ("I notice you seem distracted...")
+- 💡 Heatmap visualization of engagement over time
+
+### Won't Have (Out of Scope)
+- ❌ Text-only fallback (AV required for this feature)
+- ❌ Group learning checks
+- ❌ Live instructor takeover
+- ❌ Custom avatar selection per learner
+
+---
+
+## Success Metrics
+
+**Engagement**
+- Average engagement time per session (target: ≥150s)
+- Completion rate (target: ≥80% of learners who start)
+- Retry rate (target: <20%)
+
+**Learning Effectiveness** (Phase 2+)
+- Correlation between rubric scores and next chapter quiz performance
+- Misconception identification accuracy
+- Learner satisfaction rating (post-session survey)
+
+**Technical Performance**
+- Hair Check success rate (target: ≥95%)
+- Average time to conversation start (target: <3s)
+- Session timeout rate (target: <10%)
+
+---
+
+## Open Questions
+
+1. **Rubric Scoring**: Should rubric generation be real-time (during conversation) or post-session batch processing?
+ - **Recommendation**: Post-session batch to avoid latency and allow full transcript analysis
+
+2. **Retry Behavior**: If learner doesn't meet engagement threshold, can they retry immediately or wait 24 hours?
+ - **Recommendation**: Immediate retry for MVP, add cooldown in Phase 2 if abuse detected
+
+3. **Chapter Progression**: Does learner need to complete Learning Check to unlock next chapter, or is it optional?
+ - **Recommendation**: Optional for MVP, required for Phase 2 (track completion rate first)
+
+4. **Device Failure**: What if learner's camera/microphone fails mid-session?
+ - **Recommendation**: Auto-pause session, show reconnection UI, resume timer when reconnected
+
+5. **Conversation Quality**: Who reviews transcripts to validate AI instructor behavior?
+ - **Recommendation**: Weekly sampling by instructional designer for first 2 weeks, then monthly
+
+---
+
+## Risk Mitigation
+
+| Risk | Impact | Mitigation |
+|------|--------|------------|
+| Learner avoids speaking (low engagement) | Can't complete learning check | Clear instructions, progress bar, AI prompts "I'd love to hear your thoughts..." |
+| AI goes off-script / reveals quiz answers | Compromises assessment integrity | Persona configuration in dashboard, chapter context injection, post-launch transcript review |
+| Camera/mic permission denied | Can't start session | Clear permission instructions, browser-specific guides |
+| Tavus API downtime | Learning check unavailable | Graceful error message, retry button, fallback to "mark as attempted" |
+| Engagement tracking inaccurate | False positives/negatives | Test with multiple audio scenarios, manual review of edge cases |
+| Conversation not terminated (cost leak) | Unnecessary Tavus charges | Multiple termination triggers, cleanup in useEffect, beforeunload listener, connection monitoring |
+| User navigates mid-session | Lost session data + cost leak | Auto-save engagement data before termination, prompt "Are you sure?" modal |
+| Perception analysis parsing fails | Incomplete engagement data | Regex fallback patterns, manual review queue, default to audio-only engagement |
+| Webhook delivery fails/delays | Missing perception data | Implement retry logic, timeout after 30s, store partial data without perception |
+| Privacy concerns with visual tracking | User discomfort/legal issues | Clear consent messaging, allow opt-out (audio-only mode), anonymize perception data |
+
+---
+
+**Implementation Priority**: High — Core learning engagement feature for MVP
+
+**Estimated Total Effort**: 7-10 days (MVP through Phase 2 with Perception)
+- Phase 1 (MVP): 2-3 days
+- Phase 2 (Perception): 2 days
+- Phase 3 (Persistence): 1 day
+- Phase 4 (Rubric): 1-2 days
+- Phase 5 (Polish): 1 day
+
+**Risk Level**: Medium (depends on Tavus perception accuracy, engagement detection, and webhook reliability)
+
+---
+
+**Next Steps**: Review [Implementation Guide](./learning-check-implementation.md) for technical details and development phases
diff --git a/specs/features/learning-check/objective-completion-tracking.md b/specs/features/learning-check/objective-completion-tracking.md
new file mode 100644
index 0000000..8145333
--- /dev/null
+++ b/specs/features/learning-check/objective-completion-tracking.md
@@ -0,0 +1,428 @@
+# Feature Specification: Learning Check Objective Completion Tracking
+
+> **Product Requirements Document**
+
+---
+
+## Overview
+
+**Purpose**: Track and measure completion of structured learning objectives during Tavus conversational assessments.
+
+**Goals**:
+
+- Validate learner met all required assessment objectives (recall, application, self-explanation)
+- Collect structured assessment data (scores, key terms, examples)
+- Provide measurable completion criteria beyond time engagement
+- Enable data-driven insights into learner comprehension
+
+**Status**: MVP Implementation Phase
+
+---
+
+## User Stories
+
+### As a Learner
+
+- **US-1**: I want to know which assessment objectives I've completed during the conversation
+- **US-2**: I want to receive feedback on my performance after the learning check ends
+- **US-3**: I want clear criteria for what constitutes successful completion
+
+### As an Instructor
+
+- **US-4**: I want to see which objectives each learner completed
+- **US-5**: I want to review collected assessment data (key terms recalled, application examples)
+- **US-6**: I want to track learner scores across different assessment dimensions
+
+### As a System
+
+- **US-7**: I need to receive objective completion notifications from Tavus
+- **US-8**: I need to store completion data for reporting and analytics
+
+---
+
+## Functional Requirements
+
+### 1. Objectives Structure
+
+**Configuration**: `src/lib/tavus/config.ts`
+
+```typescript
+LEARNING_CHECK_OBJECTIVES = {
+ data: [
+ {
+ objective_name: "recall_assessment",
+ objective_prompt: "Ask recall question about key concepts",
+ output_variables: ["recall_key_terms", "recall_score"],
+ confirmation_mode: "auto",
+ next_required_objectives: ["application_assessment"],
+ },
+ {
+ objective_name: "application_assessment",
+ objective_prompt: "Ask application question about realistic scenarios",
+ output_variables: ["application_example", "application_score"],
+ confirmation_mode: "auto",
+ next_required_objectives: ["self_explanation_assessment"],
+ },
+ {
+ objective_name: "self_explanation_assessment",
+ objective_prompt: "Ask self-explanation prompt for deeper understanding",
+ output_variables: ["explanation_summary", "explanation_score"],
+ confirmation_mode: "auto",
+ },
+ ],
+};
+```
+
+**Requirements**:
+
+- ✅ All 3 objectives configured in Tavus
+- ✅ Sequential flow: recall → application → self-explanation
+- ✅ Auto-confirmation mode (AI determines completion)
+- ✅ Output variables defined for data collection
+
+**Reference**: [Tavus Objectives Documentation](https://docs.tavus.io/sections/conversational-video-interface/persona/objectives.md)
+
+---
+
+### 2. Webhook Integration
+
+**Tracking Method**: End-of-conversation webhook (MVP approach)
+
+#### Webhook Event: `application.transcription_ready`
+
+**Received After**: Conversation ends (time limit or manual end)
+
+**Payload Structure**:
+
+```json
+{
+ "conversation_id": "c0b934942640d424",
+ "event_type": "application.transcription_ready",
+ "properties": {
+ "objectives_completed": [
+ {
+ "objective_name": "recall_assessment",
+ "status": "completed",
+ "output_variables": {
+ "recall_key_terms": "bilateral stimulation, AIP model",
+ "recall_score": 85
+ }
+ }
+ ],
+ "transcript": [
+ /* full conversation */
+ ]
+ }
+}
+```
+
+**Webhook Endpoint**: `/api/webhooks/tavus`
+
+**Requirements**:
+
+- ✅ Webhook URL configured in `.env.local`
+- ✅ Secure webhook endpoint (signature verification recommended)
+- ✅ Handle multiple webhook event types
+- ✅ Extract `objectives_completed` array from payload
+
+**Reference**: [Tavus Webhooks Documentation](https://docs.tavus.io/sections/webhooks-and-callbacks.md)
+
+---
+
+### 3. Data Collection & Storage
+
+**Collected Data Per Objective**:
+
+| Field | Type | Example | Source |
+| --------------------- | ------ | ---------------------------------------------- | ----------------- |
+| `objective_name` | string | "recall_assessment" | Tavus |
+| `status` | string | "completed" | Tavus |
+| `recall_key_terms` | string | "bilateral stimulation, AIP" | Tavus AI analysis |
+| `recall_score` | number | 85 | Tavus AI scoring |
+| `application_example` | string | "Using eye movements during trauma processing" | Tavus AI analysis |
+| `application_score` | number | 90 | Tavus AI scoring |
+| `explanation_summary` | string | "EMDR helps process traumatic memories..." | Tavus AI analysis |
+| `explanation_score` | number | 88 | Tavus AI scoring |
+
+**Storage Requirements** (Post-MVP):
+
+```typescript
+interface LearningCheckResult {
+ id: string;
+ userId: string;
+ chapterId: string;
+ conversationId: string;
+
+ // Objectives completion
+ recallScore: number;
+ applicationScore: number;
+ explanationScore: number;
+ overallScore: number;
+
+ // Output variables
+ recallKeyTerms: string;
+ applicationExample: string;
+ explanationSummary: string;
+
+ // Metadata
+ transcript: ConversationMessage[];
+ duration: number;
+ objectivesCompleted: number;
+ completedAt: Date;
+}
+```
+
+---
+
+### 4. Completion Criteria
+
+**Successful Completion Requirements**:
+
+- ✅ All 3 objectives marked as "completed" by Tavus AI
+- ✅ Minimum score threshold: 70% average across all objectives (configurable)
+- ✅ Time engagement: ≥90 seconds (existing requirement)
+
+**Scoring Calculation**:
+
+```typescript
+const averageScore = (recall_score + application_score + explanation_score) / 3;
+
+const passed = averageScore >= 70; // Configurable threshold
+```
+
+---
+
+### 5. User Feedback
+
+**After Conversation Ends**:
+
+1. **Processing State** (5-10 seconds)
+ - Show loading spinner
+ - Message: "Analyzing your learning check..."
+ - Wait for webhook to arrive
+
+2. **Results Display**
+ - Overall score (0-100)
+ - Individual objective scores
+ - Key insights collected
+ - Pass/Fail status
+ - Option to review transcript (Post-MVP)
+
+**Example UI**:
+
+```
+┌────────────────────────────────────────┐
+│ Learning Check Complete! ✅ │
+│ │
+│ Overall Score: 87% │
+│ │
+│ 📚 Recall Assessment: 85% │
+│ ✓ Key terms identified │
+│ │
+│ 🎯 Application: 90% │
+│ ✓ Real-world example provided │
+│ │
+│ 💡 Self-Explanation: 88% │
+│ ✓ Demonstrated understanding │
+│ │
+│ [Continue to Next Chapter] → │
+└────────────────────────────────────────┘
+```
+
+---
+
+## Technical Implementation
+
+### Phase 1: MVP (Current Sprint)
+
+**Scope**: End-of-conversation webhook tracking
+
+**Tasks**:
+
+1. ✅ Configure objectives in `src/lib/tavus/config.ts`
+2. ✅ Sync objectives to Tavus API via update scripts
+3. ✅ Add webhook URL to conversation creation
+4. ⏳ Create webhook endpoint `/api/webhooks/tavus`
+5. ⏳ Handle `application.transcription_ready` event
+6. ⏳ Extract and calculate objective scores
+7. ⏳ Display results to learner
+8. ⏳ Store results (in-memory for MVP, database later)
+
+**Files**:
+
+- `src/app/api/webhooks/tavus/route.ts` (new)
+- `src/app/api/learning-checks/conversation/route.ts` (updated)
+- `src/components/course/chapter-content/learning-check-results.tsx` (new)
+
+---
+
+### Phase 2: Enhanced Tracking (Post-MVP)
+
+**Scope**: Real-time per-objective webhooks + analytics
+
+**Features**:
+
+- Real-time progress indicators during conversation
+- Per-objective webhook callbacks
+- Database storage with historical analytics
+- Instructor dashboard for learner insights
+- Transcript review interface
+
+---
+
+## Configuration
+
+### Environment Variables
+
+```bash
+# .env.local
+
+# Objectives & Guardrails
+NEXT_PUBLIC_TAVUS_LEARNING_CHECK_OBJECTIVES_ID=o078991a2b199
+NEXT_PUBLIC_TAVUS_LEARNING_CHECK_GUARDRAILS_ID=g7771e9a453db
+
+# Webhooks
+TAVUS_WEBHOOK_URL=https://your-app.com/api/webhooks/tavus
+TAVUS_WEBHOOK_SECRET=your_webhook_secret # Optional
+
+# Scoring Thresholds (Optional)
+LEARNING_CHECK_PASS_THRESHOLD=70 # Default: 70%
+```
+
+---
+
+## Testing Strategy
+
+### Manual Testing
+
+**Test Case 1**: Complete All Objectives
+
+1. Start learning check conversation
+2. Respond to recall question with key terms
+3. Respond to application question with example
+4. Respond to self-explanation with reasoning
+5. Wait for conversation to end
+6. Verify webhook received with 3 completed objectives
+7. Verify scores displayed to learner
+
+**Test Case 2**: Incomplete Objectives
+
+1. Start learning check conversation
+2. Only respond to recall question
+3. Stay silent or off-topic for remaining time
+4. Wait for conversation to end
+5. Verify webhook shows only 1 completed objective
+6. Verify appropriate feedback to learner
+
+**Test Case 3**: Webhook Failure
+
+1. Temporarily disable webhook endpoint
+2. Complete learning check
+3. Verify graceful fallback behavior
+4. Re-enable webhook and retry
+
+### Automated Testing (Post-MVP)
+
+- Unit tests for score calculation
+- Integration tests for webhook handling
+- E2E tests for complete flow
+
+---
+
+## Success Metrics
+
+**MVP Success Criteria**:
+
+- ✅ Webhook endpoint receives 100% of conversation completions
+- ✅ Objective completion data extracted successfully
+- ✅ Scores calculated accurately (verified against sample data)
+- ✅ Results displayed to learner within 10 seconds of conversation end
+- ✅ Zero missed webhook events (implement retry mechanism if needed)
+
+**Post-MVP Metrics**:
+
+- Average objective completion rate (target: >80%)
+- Average scores per objective type
+- Time to complete each objective
+- Correlation between objective scores and quiz scores
+
+---
+
+## Dependencies
+
+**External**:
+
+- ✅ Tavus API v2 (objectives and webhooks)
+- ✅ Tavus persona configured with objectives ID
+
+**Internal**:
+
+- ✅ Learning check conversation creation
+- ✅ Time limit enforcement (3 minutes)
+- ✅ Tavus config update scripts
+- ⏳ Webhook infrastructure
+
+---
+
+## Non-Functional Requirements
+
+**Performance**:
+
+- Webhook processing: <2 seconds
+- Results display: <10 seconds after conversation end
+- Handle up to 10 concurrent webhooks
+
+**Reliability**:
+
+- 99% webhook delivery success rate
+- Retry logic for failed webhook deliveries (Post-MVP)
+- Graceful degradation if webhook unavailable
+
+**Security**:
+
+- Webhook signature verification (recommended)
+- HTTPS only for webhook endpoint
+- No sensitive data in webhook logs
+
+---
+
+## Future Enhancements
+
+**Phase 3+**:
+
+- Visual engagement tracking (Tavus Perception Layer)
+- Adaptive difficulty based on learner performance
+- Personalized feedback recommendations
+- Multi-attempt tracking with improvement trends
+- Export capabilities for instructors
+
+---
+
+## References
+
+**Official Documentation**:
+
+- [Tavus Objectives](https://docs.tavus.io/sections/conversational-video-interface/persona/objectives.md)
+- [Tavus Webhooks](https://docs.tavus.io/sections/webhooks-and-callbacks.md)
+- [Tavus Conversation API](https://docs.tavus.io/api-reference/conversations/create-conversation.md)
+
+**Internal Documentation**:
+
+- [Technical Implementation Guide](../../../docs/TAVUS_OBJECTIVE_COMPLETION_TRACKING.md)
+- [Learning Check Spec](./learning-check-spec.md)
+- [Learning Check Implementation](./learning-check-implementation.md)
+- [Tavus Config](../../../src/lib/tavus/config.ts)
+
+---
+
+## Changelog
+
+| Date | Version | Changes |
+| ---------- | ------- | --------------------------------------------------------------------------- |
+| 2025-10-31 | 1.0 | Initial specification - MVP scope with end-of-conversation webhook tracking |
+
+---
+
+**Status**: Ready for Implementation
+**Priority**: High (MVP Feature)
+**Estimated Effort**: 1-2 sprints for MVP
diff --git a/specs/features/timer-estimation-system.md b/specs/features/timer-estimation-system.md
deleted file mode 100644
index 10f85bf..0000000
--- a/specs/features/timer-estimation-system.md
+++ /dev/null
@@ -1,429 +0,0 @@
-# Feature Specification: Timer & Estimation System
-
-## Feature Overview
-
-### Purpose
-
-Provide accurate time estimation and real-time timing functionality across the learning platform, including content completion estimates, quiz timers, and AI interaction time limits.
-
-### Goals
-
-- **Accurate Estimation**: Provide realistic completion time estimates
-- **Consistent Timing**: Unified timer component for all timed interactions
-- **User Awareness**: Clear time indicators and progress feedback
-- **Performance Tracking**: Log timing data for analytics and optimization
-
-## User Stories
-
-### As a Learner
-
-- **US-1**: I want to see estimated completion time for chapters and sections so I can plan my learning schedule
-- **US-2**: I want to see a countdown timer during quizzes so I know how much time I have left
-- **US-3**: I want to see time limits for "Ask Question" interactions so I can manage my questions effectively
-- **US-4**: I want to see a timer during Learning Check sessions so I can pace my responses
-
-### As an Instructor/Admin
-
-- **US-5**: I want to see actual vs estimated completion times to improve time estimates
-- **US-6**: I want to track how long learners spend on different content types
-- **US-7**: I want to set appropriate time limits for different interaction types
-
-## Technical Requirements
-
-### Next.js 15+ Compliance
-- **Server Components First**: Timer display components should be Server Components when possible
-- **Client Components Only**: Use "use client" only for interactive timer functionality
-- **Route Parameters**: Use `await params` in Server Components, `useParams` hook in Client Components
-
-### Timer Component Specifications
-
-#### Core Timer Component
-
-```typescript
-/**
- * Timer component props interface following development standards
- *
- * @interface TimerProps
- * @description Defines props for reusable timer component with multiple variants
- */
-interface TimerProps {
- /** Timer duration in seconds */
- duration: number;
- /** Callback fired when timer completes */
- onComplete?: () => void;
- /** Callback fired every second with remaining/elapsed time */
- onTick?: (remaining: number) => void;
- /** Whether to start timer immediately on mount */
- autoStart?: boolean;
- /** Whether to show milliseconds in display */
- showMilliseconds?: boolean;
- /** Timer behavior and visual variant */
- variant?: "countdown" | "stopwatch" | "progress";
- /** Visual size variant */
- size?: "sm" | "md" | "lg";
- /** Color theme for urgency indication */
- color?: "default" | "warning" | "danger" | "success";
- /** Accessibility label for screen readers */
- "aria-label"?: string;
- /** Additional CSS classes for styling */
- className?: string;
-}
-```
-
-#### Timer Variants
-
-1. **Countdown Timer**: Shows time remaining (Quiz, Ask Question, Learning Check)
-2. **Stopwatch Timer**: Shows elapsed time (Content consumption tracking)
-3. **Progress Timer**: Shows progress bar with time (Section completion)
-
-### Estimation System Specifications
-
-#### Content Analysis Function
-
-```typescript
-/**
- * Content analysis data structure for time estimation
- *
- * @interface ContentAnalysis
- * @description Analyzes content characteristics for accurate time estimation
- */
-interface ContentAnalysis {
- /** Total word count for reading time calculation */
- wordCount: number;
- /** Video duration in seconds (exact timing) */
- videoDuration: number;
- /** Number of interactive elements (forms, quizzes, etc.) */
- interactiveElements: number;
- /** Content complexity level affecting processing time */
- complexity: "low" | "medium" | "high";
- /** Content type for specialized estimation algorithms */
- contentType?: "text" | "video" | "interactive" | "mixed";
-}
-
-/**
- * Time estimation result with breakdown and confidence
- *
- * @interface TimeEstimate
- * @description Provides detailed time breakdown for user planning
- */
-interface TimeEstimate {
- /** Estimated reading time in minutes */
- reading: number;
- /** Video playback time in minutes */
- video: number;
- /** Additional interaction/processing time in minutes */
- interaction: number;
- /** Total estimated completion time in minutes */
- total: number;
- /** Confidence level (0-1 scale) based on data quality */
- confidence: number;
- /** Minimum estimated time (optimistic scenario) */
- minTime?: number;
- /** Maximum estimated time (pessimistic scenario) */
- maxTime?: number;
-}
-```
-
-#### Estimation Algorithm
-
-- **Reading Time**: wordCount / 225 words per minute (average reading speed)
-- **Video Time**: videoDuration (actual duration)
-- **Interactive Time**: Based on element type and complexity
-- **Buffer Time**: 10-20% buffer for navigation and processing
-
-### Implementation Architecture
-
-#### Component Structure (Following Development Standards)
-
-```
-src/components/common/
-├── timer/
-│ ├── Timer.tsx # Main timer component (Client Component)
-│ ├── CountdownTimer.tsx # Countdown variant (Client Component)
-│ ├── StopwatchTimer.tsx # Stopwatch variant (Client Component)
-│ ├── ProgressTimer.tsx # Progress variant (Client Component)
-│ └── TimerDisplay.tsx # Display formatting (Server Component)
-├── estimation/
-│ ├── EstimatedTime.tsx # Time estimate display (Server Component)
-│ ├── TimeEstimator.tsx # Estimation component (Server Component)
-│ └── CompletionBadge.tsx # Time completion indicator (Server Component)
-```
-
-#### Utility Functions (Hot-Swappable Data Layer)
-
-```
-src/lib/
-├── utils/
-│ ├── time-estimation.ts # Core estimation algorithms
-│ ├── time-formatting.ts # Time display formatting
-│ ├── content-analysis.ts # Content parsing and analysis
-│ └── timer-hooks.ts # Custom timer hooks (Client-side only)
-├── data/
-│ ├── timer-data.ts # Timer configuration data
-│ └── estimation-data.ts # Estimation algorithm data
-└── services/
- ├── timer-service.ts # Timer data persistence (hot-swappable)
- └── analytics-service.ts # Timer analytics (hot-swappable)
-```
-
-#### Clean Code & Comments Standards
-
-All components must include:
-- **JSDoc headers** with purpose, parameters, examples
-- **Business logic comments** explaining "why" decisions
-- **Edge case documentation** for error handling
-- **Performance notes** for optimization considerations
-- **Accessibility comments** for screen reader support
-
-## Detailed Specifications
-
-### Timer Component Features
-
-#### Countdown Timer (Quiz, Ask Question, Learning Check)
-
-- **Visual Indicators**: Color changes as time runs low (green → yellow → red)
-- **Audio Alerts**: Optional sound notifications at intervals
-- **Pause/Resume**: Ability to pause and resume timing
-- **Auto-submit**: Automatic form submission when time expires
-- **Grace Period**: Optional 5-second grace period for submission
-
-#### Stopwatch Timer (Content Tracking)
-
-- **Background Tracking**: Continues timing even when tab is inactive
-- **Pause Detection**: Automatically pauses when user is inactive
-- **Resume Logic**: Smart resume when user returns to content
-- **Accuracy**: Millisecond precision for detailed analytics
-
-#### Progress Timer (Section Completion)
-
-- **Visual Progress**: Circular or linear progress indicator
-- **Milestone Markers**: Show progress checkpoints
-- **Estimated vs Actual**: Compare estimated vs actual time
-- **Completion Celebration**: Visual feedback on completion
-
-### Estimation System Features
-
-#### Content Analysis
-
-- **Text Analysis**: Word count, reading complexity, technical terms
-- **Video Analysis**: Duration, chapters, interactive elements
-- **Interactive Analysis**: Forms, quizzes, simulations
-- **Historical Data**: Learn from actual completion times
-
-#### Estimation Display
-
-- **Range Estimates**: Show min-max range (e.g., "15-20 minutes")
-- **Confidence Indicators**: Visual confidence level indicators
-- **Personalization**: Adjust based on user's historical performance
-- **Real-time Updates**: Update estimates based on current progress
-
-### Integration Points
-
-#### Course Sidebar Integration
-
-```typescript
-// Display estimated time for each chapter/section
-
-```
-
-#### Quiz Integration
-
-```typescript
-// Countdown timer for quiz attempts
-
-```
-
-#### AI Integration
-
-```typescript
-// Timer for Ask Question feature
-
-```
-
-## Acceptance Criteria
-
-### Timer Component
-
-- [ ] **AC-1**: Timer displays accurate countdown/stopwatch functionality
-- [ ] **AC-2**: Visual indicators change appropriately based on time remaining
-- [ ] **AC-3**: Timer continues accurately even with tab switching
-- [ ] **AC-4**: Pause/resume functionality works correctly
-- [ ] **AC-5**: Timer integrates seamlessly with form submissions
-- [ ] **AC-6**: Component is fully accessible with screen readers
-- [ ] **AC-7**: Timer works consistently across all major browsers
-
-### Estimation System
-
-- [ ] **AC-8**: Time estimates are within 20% accuracy for 80% of content
-- [ ] **AC-9**: Estimates improve over time with user data
-- [ ] **AC-10**: Estimation component displays clearly and consistently
-- [ ] **AC-11**: Estimates account for different content types appropriately
-- [ ] **AC-12**: System handles edge cases (very short/long content)
-- [ ] **AC-13**: Estimates are personalized based on user performance
-- [ ] **AC-14**: Confidence indicators accurately reflect estimate reliability
-
-### Performance Requirements
-
-- [ ] **AC-15**: Timer updates smoothly without performance impact
-- [ ] **AC-16**: Estimation calculations complete in < 100ms
-- [ ] **AC-17**: Components render in < 50ms
-- [ ] **AC-18**: Memory usage remains stable during long sessions
-- [ ] **AC-19**: Timer accuracy maintained across device sleep/wake cycles
-
-## Dependencies
-
-### Internal Dependencies
-
-- **Progress Tracking System**: Timer data feeds into progress calculations
-- **User Profile System**: Personalization requires user learning history
-- **Content Management**: Estimation requires content metadata
-- **Analytics System**: Timer data used for performance analytics
-
-### External Dependencies
-
-- **Tavus AI Integration**: Timer integration for AI interactions
-- **Video Player**: Integration with video progress tracking
-- **Database**: Storage for timing data and user preferences
-- **Browser APIs**: Performance timing and visibility APIs
-
-## Testing Strategy
-
-### Jest Configuration Reference
-
-**Official Documentation**: [Next.js Testing with Jest](https://nextjs.org/docs/app/guides/testing/jest.md)
-
-Our testing setup follows Next.js official recommendations for:
-- **Jest Configuration**: Using `next/jest` for optimal Next.js integration
-- **React Testing Library**: Component testing best practices
-- **TypeScript Support**: Full type checking in tests
-- **Path Mapping**: Proper `@/` alias resolution in test files
-- **Mock Handling**: Next.js specific mocking patterns
-
-### Unit Tests (Jest + React Testing Library)
-
-- **Timer accuracy and functionality**: Verify countdown/stopwatch precision
-- **Estimation algorithm correctness**: Test calculation accuracy with various inputs
-- **Component rendering and props handling**: Ensure proper prop validation and rendering
-- **Edge case handling**: Zero time, negative values, extremely long durations
-- **Accessibility**: Screen reader compatibility and ARIA attributes
-- **Performance**: Component render times and memory usage
-
-### Integration Tests
-
-- **Timer integration with forms**: Auto-submission on timeout
-- **Estimation integration with content display**: Real-time estimate updates
-- **Cross-component timer synchronization**: Multiple timers coordination
-- **Database integration**: Timer data persistence and retrieval
-- **Hot-swappable data layer**: Service layer abstraction testing
-
-### User Acceptance Tests
-
-- **Timer usability**: Real learning session testing with actual users
-- **Estimation accuracy validation**: Compare estimates vs actual completion times
-- **Accessibility testing**: Screen reader and keyboard navigation testing
-- **Performance testing**: Various devices, network conditions, and browser testing
-- **Cross-browser compatibility**: Chrome, Firefox, Safari, Edge testing
-
-### Pre-commit Validation
-
-All timer components must pass:
-- **ESLint strict checks**: No unused variables, proper TypeScript usage
-- **TypeScript compilation**: Strict mode compliance
-- **Unit test coverage**: Minimum 80% coverage for new code
-- **Build verification**: Components compile without errors
-
-## Future Enhancements
-
-### Phase 2 Features
-
-- **Adaptive Timing**: AI-powered time limit adjustments
-- **Collaborative Timing**: Synchronized timers for group activities
-- **Advanced Analytics**: Detailed timing pattern analysis
-- **Gamification**: Time-based achievements and challenges
-
-### Phase 3 Features
-
-- **Predictive Estimation**: Machine learning-based time predictions
-- **Context-Aware Timing**: Environment-based timer adjustments
-- **Multi-modal Timing**: Voice and gesture-controlled timers
-- **Integration APIs**: Third-party timer service integrations
-
-## Implementation Strategy
-
-### Stacked PR Approach (Following Release Strategy)
-
-This feature will be implemented using **stacked PRs** to maintain 200-400 LOC per PR:
-
-#### Phase 1: Core Timer Infrastructure
-**Branch**: `feature/timer-01-core`
-**Size**: ~250-300 LOC
-**Files**:
-- `src/components/common/timer/Timer.tsx` (Client Component)
-- `src/components/common/timer/TimerDisplay.tsx` (Server Component)
-- `src/lib/utils/time-formatting.ts`
-- `src/hooks/useTimer.ts`
-- Basic unit tests
-
-#### Phase 2: Timer Variants
-**Branch**: `feature/timer-02-variants` (depends on Phase 1)
-**Size**: ~300-350 LOC
-**Files**:
-- `src/components/common/timer/CountdownTimer.tsx`
-- `src/components/common/timer/StopwatchTimer.tsx`
-- `src/components/common/timer/ProgressTimer.tsx`
-- Variant-specific tests
-
-#### Phase 3: Estimation System
-**Branch**: `feature/timer-03-estimation` (independent)
-**Size**: ~350-400 LOC
-**Files**:
-- `src/lib/utils/time-estimation.ts`
-- `src/lib/utils/content-analysis.ts`
-- `src/components/common/estimation/EstimatedTime.tsx`
-- `src/components/common/estimation/TimeEstimator.tsx`
-- Estimation algorithm tests
-
-#### Phase 4: Integration & Services
-**Branch**: `feature/timer-04-integration` (depends on Phases 1-3)
-**Size**: ~200-250 LOC
-**Files**:
-- `src/lib/services/timer-service.ts` (hot-swappable)
-- `src/lib/services/analytics-service.ts` (hot-swappable)
-- Integration tests
-- Final documentation updates
-
-### PR Dependencies
-```
-Phase 1 (Core) → Phase 2 (Variants)
-Phase 3 (Estimation) → Phase 4 (Integration)
- ↗
-Phase 2 (Variants) → Phase 4 (Integration)
-```
-
-### Quality Gates
-Each PR must pass:
-- ✅ ESLint strict validation
-- ✅ TypeScript compilation
-- ✅ Unit tests (80%+ coverage)
-- ✅ Build verification
-- ✅ Code review (1-2 reviewers)
-
----
-
-**Implementation Priority**: High - Required for MVP quiz and AI interaction features
diff --git a/src/app/api/learning-checks/conversation/[conversationId]/end/route.ts b/src/app/api/learning-checks/conversation/[conversationId]/end/route.ts
new file mode 100644
index 0000000..4cd72ee
--- /dev/null
+++ b/src/app/api/learning-checks/conversation/[conversationId]/end/route.ts
@@ -0,0 +1,99 @@
+import { NextRequest, NextResponse } from "next/server";
+import { TAVUS_ENV } from "@/lib/tavus";
+
+/**
+ * End Tavus Conversation
+ *
+ * Server-side endpoint to safely end a conversation using the Tavus API.
+ * API key is kept server-side only for security.
+ *
+ * @route POST /api/learning-checks/conversation/[conversationId]/end
+ * @see https://docs.tavus.io/api-reference/conversations/end-conversation
+ */
+export async function POST(
+ request: NextRequest,
+ { params }: { params: Promise<{ conversationId: string }> }
+) {
+ try {
+ // Next.js 15: params is now a Promise and must be awaited
+ const { conversationId } = await params;
+
+ // Validate conversation ID
+ if (!conversationId) {
+ return NextResponse.json(
+ { error: "Conversation ID is required" },
+ { status: 400 }
+ );
+ }
+
+ // Get API key from server environment (NEVER exposed to client)
+ const apiKey = TAVUS_ENV.getApiKey();
+
+ if (!apiKey) {
+ console.error("❌ Tavus API key not configured");
+ return NextResponse.json(
+ { error: "Server configuration error" },
+ { status: 500 }
+ );
+ }
+
+ console.log("🛑 Ending Tavus conversation:", conversationId);
+
+ // Call Tavus API to end conversation
+ // Per Tavus docs: POST /v2/conversations/{conversation_id}/end
+ const response = await fetch(
+ `https://tavusapi.com/v2/conversations/${conversationId}/end`,
+ {
+ method: "POST", // Per Tavus API spec
+ headers: {
+ "x-api-key": apiKey,
+ },
+ }
+ );
+
+ if (!response.ok) {
+ // Handle Tavus API errors
+ const errorData = await response.json().catch(() => ({}));
+ console.error("❌ Tavus API error:", {
+ status: response.status,
+ statusText: response.statusText,
+ error: errorData,
+ });
+
+ // Return appropriate error
+ if (response.status === 400) {
+ return NextResponse.json(
+ { error: errorData.error || "Invalid conversation_id" },
+ { status: 400 }
+ );
+ }
+
+ if (response.status === 401) {
+ return NextResponse.json(
+ { message: errorData.message || "Invalid access token" },
+ { status: 401 }
+ );
+ }
+
+ return NextResponse.json(
+ { error: "Failed to end conversation" },
+ { status: response.status }
+ );
+ }
+
+ const data = await response.json().catch(() => ({}));
+ console.log("✅ Conversation ended successfully:", conversationId);
+
+ return NextResponse.json({
+ success: true,
+ conversation_id: conversationId,
+ ...data,
+ });
+ } catch (error) {
+ console.error("❌ Error ending conversation:", error);
+ return NextResponse.json(
+ { error: "Internal server error" },
+ { status: 500 }
+ );
+ }
+}
diff --git a/src/app/api/learning-checks/conversation/route.ts b/src/app/api/learning-checks/conversation/route.ts
new file mode 100644
index 0000000..9906a07
--- /dev/null
+++ b/src/app/api/learning-checks/conversation/route.ts
@@ -0,0 +1,148 @@
+import { NextRequest, NextResponse } from "next/server";
+import {
+ buildChapterContext,
+ buildGreeting,
+ TAVUS_DEFAULTS,
+ TAVUS_ENV,
+} from "@/lib/tavus";
+
+/**
+ * Create Tavus Conversation for Learning Check
+ *
+ * Phase 1: Structured conversation with objectives, guardrails, and chapter context
+ * Phase 2: Add webhook URL for perception analysis
+ *
+ * Architecture:
+ * - Objectives: Enforce assessment structure (recall → application → self-explanation)
+ * - Guardrails: Enforce behavioral boundaries (quiz protection, time management, scope)
+ * - Context: Chapter-specific information and learning objectives
+ *
+ * @see specs/features/learning-check/learning-check-implementation.md
+ * @see src/lib/tavus/config.ts for configuration
+ */
+
+interface CreateConversationRequest {
+ chapterId: string;
+ chapterTitle: string;
+ objectivesId?: string; // Optional: override default objectives
+ guardrailsId?: string; // Optional: override default guardrails
+}
+
+export async function POST(request: NextRequest) {
+ try {
+ const body: CreateConversationRequest = await request.json();
+ const { chapterId, chapterTitle, objectivesId, guardrailsId } = body;
+
+ // Validate required fields
+ if (!chapterId || !chapterTitle) {
+ return NextResponse.json(
+ { error: "Missing required fields: chapterId, chapterTitle" },
+ { status: 400 }
+ );
+ }
+
+ // Get environment variables using type-safe helpers
+ const apiKey = TAVUS_ENV.getApiKey();
+ const personaId = TAVUS_ENV.getPersonaId();
+
+ if (!apiKey || !personaId) {
+ console.error("Missing Tavus configuration:", {
+ hasApiKey: !!apiKey,
+ hasPersonaId: !!personaId,
+ });
+ return NextResponse.json(
+ {
+ error:
+ "Tavus configuration missing. Please set TAVUS_API_KEY and TAVUS_PERSONA_ID.",
+ },
+ { status: 500 }
+ );
+ }
+
+ // Build chapter-specific context and greeting for AI instructor
+ const conversationalContext = buildChapterContext(chapterId, chapterTitle);
+ const customGreeting = buildGreeting(chapterTitle);
+
+ // Get learning check duration from environment (default: 180 seconds = 3 minutes)
+ const learningCheckDuration = TAVUS_ENV.getLearningCheckDuration();
+
+ // Create Tavus conversation with structured objectives and guardrails
+ const conversationBody: Record = {
+ persona_id: personaId,
+ replica_id: TAVUS_DEFAULTS.DEFAULT_REPLICA_ID, // Required if persona doesn't have default replica
+ conversational_context: conversationalContext,
+ custom_greeting: customGreeting,
+ conversation_name: `Learning Check: ${chapterTitle}`, //TODO: add session id to conversation name
+ test_mode: TAVUS_DEFAULTS.TEST_MODE,
+ // Enforce time limit (max 3600 seconds per Tavus API)
+ properties: {
+ max_call_duration: learningCheckDuration,
+ participant_left_timeout: 10, // End 10 seconds after participant leaves
+ participant_absent_timeout: 60, // End if no one joins within 60 seconds
+ },
+ };
+
+ // Add objectives ID (for structured assessment)
+ // Use provided ID or fall back to environment variable
+ const finalObjectivesId = objectivesId || TAVUS_ENV.getObjectivesId();
+ if (finalObjectivesId) {
+ conversationBody.objectives_id = finalObjectivesId;
+ }
+
+ // Add guardrails ID (for compliance enforcement)
+ // Use provided ID or fall back to environment variable
+ const finalGuardrailsId = guardrailsId || TAVUS_ENV.getGuardrailsId();
+ if (finalGuardrailsId) {
+ conversationBody.guardrails_id = finalGuardrailsId;
+ }
+
+ // Phase 2: Add callback_url for perception analysis webhook
+ const webhookUrl = TAVUS_ENV.getWebhookUrl();
+ if (webhookUrl) {
+ conversationBody.callback_url = webhookUrl;
+ }
+
+ const tavusResponse = await fetch(
+ `${TAVUS_DEFAULTS.API_BASE_URL}/conversations`,
+ {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ "x-api-key": apiKey,
+ },
+ body: JSON.stringify(conversationBody),
+ }
+ );
+
+ if (!tavusResponse.ok) {
+ const errorData = await tavusResponse.json().catch(() => ({}));
+ console.error("Tavus API error:", {
+ status: tavusResponse.status,
+ statusText: tavusResponse.statusText,
+ error: errorData,
+ });
+ return NextResponse.json(
+ { error: "Failed to create Tavus conversation" },
+ { status: tavusResponse.status }
+ );
+ }
+
+ const data = await tavusResponse.json();
+
+ return NextResponse.json({
+ conversationUrl: data.conversation_url,
+ conversationId: data.conversation_id,
+ expiresAt: data.expires_at,
+ });
+ } catch (error) {
+ console.error("Error creating conversation:", error);
+ return NextResponse.json(
+ { error: "Internal server error" },
+ { status: 500 }
+ );
+ }
+}
+
+// Helper functions moved to @/lib/tavus/config.ts for centralized configuration
+// - buildGreeting(chapterTitle): Creates custom greeting
+// - buildChapterContext(chapterId, chapterTitle): Builds chapter-specific context
diff --git a/src/app/api/learning-checks/guardrails/route.ts b/src/app/api/learning-checks/guardrails/route.ts
new file mode 100644
index 0000000..12ef4e6
--- /dev/null
+++ b/src/app/api/learning-checks/guardrails/route.ts
@@ -0,0 +1,71 @@
+import { NextRequest, NextResponse } from 'next/server';
+import { LEARNING_CHECK_GUARDRAILS } from '@/lib/tavus';
+
+/**
+ * Create Tavus Guardrails for Learning Check Compliance
+ *
+ * These guardrails enforce strict behavioral boundaries:
+ * 1. Quiz answer protection - never reveal quiz content
+ * 2. Time management - keep responses brief and efficient
+ * 3. Content scope - stay focused on chapter content
+ * 4. Encouraging tone - maintain supportive interaction
+ *
+ * Guardrails are created once and reused across all learning checks
+ *
+ * @see src/lib/tavus/config.ts for guardrail definitions
+ */
+
+export async function POST(_request: NextRequest) {
+ try {
+ const apiKey = process.env.TAVUS_API_KEY;
+
+ if (!apiKey) {
+ return NextResponse.json(
+ { error: 'TAVUS_API_KEY environment variable is required' },
+ { status: 500 }
+ );
+ }
+
+ // Use centralized guardrails configuration
+ const guardrailsConfig = LEARNING_CHECK_GUARDRAILS;
+
+ const tavusResponse = await fetch('https://tavusapi.com/v2/guardrails', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ 'x-api-key': apiKey,
+ },
+ body: JSON.stringify(guardrailsConfig)
+ });
+
+ if (!tavusResponse.ok) {
+ const errorData = await tavusResponse.json().catch(() => ({}));
+ console.error('Tavus Guardrails API error:', {
+ status: tavusResponse.status,
+ statusText: tavusResponse.statusText,
+ error: errorData
+ });
+ return NextResponse.json(
+ { error: 'Failed to create Tavus guardrails' },
+ { status: tavusResponse.status }
+ );
+ }
+
+ const data = await tavusResponse.json();
+
+ return NextResponse.json({
+ guardrailsId: data.guardrails_id,
+ guardrailsName: data.guardrails_name,
+ status: data.status,
+ createdAt: data.created_at,
+ guardrails: data.guardrails || guardrailsConfig.data
+ });
+
+ } catch (error) {
+ console.error('Error creating guardrails:', error);
+ return NextResponse.json(
+ { error: 'Internal server error' },
+ { status: 500 }
+ );
+ }
+}
diff --git a/src/app/api/learning-checks/objectives/route.ts b/src/app/api/learning-checks/objectives/route.ts
new file mode 100644
index 0000000..2e5f5b7
--- /dev/null
+++ b/src/app/api/learning-checks/objectives/route.ts
@@ -0,0 +1,72 @@
+import { NextRequest, NextResponse } from 'next/server';
+import { LEARNING_CHECK_OBJECTIVES } from '@/lib/tavus';
+
+/**
+ * Create Tavus Objectives for Learning Check Assessment
+ *
+ * These objectives provide structured enforcement of the assessment requirements:
+ * 1. Recall question about key concepts
+ * 2. Application question about real-world usage
+ * 3. Self-explanation question to check understanding
+ *
+ * Objectives are created once and reused across all learning checks
+ *
+ * @see src/lib/tavus/config.ts for objective definitions
+ */
+
+export async function POST(_request: NextRequest) {
+ try {
+ const apiKey = process.env.TAVUS_API_KEY;
+
+ if (!apiKey) {
+ return NextResponse.json(
+ { error: 'TAVUS_API_KEY environment variable is required' },
+ { status: 500 }
+ );
+ }
+
+ // Use centralized objectives configuration
+ const objectives = LEARNING_CHECK_OBJECTIVES;
+
+ const tavusResponse = await fetch('https://tavusapi.com/v2/objectives', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ 'x-api-key': apiKey,
+ },
+ body: JSON.stringify({
+ data: objectives
+ })
+ });
+
+ if (!tavusResponse.ok) {
+ const errorData = await tavusResponse.json().catch(() => ({}));
+ console.error('Tavus Objectives API error:', {
+ status: tavusResponse.status,
+ statusText: tavusResponse.statusText,
+ error: errorData
+ });
+ return NextResponse.json(
+ { error: 'Failed to create Tavus objectives' },
+ { status: tavusResponse.status }
+ );
+ }
+
+ const data = await tavusResponse.json();
+
+ return NextResponse.json({
+ objectivesId: data.objectives_id,
+ objectivesName: data.objectives_name,
+ status: data.status,
+ createdAt: data.created_at,
+ objectives: data.objectives || objectives
+ });
+
+ } catch (error) {
+ console.error('Error creating objectives:', error);
+ return NextResponse.json(
+ { error: 'Internal server error' },
+ { status: 500 }
+ );
+ }
+}
diff --git a/src/app/api/learning-checks/terminate/route.ts b/src/app/api/learning-checks/terminate/route.ts
new file mode 100644
index 0000000..608998e
--- /dev/null
+++ b/src/app/api/learning-checks/terminate/route.ts
@@ -0,0 +1,89 @@
+import { NextRequest, NextResponse } from 'next/server';
+
+/**
+ * Terminate Tavus Conversation
+ *
+ * Critical for cost management: Always end conversations when:
+ * - Timer expires
+ * - User manually ends session
+ * - User navigates away
+ * - Component unmounts
+ * - Connection lost
+ *
+ * @see specs/features/learning-check/learning-check-spec.md#conversation-termination--cost-management
+ */
+
+interface TerminateConversationRequest {
+ conversationId: string;
+}
+
+export async function POST(request: NextRequest) {
+ try {
+ const body: TerminateConversationRequest = await request.json();
+ const { conversationId } = body;
+
+ if (!conversationId) {
+ return NextResponse.json(
+ { error: 'Missing required field: conversationId' },
+ { status: 400 }
+ );
+ }
+
+ const apiKey = process.env.TAVUS_API_KEY;
+
+ if (!apiKey) {
+ console.error('Missing TAVUS_API_KEY');
+ return NextResponse.json(
+ { error: 'Tavus configuration missing' },
+ { status: 500 }
+ );
+ }
+
+ // Call Tavus API to end conversation
+ const tavusResponse = await fetch(
+ `https://tavusapi.com/v2/conversations/${conversationId}`,
+ {
+ method: 'DELETE',
+ headers: {
+ 'x-api-key': apiKey,
+ }
+ }
+ );
+
+ if (!tavusResponse.ok) {
+ const errorData = await tavusResponse.json().catch(() => ({}));
+ console.error('Tavus termination error:', {
+ conversationId,
+ status: tavusResponse.status,
+ error: errorData
+ });
+
+ // Don't fail the request if conversation is already ended
+ if (tavusResponse.status === 404) {
+ return NextResponse.json({
+ success: true,
+ message: 'Conversation already ended'
+ });
+ }
+
+ return NextResponse.json(
+ { error: 'Failed to terminate conversation' },
+ { status: tavusResponse.status }
+ );
+ }
+
+ console.log('✅ Conversation terminated:', conversationId);
+
+ return NextResponse.json({
+ success: true,
+ conversationId
+ });
+
+ } catch (error) {
+ console.error('Error terminating conversation:', error);
+ return NextResponse.json(
+ { error: 'Internal server error' },
+ { status: 500 }
+ );
+ }
+}
diff --git a/src/app/api/learning-checks/update-persona/route.ts b/src/app/api/learning-checks/update-persona/route.ts
new file mode 100644
index 0000000..d4fa028
--- /dev/null
+++ b/src/app/api/learning-checks/update-persona/route.ts
@@ -0,0 +1,129 @@
+import { NextRequest, NextResponse } from 'next/server';
+import { PERSONA_CONFIG } from '@/lib/tavus';
+
+/**
+ * Update Tavus Persona to align with Learning Check structured approach
+ *
+ * This updates the persona to work harmoniously with objectives and guardrails
+ * rather than conflicting with them.
+ *
+ * @see src/lib/tavus/config.ts for persona configuration
+ */
+
+interface UpdatePersonaRequest {
+ persona_id: string;
+ system_prompt?: string;
+ context?: string;
+ objectives_id?: string;
+ guardrails_id?: string;
+}
+
+export async function PATCH(request: NextRequest) {
+ try {
+ const body: UpdatePersonaRequest = await request.json();
+ const { persona_id, objectives_id, guardrails_id } = body;
+
+ if (!persona_id) {
+ return NextResponse.json(
+ { error: 'persona_id is required' },
+ { status: 400 }
+ );
+ }
+
+ const apiKey = process.env.TAVUS_API_KEY;
+
+ if (!apiKey) {
+ return NextResponse.json(
+ { error: 'TAVUS_API_KEY environment variable is required' },
+ { status: 500 }
+ );
+ }
+
+ // Use centralized persona configuration
+ const updatedSystemPrompt = PERSONA_CONFIG.system_prompt;
+ const updatedContext = PERSONA_CONFIG.context;
+
+ // Build JSON Patch operations array (per Tavus PATCH API spec)
+ type JsonPatchOperation = {
+ op: 'replace' | 'add' | 'remove';
+ path: string;
+ value?: string;
+ };
+
+ const patchOperations: JsonPatchOperation[] = [
+ {
+ op: 'replace',
+ path: '/system_prompt',
+ value: updatedSystemPrompt
+ },
+ {
+ op: 'replace',
+ path: '/context',
+ value: updatedContext
+ }
+ ];
+
+ // Add objectives_id if provided
+ if (objectives_id) {
+ patchOperations.push({
+ op: 'add',
+ path: '/objectives_id',
+ value: objectives_id
+ });
+ }
+
+ // Add guardrails_id if provided
+ if (guardrails_id) {
+ patchOperations.push({
+ op: 'add',
+ path: '/guardrails_id',
+ value: guardrails_id
+ });
+ }
+
+ console.log('🎭 Updating persona with JSON Patch operations:', {
+ persona_id,
+ operations: patchOperations.map(op => `${op.op} ${op.path}`)
+ });
+
+ const tavusResponse = await fetch(`https://tavusapi.com/v2/personas/${persona_id}`, {
+ method: 'PATCH',
+ headers: {
+ 'Content-Type': 'application/json',
+ 'x-api-key': apiKey,
+ },
+ body: JSON.stringify(patchOperations)
+ });
+
+ if (!tavusResponse.ok) {
+ const errorData = await tavusResponse.json().catch(() => ({}));
+ console.error('Tavus Persona Update API error:', {
+ status: tavusResponse.status,
+ statusText: tavusResponse.statusText,
+ error: errorData
+ });
+ return NextResponse.json(
+ { error: 'Failed to update Tavus persona' },
+ { status: tavusResponse.status }
+ );
+ }
+
+ const data = await tavusResponse.json();
+
+ return NextResponse.json({
+ personaId: data.persona_id,
+ personaName: data.persona_name,
+ status: data.status,
+ updatedAt: data.updated_at,
+ hasObjectives: !!objectives_id,
+ hasGuardrails: !!guardrails_id
+ });
+
+ } catch (error) {
+ console.error('Error updating persona:', error);
+ return NextResponse.json(
+ { error: 'Internal server error' },
+ { status: 500 }
+ );
+ }
+}
diff --git a/src/app/api/tavus/conversation/route.ts b/src/app/api/tavus/conversation/route.ts
deleted file mode 100644
index f760f77..0000000
--- a/src/app/api/tavus/conversation/route.ts
+++ /dev/null
@@ -1,181 +0,0 @@
-/**
- * Tavus Conversation API Route
- *
- * Creates Tavus conversation sessions with chapter-specific context
- *
- * @route POST /api/tavus/conversation
- * @body {CreateConversationRequest}
- * @returns {CreateConversationResponse}
- *
- * @example
- * ```typescript
- * const response = await fetch('/api/tavus/conversation', {
- * method: 'POST',
- * body: JSON.stringify({
- * chapterId: 'ch-1',
- * courseId: 'course-1',
- * chapterTitle: 'EMDR Foundations',
- * timeLimit: 240
- * })
- * });
- * ```
- */
-
-import { NextRequest, NextResponse } from 'next/server';
-import type {
- CreateConversationRequest,
- CreateConversationResponse,
- TavusConversationPayload,
- TavusConversationAPIResponse,
-} from '@/types/tavus';
-
-/**
- * POST /api/tavus/conversation
- *
- * Creates a new Tavus conversation session with chapter context
- *
- * @param request - Next.js request object
- * @returns Conversation URL and metadata
- */
-export async function POST(request: NextRequest) {
- try {
- // Parse request body
- const body: CreateConversationRequest = await request.json();
- const { chapterId, courseId, chapterTitle, timeLimit, personaId } = body;
-
- // Validate required fields
- if (!chapterId || !courseId || !chapterTitle) {
- return NextResponse.json(
- { error: 'Missing required fields: chapterId, courseId, chapterTitle' },
- { status: 400 }
- );
- }
-
- // Validate environment variables
- const apiKey = process.env.TAVUS_API_KEY;
- const replicaId = process.env.TAVUS_REPLICA_ID;
- const defaultPersonaId = process.env.TAVUS_PERSONA_ID;
- const defaultCallDuration = parseInt(
- process.env.TAVUS_DEFAULT_CALL_DURATION || '240',
- 10
- );
-
- if (!apiKey || !replicaId) {
- console.error('Missing required Tavus environment variables');
- return NextResponse.json(
- {
- error:
- 'Tavus API not configured. Please set TAVUS_API_KEY and TAVUS_REPLICA_ID.',
- },
- { status: 500 }
- );
- }
-
- // Generate conversational context
- const conversationalContext = generateConversationalContext({
- chapterTitle,
- chapterId,
- });
-
- // Prepare Tavus API payload
- const tavusPayload: TavusConversationPayload = {
- replica_id: replicaId,
- persona_id: personaId || defaultPersonaId,
- conversational_context: conversationalContext,
- max_call_duration: timeLimit || defaultCallDuration,
- properties: {
- chapterId,
- courseId,
- chapterTitle,
- },
- };
-
- // Call Tavus API to create conversation
- const tavusResponse = await fetch(
- 'https://tavusapi.com/v2/conversations',
- {
- method: 'POST',
- headers: {
- 'x-api-key': apiKey,
- 'Content-Type': 'application/json',
- },
- body: JSON.stringify(tavusPayload),
- }
- );
-
- if (!tavusResponse.ok) {
- const errorData = await tavusResponse.json().catch(() => ({}));
- console.error('Tavus API error:', errorData);
- return NextResponse.json(
- {
- error: 'Failed to create Tavus conversation',
- details: errorData,
- },
- { status: tavusResponse.status }
- );
- }
-
- const tavusData: TavusConversationAPIResponse = await tavusResponse.json();
-
- // Return conversation details
- const response: CreateConversationResponse = {
- conversationUrl: tavusData.conversation_url,
- conversationId: tavusData.conversation_id,
- expiresAt: tavusData.expires_at,
- };
-
- return NextResponse.json(response);
- } catch (error) {
- console.error('Error creating Tavus conversation:', error);
- return NextResponse.json(
- {
- error: 'Internal server error',
- details: error instanceof Error ? error.message : 'Unknown error',
- },
- { status: 500 }
- );
- }
-}
-
-/**
- * Generates conversational context string for Tavus AI
- *
- * @param params - Chapter context parameters
- * @returns Formatted context string for AI instructor
- *
- * Context includes:
- * - Chapter title and objectives
- * - Instructor tone and style
- * - Response guidelines
- *
- * Future Enhancement: Fetch context from database or mock-data
- */
-function generateConversationalContext(params: {
- chapterTitle: string;
- chapterId: string;
-}): string {
- const { chapterTitle } = params;
-
- // TODO: Fetch conversationalContext from mock-data.ts based on chapterId
- // For now, use a general template
-
- return `
-You are an expert EMDR therapy instructor helping a learner understand course content.
-
-Current Chapter: ${chapterTitle}
-
-Instruction Style:
-- Be conversational and encouraging
-- Use simple language to explain complex concepts
-- Provide concrete examples when possible
-- Ask clarifying questions if needed
-- Keep responses concise (30-45 seconds)
-- Reference chapter content when appropriate
-
-Question Guidelines:
-- Answer questions within the scope of this chapter
-- If asked about other chapters, redirect to current content
-- Encourage critical thinking with follow-up questions
-- Validate student understanding with examples
-`.trim();
-}
diff --git a/src/app/layout.tsx b/src/app/layout.tsx
index d899a6d..fa48deb 100644
--- a/src/app/layout.tsx
+++ b/src/app/layout.tsx
@@ -5,6 +5,7 @@ import AuthProvider from "@/components/auth/AuthProvider";
import SessionHandler from "@/components/auth/SessionHandler";
import EmailVerificationHandler from "@/components/auth/EmailVerificationHandler";
import { ThemeProvider } from "@/components/theme-provider";
+import { CVIProvider } from "@/components/cvi/components/cvi-provider";
export const metadata: Metadata = {
title: "8P3P LMS",
@@ -24,8 +25,7 @@ export default function RootLayout({
-
- {children}
+ {children}