Files
inkling/docs/superpowers/specs/2026-05-09-v0211-cut-d-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

6.8 KiB

v0.2.11 — Cut D Design (FTS5 search + 회고 view)

작성일: 2026-05-09 선행 문서:

  • docs/superpowers/specs/2026-04-25-dogfood-feedback.md (F19)
  • docs/superpowers/strategy/v028plus-roadmap.md Cut D

Cut 라벨: v0.2.11


1. Cut 정체성

recall 핵심 가치 도달 — search + 회고 view. F19 의 6 옵션 중 A (FTS5 search) + D (회고 view) 2개. B/C/E/F 는 v0.3+ deferred.


2. 범위

항목 결정
F19-A SQLite FTS5 인덱스 + inbox 헤더 search box
F19-D 일/주/월 회고 라우트 — aggregate query + N건 list + tag distribution + due 진행

3. F19-A 디테일 (FTS5)

3-1. Schema 마이그레이션 (m006)

CREATE VIRTUAL TABLE notes_fts USING fts5(
  note_id UNINDEXED,
  raw_text,
  title,
  summary,
  tags,
  tokenize='unicode61'
);

-- 기존 notes 모두 인덱스
INSERT INTO notes_fts (note_id, raw_text, title, summary, tags)
  SELECT id, raw_text, title, summary, tags_csv FROM notes WHERE status != 'trashed';

tokenize='unicode61' — 한국어 partial tokenize 가능 (단어 boundary). 향후 tokenize='porter unicode61' 또는 한국어 전용 tokenizer (예: mecab-ko-fts5) 검토 가능 — Cut D 는 unicode61 default.

tags_csv — notes.tags (JSON array) 를 csv 로 flatten 하여 인덱스 (예: "기획 회의 결재").

3-2. Trigger — auto-sync

notes INSERT/UPDATE/DELETE 시 notes_fts 자동 sync:

CREATE TRIGGER notes_ai AFTER INSERT ON notes BEGIN
  INSERT INTO notes_fts (note_id, raw_text, title, summary, tags)
    VALUES (NEW.id, NEW.raw_text, NEW.title, NEW.summary, NEW.tags_csv);
END;

CREATE TRIGGER notes_ad AFTER DELETE ON notes BEGIN
  DELETE FROM notes_fts WHERE note_id = OLD.id;
END;

CREATE TRIGGER notes_au AFTER UPDATE ON notes BEGIN
  UPDATE notes_fts SET raw_text=NEW.raw_text, title=NEW.title, summary=NEW.summary, tags=NEW.tags_csv
    WHERE note_id = NEW.id;
END;

Cut C 의 updateRawText 가 notes.raw_text UPDATE → trigger 자동 발동 → FTS5 갱신.

tags_csv 는 별도 generated column 또는 NoteRepository 에서 수동 갱신 (zod parse 후 csv join). YAGNI: 수동 갱신.

3-3. NoteRepository.search

search(query: string, opts: { limit?: number; status?: NoteStatus }): Note[] {
  const limit = opts.limit ?? 50;
  const statusClause = opts.status ? `AND n.status = ?` : '';
  const sql = `
    SELECT n.* FROM notes n
    JOIN notes_fts f ON n.id = f.note_id
    WHERE notes_fts MATCH ? ${statusClause}
    ORDER BY rank LIMIT ?
  `;
  const args = opts.status ? [query, opts.status, limit] : [query, limit];
  return this.db.prepare(sql).all(...args) as Note[];
}

MATCH 쿼리 syntax — FTS5 standard ("기획 회의", 회의 OR 결재, 기획* 등).

기존 헤더 (Inbox/완료/보관/휴지통 탭) 옆에 search input:

<input
  type="search"
  placeholder="검색..."
  value={searchQuery}
  onChange={e => setSearchQuery(e.target.value)}
  style={{ ... }}
/>

debounce 200ms → store action searchNotes(query) → inboxApi.search(query, { status: currentView }) → result list 갱신.

빈 query → 기본 inbox list 복귀.

3-5. IPC

'inbox:search': (query: string, opts: { status?: NoteStatus; limit?: number }) => Promise<Note[]>

