# v0.2.7 — Cross-Platform 입구 정상화 (Design)

**작성일:** 2026-05-06
**저자:** 김태현 (dlsrks0734@gmail.com)
**선행 문서:**

- `docs/superpowers/specs/2026-04-25-dogfood-feedback.md` (F12, F14, F15, F16)
- `docs/superpowers/v024-backlog.md` (잔여 24건)
- `docs/superpowers/strategy/dogfood-strategy.md` (운영안)

**Cut 라벨:** v0.2.7 (semver 엄밀히는 MINOR — 새 플랫폼 + 새 surface — 이지만 본 프로젝트 관습상 v0.2.x 를 feature lane 으로 사용 중이므로 v0.2.7 라벨 유지)

---

## 1. Cut 정체성

**"Cross-platform 입구 정상화" cut.** F12 / F14 / F15 / F16 4개 항목을 한 묶음으로 처리. 핵심 동기:

> Windows 트레이 의존을 끊고 macOS / Linux 사용자에게 동등한 입구를 제공한다.

현재 13개 트레이 메뉴 항목이 macOS / Linux (특히 모던 GNOME) 에서 발견 / 접근성이 떨어져 핵심 설정 (Ollama endpoint, 자동 실행 등) 진입이 막히는 구조적 문제. 트레이를 deemphasis 하고 inbox 윈도우 안에 통합 설정 페이지를 둔다. 동시에 macOS dock 동작 정상화 (F14) + Linux 앱 빌드 추가 (F15 축소판) + 자동 실행 진단 노출 (F12 deeper fix) 까지 함께 처리한다.

**의도적으로 빠진 것:**

- ~~CLI (`inkling capture` 등)~~ — DB / Ollama 동시접근 race + monorepo 재구성 부담 대비 본인 dogfood metric 직접 기여 적음. v0.2.7 에서 제외. 외부 demand 누적 시 v0.3+ 재거론.

---

## 2. 범위

| 항목 | 출처 | 작업 |
|---|---|---|
| **F15 (축소판)** | dogfood F15 | Linux 앱 빌드 (AppImage + deb x64) + better-sqlite3 prebuild linux-x64 매트릭스 |
| **F16** | dogfood F16 | 트레이 슬림 (13 → 4) + inbox 안 설정 페이지 (4 섹션) |
| **F14** | dogfood F14 | macOS dock 클릭 시 hidden 창 show/focus (activate 핸들러 5줄 수정) |
| **F12 deeper fix** | dogfood F12 (v0.2.6 진단 fallback 후속) | 설정 페이지 "자동 실행" 섹션 안에 진단 패널 노출 (withArgs vs noArgs / executableWillLaunchAtLogin / registry path) |

---

## 3. Architecture 변화

| 영역 | 현재 (v0.2.6) | v0.2.7 |
|---|---|---|
| 설정 진입 | 트레이 메뉴 13개 항목 | 트레이 4개 + 설정 페이지 (inbox 내부 라우트) |
| Ollama 설정 | OllamaSettingsModal (트레이에서만 진입) | 설정 페이지 안 "AI 제공자" 섹션 (modal 흡수) |
| 자동 실행 | 트레이 checkbox + args 명시 | 설정 페이지 안 섹션 + 진단 패널 |
| macOS dock 클릭 | activate 핸들러 no-op (length===0 분기 못 탐) | `getInboxWindow().show() + focus()` 분기 추가 |
| Linux 배포 | 없음 | AppImage + deb 산출물 |
| 빌드 매트릭스 | win-x64 + mac-arm64 | + linux-x64 |

---

## 4. 구현 순서 (Approach 2: Risk-reduction first)

```
1. Linux 빌드 (가장 unknown — better-sqlite3 prebuild linux-x64 검증)
   ↓ AppImage + deb 산출 + Linux VM/WSL2 smoke test
2. 설정 페이지 (inbox 내부 라우트 + 4 섹션)
   ↓ OllamaSettingsModal 흡수
3. 트레이 슬림 (13 → 4)
   ↓ 제거된 click 핸들러 → 설정 페이지 버튼으로 이동
4. F14 macOS dock 클릭 fix
   ↓ activate 핸들러 5줄
5. F12 deeper fix (자동 실행 진단 노출)
   ↓ IPC settings:autostart-state + 진단 panel UI
```

Linux 빌드를 먼저 두는 이유: native ABI 트랩 (메모 `project_inkling_status.md`) 이 linux-x64 에서 재발할 수 있음. 만약 prebuild 가 깔끔히 떨어지지 않으면 v0.2.7 scope 조정 (예: AppImage 만, deb 는 v0.2.8) 여유. 설정 페이지 / 트레이 슬림 / F14 / F12 는 모두 코드 작성 risk 가 낮은 영역이라 후순위로 안전.

