320 Commits

Author SHA1 Message Date
Hyungi Ahn 5cf7952b33 fix(ui): 카테고리 내비 상단 이동 + uvicorn proxy-headers
- +layout.svelte 상단 nav 에 오디오/비디오 추가 (문서/자료실 옆,
  카테고리 계열 그룹). Sidebar 는 §2 에서 추가했던 카테고리
  블록 제거하고 기존 도메인 트리 전용으로 복구 — 상단 nav 와
  중복되고, 사이드바가 카테고리 탐색 1차 진입점으로 적합하지
  않다는 피드백 반영.
- app/Dockerfile uvicorn 에 --proxy-headers --forwarded-allow-ips=*
  추가. FastAPI 의 trailing-slash 307 리다이렉트가 X-Forwarded-Proto
  를 무시해 Location 헤더를 http:// 로 생성 → HTTPS 페이지에서
  mixed-content block (/video 에서 목격). home-caddy → document-caddy
  → fastapi 체인에서 scheme 복구.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 07:28:04 +09:00
Hyungi Ahn 31d76edba0 feat(dashboard): §4 — 카테고리/제안/queue lag 카드 + docs/categories.md
frontend +page.svelte:
- 4-card 메인 row 아래 새 row 추가: 자료실/오디오/비디오 (category_counts) +
  자료실 제안 (library_pending_suggestions). 제안 ≥1 일 때 warning 색 + /library 링크.
- buildPipelineRows 가 pipeline_status (24h 누적) + queue_lag (현재 시점) 머지.
  queue_lag.oldest_pending_age_sec 가 600초 초과면 stage 라벨 옆에 경과시간 표시.
- STAGE_ORDER/LABEL 에 stt/thumbnail 추가 (§3 신규 stage 자동 커버).

docs/categories.md (신규):
- 6 활성 + 3 유보 카테고리 정의 + 저장 경로 + 처리 파이프
- 역할 분리 원칙 (category / user_tags @library/ / facet_doctype / ai_suggestion)
- 업로드 경로 매트릭스 (web/NAS/collector/UI)
- video 채널별 정책 표 (web 거부 vs NAS quarantine)
- 업로드 한도 + error_code 7종 표
- orphan 임시파일 cleanup 정책

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 07:09:37 +09:00
Hyungi Ahn cec464ae2d fix(media): §3 ship-readiness — stt preload + healthcheck + queue enum + dashboard queue_lag
stt:
- services/stt/server.py: lazy → eager preload in FastAPI lifespan.
  STT_PRELOAD=0 으로 lazy 강제 가능 (개발/테스트). preload 실패해도
  프로세스는 살아 있고 /ready false 로 남아 healthcheck 가 unhealthy 처리.
- docker-compose.yml: healthcheck /health → /ready. /health 는 단순
  liveness 라 모델 미적재 상태도 healthy 로 잡혀 운영 신호 부적합.

queue ORM:
- app/models/queue.py: process_stage enum 에 'stt'/'thumbnail' 추가 +
  create_type=False (migration 150/151 가 DB enum 확장 담당). 이게
  없으면 stt_worker INSERT 시 SQLAlchemy 가 enum value 를 거부.

dashboard 강화 (§4 선제, §3 신규 stage 까지 자동 커버):
- app/api/dashboard.py: category_counts + library_pending_suggestions +
  queue_lag (stage 별 pending/processing/failed + oldest_pending_age_sec).
- frontend/src/lib/stores/system.ts: QueueLag 타입 + DashboardSummary 확장.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 07:04:52 +09:00
Hyungi Ahn 8f25d396df feat(upload): §4-독립 — error_code 체계 + .uploading orphan cleanup + 진행률/abort UX
plan: ~/.claude/plans/luminous-sprouting-hamster.md §4 (1GB/stt/dashboard 외 독립 항목)

backend:
- _upload_error(status, code, msg) 헬퍼 정의 (§3 가 호출만 추가했던 누락 수정).
  detail = {error_code, message} — 프론트가 error_code 로 분기.
- upload_document 의 모든 HTTPException 을 _upload_error 로 전환:
  body_too_large / invalid_input / empty_file / unsupported_codec / internal
- ClientDisconnect → 499 network_abort + 임시파일 정리.
  asyncio.TimeoutError → 408 upload_timeout.
- 쓰기 중 .uploading 임시명 → 완료 후 staging.replace(target) atomic rename.
  → 프로세스 크래시 잔존물은 cleanup_orphan_uploads 가 수거.
- file_watcher SKIP_EXTENSIONS 에 .uploading 추가 (오해 픽업 방지).

cleanup scheduler:
- workers/upload_cleanup.py 신규. 10분 주기로 Inbox 하위 *.uploading 중
  mtime > orphan_max_age_sec(3600) 인 파일 삭제.
- 최근 3회 (≈30분) 누적 삭제 수가 cleanup_warn_threshold(10) 이상이면
  WARNING 로그. in-memory deque (재시작 시 리셋) — 집요한 이슈만 잡는 목적.
- core/config.py UploadConfig 에 두 임계치 필드 (defaults — config.yaml override 무관).

frontend:
- api.ts: ApiError 에 optional errorCode/errorMessage 필드 (detail string 유지로
  기존 5+ 소비자 호환). parseDetail() 가 {error_code, message} 객체 응답을 풀어
  정규화. uploadFile(path, formData, {signal, onProgress}) XHR 헬퍼 신규
  (fetch() 가 upload progress 미지원이라 XHR). 401 refresh 1회 정책 동일.
