# CLAUDE.md - YouTube Summarizer Frontend This file provides guidance to Claude Code when working with the YouTube Summarizer frontend application. ## 🚨 CRITICAL: Server Status Checking Protocol **MANDATORY**: Always check server status before testing or debugging: ```bash # ALWAYS check server status FIRST lsof -i :3002 | grep LISTEN # Check if frontend is running lsof -i :8000 | grep LISTEN # Check if backend is running # If servers are NOT running, restart them: ./scripts/restart-frontend.sh # Restart frontend ./scripts/restart-backend.sh # Restart backend ./scripts/restart-both.sh # Restart both # After making code changes: 1. ALWAYS restart the appropriate server 2. VERIFY the server is running (lsof -i) 3. ONLY THEN proceed with testing ``` **Key Rules**: - ✅ ALWAYS check server status before testing - ✅ ALWAYS restart after code changes - ✅ ALWAYS verify restart was successful - ❌ NEVER assume servers are running - ❌ NEVER test without confirming server status ## 🚨 CRITICAL: Documentation Preservation Rule **MANDATORY**: NEVER remove critical documentation sections: - ❌ **NEVER** remove existing critical sections from CLAUDE.md or AGENTS.md - ❌ **NEVER** delete server checking protocols - ❌ **NEVER** remove development standards sections - ❌ **NEVER** delete troubleshooting guides - ✅ **ONLY** remove sections when explicitly instructed by the user - ✅ **ALWAYS** preserve and build upon existing documentation ## Frontend Architecture Overview The frontend is built with React, TypeScript, and Vite, following modern React patterns with comprehensive UI components. ### ✅ **NEW**: Dual Transcript Functionality Complete The frontend now includes comprehensive dual transcript components that integrate seamlessly with the backend services: - **TranscriptSelector**: Interactive component for choosing between YouTube captions, Whisper AI, or both - **TranscriptComparison**: Side-by-side comparison with quality metrics and difference highlighting - **Enhanced SummarizeForm**: Updated to support transcript source selection - **Demo Page**: `/demo/transcript-comparison` showcasing all functionality with mock data The frontend implementation is production-ready with TypeScript safety, comprehensive error handling, and excellent developer experience. ``` frontend/ ├── src/ │ ├── components/ # Reusable UI components │ ├── hooks/ # Custom React hooks │ ├── services/ # API client and external integrations │ ├── types/ # TypeScript type definitions │ ├── utils/ # Utility functions and helpers │ └── pages/ # Page components and routing │ ├── AdminPage.tsx # No-auth admin interface (NEW) │ ├── SummarizePage.tsx # Protected main interface │ └── auth/ # Authentication pages ├── public/ # Static assets └── tests/ # Frontend tests (Vitest + RTL) ``` ## Development & Testing Routes ### Admin Page (No Authentication Required) 🚀 - **File**: `src/pages/AdminPage.tsx` - **Route**: `/admin` - **URL**: `http://localhost:3002/admin` - **Purpose**: Direct access for testing and development - **Features**: - Complete YouTube Summarizer functionality - No login or authentication required - Orange "Admin Mode" visual indicators - Shield icon and badges for clear identification - Perfect for quick testing and demos ### Protected Routes (Authentication Required) - **Dashboard**: `/dashboard` → `SummarizePage.tsx` (requires verified user) - **History**: `/history` → `SummaryHistoryPage.tsx` (requires verified user) - **Batch**: `/batch` → `BatchProcessingPage.tsx` (requires verified user) - **Login/Auth**: `/login`, `/register`, `/verify-email` etc. ## Tech Stack - **Framework**: React 18 with TypeScript - **Build Tool**: Vite for fast development and building - **Styling**: Tailwind CSS with shadcn/ui components - **State Management**: React Query (TanStack Query) for server state - **WebSocket**: Native WebSocket API for real-time updates - **Testing**: Vitest + React Testing Library - **Type Safety**: Full TypeScript coverage with production-ready configuration ## TypeScript Configuration (Project-Specific) ### Resolved Configuration Issues This project had specific TypeScript compilation issues that were systematically resolved: 1. **Fixed `tsconfig.app.json`** - Removed overly strict `verbatimModuleSyntax` setting that caused module resolution failures 2. **Corrected Path Aliases** - Synchronized Vite and TypeScript path mapping for `@/*` imports 3. **Added Node.js Types** - Included `@types/node` for proper `import.meta.env` support 4. **Module Resolution** - Fixed ES module import/export patterns in `summaryAPI.ts` ### Production-Ready Configuration ```json // tsconfig.app.json - Working configuration for this project { "compilerOptions": { "target": "ES2020", "lib": ["ES2020", "DOM", "DOM.Iterable"], "module": "ESNext", "moduleResolution": "bundler", "paths": { "@/*": ["./src/*"] }, "strict": true, "noUncheckedIndexedAccess": true, "skipLibCheck": true } } ``` ### Project-Specific TypeScript Troubleshooting **Common Issues in This Codebase**: 1. **Import/Export Resolution** - `SummaryCard.tsx` importing `Summary` from `summaryAPI.ts` - **Issue**: Module not providing export despite correct syntax - **Solution**: Simplified `summaryAPI.ts` to remove circular dependencies 2. **Vite vs TypeScript Alignment** - Path aliases not resolving - **Issue**: `@/services/summaryAPI` not found despite existing file - **Solution**: Ensure `vite.config.ts` aliases match `tsconfig.json` paths 3. **Development vs Production** - Dev server working but build failing - **Issue**: Vite bypasses TypeScript errors in development - **Solution**: Regular `npm run build` checks during development ### API Interface Completeness The `summaryAPI.ts` file should export complete interfaces: ```typescript // Essential exports for this project export interface Summary { id: string; video_url: string; video_title: string; // ... complete interface definition } export interface SummaryListResponse { summaries: Summary[]; total: number; page: number; page_size: number; has_more: boolean; } export const summaryAPI = new SummaryAPI(); ``` ## Key Components and Patterns ### API Integration **API Client** (`src/services/apiClient.ts`) ```typescript class APIClient { private baseURL = import.meta.env.VITE_API_URL || 'http://localhost:8000' async processVideo(url: string, config: PipelineConfig): Promise { const response = await fetch(`${this.baseURL}/api/process`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ video_url: url, ...config }) }) return response.json() } async getPipelineStatus(jobId: string): Promise { const response = await fetch(`${this.baseURL}/api/process/${jobId}`) return response.json() } } export const apiClient = new APIClient() ``` ### Real-time Updates with WebSocket **WebSocket Hook** (`src/hooks/useWebSocket.ts`) ```typescript import { useEffect, useRef, useState } from 'react' interface WebSocketHookProps { url?: string onProgressUpdate?: (update: ProgressUpdate) => void onComplete?: (result: CompletionData) => void onError?: (error: ErrorData) => void } export function useWebSocket({ url = 'ws://localhost:8000/ws', onProgressUpdate, onComplete, onError }: WebSocketHookProps) { const ws = useRef(null) const [isConnected, setIsConnected] = useState(false) const connect = useCallback((jobId?: string) => { const wsUrl = jobId ? `${url}?job_id=${jobId}` : url ws.current = new WebSocket(wsUrl) ws.current.onopen = () => setIsConnected(true) ws.current.onclose = () => setIsConnected(false) ws.current.onmessage = (event) => { const data = JSON.parse(event.data) switch (data.type) { case 'progress_update': onProgressUpdate?.(data.data) break case 'completion_notification': onComplete?.(data.data) break case 'error_notification': onError?.(data.data) break } } }, [url, onProgressUpdate, onComplete, onError]) const disconnect = useCallback(() => { ws.current?.close() }, []) return { connect, disconnect, isConnected } } ``` ### Pipeline Processing Hook **Pipeline Hook** (`src/hooks/usePipelineProcessor.ts`) ```typescript import { useMutation, useQuery } from '@tanstack/react-query' import { useState } from 'react' import { apiClient } from '@/services/apiClient' import { useWebSocket } from './useWebSocket' export function usePipelineProcessor() { const [activeJobId, setActiveJobId] = useState(null) const [progress, setProgress] = useState(null) const { connect, disconnect } = useWebSocket({ onProgressUpdate: (update) => setProgress(update), onComplete: (result) => { setProgress({ stage: 'completed', percentage: 100, message: 'Processing complete!' }) // Trigger refetch of final result statusQuery.refetch() }, onError: (error) => { setProgress({ stage: 'failed', percentage: 0, message: error.message }) } }) const processVideo = useMutation({ mutationFn: async (params: ProcessVideoParams) => { const result = await apiClient.processVideo(params.url, params.config) return result }, onSuccess: (data) => { setActiveJobId(data.job_id) connect(data.job_id) // Connect WebSocket for this job setProgress({ stage: 'initialized', percentage: 0, message: 'Starting processing...' }) }, onError: (error) => { console.error('Failed to start processing:', error) } }) const statusQuery = useQuery({ queryKey: ['pipeline-status', activeJobId], queryFn: () => activeJobId ? apiClient.getPipelineStatus(activeJobId) : null, enabled: !!activeJobId, refetchInterval: (data) => { // Stop polling when completed or failed return data?.status === 'completed' || data?.status === 'failed' ? false : 2000 } }) const cancelProcessing = useMutation({ mutationFn: async (jobId: string) => { await apiClient.cancelPipeline(jobId) }, onSuccess: () => { setActiveJobId(null) setProgress(null) disconnect() } }) return { processVideo: processVideo.mutateAsync, cancelProcessing: cancelProcessing.mutateAsync, isProcessing: processVideo.isPending || statusQuery.data?.status === 'processing', progress: progress || (statusQuery.data ? { stage: statusQuery.data.status, percentage: statusQuery.data.progress_percentage, message: statusQuery.data.current_message } : null), result: statusQuery.data?.result, error: processVideo.error || statusQuery.error, jobId: activeJobId } } ``` ### Admin Page Implementation **AdminPage Component** (`src/pages/AdminPage.tsx`) - **NEW FEATURE** ```tsx import { SummarizeForm } from '@/components/forms/SummarizeForm'; import { ProgressTracker } from '@/components/display/ProgressTracker'; import { TranscriptViewer } from '@/components/display/TranscriptViewer'; import { useTranscriptExtraction } from '@/hooks/useTranscriptExtraction'; import { Shield } from 'lucide-react'; import { Badge } from '@/components/ui/badge'; export function AdminPage() { const { extractTranscript, progress, transcript, metadata, isLoading, error } = useTranscriptExtraction(); return (
{/* Admin Mode Visual Indicators */}
Admin Mode

