§6.2 의 expired_banner_shown signature dedup 위치를 zustand store(renderer)
→ CaptureService(main) 로 변경. 결과: 신규 IPC 채널 1개 추가 회피.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
PR #14 회차 1 review actionable — `inbox:trashCount` 와 `emptyTrash` dialog
가 `listTrashed({limit:200})` 로 카운트를 도출하면서 (a) hot path 에서 N rows
+ tags/media JOIN hydrate 비효율 (b) trash > 200 시 dialog message 가
실제 SQL DELETE 동작과 mismatch ('200개 영구 삭제합니다' 표시 vs 500개
실제 삭제) 발생.
NoteRepository.countTrashed() — `SELECT COUNT(*) FROM notes WHERE deleted_at
IS NOT NULL` 단일 쿼리. hydrate 없이 정확한 카운트만 반환. 두 IPC 핸들러를
이 메서드 호출로 교체.
테스트: 3 신규 단위 테스트 (0 trash / 부분 trash / 200 cap 초과 범위)
292 → 295 (+3). typecheck 0 errors.
deferrable (v0.2.4 backlog 그대로): AiWorker race guard 강화, restore self-guard,
limit 200 매직 넘버 상수화.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- I1: trashCount 가 upsertNote 안에서 항상 trashNotes.length 로 덮어써져
server 값 (refreshMeta) 손상. showTrash=true (trashNotes cache-loaded)
일 때만 local recompute.
- I2: restoreNote 의 "fallback for missed event" 주석 부정확 — main 은
trash/restore 시 pushNoteUpdated 안 보냄. 자가 갱신이 primary mechanism.
주석 정정.
- M5: restoreNote 테스트가 IPC 호출만 검증, 노트 이동 미검증. trashNotes
→ notes 라우팅 + deletedAt=null 어설션 추가. + I1 회귀 가드 테스트 신규.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
review T9 flagged 2 service-layer defenses:
#1: deleteNote/restoreNote/permanentDeleteNote 의 idempotency. 이미 trash 인
노트를 trash 하거나, 이미 active 인 노트를 restore 하거나, 존재하지 않는 노트를
permanentDelete 시 telemetry 가 spurious 하게 emit → restore/trash ratio (T8)
오염. findById 가드로 의미 없는 emit skip.
#2: permanentDeleteNote 의 disk cleanup unguarded. store.deleteNoteDirectory
실패 시 (Windows file-lock 등) telemetry 가 영영 emit 안 되고 IPC 호출자가
이미 성공한 작업에 에러 propagate. emptyTrash 와 동일하게 try/catch 로 감싸
best-effort. orphan dir 은 future janitor 가 정리.
Tests: 12/12 still pass. typecheck 0 errors.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
T5 reviewer identified 2 reads outside NoteRepository that were missing the
'WHERE deleted_at IS NULL' filter, breaking the silent invariant beyond the
3 originally-listed methods.
- ContinuityService.get() now excludes trashed notes from streak / weekCount
/ lastNoteAt / recovery-toast math. A trashed note no longer counts toward
weekly streak (regression: streak felt fake after trash).
- NoteRepository.getPendingCount() now excludes trashed-but-still-pending
notes. trash() removes the pending_jobs row but leaves notes.ai_status='pending';
the count would have drifted upward as users trashed pending notes.
- MediaGc.run() gets an inline comment documenting why it intentionally does
NOT filter — trashed notes still own their media until permanentDelete /
emptyTrash. Removing here would defeat restore.
Also: migrations.due_date.test.ts had 2 brittle assertions
(latestVersion()===2, user_version===2) that broke with v3. Migration-system
version assertions belong in migrations.test.ts (already covered there);
m002-specific test keeps the due_date column assertion which is version-stable.
Tests: 245 → 265 (+20). typecheck 0 errors.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
v0.2.3 두 번째 항목의 mini-brainstorm 결과 lock.
UI=A (Inbox 탭 toggle), 필터=A (명시적 WHERE deleted_at IS NULL),
AiWorker race=C (pending_jobs cleanup + processJob 가드),
액션=B (per-card 영구 삭제 추가 — IPC 4채널 → 5채널, telemetry 3 → 4 events),
confirm/정렬/카드차이 모두 A.
self-review 후 ExportService/ImportService 충돌 정책 ambiguity 명시화.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
PR #13 회차 1 리뷰의 actionable 1건 + suggestion 3건 반영.
- `AiWorker` 의 `attempts` 필드가 success/failure 경로에서 비대칭 의미 (0-index vs count) 였던 문제. 둘 다 `attempt + 1` (실제 시도 횟수, 1-based) 로 통일. stats markdown 의 평균/분포 해석이 일관됨.
- `Date.now()` 직접 호출이 `opts.now` DI 를 우회하던 두 곳을 `this.now().getTime()` 으로 교체. 추후 durationMs 분포 테스트 작성 가능.
- `TelemetryService.emit` 의 `this.now()` 두 번 호출을 한 번 캐시로 통합. KST 자정 경계에서 ts 와 파일명 일자 불일치 가능성 제거.
- `readAllRecent` 의 `n.slice(7, 17)` 매직 슬라이스를 정규식 capture 그룹으로 교체. prefix 변경 시 한 곳만 수정.
테스트: AiWorker 성공 케이스의 `attempts: 0` → `attempts: 1` 갱신.
게이트: typecheck 0 errors, 245/245 unit tests pass.
Deferred (v0.2.4 backlog): 'aborted' user-cancel false-positive, tray menu submenu 분리.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Add .catch(...) to telemetry.cleanupOldFiles fire-and-forget for consistency
with backup.runDaily pattern (M1 from T10 code review).
- Mark Roadmap §3 #7 as completed (✓).
- Correct spec: tray:exportTelemetry was never an IPC channel — tray callbacks
run in main process directly. Replace with "트레이 콜백 (main 내부)".
Closes v0.2.3 task 1 of 7. Next task: #4 휴지통 (migration v3).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The T9 full-file replacement accidentally dropped the inline comment
documenting why the count label is conditional on _todayCount > 0
(F4-C UX rationale). No behavior change.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Original 'counts events per KST day' test used UTC times that bucket
identically under both KST and naive UTC slice — would not catch a regression
where kstDate was replaced with ev.ts.slice(0,10). Add an explicit
near-midnight case (2026-05-01T15:30Z = 2026-05-02 00:30 KST) that fails
under naive UTC and passes under correct KST conversion.
6 tests pass (was 5).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Earlier test used '/proc/0/...' as the unwritable dir. On Windows this
resolved to 'C:\proc\0\...' and mkdir({recursive: true}) silently created
it — the silent code path was never exercised, plus filesystem side-effect
leaked outside the test tmpdir.
Replace with a path that points to an existing file (mkdir on a file path
fails on every platform). Also add a companion test that confirms silent
is opt-in: without {silent: true}, the same fs failure DOES throw.
7 tests pass (was 6).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
v0.2.2 dogfood 7항목 (#7 telemetry 신설 + #1~#6) 단일 cut 로드맵.
데이터 안전 우선 (C 채택), schema migration v3 3컬럼 한 묶음 (B),
trash↔backup/export B 정책, #6 = 1 spike 흡수.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>