- UploadDropzone.svelte 재작성: 진행률 바, 파일별/전체 abort 버튼, 페이지 이탈
  beforeunload 경고, errorCode 별 토스트 메시지 분기 (7 코드 — body_too_large /
  upload_timeout / network_abort / empty_file / invalid_input / unsupported_codec /
  internal). 컴포넌트 unmount 시 진행 중 업로드 abort.

보류:
- max_bytes 1GB 상향 + Caddyfile 1100MB (별도 결정으로 100MB 유지)
- /dashboard 카테고리 카드 (별도 plan)
- docs/categories.md (§1-3 정의 안착 후)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 06:57:02 +09:00
Hyungi Ahn 1e2c004dd4 feat(media): §3 audio STT + video 재생 인프라
plan: ~/.claude/plans/luminous-sprouting-hamster.md §3

스키마:
- migrations/147_audio_segments_table.sql: audio_segments (STT 타임스탬프
  세그먼트)
- migrations/148_audio_segments_idx.sql: (document_id, start_s) idx
- migrations/149_document_media_cols.sql: documents.thumbnail_path +
  needs_conversion
- migrations/150_queue_stage_stt.sql: process_stage += 'stt'
- migrations/151_queue_stage_thumbnail.sql: process_stage += 'thumbnail'
- app/models/audio_segment.py, document.py (thumbnail_path/needs_conversion)

서비스:
- services/stt/{Dockerfile, requirements.txt, server.py} — faster-whisper
  large-v3 GPU 컨테이너. /transcribe (filePath/langs/beamSize) +
  /health + /ready (cuda device_count + model_loaded). NFC/NFD 경로
  resolver (OCR 교훈).
- docker-compose.yml: stt-service 추가 (GPU 1 예약, :3300, NAS ro mount,
  stt_models volume, start_period 300s), fastapi env 에 STT_ENDPOINT.

파이프라인 (의존 §1 category):
- app/workers/stt_worker.py 신규: stage='stt' pickup → STT_ENDPOINT 호출 →
  extracted_text + audio_segments 저장. Timeout 30분.
- app/workers/thumbnail_worker.py 신규: ffmpeg 50% 지점 1장 →
  PKM/Videos/.thumbs/{id}.jpg + thumbnail_path 세팅.
  needs_conversion=true 는 skip.
- app/workers/file_watcher.py 확장: PKM/{Inbox, Recordings, Videos}
  스캔. 확장자→category, audio→stage=stt, video .mp4/.webm→
  stage=thumbnail, video .mov/.mkv/.avi→needs_conversion=true + stage
  없음. settings.roon_library_path prefix skip.
- app/workers/queue_consumer.py 확장: stt + thumbnail workers 등록,
  BATCH_SIZE(stt=1, thumbnail=3), next_stages 에 stt→[classify] 추가
  (audio 는 extract 건너뜀).
- app/Dockerfile: ffmpeg 추가 (썸네일 subprocess 용).

API (의존 §1):
- /api/audio/{id}/segments — AudioSegment ORDER BY start_s
- /api/video/{id}/thumbnail — thumbnail_path FileResponse (쿼리 토큰)
- /api/documents/{id}/file: media_types 에 audio/video mime 포함 (§2
  커밋에 이미 포함). Starlette FileResponse 가 Range 자동.
- upload_document: .mov/.mkv/.avi 웹 업로드 거부 (error_code
  unsupported_codec). NAS 드롭은 file_watcher 가 quarantine 수용.

프론트:
- AudioPlayer.svelte: HTML5 audio + 전사 세그먼트 sticky 패널 + 줄
  클릭 seek. activeIdx 하이라이트.
- VideoPlayer.svelte: HTML5 video direct play + needs_conversion 안내
  카드. poster 는 thumbnail endpoint.
- /audio (목록 grid) + /audio/[id] (플레이어)
- /video (썸네일 grid + 변환 필요 배지) + /video/[id] (플레이어)
- Sidebar.svelte: Mic/Film 아이콘 + audio/video 네비 활성, count
  배지 (§2 /stats/category-counts 재사용).

설정:
- app/core/config.py: stt_endpoint + roon_library_path.

DoD 배포 후 smoke: /ready cuda:true, 회의 mp3 transcribe, audio
extract 없이 classify 진행(queue 회귀), /audio 재생, .mp4 재생,
.mov 웹 400, .mov NAS quarantine, Sidebar 네비 + count.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 06:47:36 +09:00
Hyungi Ahn a93d1689d8 feat(documents): §2 카테고리 전용 페이지 + 승인 UI
plan: ~/.claude/plans/luminous-sprouting-hamster.md §2

- GET /api/documents/stats/category-counts — Sidebar/Dashboard 용
  카테고리별 문서 건수 + library_pending_suggestions
- DocumentResponse 에 category / ai_suggestion 필드 노출 (§1 과 동일
  수정, rebase 시 합쳐짐)
- SuggestionReview.svelte 신규 — ai_suggestion.proposed_category='library'
  제안 카드 리스트. 단건 승인/반려 + 체크박스 대량 승인. 409 stale 시
  warning toast + 자동 refetch
- /library 상단에 SuggestionReview 배치 (자료실 + 승인 대기함 겸).
  승인/반려 후 tree/docs/facet 재조회