YouTube Summarizer

Extract and analyze YouTube video transcripts - No Authentication Required

{/* Status Badge */}
Direct Access • Full Functionality • Testing Mode
{/* Same functionality as protected routes but no auth */} {isLoading && } {error && ...} {transcript && }

Admin Mode - For testing and development purposes

); } ``` **Key Features of AdminPage**: - **No Authentication**: Bypasses all auth checks and protected routes - **Visual Indicators**: Orange theme, shield icon, clear "Admin Mode" badges - **Full Functionality**: Uses same components as protected dashboard - **Development-Friendly**: Perfect for testing, demos, and development workflow - **Self-Contained**: No dependencies on AuthContext or user state ### Main Application Component **App Component** (`src/App.tsx`) - **UPDATED WITH ADMIN ROUTE** ```tsx import { BrowserRouter as Router, Routes, Route, Navigate } from 'react-router-dom'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { AuthProvider } from '@/contexts/AuthContext'; import { ProtectedRoute } from '@/components/auth/ProtectedRoute'; // Import the new AdminPage import { AdminPage } from '@/pages/AdminPage'; function App() { return ( {/* Public routes */} } /> } /> {/* ADMIN ROUTE - No authentication required */} } /> {/* Protected routes */} } /> } /> ); } ``` ### UI Components with shadcn/ui **Video Processor Page** (`src/pages/VideoProcessorPage.tsx`) ```tsx import React, { useState } from 'react' import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card' import { Button } from '@/components/ui/button' import { Input } from '@/components/ui/input' import { Label } from '@/components/ui/label' import { Progress } from '@/components/ui/progress' import { Textarea } from '@/components/ui/textarea' import { useToast } from '@/hooks/use-toast' import { usePipelineProcessor } from '@/hooks/usePipelineProcessor' import { VideoUrlValidator } from '@/components/VideoUrlValidator' import { SummaryDisplay } from '@/components/SummaryDisplay' import { ProcessingProgress } from '@/components/ProcessingProgress' export function VideoProcessorPage() { const [videoUrl, setVideoUrl] = useState('') const [summaryLength, setSummaryLength] = useState<'brief' | 'standard' | 'detailed'>('standard') const [focusAreas, setFocusAreas] = useState([]) const { toast } = useToast() const { processVideo, isProcessing, progress, result, cancelProcessing } = usePipelineProcessor() const handleSubmit = async (e: React.FormEvent) => { e.preventDefault() try { await processVideo({ url: videoUrl, config: { summary_length: summaryLength, focus_areas: focusAreas.filter(area => area.trim()), include_timestamps: false, enable_notifications: true, quality_threshold: 0.7 } }) toast({ title: "Processing Started", description: "Your video is being processed. You'll see real-time updates below.", }) } catch (error) { toast({ title: "Processing Failed", description: error instanceof Error ? error.message : 'Unknown error occurred', variant: "destructive", }) } } return (

