두 macOS 한정 버그 묶음:
1. autostart --hidden 으로 spawn 시 quickCapture (NSPanel) 만 떠 있어
dock running indicator (점) 가 표출 안 됨 — NSPanel 은 NSApp main window
로 register 안 됨. inboxWindow 를 hidden 상태로 미리 create + ready-to-show
시점에 showInactive → hide trick 으로 NSApp 에 register, 사용자 화면
깜빡임 없이 dock 점 켜짐.
2. SettingsPage 의 자동실행 mismatch 경고가 macOS 에서 false positive.
macOS 13+ 의 SMAppService API 가 args 옵션 무시 + unsigned/Electron
앱에 대해 executableWillLaunchAtLogin 을 자주 false 로 반환 → 정상 등록
상태에서도 경고 떠 있음. AutostartDiagnostic 결과에 platform 필드 추가,
willLaunch 신호는 win32 에서만 mismatch 판정에 사용.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
gemma4:26b 등 vision 모델이 본문 없는 이미지 단독 입력을 의미 있게 처리 못 함
(여러 prompt 시도에도 빈 응답). 모델 한계 수용:
- AiWorker 가 rawText.trim()==='' && media.length>0 detect 시 vision call skip
- 자동 placeholder: '첨부 이미지' / '첨부 이미지 N장' + summary
- ai_provider='image-only-skip' (디버그성 식별자)
- NoteCard 노란 배너 제거 (사용자가 한계 수용, placeholder 자체로 충분)
- 사용자는 EditableField 로 제목/요약 직접 편집 가능
cold-start timeout / parseJsonLoose fallback / schema coerce 부담 모두 skip.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
gemma4:26b 가 본문 없이 이미지만 받으면 null 반환하는 한계 우회.
prompt 강화 + null 금지 명시 만으로 부족. one-shot 예시 (강아지/화이트보드)
2개로 모델이 입출력 구조 따라가도록 유도.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
gemma4:26b 가 "기기기기..." repetition loop 에 빠져 num_predict cap 도달 →
JSON truncate → unparseable. 두 가지 fix:
- Ollama body 에 repeat_penalty: 1.15 추가 (token repetition 억제)
- parseJsonLoose fail 시 throw 대신 {} 반환 → schema graceful coerce 가
placeholder title/summary 채움. raw_text 는 보존 → 사용자 데이터 무손실.
- coerceNullable 가 undefined 도 처리 (빈 객체 케이스).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
gemma4:26b 가 본문 빈 케이스에 title=null/summary=null 반환 → schema throw.
prompt 강화로 부족. schema 단계에서 graceful coerce:
- null/empty title → '(첨부 메모)'
- null/empty summary → '내용을 자동으로 정리하지 못했습니다.'
- 영어 title → '(첨부 메모)' (이전엔 throw)
- malformed/empty due_date → null (이전엔 throw)
raw_text 는 호출자가 보존하므로 사용자 데이터 손실 없음.
사용자가 후에 NoteCard 에서 직접 편집 가능.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
gemma4:26b 가 본문 없이 이미지만 있을 때 title=null/summary=null 반환.
prompt 가 "(이미지만 있음)" 만 던지는 게 신호 약함. 본문 비었으면
이미지 내용으로 한국어 채우라고 명시 + "null 반환 금지" 규칙 추가.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
vision model 의 markdown fence / prose 섞인 응답에서 JSON 추출 fallback.
prompt 에 title 한국어 / kebab tags / JSON-only 출력 명시 강화.
- LocalOllamaProvider: parseJsonLoose 헬퍼 (첫 { ~ 마지막 } 추출)
- visionPrompt: 4 규칙 + markdown fence 금지 명시
- 단위 +2 (fence 추출 + prose 추출)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
audit edge case 3건:
- pending 노트 "건너뛰기" 버튼 (cancelPending: pending → disabled + jobs DELETE)
- failed 노트 per-note "재시도" 버튼 (retryOneFailed: failed → pending + enqueue)
- FTS5 sanitize regex 확장 (backtick/dash/caret 추가)
동시 편집 race 는 EditableField guard 가 이미 처리 (수정 불필요).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
전수 audit 후 핵심 root fix 3 + edge cases 5:
ROOT
- inbox:set-status IPC 가 pushNoteUpdated emit (이전엔 stale → 호출처별 refreshMeta 필요)
- upsertNote 가 current view status 인식 (이전엔 잘못된 status 노트 잔류)
- store async 함수 try/catch (이전엔 IPC fail 시 무한 loading)
EDGE
- restoreNote 가 status='active' 도 갱신
- upsertNote trash 판정 deletedAt → status='trashed'
- Modal Escape dismiss 통일 (5개 modal)
- OnboardingWizard IPC fail fallback (try/catch + skip)
- MoveStatusModal overlay 클릭 close
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
MoveStatusModal 이 완료/보관/휴지통 3 button hardcode 라
완료/보관/휴지통 노트가 inbox 로 돌아오는 path 가 없던 버그 fix.
currentStatus prop 으로 4 status 중 current 제외 동적 render.
'활성' label 도 헤더 탭과 일치하도록 'Inbox' 로 통일.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
v0.3.5 의 이동 dropdown 단순화가 사용자 의도와 어긋남.
사용자는 dropdown 의 목적지 중복 (modal 도 목적지 묻기) 만 거슬렸지,
사유 입력 + AI 자동 분류 + 수동 status 선택을 한 곳에서 처리하는 modal 은
보존해야 하는 핵심 UX 였음. 단일 "이동" 버튼 → MoveStatusModal path 로 정정.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
v0.3.4 까지 누적된 dogfood UX 결함 hotfix.
사용자 직접 보고 3건 (inbox 재진입, 회고 탈출, 이동 modal 중복) + 동반 갭 4건
(count stale, 필터 잔류, 초기 로드 불일치, 배너 컨텍스트 누수).
데이터/마이그레이션 변경 없음 (스키마 v8).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
settings:configure-sync IPC 핸들러가 `git -C <syncDir> init` 호출 전에
syncDir 디렉토리를 생성하지 않아, sync 첫 설정 시 git 이 chdir 단계에서
`fatal: cannot change to '<profileDir>/sync': No such file or directory` 로
실패하던 문제. SyncService.runSync() 의 동일 패턴 (mkdir recursive) 을
핸들러에도 추가.
연쇄 증상: SyncSection 의 "연결 테스트" 버튼 disabled 조건이 저장된 url
state 기반이라, 저장 실패로 url 영영 비어 있어 버튼 활성화 불가 (닭/달걀).
mkdir fix 로 자동 해소.
회귀: sync-ipc.test.ts 에 mkdir 호출 순서 검증 1건 추가 (18 pass).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- inbox:emitRecallShown / emitRecallSnoozed: ipcMain.handle → on
(fire-and-forget honest pattern, return value 의존자 0)
- preload: ipcRenderer.invoke → send (matching on the main side)
- shared/types: Promise<void> → void on both recall emit methods
- store.ts: drop await on emitRecallSnoozed (now void)
- inboxApi-*.test.ts: add ipcMain.on to electron mock (broken by above)
- tests/unit/recall-ipc.test.ts: new TDD test for handle→on migration
Note: #20 CaptureService telemetry .catch debug log skipped —
CaptureService has no logger field; adding one would require non-trivial
constructor signature change. Reported as CONCERN below.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
err.message 안에 LAN endpoint URL (예: 192.168.x.x:11434) 이 포함될 수
있어 telemetry 파일에 PII 우회 노출. v0.2.3.1 in-app endpoint UI 가 LAN
사용을 흔하게 만들어 노출 경로 확대.
classifyFetchError 로 error class 분류 (network/timeout/dns/other) 후
reason: 'unreachable:{class}' 형태만 emit. host/IP 노출 0.
DB tags.name 가 COLLATE NOCASE 인데 vocabSet 은 strict-eq 였음 →
대문자/소문자 vocab 과 AI tag 가 다를 때 silently skip.
vocab.toLowerCase() + tagName.toLowerCase() 양쪽 normalize 로 정합.
본인 dogfood 환경 = gemma4:e4b (텍스트). vision 변종은 현재 gemma3 (vision-capable)
또는 향후 gemma4 출시 시. 양 family 모두 hint 에 포함 — capability detection 이
future-proof.
- VisionDetect.VISION_FAMILIES + VISION_NAME_HINTS 에 'gemma4' 추가
- isVisionCapable test 2건 추가 (gemma4 family / gemma4 name hint detection)
- spec §1 + §2 의 'gemma3 family default' → 'gemma family — gemma3 / gemma4'
영향: 기존 detection 정확도 무영향 (set 추가만), 사용자가 gemma4 vision 변종을
설치하면 자동 인식.
final code review (Opus) 발견 2 important issues:
1. SyncConflict.noteId 가 실제로 export filename slug (date-id8-slug) 였음 — UUID 가
아니라 git checkout path 의 stem. 명명 혼동 → 'path' 로 rename (실제 의미와 일치).
2. ConflictModal preview 가 항상 빈 문자열이라 사용자가 비교 없이 local/remote 선택해야
했음. runSync 의 conflict 분기에서 `git show :2:<path>` (ours) + `:3:<path>`
(theirs) 호출 추가하여 localText/remoteText 채움.
영향:
- SyncService.SyncConflict + shared/types.ts.SyncConflict: noteId → path
- SyncService.resolveConflict(path, choice) — 'notes/...md' 그대로 받음
- pathToNoteId 헬퍼 제거 (불필요)
- ConflictModal: c.noteId → c.path, busy 상태 + 표시 모두 path 키
- IPC handler / preload bridge / InboxApi 시그니처 모두 path 로 통일
- SyncService.bidirectional/resolveConflict/sync-ipc/ConflictModal 4 test 갱신
regression 회귀 패턴 검사: rename 후 NoteRepository / SyncService / IPC / UI 의 모든
conflict-related path 일관 (typecheck 0).
final code review 발견: F5 import path 가 note_tags INSERT 후 notes_fts.tags 갱신
안 해서 import 한 노트의 tag 가 keyword 검색에서 매칭 안 되는 회귀.
Cut C 의 importNote capture revision 누락 패턴과 동일 — single write path
정책 (Cut D 도입) 의 강제 검사 누락. importNote transaction 끝에서 호출하도록
fix + 회귀 test 2건 (insert path / fork path) 추가.
NoteRepository 안 note_tags INSERT path 는 updateAiResult / updateUserAiFields /
importNote 3곳, 셋 다 rebuildFtsTagsForNote 호출 보장 — invariant 회복.
- ftsHelpers: sanitizeFtsQuery (FTS5 special char escape) + computeCutoff (period → KST 자정)
- search: notes_fts MATCH + status filter + rank order + sanitize + 빈 query → []
- reviewAggregate: period 별 totalCount/recentNotes(50)/tagCounts(DESC)/dueProgress(passed/pending)
final code review 발견: F5 import 후 first user edit 시 import 시점 본문이
note_revisions 에 없어 history 에서 사라지는 회귀. importNote transaction 안
INSERT 추가 (createdAt = edited_at).
부수 작업: ImportNoteInput / importNote 의 "raw_text invariant guard" 주석을
v0.2.10 의 'fork-on-id-collision (sync determinism)' 정확한 의미로 갱신.
테스트 +2 — insert path / fork path 모두 capture revision 검증.