- Sidebar 재구성: 카테고리 내비(문서/자료실/뉴스/메모/검색) + 자료실
  pending 배지. /api/documents/stats/category-counts 바인딩. audio/video
  자리는 §3 주석 예약

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 15:36:22 +09:00
Hyungi Ahn d2aa6c7c41 refactor(upload): 프론트 pre-check 가 서버 공개 설정을 구독하도록 전환
프론트의 `MAX_UPLOAD_BYTES = 100 * 1000 * 1000` 하드코딩 상수를 제거하고
서버 `GET /api/config/public` 응답을 단일 진실 공급원으로 사용.
pre-check 자체는 그대로 유지 (UX 개선 — 대용량 파일을 edge proxy 까지
올리기 전 클라이언트에서 즉시 차단). 값의 출처만 서버로 이동.

변경:
- frontend/src/lib/stores/config.ts 신규 — publicConfig readable store
  * 첫 구독 시 `/config/public` 1회 fetch
  * fetch 실패 시 fallback 100MB 유지 (서버 enforcement 가 본선이라 안전)
- +layout.svelte onMount 에서 prewarm refresh() 호출
- UploadDropzone.svelte 에서 `$derived` 로 store 값을 반응형 구독
  * `maxBytes` / `maxBytesLabel` 을 파생
  * 에러 토스트 문구도 동적 라벨 사용 (`100MB` 하드코딩 제거)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-17 08:05:49 +09:00
Hyungi Ahn 6a187dbccf fix(upload): 용량 초과 안내를 실제 감시 경로(PKM/Inbox)로 정정, UI에서 SLA 숫자 제거
file_watcher.py:33 이 `Path(settings.nas_mount_path) / "PKM" / "Inbox"` 만
rglob 재귀 스캔함. 그러나 UI 문구는 "NAS의 PKM 폴더" 로 넓게 안내해
사용자가 PKM 바로 아래 다른 폴더(Reports, Archive 등) 에 파일을 두면
조용히 실패하는 silent dead end 가 생기던 문제를 정정.

또한 "5분 이내 자동 인덱싱" 같은 단정적 시간 약속을 제거. watcher 주기
(5분) 와 후속 처리 큐(extract/classify/embed) backlog 는 별개이며,
감시 주기만 5분이지 처리 완료가 5분 내라는 뜻이 아님. 숫자는 운영 지식
이지 UX 계약이 아니므로 UI 에서 제거하고 "감시 주기와 처리 대기열
상황에 따라 반영 시점은 달라질 수 있습니다" 로 정직하게 표현.

주석에서 `home-caddy` 외부 인프라 이름도 제거. 추후 Phase B 에서 이
한도는 서버가 내려주는 단일 계약값으로 이동 예정.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-17 07:53:25 +09:00
Hyungi Ahn afd592c85c fix(upload): MiB/MB 단위 혼용 해소 + 용량 초과 스킵 토스트 반영
Caddy `request_body max_size 100MB`가 go-humanize SI(100,000,000 바이트)로
파싱되는데 클라이언트 pre-check는 `100 * 1024 * 1024`(104,857,600 바이트, MiB)로
비교해 100,000,001–104,857,600 바이트 구간 파일이 사전 차단을 통과한 뒤
서버에서 413을 받던 문제를 수정. 표시 라벨도 `/1024/1024`로 나누고 'MB'라
적어 경계값 파일이 "100MB 초과 … (100.0MB)" 같은 모순 문구를 노출했음.

요약 토스트가 사전 차단된 파일(`tooLarge`)을 카운트에서 제외해 드롭 수량과
불일치하던 문제도 함께 정리. `N건 용량 초과 스킵`을 tail로 붙이고, 전부
스킵된 경우엔 추가 토스트 없이 기존 에러 토스트만 유지.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-17 07:29:25 +09:00
Hyungi Ahn d9c901087b fix(ui): 분석 명칭 '빠른 분석'으로 정직화
현재 /analyze는 12K자까지만 처리하므로 '이 문서 분석'이라는 이름은 오해 여지.
- 패널 제목: '이드 분석' → '빠른 분석'
- 버튼: '이 문서 분석' → '빠른 분석'
- 안내: 12,000자 제한 명시 + '전체 분석 추후 제공' 고지
- truncated 경고: neutral → warning 색상

전체 문서 coverage가 보장되는 '전체 분석'은 다음 iteration에서
백엔드 내부 map-reduce 청킹으로 별도 구현 예정.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 12:44:05 +09:00
Hyungi Ahn ee090089e9 fix(api): D.5 analyze timeout 20→60초, text_limit 15000→12000
doc 5271(29,837자) 등 큰 문서에서 20초 timeout 빈발.
- ANALYZE_TIMEOUT_S: 20 → 60 (safety margin 포함)
- ANALYZE_TEXT_LIMIT: 15000 → 12000 (Gemma 입력 부담 완화)
- 프론트 안내 '10초' → '10~40초 소요'

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 12:39:01 +09:00
Hyungi Ahn 305ae9b322 feat(ui): Phase D.6~D.7 — AnalysisPanel + 문서 상세 통합
D.6: AnalysisPanel 컴포넌트 — 기본 접힌 상태 + '이 문서 분석' 버튼
  - POST /documents/{id}/analyze 호출
  - docId 변경 시 state 완전 리셋 ($effect)
  - 층별 렌더 (근거/해설/사례/요약, 없는 층 생략)
  - 에러 통일 문구 + 재시도/재분석 버튼