YouTube Summarizer

Transform any YouTube video into intelligent summaries powered by AI

Video Processing Enter a YouTube URL to generate an AI-powered summary with key insights
setFocusAreas(e.target.value.split(',').map(s => s.trim()))} disabled={isProcessing} />
{isProcessing && ( )}
{progress && ( cancelProcessing()} /> )} {result && ( )}
) } ``` ## Component Patterns ### Form Validation Component **Video URL Validator** (`src/components/VideoUrlValidator.tsx`) ```tsx import React, { useState, useEffect } from 'react' import { Input } from '@/components/ui/input' import { Alert, AlertDescription } from '@/components/ui/alert' import { CheckCircle2, XCircle } from 'lucide-react' interface VideoUrlValidatorProps { value: string onChange: (value: string) => void disabled?: boolean } export function VideoUrlValidator({ value, onChange, disabled }: VideoUrlValidatorProps) { const [validationStatus, setValidationStatus] = useState<'valid' | 'invalid' | 'idle'>('idle') const [validationMessage, setValidationMessage] = useState('') useEffect(() => { if (!value) { setValidationStatus('idle') return } const youtubeRegex = /^(https?:\/\/)?(www\.)?(youtube\.com\/watch\?v=|youtu\.be\/)([\w\-_]+)(&\S*)?$/ if (youtubeRegex.test(value)) { setValidationStatus('valid') setValidationMessage('Valid YouTube URL') } else { setValidationStatus('invalid') setValidationMessage('Please enter a valid YouTube URL') } }, [value]) return (
onChange(e.target.value)} disabled={disabled} className={cn( "pr-10", validationStatus === 'valid' && "border-green-500", validationStatus === 'invalid' && "border-red-500" )} /> {validationStatus !== 'idle' && (
{validationStatus === 'valid' ? ( ) : ( )}
)}
{validationStatus !== 'idle' && ( {validationMessage} )}
) } ``` ### Progress Display Component **Processing Progress** (`src/components/ProcessingProgress.tsx`) ```tsx import React from 'react' import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card' import { Progress } from '@/components/ui/progress' import { Button } from '@/components/ui/button' import { Loader2, X } from 'lucide-react' interface ProcessingProgressProps { progress: { stage: string percentage: number message: string details?: any } onCancel: () => void } export function ProcessingProgress({ progress, onCancel }: ProcessingProgressProps) { const getStageDisplayName = (stage: string) => { const stageNames = { 'initialized': 'Initializing', 'validating_url': 'Validating URL', 'extracting_metadata': 'Extracting Video Info', 'extracting_transcript': 'Getting Transcript', 'analyzing_content': 'Analyzing Content', 'generating_summary': 'Generating Summary', 'validating_quality': 'Quality Check', 'completed': 'Complete', 'failed': 'Failed' } return stageNames[stage] || stage.replace('_', ' ').toUpperCase() } return ( Processing Video
{getStageDisplayName(progress.stage)} {Math.round(progress.percentage)}%

