## 2026-05-28 — Vault 주입 UI 재설계 (deprecated 위키 컨트롤 → 파일 셀렉터, +HTML) + 프리뷰 인터랙티브화 (컨텍스트 메뉴 + Find/Replace + 주석 + 태스크 체크박스)

### 배경

Obsidian-style Vault 도입 이후 두 가지 후속 작업이 누적돼 있었다:

1. **세션/디스커션/투두 폼의 주입 컨트롤이 deprecated 상태**였다. DB 노드 그래프 기반 `MemoryInjectControl`이 그대로 마운트되어 있어 노드 ID 목록(`memory_node_ids`)을 선택하는 흐름이 그대로 노출돼 있었지만, 실제로 사용자는 vault 도입 이후 그래프 노드를 거의 만들지 않고 있었다. UI는 vault로 한 번 더 일원화해야 하고, MD 외에 `.html` 파일도 같은 흐름으로 주입할 수 있어야 했다.
2. **Vault 마크다운 프리뷰는 사실상 정적 뷰어**였다. 헤더의 Edit / Find 같은 버튼 외에는 우클릭 시 아무 액션도 없었고, 검색은 브라우저 Ctrl+F로 떨어졌으며 (CodeMirror 검색은 따로), GFM 태스크 체크박스는 disabled로 렌더링돼 있어 디스크에 반영하려면 매번 편집 모드로 들어가야 했다. Vault를 "읽기 + 가볍게 살아있는 노트" 도구로 만들려면 read-time 상호작용이 필요했다.

### 주요 변경

#### 1. MemoryInjectControl 삭제 → VaultInjectControl 단일 경로 (`3859e38`)

- **`src/client/src/components/VaultInjectControl.tsx`** (확장): 4-mode 토글(`none / auto / all / selected`)은 유지하되, `selected` 모드 본문을 디렉터리별 그룹화 + MD/HTML 아이콘 구분 + 검색 박스 + `includeLinked` 토글로 재구성. `includeLinked`가 켜진 상태에서 파일을 체크하면 그 파일의 `[[wikilink]]`로 직접 연결된 1-hop 이웃까지 자동으로 selection에 포함됨. 각 행에는 추가로 끌려 들어갈 이웃 수가 `+N개 연결` 배지로 미리 표시.
- **`SessionForm` / `DiscussionForm` / `TodoForm`**: `MemoryInjectControl` 사용처 3개를 `VaultInjectControl`로 일괄 교체. 폼 외부에 노출되는 필드는 `memoryInjectMode` + `vaultFilePaths` + `includeLinked`. `memory_node_ids`는 빈 배열로 고정해 DB 컬럼은 그대로 두고 dual-path memory-inject-hook의 vault 분기로 라우팅.
- **`MemoryInjectControl.tsx` 삭제**, `TodoForm`/`TodoItem`에서 미사용 `initialMemoryNodeIds` prop 제거.
- **`src/server/services/file-scanner.ts`**: 스캐너가 `.md` 외에 `.html` / `.htm`도 인식. HTML은 `<title>` 태그에서 title 추출, 본문은 태그 제거 후 plain text preview를 생성. `VaultFile`에 `kind: 'md' | 'html'` 필드 추가.
- **`src/server/services/vault-injector.ts`**: HTML 파일도 같은 `<long_term_memory>` 래퍼 안에 직렬화. 내부 블록 태그를 `<wiki_file>` → `<vault_file type="md" | "html">`로 변경. 외부 `<long_term_memory>` 시맨틱 힌트 태그는 유지.
- **`src/server/routes/vault.ts`**: 파일 읽기 / preview 엔드포인트에서 HTML 확장자도 허용. preview 입력 경로는 sanitize.
- **i18n**: `vaultInject.title` → "Vault 주입". `vaultInject.includeLinked / linkedAdded / empty / noResults / searchPlaceholder` 키 추가 (en/ko). 더 이상 사용되지 않는 `wikiInject.*` 19개 키 제거.

#### 2. Vault 프리뷰 컨텍스트 메뉴 + Find/Replace + 주석 + 태스크 체크박스 토글 (`833ccba`)

`PreviewPanel`에 read-time 상호작용 네 가지를 같은 우클릭 ContextMenu 인프라 위에 얹었다. 모두 적용 불가 상황(편집 모드 / 바이너리 / 비-마크다운)에서는 자연스럽게 비활성화됨.

- **우클릭 ContextMenu (프리뷰 + 편집 모드)**:
  - 인라인 `MdContextMenu` 컴포넌트 — `FileExplorerPanel`의 portal + viewport clamp + Esc/scroll/resize close 패턴을 그대로 차용.
  - 모드 인식: 프리뷰면 `Edit / Find / Start drawing`, 편집 모드면 `Done / Find / Stop drawing`.

- **Find & Replace 바 (`src/client/src/components/vault/FindReplaceBar.tsx`, 신규)**:
  - 단축키: `Ctrl+F` 열기, `Ctrl+H` Replace 모드, `F3` / `Shift+F3` 다음/이전, `Esc`/X 닫기.
  - Case-sensitive / Whole word / Regex 토글, 매치 카운터, invalid-regex 인디케이터.
  - 프리뷰 모드: 자체 TreeWalker DOM 하이라이팅 + 활성 매치 scroll-into-view + wrap-around 네비게이션.
  - 편집 모드: `@codemirror/search`의 `SearchQuery` + `findNext / findPrev / replaceNext / replaceAll` API로 브리지. `basicSetup.searchKeymap`은 충돌 방지를 위해 끔.