D.7: 문서 상세 페이지 우측 editors stack에 Card 래핑으로 삽입
  - AIClassificationEditor 다음, FileInfoView 이전
  - DocumentViewer / PreviewPanel 변경 없음

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 12:35:04 +09:00
Hyungi Ahn 6bc52928b6 fix(ui): 검색 input Enter 시 문서 열림 방지 (stopPropagation)
검색바에서 Enter → submitSearch()만 실행되어야 하는데
useListKeyboardNav의 window 리스너가 Enter를 잡아 selectDoc() 호출.
stopPropagation으로 이벤트 전파 차단.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 11:28:55 +09:00
Hyungi Ahn f4791cfada feat(ui): Phase D.1~D.4 — 검색 페이지 이드 답변 통합
D.1: documents route 디자인 토큰 정리 (var(--*) → 시맨틱 토큰, 잔여 0)
D.2: isQuestion 질문형 감지 유틸 (? 단일단어 허용, 한/영 6규칙)
D.3: AskAnswerCard 컴팩트 답변 카드 + analyze.ts 타입 정의
D.4: 질문형 검색 시 /search/ask 병렬 호출 + 상단 카드 배치

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 11:09:58 +09:00
Hyungi Ahn ef9687b0bf feat(library): Phase 2B 문서 상세 facet 편집 + 업로드 facet 전달
FileInfoView에 회사/주제/연도/문서유형 select 4개 추가.
facet 옵션은 /api/library/facets에서 로드, 세션 캐시.
업로드 엔드포인트에 facet Form 파라미터 4개 추가.
업로드 시 현재 선택 facet 자동 전달 + 미리보기 텍스트.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 10:27:03 +09:00
Hyungi Ahn 776734c897 feat(library): Phase 2B facet 필터 패널 + 문서 목록 연동
자료실 좌측에 회사/주제/연도/문서유형 facet pill 패널 추가.
single-select 토글, count 표시, 교차 필터 (자기 축 제외).
URL searchParams 기반 상태 관리 (뒤로가기/새로고침 유지).
loadDocs에 facet 파라미터 전달, loadFacetCounts 분리 (page/sort 제외).
count 0은 dim+disabled, 초기화 버튼 포함.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 10:25:39 +09:00
Hyungi Ahn 964d4ffc67 feat(library): 자료실 분류 체계 독립 관리 Phase 1
library_categories 테이블 추가로 빈 카테고리 생성 가능.
CRUD API (생성/leaf rename/leaf delete) + 트리 머지 엔드포인트.
사이드바 트리에 컨텍스트 메뉴 (추가/이름변경/삭제).
LibraryPathEditor를 카테고리 기반 flat selector로 전환.
미분류는 시스템 분류로 보호 (삭제/이름변경 불가).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 10:01:53 +09:00
Hyungi Ahn ef89d48bfe fix(library): 자료실 루트 업로드 시 @library/ 태그 누락 수정
폴더 미선택 상태에서 업로드하면 doc_purpose='business'만 설정되고
@library/ 태그가 빠져서 자료실에 문서가 표시되지 않던 버그 수정.
백엔드: business 업로드에 library_path 없으면 @library/미분류 자동 태깅.
프론트: activePath 없을 때 기본값 '미분류' 전송.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 07:36:40 +09:00
Hyungi Ahn e89e19f365 feat(library): 자료실 드래그 업로드 + 오버레이
자료실 페이지에서 드래그 앤 드롭 업로드 지원.
업로드 후 자료실 내에서 트리+목록 새로고침 (문서 페이지로 이동하지 않음).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 15:53:09 +09:00
Hyungi Ahn d6756010b1 fix(memos): 마감일 버튼 줄바꿈 없이 커서 옆에 삽입
insertAtCursor가 자동 줄바꿈 추가해서 마감일이 아랫줄에 생성됨.
직접 삽입으로 변경하여 현재 커서 위치 바로 옆에 @날짜 삽입.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 15:46:37 +09:00
Hyungi Ahn 804ba1f4c7 feat(memos): 체크박스 수정 + 마감일 badge + 대시보드 인터랙션
공통 유틸 memoRenderer.ts 분리 (drift 방지):
- checkbox regex 속성 순서 독립으로 수정 (버그 원인)
- due date: checkbox line 마지막 @YYYY-MM-DD만 badge 변환
  overdue=빨강, soon(3일)=노랑, normal=dim, checked=dim
- toggleTaskLine: taskIndex 기반 안전한 토글
- 날짜 비교 로컬 기준 (TZ 이슈 회피)

메모 페이지:
- 렌더링/토글 공통 유틸 import
- 툴바에 📅 마감일 버튼 추가

대시보드:
- 핀 메모 체크박스 토글 가능 (optimistic + rollback)
- stopPropagation으로 details 토글 충돌 방지
- renderMdSimple → renderMemoHtml 통일

