Daeseon Yoo

Active · May 23, 2026

TubeShadow

A YouTube-based English shadowing tool with clip mining, AI-explained transcripts, and SM-2 spaced repetition. Built for developers and knowledge workers.

Role
Solo
Stack
Java 21 · Spring Boot 3.3 · PostgreSQL · Next.js 16 · TypeScript · Tailwind 4 · Claude Haiku

A YouTube clip miner + shadowing trainer. Paste a YouTube URL, pull subtitles and metadata, cut free clips with start/end markers or double-clicking captions, then put them into a personal library with tags, decks, and SM-2 spaced repetition.

What it does

Stack

Backend: Java 21 · Spring Boot 3.3 · Gradle Kotlin DSL · PostgreSQL · Flyway · Spring Security + JWT. Frontend: Next.js 16 (App Router) · TypeScript strict · Tailwind 4 · shadcn/ui · Zustand · TanStack Query. AI: Anthropic Claude Haiku 4.5 with prompt caching (also pluggable Gemini 2.5 Flash for free-tier deployments).

What it doesn't do (yet)

Project log

Chronological record of troubleshooting, retros, and updates while building this.

filter
  1. AuthRateLimitFilter: Filter auto-registration trap → HandlerInterceptor

    Tech retro

    May 23, 2026 · 1 min

    A `Filter` bean was auto-registered by Spring Boot independent of the `SecurityFilterChain` wiring and tried to instantiate it with a default constructor it didn't have. Switched to `HandlerInterceptor`; same behavior, no auto-registration trap.

  2. YouTube transcript fetch replaced with yt-dlp + self-healing import

    Troubleshoot

    May 23, 2026 · 1 min

    The `timedtext` URL embedded in YouTube watch HTML carries a short-lived token; subtitle fetch returned 200 with empty body within minutes. Migrated entirely to `yt-dlp` subprocess and added an idempotent recovery hook so re-importing the same URL retries the transcript and the dimension probe.

  3. BYOAI — send analysis prompts to the user's own ChatGPT/Claude/Gemini

    Monetization

    May 24, 2026 · 1 min

    Built a 'Send to my own AI' button that constructs an analysis prompt locally and opens ChatGPT/Claude/Gemini/Perplexity with `?q=...`, falling back to clipboard if auto-fill fails. Operating cost: zero; user keeps the conversation in their preferred model. Sets up a clean alternative to a paid tier.

  4. StaleObjectStateException on clip delete cascading to recordings

    Troubleshoot

    May 24, 2026 · 1 min

    Spring Data's derived `deleteByClipId` hydrates the entity into the persistence context before deleting it, racing with the DB-level `ON DELETE CASCADE`. Switched to `@Modifying @Query` so the delete bypasses the session cache.

  5. i18n: next-intl with path-based routing across 5 locales

    Tech retro

    May 25, 2026 · 2 min

    Migrated the entire frontend to `next-intl` with path-based routing (`/en`, `/ko`, `/ja`, `/zh`, `/es`), default English. All 17 user-facing files were keyed; `<Link>` and `useRouter` imports across the app moved to `@/i18n/navigation` wrappers. ja/zh/es fall back to English copy for now (translation is a non-engineering task).

  6. Recording upload 415: MIME `Content-Type` whitelist must strip codec parameter

    Troubleshoot

    May 26, 2026 · 1 min

    Chrome's MediaRecorder tags audio with `audio/webm;codecs=opus`. The recording upload service compared the full string against a whitelist of base types, so Chrome uploads were rejected with 415. Stripped MIME parameters before the check.

  7. AI provider abstraction — Claude credit ran out, swapped to Gemini in one env var

    Tech retro

    May 27, 2026 · 1 min

    Anthropic credit hit zero mid-session. Introduced `AiAnalysisClient` interface; `ClaudeClient` and a new `GeminiClient` both implement it, each gated by `@ConditionalOnProperty(name = "tubeshadow.ai.provider")`. Switching providers is now one env var. Gemini's free tier (1500 req/day) covers personal use indefinitely; operating cost dropped to $0/mo.

  8. Decks (Anki-style clip grouping) + Review next-due toast

    Update

    May 27, 2026 · 2 min

    Two user-perception fixes shipped together. (1) Anki-style decks: a new `decks` table, nullable `deck_id` on `clips` (`ON DELETE SET NULL` so clips fall back to Inbox), library sidebar, per-card move dropdown, deck filter on review. (2) After Easy/Good/Hard/Again, a toast surfaces the actual next-due date so the clip doesn't feel like it 'vanished' from the queue.

  9. Gemini 2.5 Flash: thinking tokens silently truncated JSON output

    Troubleshoot

    May 27, 2026 · 1 min

    After switching to Gemini, every analysis came back as `GEMINI_PARSE_FAILED: Unexpected end-of-input` at the same column. Cause: Gemini 2.5 Flash burns 'thinking' tokens before emitting visible output, eating most of `maxOutputTokens: 800` and truncating the JSON mid-string. Fix: `thinkingConfig: { thinkingBudget: 0 }` and bumped output cap to 4096.

  10. Prompt-engineering 직독직해: forcing English word order in Korean output

    UX retro

    May 27, 2026 · 3 min

    Asking Gemini for 'Korean chunks paired with English chunks' produced grammatical Korean in natural Korean order — useless for shadowing practice. Three prompt revisions: (1) hard rules + bad examples to force source order, (2) tune chunk size from 1-word to 2–5-word sense groups, (3) explicit BAD examples that show the failure mode.