불만② 개요→본문 점프의 deterministic anchor 좌표 산출(경로 A, FE-only).
게이트 측정상 textContent 매칭은 중복 63%·비-ATX 로 5% + silent 오점프 → md_content
에서 각 절 heading 라인 offset 을 찾아 <a id="sec-{chunk_id}"> 주입 좌표를 만든다.
★ false-early-match 방어 3중 (적대 리뷰 반영):
- 라인-시작(전체-라인) 매칭 → 본문 중간 상호참조("see Part UW")는 라인 전체가 제목과
같지 않아 제외(forward-cursor 가 못 막던 핵심 구멍).
- 전체 매칭 + truncation(builder [:200]) 처리 → '제1조'가 '제1조의2' 오매칭 차단.
- 단조 커서 + 코드펜스 회피 → 역행/펜스 매칭 거부 = anchor 없음(점프 비활성, 오점프 금지).
window/section_split 조각·빈 제목은 skip. node test 10/10 PASS(상호참조 선행·중복 단조·
prefix·평문 제N조·펜스·window·miss·heading_path fallback). 순수 함수, vite build PASS.
다음 commit = MarkdownDoc splice + SectionOutline 점프 + DocumentViewer rail/scroll-spy.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
메인 /documents 3-pane 의 중앙 리더(DocumentViewer)가 md_content 를 안 쓰고
PDF=raw iframe·md/txt=plain marked(extracted_text)만 렌더하던 이원화 제거.
"전부 MD화" 한 canonical markdown 이 전체보기 없이 메인에서 바로 보이게 함(불만①).
- viewerType.ts 신설: 분류 단일 source(상세페이지와 공유 예정, drift 차단).
csv/json/xml/html→text(<pre>, 콤마 뭉침 회피), office→preview-pdf, hwp→hwp-markdown.
- DocumentViewer: 자체 getViewerType/renderMd(본문) 제거 → viewerType.ts + MarkdownDoc.
- pdf: canShowMarkdown(isMdSuccess+md_content) 시 MarkdownDoc 기본 + [Markdown|PDF원본]
토글 + MarkdownStatusBadge, 아니면 PDF iframe. lastDocId 가드는 fullDoc.id(prop) 키잉.
- markdown(md/txt): MarkdownDoc(extracted_text=표시·편집 단일 필드), 편집 유지.
- hwp-markdown/article: MarkdownDoc(앵커/KaTeX/이미지). 편집 미리보기만 plain marked 유지.
- article/preview-pdf/image/text/cad/synology/unsupported 분기 보존(회귀 금지) + synology 신설.
API md_status='completed'(S1 validator live) 대응 = isMdSuccess. FE only, BE/스키마 무변.
vite build + lint:tokens(신규 위반 0) PASS. 후속: 개요 rail·안전점프(commit 2), [id] 정합(commit 3).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
S1 backend(이미 main 머지, app/api/documents.py field_validator
_db_success_to_completed)가 직렬화 시 DB 'success'를 API 'completed'로 remap한다.
그런데 프론트 3곳이 raw 'success' 만 검사 → S1 backend 배포 시 침묵 회귀:
- documents/[id]/+page.svelte canShowMarkdown: completed PDF가 markdown-first
대신 raw PDF로 표시
- documents/+page.svelte 인스펙터 칩 게이트: success 문서 칩 사라짐
- MarkdownStatusBadge: 'completed'→default→null (성공 칩 사라짐)
DB↔API enum divergence guard: 두 어휘를 모두 성공으로 취급해야 S1 배포
전(API='success')·후(API='completed') 모두 안전. 단일 source 헬퍼로 수렴.
- lib/utils/mdStatus.ts 신설: isMdSuccess / isMdStatusVisible (raw 비교 산재 금지)
- [id] canShowMarkdown → isMdSuccess()
- documents 인스펙터 게이트 → isMdStatusVisible()
- MarkdownStatusBadge: case 'completed' 를 'success' 동의어로 추가
FE only, 백엔드/스키마/마이그레이션 무변. vite build + lint:tokens(신규 위반 0) PASS.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
plan v6 PR-2 scope. 5초 행동 기록 UX 가 핵심 가설.
Backend:
- GET /api/events/{id}/history — events_history timeline 조회 (lifecycle op 자동 기록)
Frontend (SvelteKit 5 runes mode):
- /events 메인 — 4-tab (오늘/Inbox/예정/활동) + 빠른 행동 기록 widget
· 단일 입력 + Enter → POST /api/events kind=activity_log
· status=done + 시간 default 채워짐 (서버 측) → Activity 탭 즉시 반영
· 새 항목을 list 최상단 prepend (refetch 불필요)
· 연속 입력 위해 입력 ref focus 유지
· lifecycle 버튼 (complete/defer/cancel/reactivate) — activity_log 는 lifecycle 대상 X
- /events/[id] 상세 — PATCH 허용 필드 edit (title/desc/시간/priority/project_tag) + history timeline
· PATCH 금지 필드는 UI 노출 X (status/completed_at/cancelled_at/defer_until 은 별 버튼)
- /events/new — kind 선택 (task/calendar_event/activity_log) 후 필드 분기 form
· task: due_at + start_at (선택, "14:00 전화" 같은 시각 task 허용 — 라운드 10)
· calendar_event: start_at 필수 + end_at + all_day
· activity_log: started_at/ended_at 비우면 서버 default now()
- Sidebar 메모 옆에 events 진입점 (CalendarCheck icon)
API helpers: frontend/src/lib/utils/events.ts (createEvent / logActivity / list*
/ lifecycle ops / kind&status enum label/color).
quickref doc: docs/events_api_quickref.md (이전 commit, PR-2 frontend reference).
PR-2 핵심 가설 검증 = 빠른 입력 → 저장 → Activity 즉시 반영 → 새로고침 유지.
PR-1 deferred HTTP behavior 5건도 본 UI 의 자연 사용으로 닫힘.
Markdown Canonical Phase 1B.5 — marker 가 추출하던 이미지를 NAS 에 영구 저장하고
DB 메타 + 인증 라우트 + 프론트 swap 까지 wiring.
핵심 변경:
- marker-service /convert 응답에 base64 image 리스트 포함 (stateless 유지, NAS write 권한 X)
- marker_worker 가 NAS `/documents/extracted_images/{doc_id}/` 에 persist + UPSERT +
고아 row DELETE + md_content ref 를 `docimg:img_NNN` stable scheme 으로 정규화
- /api/documents/{id}/images/{key}/raw 인증 라우트 (Cache-Control private + ETag = content_hash)
- frontend MarkdownDoc 가 placeholder card 안의 docimg ref 를 실제 <img> 로 swap
원칙:
- 이미지 binary = NAS, metadata = Postgres (학습 섹션 패턴 동일)
- image_key sequence 기반 결정적 → 재변환 idempotent
- MARKDOWN_IMAGE_PERSIST=false env 로 rollback 가능 (placeholder card 폴백 자연 유지)
기존 28건 marker success 문서는 본 PR 에서 건드리지 않음 — deploy + 신규 업로드 1건 +
sample 5건 검증 후 scripts/marker_reprocess_existing_success.py 로 targeted reprocess.
plan: ~/.claude/plans/piped-humming-crystal.md
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Phase 1B marker_worker 결과(현재 success 29건, 전부 PDF)를 사용자 흐름에
연결하고 1D pilot 품질 평가 데이터를 확보하기 위한 viewer 마무리 작업.
빠진 부분 3가지를 닫는다:
1) PDF viewerType 기본 view = Markdown
- md_status='success' AND md_content 비어있지 않음일 때 MarkdownDoc 기본 표시.
- 사용자가 "PDF 원본" 토글 시 iframe.
- pdfViewMode 초기화는 doc.id 변경 시에만 (lastDocId tracker) — reactive cycle
이 사용자 토글을 덮어쓰지 않도록 보호.
- markdown 사라지는 케이스(success → failed 재처리)는 자동으로 pdf 로 보호.
2) Image renderer → placeholder card (docMarkdown.ts)
- md_content 의 69%(20/29)에 image syntax 포함. asset serving(1B.5) 미구현
상태에서 raw <img> 를 emit 하면 깨진 아이콘 → 1D pilot 평가가 markdown
품질이 아닌 viewer 미완성 문제로 오염됨.
- href / alt / basename 모두 escape 후 figure.md-image-placeholder 로 렌더.
- 원본 src 는 data-md-image-src 에 escape 보존 → 1B.5 ImgAuth selector 로
실제 <img> 로 교체할 entry point 마련.
- DOMPurify ADD_ATTR 에 data-md-image-src 추가.
3) MarkdownStatusBadge (신규) — 4-state badge
- pending 숨김(legacy 9792건 시각 노이즈 회피).
- processing/success/skipped/failed 표시.
- success tooltip: md_extraction_quality 의 metrics raw 일부
(markdown_heading_count / markdown_table_row_count / markdown_image_count /
text_length_ratio / warnings) 만 노출. text_length_ratio / null /
metrics nested / flat fallback 모두 방어.
- skipped/failed tooltip: md_extraction_error 또는 정책 문구.
- MarkdownDoc 내부 + PDF iframe fallback 양쪽에서 재사용 → failed 같이
MarkdownDoc 가 안 렌더되는 경로에서도 사용자가 상태를 알 수 있음.
기존 markdown/hwp-markdown/article 분기에도 mdExtractionQuality prop 전달.
Out of scope (1B.5 또는 후속):
- ImgAuth blob URL 실제 wiring (data-md-image-src selector + Bearer raw)
- /data/assets/<doc_id>/ 저장 + 서빙
- Caddy /data/assets/* 라우팅
- localStorage 사용자 view preference 저장
- side-by-side viewer (1D pilot 결과 본 후)
- quality chip 별도 UI (1D 후)
Verify:
- npm run build 통과
- npm run lint:tokens 신규 파일 위반 0
- 관련 plan: ~/.claude/plans/iterative-nibbling-catmull.md
- pre-flight: md_extraction_quality 실제 shape 확인 ({score, metrics:{...}, warnings:[]})
Risks:
- feature/design-system worktree 가 [id]/+page.svelte 의 stale 버전 보유
(main 보다 212 commits behind, MarkdownDoc 부재). 1C 머지 후 worktree
머지 시 conflict 확정 — 그쪽 rebase 필요 (별건).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
문서 상세 페이지에서 canonical markdown(md_content) 을 우선 렌더하고
없으면 extracted_text fallback. md_frontmatter 가 있으면 본문 위에 메타
박스. h1~h6 에 GFM heading id + hover 시 # 링크 표시. 이미지 alt 가
있으면 figure + figcaption. KaTeX 수식 ($...$ / $$...$$) 지원.
Backend:
- DocumentDetailResponse 신규 (DocumentResponse + extracted_text + md_*)
- GET /documents/{doc_id} 응답 모델 전환
- 리스트 응답은 DocumentResponse 그대로 (페이로드 비대화 회피)
Frontend:
- lib/utils/docMarkdown.ts — 별도 Marked 인스턴스 (study mathMarkdown.ts
영향 0). marked-katex-extension + marked-gfm-heading-id + custom image
renderer (figure/figcaption + data-md-img marker).
- lib/components/MarkdownDoc.svelte — md_content/extracted_text 우선순위,
frontmatter 박스, mdStatus=failed 안내 배지, heading anchor DOM 후처리.
- /documents/[id] markdown / hwp-markdown / article viewer 3 곳 wiring.
- app.css — .markdown-doc heading-anchor / md-figure / katex 가로 스크롤.
이미지 ImgAuth 후처리(blob URL 교체) wiring 은 Phase 1B.5 에서. 현재는
data-md-img="1" 마킹만 두고 marker 출력 src 그대로.
Plan: ~/.claude/plans/plan-idempotent-sundae.md (Phase 1C)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
AI 응답이 마크다운 자체를 \`\`\` 으로 감싸서 오는 패턴 (시작만 있고 닫음 누락 포함)
때문에 explanation/AI 해설 영역이 raw 코드블록으로 보이는 회귀.
- frontend/lib/utils/mathMarkdown.ts: stripOuterFence helper.
- terminated wrap 처리 (inner 에 \`\`\` 추가 있으면 보존)
- unterminated 처리 (백틱 그룹 == 1 인 경우만 안전하게 unwrap)
- 본문 중간 정상 코드블록은 보존
- scripts/strip_outer_fences.py: dry-run + --apply 양 모드.
- 5개 필드 (question_text, choice_1~4, explanation, ai_explanation, content) 검사.
- 운영 결과 explanation 34건 unwrap 적용 완료, recount 0 검증.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
기사시험 문제·해설에 √, ρ, R̄, α/β/γ, ㎥, $\\sqrt{...}$ 같은 수식이 자주
들어가는데 기존 plain text 표시는 LaTeX 문법이 그대로 노출되거나 깨짐.
표시·미리보기 영역에서만 KaTeX 렌더링 (입력 textarea 는 plain text 유지).
의존성: marked-katex-extension + katex (frontend/).
공통 유틸 frontend/src/lib/utils/mathMarkdown.ts:
- renderMathMarkdown(text): block 렌더 (문제 본문·해설·AI 해설용)
- renderMathMarkdownInline(text): inline parseInline (보기 1~4 button 안)
- 별도 marked 인스턴스 사용 → 글로벌 marked 영향 없음
- $...$ inline / $$...$$ block 모두 지원
- KaTeX throwOnError=false → 잘못된 수식은 빨간색 fallback (페이지 안 깨짐)
- DOMPurify USE_PROFILES.html + ADD_ATTR style/aria-hidden + FORBID
script/iframe/onclick 등 — XSS 차단 유지
- 실패 시 text-only fallback (HTML escape)
CSS (app.css):
- .math-area .katex-display { overflow-x: auto } — 모바일 가로 overflow
생기면 수식만 가로 스크롤, 페이지 레이아웃 보존
- .katex { white-space: nowrap } — KaTeX 자체 줄바꿈 방지
적용 위치 (표시·미리보기만, textarea 무변경):
- review: 문제 본문, 보기 1~4(inline), 답 제출 후 explanation, AI 해설
- edit: AI 해설 본문 (기존 marked → 통일)
- new 화면 preview / 통합뷰 카드 snippet: 무변경 (1차 보류, 사용자 요청 시 추가)
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 누수 방지
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>
공통 유틸 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>