Merge pull request 'chore(release): v0.2.4 — patch cut (backlog 5건 + dogfood unblock)' (#22) from feat/v024-patch-cleanup into main

Reviewed-on: #22
This commit was merged in pull request #22.
This commit is contained in:
2026-05-04 15:24:46 +00:00
9 changed files with 112 additions and 12 deletions

View File

@@ -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 유지하고 내용만 갱신)

View File

@@ -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 사유 카테고리

4
package-lock.json generated
View File

@@ -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",

View File

@@ -1,6 +1,6 @@
{
"name": "inkling",
"version": "0.2.3.1",
"version": "0.2.4",
"private": true,
"description": "Inkling — local-first 한 줄 보관 도구",
"author": "altair823 <dlsrks0734@gmail.com>",

View File

@@ -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<string, unknown>));
void gc.run()
.then((r) => logger.info('media.gc', { ...r } as Record<string, unknown>))
.catch((e) => logger.warn('media.gc.failed', { reason: String(e) }));
const exportSvc = new ExportService(repo, store);
const importSvc = new ImportService(repo, store);

View File

@@ -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 변경 시 정규식 한 곳만 고치면 됨.

View File

@@ -1,6 +1,36 @@
import electron from 'electron';
import type { Tray as TrayType, MenuItemConstructorOptions } from 'electron';
const { app, Tray, Menu, nativeImage } = electron;
import { platform, release, EOL } 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');
// OS EOL 사용 — 클립보드 → Notepad 등에서 줄바꿈 정상.
const detail = [
`버전: ${version}`,
`Electron: ${electronVersion}`,
`Node: ${nodeVersion}`,
`OS: ${platform()} ${release()}`,
`데이터 위치: ${profileDir}`
].join(EOL);
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}${EOL}${detail}`);
}).catch(() => {
// dialog reject 는 일반 사용에서 발생 X — main process crash 등 예외 케이스 silent.
});
}
let tray: TrayType | null = null;
let _showInbox: () => void = () => {};
@@ -60,6 +90,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);
}

View File

@@ -147,7 +147,6 @@ export function App(): React.ReactElement {
trashNotes.map((n) => (
<NoteCard
key={n.id} note={n} mode="trash"
onDeleted={() => removeNote(n.id)}
onUpdated={(u) => upsertNote(u)}
onRestore={() => void restoreNote(n.id)}
onPermanentDelete={() => void permanentDeleteNote(n.id)}

View File

@@ -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) {