Files
inkling/docs/superpowers/specs/2026-05-09-v028-cut-a-design.md
altair823 7d2b8c95ec docs(v028+): F17~F25 dogfood + roadmap + Cut A~G specs + Cut A plan
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>
2026-05-09 15:09:02 +09:00

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 가 raw file:// 직접 접근 어려운 보안 정책 우회 — 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.tswhenReady 이전 (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.jsonbuild 블록 갱신:

"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 유지 + prebuild script 등으로 빌드 시 매번 재생성. 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.mjssharp 활용 ~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:iconsbuild/icon.ico build/icon.icns build/icon.png 존재 확인
Win exe 아이콘 - npm run dist:winInkling 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 항목:

  1. inbox 의 capture-with-image 흐름 — 캡처 → 이미지 thumbnail 표시 → 클릭 → OS viewer
  2. 새 아이콘이 트레이 / Windows taskbar / dock 모두 정확 표시
  3. 다중 이미지 (capture 가 N개 첨부) 의 grid layout — flex-wrap 적용 시 N row 자연스러운지

이슈 발견 시 dogfood-feedback.md F26 부터 누적.