---

## 5. Linux 빌드 디테일

### 5-1. electron-builder config 추가

```json
"linux": {
  "target": [
    { "target": "AppImage", "arch": ["x64"] },
    { "target": "deb", "arch": ["x64"] }
  ],
  "category": "Utility",
  "synopsis": "로컬 메모 캡처 + AI 태그",
  "description": "Inkling — 잠깐 스친 생각을 잡아두는 로컬-우선 메모 도구."
}
```

### 5-2. npm scripts 추가

```json
"predist:linux": "npm run rebuild:electron && npm run build",
"dist:linux": "electron-builder --linux --x64"
```

`rebuild:electron` 의 `--target=41.3.0` 그대로. `prebuild-install` 이 linux-x64 prebuild 를 npm 레지스트리에서 받아오는지 검증. 없으면 `node-gyp` fallback 으로 로컬 컴파일.

### 5-3. 빌드 호스트 전략

**1차: macOS 호스트** (이미 DMG 빌드 호스트). brew 로 도구 설치:

```bash
brew install dpkg fakeroot
```

electron-builder 가 cross-build 지원. AppImage 는 Mac 에서 직접 빌드 가능 (Linux 유저랜드 도구만 필요한 부분은 electron-builder 내장 + AppImageKit 자동 다운로드). deb 는 dpkg-deb 필요.

**Fallback (1차 실패 시): Docker on Mac/Windows.** `electronuserland/builder` 이미지로 Linux 빌드 환경 격리. v0.2.7 scope 안에서 결정.

### 5-4. Smoke test

`dist/` 산출물:

- `Inkling-0.2.7.AppImage` (x64) — Linux VM 또는 WSL2 에서 chmod +x → 실행 → 마이그레이션 통과 확인 → capture / recall 한 사이클.
- `inkling_0.2.7_amd64.deb` — Ubuntu/Debian VM 또는 WSL2 에서 `sudo dpkg -i` → `inkling` 실행 → 동일 검증.

검증 항목:

1. better-sqlite3 native module 로드 성공 (마이그레이션 0 → m003 통과)
2. Ollama 연결 시도 (settings.json 의 endpoint 또는 `INKLING_OLLAMA_ENDPOINT` env) — 본인 LAN 서버 `http://192.168.0.47:11434` 사용
3. capture 한 줄 → AI 처리 → tag 표시
4. 트레이 (KDE/Cinnamon DE 가정) 4 항목 표시
5. 트레이 없는 DE (모던 GNOME) — launcher 에서 앱 실행 → inbox 윈도우 → 톱니바퀴 → 설정 페이지 진입

---

## 6. 설정 페이지 디테일

### 6-1. 라우팅 방식

React Router 도입 안 함 (의존성 + 학습 비용). zustand store 의 `view: 'inbox' | 'trash' | 'settings'` state + 조건부 렌더 — 기존 trash view 와 동일 패턴. 새 의존성 0.

### 6-2. 진입점

| 진입 | 동작 |
|---|---|
| 트레이 "설정..." 클릭 | main → IPC `inbox:navigate` 'settings' → renderer store action `setView('settings')` + inbox 윈도우 show/focus |
| inbox 헤더 톱니바퀴 아이콘 | renderer store action `setView('settings')` |
| 설정 페이지 안 "← 돌아가기" 버튼 | `setView('inbox')` |

### 6-3. 섹션 4개

#### 6-3-1. AI 제공자

흡수 대상: OllamaSettingsModal 전체 + 트레이 "Ollama 재확인".

UI 요소:

- Endpoint URL 입력 (zod 검증 — 기존 modal 의 `safeParse` 재활용)
- Model 입력 (빈 값 guard)
- "지금 재확인" 버튼 → ProviderHolder 의 health check trigger
- 마지막 ping 결과 표시 (성공 시각 또는 실패 사유)
- "기본값으로 되돌리기" 버튼

저장: 기존 SettingsService (atomic temp+rename + zod) 그대로.

#### 6-3-2. 자동 실행

흡수 대상: 트레이 "윈도우 시작 시 자동 실행" checkbox.

UI 요소:

- 토글 ("앱 시작 시 자동으로 실행")
- 진단 패널 (펼치기 가능 — 평소엔 접혀 있음)
- "재등록" 버튼 (setLoginItemSettings 강제 재호출)

진단 패널 디테일은 §9 (F12 deeper fix) 참조.

