# Contract: 프론트 데이터 인터페이스 (`snippets.api.ts`) feature의 유일한 데이터 진입점. 화면·hook은 이 인터페이스만 알고, 뒤가 브릿지(SQLite)인지 나중에 백엔드인지는 모른다. (spec FR-022 / plan 편차1: HTTP 클라이언트 미경유) ```ts import type { Snippet, SnippetInput } from "../types" export interface SnippetsApi { list(): Promise // usage 병합, 정렬은 프론트가 create(input: SnippetInput): Promise // 중복 name → reject(Error) update(input: SnippetInput): Promise // name=키 remove(name: string): Promise recordUse(name: string): Promise<{ name: string; usageCount: number; lastUsed: number }> // 붙여넣기 성공 후, 갱신된 usage 반환(caller는 무시 가능) } ``` - v1 구현체 `snippetBridge`(브릿지 왕복). 나중에 백엔드 전환 시 이 인터페이스 뒤 구현만 교체. - 반환 타입은 순수 데이터(브릿지/HTTP 세부 노출 안 함). 실패는 `Error` reject. ## react-query 계약 (`hooks/useSnippets.ts`) — memos 패턴 준용 ```ts export const SNIPPETS_KEY = "snippets" useSnippets() // useQuery([SNIPPETS_KEY]) → Snippet[] useCreateSnippet() // mutation → invalidate([SNIPPETS_KEY]) + sonner "스니펫 생성됨" useUpdateSnippet() // → invalidate + "저장됨" useDeleteSnippet() // → invalidate + "삭제됨" useRecordUse() // → invalidate([SNIPPETS_KEY]) (랭킹 갱신). 토스트 없음(조용히) ``` - 에러: 브릿지 실패는 `ApiError`(HTTP 전용)가 아니라 rejected promise의 순수 `Error`임 → `err instanceof Error ? err.message : "<한글 폴백>"` 형태(memos 톤은 같되 타입만 Error). - `useSnippets`가 준 목록에 `core/search`+`core/ranking`을 화면에서 적용(서버 정렬 아님 — 로컬 계산). ## 붙여넣기 (기존 재사용, 신규 아님) ```ts import { pasteToApp } from "@/lib/bridge/webviewBridge" // Enter / Alt+숫자 → pasteToApp(snippet.body) // 기존 paste.code — C# PasteService가 앞 창에 Ctrl+V + 창 숨김 await recordUse(snippet.name) ```