{progress.message}

{progress.details && (
Technical Details
              {JSON.stringify(progress.details, null, 2)}
            
)}
) } ``` ### Summary Display Component **Summary Display** (`src/components/SummaryDisplay.tsx`) ```tsx import React from 'react' import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card' import { Badge } from '@/components/ui/badge' import { Separator } from '@/components/ui/separator' import { Clock, ThumbsUp, Download, Share2 } from 'lucide-react' import { Button } from '@/components/ui/button' interface SummaryDisplayProps { result: { summary: string key_points: string[] main_themes: string[] actionable_insights: string[] confidence_score: number quality_score: number } videoMetadata?: { title: string duration: string thumbnail_url: string } } export function SummaryDisplay({ result, videoMetadata }: SummaryDisplayProps) { const handleExport = (format: 'markdown' | 'json' | 'pdf') => { // Implementation for exporting summary in different formats console.log(`Exporting as ${format}`) } const handleShare = () => { // Implementation for sharing summary console.log('Sharing summary') } return (
Summary Complete {Math.round(result.quality_score * 100)}% Quality {videoMetadata && (

{videoMetadata.title}

)}
handleExport('markdown')}> Markdown handleExport('json')}> JSON handleExport('pdf')}> PDF
{/* Main Summary */}

Summary

