Files
inkling/docs/superpowers/v024-backlog.md
altair823 8ba43d939e docs(backlog): v0.2.3.1 dogfood 발견 +2건 (#44 버전 정보, #45 자동실행 버그)
PR #21 머지 후 dogfood 중 사용자 발견:
- #44: 버전 / 빌드 정보 표시 surface 부재 (트레이 / Inbox footer / About 모달)
- #45: 윈도우 자동 실행 옵션 재시작 후 풀려있는 버그
  (tray.ts:47-58, app.setLoginItemSettings + getLoginItemSettings 비대칭)

PR review deferred 와 별개의 raw UX/bug 발견. 신설 섹션 "v0.2.3 / v0.2.3.1
dogfood 발견" 으로 분리 — v0.2.4 brainstorm 시 우선순위 결정.

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

17 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 영역으로 미룬 명시적 사유 가짐:

  1. Cross-cutting refactor — 한 PR 안에서 부분만 고치면 inconsistency. 일괄 cleanup task 영역. (예: KST helper 4 callsite 통합, createTray positional callbacks 전체 객체화)
  2. Data-dependent — dogfood telemetry 분포 보고 결정해야 의미. (예: top-N 튜닝, recall_shown lifetime dedup 정책)
  3. 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)

  1. 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() 한 번 추출.

  2. DAY_MS = 24*60*60*1000 magic numbercleanupOldFiles:39 + readAllRecent:78 (+ KST_OFFSET_MS 간접). 모듈 상단에 const DAY_MS = 24 * 60 * 60 * 1000; 추출.

  3. KST helper duplicationTelemetryService.todayKstIso + telemetryStats.kstDate + AiWorker.todayKstAsDate/todayKstAsIso. 4번째 caller (예: 회상 schedule, 만료 batching) 등장 시 src/main/util/kst.ts 로 통합.

  4. createTray positional 폭주tray.ts:51 가 7 positional callbacks. #1 ollama 회복 / #4 휴지통 비우기 등 트레이 메뉴 추가 시 8+ 도달 → readability threshold 넘김. TrayCallbacks object 로 refactor.

  5. AiFailedReason union 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 파생만)

  6. media.gc.run().catch 누락 — T11 에서 telemetry.cleanupOldFiles.catch 일관성 처리 시 media.gc 도 같은 패턴 (.catch 없음) 발견. backup.runDaily() 와 컨벤션 통일 위해 .catch((e) => logger.warn('media.gc.failed', { reason: String(e) })) 추가.

  7. 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)

  1. stats.md exhaustiveness checktelemetryStats.aggregateStats 의 7-arm if/else if 가 union 확장 시 silent fall-through. else { const _: never = ev; } 추가로 컴파일 단계 가드.

  2. 휴지통 회수율 ratio 의미 코멘트restore / trash 가 event-level ratio (한 노트 trash-restore 반복 시 100% 가능). spec §6.2 의 "회수 도구 동작?" 질문에는 충분, 단 unique-note 회수율로 오해할 여지. 코드 옆 1줄 코멘트.

  3. restore 시 AI 결과 보존 + pending_jobs 미재생성 — restore 가 deleted_at = NULL 만, pending_jobs 안 재생성. 사용자가 trash 도중 AI fail 한 노트를 restore 시 재처리 경로 부재. v0.2.3 dogfood 에서 빈도 보고 결정 — drop / per-note retry 버튼 / 자동 재처리 중.

  4. restoreNote(id) precondition 노출 — store 의 낙관적 갱신이 trashNotes 에 노트가 있어야 동작. 명령 팔레트 / 프로그래밍 호출 케이스 시 silently no-op. 현재는 trash view 한정이라 OK. main 이 trash/restore 시 pushNoteUpdated 보내도록 변경하면 더 견고.

  5. inbox:trashCount cap 200 silent undercount — UI 만 200 cap, repo.emptyTrash() SQL 은 unbounded. 350 노트 trash 시 dialog "200개 영구 삭제" 표시되지만 실제 350 모두 삭제. repo.countTrashed() 추가로 둘 다 정확히. (잠재 UX 버그 — pull-forward 후보)

  6. NoteCard mode='trash'onDeleted dead-code — trash 카드는 onPermanentDelete/onRestore 만 사용. onDeleted prop 은 호출되지 않음 (App.tsx 가 pass-through). API 깔끔히 — onDeleted? optional + trash 분기 미전달.

  7. 탭 ARIA 패턴aria-pressed 로 toggle 버튼 표현. canonical 은 role="tab" + aria-selected. screen reader 동작 OK 지만 a11y audit 시 정정 후보.

  8. inbox:delete 채널 rename — semantic 이 hard → soft 인데 채널 이름 그대로. v0.2.4 에서 inbox:trash 로 rename 검토 (기존 호출 0건 보장 후).

  9. per-note 영구 삭제 telemetry 사용량 — v0.2.3 dogfood 에서 permanent_delete event 빈도 확인. 거의 0 이면 v0.2.4 에서 per-card "영구 삭제" 버튼 제거 + bulk emptyTrash 만 (UX 단순화). 빈번하면 유지.

