feat(v026): bugs + cleanup — 16 backlog 항목 처리 #24

Merged
altair823 merged 16 commits from feat/v026-bugs-cleanup into main 2026-05-04 17:06:33 +00:00
Owner

Summary

정식 v0.2.6 cut. backlog 16건 (4 bug + 12 cleanup → 13 task) 통합 처리. v0.2.4/v0.2.5 patch + hotfix 후 첫 정식 cut.

Backlog 처리 (16건)

Bugs (4)

# 항목 처리
#10 restoreNote + pending_jobs 재생성 failed → pending reset + INSERT OR IGNORE
#12 trashCount cap 이미 fix (v0.2.3 #4) — tests +2
#45 autostart 풀림 getLoginItemSettings({ args: ['--hidden'] }) 명시 + 진단 로그 (dogfood verify)
#46 hidden-start race requestSingleInstanceLock(additionalData) + handler hidden flag 체크

Cleanup (12 → 9 cluster)

Cluster 항목 처리
C1 #3+#19+#34 KST helper 통합 → src/shared/util/kstDate.ts (4 callsite migrate)
C2+C3 #4+#23+#26+#27 TrayCallbacks 객체화 (10 positional → 1-arg + Partial)
C4 #5 AiFailedReason union 단일 export (zod z.infer)
C5 #21 hasNoteId type predicate (TelemetryService.test narrowing 단축)
C6 #22 NoteRepository hydrate as Record<string, unknown>[] 통일
C7 #24+#41 <Banner severity> shared component (4 banner migrate)
C8 #8 aggregateStats exhaustiveness check (else { _: never })
C9 #15+#29+#42+#9 microfixes (channel rename / VOCAB_TOP_N const / Modal URL pre-check / ratio comment)

Out of scope (잔여 14건)

