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
348e9ee402
chore(recall): #6 closure — strategy.md 갱신 + roadmap mark + 게이트 검증
...
- strategy.md §2.3 (오늘 회상 surface) / §4.3 (F4 측정 인프라) / §8 (banner stack) 갱신
- typecheck 0 / 단위 403 / e2e 1
- v0.2.3 7/7 — 모든 cut 완료. 다음: v0.2.3 binary 빌드
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com >
2026-05-02 13:30:49 +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
746671059e
docs(recall): #6 plan — 8 tasks TDD + 17 단위 cases (v0.2.3)
...
8 task TDD plan:
T1 NoteRepository (find/markOpened/dismiss, +5 cases)
T2 telemetryEvents (recall_shown 4 union members, +3 cases)
T3 telemetryStats + EmitInput union 19 (+2 cases)
T4 CaptureService (5 methods, +4 cases)
T5 IPC + preload + types (5 channels)
T6 Renderer store (recallCandidate + 4 actions, +3 cases)
T7 RecallBanner + App.tsx + NoteCard id
T8 closure (strategy.md + roadmap + gates)
총 신규 단위 +17. 단위 386 → 403 예상.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com >
2026-05-02 13:08:32 +09:00
altair823
e6494b8778
docs(recall): #6 spec — RecallBanner + 4 telemetry events (v0.2.3)
...
mini-brainstorm 2개 결정:
- Q1=A: snooze in-memory (KST 다음 자정, ExpiryBanner 패턴 일관)
- Q2=B: ageDays = last_recalled_at ?? created_at 기준
자명 결정:
- Banner 위치: ExpiryBanner 다음 (stack 끝)
- 0건 시 null return
- "열어보기" 동작: scrollIntoView (NoteCard 항상 expanded)
- scroll target: id="note-${id}" (ref 시스템 복잡도 회피)
핵심 invariants 6개 + privacy invariant + tests 17개 약속.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com >
2026-05-02 13:03:58 +09:00