QuickMemoButton:
- 체크리스트 + 마감일 버튼 2개 추가

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 15:40:18 +09:00
Hyungi Ahn 5c58778a41 feat(library): doc_purpose 필드 + 자료실 업로드 기능
지식/업무 문서 1차 구분을 위한 doc_purpose(business|knowledge) 추가.
- 마이그레이션: document_purpose enum + 컬럼
- AI 분류: docPurpose 자동 추론 (빈 값만 채움)
- 업로드 API: doc_purpose + library_path Form 파라미터
- 자료실 업로드: business 기본값 + 선택 경로 자동 태깅
- FileInfoView: 용도 select (수동 변경, 실패 롤백)
- DocumentCard: 업무/참조 배지

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 15:26:59 +09:00
Hyungi Ahn 96ab2369a7 fix(memos): 수정 500 에러 + 줄바꿈 렌더링
1. DocumentChunk.document_id → doc_id (실제 컬럼명)
2. marked breaks: true — 단일 줄바꿈이 <br>로 변환

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 15:24:46 +09:00
Hyungi Ahn 5ce0e848a3 feat(memos): 선택적 제목, 툴바 버튼, 대시보드 핀 펼침
메모 입력/편집:
- 선택적 제목 토글 (기본 숨김, "제목" 버튼으로 활성화)
- 툴바 버튼: 체크리스트/굵게/제목 (모바일에서 마크다운 수동 입력 불필요)
- 편집 모드에도 동일 툴바

대시보드 핀 메모:
- 클릭 시 /memos 이동 대신 인라인 펼침/접힘 (details)
- 제목이 있으면 제목 표시, 없으면 첫 줄
- 펼치면 마크다운 렌더링된 본문 + "메모함에서 보기" 링크

Backend:
- MemoCreate/MemoUpdate에 선택적 title 파라미터 복원

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 15:06:43 +09:00
Hyungi Ahn deb5c1b704 feat(library): 자료실 — 태그 기반 트리 문서 관리 기능
목적성 문서(양식, 템플릿, 연간보고서)를 @library/ 태그로 분류하고
트리 구조로 탐색하는 자료실 페이지 추가.

백엔드: 경로 정규화 유틸, library-tree/library 엔드포인트,
다운로드 Content-Disposition 개선(원본/PDF 분리, 한글 filename*)
프론트: /library 페이지, LibraryPathEditor, 상단 nav/사이드바 링크

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 14:55:45 +09:00
Hyungi Ahn 6067177913 fix(memos): 모바일 액션 버튼 항상 표시
hover 기반 opacity가 모바일에서 동작하지 않아 편집/삭제/핀 등
액션 버튼 접근 불가. md 이상에서만 hover 숨김, 모바일은 항상 표시.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 14:47:33 +09:00
Hyungi Ahn c9eeee5fd5 feat(news): 모바일 스플릿뷰 + 책갈피 기능
모바일 풀스크린 오버레이를 제거하고 리스트(35%)+미리보기(65%) 분할뷰로 전환.
pinned 필드를 활용한 책갈피 토글 및 필터 추가.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 14:43:04 +09:00
Hyungi Ahn 2b5a6d410b refactor(dashboard): 상황판 재설계 — 사용자 지시서 기반 구현
대시보드를 통계판에서 상황판으로 전환:
- 헤더 + 시스템 상태 인라인 (비클릭)
- 핀 메모 최상단 조건부 (컴팩트 띠, 최대 3개)
- 카드 4개 (문서함/메모/뉴스/승인대기) 모바일 2×2
- 최근 활동 전체 너비 7건, 2줄 스캔형 + 법령 배지
- 파이프라인 details 접힘 (실패 시 자동 open)
- 제거: 도메인 분포, 법령/시스템 별도 카드, 8:4 분할

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 14:42:15 +09:00
Hyungi Ahn 93fcd4cf0b refactor(dashboard): 원래 8:4 2열 레이아웃 복원 + 개선 유지
이전 재설계에서 위젯을 과도하게 제거해 퇴화.
원래 12칸 그리드 + 8:4 2열 구조 복원하면서 개선 유지:
- 행1: 4개 카드 (문서함/메모/뉴스/승인대기)
- 행2: 파이프라인(8) + 도메인 분포(4)
- 행3: 최근 문서(8) + 법령/시스템(4)
- 핀 메모 상단 조건부 표시
- CalDAV stub → 법령 알림 + 시스템 상태 카드

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 14:30:21 +09:00
Hyungi Ahn 3fe4c16d3a refactor(dashboard): UI/UX 재설계 — 정보 위계 + 모바일 최적화
대시보드 전면 재작성:
- 핀 메모: 최상단 조건부 컴팩트 띠 (pinned=true API 파라미터 추가)
- 4개 핵심 카드: 문서함/메모/뉴스/승인대기 (2×2 모바일, 4열 데스크탑)
- 승인 대기: 액션형 카드 (warning + 검토하기 CTA)
- 최근 활동: 전체 너비, 2줄 스캔형, 법령 알림 뱃지
- 파이프라인: details 기반 접힘 (실패 시 자동 펼침, 수동 접힘 유지)
- 시스템 상태: 헤더 인라인 배지 (비클릭)
- CalDAV stub/도메인 분포 위젯 제거

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 14:25:19 +09:00
Hyungi Ahn f231dae5af fix(dashboard): 카드 균등 꽉 채우기 — min-w-0 + overflow-hidden
grid 셀이 콘텐츠 최소 너비에 의존해 우측 잘림.
min-w-0으로 shrink 허용, overflow-hidden으로 넘침 방지.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 09:38:49 +09:00
Hyungi Ahn e0d92a3a28 fix(dashboard): 카드 균등 비율 + 모바일 3열×2행 레이아웃
모바일 3열×2행, 데스크탑 6열×1행. 텍스트 중앙 정렬,
보조 텍스트 높이 통일 (투명 placeholder), 카드 크기 균등.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 09:33:14 +09:00
Hyungi Ahn e43ea137b6 fix(dashboard): 카드 레이아웃 모바일 반응형 개선
모바일 2열×3행 / md 3열×2행 / lg 6열×1행 그리드.
아이콘을 라벨 옆으로 이동, "오늘 +0" 숨김, 카드 높이 통일.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 09:23:40 +09:00
Hyungi Ahn fa0175058a feat(dashboard): 카운트 분리 — 문서함/메모/뉴스/승인대기
전체 문서 1개 카드를 6개로 분리: 문서함, 메모, 뉴스, 승인대기,
법령알림, 시스템. 단일 FILTER 쿼리로 효율적 카운트.
각 카드 클릭 시 해당 페이지로 이동.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 09:19:00 +09:00
Hyungi Ahn 087cbdc900 fix(memos): 뉴스 혼입 방지 + 스크롤 차단 수정
1. 메모 목록 쿼리에 source_channel='memo' 조건 추가.
   뉴스가 file_type='note'로 저장되어 메모 피드에 혼입됨.
