# v0.2.x Backlog

> 누적 backlog. v0.2.3 cut (7항목 / PR #13~#19) 시점부터 PR review deferred + dogfood 발견 모두 합산. **파일명은 historic** (`v024-backlog.md`) — v0.2.4 ~ v0.2.6 cut 후에도 이어 사용. **v0.2.7 brainstorm 시** 신규 피드백 + 잔여 일괄 triage.

**누적 시작일:** 2026-05-01 (#7 telemetry skeleton 머지 시점)
**최종 갱신:** 2026-05-10 (v0.3.2 cleanup cut — 잠재 bug 4 + cosmetic 5 + #20 deferred)
**총 항목 수:** 46 (#1 stale 포함)
**잔여:** 14건 (=46 − 처리 31 − stale 1)

## 처리 이력 / 진행 흐름

| 항목 | 상태 | Cut |
|---|---|---|
| #1 (`now()` 2번 호출) | ✅ 이미 fix (PR #13 round 1 — backlog stale) | - |
| #2 (`DAY_MS` magic) | ✅ 처리 | v0.2.4 (commit `ef5d3da`) |
| #6 (`media.gc.run()` `.catch`) | ✅ 처리 | v0.2.4 (commit `ef5d3da`) |
| #13 (NoteCard `onDeleted` dead-code) | ✅ 처리 | v0.2.4 (commit `c87c248`) |
| #44 (버전 정보 surface) | ✅ 처리 (트레이 "Inkling 정보..." + native dialog) | v0.2.4 (commit `d3dfe1e`) |
| **out-of-backlog**: multi-instance bug (single-instance lock) | ✅ critical hotfix | v0.2.5 (PR #23, `7187aea`) |
| #10 (restoreNote + pending_jobs) | ✅ 처리 (repo 메서드 + CaptureService production path) | v0.2.6 (commit `df27a96` + `a991008`) |
| #12 (trashCount cap) | ✅ 이미 fix (v0.2.3 #4) — tests +2 추가 | v0.2.6 (commit `e2c53a2`) |
| #45 (자동실행 풀림 버그) | ✅ 처리 (진단 노출 + 재등록 버튼) | v0.2.7 (commit `8a8652e` + `8368286`) |
| #46 (hidden-start race) | ✅ 처리 (`additionalData` + handler hidden flag) | v0.2.6 (commit `e485b77`) |
| #3+#19+#34 (KST helper 통합) | ✅ 처리 → `src/shared/util/kstDate.ts` (4 callsite migrate) | v0.2.6 (commit `3cfa60b`) |
| #5 (AiFailedReason union) | ✅ 처리 (zod z.infer 단일 export) | v0.2.6 (commit `a2c17a8`) |
| #21 (hasNoteId predicate) | ✅ 처리 (NO_NOTE_ID_KINDS Set + type predicate) | v0.2.6 (commit `05c45c1`) |
| #22 (hydrate `as any[]`) | ✅ 처리 (`as Record<string, unknown>[]` 통일) | v0.2.6 (commit `983306e`) |
| #8 (stats exhaustiveness) | ✅ 처리 (`else { _: never = ev }`) | v0.2.6 (commit `9230ebf`) |
| #4+#23+#26+#27 (TrayCallbacks 객체화) | ✅ 처리 (1-arg + `Partial<TrayState>`) | v0.2.6 (commit `476a519`) |
| #24+#41 (Banner shared component) | ✅ 처리 (`Banner severity=...` 4 callsite) | v0.2.6 (commit `0447b69`) |
| #15 (IPC channel rename) | ✅ 처리 (`inbox:delete` → `inbox:trash`) | v0.2.6 (commit `8b2920f`) |
| #29 (VOCAB_TOP_N const) | ✅ 처리 (튜닝 자체는 telemetry 후) | v0.2.6 (commit `8b2920f`) |
| #42 (Modal URL pre-check) | ✅ 처리 (zod safeParse) | v0.2.6 (commit `8b2920f`) |
| #9 (휴지통 회수율 ratio 코멘트) | ✅ 처리 (1줄 코멘트) | v0.2.6 (commit `8b2920f`) |

### v0.2.6 PR #24 round 1 발견 (Critical fix)

| 항목 | 상태 | Cut |
|---|---|---|
| **B1 production path** (CaptureService.restoreNote 가 옛 `repo.restore` 호출) | ✅ Critical fix (commit `a991008`) | v0.2.6 round 1 |

### v0.3.2 cleanup cut (2026-05-10)

| 항목 | 상태 | Cut |
|---|---|---|
| #14 (탭 ARIA `aria-pressed`→`role="tab"`) | ✅ 처리 | v0.3.2 |
| #18 (`loadExpired` 미사용 제거) | ✅ 처리 | v0.3.2 |
| #19 (KST inline 5 callsite 잔여 migrate) | ✅ 처리 | v0.3.2 |
| #20 (telemetry `.catch` silent → debug log) | 🟡 deferred (CaptureService logger 미주입 — constructor 변경 회피) | v0.3.2 audit |
| #31 (vocabSet COLLATE NOCASE 정합) | ✅ 처리 | v0.3.2 |
| #32 (per-tag emit `Promise.all` 병렬화) | ✅ 처리 | v0.3.2 |
| #36 (recall IPC `handle`→`on`) | ✅ 처리 | v0.3.2 |
| #39 (`ollama_unreachable.reason` PII 마스킹) | ✅ 처리 | v0.3.2 |
| #41+#42 (`OllamaSettingsModal` 폐기 audit) | ✅ 자연 소멸 (v0.2.7) | v0.3.2 audit |
| time-dependent test flake fix | ✅ 처리 | v0.3.2 |

### v0.2.6 final reviewer + round 1 minors (deferred)

| 항목 | 상태 |
|---|---|
| NoteRepository.countToday inline KST_OFFSET_MS | v0.2.7 cleanup (C1 spec 외) |
| BackupService / ContinuityService inline KST_OFFSET_MS | v0.2.7 cleanup |
| NoteRepository.test.ts:125 `as any` | v0.2.7 (C6 spec 외) |
| OllamaSettingsModal `#fce4e4` inline (C7 spec 5번째) | modal 컨텍스트라 보류 |
| `kstDate(ts)` semantic naming (telemetryStats) | v0.2.7 |
| store.ts:177 trashCount race on `trashExpiredBatch` | pre-existing, v0.2.7 |
| ExpiryBanner useEffect 24h+ closure | edge case, defer |

**잔여 24건** (= 46 − 처리 21 − stale 1). v0.2.7 brainstorm 시 신규 dogfood 피드백 + #45 deeper fix + 위 deferred 항목 일괄 triage.

## 명명 노트

- v0.2.3.1 / v0.2.4 / v0.2.5 는 **dogfood unblock patch** (semver bump 강제 / hotfix)
- v0.2.6 = 첫 정식 cut (16 backlog 항목 처리)
- v0.2.7 = 다음 정식 feature cut (telemetry data-dependent 14건 + 신규 피드백 + 잔여 deferred)
- 본 backlog 파일은 v0.2.7 cut 시점에 prune + rename 검토 (`v027-backlog.md` 또는 stable 한 `feature-backlog.md`)

## Defer 사유 카테고리

각 항목은 머지 전 inline fix 보다 v0.2.4 영역으로 미룬 명시적 사유 가짐:

1. **Cross-cutting refactor** — 한 PR 안에서 부분만 고치면 inconsistency. 일괄 cleanup task 영역. (예: KST helper 4 callsite 통합, `createTray` positional callbacks 전체 객체화)
2. **Data-dependent** — dogfood telemetry 분포 보고 결정해야 의미. (예: top-N 튜닝, recall_shown lifetime dedup 정책)
3. **Cosmetic / style** — 동작 영향 0, 다른 일괄 cleanup task. (예: `now()` 두 번 호출, `as any[]` 통합)

## How to apply

v0.2.6 brainstorm 시 본 리스트를 1차 backlog 로 사용. 항목별로:

- (a) 그대로 cleanup
- (b) #4~#6 영향 받아 변형
- (c) defer-further 결정
- (d) drop (만에 하나 outdated 또는 v0.2.4/v0.2.5 patch 가 우회 처리)

## v0.2.3 #7 Telemetry skeleton 누적 (2026-05-01)

1. **`now()` 두 번 호출** — `TelemetryService.emit` (`src/main/services/TelemetryService.ts:58, :60`) 가 같은 emit 안에서 `this.now()` 두 번. 이론적 midnight straddle 가능 (ts vs filePath 다른 KST 일자), 실제 영향 cosmetic. cleanup: `const nowDate = this.now()` 한 번 추출.

2. **`DAY_MS = 24*60*60*1000` magic number** — `cleanupOldFiles:39` + `readAllRecent:78` (+ `KST_OFFSET_MS` 간접). 모듈 상단에 `const DAY_MS = 24 * 60 * 60 * 1000;` 추출.

3. **KST helper duplication** — `TelemetryService.todayKstIso` + `telemetryStats.kstDate` + `AiWorker.todayKstAsDate`/`todayKstAsIso`. 4번째 caller (예: 회상 schedule, 만료 batching) 등장 시 `src/main/util/kst.ts` 로 통합.

4. **`createTray` positional 폭주** — `tray.ts:51` 가 7 positional callbacks. #1 ollama 회복 / #4 휴지통 비우기 등 트레이 메뉴 추가 시 8+ 도달 → readability threshold 넘김. `TrayCallbacks` object 로 refactor.

5. **`AiFailedReason` union 3 곳 중복** — `'unreachable' | 'schema' | 'timeout' | 'other'` 가 `telemetryEvents.ts:15` (zod), `TelemetryService.ts:21` (EmitInput), `AiWorker.ts:19, :34` (classifier + emitter) 에 분산. `export type AiFailedReason` 하나로 통합. (단 zod enum + TS literal 의 inherent dual-define 은 어쩔 수 없음 — `z.infer` 통해 type 파생만)

6. **`media.gc.run()` 의 `.catch` 누락** — T11 에서 `telemetry.cleanupOldFiles` 의 `.catch` 일관성 처리 시 `media.gc` 도 같은 패턴 (`.catch` 없음) 발견. `backup.runDaily()` 와 컨벤션 통일 위해 `.catch((e) => logger.warn('media.gc.failed', { reason: String(e) }))` 추가.

7. **stats.md 의 reason 분포 미포함** — `telemetryStats.aggregateStats` 가 AI 성공률만 계산, `ai_failed.payload.reason` 의 분포 (unreachable/schema/timeout/other counts) 는 미집계. roadmap §6.2 의 "Ollama unreachable 빈도?" 질문이 부분적으로만 답해짐. v0.2.3 dogfood 후 실제 reason 분포 보고 결정.

## v0.2.3 #4 휴지통 누적 (2026-05-01)

8. **stats.md exhaustiveness check** — `telemetryStats.aggregateStats` 의 7-arm if/else if 가 union 확장 시 silent fall-through. `else { const _: never = ev; }` 추가로 컴파일 단계 가드.

9. **휴지통 회수율 ratio 의미 코멘트** — `restore / trash` 가 event-level ratio (한 노트 trash-restore 반복 시 100% 가능). spec §6.2 의 "회수 도구 동작?" 질문에는 충분, 단 unique-note 회수율로 오해할 여지. 코드 옆 1줄 코멘트.

10. **`restore` 시 AI 결과 보존 + pending_jobs 미재생성** — restore 가 `deleted_at = NULL` 만, pending_jobs 안 재생성. 사용자가 trash 도중 AI fail 한 노트를 restore 시 재처리 경로 부재. v0.2.3 dogfood 에서 빈도 보고 결정 — drop / per-note retry 버튼 / 자동 재처리 중.

11. **`restoreNote(id)` precondition 노출** — store 의 낙관적 갱신이 `trashNotes` 에 노트가 있어야 동작. 명령 팔레트 / 프로그래밍 호출 케이스 시 silently no-op. 현재는 trash view 한정이라 OK. main 이 trash/restore 시 `pushNoteUpdated` 보내도록 변경하면 더 견고.

12. **`inbox:trashCount` cap 200 silent undercount** — UI 만 200 cap, `repo.emptyTrash()` SQL 은 unbounded. 350 노트 trash 시 dialog "200개 영구 삭제" 표시되지만 실제 350 모두 삭제. `repo.countTrashed()` 추가로 둘 다 정확히. **(잠재 UX 버그 — pull-forward 후보)**

13. **NoteCard `mode='trash'` 의 `onDeleted` dead-code** — trash 카드는 `onPermanentDelete`/`onRestore` 만 사용. `onDeleted` prop 은 호출되지 않음 (App.tsx 가 pass-through). API 깔끔히 — `onDeleted?` optional + trash 분기 미전달.

14. **탭 ARIA 패턴** — `aria-pressed` 로 toggle 버튼 표현. canonical 은 `role="tab"` + `aria-selected`. screen reader 동작 OK 지만 a11y audit 시 정정 후보.

15. **`inbox:delete` 채널 rename** — semantic 이 hard → soft 인데 채널 이름 그대로. v0.2.4 에서 `inbox:trash` 로 rename 검토 (기존 호출 0건 보장 후).

16. **per-note 영구 삭제 telemetry 사용량** — v0.2.3 dogfood 에서 `permanent_delete` event 빈도 확인. 거의 0 이면 v0.2.4 에서 per-card "영구 삭제" 버튼 제거 + bulk emptyTrash 만 (UX 단순화). 빈번하면 유지.

## v0.2.3 #5 만료 추천 누적 (2026-05-01)

17. **dialog 버튼 순서 vs spec §5.3** — spec 은 `['취소','옮기기'], default=0`, 구현은 `['옮기기','취소'], defaultId=1, cancelId=1` (`inboxApi.ts:117`). 효과 동일 (default = cancel). v0.2.4 에서 spec 또는 impl 한쪽 통일.

18. **`loadExpired()` 미사용** — `loadInitial`/`refreshMeta` 가 inline fetch, App.tsx 도 호출 안 함 (test 만 exercise). v0.2.4 dogfood 후에도 consumer 미발생 시 제거 검토.

19. **store `KST_OFFSET_MS` inline duplication** — `store.ts:166` 의 `snoozeExpired` 가 inline KST 계산. `@main/util/kstDate.ts` 와 동일 알고리즘이지만 alias 경계 (main vs renderer) 로 import 불가. `src/shared/util/kstDate.ts` 로 lift 검토. (#3, #34 와 합산 가능)

20. **telemetry emit `.catch(() => {})` 가 silent** — `CaptureService.listExpired`/`trashExpiredBatch` 가 그대로. v0.2.4 telemetry 하드닝 시 debug log path (project pattern 통일) 추가 검토.

21. **TelemetryService.test.ts 의 noteId 가드 widening** — `e.kind !== 'empty_trash' && e.kind !== 'expired_banner_shown' && e.kind !== 'expired_batch_trash'` 체인이 #6 추가 시 더 길어짐. `hasNoteId(ev)` type predicate helper 추출 검토.

22. **NoteRepository hydrate 의 `as any[]` 일괄 cleanup** — `findExpiredCandidates` round 1 review 의 nit 가 단독 fix 시 다른 hydrate-using methods 와 inconsistency. `db.prepare().all()` 의 row type 을 `Record<string, unknown>[]` 또는 explicit row interface 로 통일하는 repo-wide refactor.

## v0.2.3 #1 ollama 회복 누적 (2026-05-01)

23. **`createTray` 8 positional callbacks** — #1 cut 에서 8개 도달, v0.2.4 backlog #4 와 정합 (TrayCallbacks object refactor 약속). #2 retry 또는 #6 reminder cut 에서 추가 항목 (예: "재시도 N건") 등장 시 9+ 회피 위해 본격 refactor.

24. **Banner CSS 스타일 inline 중복** — ExpiryBanner (`#fff7e6 / #d99500 / #946100` 황색) / OllamaBanner (동) / FailedBanner (`#fce4e4 / #a33` 적색) / RecallBanner (`#e8f0fe / #4a7ec0` 청색) 모두 색상 hardcode. v0.2.4 에서 CSS variables 또는 banner shared component (`<Banner severity="warning|error|info" />`) 추출 검토.

25. **HealthChecker `inFlight` 가드의 manual emit ordering** — manual emit 이 inFlight 체크 전 발생해 user 가 빠르게 N번 클릭하면 N개 manual telemetry. spec 의도 (1:1 보장) 와 정합이지만, 향후 dedup 정책 (예: 1초 윈도우) 으로 변형 가능성. v0.2.4 dogfood soak 결과로 결정.

## v0.2.3 #2 AI retry 누적 (2026-05-02)

26. **`createTray` 9 positional callbacks** — #2 cut 에서 9개 도달 (refreshTrayFailedCount 포함). #4 `TrayCallbacks` object refactor 가 이제 readability blocker. #3 / #6 cut 어느 쪽이든 추가 callback 더 들어오기 전에 우선 처리.

27. **`refreshTrayFailedCount` exported singleton state** — `tray.ts` 에 `_failedCount` module-scoped state + setter 패턴. 모듈 캡슐화로 작동하지만 multi-window 또는 multi-tray 시 broken. v0.2.4 refactor 시 TrayController class 또는 store-driven 으로 정리.

28. **`AiWorker.unreachableBackoffStep` 단일 카운터 vs job-level** — 모든 job 이 step counter 공유. 1 job timeout → step↑, 다른 job 정상 처리해도 step reset. 현재는 cross-job correlation 없으니 OK 가정 (Ollama daemon 단일이라 모든 job 이 같은 백엔드 의존). multi-provider 가 들어오면 provider-level step 으로 분리 필요.

## v0.2.3 #3 태그 vocab 누적 (2026-05-02)

29. **`getTopUsedTags(20)` magic number** — `AiWorker.processJob:137` 가 `repo.getTopUsedTags(20)` hardcoded. spec §7 Out 에 "top-N 튜닝" 명시. v0.2.4 dogfood telemetry (`tag_vocab_hit/miss` ratio) 보고 `VOCAB_TOP_N` 모듈 상수 추출 + 튜닝 결정.

30. **`getTopUsedTags` LIMIT-then-filter 의미** — SQL 가 limit 만큼 가져온 후 JS regex 가 후처리 → top-20 안에 한글/공백 태그 섞이면 결과 length < limit. dogfood 규모 OK 가정 + 테스트 lock-in (v0.2.3 round 1 m2 fix). v0.2.4 에서 vocab pool 확장 시 SQL `GLOB` 으로 SQL-side 필터 대안 검토 (또는 `LIMIT ?*2` overfetch+slice).

31. **`vocabSet` strict-eq vs DB COLLATE NOCASE 불일치** — `vocabSet = new Set(vocab)` 은 JS 대소문자 strict, `tags.name` 은 COLLATE NOCASE. 현재는 kebab-case 필터로 vocab 이 항상 lowercase + AI prompt 도 lowercase 강제라 충돌 없지만, vocab pool 확장 시 (예: `'Design'` 사용자 직접 추가) `getTagIdByName('Design')` 은 매치하지만 `vocabSet.has('Design')` 은 miss → tagId 없는 hit 가 silently skip. v0.2.4 에서 `vocabSet = new Set(vocab.map(v => v.toLowerCase()))` + `vocabSet.has(tagName.toLowerCase())` 로 normalize 검토.

32. **AiWorker per-tag emit serial await** — `for (const tag of new Set(...))` 안의 `await this.telemetry.emit(...)` 가 직렬. 3 태그 시 file-append 3 round-trip. `Promise.all` 로 병렬화 가능, 단 `ai_succeeded` emit 도 serial 이라 패턴 일관성 우선 skip. v0.2.4 telemetry 하드닝 시 일괄 변경 검토.

33. **`PROMPT_VERSION` telemetry payload 미포함** — v0.2.3 cut 에선 단일 버전 (4) 만 굴러가서 무의미. v0.2.4/v0.2.5 prompt 튜닝 후 어느 버전이 어떤 hit-rate 만든지 추적 시 `tag_vocab_hit/miss` payload 에 `promptVersion` 추가 검토. spec §7 Out 명시.

## v0.2.3 #6 RecallBanner 누적 (2026-05-02)

34. **KST midnight inline calc 4번째 복제** — `store.ts` 의 `snoozeRecall` (#6) + `snoozeExpired` (#5) + `NoteCard.todayKstIso` + 다른 1곳, 그리고 `kstDate.ts` util 도 별도 존재. 4 callsite 모두 동일 알고리즘. v0.2.4 에서 `nextKstMidnightMs()` / `kstTodayIso()` 단일 util 통합 + alias 경계 (main vs renderer) 해결책. backlog #3, #19 와 합산.

35. **`recall_shown` per-banner-lifetime emit 보장** — useState→useRef 로 race 차단했지만 RecallBanner 컴포넌트 unmount/remount 시 reset. 사용자가 페이지 이동 후 돌아오면 같은 노트가 재emit 가능. v0.2.4 dogfood telemetry 에서 동일 noteId 의 `recall_shown` 빈도 보고 결정 (per-noteId 24h dedup 또는 per-noteId 영속 마커).

36. **`emitRecallShown` / `emitRecallSnoozed` 가 fire-and-forget 인데 `ipcMain.handle` 사용** — 더 honest 한 패턴은 `ipcMain.on` (return value 없음). 현재는 다른 IPC 와 패턴 일관성 우선. v0.2.4 IPC 정리 시 `handle` vs `on` 구분 일괄 검토.

37. **NoteCard `id="note-${id}"` load-bearing** — RecallBanner 의 `scrollIntoView` target. 단순 DOM lookup 이라 shadow DOM / portal 미지원. v0.2.4 에서 다른 surface (예: 검색 결과에서 스크롤) 등장 시 ref-forwarding 패턴 검토.

## v0.2.3.1 Ollama Settings 누적 (2026-05-04)

39. **`ollama_unreachable.reason` 에 endpoint URL 노출 (PII 우회)** — `LocalOllamaProvider.healthCheck` 가 catch err 시 `reason: \`unreachable: ${err.message}\`` 로 emit. `err.message` 안에 `http://192.168.x.x:11434/api/tags` 같은 LAN endpoint URL 포함 가능. v0.2.3.1 의 in-app endpoint UI 가 LAN 사용을 흔하게 만들어 PII 우회 노출 경로 확대. v0.2.4 telemetry 하드닝 시: error class only (network/dns/timeout/...) 또는 host 마스킹 (`<host>:11434`) 정책. PR #21 round 1 m2 deferred.

40. **Settings 저장 vs HealthChecker 60s tick race** — `saveOllamaSettings` IPC 가 `health.runOnce()` 호출, 동시에 60s 주기 tick 도 `inFlight` 가드 통해 같이 실행 시도. 정확성 영향 0 (가드로 dedup), 단 modal 닫기 직전 banner flicker 가능. PR #21 round 1 i1 acknowledge only. v0.2.4 dogfood 에서 실제 빈도 확인 후 결정 (visible 빈도 낮으면 무시).

41. **`OllamaSettingsModal` 인라인 스타일** — 60+ 줄 inline style. backlog #24 (banner CSS 추출) 와 합산. v0.2.4 에서 CSS module / theme variables 추출 시 함께.

42. **Modal 의 client-side URL validation 부재** — endpoint freetext 가 잘못된 형식 (예: 빈 문자열, 한글) 일 때 server-side healthCheck 만 검증. zod URL error message 가 opaque ("Invalid url"). v0.2.4 에서 client-side z.string().url() pre-check + 친화적 에러 메시지.

43. **`createTray` 10번째 positional callback** — v0.2.3.1 cut 에서 10개 도달 (`runOpenOllamaSettings` 추가). backlog #4/#26 (TrayCallbacks object refactor) blocker 수준. v0.2.4 첫 cleanup 항목 후보.

## v0.2.3 / v0.2.3.1 dogfood 발견 (2026-05-05)

> 본 cut 들의 머지 후 사용자가 dogfood 중 발견한 항목. PR review deferred 와 달리 raw UX/bug 발견.

44. **버전 및 프로그램 정보 표시 방법 부재** — 현재 사용자가 설치된 Inkling 의 버전 (package.json `0.2.3.1`) 을 UI 에서 확인할 path 없음. 트레이 메뉴 / Inbox 푸터 / 별도 "About Inkling" 모달 어느 surface 에도 정보 없음. 핸드오프 후 다른 머신에서 같은 버전인지 사용자가 직접 검증 불가. v0.2.4 에서 트레이 메뉴 "Inkling 0.2.3.1 정보..." 또는 Inbox 우하단 footer 형태로 추가 검토. 곁들여: 빌드 commit SHA, electron/node 버전, OS, profileDir 경로 등 디버그 정보 노출 (사용자가 issue report 시 첨부 가능).

45. **윈도우 자동 실행 옵션이 재시작 후 풀려있는 버그** — 트레이 메뉴 "윈도우 시작 시 자동 실행" 체크 → 종료 → 재실행 시 체크박스가 풀려서 표시됨. 코드 (`src/main/tray.ts:47-58`) 가 `app.setLoginItemSettings({ openAtLogin, args: ['--hidden'] })` 호출 후 다음 부팅 시 `app.getLoginItemSettings().openAtLogin` 이 false 반환. 추정 원인:
    - (a) Windows registry 에 쓴 exe path 와 현재 프로세스 path 가 다름 (NSIS 설치 위치 변경 / 버전 업데이트 시 새 디렉터리)
    - (b) Electron `setLoginItemSettings` Windows 구현 의 path canonicalization 이슈
    - (c) 우리 `args: ['--hidden']` 와 actual launch 시 args 비교 mismatch
    - 영향: dogfood UX 핵심 마찰 — autostart 가 핸드오프 시 매번 수동 재설정 필요. 자동 실행 의도 자체가 dogfood "잊지 않고 매일 사용" 목적인데 깨짐.
    - v0.2.6 에서 우선순위 높음. 진단 절차: (1) `app.getLoginItemSettings({ args: ['--hidden'] })` 형태로 args 전달해 비교 정확도 올리기, (2) registry 직접 inspect (`HKCU\Software\Microsoft\Windows\CurrentVersion\Run\inkling`) 로 path/args 확인, (3) executable path canonicalization (electron 이 short path 변환 적용 여부).

## v0.2.5 critical hotfix 누적 (2026-05-05)

> v0.2.5 single-instance lock hotfix (PR #23) 의 reviewer deferred 항목.

46. **Hidden-start race (NSIS installer 자동 실행 + 사용자 클릭 충돌)** — NSIS installer 가 설치 직후 사용자가 시작메뉴 / 데스크톱 아이콘 클릭 (`inkling.exe`) + autostart entry (`inkling.exe --hidden`) 을 짧은 간격에 둘 다 시도 시 — 첫 lock 보유자에 따라 visible 여부 race. 본 cut 의 `second-instance` handler 는 무조건 inbox 창 띄움 (사용자 클릭 = 보고 싶다는 강한 시그널 가정). 매우 드문 시나리오 + lock 자체는 정상 동작 (한 쪽만 살아남음).
    - 영향: drm-edge 케이스만, 실 사용 거의 X
    - v0.2.6 에서: `app.requestSingleInstanceLock(additionalData)` 의 `additionalData: { hidden: startedHidden }` 전달 → `second-instance(event, argv, cwd, additionalData)` 에서 두 번째 호출이 hidden 이면 창 안 띄우는 정책. 첫 instance 가 자기 자신의 hidden 상태와 비교해 visible 결정.
    - PR #23 round 1 reviewer Important — acknowledge only, defer to v0.2.6.

## post-cut next-step (status, not backlog)

38. **빌드 / release 흐름 (status)** — v0.2.3 cut 7/7 (PR #13~#19) → binary v0.2.3 release → 11434 포트 reserved 발견 → v0.2.3.1 attempt (PR #21) → semver 거부 → v0.2.4 (PR #22, backlog 5건 + Ollama 설정 UI) → release → multi-instance bug 발견 → **v0.2.5 critical hotfix** (PR #23, single-instance lock) → release ✅ (2026-05-05). 다음: dogfood ≥1주 soak → telemetry export + 신규 피드백 → **v0.2.6 brainstorm 트리거** (잔여 backlog 40건 일괄 triage).

## v0.2.3 cut 후 final reviewer 가 칭찬한 부분

- 2-layer privacy invariant (zod outer + payload `.strict()`) 가 강한 defense
- KST 처리 일관성 — 4 callsites 동일 패턴
- backward compat — 기존 13 테스트 (Capture 4 + AiWorker 9) 무수정 통과
- 신규 dep 0 (zip 회피로 폴더 + 2 file 정책)
- TelemetryService surface 가 깔끔한 foundation — 다음 항목들이 (a) zod schema 추가, (b) EmitInput arm 추가, (c) emit 호출만 하면 됨