v0.2.3 #5 만료 추천 누적 (2026-05-01)

  1. dialog 버튼 순서 vs spec §5.3 — spec 은 ['취소','옮기기'], default=0, 구현은 ['옮기기','취소'], defaultId=1, cancelId=1 (inboxApi.ts:117). 효과 동일 (default = cancel). v0.2.4 에서 spec 또는 impl 한쪽 통일.

  2. loadExpired() 미사용loadInitial/refreshMeta 가 inline fetch, App.tsx 도 호출 안 함 (test 만 exercise). v0.2.4 dogfood 후에도 consumer 미발생 시 제거 검토.

  3. store KST_OFFSET_MS inline duplicationstore.ts:166snoozeExpired 가 inline KST 계산. @main/util/kstDate.ts 와 동일 알고리즘이지만 alias 경계 (main vs renderer) 로 import 불가. src/shared/util/kstDate.ts 로 lift 검토. (#3, #34 와 합산 가능)

  4. telemetry emit .catch(() => {}) 가 silentCaptureService.listExpired/trashExpiredBatch 가 그대로. v0.2.4 telemetry 하드닝 시 debug log path (project pattern 통일) 추가 검토.

  5. TelemetryService.test.ts 의 noteId 가드 wideninge.kind !== 'empty_trash' && e.kind !== 'expired_banner_shown' && e.kind !== 'expired_batch_trash' 체인이 #6 추가 시 더 길어짐. hasNoteId(ev) type predicate helper 추출 검토.

  6. NoteRepository hydrate 의 as any[] 일괄 cleanupfindExpiredCandidates round 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)

  1. createTray 8 positional callbacks — #1 cut 에서 8개 도달, v0.2.4 backlog #4 와 정합 (TrayCallbacks object refactor 약속). #2 retry 또는 #6 reminder cut 에서 추가 항목 (예: "재시도 N건") 등장 시 9+ 회피 위해 본격 refactor.

  2. 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" />) 추출 검토.

  3. 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)

  1. createTray 9 positional callbacks — #2 cut 에서 9개 도달 (refreshTrayFailedCount 포함). #4 TrayCallbacks object refactor 가 이제 readability blocker. #3 / #6 cut 어느 쪽이든 추가 callback 더 들어오기 전에 우선 처리.

  2. refreshTrayFailedCount exported singleton statetray.ts_failedCount module-scoped state + setter 패턴. 모듈 캡슐화로 작동하지만 multi-window 또는 multi-tray 시 broken. v0.2.4 refactor 시 TrayController class 또는 store-driven 으로 정리.

  3. 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)

  1. getTopUsedTags(20) magic numberAiWorker.processJob:137repo.getTopUsedTags(20) hardcoded. spec §7 Out 에 "top-N 튜닝" 명시. v0.2.4 dogfood telemetry (tag_vocab_hit/miss ratio) 보고 VOCAB_TOP_N 모듈 상수 추출 + 튜닝 결정.

  2. getTopUsedTags LIMIT-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 확장 시 SQL GLOB 으로 SQL-side 필터 대안 검토 (또는 LIMIT ?*2 overfetch+slice).

  3. vocabSet strict-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 검토.

  4. AiWorker per-tag emit serial awaitfor (const tag of new Set(...)) 안의 await this.telemetry.emit(...) 가 직렬. 3 태그 시 file-append 3 round-trip. Promise.all 로 병렬화 가능, 단 ai_succeeded emit 도 serial 이라 패턴 일관성 우선 skip. v0.2.4 telemetry 하드닝 시 일괄 변경 검토.

  5. PROMPT_VERSION telemetry payload 미포함 — v0.2.3 cut 에선 단일 버전 (4) 만 굴러가서 무의미. v0.2.4/v0.2.5 prompt 튜닝 후 어느 버전이 어떤 hit-rate 만든지 추적 시 tag_vocab_hit/miss payload 에 promptVersion 추가 검토. spec §7 Out 명시.

v0.2.3 #6 RecallBanner 누적 (2026-05-02)

  1. KST midnight inline calc 4번째 복제store.tssnoozeRecall (#6) + snoozeExpired (#5) + NoteCard.todayKstIso + 다른 1곳, 그리고 kstDate.ts util 도 별도 존재. 4 callsite 모두 동일 알고리즘. v0.2.4 에서 nextKstMidnightMs() / kstTodayIso() 단일 util 통합 + alias 경계 (main vs renderer) 해결책. backlog #3, #19 와 합산.

  2. recall_shown per-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 영속 마커).

  3. emitRecallShown / emitRecallSnoozed 가 fire-and-forget 인데 ipcMain.handle 사용 — 더 honest 한 패턴은 ipcMain.on (return value 없음). 현재는 다른 IPC 와 패턴 일관성 우선. v0.2.4 IPC 정리 시 handle vs on 구분 일괄 검토.

  4. NoteCard id="note-${id}" load-bearing — RecallBanner 의 scrollIntoView target. 단순 DOM lookup 이라 shadow DOM / portal 미지원. v0.2.4 에서 다른 surface (예: 검색 결과에서 스크롤) 등장 시 ref-forwarding 패턴 검토.