2. main 컨테이너 overflow-hidden → overflow-auto.
   메모 페이지가 body 스크롤에 의존하는데 차단되어 있었음.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 09:09:01 +09:00
Hyungi Ahn e889b33dd6 fix(memos): API 호출에 trailing slash 추가 (Mixed Content 수정)
FastAPI가 /api/memos → /api/memos/ 리다이렉트 시 프록시 뒤라
HTTP URL을 생성하여 HTTPS 페이지에서 Mixed Content 차단됨.
리스트/생성 엔드포인트 호출에 trailing slash 추가.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 08:42:18 +09:00
Hyungi Ahn e435332ea1 feat(memos): UX 개선 — 편집 수정, 제목 제거, 체크박스, 아카이브
Phase A: 편집 버그 수정 (content만 PATCH, Ctrl+Enter/Esc),
제목 UI 제거 (자동생성 80자, 내부용), 카드 경량화.

Phase B: GFM task list 지원, taskIndex 기반 인터랙티브 토글,
DOMPurify checkbox 최소 허용, optimistic update + 롤백.

Phase C: archived 컬럼 (메모 UX 전용, 문서 미노출),
멱등 세팅 API (토글 아님), 활성/아카이브 뷰 분리 쿼리,
핀은 활성 메모용 (archived 시 무시).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 08:36:16 +09:00
Hyungi Ahn 70729fd8a3 refactor(frontend): 상단 nav 재구성 — 핵심 기능 중심 4개 고정
상단 nav를 질문|메모|뉴스|Inbox 4개 핵심 기능으로 재정렬.
설정/로그아웃은 더보기(⋮) 드롭다운으로 이동.
메모 링크가 모바일에서 사이드바 없이 바로 접근 가능.
active 상태 표시(startsWith), 접근성 속성, 오버레이 닫기.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 07:00:54 +09:00
Hyungi Ahn b46a75758b feat(memos): 내장 메모 기능 — 파일 없는 문서(file_type='note')
Document Server에 Memos 앱 대체 기능 내장. 메모를 documents 테이블의
file_type='note' 레코드로 관리하여 기존 AI 파이프라인(classify/embed/
chunk/search/ask) 재활용.

Backend:
- migration 105: source_channel 'memo', file_path NULL 허용,
  user_tags/pinned/ask_includable 컬럼, 메모 인덱스
- api/memos.py: CRUD 7개 엔드포인트 + #태그 파싱 + stale AI 초기화
  + 큐 pending 중복 방지
- queue_consumer: note extract/preview skip
- documents API: file_path NULL 가드, 목록에서 메모 제외
- search /ask: ask_includable=false 문서 evidence 제외

Frontend:
- /memos 타임라인 페이지 (빠른 입력 + 피드 + 인라인 편집 + 태그 필터)
- QuickMemoButton FAB (Ctrl+M, 모든 페이지)
- Sidebar 메모 링크

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 16:00:00 +09:00
Hyungi Ahn 33ce4292ca fix(frontend): add min-w-0 to flex chain for mobile card overflow
flex 체인에서 min-width: auto 기본값이 카드 shrink를 막아
모바일에서 콘텐츠가 뷰포트를 초과하던 문제 수정.
- +page.svelte line 418: flex-1 → flex-1 min-w-0
- +page.svelte line 693: overflow-x-hidden 추가
- DocumentCard.svelte: button에 min-w-0 추가

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 15:54:58 +09:00
Hyungi Ahn 7fb093884a fix(frontend): mobile responsive document list — hide table columns + card padding
- DocumentTable: 분류/타입/크기 컬럼 모바일 숨김 (hidden md:flex/block)
- DocumentCard: gap/padding 축소 (gap-2 p-2 sm:gap-3 sm:p-3), data_origin 모바일 숨김, 태그 모바일 1개
- 검색바: 검색모드 select 모바일 숨김, AI답변 텍스트 모바일 숨김

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 15:17:29 +09:00
Hyungi Ahn 06443947bf feat(ask): Phase 3.5a guardrails (classifier + refusal gate + grounding + partial)
신규 파일:
- classifier_service.py: exaone binary classifier (sufficient/insufficient)
  parallel with evidence, circuit breaker, timeout 5s
- refusal_gate.py: multi-signal fusion (score + classifier)
  AND 조건, conservative fallback 3-tier (classifier 부재 시)
- grounding_check.py: strong/weak flag 분리
  strong: fabricated_number + intent_misalignment(important keywords)
  weak: uncited_claim + low_overlap + intent_misalignment(generic)
  re-gate: 2+ strong → refuse, 1 strong → partial
