diff --git a/docs/superpowers/plans/2026-05-09-v028-cut-a.md b/docs/superpowers/plans/2026-05-09-v028-cut-a.md
new file mode 100644
index 0000000..26f9b87
--- /dev/null
+++ b/docs/superpowers/plans/2026-05-09-v028-cut-a.md
@@ -0,0 +1,705 @@
+# v0.2.8 Cut A Implementation Plan — 이미지 렌더링 + 앱 아이콘
+
+> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
+
+**Goal:** F22 (NoteCard 의 회색 placeholder → 실제 `` + 클릭 시 OS viewer) + chore (앱 아이콘 SVG → ICO/ICNS/PNG 다중 size + electron-builder 통합).
+
+**Architecture:** Electron renderer 보안 정책 우회를 위해 main process 에 `inkling-media://` custom protocol 등록 — `/media//` 을 fetch 가능하게 함. NoteCard 가 protocol URL 을 `` 로 사용. 클릭 시 IPC `inbox:open-media` → `shell.openPath` 로 OS default viewer 열기. 앱 아이콘은 `assets/icon.svg` (이미 작성) 를 `electron-icon-builder` 로 한 번 빌드 → `build/icon.ico/icns/png` 산출물 git 추적 + electron-builder config 매핑.
+
+**Tech Stack:** Electron 41 protocol API + React 19 + better-sqlite3 + electron-icon-builder + sharp (SVG 변환 fallback)
+
+**선행 spec:** [docs/superpowers/specs/2026-05-09-v028-cut-a-design.md](docs/superpowers/specs/2026-05-09-v028-cut-a-design.md)
+
+---
+
+## File Structure
+
+### 신규 파일
+
+| 경로 | 책임 |
+|---|---|
+| `src/main/protocol/inklingMedia.ts` | `inkling-media://` scheme 권한 + handler 등록 (path traversal 검사 + inferMime) |
+| `tests/unit/inklingMedia.test.ts` | protocol handler 단위 테스트 (path traversal 403 / 정상 200 / 404 / mime) |
+| `assets/icon.svg` | (이미 v0.2.7 turn 에서 생성 — Cut A 시작 전 commit 필요 시 재확인) |
+
+### 수정 파일
+
+| 경로 | 변경 |
+|---|---|
+| `src/main/index.ts` | top-level `protocol.registerSchemesAsPrivileged` + whenReady 안 `registerInklingMediaProtocol(paths.profileDir)` 호출 + `registerInboxApi` 가 신규 IPC 채널 등록 |
+| `src/main/ipc/inboxApi.ts` | 신규 IPC `inbox:open-media` 핸들러 (path traversal 검사 + `shell.openPath`) |
+| `src/preload/index.ts` | `inbox:open-media` 채널 화이트리스트 |
+| `src/shared/types.ts` | `InboxApi` 에 `openMedia(relPath: string): Promise<{ ok: boolean; reason?: string }>` 시그니처 추가 |
+| `src/renderer/inbox/api.ts` | inboxApi 객체에 `openMedia` wrapper |
+| `src/renderer/inbox/components/NoteCard.tsx` | 회색 `
+)}
+```
+
+`inboxApi` import 가 이미 있는지 확인 — 있으면 그대로. 없으면 추가:
+
+```tsx
+import { inboxApi } from '../api.js';
+```
+
+- [ ] **Step 4: 테스트 통과 + typecheck**
+
+```bash
+npx vitest run tests/unit/NoteCard.test.tsx
+npm run typecheck
+```
+
+Expected: 2 test pass (`openMedia` 가 types.ts 에 아직 없으니 typecheck error 가능 — Task 3 에서 해결). 일단 임시로 NoteCard.test.tsx 의 mock 만 동작.
+
+만약 typecheck error 발생: `inboxApi.openMedia` 가 InboxApi 인터페이스에 없음 → 일시 `(inboxApi as any).openMedia(m.relPath)` 로 cast. **Task 3 에서 정식 시그니처 추가 후 cast 제거.**
+
+- [ ] **Step 5: commit (with TODO note for Task 3)**
+
+```bash
+git add src/renderer/inbox/components/NoteCard.tsx tests/unit/NoteCard.test.tsx
+git commit -m "feat(v028): NoteCard 이미지 렌더링 + onClick (openMedia 시그니처는 Task 3)"
+```
+
+---
+
+## Phase 3: IPC `inbox:open-media`
+
+### Task 3: main IPC 핸들러 + api.ts wrapper + types
+
+**Files:**
+
+- Modify: `src/main/ipc/inboxApi.ts`
+- Modify: `src/preload/index.ts`
+- Modify: `src/shared/types.ts`
+- Modify: `src/renderer/inbox/api.ts`
+- Modify: `src/renderer/inbox/components/NoteCard.tsx` (Task 2 cast 제거)
+
+- [ ] **Step 1: failing test 작성 (IPC handler 단위)**
+
+```ts
+// tests/unit/inboxApi-openMedia.test.ts
+import { describe, it, expect, vi, beforeEach } from 'vitest';
+import { join, sep } from 'node:path';
+
+const handlers: Record = {};
+const mockOpenPath = vi.fn(async () => '');
+
+vi.mock('electron', () => ({
+ default: {
+ ipcMain: { handle: (ch: string, fn: Function) => { handlers[ch] = fn; } },
+ shell: { openPath: mockOpenPath }
+ }
+}));
+
+import { registerInboxApi } from '../../src/main/ipc/inboxApi';
+
+describe('inbox:open-media IPC', () => {
+ beforeEach(() => { vi.clearAllMocks(); for (const k of Object.keys(handlers)) delete handlers[k]; });
+
+ it('opens valid relPath with shell.openPath', async () => {
+ // 기존 registerInboxApi 가 deps 받으므로 minimal stub 만 — paths 만 사용
+ registerInboxApi({ paths: { profileDir: '/profile' } } as any);
+ const r = await handlers['inbox:open-media'](null, 'media/note1/img.png');
+ expect(r).toEqual({ ok: true });
+ expect(mockOpenPath).toHaveBeenCalledWith(join('/profile', 'media/note1/img.png'));
+ });
+
+ it('rejects path traversal', async () => {
+ registerInboxApi({ paths: { profileDir: '/profile' } } as any);
+ const r = await handlers['inbox:open-media'](null, '../etc/passwd');
+ expect(r.ok).toBe(false);
+ expect(r.reason).toBe('invalid path');
+ expect(mockOpenPath).not.toHaveBeenCalled();
+ });
+});
+```
+
+- [ ] **Step 2: 테스트 실행 → fail**
+
+```bash
+npx vitest run tests/unit/inboxApi-openMedia.test.ts
+```
+
+Expected: handler 미등록.
+
+- [ ] **Step 3: src/main/ipc/inboxApi.ts 핸들러 추가**
+
+기존 `registerInboxApi(deps)` 함수 안에 다음 추가 (다른 IPC 핸들러와 같은 패턴):
+
+```ts
+import { join, normalize, sep } from 'node:path';
+import electron from 'electron';
+const { ipcMain, shell } = electron;
+
+// registerInboxApi 안:
+ipcMain.handle('inbox:open-media', async (_e, relPath: string) => {
+ const mediaRoot = join(deps.paths.profileDir, 'media');
+ const target = normalize(join(deps.paths.profileDir, relPath));
+ if (!target.startsWith(mediaRoot + sep) && target !== mediaRoot) {
+ return { ok: false, reason: 'invalid path' };
+ }
+ await shell.openPath(target);
+ return { ok: true };
+});
+```
+
+(기존 deps 타입에 `paths.profileDir` 가 있는지 확인. 없으면 SettingsIpcDeps 와 비슷한 형태로 추가.)
+
+- [ ] **Step 4: src/shared/types.ts InboxApi 갱신**
+
+```ts
+// 기존 InboxApi 인터페이스 안:
+openMedia(relPath: string): Promise<{ ok: true } | { ok: false; reason: string }>;
+```
+
+- [ ] **Step 5: preload + api.ts wrapper**
+
+`src/preload/index.ts` 의 invoke whitelist 또는 API expose 객체 안에 `'inbox:open-media'` 추가 (기존 `'inbox:*'` 채널 패턴 따름).
+
+`src/renderer/inbox/api.ts` 의 inboxApi 객체에 추가:
+
+```ts
+async openMedia(relPath: string) {
+ return await window.inkling.invoke('inbox:open-media', relPath);
+}
+```
+
+(또는 기존 wildcard re-export 사용 시 자동 노출 — `feb7c62` commit 의 `onNavigate` 처럼.)
+
+- [ ] **Step 6: NoteCard.tsx cast 제거**
+
+Task 2 에서 임시 `(inboxApi as any).openMedia` cast 했다면 → `inboxApi.openMedia` 정상 사용으로 변경. typecheck 통과 확인.
+
+- [ ] **Step 7: 테스트 + typecheck + 회귀**
+
+```bash
+npx vitest run tests/unit/inboxApi-openMedia.test.ts
+npm run typecheck
+npx vitest run
+```
+
+Expected: IPC 2 test pass + 466 → 468 (+2) + 0 typecheck.
+
+- [ ] **Step 8: commit**
+
+```bash
+git add -A src/main/ipc/ src/preload/ src/shared/ src/renderer/ tests/unit/inboxApi-openMedia.test.ts
+git commit -m "feat(v028): IPC inbox:open-media + path traversal + NoteCard cast 정리"
+```
+
+---
+
+## Phase 4: 앱 아이콘 빌드 + config
+
+### Task 4: electron-icon-builder + 산출물 + builder config
+
+**Files:**
+
+- Modify: `package.json`
+- Modify: `.gitignore`
+- Create: `build/icon.ico`, `build/icon.icns`, `build/icon.png` (빌드 산출물)
+- (조건부) Create: `scripts/svg-to-png.mjs` (SVG → PNG fallback if needed)
+
+- [ ] **Step 1: devDep 설치**
+
+```bash
+npm install --save-dev electron-icon-builder
+```
+
+확인: `package.json` 의 devDependencies 에 `"electron-icon-builder": "^2.x.x"` 추가됨.
+
+- [ ] **Step 2: package.json scripts + builder config**
+
+`package.json` 의 scripts 블록에 추가:
+
+```json
+"build:icons": "electron-icon-builder --input=assets/icon.svg --output=build --flatten"
+```
+
+(만약 SVG 직접 input 안 되면 — Step 3 에서 sharp fallback 으로 분기.)
+
+`build` 블록 갱신 (기존 win/mac/linux 에 icon 키 추가):
+
+```json
+"win": { "icon": "build/icon.ico", ... 기존 그대로 ... },
+"mac": { "icon": "build/icon.icns", ... 기존 그대로 ... },
+"linux": { "icon": "build/icon.png", ... 기존 그대로 ... }
+```
+
+- [ ] **Step 3: 빌드 실행**
+
+```bash
+npm run build:icons
+```
+
+Expected:
+- `build/icon.ico` (Win)
+- `build/icon.icns` (macOS)
+- `build/icon.png` (1024x1024, Linux)
+
+만약 SVG 직접 안 되면 (electron-icon-builder 가 PNG 만 input 받음):
+
+1. `scripts/svg-to-png.mjs` 작성:
+
+```js
+import sharp from 'sharp';
+import { readFileSync, writeFileSync } from 'node:fs';
+
+const [,, input, output, size = '1024'] = process.argv;
+const svg = readFileSync(input);
+const png = await sharp(svg).resize(Number(size), Number(size)).png().toBuffer();
+writeFileSync(output, png);
+console.log(`OK: ${output} (${size}x${size})`);
+```
+
+2. `package.json` scripts 갱신:
+
+```json
+"build:icons:png": "node scripts/svg-to-png.mjs assets/icon.svg build/icon-source.png 1024",
+"build:icons": "npm run build:icons:png && electron-icon-builder --input=build/icon-source.png --output=build --flatten"
+```
+
+3. `npm install --save-dev sharp` (이미 있으면 skip).
+
+4. 다시 `npm run build:icons` 실행.
+
+- [ ] **Step 4: .gitignore 갱신 (build/ 안 산출물 whitelist)**
+
+`.gitignore` 의 `build/` 또는 `dist/` 항목 확인. 만약 `build/` 가 ignore 되어 있다면:
+
+```
+build/
+!build/icon.ico
+!build/icon.icns
+!build/icon.png
+```
+
+(만약 `build/` 가 ignore 안 되어 있다면 — 모두 추적 가능 — Step 4 skip.)
+
+- [ ] **Step 5: 산출물 확인 + commit**
+
+```bash
+ls -la build/icon.ico build/icon.icns build/icon.png
+```
+
+Expected: 3 파일 모두 size 양수 (수십 KB 이상).
+
+```bash
+git add package.json package-lock.json .gitignore scripts/svg-to-png.mjs build/icon.ico build/icon.icns build/icon.png
+git commit -m "chore(v028): 앱 아이콘 (assets/icon.svg → ICO/ICNS/PNG) + electron-builder config"
+```
+
+(scripts/svg-to-png.mjs 는 sharp fallback 사용 시만 추가.)
+
+- [ ] **Step 6: typecheck + 전체 회귀**
+
+```bash
+npm run typecheck
+npx vitest run
+```
+
+Expected: 0 errors + 468 pass (이전 + Task 1-3 변경 포함). 아이콘 변경은 테스트 영향 X.
+
+---
+
+## Phase 5: verification + version bump
+
+### Task 5: 회귀 + dogfood-feedback 마킹 + version bump
+
+**Files:**
+
+- Modify: `docs/superpowers/specs/2026-04-25-dogfood-feedback.md` (F22 promoted 마킹)
+- Modify: `package.json` (version 0.2.7 → 0.2.8)
+
+- [ ] **Step 1: 단위 + e2e + typecheck 일괄**
+
+```bash
+npm run rebuild:node
+npm test
+npm run typecheck
+npm run rebuild:electron
+npm run test:e2e
+```
+
+Expected: 모두 pass. 단위 460 → 약 468 (+8: inferMime 5 + protocol handler 3 + NoteCard img 2 + IPC 2 = 12, 일부 mock 충돌 가능 — 실제 카운트 ±). e2e 1/1.
+
+- [ ] **Step 2: 수동 launch 검증 (Win + macOS 가능 시)**
+
+```bash
+npm run start
+```
+
+체크리스트:
+
+- inbox 의 capture-with-image 노트의 thumbnail = 실제 이미지 (회색 사각형 X)
+- thumbnail 클릭 → OS default viewer (예: Win Photos / macOS Preview) 열림
+- 새 아이콘이 트레이 / Windows taskbar / dock 정확 표시
+- 다중 이미지 노트의 grid layout (flex-wrap) 자연스러움
+
+- [ ] **Step 3: dogfood-feedback.md F22 promoted 마킹**
+
+`docs/superpowers/specs/2026-04-25-dogfood-feedback.md` 의 F22 entry 헤더 갱신:
+
+```markdown
+## F22. NoteCard 이미지가 회색 placeholder 만 표시 (🚀 promoted → docs/superpowers/specs/2026-05-09-v028-cut-a-design.md)
+```
+
+진행 상태 line 도 `🚀 promoted` 로 갱신.
+
+- [ ] **Step 4: package.json version bump**
+
+```json
+"version": "0.2.8"
+```
+
+- [ ] **Step 5: commit**
+
+```bash
+git add docs/superpowers/specs/2026-04-25-dogfood-feedback.md package.json
+git commit -m "chore(release): v0.2.8 — Cut A (이미지 렌더링 + 앱 아이콘)"
+```
+
+---
+
+## Self-Review
+
+**Spec coverage:**
+
+| Spec 섹션 | task |
+|---|---|
+| §3-1 protocol 등록 | Task 1 |
+| §3-2 NoteCard `` | Task 2 |
+| §3-3 IPC inbox:open-media | Task 3 |
+| §3-4 보안 (path traversal) | Task 1 + Task 3 |
+| §4-1 devDep + scripts | Task 4 |
+| §4-2 builder config | Task 4 |
+| §4-3 산출물 git 추적 | Task 4 |
+| §4-4 SVG → PNG fallback | Task 4 (조건부) |
+| §5 테스트 | 각 task 안 단위 + Task 5 회귀 |
+| §6 Risk | Task 4 fallback 분기 |
+
+모든 spec 요구가 task 매핑됨.
+
+**Placeholder scan**: "TBD" / "TODO" / "implement later" 없음. 각 step 의 코드/명령 실행 가능 형태.
+
+**Type consistency**:
+
+- `InboxApi.openMedia(relPath: string): Promise<{ ok: true } | { ok: false; reason: string }>` — Task 3 정의, Task 2 cast → Task 3 cast 제거 일관.
+- `inferMime(filename: string): string` — Task 1 정의, Task 1 안 사용.
+- `registerInklingMediaProtocol(profileDir: string): void` / `registerSchemesAsPrivileged(): void` — Task 1 export, src/main/index.ts import 일관.
+
+이슈 없음.
+
+---
+
+## Execution Handoff
+
+Plan 작성 완료, `docs/superpowers/plans/2026-05-09-v028-cut-a.md` 저장.
+
+두 가지 실행 옵션:
+
+**1. Subagent-Driven (recommended)** — fresh subagent per task, two-stage review (spec compliance + code quality), 빠른 iteration
+
+**2. Inline Execution** — 본 세션에서 task 일괄 실행 + checkpoint 마다 review
+
+어느 쪽?
diff --git a/docs/superpowers/specs/2026-04-25-dogfood-feedback.md b/docs/superpowers/specs/2026-04-25-dogfood-feedback.md
index a991f2d..ee93a44 100644
--- a/docs/superpowers/specs/2026-04-25-dogfood-feedback.md
+++ b/docs/superpowers/specs/2026-04-25-dogfood-feedback.md
@@ -1305,6 +1305,341 @@ app.on('activate', () => {
- **트레이 코드 단순화**: 13 항목 → 3~4 항목. v0.2.6 의 TrayCallbacks 객체화 (C2) 효과 가시화.
- Risk: Windows 사용자 흐름 변경 — 트레이 한 클릭으로 끝나던 동작이 inbox 열기 → 설정 → 항목 클릭 으로 늘어남. 단, 빈도 낮은 동작 (Ollama 설정 변경, 백업 등) 만 이동하고 자주 쓰는 캡처/보관함 은 트레이 잔류 → 체감 마찰 ↓ 예상.
+---
+## F17. 휴지통의 의미 혼재 — 완료/보관과 버림 구분 (🌱 raw — v0.2.8 후보, 큰 design 결정)
+
+**진행 상태:** 🌱 raw — 본인 dogfood 발견. F18 (사유 입력) 와 강하게 연관. v0.2.8 brainstorm 시 함께 triage.
+
+**발견:** 2026-05-09 v0.2.7 release 후 본인 dogfood.
+
+### 관찰
+
+- 현재 메모 destination = active(`deleted_at IS NULL`) 또는 휴지통(`deleted_at != NULL`) 단일 분기.
+- 사용자 의도가 갈리는 case:
+ - (a) **불필요해서 버림** — 잘못 적은 캡처, 더 이상 의미 없음. 휴지통 자연스러움.
+ - (b) **완료해서 더 이상 안 띄움** — 작업 끝났지만 기록은 보존하고 싶음. 휴지통은 의미 부적합 (회수 대상이 아니라 정리 대상).
+ - (c) **급하지 않아 미루기** — 지금은 안 띄우지만 나중에 다시 보고 싶음. 휴지통도 active 도 어색.
+- 모두 휴지통으로 보내면 의미 혼재 + 30일 후 영구 삭제 정책 (현재) 이 (b)/(c) 의도 깨뜨림.
+
+### 제안 방향 (옵션 분석)
+
+**A. status 컬럼 분기** (notes 테이블에 `status`: `'active' | 'completed' | 'archived' | 'trashed'`):
+- `completed`: 작업 끝, 영구 보관 (회수 대상 아님, 검색 가능)
+- `archived`: 장기 보관 (회수 가능, 별도 view)
+- `trashed`: 30일 후 영구 삭제 (현재 휴지통)
+- inbox 탭 + 휴지통 탭 외에 "완료" / "보관함" 탭 추가
+- 마이그레이션 비용 + UI 변경 (NoteCard 액션 메뉴 + 헤더 탭)
+
+**B. AI 자동 분류** (옵션 A 의 extension):
+- "완료" 키워드 / 패턴 (예: "X 끝남", "처리됨", "결재됨") AI 가 감지 → 자동 `status='completed'` 제안
+- 사용자가 confirm/dismiss
+- F19 의 recall 강화와 결합 시 효율 ↑
+
+**C. 휴지통 의미 유지 + 보관함만 별도** (간단):
+- 휴지통은 그대로 (불필요 — 30일 영구 삭제)
+- 보관함만 신규 — `status='archived'` (회수 가능, 영구 보존)
+- 완료 / 미루기 모두 보관함 사용
+- 변경 작음, 의미 명확
+
+### 결정 대기 (v0.2.8 brainstorm)
+
+- 보관함 detail UI: 별도 탭 vs 별도 라우트 vs filter 토글
+- "완료" 와 "보관" 을 같은 destination 으로 둘지 (옵션 C) vs 분리 (옵션 A)
+- AI 자동 분류 (옵션 B) — 정확도 + false-positive risk 측정 필요. v0.2.8 본 cut 보다 나중 가능.
+
+### 가설·측정
+
+- 본인 dogfood: 2주간 휴지통 이동한 메모 중 (a)/(b)/(c) 비율 — telemetry 또는 자기 회상 으로 측정. (b)/(c) 비율 ≥30% 면 의미 분기 가치 큼.
+- 옵션 C 후 보관함 사용 빈도 — active 메모 대비 비율.
+
+### 범위
+
+- 옵션 A: 큰 작업 (DB 마이그레이션 + UI 탭 + IPC + telemetry kind 추가). 별도 spec 가치.
+- 옵션 C: 1주 spike 가능 (status 컬럼 또는 별도 `archived_at` 추가 + UI 탭).
+- 옵션 B: 추가 cut — 옵션 A/C 안정 후.
+
+### 영향
+
+- 메모 의미가 명확해짐 — "버림" vs "정리" 구분.
+- 휴지통의 30일 영구 삭제 정책 유지 가능 (의미 혼재 X).
+- F18 (사유 입력) 와 결합 시 사용자 의도 데이터 누적 → recall 알고리즘 (F19) 기여.
+
+---
+
+## F18. 메모 휴지통/보관 이동 시 사유 입력 (🌱 raw — v0.2.8 후보, F17 와 묶음)
+
+**진행 상태:** 🌱 raw — F17 과 강한 연관. v0.2.8 brainstorm 시 함께 triage.
+
+**발견:** 2026-05-09 v0.2.7 release 후 본인 dogfood.
+
+### 관찰
+
+- 휴지통 이동 = silent (단순 `deleted_at` 셋팅).
+- 사용자 의도 다양 ("완료", "급하지 않음", "더 이상 필요 없음", "잘못 적음" 등) — 데이터로 보존 안 됨.
+- 사유 데이터가 있으면:
+ - 자기 회상 (왜 버렸는지 다시 보기)
+ - recall 알고리즘 (F19) 학습 입력 — "급하지 않음" 메모는 N일 후 재surface
+ - telemetry 분석 (어떤 종류 메모가 빠르게 trash 되는가)
+
+### 제안 방향
+
+**A. 자유 텍스트 reason 필드** (notes.deleted_reason VARCHAR 또는 별도 trash_log 테이블):
+- 휴지통 이동 시 prompt: "왜 버려? (선택사항)" 한 줄 입력
+- 빈 값 허용
+- 검색/필터 가능
+
+**B. preset 사유 + 자유 텍스트 옵션**:
+- 빠른 선택: "완료" / "급하지 않음" / "잘못 적음" / "기타"
+- "기타" 선택 시 자유 텍스트
+- 통계 가능 (preset 분류)
+
+**C. F17 의 status 분기 + 사유 결합**:
+- status='completed' 이면 사유 = "완료" 자동 inferred
+- status='archived' 이면 사유 입력 prompt
+- status='trashed' 이면 사유 선택사항
+
+### 결정 대기
+
+- 사유 입력 friction vs 데이터 가치 — 매번 묻으면 capture 흐름 깨짐 risk
+- preset 만 vs 자유 텍스트 — preset 만이 friction 최소
+- 위치: NoteCard 인라인 dropdown vs 별도 modal
+
+### 가설·측정
+
+- 본인 dogfood 1주 — 사유 입력 비율 (skip 없이 입력) ≥50% 면 데이터 가치 충분.
+- 사유 distribution — preset N개 / "기타" 비율 — preset 명세 검증.
+
+### 범위
+
+- A: 1일 (DB column + UI prompt + IPC). 가장 작음.
+- B: 1.5일 (+ preset UI).
+- C: F17 의 옵션 A/C 안에 포함 가능 (한 cut).
+
+### 영향
+
+- 사용자 의도 데이터 누적 → F19 recall 알고리즘 입력 + 자기 회상 surface.
+- F17 의 status 분기 가치 ↑ (사유 + status 의 의미 결합).
+- friction 우려 — preset 또는 skip 가능 으로 완화.
+
+---
+
+## F19. 획기적 recall 메커니즘 (🌱 raw — v0.2.8+ 큰 영역, 본질 재설계 가능)
+
+**진행 상태:** 🌱 raw — 핵심 가치 영역. v0.2.8 brainstorm 시 별도 spec 후보 (recall 만 단독 cut 가치).
+
+**발견:** 2026-05-09 v0.2.7 release 후 본인 dogfood. "메모의 빠른 기록도 중요하지만 적절한 recall 도 훨씬 중요" — 본인 표현.
+
+### 관찰
+
+- 현재 recall surface:
+ - **RecallBanner** (v0.2.3 #6) — 1일 1회, 14~21일 전 candidate 1건. 한 번에 1건 + dismiss 후 다음 날까지 안 뜸.
+ - **RecoveryToast** — 주간 회복 ping (1주 capture 끊김 시 한정).
+ - **search/filter** — tag 필터링만, free text search 없음.
+- 빠른 기록 (capture) 는 잘 됨 — 한 줄 적기 + 트레이 hotkey 가 ≤1초 완성.
+- recall 은 약함:
+ - 적극적 recall 안 됨 (메모를 다시 봐야 의미 있는데 surface 가 너무 약함)
+ - search 부재 — "뭐였더라" 회상 시 인덱스 없음
+ - context-based recall 없음 (시간/태그/장소 기반)
+ - AI 가 capture 단계에서만 활용, recall 단계 X
+- 사용자 표현 "획기적 방법" — 현 RecallBanner 의 점진 개선이 아니라 질적 변화 요구.
+
+### 제안 방향 (브레인스토밍 후보 — v0.2.8 spec 단계에서 압축)
+
+**A. Free text search**:
+- inbox 헤더 search box → raw_text + summary + tags 인덱스 검색
+- SQLite FTS5 (Full-Text Search) 활용
+- 가장 작은 단위 첫걸음. 다른 옵션의 prerequisite.
+
+**B. Context-based recall**:
+- 시간: "이 시간대 (오전/오후)" 메모 추천
+- 요일: "이전 월요일" 메모
+- 태그 클러스터링 — 현재 active tag 와 연관 메모 surface
+- 사용자가 inbox 보고 있을 때 sidebar 또는 banner 로 노출
+
+**C. AI-driven 연관 메모 추천**:
+- 현재 보고 있는 메모와 의미 유사한 옛 메모 surface (embedding-based or LLM-judged)
+- "이거랑 비슷한 옛날 메모 N건" UI
+- Ollama embedding API 활용 가능 (모델 추가 부담)
+
+**D. 회고 view (일/주/월)**:
+- "지난 주 기록" 한 페이지 — N건 메모 + tag distribution + due date 진행
+- 정해진 시점 (월요일 아침 등) banner 또는 별도 라우트
+
+**E. Spaced repetition recall**:
+- Anki 같은 SM-2 알고리즘 — "잊을 만한 시점" 에 surface
+- 사용자가 confirm/dismiss → 다음 surface 시점 학습
+- 현 RecallBanner 의 발전형
+
+**F. 검색 + AI 자연어 query**:
+- "지난 달 회의 메모 보여줘" 자연어 → SQL or filter 자동 변환
+- Ollama function-calling 또는 prompt template
+- C/D 의 통합 진입점.
+
+### 결정 대기 (v0.2.8 brainstorm)
+
+- "획기적" 의 정의: 현 RecallBanner 점진 vs 새 핵심 surface
+- 1차 cut scope — A (search) 만으로 충분한 가치인가, B/C 와 묶어야 가치인가
+- AI 의존 (C/F) 의 latency / 정확도 trade-off — Ollama 추론 비용
+- F17/F18 의 status + 사유 데이터 가 recall 입력으로 활용 가능한가
+
+### 가설·측정
+
+- 본인 dogfood: 옛 메모 다시 본 횟수 / 캡처 수 비율 — 현재 < 5% 추정. ≥20% 까지 끌어올리는 것이 "획기적" 기준.
+- search 사용 빈도 (A 만 도입 시) — 일 1회 미만이면 의미 약함.
+
+### 범위
+
+- A: 3-4일 (FTS5 + UI search box).
+- B: 1주 + (시간/태그/요일 로직 + UI).
+- C: 2주 (embedding 인프라 추가 — Ollama embedding 모델 + 벡터 저장).
+- D: 1주 (회고 라우트 + aggregate query).
+- E: 2주 (SM-2 + UI + 사용자 feedback loop).
+- F: C 위에 추가 1주.
+
+→ 한 cut 에 다 넣기 무리. v0.2.8 = A + D 또는 A + B 권장. C/E/F 는 v0.3+.
+
+### 영향
+
+- 핵심 가치 (capture → 의미 있는 보존 → 다시 보기) 의 후반 절반 완성.
+- F1 (Due Date), F4 (Aha Moment), F17 (status 분기) 모두 recall 강화로 가치 ↑.
+- v0.4 slice 종료 조건 (본인 2주 dogfood 완주) 의 1주차 검증 항목 = recall 효과 측정.
+- 큰 cut — separate spec 이 자연스러움.
+
+---
+
+## F20. 기존 메모 본문 (raw_text) 수정 가능성 (🌱 raw — v0.2.8 후보, **load-bearing invariant 재검토**)
+
+**진행 상태:** 🌱 raw — 메모리 정책 `raw_text 불변` 재논의 트리거. v0.2.8 brainstorm 시 invariant 변경 여부 결정.
+
+**발견:** 2026-05-09 v0.2.7 release 후 본인 dogfood.
+
+### 관찰
+
+- 현재 NoteCard 의 EditableField 는 AI 결과 필드 (title / summary / tags) 만 수정 가능.
+- raw_text (capture 시점 원본 본문) 는 read-only — 메모리 정책상 **load-bearing invariant**.
+- dogfood 시 빈도 높은 use case:
+ - **오타 정정** — 빠른 capture 중 잘못 입력 (예: "회으" → "회의")
+ - **의미 보강** — 나중에 보니 정보 부족 ("회의" → "월요일 분기 회의 안건")
+ - **잘못된 캡처 정정** — 음성/clipboard 자동 입력 시 오인식
+
+### 제안 방향 (옵션 분석 — invariant trade-off)
+
+**A. raw_text 수정 허용** (invariant 폐기):
+- 가장 단순 — EditableField 가 raw_text 도 수정 가능.
+- 비용: capture 시점 원본 lost. AI 재실행 시 input 이 user-edited 본문 — 그 시점 의도와 다를 수 있음.
+- 영향: 다른 spec (F1 Due Date, F4 Aha Moment 등) 의 raw_text 불변 가정 재검토 필요.
+
+**B. raw_text 불변 유지 + `user_edited_text` 필드 추가**:
+- 원본 보존 + 사용자 정정 별도 컬럼.
+- NoteCard 가 user_edited_text 우선 표시 (없으면 raw_text fallback).
+- AI 재실행 시 어느 입력을 사용할지 결정 — 원본 (안정성) 또는 사용자 정정 (의도 정확성).
+- 마이그레이션 + UI 분기 비용.
+
+**C. raw_text 수정 허용 + revision history**:
+- `note_revisions` 테이블 — 변경 이력 보존.
+- 사용자가 옛 버전 회수 가능.
+- 비용 가장 큼 (스키마 + UI + 회수 흐름).
+
+**D. invariant 유지 (현 동작)** — 이 피드백 reject:
+- 정책 사유: capture = "기록", 수정 시 의미 본질 변경.
+- 그러나 dogfood 실용 마찰 클 가능성.
+
+### 결정 대기 (v0.2.8 brainstorm — 핵심 결정)
+
+- **invariant 재검토**: 본인 dogfood 1주 누적 시 raw_text 정정 욕구 빈도 측정. 주 ≥3건 면 옵션 A/B/C 검토 가치.
+- AI 재실행 input: raw_text vs user_edited — 메모리 정책 `AI 재실행은 user-edited 필드 덮어쓰기 금지` 와 정합 검토.
+- F1 (Due Date 파서), F4 (Aha Moment) 의 raw_text 가정 영향 분석.
+
+### 가설·측정
+
+- 본인 dogfood 1주: 메모 정정 욕구 발생 횟수 / 캡처 수. ≥10% 면 옵션 A 또는 B 강한 motivation.
+- 옵션 B 시 user_edited 사용 비율 — ≥30% 면 분기 가치.
+
+### 범위
+
+- A: 1일 (EditableField 가 raw_text 도 처리 + IPC 수정).
+- B: 3-4일 (스키마 마이그레이션 + UI 분기 + AI 재실행 정책 결정).
+- C: 1주 + (revisions 테이블 + UI 회수 흐름).
+
+### 영향
+
+- **load-bearing invariant 재검토** — 메모리 정책 갱신 가치.
+- F1 / F4 / F17 / F19 모두 raw_text 가정 재검토 영향.
+- 사용자 마찰 ↓ (오타/오인식 정정 가능) vs 기록 본질 약화 trade-off.
+- 옵션 B 가 가장 균형 — 원본 보존 + 사용자 정정 모두 가능.
+
+---
+
+## F21. 다기기 git-based 동기화 (🌱 raw — v0.2.8 후보, **부분 구현됨**)
+
+**진행 상태:** 🌱 raw — `SyncService` + `GitClient` 가 이미 push-only 형태로 존재. **양방향 동기화 + UI 구성** 이 누락된 핵심 부분. v0.2.8 brainstorm 시 명확한 cut.
+
+**발견:** 2026-05-09 v0.2.7 release 후 본인 dogfood. 사용자 표현: "그 중심에 git repo 를 쓸 수 있으면 좋겠어".
+
+### 관찰 (현재 동작)
+
+[src/main/services/SyncService.ts](src/main/services/SyncService.ts) 의 `sync()`:
+
+1. SQLite → markdown export (ExportService) 를 `/sync/` 에 산출
+2. `git add -A && git commit -m "chore(notes): sync " && git push`
+3. `not_configured` 시 skip (`/sync/` 가 git repo + origin remote 가져야 함)
+
+즉 **outbound only** — 다른 기기로 보내는 흐름은 있음.
+
+### 누락 부분 (다기기 동기화 충족 X)
+
+- **Pull**: 다른 기기에서 push 한 변경 가져오기 — `git fetch && git pull` 흐름 부재
+- **Re-import**: pull 한 markdown 을 SQLite 로 다시 적재 (`ImportService` 가 활용 가능 — F6 백업 복원 흐름과 유사)
+- **Conflict resolution**: 같은 노트를 두 기기에서 동시 수정 시 우선순위 + merge 정책
+- **Configure UI**: 사용자가 `/sync/` 에 git init + `git remote add origin ` 수동 — GUI 부재
+- **raw_text 불변 + user-edited 덮어쓰기 금지** (메모리 정책) 다기기 환경에서 어느 기기 user-edited 가 정답인가 결정 필요 — F20 (raw_text 수정 옵션 B `user_edited_text`) 와 강하게 연관
+
+### 제안 방향
+
+**A. SyncService 양방향화** (가장 작은 첫걸음):
+- `sync()` 가 push 전 pull 먼저 — `git fetch && git rebase origin/main` 또는 `merge`
+- pull 후 변경된 markdown → re-import 하여 SQLite 갱신
+- conflict 시 user prompt (또는 일단 fail + 수동 resolve 안내)
+
+**B. Configure UI** (설정 페이지 안 신규 sub-section 또는 별도 섹션):
+- "동기화 저장소 URL" 입력 → SyncService 가 `/sync/` 에 git init + remote add origin 자동
+- 인증 안내 (SSH key / token) — Gitea/GitHub 양쪽 호환
+- 마지막 sync 결과 + 시간 표시
+
+**C. Conflict resolution UX**:
+- 옵션 1: `git merge` 시도 → 실패하면 "양쪽 비교" UI (note id 단위, 각 기기 본문 + AI 결과 비교)
+- 옵션 2: timestamp 기반 자동 (마지막 수정 우선) — 데이터 lost risk
+- 옵션 3: "내 기기 우선" / "원격 우선" / "수동 merge" 사용자 선택
+
+**D. F20 (user_edited_text) 옵션 B 와 결합**:
+- raw_text = 캡처 시점 원본 (절대 충돌 X — capture 는 한 기기에서만)
+- user_edited_text = 다기기 sync 대상. timestamp + conflict resolution 적용
+- AI 결과 (title/summary/tags) = 어느 기기 가장 최근 결과 사용
+
+### 결정 대기 (v0.2.8 brainstorm)
+
+- 충돌 처리 정책 — A 옵션의 default (rebase / merge / fail) 결정
+- F20 invariant 결정 후 결합 — F20 옵션 B (`user_edited_text`) 가 채택되면 sync 가치 ↑
+- pull 후 re-import 시점 — manual ("지금 동기화" 클릭) vs 주기적 (5분/30분 자동)
+- 다기기 운영 시 Aha Moment metric (7일/3일) 측정 — sync lag 영향
+
+### 가설·측정
+
+- 본인 dogfood: Mac + Windows 두 기기 사용 시 (메모: 본인 Mac=업무, Windows=개인+dogfood) — 현재 single-device. 양방향 sync 후 Mac dogfood 가능성 측정.
+- conflict 발생 빈도 — 양 기기에서 동일 노트 수정 케이스 (낮을 것 추정).
+
+### 범위
+
+- A (양방향 sync) + B (Configure UI): 1주 spike 가능.
+- A + B + C (conflict UI): 2주.
+- D (F20 결합): F20 채택 후 추가 1주.
+
+### 영향
+
+- 다기기 운영 → Aha Moment metric 직접 기여 (Mac 업무 시간에도 capture 가능).
+- F20 (raw_text 수정) + F19 (recall) 모두 sync 데이터 일관성 의존.
+- v0.4 slice 종료 조건 (본인 2주 dogfood 완주) 의 핵심 인프라 — 단일 기기 dogfood 의 한계 극복.
+
---
## F22. NoteCard 이미지가 회색 placeholder 만 표시 (🚀 promoted → docs/superpowers/specs/2026-05-09-v028-cut-a-design.md)
@@ -1375,9 +1710,264 @@ app.on('activate', () => {
- v0.2.8 narrow scope 에 포함 가치 (1-2일 작업).
---
+## F23. 로컬 LLM 활성화 옵션 (Ollama-less 모드) (🌱 raw — v0.2.8 후보, 큰 영향)
+
+**진행 상태:** 🌱 raw — Ollama 의존성 옵션화. v0.2.8 brainstorm 시 cut. F17/F19 와 연관.
+
+**발견:** 2026-05-09 v0.2.7 release 후 본인 dogfood. 사용자 표현: "Ollama 를 쓰지 못하는 환경을 위해 로컬 llm 활성화 옵션을 만들고, Ollama 를 안 쓰는 경우 그냥 원문만 저장하고 보여주도록".
+
+### 관찰
+
+- 현재 capture 흐름: `CaptureService.create()` → notes INSERT (ai_status='pending') → `pending_jobs` enqueue → AiWorker → Ollama 호출 → title/summary/tags 채움 → NoteCard 표시.
+- Ollama 의존 강제 — 회사 환경 / offline / low-resource device / 사용자 선호도 등 Ollama 안 쓰고 싶은 경우 옵션 부재.
+- 현재 Ollama 끊긴 동안 capture 는 가능 (notes INSERT + pending_jobs enqueue), 단 ai_status 가 'pending' 또는 'failed' 로 고착 → FailedBanner / OllamaBanner 가 지속 노출 (사용자 불필요한 마찰).
+
+### 제안 방향
+
+**핵심 설계: "AI 활성화" 토글 (default ON, OFF 시 raw-only 모드)**
+
+설정 페이지 → AI 제공자 섹션 → 새 토글 "AI 자동 처리 사용" (default ON).
+
+#### A. OFF 일 때 동작
+
+1. `CaptureService.create()` 가 notes INSERT 시 ai_status='disabled' (신규 enum value) + pending_jobs enqueue **skip**.
+2. AiWorker 가 'disabled' 상태 노트를 pull 안 함.
+3. NoteCard 표시:
+ - title = raw_text 첫 줄 (또는 빈 값) — fallback rendering
+ - summary = 빈 값 (UI 에서 hide)
+ - tags = 빈 배열 (tag filter 비활성)
+ - raw_text 그대로 노출 (이미 NoteCard 가 표시함)
+4. OllamaBanner / FailedBanner 비활성 (AI off 면 의미 없음).
+5. 트레이/banner 의 "지금 AI 처리" / "Ollama 재확인" surface 비활성.
+
+#### B. OFF → ON 전환 시
+
+기존 ai_status='disabled' 노트들 — 두 옵션:
+
+- **B1**: 기존 disabled 잔류 (사용자가 "지금 처리" 버튼 1회 눌러야 재처리). 안전.
+- **B2**: 자동 enqueue (모든 disabled → pending + pending_jobs INSERT). 사용자 의도 불일치 risk (옛 메모 갑자기 AI 처리되며 큐 폭증).
+
+추천: **B1** — 사용자 명시적 trigger 만 옛 노트 처리. 새 노트는 ON 이후 capture 분만 자동 처리.
+
+#### C. ON → OFF 전환 시
+
+- 큐의 pending 잔재 — drain (현 실행 중) + enqueue stop.
+- 옵션: pending → disabled 자동 변환 (대량 cleanup) vs 잔류 (다시 ON 시 재개).
+- 추천: 잔류 (사용자 의도 보존).
+
+### 결정 대기 (v0.2.8 brainstorm)
+
+- ai_status 새 enum 값 'disabled' vs 별도 컬럼 (notes.ai_enabled BOOLEAN)
+- B1 vs B2 default — B1 추천.
+- raw-only 모드의 NoteCard title fallback — raw_text 첫 줄 / 첫 N자 / 사용자 입력 prompt.
+- F19 (recall) 가 raw-only 모드에서도 동작 — tag 부재 시 시간 기반 candidate 만.
+- F17 (status 분기) 의 AI 자동 분류 (옵션 B) 가 raw-only 모드에서 비활성 — 정합 검토.
+- 처음 설치 시 default — 메모리 정책 "주 타깃 OS 는 Windows + 본인 dogfood 우선" 고려, default ON 유지 (LAN Ollama 가정).
+
+### 가설·측정
+
+- 본인 dogfood: 회사 환경 (Mac 업무) 에서 Ollama 못 쓰는 시간 — raw-only 모드로 capture 만 가능해지면 dogfood metric 영향 측정.
+- 사용자 (외부) 가 raw-only 시작 후 ON 전환 비율 — onboarding 흐름 검증.
+
+### 범위
+
+- A 기본 (토글 + ai_status 'disabled' + capture skip): 2-3일.
+- B/C 전환 정책 (B1 추천 — 가장 작음): + 0.5일.
+- raw-only NoteCard fallback (title=raw_text 첫 줄): + 0.5일.
+- 합 3-4일 — v0.2.8 narrow scope 가능.
+
+### 영향
+
+- **Ollama 의존성 옵션화** — 환경 다양성 (회사 / offline / 저사양) 대응.
+- F17 (status 자동 분류 옵션 B) 무력화 — AI off 시 옵션 C (보관함만 별도) 가 default 흐름.
+- F19 (recall) 가 tag 데이터 부재로 단순화 — context-based / spaced repetition 기반 가능 (search 는 raw_text 가 인덱스 source).
+- F1 (Due Date 파서) 는 raw_text 정규식 기반이라 raw-only 모드에서도 동작 ✅.
+- 메모리 정책 "raw_text 불변" 의 가치 ↑ — raw_text 자체가 1차 surface 가 되므로 보존 의의 강화.
+- v0.4 slice 종료 조건 (본인 2주 dogfood 완주) 의 대안 경로 — Mac 업무 시간 raw-only capture + 저녁 Windows 에서 batch AI 처리 가능.
+
+---
+
+## F24. 이미지 멀티모달 AI 분석 (🌱 raw — v0.2.8/v0.3 후보, capability gated)
+
+**진행 상태:** 🌱 raw — Ollama vision 모델 (llava / llama3.2-vision / gemma3-multimodal 등) 활용. 사용자 표현: "가능할 경우만 하면 될 것 같다" — capability detection + opt-in 명시.
+
+**발견:** 2026-05-09 v0.2.7 release 후 본인 dogfood. F22 (이미지 렌더링) + F23 (Ollama-less 모드) 와 강하게 연관.
+
+### 관찰
+
+[src/main/ai/LocalOllamaProvider.ts:33-42](src/main/ai/LocalOllamaProvider.ts#L33-L42) — 현재 `/api/generate` 호출 시 text-only prompt:
+
+```ts
+const res = await request(`${this.endpoint}/api/generate`, {
+ prompt: buildPrompt(input.text, input.todayKst, input.dueDateCandidates, input.vocab ?? []),
+ ...
+});
+```
+
+`InferenceProvider.GenerateInput` 인터페이스에 `images` 필드 부재. Ollama API 자체는 `images: string[]` (base64) 지원하나 모델이 vision 지원해야 함.
+
+이미지가 있는 capture (paste 또는 첨부) 의 경우:
+- 현재: title/summary/tags 모두 raw_text 기반만 — 이미지 내용 ignore
+- 이미지만 있는 capture (raw_text 빈 값): AI 처리 무의미 → 사용자가 빈 메모 받음
+
+### 제안 방향
+
+**A. Vision capability detection + opt-in 모델 선택** (권장):
+1. 설정 페이지 → AI 제공자 섹션 → "이미지 분석 모델" 입력 (별도 필드, default 빈 값 = 비활성).
+2. main process 가 startup / 사용자 트리거 시 `GET /api/tags` 로 사용자 Ollama 의 모델 목록 조회 + vision capable 모델 (llava / llama3.2-vision / gemma3 family 등) 자동 감지 → 추천 표시.
+3. 사용자가 vision 모델 명시 + 본인 cluster 에 해당 모델 pull 되어 있어야 enable.
+4. capability 부재 (모델 없음 / endpoint 끊김 / 빈 값) 면 vision 분석 skip — 텍스트만 + raw_text 처리.
+
+**B. 분석 흐름 (vision enabled 시)**:
+
+각 capture 의 이미지 + raw_text 결합 prompt 전송:
+
+- raw_text 있음 + 이미지 있음 → "다음 텍스트 + 이미지를 종합 요약" prompt
+- raw_text 빈 값 + 이미지만 → "이미지 내용 요약 + 한국어 태그" prompt
+- AI 응답 형식은 기존 (`title`/`summary`/`tags`) 그대로 — vision 결과가 raw_text 자리에 들어가 자연스럽게 채워짐
+
+**C. InferenceProvider 인터페이스 확장**:
+
+```ts
+interface GenerateInput {
+ text: string;
+ images?: Array<{ base64: string; mime: string }>;
+ ...
+}
+```
+
+`LocalOllamaProvider` 가 `images` 비어 있지 않으면 Ollama `/api/generate` 의 `images: [base64...]` 필드 추가 + vision 모델로 호출. provider 가 vision capability 부재 시 images ignore (graceful degrade).
+
+### 결정 대기 (v0.2.8/v0.3 brainstorm)
+
+- vision 모델 default 추천 — 한국어 + 이미지 동시 잘하는 모델 (llama3.2-vision / gemma3 family 등 — dogfood 검증 후 결정)
+- 이미지 base64 변환 위치 — main process (MediaStore 가 이미 file path 보유) 가 자연스러움
+- 처리 비용 — vision 모델 추론 시간 (수 초~수십 초) + 메모리 부담. capture 흐름 backend 처리 라 사용자 대기 X.
+- raw-only 모드 (F23 OFF) 와 정합 — F23 토글 OFF 시 vision 도 OFF (자명).
+- 이미지 alt text 자동 생성 (F22 가설 슬롯) 가능 — vision 모델이 alt 도 같이 출력하도록 prompt 설계.
+
+### 가설·측정
+
+- 본인 dogfood: capture 시 이미지 첨부 비율 — 현재 추정 < 일 1건. 일 ≥ 1건 누적 + vision 분석 결과 정확도 측정.
+- vision 결과의 사용자 수정 비율 — 높으면 모델 부적합 (다른 모델 / prompt 튜닝).
+- "이미지만 있는 capture" 의 처리 가능성 — vision 으로 의미 있는 title/summary 생성 가능한지 검증.
+
+### 범위
+
+- A (capability detection + 설정) + B (vision prompt) + C (InferenceProvider 확장): 1주.
+- 이미지 alt text 자동 생성: + 0.5일.
+- F22 (이미지 렌더링) 가 선행 prerequisite — 같이 묶으면 1.5주.
+
+### 영향
+
+- 멀티모달 capture — "사진만 찍고 끝" 흐름 가능 (회의 화이트보드 / 영수증 / 메뉴판 등).
+- F22 (이미지 렌더링) 의 가치 ↑ — 보이는 이미지 + AI 가 의미 추출.
+- F19 (recall) 강화 — vision-derived tags + summary 가 이미지 내용 기반 검색 가능하게 함.
+- F23 (Ollama-less 모드) 와 trade-off — AI 토글 OFF 시 vision 도 자동 OFF (자명, 추가 분기 X).
+- 메모리 정책 "raw_text 불변" 그대로 — vision 결과는 summary/tags 에만 반영.
+- Ollama 의존성 ↑ (vision 모델 추가 pull 부담) — F23 OFF 시 회피 가능.
+
+---
+
+## F25. 사이드바 — 메모 리스트 + 메모 저장소 리스트 (🌱 raw — v0.2.8/v0.3 후보, layout 큰 변화)
+
+**진행 상태:** 🌱 raw — layout 재구성 + "저장소" 정의 필요. v0.2.8 brainstorm 시 F17/F21 와 함께 triage.
+
+**발견:** 2026-05-09 v0.2.7 release 후 본인 dogfood. 사용자 표현: "사이드에 메모 리스트, 메모 저장소 리스트도 보여줬으면".
+
+### 관찰
+
+- 현재 inbox layout = single-pane (header + NoteCard list). 사이드바 부재.
+- "메모 저장소" 의미 모호 — 추정 옵션:
+ - (a) **다중 profile/database** — "회사 메모 / 개인 / 학습" 등 분리. 현재 single profile (`/default/`).
+ - (b) **카테고리/폴더** — 단일 DB 안 그룹화. 현재 tag 만 존재.
+ - (c) **다중 sync repo** — F21 의 git sync 가 여러 remote 가능. 본인 + 외부 collaborator.
+- 사용자 의도 = (a) 가 가장 자연스러움 ("저장소" 표현). 본인이 dogfood 중 분리 욕구 발생 추정.
+
+### 제안 방향 (사이드바 + 저장소 모델 분리 결정)
+
+#### 1. 사이드바 layout
+
+좌측 또는 우측 column. 폭 240-320px. 상단: 저장소 selector. 하단: 메모 list (현 inbox compact view).
+
+```
+┌───────────┬────────────────────────────┐
+│ 저장소 │ inbox / 휴지통 (탭) │
+│ • 기본 ├────────────────────────────┤
+│ • 회사 │ │
+│ • 학습 │ NoteCard detail or grid │
+├───────────┤ │
+│ 메모 리스트│ │
+│ - title 1│ │
+│ - title 2│ │
+│ - title 3│ │
+└───────────┴────────────────────────────┘
+```
+
+#### 2. "저장소" 정의 (큰 결정)
+
+**A. 다중 profile** (`//` 별도 DB):
+- 가장 정의 명확 — sqlite db / media / sync 모두 저장소 단위 분리
+- 사용자가 새 저장소 생성 → migrations 새로 적용 → 빈 DB
+- 메모 / 태그 / due / pending_jobs 모두 저장소 안 격리
+- 마이그레이션 부담 — `resolveProfilePaths` 가 'default' fixed 인 부분 다중 지원
+- AiWorker / HealthChecker / SyncService 등 모두 active profile 기반 재초기화 필요 (큰 refactor)
+
+**B. 카테고리/폴더** (단일 DB 안 `notebook_id` 컬럼):
+- 가벼움 — schema 추가만, services 영향 적음
+- 단점: "회사 메모 따로 백업/sync" 불가 (단일 DB 백업)
+- F17 (status 분기) 와 비슷한 의미 layer (status + notebook 두 분기 가치 충돌 가능)
+
+**C. 단일 profile + 다중 git remote** (F21 sync 관련):
+- 한 DB → 여러 sync 대상 (회사 git + 개인 git)
+- "저장소" 가 sync target 의미라면 자연스러움
+- 데이터 자체는 분리 안 됨 — "메모 저장소" 이름과 의미 불일치 risk
+
+추천: **A** — "저장소" 의 사용자 의도가 데이터 분리 (회사/개인) 라면 명확. 그러나 큰 refactor.
+대안: **B** — 빠른 구현, 사용자 의도 (a) 일부 충족.
+
+#### 3. 메모 리스트 (사이드바 안)
+
+현 inbox NoteCard list 의 compact 버전 — title + tag chip 만, raw_text/summary hide. 클릭 시 main 에서 NoteCard expand.
+
+main pane 의 view 옵션:
+- (i) 단일 detail (사이드 클릭으로 전환)
+- (ii) 현재처럼 NoteCard grid (사이드는 scroll/jump 용)
+
+추천: (ii) — 현 흐름 보존, 사이드바는 navigation 보조.
+
+### 결정 대기 (v0.2.8 brainstorm)
+
+- "저장소" 정의 — A/B/C 중 사용자 의도 확인 (직접 묻거나, dogfood 중 욕구 정확히 파악)
+- 사이드바 토글 — default visible vs hide-by-default (좁은 화면 + 단일 저장소 시 noise)
+- 메모 리스트 sort 기준 — 최신순 / tag / due date
+- F17 (status 분기), F19 (recall search) 와 layout 조화 — 검색 박스 위치 (사이드 vs header)
+
+### 가설·측정
+
+- 본인 dogfood: "회사" / "개인" 메모 분리 욕구 빈도 — 1주 soak 후 측정. 빈도 낮으면 옵션 B 또는 C 충분.
+- 사이드바 사용 빈도 — main pane 만으로 동작 가능하면 사이드바 noise.
+
+### 범위
+
+- A (다중 profile + 사이드바 + 저장소 selector): 2-3주. AiWorker / HealthChecker / SyncService / 마이그레이션 모두 영향.
+- B (notebook_id + 사이드바): 1주. schema + repo 메서드 + UI.
+- C (다중 sync remote + 사이드바 navigation 만): 0.5주 (F21 의 일부).
+- 사이드바 자체 (저장소 결정 무관, navigation 기능만): 2-3일.
+
+### 영향
+
+- **layout 큰 변화** — 현재 single-pane → two-pane. 좁은 화면 (1280×720 dev) 영향 검증 필요.
+- F17 (status 분기) 와 conceptual overlap — "저장소" + "status" + "tag" 세 분기 layer 가 사용자 정신 부담.
+- F19 (recall search) — 사이드바에 search box 둘지 결정.
+- F21 (git sync) — 옵션 A 시 저장소별 별도 sync repo 자연스러움.
+- 본인 dogfood metric — Mac 업무 (회사 메모) + Windows 개인 (dogfood) 분리 시 가치 ↑.
+
+---
+
## (다음 항목 자리)
-새 피드백 추가 시 `## F23. 짧은 제목 (🌱 raw)` 헤더로 시작. 표준 슬롯 6개 채우거나 비워둔 채 시작 가능.
+새 피드백 추가 시 `## F26. 짧은 제목 (🌱 raw)` 헤더로 시작. 표준 슬롯 6개 채우거나 비워둔 채 시작 가능.
v0.2.8 release 후 dogfood ≥1주 soak 동안 새 발견 항목들 여기 누적 → v0.2.9 brainstorm 트리거.
diff --git a/docs/superpowers/specs/2026-05-09-v0210-cut-c-design.md b/docs/superpowers/specs/2026-05-09-v0210-cut-c-design.md
new file mode 100644
index 0000000..962b09c
--- /dev/null
+++ b/docs/superpowers/specs/2026-05-09-v0210-cut-c-design.md
@@ -0,0 +1,204 @@
+# v0.2.10 — Cut C Design (raw_text 수정 + revision history)
+
+**작성일:** 2026-05-09
+**선행 문서:**
+
+- `docs/superpowers/specs/2026-04-25-dogfood-feedback.md` (F20)
+- `docs/superpowers/strategy/v028plus-roadmap.md` Cut C
+
+**Cut 라벨:** v0.2.10 — load-bearing invariant 변경 (raw_text 불변 폐기 + revision history). semver 엄밀히 minor 이지만 v0.2.x 관습.
+
+---
+
+## 1. Cut 정체성
+
+메모리 정책 `raw_text 불변` invariant 폐기 + 변경 이력 (revision) 보존. 사용자가 raw_text 자유 수정 + 옛 버전 회수 가능.
+
+**load-bearing 정책 변경**:
+
+- 옛: `raw_text 불변` (capture 시점 원본 영구 보존)
+- 새: `raw_text 가변` + `note_revisions 테이블` (옛 버전 모두 보존, rollback 가능)
+
+이는 F1 / F4 / F17 / F19 의 raw_text 가정에 영향 — 모두 current latest raw_text 기준으로 동작 (시간 경과 시 정정된 값 사용).
+
+---
+
+## 2. 범위
+
+| 항목 | 결정 |
+|---|---|
+| **F20** | C 옵션 — raw_text 수정 허용 + `note_revisions` 테이블 + 옛 버전 회수 UI. AI 재실행 input = current latest raw_text (B 옵션). |
+
+---
+
+## 3. Schema 마이그레이션 (m005)
+
+```sql
+CREATE TABLE note_revisions (
+ rev_id INTEGER PRIMARY KEY AUTOINCREMENT,
+ note_id TEXT NOT NULL,
+ raw_text TEXT NOT NULL,
+ edited_at TEXT NOT NULL,
+ edited_by TEXT NOT NULL DEFAULT 'user', -- 'user' or 'capture' (최초 캡처)
+ FOREIGN KEY (note_id) REFERENCES notes(id) ON DELETE CASCADE
+);
+
+CREATE INDEX idx_note_revisions_note_id ON note_revisions(note_id, edited_at DESC);
+
+-- 기존 notes 의 모든 raw_text 를 첫 revision 으로 backfill
+INSERT INTO note_revisions (note_id, raw_text, edited_at, edited_by)
+ SELECT id, raw_text, created_at, 'capture' FROM notes;
+```
+
+`note_revisions.rev_id = AUTOINCREMENT` — chronological 순서 보장. `edited_by` = 'user' (사용자 정정) 또는 'capture' (최초).
+
+`notes.raw_text` 컬럼 그대로 — current latest 값. 검색 인덱스 (F19 FTS5) 가 이걸 source 로 사용 → revision 검색 X (latest only). YAGNI.
+
+---
+
+## 4. NoteRepository 메서드
+
+```ts
+class NoteRepository {
+ // 기존
+ insert(input: ...): Note; // 내부에서 note_revisions INSERT (edited_by='capture')
+
+ // 신규
+ updateRawText(id: string, newText: string, now: Date): void {
+ const tx = this.db.transaction(() => {
+ this.db.prepare(`UPDATE notes SET raw_text=?, updated_at=? WHERE id=?`).run(newText, now.toISOString(), id);
+ this.db.prepare(`INSERT INTO note_revisions (note_id, raw_text, edited_at, edited_by) VALUES (?, ?, ?, 'user')`).run(id, newText, now.toISOString());
+ });
+ tx();
+ }
+
+ listRevisions(id: string): NoteRevision[] {
+ return this.db.prepare(`SELECT * FROM note_revisions WHERE note_id=? ORDER BY edited_at DESC`).all(id) as NoteRevision[];
+ }
+
+ restoreRevision(id: string, revId: number, now: Date): void {
+ const rev = this.db.prepare(`SELECT raw_text FROM note_revisions WHERE rev_id=? AND note_id=?`).get(revId, id) as { raw_text: string } | undefined;
+ if (!rev) throw new Error(`revision ${revId} not found`);
+ this.updateRawText(id, rev.raw_text, now); // 새 revision 으로 복원 (linear history 유지)
+ }
+}
+```
+
+`restoreRevision` 은 옛 raw_text 를 **새 revision** 으로 INSERT — chain 끊지 않고 latest = restored. timestamp/순서 명확.
+
+---
+
+## 5. UI — NoteCard 수정 흐름
+
+### 5-1. raw_text 편집 UI
+
+기존 NoteCard 의 "원문 보기" 펼침 → 추가 "편집" 버튼:
+
+```tsx
+{rawOpen && (
+
+ {editingRaw ? (
+ <>
+
+)}
+```
+
+### 5-2. Revision 회수 UI
+
+"이력" 클릭 → modal 또는 확장 panel:
+
+```
+이력 (3 buah)
+[2026-05-12 14:30 사용자] 본문... [회수]
+[2026-05-10 09:15 사용자] 옛 본문... [회수]
+[2026-05-09 11:00 캡처] 최초 캡처 본문... [회수]
+```
+
+회수 클릭 → confirm dialog ("이 버전으로 되돌릴까요? 현재 본문도 이력에 보존됩니다.") → `restoreRevision()` 호출.
+
+---
+
+## 6. AI 재실행 정책
+
+**입력 = current notes.raw_text (latest)**. 옛 revision 은 AI 재실행 input X. 정책 일관 (사용자 정정 의도 반영).
+
+`AiWorker` 의 input 추출 코드는 변경 없음 — `notes.raw_text` 그대로 사용.
+
+---
+
+## 7. F1 (Due Date) / F4 (Aha Moment) / F17 / F19 영향
+
+| 영역 | 영향 |
+|---|---|
+| F1 Due Date 파서 | input = current raw_text. 사용자 정정 후 due 갱신 가능 — 정책 충실 (수정 시 의도 반영) |
+| F4 Aha Moment | capture 카운트 = notes 갯수. revision 갯수 무관 |
+| F17 status | 영향 X (raw_text 수정과 status 분기 독립) |
+| F19 search FTS5 | 인덱스 source = notes.raw_text (latest). revision 검색 미지원. 향후 cut 에서 옵션 |
+
+---
+
+## 8. IPC + types
+
+```ts
+// 신규
+'inbox:update-raw-text': (id: string, newText: string) => Promise<{ ok: true }>
+'inbox:list-revisions': (id: string) => Promise
+'inbox:restore-revision': (id: string, revId: number) => Promise<{ ok: true }>
+
+interface NoteRevision {
+ revId: number;
+ noteId: string;
+ rawText: string;
+ editedAt: string;
+ editedBy: 'user' | 'capture';
+}
+```
+
+---
+
+## 9. 테스트 전략
+
+| 영역 | 단위 |
+|---|---|
+| m005 마이그레이션 | 기존 notes → revision backfill (edited_by='capture') |
+| `updateRawText` | notes.raw_text 갱신 + 새 revision INSERT atomic |
+| `listRevisions` | DESC 순 + edited_by 정확 |
+| `restoreRevision` | 옛 raw_text 가 새 revision 으로 INSERT + notes.raw_text 갱신 |
+| 편집 UI | textarea 입력 + 저장 → IPC 호출 + store 갱신 |
+| 이력 modal | revision 목록 표시 + 회수 클릭 → confirm + IPC |
+| AiWorker input | current notes.raw_text 사용 (revision X) 회귀 |
+
+**목표**: 단위 490 → 약 505 (+15), typecheck 0.
+
+---
+
+## 10. Risk
+
+| Risk | 대응 |
+|---|---|
+| revision 무한 누적 (메모 1개당 100+ revision 시 DB bloat) | 향후 cut 에서 N개 cap 정책 (예: 최근 50개만 보존). 본 cut 은 unlimited |
+| 사용자가 실수로 옛 revision 회수 | confirm dialog 강제 |
+| F1 Due Date 가 raw_text 변경 시 재추출 안 함 | 별도 cut. 본 cut 은 raw_text 갱신 + 기존 due 잔류 (사용자 의도 보존) |
+| 메모리 정책 갱신 필수 | `project_inkling_status.md` 의 load-bearing invariant 갱신 |
+
+---
+
+## 11. 메모리 정책 갱신 (Cut C 머지 후 필수)
+
+`raw_text 불변` → `raw_text 가변 + revision 보존`. 메모 갱신:
+
+```
+- ~~raw_text 불변~~ → raw_text 가변 (사용자 편집 가능, note_revisions 테이블에 변경 이력 보존)
+- AI 재실행 input = current latest raw_text (옛 revision X)
+```
diff --git a/docs/superpowers/specs/2026-05-09-v0211-cut-d-design.md b/docs/superpowers/specs/2026-05-09-v0211-cut-d-design.md
new file mode 100644
index 0000000..23dea46
--- /dev/null
+++ b/docs/superpowers/specs/2026-05-09-v0211-cut-d-design.md
@@ -0,0 +1,228 @@
+# v0.2.11 — Cut D Design (FTS5 search + 회고 view)
+
+**작성일:** 2026-05-09
+**선행 문서:**
+
+- `docs/superpowers/specs/2026-04-25-dogfood-feedback.md` (F19)
+- `docs/superpowers/strategy/v028plus-roadmap.md` Cut D
+
+**Cut 라벨:** v0.2.11
+
+---
+
+## 1. Cut 정체성
+
+recall 핵심 가치 도달 — search + 회고 view. F19 의 6 옵션 중 **A (FTS5 search) + D (회고 view)** 2개. B/C/E/F 는 v0.3+ deferred.
+
+---
+
+## 2. 범위
+
+| 항목 | 결정 |
+|---|---|
+| **F19-A** | SQLite FTS5 인덱스 + inbox 헤더 search box |
+| **F19-D** | 일/주/월 회고 라우트 — aggregate query + N건 list + tag distribution + due 진행 |
+
+---
+
+## 3. F19-A 디테일 (FTS5)
+
+### 3-1. Schema 마이그레이션 (m006)
+
+```sql
+CREATE VIRTUAL TABLE notes_fts USING fts5(
+ note_id UNINDEXED,
+ raw_text,
+ title,
+ summary,
+ tags,
+ tokenize='unicode61'
+);
+
+-- 기존 notes 모두 인덱스
+INSERT INTO notes_fts (note_id, raw_text, title, summary, tags)
+ SELECT id, raw_text, title, summary, tags_csv FROM notes WHERE status != 'trashed';
+```
+
+`tokenize='unicode61'` — 한국어 partial tokenize 가능 (단어 boundary). 향후 `tokenize='porter unicode61'` 또는 한국어 전용 tokenizer (예: `mecab-ko-fts5`) 검토 가능 — Cut D 는 unicode61 default.
+
+`tags_csv` — notes.tags (JSON array) 를 csv 로 flatten 하여 인덱스 (예: `"기획 회의 결재"`).
+
+### 3-2. Trigger — auto-sync
+
+`notes` INSERT/UPDATE/DELETE 시 `notes_fts` 자동 sync:
+
+```sql
+CREATE TRIGGER notes_ai AFTER INSERT ON notes BEGIN
+ INSERT INTO notes_fts (note_id, raw_text, title, summary, tags)
+ VALUES (NEW.id, NEW.raw_text, NEW.title, NEW.summary, NEW.tags_csv);
+END;
+
+CREATE TRIGGER notes_ad AFTER DELETE ON notes BEGIN
+ DELETE FROM notes_fts WHERE note_id = OLD.id;
+END;
+
+CREATE TRIGGER notes_au AFTER UPDATE ON notes BEGIN
+ UPDATE notes_fts SET raw_text=NEW.raw_text, title=NEW.title, summary=NEW.summary, tags=NEW.tags_csv
+ WHERE note_id = NEW.id;
+END;
+```
+
+Cut C 의 `updateRawText` 가 `notes.raw_text` UPDATE → trigger 자동 발동 → FTS5 갱신.
+
+`tags_csv` 는 별도 generated column 또는 NoteRepository 에서 수동 갱신 (zod parse 후 csv join). YAGNI: 수동 갱신.
+
+### 3-3. NoteRepository.search
+
+```ts
+search(query: string, opts: { limit?: number; status?: NoteStatus }): Note[] {
+ const limit = opts.limit ?? 50;
+ const statusClause = opts.status ? `AND n.status = ?` : '';
+ const sql = `
+ SELECT n.* FROM notes n
+ JOIN notes_fts f ON n.id = f.note_id
+ WHERE notes_fts MATCH ? ${statusClause}
+ ORDER BY rank LIMIT ?
+ `;
+ const args = opts.status ? [query, opts.status, limit] : [query, limit];
+ return this.db.prepare(sql).all(...args) as Note[];
+}
+```
+
+`MATCH` 쿼리 syntax — FTS5 standard (`"기획 회의"`, `회의 OR 결재`, `기획*` 등).
+
+### 3-4. UI — inbox 헤더 search box
+
+기존 헤더 (Inbox/완료/보관/휴지통 탭) 옆에 search input:
+
+```tsx
+ setSearchQuery(e.target.value)}
+ style={{ ... }}
+/>
+```
+
+debounce 200ms → store action `searchNotes(query)` → `inboxApi.search(query, { status: currentView })` → result list 갱신.
+
+빈 query → 기본 inbox list 복귀.
+
+### 3-5. IPC
+
+```ts
+'inbox:search': (query: string, opts: { status?: NoteStatus; limit?: number }) => Promise
+```
+
+---
+
+## 4. F19-D 디테일 (회고 view)
+
+### 4-1. 라우트 추가
+
+`useInbox.view` enum 에 `'review-daily' | 'review-weekly' | 'review-monthly'` 추가. 진입점:
+
+- 헤더 메뉴: "📅 회고" 버튼 → 드롭다운 (일/주/월)
+- 또는 별도 라우트 (Settings 옆)
+
+### 4-2. 회고 view 컴포넌트
+
+```tsx
+// src/renderer/inbox/components/ReviewView.tsx
+export function ReviewView({ period }: { period: 'daily' | 'weekly' | 'monthly' }): ReactElement {
+ const data = useReviewData(period); // store action — aggregate query 결과
+ return (
+
+
{periodLabel(period)} 회고
+
총 N건 • 오늘 N건 • 평균 일 N건
+
+
+
+
+ );
+}
+```
+
+### 4-3. Aggregate query
+
+NoteRepository:
+
+```ts
+reviewAggregate(period: 'daily' | 'weekly' | 'monthly', now: Date): {
+ totalCount: number;
+ recentNotes: Note[];
+ tagCounts: Array<{ tag: string; count: number }>;
+ dueProgress: { total: number; passed: number; pending: number };
+} {
+ const cutoff = computeCutoff(period, now);
+ // 단일 transaction 안에 N개 query
+ const totalCount = this.db.prepare(`SELECT COUNT(*) as c FROM notes WHERE created_at >= ? AND status != 'trashed'`).get(cutoff).c;
+ const recentNotes = this.db.prepare(`SELECT * FROM notes WHERE created_at >= ? AND status != 'trashed' ORDER BY created_at DESC LIMIT 50`).all(cutoff);
+ // tagCounts — JSON tags array unnest → group by
+ // dueProgress — due_date 컬럼 + KST 비교
+ return { ... };
+}
+```
+
+`computeCutoff('daily', now)` = KST 자정. `'weekly'` = 7일 전 KST. `'monthly'` = 30일 전 KST.
+
+### 4-4. Tag distribution chart
+
+간단한 bar list (CSS — chart 라이브러리 X):
+
+```tsx
+{data.tagCounts.slice(0, 10).map(t => (
+
+ {t.tag}
+
+ {t.count}
+
+))}
+```
+
+### 4-5. Due progress
+
+```
+완료 (passed): 12 / 25
+대기 (pending): 13
+이번 주 due: 3건
+```
+
+---
+
+## 5. 테스트 전략
+
+| 영역 | 단위 |
+|---|---|
+| m006 마이그레이션 | FTS5 virtual table 생성 + 기존 notes backfill (status != 'trashed' 만) |
+| Trigger sync | INSERT/UPDATE/DELETE → notes_fts 자동 sync |
+| `search` | 한국어 token 매칭 + status filter |
+| inbox header search box | debounce + 빈 값 → 기본 list 복귀 |
+| ReviewView 단위 | aggregate query 결과 렌더 |
+| `reviewAggregate` | period 별 cutoff 정확 + tag count + due progress |
+
+**목표**: 단위 505 → 약 528 (+23), typecheck 0.
+
+---
+
+## 6. Risk
+
+| Risk | 대응 |
+|---|---|
+| FTS5 한국어 token 정확도 (unicode61 가 word boundary 부정확) | dogfood 검증. 부족 시 v0.3+ 에서 mecab-ko 또는 trigram tokenize 검토 |
+| FTS5 인덱스 size (notes 수만건 시 DB 크기 ↑) | 수만건 도달 전엔 무시. v0.3+ 에서 prune 또는 partial 인덱스 |
+| 회고 aggregate query latency | LIMIT 50 + index 활용 (`created_at DESC`). 수만건도 sub-second 예상 |
+| Cut C revision 추가 시 FTS 영향 | revision 은 인덱스 X (latest only). 정책 일관 |
+
+---
+
+## 7. v0.2.11 후
+
+**Cut E** (v0.3.0) — F21 양방향 sync.
+
+**dogfood verify**:
+
+1. search 일 사용 빈도 (가설: ≥ 일 1회면 가치 있음)
+2. 회고 view 사용 빈도 (월요일 자동 prompt 추가 검토 — v0.3+)
+3. FTS5 한국어 token 정확도 (사용자 query 결과 만족도)
diff --git a/docs/superpowers/specs/2026-05-09-v028-cut-a-design.md b/docs/superpowers/specs/2026-05-09-v028-cut-a-design.md
new file mode 100644
index 0000000..98fc5dc
--- /dev/null
+++ b/docs/superpowers/specs/2026-05-09-v028-cut-a-design.md
@@ -0,0 +1,226 @@
+# v0.2.8 — Cut A Design (이미지 렌더링 + 앱 아이콘)
+
+**작성일:** 2026-05-09
+**저자:** 김태현 (dlsrks0734@gmail.com)
+**선행 문서:**
+
+- `docs/superpowers/specs/2026-04-25-dogfood-feedback.md` (F22)
+- `docs/superpowers/strategy/v028plus-roadmap.md` (Cut A 분할 + 우선순위)
+
+**Cut 라벨:** v0.2.8 (semver patch — bug fix + asset 추가)
+
+---
+
+## 1. Cut 정체성
+
+**"이미지 렌더링 + 앱 아이콘 polish" cut.** 두 작은 항목 묶음:
+
+- **F22 (이미지 렌더링)**: NoteCard 의 회색 placeholder div 를 실제 `` 로 교체. Electron renderer 가 raw `file://` 직접 접근 어려운 보안 정책 우회 — `inkling-media://` custom protocol 등록.
+- **chore (앱 아이콘)**: 사용자 첨부 SVG (이미 `assets/icon.svg` 작성·검토 완료) → ICO/ICNS/PNG 다중 size 자동 생성 + electron-builder config 통합.
+
+명확/작은 작업, 의사결정 거의 없음. 빠른 release polish.
+
+---
+
+## 2. 범위
+
+| 항목 | 출처 | 작업 |
+|---|---|---|
+| **F22** | dogfood F22 | `inkling-media://` protocol + NoteCard `` + 클릭 시 OS viewer (`shell.openPath`) |
+| **chore** | roadmap | `electron-icon-builder` devDep + `npm run build:icons` + electron-builder config (`build.win.icon` / `build.mac.icon` / `build.linux.icon`) |
+
+---
+
+## 3. F22 디테일
+
+### 3-1. Custom protocol 등록
+
+`src/main/index.ts` 의 `whenReady` **이전** (top-level) 에 scheme 권한 등록:
+
+```ts
+import { protocol } from 'electron';
+
+protocol.registerSchemesAsPrivileged([
+ { scheme: 'inkling-media', privileges: { secure: true, supportFetchAPI: true, stream: true } }
+]);
+```
+
+`whenReady` 안에서 handler 등록:
+
+```ts
+import { promises as fs } from 'node:fs';
+import { join, normalize, sep } from 'node:path';
+
+protocol.handle('inkling-media', async (req) => {
+ const url = new URL(req.url);
+ const relPath = decodeURIComponent(url.pathname).replace(/^\//, '');
+ const mediaRoot = join(paths.profileDir, 'media');
+ const target = normalize(join(mediaRoot, relPath));
+ if (!target.startsWith(mediaRoot + sep)) {
+ return new Response(null, { status: 403 });
+ }
+ try {
+ const data = await fs.readFile(target);
+ return new Response(data, { headers: { 'content-type': inferMime(target) } });
+ } catch {
+ return new Response(null, { status: 404 });
+ }
+});
+```
+
+`inferMime()` — 파일 확장자 → MIME (png/jpg/jpeg/gif/webp). 작은 함수 (별도 util 또는 inline).
+
+### 3-2. NoteCard 갱신
+
+[src/renderer/inbox/components/NoteCard.tsx:336-338](src/renderer/inbox/components/NoteCard.tsx#L336-L338) 의 회색 div 를 `` 로 교체:
+
+```tsx
+{local.media.map((m) => (
+ inboxApi.openMedia(m.relPath)}
+ style={{
+ width: 48,
+ height: 48,
+ objectFit: 'cover',
+ borderRadius: 4,
+ cursor: 'pointer',
+ border: '1px solid #e0e0e0'
+ }}
+ />
+))}
+```
+
+`m.relPath` 형식 = `media//`. URL 형식: `inkling-media://media//`. handler 가 prefix 제거 후 `/media//` 으로 resolve.
+
+### 3-3. IPC `inbox:open-media`
+
+`src/main/ipc/inboxApi.ts` 에 신규 핸들러:
+
+```ts
+ipcMain.handle('inbox:open-media', async (_e, relPath: string) => {
+ const mediaRoot = join(paths.profileDir, 'media');
+ const target = normalize(join(mediaRoot, relPath));
+ if (!target.startsWith(mediaRoot + sep)) return { ok: false, reason: 'invalid path' };
+ await shell.openPath(target);
+ return { ok: true };
+});
+```
+
+preload 화이트리스트 + `src/shared/types.ts` `InboxApi.openMedia(relPath: string)` 시그니처 + `src/renderer/inbox/api.ts` wrapper.
+
+### 3-4. 보안 검토
+
+- **Path traversal**: protocol handler + IPC 핸들러 모두 `target.startsWith(mediaRoot + sep)` 검사. 통과 못 하면 403/실패.
+- **Schemes privileges**: `secure: true` 로 https 동등 권한 — webContents 가 페이지 안에서 `` 정상 로드.
+- **CORS**: same-origin 정책 영향 X (custom protocol 이라 별도). webContents 안 동일 origin 으로 인식.
+- **인증**: 단일 사용자 desktop app — 추가 인증 X.
+
+---
+
+## 4. chore 디테일
+
+### 4-1. 의존성 + scripts
+
+`package.json`:
+
+```json
+"devDependencies": {
+ "electron-icon-builder": "^2.0.1"
+},
+"scripts": {
+ "build:icons": "electron-icon-builder --input=assets/icon.svg --output=build --flatten"
+}
+```
+
+`--flatten` 옵션 = output 을 `build/icon.ico`, `build/icon.icns`, `build/icon.png` (1024x1024) 평면 배치. nested `build/icons/png/.png` 도 함께.
+
+### 4-2. electron-builder config
+
+`package.json` 의 `build` 블록 갱신:
+
+```json
+"win": { "icon": "build/icon.ico", ... },
+"mac": { "icon": "build/icon.icns", ... },
+"linux": { "icon": "build/icon.png", ..., "target": [ ... ] }
+```
+
+기존 win/mac/linux 블록에 `"icon"` 키만 추가 (다른 설정 그대로).
+
+### 4-3. 산출물 git 추적
+
+`build/` 가 `.gitignore` 에 있다면 — 두 옵션:
+
+- (a) **`build/icon.*` 만 ignore 풀고 commit** (size 약 200KB-1MB 작음 — 바이너리 commit 일반적). SVG 갱신 시 `npm run build:icons` 후 commit.
+- (b) **모두 ignore 유지** + `prebuild` script 등으로 빌드 시 매번 재생성. dist 빌드 시 자동 — 그러나 dev 환경 (npm start) 에서 아이콘 미생성 시 fallback 필요.
+
+추천: **(a)** — 단순, 빌드 시간 ↓, dev 환경 문제 X.
+
+`.gitignore` 갱신 예:
+
+```
+build/
+!build/icon.ico
+!build/icon.icns
+!build/icon.png
+```
+
+### 4-4. SVG 가 input 으로 바로 가능?
+
+`electron-icon-builder` v2.0.1 docs 검토 — PNG 1024x1024 입력 권장, SVG 는 `librsvg` 등 의존. SVG 직접 안 되면 `sharp` 로 SVG → PNG 1024 변환 후 input.
+
+대안 (SVG 직접 안 될 시):
+
+```json
+"build:icons:png": "node scripts/svg-to-png.mjs assets/icon.svg build/icon-source.png 1024",
+"build:icons": "npm run build:icons:png && electron-icon-builder --input=build/icon-source.png --output=build --flatten"
+```
+
+`scripts/svg-to-png.mjs` — `sharp` 활용 ~10줄 스크립트.
+
+---
+
+## 5. 테스트 전략
+
+| 영역 | 단위 | 수동 |
+|---|---|---|
+| protocol handler — path traversal | mock fs + URL 입력 (`../etc/passwd` 형태) → 403 | - |
+| protocol handler — 정상 200 | mock fs.readFile → bytes + content-type 검증 | - |
+| protocol handler — 404 | fs.readFile reject → 404 | - |
+| `inferMime` | 확장자별 정확 mapping | - |
+| NoteCard `` 렌더 | media 배열 길이 N → `` N 개 (jsdom mock) | - |
+| `` 클릭 → IPC | onClick stub → `inboxApi.openMedia` 호출 | - |
+| IPC `inbox:open-media` | path traversal mock → 'invalid path' 반환 | - |
+| 아이콘 빌드 | - | `npm run build:icons` → `build/icon.ico` `build/icon.icns` `build/icon.png` 존재 확인 |
+| Win exe 아이콘 | - | `npm run dist:win` → `Inkling Setup 0.2.8.exe` 우클릭 → properties → 아이콘 = 새 디자인 |
+| dogfood image flow | - | inbox 의 thumbnail 클릭 → OS viewer 열림 (Win + macOS) |
+
+**목표**: 단위 460 → 약 467 (+7), typecheck 0.
+
+---
+
+## 6. Risk + Known unknowns
+
+| Risk | 발생 시 대응 |
+|---|---|
+| `electron-icon-builder` SVG 직접 미지원 | `sharp` 로 SVG → PNG 1024 변환 (4-4 대안 적용) |
+| `protocol.handle` 가 Electron 41 미지원 (deprecated `protocol.registerFileProtocol` 만 있는 경우) | Electron 41 docs 확인 후 deprecated API 사용 또는 newer API |
+| `` 가 inkling-media:// 로드 실패 (CSP 차단 등) | webContents 의 contentSecurityPolicy 검토. v0.2.5/6 의 single-instance lock + B4 #46 hidden flag 와 무관 |
+| Win/Mac dogfood 시 OS viewer 가 default 미설정 | 사용자 OS settings — Inkling 책임 외 (그러나 안내 메시지 가능) |
+
+---
+
+## 7. v0.2.8 후
+
+**다음**: Cut B (v0.2.9) — F17 status 분기 + F18 사유 + F23 Ollama-less. 데이터 모델 정비 cut.
+
+**Cut A 머지 후 dogfood verify 항목**:
+
+1. inbox 의 capture-with-image 흐름 — 캡처 → 이미지 thumbnail 표시 → 클릭 → OS viewer
+2. 새 아이콘이 트레이 / Windows taskbar / dock 모두 정확 표시
+3. 다중 이미지 (capture 가 N개 첨부) 의 grid layout — flex-wrap 적용 시 N row 자연스러운지
+
+이슈 발견 시 dogfood-feedback.md F26 부터 누적.
diff --git a/docs/superpowers/specs/2026-05-09-v029-cut-b-design.md b/docs/superpowers/specs/2026-05-09-v029-cut-b-design.md
new file mode 100644
index 0000000..a28ae42
--- /dev/null
+++ b/docs/superpowers/specs/2026-05-09-v029-cut-b-design.md
@@ -0,0 +1,269 @@
+# v0.2.9 — Cut B Design (status 4분기 + 사유 + Ollama-less)
+
+**작성일:** 2026-05-09
+**선행 문서:**
+
+- `docs/superpowers/specs/2026-04-25-dogfood-feedback.md` (F17, F18, F23)
+- `docs/superpowers/strategy/v028plus-roadmap.md` Cut B
+
+**Cut 라벨:** v0.2.9 — semver 엄밀히 minor (새 status enum + onboarding wizard) 이지만 v0.2.x feature lane 관습 유지.
+
+---
+
+## 1. Cut 정체성
+
+데이터 모델 정비 cut. 메모의 의미 분기 (active / completed / archived / trashed) + 이동 시 사유 입력 + Ollama-less 모드 onboarding. 세 항목이 같은 schema 영역 (notes 테이블 + ai_status enum) 영향이라 한 cut.
+
+---
+
+## 2. 범위
+
+| 항목 | 결정 |
+|---|---|
+| **F17** | status 4분기 (`active`/`completed`/`archived`/`trashed`) + AI 자동 분류 버튼 (사유 입력 후 클릭 → AI 가 reason+raw_text 분석 → status 추천 → 사용자 confirm/dismiss) |
+| **F18** | 자유 텍스트 사유 (preset X — friction 최소). notes.move_reason 컬럼 또는 별도 trash_log 테이블 |
+| **F23** | 첫 launch wizard (Y/N) + Ollama 최적화 안내 + 설치 가이드 페이지 링크. ai_status='disabled' 신규 enum + capture skip + raw-only NoteCard fallback |
+
+---
+
+## 3. F17 디테일
+
+### 3-1. Schema 마이그레이션 (m004)
+
+```sql
+ALTER TABLE notes ADD COLUMN status TEXT NOT NULL DEFAULT 'active'
+ CHECK (status IN ('active', 'completed', 'archived', 'trashed'));
+ALTER TABLE notes ADD COLUMN status_changed_at TEXT;
+ALTER TABLE notes ADD COLUMN move_reason TEXT;
+
+-- 기존 deleted_at != NULL 노트 → status='trashed' migrate
+UPDATE notes SET status='trashed', status_changed_at=deleted_at
+ WHERE deleted_at IS NOT NULL;
+```
+
+`deleted_at` 컬럼 — backward compat 위해 잔류 (status='trashed' 와 동기화). 향후 cut 에서 제거 가능.
+
+### 3-2. 인터페이스
+
+```ts
+type NoteStatus = 'active' | 'completed' | 'archived' | 'trashed';
+
+interface Note {
+ // ... 기존 필드
+ status: NoteStatus;
+ statusChangedAt: string | null;
+ moveReason: string | null;
+}
+```
+
+NoteRepository 메서드:
+
+- `setStatus(id: string, status: NoteStatus, reason: string | null): void`
+- `listByStatus(status: NoteStatus, limit?: number): Note[]`
+- 기존 `restoreNote()` → `setStatus(id, 'active', null)` 으로 재구현
+
+### 3-3. UI — inbox 헤더 탭 4개
+
+기존 Inbox / 휴지통 2탭 → **Inbox / 완료 / 보관 / 휴지통** 4탭. 헤더 폭 좁아질 수 있어 short label + count badge.
+
+```tsx
+
+```
+
+`useInbox` store 의 `view: 'inbox' | 'completed' | 'archived' | 'trash' | 'settings'` enum 확장 (기존 `showTrash` boolean + `showSettings` boolean → enum 통합 권장 — 또는 boolean 3개 유지). enum 통합이 깔끔.
+
+### 3-4. NoteCard 액션 메뉴
+
+기존 휴지통 버튼 1개 → 메뉴 (설정 페이지 내부의 dropdown 또는 inline 버튼 group):
+
+- "완료로 이동"
+- "보관함으로 이동"
+- "휴지통으로 이동"
+
+각 클릭 → 사유 입력 modal (한 줄 textarea, 빈 값 허용) → 확인 → `setStatus(id, target, reason)`.
+
+### 3-5. AI 자동 분류 버튼
+
+사유 입력 modal 안 옵션:
+
+```
+사유: [____________________________]
+[ AI 자동 분류 ] [ 완료 ] [ 보관 ] [ 휴지통 ] [ 취소 ]
+```
+
+"AI 자동 분류" 클릭 → main 의 `ai:classify-status` IPC → AiWorker 가 prompt:
+
+```
+다음 메모와 사용자 사유를 보고 어디로 이동해야 할지 판단:
+- 메모 본문:
+- 메모 요약:
+- 사용자 사유:
+- 가능한 status: completed (작업 끝), archived (장기 보관, 회수 가능), trashed (불필요)
+JSON 출력: { "recommended": "completed|archived|trashed", "rationale": "..." }
+```
+
+응답 → 사용자에게 추천 + rationale 표시:
+
+```
+AI 추천: 완료
+이유: "처리됨" 표현 + 사용자 사유 "결재 끝" 일치
+[ 확정 ] [ 다른 status 선택 ]
+```
+
+확정 → setStatus 적용. 다른 선택 → preset 버튼 노출.
+
+### 3-6. IPC
+
+```ts
+// 신규
+'inbox:set-status': (id: string, status: NoteStatus, reason: string | null) => Promise<{ ok: true }>
+'ai:classify-status': (id: string, reason: string) => Promise<{ recommended: NoteStatus; rationale: string }>
+```
+
+---
+
+## 4. F18 디테일
+
+자유 텍스트 사유 입력 — F17 의 modal 안에 그대로 포함. 별도 컬럼 `notes.move_reason TEXT` (가장 마지막 사유 보존). 변경 이력 보존 시 별도 테이블 (`note_status_log`) 가능 but YAGNI — 마지막 사유만으로 충분.
+
+빈 값 허용 (preset X 정책 따라). 검색/필터 — 향후 cut (F19 search) 에서 검색 인덱스 포함 가능.
+
+---
+
+## 5. F23 디테일
+
+### 5-1. Schema
+
+ai_status enum 확장: `pending | processing | complete | failed | disabled`. 마이그레이션 m004 동일 commit:
+
+```sql
+-- ai_status 가 enum text 라 그대로 새 값 INSERT 가능. CHECK constraint 갱신:
+-- (SQLite 는 CHECK ALTER 직접 안 됨 → table 재생성 또는 trigger 추가)
+```
+
+SQLite CHECK 갱신 어려움 — 옵션:
+
+- (a) 기존 CHECK 제거 + 새 CHECK 추가 (table 재생성)
+- (b) CHECK 부재 + application-level 검증
+
+추천: (b) — application 검증 (zod schema). 마이그레이션 비용 ↓.
+
+### 5-2. Onboarding wizard
+
+첫 launch (settings.json 의 `onboarding_completed` flag 부재) 시 모달:
+
+```
+Inkling 사용 시작
+
+Inkling 은 로컬 LLM (Ollama) 으로 메모를 자동 정리합니다.
+Ollama 가 설치되어 있고 한국어 지원 모델 (gemma3, gemma2 등) 이 pull 되어 있어야 최적의 경험이 가능합니다.
+
+설치 가이드: https://ollama.com/download
+
+[ AI 자동 처리 사용 (Ollama 필요) ]
+[ 원문만 저장 (AI 처리 안 함) ]
+[ 나중에 설정 ]
+```
+
+3 옵션:
+
+- (1) AI 사용 → settings.ai_enabled=true + onboarding_completed=true
+- (2) 원문만 → ai_enabled=false + onboarding_completed=true
+- (3) 나중에 → onboarding_completed=false (다음 launch 다시 prompt — 하지만 capture 가능, ai_enabled=null=undefined → default true)
+
+추천: 3 옵션 모두 onboarding_completed=true 로 설정 + 사용자가 설정 페이지에서 언제든 변경. (3) 만 다시 prompt 면 friction.
+
+수정: 3 옵션 모두 close 시 onboarding_completed=true. (3) 은 default ai_enabled=true (LAN Ollama 가정 본인 dogfood).
+
+### 5-3. AI off 시 capture path
+
+CaptureService.create():
+
+```ts
+const aiEnabled = await settingsService.get('ai_enabled', true);
+if (!aiEnabled) {
+ // notes INSERT with ai_status='disabled' + skip pending_jobs enqueue
+ this.repo.insert({ ...input, ai_status: 'disabled' });
+ return { id, ... };
+}
+// 기존 path
+```
+
+### 5-4. NoteCard fallback
+
+ai_status='disabled' 노트 → title fallback = raw_text 첫 60자 (또는 첫 줄).
+
+```tsx
+const displayTitle = note.title?.trim() || note.rawText.split('\n')[0].slice(0, 60) || '(빈 메모)';
+```
+
+summary/tags hide. raw_text 그대로.
+
+### 5-5. Banner 비활성
+
+ai_enabled=false → OllamaBanner / FailedBanner 자동 비활성 (state 가 false 면 render skip). HealthChecker 도 ai_enabled=false 시 polling 중단.
+
+### 5-6. 설정 페이지 토글
+
+AI 제공자 섹션 상단 추가:
+
+```
+[ ] AI 자동 처리 사용
+
+ ↳ AI 처리를 사용하면 메모의 제목/요약/태그가 자동 생성됩니다.
+ Ollama 로컬 LLM 이 필요합니다. 설치 가이드: ollama.com/download
+```
+
+토글 OFF → ON 전환 시: onboarding wizard 와 동일 prompt 재노출 (간소화 — 그냥 endpoint 검증 후 결과 표시).
+
+### 5-7. 옛 노트 처리 (ON ↔ OFF 전환)
+
+**B1 정책 채택** (roadmap):
+
+- ON → OFF: 기존 pending 잔재 그대로 (드레인 후 enqueue stop)
+- OFF → ON: 기존 disabled 잔류 (사용자 명시 trigger 만 처리)
+
+설정 페이지 안 "기존 disabled 메모 N건 — 지금 모두 처리" 버튼 (ON 전환 후 disabled count > 0 시 노출).
+
+---
+
+## 6. 테스트 전략
+
+| 영역 | 단위 | 수동 |
+|---|---|---|
+| m004 마이그레이션 | mock db → status 컬럼 + 기존 deleted_at != NULL → trashed | - |
+| `setStatus` repo | 4 status 전환 + reason 저장 + statusChangedAt | - |
+| `listByStatus` | 각 status filter | - |
+| 4탭 UI 렌더 | view enum 4값 분기 + count badge | - |
+| 사유 입력 modal | 자유 텍스트 입력 + 빈 값 허용 + 4 status 버튼 | - |
+| `ai:classify-status` IPC | mock provider 응답 → recommended + rationale 반환 | - |
+| AI 자동 분류 UI | recommended 표시 + 확정 클릭 시 setStatus 호출 | - |
+| ai_status='disabled' enum | application zod 검증 + capture path skip pending_jobs | - |
+| Onboarding wizard | 첫 launch 시 표시 (settings 부재) + 3 옵션 결과 | 첫 launch 시 표시 확인 |
+| AI off 시 NoteCard | title fallback (raw 첫 줄), summary/tags hide | - |
+| AI off 시 Banner | render skip 회귀 | - |
+
+**목표**: 단위 467 → 약 490 (+23), typecheck 0.
+
+---
+
+## 7. Risk
+
+| Risk | 대응 |
+|---|---|
+| AI 자동 분류 정확도 낮음 | 추천만 표시, 사용자 confirm 강제. fallback = preset 4 status 버튼 |
+| status 4분기 + tag + reason layer 가 사용자 정신 부담 | dogfood 1주 측정. 사용 빈도 낮은 status 는 v0.2.10+ 에서 hide 옵션 |
+| Onboarding wizard 가 첫 launch 흐름 차단 | "나중에 설정" 옵션 제공 + close 가능 |
+| ai_enabled false 시 회귀 (기존 pending → 영원히 잔류) | 설정 페이지의 "기존 disabled 메모 N건 처리" 버튼 |
+
+---
+
+## 8. v0.2.9 후
+
+**Cut C** (v0.2.10) — F20 raw_text revision history. AI 재실행 input = current raw_text (latest revision).
+
+**dogfood verify**:
+
+1. 4탭 사용 빈도 (active/completed/archived/trashed) — 사용 안 되는 status 발견 시 cut B+1 에서 hide
+2. AI 자동 분류 정확도 (사용자 confirm 비율)
+3. Onboarding wizard 의 3 옵션 비율
diff --git a/docs/superpowers/specs/2026-05-09-v030-cut-e-design.md b/docs/superpowers/specs/2026-05-09-v030-cut-e-design.md
new file mode 100644
index 0000000..4d2a2e1
--- /dev/null
+++ b/docs/superpowers/specs/2026-05-09-v030-cut-e-design.md
@@ -0,0 +1,219 @@
+# v0.3.0 — Cut E Design (다기기 git-based 양방향 sync)
+
+**작성일:** 2026-05-09
+**선행 문서:**
+
+- `docs/superpowers/specs/2026-04-25-dogfood-feedback.md` (F21)
+- `docs/superpowers/strategy/v028plus-roadmap.md` Cut E
+
+**Cut 라벨:** v0.3.0 — semver MINOR (새 인프라 — 양방향 sync + Configure UI). Major 영역 진입.
+
+---
+
+## 1. Cut 정체성
+
+기존 push-only `SyncService` → 양방향 (pull + import + conflict resolution + Configure UI). 다기기 (Mac 업무 + Windows 개인) dogfood 가능.
+
+---
+
+## 2. 범위
+
+| 항목 | 결정 |
+|---|---|
+| **F21 옵션 A** | `git fetch && rebase` 후 markdown → SQLite re-import. 자동 rebase default (충돌 시 fail + 사용자 prompt) |
+| **F21 옵션 B** | 설정 페이지 안 "동기화 저장소" sub-section — URL 입력 + 인증 안내 + 마지막 sync 결과 |
+| **F21 옵션 C** | conflict UI — 자동 rebase 실패 시 양쪽 비교 + 사용자 선택 |
+| **pull 시점** | 양쪽 — manual ("지금 동기화") + 자동 주기 (사용자 설정 가능 interval, default 30분) |
+| **revision 결합 (Cut C)** | note_revisions 가 sync 대상 — 양 기기 rev 가 다른 chain 에 있으면 timestamp linear merge (옛 rev 가 sync source 로 inserted) |
+
+---
+
+## 3. SyncService 양방향화
+
+### 3-1. 갱신된 sync() 흐름
+
+```ts
+async sync(opts: { interval?: boolean } = {}): Promise {
+ if (!(await this.isConfigured())) return { ok: false, reason: 'not_configured' };
+
+ const git = new GitClient(this.syncDir);
+
+ // 1. fetch
+ const fetchR = await git.fetch();
+ if (fetchR.exitCode !== 0) return { ok: false, reason: `fetch failed: ${fetchR.stderr}` };
+
+ // 2. local export (변경 감지 위해)
+ await this.exportSvc.export(this.syncDir, { includeMedia: true });
+ await git.addAll();
+ const localChanged = await git.hasUncommittedChanges();
+
+ // 3. local commit (있으면)
+ let localSha: string | null = null;
+ if (localChanged) {
+ const c = await git.commit(`chore(notes): sync ${this.now().toISOString()}`);
+ localSha = c.sha;
+ }
+
+ // 4. rebase
+ const rebaseR = await git.rebaseOnto('origin/main');
+ if (rebaseR.exitCode !== 0) {
+ // conflict — abort + 사용자에게 conflict UI 안내
+ await git.rebaseAbort();
+ return { ok: false, reason: 'conflict', conflicts: await this.listConflicts() };
+ }
+
+ // 5. re-import (rebase 후 markdown 변경 → SQLite 적용)
+ const imported = await this.importSvc.importAll(this.syncDir);
+
+ // 6. push
+ const pushR = await git.push();
+ if (pushR.exitCode !== 0) return { ok: false, reason: `push failed: ${pushR.stderr}` };
+
+ return { ok: true, changed: localChanged || imported.changedCount > 0, localSha, importedCount: imported.changedCount, pushed: true };
+}
+```
+
+### 3-2. ImportService 활용
+
+기존 ImportService (백업 복원 흐름) 가 markdown → SQLite 적재. sync 의 re-import 도 같은 service 활용:
+
+```ts
+class ImportService {
+ async importAll(dir: string): Promise<{ changedCount: number; conflicts: string[] }> {
+ // dir 하위의 모든 .md 파일 → frontmatter parse → notes UPSERT
+ // existing note 와 비교 — updated_at 더 최신이면 갱신, 아니면 skip
+ // raw_text 다른 경우 → note_revisions 에 INSERT (new rev, edited_by='sync')
+ }
+}
+```
+
+**revision linear merge 정책**:
+
+- 옛 rev (origin/main 의 rev_5) 가 local 에 없으면 → INSERT note_revisions (timestamp 기준 적절 위치)
+- local rev 와 origin rev 가 동일 timestamp + 다른 raw_text → conflict (사용자 prompt)
+- 일반적으로 다른 timestamp 면 timestamp 순 linear chain 으로 merge
+
+### 3-3. GitClient 확장
+
+```ts
+class GitClient {
+ // 기존: run, isRepo, hasRemote, addAll, commit, push
+
+ // 신규
+ async fetch(): Promise;
+ async rebaseOnto(ref: string): Promise;
+ async rebaseAbort(): Promise;
+ async hasUncommittedChanges(): Promise;
+ async listConflicts(): Promise; // git diff --name-only --diff-filter=U
+}
+```
+
+---
+
+## 4. Configure UI (옵션 B)
+
+설정 페이지 → 신규 sub-section "동기화 저장소":
+
+```
+[동기화 저장소]
+저장소 URL: [git@gitea.example.com:user/inkling-notes.git]
+[ 저장 ] [ 연결 테스트 ]
+
+마지막 sync: 2026-05-09 14:32 (성공, 3건 가져옴, 2건 보냄)
+다음 자동 sync: 2026-05-09 15:02
+
+[ 자동 sync 사용 ]
+interval: [30] 분
+
+[ 지금 동기화 ] [ 충돌 해결... ]
+```
+
+저장소 URL 변경 → main 의 `settings:configure-sync` IPC 호출 → SyncService 가 `/sync/` 에 git init + remote add origin (없으면). 인증 (SSH key / token) 은 사용자 OS 설정 (`~/.ssh/` 또는 git credential helper) — Inkling 자체 인증 X, 안내 메시지만.
+
+---
+
+## 5. Conflict UI (옵션 C)
+
+자동 rebase 실패 시 SyncService 가 `{ ok: false, reason: 'conflict', conflicts: [...] }` 반환. 설정 페이지 의 "충돌 해결..." 버튼 활성화.
+
+클릭 → modal:
+
+```
+충돌 N건
+
+[note-id-1.md]
+< 내 기기 > | < 다른 기기 >
+본문 A | 본문 B
+ |
+[ 내 것 사용 ] [ 원격 사용 ] [ 양쪽 보존 (옛 revision 으로) ]
+```
+
+선택:
+
+- **내 것 사용**: local 채택 (origin 변경 폐기)
+- **원격 사용**: origin 채택 (local 변경 → note_revisions 에 보존)
+- **양쪽 보존**: local + origin 모두 note_revisions 에 INSERT, latest = 사용자 선택 (또는 timestamp 더 최신)
+
+확정 → SyncService.resolveConflict(noteId, choice) → git rebase --continue → push.
+
+---
+
+## 6. 자동 주기 sync
+
+main process 가 settings.sync_interval_min (default 30) 마다 `SyncService.sync({ interval: true })` 호출. interval=true 시 conflict 발생해도 silent (notification 만, 사용자가 다음 manual 또는 conflict UI 진입 시 처리).
+
+settings: `sync_auto_enabled: boolean` (default true 단, configured 일 때만), `sync_interval_min: number` (default 30, min 5).
+
+---
+
+## 7. IPC
+
+```ts
+// 신규
+'settings:configure-sync': (url: string) => Promise<{ ok: true } | { ok: false; reason: string }>
+'settings:test-sync-connection': () => Promise<{ ok: true } | { ok: false; reason: string }> // git ls-remote
+'sync:list-conflicts': () => Promise>
+'sync:resolve-conflict': (noteId: string, choice: 'local' | 'remote' | 'both') => Promise<{ ok: true }>
+'sync:get-status': () => Promise<{ lastAt: string | null; lastResult: SyncStatus | null; nextAt: string | null }>
+```
+
+---
+
+## 8. 테스트 전략
+
+| 영역 | 단위 |
+|---|---|
+| `GitClient.fetch / rebaseOnto / rebaseAbort` | mock execFile + 결과 검증 |
+| `SyncService.sync` 양방향 | mock GitClient + ImportService → 6 단계 흐름 |
+| 자동 rebase 성공 | conflict 없는 시나리오 |
+| 자동 rebase 실패 → abort | conflict 시 rebaseAbort + reason 반환 |
+| ImportService.importAll | markdown → notes UPSERT + revision INSERT |
+| revision merge | 양 chain → timestamp 순 linear |
+| Configure UI | URL 입력 → IPC → git init/remote add |
+| Conflict UI | 3 choice 별 sync 동작 |
+| 자동 주기 sync | timer + interval=true mode |
+
+**목표**: 단위 528 → 약 555 (+27), typecheck 0.
+
+---
+
+## 9. Risk
+
+| Risk | 대응 |
+|---|---|
+| 인증 설정 실패 (사용자 SSH key 부재) | Configure UI 의 "연결 테스트" 버튼 — git ls-remote 결과 사용자에게 표시 |
+| revision linear merge 정확도 | timestamp 단조 증가 가정 (양 기기 시계 동기화). NTP 부재 시 충돌 risk → 사용자 prompt |
+| 자동 주기 sync 의 silent 충돌 누적 | interval mode 충돌 시 notification + 충돌 UI 자동 popup option |
+| Cut C revision history 와 sync 결합 시 chain 분기 | 본 cut 의 정책: timestamp linear, branch 분기 미지원 (사용자 manual 결정으로 처리) |
+
+---
+
+## 10. v0.3.0 후
+
+**Cut F** (v0.3.1) — F24 멀티모달 vision.
+
+**dogfood verify**:
+
+1. Mac + Windows 양 기기 sync 1주 — 충돌 빈도 측정
+2. 자동 주기 sync 의 timing — battery / network 영향
+3. revision merge 정확도 (사용자 confirm 비율)
diff --git a/docs/superpowers/specs/2026-05-09-v031-cut-f-design.md b/docs/superpowers/specs/2026-05-09-v031-cut-f-design.md
new file mode 100644
index 0000000..ae5767b
--- /dev/null
+++ b/docs/superpowers/specs/2026-05-09-v031-cut-f-design.md
@@ -0,0 +1,246 @@
+# v0.3.1 — Cut F Design (멀티모달 vision AI)
+
+**작성일:** 2026-05-09
+**선행 문서:**
+
+- `docs/superpowers/specs/2026-04-25-dogfood-feedback.md` (F24)
+- `docs/superpowers/strategy/v028plus-roadmap.md` Cut F
+
+**Cut 라벨:** v0.3.1 — patch (vision 추가, 기존 기능 영향 X)
+
+---
+
+## 1. Cut 정체성
+
+Ollama vision 모델 (gemma3 family default) 활용 — 이미지 + raw_text 결합 prompt 또는 이미지 단독 분석 → title/summary/tags 자동 생성. F22 prerequisite (Cut A) 이미 완료.
+
+---
+
+## 2. 범위
+
+| 항목 | 결정 |
+|---|---|
+| **F24 default 모델** | gemma3 family (한국어 + 이미지 둘 다 강함, 본인 메모 `gemma4:e4b` 텍스트 모델과 같은 가족) |
+| **prompt 모드** | 단일 vision 모델 호출 (vision 모델이 텍스트도 처리). 모델 capability 부족 시 2단계 fallback (자동) |
+| **capability detection** | app launch 시 1회 + 설정 페이지 manual refresh 버튼 |
+| **F23 OFF 시 자동 OFF** | `ai_enabled=false` → vision 도 자동 OFF (자명) |
+
+---
+
+## 3. Capability Detection
+
+### 3-1. Ollama API 활용
+
+`GET /api/tags` → 사용자 Ollama instance 의 모델 목록. response:
+
+```json
+{
+ "models": [
+ { "name": "gemma4:e4b", "details": { "family": "gemma" } },
+ { "name": "gemma3:12b-vision", "details": { "family": "gemma3", "families": ["gemma3"] } },
+ { "name": "llava:13b", "details": { "family": "llava" } }
+ ]
+}
+```
+
+vision capable 판정 — 모델 이름 또는 family 기반:
+
+```ts
+const VISION_FAMILIES = new Set(['gemma3', 'llava', 'llama3.2-vision', 'minicpm-v', 'pixtral']);
+const VISION_NAME_HINTS = ['vision', 'vl', 'multimodal', 'gemma3'];
+
+function isVisionCapable(model: { name: string; details?: { family?: string; families?: string[] } }): boolean {
+ if (model.details?.family && VISION_FAMILIES.has(model.details.family)) return true;
+ if (model.details?.families?.some(f => VISION_FAMILIES.has(f))) return true;
+ return VISION_NAME_HINTS.some(h => model.name.toLowerCase().includes(h));
+}
+```
+
+### 3-2. Settings storage
+
+```ts
+interface SettingsSchema {
+ // ... 기존
+ vision_model?: string; // 사용자 명시 모델 (빈 값 = 비활성)
+ vision_capable_cache?: string[]; // launch 시 detected 결과 cache
+ vision_cache_at?: string; // ISO timestamp
+}
+```
+
+### 3-3. AppLaunchDetect
+
+```ts
+// src/main/index.ts whenReady 안 (settings 초기화 후)
+async function refreshVisionCache(): Promise {
+ if (!settingsService.get('ai_enabled', true)) return;
+ try {
+ const tags = await fetch(`${endpoint}/api/tags`).then(r => r.json());
+ const capable = tags.models.filter(isVisionCapable).map((m: any) => m.name);
+ settingsService.set('vision_capable_cache', capable);
+ settingsService.set('vision_cache_at', new Date().toISOString());
+ } catch {
+ // network fail — silent, cache 유지
+ }
+}
+
+void refreshVisionCache();
+```
+
+### 3-4. 설정 페이지 UI (AI 제공자 섹션 확장)
+
+```
+[AI 제공자]
+Endpoint: [http://localhost:11434]
+모델: [gemma4:e4b]
+
+[이미지 분석 모델 (선택사항)]
+[gemma3:12b-vision ▾] ← dropdown, 비어 있으면 비활성
+ 가능한 모델: gemma3:12b-vision, llava:13b, ...
+[ 다시 감지 ] 마지막 감지: 2026-05-09 14:30
+```
+
+dropdown — `vision_capable_cache` 결과 + 빈 옵션. "다시 감지" → `refreshVisionCache()` + UI 갱신.
+
+---
+
+## 4. InferenceProvider 확장
+
+### 4-1. 인터페이스
+
+```ts
+// src/main/ai/InferenceProvider.ts
+interface GenerateInput {
+ text: string;
+ images?: Array<{ base64: string; mime: string }>; // NEW
+ todayKst: string;
+ dueDateCandidates: string[];
+ vocab?: string[];
+}
+
+interface InferenceProvider {
+ generate(input: GenerateInput, opts?: { visionModel?: string }): Promise;
+ abort?(): void;
+}
+```
+
+### 4-2. LocalOllamaProvider 갱신
+
+```ts
+async generate(input: GenerateInput, opts?: { visionModel?: string }): Promise {
+ const useVision = !!opts?.visionModel && (input.images?.length ?? 0) > 0;
+ const model = useVision ? opts.visionModel : this.textModel;
+
+ const body: any = {
+ model,
+ prompt: useVision
+ ? buildVisionPrompt(input.text, input.todayKst, input.dueDateCandidates, input.vocab ?? [])
+ : buildPrompt(input.text, input.todayKst, input.dueDateCandidates, input.vocab ?? []),
+ stream: false,
+ format: 'json'
+ };
+ if (useVision) {
+ body.images = input.images!.map(i => i.base64);
+ }
+
+ const res = await request(`${this.endpoint}/api/generate`, body);
+ // ... 기존 parse
+}
+```
+
+### 4-3. buildVisionPrompt
+
+```ts
+function buildVisionPrompt(text: string, todayKst: string, dueCandidates: string[], vocab: string[]): string {
+ return `다음 메모와 첨부 이미지를 종합 분석해 한국어로 요약하세요.
+
+메모 본문 (비어 있을 수 있음):
+${text || '(이미지만 있음)'}
+
+이미지 분석 시 주요 시각적 정보 (텍스트, 사람, 장면) 도 포함해 요약하세요.
+출력 JSON: { "title": "...", "summary": "...", "tags": [...], "due_date": "..." }
+오늘: ${todayKst}
+가능한 due 후보: ${dueCandidates.join(', ')}
+빈출 태그: ${vocab.slice(0, 20).join(', ')}`;
+}
+```
+
+---
+
+## 5. AiWorker 통합
+
+CaptureService 가 capture 시 image 첨부했으면 → notes.media 에 저장 + pending_jobs INSERT. AiWorker 가 job 처리 시:
+
+```ts
+// src/main/ai/AiWorker.ts
+async processJob(noteId: string): Promise {
+ const note = this.repo.getById(noteId);
+ const media = this.repo.listMediaByNote(noteId);
+ const visionModel = this.settings.get('vision_model');
+
+ let images: Array<{ base64: string; mime: string }> | undefined;
+ if (visionModel && media.length > 0) {
+ images = await Promise.all(media.map(async (m) => ({
+ base64: (await fs.readFile(this.mediaStore.absolutePath(m.relPath))).toString('base64'),
+ mime: m.mime
+ })));
+ }
+
+ const provider = this.providerHolder.get();
+ const response = await provider.generate({ text: note.rawText, images, ... }, { visionModel });
+ // ... 기존 결과 적용
+}
+```
+
+`media.length > 0 && visionModel` 둘 다 true 일 때만 vision path. 그 외는 기존 text-only.
+
+---
+
+## 6. 이미지만 있는 capture
+
+`raw_text` 빈 값 + media 첨부만:
+
+- 기존 동작: notes INSERT (raw_text=''), AiWorker 가 빈 prompt 로 호출 → ai_status='failed' 또는 무의미 응답
+- vision enabled: AiWorker 가 vision prompt + images → 의미 있는 title/summary/tags 응답
+- vision disabled (visionModel 빈 값): notes 저장만, ai_status='disabled' 신규 enum 활용 (Cut B 의 ai_enabled false 와 비슷한 의미 — 그러나 부분 disable, 즉 "이미지 only 라 처리 불가" 상태)
+
+추천: vision disabled + image-only capture 시 `ai_status='skipped'` 신규 enum (Cut B 의 'disabled' 와 다름). title fallback = "(이미지 N개)" 또는 첫 이미지 파일명.
+
+---
+
+## 7. 테스트 전략
+
+| 영역 | 단위 |
+|---|---|
+| `isVisionCapable` | family / families / name hint 별 판정 |
+| `refreshVisionCache` | mock /api/tags → capable 추출 + settings 저장 |
+| 설정 페이지 dropdown | cache 기반 옵션 + "다시 감지" 클릭 → IPC |
+| `LocalOllamaProvider.generate` vision path | images 비어있음 → text-only / images 있음 + visionModel → vision body |
+| `buildVisionPrompt` | 빈 text + images 만 케이스 정확 prompt |
+| `AiWorker.processJob` vision integration | media + visionModel 있을 때만 base64 변환 |
+| 이미지 only capture | raw_text='' + media → vision 결과 정상 또는 'skipped' 분기 |
+
+**목표**: 단위 555 → 약 575 (+20), typecheck 0.
+
+---
+
+## 8. Risk
+
+| Risk | 대응 |
+|---|---|
+| vision 모델 추론 latency 큼 (수 초~분) | AiWorker backend 처리 — 사용자 대기 X. NoteCard 가 ai_status='processing' 표시 |
+| 이미지 base64 메모리 부담 | media 1개당 평균 < 1MB. 다중 이미지 시 N×base64 = 메모리 N배. cap (이미지당 max size 5MB) 적용 |
+| capability detection 실패 시 fallback | cache 부재 → vision dropdown 비어있음 표시 + "다시 감지" 안내 |
+| vision 모델 한국어 정확도 | dogfood 검증. gemma3 가 한국어 약하면 다른 family 추천 갱신 (메모리 정책 갱신) |
+| Ollama 가 vision images 필드 무시 (모델이 multimodal 미지원) | 자동 2단계 fallback — vision 모델로 caption 추출 → 텍스트 모델로 종합 (capability 부족 시) |
+
+---
+
+## 9. v0.3.1 후
+
+**Cut G** (v0.3.2) — F25 사이드바 + notebook_id.
+
+**dogfood verify**:
+
+1. 이미지 capture 빈도 (가설: 일 ≥ 1건 = vision 가치)
+2. vision 결과 사용자 수정 비율 (정확도 측정)
+3. capability detection 정확도 (false-positive / false-negative)
diff --git a/docs/superpowers/specs/2026-05-09-v032-cut-g-design.md b/docs/superpowers/specs/2026-05-09-v032-cut-g-design.md
new file mode 100644
index 0000000..baa4f43
--- /dev/null
+++ b/docs/superpowers/specs/2026-05-09-v032-cut-g-design.md
@@ -0,0 +1,227 @@
+# v0.3.2 — Cut G Design (사이드바 + notebook 카테고리)
+
+**작성일:** 2026-05-09
+**선행 문서:**
+
+- `docs/superpowers/specs/2026-04-25-dogfood-feedback.md` (F25)
+- `docs/superpowers/strategy/v028plus-roadmap.md` Cut G
+
+**Cut 라벨:** v0.3.2
+
+---
+
+## 1. Cut 정체성
+
+inbox layout 재구성 — 사이드바 + 메모 카테고리 (notebook). single-pane → two-pane. 단일 DB 안 `notebook_id` 컬럼 (옵션 B — 1주 scope, 다중 profile 옵션 A 는 v0.4+ 후보).
+
+---
+
+## 2. 범위
+
+| 항목 | 결정 |
+|---|---|
+| **F25 저장소 정의** | B — 카테고리/폴더 (notebook_id, 단일 DB 안 그룹화) |
+| **사이드바 가시성** | 사용자 토글 + last state 보존 (settings) |
+| **사이드바 내용** | 상단 notebook 목록 + 하단 메모 list (compact view) |
+
+---
+
+## 3. Schema 마이그레이션 (m007)
+
+```sql
+CREATE TABLE notebooks (
+ id TEXT PRIMARY KEY,
+ name TEXT NOT NULL,
+ color TEXT, -- accent color for UI (옵션)
+ created_at TEXT NOT NULL,
+ position INTEGER NOT NULL DEFAULT 0
+);
+
+INSERT INTO notebooks (id, name, created_at, position)
+ VALUES ('default', '기본', '2026-05-09T00:00:00Z', 0);
+
+ALTER TABLE notes ADD COLUMN notebook_id TEXT NOT NULL DEFAULT 'default'
+ REFERENCES notebooks(id) ON DELETE RESTRICT;
+
+CREATE INDEX idx_notes_notebook_status ON notes(notebook_id, status, created_at DESC);
+```
+
+기존 모든 notes → notebook_id='default'. 사용자가 새 notebook 생성 후 메모 이동 가능.
+
+`ON DELETE RESTRICT` — notebook 삭제 시 노트 잔류해야 함. notebook 삭제 흐름은 사용자가 명시 (메모 이동 후 삭제).
+
+---
+
+## 4. NotebookRepository
+
+```ts
+class NotebookRepository {
+ list(): Notebook[];
+ get(id: string): Notebook | undefined;
+ create(name: string, color?: string): Notebook;
+ rename(id: string, name: string): void;
+ delete(id: string): void; // notebook 안 메모 0건일 때만 (RESTRICT 위반 시 throw)
+ reorder(ids: string[]): void; // position 갱신
+ countNotes(id: string, opts?: { status?: NoteStatus }): number;
+}
+```
+
+NoteRepository 의 모든 query 에 `notebook_id` filter 추가:
+
+```ts
+listByStatus(status: NoteStatus, opts: { notebookId?: string; limit?: number }): Note[];
+moveToNotebook(noteId: string, notebookId: string): void;
+```
+
+---
+
+## 5. UI — 사이드바
+
+### 5-1. layout
+
+```
+┌──────────────┬───────────────────────────────────┐
+│ [≡] Inkling │ [Inbox(N) 완료(N) 보관(N) 휴지통(N)] [🔍 search] [⚙] │
+├──────────────┼───────────────────────────────────┤
+│ 노트북 │ │
+│ • 기본 (12) │ NoteCard list (current view) │
+│ • 회사 (5) │ │
+│ • 학습 (3) │ │
+│ + 새 노트북 │ │
+├──────────────┤ │
+│ 메모 빠른 list│ │
+│ - title 1 │ │
+│ - title 2 │ │
+│ - title 3 │ │
+│ ... │ │
+└──────────────┴───────────────────────────────────┘
+```
+
+폭: 240px (settings 의 `sidebar_width` 사용자 조정 가능, default 240, min 180, max 400).
+
+### 5-2. 토글
+
+헤더 좌측 햄버거 (`≡`) 버튼 → `useInbox.sidebarVisible` toggle. last state 저장 (`settings.sidebar_visible`).
+
+키보드 shortcut: `Ctrl+B` (또는 `Cmd+B` macOS) — 빠른 토글.
+
+### 5-3. Notebook 목록
+
+상단 panel — `NotebookRepository.list()` + 각 notebook 의 active 메모 count.
+
+- 클릭 → `useInbox.selectedNotebookId` 갱신 → main pane 의 NoteCard list 가 해당 notebook 만 표시.
+- 우클릭 → context menu: 이름 변경 / 색 변경 / 삭제 (메모 0건일 때만).
+- "+ 새 노트북" 버튼 → modal: name 입력 + color picker (선택사항) → create.
+
+### 5-4. 메모 빠른 list
+
+하단 panel — selected notebook + selected status (Inbox/완료/보관/휴지통 탭) 의 NoteCard 들의 compact view.
+
+- title + tag chip 1-2 개 + 시간 (relative — "2시간 전")
+- 클릭 → main pane 가 해당 NoteCard 위치로 scroll (또는 강조)
+
+main pane 의 NoteCard grid 와 사이드 빠른 list 는 동일 데이터 — 단지 view 다름. 사이드는 navigation, main 은 detail.
+
+### 5-5. NoteCard 갱신 — notebook 이동
+
+NoteCard 액션 메뉴 (Cut B 의 status 메뉴 옆):
+
+- "다른 노트북으로 이동" → notebook 목록 dropdown → 선택 → `moveToNotebook` IPC
+
+---
+
+## 6. store 갱신
+
+```ts
+interface InboxState {
+ // 기존
+ view: 'inbox' | 'completed' | 'archived' | 'trash' | 'review-daily' | 'review-weekly' | 'review-monthly' | 'settings';
+
+ // 신규 (Cut G)
+ notebooks: Notebook[];
+ selectedNotebookId: string;
+ sidebarVisible: boolean;
+ loadNotebooks: () => Promise;
+ selectNotebook: (id: string) => void;
+ createNotebook: (name: string, color?: string) => Promise;
+ renameNotebook: (id: string, name: string) => Promise;
+ deleteNotebook: (id: string) => Promise;
+ toggleSidebar: () => void;
+}
+```
+
+`refreshMeta` / `loadInitial` 가 notebooks 도 함께 fetch.
+
+---
+
+## 7. IPC
+
+```ts
+'inbox:list-notebooks': () => Promise
+'inbox:create-notebook': (name: string, color?: string) => Promise
+'inbox:rename-notebook': (id: string, name: string) => Promise<{ ok: true }>
+'inbox:delete-notebook': (id: string) => Promise<{ ok: true } | { ok: false; reason: string }>
+'inbox:move-to-notebook': (noteId: string, notebookId: string) => Promise<{ ok: true }>
+'inbox:reorder-notebooks': (ids: string[]) => Promise<{ ok: true }>
+```
+
+---
+
+## 8. F19 search 와 결합 (Cut D 후)
+
+search box — 사이드바 도입 후 위치 검토:
+
+- (a) **inbox 헤더 잔류** (Cut D 결정) — 단순. 사이드바 토글 무관.
+- (b) **사이드바 안 상단** — 사이드바 visible 일 때만 search. hidden 시 inbox 헤더 fallback.
+
+추천: (a) — Cut D 결정 보존, 사이드바 토글 무관. UX 일관.
+
+search 결과 — current selectedNotebookId 안만 또는 모든 notebook? settings 토글 또는 search options dropdown. 추천: 기본 current notebook 안 검색 + "모든 노트북에서 검색" 옵션.
+
+---
+
+## 9. 테스트 전략
+
+| 영역 | 단위 |
+|---|---|
+| m007 마이그레이션 | notebooks 테이블 + 'default' INSERT + notes.notebook_id backfill |
+| `NotebookRepository.list/create/rename/delete/reorder` | 각 메서드 |
+| `delete` RESTRICT | 메모 잔류 시 throw |
+| `moveToNotebook` | notebook_id 갱신 + 카운트 영향 |
+| 사이드바 토글 | store action + settings 저장 |
+| Notebook 목록 렌더 | count badge + 클릭 → selectedNotebookId 갱신 |
+| 메모 빠른 list | selectedNotebook + selectedView 필터 |
+| Notebook 생성 modal | name 입력 + color picker → create |
+| Notebook 삭제 | 메모 잔류 시 error 표시 |
+| search + notebook scope | 'current notebook' / 'all' 옵션별 필터 |
+
+**목표**: 단위 575 → 약 600 (+25), typecheck 0.
+
+---
+
+## 10. Risk
+
+| Risk | 대응 |
+|---|---|
+| 사이드바 폭이 좁은 화면 (1280×720) 에서 너무 큼 | default hidden 옵션? settings 의 width 조정 + 좁은 화면 시 자동 hide |
+| Notebook 삭제 시 RESTRICT error UX | error message + "메모 N건 이동 후 다시 시도" 안내 |
+| 다중 notebook 시 search default scope 혼란 | search box 옆 'current/all' 토글 + 기본 current |
+| F21 sync (Cut E) 와 결합 시 notebook 정합성 | sync markdown export 가 notebook_id 도 frontmatter 에 포함 — Cut E ImportService 갱신 (미리 spec 잔류 — Cut G 머지 시 ImportService 갱신 commit 포함) |
+| 다중 profile 옵션 A 로 진화 시 notebook → profile 마이그레이션 | v0.4+ 영역. 본 cut 은 단일 profile + notebook 다 |
+
+---
+
+## 11. v0.3.2 후
+
+**v0.4 후보** (사용자 dogfood metric 충족 후 외부 확장):
+
+- F25 옵션 A (다중 profile 분리 DB) — 외부 user 확장 시
+- F19 옵션 B (context-based recall — 시간/태그/요일)
+- F19 옵션 E (spaced repetition)
+- F25 옵션 C (다중 sync remote)
+
+**dogfood verify**:
+
+1. 사이드바 사용 빈도 (열린 채로 유지 / 토글 자주)
+2. notebook 갯수 (본인 dogfood — 1개 vs N개)
+3. notebook 간 메모 이동 빈도 (분류 욕구 측정)
diff --git a/docs/superpowers/strategy/v028plus-roadmap.md b/docs/superpowers/strategy/v028plus-roadmap.md
new file mode 100644
index 0000000..3c3017d
--- /dev/null
+++ b/docs/superpowers/strategy/v028plus-roadmap.md
@@ -0,0 +1,211 @@
+# v0.2.8+ Roadmap — F17~F25 cut 분할 + 우선순위
+
+**작성일:** 2026-05-09
+**저자:** 김태현
+**선행 문서:**
+
+- `docs/superpowers/specs/2026-04-25-dogfood-feedback.md` (F17~F25 raw + chore 아이콘)
+- `docs/superpowers/v024-backlog.md` (잔여 23건 — v0.2.6 cut 후 deferred)
+- `docs/superpowers/strategy/strategy.md` (심리학 전략)
+
+**목적:** v0.2.7 release 후 dogfood 9건 누적 + chore 1건 의 cut sequencing + 우선순위 + dependency 결정. v0.2.8 brainstorm 진입 직전 alignment 문서.
+
+---
+
+## 1. 항목 요약
+
+| ID | 제목 | scope | 분류 |
+|---|---|---|---|
+| F17 | 휴지통 의미 분기 (완료/보관/버림) | 1주 (옵션 C 보관함만 별도) | 데이터 모델 |
+| F18 | 메모 이동 시 사유 입력 | 1일 (F17 묶음) | 데이터 모델 |
+| F19 | 획기적 recall (search/context/AI/회고/spaced/자연어) | A 단독 3-4일 / 묶음 1-2주 | UX 본질 |
+| F20 | 기존 메모 raw_text 수정 (load-bearing invariant 재검토) | 옵션 B 3-4일 | 데이터 모델 |
+| F21 | 다기기 git-based sync (양방향 + Configure + conflict) | 1-2주 | 인프라 |
+| F22 | NoteCard 이미지 회색 placeholder bug | 1-2일 | 명확한 bug |
+| F23 | 로컬 LLM 활성화 옵션 (Ollama-less 모드) | 3-4일 | 환경 대응 |
+| F24 | 이미지 멀티모달 vision AI | 1주 (F22 prerequisite) | AI 확장 |
+| F25 | 사이드바 + 메모 저장소 리스트 | 옵션 결정 후 1-3주 | UI 큰 변화 |
+| chore | 앱 아이콘 SVG → ICO/ICNS/PNG + builder 통합 | 0.5일 | release polish |
+
+---
+
+## 2. Dependency Graph
+
+```dot
+digraph G {
+ rankdir=LR;
+ F22 -> F24 [label="prerequisite (이미지 렌더 → vision 결과 surface)"];
+ F17 -> F18 [label="conceptual 강한 결합 (status + reason)"];
+ F17 -> F19 [label="status 분기 데이터가 recall 입력"];
+ F20 -> F21 [label="user_edited_text 가 sync 충돌 정책 입력"];
+ F23 -> F19 [label="Ollama-less 시 recall 단순화 (tag 부재)"];
+ F23 -> F17 [label="raw-only 모드에서 status 자동 분류 무력"];
+ F25 -> F17 [label="저장소 + status + tag 분기 layer 정합 필요"];
+ chore [shape=box, style=filled];
+ F22 [shape=box, style=filled];
+ chore -> "v0.2.8";
+ F22 -> "v0.2.8";
+}
+```
+
+**핵심 prerequisite chain:**
+
+- F22 → F24 (이미지 보여야 vision 결과 surface 의미)
+- F20 → F21 (sync 충돌 정책 = `user_edited_text` 우선순위)
+- F17 + F23 → F19 (recall 알고리즘 입력은 status / Ollama-less 영향)
+
+**독립 항목 (다른 항목 영향 받지 않음):**
+
+- F22 (bug fix)
+- chore (icon)
+
+---
+
+## 3. Cut 분할 + 버전 매핑
+
+### Cut A — v0.2.8 (1주 미만, 빠른 polish)
+
+**테마:** dogfood UX 마찰 + release polish
+
+| 항목 | scope |
+|---|---|
+| F22 (이미지 렌더링 fix) | 1-2일 — `inkling-media://` custom protocol + `` |
+| chore (앱 아이콘) | 0.5일 — SVG → ICO/ICNS/PNG 다중 size + electron-builder config |
+
+**합 2-3일.** 명확한 작업, 빠른 release. 의사결정 X (기술 detail 만).
+
+### Cut B — v0.2.9 (2주, 데이터 모델 정비 1차)
+
+**테마:** 휴지통의 의미 분기 + 사유 + Ollama-less
+
+| 항목 | scope |
+|---|---|
+| F17 (status — 옵션 C 보관함만 별도) | 1주 — `archived_at` 컬럼 + UI 탭 + 마이그레이션 |
+| F18 (사유 입력 — preset + 자유 텍스트) | 1일 (F17 묶음) |
+| F23 (Ollama-less 토글) | 3-4일 — ai_status='disabled' enum + capture skip + UI fallback |
+
+**합 1.5-2주.** F17/F18 같은 데이터 모델 변경 cut 안에 함께. F23 의 raw-only 모드가 F17 status 와 같은 schema 영역이라 효율.
+
+**의사결정 필요 (brainstorm 단계)**:
+
+- F17 옵션 A/B/C 중 — C 추천 (보관함만 별도) 가 가장 균형
+- F18 preset 항목 명세 ("완료" / "급하지 않음" / "잘못 적음" / "기타")
+- F23 ON↔OFF 전환 정책 (B1 추천 — 잔류)
+
+### Cut C — v0.2.10 (1주, raw_text invariant)
+
+**테마:** F20 단독 — load-bearing invariant 재검토
+
+| 항목 | scope |
+|---|---|
+| F20 (raw_text 수정 — 옵션 B `user_edited_text`) | 3-4일 |
+
+**합 1주.** Cut C 단독 cut 인 이유 = invariant 정책 변경 자체가 의사결정 큰 작업. 별도 PR 로 review focus 보장. 후속 Cut D (sync) 의 prerequisite.
+
+**의사결정 필요**:
+
+- 옵션 A (raw_text 직접 수정 + 원본 lost) vs B (`user_edited_text` 분기) — B 추천
+- AI 재실행 시 input — raw_text vs user_edited_text 우선순위
+
+### Cut D — v0.2.11 (1.5-2주, recall 1차)
+
+**테마:** F19 — search 진입 + 회고 view
+
+| 항목 | scope |
+|---|---|
+| F19 옵션 A (FTS5 free text search) | 3-4일 |
+| F19 옵션 D (회고 view) | 1주 |
+
+**합 1.5-2주.** F19 의 6 옵션 중 가장 작은 + 가치 큰 둘 (search + 회고). B/C/E/F 는 v0.3+ deferred.
+
+**의사결정 필요**:
+
+- search box 위치 (header / 사이드바 — F25 결정 영향)
+- 회고 view 트리거 (수동 라우트 / 월요일 자동 banner)
+
+### Cut E — v0.3.0 (2주, 다기기 sync)
+
+**테마:** F21 — 양방향 sync + Configure UI
+
+| 항목 | scope |
+|---|---|
+| F21 옵션 A (양방향 sync — fetch+rebase+import) | 1주 |
+| F21 옵션 B (Configure UI) | 3-4일 |
+| F21 옵션 C (conflict UI) | 0.5주 |
+
+**합 2주.** F20 의 user_edited_text 가 conflict 정책 입력 — 따라서 Cut C 후. v0.3.0 = MINOR bump (semver 엄밀히도 minor — 새 feature 큰 영역).
+
+### Cut F — v0.3.1 (1-1.5주, 멀티모달 vision)
+
+**테마:** F24 — Ollama vision 모델 활용
+
+| 항목 | scope |
+|---|---|
+| F24 (capability detection + 멀티모달 prompt + InferenceProvider 확장) | 1주 |
+
+**합 1주.** F22 prerequisite 충족 (Cut A) 이므로 진행 가능. F23 (Ollama-less) OFF 시 자동 OFF.
+
+### Cut G — v0.3.2 (1-3주, 사이드바 + 저장소)
+
+**테마:** F25 — UI 큰 변화
+
+| 항목 | scope |
+|---|---|
+| F25 옵션 A (다중 profile) | 2-3주 — 큰 refactor |
+| F25 옵션 B (notebook_id) | 1주 |
+| F25 옵션 C (다중 sync remote) | 0.5주 |
+
+**의사결정 필요 (직접 사용자 의도 확인)**:
+
+- "메모 저장소" = 다중 DB 분리 (A) / 카테고리 폴더 (B) / sync remote (C) 어느 의미인가
+
+---
+
+## 4. 우선순위 + 시간선 추정
+
+```
+2026-05-09 ~ 2026-05-15 Cut A (v0.2.8) ✦ 빠른 polish
+2026-05-15 ~ 2026-05-29 Cut B (v0.2.9) ✦ 데이터 모델 정비
+2026-05-29 ~ 2026-06-05 Cut C (v0.2.10) ✦ invariant 변경
+2026-06-05 ~ 2026-06-19 Cut D (v0.2.11) ✦ recall 1차
+2026-06-19 ~ 2026-07-03 Cut E (v0.3.0) ✦ 다기기 sync
+2026-07-03 ~ 2026-07-10 Cut F (v0.3.1) ✦ 멀티모달
+2026-07-10 ~ 2026-07-31 Cut G (v0.3.2) ✦ 사이드바 + 저장소
+```
+
+**총 약 12주.** 본인 dogfood 2주 완주 종료 조건 (v0.4 slice §1.3) 은 Cut B 종료 시점 도달. 그 후 Cut C-G 는 외부 확장 영역.
+
+---
+
+## 5. Risk + Open Questions
+
+| ID | 질문 |
+|---|---|
+| F17 | A/B/C 중 결정 — dogfood 1주 측정 후? |
+| F18 | preset 항목 정확 명세 |
+| F19 | recall 6 옵션 중 cut D 에 A+D 외 추가 여부 |
+| F20 | invariant 폐기 (옵션 A) 충분 vs B (`user_edited_text`) 분기 — B 균형 추천 |
+| F21 | conflict 처리 default (rebase / merge / 사용자 prompt) |
+| F23 | default ON / OFF — 본인 LAN Ollama 가정 시 ON, 외부 user 첫 실행 OFF? |
+| F24 | vision 모델 default 추천 (한국어 + 이미지) — dogfood 검증 필요 |
+| F25 | "메모 저장소" 정의 (A/B/C) — 직접 사용자 확인 |
+
+---
+
+## 6. v0.2.8 brainstorm 진입 시 결정 사항
+
+Cut A (v0.2.8) 는 의사결정 거의 없는 작업이라 brainstorm 가벼움. 그러나 절차상 진입.
+
+**Cut A brainstorm focus:**
+
+1. F22 — `inkling-media://` custom protocol 디테일 (path traversal 검사 / fallback / thumbnail vs full-size)
+2. chore — 아이콘 size 매트릭스 (16/32/64/128/256/512/1024) + electron-builder config (`build.win.icon`/`build.mac.icon`/`build.linux.icon`)
+3. v0.2.8 release notes 초안
+
+이후 Cut B brainstorm 은 F17 옵션 결정 + F18 preset + F23 정책 등 의사결정 多. 별도 brainstorm 세션.
+
+---
+
+## 7. 변경 이력
+
+- 2026-05-09: 작성. F17~F25 + chore 9+1 entry triage. Cut A~G 분할.