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
This commit is contained in:
Hyungi Ahn
2026-04-07 12:14:48 +09:00
parent 8ec89517ee
commit c294df5987
3 changed files with 82 additions and 81 deletions

View File

@@ -116,29 +116,29 @@
<svelte:window on:keydown={handleKeydown} /> <svelte:window on:keydown={handleKeydown} />
<div class="h-full flex flex-col bg-[var(--surface)] border-t border-[var(--border)]"> <div class="h-full flex flex-col bg-surface border-t border-default">
<!-- 뷰어 툴바 --> <!-- 뷰어 툴바 -->
{#if fullDoc && !loading} {#if fullDoc && !loading}
<div class="flex items-center justify-between px-3 py-1.5 border-b border-[var(--border)] bg-[var(--sidebar-bg)] shrink-0"> <div class="flex items-center justify-between px-3 py-1.5 border-b border-default bg-sidebar shrink-0">
<span class="text-xs text-[var(--text-dim)] truncate">{fullDoc.title || '제목 없음'}</span> <span class="text-xs text-dim truncate">{fullDoc.title || '제목 없음'}</span>
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
{#if viewerType === 'markdown'} {#if viewerType === 'markdown'}
{#if editMode} {#if editMode}
<button <button
onclick={saveContent} onclick={saveContent}
disabled={saving} disabled={saving}
class="flex items-center gap-1 px-2 py-1 text-xs bg-[var(--accent)] text-white rounded hover:bg-[var(--accent-hover)] disabled:opacity-50" class="flex items-center gap-1 px-2 py-1 text-xs bg-accent text-white rounded hover:bg-accent-hover disabled:opacity-50"
> >
<Save size={12} /> {saving ? '저장 중...' : '저장'} <Save size={12} /> {saving ? '저장 중...' : '저장'}
</button> </button>
<button <button
onclick={() => editMode = false} onclick={() => editMode = false}
class="px-2 py-1 text-xs text-[var(--text-dim)] hover:text-[var(--text)]" class="px-2 py-1 text-xs text-dim hover:text-text"
>취소</button> >취소</button>
{:else} {:else}
<button <button
onclick={startEdit} onclick={startEdit}
class="px-2 py-1 text-xs text-[var(--text-dim)] hover:text-[var(--accent)] border border-[var(--border)] rounded" class="px-2 py-1 text-xs text-dim hover:text-accent border border-default rounded"
>편집</button> >편집</button>
{/if} {/if}
{/if} {/if}
@@ -147,14 +147,14 @@
href={editInfo.url} href={editInfo.url}
target="_blank" target="_blank"
rel="noopener" rel="noopener"
class="flex items-center gap-1 px-2 py-1 text-xs text-[var(--text-dim)] hover:text-[var(--accent)] border border-[var(--border)] rounded" class="flex items-center gap-1 px-2 py-1 text-xs text-dim hover:text-accent border border-default rounded"
> >
<ExternalLink size={12} /> {editInfo.label} <ExternalLink size={12} /> {editInfo.label}
</a> </a>
{/if} {/if}
<a <a
href="/documents/{fullDoc.id}" href="/documents/{fullDoc.id}"
class="px-2 py-1 text-xs text-[var(--text-dim)] hover:text-[var(--accent)] border border-[var(--border)] rounded" class="px-2 py-1 text-xs text-dim hover:text-accent border border-default rounded"
>전체 보기</a> >전체 보기</a>
</div> </div>
</div> </div>
@@ -164,27 +164,27 @@
<div class="flex-1 overflow-auto min-h-0"> <div class="flex-1 overflow-auto min-h-0">
{#if loading} {#if loading}
<div class="flex items-center justify-center h-full"> <div class="flex items-center justify-center h-full">
<p class="text-sm text-[var(--text-dim)]">로딩 중...</p> <p class="text-sm text-dim">로딩 중...</p>
</div> </div>
{:else if fullDoc} {:else if fullDoc}
{#if viewerType === 'markdown'} {#if viewerType === 'markdown'}
{#if editMode} {#if editMode}
<!-- Markdown 편집 (탭 전환) --> <!-- Markdown 편집 (탭 전환) -->
<div class="flex flex-col h-full"> <div class="flex flex-col h-full">
<div class="flex gap-1 px-3 py-1 border-b border-[var(--border)] shrink-0"> <div class="flex gap-1 px-3 py-1 border-b border-default shrink-0">
<button <button
onclick={() => editTab = 'edit'} onclick={() => editTab = 'edit'}
class="px-3 py-1 text-xs rounded-t {editTab === 'edit' ? 'bg-[var(--surface)] text-[var(--text)]' : 'text-[var(--text-dim)]'}" class="px-3 py-1 text-xs rounded-t {editTab === 'edit' ? 'bg-surface text-text' : 'text-dim'}"
>편집</button> >편집</button>
<button <button
onclick={() => editTab = 'preview'} onclick={() => editTab = 'preview'}
class="px-3 py-1 text-xs rounded-t {editTab === 'preview' ? 'bg-[var(--surface)] text-[var(--text)]' : 'text-[var(--text-dim)]'}" class="px-3 py-1 text-xs rounded-t {editTab === 'preview' ? 'bg-surface text-text' : 'text-dim'}"
>미리보기</button> >미리보기</button>
</div> </div>
{#if editTab === 'edit'} {#if editTab === 'edit'}
<textarea <textarea
bind:value={editContent} bind:value={editContent}
class="flex-1 w-full p-4 bg-[var(--bg)] text-[var(--text)] text-sm font-mono resize-none outline-none" class="flex-1 w-full p-4 bg-bg text-text text-sm font-mono resize-none outline-none"
spellcheck="false" spellcheck="false"
></textarea> ></textarea>
{:else} {:else}
@@ -221,22 +221,22 @@
</div> </div>
{:else if viewerType === 'text'} {:else if viewerType === 'text'}
<div class="p-4"> <div class="p-4">
<pre class="text-sm text-[var(--text)] whitespace-pre-wrap font-mono">{fullDoc.extracted_text || '텍스트 없음'}</pre> <pre class="text-sm text-text whitespace-pre-wrap font-mono">{fullDoc.extracted_text || '텍스트 없음'}</pre>
</div> </div>
{:else if viewerType === 'cad'} {:else if viewerType === 'cad'}
<div class="flex flex-col items-center justify-center h-full gap-3"> <div class="flex flex-col items-center justify-center h-full gap-3">
<p class="text-sm text-[var(--text-dim)]">CAD 미리보기 (향후 지원 예정)</p> <p class="text-sm text-dim">CAD 미리보기 (향후 지원 예정)</p>
<a <a
href="https://web.autocad.com" href="https://web.autocad.com"
target="_blank" target="_blank"
class="px-3 py-1.5 text-sm bg-[var(--accent)] text-white rounded hover:bg-[var(--accent-hover)]" class="px-3 py-1.5 text-sm bg-accent text-white rounded hover:bg-accent-hover"
>AutoCAD Web에서 열기</a> >AutoCAD Web에서 열기</a>
</div> </div>
{:else if viewerType === 'article'} {:else if viewerType === 'article'}
<!-- 뉴스 전용 뷰어 --> <!-- 뉴스 전용 뷰어 -->
<div class="p-5 max-w-3xl mx-auto"> <div class="p-5 max-w-3xl mx-auto">
<h1 class="text-lg font-bold mb-2">{fullDoc.title}</h1> <h1 class="text-lg font-bold mb-2">{fullDoc.title}</h1>
<div class="flex items-center gap-2 mb-4 text-xs text-[var(--text-dim)]"> <div class="flex items-center gap-2 mb-4 text-xs text-dim">
{#if fullDoc.ai_tags?.length} {#if fullDoc.ai_tags?.length}
{#each fullDoc.ai_tags.filter(t => t.startsWith('News/')) as tag} {#each fullDoc.ai_tags.filter(t => t.startsWith('News/')) as tag}
<span class="px-1.5 py-0.5 rounded bg-blue-900/30 text-blue-400">{tag.replace('News/', '')}</span> <span class="px-1.5 py-0.5 rounded bg-blue-900/30 text-blue-400">{tag.replace('News/', '')}</span>
@@ -247,13 +247,13 @@
<div class="markdown-body mb-6"> <div class="markdown-body mb-6">
{@html renderMd(fullDoc.extracted_text || '')} {@html renderMd(fullDoc.extracted_text || '')}
</div> </div>
<div class="flex items-center gap-3 pt-4 border-t border-[var(--border)]"> <div class="flex items-center gap-3 pt-4 border-t border-default">
{#if fullDoc.edit_url} {#if fullDoc.edit_url}
<a <a
href={fullDoc.edit_url} href={fullDoc.edit_url}
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
class="flex items-center gap-1 px-3 py-1.5 text-sm bg-[var(--accent)] text-white rounded-lg hover:bg-[var(--accent-hover)]" class="flex items-center gap-1 px-3 py-1.5 text-sm bg-accent text-white rounded-lg hover:bg-accent-hover"
> >
<ExternalLink size={14} /> 원문 보기 <ExternalLink size={14} /> 원문 보기
</a> </a>
@@ -262,7 +262,7 @@
</div> </div>
{:else} {:else}
<div class="flex items-center justify-center h-full"> <div class="flex items-center justify-center h-full">
<p class="text-sm text-[var(--text-dim)]">미리보기를 지원하지 않는 형식입니다 ({fullDoc.file_format})</p> <p class="text-sm text-dim">미리보기를 지원하지 않는 형식입니다 ({fullDoc.file_format})</p>
</div> </div>
{/if} {/if}
{/if} {/if}

View File

@@ -125,18 +125,18 @@
} }
</script> </script>
<aside class="h-full flex flex-col bg-[var(--sidebar-bg)] border-l border-[var(--border)] overflow-y-auto"> <aside class="h-full flex flex-col bg-sidebar border-l border-default overflow-y-auto">
<!-- 헤더 --> <!-- 헤더 -->
<div class="flex items-center justify-between px-4 py-3 border-b border-[var(--border)] shrink-0"> <div class="flex items-center justify-between px-4 py-3 border-b border-default shrink-0">
<div class="flex items-center gap-2 min-w-0"> <div class="flex items-center gap-2 min-w-0">
<FormatIcon format={doc.file_format} size={16} /> <FormatIcon format={doc.file_format} size={16} />
<span class="text-sm font-medium truncate">{doc.title || '제목 없음'}</span> <span class="text-sm font-medium truncate">{doc.title || '제목 없음'}</span>
</div> </div>
<div class="flex items-center gap-1"> <div class="flex items-center gap-1">
<a href="/documents/{doc.id}" class="p-1 rounded hover:bg-[var(--surface)] text-[var(--text-dim)]" title="전체 보기"> <a href="/documents/{doc.id}" class="p-1 rounded hover:bg-surface text-dim" title="전체 보기">
<ExternalLink size={14} /> <ExternalLink size={14} />
</a> </a>
<button onclick={onclose} class="p-1 rounded hover:bg-[var(--surface)] text-[var(--text-dim)]" aria-label="닫기"> <button onclick={onclose} class="p-1 rounded hover:bg-surface text-dim" aria-label="닫기">
<X size={16} /> <X size={16} />
</button> </button>
</div> </div>
@@ -145,31 +145,31 @@
<div class="flex-1 p-4 space-y-4"> <div class="flex-1 p-4 space-y-4">
<!-- 메모 --> <!-- 메모 -->
<div> <div>
<h4 class="text-xs font-semibold text-[var(--text-dim)] uppercase mb-1.5">메모</h4> <h4 class="text-xs font-semibold text-dim uppercase mb-1.5">메모</h4>
{#if noteEditing} {#if noteEditing}
<textarea <textarea
bind:value={noteText} bind:value={noteText}
class="w-full h-24 px-3 py-2 bg-[var(--bg)] border border-[var(--border)] rounded-lg text-sm text-[var(--text)] resize-none outline-none focus:border-[var(--accent)]" class="w-full h-24 px-3 py-2 bg-bg border border-default rounded-lg text-sm text-text resize-none outline-none focus:border-accent"
placeholder="메모 입력..." placeholder="메모 입력..."
></textarea> ></textarea>
<div class="flex gap-2 mt-1.5"> <div class="flex gap-2 mt-1.5">
<button <button
onclick={saveNote} onclick={saveNote}
disabled={noteSaving} disabled={noteSaving}
class="flex items-center gap-1 px-2 py-1 text-xs bg-[var(--accent)] text-white rounded hover:bg-[var(--accent-hover)] disabled:opacity-50" class="flex items-center gap-1 px-2 py-1 text-xs bg-accent text-white rounded hover:bg-accent-hover disabled:opacity-50"
> >
<Save size={12} /> 저장 <Save size={12} /> 저장
</button> </button>
<button <button
onclick={() => { noteEditing = false; noteText = doc.user_note || ''; }} onclick={() => { noteEditing = false; noteText = doc.user_note || ''; }}
class="px-2 py-1 text-xs text-[var(--text-dim)] hover:text-[var(--text)]" class="px-2 py-1 text-xs text-dim hover:text-text"
>취소</button> >취소</button>
</div> </div>
{:else} {:else}
<button <button
onclick={() => noteEditing = true} onclick={() => noteEditing = true}
class="w-full text-left px-3 py-2 bg-[var(--bg)] border border-[var(--border)] rounded-lg text-sm min-h-[40px] class="w-full text-left px-3 py-2 bg-bg border border-default rounded-lg text-sm min-h-[40px]
{noteText ? 'text-[var(--text)]' : 'text-[var(--text-dim)]'}" {noteText ? 'text-text' : 'text-dim'}"
> >
{noteText || '메모 추가...'} {noteText || '메모 추가...'}
</button> </button>
@@ -178,40 +178,40 @@
<!-- 편집 URL --> <!-- 편집 URL -->
<div> <div>
<h4 class="text-xs font-semibold text-[var(--text-dim)] uppercase mb-1.5">편집 링크</h4> <h4 class="text-xs font-semibold text-dim uppercase mb-1.5">편집 링크</h4>
{#if editUrlEditing} {#if editUrlEditing}
<div class="flex gap-1"> <div class="flex gap-1">
<input <input
bind:value={editUrlText} bind:value={editUrlText}
placeholder="Synology Drive URL 붙여넣기..." placeholder="Synology Drive URL 붙여넣기..."
class="flex-1 px-2 py-1 bg-[var(--bg)] border border-[var(--border)] rounded text-xs text-[var(--text)] outline-none focus:border-[var(--accent)]" class="flex-1 px-2 py-1 bg-bg border border-default rounded text-xs text-text outline-none focus:border-accent"
/> />
<button onclick={saveEditUrl} class="px-2 py-1 text-xs bg-[var(--accent)] text-white rounded">저장</button> <button onclick={saveEditUrl} class="px-2 py-1 text-xs bg-accent text-white rounded">저장</button>
<button onclick={() => { editUrlEditing = false; editUrlText = doc.edit_url || ''; }} class="px-2 py-1 text-xs text-[var(--text-dim)]">취소</button> <button onclick={() => { editUrlEditing = false; editUrlText = doc.edit_url || ''; }} class="px-2 py-1 text-xs text-dim">취소</button>
</div> </div>
{:else if doc.edit_url} {:else if doc.edit_url}
<div class="flex items-center gap-1"> <div class="flex items-center gap-1">
<a href={doc.edit_url} target="_blank" class="text-xs text-[var(--accent)] truncate hover:underline">{doc.edit_url}</a> <a href={doc.edit_url} target="_blank" class="text-xs text-accent truncate hover:underline">{doc.edit_url}</a>
<button onclick={() => editUrlEditing = true} class="text-[10px] text-[var(--text-dim)] hover:text-[var(--text)]">수정</button> <button onclick={() => editUrlEditing = true} class="text-[10px] text-dim hover:text-text">수정</button>
</div> </div>
{:else} {:else}
<button <button
onclick={() => editUrlEditing = true} onclick={() => editUrlEditing = true}
class="text-xs text-[var(--text-dim)] hover:text-[var(--accent)]" class="text-xs text-dim hover:text-accent"
>+ URL 추가</button> >+ URL 추가</button>
{/if} {/if}
</div> </div>
<!-- 태그 --> <!-- 태그 -->
<div> <div>
<h4 class="text-xs font-semibold text-[var(--text-dim)] uppercase mb-1.5">태그</h4> <h4 class="text-xs font-semibold text-dim uppercase mb-1.5">태그</h4>
<div class="flex flex-wrap gap-1 mb-2"> <div class="flex flex-wrap gap-1 mb-2">
{#each doc.ai_tags || [] as tag} {#each doc.ai_tags || [] as tag}
<span class="inline-flex items-center gap-0.5"> <span class="inline-flex items-center gap-0.5">
<TagPill {tag} clickable={false} /> <TagPill {tag} clickable={false} />
<button <button
onclick={() => removeTag(tag)} onclick={() => removeTag(tag)}
class="text-[var(--text-dim)] hover:text-[var(--error)] text-[10px]" class="text-dim hover:text-error text-[10px]"
title="삭제" title="삭제"
>×</button> >×</button>
</span> </span>
@@ -222,14 +222,14 @@
<input <input
bind:value={newTag} bind:value={newTag}
placeholder="태그 입력..." placeholder="태그 입력..."
class="flex-1 px-2 py-1 bg-[var(--bg)] border border-[var(--border)] rounded text-xs text-[var(--text)] outline-none focus:border-[var(--accent)]" class="flex-1 px-2 py-1 bg-bg border border-default rounded text-xs text-text outline-none focus:border-accent"
/> />
<button type="submit" class="px-2 py-1 text-xs bg-[var(--accent)] text-white rounded">추가</button> <button type="submit" class="px-2 py-1 text-xs bg-accent text-white rounded">추가</button>
</form> </form>
{:else} {:else}
<button <button
onclick={() => tagEditing = true} onclick={() => tagEditing = true}
class="flex items-center gap-1 text-xs text-[var(--text-dim)] hover:text-[var(--accent)]" class="flex items-center gap-1 text-xs text-dim hover:text-accent"
> >
<Plus size={12} /> 태그 추가 <Plus size={12} /> 태그 추가
</button> </button>
@@ -239,12 +239,12 @@
<!-- AI 분류 --> <!-- AI 분류 -->
{#if doc.ai_domain} {#if doc.ai_domain}
<div> <div>
<h4 class="text-xs font-semibold text-[var(--text-dim)] uppercase mb-1.5">분류</h4> <h4 class="text-xs font-semibold text-dim uppercase mb-1.5">분류</h4>
<!-- domain breadcrumb --> <!-- domain breadcrumb -->
<div class="flex flex-wrap gap-1 mb-2"> <div class="flex flex-wrap gap-1 mb-2">
{#each doc.ai_domain.split('/') as part, i} {#each doc.ai_domain.split('/') as part, i}
{#if i > 0}<span class="text-[10px] text-[var(--text-dim)]"></span>{/if} {#if i > 0}<span class="text-[10px] text-dim"></span>{/if}
<span class="text-xs text-[var(--accent)]">{part}</span> <span class="text-xs text-accent">{part}</span>
{/each} {/each}
</div> </div>
<!-- document_type + confidence --> <!-- document_type + confidence -->
@@ -268,30 +268,30 @@
<!-- 파일 정보 --> <!-- 파일 정보 -->
<div> <div>
<h4 class="text-xs font-semibold text-[var(--text-dim)] uppercase mb-1.5">정보</h4> <h4 class="text-xs font-semibold text-dim uppercase mb-1.5">정보</h4>
<dl class="space-y-1.5 text-xs"> <dl class="space-y-1.5 text-xs">
<div class="flex justify-between"> <div class="flex justify-between">
<dt class="text-[var(--text-dim)]">포맷</dt> <dt class="text-dim">포맷</dt>
<dd class="uppercase">{doc.file_format}{doc.original_format ? ` (원본: ${doc.original_format})` : ''}</dd> <dd class="uppercase">{doc.file_format}{doc.original_format ? ` (원본: ${doc.original_format})` : ''}</dd>
</div> </div>
<div class="flex justify-between"> <div class="flex justify-between">
<dt class="text-[var(--text-dim)]">크기</dt> <dt class="text-dim">크기</dt>
<dd>{formatSize(doc.file_size)}</dd> <dd>{formatSize(doc.file_size)}</dd>
</div> </div>
{#if doc.source_channel} {#if doc.source_channel}
<div class="flex justify-between"> <div class="flex justify-between">
<dt class="text-[var(--text-dim)]">출처</dt> <dt class="text-dim">출처</dt>
<dd>{doc.source_channel}</dd> <dd>{doc.source_channel}</dd>
</div> </div>
{/if} {/if}
{#if doc.data_origin} {#if doc.data_origin}
<div class="flex justify-between"> <div class="flex justify-between">
<dt class="text-[var(--text-dim)]">구분</dt> <dt class="text-dim">구분</dt>
<dd>{doc.data_origin}</dd> <dd>{doc.data_origin}</dd>
</div> </div>
{/if} {/if}
<div class="flex justify-between"> <div class="flex justify-between">
<dt class="text-[var(--text-dim)]">등록일</dt> <dt class="text-dim">등록일</dt>
<dd>{formatDate(doc.created_at)}</dd> <dd>{formatDate(doc.created_at)}</dd>
</div> </div>
</dl> </dl>
@@ -299,42 +299,42 @@
<!-- 처리 상태 --> <!-- 처리 상태 -->
<div> <div>
<h4 class="text-xs font-semibold text-[var(--text-dim)] uppercase mb-1.5">처리</h4> <h4 class="text-xs font-semibold text-dim uppercase mb-1.5">처리</h4>
<dl class="space-y-1 text-xs"> <dl class="space-y-1 text-xs">
<div class="flex justify-between"> <div class="flex justify-between">
<dt class="text-[var(--text-dim)]">추출</dt> <dt class="text-dim">추출</dt>
<dd class={doc.extracted_at ? 'text-[var(--success)]' : 'text-[var(--text-dim)]'}>{doc.extracted_at ? '완료' : '대기'}</dd> <dd class={doc.extracted_at ? 'text-success' : 'text-dim'}>{doc.extracted_at ? '완료' : '대기'}</dd>
</div> </div>
<div class="flex justify-between"> <div class="flex justify-between">
<dt class="text-[var(--text-dim)]">분류</dt> <dt class="text-dim">분류</dt>
<dd class={doc.ai_processed_at ? 'text-[var(--success)]' : 'text-[var(--text-dim)]'}>{doc.ai_processed_at ? '완료' : '대기'}</dd> <dd class={doc.ai_processed_at ? 'text-success' : 'text-dim'}>{doc.ai_processed_at ? '완료' : '대기'}</dd>
</div> </div>
<div class="flex justify-between"> <div class="flex justify-between">
<dt class="text-[var(--text-dim)]">임베딩</dt> <dt class="text-dim">임베딩</dt>
<dd class={doc.embedded_at ? 'text-[var(--success)]' : 'text-[var(--text-dim)]'}>{doc.embedded_at ? '완료' : '대기'}</dd> <dd class={doc.embedded_at ? 'text-success' : 'text-dim'}>{doc.embedded_at ? '완료' : '대기'}</dd>
</div> </div>
</dl> </dl>
</div> </div>
<!-- 삭제 --> <!-- 삭제 -->
<div class="pt-2 border-t border-[var(--border)]"> <div class="pt-2 border-t border-default">
{#if deleteConfirm} {#if deleteConfirm}
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<span class="text-xs text-[var(--error)]">정말 삭제?</span> <span class="text-xs text-error">정말 삭제?</span>
<button <button
onclick={deleteDoc} onclick={deleteDoc}
disabled={deleting} disabled={deleting}
class="px-2 py-1 text-xs bg-[var(--error)] text-white rounded disabled:opacity-50" class="px-2 py-1 text-xs bg-error text-white rounded disabled:opacity-50"
>{deleting ? '삭제 중...' : '확인'}</button> >{deleting ? '삭제 중...' : '확인'}</button>
<button <button
onclick={() => deleteConfirm = false} onclick={() => deleteConfirm = false}
class="px-2 py-1 text-xs text-[var(--text-dim)]" class="px-2 py-1 text-xs text-dim"
>취소</button> >취소</button>
</div> </div>
{:else} {:else}
<button <button
onclick={() => deleteConfirm = true} onclick={() => deleteConfirm = true}
class="flex items-center gap-1 text-xs text-[var(--text-dim)] hover:text-[var(--error)]" class="flex items-center gap-1 text-xs text-dim hover:text-error"
> >
<Trash2 size={12} /> 문서 삭제 <Trash2 size={12} /> 문서 삭제
</button> </button>

View File

@@ -10,6 +10,15 @@
const PUBLIC_PATHS = ['/login', '/setup', '/__styleguide']; const PUBLIC_PATHS = ['/login', '/setup', '/__styleguide'];
const NO_CHROME_PATHS = ['/login', '/setup', '/__styleguide']; const NO_CHROME_PATHS = ['/login', '/setup', '/__styleguide'];
// toast 의미 토큰 매핑 (A-8 B3)
const TOAST_CLASS = {
success: 'bg-success/10 text-success border-success/30',
error: 'bg-error/10 text-error border-error/30',
warning: 'bg-warning/10 text-warning border-warning/30',
info: 'bg-accent/10 text-accent border-accent/30',
};
let authChecked = $state(false); let authChecked = $state(false);
let sidebarOpen = $state(false); let sidebarOpen = $state(false);
@@ -54,7 +63,7 @@
{#if !authChecked} {#if !authChecked}
<div class="min-h-screen flex items-center justify-center"> <div class="min-h-screen flex items-center justify-center">
<p class="text-[var(--text-dim)]">로딩 중...</p> <p class="text-dim">로딩 중...</p>
</div> </div>
{:else if $isAuthenticated || PUBLIC_PATHS.some(p => $page.url.pathname.startsWith(p))} {:else if $isAuthenticated || PUBLIC_PATHS.some(p => $page.url.pathname.startsWith(p))}
{#if showChrome} {#if showChrome}
@@ -65,7 +74,7 @@
{#if !$page.url.pathname.startsWith('/news')} {#if !$page.url.pathname.startsWith('/news')}
<button <button
onclick={() => sidebarOpen = !sidebarOpen} onclick={() => sidebarOpen = !sidebarOpen}
class="p-1.5 rounded-md hover:bg-[var(--border)] text-[var(--text-dim)]" class="p-1.5 rounded-md hover:bg-default text-dim"
aria-label="사이드바 토글" aria-label="사이드바 토글"
> >
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
@@ -73,17 +82,17 @@
</svg> </svg>
</button> </button>
{/if} {/if}
<a href="/" class="text-sm font-semibold hover:text-[var(--accent)]">PKM</a> <a href="/" class="text-sm font-semibold hover:text-accent">PKM</a>
<span class="text-[var(--text-dim)] text-xs">/</span> <span class="text-dim text-xs">/</span>
<a href="/documents" class="text-xs hover:text-[var(--accent)]">문서</a> <a href="/documents" class="text-xs hover:text-accent">문서</a>
</div> </div>
<div class="flex items-center gap-3"> <div class="flex items-center gap-3">
<a href="/news" class="text-xs text-[var(--text-dim)] hover:text-[var(--text)]">뉴스</a> <a href="/news" class="text-xs text-dim hover:text-text">뉴스</a>
<a href="/inbox" class="text-xs text-[var(--text-dim)] hover:text-[var(--text)]">Inbox</a> <a href="/inbox" class="text-xs text-dim hover:text-text">Inbox</a>
<a href="/settings" class="text-xs text-[var(--text-dim)] hover:text-[var(--text)]">설정</a> <a href="/settings" class="text-xs text-dim hover:text-text">설정</a>
<button <button
onclick={() => logout()} onclick={() => logout()}
class="text-xs text-[var(--text-dim)] hover:text-[var(--text)]" class="text-xs text-dim hover:text-text"
>로그아웃</button> >로그아웃</button>
</div> </div>
</nav> </nav>
@@ -119,15 +128,7 @@
<div class="fixed top-4 right-4 z-50 flex flex-col gap-2 max-w-sm" role="status" aria-live="polite"> <div class="fixed top-4 right-4 z-50 flex flex-col gap-2 max-w-sm" role="status" aria-live="polite">
{#each $toasts as toast (toast.id)} {#each $toasts as toast (toast.id)}
<button <button
class="px-4 py-3 rounded-lg shadow-lg text-sm flex items-center gap-2 cursor-pointer text-left" class="px-4 py-3 rounded-lg shadow-lg text-sm flex items-center gap-2 cursor-pointer text-left border {TOAST_CLASS[toast.type] || TOAST_CLASS.info}"
class:bg-green-900={toast.type === 'success'}
class:bg-red-900={toast.type === 'error'}
class:bg-yellow-900={toast.type === 'warning'}
class:bg-blue-900={toast.type === 'info'}
class:text-green-200={toast.type === 'success'}
class:text-red-200={toast.type === 'error'}
class:text-yellow-200={toast.type === 'warning'}
class:text-blue-200={toast.type === 'info'}
onclick={() => removeToast(toast.id)} onclick={() => removeToast(toast.id)}
> >
{toast.message} {toast.message}