{result.summary}

{/* Key Points */}

Key Points

    {result.key_points.map((point, index) => (
  • {index + 1} {point}
  • ))}
{/* Main Themes */}

Main Themes

{result.main_themes.map((theme, index) => ( {theme} ))}
{/* Actionable Insights */} {result.actionable_insights.length > 0 && ( <>

Actionable Insights

    {result.actionable_insights.map((insight, index) => (
  • {insight}
  • ))}
)}
) } ``` ## Development Setup ### Environment Configuration **Environment Variables** (`.env.local`) ```bash VITE_API_URL=http://localhost:8000 VITE_WS_URL=ws://localhost:8000/ws VITE_APP_TITLE=YouTube Summarizer ``` ### Development Commands ```bash # Install dependencies npm install # Start development server npm run dev # Build for production npm run build # Preview production build npm run preview # Run tests npm run test # Run tests with coverage npm run test:coverage # Type checking npm run type-check # Linting npm run lint npm run lint:fix ``` ### Testing Setup **Vitest Configuration** (`vitest.config.ts`) ```typescript import { defineConfig } from 'vitest/config' import react from '@vitejs/plugin-react' import path from 'path' export default defineConfig({ plugins: [react()], test: { globals: true, environment: 'jsdom', setupFiles: ['./src/test/setup.ts'], }, resolve: { alias: { '@': path.resolve(__dirname, './src'), }, }, }) ``` **Test Utilities** (`src/test/test-utils.tsx`) ```tsx import React, { ReactElement } from 'react' import { render, RenderOptions } from '@testing-library/react' import { QueryClient, QueryClientProvider } from '@tanstack/react-query' const createTestQueryClient = () => new QueryClient({ defaultOptions: { queries: { retry: false }, mutations: { retry: false }, }, }) const AllTheProviders = ({ children }: { children: React.ReactNode }) => { const testQueryClient = createTestQueryClient() return ( {children} ) } const customRender = ( ui: ReactElement, options?: Omit ) => render(ui, { wrapper: AllTheProviders, ...options }) export * from '@testing-library/react' export { customRender as render } ``` ## Type Definitions **API Types** (`src/types/api.ts`) ```typescript export interface ProcessVideoRequest { video_url: string summary_length: 'brief' | 'standard' | 'detailed' focus_areas?: string[] include_timestamps?: boolean enable_notifications?: boolean quality_threshold?: number } export interface ProcessVideoResponse { job_id: string status: string message: string estimated_completion_time?: number } export interface PipelineStatusResponse { job_id: string status: string progress_percentage: number current_message: string video_metadata?: VideoMetadata result?: SummaryResult error?: ErrorData processing_time_seconds?: number } export interface SummaryResult { summary: string key_points: string[] main_themes: string[] actionable_insights: string[] confidence_score: number quality_score: number cost_data: { input_cost_usd: number output_cost_usd: number total_cost_usd: number } } export interface VideoMetadata { title: string description: string duration: string thumbnail_url: string channel_name: string published_at: string } export interface ProgressUpdate { stage: string percentage: number message: string details?: Record } export interface ErrorData { message: string type: string stage?: string retry_count?: number } ``` ## Styling with Tailwind CSS The project uses Tailwind CSS with shadcn/ui components for consistent styling: **Tailwind Config** (`tailwind.config.js`) ```javascript module.exports = { darkMode: ["class"], content: [ "./pages/**/*.{ts,tsx}", "./components/**/*.{ts,tsx}", "./app/**/*.{ts,tsx}", "./src/**/*.{ts,tsx}", ], theme: { container: { center: true, padding: "2rem", screens: { "2xl": "1400px", }, }, extend: { colors: { border: "hsl(var(--border))", input: "hsl(var(--input))", ring: "hsl(var(--ring))", background: "hsl(var(--background))", foreground: "hsl(var(--foreground))", primary: { DEFAULT: "hsl(var(--primary))", foreground: "hsl(var(--primary-foreground))", }, secondary: { DEFAULT: "hsl(var(--secondary))", foreground: "hsl(var(--secondary-foreground))", }, // ... other color definitions }, keyframes: { "accordion-down": { from: { height: 0 }, to: { height: "var(--radix-accordion-content-height)" }, }, "accordion-up": { from: { height: "var(--radix-accordion-content-height)" }, to: { height: 0 }, }, }, animation: { "accordion-down": "accordion-down 0.2s ease-out", "accordion-up": "accordion-up 0.2s ease-out", }, }, }, plugins: [require("tailwindcss-animate")], } ``` ## Error Handling and User Experience ### Error Boundary Component ```tsx import React from 'react' import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert' import { Button } from '@/components/ui/button' import { RefreshCw, AlertTriangle } from 'lucide-react' interface ErrorBoundaryState { hasError: boolean error?: Error } export class ErrorBoundary extends React.Component< React.PropsWithChildren<{}>, ErrorBoundaryState > { constructor(props: React.PropsWithChildren<{}>) { super(props) this.state = { hasError: false } } static getDerivedStateFromError(error: Error): ErrorBoundaryState { return { hasError: true, error } } componentDidCatch(error: Error, errorInfo: React.ErrorInfo) { console.error('ErrorBoundary caught an error:', error, errorInfo) } render() { if (this.state.hasError) { return ( Something went wrong

An unexpected error occurred. Please try refreshing the page.

{this.state.error && (
Error details
                  {this.state.error.message}
                
)}
) } return this.props.children } } ``` ## Performance Optimization ### React Query Configuration - Stale time: 5 minutes for cached data - Retry: Only 1 retry for failed requests - Background refetch disabled for completed operations ### WebSocket Management - Automatic reconnection with exponential backoff - Connection cleanup on component unmount - Heartbeat mechanism for connection health ### Bundle Optimization - Lazy loading for non-critical components - Code splitting by routes - Tree shaking for unused imports - Optimized build with Vite ## Accessibility ### ARIA Labels and Semantic HTML ```tsx
Video URL and Options
Enter a valid YouTube URL to process
``` ### Keyboard Navigation - All interactive elements focusable - Logical tab order - Escape key support for modals - Enter key support for form submission ### Screen Reader Support - Proper heading hierarchy - Live regions for progress updates - Descriptive button labels - Status announcements This frontend provides a comprehensive, accessible, and user-friendly interface for the YouTube Summarizer with real-time updates and modern UI patterns.