유대선

진행 중 · 2026년 5월 23일

TubeShadow

YouTube 영상에서 클립 따고 AI 분석 받고 SM-2 간격 반복으로 복습하는 영어 쉐도잉 도구. 개발자와 지식노동자용.

역할
Solo
스택
Java 21 · Spring Boot 3.3 · PostgreSQL · Next.js 16 · TypeScript · Tailwind 4 · Claude Haiku

YouTube 클립 마이너 + 쉐도잉 트레이너. YouTube URL 붙여넣으면 자막/메타데이터 자동 추출, 자막 더블클릭 또는 수동으로 시작/끝 마커 찍어서 자유롭게 클립 자르고, 태그/덱/SM-2 간격 반복이 있는 개인 라이브러리에 모음.

무엇을 하는가

스택

백엔드: Java 21 · Spring Boot 3.3 · Gradle Kotlin DSL · PostgreSQL · Flyway · Spring Security + JWT. 프론트엔드: Next.js 16 (App Router) · TypeScript strict · Tailwind 4 · shadcn/ui · Zustand · TanStack Query. AI: Anthropic Claude Haiku 4.5 + prompt caching (Gemini 2.5 Flash도 free-tier용으로 pluggable).

아직 없는 것

프로젝트 로그

이 프로젝트를 만들면서 남긴 트러블슈팅 · 회고 · 업데이트의 시간순 기록.

필터
  1. AuthRateLimitFilter: Filter auto-registration trap → HandlerInterceptor

    기술 회고

    2026년 5월 23일 · 1

    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

    트러블슈팅

    2026년 5월 23일 · 1

    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

    수익화

    2026년 5월 24일 · 1

    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

    트러블슈팅

    2026년 5월 24일 · 1

    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

    기술 회고

    2026년 5월 25일 · 2

    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

    트러블슈팅

    2026년 5월 26일 · 1

    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

    기술 회고

    2026년 5월 27일 · 1

    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

    업데이트

    2026년 5월 27일 · 2

    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

    트러블슈팅

    2026년 5월 27일 · 1

    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

    사용성 회고

    2026년 5월 27일 · 3

    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.