- sentence_splitter.py: regex 기반 (Phase 3.5b KSS 업그레이드)
- classifier.txt: exaone Y+ prompt (calibration examples 포함)
- search_synthesis_partial.txt: partial answer 전용 프롬프트
- 102_ask_events.sql: /ask 관측 테이블 (completeness 3-분리 지표)
- queries.yaml: Phase 3.5 smoke test 평가셋 10개

수정 파일:
- search.py /ask: classifier parallel + refusal gate + grounding re-gate
  + defense_layers 로깅 + AskResponse completeness/aspects/confirmed_items
- config.yaml: classifier model 섹션 (exaone3.5:7.8b GPU Ollama)
- config.py: classifier optional 파싱
- AskAnswer.svelte: 4분기 렌더 (full/partial/insufficient/loading)
- ask.ts: Completeness + ConfirmedItem 타입

P1 실측: exaone ternary 불안정 → binary gate 축소. partial은 grounding이 담당.
토론 9라운드 확정. plan: quiet-meandering-nova.md

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 08:49:11 +09:00
Hyungi Ahn 0eecf1afca fix(frontend): favicon 404 제거 — SVG favicon + app.html link 추가 2026-04-10 08:41:23 +09:00
Hyungi Ahn 563f54d7d5 fix(upload): 100MB 초과 파일 사전 차단 + NAS file_watcher 안내
home-caddy 의 request_body max_size 100MB 한도 (infra_inventory.md D8 /
Cloudflare 섹션 참조) 에 걸리는 업로드 시 사용자 콘솔에 의미 없는 413 만
나오던 문제. 이제:

1. 클라이언트 사전 검사: 100MB 초과 파일은 업로드 자체를 시도 안 하고
   즉시 toast 로 안내 (파일명 + 크기 + NAS 우회 경로)
2. 서버 fallback: 사전 검사를 통과했으나 인프라 한도에 걸려 413 응답이
   오는 경우에도 같은 안내 메시지

NAS 우회 경로: NAS 의 PKM 폴더에 직접 두면 file_watcher 가 5분 간격으로
자동 인덱싱. 이게 100MB+ 파일의 정식 처리 경로 (infra_inventory.md
Cloudflare 섹션의 413 정책).
2026-04-09 14:26:18 +09:00
Hyungi Ahn bfdf33b442 feat(frontend): Phase 3.4 Ask pipeline UI (/ask 3-panel)
- routes/ask/+page.svelte: URL-driven orchestrator, lastQuery guard
  (hydration 중복 호출 방지), citation scroll 연동
- lib/components/ask/AskAnswer: answer body + clickable [n] +
  confidence/status Badge + warning EmptyState (no_results_reason +
  /documents?q=<same> 역링크)
- lib/components/ask/AskEvidence: span_text ONLY 렌더 (full_snippet
  금지 룰 컴포넌트 주석에 박음) + active highlight + doc-group ordering 유지
- lib/components/ask/AskResults: inline 카드 (DocumentCard 의존 회피)
- lib/types/ask.ts: backend AskResponse 스키마 1:1 매칭
- +layout.svelte: 탑 nav 질문 버튼 추가
- documents/+page.svelte: 검색바 옆 AI 답변 링크 (searchQuery 있을 때만)

plan: ~/.claude/plans/quiet-meandering-nova.md (Phase 3.4)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-09 08:45:24 +09:00
Hyungi Ahn cdcbb07561 fix(inbox): page_size=200 → 422 해결, review_status 서버 필터 추가
Inbox 페이지가 /documents/?page_size=200 를 호출하는데 백엔드 Query 가
le=100 이라 422 발생 — Phase 2 첫 commit(2026-04-02)부터 dormant 버그.
inbox 코드 안에 'TODO(backend): review_status filter 지원 시 page_size 축소'
주석이 있던 상태.

backend:
- list_documents 에 review_status: str | None Query 파라미터 추가
- WHERE 절에 review_status 매칭 분기 추가

frontend:
- /documents/?review_status=pending&page_size=100 으로 변경
- 클라이언트 필터링 코드 제거 (서버 필터로 대체)

100 미만 안전. pending 이 100 넘으면 다음 페이지 로직 추가 필요 (별도 작업).
2026-04-09 08:31:51 +09:00
Hyungi Ahn 7cf7662ba5 fix(ui): Tailwind v4 z-index 유틸리티 누락 — @utility 로 등록
## 증상
/documents 페이지에서 사이드바 drawer 를 열면 뒤의 필터 칩 row
(`+ 태그`, `+ 형식`) 와 sticky 선택 toolbar 가 사이드바 위로 **그대로
비쳐 보이는** 시각 버그. 사이드바 tree 내용과 섞여 완전히 사용 불가.

## 근본 원인
`@theme { --z-dropdown: 30; --z-drawer: 40; --z-toast: 60 }` 로 정의했지만,
Tailwind v4 는 `--z-*` 를 utility namespace 로 인식하지 않음. 그래서 Drawer
및 페이지의 `class="... z-drawer"`, `class="... z-dropdown"` 이
컴파일 CSS 에 **아예 없는 클래스 (.z-drawer 등 생성 안 됨)** → `z-index:
auto` 로 fallback.

CSS 2.1 stacking 규칙상 positioned z-auto 끼리는 **DOM order** 로 paint 됨.
layout.svelte 의 Drawer 가 먼저 렌더되고 페이지 `<slot/>` 의 `.relative`
필터 칩 popover 컨테이너가 나중에 렌더 → 필터 칩이 사이드바 위에 그려짐.

