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>
설정 다 한 사용자도 본문 없이 이미지만 첨부하면 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>
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>
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>
5 callsite (NoteRepository, ftsHelpers, BackupService, ContinuityService,
NoteCard) 모두 canonical export 로 정리. 알고리즘 동일 (9 * 60 * 60 * 1000),
회귀 PASS 검증.
v0.2.6 commit 3cfa60b 가 4 callsite migrate 했지만 5 callsite 잔여.
Cut F audit 에서 발견.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Cut B Task 6 — 모든 view 공통 "이동 ▾" dropdown.
- 기존 휴지통/삭제 버튼 위치에 dropdown 추가 (모든 mode 공통)
- 현재 status 외 3개 목적지만 표시 (active 노트 → 완료/보관/휴지통)
- 메뉴 항목 클릭 → MoveStatusModal(initialTarget) 열기
- onMoved → local 상태 갱신 + onUpdated + (status 변경 시) onDeleted (list 제거)
- trash mode 의 영구 삭제/복구 버튼은 보존 (휴지통 단독 액션)
- 사용되지 않게 된 handleDelete 제거 (deleteNote 는 capture path 만)
- NoteCard 메뉴 단위 테스트 2건 (메뉴 표시 / 클릭 → modal → setStatus)
- 회색 placeholder div → <img src=inkling-media://...> 로 교체
- onClick 으로 inboxApi.openMedia(relPath) 호출 (현재는 InboxApi 인터페이스에 부재 → unknown cast 사용; Task 3 에서 정식 시그니처 추가 후 cast 제거 예정)
- alt='' 로 decorative 처리 (role=presentation), title 에 relPath 유지
- flex-wrap 추가 — 다수 이미지 시 줄바꿈
Tests: tests/unit/NoteCard.test.tsx 신규 2건 (img src 검증, click → openMedia 호출)
회귀: 468 → 470 pass
- onDeleted: () => void → onDeleted?: () => void (inbox mode 전용 명시)
- handleDelete 내부 onDeleted() → onDeleted?.()
- App.tsx 의 trash mode NoteCard 가 onDeleted prop 미전달 (dead-code 제거)
- API 시그니처 정리 — trash mode 는 onPermanentDelete/onRestore 만 의미
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- RecallBanner: 노트 제목 + N일 전 + 3 버튼 (열어보기/다음에/더 이상)
- 첫 렌더 시 emitRecallShown (recallShownIds Set 으로 per-session 1회 제약)
- snoozeUntilMs 만료 setTimeout (ExpiryBanner 패턴)
- 위치: ExpiryBanner 다음 (banner stack 끝)
- NoteCard 외곽 div 에 id="note-${note.id}" — "열어보기" scrollIntoView target
- 컬러 테마: 파랑 (#e8f0fe / #4a7ec0) — 다른 banner (적/황/적) 와 구별
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Splits the tag chip into two actions per F2 dogfood feedback:
- short click on chip text → applies the tag to the inbox filter
(Inbox header shows "🔎 필터: #tag (n개)" banner with ✕ 해제 button)
- × button on chip → immediately removes the tag and surfaces a
module-level pub/sub undo toast for 5 seconds; clicking the toast
restores the tag
`TagUndoToast` is a tiny self-contained component: `pushTagUndo()` from
NoteCard publishes an entry, the mounted `<TagUndoToast />` near the
end of `<App>` subscribes and renders it. Auto-dismiss after 5 s,
click-to-undo cancels the timer and runs the restore callback.
AI vs user tags share the same behaviour — only the chip background
colour distinguishes them, matching the F2 decision table.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Inline 📅 YYYY-MM-DD badge appears in NoteCard between summary and
tags. Click to edit (HTML date input). Past dates: gray + line-through.
AI label shown when not user-edited (mirrors title/summary AI badge
policy). Empty state shows '📅 마감일 추가' link in gray.
New IPC inbox:setDueDate routes to NoteRepository.setDueDate which
sets due_date_edited_by_user=1 (per slice §1.1 invariant 2 — user
edit blocks future AI overwrite). Preload bridge + InboxApi type
extended.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Task 25 of the slice plan. Single-card view of a note with
local optimistic state plus the IPC writes. Per-status branches:
- pending: 'Inkling이 정리하는 중…' headline, raw text auto-open.
- failed: '정리 보류 — 원문은 안전합니다' with hover tooltip.
- done: editable title + summary (each with the gray 'AI' badge
hidden as soon as *_edited_by_user flips), tag chips with
AI subscript, optional 💡 user_intent row, IntentBanner
surfaced exactly when intent_prompted_at is null.
Tag click removes (per spec), title/summary edits route through
inboxApi.updateAiFields (which sets the edited flag server-side),
intent edits use inboxApi.setIntent. Delete confirms with '이
기억을 버릴까요? 되돌릴 수 없습니다.' and routes through
CaptureService.deleteNote so media gets cleaned up.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Task 22 of the slice plan. Wires the Inbox window's renderer:
- index.html (replaces the placeholder shipped in Task 2) with
the full layout styles + module script.
- api.ts re-exports window.inkling.inbox as a typed InboxApi.
- recoveryToast.ts persists per-day toast dismissal in
localStorage (KST date key) so the banner never re-fires
after the user closes it.
- store.ts: zustand store with notes, continuity, pendingCount,
ollamaStatus, loadInitial(), refreshMeta(), upsert/remove.
- main.tsx mounts <App />.
- App.tsx orchestrates loadInitial + onNoteUpdated subscription
+ window-focus refresh, renders header / banners / note list.
- 7 component stubs (NoteCard / EditableField / IntentBanner /
RecoveryToast / ContinuityBadge / PendingBanner / OllamaBanner)
so the shell typechecks today; Tasks 23-28 swap each in.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>