4. F19-D 디테일 (회고 view)

4-1. 라우트 추가

useInbox.view enum 에 'review-daily' | 'review-weekly' | 'review-monthly' 추가. 진입점:

  • 헤더 메뉴: "📅 회고" 버튼 → 드롭다운 (일/주/월)
  • 또는 별도 라우트 (Settings 옆)

4-2. 회고 view 컴포넌트

// src/renderer/inbox/components/ReviewView.tsx
export function ReviewView({ period }: { period: 'daily' | 'weekly' | 'monthly' }): ReactElement {
  const data = useReviewData(period);  // store action — aggregate query 결과
  return (
    <div>
      <h2>{periodLabel(period)} 회고</h2>
      <div>총 N건 • 오늘 N건 • 평균 일 N건</div>
      <TagDistributionChart tags={data.tagCounts} />
      <DueProgressChart due={data.dueProgress} />
      <NoteList notes={data.recentNotes} />
    </div>
  );
}

4-3. Aggregate query

NoteRepository:

reviewAggregate(period: 'daily' | 'weekly' | 'monthly', now: Date): {
  totalCount: number;
  recentNotes: Note[];
  tagCounts: Array<{ tag: string; count: number }>;
  dueProgress: { total: number; passed: number; pending: number };
} {
  const cutoff = computeCutoff(period, now);
  // 단일 transaction 안에 N개 query
  const totalCount = this.db.prepare(`SELECT COUNT(*) as c FROM notes WHERE created_at >= ? AND status != 'trashed'`).get(cutoff).c;
  const recentNotes = this.db.prepare(`SELECT * FROM notes WHERE created_at >= ? AND status != 'trashed' ORDER BY created_at DESC LIMIT 50`).all(cutoff);
  // tagCounts — JSON tags array unnest → group by
  // dueProgress — due_date 컬럼 + KST 비교
  return { ... };
}

computeCutoff('daily', now) = KST 자정. 'weekly' = 7일 전 KST. 'monthly' = 30일 전 KST.

4-4. Tag distribution chart

간단한 bar list (CSS — chart 라이브러리 X):

{data.tagCounts.slice(0, 10).map(t => (
  <div key={t.tag}>
    <span>{t.tag}</span>
    <div style={{ width: `${(t.count / max) * 100}%`, background: '#4ec5b8', height: 8 }} />
    <span>{t.count}</span>
  </div>
))}

4-5. Due progress

완료 (passed): 12 / 25
대기 (pending): 13
이번 주 due: 3건

5. 테스트 전략

영역 단위
m006 마이그레이션 FTS5 virtual table 생성 + 기존 notes backfill (status != 'trashed' 만)
Trigger sync INSERT/UPDATE/DELETE → notes_fts 자동 sync
search 한국어 token 매칭 + status filter
inbox header search box debounce + 빈 값 → 기본 list 복귀
ReviewView 단위 aggregate query 결과 렌더
reviewAggregate period 별 cutoff 정확 + tag count + due progress

목표: 단위 505 → 약 528 (+23), typecheck 0.


6. Risk

Risk 대응
FTS5 한국어 token 정확도 (unicode61 가 word boundary 부정확) dogfood 검증. 부족 시 v0.3+ 에서 mecab-ko 또는 trigram tokenize 검토
FTS5 인덱스 size (notes 수만건 시 DB 크기 ↑) 수만건 도달 전엔 무시. v0.3+ 에서 prune 또는 partial 인덱스
회고 aggregate query latency LIMIT 50 + index 활용 (created_at DESC). 수만건도 sub-second 예상
Cut C revision 추가 시 FTS 영향 revision 은 인덱스 X (latest only). 정책 일관

7. v0.2.11 후

Cut E (v0.3.0) — F21 양방향 sync.

dogfood verify:

  1. search 일 사용 빈도 (가설: ≥ 일 1회면 가치 있음)
  2. 회고 view 사용 빈도 (월요일 자동 prompt 추가 검토 — v0.3+)
  3. FTS5 한국어 token 정확도 (사용자 query 결과 만족도)