[#1 모든 획이 안 들어옴]
- pointerleave 핸들러 제거 — stale leave 가 isDrawing=false 만들어 다음
pointermove 가 다 무시되던 핵심 누락 원인 차단.
pointerup / pointercancel 만으로 finalize.
- 1점 stroke (짧은 탭) 도 strokes 에 보존. length>1 검사 제거.
[#2 점선 stroke (긴 직선이 ........)]
- pushPointWithInterp: 점 사이 거리가 8px 초과 시 중간 점 자동 보간.
iPad 60Hz pointermove + 빠른 펜 이동에서 sparse point 일 때도 매끈.
- perfect-freehand 옵션 재튜닝:
thinning 0.4 → 0.25 (얇아지지 않게)
smoothing 0.62 → 0.85 (sparse point 도 부드럽게)
streamline 0.5 → 0.65 (손떨림 보정 강화)
[#4 Safari 팝업 가끔 뜸]
- pointerdown 시점에 document.getSelection().removeAllRanges() 강제 clear.
selectstart preventDefault 만으로 부족한 케이스 (펜이 이미 선택된 영역
위에서 시작) 방어.
기존 문제: 점선 stroke / 연속 입력 누락 / 버튼 focus zoom / Safari 선택 팝업.
원인을 4축으로 분리해서 한꺼번에 fix.
[1] 입력 수집 (PointerEvent 상태머신)
- isDrawing flag + activePointerId 매칭으로 stroke 누락 방지
- pointerdown: 이전 inflight 가 살아있으면 finalize 후 새 stroke 시작
- setPointerCapture (try-catch) — element 외 pointer move 도 받음
- pointerup / pointercancel / pointerleave 통합 endStroke
- pointerType === 'pen' (mouse 도 데스크톱) 만, 손가락 거부
[2] coalesced events
- pointermove 의 e.getCoalescedEvents() 모두 points 에 push
- 빠른 필기에서 sparse point → 점선 현상 방지 핵심
- normalizePressure: 0/비정상 값은 0.5 fallback
[3] 렌더링: perfect-freehand polygon fill
- getStroke(thinning:0.4, smoothing:0.62, streamline:0.5, last:true)
- getSvgPathFromStroke (perfect-freehand README 표준 builder)
→ Path2D → ctx.fill() — anti-aliased polygon
- 1점 케이스: arc fill 폴백
- last: true 항상 (진행 중에도 polygon 닫힘)
[4] autosave 입력 분리
- 3초 idle debounce
- flushSave 는 setTimeout 0 으로 다음 macrotask
- PATCH 응답이 strokes 를 덮어쓰지 않음 (응답 무시, fire-and-forget)
[5] Safari/Chrome hardening
- 캔버스/컨테이너: touch-action: none + user-select: none +
-webkit-touch-callout: none + -webkit-tap-highlight-color: transparent
- canvas 에 oncontextmenu / onselectstart preventDefault
- 모든 toolbar 버튼: clickThenBlur(fn) + tabindex=-1 + BTN_STYLE
→ button focus zoom 차단 (사용자 보고 "버튼 누르면 화면 확대" 핵심)
[6] resize 정책
- ResizeObserver + window resize/orientationchange 만 트리거
- pointermove 마다 resize 절대 안 함
- DPR 반영 + setTransform(dpr,...) 으로 retina 선명
수정 범위 (사용자 명시): HandwriteCanvas.svelte 만. 다른 영역 무수정.
증상 1 (사용자 보고): 펜/지우개/굵기 등 어떤 toolbar 버튼이든 누르면 화면
확대. 창을 옮기면 정상 크기. 다시 누르면 또 확대.
원인: iPad/Chrome 의 button focus 시 자동 zoom (focus 후 layout 변경 또는
브라우저 자체 zoom). 우리 fix 들이 핀치줌만 보고 focus zoom 을 놓침.
Fix 1 — clickThenBlur + tabindex=-1:
- 모든 toolbar/header button 의 onclick 을 clickThenBlur(fn) 로 감쌈.
click 시 즉시 e.currentTarget.blur() 호출 → focus 안 받음 → zoom 안 일어남.
- tabindex={-1} 추가 — 키보드 포커스 자체 차단.
증상 2 (사용자 사진): 빠르게 그린 stroke 가 점선처럼. perfect-freehand 의
polygon outline 이 sparse point 에서 깨짐.
Fix 2 — perfect-freehand 제거, 단순 quadratic bezier:
- ctx.moveTo + 점-점 사이 quadraticCurveTo 보간 + ctx.stroke() 한 번 호출.
- lineCap/lineJoin round, lineWidth = effectiveSize.
- 압력 효과는 미반영 (단일 굵기) — 안정성 우선. 점선 안 됨.
- 1점/2점 케이스 폴백 (arc / lineTo).
P1 데스크톱 trackpad pinch 줌 차단 (Chrome/Firefox macOS):
- wheel + ctrlKey/metaKey preventDefault 추가 (페이지 zoom 방지)
- 데스크톱 Chrome 은 gesture 이벤트 미발화, wheel + ctrlKey 만 발화
- 사용자 사진 8854/8855: 모드 토글 사이 trackpad pinch 로 페이지 zoom 발생
P2 iPad 입력 씹힘 — main thread 블록 해소:
- offscreen buffer canvas 도입. 완료 stroke 들은 buffer 에 한 번만
perfect-freehand getStroke + Path2D fill 로 그림.
- 매 frame 의 redraw 는 ctx.drawImage(buffer) + inflight 만 처리.
- strokes 변경 시만 bufferDirty=true → 다음 redraw 에서 rebuild.
- iPad CPU 에서 33+ stroke 매 frame 재계산이 16ms 초과해 pointer event
누락하던 문제 해소.
Helper:
- setStrokes(next): strokes 재할당 시 buffer rebuild 자동 마킹.
모든 strokes 갱신 (snapshot, eraseAt, finalize, undo, redo, clear,
restoreFromLocalStorage) 에 적용.
여전히 발생하는 입력 누락 / 지우개 누르면 확대 재시도.
P1 줌 차단 강화:
- gesturestart/change/end 를 document level 로 다시 등록 (element-level
ongesturestart 가 일부 iPad Safari 빌드에서 미발화)
- touchstart/touchmove 의 e.touches.length > 1 도 preventDefault — gesture
이벤트 자체가 안 들어오는 경우의 핀치 zoom 백업 방어
P2 입력 누락 — 입력 루프와 redraw/저장 분리:
- pointermove 의 redraw() 를 RAF throttle (scheduleRedraw) — 60Hz 보다 빠른
pointermove 에서 매번 redraw 하던 부담 제거. input 처리 즉시, render 는 frame 당 1회.
- autosave: 5 stroke 즉시 flush 제거 — 빠른 필기 중 JSON.stringify 부하 차단.
3초 idle debounce 만 유지.
- onChange 호출을 setTimeout 0 으로 다음 macrotask 에 ship — 직렬화가
pointer event 와 충돌 안 함.
P1 Safari 줌 차단:
- viewport meta 의 maximum-scale / user-scalable=no 제거 (접근성)
- 페이지 root div 의 ongesturestart/change/end preventDefault — 영역 제한
- 모든 toolbar/header button 에 직접 inline style 적용:
touch-action: manipulation, user-select/-webkit-user-select: none,
-webkit-touch-callout: none, -webkit-tap-highlight-color: transparent
P2 연속 stroke 누락:
- onPointerDown: 이전 inflight 강제 finalize 후 새 stroke 시작
- onPointerMove: pointerId 매칭 완화, isPenLike + inflight 만 체크
(Apple Pencil pointerId 재사용/변경 케이스 방어)
- endStroke: pointerleave race 방어, pointerup/pointercancel 은 무조건 finalize
- 자동 저장 (PATCH) 은 fire-and-forget 그대로 — 입력과 분리
P3 점선 렌더링 품질:
- perfect-freehand 표준 getSvgPathFromStroke + Path2D fill 로 교체
(직접 quadraticCurveTo 보다 안정적)
- thinning 0.5, smoothing 0.7, streamline 0.55 로 튜닝
- normalizePressure: 0/비정상 값은 0.5 fallback (점선 방지)
- coalesced events 모두 points 에 push (빠른 필기 샘플 간격 좁힘)
- 단일 점 (탭) 은 작은 원으로 폴백
증상 (사용자 사진 8856): 펜으로 쓰는데 "복사하기 / Google 으로 검색" 같은
iOS 텍스트 선택 메뉴가 뜸. Safari 가 펜 입력을 텍스트 선택으로 해석.
Fix:
- 캔버스 + 컨테이너 + 페이지 root 에 user-select / -webkit-user-select /
-webkit-touch-callout / -webkit-tap-highlight-color 적용
- canvas 에 oncontextmenu preventDefault — long-press 후 메뉴 차단
증상 (사용자 사진 8854/8855): 펜 → 지우개 토글 사이에 두 손가락이 캔버스에
닿으면서 페이지 전체가 핀치줌되어 글자가 커보이고 stroke 점들이 띄엄띄엄
표시. undo/redo 도 zoom 된 좌표계라 효과 안 보임.
원인: touch-action: none / manipulation 만으로 iOS Safari 의 visualViewport
스케일 기반 핀치줌이 차단되지 않음.
Fix:
- /study/write/[id] 페이지 단위 viewport meta override:
maximum-scale=1, minimum-scale=1, user-scalable=no
(페이지 unmount 시 svelte:head 가 자동 해제)
- document level gesturestart/gesturechange/gestureend 이벤트
preventDefault — iOS 비표준 gesture 이벤트 차단
- onDestroy 에서 cleanup
필기감:
- perfect-freehand 재도입 (effect race 제거됐으니 안전)
- thinning 0.6, smoothing 0.65, streamline 0.5
- simulatePressure false → 실제 e.pressure 반영
- outline polygon 을 quadratic bezier 로 연결 → 부드러운 곡선 (직선 segment ❌)
- ctx.fill() anti-aliased
UI:
- 굵기 토글 (가늘게/보통/굵게) — baseSize × {0.6, 1, 1.6}
- Pencil only (touch 차단)
연속 stroke race fix:
- setPointerCapture/release 제거 → 빠른 pointerup→pointerdown race 차단
- onPointerDown 시 이전 inflight 강제 보존 (드물지만 stale 한 경우)
- pointerleave 핸들러는 inflight 가 살아있을 때만 endStroke
- endStroke: inflight 없으면 즉시 return, activePointerId 만 정리
이전 보고: "ㄱ 쓰고 ㅏ 바로 쓰면 ㅏ 가 입력 안됨" 핵심 원인은 stale
pointerleave 가 두번째 stroke 를 강제 종료시킨 것. 위 race fix 로 해결.
- isPenLike: 'touch' 제거. pen/mouse 만 허용 → 손가락 stroke/지우개 차단
- 페이지/툴바 영역에 touch-action: manipulation → 버튼 빠른 두 번 탭 시
iOS Safari 더블탭 줌 차단. 지우개/펜 토글 시 화면 확대되던 현상 fix.
원인: \$effect(initialStrokes 동기화) 가 strokes 도 의존성으로 추적함.
사용자가 펜으로 그린 후 strokes 변경 → effect 재실행 → 조건
"initialStrokes.strokes !== strokes" 가 true → strokes 를 옛 initialStrokes
값으로 되돌림 → 새 stroke 사라짐.
지우개 누르면 글자가 커지는 현상도 같은 effect 가 trigger 되며 strokes 가
옛 값으로 reset + canvas 비율 재계산이 겹쳐 발생.
Fix:
- \$effect 제거. 초기 strokes 는 \$state initial value 로 한 번만 set.
부모가 prop 새 값을 줘도 무시 (사용자 진행 stroke 우선).
- traceText effect 는 명시적 prev 비교로만 redraw 트리거.
- 디버그용 빨간 사각형 / 빨간 strokeStyle 제거. 정상 색 (--text) 복귀.
stroke 가 안 보이는 원인 격리. iPad 화면에서:
- 좌상단 빨간 50x50 사각형 보임 + 빨간 stroke 보임 → 토큰 색 문제
- 사각형 보임 + stroke 안 보임 → drawStroke / strokeStyle 문제
- 사각형도 안 보임 → redraw 미호출 또는 canvas 자체 가려짐
증상: stroke count 는 올라가는데 화면에 그려지지 않음 + 위치 어긋남.
원인 격리 시도:
- perfect-freehand 의 polygon fill 이 일부 환경에서 제대로 그려지지 않는 것으로
보여 단순 ctx.beginPath/moveTo/lineTo/stroke() 로 갈아치움. lineCap/lineJoin
'round' + lineWidth=baseSize 로 자연스러운 라인. 압력 효과는 일시 제거.
- getLocalXY 에 scale 보정 추가: canvas.style.width(cssWidth) 와 rect.width 가
다른 ResizeObserver 지연 케이스에서 좌표가 어긋나지 않도록 비율 보정.
이번 변경으로도 stroke 가 안 보이면 디버그 오버레이의 좌표/크기를 보고
다른 경로 (캔버스 자체 비활성, layer 가림 등) 추적.
증상: iPad 에서 펜 입력이 안 들어가거나 다른 위치에 그려지는 보고. 원인은
좌우 분할 layout 에서 우측 캔버스 영역이 좁거나 layout 이 stale.
UI:
- /study/write/[id] layout 을 캔버스 풀스크린 + 좌측 floating panel 로 변경
- 헤더에 패널 토글 버튼. 패널 default closed → 캔버스가 화면 거의 전체
- 캔버스 컨테이너에 border-default/30 추가 (영역 가시화)
좌표/입력:
- isPenLike: 'touch' 도 허용 (iPad 일부 빌드에서 Pencil 이 'pen' 으로 안 들어오는 케이스 방어)
- 디버그 오버레이: 캔버스 크기 + 마지막 pointer 좌표/pressure/type 표시
- ResizeObserver 외에 window resize / orientationchange 리스너 추가
- 마운트 직후 RAF×2 후 한 번 더 resizeCanvas (flex 레이아웃 0x0 첫 paint 방어)
stroke 가 안 그려지는 이슈 수정 + 사용자 요청한 부분 지우개 추가.
렌더링 fix:
- last:true 항상 (진행 중 stroke 도 양쪽 outline + cap 완성, polygon 닫힘 보장).
이전엔 inflight 일 때 last:false 라서 outline 한쪽만 그려져 fill 영역 거의 0.
- thinning 0.5 → 0.3 (시작/끝 부분이 너무 얇아지지 않게)
- baseSize default 4 → 6
- pointermove: main 점을 항상 push (coalesced 는 보간 보조)
부분 지우개:
- tool: 'pen' | 'eraser' state. 툴바에 펜/지우개 토글
- eraser 모드: pointer 가 지나가는 stroke 를 점-원 hit-test 로 즉시 삭제
- eraserRadius = baseSize * 4 (최소 16 px)
- 삭제된 stroke 는 undoStack 으로 — undo 로 복구 가능
- cursor: eraser 면 'cell', 펜이면 'crosshair'
- 전체 지우기는 별도 Trash2 버튼으로 분리
문제: dark mode 에서 stroke #111 이 --bg #0f1117 와 거의 같아 안 보임 +
Apple Pencil pressure 0 케이스 방어 부재.
수정:
- strokeColor 를 마운트 시 --text 토큰 실측 (e4e4e7 등) 으로 갱신
- simulatePressure true 로 변경 — 압력 0 으로 들어와도 속도 기반으로 굵기 보장
- thinning 0.55 → 0.5
3일 telemetry (599 triage / 555 deep) 기반 임계치 재평가:
1. 에스컬레이션 비율 — 임계치 의미 reframe
- 기존: >20% 적색 (튜닝 필요) → 항상 적색 (운영 패턴 97%)
- 신규: <80% 적색 (정책 매칭 실패 증가)
- 메시지: "safety 정책상 95~100% 가 정상" 보조 표시
- safety_reference 99.7%, generic 100% (fallback risk_flag), msds 46.2%
→ 운영 정상 패턴 확인
2. Deep summary 안정성 — 신규 카드 추가
- mode='summary_deep' 의 error_code IS NOT NULL 비율
- 현재 5.2% (call_failed 21 + parse:ValidationError 8)
- >5% 적색 임계
- MLX 호출 timeout / JSON 파싱 실패 모니터
3. triage JSON 건강도, Backlog Suppression — 임계치 유지
- 현재 0%, 1% — 매우 안정. 보수적 임계 유효.
Backend: TierHealthStack 에 deep_total / deep_err_total 추가
Frontend: 카드 그리드 3열 → 4열 (lg), Day 4 신규 카드.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
체크박스 체크 후 10초 경과 항목을 대시보드 핀 메모 / /memos 에서
자동 숨김, 메모 푸터 "완료 N개 보기" 버튼으로 토글.
- migration 161: documents.memo_task_state JSONB — {"<idx>":{"checked_at":"ISO"}}
- PATCH /memos/{id}/tasks/{task_index} 전용 엔드포인트:
· SELECT FOR UPDATE 로 동시 토글 race 차단
· task_index drift 시 stale state 자동 정리 (400 대신 200)
· AI 재처리/큐 enqueue 의도적 스킵 + memo_task_toggle_skip_ai 로그
- renderMemoHtml(taskStates, now) → 경과 항목에 memo-task-hidden 클래스
- Svelte 5 $effect cleanup 으로 setInterval 누수 방지
`/documents?category=law` 같은 URL 이 프론트에서 무시되던 버그 — `+page.svelte` 의 filter state 에 `category` 가 빠져 있어 API 호출 시 `?category=` 가 서버로 전달 안 됐음. 결과적으로 default 목록 (news/law 만 제외한 전체) 이 반환됐다.
Sidebar '법령 알림' 버튼 (e88640d) + API `category` 필터 (§§2A) 는 이미 반영됨 — 프론트 middleware 만 추가.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- migrations/152: ALTER TYPE doc_category ADD VALUE 'law' (DDL only; PG16 단일-트랜잭션 제약상 backfill 은 별도)
- models/document.py: Enum 에 'law' 추가 (7 활성 + 3 유보)
- workers/law_monitor.py: Document(..., category='law') — 신규 유입부터 세팅
- workers/classify_worker.py: source_channel='law_monitor' early-return + 최소 필드 (ai_domain='법령', ai_tags=['법령'], importance='medium'). AI classify skip — 법령 구조 고정/외부 source of truth/자동 재수집
- scripts/backfill_category.py: law 분기 + WHERE re-target ((source_channel='law_monitor' AND category='document')) + VERIFY cat_law/law_source_count + fail 조건
- api/documents.py: default 목록 제외에 law_monitor 추가 (news 와 동일 패턴)
- api/dashboard.py: documents count FILTER 에 law_monitor 제외 (category_counts.law 는 기존 GROUP BY category 로 자동 노출)
- frontend/Sidebar.svelte: '법령 알림' 버튼 ?source=law_monitor → ?category=law (explicit category 경로가 default exclusion 을 skip)
plan: ~/.claude/plans/stateless-churning-raccoon.md
axis 원칙: category=UI 축, policy/telemetry=source_channel+ai_domain 축 (feedback_category_vs_ai_domain_axis.md)
배포 순서: push → GPU pull → compose up --build fastapi frontend → backfill --dry-run → --apply.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- +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>
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>
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>
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>
프론트의 `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>
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>
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>
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>
검색바에서 Enter → submitSearch()만 실행되어야 하는데
useListKeyboardNav의 window 리스너가 Enter를 잡아 selectDoc() 호출.
stopPropagation으로 이벤트 전파 차단.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
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>
FileInfoView에 회사/주제/연도/문서유형 select 4개 추가.
facet 옵션은 /api/library/facets에서 로드, 세션 캐시.
업로드 엔드포인트에 facet Form 파라미터 4개 추가.
업로드 시 현재 선택 facet 자동 전달 + 미리보기 텍스트.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
library_categories 테이블 추가로 빈 카테고리 생성 가능.
CRUD API (생성/leaf rename/leaf delete) + 트리 머지 엔드포인트.
사이드바 트리에 컨텍스트 메뉴 (추가/이름변경/삭제).
LibraryPathEditor를 카테고리 기반 flat selector로 전환.
미분류는 시스템 분류로 보호 (삭제/이름변경 불가).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
폴더 미선택 상태에서 업로드하면 doc_purpose='business'만 설정되고
@library/ 태그가 빠져서 자료실에 문서가 표시되지 않던 버그 수정.
백엔드: business 업로드에 library_path 없으면 @library/미분류 자동 태깅.
프론트: activePath 없을 때 기본값 '미분류' 전송.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
공통 유틸 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>