From 4042d9ec61f3faa0357ad3de1ca9f848a90808e5 Mon Sep 17 00:00:00 2001 From: hyungi Date: Mon, 8 Jun 2026 14:48:38 +0900 Subject: [PATCH] =?UTF-8?q?fix(ui):=20md=5Fstatus=20'success'/'completed'?= =?UTF-8?q?=20=EC=96=B4=ED=9C=98=20=EC=96=91=EB=A6=BD=20(S1=20API=20remap?= =?UTF-8?q?=20=EB=8C=80=EB=B9=84)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) --- .../lib/components/MarkdownStatusBadge.svelte | 1 + frontend/src/lib/utils/mdStatus.ts | 25 +++++++++++++++++++ frontend/src/routes/documents/+page.svelte | 3 ++- .../src/routes/documents/[id]/+page.svelte | 3 ++- 4 files changed, 30 insertions(+), 2 deletions(-) create mode 100644 frontend/src/lib/utils/mdStatus.ts diff --git a/frontend/src/lib/components/MarkdownStatusBadge.svelte b/frontend/src/lib/components/MarkdownStatusBadge.svelte index c73c29c..e4f8a88 100644 --- a/frontend/src/lib/components/MarkdownStatusBadge.svelte +++ b/frontend/src/lib/components/MarkdownStatusBadge.svelte @@ -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', diff --git a/frontend/src/lib/utils/mdStatus.ts b/frontend/src/lib/utils/mdStatus.ts new file mode 100644 index 0000000..6311e82 --- /dev/null +++ b/frontend/src/lib/utils/mdStatus.ts @@ -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' + ); +} diff --git a/frontend/src/routes/documents/+page.svelte b/frontend/src/routes/documents/+page.svelte index 0e2604f..1f2d96d 100644 --- a/frontend/src/routes/documents/+page.svelte +++ b/frontend/src/routes/documents/+page.svelte @@ -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}
하위{doc.ai_sub_group}
{/if}
수정{shortDate(doc.updated_at || doc.created_at)}
{#if size}
원본{size}
{/if} - {#if ['processing', 'success', 'partial', 'skipped', 'failed'].includes(doc.md_status)}
md 상태
{/if} + {#if isMdStatusVisible(doc.md_status)}
md 상태
{/if} {#if doc.read_count}
읽음{doc.read_count}회
{/if} diff --git a/frontend/src/routes/documents/[id]/+page.svelte b/frontend/src/routes/documents/[id]/+page.svelte index 3ab8d40..40ad723 100644 --- a/frontend/src/routes/documents/[id]/+page.svelte +++ b/frontend/src/routes/documents/[id]/+page.svelte @@ -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(() => {