F.1 review_status 버그 fix + 승인 UX 가드:
- PATCH body에 review_status: 'approved' 누락 버그 수정 (hotfix)
→ 기존에는 승인해도 문서가 inbox에서 사라지지 않던 증상 해결
- isApprovable(doc): effective domain(override or original)이 비어 있으면 false
- 미분류 행: 체크박스 disabled + ⚠ "도메인 선택 필요" Badge 인라인 표시
+ 카드 border-warning 강조. 클릭 자체가 막힘 (toast 경고 아님)
F.2 runes 마이그레이션 + 프리미티브 전환:
- let → $state/$derived/$derived.by, onMount 유지
- Card/Button/Select/TextInput/Badge/EmptyState/Skeleton/Modal/
ConfirmDialog/FormatIcon/TagPill 프리미티브로 전면 재작성
- 기존 bg-[var(--*)] 클러스터 전부 제거
F.3 필터 row:
- source / format Select 드롭다운 (현재 documents에서 동적 집계)
- confidence는 백엔드 ai_confidence 필드 추가 대기 — 주석 TODO(backend)
F.4 처리 단계 가시성:
- extracted_at / ai_processed_at / embedded_at 3개 Badge
(success tone = 완료, neutral = 대기) + source_channel 표시
- backend 전용 endpoint 없이 기존 응답 필드만으로 stop-gap
F.5 행별 override:
- Map<id, { domain?, tags? }> 로컬 state
- 도메인 select 변경 시 overrides에 기록, 원복 버튼으로 clear
- 승인(approveOne) 시점에 override를 PATCH body에 병합
- 도메인 override로 미분류 → 분류 전환 가능 (바로 승인 가능해짐)
F.6 배치 override + 재시도 stub:
- 선택 toolbar: 일괄 도메인 / 일괄 태그 modal
- 배치 override는 로컬 Map만 갱신, 실제 PATCH는 승인 시 1회
- 재시도 버튼: disabled stub (TODO backend POST /queue/retry)
- 선택 상한 50건, pLimit(5) + Promise.allSettled 일괄 승인
검증:
- npm run build 통과 (a11y 경고 fix: label → span + aria-label)
- npm run lint:tokens 229 → 204 (inbox 레거시 var() 토큰 전부 제거, -25)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- 검색바 아래 새 필터 칩 row: domain/tag/format/source 활성 필터를
인라인 칩으로 렌더, 각 칩에 X 버튼으로 제거.
- `+ 태그` popover: 현재 결과의 상위 20개 태그 클라이언트 집계
(items.flatMap(d => d.ai_tags).counts + sort) → 선택 시 ?tag=...
- `+ 형식` popover: FORMATS 화이트리스트 (pdf/hwp/hwpx/md/docx/xlsx/png/jpg)
→ 선택 시 ?format=...
- 바깥 클릭으로 popover 자동 close ($effect + document listener)
- filterFormat $derived + loadDocuments params 확장 + hasActiveFilters 확장
- 결과 헤더는 카운트만 남기고 필터 표시/초기화는 칩 row로 이전 (중복 제거)
- addFilter/removeFilter 헬퍼로 URL 라운드트립 관리 (domain 제거 시 sub_group 함께)
- 백엔드 변경 없음 (GET /documents/가 이미 tag/format 지원)
검증:
- npm run build 통과
- npm run lint:tokens 236 → 231 (신규 코드 0 위반, 결과 헤더 리팩토링으로
5건 organically 감소)
- popover 키보드 a11y (role=listbox/option, aria-expanded, aria-selected)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- 가로 flex 최상위 + 가운데 flex-1 (기존 list/viewer 세로 split 그대로 보존)
- xl+ (≥1280px): 우측 320px persistent rail, 접기 시 40px sliver.
localStorage.metaRailOpen 으로 상태 유지.
- < xl : 기존 수동 drawer 제거하고 ui/Drawer primitive + uiState 사용.
- 리사이즈 시 xl+ 진입하면 drawer 자동 close (rail로 승계).
- handleKeydown → ui.handleEscape() 로 중앙화.
- ℹ 버튼 token 기반 재작성 (isXl 분기로 rail/drawer 토글).
- PreviewPanel.svelte 한 글자도 수정 없음 (Phase E 영역).
신규:
- frontend/src/lib/composables/useMedia.svelte.ts — matchMedia runes 컴포저블
- frontend/src/lib/components/DocumentMetaRail.svelte — PreviewPanel wrapper
검증:
- npm run build 통과
- npm run lint:tokens 241 → 236 (신규 코드 0 위반, 레거시 drawer/ℹ 버튼
제거로 5건 organically 감소)
- PreviewPanel diff 0줄
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
A-8 작전 후 사용자 보고: 마크다운 전체보기에서 "텍스트 추출 대기 중"
fallback이 뜨는 문서가 있음.
원인: split view의 DocumentViewer는 extracted_text 없으면 원본 .md
파일을 fetch해서 보여주는데, detail view (routes/documents/[id]/+page.svelte)
는 fetch fallback이 없어 즉시 fallback 메시지로 떨어짐. 두 view의 동작
불일치가 A-8 작업 중 사용자 시각 검증 과정에서 드러남.
A-8 회귀 아님 — 이 페이지는 routes 잔존 그룹(36 hits)이라 A-8 batch에서
한 줄도 변경 안 됨 (git diff fcce764..c294df5로 검증).
해결: DocumentViewer와 동일한 fetch fallback 로직을 detail view에도 추가.
fallback 우선순위:
1. doc.extracted_text 있으면 사용
2. 없으면 raw markdown fetch 시도
3. 둘 다 없으면 "*텍스트 추출 대기 중*" 메시지
scope:
- script onMount: vt가 markdown/hwp-markdown이고 extracted_text 없으면
/api/documents/{id}/file fetch
- template: renderMd fallback chain에 rawMarkdown 추가
routes 색상 토큰 swap (이 페이지의 36 hits)은 별도 이슈 — Phase D에서
정식 처리. 본 hotfix는 콘텐츠 표시 문제만 해결.
A-9 styleguide 라우트가 dev 환경에서 auth gate를 우회할 수 있도록
PUBLIC_PATHS / NO_CHROME_PATHS에 /__styleguide 추가.
production 영향 0 — +page.ts의 dev 가드가 비-dev 환경에서는 /로
redirect하므로 styleguide 라우트 자체에 도달 못 함.
A-8 토큰 swap 작전과 의미적으로 무관한 dev-only 변경이라
revert 단위 분리를 위해 단독 commit.
23개 평가셋 × 3 전략(legacy/rrf/rrf_boost) 측정 + 분석.
핵심 발견:
- 전체 NDCG: legacy 0.705 → rrf 0.699 → rrf_boost 0.700 (미세 차이)
- RRF가 약간 나쁜 이유: kw_001(산업안전보건법 제6장)에서 RRF가 4041
(근로기준법 안전과 보건)을 false positive로 promotion. NDCG 1.000→0.906.
- boost가 가치 입증한 사례: news_004(guerre en Iran)에서 RRF의 미스를
완벽 보정해 legacy NDCG 복원.
- RRF의 진짜 가치는 Phase 1+ 다중 신호(trigram, reranker, multi-query)
통합 시 발휘됨. 현 평가셋은 너무 단순해서 차이가 noise에 묻힘.
결정: rrf_boost를 default로 유지. Phase 1 후 재측정.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
logging.getLogger("search")만 사용하면 uvicorn 기본 설정에서 INFO가
stdout에 안 나옴. 기존 core.utils.setup_logger 패턴 사용:
- logs/search.log 파일 핸들러
- stdout 콘솔 핸들러
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
vector-only 매치(match_reason == 'vector')에서 raw 코사인 0.43이
0.6으로 잘못 amplify되어 low_confidence threshold(0.5)를 못 넘기던 문제.
- vector-only 분기: amplify 제거, _cosine_to_confidence로 일관 환산
- _cosine_to_confidence: bge-m3 코사인 분포 (무관 텍스트 ~0.4) 반영
- 코사인 0.55 = threshold 경계(0.50), 0.45 미만은 명확히 low
smoke test 결과 zzzqxywvkpqxnj1234 같은 무의미 쿼리(top cosine 0.43)가
low_confidence로 잡히지 않던 문제 해결.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
UX/UI 개편 Phase A-2. lib/stores/ui.ts에 섞여 있던 toast 시스템과
UI layer 상태(미사용 dead export 포함)를 의미 단위로 분리한다.
한 파일이 비대해지는 시나리오를 처음부터 차단(plan 8대 원칙 #7).
- lib/stores/toast.ts 신규 — toasts/addToast/removeToast (Toast interface export)
- lib/stores/uiState.svelte.ts 신규 — drawer 단일 slot + modal stack 클래스 (5대 원칙 #2)
· openDrawer/closeDrawer/isDrawerOpen
· openModal/closeTopModal/isModalOpen/modalIndex/topModal
· handleEscape (modal stack 우선 → drawer)
- lib/stores/ui.ts 삭제 — sidebarOpen/selectedDocId는 어디서도 import되지 않은 dead export였음
- 11개 파일 import 경로 갱신: \$lib/stores/ui → \$lib/stores/toast
uiState는 아직 어디서도 사용 안 함 — Phase B에서 sidebar/meta drawer가 전환될 때
ui.openDrawer('sidebar') 형태로 채택. 동작 변경 0.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
22개 쿼리(6개 카테고리)와 Recall/MRR/NDCG@10 + latency p50/p95
측정 스크립트 추가. wiggly-weaving-puppy 플랜 Phase 0.2 산출물.
- queries.yaml: 정확키워드/한국어자연어/crosslingual/뉴스/실패 케이스
실제 코퍼스(2026-04-07, 753 docs) 기반 정답 doc_id 매핑
- run_eval.py: 단일 평가 + A/B 비교 모드, CSV 저장
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
UX/UI 개편 Phase A-1. CSS 변수를 Tailwind 유틸리티로 노출해서
이후 컴포넌트가 bg-surface / text-dim / border-default 형태로 작성될
수 있도록 한다. bg-[var(--*)] 임의값 패턴은 후속 lint 규칙으로 차단 예정.
- app.css에 @theme 블록 추가 (color/radius/z/spacing/domain 토큰)
- 기존 :root 변수는 .markdown-body 호환 위해 공존 유지
- +layout.svelte nav 한 줄 swap으로 v4 빌드/HMR 인식 검증 (동일 색상값)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- AI 요약: 파란 박스로 상단에 별도 표시
- 본문 입력: extracted_text에 추가 (기사 전문 붙여넣기)
- 메모: user_note에 저장 (개인 메모)
- 기사 선택 시 편집 상태 초기화
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- PAPER_NAMES 매핑으로 'Le Monde', 'Der Spiegel' 등 정확 분리
- NewsSourceResponse datetime 타입 수정
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- summarize_worker: 요약만 생성 (분류 안 함)
- queue_consumer: summarize stage 추가 (batch 3)
- news_collector: summarize + embed 큐 등록
- process_stage enum에 'summarize' 추가
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- /news 전용 페이지: 신문사 필터, 읽지않음 필터, 시간순 리스트, 미리보기
- 뉴스 분류 격리: ai_domain='News', classify 제거, embed만 등록
- is_read: 클릭 시 자동 읽음, 전체 읽음 API
- documents 목록에서 뉴스 제외 (source_channel != 'news')
- nav에 뉴스 링크 추가
- GET /api/news/articles, POST /api/news/mark-all-read
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- DocumentViewer: source_channel=news → article 전용 뷰어
(제목/소스/날짜/요약/원문 링크 rel=noopener)
- DocumentCard: 뉴스 카드에 📰 아이콘
- settings: 뉴스 소스 관리 (목록/추가/삭제/토글/수집/마지막 시간)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- config.yaml: embedding model → bge-m3
- document.py: Vector(768) → Vector(1024)
- embed_worker.py: 모델 버전 업데이트
- migration 011: 벡터 컬럼 재생성 (기존 임베딩 초기화)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>