## 2026-05-27 — Vault 에디터 UX 다듬기 (Ctrl+휠 줌, Done 버튼, 인라인 저장 표시) + Files 핀-투-탑

### 배경

Obsidian-style Vault 도입 직후 에디터/프리뷰 영역의 자잘한 거슬림이 누적돼 있었다:

1. Vault 안에서 Ctrl+휠을 굴리면 **브라우저 페이지 전체 줌**이 먹어 레이아웃이 통째로 흔들렸다. 사용자는 Vault 안에서만 폰트를 키우고 싶을 뿐인데 IDE 같은 줌 UX가 없었다.
2. 에디터 종료 버튼이 `X` + "Cancel" 라벨이라 마치 편집 내용을 폐기하는 듯한 의미였지만, 실제로는 그냥 프리뷰로 돌아가는 동작이라 라벨이 misleading.
3. 마크다운/HTML 미리보기에 "Rendered ↔ Source" 토글이 남아 있었는데 vault refactor 이후 사용 빈도가 사실상 0 — 토글 UI 자체가 노이즈.
4. 저장 성공 토스트가 우상단에 떴다 사라지는 형태라 *어디서* 저장 액션이 났는지 시선이 분산됐다. Save 버튼 옆에 인라인으로 표시되는 게 더 자연스러움.
5. Files 탭 트리에서 자주 여는 파일/폴더가 항상 같은 위치(알파벳 정렬)에 있어 매번 스크롤로 찾아가는 게 불편.

### 주요 변경

#### 1. Vault Ctrl+휠 폰트 줌 (`e985a2b`)

- **`src/client/src/hooks/useVaultZoom.ts`** (신규): `useSessionFontSize` 패턴(module-level cache + listener Set + globalDefault fallback)을 그대로 차용한 per-project 폰트 사이즈 훅. 키 `vault:zoom:${projectId}`, 8~28px clamp, default 13. 다중 컨슈머(에디터 + 프리뷰)가 같은 값을 공유하면서 context re-render 없이 동기화.
- **`src/client/src/components/vault/VaultLayout.tsx`**: `containerRef` div에 native `wheel` 리스너를 `{ passive: false }`로 부착. Ctrl/Cmd+휠일 때 `preventDefault` + `stopPropagation` 후 `bumpVaultZoom(projectId, deltaY < 0 ? +1 : -1)`. 일반 휠은 통과시켜 Vault 내부 스크롤 그대로.
- **`src/client/src/components/vault/PreviewPanel.tsx`**: CodeMirror 래퍼와 마크다운 래퍼에 `style={{ fontSize: ${zoom}px }}` 적용. 마크다운 래퍼에 `.vault-md-zoom` 스코프 클래스 추가.
- **`src/client/src/index.css`**: `.vault-md-zoom .markdown-content` 블록 추가 — 본문 1em, h1 1.4em, h2 1.2em, h3 1.05em, code 0.95em 등 em 기반. `.markdown-content`의 원래 rem 기반 absolute 사이즈를 스코프 내부에서만 em으로 오버라이드해, 토론 등 다른 `.markdown-content` 컨슈머는 영향 없음.

#### 2. 에디터 Cancel → Done 라벨/아이콘 변경 (`586f5ee`)

- **`src/client/src/components/vault/PreviewPanel.tsx`**: 편집 종료 버튼 아이콘 `X` → `Check`, i18n 키 `files.editor.cancel` → `files.editor.done`. 편집 내용은 이미 자동/수동 Save로 보존되고 이 버튼은 단순히 프리뷰 모드 복귀이므로 "완료" 시맨틱이 더 정확.
- **`src/client/src/i18n.tsx`**: en `"Done"`, ko `"완료"`.

#### 3. Rendered/Source 토글 제거 (`813da53`)

