fix(ui): md_status 'success'/'completed' 어휘 양립 (S1 API remap 대비)

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>
This commit is contained in:
hyungi
2026-06-08 14:48:38 +09:00
parent c2d2a0aa4d
commit 4042d9ec61
4 changed files with 30 additions and 2 deletions
@@ -77,6 +77,7 @@
case 'processing':
return { tone: 'accent', label: 'Markdown 변환 중', tooltip: null };
case 'success':
case 'completed': // API field_validator 가 DB 'success'→'completed' remap (S1 backend) — 동의어
return {
tone: 'success',
label: 'Markdown',
+25
View File
@@ -0,0 +1,25 @@
// md_status 어휘 단일 source.
//
// DB CHECK enum 은 'success' 이지만, API 직렬화 시 field_validator
// `_db_success_to_completed`(app/api/documents.py) 가 'success' → 'completed' 로 remap 한다
// (S1 backend). 나머지 상태(pending/processing/partial/skipped/failed)는 양쪽 동일.
//
// 따라서 프론트는 두 어휘를 모두 "성공" 으로 취급해야 S1 backend 배포 전(API='success')·
// 후(API='completed') 모두 안전하다. (DB↔API enum divergence guard — md_status 비교는
// 반드시 이 헬퍼 경유, raw `=== 'success'` / `=== 'completed'` 산재 금지.)
/** DB 'success' 또는 API 'completed' = 변환 성공(markdown 준비됨). */
export function isMdSuccess(status: string | null | undefined): boolean {
return status === 'success' || status === 'completed';
}
/** md상태 칩 렌더 대상 상태. pending/null 은 숨김(legacy 대량 노이즈 회피). */
export function isMdStatusVisible(status: string | null | undefined): boolean {
return (
status === 'processing' ||
isMdSuccess(status) ||
status === 'partial' ||
status === 'skipped' ||
status === 'failed'
);
}
+2 -1
View File
@@ -11,6 +11,7 @@
import { Info, X, Plus, Trash2, Tag, FolderTree, Sparkles, ChevronLeft, ArrowUpDown } from 'lucide-svelte';
import DocumentViewer from '$lib/components/DocumentViewer.svelte';
import MarkdownStatusBadge from '$lib/components/MarkdownStatusBadge.svelte';
import { isMdStatusVisible } from '$lib/utils/mdStatus';
import UploadDropzone from '$lib/components/UploadDropzone.svelte';
import Drawer from '$lib/components/ui/Drawer.svelte';
import Modal from '$lib/components/ui/Modal.svelte';
@@ -679,7 +680,7 @@
{#if doc.ai_sub_group}<div class="flex justify-between gap-2 text-xs py-1"><span class="text-dim">하위</span><span class="text-text font-medium text-right truncate">{doc.ai_sub_group}</span></div>{/if}
<div class="flex justify-between gap-2 text-xs py-1"><span class="text-dim">수정</span><span class="text-text font-medium text-right">{shortDate(doc.updated_at || doc.created_at)}</span></div>
{#if size}<div class="flex justify-between gap-2 text-xs py-1"><span class="text-dim">원본</span><span class="text-text font-medium text-right">{size}</span></div>{/if}
{#if ['processing', 'success', 'partial', 'skipped', 'failed'].includes(doc.md_status)}<div class="flex items-center justify-between gap-2 text-xs py-1"><span class="text-dim">md 상태</span><MarkdownStatusBadge mdStatus={doc.md_status} mdExtractionError={doc.md_extraction_error} mdExtractionQuality={doc.md_extraction_quality} /></div>{/if}
{#if isMdStatusVisible(doc.md_status)}<div class="flex items-center justify-between gap-2 text-xs py-1"><span class="text-dim">md 상태</span><MarkdownStatusBadge mdStatus={doc.md_status} mdExtractionError={doc.md_extraction_error} mdExtractionQuality={doc.md_extraction_quality} /></div>{/if}
{#if doc.read_count}<div class="flex justify-between gap-2 text-xs py-1"><span class="text-dim">읽음</span><span class="text-text font-medium text-right">{doc.read_count}</span></div>{/if}
</div>
</div>
@@ -6,6 +6,7 @@
import { page } from '$app/stores';
import { goto } from '$app/navigation';
import { api, getAccessToken } from '$lib/api';
import { isMdSuccess } from '$lib/utils/mdStatus';
import { addToast } from '$lib/stores/toast';
import { marked } from 'marked';
import DOMPurify from 'dompurify';
@@ -147,7 +148,7 @@
let pdfViewMode = $state('markdown'); // 'markdown' | 'pdf'
let lastDocId = $state(null);
let canShowMarkdown = $derived(
!!(doc?.md_status === 'success' && doc?.md_content?.trim())
!!(isMdSuccess(doc?.md_status) && doc?.md_content?.trim())
);
$effect(() => {