Commit Graph

89 Commits

Author SHA1 Message Date
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
altair823
f4d78456ae docs(spec): promote F5 export
Extracted to own spec with mini-brainstorm decisions captured.
F5 in dogfood-feedback.md marked 🚀 promoted with link.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 10:46:37 +09:00
altair823
27666178a2 feat(export): wire ExportService — tray '내보내기...' menu + dialog
Tray now has 4th callback that opens directory chooser, exports all
notes via ExportService with includeMedia=true default. Dialog
message warns about raw_text plain-text + recommends private location.
Native toast on success/failure with note + media counts.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 10:44:38 +09:00
altair823
9fdfd6610c feat(export): ExportService writing frontmatter tree + media + manifest
ExportService composes pure exportFormat layer + reads from
NoteRepository.listAll (new, asc-ordered) + MediaStore.absolutePath
(new helper). Writes notes/{date-id8-slug.md}, media/{id8__n.ext},
index.jsonl, manifest.json, README.md to user-picked dir.
6 unit tests against tmp dirs + :memory: DB.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 10:42:43 +09:00
altair823
8e09464d5e feat(export): pure frontmatter + slug + markdown + jsonl + manifest composers
Pure compose layer for F5 (Export). slugifyTitle, composeFilename,
composeFrontmatter, composeMarkdown, composeIndexJsonl, composeManifest
+ ExportNote/ExportNoteMedia/ExportNoteTag types. No fs deps.
24 unit tests covering normal cases + edge cases (null title,
forbidden chars, multiline summary needing block scalar, colon
needing single-quote, image numbering by id8__n.ext).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 10:39:32 +09:00
altair823
c3b650058a docs(plan): F5 export 구현 계획 (4 tasks) 2026-04-26 10:36:17 +09:00
altair823
9d63e95173 docs(spec): F6-L1 후속 항목 4개 추가 (final code review 반영)
final reviewer 가 식별한 forward-looking polish 4건을 후속 리스트에
명시. 4번째는 F1 (Due Date 마이그레이션 v2) PR 시 즉시 반영 권장:
openDb() 가 마이그레이션을 BackupService 인스턴스화 전 호출하므로
마이그레이션 결함 시 첫 실행 직전 상태 회수 불가.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 03:17:13 +09:00
altair823
d6ead8f1c6 docs(spec): promote F6-L1 local snapshot
Extracted to its own spec, dogfood-feedback.md F6 header reflects
L1 promoted status while L2/L3 remain raw.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 03:13:06 +09:00
altair823
06817f2b0b feat(backup): wire BackupService — whenReady + before-quit + tray
Instantiate BackupService at app.whenReady, run daily snapshot then
again before quit (synchronous-blocking via preventDefault). Tray menu
gets '지금 백업' entry that triggers manual runDaily with native
toast feedback.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 03:10:49 +09:00
altair823
4898e13308 feat(backup): runDaily() with .last-snapshot marker + rotate after snapshot
Skips when marker matches today's KST date. Marker written after
successful snapshot, before rotation. lastSnapshotAt() exposed for UI.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 03:08:30 +09:00
altair823
a728434b2e fix(backup): cleanup orphan .tmp on db.backup() failure + concurrency note
Code review I1: wrap snapshot's backup+rename in try/catch, unlink
orphan tmp file on failure so the next run does not encounter a
confusing 'existing file is not a database' error from sqlite.

Code review I2: JSDoc note that snapshot() is not safe for concurrent
calls — callers should serialize via runDaily()'s marker.

New unit test injects a fake db whose backup() rejects after a partial
write, asserts no .tmp / .sqlite remains.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 02:49:01 +09:00
altair823
714dd3fc9f feat(backup): atomic SQLite snapshot to inkling-YYYY-MM-DD.sqlite
KST date filename, tmp+rename atomic write, mkdir on demand.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 02:44:28 +09:00
altair823
603588cc4f chore(backup): polish — boundary test, roundtrip lock-in, precompute today
Code reviewer minor nitpicks:
- Add test for inkling-2026-02-30.sqlite (locks roundtrip-validation contract)
- Add test for weekly window inclusive at oldest boundary
- Precompute today=startOfDayUtc(now) once outside the loop, pass to helpers