- **`src/client/src/components/vault/PreviewPanel.tsx`**: `viewMode` state + 토글 버튼 + Rendered/Source 분기 렌더링 코드 일괄 제거. 비편집 상태에서는 마크다운/HTML 모두 항상 rendered 모드로 표시. 원본 소스를 보고 싶으면 Edit 버튼으로 진입.
- **`src/client/src/i18n.tsx`**: `files.viewMode.*` 키 3개 제거.

#### 4. 저장 성공 → 인라인 flash (`affba53`)

- **`src/client/src/components/vault/PreviewPanel.tsx`**: 저장 성공 시 토스트 대신 Save 버튼 옆에 Check 아이콘 + "saved" 텍스트가 800ms간 표시되고 페이드. `savedFlash` state + timer ref로 관리, unmount 시 cleanup. 실패 토스트는 그대로 유지(사용자가 놓치면 안 되는 정보).

#### 5. Files 탐색기 파일/폴더 핀-투-탑 (`9ddff87`)

- **`src/client/src/components/vault/panels/FileExplorerPanel.tsx`**: 우클릭 컨텍스트 메뉴에 `Pin to top` / `Unpin` 액션 추가. pin 목록은 프로젝트별 localStorage(`vault:pins:${projectId}`)에 영속화. 트리 상단에 `PinnedSection`을 별도 영역으로 렌더(amber pin 아이콘 + 선택 상태 표시). 일반 트리와 pin 섹션은 같은 selection 상태를 공유.
- **`src/client/src/i18n.tsx`**: `files.pin`, `files.unpin`, `files.pinned` 추가 (en/ko).

### 수정된 주요 파일

| 파일 | 변경 내용 |
|------|----------|
| `src/client/src/hooks/useVaultZoom.ts` | 신규 — per-project 폰트 사이즈 훅 |
| `src/client/src/components/vault/VaultLayout.tsx` | native wheel 리스너로 Ctrl+휠 가로채기 |
| `src/client/src/components/vault/PreviewPanel.tsx` | zoom 적용 + Done 버튼 + 토글 제거 + 인라인 saved flash |
| `src/client/src/index.css` | `.vault-md-zoom` 스코프 em 오버라이드 |
| `src/client/src/components/vault/panels/FileExplorerPanel.tsx` | pin/unpin 컨텍스트 액션 + PinnedSection |
| `src/client/src/i18n.tsx` | done/pin/pinned 추가, viewMode/cancel 제거 |

### 아키텍처 결정

1. **Vault zoom은 SessionTerminal과 같은 패턴 재사용, 훅만 분리**: `useSessionFontSize`의 구조가 거의 그대로 맞는다(module-level cache + listener + globalDefault). 한 훅으로 합쳐 sessionId/projectId 키로 두 용도를 처리하는 방안도 있었으나, 시맨틱 분리(세션 터미널 폰트 ≠ Vault 폰트)와 기본값/clamp 범위가 독립적으로 진화할 가능성을 감안해 별도 훅으로 복사.
2. **마크다운 폰트 스케일은 em 스코프 오버라이드로**: `.markdown-content`는 토론 등 여러 곳에서 재사용되므로 원본 CSS를 em으로 바꾸면 의도치 않은 회귀가 생길 수 있다. `.vault-md-zoom` parent 스코프에서만 em으로 재정의해 안전한 변경 범위를 보장.
3. **alt-buffer fit 우회는 Vault zoom과 별도 결정이지만 같은 사용자 의도**: 둘 다 "줌은 폰트만 바뀌고 콘텐츠/레이아웃은 흔들리지 않아야 한다"는 원칙의 다른 표현. 한쪽은 브라우저 줌을 막고, 다른쪽은 PTY 리사이즈를 막는다.
4. **저장 성공은 *조용히*, 실패는 *시끄럽게***: saved flash는 "잘 됐다"는 사실만 0.8초 알려주면 충분. 실패는 사용자가 놓치면 안 되므로 토스트 유지. 일관된 비대칭은 alert fatigue를 줄이는 의도된 선택.