- **Ephemeral 주석 오버레이 (`src/client/src/components/vault/AnnotationOverlay.tsx`, 신규)**:
  - 도구: 펜(빨강 2px) / 형광펜(빨강 14px, opacity 0.4) / 지우개(stroke 단위 삭제) / Clear all.
  - SVG absolute 오버레이가 `.vault-md-zoom` 안에 들어가서 zoom된 프리뷰와 좌표가 자동 동기화. `pointer-events`는 모드 토글로 게이팅 — 끈 상태에서는 텍스트 선택이 그대로 작동.
  - 진입 경로: 헤더 펜 토글 / 미니 툴바 / 컨텍스트 메뉴. 파일 변경 / 편집 모드 진입 시 자동 초기화 (저장하지 않는 ephemeral 디자인).

- **클릭 가능한 GFM 태스크 체크박스**:
  - `src/client/src/components/MarkdownContent.tsx`: opt-in `onCheckboxToggle` prop을 받음. input 컴포넌트 오버라이드로 disabled 상태를 풀고 클릭 핸들을 부착.
  - `PreviewPanel`: 클릭된 박스의 N번째 task를 찾아 `- [ ]`/`- [x]`를 토글하는 정규식(`-`/`*`/`+` + ordered list 지원). `saveFileContent`로 optimistic write, 409 conflict 시 롤백.

- **i18n**: `find.*` / `annotate.*` 네임스페이스에 키 25개 추가 (en/ko).
- **CSS**: `.vault-find-match` / `.vault-find-match-active` 클래스를 `mark` 요소에 스코프해 다크 모드와도 잘 어울리는 하이라이트.

### 수정된 주요 파일

| 파일 | 변경 내용 |
|------|----------|
| `src/client/src/components/VaultInjectControl.tsx` | 디렉터리 그룹화 + MD/HTML 아이콘 + `includeLinked` 토글 + `+N개 연결` 배지 |
| `src/client/src/components/{SessionForm,DiscussionForm,TodoForm}.tsx` | `MemoryInjectControl` → `VaultInjectControl` 교체 |
| `src/client/src/components/MemoryInjectControl.tsx` | 삭제 |
| `src/server/services/file-scanner.ts` | `.html` / `.htm` 스캔 + `<title>` 추출 + plain text preview + `VaultFile.kind` |
| `src/server/services/vault-injector.ts` | HTML 직렬화 + `<vault_file type="...">` 태그 |
| `src/server/routes/vault.ts` | 파일 읽기/preview에서 HTML 허용 |
| `src/client/src/components/vault/PreviewPanel.tsx` | ContextMenu + Find/Replace 통합 + AnnotationOverlay + 태스크 체크박스 토글 |
| `src/client/src/components/vault/FindReplaceBar.tsx` | 신규 — Find/Replace 바 (프리뷰 TreeWalker + 편집 CodeMirror 브리지) |
| `src/client/src/components/vault/AnnotationOverlay.tsx` | 신규 — SVG 기반 ephemeral 주석 |
| `src/client/src/components/MarkdownContent.tsx` | `onCheckboxToggle` opt-in prop + input override |
| `src/client/src/i18n.tsx` | `find.*` / `annotate.*` 25키 + `vaultInject.*` 신규 키 추가, `wikiInject.*` 19키 삭제 |
| `src/client/src/index.css` | `.vault-find-match` / `-active` 하이라이트 클래스 |

### 아키텍처 결정

1. **주입 경로는 단일 채널, DB 컬럼은 보존**: VaultInjectControl로 일원화하되 DB 스키마(`memory_node_ids`, `memory_raw_file_paths`)는 그대로 두고 vault 경로는 `memory_raw_file_paths`(파일 경로 리스트) 컬럼에 저장. memory-inject-hook의 dual-path는 빈 `nodeIds` + 파일 경로가 있으면 vault 분기로 라우팅하는 식으로 그대로 활용 — 마이그레이션 없이 UI만 교체.
2. **HTML도 같은 래퍼 + 타입 어트리뷰트**: 별도 `<long_term_html>` 같은 새 외부 태그를 만들지 않고 `<long_term_memory>` 외부 래퍼는 유지. 내부에서 `<vault_file type="md" | "html">`로 구분. LLM 입장에서 시맨틱 힌트는 외부 래퍼로 충분하고, 내부 타입은 본문 파싱이 필요할 때만 참조하면 됨.
3. **주석은 ephemeral, 저장하지 않음**: 페이지 마진의 손글씨 메모처럼 "이번 read 동안만" 살아 있는 디자인. localStorage / DB / SVG 익스포트 어디에도 영속화하지 않음 — 영속 메모가 필요하면 파일 본문에 적는 게 vault의 정신과 일치. 파일 전환 / 편집 진입 시 자동 클리어.
4. **태스크 토글은 optimistic + 409 롤백**: 디스크 충돌 가능성이 있으니 매번 read-then-write보다 optimistic write + 충돌 시 롤백이 사용자 응답성에 유리. `saveFileContent`는 이미 ETag-like 토큰을 받고 409를 반환하므로 추가 인프라 없음.
5. **Find/Replace는 프리뷰와 편집에 같은 키바인딩, 다른 엔진**: 프리뷰는 렌더된 DOM 위에서 동작해야 하므로 TreeWalker 자체 구현. 편집은 CodeMirror의 native search를 다시 만들지 않고 `@codemirror/search`의 명령 API에 브리지. UI는 같은 컴포넌트라 사용자에게는 통일된 경험.