#### 6-3-3. 백업 / 복원 / 내보내기

흡수 대상: 트레이의 5개 항목 — "지금 백업" / "내보내기..." / "백업에서 복원..." / "지금 동기화" / "사용 로그 내보내기...".

UI 요소: 5개 버튼 + 각 작업 마지막 실행 시각 (가능하면) + 결과 toast.

IPC 핸들러는 기존 그대로 — 트레이 click 핸들러였던 함수를 IPC 핸들러로 등록 + renderer 에서 invoke.

#### 6-3-4. 정보

흡수 대상: 트레이 "Inkling 정보..." dialog.

UI 요소: 버전 / Electron / Node / OS / 데이터 위치 텍스트 + "데이터 위치 열기" 버튼 + "정보 복사" 버튼.

기존 `showAboutDialog` 의 detail 문자열 그대로 활용 — clipboard.writeText / shell.openPath 호출도 동일.

### 6-4. 제외 항목

- "지금 AI 처리 (실패 N건)" — 이미 inbox FailedBanner 가 surface. 트레이 / 설정 둘 다 제거.
- "Ollama 재확인" 트레이 메뉴 단독 — OllamaBanner (끊김 시) + 설정 페이지 AI 섹션 "지금 재확인" 버튼이 surface. 트레이 단독 메뉴 제거.

---

## 7. 트레이 슬림 디테일

### 7-1. 잔류 4개 (Win / Mac / Linux 동일)

```ts
items.push({ label: '한 줄 적기', click: cb.showCapture });
items.push({ label: '보관한 메모 보기', click: cb.showInbox });
items.push({ type: 'separator' });
items.push({ label: '설정...', click: cb.showSettings });
items.push({ type: 'separator' });
items.push({ label: '종료', click: () => { app.isQuitting = true; app.quit(); } });
```

todayCount tooltip (`Inkling — 오늘 N`) 잔류. F4-C 의 "오늘 N번 잡아둠" 비활성 라벨도 잔류 (정체성 신호).

### 7-2. TrayCallbacks / TrayState 갱신

```ts
export interface TrayCallbacks {
  showInbox: () => void;
  showCapture: () => void;
  showSettings: () => void; // NEW — IPC 'inbox:navigate' 'settings' 송출
}

// 메뉴 영향 state 슬림
export interface TrayState {
  todayCount: number;
}
```

**제거 대상:**

- `runBackup`, `runExport`, `runImport`, `runSync`, `runExportTelemetry` callback (5개) → 설정 페이지 버튼으로 이동
- `runOllamaRecheck`, `runRetryAllFailed`, `runOpenOllamaSettings` callback (3개) → 설정 페이지 또는 banner 로 이동
- `ollamaOk`, `failedCount` state field (2개) → 트레이 메뉴 영향 사라짐 (banner 가 surface)
- `refreshTray({ ollamaOk })`, `refreshTray({ failedCount })` 호출부 (HealthChecker, AiWorker) → 제거. todayCount 만 남음.

v0.2.6 의 Partial<TrayState> 패턴 그대로 활용 — 인터페이스 좁아질 뿐.

### 7-3. 자동 실행 토글 트레이 잔류 X

기존 트레이 안 checkbox (`type: 'checkbox'`) 는 제거. 설정 페이지 "자동 실행" 섹션 토글이 단일 진입점.

이유: 자동 실행 토글은 빈도 낮은 액션 + F12 진단이 같은 자리에 있어야 의미. 트레이 잔류 시 두 surface mismatch 위험.

---

## 8. F14 macOS dock 클릭 fix

