v0.2.3 cut 7항목 동안 final reviewer + PR review 에서 발견된 minor/nit 중 의도적 deferred 38건 누적. 기존엔 user-level memory 에만 있어 사용자가 직접 보거나 편집 어려움 → repo 안으로 lift. dogfood 1주 soak 동안 user 가 직접 prune / 우선순위 표시 / 새 항목 추가 가능. v0.2.4 brainstorm 진입 시 본 doc 가 1차 backlog reference. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
13 KiB
v0.2.4 Backlog
v0.2.3 cut (7항목 / PR #13~#19) 동안 final reviewer + PR review round 1 에서 발견된 minor / nit 중 의도적으로 deferred 한 항목 누적. v0.2.3 dogfood soak 후 신규 피드백 + 본 리스트 일괄 triage → v0.2.4 cut 결정.
누적 시작일: 2026-05-01 (#7 telemetry skeleton 머지 시점) 최종 갱신: 2026-05-02 (v0.2.3 cut 7/7 완료) 총 항목 수: 38
Defer 사유 카테고리
각 항목은 머지 전 inline fix 보다 v0.2.4 영역으로 미룬 명시적 사유 가짐:
- Cross-cutting refactor — 한 PR 안에서 부분만 고치면 inconsistency. 일괄 cleanup task 영역. (예: KST helper 4 callsite 통합,
createTraypositional callbacks 전체 객체화) - Data-dependent — dogfood telemetry 분포 보고 결정해야 의미. (예: top-N 튜닝, recall_shown lifetime dedup 정책)
- Cosmetic / style — 동작 영향 0, 다른 일괄 cleanup task. (예:
now()두 번 호출,as any[]통합)
How to apply
v0.2.4 brainstorm 시 본 리스트를 1차 backlog 로 사용. 항목별로:
- (a) 그대로 cleanup
- (b) #4~#6 영향 받아 변형
- (c) defer-further 결정
- (d) drop (만에 하나 outdated)
v0.2.3 #7 Telemetry skeleton 누적 (2026-05-01)
-
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()한 번 추출. -
DAY_MS = 24*60*60*1000magic number —cleanupOldFiles:39+readAllRecent:78(+KST_OFFSET_MS간접). 모듈 상단에const DAY_MS = 24 * 60 * 60 * 1000;추출. -
KST helper duplication —
TelemetryService.todayKstIso+telemetryStats.kstDate+AiWorker.todayKstAsDate/todayKstAsIso. 4번째 caller (예: 회상 schedule, 만료 batching) 등장 시src/main/util/kst.ts로 통합. -
createTraypositional 폭주 —tray.ts:51가 7 positional callbacks. #1 ollama 회복 / #4 휴지통 비우기 등 트레이 메뉴 추가 시 8+ 도달 → readability threshold 넘김.TrayCallbacksobject 로 refactor. -
AiFailedReasonunion 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 파생만) -
media.gc.run()의.catch누락 — T11 에서telemetry.cleanupOldFiles의.catch일관성 처리 시media.gc도 같은 패턴 (.catch없음) 발견.backup.runDaily()와 컨벤션 통일 위해.catch((e) => logger.warn('media.gc.failed', { reason: String(e) }))추가. -
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)
-
stats.md exhaustiveness check —
telemetryStats.aggregateStats의 7-arm if/else if 가 union 확장 시 silent fall-through.else { const _: never = ev; }추가로 컴파일 단계 가드. -
휴지통 회수율 ratio 의미 코멘트 —
restore / trash가 event-level ratio (한 노트 trash-restore 반복 시 100% 가능). spec §6.2 의 "회수 도구 동작?" 질문에는 충분, 단 unique-note 회수율로 오해할 여지. 코드 옆 1줄 코멘트. -
restore시 AI 결과 보존 + pending_jobs 미재생성 — restore 가deleted_at = NULL만, pending_jobs 안 재생성. 사용자가 trash 도중 AI fail 한 노트를 restore 시 재처리 경로 부재. v0.2.3 dogfood 에서 빈도 보고 결정 — drop / per-note retry 버튼 / 자동 재처리 중. -
restoreNote(id)precondition 노출 — store 의 낙관적 갱신이trashNotes에 노트가 있어야 동작. 명령 팔레트 / 프로그래밍 호출 케이스 시 silently no-op. 현재는 trash view 한정이라 OK. main 이 trash/restore 시pushNoteUpdated보내도록 변경하면 더 견고. -
inbox:trashCountcap 200 silent undercount — UI 만 200 cap,repo.emptyTrash()SQL 은 unbounded. 350 노트 trash 시 dialog "200개 영구 삭제" 표시되지만 실제 350 모두 삭제.repo.countTrashed()추가로 둘 다 정확히. (잠재 UX 버그 — pull-forward 후보) -
NoteCard
mode='trash'의onDeleteddead-code — trash 카드는onPermanentDelete/onRestore만 사용.onDeletedprop 은 호출되지 않음 (App.tsx 가 pass-through). API 깔끔히 —onDeleted?optional + trash 분기 미전달. -
탭 ARIA 패턴 —
aria-pressed로 toggle 버튼 표현. canonical 은role="tab"+aria-selected. screen reader 동작 OK 지만 a11y audit 시 정정 후보. -
inbox:delete채널 rename — semantic 이 hard → soft 인데 채널 이름 그대로. v0.2.4 에서inbox:trash로 rename 검토 (기존 호출 0건 보장 후). -
per-note 영구 삭제 telemetry 사용량 — v0.2.3 dogfood 에서
permanent_deleteevent 빈도 확인. 거의 0 이면 v0.2.4 에서 per-card "영구 삭제" 버튼 제거 + bulk emptyTrash 만 (UX 단순화). 빈번하면 유지.
v0.2.3 #5 만료 추천 누적 (2026-05-01)
-
dialog 버튼 순서 vs spec §5.3 — spec 은
['취소','옮기기'], default=0, 구현은['옮기기','취소'], defaultId=1, cancelId=1(inboxApi.ts:117). 효과 동일 (default = cancel). v0.2.4 에서 spec 또는 impl 한쪽 통일. -
loadExpired()미사용 —loadInitial/refreshMeta가 inline fetch, App.tsx 도 호출 안 함 (test 만 exercise). v0.2.4 dogfood 후에도 consumer 미발생 시 제거 검토. -
store
KST_OFFSET_MSinline duplication —store.ts:166의snoozeExpired가 inline KST 계산.@main/util/kstDate.ts와 동일 알고리즘이지만 alias 경계 (main vs renderer) 로 import 불가.src/shared/util/kstDate.ts로 lift 검토. (#3, #34 와 합산 가능) -
telemetry emit
.catch(() => {})가 silent —CaptureService.listExpired/trashExpiredBatch가 그대로. v0.2.4 telemetry 하드닝 시 debug log path (project pattern 통일) 추가 검토. -
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 추출 검토. -
NoteRepository hydrate 의
as any[]일괄 cleanup —findExpiredCandidatesround 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)
-
createTray8 positional callbacks — #1 cut 에서 8개 도달, v0.2.4 backlog #4 와 정합 (TrayCallbacks object refactor 약속). #2 retry 또는 #6 reminder cut 에서 추가 항목 (예: "재시도 N건") 등장 시 9+ 회피 위해 본격 refactor. -
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" />) 추출 검토. -
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)
-
createTray9 positional callbacks — #2 cut 에서 9개 도달 (refreshTrayFailedCount 포함). #4TrayCallbacksobject refactor 가 이제 readability blocker. #3 / #6 cut 어느 쪽이든 추가 callback 더 들어오기 전에 우선 처리. -
refreshTrayFailedCountexported singleton state —tray.ts에_failedCountmodule-scoped state + setter 패턴. 모듈 캡슐화로 작동하지만 multi-window 또는 multi-tray 시 broken. v0.2.4 refactor 시 TrayController class 또는 store-driven 으로 정리. -
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)
-
getTopUsedTags(20)magic number —AiWorker.processJob:137가repo.getTopUsedTags(20)hardcoded. spec §7 Out 에 "top-N 튜닝" 명시. v0.2.4 dogfood telemetry (tag_vocab_hit/missratio) 보고VOCAB_TOP_N모듈 상수 추출 + 튜닝 결정. -
getTopUsedTagsLIMIT-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 확장 시 SQLGLOB으로 SQL-side 필터 대안 검토 (또는LIMIT ?*2overfetch+slice). -
vocabSetstrict-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 검토. -
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_succeededemit 도 serial 이라 패턴 일관성 우선 skip. v0.2.4 telemetry 하드닝 시 일괄 변경 검토. -
PROMPT_VERSIONtelemetry payload 미포함 — v0.2.3 cut 에선 단일 버전 (4) 만 굴러가서 무의미. v0.2.4/v0.2.5 prompt 튜닝 후 어느 버전이 어떤 hit-rate 만든지 추적 시tag_vocab_hit/misspayload 에promptVersion추가 검토. spec §7 Out 명시.
v0.2.3 #6 RecallBanner 누적 (2026-05-02)
-
KST midnight inline calc 4번째 복제 —
store.ts의snoozeRecall(#6) +snoozeExpired(#5) +NoteCard.todayKstIso+ 다른 1곳, 그리고kstDate.tsutil 도 별도 존재. 4 callsite 모두 동일 알고리즘. v0.2.4 에서nextKstMidnightMs()/kstTodayIso()단일 util 통합 + alias 경계 (main vs renderer) 해결책. backlog #3, #19 와 합산. -
recall_shownper-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 영속 마커). -
emitRecallShown/emitRecallSnoozed가 fire-and-forget 인데ipcMain.handle사용 — 더 honest 한 패턴은ipcMain.on(return value 없음). 현재는 다른 IPC 와 패턴 일관성 우선. v0.2.4 IPC 정리 시handlevson구분 일괄 검토. -
NoteCard
id="note-${id}"load-bearing — RecallBanner 의scrollIntoViewtarget. 단순 DOM lookup 이라 shadow DOM / portal 미지원. v0.2.4 에서 다른 surface (예: 검색 결과에서 스크롤) 등장 시 ref-forwarding 패턴 검토.
post-cut next-step (status, not backlog)
- v0.2.3 cut 7/7 완료 → binary 빌드 단계 — slice §7 strict-pin patch 증분으로 v0.2.3 binary 빌드 + dogfood 핸드오프. ≥1주 soak 후 telemetry export 분석으로 v0.2.4 brainstorm 트리거. (✓ 2026-05-02 빌드 완료, hotfix #20 + publish:null 포함, release 재생성 완료)
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 호출만 하면 됨