- m009 마이그레이션: notebooks.sort_order INTEGER 컬럼 추가, 기존 rows created_at 순으로 backfill
- NotebookRepository.list ORDER BY sort_order ASC, name ASC 로 변경
- NotebookRepository.create 신규 노트북 sort_order = max+1 자동 할당
- NotebookRepository.reorder(id, direction) — swap transaction 으로 atomic 순서 변경
- IPC notebook:reorder 핸들러 등록, preload/shared types pass-through
- 테스트 45개 추가 (m009, reorder 케이스 4, list ORDER BY, IPC 핸들러 2)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
기존 v0.4.0 Notebooks + Lifecycle Simplification 노트 위에 force re-tag 로
묶인 후속 변경 (NotebookChip 강화 / 헤더 토글 / default visible / 창 크기)
section 을 prepend. 새 minor 안 늘리는 패턴 유지.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
dogfood 발견 사항 묶음:
- **NotebookChip** 시각 강화 — 청색 배경 + 📓 아이콘 + ▾ caret + dropdown
헤더 '이동할 노트북'. 클릭 시 다른 노트북 dropdown 명확히 발견 가능.
다른 노트북 없으면 disabled state.
- **헤더 좌측 ☰ 햄버거 버튼** — 마우스로 사이드바 토글 (Cmd/Ctrl+B 와 동일).
- **사이드바 default visible** — settings.getSidebarVisible 의 default false→true,
store init 도 동일. 기존 사용자가 명시적으로 false 저장했다면 그 값 유지.
- **inboxWindow 기본 크기 확장** — 900×720 → 1200×800. 사이드바 240px 가
default 가시화되므로 main 영역 확보.
851 tests pass + typecheck clean.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
final code review 의 Important issue 대응. SettingsService 의 setSidebarVisible/
setSidebarWidth getter/setter 는 이미 있었지만 IPC handler + store hydration
missing 으로 매 launch 시 사이드바 닫힌 상태로 시작하던 회귀.
- settings:set-sidebar-visible / set-sidebar-width IPC 핸들러 추가
- InboxApi.getSettings 응답에 sidebar_visible/sidebar_width 포함
- preload 의 setSidebarVisible/setSidebarWidth invoke 노출
- store.loadInitial 가 settings.sidebar_visible/sidebar_width 로 hydrate
- store.toggleSidebar 가 IPC 호출하여 영속화
- test mock 에 setSidebarVisible/setSidebarWidth 추가
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- selectNotebook: set 후 loadByView(view) + refreshMeta() 자동 호출 (inbox/completed/trash view 한정 list 갱신, 모든 view 에서 counts 갱신)
- loadInitial / loadByView / refreshMeta: selectedNotebookId 를 listByStatus / countsByStatus 에 notebookId 옵션으로 전달
- tests: selectNotebook→loadByView+refreshMeta 호출 검증, notebookId 전달 검증, review view 에선 listByStatus 미호출 검증 (4케이스 추가)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- NotebookRepository.findByName(name) 추가 — COLLATE NOCASE case-insensitive 조회
- AiWorkerOptions.notebookRepo 옵션 추가 (optional Pick<NotebookRepository, ...>)
- processJob: generate 전 notebookRepo.list() → notebooks 배열 GenerateInput 에 주입
- processJob: updateAiResult 후 res.notebookMatch valid 이름이면 findByName + moveNote 호출
- main/index.ts: AiWorker 생성 시 notebookRepo 전달
- NotebookRepository.test.ts: findByName 3개 테스트 추가
- AiWorker.test.ts: notebook 매칭 describe 4개 테스트 추가 (총 45 테스트 통과)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Note.notebookId 필드 추가(required), NoteRepository.create/importNote/upsertFromSync INSERT 에
notebook_id 컬럼 포함 — 미지정 시 getDefaultNotebookId() 로 가장 오래된 notebook 자동 할당.
hydrate 에 notebookId 반환 추가. 관련 test fixture 5곳 notebookId 보강.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Notebook 인터페이스 src/shared/types.ts 에 추가 (noteCount = active 노트 수)
- NotebookRepository.ts 신설: list / findById / create / rename / setColor / delete / moveNote
- delete: FK RESTRICT 위반 → ok:false reason='has_notes', 미존재 → 'not_found'
- noteCount 서브쿼리: status='active' 만 카운트 (completed/trashed 제외)
- 테스트 10개 모두 통과, typecheck clean
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
기존 v0.3.2 Cut G design (사이드바 + notebook 카테고리) 을 v0.4 로 승격하면서
lifecycle 단순화 (archived 제거) + AI × Notebook 통합 (자동 fit 매칭 + tag 기반
promotion 제안) 을 함께 다룸. dogfood 19일 데이터 (archived 0건 / mlx-ops tag
6건이 사실상 컨텍스트 그룹 역할) 가 묶음 변경의 근거.
핵심 결정:
- notebook 모델 = 옵션 B (단일 DB + notebook_id), 다중 profile 옵션 A 는 v0.5+ 보류
- lifecycle 3분기 — active/completed/trashed, archived 제거 (마이그레이션 시 completed 로 합침)
- AI autonomy = 제안 + 1-click 수락 (새 notebook 자동 생성 X, fit 매칭만 자동 배치)
- promotion trigger = tag 3건 이상 default notebook 누적 (v0.4 first release 는 rule only)
- 사이드바 default hidden, Cmd+B / Ctrl+B 토글
선행 Cut G design (`2026-05-09-v032-cut-g-design.md`) 상단에 deprecate 노트 추가.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
기존 v0.3.14 'AI 처리 fail 원인 가시화' 노트 위에 force re-tag 로 묶인
후속 변경 (capture / cmd 안내 / macOS / notes 재처리 / expiry / sync /
settings 풀어쓰기) section 을 prepend. 새 minor 안 늘리는 패턴 유지.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
dogfood: 설정 페이지의 각 section 이 너무 단답형이고 도움말 텍스트도
기술 용어 (rebase, fast-forward, NTP) 위주라 불친절.
- 공통 SectionIntro 컴포넌트 신설 (12px gray paragraph, margin-bottom 12).
- 6 section (AI 제공자 / Vision / 자동실행 / 백업 / 동기화 / 정보) 상단에
"이게 뭐고 왜 필요한지" 1-2 문장 안내 추가. 톤은 담백 + 업무적 (존댓말,
Inkling 1인칭).
- SyncHelpModal section 1, 2, 3 의 기술 용어를 사용자 언어로 풀어쓰기.
"fetch + rebase" → "원격 변경 먼저 받아오기", "NTP" → "기기 시각 어긋남",
"non-fast-forward push 거부" → "업로드 거부 시 자동 재시도" 등.
시각/레이아웃은 그대로 유지 — 텍스트 변경만.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
dogfood: 노트 변경이 0건이어도 자동 sync 가 매번 commit + push 를 생성.
원인은 manifest.json 의 exported_at timestamp 가 매 export 마다 갱신되어
git diff 가 항상 1줄 발생.
해결: composeManifest 의 exportedAt 입력 제거 + 출력 JSON 에서 필드 삭제.
이 필드는 ImportService 가 read 하지 않고 UI 표시도 없는 cosmetic 정보였음.
이제 노트 변경 있을 때만 commit/push 가 일어난다.
회귀 테스트: 같은 input 으로 두 번 호출 시 stable 출력 invariant 추가.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
dogfood: 마감 알림이 (1) 완료/보관 status 노트도 포함하고 (2) 오늘 당일
마감 메모는 빠져 있어 사용자 불편.
NoteRepository.findExpiredCandidates 변경:
- due_date < today → <=today (오늘 당일 포함)
- status='active' 필터 추가 (inbox 만, completed/archived/trashed 제외)
- ORDER BY due_date DESC → 오늘 → 어제 → 그저께 순
ExpiryBanner UX:
- 헤딩 분리 카운트 "오늘 마감 X · 지난 Y" (한 쪽만이면 단독 표시)
- 노트 옆 due_date → 상대 라벨 ([오늘] / [N일 지남]) + hover tooltip 으로
원본 ISO 날짜 노출
- 노트 제목 클릭 → note-{id} 로 smooth scroll (RecallBanner 와 동일 패턴).
checkbox 와 분리하기 위해 label → div + button 으로 구조 변경.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
dogfood: 사용자가 노트 본문 수정해도 기존 AI 제목/요약이 그대로 남는 문제.
NoteRepository.markAiPendingForReprocess 추가 — done/failed/pending 노트를
pending 으로 reset + pending_jobs 재투입. disabled 는 사용자가 명시적으로
비활성화한 상태라 존중하여 no-op.
inboxApi 의 update-raw-text / restore-revision 핸들러가 raw 갱신 후 위
헬퍼 + worker.enqueue + pushNoteUpdated 호출. NoteCard.saveRaw 는 optimistic
으로 aiStatus='pending' 즉시 반영 → UI 가 "Inkling이 정리하는 중…" 즉시
표시, 백엔드 push 로 자동 sync. updateAiResult 의 user-edit 가드가 사용자가
직접 편집한 title/summary 는 새 AI 결과로 덮어쓰지 않으므로 안전.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
두 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>
renderer 두 곳의 단축키 안내 텍스트가 'Ctrl+...' hardcoded 였음. 사용자에게
보여지는 hint 만 platform-aware 로 분기 (navigator.platform 검사) — Mac
에서는 'Cmd+Shift+J', 'Cmd+Enter' 로 표시.
main 의 globalShortcut accelerator 는 이미 platform 별 분기되어 있어 별개
영향 없음. UI 안내만 일치시키는 변경.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
dogfood: 사용자가 quickCapture 띄운 채로 다른 창 클릭 시 즉시 hide 되어
저장/취소 의도를 명확히 표시하기 전에 사라지는 현상. blur 핸들러 제거 →
ESC (취소) / Cmd+Enter (저장) 누를 때까지 창 유지. alwaysOnTop +
screen-saver level 이라 다른 앱 위에 떠 있음.
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>
설정 다 한 사용자도 본문 없이 이미지만 첨부하면 placeholder 떨어지는 케이스 잦음
(gemma4:26b 등 vision 모델의 본문 없는 이미지 처리 한계).
배너가 "설정 확인" 권유 → 사용자 혼란.
"본문 없이 이미지만 첨부한 경우 일부 vision 모델이 빈 응답" + "본문 추가 또는 직접 수정"
으로 변경. 실제 원인 명시.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
이전 fix 후에도 사용자가 "(첨부 메모)" placeholder 만 보이면 왜 fail 한지 모름.
가능성 큰 원인: vision_model 미설정 → text-only path → 본문 빈 응답.
- AiWorker: ai.vision.decide 로그 추가 — visionActive / visionModelConfigured /
mediaCount / mediaStorePresent. logs/main.log 에서 진단 가능.
- NoteCard: ai_status='done' + title='(첨부 메모)' + media 있을 때 노란 banner.
"vision 모델 설정 확인 + 직접 편집" 안내.
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>
gemma4:26b (25B MoE + vision encoder 550M) 등 대형 vision 모델의
cold-start 가 60-180s 소요. 기본 120s timeout 으로 첫 호출 fail 빈번.
vision path 에 한해 Math.max(timeoutMs, 300_000) — text-only 영향 없음.
gemma4:26b 가 Text+Image 양 modality 지원 검증 완료
(blog.google/gemma-4, ollama.com/library/gemma4:26b).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>