Telemetry data-dependent (#7/#16/#18/#20/#25/#28/#30/#31/#32/#33/#35/#36/#39/#40) — v0.2.7 brainstorm 영역. 별도 brainstorm (#11/#14/#17/#37) 도 후속.

Spec & Plan

  • Spec: docs/superpowers/specs/2026-05-05-v026-bugs-cleanup-design.md
  • Plan: docs/superpowers/plans/2026-05-05-v026-bugs-cleanup.md

Gates

  • typecheck 0
  • 단위 413 → 424 (+11: kstDate +4 / restoreNote +3 / countTrashed +2 / hasNoteId +2)
  • e2e 1/1
  • Version 0.2.5 → 0.2.6

Test Plan (dogfood)

  • typecheck / unit / e2e
  • dogfood: #10 trash 도중 AI fail → restore 시 자동 재처리 확인
  • dogfood: #12 trash 200+ 시 dialog 정확 N 표시
  • dogfood: #45 autostart 토글 → 종료 → 재실행 → 체크박스 유지 + autostart.state 로그 확인
  • dogfood: #46 NSIS install 직후 사용자 클릭 + autostart 동시 시도 → hidden 인스턴스가 inbox 창 안 띄우는지
  • dogfood: 트레이 메뉴 (#4-callback object 화) UX 회귀 X
  • dogfood: 4 banner 외관 회귀 X (warning 황색 / error 적색 / info 청색)

다음

머지 후 binary v0.2.6 빌드 (Windows + Mac) + Gitea release. v0.2.7 brainstorm 트리거: dogfood ≥1주 soak + telemetry export + 잔여 14건 + 신규 피드백 일괄 triage.

Final reviewer 칭찬

  • 13 commit cluster cumulative 일관성 ✓
  • B1 transaction race-safe + INSERT OR IGNORE
  • B4 single-instance lock + additionalData 정확
  • C1 alias 경계 (main vs renderer) 정상 처리
  • C2 TrayState Partial merge 깔끔
  • C5 hasNoteId predicate 가 NO_NOTE_ID_KINDS Set 기반 union 확장 친화
  • C8 exhaustive never 컴파일 가드
## Summary 정식 v0.2.6 cut. backlog 16건 (4 bug + 12 cleanup → 13 task) 통합 처리. v0.2.4/v0.2.5 patch + hotfix 후 첫 정식 cut. ## Backlog 처리 (16건) ### Bugs (4) | # | 항목 | 처리 | |---|---|---| | #10 | restoreNote + pending_jobs 재생성 | failed → pending reset + INSERT OR IGNORE | | #12 | trashCount cap | 이미 fix (v0.2.3 #4) — tests +2 | | #45 | autostart 풀림 | `getLoginItemSettings({ args: ['--hidden'] })` 명시 + 진단 로그 (dogfood verify) | | #46 | hidden-start race | `requestSingleInstanceLock(additionalData)` + handler hidden flag 체크 | ### Cleanup (12 → 9 cluster) | Cluster | 항목 | 처리 | |---|---|---| | C1 | #3+#19+#34 | KST helper 통합 → `src/shared/util/kstDate.ts` (4 callsite migrate) | | C2+C3 | #4+#23+#26+#27 | TrayCallbacks 객체화 (10 positional → 1-arg + Partial<TrayState>) | | C4 | #5 | AiFailedReason union 단일 export (zod z.infer) | | C5 | #21 | `hasNoteId` type predicate (TelemetryService.test narrowing 단축) | | C6 | #22 | NoteRepository hydrate `as Record<string, unknown>[]` 통일 | | C7 | #24+#41 | `<Banner severity>` shared component (4 banner migrate) | | C8 | #8 | aggregateStats exhaustiveness check (`else { _: never }`) | | C9 | #15+#29+#42+#9 | microfixes (channel rename / VOCAB_TOP_N const / Modal URL pre-check / ratio comment) | ### Out of scope (잔여 14건) Telemetry data-dependent (#7/#16/#18/#20/#25/#28/#30/#31/#32/#33/#35/#36/#39/#40) — v0.2.7 brainstorm 영역. 별도 brainstorm (#11/#14/#17/#37) 도 후속. ## Spec & Plan - Spec: `docs/superpowers/specs/2026-05-05-v026-bugs-cleanup-design.md` - Plan: `docs/superpowers/plans/2026-05-05-v026-bugs-cleanup.md` ## Gates - typecheck 0 - 단위 413 → **424** (+11: kstDate +4 / restoreNote +3 / countTrashed +2 / hasNoteId +2) - e2e 1/1 - Version 0.2.5 → **0.2.6** ## Test Plan (dogfood) - [x] typecheck / unit / e2e - [ ] dogfood: #10 trash 도중 AI fail → restore 시 자동 재처리 확인 - [ ] dogfood: #12 trash 200+ 시 dialog 정확 N 표시 - [ ] dogfood: #45 autostart 토글 → 종료 → 재실행 → 체크박스 유지 + `autostart.state` 로그 확인 - [ ] dogfood: #46 NSIS install 직후 사용자 클릭 + autostart 동시 시도 → hidden 인스턴스가 inbox 창 안 띄우는지 - [ ] dogfood: 트레이 메뉴 (#4-callback object 화) UX 회귀 X - [ ] dogfood: 4 banner 외관 회귀 X (warning 황색 / error 적색 / info 청색) ## 다음 머지 후 binary v0.2.6 빌드 (Windows + Mac) + Gitea release. v0.2.7 brainstorm 트리거: dogfood ≥1주 soak + telemetry export + 잔여 14건 + 신규 피드백 일괄 triage. ## Final reviewer 칭찬 - 13 commit cluster cumulative 일관성 ✓ - B1 transaction race-safe + INSERT OR IGNORE - B4 single-instance lock + additionalData 정확 - C1 alias 경계 (main vs renderer) 정상 처리 - C2 TrayState Partial merge 깔끔 - C5 hasNoteId predicate 가 NO_NOTE_ID_KINDS Set 기반 union 확장 친화 - C8 exhaustive never 컴파일 가드
altair823 added 15 commits 2026-05-04 16:52:31 +00:00
bugs (4): #10 restore + pending_jobs / #12 trashCount cap / #45 autostart 풀림 / #46 hidden-start race
cleanup (12 → 9 cluster): KST helper / TrayCallbacks 객체 / refreshTrayFailedCount singleton /
  AiFailedReason union / hasNoteId predicate / hydrate as any[] / Banner shared component /
  exhaustiveness check / microfixes (channel rename + VOCAB_TOP_N + Modal URL pre-check + ratio 코멘트)

dogfood telemetry 필요 14건은 v0.2.7 영역. 별도 brainstorm 4건도 v0.2.7+.

게이트 추정: 단위 413 → 427 (+14). version 0.2.5 → 0.2.6.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
순서: B1 → B2 → B4 → B3 → C1 → C4 → C5 → C6 → C8 → C2+C3 → C7 → C9 → T13.
B3 (autostart) 위험 task 는 cleanup 시작 직전, fail 시 빠른 회피.

각 task 별 file path / 상세 step / commit message 포함.
신규 단위 추정 +14 (413 → ~427).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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>
기존 UI 가 listTrash 200 limit 후 length 사용 → 350개 trash 시 dialog
"200개 영구 삭제" 표시되지만 실제 350 모두 삭제. 사용자 혼동 해소.

- NoteRepository.countTrashed() 신규 — SELECT COUNT(*) WHERE deleted_at IS NOT NULL
- IPC inbox:trashCount → countTrashed 사용
- 단위 +2 cases (>200 not capped, empty 0)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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>
추정 원인 (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>
기존 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>
기존 '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>
기존 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>
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>
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>
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>
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>
- #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>
bugs (4):
- #10 restoreNote 가 failed 노트 시 pending_jobs 재생성
- #12 trashCount cap → countTrashed() 정확 N (이미 fix 됨, tests 추가)
- #45 autostart 풀림 — args 비교 정확도 + 진단 로그
- #46 hidden-start race — additionalData 로 두 번째 hidden 구분

cleanup (12 → 9 cluster):
- #3+#19+#34 KST helper 통합 → src/shared/util/kstDate.ts (4 callsite migrate)
- #4+#23+#26+#27 TrayCallbacks 객체화 + state 통합 (10 positional → 1-arg + Partial<TrayState>)
- #5 AiFailedReason union 단일 export (zod z.infer)
- #21 hasNoteId type predicate (TelemetryService.test.ts narrowing 단축)
- #22 NoteRepository hydrate row type 통일 (as Record<string, unknown>[])
- #24+#41 Banner shared component (severity prop, 4 banner migrate)
- #8 stats.md exhaustiveness check (else { _: never })
- #15 IPC channel inbox:delete → inbox:trash
- #29 VOCAB_TOP_N const
- #42 Modal client-side URL pre-check (zod safeParse)
- #9 휴지통 회수율 ratio 의미 코멘트

게이트: typecheck 0 / 단위 424 / e2e 1
잔여 backlog: 14건 (telemetry data-dependent, v0.2.7 brainstorm)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
altair823 added 1 commit 2026-05-04 16:58:55 +00:00
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>
Author
Owner

PR #24 Round 1 review (controller-side)

항목 결과
Critical 1 (B1 production path dead code)
Important 1 (B1 in-memory worker queue 미통지)
Minor 2 (kstDate naming, trashCount race)
Nit 1 (ExpiryBanner useEffect closure)

Critical fix (commit a991008)

B1 (#10) 가 dead code 였음NoteRepository.restoreNote 새 메서드는 unit test 만 호출, production path (CaptureService.restoreNote) 는 옛 repo.restore() 호출. ai_status reset + pending_jobs INSERT 우회.

Fix: CaptureService.restoreNoterepo.restoreNote 호출 + before status 가 failed/pending 이면 deps.enqueue(id) 도 호출 (in-memory worker queue 갱신).

테스트 +2: failed → enqueue, done → skip enqueue.

Round 1 minors (skip / defer)

  • Minor 1 (kstDate naming): telemetryStats.kstDate(ts)kstTodayIso 사용 — historical event 의 KST 일자 추출이라 의미 정합. naming 리팩 v0.2.7 영역.
  • Minor 2 (trashCount race on showTrash): pre-existing, badge over-count 임시. v0.2.7 영역.
  • Nit (ExpiryBanner useEffect): 24h+ tab 켠 채 자정 넘는 edge case. 실용 영향 미미.

Round 1 final reviewer Important (B3 dogfood verify)

#45 autostart 풀림 fix 는 진단 fallback (args 명시 + 진단 로그). dogfood 에서 autostart.state 로그 확인 후 v0.2.7 결정.

Spec 컴플라이언스

  • 16 backlog 처리 (B1 production path 도 수정)
  • Out of scope 14건 미처리
  • Gates: typecheck 0 / 단위 413 → 426 (+13: kstDate +4, restoreNote +3, countTrashed +2, hasNoteId +2, CaptureService +2) / e2e 1/1

Verdict

APPROVE WITH FIX → round 2 APPROVE — Critical + Important inline 처리 완료, minors 수용 가능 수준.

## PR #24 Round 1 review (controller-side) | 항목 | 결과 | |---|---| | Critical | 1 (B1 production path dead code) | | Important | 1 (B1 in-memory worker queue 미통지) | | Minor | 2 (kstDate naming, trashCount race) | | Nit | 1 (ExpiryBanner useEffect closure) | ### Critical fix (commit `a991008`) **B1 (#10) 가 dead code 였음** — `NoteRepository.restoreNote` 새 메서드는 unit test 만 호출, production path (`CaptureService.restoreNote`) 는 옛 `repo.restore()` 호출. ai_status reset + pending_jobs INSERT 우회. **Fix**: `CaptureService.restoreNote` 가 `repo.restoreNote` 호출 + before status 가 failed/pending 이면 `deps.enqueue(id)` 도 호출 (in-memory worker queue 갱신). 테스트 +2: failed → enqueue, done → skip enqueue. ### Round 1 minors (skip / defer) - **Minor 1 (kstDate naming)**: `telemetryStats.kstDate(ts)` 가 `kstTodayIso` 사용 — historical event 의 KST 일자 추출이라 의미 정합. naming 리팩 v0.2.7 영역. - **Minor 2 (trashCount race on showTrash)**: pre-existing, badge over-count 임시. v0.2.7 영역. - **Nit (ExpiryBanner useEffect)**: 24h+ tab 켠 채 자정 넘는 edge case. 실용 영향 미미. ### Round 1 final reviewer Important (B3 dogfood verify) `#45 autostart 풀림` fix 는 진단 fallback (args 명시 + 진단 로그). dogfood 에서 `autostart.state` 로그 확인 후 v0.2.7 결정. ### Spec 컴플라이언스 - 16 backlog 처리 ✅ (B1 production path 도 수정) - Out of scope 14건 미처리 ✅ - Gates: typecheck 0 / 단위 413 → **426** (+13: kstDate +4, restoreNote +3, countTrashed +2, hasNoteId +2, CaptureService +2) / e2e 1/1 ### Verdict **APPROVE WITH FIX → round 2 APPROVE** — Critical + Important inline 처리 완료, minors 수용 가능 수준.
Author
Owner

Round 2 — APPROVE

단계 결과
15 commits + Critical fix (a991008) pushed
Critical fix B1 production path 활성화 (CaptureService.restoreNote → repo.restoreNote + worker.enqueue)
Important 동시 처리 (in-memory queue 갱신)
Round 2 verdict APPROVE (0 new issue)
Gates typecheck 0 / 단위 426/426 / e2e 1/1

머지 후 알려줘. closure (local main sync + 브랜치 정리 + memory status v0.2.5 → v0.2.6 + binary 빌드 v0.2.6) 진행.

## Round 2 — APPROVE | 단계 | 결과 | |---|---| | 15 commits + Critical fix (`a991008`) | pushed | | Critical fix | B1 production path 활성화 (CaptureService.restoreNote → repo.restoreNote + worker.enqueue) | | Important | 동시 처리 (in-memory queue 갱신) | | Round 2 verdict | **APPROVE** (0 new issue) | | Gates | typecheck 0 / 단위 **426/426** / e2e 1/1 | 머지 후 알려줘. closure (local main sync + 브랜치 정리 + memory status v0.2.5 → v0.2.6 + binary 빌드 v0.2.6) 진행.
altair823 merged commit 8bc33da954 into main 2026-05-04 17:06:33 +00:00
altair823 deleted branch feat/v026-bugs-cleanup 2026-05-04 17:06:35 +00:00
Sign in to join this conversation.
No Reviewers
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: altair823-org/inkling#24