`--z-modal` 만 살아남은 이유: Modal.svelte 가 `calc(var(--z-modal) + ...)`
로 inline style 에서 실제 var() 참조해서 Tailwind 가 tree-shaking 에서
제외함.

## 수정
`frontend/src/app.css` 의 `@theme` 블록 바로 아래에 Tailwind v4
`@utility` directive 로 4개 유틸리티 명시 등록:

```css
@utility z-dropdown { z-index: var(--z-dropdown); }
@utility z-drawer   { z-index: var(--z-drawer); }
@utility z-modal    { z-index: var(--z-modal); }
@utility z-toast    { z-index: var(--z-toast); }
```

var() 참조 덕분에 `--z-*` 변수도 tree-shaking 에서 제외됨.

## 다른 파일 변경 없음
Drawer.svelte, documents/+page.svelte, inbox/+page.svelte, Modal.svelte
의 기존 클래스 사용부는 **한 글자도 수정 안 함**. @utility 등록만으로
자동 재활성.

## 검증
- npm run build 통과
- 컴파일 CSS 에 .z-drawer/.z-dropdown/.z-modal 클래스 실제 생성 확인
  (.z-toast 는 소스 사용부가 없어 JIT 제외, 필요 시 자동 생성)
- --z-dropdown/--z-drawer/--z-modal/--z-toast 4개 모두 :root 에 emit
- lint:tokens 168 유지

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 13:28:54 +09:00
Hyungi Ahn 4938b25d12 feat(ui): Phase E — PreviewPanel 분할 + detail inline + viewer Tabs
E.1 PreviewPanel 7개 editors/* 분할:
- frontend/src/lib/components/editors/ 신설 (7개 컴포넌트):
  * NoteEditor — 사용자 메모 편집
  * EditUrlEditor — 외부 편집 URL (Synology Drive 등)
  * TagsEditor — 태그 추가/삭제
  * AIClassificationEditor — AI 분류 read-only 표시
    (breadcrumb + document_type + confidence tone Badge + importance)
  * FileInfoView — 파일 메타 dl
  * ProcessingStatusView — 파이프라인 단계 status dl
  * DocumentDangerZone — 삭제 (ConfirmDialog 프리미티브 + id 고유화)
- PreviewPanel.svelte 344줄 → 60줄 얇은 wrapper로 축소
  (header + 7개 editors 조합만)
- DocumentMetaRail (D.1)과 detail 페이지(E.2)가 동일 editors 재사용

E.2 detail 페이지 inline 편집:
- documents/[id]/+page.svelte: 기존 read-only 메타 패널 전면 교체
- 오른쪽 aside = 7개 editors 스택 (Card 프리미티브로 감쌈)
- 왼쪽 affordance row: Synology 편집 / 다운로드 / 링크 복사
- 삭제는 DocumentDangerZone이 담당 (ondelete → goto /documents)
- loading/error 상태도 EmptyState 프리미티브로 교체
- marked/DOMPurify renderer 유지, viewer 분기 그대로

E.3 관련 문서 stub:
- detail 페이지 오른쪽 aside에 "관련 문서" Card
- EmptyState "추후 지원" + TODO(backend) GET /documents/{id}/related

E.4 DocumentViewer Tabs 프리미티브:
- Markdown 편집 모드의 편집/미리보기 토글 → Tabs 프리미티브
- 키보드 nav (←→/Home/End), ARIA tablist/tab/tabpanel 자동 적용

검증:
- npm run build 통과 (editors/* 7개 모두 clean, $state 초기값
  warning은 빈 문자열로 초기화하고 $effect로 doc 동기화해 해결)
- npm run lint:tokens 204 → 168 (detail 페이지 + PreviewPanel 전면
  token 기반 재작성으로 -36)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 12:51:10 +09:00
Hyungi Ahn 7a38c95f3f feat(ui): Phase F — Inbox 분류 UX + review_status hotfix
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>
2026-04-08 12:46:03 +09:00
Hyungi Ahn d83842ccd8 feat(ui): Phase D.4/D.5 — keyboard nav + density toggle
D.4 useListKeyboardNav:
- 신규: frontend/src/lib/composables/useListKeyboardNav.svelte.ts
  j/k/Arrow/Enter/Esc, isTypingTarget 가드 (input/textarea/select/
  contenteditable 포커스 시 비활성)
- documents/+page.svelte: kbIndex $state, kbSelectedId $derived,
  items 변경 시 clamp, URL 변경 시 0 리셋
- DocumentTable/DocumentCard: kbSelectedId prop → data-kb-selected
  속성 + ring-accent-ring 시각 표시
- scrollSelectedIntoView: queueMicrotask + querySelector로 현재
  커서를 뷰포트 내로 스크롤 (block: nearest)

D.5 Table density:
- DocumentTable: density prop (compact/comfortable), rowPaddingClass
  ($derived: py-1 | py-2.5), rowTextClass (text-[10px] | text-xs)
- documents/+page.svelte: tableDensity $state, toggleDensity 헬퍼,
  localStorage.tableDensity persistent, 테이블 뷰에서만 토글 버튼
  노출 (Rows2/Rows3 아이콘)
- 뷰 모드 버튼도 token 기반으로 리팩토링

검증:
- npm run build 통과
- npm run lint:tokens 231 → 229 (뷰 모드 버튼 token swap으로 -2)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 12:42:03 +09:00