From 4bde148cdc7e71a648215dea9d122a9efbf68c5a Mon Sep 17 00:00:00 2001 From: altair823 Date: Tue, 5 May 2026 00:09:56 +0900 Subject: [PATCH 1/6] =?UTF-8?q?docs(v024):=20patch=20cleanup=20spec=20?= =?UTF-8?q?=E2=80=94=205=20backlog=20=ED=95=AD=EB=AA=A9=20+=20version=20bu?= =?UTF-8?q?mp?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 0.2.3.1 semver 위반 → 0.2.4 minor bump 이용해 backlog risk 낮은 cleanup 5건 + dogfood 가치 #44 묶음 cut. v0.2.4 정식 brainstorm 은 v0.2.5 로 이동. In: #1 (now() 2번), #2 (DAY_MS), #6 (media.gc .catch), #13 (NoteCard onDeleted), #44 (버전 정보 surface), version bump Out: #45 (autostart bug — 별도 cut), #3/#4/#5/#22/#26 (큰 refactor), #39~#43 (PR #21 deferred — v0.2.5 brainstorm) Co-Authored-By: Claude Opus 4.7 (1M context) --- .../2026-05-05-v024-patch-cleanup-design.md | 54 +++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 docs/superpowers/specs/2026-05-05-v024-patch-cleanup-design.md diff --git a/docs/superpowers/specs/2026-05-05-v024-patch-cleanup-design.md b/docs/superpowers/specs/2026-05-05-v024-patch-cleanup-design.md new file mode 100644 index 0000000..c3c75c8 --- /dev/null +++ b/docs/superpowers/specs/2026-05-05-v024-patch-cleanup-design.md @@ -0,0 +1,54 @@ +# v0.2.4 Patch Cleanup — Design Spec (Brief) + +> 작성: 2026-05-05 · 0.2.3.1 semver 위반 (`X.Y.Z.W` 4-part) → 0.2.4 minor bump 이용해 backlog 의 simple cleanup 5건 + 사용자 가치 1건 합쳐서 묶음 cut. v0.2.4 정식 brainstorm 은 v0.2.5 로 이동. + +## 1. Goal + +PR #21 머지 후 0.2.3.1 binary 빌드 시도가 electron-builder 의 semver validation 으로 실패. 0.2.4 minor bump 으로 우회. 이번 cut 에는 dogfood unblock 외 backlog 의 risk 낮은 cleanup + 사용자 가치 항목 동봉. + +## 2. Scope (5 backlog 항목 + version bump) + +| backlog # | 항목 | 가치 | 작업량 | +|---|---|---|---| +| #1 | `TelemetryService.emit` 의 `now()` 2번 호출 → 1번 추출 | cosmetic (KST midnight straddle 이론) | 1줄 | +| #2 | `DAY_MS = 24*60*60*1000` magic number → 모듈 상단 상수 | cosmetic | 1줄 | +| #6 | `media.gc.run()` `.catch` 누락 → backup pattern 통일 | consistency | 1줄 | +| #13 | NoteCard `mode='trash'` 의 `onDeleted` dead-code prop 제거 | API 청소 | 작음 | +| #44 | 트레이 메뉴 + Inbox footer 에 "Inkling 0.2.4" 버전 정보 | **사용자 dogfood 가치** | 1 task | +| - | version bump 0.2.3.1 → 0.2.4 | semver 표준 | trivial | + +## 3. Out of scope + +- **#45 (자동실행 버그)**: Windows registry 디버깅 필요, simple X. 별도 cut. +- **#3/#4/#26 (KST 통합 / TrayCallbacks refactor)**: multi-file, 크다. 별도. +- **#5/#22 (Union 통합 / hydrate cleanup)**: repo-wide. +- **#39~#43 (PR #21 deferred)**: telemetry masking 등 의미 있는 결정 필요. v0.2.5 brainstorm 영역. +- 기타 backlog 39건. + +## 4. Architecture changes + +본 cut 은 의미 있는 architecture 변경 없음. 기존 pattern 강화만: +- `TelemetryService.emit` 의 atomic timestamp 보장 (now() 1회) +- 모듈 상단 magic number 상수화 패턴 (다른 파일은 이미 그 패턴, TelemetryService 만 예외) +- `.catch` consistency (backup.runDaily / telemetry.cleanupOldFiles 와 동일 wrapper) +- React props 청소 (현재 호출되지 않는 prop 제거) +- 신규 surface: 트레이 메뉴 "Inkling 정보..." → modal 또는 dialog + +## 5. Tests + +테스트 추가 없음 (모두 cosmetic / refactor). 기존 단위 413/413 회귀 X 확인만. + +#44 의 modal 은 컴포넌트 단위 테스트 X (Inkling 패턴 — store-only). + +## 6. Gates + +- typecheck 0 +- 단위 413/413 (회귀 X) +- e2e 1/1 +- backward compat: 기존 사용자 영향 0 (cosmetic + 새 surface) + +## 7. Roadmap relation + +- 0.2.3 cut 7/7 (PR #13~#19) + 0.2.3.1 patch (PR #21) 누적 후 binary 빌드를 위한 v0.2.4 minor bump +- v0.2.5 brainstorm 트리거: dogfood ≥1주 soak + telemetry export + backlog 39건 (=45-5-1) + 신규 피드백 일괄 triage +- backlog 명명 `v024-backlog.md` → 본 cut 후 `v025-backlog.md` 로 rename 검토 (또는 v024-backlog.md 유지하고 내용만 갱신) From ef5d3daf4cc6db1c829c238f90d2f6461659b620 Mon Sep 17 00:00:00 2001 From: altair823 Date: Tue, 5 May 2026 00:11:38 +0900 Subject: [PATCH 2/6] =?UTF-8?q?refactor(v024):=20TelemetryService=20DAY=5F?= =?UTF-8?q?MS=20=EC=83=81=EC=88=98=20+=20media.gc=20.catch=20(backlog=20#2?= =?UTF-8?q?=20#6)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - #2: 24*60*60*1000 magic number → 모듈 상단 const DAY_MS cleanupOldFiles + readAllRecent 두 callsite 통일 - #6: gc.run() 의 .catch 누락 → backup.runDaily 패턴 통일 실패 시 logger.warn('media.gc.failed', { reason }) Note: backlog #1 (now() 2번 호출) 은 PR #13 round 1 review 시 이미 fix — backlog 항목 stale. v0.2.5 brainstorm 시 backlog 정리. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/main/index.ts | 4 +++- src/main/services/TelemetryService.ts | 5 +++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/main/index.ts b/src/main/index.ts index f4f3932..d102550 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -146,7 +146,9 @@ app.whenReady().then(async () => { await worker.loadFromDb(); const gc = new MediaGc(db, store); - void gc.run().then((r) => logger.info('media.gc', { ...r } as Record)); + void gc.run() + .then((r) => logger.info('media.gc', { ...r } as Record)) + .catch((e) => logger.warn('media.gc.failed', { reason: String(e) })); const exportSvc = new ExportService(repo, store); const importSvc = new ImportService(repo, store); diff --git a/src/main/services/TelemetryService.ts b/src/main/services/TelemetryService.ts index cb5e0c6..fe7abd1 100644 --- a/src/main/services/TelemetryService.ts +++ b/src/main/services/TelemetryService.ts @@ -4,6 +4,7 @@ import { validateEvent, TelemetryEvent } from './telemetryEvents.js'; import { aggregateStats } from './telemetryStats.js'; const KST_OFFSET_MS = 9 * 60 * 60 * 1000; +const DAY_MS = 24 * 60 * 60 * 1000; function todayKstIso(now: Date): string { const k = new Date(now.getTime() + KST_OFFSET_MS); @@ -52,7 +53,7 @@ export class TelemetryService { } catch { return { removed }; } - const cutoff = new Date(this.now().getTime() - this.retentionDays * 24 * 60 * 60 * 1000); + const cutoff = new Date(this.now().getTime() - this.retentionDays * DAY_MS); const cutoffIso = todayKstIso(cutoff); // KST 일자 비교 for (const name of entries) { const m = /^events-(\d{4}-\d{2}-\d{2})\.jsonl$/.exec(name); @@ -94,7 +95,7 @@ export class TelemetryService { } catch { return events; } - const cutoffMs = this.now().getTime() - this.retentionDays * 24 * 60 * 60 * 1000; + const cutoffMs = this.now().getTime() - this.retentionDays * DAY_MS; const cutoffIso = todayKstIso(new Date(cutoffMs)); // 회차 1 review (PR #13) — 매직 슬라이스 `n.slice(7, 17)` 대신 정규식 capture 그룹으로 // 일자를 추출. prefix 변경 시 정규식 한 곳만 고치면 됨. From c87c248e89e5893618fbef32b9b3856713d46d89 Mon Sep 17 00:00:00 2001 From: altair823 Date: Tue, 5 May 2026 00:12:56 +0900 Subject: [PATCH 3/6] =?UTF-8?q?refactor(v024):=20NoteCard=20onDeleted=20op?= =?UTF-8?q?tional=20+=20trash=20mode=20=EB=AF=B8=EC=A0=84=EB=8B=AC=20(back?= =?UTF-8?q?log=20#13)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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) --- src/renderer/inbox/App.tsx | 1 - src/renderer/inbox/components/NoteCard.tsx | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/renderer/inbox/App.tsx b/src/renderer/inbox/App.tsx index 0926b68..54aad1b 100644 --- a/src/renderer/inbox/App.tsx +++ b/src/renderer/inbox/App.tsx @@ -147,7 +147,6 @@ export function App(): React.ReactElement { trashNotes.map((n) => ( removeNote(n.id)} onUpdated={(u) => upsertNote(u)} onRestore={() => void restoreNote(n.id)} onPermanentDelete={() => void permanentDeleteNote(n.id)} diff --git a/src/renderer/inbox/components/NoteCard.tsx b/src/renderer/inbox/components/NoteCard.tsx index 46fd9af..b9242e2 100644 --- a/src/renderer/inbox/components/NoteCard.tsx +++ b/src/renderer/inbox/components/NoteCard.tsx @@ -8,7 +8,7 @@ import { pushTagUndo } from './TagUndoToast.js'; interface Props { note: Note; - onDeleted: () => void; + onDeleted?: () => void; // inbox mode 전용 (trash mode 에서 미사용) onUpdated: (n: Note) => void; mode?: 'inbox' | 'trash'; // default 'inbox' onRestore?: () => void; @@ -119,7 +119,7 @@ export function NoteCard({ note, onDeleted, onUpdated, mode = 'inbox', onRestore async function handleDelete() { if (!window.confirm('이 기억을 버릴까요? 되돌릴 수 없습니다.')) return; await inboxApi.deleteNote(note.id); - onDeleted(); + onDeleted?.(); } async function saveTitle(next: string) { From d3dfe1e4e24f94ac05acef2a9fd6eac0448aed42 Mon Sep 17 00:00:00 2001 From: altair823 Date: Tue, 5 May 2026 00:14:30 +0900 Subject: [PATCH 4/6] =?UTF-8?q?feat(v024):=20"Inkling=20=EC=A0=95=EB=B3=B4?= =?UTF-8?q?..."=20=ED=8A=B8=EB=A0=88=EC=9D=B4=20=EB=A9=94=EB=89=B4=20+=20n?= =?UTF-8?q?ative=20About=20dialog=20(backlog=20#44)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit dogfood 발견 #44 fix — 사용자가 설치된 버전 확인 path 부재 해소. - 트레이 메뉴 마지막 항목 (종료 직전): "Inkling 정보..." - 클릭 시 native dialog (showMessageBox): - title: Inkling 정보 - message: Inkling {version} - detail: 버전, Electron, Node, OS platform/release, 데이터 위치 - 버튼 3개: 확인 / 데이터 위치 열기 (shell.openPath) / 정보 복사 (clipboard) - 디버그 정보 노출로 사용자가 issue report 시 첨부 가능 Co-Authored-By: Claude Opus 4.7 (1M context) --- src/main/tray.ts | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/src/main/tray.ts b/src/main/tray.ts index 38af617..a78472f 100644 --- a/src/main/tray.ts +++ b/src/main/tray.ts @@ -1,6 +1,33 @@ import electron from 'electron'; import type { Tray as TrayType, MenuItemConstructorOptions } from 'electron'; -const { app, Tray, Menu, nativeImage } = electron; +import { platform, release } from 'node:os'; +const { app, Tray, Menu, nativeImage, dialog, shell, clipboard } = electron; + +function showAboutDialog(): void { + const version = app.getVersion(); + const electronVersion = process.versions.electron ?? '?'; + const nodeVersion = process.versions.node ?? '?'; + const profileDir = app.getPath('userData'); + const detail = [ + `버전: ${version}`, + `Electron: ${electronVersion}`, + `Node: ${nodeVersion}`, + `OS: ${platform()} ${release()}`, + `데이터 위치: ${profileDir}` + ].join('\n'); + void dialog.showMessageBox({ + type: 'info', + title: 'Inkling 정보', + message: `Inkling ${version}`, + detail, + buttons: ['확인', '데이터 위치 열기', '정보 복사'], + defaultId: 0, + cancelId: 0 + }).then((r) => { + if (r.response === 1) void shell.openPath(profileDir); + if (r.response === 2) clipboard.writeText(`Inkling ${version}\n${detail}`); + }); +} let tray: TrayType | null = null; let _showInbox: () => void = () => {}; @@ -60,6 +87,7 @@ function buildMenu() { } else { items.push({ type: 'separator' }); } + items.push({ label: 'Inkling 정보...', click: showAboutDialog }); items.push({ label: '종료', click: () => { app.isQuitting = true; app.quit(); } }); return Menu.buildFromTemplate(items); } From 298d1c6182b2cafa1c9ff27a78146eb0dca7f97a Mon Sep 17 00:00:00 2001 From: altair823 Date: Tue, 5 May 2026 00:15:51 +0900 Subject: [PATCH 5/6] =?UTF-8?q?chore(release):=20v0.2.4=20=E2=80=94=20patc?= =?UTF-8?q?h=20cut=20(backlog=205=EA=B1=B4=20=EC=B2=98=EB=A6=AC=20+=20dogf?= =?UTF-8?q?ood=20unblock)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PR #21 머지 후 v0.2.3.1 binary 빌드 시도 → electron-builder semver 검증 실패 (4-part X.Y.Z.W 비호환). v0.2.4 minor bump 으로 우회. 본 cut 동봉: - 0.2.3.1 의 in-app Ollama 설정 UI (PR #21 fee982a) - backlog #2 (DAY_MS 상수) - backlog #6 (media.gc .catch) - backlog #13 (NoteCard onDeleted optional) - backlog #44 (버전 정보 트레이 메뉴) - backlog #1 stale 표기 (PR #13 시 이미 fix) 게이트: typecheck 0 / 단위 413 / e2e 1 다음: PR + 머지 후 binary 빌드 v0.2.4 + Gitea release v0.2.5 brainstorm 트리거 시 잔여 backlog 39건 일괄 triage Co-Authored-By: Claude Opus 4.7 (1M context) --- docs/superpowers/v024-backlog.md | 17 +++++++++++++++-- package-lock.json | 4 ++-- package.json | 2 +- 3 files changed, 18 insertions(+), 5 deletions(-) diff --git a/docs/superpowers/v024-backlog.md b/docs/superpowers/v024-backlog.md index 45fc508..5585ed8 100644 --- a/docs/superpowers/v024-backlog.md +++ b/docs/superpowers/v024-backlog.md @@ -3,8 +3,21 @@ > v0.2.3 cut (7항목 / PR #13~#19) 동안 final reviewer + PR review round 1 에서 발견된 minor / nit 중 의도적으로 deferred 한 항목 누적. v0.2.3 dogfood soak 후 신규 피드백 + 본 리스트 일괄 triage → v0.2.4 cut 결정. **누적 시작일:** 2026-05-01 (#7 telemetry skeleton 머지 시점) -**최종 갱신:** 2026-05-02 (v0.2.3 cut 7/7 완료) -**총 항목 수:** 38 +**최종 갱신:** 2026-05-05 (v0.2.4 patch cut — backlog 5건 처리) +**총 항목 수:** 45 (잔여 39 = 45 − [#1 stale + #2/#6/#13/#44/#45 본 cut 처리 5건] 단 #45 는 별도 cut, 아래 표 참조) + +## 처리 이력 + +| 항목 | 상태 | Cut | +|---|---|---| +| #1 (`now()` 2번 호출) | 이미 fix (PR #13 round 1 — backlog stale) | - | +| #2 (`DAY_MS` magic) | ✅ 처리 | v0.2.4 patch (commit `ef5d3da`) | +| #6 (`media.gc.run()` `.catch`) | ✅ 처리 | v0.2.4 patch (commit `ef5d3da`) | +| #13 (NoteCard `onDeleted` dead-code) | ✅ 처리 | v0.2.4 patch (commit `c87c248`) | +| #44 (버전 정보 surface) | ✅ 처리 (트레이 "Inkling 정보..." + native dialog) | v0.2.4 patch (commit `d3dfe1e`) | +| #45 (자동실행 풀림 버그) | 별도 cut 예정 (Windows registry 디버깅) | TBD | + +**잔여 39건.** v0.2.5 brainstorm 시 신규 dogfood 피드백 + 잔여 39건 일괄 triage. ## Defer 사유 카테고리 diff --git a/package-lock.json b/package-lock.json index 0371cbe..4ddafcc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "inkling", - "version": "0.2.3.1", + "version": "0.2.4", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "inkling", - "version": "0.2.3.1", + "version": "0.2.4", "dependencies": { "better-sqlite3": "12.9.0", "electron-log": "5.2.0", diff --git a/package.json b/package.json index 864cd91..473ff16 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "inkling", - "version": "0.2.3.1", + "version": "0.2.4", "private": true, "description": "Inkling — local-first 한 줄 보관 도구", "author": "altair823 ", From d213d45f9288e0cdd55670193b3734c80ed051b4 Mon Sep 17 00:00:00 2001 From: altair823 Date: Tue, 5 May 2026 00:22:00 +0900 Subject: [PATCH 6/6] fix(v024): About dialog EOL + .catch (round 1 review) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Round 1 review minor + final reviewer minor 일괄: - About dialog detail/clipboard 의 줄바꿈 → os.EOL (Windows Notepad 등에서 줄바꿈 정상) - showMessageBox().then().catch(() => {}) — dialog reject (main crash 예외) silent (tray.ts 가 logger 미import — minimal swallow 패턴 채택) skip: - nit: 트레이 메뉴 ordering ("정보" → "종료" 한 그룹) — 현재 패턴도 흔함, 호불호 영역 - nit: process.versions.electron ?? '?' dead branch — 안전 fallback 유지 Co-Authored-By: Claude Opus 4.7 (1M context) --- src/main/tray.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/main/tray.ts b/src/main/tray.ts index a78472f..3f02373 100644 --- a/src/main/tray.ts +++ b/src/main/tray.ts @@ -1,6 +1,6 @@ import electron from 'electron'; import type { Tray as TrayType, MenuItemConstructorOptions } from 'electron'; -import { platform, release } from 'node:os'; +import { platform, release, EOL } from 'node:os'; const { app, Tray, Menu, nativeImage, dialog, shell, clipboard } = electron; function showAboutDialog(): void { @@ -8,13 +8,14 @@ function showAboutDialog(): void { const electronVersion = process.versions.electron ?? '?'; const nodeVersion = process.versions.node ?? '?'; const profileDir = app.getPath('userData'); + // OS EOL 사용 — 클립보드 → Notepad 등에서 줄바꿈 정상. const detail = [ `버전: ${version}`, `Electron: ${electronVersion}`, `Node: ${nodeVersion}`, `OS: ${platform()} ${release()}`, `데이터 위치: ${profileDir}` - ].join('\n'); + ].join(EOL); void dialog.showMessageBox({ type: 'info', title: 'Inkling 정보', @@ -25,7 +26,9 @@ function showAboutDialog(): void { cancelId: 0 }).then((r) => { if (r.response === 1) void shell.openPath(profileDir); - if (r.response === 2) clipboard.writeText(`Inkling ${version}\n${detail}`); + if (r.response === 2) clipboard.writeText(`Inkling ${version}${EOL}${detail}`); + }).catch(() => { + // dialog reject 는 일반 사용에서 발생 X — main process crash 등 예외 케이스 silent. }); }