Commit Graph

178 Commits

Author SHA1 Message Date
altair823
3a8137f334 feat(v027): AutostartDiagnostic — withArgs/noArgs/execPath 수집 2026-05-07 02:23:52 +09:00
altair823
3b53cec663 fix(v027): F14 — macOS dock 클릭 시 hidden inbox 창 show/focus 2026-05-07 02:22:40 +09:00
altair823
9c8ba8ad09 feat(v027): createTray wiring 3-callback + refreshTray 호출부 슬림 2026-05-07 02:18:32 +09:00
altair823
f30fbddd38 feat(v027): tray.ts 의 showAboutDialog + 자동실행 분기 + 미사용 import 제거 2026-05-07 02:16:55 +09:00
altair823
77effb4526 feat(v027): TrayCallbacks/TrayState 슬림 + buildMenu 4 항목 2026-05-07 02:16:29 +09:00
altair823
feb7c62f19 feat(v027): IPC inbox:navigate — 외부에서 설정 페이지 진입 2026-05-07 02:12:45 +09:00
altair823
95ed0fba93 feat(v027): App.tsx 헤더 톱니바퀴 + showSettings 분기 2026-05-07 02:10:01 +09:00
altair823
6ab518410e feat(v027): InfoSection — 버전/데이터 위치/복사 + IPC 2026-05-07 02:07:20 +09:00
altair823
5cd38f2537 feat(v027): BackupSection — 5 버튼 + IPC 핸들러 2026-05-07 02:03:31 +09:00
altair823
fca28fb0c4 feat(v027): AutostartSection 토글 (진단 패널은 후속 task) 2026-05-07 01:56:58 +09:00
altair823
7301f4d73d feat(v027): AiProviderSection — OllamaSettingsModal 흡수 + 지금 재확인 2026-05-07 01:51:53 +09:00
altair823
91bf98f1a2 feat(v027): SettingsPage scaffold — 4 섹션 placeholder + 돌아가기
v027 plan Task 7. zustand store 의 showSettings 를 사용하는 첫 컴포넌트.
4 섹션 (AI 제공자/자동 실행/백업·복원/정보) placeholder 와 헤더 + 돌아가기 버튼만.
실 콘텐츠는 후속 Task 8-11 에서 채움.

테스트 인프라 동시 추가 (v027 의 첫 React 컴포넌트 테스트):
- @testing-library/react + @testing-library/jest-dom + jsdom devDep 추가
- vitest.config: plugin-react 적용, include 에 .test.tsx 포함
- 환경 분리는 per-file `// @vitest-environment jsdom` directive 로 처리
  (vitest 4.x 에서 environmentMatchGlobs 미지원 — 기존 .ts 단위 테스트는 node env 유지)
