Files
2026-09-16 17:22:14 +09:00

194 lines
15 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
description: "Task list for 스니펫 팔레트 (Snippet Palette)"
---
# Tasks: 스니펫 팔레트 (Snippet Palette)
**Input**: `specs/001-snippet-palette/` (plan.md, spec.md, research.md, data-model.md, contracts/, quickstart.md)
**Tests**: 포함(이 프로젝트는 superpowers TDD RED→GREEN + `npm run test`/`dotnet test` 통과가 완료 기준 — CLAUDE.md §4).
**Organization**: user story별 phase. US1(P1)만 해도 MVP(스니펫 있으면 검색→붙여넣기 됨).
## Format: `[ID] [P?] [Story] Description (파일경로)`
- **[P]**: 다른 파일·의존 없음 → 병렬 가능
- **[US#]**: 해당 user story
- 핸드오프: 여기서부터 superpowers 뒷단(`executing-plans`/`subagent-driven-development` + `test-driven-development`). 단계(phase) 하나 끝날 때마다 `specs/001-snippet-palette/stage-N.md` 기록.
---
## Phase 1: Setup (공용 기반)
- [ ] T001 `Microsoft.Data.Sqlite` 패키지 추가 (`3_windowsApp/CodeAssist.Shell/CodeAssist.Shell.csproj`)
- [ ] T002 [P] `PATHS.SNIPPET = "/snippet"` 추가 (`2_frontend/src/config/routes.ts`)
- [ ] T003 [P] feature 폴더 뼈대 + 타입 생성: `Snippet`/`SnippetInput` (`2_frontend/src/features/snippets/types.ts`, data-model.md 준용)
---
## Phase 2: Foundational (모든 story 선행 — 반드시 먼저)
**목표 체크포인트**: `Ctrl+1` → 팔레트가 `/snippet`(빈 화면)로 뜨고, `snippets.api.list()`가 시드된 데이터를 왕복으로 돌려준다.
- [ ] T004 `HotKeyService` 멀티 핫키로 확장 — `Register(int id, mods, vk)`·`HashSet<int>` 추적·`event Action<int> HotKeyPressed`·`ProcessMessage(int msg, IntPtr wParam)` (`3_windowsApp/CodeAssist.Shell/Platform/HotKeyService.cs`) + `App.WndProc`가 wParam 전달하도록 수정 (`3_windowsApp/CodeAssist.App/App.xaml.cs`)
- [ ] T005 `Ctrl+1`(id `0x1001`, `MOD_CONTROL|VK_1`) 등록 + 눌리면 `ShowPalette()` 후 navigate 신호 송신. 핫키 조합 상수는 한 곳에 모음 (`3_windowsApp/CodeAssist.App/App.xaml.cs`) — depends T004
- [ ] T006 `WebHostView``SendToWeb(object)`(=`PostWebMessageAsJson`) 추가 + `OnWebMessageReceived``snippets.*``SnippetBridge`로 위임 + `navigate` 푸시 경로 (`3_windowsApp/CodeAssist.App/Views/WebHostView.xaml.cs`) — depends T004/T005 (navigate 대상)
- [ ] T007 [P] `SnippetRepository` 뼈대 + 스키마 초기화(`CREATE TABLE snippets/usage`) + DB 경로 `%LOCALAPPDATA%\CodeAssist\snippets.db` (`3_windowsApp/CodeAssist.Shell/Storage/SnippetRepository.cs`, data-model.md 스키마)
- [ ] T008 [P] `SnippetSeed` — 첫 실행 시 대상 DB 없고 003 `snippets.db` 있으면 1회 복사, 없으면 빈 DB. App 시작 시 repo 사용 전 호출 (`3_windowsApp/CodeAssist.Shell/Storage/SnippetSeed.cs`) — 003 경로는 구현 시 확인(research R5)
- [ ] T009 [P] 프론트 브릿지 왕복 인프라 — `reqId` Map + `chrome.webview` 메시지 리스너(`snippets.result` 매칭 resolve/reject), 순수 브라우저면 비활성 (`2_frontend/src/lib/bridge/snippetBridge.ts`, contracts/bridge-messages.md)
- [ ] T010 [P] `bridgeNavigate.ts``navigate` 메시지 수신 → 등록된 react-router navigate 콜백 호출. 앱 최상위에서 mount 시 콜백 등록 (`2_frontend/src/lib/bridge/bridgeNavigate.ts` + 등록 지점)
- [ ] T011 `/snippet` 라우트 + 임시 placeholder 페이지를 `SnapLayout` 그룹에 추가 (`2_frontend/src/routes.tsx`) — depends T002
**Checkpoint**: Ctrl+1 → /snippet 빈 화면 뜨고 list 왕복 확인.
---
## Phase 3: User Story 1 - 핫키로 검색→앞 창 붙여넣기 (P1) 🎯 MVP
**Goal**: 스니펫 검색·프리뷰·usage랭킹 + Enter/Alt로 앞 창에 붙여넣기.
**Independent Test**: 시드된 스니펫 상태에서 에디터 포커스 → Ctrl+1 → 검색 → Enter → 에디터에 body 원문 삽입 + usage 상승.
### Tests (RED 먼저)
- [ ] T012 [P] [US1] `core/search` 단위테스트 — 키워드 AND, name+desc 대문자, body 제외, 빈쿼리 (`2_frontend/src/features/snippets/core/search.test.ts`)
- [ ] T013 [P] [US1] `core/ranking` 단위테스트 — `(-count,-lastUsed,name)` 정렬 (`2_frontend/src/features/snippets/core/ranking.test.ts`)
- [ ] T014 [P] [US1] `SnippetRepository.List/RecordUse` xUnit (temp sqlite, usage 조인·upsert) (`3_windowsApp/CodeAssist.Tests/SnippetRepositoryTests.cs`)
- [ ] T015 [P] [US1] `snippets.api` list/recordUse — 브릿지 mock 테스트 (`2_frontend/src/features/snippets/api/snippets.api.test.ts`)
### Impl
- [ ] T016 [P] [US1] `core/search.ts` (검색 순수함수, category 파라미터 포함) (`2_frontend/src/features/snippets/core/search.ts`)
- [ ] T017 [P] [US1] `core/ranking.ts` (usage 정렬) (`2_frontend/src/features/snippets/core/ranking.ts`)
- [ ] T018 [US1] `SnippetRepository.List`(snippets⨝usage) + `RecordUse`(count+1,last_used=now) (`3_windowsApp/CodeAssist.Shell/Storage/SnippetRepository.cs`)
- [ ] T019 [US1] `SnippetBridge` 핸들러 `snippets.list`·`snippets.recordUse` → repo 호출 후 `snippets.result` 응답 (`3_windowsApp/CodeAssist.App/Views/WebHostView.xaml.cs` 또는 신규 SnippetBridge.cs)
- [ ] T020 [US1] `snippets.api.ts`(list, recordUse) + `useSnippets`/`useRecordUse` (SNIPPETS_KEY·invalidate, memos 패턴) (`2_frontend/src/features/snippets/api/snippets.api.ts`, `hooks/useSnippets.ts`)
- [ ] T021 [US1] `SnippetPalettePage` — snap `SessionListPage` 복제 → 검색입력 + 결과리스트 + 우측 프리뷰, Raycast 룩 리스킨 (`2_frontend/src/features/snippets/pages/SnippetPalettePage.tsx`)
- [ ] T022 [US1] 키 조작 — ↑↓ nav(프리뷰 갱신), Enter→`pasteToApp(body)`+`recordUse`+`hideWindow`, Alt+1~9 즉시 붙여넣기, Esc 단계(비움→숨김), IME 안전 (`SnippetPalettePage.tsx`)
- [ ] T023 [P] [US1] `SnippetRow`(제목/부제/우측 번호 액세서리) + `PreviewPane`(`<pre>` 고정폭·원문보존, react-markdown 안 씀) Raycast 결 (`2_frontend/src/features/snippets/components/`)
- [ ] T024 [US1] 빈 상태 안내 + 상위 9개 `1~9` 번호 표시 (`SnippetPalettePage.tsx`)
**Checkpoint**: US1 단독으로 완전 동작(MVP). **STOP & VALIDATE** — quickstart 1~6,10,11.
---
## Phase 4: User Story 2 - 생성/편집/삭제 (P2)
**Goal**: 팔레트에서 스니펫 CRUD. 생성 시 body는 클립보드로 프리필.
**Independent Test**: 생성→검색에 뜸 / 빈 이름·내용 거부 / 중복 이름 거부 / F2 편집(이름 읽기전용) / 편집창 삭제.
### Tests (RED 먼저)
- [ ] T025 [P] [US2] `SnippetRepository.Create/Update/Delete` xUnit — 중복 거부·정규화·검증 (`3_windowsApp/CodeAssist.Tests/SnippetRepositoryTests.cs`)
- [ ] T026 [P] [US2] `snippets.api` create/update/remove — 브릿지 mock(성공/`ok:false` reject) (`2_frontend/src/features/snippets/api/snippets.api.test.ts`)
### Impl
- [ ] T027 [US2] `SnippetRepository.Create/Update/Delete` — name 정규화·유일성·body 비어있지않음(위반 시 에러) (`3_windowsApp/CodeAssist.Shell/Storage/SnippetRepository.cs`)
- [ ] T028 [US2] `SnippetBridge` 핸들러 create/update/delete (`ok:false`+한글 에러 계약) (`WebHostView.xaml.cs`/SnippetBridge.cs)
- [ ] T029 [US2] `snippets.api` create/update/remove + `useCreateSnippet`/`useUpdateSnippet`/`useDeleteSnippet`(invalidate + sonner 한글 토스트, ApiError 형태) (`api/snippets.api.ts`, `hooks/useSnippets.ts`)
- [ ] T030 [US2] `EditDialog` — 생성/편집 공용, 편집 시 name 읽기전용, 생성 시 body=클립보드(`navigator.clipboard.readText()`, 실패 시 빈값), 검증 (`2_frontend/src/features/snippets/components/EditDialog.tsx`)
- [ ] T031 [US2] 팔레트 배선 — 생성 액션(Ctrl+N/버튼), F2·더블클릭 편집, 다이얼로그 삭제+확인, 완료 후 목록 새로고침 (`SnippetPalettePage.tsx`)
**Checkpoint**: US1+US2 동작. quickstart 7~8.
---
## Phase 5: User Story 3 - category 칩 필터 (P3)
**Goal**: category 칩으로 분류별 필터.
**Independent Test**: 다른 category 스니펫 있을 때 칩 선택 → 그 분류만.
### Tests (RED 먼저)
- [x] T032 [P] [US3] `core/search` category 필터 케이스 추가 (`core/search.test.ts`)
### Impl
- [x] T033 [US3] category 필터 확정(`core/search.ts` 파라미터 활용) + 목록에서 존재하는 category 도출 (`core/search.ts`, `SnippetPalettePage.tsx`)
- [x] T034 [US3] `CategoryChips` — "전체" + categories, `Ctrl+←/→` 칩 이동, 페이지 배선 (`2_frontend/src/features/snippets/components/CategoryChips.tsx`, `SnippetPalettePage.tsx`)
**Checkpoint**: 전 story 동작. quickstart 9.
---
## Phase 6: Polish & 검증
- [ ] T035 `npm run format``npm run lint` + `npm run test` + `npm run build` 통과 + `dotnet test 3_windowsApp/CodeAssist.Tests` 통과
- [ ] T036 quickstart.md 수동 시나리오 1~11 전체 확인(붙여넣기 대상 정확성·영속·시드)
- [ ] T037 [P] `specs/001-snippet-palette/report.md`(계획 vs 결과) 작성 → 최종 승인용
---
## Dependencies & Execution Order
- **Phase 1 Setup** → **Phase 2 Foundational**(전 story 차단) → **US1 → US2 → US3****Polish**.
- Foundational 안: T004→T005→T006 순(같은 C# App/WebHostView 계열). T007/T008/T009/T010은 서로 [P]. T011은 T002 이후.
- US 간: US1은 read/paste, US2는 write(같은 repo/api/page 파일 확장이라 US1 뒤가 안전 — 병렬 시 파일 충돌 주의), US3은 category(대부분 독립).
- Story 내: 테스트(RED) → 순수함수/모델 → repo/bridge → api/hooks → 페이지/컴포넌트.
## Parallel Opportunities
- Setup: T002·T003 [P].
- Foundational: T007·T008·T009·T010 [P](C# 저장소 vs 프론트 브릿지 서로 다른 파일).
- US1 테스트: T012·T013·T014·T015 [P]. US1 순수함수: T016·T017 [P]. 컴포넌트 T023 [P].
- **주의**: `SnippetRepository.cs`·`snippets.api.ts`·`SnippetPalettePage.tsx`는 여러 태스크가 손대므로 같은 story 안에선 [P] 안 붙임(파일 충돌).
## Implementation Strategy
1. Setup + Foundational → "Ctrl+1로 /snippet 뜨고 데이터 왕복" 체크포인트.
2. **US1(MVP)** → 독립 검증(붙여넣기 되면 도구로서 완성) → 데모.
3. US2(CRUD) → US3(category) 순 증분.
4. 각 단계 끝 `stage-N.md`, 완료 전 `superpowers:verification-before-completion` + `/code-review`, 최종 `report.md` → 승인 → merge(`/cmp`).
## Notes
- [P] = 다른 파일·무의존. [US#] = 추적용.
- 정당 편차(plan §Constitution): 데이터는 `@/lib/api/client` 아닌 브릿지, `Snippet` 타입은 feature-local.
- 붙여넣기·핫키·앞창캡처·팔레트 nav는 **기존 재사용** — 새로 만들지 말 것.
## 2026-09-11: 단계별 런처와 실제 창 크기
`spec.md`의 2026-09-11 사용자 승인 범위만 완료함. 위 초기 .NET 이식·CRUD·사용기록의 미완료 항목을 완료로 바꾸지 않음.
- [x] 검색창만 시작하고 검색 후 결과를 표시. 미선택 Enter는 아무것도 복사하지 않음.
- [x] 화살표·클릭으로 직접 선택할 때만 프리뷰를 열고 검색·분류 변경 시 선택 해제.
- [x] 결과 행을 간결하게 바꾸고 하단 키 안내 추가. 기존 코드뷰어·분할 크기 조절 유지.
- [x] Tauri `window_snippet_layout`으로 검색 640×84 / 결과 640×440 / 프리뷰·편집 960×600 적용. DPI·작업 영역·창 테두리 보정.
- [x] 챗봇 복귀·최대화 복원·종료 저장에서 원래 배치 보존. CloseRequested 캐시 순서 문제를 실제로 재현하고 명시적 저장으로 수정.
- [x] Esc 포커스 복원과 재소환 시 삭제 확인창 닫힘을 실패하는 회귀 검사로 재현 후 수정.
- [x] `npm run format`, lint(오류 0·기존 경고 9), 프론트 46파일·259검사, build 통과. Rust fmt·6검사 통과·로컬 DB 전용 1검사 기본 제외, clippy `-D warnings` 통과.
- [x] 실제 저장된 스니펫 SELECT 검색 7개, 키보드 선택·코드 프리뷰·빈 결과·편집·재소환을 Tauri에서 확인. 데이터 저장·삭제는 실행하지 않음.
- [x] 실제 Windows 크기 전환·화면 우하단 확장·최대화 전 일반 배치 복원·축소 종료 후 960×680 재시작 확인.
## 2026-09-11: 코드 프리뷰 군더더기 제거
- [x] 프리뷰 이름·설명·분류, 장식 점과 코드 헤더, 박스 테두리·그림자를 제거. 문법 강조와 줄 번호 유지.
- [x] `CodeBlock`의 plain 표시를 재사용해 프리뷰 안쪽 스크롤을 하나로 합침. 챗봇 카드의 기본 모양은 유지.
- [x] 기존 복사·붙여넣기 버튼을 `CodeActions`로 공유하고 스니펫 하단에 배치. 버튼 Enter를 전역 즉시 복사가 가로채지 않도록 수정.
- [x] 실제 Tauri 프리뷰의 헤더·버튼 0개, 세로 스크롤 1개, 하단 액션 2개와 문법 강조를 확인. 클릭·키보드 복사 원문과 창 유지 회귀 검사 통과.
- [x] format·lint(오류 0, 기존 경고 9)·46파일 260검사·build 통과.
### 별도 관찰
- [ ] 최소화된 앱을 검사 도중 종료한 뒤 `/snap`이 640×84로 시작하는 현상을 관찰함. 최소화 종료 경로의 배치 저장은 별도로 재현·확인할 것. 이번 프리뷰 UI 변경에는 Rust 수정을 포함하지 않음.
## 2026-09-11: 프리뷰 전체 창 축소
- [x] 사용자 추가 요청에 따라 코드 프리뷰만 960×600에서 840×520으로 축소. 검색 640×84·결과 640×440·편집 960×600은 유지.
- [x] 실제 Tauri에서 840×520 화면, 하단 액션 표시, 세로 스크롤 1개를 확인. F2 편집 960×600 → 닫기 후 프리뷰 840×520 복귀 확인.
- [x] Rust fmt 검사와 6개 검사 통과. 프론트는 직전 UI 변경 검증 46파일·260개 검사 및 build 통과 상태를 유지함.
## 2026-09-11: Ctrl+Enter 앱에 붙여넣기
- [x] 기존 `pasteToApp`을 Ctrl+Enter에서도 사용. 일반 Enter·복사 버튼 동작은 유지하고, 미선택·IME·편집·브라우저·반복키·추가 modifier는 차단.
- [x] 네이티브 붙여넣기를 비동기 작업으로 실행. 키 해제는 최대 2초 대기하고, 중복 요청·대상 HWND/PID/thread 변경·포커스 이탈·재입력은 취소.
- [x] `SetForegroundWindow` 직후 정상 활성화를 실패로 오판하는 경합을 실제로 재현. 80ms 구간에서 첫 활성화 전 팔레트·NULL만 기다리고, 대상 활성화 이후 이탈은 취소하도록 수정.
- [x] `SendInput` 전송 개수를 확인하고 성공 때만 창 숨김. 실패하면 창과 오류 안내 유지, 이미 복사된 원문은 클립보드에 보존.
- [x] 저장하지 않는 별도 Windows 입력창에서 실제 Ctrl+Enter 누름·반복·해제 → 1,420자 원문 1회 입력·창 숨김 확인. 기존 붙여넣기 버튼도 같은 원문 입력 확인.
- [x] 실제 네이티브 입력으로 한글·CRLF·탭·앞뒤 공백 보존 확인. 대상 없음·닫힌 대상·중복 요청·2초 키 대기 초과·대기 중 포커스 이탈에서 입력 취소·오류·클립보드 보존 확인.
- [x] 프론트 format·lint(오류 0, 기존 경고 9)·46파일 264검사·build 통과. Rust fmt·6검사(로컬 DB 검사 1개 기본 제외)·clippy `-D warnings` 통과.
- 검증 범위: 별도 WinForms 입력창을 사용했고 사용자 문서·원본 스니펫 DB를 바꾸지 않음. VS Code·SAP·관리자 권한 앱 각각의 실제 수신 검사는 별도이며, 키 전송 성공이 모든 앱의 붙여넣기 수신을 보장하지는 않음.