v0.2.7 release 후 dogfood 9건 누적 (F17~F25) 정리: - F17 휴지통 의미 분기 / F18 사유 입력 / F19 recall / F20 raw_text 가변 - F21 다기기 sync / F22 이미지 렌더링 (이미 v0.2.8 promoted) / F23 Ollama-less - F24 멀티모달 vision / F25 사이드바 + 저장소 추가: - v0.2.8+ roadmap: 7 cut 분할 (A~G), 12주 시간선, dependency graph - Cut A~G design specs (각 cut 별 design 결정 + schema + UI + 테스트 전략) - Cut A implementation plan (이미 v0.2.8 머지로 실행 완료, 참고 보존) PR #26 머지 후 main 에 doc commits rebase 안 되어 manual merge 진행: - F22 entry 는 origin/main 의 promoted 형태 우선 - 신규 9 파일 (specs/plan/roadmap) 은 origin/main 에 없는 파일 - "다음 항목 자리" 안내 F23 → F26 갱신 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
8.2 KiB
v0.2.8 — Cut A Design (이미지 렌더링 + 앱 아이콘)
작성일: 2026-05-09 저자: 김태현 (dlsrks0734@gmail.com) 선행 문서:
docs/superpowers/specs/2026-04-25-dogfood-feedback.md(F22)docs/superpowers/strategy/v028plus-roadmap.md(Cut A 분할 + 우선순위)
Cut 라벨: v0.2.8 (semver patch — bug fix + asset 추가)
1. Cut 정체성
"이미지 렌더링 + 앱 아이콘 polish" cut. 두 작은 항목 묶음:
- F22 (이미지 렌더링): NoteCard 의 회색 placeholder div 를 실제
<img>로 교체. Electron renderer 가 rawfile://직접 접근 어려운 보안 정책 우회 —inkling-media://custom protocol 등록. - chore (앱 아이콘): 사용자 첨부 SVG (이미
assets/icon.svg작성·검토 완료) → ICO/ICNS/PNG 다중 size 자동 생성 + electron-builder config 통합.
명확/작은 작업, 의사결정 거의 없음. 빠른 release polish.
2. 범위
| 항목 | 출처 | 작업 |
|---|---|---|
| F22 | dogfood F22 | inkling-media:// protocol + NoteCard <img> + 클릭 시 OS viewer (shell.openPath) |
| chore | roadmap | electron-icon-builder devDep + npm run build:icons + electron-builder config (build.win.icon / build.mac.icon / build.linux.icon) |
3. F22 디테일
3-1. Custom protocol 등록
src/main/index.ts 의 whenReady 이전 (top-level) 에 scheme 권한 등록:
import { protocol } from 'electron';
protocol.registerSchemesAsPrivileged([
{ scheme: 'inkling-media', privileges: { secure: true, supportFetchAPI: true, stream: true } }
]);
whenReady 안에서 handler 등록:
import { promises as fs } from 'node:fs';
import { join, normalize, sep } from 'node:path';
protocol.handle('inkling-media', async (req) => {
const url = new URL(req.url);
const relPath = decodeURIComponent(url.pathname).replace(/^\//, '');
const mediaRoot = join(paths.profileDir, 'media');
const target = normalize(join(mediaRoot, relPath));
if (!target.startsWith(mediaRoot + sep)) {
return new Response(null, { status: 403 });
}
try {
const data = await fs.readFile(target);
return new Response(data, { headers: { 'content-type': inferMime(target) } });
} catch {
return new Response(null, { status: 404 });
}
});
inferMime() — 파일 확장자 → MIME (png/jpg/jpeg/gif/webp). 작은 함수 (별도 util 또는 inline).
3-2. NoteCard 갱신
src/renderer/inbox/components/NoteCard.tsx:336-338 의 회색 div 를 <img> 로 교체:
{local.media.map((m) => (
<img
key={m.id}
src={`inkling-media://${m.relPath}`}
alt=""
title={m.relPath}
onClick={() => inboxApi.openMedia(m.relPath)}
style={{
width: 48,
height: 48,
objectFit: 'cover',
borderRadius: 4,
cursor: 'pointer',
border: '1px solid #e0e0e0'
}}
/>
))}
m.relPath 형식 = media/<noteId>/<filename>. URL 형식: inkling-media://media/<noteId>/<filename>. handler 가 prefix 제거 후 <profileDir>/media/<noteId>/<filename> 으로 resolve.
3-3. IPC inbox:open-media
src/main/ipc/inboxApi.ts 에 신규 핸들러:
ipcMain.handle('inbox:open-media', async (_e, relPath: string) => {
const mediaRoot = join(paths.profileDir, 'media');
const target = normalize(join(mediaRoot, relPath));
if (!target.startsWith(mediaRoot + sep)) return { ok: false, reason: 'invalid path' };
await shell.openPath(target);
return { ok: true };
});
preload 화이트리스트 + src/shared/types.ts InboxApi.openMedia(relPath: string) 시그니처 + src/renderer/inbox/api.ts wrapper.
3-4. 보안 검토
- Path traversal: protocol handler + IPC 핸들러 모두
target.startsWith(mediaRoot + sep)검사. 통과 못 하면 403/실패. - Schemes privileges:
secure: true로 https 동등 권한 — webContents 가 페이지 안에서<img src="inkling-media://...">정상 로드. - CORS: same-origin 정책 영향 X (custom protocol 이라 별도). webContents 안 동일 origin 으로 인식.
- 인증: 단일 사용자 desktop app — 추가 인증 X.
4. chore 디테일
4-1. 의존성 + scripts
package.json:
"devDependencies": {
"electron-icon-builder": "^2.0.1"
},
"scripts": {
"build:icons": "electron-icon-builder --input=assets/icon.svg --output=build --flatten"
}
--flatten 옵션 = output 을 build/icon.ico, build/icon.icns, build/icon.png (1024x1024) 평면 배치. nested build/icons/png/<size>.png 도 함께.
4-2. electron-builder config
package.json 의 build 블록 갱신:
"win": { "icon": "build/icon.ico", ... },
"mac": { "icon": "build/icon.icns", ... },
"linux": { "icon": "build/icon.png", ..., "target": [ ... ] }
기존 win/mac/linux 블록에 "icon" 키만 추가 (다른 설정 그대로).
4-3. 산출물 git 추적
build/ 가 .gitignore 에 있다면 — 두 옵션:
- (a)
build/icon.*만 ignore 풀고 commit (size 약 200KB-1MB 작음 — 바이너리 commit 일반적). SVG 갱신 시npm run build:icons후 commit. - (b) 모두 ignore 유지 +
prebuildscript 등으로 빌드 시 매번 재생성. dist 빌드 시 자동 — 그러나 dev 환경 (npm start) 에서 아이콘 미생성 시 fallback 필요.
추천: (a) — 단순, 빌드 시간 ↓, dev 환경 문제 X.
.gitignore 갱신 예:
build/
!build/icon.ico
!build/icon.icns
!build/icon.png
4-4. SVG 가 input 으로 바로 가능?
electron-icon-builder v2.0.1 docs 검토 — PNG 1024x1024 입력 권장, SVG 는 librsvg 등 의존. SVG 직접 안 되면 sharp 로 SVG → PNG 1024 변환 후 input.
대안 (SVG 직접 안 될 시):
"build:icons:png": "node scripts/svg-to-png.mjs assets/icon.svg build/icon-source.png 1024",
"build:icons": "npm run build:icons:png && electron-icon-builder --input=build/icon-source.png --output=build --flatten"
scripts/svg-to-png.mjs — sharp 활용 ~10줄 스크립트.
5. 테스트 전략
| 영역 | 단위 | 수동 |
|---|---|---|
| protocol handler — path traversal | mock fs + URL 입력 (../etc/passwd 형태) → 403 |
- |
| protocol handler — 정상 200 | mock fs.readFile → bytes + content-type 검증 | - |
| protocol handler — 404 | fs.readFile reject → 404 | - |
inferMime |
확장자별 정확 mapping | - |
NoteCard <img> 렌더 |
media 배열 길이 N → <img> N 개 (jsdom mock) |
- |
<img> 클릭 → IPC |
onClick stub → inboxApi.openMedia 호출 |
- |
IPC inbox:open-media |
path traversal mock → 'invalid path' 반환 | - |
| 아이콘 빌드 | - | npm run build:icons → build/icon.ico build/icon.icns build/icon.png 존재 확인 |
| Win exe 아이콘 | - | npm run dist:win → Inkling Setup 0.2.8.exe 우클릭 → properties → 아이콘 = 새 디자인 |
| dogfood image flow | - | inbox 의 thumbnail 클릭 → OS viewer 열림 (Win + macOS) |
목표: 단위 460 → 약 467 (+7), typecheck 0.
6. Risk + Known unknowns
| Risk | 발생 시 대응 |
|---|---|
electron-icon-builder SVG 직접 미지원 |
sharp 로 SVG → PNG 1024 변환 (4-4 대안 적용) |
protocol.handle 가 Electron 41 미지원 (deprecated protocol.registerFileProtocol 만 있는 경우) |
Electron 41 docs 확인 후 deprecated API 사용 또는 newer API |
<img> 가 inkling-media:// 로드 실패 (CSP 차단 등) |
webContents 의 contentSecurityPolicy 검토. v0.2.5/6 의 single-instance lock + B4 #46 hidden flag 와 무관 |
| Win/Mac dogfood 시 OS viewer 가 default 미설정 | 사용자 OS settings — Inkling 책임 외 (그러나 안내 메시지 가능) |
7. v0.2.8 후
다음: Cut B (v0.2.9) — F17 status 분기 + F18 사유 + F23 Ollama-less. 데이터 모델 정비 cut.
Cut A 머지 후 dogfood verify 항목:
- inbox 의 capture-with-image 흐름 — 캡처 → 이미지 thumbnail 표시 → 클릭 → OS viewer
- 새 아이콘이 트레이 / Windows taskbar / dock 모두 정확 표시
- 다중 이미지 (capture 가 N개 첨부) 의 grid layout — flex-wrap 적용 시 N row 자연스러운지
이슈 발견 시 dogfood-feedback.md F26 부터 누적.