| Layer | Technology | Version / Notes |
|---|---|---|
| Framework | Next.js | 15.5, App Router, Turbopack in dev |
| Language | TypeScript | 5, strict mode enabled |
| UI Library | React | 19 |
| Styling | Tailwind CSS | 4 |
| Component System | shadcn/ui | New York style, built on Radix UI primitives, Lucide icons |
| Unit Testing | Vitest | 4, jsdom environment |
| E2E Testing | Playwright | 1.57 |
| PDF Generation | jsPDF | 4 |
| Markdown Rendering | react-markdown | with remark-gfm plugin |
src/
├── app/ # Next.js App Router
│ ├── layout.tsx
│ ├── page.tsx
│ └── api/
│ ├── transcript/ # Transcript fetching and processing
│ ├── channel/ # YouTube channel data
│ ├── discover/ # Video discovery
│ └── ai-summary/ # LLM-powered summarization
│ └── config # Provider availability (booleans only)
│
├── components/
│ ├── ui/ # shadcn/ui primitives (Button, Card, Dialog, etc.)
│ ├── layout/
│ │ ├── Header
│ │ └── Footer
│ └── features/
│ ├── VideoPreview
│ ├── ProcessingOptions
│ ├── TranscriptViewer
│ ├── AISummary
│ ├── FavoriteChannels # Saved podcast channels with episode browsing
│ ├── SummarizePipelineModal # Step-by-step pipeline progress modal
│ ├── ExportControls
│ └── ...
│
├── lib/
│ ├── transcript-processor # Core transcript parsing and formatting
│ ├── ytdlp-service # yt-dlp integration for video metadata
│ ├── api-client # Client-side API abstraction
│ ├── llm-service # Unified LLM provider interface
│ ├── llm-config # Provider model/temperature definitions
│ ├── rate-limiter # Per-IP, per-endpoint rate limiting
│ └── errors # Structured error types
│
├── hooks/
│ ├── useChannelData
│ ├── useTranscriptProcessing
│ ├── useProcessingOptions
│ ├── useAISummary
│ ├── useUrlValidation
│ ├── useFavoriteChannels # Channel CRUD, episode fetching, localStorage
│ ├── useUrlDetection # Channel/playlist URL detection and resolution
│ ├── useUrlSubmission # URL validation, transcript fetching, video state
│ └── useSummarizePipeline # Pipeline orchestration (useReducer + refs)
│
└── types/
└── index.ts # Shared type definitions
Prompt templates live outside src/:
prompts/
├── bullets.md
├── narrative.md
├── technical.md
└── fallback.md
- Bundler: Turbopack for fast HMR and incremental compilation.
- Command:
next dev --turbopack
- Compiler: SWC for minification and transpilation.
- Command:
next build
Webpack configuration defines explicit chunk groups:
// next.config.ts (simplified)
splitChunks: {
cacheGroups: {
vendor: { ... }, // node_modules
common: { ... }, // shared application code
}
}- Images: AVIF and WebP formats via
next/image. Remote patterns configured for YouTube thumbnail domains (i.ytimg.com,yt3.ggpht.com). - Fonts: Geist font family loaded with
display: swapfor zero layout shift.
| Setting | Value |
|---|---|
| Environment | jsdom |
| Coverage provider | v8 |
| Coverage reporters | text, json, html |
| Path aliases | @/ mapped to src/ |
| Setup file | vitest.setup.ts |
| Library | React Testing Library |
Run with:
npx vitest
npx vitest --coverage| Setting | Value |
|---|---|
| Browsers | Chromium, Firefox, WebKit |
| Dev server | Auto-started before test run |
| Execution | Parallel across browsers |
Run with:
npx playwright testCSP headers are configured in next.config.ts. The connect-src directive whitelists the following external origins:
https://api.anthropic.com(Anthropic / Claude)https://generativelanguage.googleapis.com(Google Gemini)https://api.perplexity.ai(Perplexity / Sonar)
| Header | Value |
|---|---|
X-Frame-Options |
DENY |
X-Content-Type-Options |
nosniff |
- In-memory store, keyed by client IP address.
- Limits applied per API endpoint independently.
- No external dependencies (no Redis required).
All API routes validate incoming parameters before processing.
The /api/ai-summary/config endpoint returns only boolean flags indicating whether each provider is configured. API keys are never sent to the client.
| Provider | Model | Temperature | Notes |
|---|---|---|---|
| Anthropic | Claude Sonnet 4.5 | 0.7 | System + user message split (Anthropic best practice) |
| Google Gemini | 2.5 Flash | 0.7 | Single content block |
| Perplexity | Sonar | 0.7 | Single content block |
Prompt templates are stored as Markdown files in the prompts/ directory:
| File | Purpose |
|---|---|
bullets.md |
Bullet-point summary format |
narrative.md |
Prose/narrative summary format |
technical.md |
Technical deep-dive format |
fallback.md |
Default when no style is specified |
Anthropic-specific behavior: The LLM service constructs a two-part message (system message + user message), following Anthropic's recommended API pattern for separating instructions from content.
Other providers: Receive a single combined content block.
If the /api/ai-summary/config endpoint is unreachable, the client assumes all providers are configured and available. This prevents a config outage from disabling the summarization feature entirely.
Users can save up to 5 YouTube podcast channels and browse their latest episodes. A one-click "Summarize" button triggers an automated pipeline that fetches the transcript, processes it, and generates an AI summary.
The main page (page.tsx) is a thin orchestrator with 0 useState calls. All state lives in 5 hooks:
| Hook | State Management | Variables |
|---|---|---|
useProcessingOptions |
useState |
Processing config |
useTranscriptProcessing |
useState |
Transcript result, progress |
useUrlDetection |
useState |
Channel/playlist detection (7 vars) |
useUrlSubmission |
useState |
Video metadata, segments (7 vars) |
useSummarizePipeline |
useReducer |
Pipeline modal, steps, summaries |
The summarize pipeline uses useReducer for predictable state transitions:
INIT → SET_STEP(1) → SET_STEP(2) → ... → COMPLETE → CLOSE
└── FAIL → RETRY → INIT
On failure, a second failure triggers CLOSE with rollback to pre-pipeline state.
All favorite channel data is persisted in localStorage. See docs/features/FAVORITE_CHANNELS.md for details.
- Session-based channel data cache with a 5-minute TTL. Channel metadata is fetched once and reused across subsequent requests within the same session window.
An in-flight request map prevents duplicate concurrent requests to the same resource. If a request for a given key is already pending, subsequent callers receive the same promise rather than triggering a new network call.
React.memoon presentational components to avoid unnecessary re-renders.useMemofor expensive derived values (formatted transcripts, filtered lists).useCallbackfor event handlers passed as props.
- Lazy loading of non-critical components (below-the-fold features).
- Code splitting via dynamic imports and the webpack chunk configuration described above.
Web Vitals (LCP, FID, CLS) are tracked to surface performance regressions.
An interactive architecture overview is available at /how-it-works.html within the running application. It provides a visual walkthrough of the transcript processing pipeline, LLM integration, and export flow.