# Implementation Plan: Tauri 기준 데스크톱 앱 전환 **Branch**: `004-tauri-shell` | **Created**: 2026-08-11 | **Updated**: 2026-09-09 | **Spec**: [spec.md](./spec.md) **Input**: Feature specification from `/specs/004-tauri-shell/spec.md` ## Summary 프론트(`2_frontend`)에 **호스트 자동 감지 seam** 하나를 두고, 기준 데스크톱 앱인 `4_rust_tauri`(Tauri v2)에 현재 화면·창 제어·붙여넣기·캡쳐·스니펫 저장을 전부 연결한다. 기존 `3_windowsApp`(.NET)은 삭제하지 않지만 참고용 legacy로만 남긴다. 핵심 설계는 "기존 브릿지 계약을 Tauri 수단으로 그대로 나른다"는 것. Rust→JS 푸시를 **단일 이벤트 채널에 `{type, ...}` 그대로** 실어보내면 프론트 `bridgeNavigate.ts`의 `type` 분기·CustomEvent dispatch 로직을 재사용할 수 있다. JS→Rust는 `invoke`의 Promise를 쓴다. **프론트 빌드·dist는 한 벌**만 유지한다. Tauri 앱과 브라우저 개발 화면이 같은 결과물을 쓰고, 참고용 WebView2 adapter도 transport 안에 남긴다. ## Technical Context **Language/Version**: TypeScript 5.6 (프론트) / Rust 2021 edition (기준 데스크톱 앱) **Primary Dependencies**: - 프론트 추가: `@tauri-apps/api` v2 — 유일한 신규 프론트 의존성 - Rust: `tauri` 2 (tray-icon, image-png), `tauri-plugin-global-shortcut`, `tauri-plugin-single-instance`, `tauri-plugin-window-state`, `rusqlite`(bundled), `windows`(Win32 붙여넣기·캡쳐), `serde` - legacy .NET: 변경·새 의존성 없음 **Storage**: - 스니펫: `%LocalAppData%\CodeAssist\snippets.db` — 기존 사용자 데이터를 그대로 이어서 씀(FR-020) - 창 위치·크기: `tauri-plugin-window-state` 경로(`%LocalAppData%\com.codeassist.app\`) - 핀(항상 위) 상태: 위 폴더의 단일 파일. window-state 플러그인이 always-on-top은 저장 안 함 **Testing**: - 프론트: `vitest` — transport seam 단위 테스트 + 기존 브릿지 테스트 3개 mock 교체 - Rust: `cargo test` — 순수 로직(챗봇 토글 규칙, 스니펫 검증) - 수동: Tauri 창·트레이·표준 단축키·붙여넣기·멀티모니터 캡쳐를 `quickstart.md`에서 실제 실행 - 브라우저: 개발 화면과 데스크톱 기능 없는 경로를 실제 브라우저에서 확인 **Target Platform**: Windows 11. WebView2 런타임 사용 **Project Type**: Rust + Tauri 데스크톱 앱 + React SPA 프론트. .NET 앱은 참고용 legacy **Performance Goals**: 별도 계측 목표 없음. 전역 단축키와 붙여넣기·캡쳐 흐름에서 사용자가 느끼는 멈춤이나 무반응이 없어야 함 **Constraints**: - Tauri가 표준 단축키 `Ctrl+Shift+7/8/9`를 소유함 - legacy 앱과 동시 실행은 지원하지 않음 - 프론트 소비자 코드 변경 **0줄** (SC-001) - US1~US5 전부 완료하며 미구현 성공 응답을 남기지 않음 - 기본 실행·배포·검증에서 .NET을 요구하지 않음 (SC-008) **Scale/Scope**: 프론트 변경은 `lib/bridge/` 4파일 + 관련 테스트로 국한. 데스크톱 기능은 `4_rust_tauri` 안에서 완성 ## Constitution Check *GATE: 구현 전에 통과했고 설계 갱신 후 다시 확인함.* `.specify/memory/constitution.md`는 빈 템플릿이다. CLAUDE.md 규칙을 게이트로 쓴다. | 게이트 (출처) | 판정 | 근거 | |---|---|---| | **재사용 우선** (CLAUDE.md 서두) | ✅ | 프론트는 기존 브릿지 계약·분기·소비자 코드를 재사용하고, Rust는 기존 스캐폴드와 검증된 .NET 동작 순서를 참고함 | | **최소 diff / YAGNI** (ponytail) | ✅ | 프론트 신규 파일은 transport 하나. 창 상태·단일 인스턴스·단축키는 공식 플러그인을 씀 | | **API 호출은 `@/lib/api/client`만** (§4) | ✅ 해당없음 | 서버 API를 건드리지 않음 | | **서버 DTO는 `src/types/api`에만** (§4) | ✅ 해당없음 | 브릿지 타입은 서버 DTO가 아님 | | **주석·문서 한글 반말** (§0, §4) | ✅ | feature 문서와 새 주석을 한글로 유지 | | **편집 후 format, 완료 전 lint+test+build** (§4) | ⏳ 실행 시 | tasks와 quickstart에 검증 순서를 둠 | | **docs-lib 선참조** (§3) | ✅ | Tauri v2와 프론트 API 문서 박제 완료 | | **plan 집은 하나** (§1) | ✅ | `specs/004-tauri-shell/`만 사용 | | **버그는 systematic-debugging 먼저** (§1) | ✅ | 구현 중 실패가 나오면 적용 | **게이트 결과**: 통과. 신규 프론트 의존성 1개는 Tauri 내부 프로토콜을 직접 흉내내지 않기 위한 정당한 편차다. ## Project Structure ### Documentation (this feature) ```text specs/004-tauri-shell/ ├── plan.md # 이 파일 ├── research.md # Phase 0 — 기술 결정 12건 ├── data-model.md # Phase 1 — 엔티티·상태 ├── quickstart.md # Phase 1 — Tauri 단독 실행·검증 시나리오 ├── contracts/ │ └── transport-mapping.md # Phase 1 — 기존 계약 → Tauri·legacy·브라우저 수단 매핑 ├── checklists/ │ └── requirements.md # spec 품질 체크리스트 (완료) └── tasks.md # Phase 2 — /speckit-tasks 가 만듦 (여기선 안 만듦) ``` **기존 계약 문서는 여기 복사하지 않는다.** 진실원천은 그대로 아래 둘이고, `contracts/transport-mapping.md` 는 그걸 **가리키면서** 껍데기별 수단만 대응시킨다. - `specs/001-snippet-palette/contracts/bridge-messages.md` - `specs/002-capture-to-chat/contracts/bridge-capture.md` ### Source Code (repository root) ```text 2_frontend/ # Tauri와 브라우저가 공유. 빌드 한 벌 ├── src/lib/bridge/ │ ├── transport.ts # [신규] 호스트 감지 + 전송수단 3종 │ ├── webviewBridge.ts # [수정] transport 위로 얇게. export 시그니처 불변 │ ├── snippetBridge.ts # [축소] 요청 표면만 남김 │ ├── bridgeNavigate.ts # [수정] 리스너 부착만 교체, 분기 로직 그대로 │ └── *.test.ts # [수정] transport mock 사용 ├── src/features/** # [무변경] 소비자 8곳 — SC-001 └── docs-lib/ # Tauri 프론트 API 박제 3_windowsApp/ # [무변경] 참고용 legacy └── ... 4_rust_tauri/ # 기준 데스크톱 앱 ├── docs-lib/ │ ├── README.md # [신규] 카탈로그 │ └── tauri-v2.md # [신규] API 박제 — 첫 Rust 코드보다 먼저 (R8) ├── package.json # tauri CLI 만 └── src-tauri/ ├── Cargo.toml ├── tauri.conf.json # 제품명 CodeAssist, devUrl → 2_frontend:15173, frontendDist → dist ├── capabilities/default.json ├── icons/ # 생성기 산출물 — 유지 └── src/ ├── main.rs # 진입점 ├── lib.rs # 조립부 ← .NET App.xaml.cs 대응 ├── shell/ # 프로젝트 무관 재사용 층 ← .NET CodeAssist.Shell 대응 │ ├── mod.rs │ ├── hotkey.rs # US2 │ ├── tray.rs # US2 │ ├── window.rs # US2 │ ├── paste.rs # US3 │ └── capture.rs # US4 ├── bridge/ # 계약 층 │ ├── mod.rs # 푸시 타입 + emit │ └── commands.rs # #[tauri::command] 모음 └── storage/ └── snippets.rs # US5 — rusqlite, .NET 과 같은 DB 파일 ``` **Structure Decision**: 기준 데스크톱 앱은 `4_rust_tauri`다. 그 안에서는 기존 .NET 구현에서 검증된 책임 경계를 참고하되 Tauri 플러그인과 Rust 코드에 맞게 최소로 유지한다. - `shell/` = CodeAssist를 모르는 Windows/Tauri 동작 층 - `lib.rs` = 어느 단축키가 무엇을 하는지와 화면 이동 규칙을 모으는 조립부 - `bridge/` = 기존 메시지 계약을 Tauri `invoke`/`emit`으로 나르는 층 통합 진입점(`shell::init(config)`)은 만들지 않는다. 세 모듈이 서로 독립이고 콜백도 달라서 감싸면 넘겨주기만 하는 코드가 늘어난다. ## Constitution Check — 설계 후 재확인 (Phase 1 완료 시점) | 게이트 | 판정 | 설계로 확인된 것 | |---|---|---| | **재사용 우선** | ✅ | `bridgeNavigate.ts`의 분기·dispatch와 기존 메시지 계약을 그대로 쓰고, WebView2 요청 짝맞춤은 legacy adapter 안으로 이동함 | | **최소 diff / YAGNI** | ✅ | 프론트 신규 파일 1개. 구현 하나짜리 통합 진입점과 자동 계측 장치를 만들지 않음 | | **docs-lib 선참조** | ✅ | Tauri v2·프론트 invoke/listen 표면과 DPI 동작 확인 완료 | | **주석·문서 한글 반말** | ✅ | 산출물 전부 한글 | | **plan 집은 하나** | ✅ | `specs/004-tauri-shell/`만 사용하고 기존 계약 문서는 링크함 | | **완료 전 lint+test+build** | ⏳ | quickstart와 tasks에 프론트·Rust·Tauri release 검증을 둠 | **설계가 드러낸 위험 1건**: > `2_frontend`의 브릿지 테스트 3개가 `chrome.webview` mock에 의존한다. transport mock으로 바꾸면서 실제 Tauri 경로가 아니라 mock끼리만 맞는 상태가 생길 수 있다. > > **완화**: 자동 검사 뒤 브라우저와 실제 Tauri 앱을 각각 실행한다. 기존 WebView2 경로는 adapter 단위 테스트로 남기되 legacy 앱 수동 회귀를 완료 게이트로 두지 않는다. **게이트 결과**: 통과. 아래 Complexity Tracking의 2건은 근거 있는 편차다. ## Complexity Tracking > Constitution Check 위반 중 정당화가 필요한 것만 | Violation | Why Needed | Simpler Alternative Rejected Because | |-----------|------------|-------------------------------------| | 프론트 신규 의존성 `@tauri-apps/api` | `invoke`/`listen`은 Tauri 내부 프로토콜이라 공식 API를 쓰는 편이 작고 안전함 | 직접 구현은 버전마다 깨질 내부 표면을 소유하게 됨 | | `4_rust_tauri`의 미완성 스캐폴드 유지 | 아이콘·`build.rs`·capabilities는 생성기가 만든 유효한 산출물 | 통째 재생성해도 같은 파일만 다시 생김. 손으로 쓴 소스만 컴파일하며 재검증함 |