v0.2.3.1 Ollama Settings 누적 (2026-05-04)

  1. ollama_unreachable.reason 에 endpoint URL 노출 (PII 우회)LocalOllamaProvider.healthCheck 가 catch err 시 reason: \unreachable: ${err.message}`로 emit.err.message안에http://192.168.x.x:11434/api/tags 같은 LAN endpoint URL 포함 가능. v0.2.3.1 의 in-app endpoint UI 가 LAN 사용을 흔하게 만들어 PII 우회 노출 경로 확대. v0.2.4 telemetry 하드닝 시: error class only (network/dns/timeout/...) 또는 host 마스킹 (:11434`) 정책. PR #21 round 1 m2 deferred.

  2. Settings 저장 vs HealthChecker 60s tick racesaveOllamaSettings IPC 가 health.runOnce() 호출, 동시에 60s 주기 tick 도 inFlight 가드 통해 같이 실행 시도. 정확성 영향 0 (가드로 dedup), 단 modal 닫기 직전 banner flicker 가능. PR #21 round 1 i1 acknowledge only. v0.2.4 dogfood 에서 실제 빈도 확인 후 결정 (visible 빈도 낮으면 무시).

  3. OllamaSettingsModal 인라인 스타일 — 60+ 줄 inline style. backlog #24 (banner CSS 추출) 와 합산. v0.2.4 에서 CSS module / theme variables 추출 시 함께.

  4. Modal 의 client-side URL validation 부재 — endpoint freetext 가 잘못된 형식 (예: 빈 문자열, 한글) 일 때 server-side healthCheck 만 검증. zod URL error message 가 opaque ("Invalid url"). v0.2.4 에서 client-side z.string().url() pre-check + 친화적 에러 메시지.

  5. createTray 10번째 positional callback — v0.2.3.1 cut 에서 10개 도달 (runOpenOllamaSettings 추가). backlog #4/#26 (TrayCallbacks object refactor) blocker 수준. v0.2.4 첫 cleanup 항목 후보.

v0.2.3 / v0.2.3.1 dogfood 발견 (2026-05-05)

본 cut 들의 머지 후 사용자가 dogfood 중 발견한 항목. PR review deferred 와 달리 raw UX/bug 발견.

  1. 버전 및 프로그램 정보 표시 방법 부재 — 현재 사용자가 설치된 Inkling 의 버전 (package.json 0.2.3.1) 을 UI 에서 확인할 path 없음. 트레이 메뉴 / Inbox 푸터 / 별도 "About Inkling" 모달 어느 surface 에도 정보 없음. 핸드오프 후 다른 머신에서 같은 버전인지 사용자가 직접 검증 불가. v0.2.4 에서 트레이 메뉴 "Inkling 0.2.3.1 정보..." 또는 Inbox 우하단 footer 형태로 추가 검토. 곁들여: 빌드 commit SHA, electron/node 버전, OS, profileDir 경로 등 디버그 정보 노출 (사용자가 issue report 시 첨부 가능).

  2. 윈도우 자동 실행 옵션이 재시작 후 풀려있는 버그 — 트레이 메뉴 "윈도우 시작 시 자동 실행" 체크 → 종료 → 재실행 시 체크박스가 풀려서 표시됨. 코드 (src/main/tray.ts:47-58) 가 app.setLoginItemSettings({ openAtLogin, args: ['--hidden'] }) 호출 후 다음 부팅 시 app.getLoginItemSettings().openAtLogin 이 false 반환. 추정 원인:

    • (a) Windows registry 에 쓴 exe path 와 현재 프로세스 path 가 다름 (NSIS 설치 위치 변경 / 버전 업데이트 시 새 디렉터리)
    • (b) Electron setLoginItemSettings Windows 구현 의 path canonicalization 이슈
    • (c) 우리 args: ['--hidden'] 와 actual launch 시 args 비교 mismatch
    • 영향: dogfood UX 핵심 마찰 — autostart 가 핸드오프 시 매번 수동 재설정 필요. 자동 실행 의도 자체가 dogfood "잊지 않고 매일 사용" 목적인데 깨짐.
    • v0.2.4 에서 우선순위 높음. 진단 절차: (1) app.getLoginItemSettings({ args: ['--hidden'] }) 형태로 args 전달해 비교 정확도 올리기, (2) registry 직접 inspect (HKCU\Software\Microsoft\Windows\CurrentVersion\Run\inkling) 로 path/args 확인, (3) executable path canonicalization (electron 이 short path 변환 적용 여부).

post-cut next-step (status, not backlog)

  1. 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 호출만 하면 됨