No behavior change; tests added cover existing semantics.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 02:13:47 +09:00
altair823
902bc30adc chore(backup): rename WEEKLY_WINDOW_COUNT, document anchor+4 semantic
Spec reviewer flagged the weekly window keeps 5 Mondays (anchor + 4
prior), not 4 as the plan prose said. Code preserved (tests pass);
constant renamed and comment made honest about the actual semantic.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 02:10:37 +09:00
altair823
5e8e652ee0 feat(backup): GFS retention policy (pure)
14 daily + 4 weekly (Mondays) + 6 monthly (1st). Future-dated files
preserved (clock skew). Unrecognized filenames ignored (no delete).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 02:07:09 +09:00
altair823
7973ea5046 docs(plan): F6-L1 local snapshot 구현 계획 (TDD, 5 tasks)
Task 1: 순수 GFS retention 함수 + 7 단위 테스트
Task 2: BackupService.snapshot() — KST 날짜·tmp+rename 원자성 + 6 단위 테스트
Task 3: runDaily() — .last-snapshot 마커 + lastSnapshotAt + 7 단위 테스트
Task 4: main/index.ts wiring (whenReady + before-quit) + tray '지금 백업'
Task 5: F6-L1 promotion (별 spec 분기 + dogfood-feedback.md 상태 갱신)

backup 위치: <profileDir>/backups/ (mini-brainstorm 결과 A 채택).
스키마 변경 0, 외부 dep 0. better-sqlite3.backup() API 가정.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 01:51:27 +09:00
altair823
6d3df0273e docs(spec): F1~F6 dogfood 피드백 로드맵 v0.2.1 설계
8개 항목 순차 작업 (F6-L1 → F5 → F6-L3 → F1 → F2 → F3+F4-E →
F6-L2 → F4-C·F) + 데이터 안전 우선 + 머지+테스트 게이트 + 단일
v0.2.1 cut 후 dogfood 재설치 + 1주 soak. F4-A·D 는 측정 후
별 brainstorm 으로 deferred. 항목별 mini-brainstorm 에서
decision-pending 답변하는 라이프사이클.

본 spec 은 순서·범위·게이트만 정의, 항목 내부 설계는 per-item.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 01:42:46 +09:00
altair823
2e709652fe docs(feedback): add F6 — 메모 데이터 백업 + 복원 (3-layer)
L1 로컬 원자 스냅샷 (db.backup + GFS 로테이션) + L2 git remote 마크다운
동기화 (F5 형식 그대로 추적, SQLite 바이너리 push 회피) + L3 F5+import.
gitea 자체 호스팅 인프라 활용 가능. L2 는 별 spec, L1+L3 은 슬라이스 후속.
'데이터 손실 0회' 를 slice §1.3 silent invariant 후보로 제안.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 00:29:49 +09:00
altair823
830c2beed3 docs(feedback): add F5 — 마크다운 일괄 export (RAG 활용)
dogfood 외부 회수 채널 부재. RAG 친화 형식으로
notes/{date}-{id8}-{slug}.md (frontmatter+본문) +
index.jsonl + manifest.json + media/ 트리 권장. 스키마는
현행으로 충분, ExportService 신규 + 트레이 메뉴 1개. F4-H5
(외부 회수) 측정의 dependency 가 됨.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 00:23:43 +09:00
altair823
aa51df9b41 docs(feedback): add F4 — 떠오른 순간 → Inkling 자동 연상 만들기
slice + strategy.md §3 가 회의·퇴근 같은 강한 contextual cue 만 다루고
샤워·산책·자기 직전 같은 ambient 떠오름은 사각지대. 6개 심리 메커니즘
(habit stacking, ambient if-then, 환경 앵커, variable interval prompt,
Zeigarnik, 정체성 고리) 후보 + 슬라이스 내/후속 분류 + H1~H5 가설.
권장 순서: E (카피 priming) → 데이터 → C·F → A·D.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 00:19:37 +09:00
altair823
0cd9b99659 docs(feedback): add F3 — '구출' 카피의 한국어 어색함
dogfood 첫 알림 노출에서 '구출' 표현이 일상 한국어로 부자연스럽다는
피드백. 5개 UI 표면 + e2e 단언 + strategy.md §1·§3·§7 의 어휘 결정에
함께 묶임. drafting 시 결정 대기 #1(strategy 재검토 동반 여부)이
promoted 경로 좌우.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 00:14:00 +09:00
altair823
62a13ebf9f feat(packaging): add electron-builder NSIS installer + Windows autostart
- electron-builder 26.8.1 (devDep, exact pin) with NSIS x64 target
- moved electron 41.3.0 to devDependencies (electron-builder requirement)
- new scripts: dist, dist:dir, predist runs rebuild:electron + build
- main: detect --hidden arg, skip inbox window on hidden launch
- main: first-run autostart enable on packaged Windows (.autostart-init flag)
- tray: 'Windows 시작 시 자동 실행' checkbox (packaged only)
- README: packaging section + Dev Mode requirement

