Commit Graph

124 Commits

Author SHA1 Message Date
6f8ae75ff7 Merge pull request 'feat(telemetry): #7 telemetry skeleton (v0.2.3 1/7)' (#13) from feat/v023-telemetry into main
Reviewed-on: #13
2026-05-01 10:37:55 +00:00
altair823
7e8e2b598d fix(telemetry): 회차 1 review 반영 — attempts 의미 통일 + DI 우회 제거 + 매직 슬라이스 제거
PR #13 회차 1 리뷰의 actionable 1건 + suggestion 3건 반영.

- `AiWorker` 의 `attempts` 필드가 success/failure 경로에서 비대칭 의미 (0-index vs count) 였던 문제. 둘 다 `attempt + 1` (실제 시도 횟수, 1-based) 로 통일. stats markdown 의 평균/분포 해석이 일관됨.
- `Date.now()` 직접 호출이 `opts.now` DI 를 우회하던 두 곳을 `this.now().getTime()` 으로 교체. 추후 durationMs 분포 테스트 작성 가능.
- `TelemetryService.emit` 의 `this.now()` 두 번 호출을 한 번 캐시로 통합. KST 자정 경계에서 ts 와 파일명 일자 불일치 가능성 제거.
- `readAllRecent` 의 `n.slice(7, 17)` 매직 슬라이스를 정규식 capture 그룹으로 교체. prefix 변경 시 한 곳만 수정.

테스트: AiWorker 성공 케이스의 `attempts: 0` → `attempts: 1` 갱신.
게이트: typecheck 0 errors, 245/245 unit tests pass.

Deferred (v0.2.4 backlog): 'aborted' user-cancel false-positive, tray menu submenu 분리.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 18:41:26 +09:00
altair823
5c97397cbe chore(telemetry): #7 closure — gate verification + .catch consistency + spec fix
- Add .catch(...) to telemetry.cleanupOldFiles fire-and-forget for consistency
  with backup.runDaily pattern (M1 from T10 code review).
- Mark Roadmap §3 #7 as completed (✓).
- Correct spec: tray:exportTelemetry was never an IPC channel — tray callbacks
  run in main process directly. Replace with "트레이 콜백 (main 내부)".

Closes v0.2.3 task 1 of 7. Next task: #4 휴지통 (migration v3).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 17:37:00 +09:00
altair823
fe24ff577f feat(telemetry): wire TelemetryService + tray export (#7 v0.2.3)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-01 17:30:54 +09:00
altair823
dca6aed44e docs(tray): restore F4-C identity-signal intent comment
The T9 full-file replacement accidentally dropped the inline comment
documenting why the count label is conditional on _todayCount > 0
(F4-C UX rationale). No behavior change.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 17:28:04 +09:00
altair823
4213745dc7 feat(telemetry): tray menu '사용 로그 내보내기...' (#7 v0.2.3) 2026-05-01 17:25:52 +09:00
altair823
01447ddaad feat(telemetry): AiWorker emits ai_succeeded/ai_failed with reason (#7 v0.2.3) 2026-05-01 17:21:08 +09:00
altair823
f0cef95d3f feat(telemetry): CaptureService emits capture event (#7 v0.2.3) 2026-05-01 17:15:24 +09:00
altair823
36a5c67ed6 feat(telemetry): exportTo writes events.jsonl + stats.md (#7 v0.2.3) 2026-05-01 17:08:34 +09:00
altair823
2036c687d2 test(telemetry): add KST regression test for near-midnight UTC bucketing
Original 'counts events per KST day' test used UTC times that bucket
identically under both KST and naive UTC slice — would not catch a regression
where kstDate was replaced with ev.ts.slice(0,10). Add an explicit
near-midnight case (2026-05-01T15:30Z = 2026-05-02 00:30 KST) that fails
under naive UTC and passes under correct KST conversion.

6 tests pass (was 5).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 17:06:29 +09:00
altair823
9a066ed807 feat(telemetry): telemetryStats.aggregateStats (#7 v0.2.3) 2026-05-01 17:03:31 +09:00
altair823
729a3f9c47 feat(telemetry): readAllRecent with malformed-line tolerance (#7 v0.2.3) 2026-05-01 16:58:45 +09:00
altair823
0501bd1762 feat(telemetry): cleanupOldFiles with 14-day KST retention (#7 v0.2.3) 2026-05-01 16:54:36 +09:00
altair823
50b6d05bcb fix(telemetry): silent-fs-error test exercises the actual code path
Earlier test used '/proc/0/...' as the unwritable dir. On Windows this
resolved to 'C:\proc\0\...' and mkdir({recursive: true}) silently created
it — the silent code path was never exercised, plus filesystem side-effect
leaked outside the test tmpdir.

Replace with a path that points to an existing file (mkdir on a file path
fails on every platform). Also add a companion test that confirms silent
is opt-in: without {silent: true}, the same fs failure DOES throw.

7 tests pass (was 6).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 16:52:11 +09:00
altair823
93e278b241 feat(telemetry): TelemetryService.emit with KST rotation (#7 v0.2.3) 2026-05-01 14:18:59 +09:00
altair823
0a0ef11327 feat(telemetry): event schema + privacy invariant (#7 v0.2.3) 2026-05-01 14:14:19 +09:00
altair823
358cada017 docs(plan): #7 telemetry skeleton 구현 계획 (v0.2.3 1/7)
11 task TDD plan — events schema/privacy invariant, JSONL emit/rotation,
14d cleanup, readAllRecent, stats aggregator, exportTo(folder),
CaptureService/AiWorker hooks, tray menu, index.ts wiring, gates.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 14:02:48 +09:00
altair823
22a25cc622 docs(spec): v0.2.3 dogfood feedback roadmap (7 items, single cut)
v0.2.2 dogfood 7항목 (#7 telemetry 신설 + #1~#6) 단일 cut 로드맵.
데이터 안전 우선 (C 채택), schema migration v3 3컬럼 한 묶음 (B),
trash↔backup/export B 정책, #6 = 1 spike 흡수.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 13:56:16 +09:00
altair823
06cfa1c151 chore(release): v0.2.2 — F7 + Quick Capture 스크롤 fix
- F7 (이미 main 병합): AI-primary due_date flow, 다중 후보 추출
- fix(quickcapture): textarea min-height: 0 + .card overflow: hidden 으로 hint 노출 보장

CHANGELOG.md / package.json 0.2.1 → 0.2.2.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
v0.2.2
2026-04-26 23:25:53 +09:00
altair823
579450ef4f docs(spec): promote F7 AI-primary due date
신규 spec 파일 추가 (구현 결과 반영). dogfood-feedback.md 의 F7
헤더 🔬 drafting → 🚀 promoted 로 갱신. F1 spec 의 후속 리스트에
F7 링크 추가.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 13:07:06 +09:00
altair823
723dccd61d feat(ai): AI-primary due_date flow — rule as prompt candidates only
Flow 반전 (F7-D 채택):
- 기존: rule.iso ?? ai.dueDate (rule 우선)
- 신규: ai.dueDate ?? null (AI 우선)

규칙은 parseAllCandidates 로 모든 매치를 추출 → prompt 에 후보
힌트로 주입. AI 가 종합 판단. AI 실패 시 due_date null (별 fallback 없음).

해결되는 케이스: '내일 모레' → AI 가 ambiguous 인지 → null.

PROMPT_VERSION → 3. GenerateInput.dueDateCandidates 신규.
buildPrompt(rawText, todayKst, candidates) — 빈 배열일 때 hint 섹션 생략.

Tests:
- AiWorker.test.ts — 'rule priority' 테스트 → 'AI dueDate wins' flip
- AiWorker.test.ts — passes todayKst 테스트 확장 (dueDateCandidates 도 검증)
- AiWorker.test.ts — 신규 'passes parseAllCandidates result as dueDateCandidates'
- LocalOllamaProvider.test.ts / ollama-golden.test.ts — generate 호출에 dueDateCandidates: [] 추가

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 13:06:12 +09:00
altair823
1c72b64c2f feat(due-date): parseAllCandidates — extract all matches (text order)
기존 parseDueDate (first-match-wins) 는 backward compat 로 보존.
parseAllCandidates 가 모든 high/medium 매치를 text 순서로 반환 — F7
AI-primary flow 의 prompt 후보 주입 입력으로 사용.

Overlapping-span suppression: 다음 주 월요일 같은 케이스에서 rule 10
(전체) 이 rule 13 (다음 주 alone) 을 포함하면 후자 매치 제거.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 13:04:17 +09:00
altair823
2ee45bc53c docs(plan): F7 AI-primary due date 구현 계획 (D 채택) 2026-04-26 13:00:35 +09:00
altair823
742eec00f4 docs(feedback): add F7 — Due Date 규칙 파서 합성 표현 first-match-wins 한계
v0.2.1 dogfood 첫 실증 피드백. '내일 모레' → 내일로 잘못 잡힘.
규칙 파서 한계 3 (합성/양가 / 범위 / 모호) + 5 후보 방향 (A 화이트
리스트 / B 충돌 감지 / C UI 신호 / D AI 우선 / E 규칙 폐기).
1차 A+B 작은 PR 즉시 시도 가능, 2차 C UI 신호, 후속 D/E 결정.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 12:56:32 +09:00
altair823
a38b6fdeea chore(release): bump version to 0.2.1 + CHANGELOG
8 항목 dogfood-feedback 로드맵 (F1·F2·F3·F4-E·F5·F6-L1·F6-L2·F6-L3·F4-C·F)
한 번에 흡수. migration v2 (due_date) + pre-migration snapshot.
단위 테스트 52 → 197. 신규 npm dep 0, 시스템 dep 추가는 git CLI (이미 사전 요구).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
v0.2.1
2026-04-26 11:55:44 +09:00
altair823
72e69fb53a docs(spec): promote F4-C·F cue strengthening
F4 의 6개 cue 메커니즘 중 외부 신호 없이 즉시 구현 가능한 두 가지
(C 환경 앵커, F 정체성 고리) 를 묶어 promoted spec 으로 추출. A (잠금
hook), D (variable interval prompt), B (ambient if-then) 는 dogfood
soak 측정 결과를 본 뒤 결정.

F4 헤더를 🌱 raw → 🔬 drafting (C·E·F promoted) 로 갱신하고, F4 진행
상태에 두 promoted 경로를 명시.
2026-04-26 11:49:48 +09:00
altair823
bcd1151a24 feat(cue): IdentityCounter + tray refresh — 오늘 N번 잡아뒀다
F4-C·F (cue 강화) — Inkling 의 두 표면에 정체성 신호를 추가.

F4-C 환경 앵커 (tray):
- nativeImage 색 변경은 미지원이므로 색 뱃지 대신 tooltip + 메뉴 첫
  비활성 라벨로 대체. tooltip 은 항상 `Inkling — 오늘 N`,
  메뉴 첫 항목은 N>0 일 때 `오늘 N번 잡아둠` (비활성).
- `tray.ts` 가 `refreshTray(todayCount)` 를 export 하여 main 이
  60s interval + AiWorker.onUpdate hook 에서 갱신을 트리거.
- N=0 일 때는 라벨을 띄우지 않아 메뉴가 자연스럽게 시작.

F4-F 정체성 고리 (Inbox 헤더):
- ContinuityBadge 옆에 새 IdentityCounter 컴포넌트.
- N>0 → `오늘 N번 잡아뒀다` (정체성 강화 카피).
- N=0 → `오늘은 처음 한 줄?` (priming 카피로 첫 캡처 유도).
- 갱신은 `loadInitial` / `refreshMeta` (focus + note:updated) 경로
  공유 — 별도 IPC subscription 없음.

Wiring:
- `NoteRepository.countToday()` 를 `inbox:todayCount` IPC 로 노출.
- preload bridge `getTodayCount`, `InboxApi.getTodayCount()` 타입.
- 스토어에 `todayCount: number` 필드 추가, 두 메타 fetch 경로 모두에서 갱신.

스키마 변경 없음. 197/197 unit pass, 1/1 e2e pass.
2026-04-26 11:49:09 +09:00
altair823
cca3029b7e feat(repo): countToday(now?) — KST midnight bucket count
For the F4-C·F cue strengthening surfaces (tray tooltip + Inbox identity
counter), main + renderer need a single source of truth for "오늘 N번
잡아뒀다". Implements `NoteRepository.countToday(now?)` that computes the
half-open UTC interval covering the KST calendar date of `now` and counts
rows whose `created_at` falls inside.

`now` is injectable for deterministic tests across the KST/UTC boundary
(02:00 KST and 23:00 KST land on different UTC dates yet the same / a
different KST day). Four new cases cover empty DB, KST-day filtering,
KST-midnight crossover, and the default-arg branch.
2026-04-26 11:47:03 +09:00
altair823
5b6003bdcd docs(spec): promote F6-L2 git sync MVP + README setup section
- 신규 spec docs/superpowers/specs/2026-04-26-f6-l2-git-sync.md
    결정 표 + 범위 + 시스템 의존 + 동작 요약 + 후속 후보
- 2026-04-25-dogfood-feedback.md F6 진행 상태 line 갱신:
    L2 — 🌱 raw → 🚀 promoted (MVP)
- README.md '원격 백업 (선택, F6-L2)' 섹션:
    일회 설정 (git init + remote add + 자격증명) + 사용법

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 11:42:16 +09:00
altair823
66725dacae feat(sync): wire SyncService — tray '지금 동기화' + on-quit drain
- src/main/index.ts: SyncService instantiate with paths.profileDir + exportSvc
- 트레이 6번째 콜백 — 토스트로 not_configured / done / unchanged / failed 안내
- before-quit 훅에 sync drain 추가 (backup 완료 후, syncSvc.isConfigured() 인 경우만)
- src/main/tray.ts: 6번째 callback runSync, '지금 동기화' 메뉴 (내보내기/복원 다음)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 11:40:50 +09:00
altair823
eaf66e6c10 feat(sync): SyncService — F5 export + git add/commit/push to <profileDir>/sync/
F6-L2 MVP 의 오케스트레이터.

- isConfigured(): syncDir 가 git repo + origin remote 있을 때만 true
- sync():
    1) ExportService.export(<syncDir>, includeMedia: true) — F5 트리 그대로 덮어쓰기
    2) git add -A
    3) git commit -m "chore(notes): sync <ts>"
    4) "nothing to commit" 이면 changed=false 로 정상 반환
    5) git push (upstream 미설정이면 -u origin <branch> 자동)
- GitClient.push() 에 hasUpstream() + 자동 -u 추가 (첫 push 케이스)
- 5 vitest cases — bare local remote 로 push 검증, 두 번째 sync 는 변경 없음 확인

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 11:39:42 +09:00
altair823
32c7becd47 feat(sync): GitClient — async wrapper for git CLI
얇은 git CLI 래퍼. F6-L2 sync MVP 의 빌딩 블록.

- run/isRepo/hasRemote/addAll/commit/push/currentBranch
- commit() 은 "nothing to commit" 을 changed=false 로 구분 (정상 path)
- 그 외 실패는 throw, exitCode + stderr 보존
- 8 vitest cases — empty file 로 GIT_CONFIG_GLOBAL/SYSTEM 격리

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 11:37:43 +09:00
altair823
6310716fb7 docs(spec): promote F3 + F4-E copy
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 11:31:22 +09:00
altair823
1e5847dcc6 docs: align strategy.md + slice §5.5 with F3 copy + F4-E Zeigarnik
strategy.md §1 heading and core sentence reframed to '머릿속에서
꺼내 두기' as the load-bearing user action (was '기억 구출').
§4.1 보상 카피 list updated to match shipped copy. Brief Zeigarnik-
effect rationale added per F4-E. §7 example updated.

Slice spec §5.5 카피 테이블 4개 항목 갱신 — code 와 spec 의 카피
드리프트 차단.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 11:30:37 +09:00
altair823
2d90a48621 feat(copy): replace '기억 구출' framing with 표면별 자연 동사 + Zeigarnik priming
F3: '구출' (rescue) is unnatural everyday Korean. Replace per-surface:
  - 트레이 '구출한 메모 보기' → '보관한 메모 보기'
  - 트레이 '기억 구출하기' → '한 줄 적기'
  - 토스트 #2 → '머릿속에서 꺼내 두었습니다.'
  - 토스트 #3 → '방금 한 줄 잡아뒀습니다.'
  - QC 힌트 'Ctrl+Enter 구출' → 'Ctrl+Enter 저장'
  - package.json description → 'local-first 한 줄 보관 도구'

F4-E (Zeigarnik priming): empty state copy reframed to evoke the
"unfinished thought tugging at memory" → "외재화로 해소" loop:
  - '첫 기억을 구출해보세요.' → '머릿속에 떠다니는 한 줄을 적어보세요.'

E2E smoke assertion updated to match. Slice §1.1 invariant 5
('실패/끊김/연속 실패' 금지) preserved.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 11:29:31 +09:00
altair823
c4a7985456 docs(spec): promote F2 tag click
Marks F2 in dogfood-feedback as 🚀 promoted and adds the standalone
spec at docs/superpowers/specs/2026-04-26-f2-tag-click.md capturing
the mini-brainstorm decisions, scope, and follow-ups (multi-tag
filter, rename/merge, source preservation on undo).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 11:25:31 +09:00
altair823
8373f06045 feat(inbox): tag chip click = filter, separate × button + undo toast
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>
2026-04-26 11:25:24 +09:00
altair823
aad9d403ce feat(inbox): tagFilter store + pure selectFilteredNotes + tests
Adds `tagFilter: string | null` and `setTagFilter` to the inbox store, plus
an extracted pure `selectFilteredNotes` selector so unit tests can import it
under vitest's `node` environment without dragging `api.ts` (which touches
`window.inkling` at module load).

Tests cover four cases: null filter passes through, single-tag match,
no-match empty result, and any-tag-matches semantics.

F2 dogfood feedback step 1/3.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 11:25:13 +09:00
altair823
ab68b19144 docs(spec): promote F1 due date
Extracted to own spec with mini-brainstorm decisions captured.
F1 in dogfood-feedback.md marked 🚀 promoted with link.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 11:18:38 +09:00
altair823
9ab70868d1 feat(due-date): NoteCard badge + edit + IPC
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>
2026-04-26 11:17:57 +09:00
altair823
adae90eb61 feat(ai): AiWorker merges rule parser + AI due_date
GenerateInput gains todayKst field. AiWorker computes KST-aligned
date once per job, runs parseDueDate on rawText, calls provider.generate
with todayKst, then merges: rule.iso wins if matched (deterministic),
else AI's due_date, else null. Logs dueDateSource (rule|ai|none) for
debugging. now() injection for testability.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 11:14:46 +09:00
altair823
4ee135dcd6 feat(ai): zod due_date field + prompt {{TODAY_KST}} injection
AiResponse extends with dueDate: string|null. zod regex
^\d{4}-\d{2}-\d{2}$, follow-up roundtrip check coerces invalid
dates (2026-13-99 etc.) to null. PROMPT_VERSION → 2: prompt now
takes todayKst arg, asks model to extract due_date as ISO or null.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 11:12:45 +09:00
altair823
95ba1653d7 feat(due-date): pure rule parser for Korean date expressions
Regex + KST math, returns ISO YYYY-MM-DD or null. 14 high-confidence
rules (literal date, N월 N일, MM/DD, N일/주/개월 뒤, 모레/내일/글피/오늘,
다음/이번 주 X요일, 다음 달). Ambiguous tokens (월말, 주말, 퇴근 전,
시각) return iso=null + confidence='medium' so caller (AiWorker)
can defer to AI. 26+ golden fixtures.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 11:09:51 +09:00
altair823
0bb6c12bbb feat(db): migration v2 — due_date columns + pre-migration snapshot
ALTER TABLE notes adds due_date TEXT + due_date_edited_by_user INTEGER.
openDb takes <dbFile>.pre-v<N>.bak before running migrations
(F6-L1 follow-up #4 — preserves recoverable state if migration fails).
NoteRepository: updateAiResult accepts dueDate?, setDueDate +
edited-flag CASE WHEN guard mirroring title/summary pattern.
Note interface gains dueDate + dueDateEditedByUser fields.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 11:05:44 +09:00
altair823
cfd34c352b docs(plan): F1 due date 구현 계획 (6 tasks, migration v2) 2026-04-26 11:01:37 +09:00
altair823
9407f398c8 docs(spec): promote F6-L3 import
Extracted to own spec. F6 진행 상태 라인 갱신.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 10:57:14 +09:00
altair823
e728a11e09 feat(import): wire ImportService — tray '백업에서 복원...' + preview dialog
Tray gets 5th callback. Directory chooser → preview (count of
new/skip/forked + media) → confirm message box → run. Slice §1.1
copy policy preserved (no '실패'/'끊김'). Notification on
success/failure.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 10:56:29 +09:00
altair823
d76cca68df feat(import): ImportService with conflict policy + media copy
Three-state outcome per note: inserted (new id), skipped (id+rawText
match), forked (id match but rawText differs → new uuidv7 to preserve
raw_text invariant from slice §1.1). Media files copied into
MediaStore convention <profileDir>/media/{noteId}/{n}.{ext} with
new media DB rows.

NoteRepository.importNote handles full provenance: ai_status='done',
ai_provider, ai_generated_at, edited flags, intent fields, tags
with source preserved.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 10:55:13 +09:00
altair823
e8587c1986 feat(import): pure parser for F5 export format
parseExportNote reverses composeMarkdown — minimal YAML parser
covering only the variants F5 emits (plain, single-quoted, block
scalar, tag/image lists). Body extraction strips h1 + blockquote +
image refs to recover rawText. Round-trip tested against
exportFormat.composeMarkdown.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 10:53:29 +09:00
altair823
fe6bbd3104 docs(plan): F6-L3 import 구현 계획 (4 tasks) 2026-04-26 10:50:01 +09:00