2026-05-07 01:42:54 +09:00
altair823
5b37529175 feat(v027): inbox store 에 showSettings state + setShowSettings action 2026-05-07 01:36:26 +09:00
altair823
a991008689 fix(v026): PR #24 round 1 Critical — B1 production path activation
Round 1 reviewer 발견: B1 (#10) fix 가 dead code. NoteRepository.restoreNote
새 메서드는 unit test 만 호출, production path (CaptureService.restoreNote)
는 옛 repo.restore() 호출 → ai_status reset + pending_jobs INSERT 우회.

Fix:
- CaptureService.restoreNote 가 repo.restoreNote 호출
- before 의 ai_status 가 'failed' or 'pending' 이면 worker.enqueue(id) 도 호출
  (in-memory queue 갱신 — restoreNote 가 DB 만 갱신하면 다음 app start 까지
   처리 안 됨)

Round 1 Important 도 함께 처리.

단위 +2 cases (failed → enqueue, done → skip enqueue).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-05 01:58:27 +09:00
altair823
8b2920fee4 refactor(v026): C9 microfixes — #15 #29 #42 #9
- #15: IPC channel inbox:delete → inbox:trash (semantic = soft delete)
  channel name 만 변경, InboxApi method name (deleteNote) 은 backward compat 유지
- #29: getTopUsedTags(20) → VOCAB_TOP_N const (튜닝 자체는 dogfood telemetry 후)
- #42: OllamaSettingsModal client-side URL validation (zod safeParse pre-check)
  + model 빈 문자열 가드. server-side healthCheck 전에 친화적 에러 메시지.
- #9: 휴지통 회수율 ratio 의미 1줄 코멘트 (event-level, unique-note 아님)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-05 01:44:58 +09:00
altair823
0447b69b82 refactor(v026): #24+#41 Banner shared component (severity prop)
4 banner inline style 중복 (warning 황색 / error 적색 / info 청색)
→ <Banner severity="warning|error|info"> wrapper. THEMES map 단일 source.

- ExpiryBanner: warning
- OllamaBanner: warning
- FailedBanner: error
- RecallBanner: info

OllamaSettingsModal 은 modal 형식이라 banner 와 분리 (별개 inline style 유지).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-05 01:42:16 +09:00
altair823
476a519fb5 refactor(v026): #4+#23+#26+#27 TrayCallbacks 객체화 + state 통합
createTray(callbacks: TrayCallbacks) 1-arg signature. 기존 10 positional 폐기.
TrayState 통합 (ollamaOk, todayCount, failedCount) — refreshTray({...partial})
1개 setter 로 일원화.

기존 refreshTrayOllama / refreshTrayFailedCount export 제거 — 호출자 모두
refreshTray({ ollamaOk: ... }) / refreshTray({ failedCount: ... }) 로 migrate.
module-scoped 개별 state 변수 (_failedCount 등) 제거.

backlog 4건 일괄: #4 (positional 폭주) / #23 (8 callbacks) / #26 (10 callbacks) /
#27 (refreshTrayFailedCount singleton). 다음 menu item 추가 시 callback
프로퍼티 추가만 — readability blocker 해소.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-05 01:38:51 +09:00
altair823
9230ebff9d refactor(v026): #8 telemetryStats.aggregateStats exhaustiveness check
if/else if 체인 끝에 const _exhaustive: never = ev — 새 telemetry kind
추가 시 본 함수 분기 누락을 컴파일 단계에서 catch.

silent fall-through 방지 — kind 추가 → typecheck 실패 → 강제 분기 추가.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-05 01:35:04 +09:00
altair823
983306e004 refactor(v026): #22 NoteRepository hydrate row type 통일
db.prepare().all() 의 row type cast s any[] / s unknown[] →
s Record<string, unknown>[] 일괄 통일. hydrate() signature 도 동일
(이미 그렇거나 갱신).

- TS strict 환경 친화 (any 보다 narrow)
- 향후 explicit row interface 추가 시 base 형 명확
- runtime 동작 변경 0

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-05 01:33:30 +09:00
altair823
05c45c1e10 refactor(v026): #21 hasNoteId type predicate helper
기존 4-line narrowing 체인 (e.kind !== 'empty_trash' && ... && ...) 이
union 확장 시 길어짐 → hasNoteId(ev) type predicate 로 통합.

- telemetryEvents.ts: NO_NOTE_ID_KINDS Set + hasNoteId(ev): ev is ... export
- TelemetryService.test.ts: 2 narrowing callsite 단축
- 단위 +2 cases (noteId-bearing / noteId-less)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-05 01:31:16 +09:00
altair823
a2c17a8b0d refactor(v026): #5 AiFailedReason union 단일 export 통합
기존 'unreachable' | 'schema' | 'timeout' | 'other' literal 이 3곳에 분산:
- telemetryEvents.ts (zod enum AiFailedReason)
- TelemetryService.ts (EmitInput 안 inline literal)
- AiWorker.ts (classifyReason 반환 + AiTelemetryEmitter inline literal)

zod enum z.infer 통해 type 파생, 단일 export AiFailedReason 으로 통합.
- AiFailedReasonSchema (zod enum) + AiFailedReason (type) 둘 다 export
- TelemetryService EmitInput / AiWorker classifyReason / AiTelemetryEmitter
  모두 import type AiFailedReason 사용

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-05 01:29:11 +09:00
altair823
3cfa60bbba refactor(v026): #3+#19+#34 KST helper 통합 → src/shared/util/kstDate.ts
기존 src/main/util/kstDate.ts (2 함수) 를 shared 로 이동 + kstTodayAsDate 추가.
main + renderer 양쪽 import 가능. 6 callsite 통합:
- NoteRepository.findExpiredCandidates (todayInKstString → kstTodayIso)
- TelemetryService.todayKstIso (inline 제거)
- telemetryStats.kstDate (inline 제거)
- AiWorker.todayKstAsDate / todayKstAsIso (inline 제거)
- store.snoozeExpired + snoozeRecall (inline 제거 → nextKstMidnightMs)

API: kstTodayIso(now) / nextKstMidnightMs(now) / kstTodayAsDate(now)
+ KST_OFFSET_MS, DAY_MS 상수 export.

단위 +4 cases (boundary, format, midnight, asDate). 418 → 422.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-05 01:27:25 +09:00
altair823
075f395b6d fix(v026): #45 autostart 풀림 — args 비교 정확도 + 진단 로그
추정 원인 (a)/(b)/(c):
- (a) Windows registry path mismatch (NSIS 설치 위치 변경)
- (b) electron path canonicalization
- (c) args 비교 mismatch — getLoginItemSettings 가 args 와 함께 read 해야 매치

Fix:
- tray.ts: getLoginItemSettings({ args: ['--hidden'] }) 명시 — 트레이 checkbox
  의 checked 상태가 실제 LoginItem args 와 정합하게 비교
- index.ts firstRun 후: autostart.state 진단 로그 (withArgs vs noArgs 비교
  + executableWillLaunchAtLogin) — dogfood 에서 실제 동작 확인

Fix 가 충분하지 않으면 dogfood 로그 분석 후 v0.2.7 deeper fix.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-05 01:22:34 +09:00
altair823
e485b77888 fix(v026): #46 hidden-start race — additionalData 로 두 번째 hidden 구분
PR #23 single-instance lock 의 second-instance handler 가 무조건 inbox 창
띄움. NSIS installer 직후 사용자 클릭 + autostart --hidden 동시 시도 시
두 번째가 hidden 이어도 창 띄워서 "트레이만" 의도 위반.

Fix: requestSingleInstanceLock 에 additionalData = { hidden: startedHidden }
전달, second-instance 콜백 signature (event, argv, cwd, additionalData) 의
4번째 인자에서 hidden flag 확인 → true 면 early return (창 안 띄움).

PR #23 round 1 reviewer Important deferred 처리.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-05 01:20:44 +09:00
altair823
df27a9637e fix(v026): #10 restoreNote 가 failed 노트 시 pending_jobs 재생성
restore 가 deleted_at = NULL 만 했음 → ai_status='failed' 인 노트는
영구 fail 상태로 복구. atomic transaction 안에서 ai_status='pending' reset
+ INSERT OR IGNORE INTO pending_jobs.

- failed → pending + pending_jobs 재처리 path 복구
- done 은 영향 X (이미 결과 있음)
- pending 은 pending_jobs 재생성 (defensive — trash 도중 jobs 미정상 상태 가능)
- 단위 +3 cases (failed/done/pending 각 케이스)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-05 01:15:23 +09:00
altair823
7187aea0a9 hotfix(critical): single-instance lock — multi-process SQLite race 방지
dogfood 발견 — 앱 아이콘 클릭 시마다 새 process 가 떠서 트레이 아이콘 여러 개,
SQLite 동시 접근 + AiWorker 중복 처리 + HealthChecker 중복 polling 등
**데이터 corruption 위험**.

원인: app.requestSingleInstanceLock() 호출 부재. Electron default 가
multi-instance 라 .exe 실행마다 별도 process.

Fix:
- app.requestSingleInstanceLock() 첫 줄에서 호출
- 두 번째 인스턴스 → app.quit() 즉시 종료
- 'second-instance' 이벤트 → 기존 inbox 창 restore + show + focus
  (사용자 의도는 "앱 보기" 라 가정)

게이트: typecheck 0 / 단위 413 / e2e 1
version: 0.2.4 → 0.2.5 (critical hotfix patch)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-05 00:42:50 +09:00
altair823
d213d45f92 fix(v024): About dialog EOL + .catch (round 1 review)
Round 1 review minor + final reviewer minor 일괄:
- About dialog detail/clipboard 의 줄바꿈 → os.EOL (Windows Notepad 등에서 줄바꿈 정상)
- showMessageBox().then().catch(() => {}) — dialog reject (main crash 예외) silent
  (tray.ts 가 logger 미import — minimal swallow 패턴 채택)

skip:
- nit: 트레이 메뉴 ordering ("정보" → "종료" 한 그룹) — 현재 패턴도 흔함, 호불호 영역
- nit: process.versions.electron ?? '?' dead branch — 안전 fallback 유지

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-05 00:22:00 +09:00
altair823
d3dfe1e4e2 feat(v024): "Inkling 정보..." 트레이 메뉴 + native About dialog (backlog #44)
dogfood 발견 #44 fix — 사용자가 설치된 버전 확인 path 부재 해소.

- 트레이 메뉴 마지막 항목 (종료 직전): "Inkling 정보..."
- 클릭 시 native dialog (showMessageBox):
  - title: Inkling 정보
  - message: Inkling {version}
  - detail: 버전, Electron, Node, OS platform/release, 데이터 위치
  - 버튼 3개: 확인 / 데이터 위치 열기 (shell.openPath) / 정보 복사 (clipboard)
- 디버그 정보 노출로 사용자가 issue report 시 첨부 가능

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-05 00:14:30 +09:00
altair823
c87c248e89 refactor(v024): NoteCard onDeleted optional + trash mode 미전달 (backlog #13)
- onDeleted: () => void → onDeleted?: () => void (inbox mode 전용 명시)
- handleDelete 내부 onDeleted() → onDeleted?.()
- App.tsx 의 trash mode NoteCard 가 onDeleted prop 미전달 (dead-code 제거)
- API 시그니처 정리 — trash mode 는 onPermanentDelete/onRestore 만 의미

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-05 00:12:56 +09:00
altair823
ef5d3daf4c refactor(v024): TelemetryService DAY_MS 상수 + media.gc .catch (backlog #2 #6)
- #2: 24*60*60*1000 magic number → 모듈 상단 const DAY_MS
  cleanupOldFiles + readAllRecent 두 callsite 통일
- #6: gc.run() 의 .catch 누락 → backup.runDaily 패턴 통일
  실패 시 logger.warn('media.gc.failed', { reason })

Note: backlog #1 (now() 2번 호출) 은 PR #13 round 1 review 시 이미 fix —
backlog 항목 stale. v0.2.5 brainstorm 시 backlog 정리.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-05 00:11:38 +09:00
altair823
6f95e89456 fix(ollama): PR #21 review round 1 — m1+m3+m4+n1 (v0.2.3.1)
- m1 (Minor): saveOllamaSettings IPC가 setOllama throw 시 try/catch
  → { ok: false, reason: 'persist failed: ...' } 대칭 응답
- m3 (Minor): Modal ESC=close + Enter=save 키 핸들러 + 첫 input autoFocus
- m4 (Minor): handleSave 첫 줄 if (saving) return; — sync double-click 가드
- n1 (Nit): 'gemma4:e4b' / 'http://localhost:11434' magic
  → src/shared/constants.ts 의 DEFAULT_OLLAMA_MODEL / DEFAULT_OLLAMA_ENDPOINT

defer to v0.2.4 backlog:
- m2: ollama_unreachable.reason 에 endpoint URL 노출 (PII 우회) — telemetry masking 정책

skip:
- i1 (race UX): acknowledge only, 정확성 영향 0
- m5 (abort try/catch): 현재 LocalOllamaProvider.abort 는 throw X
- m6 (first-boot blocking): 무시 가능
- n2 (offReplace): 현재 listener callsite 0건

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 23:53:42 +09:00
altair823
0c0327ddb6 feat(ollama): 트레이 메뉴 "Ollama 설정..." (v0.2.3.1)
- createTray 10번째 positional callback runOpenOllamaSettings
- 트레이 → 메뉴 클릭 → main 이 inbox:openOllamaSettings IPC push
- renderer App.tsx 가 구독해 modal open

backlog #4/#26 (TrayCallbacks object refactor) 와 합산 — v0.2.4 시 일괄 정리

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 23:43:00 +09:00
altair823
833a598368 feat(ollama): OllamaSettingsModal + App mount + OllamaBanner 설정 링크 (v0.2.3.1)
- OllamaSettingsModal: endpoint + model freetext 입력, 저장 시 healthCheck → 성공 닫기, 실패 inline 에러
- App.tsx: ollamaSettingsOpen state + onOpenOllamaSettings IPC subscribe
- OllamaBanner: onOpenSettings prop 추가, 우측 "설정" 버튼
- preload + types: onOpenOllamaSettings listener bridge

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 23:40:31 +09:00
altair823
4153284af1 fix(ollama): saveOllamaSettings 가 health.runOnce() 즉시 호출 (T4 review)
T4 fallback comment "60s polling cycle" 대신 HealthChecker 의 기존 public
method runOnce() 사용. 사용자가 settings 저장하자마자 OllamaBanner 갱신.
runOnce 는 이미 inbox:ollamaRecheck IPC 가 사용 중인 패턴.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 23:37:57 +09:00
altair823
cee39a90aa feat(ollama): index 부팅 + IPC + preload + types (v0.2.3.1)
- index.ts: SettingsService.load() 후 endpoint/model 결정 (settings > env > default)
- IPC: inbox:loadOllamaSettings + inbox:saveOllamaSettings
  - save: 임시 provider 로 healthCheck 통과 시에만 영속화 + holder.replace
  - 기존 in-flight generate 는 abort?.() (optional method)
- preload + InboxApi shared types

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 23:36:46 +09:00
altair823
d1f36250e7 fix(ollama): InferenceProvider — abort?: () => void optional 추가 (T3 review)
T3 가 ProviderHolder 를 InferenceProvider 로 추상화. 단 IPC handler 가
holder.get().abort() 호출 예정 — interface 에 method 가 없으면 typecheck 실패.

abort 는 in-flight generate 중단용이라 모든 provider 가 지원할 필요는 없음
→ optional method 로 추가. caller 는 holder.get().abort?.() 패턴 사용.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 23:34:17 +09:00
altair823
9fef2edb6e feat(ollama): ProviderHolder + AiWorker/HealthChecker refactor (v0.2.3.1)
- ProviderHolder: mutable holder + listeners, indirection layer
- AiWorker: constructor InferenceProvider → ProviderHolder
  this.provider.x → this.holder.get().x 전환
- HealthChecker: 동일 패턴
- src/main/index.ts: provider 를 ProviderHolder 로 감싸서 생성
- 기존 AiWorker / HealthChecker 테스트의 constructor 호출에 ProviderHolder wrap
- 단위 +2 cases (ProviderHolder)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 23:32:20 +09:00
altair823
c77c30be83 feat(ollama): LocalOllamaProvider — abort() + AbortController instance field (v0.2.3.1)
- abortController 가 method-local 에서 private instance field 로 이동
- public abort() 메서드 — 외부에서 in-flight generate 강제 중단
- ProviderHolder.replace() 시 호출되어 endpoint 변경 즉시 반영
- 단위 +2 cases (abort cancellation, model 파라미터)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 23:26:48 +09:00
altair823
de895b8fec feat(settings): SettingsService — JSON 영속화 + zod 검증 (v0.2.3.1)
- `<profileDir>/settings.json` atomic write (temp + rename)
- 손상 JSON / 파일 없음 → 빈 객체 fallback (no throw)
- in-memory cache (load 1회 file read)
- zod .strict() schema for ollama { endpoint: URL, model: string }
- 단위 +6 cases

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 23:23:32 +09:00
altair823
61b6fa6c1f fix(recall): PR review round 1 — i1 race + m1~m4 + n2 (#6 v0.2.3)
- i1 (Important): RecallBanner shownIds → useRef (state setState 트리거 race 차단)
  store 의 recallShownIds 필드 제거 (dead — useRef 가 대체)
- m1 (Minor): snoozeRecall candidate-null race 코멘트 (의도적 emit skip 명시)
- m2 (Minor): dismissRecallNote 후 recallSnoozeUntilMs = null clear
- m3 (Minor): CaptureService.markRecallOpened 의 dead local 'before' inline check 로 제거
- m4 (Minor): RecallBanner title 빈 케이스 fallback '(제목 없음)'
- n2 (Nit): NoteCard id load-bearing 의미 1줄 코멘트

skip: n1 (KST 4번째 inline duplicate — 프로젝트 전반 패턴, v0.2.4 nextKstMidnightMs 통합),
      n3 (ipcMain.on vs handle — 다른 IPC 와 패턴 일관)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-02 13:38:52 +09:00
altair823
646fe7a7ab feat(recall): RecallBanner + App.tsx mount + NoteCard id (#6 v0.2.3)
- RecallBanner: 노트 제목 + N일 전 + 3 버튼 (열어보기/다음에/더 이상)
- 첫 렌더 시 emitRecallShown (recallShownIds Set 으로 per-session 1회 제약)
- snoozeUntilMs 만료 setTimeout (ExpiryBanner 패턴)
- 위치: ExpiryBanner 다음 (banner stack 끝)
- NoteCard 외곽 div 에 id="note-${note.id}" — "열어보기" scrollIntoView target
- 컬러 테마: 파랑 (#e8f0fe / #4a7ec0) — 다른 banner (적/황/적) 와 구별

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-02 13:28:58 +09:00
altair823
f4e1af83fe feat(recall): renderer store — recallCandidate + 4 actions (#6 v0.2.3)
- recallCandidate, recallSnoozeUntilMs, recallShownIds (Set) state
- loadInitial / refreshMeta 가 listRecallCandidate Promise.all 합류
- loadRecallCandidate / openRecall / dismissRecallNote / snoozeRecall actions
- snoozeRecall: KST 다음 자정 (snoozeExpired 패턴 일관) + emitRecallSnoozed
- openRecall / dismissRecallNote: API 호출 후 다음 후보 fetch
- 신규 store.recall.test.ts +3 cases

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-02 13:25:49 +09:00
altair823
20394bf2a3 feat(recall): IPC + preload + InboxApi — 5 channels (#6 v0.2.3)
- ipcMain.handle: list/markOpened/dismiss/emitShown/emitSnoozed
- preload inboxApi: 5 entries (ipcRenderer.invoke)
- shared/types InboxApi: 5 method signatures

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-02 13:22:16 +09:00
altair823
0c59ce3715 feat(recall): CaptureService — 5 methods (list/open/dismiss/shown/snoozed) (#6 v0.2.3)
- listRecallCandidate(): repo.findRecallCandidate 위임
- markRecallOpened(id): last_recalled_at 갱신 + recall_opened emit
- dismissRecall(id): recall_dismissed_at 갱신 + recall_dismissed emit
- emitRecallShown(id): ageDays 계산 + recall_shown emit
- emitRecallSnoozed(id): recall_snoozed emit
- private computeAgeDays(note): last_recalled_at ?? created_at 기준 일수
- 단위 +4 cases

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-02 13:20:44 +09:00
altair823
59cfb711cd feat(recall): telemetryStats + EmitInput — recall 누적 + 열림율 + 평균 ageDays (#6 v0.2.3)
- DailyRow +4 cols (recall_shown/opened/dismissed/snoozed)
- accumulators + 4 branches + recallAgeDaysSum
- table 컬럼 +4
- summary lines: "- 회상 추천: shown N / opened O / dismissed D / snoozed S (열림율 X%)"
                 "- 회상 평균 ageDays: avg"
- TelemetryService.EmitInput union 15 → 19
- 단위 +2 cases

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-02 13:17:49 +09:00
altair823
b94e68238c feat(recall): telemetryEvents — recall_shown/opened/dismissed/snoozed zod schemas (#6 v0.2.3)
- RecallShownPayload { noteId, ageDays: int>=0 } .strict()
- recall_opened/dismissed/snoozed → NoteIdPayload 재사용
- TelemetryEventSchema union 15 → 19
- 단위 +3 cases (recall_shown valid, extra field 거부, opened/dismissed/snoozed valid)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-02 13:13:49 +09:00
altair823
0eb2e6282f feat(recall): NoteRepository — findRecallCandidate + markRecallOpened + dismissRecall (#6 v0.2.3)
- findRecallCandidate(): 7일+ 안 본 + 30일+ dismiss 만료 + ai='done' + 마감 안 임박 + LIMIT 1
- markRecallOpened(id, now): last_recalled_at 갱신
- dismissRecall(id, now): recall_dismissed_at 갱신
- KST 보정 SQL date('now','+9 hours')
- 단위 +5 cases (empty/recent/old/dismiss expiry/exclude variants)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-02 13:11:14 +09:00
altair823
d8621d55e0 fix(tag-vocab): PR review round 1 — i1 dedup + m2 test gap (#3 v0.2.3)
- i1 (Important): AiWorker per-tag emit 루프에 res.tags Set dedup
  AI 가 같은 태그 중복 응답 시 hit count 2번 emit 되던 통계 왜곡 수정
  + 테스트 1개 (중복 태그 1 hit + 1 miss 검증)
- m2 (Minor): NoteRepository.getTopUsedTags LIMIT-then-filter 테스트 갭
  + 테스트 1개 (limit=3 + 한글 1 + kebab 2 → 결과 length=2 lock-in)

skip: m1 (per-tag serial await — ai_succeeded 패턴 일관),
      n1 (prompt 빈 줄 cosmetic), n2 (tagId positive — AUTOINCREMENT 1+)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-02 12:49:36 +09:00
altair823
3e0f710c70 feat(tag-vocab): AiWorker — vocab fetch + per-tag hit/miss emit (#3 v0.2.3)
- processJob 가 generate 직전 repo.getTopUsedTags(20) fetch
- provider.generate 에 vocab 전달 (LocalOllamaProvider 가 prompt 에 주입)
- ai_succeeded emit 후 per-tag 분류 → tag_vocab_hit/miss emit
  - hit: vocabSet.has + getTagIdByName lookup → { tagId, vocabSize }
  - miss: { vocabSize }
- AiTelemetryEmitter union 4종 (ai_succeeded/ai_failed/tag_vocab_hit/tag_vocab_miss)
- 단위 +4 cases (vocab passthrough, hit+miss, vocab=[] all miss, per-tag emit count)
- collectingTelemetry mock → AiTelemetryEmitter 타입 적용 (typecheck 통과)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-02 12:33:16 +09:00
altair823
26f1db5626 feat(tag-vocab): TelemetryService EmitInput +tag_vocab_hit/miss + 테스트 narrowing 확장 (#3 v0.2.3)
- EmitInput union 13 → 15
- narrowing guards (noteId 없는 kind 분기) 에 tag_vocab_hit/miss 추가

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-02 12:29:24 +09:00