Build verified: dist/Inkling Setup 0.2.0.exe (100MB), dist/win-unpacked/
runs better-sqlite3 native module from app.asar.unpacked.

Note: requires Windows Developer Mode ON (winCodeSign cache extraction
contains darwin symlinks that need SeCreateSymbolicLinkPrivilege).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 23:45:56 +09:00
altair823
be24458450 chore(deps): bump uuid to ^14.0.0
v4/v7 imports stay API-compatible — typecheck and unit suite (52/52)
both pass against the new resolution. The caret range deviates from
the slice's strict-pin invariant (spec §7.1); revisit if reproducible
builds become an issue.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 17:33:32 +09:00
8eeba97a3a Merge pull request 'chore/post-slice-followups' (#1) from chore/post-slice-followups into main
Reviewed-on: #1
2026-04-25 08:29:16 +00:00
altair823
fac019e8a0 docs(spec): convert due-date stub into dogfood-feedback collection
Reframe the file as a living register for in-flight dogfood feedback
rather than a single-feature stub. Each item carries a status label
(raw / drafting / ready-for-spec / promoted / rejected) and a fixed
six-slot template (관찰 / 제안 방향 / 결정 대기 / 가설·측정 / 범위 /
영향). Items graduate to their own spec file once mature; the entry
here then collapses to a one-line link.

- F1: Due-date 추출 (drafting) — content from the previous stub
  normalized to the new template.
- F2: 태그 클릭 = 즉시 삭제 + undo 부재 (raw) — NoteCard.tsx:110
  binds chip onClick to removeTag, no confirm or undo, and there is
  no "filter by tag" affordance to match the user's mental model.
  Direction: split click=filter, ✕-icon=remove with 5s undo toast.

README docs map updated to point at the new path.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 17:14:38 +09:00
altair823
1fb6670e67 docs: decouple due-date spec from dogfood strategy doc
The due-date extraction work originated as feedback during dogfood, but
the dogfood strategy doc itself should stay feature-agnostic — it's the
generic operating manual for the 2-week dogfood, not a feedback log.

- Remove the "시간 표현 포함 노트 수" row from dogfood-strategy §3.1.
- Rephrase the due-date spec stub so H1 / §7 / §9 reference the spec's
  own sample-collection plan instead of relying on the dogfood retro.
- Spec is now framed as "independent of slice exit"; entry timing is a
  separate decision once an accumulated sample meets H1.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 17:10:04 +09:00
altair823
c5e5975a12 docs(spec): stub due-date extraction spec for post-slice work
Captures the design skeleton, hypotheses, and open questions for the
hybrid (rule-based first → AI fallback) Korean date-phrase extractor.
Schema impact, prompt change, UX copy, and library tradeoffs are
sketched but deferred — formalization waits for slice v0.4 dogfood
Pass and the H1 (time-phrase frequency ≥ 30%) check in week-1 retro.

Cross-link added to dogfood-strategy daily-metric table so the H1
data is collected during dogfood, and to README docs map.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 17:07:20 +09:00
altair823
df56661f4f feat(diag): log resolved Ollama endpoint + surface health reason in banner
When the OllamaBanner appears, it was generic enough that the user
couldn't tell whether the env var was missing, the LAN host was
unreachable, the model was uninstalled, or DNS was failing. Log the
resolved endpoint at startup with a `fromEnv` flag so we can confirm
INKLING_OLLAMA_ENDPOINT was actually read, and render the underlying
health-check reason as a small subtitle under the banner copy.

The user-facing primary message still avoids the forbidden tone words
("실패"/"끊김"/"연속 실패"); the diagnostic line is technical and only
appears when status.reason is set.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 16:20:57 +09:00
altair823
f9b0e67ec5 docs: add Korean README + 2-week dogfood strategy
- README.md: Korean project intro, prereqs (Volta + VS Build Tools 2022 +
  LAN Ollama), npm scripts, ABI/ELECTRON_RUN_AS_NODE gotchas, slice
  invariants, doc map.
- docs/superpowers/strategy/dogfood-strategy.md: 2-week self-dogfood plan
  to satisfy slice exit condition (spec §1.3). Covers Day 0 checklist,
  daily triggers tied to strategy §3 if-then implementation intentions,
  weekly continuity tracking, friction-vs-backlog triage rule, and a
  Pass/Conditional/Fail verdict at Day 14.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 16:12:08 +09:00
altair823
c7e9463adb fix(e2e): rebuild better-sqlite3 per ABI + select inbox window by title
- Vitest needs the node-ABI prebuilt sqlite binary; Electron 41 needs ABI 145.
  Add `rebuild:node` / `rebuild:electron` scripts and wire them as
  pre-hooks for `test`, `test:e2e`, `start`, `dev`, `test:integration`.
- Smoke test was racing on `firstWindow()`, which non-deterministically
  returned the quickcapture window. Strip ELECTRON_RUN_AS_NODE from the
  launch env, wait for both windows to register, then pick the inbox by
  title and assert the heading via `getByRole`.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 16:05:15 +09:00
altair823
96cab71fd6 chore(types): re-add type-only BrowserWindow / Tray imports
Follow-up to the CJS refactor: destructuring electron.BrowserWindow /
electron.Tray pulled the values into scope but lost the type
namespace, breaking sites that used the bare class name as a type
(let win: BrowserWindow | null, return-type annotations,
implicitly-any close handlers). Added 'import type { ... as ...Type }'
for each affected file and replaced the type-position references
with the aliased names. Runtime semantics unchanged; typecheck
exits 0 again.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 12:34:06 +09:00
altair823
94fc7c72d3 test(e2e): smoke test verifying v0.2 inbox empty state
Task 32 of the slice plan. playwright.config.ts (testDir
tests/e2e, single worker, 60s default timeout, list reporter)
and a smoke spec that launches the built main bundle, waits
for the inbox renderer, and asserts the v0.2 empty-state copy
'첫 기억을 구출해보세요.' is rendered.

Build verified end-to-end (`npm run build` exits 0, produces
out/main/index.js + out/preload/index.mjs + the inbox /
quickcapture renderer chunks). Playwright's chromium bundle
installed.

Known issue (deferred to a follow-up debug session): on this
specific Windows machine, electron@41.3.0's main-process
module hook is not injecting the real `electron` API into the
launched bundle. `require('electron')` from the entry script
returns the on-disk path string (the package's index.js
default), and `process.electronBinding` / `process.type` are
undefined inside the spawned process. This reproduces with a
minimal package.json + main.js outside this repo — i.e. it's
not a slice bug. The smoke test currently fails with
'Process failed to launch!' at the
`app.whenReady().then(...)` line because `app` is undefined.

When the host issue is sorted, no test changes should be
required: build, config, and spec are correct. Likely fixes:
clean-reinstall electron + node-modules, try electron@latest,
or invoke through `npx electron` rather than the binary
directly. Captured in project_inkling_status memory under
'open issues'.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 12:32:42 +09:00
altair823
7b129fec9f chore(build): switch main+preload to CJS for Electron 41 module hook
Two related runtime defects surfaced when first attempting
`npm run build` + Electron launch:

1. Renderer html files referenced `/src/.../main.tsx`, which
   vite dev resolves but vite production rollup cannot. Changed
   both inbox and quickcapture to `./main.tsx` (sibling-relative).

2. Electron 41's main-process module hook only injects the
   real `electron` API into `require('electron')` calls inside
   CommonJS modules. With package.json `"type": "module"` set,
   electron-vite emitted ESM bundles that did
   `import { app } from "electron"`; Node's ESM CJS-interop
   then loaded the on-disk index.js (which exports the binary
   path string) instead, leaving `app` undefined at startup.

Fix: drop `"type": "module"` so electron-vite emits CJS for
main + preload (renderer is unaffected — vite handles its own
ESM transform regardless). Source files keep modern import
syntax via the default-import + destructure pattern
(`import electron from 'electron'; const { app } = electron;`)
which transpiles correctly to `const { app } = require('electron')`
in the CJS output.

Also updated `electron-log/main` -> `electron-log/main.js`
because Node ESM strict resolution requires the `.js` extension
even though the package's CJS entry serves both.

Verification: `npm run build` produces 47-module renderer +
1005KB main bundle without errors. `npm run typecheck` clean.
Unit suite (52 tests) unaffected.

Plan deviation log: Task 4 (logger), Task 30 (main wiring), and
Tasks 17/18/22/30 referencing electron imports landed with named
imports per plan; this commit refactors them to default+destructure
without changing semantics.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 12:32:21 +09:00
altair823
580f0a54c9 feat(media): MediaGc for orphan dir cleanup on startup
Task 31 of the slice plan. Diff list of media/ subdirectories
against current note ids (SELECT id FROM notes); deletes
directories whose noteId is no longer referenced. Runs once on
startup from main; returns { removed } so logger captures the
count. Covers the 'media delete failure leaves orphan' branch
of spec §6.1 row 8.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 12:19:53 +09:00
altair823
71aafa2337 feat(app): wire NotificationService, IntentService, ContinuityService into main
Task 30 of the slice plan. Replaces the Task 2 placeholder main
entry with the final whenReady wiring:
- profile path resolution + better-sqlite3 open + migrations.
- repo / store / continuity / intent / health constructed
  against the open db.
- LocalOllamaProvider reads INKLING_OLLAMA_ENDPOINT for LAN
  dogfood, falls back to localhost:11434 otherwise.
- AiWorker registers an onUpdate that fans note:updated through
  pushNoteUpdated(getInboxWindow, note).
- NotificationService is plumbed to electron.Notification with
  isSupported gating; CaptureService gets the worker.enqueue +
  notify.celebrate hooks.
- IPC bindings for both capture and inbox surfaces.
- HotkeyService registers Ctrl/Cmd+Shift+J -> showQuickCapture;
  failure logged but not fatal.
- Tray menu '구출한 메모 보기' / '기억 구출하기' / '종료'
  with click-to-show-inbox.
- worker.loadFromDb() resumes pending jobs at startup.
- MediaGc runs once and logs the count of removed orphan dirs.
- Two logger.info(..., {...obj} as Record<string, unknown>)
  casts at the health and gc result sites to satisfy the
  index-signature requirement on logger meta — the result
  objects (HealthResult, {removed: number}) are assignment-
  compatible with Record<string, unknown> at runtime but TS
  refuses without the spread + cast.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 12:19:53 +09:00
altair823
58b85ff5c3 feat(health): OllamaBanner v0.2 copy
Task 29 of the slice plan. Subscribes to ollamaStatus from the
inbox store and renders one of two messages: 'ollama pull
gemma4:e4b 실행 후 앱을 재시작해주세요.' when the model isn't
installed (status.reason includes 'not installed'), or
'Inkling 정리가 잠시 멈췄습니다. Ollama를 실행해주세요.' for
any other unhealthy state. Capture continues regardless.

HealthChecker.ts itself was pulled forward in Task 21 so its
import would resolve from inboxApi.ts at compile time.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 12:19:53 +09:00