Commit Graph

104 Commits

Author SHA1 Message Date
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
Hyungi Ahn 3375a5f1b1 feat(ui): Phase D.3 — multi-select + batch actions (pLimit)
- DocumentTable/DocumentCard: selectable/selectedIds/onselectionchange props
  * Table: 왼쪽 6px 너비 체크박스 컬럼
  * Card: 좌상단 absolute 체크박스 (hover 또는 selected 시 표시)
  * 체크박스 onclick stopPropagation으로 행 select와 분리
- documents/+page.svelte:
  * selectedIds = $state(new Set()), URL/필터 변경 시 자동 초기화
  * sticky 선택 toolbar (selection > 0): N건 / 전체 선택 / 선택 해제 /
    일괄 도메인 / 일괄 태그 / 일괄 삭제
  * 50건 상한 UI 가드 (초과 시 경고 + 모든 bulk 버튼 disabled)
  * Bulk modals:
    - 일괄 도메인: Select (Knowledge/* 6종 + Reference)
    - 일괄 태그: TextInput (기존 ai_tags에 추가, 중복 skip)
    - 일괄 삭제: ConfirmDialog (delete_file=true)
  * runBulk 헬퍼: pLimit(5) + Promise.allSettled로 concurrency 제한,
    성공/실패 카운트 toast (5대 원칙 #4)
  * TODO(backend): POST /documents/batch-update — 단일 트랜잭션으로 교체

검증:
- npm run build 통과 (새 경고 없음, label → span 교체로 a11y clean)
- npm run lint:tokens 231 유지 (신규 코드 위반 0)
- 기존 pLimit.ts (Phase A 머지) 재사용, 외부 의존성 없음

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 12:34:02 +09:00
Hyungi Ahn 8f312f50a7 feat(ui): Phase D.2 — filter chips + URL sync
- 검색바 아래 새 필터 칩 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>
2026-04-08 12:27:13 +09:00
Hyungi Ahn ffac4975b9 feat(ui): Phase D.1 — 3-panel layout + DocumentMetaRail + useMedia
- 가로 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>
2026-04-08 12:18:06 +09:00
Hyungi Ahn b3124928a6 feat(ui): Phase C — 대시보드 위젯 그리드 + dashboardSummary 공유 fetch
- dashboardSummary store 구독으로 SystemStatusDot과 fetch 1회 공유 (60초 폴링)
- Svelte 5 runes + Card/EmptyState/Skeleton/FormatIcon 프리미티브
- 12-col 그리드 (sm 1열 / md 2열 / lg 표 그대로):
  * 행1: stat 4장 (전체/Inbox/법령/시스템 상태)
  * 행2: 파이프라인 가로 막대 차트(8) + 오늘 도메인 누적바(4)
  * 행3: 최근 문서(8) + CalDAV stub(4)
- 신규 util: domainSlug.ts — ai_domain → bg-domain-{slug} + 라벨 매핑
- 새 코드에 bg-[var(--*)] 0건 (lint:tokens 통과)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 12:13:36 +09:00
Hyungi Ahn 0c63c0b6ab feat(ui): Phase B — sidebar drawer + SystemStatusDot + 키보드 nav
- +layout.svelte: 햄버거 → IconButton, 우측 nav → Button ghost,
  sidebar overlay → Drawer (uiState 단일 slot),
  Esc 글로벌 핸들러 ui.handleEscape() 위임 (5대 원칙 #2)
- lib/stores/system.ts (신규): dashboardSummary writable + 60s 폴링,
  단일 fetch를 SystemStatusDot(B)와 dashboard(C)가 공유
- SystemStatusDot.svelte (신규): 8px 도트 + tooltip,
  failed > 0 → error / pending > 10 → warning / 그 외 → success
- Sidebar.svelte: 트리에 ArrowUp/Down 키보드 nav,
  활성 도메인 row에 aria-current="page"
2026-04-07 13:52:24 +09:00
Hyungi Ahn a2941487fe fix(documents): detail view에 raw markdown fetch fallback 추가
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는 콘텐츠 표시 문제만 해결.
2026-04-07 12:55:59 +09:00
Hyungi Ahn c294df5987 refactor(tokens): A-8 Batch 3 — PreviewPanel / DocumentViewer / +layout(toast)
가장 큰 위험 batch. Phase A 디자인 시스템 정착 마지막 mechanical refactor
(8 파일 8/8 누적 — core components 0 hit 달성).

PreviewPanel (53 hits → 0):
- bg-[var(--sidebar-bg)] → bg-sidebar (메인 aside)
- bg-[var(--bg)]         → bg-bg (input 배경)
- bg-[var(--surface)]    → bg-surface (hover)
- bg-[var(--accent)]     → bg-accent + hover:bg-accent-hover (저장 버튼)
- bg-[var(--error)]      → bg-error (삭제 확인)
- text/border 토큰 일괄 swap
- focus:border-accent (input)
- confidence 색상 (green/amber/red palette)은 plan B3 명시 없어 그대로

DocumentViewer (28 hits → 0):
- 뷰어 본체 bg-surface border-default
- 툴바 bg-sidebar
- 마크다운 편집 탭 bg-surface, edit textarea bg-bg
- 상태별 hover 토큰 swap
- 뉴스 article 태그 blue-900/30 그대로 (lint:tokens 미검출)

+layout.svelte (10 hits → 0):
- nav 잔여 var() (햄버거, 로고, 메뉴 링크) 토큰 swap
- 로딩 텍스트 text-dim
- toast 영역 의미 swap (plan B3 명시):
  * green-900/200  → bg-success/10 + text-success + border-success/30
  * red-900/200    → bg-error/10 + text-error + border-error/30
  * yellow-900/200 → bg-warning/10 + text-warning + border-warning/30
  * blue-900/200   → bg-accent/10 + text-accent + border-accent/30
- class:* 디렉티브 8개 → script TOAST_CLASS dict + dynamic class binding
  (svelte 5에서 슬래시 포함 클래스명을 class: 디렉티브로 못 씀)

검증:
- npm run lint:tokens : 360 → 269 (-91, B3 파일 0 hit)
- 누적 진행: 421 → 269 (-152 / 8 파일 완료, plan 정정 목표 정확 달성)
- npm run build       : 
- npx svelte-check    :  0 errors
- ⚠ 3-risk grep       : hover/border-border/var() 잔여 0건

A-8 종료 시점 상태:
- core components 8 파일: lint:tokens 0 hit 
- routes 7 파일 잔존 (~269): news 92, settings 47, documents/[id] 36,
  +page 28, documents 26, inbox 25, login 15
- lint:tokens 강제화 (pre-commit hook)는 Phase D + F 완료 후 별도 commit

플랜: ~/.claude/plans/compressed-churning-dragon.md §A.4 Batch 3
2026-04-07 12:14:48 +09:00
Hyungi Ahn 8ec89517ee refactor(tokens): A-8 Batch 2 — Sidebar / DocumentCard / DocumentTable
목록/사이드바 영역의 var() 토큰을 의미 토큰으로 swap. Phase A 디자인
시스템 정착의 두 번째 mechanical refactor batch (8 파일 중 5/8 누적).

Sidebar:
- bg-[var(--sidebar-bg)]  → bg-sidebar  (이름 변경)
- border-[var(--border)]  → border-default
- text-[var(--text)]      → text-text
- text-[var(--text-dim)]  → text-dim
- bg-[var(--accent)]/15   → bg-accent/15
- hover:bg-[var(--surface)] → hover:bg-surface
- domain 색상 inline style (DOMAIN_COLORS)은 그대로 유지

DocumentCard:
- bg/border/text/hover 토큰 일괄 swap
- DOMAIN_COLORS의 var(--domain-*) 유지 (plan B2 비고)
- blue-400/blue-900/30 (news icon, data_origin work) 그대로
  (lint:tokens 미검출 + plan 명시 없음)

DocumentTable:
- 헤더 + 행 + selected 상태 + 컬럼 텍스트 일괄 swap
- border-l-[var(--accent)] → border-l-accent
- border-default/30 opacity suffix (행 구분선) v4 시각 검증 필요

검증:
- npm run lint:tokens : 407 → 360 (-47, B2 파일 0 hit)
- npm run build       : 
- npx svelte-check    :  0 errors
- ⚠ 3-risk grep       : hover/border-border/var() 잔여 0건

플랜: ~/.claude/plans/compressed-churning-dragon.md §A.4 Batch 2
2026-04-07 12:04:37 +09:00
Hyungi Ahn 451c2181a0 refactor(tokens): A-8 Batch 1 — TagPill / UploadDropzone
색상 시스템을 의미 토큰으로 swap. Phase A 디자인 시스템 정착의 첫
mechanical refactor batch (8 파일 중 2 파일).

TagPill: 4가지 prefix별 색상을 의미 토큰화
- @상태/  amber → warning
- #주제/  blue  → accent
- $유형/  green → success
- !우선순위/ red → error
- fallback bg-[var(--border)] → bg-default,
           text-[var(--text-dim)] → text-dim

UploadDropzone: 드래그 오버레이 + 업로드 진행 영역
- bg-[var(--accent)]/10 → bg-accent/10
- bg-[var(--surface)]   → bg-surface
- border-[var(--border)] → border-default
- text-[var(--text-dim)] → text-dim
- 상태별 텍스트: text-success / text-error / text-accent / text-dim

검증:
- npm run lint:tokens : 421 → 407 (-14, B1 파일 0 hit)
- npm run build       : 
- npx svelte-check    :  0 errors
- ⚠ 3-risk grep       : hover/border-border/var() 잔여 0건

플랜: ~/.claude/plans/compressed-churning-dragon.md §A.4 Batch 1
참고: 본 plan은 161ff18(search Phase 0.5 commit)에 styleguide 2개 파일이
의도와 다르게 묶여 main에 들어왔음. 기능 영향 0 — Option A 결정으로
commit history 미수정.
2026-04-07 11:44:29 +09:00
Hyungi Ahn 6b2747de96 chore: allow /__styleguide in dev public paths
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.
2026-04-07 09:39:39 +09:00
Hyungi Ahn 161ff18a31 feat(search): Phase 0.5 RRF fusion + 강한 신호 boost
기존 weighted-sum merge를 Reciprocal Rank Fusion으로 교체.
정확 키워드 매치에서 RRF가 평탄화되는 문제는 boost로 보완.

신규 모듈 app/services/search_fusion.py:
- FusionStrategy ABC
- LegacyWeightedSum  : 기존 _merge_results 동작 (A/B 비교용)
- RRFOnly            : 순수 RRF, k=60
- RRFWithBoost       : RRF + title/tags/법령조문/high-text-score boost (default)
- normalize_display_scores: SearchResult.score를 [0..1] 랭크 기반 정규화
  (프론트엔드가 score*100을 % 표시하므로 RRF 원본 점수 노출 시 표시 깨짐)

search.py:
- ?fusion=legacy|rrf|rrf_boost 파라미터 (default rrf_boost)
- _merge_results 제거 (LegacyWeightedSum에 흡수)
- pre-fusion confidence: hybrid는 raw text/vector 신호로 계산
  (fused score는 fusion 전략마다 스케일이 달라 일관 비교 불가)
- timing에 fusion_ms 추가
- debug notes에 fusion 전략 표시

telemetry:
- compute_confidence_hybrid(text_results, vector_results) 헬퍼
- record_search_event에 confidence override 파라미터

run_eval.py:
- --fusion CLI 옵션, call_search 쿼리 파라미터에 전달

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 08:58:33 +09:00
Hyungi Ahn e104d1b47c feat: Layer 프리미티브 (Drawer / Modal / ConfirmDialog / Tabs)
UX/UI 개편 Phase A-7. uiState와 결합한 layer/dialog 컴포넌트.

신규 컴포넌트 (lib/components/ui/)
- Drawer.svelte: 단일 slot drawer (id: 'sidebar' | 'meta').
  ui.isDrawerOpen(id)로 표시 여부 결정. 새 drawer 열면 이전 drawer 자동 close.
  side(left/right) + width(sidebar/rail). backdrop 클릭으로 close.
  z-drawer 사용. 8대 원칙 #2.

- Modal.svelte: stack 지원 modal (5대 원칙 #2 — confirm 위에 nested 가능).
  native <dialog> 대신 div 기반 — top-layer가 단일이라 <dialog>로는 stack 불가.
  z-index = z-modal + (stackIndex * 2): backdrop과 panel을 별개의 stacking
  context로 두기 위해 *2. 최상단 modal만 focus trap + 키보드 nav 활성,
  아래는 inert 처리. 수동 Tab/Shift+Tab cycling.
  closable + IconButton(X) 헤더, footer snippet 지원.

- ConfirmDialog.svelte: Modal 위 얇은 wrapper. 삭제/되돌릴 수 없는 작업에
  사용. tone(danger/primary), confirmLabel/cancelLabel, onconfirm 콜백.
  ui.openModal(id)로 호출.

- Tabs.svelte: ARIA tablist + tab + tabpanel.
  좌우 화살표 / Home / End 키 nav, \$props.id() 기반 SSR-safe ID.
  tabs: { id, label, disabled? }[], value \$bindable.
  children snippet은 (activeId) => UI 시그니처 — DocumentViewer 편집/미리보기
  토글 등 단일 컨테이너 레이아웃에 쓰기 좋게 설계.

이로써 Phase A 프리미티브 13종 완비:
  Button, IconButton, Card, Badge, Skeleton, EmptyState,
  TextInput, Textarea, Select,
  Drawer, Modal, ConfirmDialog, Tabs.

모든 컴포넌트는 Svelte 5 runes mode strict, @theme 토큰만 사용,
focus-visible ring 통일, slot은 {@render children?.()}로 작성.

svelte-check: 0 errors / 8 warnings (전부 기존 latent, 새 코드 무관)
build: 2.07s 무경고

남은 Phase A:
- A-8 토큰 swap (Sidebar/TagPill/UploadDropzone/PreviewPanel/DocumentCard/
  DocumentTable/+layout toast — baseline 421건 → 0건)
- A-9 __styleguide 라우트 (전체 시각 검증 + Modal stack 데모)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 08:40:08 +09:00
Hyungi Ahn ad23925ed5 feat: 입력 프리미티브 (TextInput / Textarea / Select) + tsconfig 보정
UX/UI 개편 Phase A-6.

신규 컴포넌트 (lib/components/ui/)
- TextInput.svelte: \$bindable value, label/error/hint, leading/trailing icon,
  \$props.id() 기반 SSR-safe 자동 id, aria-describedby 자동 연결.
- Textarea.svelte: TextInput과 동일 구조 + autoGrow 옵션
  (\$effect로 scrollHeight 동기화, maxRows 지원).
- Select.svelte: 네이티브 <select> 래퍼, ChevronDown 표시.
  options: { value, label, disabled? }[]

빌드 환경 보정
- frontend/tsconfig.json 신규: svelte-kit 자동 생성 .svelte-kit/tsconfig.json을
  extends. 이게 없으면 svelte-check가 \$lib path mapping과 .svelte.ts 모듈
  resolution을 못 잡아 "Cannot find module" 에러 발생. SvelteKit 표준 패턴.
  strict는 false로 시작 (기존 코드 implicit any 다수 — 점진적 정리 예정).
- Button/IconButton/EmptyState/TextInput의 icon prop 타입을 IconComponent(any)로
  완화. lucide-svelte v0.400은 legacy SvelteComponentTyped 기반이라 Svelte 5의
  Component<P, E, B> 시그니처와 호환 안 됨. v0.469+ 업그레이드 후 좁힐 예정.

svelte-check: 0 errors / 8 warnings (전부 기존 latent, 새 코드 무관)
build: 2.07s 무경고

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 08:39:44 +09:00
Hyungi Ahn 7fa7dc1510 feat: 디자인 시스템 기반 — 유틸 헬퍼 + CI rule + 첫 6개 프리미티브
UX/UI 개편 Phase A-3 / A-4 / A-5. 후속 phase가 곧바로 소비할 수 있도록
디자인 시스템의 코어 자산을 한꺼번에 도입한다.

A-3 — 유틸 헬퍼 (lib/utils/)
- pLimit.ts: 동시 실행 N개 제한 (5대 원칙 #4 — 일괄 PATCH/DELETE에서
  GPU 서버/SSE 부하 방지). 외부 의존성 없음.
- mergeDoc.ts: PATCH/SSE 응답을 로컬 cache에 머지할 때 updated_at으로
  stale 갱신 차단 (5대 원칙 #6 — optimistic update conflict resolution).
  dropDoc 헬퍼 포함.

A-4 — CI 토큰 차단 (5대 원칙 #1)
- scripts/check-tokens.sh: bg-[var(--*)] 등 임의값 토큰 우회 grep 차단.
- npm run lint:tokens 등록.
- 현재 baseline 421건 — A-8 토큰 swap에서 0으로 떨어진 후 pre-commit 강제화.

A-5 — 첫 6개 프리미티브 (lib/components/ui/)
- Button.svelte: variant(primary/secondary/ghost/danger) × size(sm/md),
  loading/disabled, icon 슬롯, href 자동 a 변환, focus-visible ring.
- IconButton.svelte: 정사각형, aria-label 필수, Button과 동일 variant 체계.
- Card.svelte: bg-surface + rounded-card + border-default 패턴 1군데화.
  padded/interactive 옵션, interactive면 button 시맨틱.
- Badge.svelte: 의미적 tone(neutral/success/warning/error/accent) 표시.
  TagPill과 별개 (TagPill은 도메인 prefix 코드 전용).
- Skeleton.svelte: ad-hoc animate-pulse div 통합. w/h/rounded prop.
- EmptyState.svelte: icon + title + description + action slot.

모든 프리미티브는 Svelte 5 runes mode strict (\$props/\$derived/\$bindable),
@theme 토큰만 사용 (bg-surface, text-dim, border-default 등 — bg-[var(--*)] 미사용),
focus-visible ring 통일, slot은 {@render children?.()}로 작성.

svelte-check: 0 errors, 8 warnings (모두 기존 latent 이슈, 새 코드 무관).
build: 1.95s 무경고.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 08:26:35 +09:00
Hyungi Ahn 8742367bc2 refactor: stores 분리 — toast / uiState 단일 책임화
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>
2026-04-07 08:26:11 +09:00
Hyungi Ahn f523752971 feat: Tailwind v4 @theme 토큰 도입 — 디자인 시스템 기반 마련
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>
2026-04-07 08:15:33 +09:00