108 lines
8.4 KiB
Markdown
108 lines
8.4 KiB
Markdown
# Implementation Plan: 스니펫 팔레트 (Snippet Palette)
|
|
|
|
**Branch**: `001-snippet-palette` | **Date**: 2026-08-05 | **Spec**: [spec.md](./spec.md)
|
|
|
|
**Input**: Feature specification from `specs/001-snippet-palette/spec.md`
|
|
|
|
## 2026-09-11 실행 범위
|
|
|
|
현재 구현 계획은 `spec.md`의 단계별 런처 확정을 따름. 아래 .NET 초기 이식 계획은 미완료 저장 기능의 배경이며, 이번 작업은 Tauri 기준으로 진행함.
|
|
|
|
1. `SnippetPalettePage.tsx`: 빈 검색 결과 숨김, 명시적인 선택만 프리뷰 표시, 검색·분류·재소환 시 선택 해제. 기존 검색·랭킹·복사 함수와 편집기를 재사용.
|
|
2. `SnippetRow.tsx`와 `PaletteShell.tsx`: 테두리 없는 행과 선택 강조, 복사 전용 런처에는 대상 앱 배지를 표시하지 않음.
|
|
3. `window.snippetLayout`을 기존 transport로 전달. Rust `commands.rs`가 단계별 크기를 선택하고 `shell/window.rs`가 이전 배치 보관·작업 영역 보정·복원을 담당.
|
|
4. `report_route`에서 다른 화면 복원. 창이 닫힐 때 복원한 실측값을 window-state 캐시에 명시적으로 저장해 종료 이벤트 순서에 의존하지 않음.
|
|
5. 검색 전환·원문 복사·IME·Esc 포커스·삭제 확인 닫힘은 Vitest, 경계 계산은 Rust 검사. 실제 Tauri에서 크기 전환·최대화 복원·축소 상태 종료 후 재시작 확인.
|
|
|
|
## Summary
|
|
|
|
003(파이썬 winkit-snippet)의 스니펫 검색·붙여넣기 기능을 이 앱(React SPA + .NET WebView2 런처)에 재개발 이식한다. 전역 핫키 `Ctrl+1`로 팔레트를 소환해, 검색해서 방금 쓰던 창에 스니펫을 붙여넣는다. UI는 Raycast 결. 저장은 C# 호스트가 소유한 로컬 SQLite(003 스키마 그대로), React는 WebView2 브릿지로 접근.
|
|
|
|
**기술 접근(재사용 우선):** 이 앱은 이미 팔레트에 필요한 걸 대부분 갖고 있다 — 커맨드 팔레트 UI(`/snap`의 `SessionListPage`: 자동포커스 검색 + ↑↓/Enter nav + IME 안전 + 단계 Esc), 앞 창에 붙여넣는 배선(`pasteToApp`→`PasteService`), 전역 핫키·트레이·단일실행·앞창 캡처. 그래서 신규는 딱 세 곳만 새로 짠다: (1) 두 번째 전역 핫키, (2) C#→JS `navigate` 신호, (3) React↔SQLite 요청-응답 브릿지 + C# `SnippetRepository`. 나머지는 memos(feature 모듈 카논)·snap(팔레트)의 패턴 복제·리스킨.
|
|
|
|
## Technical Context
|
|
|
|
**Language/Version**: TypeScript 5 / React 18 (Vite) · C# / .NET (WPF, WebView2)
|
|
|
|
**Primary Dependencies**: @tanstack/react-query, shadcn/ui, sonner (프론트) · Microsoft.Web.WebView2, **Microsoft.Data.Sqlite(신규 추가)** (C#)
|
|
|
|
**Storage**: 로컬 SQLite 파일 `snippets.db`(C# 호스트 소유, 003과 동일 스키마). 브라우저 아님.
|
|
|
|
**Testing**: vitest(프론트: 검색/랭킹 순수함수 + 브릿지 mock) · xUnit(C#: `SnippetRepository` CRUD/usage against temp db) — TDD(RED→GREEN)는 superpowers 뒷단에서.
|
|
|
|
**Target Platform**: Windows 데스크톱(WebView2 런처). 순수 브라우저(dev)는 브릿지 no-op → 스니펫 기능 비활성(데스크톱 전용).
|
|
|
|
**Project Type**: 기존 웹앱(2_frontend) + 데스크톱 호스트(3_windowsApp)에 feature 추가.
|
|
|
|
**Performance Goals**: 핫키→팔레트 표시 체감 즉시(수백 ms). 검색은 수백~수천 스니펫에서 substring 필터로 즉각(퍼지 없음).
|
|
|
|
**Constraints**: 붙여넣기 대상은 항상 "소환 직전 창"(기존 캡처 로직 재사용). 붙여넣기 순서 계약(포커스→클립보드→포커스→Ctrl+V)은 기존 `PasteService`가 이미 지킴.
|
|
|
|
**Scale/Scope**: 개인 단일 PC. 스니펫 수백~수천 규모. 화면 1개(팔레트) + 편집 다이얼로그.
|
|
|
|
## Constitution Check
|
|
|
|
*GATE: Phase 0 전 통과, Phase 1 후 재확인.*
|
|
|
|
`.specify/memory/constitution.md`는 빈 템플릿 → 형식 게이트 없음. **실질 캐논은 `CLAUDE.md`**(재사용 우선·feature 모듈 컨벤션·react-query·TDD·docs-lib 선참조). 이에 비춘 점검:
|
|
|
|
- ✅ **재사용 우선**: 팔레트 UI(snap)·붙여넣기(pasteToApp)·핫키/트레이/캡처 전부 기존 것 재사용. 신규는 최소 3곳.
|
|
- ✅ **feature 모듈 컨벤션**: `features/snippets/{api,hooks,components,pages}` + react-query(KEY 상수·invalidate·sonner 한글 토스트·ApiError 분기)로 memos 패턴 준용.
|
|
- ⚠️ **의도된 편차 1 — 데이터 접근이 `@/lib/api/client`(apiGet/apiPost) 경유 아님**: CLAUDE.md 4번은 "axios/HTTP는 apiGet/apiPost만"인데, 스니펫 저장은 **HTTP 백엔드가 아니라 로컬 SQLite(WebView2 브릿지)**다. 그래서 `snippets.api.ts`는 브릿지 백엔드로 구현. react-query로 감싸는 건 유지. → 백엔드가 아니라 편차가 정당(사용자가 로컬 SQLite로 결정). tasks/impl에서 명시.
|
|
- ⚠️ **의도된 편차 2 — 타입 위치**: 서버 DTO는 `types/api.ts`가 규칙이나, `Snippet`은 서버 DTO가 아니라 로컬 엔티티 → snap이 `features/snap/contract/types.ts` 두는 것처럼 `features/snippets/types.ts`에 둠. 정당.
|
|
|
|
Gate: **PASS** (편차 2건은 문서화된 정당 편차, 복잡도 증가 아님).
|
|
|
|
## Project Structure
|
|
|
|
### Documentation (this feature)
|
|
|
|
```text
|
|
specs/001-snippet-palette/
|
|
├── plan.md # 이 파일
|
|
├── research.md # Phase 0 — 미해결점 결정(브릿지 왕복·멀티핫키·SQLite·시드·프리뷰)
|
|
├── data-model.md # Phase 1 — Snippet/Usage 엔티티 + DB 스키마 + TS 타입 + 랭킹규칙
|
|
├── contracts/ # Phase 1 — 브릿지 메시지 계약 + 프론트 데이터 인터페이스
|
|
│ ├── bridge-messages.md
|
|
│ └── snippet-data-interface.md
|
|
├── quickstart.md # Phase 1 — end-to-end 검증 시나리오
|
|
└── checklists/requirements.md
|
|
```
|
|
|
|
### Source Code (repository root)
|
|
|
|
```text
|
|
2_frontend/src/
|
|
├── features/snippets/ # ★ 신규 feature (memos 모양 + snap 팔레트 재사용)
|
|
│ ├── types.ts # Snippet 로컬 타입(서버 DTO 아님)
|
|
│ ├── api/snippets.api.ts # 데이터 인터페이스(브릿지 백엔드). list/create/update/delete/recordUse
|
|
│ ├── core/search.ts # 순수 검색(키워드 AND, name+desc, 대문자) — 단위테스트
|
|
│ ├── core/ranking.ts # usage 정렬 (-count,-lastUsed,name) — 단위테스트
|
|
│ ├── hooks/useSnippets.ts # react-query (SNIPPETS_KEY·invalidate·토스트)
|
|
│ ├── components/ # SnippetRow·PreviewPane·CategoryChips·EditDialog (Raycast 리스킨)
|
|
│ └── pages/SnippetPalettePage.tsx # snap SessionListPage 복제 + Raycast 룩 + Enter→pasteToApp
|
|
├── lib/bridge/
|
|
│ ├── webviewBridge.ts # (기존) + C#→JS 리스너 확장
|
|
│ ├── snippetBridge.ts # ★ 신규: 요청-응답(reqId) 래퍼 — snippets.api 가 사용
|
|
│ └── bridgeNavigate.ts # ★ 신규: C#→JS navigate 수신 → react-router 이동
|
|
├── config/routes.ts # + PATHS.SNIPPET
|
|
└── routes.tsx # + SnippetPalettePage 라우트(SnapLayout 그룹)
|
|
|
|
3_windowsApp/
|
|
├── CodeAssist.Shell/
|
|
│ ├── Storage/SnippetRepository.cs # ★ 신규: 003 repository.py+usage.py 포팅(Microsoft.Data.Sqlite)
|
|
│ ├── Storage/SnippetSeed.cs # ★ 신규: 첫 실행 시 003 snippets.db 1회 복사
|
|
│ ├── Platform/HotKeyService.cs # 확장: 멀티 핫키(id별 등록/디스패치)
|
|
│ └── CodeAssist.Shell.csproj # + Microsoft.Data.Sqlite
|
|
├── CodeAssist.App/
|
|
│ ├── App.xaml.cs # 확장: Ctrl+1 두 번째 핫키 + WndProc wParam 전달
|
|
│ └── Views/WebHostView.xaml.cs # 확장: snippets.* 브릿지 핸들러 + navigate 송신(SendToWeb)
|
|
└── CodeAssist.Tests/ # + SnippetRepository 테스트
|
|
```
|
|
|
|
**Structure Decision**: 기존 2폴더 구조(2_frontend feature 모듈 + 3_windowsApp WPF/Shell) 유지. 신규 코드는 위 ★ 표시. 새 프로젝트/레이어 없음.
|
|
|
|
## Complexity Tracking
|
|
|
|
> Constitution 위반 없음. 편차 2건은 §Constitution Check에 정당화(로컬 SQLite라 HTTP 클라이언트 미경유 / 로컬 엔티티라 feature-local 타입). 별도 정당화 표 불필요.
|