11 KiB
Implementation Plan: Tauri 기준 데스크톱 앱 전환
Branch: 004-tauri-shell | Created: 2026-08-11 | Updated: 2026-09-09 | Spec: 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/apiv2 — 유일한 신규 프론트 의존성 - Rust:
tauri2 (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)
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.mdspecs/002-capture-to-chat/contracts/bridge-capture.md
Source Code (repository root)
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/= 기존 메시지 계약을 Tauriinvoke/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.webviewmock에 의존한다. 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는 생성기가 만든 유효한 산출물 |
통째 재생성해도 같은 파일만 다시 생김. 손으로 쓴 소스만 컴파일하며 재검증함 |