- 저장 완료 카드에 회색 톤(.saved)과 '저장됨 ✓' 버튼 상태 부여
- 저장 표시는 화면 전용: 캡처 직전 .saved를 벗겨 PNG엔 남지 않음
- 댓글에 고유 id를 붙여 필터·정렬 재렌더 후에도 저장 표시 유지
- '저장 폴더' 버튼 재클릭 시 지정 해제 → 브라우저 다운로드 폴더로 저장
(해제 이후 '전체 저장'은 폴더를 다시 묻지 않음)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- '저장 폴더' 버튼(File System Access API)으로 폴더를 한 번 고르면
⬇ 저장·전체 저장이 그 폴더로 바로 저장(이 사이트 세션에만 적용)
- '전체 저장' 버튼: 현재 표시된 카드를 순서대로 일괄 저장(진행 표시)
- 미지원 브라우저(파이어폭스/사파리)는 폴더 버튼 숨김·기존 다운로드 폴백
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- 토글(모자이크/모서리/단어가리기) ON 상태 accent로 시각화 + aria-pressed
- 검색·좋아요 필터 200ms 디바운스(대량 카드 렉 완화), 아바타 loading=lazy
- 수집 중 스피너, prefers-reduced-motion 대응
- 작은 회색 글자 대비 상향(text-3→text-2), 숫자 tabular-nums
- 아이콘 버튼/셀렉트 터치 타깃 확대, 입력 16px(iOS 확대 방지)
- 월별 댓글량 값 상시 노출(hover 의존 제거)
- 카드 키보드 접근(role/tabindex/Enter·Space 복사), focus-visible 링
- 섹션 아이콘 이모지→Lucide 통일(카드 내 👍💬는 export용 유지)
- 입력 aria-label, 결과 URL ?v= 딥링크(공유·새로고침 유지)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
기본은 댓글 단어 클릭 시 해당 단어만 블러(다시 클릭 해제).
'🖍 단어 가리기' 토글을 끄면 카드 클릭=이미지 복사로 전환.
블러는 캡처 PNG에도 그대로 반영됨.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- 수집 상한 1,000→20,000개(안전 상한)로 상향해 전체 댓글 기준 분석
- 분석 패널: 많이 언급된 타임라인(클릭 시 해당 시점 유튜브 이동), 자주 나온 단어(조사 제거+불용어, 좋아요 가중 토글), 요약 통계(총/평균 좋아요·참여자·최다 좋아요 댓글·월별 댓글량)
- 모자이크 기본 ON, '모서리 둥글게' 토글 추가(기본 각진 모서리)
- 카드 렌더는 상위 2,000개로 제한(브라우저 보호), 분석은 전체 기준
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
필터바에 검색 입력창 추가 → 입력 즉시 댓글 내용에 그 단어가 포함된 카드만 노출
(작성자 제외, 내용만 매칭). 클라이언트 필터라 추가 API 호출 없음. JS 캐시버스터 갱신.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
캡컷은 클립보드 이미지 붙여넣기를 못 받아 파일 불러오기가 필요 → 카드별 '⬇ 저장' 버튼으로
PNG 다운로드 제공. 저장 버튼은 cc-noexport+modern-screenshot filter로 캡처 이미지에서 제외.
카드 클릭=클립보드 복사는 유지.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
복사 버튼이 캡처 이미지에 함께 찍히던 문제 해결. 버튼을 없애고 카드 자체 클릭으로
복사하도록 변경, 완료 피드백은 카드 밖 토스트로 표시(캡처에 미포함). JS 캐시버스터 갱신.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
grid가 행 단위로 최대 높이에 맞춰 늘어나던 것을 CSS 다단(column)으로 변경.
각 카드가 내용 높이에 맞고 빈틈없이 쌓이도록 break-inside:avoid + margin-bottom 적용.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
commentThreads가 order=relevance에서 간헐적으로 403 forbidden을 반환하는 문제 대응.
generic 403/rateLimit/5xx는 백오프 후 최대 2회 재시도, commentsDisabled/quotaExceeded는
재시도 없이 즉시 안내. 재시도 성공/비재시도 단위 테스트 추가.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
403을 무조건 '댓글 비활성화'로 표시하던 문제 수정. error.errors[0].reason을 파싱해
commentsDisabled/quotaExceeded/기타(keyInvalid 등)를 각각 안내하고, 비-403 오류가
opaque 500으로 새던 것도 의미있는 메시지로 노출. 단위 테스트 추가.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
HTML5 드래그가 터치에서 동작 안 하는 문제 — 카드마다 상태 드롭다운을 두어
터치로도 컬럼 이동 가능. 데스크톱은 드래그 유지(드롭다운은 모바일/터치에서만 노출).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Channel 에 country 추가(YouTube snippet.country 저장), 추천 채널 region 으로 기존 채널
백필(POST /api/channels/backfill-country), /channels 에 국가 탭+카운트 필터.
선택 영상 보기 버튼 한글화.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
querySelector('i')가 lucide 치환 후 null→TypeError로 시각 토글이 첫 카드에서 멈춰
전체 선택이 안 되는 것처럼 보였음. querySelector('svg, i') + null 가드로 수정.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- 목록 API status 파라미터(NEW|EXCLUDED|REGISTERED), POST /{id}/restore(→NEW)
- recommend.html: 헤더 '제외 목록' 토글로 EXCLUDED 채널 보기, 카드에 '복원' 버튼
(제외 보기에선 등록/제외 대신 복원+채널이동만), 지역 탭은 그대로 적용
검증: 제외→EXCLUDED 목록 노출→복원→NEW 복귀 end-to-end 확인.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- region 을 채널 국가가 아닌 '검색 발견 지역(regionCode)'으로 태깅(UNKNOWN 해소)
- upsert: 같은 배율이면 갱신(지역 등 메타 최신화)
- recommend.html: 전체/KR/JP/US 탭으로 필터, 등록/제외 시 목록 갱신
검증: 재발굴 후 KR14·JP7·US1 로 분포, 탭 필터 동작.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
지역(KR,JP,US) 인기 Shorts 주기 검색 → 작은구독자·고배율 떡상 채널 발굴 →
RecommendedChannel 저장 → /recommend 페이지에서 등록/제외. 쿼터 가드·기존 검색 재사용.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
URL자막 버튼 제거에 따라 안내문/placeholder의 'URL자막' 언급 삭제,
미사용 함수 extractScript/translateToEditor 제거(copyToEditor는 유지).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
AI 자막(음성/화면) 기능이 번역을 대체해 중복되는 버튼 3개 삭제.
원본 다운로드·자막 추출(음성)·받은 원본 삭제·영상 업로드·에디터로 복사는 유지.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
화면(Gemini)·음성 패널에서 원문과 한국어 번역을 함께 보여줘 비교 가능하게 함.
- ScriptResponseDto.Segment: original 필드 추가(NON_NULL)
- Gemini 프롬프트/스키마: original(화면 원문) + text(한국어 번역) 둘 다 요청,
text 는 반드시 한국어(영어 방지) 명시
- rework.html: 각 줄에 한국어(위)+원문(아래 회색) 표시, 음성 패널은 Whisper 원문 사용
검증: 470 화면자막 → 원문 일본어 + 한국어 번역 + 진행형 타임스탬프 정상.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
원인: Gemini 2.5 Flash의 thinking(타임스탬프 추론)+JSON 출력이 자막 많은 영상에서
출력 토큰을 초과해 JSON이 잘림 → 0개 또는 타임스탬프 전부 0.
- maxOutputTokens 65536(최대)로 상향 — thinking+JSON 잘림 방지(핵심)
- temperature 0 — 일관성
- 프롬프트: 시작/끝 시각 정확히, 전부 0 금지, 누락 없이 추출하도록 보강
- 빈 결과면 최대 3회 재시도(영상이해 비결정성 보완)
검증: 470·560(자막 많은 영상) 모두 진행형 타임스탬프로 정상 추출.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
재생 중 활성 세그먼트로 이동할 때 scrollIntoView가 페이지를 스크롤해 sticky 영상이
위아래로 튀던 현상 수정. 세그먼트 박스(#segmentList) 내부에서만 스크롤하도록 변경.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- 이해하기 쉽게 모든 '전사' 문구를 '자막 추출'로 변경(메인 버튼은 '자막 추출(음성)')
- 좌측 영상 카드 position:sticky(top:1rem) → 스크롤해도 상단에 고정되어 항상 보임
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
말소리 없는 영상(음악·화면자막만)의 음성 패널을 빨강 오류가 아니라 회색 안내
('음성 없음 — 말소리가 없거나 전사 미실행')로 표시. renderAiPanel에 note 인자 추가.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- GeminiSubtitleService: 403(프로젝트 차단)/429(한도)/400(키 무효)을 한국어 메시지로
IllegalArgumentException throw → 'Internal Server Error' 대신 실제 사유 노출
- rework.html: 음성 패널 빈 경우 '말소리 없음(음악/화면자막만) 또는 전사 미실행'으로 정정
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
재가공 화면에 'AI 자막(음성/화면)' 카드 추가. '생성' 한 번에:
- 왼쪽(음성): 저장된 Whisper 세그먼트(정밀 타임스탬프) → LibreTranslate 한국어
- 오른쪽(화면): 유튜브 URL → Gemini가 화면 박힌 자막 추출+한국어 번역
각 패널을 [00:00] 한국어 리스트로 표시, 각각 SRT 다운로드(audio_ko/screen_ko).
- GeminiSubtitleService 신규: youtube URL을 generativelanguage API에 전송,
responseMimeType=json 구조화 응답 파싱. buildRequest/extractSegments/sanitizeApiKey 순수+단위테스트
- sanitizeApiKey: 잘못 붙은 선행 '='·공백 정리(env var 오타 방어)
- geminiRestTemplate 빈(5분), POST /{id}/gemini-subtitles, CurationService.geminiScreenSubtitles
- 설정 gemini.api-key/model(gemini-2.5-flash), rework.html 분할 카드+JS
- 음성 한국어는 기존 translate(format=segments) 재사용
검증: 470 화면자막 → 한국어 27세그먼트 30초 추출(스타일 일본어 자막도 정확).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
좌우 분할: 왼쪽=음성(Whisper 정밀+한국어), 오른쪽=화면(Gemini 한국어). 각각 SRT 다운로드.
음성 한국어는 기존 전사+LibreTranslate 재사용, 화면은 Gemini 신규(transient).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>