[src/main/index.ts:411-413](src/main/index.ts#L411-L413) 수정:

```ts
app.on('activate', () => {
  const win = getInboxWindow();
  if (win && !win.isDestroyed()) {
    if (!win.isVisible()) win.show();
    win.focus();
  } else {
    createInboxWindow();
  }
});
```

second-instance 핸들러 (B4 #46) 와 패턴 일치 — 양쪽 모두 "살아있으면 show, 죽었으면 create".

테스트: BrowserWindow + activate 이벤트 mocking 비용 ↑ → manual dogfood 검증으로 충분 (macOS 빨간 신호등 → dock 클릭 → 즉시 창 등장).

---

## 9. F12 deeper fix — 자동 실행 진단 노출

### 9-1. 정보 모델

```ts
// 신규 IPC: settings:autostart-state
interface AutostartState {
  withArgs: { openAtLogin: boolean; executableWillLaunchAtLogin: boolean };
  noArgs: { openAtLogin: boolean; executableWillLaunchAtLogin: boolean };
  execPath: string; // process.execPath
  registryPath?: string; // Windows only
  registryValue?: string; // Windows only — null 또는 string
}
```

### 9-2. main process 핸들러

```ts
ipcMain.handle('settings:autostart-state', async () => {
  const withArgs = app.getLoginItemSettings({ args: ['--hidden'] });
  const noArgs = app.getLoginItemSettings();
  const state: AutostartState = {
    withArgs: {
      openAtLogin: withArgs.openAtLogin,
      executableWillLaunchAtLogin: withArgs.executableWillLaunchAtLogin
    },
    noArgs: {
      openAtLogin: noArgs.openAtLogin,
      executableWillLaunchAtLogin: noArgs.executableWillLaunchAtLogin
    },
    execPath: process.execPath
  };
  if (process.platform === 'win32') {
    state.registryPath = 'HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Run\\Inkling';
    state.registryValue = await readRegistryValueSilent(state.registryPath);
  }
  return state;
});
```

`readRegistryValueSilent`: `child_process.execFile('reg', ['query', path, '/v', 'Inkling'])` 1회. 실패 시 null 반환 (silent fallback — 사용자에 에러 노출 X).

새 dependency 추가 X (`winreg` 등 X) — built-in `child_process` + Windows `reg.exe` 만 활용.

### 9-3. UI

설정 페이지 "자동 실행" 섹션:

```
[ ] 앱 시작 시 자동으로 실행
   상태: ✅ 등록됨 / ⚠️ 등록 안 됨 / ⚠️ args 미스매치
   ▾ 진단 정보 (펼치기)
     - 표준 조회 (args 명시): openAtLogin=true, willLaunch=true
     - 비교 조회 (args 없이): openAtLogin=false, willLaunch=true   ← mismatch ⚠️
     - 실행 파일 경로: /Applications/Inkling.app/Contents/MacOS/Inkling
     - registry 경로 (Windows): HKCU\...\Run\Inkling
     - registry 값: "C:\Users\...\Inkling.exe" --hidden
   [ 재등록 ] 버튼
```

### 9-4. dogfood 시나리오

1. 토글 ON → 재시작 → 풀려있으면 진단 패널 펼침.
2. withArgs vs noArgs mismatch 보임 → args canonicalization 문제 확인.
3. registry 값 vs execPath 비교 — 다르면 path canonicalization 문제 (NSIS 재설치 시 path 바뀜).
4. "재등록" 버튼 → setLoginItemSettings 재호출 → 다시 재시작 → 효과 측정.

수집된 데이터로 v0.2.8 root cause fix 작성.

---

## 10. 테스트 전략

| 영역 | 단위 | e2e | Manual dogfood |
|---|---|---|---|
| Linux 빌드 (F15) | - | - | AppImage + deb 산출 + Linux VM 실행 + 마이그레이션/캡처/recall 한 사이클 |
| 설정 페이지 라우팅 | zustand store action `setView('settings')` 단위 | (선택) 트레이 "설정..." → IPC → view 전환 e2e | 실제 클릭 흐름 |
| Ollama 섹션 흡수 | 기존 OllamaSettingsModal 단위 + 흡수 후 회귀 | - | 1회 |
| 자동 실행 진단 IPC | autostart-state 핸들러 단위 (mock electron API + child_process) | - | Win 토글 → 재시작 → 진단 패널 mismatch 검출 |
| 트레이 슬림 | tray.ts buildMenu 단위 (4 항목 검증 + 제거된 항목 부재) | - | - |
| F14 dock fix | (mock 비용 ↑) — manual 만 | - | macOS dock 클릭 |
| F12 진단 UI | mismatch 시 ⚠️ 렌더 단위 | - | F12 시나리오 재현 |

**목표**: 단위 426 → 약 450 (+24), e2e 1 유지 또는 +1.

---

## 11. Risk / Known unknowns

| Risk | 발생 시 대응 |
|---|---|
| linux-x64 prebuild 부재 → node-gyp 빌드 실패 | Docker `electronuserland/builder` fallback. 그래도 실패 시 v0.2.7 scope 조정: AppImage 만, deb 는 v0.2.8. [2026-05-07 검증: ✅ prebuild 존재 — electron-v145 (v41.3.0 ABI) 다운로드 성공, better_sqlite3.node 파일 생성] |
| ELECTRON_RUN_AS_NODE 함정 (메모) 가 Linux 환경에서 재현 | smoke test launch env 에서 strip — 기존 e2e 의 strip 패턴 그대로 |
| AppImage 가 모던 GNOME 에서 트레이 표시 안 됨 | 의도적 — 그래서 dock/launcher → inbox → 설정 페이지 흐름이 안전망. F14 fix 가 이 흐름의 핵심. |
| 설정 페이지 라우팅이 inbox 의 keyboard shortcut / hotkey 와 충돌 | view='settings' 시 inbox-only shortcut 비활성. zustand state 분기. |
| 자동 실행 진단 패널이 Mac/Linux 에선 의미 없는 정보 노출 | 플랫폼 분기 — Mac/Linux 는 registry 행 숨김 + executableWillLaunchAtLogin 만 표시 |
| 트레이 callback 8개 제거 시 import 그래프에서 dead code 잔존 | 제거 후 typecheck + grep 으로 검증 |

---

### v0.2.7 Linux 빌드 1차 시도 결과 (2026-05-07, Windows 호스트)

`npm run dist:linux` 실행 — Windows 11 호스트.

**진행 단계:**

- ✅ predist:linux (electron rebuild + electron-vite build) — 성공
- ✅ electron-builder linux x64 패키징 prep — 성공
- ✅ electron-v41.3.0-linux-x64.zip 다운로드 (117 MB)
- ✅ `dist/linux-unpacked/` 스테이징 생성 — 322 MB (electron + native modules + app)
- ✅ appimage-12.0.1.7z 다운로드 (mksquashfs 등 Linux 유저랜드 도구 캐시)
- ⚠️ **AppImage 패키징 실패** — `mksquashfs` 실행 불가
- ⏭️ **deb 패키징은 시도조차 못함** (AppImage 실패로 빌드 중단)

**핵심 에러:**

```text
⨯ cannot execute  cause=exec: "C:\Users\...\electron-builder\Cache\appimage\appimage-12.0.1\linux-x64\mksquashfs": file does not exist
⨯ failed to build AppImage  error=...app-builder.exe process failed ERR_ELECTRON_BUILDER_CANNOT_EXECUTE
Exit code: 2
```

**진단:** `mksquashfs` 파일은 캐시에 존재하나 (270 KB Linux ELF 바이너리), Windows 가 ELF 를 실행 불가 → electron-builder 가 "file does not exist" 로 보고. AppImage cross-build from Windows 는 **근본적으로 불가능** (WSL/Docker/Linux/Mac 호스트 필요).

**결론:**

- AppImage: ⚠️ 실패 (Windows 호스트는 mksquashfs ELF 실행 불가 — 환경 제약)
- deb: ⚠️ 미시도 (`dpkg-deb` + `fakeroot` 부재 추정. AppImage 실패로 도달 못함)

**Fallback 결정:** Mac (사용자 업무 호스트) 또는 Linux/WSL/Docker 핸드오프 필수. Windows 단독으로는 v0.2.7 Linux 산출물 생성 불가. plan Task 3 은 "시도 + 결과 기록" 이 핵심이었고, **macOS 후속 시도가 본 빌드** — Windows 시도는 환경 한계 확인용.

**권장 후속:**

1. 사용자 macOS 업무 호스트에서 동일 명령 (`npm run dist:linux`) 재시도. brew 로 `dpkg` + `fakeroot` 사전 설치 (`brew install dpkg`). AppImage 는 macOS 에서 정상 cross-build 가능 (mksquashfs Mach-O 바이너리 caching).
2. macOS 에서도 deb 가 실패할 경우 — v0.2.7 scope 를 **AppImage only** 로 축소, deb 는 v0.2.8 또는 Docker `electronuserland/builder` 환경으로 이동. package.json `linux.target` 에서 deb 제거하거나 별도 task 로 분리.
3. 향후 자동화: GitHub Actions / Gitea Actions 에서 ubuntu-latest runner 로 Linux build 자동화 (현 수동 cross-build 환경 의존성 제거).

---

## 12. v0.2.7 후

**잔여 backlog (24건)** — `docs/superpowers/v024-backlog.md`:

- v0.2.6 final reviewer minor cleanup 6건 — kstDate 의미 정정 / NoteRepository.test.ts as any / store.ts trashCount race 등
- telemetry data-dependent 14건 — VOCAB_TOP_N 튜닝, recallBanner 임계값 등 — v0.2.8 후보 (v0.2.7 dogfood soak ≥1주 후 telemetry export 누적)
- v0.2.7 본 cut 안 신규 발견 — F12 root cause 가 진단 데이터 누적 후 결정될 가능성

**v0.2.8 트리거**: v0.2.7 release 후 dogfood ≥1주 soak + telemetry export + F12 진단 데이터 → v0.2.8 brainstorm.
