Merge pull request 'chore/post-slice-followups' (#1) from chore/post-slice-followups into main
Reviewed-on: #1
This commit was merged in pull request #1.
This commit is contained in:
174
README.md
Normal file
174
README.md
Normal file
@@ -0,0 +1,174 @@
|
||||
# Inkling
|
||||
|
||||
기록 습관이 없는 사람도 3초 안에 던지고, 로컬 AI가 정리하고, 나중에 업무 자산으로 회수할 수 있게 해주는 데스크톱 메모 앱이다. "메모 = 잊기 전에 머리에서 꺼내는 1회 행동"으로 행동 기준을 낮추고, 정리는 Ollama가 맡는다.
|
||||
|
||||
이 저장소는 슬라이스 v0.4 단계다 — 가장 얇은 종단 경로(Quick Capture → SQLite → 로컬 Ollama → Inbox)만 동작한다. 음성·검색·내보내기·온보딩·캘린더 트리거는 후속 spec의 범위.
|
||||
|
||||
---
|
||||
|
||||
## 상태
|
||||
|
||||
- **버전:** Slice v0.4
|
||||
- **플랫폼:** Windows(dogfood 우선) + macOS(빌드 통과 수준)
|
||||
- **테스트:** 단위 52/52, e2e smoke 1/1, typecheck/build 통과
|
||||
- **종료 조건:** 본인 2주 dogfood 완주 + AI 결과 7/10 수용 + 크래시 0회 (spec §1.3)
|
||||
|
||||
---
|
||||
|
||||
## 사전 요구
|
||||
|
||||
| 항목 | 버전/메모 |
|
||||
|------|----------|
|
||||
| Node | 24.15.0 (`.nvmrc`) — Volta 권장, nvm-windows도 가능 |
|
||||
| Visual Studio Build Tools | 2022 + "Desktop development with C++" 워크로드 (better-sqlite3 빌드용) |
|
||||
| Ollama | LAN 서버에서 실행, `gemma4:e4b` 모델 pull (8.0B Q4_K_M, 효율 변형) |
|
||||
| Git for Windows | 최신 |
|
||||
|
||||
LAN Ollama 엔드포인트는 환경변수 `INKLING_OLLAMA_ENDPOINT` 로 주입한다.
|
||||
|
||||
```
|
||||
INKLING_OLLAMA_ENDPOINT=http://192.168.0.47:11434
|
||||
```
|
||||
|
||||
미설정 시 `http://localhost:11434` 로 폴백.
|
||||
|
||||
---
|
||||
|
||||
## 시작하기
|
||||
|
||||
```bash
|
||||
# 1. 의존성 설치
|
||||
npm install
|
||||
|
||||
# 2. 개발 모드 (electron-vite dev)
|
||||
npm run dev
|
||||
|
||||
# 3. 프로덕션 빌드
|
||||
npm run build
|
||||
|
||||
# 4. 빌드 결과 실행
|
||||
npm start
|
||||
```
|
||||
|
||||
전역 단축키: `Ctrl+Shift+J` (Windows) / `Cmd+Shift+J` (macOS).
|
||||
Quick Capture 창이 화면 중앙 상단에 뜬다. 한 줄 던지고 `Ctrl+Enter` 로 저장.
|
||||
|
||||
---
|
||||
|
||||
## 테스트
|
||||
|
||||
```bash
|
||||
# 단위 테스트 (vitest)
|
||||
npm test
|
||||
|
||||
# 통합 테스트 (실제 Ollama 호출, 게이트됨)
|
||||
npm run test:integration
|
||||
|
||||
# e2e smoke 테스트 (Playwright + Electron)
|
||||
npm run test:e2e
|
||||
|
||||
# 타입 체크
|
||||
npm run typecheck
|
||||
```
|
||||
|
||||
### Native ABI 함정
|
||||
|
||||
`better-sqlite3` 는 두 가지 런타임에서 호출된다 — vitest(Node ABI)와 Electron(ABI 145). 한 번에 한 ABI 로만 빌드되므로 npm 스크립트가 자동으로 prebuild-install 을 돌린다.
|
||||
|
||||
- `pretest` / `pretest:integration` → `rebuild:node`
|
||||
- `pretest:e2e` / `prestart` / `predev` → `rebuild:electron`
|
||||
|
||||
새 native dep 을 추가하거나 Electron 메이저 버전을 올리면 `package.json` 의 `rebuild:*` 스크립트도 갱신해야 한다.
|
||||
|
||||
### ELECTRON_RUN_AS_NODE 함정
|
||||
|
||||
이 환경변수가 부모 셸에 살아있으면 Electron main-process 모듈 hook 이 비활성화돼 `require('electron')` 이 path string 만 반환한다. e2e 스펙에서 launch env 단계에 명시적으로 strip 한다. 새 main-launch 스크립트를 작성할 때도 동일 strip 필요.
|
||||
|
||||
---
|
||||
|
||||
## 아키텍처
|
||||
|
||||
```
|
||||
src/
|
||||
main/ Electron main process
|
||||
db/ SQLite + 마이그레이션
|
||||
repository/ NoteRepository (CRUD + edited-flag overwrite guard)
|
||||
services/ Capture, Continuity, Hotkey, Intent, Notification, MediaStore, MediaGc, HealthChecker
|
||||
ai/ InferenceProvider 인터페이스 + LocalOllamaProvider + AiWorker (3-attempt backoff)
|
||||
ipc/ contextBridge용 IPC 핸들러
|
||||
windows/ Inbox + QuickCapture BrowserWindow 팩토리
|
||||
tray.ts Tray 아이콘 + 메뉴
|
||||
paths.ts 프로필 디렉터리 해석
|
||||
logger.ts electron-log 래퍼 (raw_text 절대 미기록)
|
||||
preload/ contextBridge로 typed API expose
|
||||
renderer/
|
||||
inbox/ React 19 + Zustand 스토어
|
||||
quickcapture/ React 19, frameless 640x280
|
||||
shared/
|
||||
types.ts IPC 계약 타입
|
||||
intentPrompts.ts 의미 한 줄 회전 카피 4종
|
||||
tests/
|
||||
unit/ vitest, 52 cases
|
||||
integration/ Ollama golden test (게이트됨)
|
||||
e2e/ Playwright smoke
|
||||
docs/
|
||||
superpowers/specs/ 설계 문서
|
||||
superpowers/plans/ 구현 계획
|
||||
superpowers/strategy/ 심리학·dogfooding 전략
|
||||
handoff/ 세션 간 핸드오프
|
||||
inkling.md 원본 제품 브리프 v1.4
|
||||
```
|
||||
|
||||
핵심 흐름은 Capture → Clarify → Capitalize 3단계 (Strategy §2). 슬라이스에서는 Capture + Clarify 만 다루고 Capitalize(Confluence 내보내기·주간 회고) 는 후속.
|
||||
|
||||
---
|
||||
|
||||
## Load-bearing 불변 (재논의 금지)
|
||||
|
||||
이 규칙들은 spec, 테스트, 코드 곳곳에 박혀 있다. 변경하려면 spec v0.4 §3.3 부터 다시 검토.
|
||||
|
||||
1. **`raw_text` 는 불변.** update method 없음. 추가 금지.
|
||||
2. **AI 재실행은 user-edited 필드를 덮어쓰지 않는다.** `*_edited_by_user` 컬럼 + CASE WHEN 가드.
|
||||
3. **IntentBanner 는 노트당 정확히 1회.** `intent_prompted_at IS NULL` 이 게이트. setIntent · dismissIntent 둘 다 게이트 닫음.
|
||||
4. **로그에 본문 금지.** `raw_text` / `ai_title` / `ai_summary` / `user_intent` 내용 절대 미기록. ID, 길이, 해시 prefix 만.
|
||||
5. **"실패", "끊김", "연속 실패" 단어는 모든 UI 문자열에서 금지.** 회복 친화 톤이 강제.
|
||||
6. **네이티브 알림은 submit 을 절대 막지 않는다.** 권한 거부 = silent fallback. 캡처는 계속 동작.
|
||||
7. **버전은 정확 pin (`^` 금지).** `package.json` 갱신 시 spec §7.2 와 동시 업데이트.
|
||||
|
||||
---
|
||||
|
||||
## Quick Capture 제약
|
||||
|
||||
- 단일 입력창, 텍스트 + 클립보드 이미지만.
|
||||
- 제목·태그·폴더 선택 강요 안 함 (Strategy §2.1).
|
||||
- 저장 직후 OS 네이티브 토스트 1개 (회전 카피 4종).
|
||||
- AI 처리는 비동기 — 저장은 즉시, 정리는 백그라운드.
|
||||
|
||||
## Inbox 제약
|
||||
|
||||
- 날짜 내림차순 리스트, 같은 ms 동기화 시 `id DESC` 보조 정렬.
|
||||
- AI 필드(제목·요약) 는 인라인 편집 가능. 편집 시 `*_edited_by_user=1` 셋 → 이후 AI 재실행은 해당 필드 보존.
|
||||
- 원문은 read-only.
|
||||
- AI 가 생성한 필드 옆에 회색 "AI" 라벨. 사용자 편집 시 라벨 사라짐 ("내 메모" 정체성 표시).
|
||||
- "의미 한 줄" 배너는 AI done 직후 1회만, skip 시 해당 카드에선 다시 안 뜸.
|
||||
- Weekly Continuity 배지: 7건/주 (KST 월~일). 0/7 도 "이번 주 한 줄이면 시작입니다".
|
||||
|
||||
---
|
||||
|
||||
## 문서
|
||||
|
||||
| 문서 | 경로 | 용도 |
|
||||
|------|------|------|
|
||||
| 제품 브리프 | `inkling.md` | 원본 v1.4, 전체 비전 |
|
||||
| 심리학 전략 | `docs/superpowers/strategy/strategy.md` | 행동 변화 모델, 회복 친화 스트릭 |
|
||||
| Dogfooding 전략 | `docs/superpowers/strategy/dogfood-strategy.md` | 2주 자기 dogfood 운영안 |
|
||||
| 슬라이스 설계 | `docs/superpowers/specs/2026-04-24-inkling-vertical-slice-design.md` | v0.4 spec, 부하 전달 결정의 출처 |
|
||||
| Dogfood 피드백 | `docs/superpowers/specs/2026-04-25-dogfood-feedback.md` | 슬라이스 dogfood 중 발견된 본인 피드백 수집 (living document) |
|
||||
| 구현 계획 | `docs/superpowers/plans/2026-04-24-inkling-vertical-slice.md` | 33-task TDD 플랜 |
|
||||
| 핸드오프 | `docs/handoff/2026-04-25-linux-to-windows.md` | Linux→Windows 전환 메모 |
|
||||
|
||||
---
|
||||
|
||||
## 라이선스
|
||||
|
||||
비공개. 본인 dogfood + 10인 사내 베타까지의 스코프.
|
||||
164
docs/superpowers/specs/2026-04-25-dogfood-feedback.md
Normal file
164
docs/superpowers/specs/2026-04-25-dogfood-feedback.md
Normal file
@@ -0,0 +1,164 @@
|
||||
# Dogfood 피드백 수집
|
||||
|
||||
**작성일:** 2026-04-25 (open)
|
||||
**저자:** 김태현 (dlsrks0734@gmail.com)
|
||||
**문서 성격:** 슬라이스 v0.4 dogfood 중 발견된 본인 피드백을 수집·정제하는 living document. 각 항목은 후속 spec 으로 승격될 후보다. 정식 spec 이 분기될 만큼 성숙하면 별도 파일로 추출하고 여기엔 링크만 남긴다.
|
||||
|
||||
**선행 문서:**
|
||||
- `docs/superpowers/specs/2026-04-24-inkling-vertical-slice-design.md` v0.4 (슬라이스 본문)
|
||||
- `docs/superpowers/strategy/strategy.md` (심리학 전략)
|
||||
- `docs/superpowers/strategy/dogfood-strategy.md` (dogfood 운영안 — 본 문서와 의존 없음)
|
||||
|
||||
---
|
||||
|
||||
## 0. 사용 규칙
|
||||
|
||||
- 새 피드백은 다음 번호 (`F<N>`) 로 추가. 절대 기존 번호 재사용 금지 (외부 링크 안전성).
|
||||
- 각 항목 상단에 상태 라벨 한 줄:
|
||||
- 🌱 **raw** — 막 적은 관찰. 가공 안 됨.
|
||||
- 🔬 **drafting** — 범위·가설·결정 대기 항목 분석 중.
|
||||
- 📝 **ready-for-spec** — 정식 spec 으로 승격 가능. 다음 단계는 별도 파일 추출.
|
||||
- 🚀 **promoted** — 별도 spec 파일로 분기 완료. 본 문서엔 요약 + 링크만.
|
||||
- ❌ **rejected** — 가설 미달 또는 우선순위 외, 닫음.
|
||||
- 각 항목의 표준 슬롯: **관찰 / 제안 방향 / 결정 대기 / 가설·측정 / 범위 / 영향**. 슬롯이 비어 있으면 비워두고 채워질 때 갱신.
|
||||
|
||||
---
|
||||
|
||||
## F1. Due Date 추출 (🔬 drafting)
|
||||
|
||||
**발견:** 2026-04-25 dogfood 시작 직전 사고 실험.
|
||||
|
||||
### 관찰
|
||||
|
||||
캡처된 노트 본문에 `오늘`, `내일`, `이번 주`, `다음 주`, `다음 달`, `N일 뒤`, `N월 N일`, `금요일까지` 같은 시간 표현이 자주 들어간다. 현재 슬라이스는 이걸 그냥 평문 본문에 두고 끝. 사용자는 노트가 "언제까지 해야 하는 일" 인지 다시 본문을 읽어 파악해야 한다.
|
||||
|
||||
### 제안 방향
|
||||
|
||||
**하이브리드 — 규칙 파서 1차, AI 위임 2차.**
|
||||
|
||||
```
|
||||
입력 텍스트
|
||||
↓
|
||||
규칙 파서 (정규식 + KST 변환)
|
||||
↓
|
||||
매칭 있음? ──→ ISO YYYY-MM-DD + confidence: high
|
||||
│
|
||||
└─ 시간 후보 어휘 감지 + 매칭 없음 ──→ AI 프롬프트에 due_date 필드 추가
|
||||
↓
|
||||
AI 응답에 ISO 있음? ──→ confidence: medium
|
||||
AI 응답 비었음? ──→ due_date null
|
||||
```
|
||||
|
||||
규칙 파서는 결정론적·테스트 쉬움. AI 위임은 같은 generate 응답에 필드만 추가 — 별도 호출 없음. UI 라벨은 작은 회색 `📅 YYYY-MM-DD`, AI 라벨 정책과 동일하게 사용자 편집 시 진해지고 라벨 사라짐 (Strategy §7-원칙4 "AI 확신도 낮게").
|
||||
|
||||
### 결정 대기
|
||||
|
||||
1. 시간 표현이 들어간 노트 비율 — 누적 50건 표본 분류 (가설 H1)
|
||||
2. "오늘 PR 리뷰" 같은 노트의 due 가 정말 오늘인가, 단순 맥락어인가? false positive 비율
|
||||
3. due_date 가 있는 노트의 재방문 트리거 — Inbox 정렬 / 별도 뷰 / 알림 중 어디
|
||||
4. NoteCard 의 라벨 슬롯 위치 — 제목 옆, 태그 옆, 별도 줄
|
||||
5. 만료된 노트 처리 — 자동 done ❌, 자동 삭제 ❌, 시각적 표시만? 별도 필터?
|
||||
6. 음력·"월말"·"주말 안에" 등 모호 표현은 AI 위임만으로 충분한가
|
||||
|
||||
### 가설·측정
|
||||
|
||||
| # | 가설 | 측정 |
|
||||
|---|------|------|
|
||||
| H1 | 누적 캡처 노트 중 시간 표현 포함 비율 ≥ 30% | 표본 50건 본인 라벨링 |
|
||||
| H2 | 규칙 파서가 일상 한국어 시간 표현의 ≥ 80% 정확 변환 | 골든 픽스처 50건 + 단위 테스트 |
|
||||
| H3 | due_date 후보 수락률 ≥ 60% | 정식 출시 후 측정 |
|
||||
|
||||
H1 이 미달이면 본 항목 ❌ rejected.
|
||||
|
||||
### 범위
|
||||
|
||||
- **In:** `오늘`/`내일`/`모레`, `이번/다음 주`, `다음 달`, `N일·주·개월 뒤`, `N월 N일`, `MM/DD`, `YYYY-MM-DD`, KST 자정 기준
|
||||
- **Out:** 시각 단위 (`오후 3시`), 음력·절기, 반복 일정 (`매주 월요일`), 외부 캘린더 연동
|
||||
|
||||
### 영향
|
||||
|
||||
- **Schema:** migration v2 — `notes.due_date TEXT`, `notes.due_date_edited_by_user INTEGER NOT NULL DEFAULT 0`
|
||||
- **NoteRepository:** `updateAiResult` 시그니처에 `dueDate?: string | null`, edited-flag CASE WHEN 가드. 신규 `setDueDate(noteId, date)`
|
||||
- **AI:** zod 스키마에 `due_date: z.string().regex(/^\d{4}-\d{2}-\d{2}$/).nullable().optional()`, 프롬프트 끝에 "추출 가능하면 ISO, 아니면 null" + `{{TODAY_KST}}` 주입
|
||||
- **Renderer:** NoteCard 라벨 슬롯, EditableField 재사용, 만료 시 회색 + 취소선 (단어는 "실패"/"지각" 금지 — slice §1.1 카피 정책)
|
||||
- **Lib:** 외부 의존성 0 권장. 자체 정규식 + Intl.DateTimeFormat. ContinuityService 의 KST 헬퍼 재사용
|
||||
|
||||
### 골든 픽스처 후보
|
||||
|
||||
```
|
||||
"오늘 PR 리뷰" → null 또는 today (양가)
|
||||
"내일 회의 준비" → tomorrow
|
||||
"다음 주 월요일까지 슬라이드" → next-mon
|
||||
"3일 뒤 데모" → today+3
|
||||
"5월 1일 휴가 신청" → 2026-05-01
|
||||
"이번 주 안에 리팩터링" → 이번 주 일요일
|
||||
"월말 마감" → 해당 월 말일 (AI 위임)
|
||||
"주말까지" → 이번 주 일요일 (AI 위임)
|
||||
"퇴근 전" → today (시각 단위 fallback)
|
||||
```
|
||||
|
||||
각 케이스의 confidence + 가설 행동(수락/편집/무시) 라벨링이 H2/H3 측정의 근거.
|
||||
|
||||
---
|
||||
|
||||
## F2. 태그 클릭 = 즉시 삭제 + undo 부재 (🌱 raw)
|
||||
|
||||
**발견:** 2026-04-25 dogfood 중. 슬라이스 v0.4 동작.
|
||||
|
||||
### 관찰
|
||||
|
||||
`NoteCard.tsx:110` 에서 태그 칩의 `onClick` 이 `removeTag(t.name)` 으로 묶여 있다. 즉시 DB 에 반영되고, 확인 다이얼로그도 undo 도 없다. hover title 은 "클릭으로 제거" 로 의도된 동작이지만, 사용자는 태그 UI 의 일반적 관행 (클릭 = 동일 태그 노트 필터링) 을 기대했다.
|
||||
|
||||
문제 두 갈래:
|
||||
|
||||
1. **기능 부재:** "이 태그를 가진 노트만 보기" 가 슬라이스에 없음. 사용자 멘탈 모델과 어긋남.
|
||||
2. **데이터 손실 마찰:** 평범한 클릭 한 번으로 AI 가 만든 태그가 사라지는데 되돌릴 수 없음. 슬라이스 §1.1 의 "AI 라벨 → 사용자 편집으로 전환" 정체성 흐름과도 충돌 — 삭제는 편집이 아니라 파괴.
|
||||
|
||||
### 제안 방향
|
||||
|
||||
세 동작을 분리한다.
|
||||
|
||||
| 동작 | 트리거 | 결과 |
|
||||
|------|--------|------|
|
||||
| **필터** | 짧은 클릭 | Inbox 가 동일 태그 노트만 표시 (헤더에 "필터: #tag · 해제" 칩) |
|
||||
| **편집** | 칩 더블클릭 또는 칩 우측 작은 ✕ 아이콘 | 태그 이름 인라인 편집 또는 단일 태그 제거 |
|
||||
| **재추가 (undo)** | 방금 제거한 직후 토스트 "태그 'foo' 를 제거했습니다 · 되돌리기" 5초 노출 | 클릭 시 즉시 복원 |
|
||||
|
||||
권장 1차 패치 (작음): 클릭 = 필터로 변경 + 칩에 ✕ 아이콘 추가 (✕ 클릭 시 confirm 또는 5초 undo 토스트). 필터는 zustand 상태에 `tagFilter: string | null` 추가 + Inbox 의 `notes.filter` 단계 한 줄.
|
||||
|
||||
권장 2차 (별 spec): 다중 태그 필터 (AND/OR), 태그 편집 (rename / merge), 태그 단위 일괄 작업.
|
||||
|
||||
### 결정 대기
|
||||
|
||||
1. 클릭 = 필터 라는 가정이 dogfood 본인에게도 성립하는가? — 본인 예상 동작 한 번 더 셀프 체크
|
||||
2. AI 태그 vs 사용자 추가 태그 클릭 동작이 같아야 하는가, 달라야 하는가?
|
||||
3. undo 토스트의 dismissal 정책 — 5초 자동 사라짐 vs 다음 작업까지 유지
|
||||
4. 필터링 시 Continuity 배지·Pending 배너의 카운트는 전체 기준인가, 필터된 부분 기준인가?
|
||||
5. 태그 단일 제거 외에 "이 태그가 붙은 모든 노트에서 제거" 같은 일괄 작업도 묶을지 — 별 spec 으로 분리 권장
|
||||
|
||||
### 가설·측정
|
||||
|
||||
| # | 가설 | 측정 |
|
||||
|---|------|------|
|
||||
| H1 | 슬라이스의 현재 "클릭 = 삭제" 가 본인 dogfood 에서 ≥ 1회 실수 유발 | 일일 로그의 마찰 사건 |
|
||||
| H2 | 클릭 = 필터로 바꾸면 본인이 태그를 통해 과거 노트를 재방문하는 빈도 ≥ 주 1회 | 사용 로그 |
|
||||
| H3 | undo 토스트가 있으면 의도된 태그 제거의 인지 부담 감소 | 정성 평가 |
|
||||
|
||||
### 범위
|
||||
|
||||
- **In:** 단일 태그 클릭 동작 변경, 단일 태그 제거 시 undo, Inbox 의 단일 태그 필터
|
||||
- **Out:** 다중 태그 필터, 태그 rename / merge, 태그 단위 일괄 작업, 태그 자동완성
|
||||
|
||||
### 영향
|
||||
|
||||
- **Schema:** 없음 (이 패치 자체는 DB 변경 없음. 일괄 작업이 들어오면 별 spec)
|
||||
- **Renderer:** `NoteCard.tsx` 의 칩 `onClick` 변경, 칩 우측 ✕ 아이콘 추가, Inbox `App.tsx` 에 필터 상태 + 헤더 칩, undo 토스트 컴포넌트 (RecoveryToast 패턴 재사용 가능)
|
||||
- **Store:** zustand `tagFilter` 상태 + `setTagFilter`, `notes` 셀렉터에 필터 적용
|
||||
- **API:** 변경 없음 (필터는 클라이언트 사이드만)
|
||||
- **카피:** 슬라이스 §1.1 "실패"/"끊김"/"연속 실패" 금지 정책 그대로. undo 토스트 문구는 "되돌리기" / "방금 제거" 같은 중립 표현
|
||||
|
||||
---
|
||||
|
||||
## (다음 항목 자리)
|
||||
|
||||
새 피드백 추가 시 `## F3. 짧은 제목 (🌱 raw)` 헤더로 시작. 표준 슬롯 6개 채우거나 비워둔 채 시작 가능.
|
||||
259
docs/superpowers/strategy/dogfood-strategy.md
Normal file
259
docs/superpowers/strategy/dogfood-strategy.md
Normal file
@@ -0,0 +1,259 @@
|
||||
# Inkling — Dogfooding 전략
|
||||
|
||||
**작성일:** 2026-04-25
|
||||
**대상:** 김태현 (저자 본인) — 슬라이스 v0.4 dogfood 단계
|
||||
**스펙 의존:** `2026-04-24-inkling-vertical-slice-design.md` v0.4 §1.3 (종료 조건)
|
||||
**전략 의존:** `strategy.md` §1·§2·§5 (행동 정의, Capture→Clarify→Capitalize, 회복 친화 스트릭)
|
||||
|
||||
---
|
||||
|
||||
## 0. 목적
|
||||
|
||||
이 문서는 슬라이스 v0.4 의 종료 조건을 충족시키는 **2주 자기 dogfood 운영안**이다. 외부 베타 모집 전에 본인이 먼저 매일 쓰면서 세 가설을 검증한다.
|
||||
|
||||
| # | 가설 | 출처 | 측정 방법 |
|
||||
|---|------|------|----------|
|
||||
| H1 | 전역 단축키 → 입력 → 저장이 실제로 3초 내 마찰 없이 이뤄진다 | spec §0 | 일일 로그의 마찰 사건 수 + 주관 평점 |
|
||||
| H2 | LAN Ollama(`gemma4:e4b`)가 한국어 제목·3줄 요약·태그를 실용 가능 품질로 생성한다 | spec §0 | 10건 중 7건 이상 사용자 수용 (spec §1.3) |
|
||||
| H3 | Capture→Clarify→Capitalize 행동 루프와 회복 친화 스트릭이 "메모를 던지고 싶은 충동"을 만든다 | strategy §2·§5 | 7일 이내 3일 연속 기록(Aha) + 2주 dogfood 완주 |
|
||||
|
||||
**가설 검증이 목적이지 기능 추가가 목적이 아니다.** dogfood 중 떠오르는 아이디어는 별도 백로그로 적고 슬라이스 코드에 손대지 않는다 (예외: 크래시·데이터 손실급 버그).
|
||||
|
||||
---
|
||||
|
||||
## 1. 시작 체크리스트 (Day 0)
|
||||
|
||||
dogfood 첫 날 시작 전, 환경을 한 번에 정렬한다.
|
||||
|
||||
### 1.1 환경
|
||||
|
||||
- [ ] `.nvmrc` 의 Node 버전 (24.15.0) 활성화
|
||||
- [ ] `INKLING_OLLAMA_ENDPOINT` 가 LAN Ollama (`http://192.168.0.47:11434`) 를 가리킴
|
||||
- [ ] LAN Ollama 에 `gemma4:e4b` 가 pull 된 상태 확인 (`curl http://192.168.0.47:11434/api/tags`)
|
||||
- [ ] `npm run build` 후 `npm start` 로 정식 실행 (dev 모드 아님 — dogfood 는 프로덕션 빌드)
|
||||
- [ ] 윈도우 트레이에 Inkling 아이콘 떠 있음
|
||||
- [ ] `Ctrl+Shift+J` 가 다른 앱(Chrome, Edge DevTools 등)에 충돌 없이 잡힘
|
||||
- [ ] OS 알림 권한 허용 — 첫 토스트 후 시스템 트레이에서 확인
|
||||
- [ ] `%APPDATA%\Inkling\default\inkling.db` 가 새로 생성됨 (이전 dogfood 데이터 분리하려면 이 파일을 백업·삭제)
|
||||
|
||||
### 1.2 dogfood 로그 파일 준비
|
||||
|
||||
`docs/dogfood/2026-04-25-week1.md` 를 새로 만든다. 이 문서는 매일 한 줄 이상 채우고 dogfood 종료 시 회고 자료가 된다. 슬라이스 종료 후 commit (Inkling 자체에 메모하지 않는 이유: 본 dogfood 가 깨지면 메모가 묶여 사라지므로).
|
||||
|
||||
템플릿 — §6 참조.
|
||||
|
||||
### 1.3 사전 마음가짐
|
||||
|
||||
- "메모를 잘 써야 한다" 가 아니라 "지금 머릿속에서 꺼낸다" (strategy §1).
|
||||
- 오늘 0건이라도 죄책감 금지. 0/7 도 "이번 주 한 줄이면 시작입니다" 라는 슬라이스 카피 자체가 본인을 향한다.
|
||||
- 기록의 품질은 AI 와 미래의 자신이 책임진다. 사용자의 책임은 캡처뿐.
|
||||
|
||||
---
|
||||
|
||||
## 2. 일일 운영안
|
||||
|
||||
### 2.1 트리거 (실행 의도, strategy §3)
|
||||
|
||||
매번 "메모할까 말까" 를 판단하면 마찰이 누적된다. 다음 if-then 두 개를 본인 캘린더·습관에 박는다.
|
||||
|
||||
- **회의 종료 후 → `Ctrl+Shift+J` 1회.** 결정 / 액션 / 리스크 중 하나만.
|
||||
- **퇴근 10분 전 → `Ctrl+Shift+J` 1회.** "오늘 다시 찾고 싶을 것" 한 줄.
|
||||
|
||||
이 두 트리거가 dogfood 1주차의 최저 행동 기준이다. 추가 캡처는 보너스.
|
||||
|
||||
### 2.2 캡처 규칙
|
||||
|
||||
- 한 줄로 충분. 20자 이하도 OK.
|
||||
- 제목 고민 금지 — AI 가 생성.
|
||||
- 클립보드 이미지가 있으면 paste, 없으면 텍스트만.
|
||||
- 저장 후 토스트 ("이 생각은 이제 Inkling이 들고 있습니다." 등) 가 떴는지 확인. 안 떴으면 §5.1 마찰 로그 항목.
|
||||
|
||||
### 2.3 Inbox 점검 (하루 1회, 5분)
|
||||
|
||||
- 저녁에 Inbox 열기.
|
||||
- 오늘 캡처한 노트의 AI 제목·요약·태그 검토. 사용자 수용 가능 = 손대지 않음. 부족 = 인라인 편집.
|
||||
- "의미 한 줄" 배너가 뜬 노트 중 1~2개만 입력. 나머지는 skip 가능 (배너는 1회만 노출되므로 부담 없음).
|
||||
- AI 가 `pending` 으로 멈춰 있으면 OllamaBanner 가 뜸 → LAN Ollama 상태 확인.
|
||||
|
||||
### 2.4 주간 회고 (일요일 KST 22:00, 30분)
|
||||
|
||||
- Weekly Continuity 배지 캡처: 이번 주 N/7 + 연속 K주.
|
||||
- 일일 dogfood 로그를 빠르게 읽고 §3 의 지표를 채움.
|
||||
- 다음 주 트리거 조정 — 너무 빈번이면 1개로 줄이고, 너무 드물면 1개 추가.
|
||||
|
||||
---
|
||||
|
||||
## 3. 측정 지표
|
||||
|
||||
매일 / 매주 갱신할 숫자. 자동 추적 인프라는 슬라이스 범위 밖이라 수기로 적는다. 단, Inbox 의 노트 수와 Weekly Continuity 배지는 앱이 직접 보여준다.
|
||||
|
||||
### 3.1 행동 지표 (매일 1줄)
|
||||
|
||||
| 지표 | 의미 | 출처 |
|
||||
|------|------|------|
|
||||
| 일일 캡처 수 | 오늘 던진 노트 수 | Inbox |
|
||||
| Time to Capture (체감) | 단축키 → 저장까지 체감 초 (>3초면 마찰 사건) | 본인 추정 |
|
||||
| 트리거 응답률 | 회의 후·퇴근 전 트리거에 실제로 캡처한 횟수 / 트리거 발생 횟수 | 본인 카운트 |
|
||||
| AI 수용률 | AI 생성 필드를 그대로 둔 노트 수 / 전체 캡처 수 | Inbox 의 "AI" 라벨 잔존율 |
|
||||
| `pending` 스택 | 24시간 이상 `pending` 상태로 남은 노트 수 | Inbox PendingBanner |
|
||||
|
||||
### 3.2 주간 지표 (일요일 갱신)
|
||||
|
||||
| 지표 | 합격 기준 (slice §1.3 기반) |
|
||||
|------|--------------------------|
|
||||
| Weekly Continuity | ≥ 7/7 |
|
||||
| Aha Moment | 설치 후 7일 이내 3일 연속 기록 — 1주차 안에 충족 (inkling.md §7.1) |
|
||||
| AI 수용률 (주 평균) | ≥ 70% |
|
||||
| 앱 크래시 | 0 회 |
|
||||
| 데이터 손실 | 0 건 (재시작 후 노트 사라짐 0) |
|
||||
|
||||
### 3.3 심리 지표 (주말 5문항, 1~5점)
|
||||
|
||||
strategy §10 의 정성 설문을 슬라이스 규모로 축약. 매주 일요일에 자기 평가.
|
||||
|
||||
1. Inkling 덕분에 업무 기억 부담이 줄었다 _(인지적 오프로딩)_
|
||||
2. 나는 업무 중 중요한 것을 놓칠 가능성이 줄었다 _(자기효능감)_
|
||||
3. 기록이 귀찮은 일이 아니라 도움이 되는 일로 느껴진다 _(내재화)_
|
||||
4. 나는 이제 "기록하는 사람" 에 가까워졌다 _(정체성 변화)_
|
||||
5. 0/7 → N/7 회복 카피가 죄책감 대신 안도감을 줬다 _(strategy §5 회복 친화 검증)_
|
||||
|
||||
평균이 3.0 미만이면 슬라이스 가설 H3 (행동 루프 흡인력) 가 약하다는 신호 — 다음 spec 우선순위에 반영.
|
||||
|
||||
---
|
||||
|
||||
## 4. 운영 모드별 시나리오
|
||||
|
||||
### 4.1 LAN Ollama 다운
|
||||
|
||||
- OllamaBanner 가 노란색으로 뜸. 캡처는 계속 동작, AI 처리만 멈춤.
|
||||
- LAN 서버 살아나면 AiWorker 가 자동 재처리 (3-attempt backoff: immediate / 30s / 120s).
|
||||
- dogfood 영향: AI 수용률 측정 1일 누락. 다음 날 다시 측정.
|
||||
|
||||
### 4.2 단축키 충돌
|
||||
|
||||
- DevTools 열린 Chrome/Edge 가 우선권. Quick Capture 안 뜸.
|
||||
- 임시 우회: 트레이 아이콘 → "기억 구출" 메뉴.
|
||||
- 마찰 사건으로 로그하고, 충돌 빈도가 주 3회 넘으면 후속 spec 에 단축키 변경 안건.
|
||||
|
||||
### 4.3 0/7 주
|
||||
|
||||
- 회복 카피 ("이번 주 한 줄이면 시작입니다") 가 떴는지 확인. 안 떴으면 ContinuityService 버그.
|
||||
- 회복 카피가 정상 노출됐다면 dogfood 가 진행 가능 — 죄책감 금지가 슬라이스 자체의 가설.
|
||||
- 2주 연속 0/7 이면 H3 명확히 미달. spec §0 가설 재검토.
|
||||
|
||||
### 4.4 크래시·데이터 손실
|
||||
|
||||
- 즉시 dogfood 중단. `%APPDATA%\Inkling\default\inkling.db` 와 로그 (`%APPDATA%\Inkling\logs\`) 백업.
|
||||
- 슬라이스 invariant 위반 — 후속 작업 최우선. 본 dogfood 는 crash fix + 검증 후 재개.
|
||||
|
||||
---
|
||||
|
||||
## 5. dogfood 중 떠오른 아이디어 처리
|
||||
|
||||
### 5.1 마찰 로그 vs 백로그 vs 슬라이스 패치
|
||||
|
||||
dogfood 중 발견하는 사항은 세 묶음으로 분류한다.
|
||||
|
||||
| 유형 | 예시 | 처리 |
|
||||
|------|------|------|
|
||||
| 슬라이스 invariant 위반 | raw_text 가 변형됨, "실패" 문구가 UI 에 노출됨, 노트 손실 | 즉시 슬라이스 패치 (spec §3.3) |
|
||||
| 마찰 사건 | 단축키 충돌, 토스트 안 뜸, 캡처 → 저장 5초 이상 | 일일 로그 §6 항목에 기록, 주말 회고에서 분류 |
|
||||
| 신기능 아이디어 | "주간 자동 요약 보고서", "노트 검색", "캘린더 트리거" | `docs/superpowers/specs/` 에 후속 spec 초안. 슬라이스 코드 미수정 |
|
||||
|
||||
**dogfood 중 슬라이스 코드에 신기능을 추가하는 것은 금지.** 본 dogfood 의 가설 검증 자체가 무효화된다.
|
||||
|
||||
---
|
||||
|
||||
## 6. 일일 로그 템플릿
|
||||
|
||||
`docs/dogfood/2026-MM-DD-weekN.md` 에 매일 추가. 한 줄도 OK.
|
||||
|
||||
```markdown
|
||||
# Week N · YYYY-MM-DD (요일)
|
||||
|
||||
## 캡처
|
||||
- 일일 캡처 수: __
|
||||
- 트리거 응답: 회의후 _/_ · 퇴근전 _/_
|
||||
|
||||
## AI
|
||||
- 수용률 체감: __%
|
||||
- AI 가 거의 손대지 않은 노트: __
|
||||
- AI 결과가 부족해 편집한 노트: __
|
||||
|
||||
## 마찰 사건 (있으면 1줄씩)
|
||||
-
|
||||
|
||||
## 오늘 가장 잘 구출한 메모 1개
|
||||
- (제목만, 본문은 적지 않음 — 로그는 git 에 들어가므로)
|
||||
|
||||
## 자유 메모
|
||||
-
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. 종료 판정
|
||||
|
||||
2주차 마지막 날 (= 14일째) 에 다음을 평가한다.
|
||||
|
||||
### 7.1 Pass — 다음 spec 으로
|
||||
|
||||
다음 모두 충족 시 슬라이스 종료 + `v0.2.0-slice` git tag + 다음 spec 작성.
|
||||
|
||||
- [ ] 2주 연속 dogfood 완주 (1주차 최소 5일, 2주차 최소 5일 캡처)
|
||||
- [ ] AI 수용률 주 평균 ≥ 70%
|
||||
- [ ] Weekly Continuity 2주 모두 ≥ 7/7
|
||||
- [ ] 크래시 0회 + 데이터 손실 0건
|
||||
- [ ] 심리 지표 주 평균 ≥ 3.0
|
||||
|
||||
### 7.2 Conditional Pass — 슬라이스 패치 후 1주 추가
|
||||
|
||||
H1 (3초 마찰) 또는 H2 (AI 품질) 만 미달, 그 외는 충족.
|
||||
|
||||
- 마찰 사건 패치 또는 프롬프트 튜닝 → 슬라이스 패치 PR 1회
|
||||
- 추가 1주 dogfood 후 재평가
|
||||
|
||||
### 7.3 Fail — 가설 재검토
|
||||
|
||||
H3 (행동 루프) 미달 또는 다중 가설 미달.
|
||||
|
||||
- spec §0 의 가설 수정 또는 슬라이스 범위 조정
|
||||
- 본 dogfood 종료, 외부 베타 보류
|
||||
- strategy §11 의 A/B 테스트 항목으로 직진 (실험 4 = 매일 스트릭 vs 주 3회 연속성 → 본 슬라이스가 후자 채택, 그 가설을 다시 의심)
|
||||
|
||||
---
|
||||
|
||||
## 8. 외부 베타 직전 점검 (slice 종료 후)
|
||||
|
||||
본 dogfood 가 Pass 여도, 외부 10인 베타 전에 다음을 추가로 확인한다 — 본 문서 범위 밖이지만 메모 차원으로 적어둠.
|
||||
|
||||
- 다중 프로필·잠금 (privacy)
|
||||
- 코드 서명·SmartScreen
|
||||
- 자동 업데이트
|
||||
- 충돌 리포팅
|
||||
|
||||
이 항목들은 slice 후속 spec 에서 다룬다.
|
||||
|
||||
---
|
||||
|
||||
## 부록 A. dogfood 와 슬라이스 자체의 차이
|
||||
|
||||
이 dogfood 는 **슬라이스 v0.4 의 종료 조건**을 충족시키는 활동이다. 그러므로:
|
||||
|
||||
- 일정: 14일 고정
|
||||
- 산출물: dogfood 로그 (`docs/dogfood/`) + Pass/Fail 판정 메모
|
||||
- 책임자: 본인 (외부 참여 없음)
|
||||
- 평가 시점: Day 14 의 회고
|
||||
|
||||
이는 inkling.md §7.1 의 Aha Moment (사용자 일반론) 와 다르다. Aha 는 슬라이스가 충족시켜야 할 부분 조건이고, 본 dogfood 종료는 슬라이스 자체의 졸업 시험이다.
|
||||
|
||||
## 부록 B. 카피 회수 검증 항목
|
||||
|
||||
본 dogfood 는 strategy 문서의 카피 정책이 실제 본인 감정에 어떻게 닿는지 검증할 수 있는 마지막 단계다. 다음 카피가 "본인을 향했을 때 적절한가" 를 매주 1점만 추가 평가:
|
||||
|
||||
- "이 생각은 이제 Inkling이 들고 있습니다."
|
||||
- "🌱 흐름을 다시 이어갑니다"
|
||||
- "이번 주 한 줄이면 시작입니다"
|
||||
- "내일의 내가 알아야 할 것은?"
|
||||
|
||||
이 평가가 낮으면 다음 spec 의 카피 카탈로그 갱신 안건.
|
||||
@@ -7,6 +7,13 @@
|
||||
"dev": "electron-vite dev",
|
||||
"build": "electron-vite build",
|
||||
"start": "electron-vite preview",
|
||||
"rebuild:node": "cd node_modules/better-sqlite3 && prebuild-install",
|
||||
"rebuild:electron": "cd node_modules/better-sqlite3 && prebuild-install --runtime=electron --target=41.3.0",
|
||||
"pretest": "npm run rebuild:node",
|
||||
"pretest:integration": "npm run rebuild:node",
|
||||
"pretest:e2e": "npm run rebuild:electron && npm run build",
|
||||
"prestart": "npm run rebuild:electron",
|
||||
"predev": "npm run rebuild:electron",
|
||||
"test": "vitest run",
|
||||
"test:watch": "vitest",
|
||||
"test:integration": "INKLING_INTEGRATION=1 vitest run tests/integration",
|
||||
|
||||
@@ -34,9 +34,12 @@ app.whenReady().then(async () => {
|
||||
const continuity = new ContinuityService(db);
|
||||
const intent = new IntentService(repo);
|
||||
|
||||
const provider = new LocalOllamaProvider({
|
||||
endpoint: process.env.INKLING_OLLAMA_ENDPOINT
|
||||
const resolvedEndpoint = process.env.INKLING_OLLAMA_ENDPOINT ?? 'http://localhost:11434';
|
||||
logger.info('ai.endpoint', {
|
||||
endpoint: resolvedEndpoint,
|
||||
fromEnv: process.env.INKLING_OLLAMA_ENDPOINT !== undefined
|
||||
});
|
||||
const provider = new LocalOllamaProvider({ endpoint: resolvedEndpoint });
|
||||
const health = new HealthChecker(provider);
|
||||
void health.runOnce().then((h) => logger.info('ai.health', { ...h } as Record<string, unknown>));
|
||||
|
||||
|
||||
@@ -9,8 +9,13 @@ export function OllamaBanner(): React.ReactElement | null {
|
||||
? '`ollama pull gemma4:e4b` 실행 후 앱을 재시작해주세요.'
|
||||
: 'Inkling 정리가 잠시 멈췄습니다. Ollama를 실행해주세요.';
|
||||
return (
|
||||
<div className="banner warn">
|
||||
<div className="banner warn" style={{ flexDirection: 'column', alignItems: 'flex-start' }}>
|
||||
<span>⚠ {message}</span>
|
||||
{status.reason ? (
|
||||
<span style={{ fontSize: 11, opacity: 0.7, marginTop: 4 }}>
|
||||
진단: {status.reason}
|
||||
</span>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2,13 +2,30 @@ import { test, expect, _electron as electron } from '@playwright/test';
|
||||
import { resolve } from 'node:path';
|
||||
|
||||
test('inbox shell shows v0.2 empty state', async () => {
|
||||
// Strip ELECTRON_RUN_AS_NODE if it leaked in from the parent process
|
||||
// (some harnesses set it for native-module rebuild and forget to clear it).
|
||||
// When set, Electron's main-process module hook is skipped and require('electron')
|
||||
// returns only the binary path, so app.whenReady is undefined.
|
||||
const env: Record<string, string> = {};
|
||||
for (const [k, v] of Object.entries(process.env)) {
|
||||
if (k === 'ELECTRON_RUN_AS_NODE') continue;
|
||||
if (typeof v === 'string') env[k] = v;
|
||||
}
|
||||
env.INKLING_DEBUG = '1';
|
||||
const app = await electron.launch({
|
||||
args: [resolve('out/main/index.js')],
|
||||
env: { ...process.env, INKLING_DEBUG: '1' }
|
||||
env
|
||||
});
|
||||
const inbox = await app.firstWindow();
|
||||
await inbox.waitForLoadState('domcontentloaded');
|
||||
await expect(inbox.getByText('Inkling')).toBeVisible();
|
||||
const first = await app.firstWindow();
|
||||
// Both the inbox and quickcapture windows are created at startup;
|
||||
// firstWindow() may pick either, so select by title.
|
||||
await new Promise((r) => setTimeout(r, 500));
|
||||
let inbox = first;
|
||||
for (const w of app.windows()) {
|
||||
if ((await w.title()) === 'Inkling') { inbox = w; break; }
|
||||
}
|
||||
await inbox.waitForLoadState('load');
|
||||
await expect(inbox.getByRole('heading', { name: 'Inkling' })).toBeVisible();
|
||||
await expect(inbox.getByText('첫 기억을 구출해보세요.')).toBeVisible();
|
||||
await app.close();
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user