Initial Commit
This commit is contained in:
@@ -0,0 +1,118 @@
|
||||
# Contract: 브릿지 계약 → 환경별 전달 수단 매핑
|
||||
|
||||
**Created**: 2026-08-11 | **Updated**: 2026-09-11
|
||||
|
||||
## 이 문서의 위치
|
||||
|
||||
**메시지 계약의 진실원천은 여기가 아니다.** 원본은 아래 둘이고, 이 문서는 같은 메시지를 Tauri·참고용 legacy·브라우저가 어떻게 나르는지만 대응시킨다.
|
||||
|
||||
- `specs/001-snippet-palette/contracts/bridge-messages.md` — window.hide / paste.code / navigate / snippets.*
|
||||
- `specs/002-capture-to-chat/contracts/bridge-capture.md` — capture.image
|
||||
|
||||
메시지의 의미·필드·에러 규칙을 바꿔야 한다면 원본을 먼저 고친다. Tauri 사정만으로 여기서 다른 계약을 만들지 않는다.
|
||||
|
||||
---
|
||||
|
||||
## 1. JS → 데스크톱 앱 (요청)
|
||||
|
||||
| 계약 메시지 | 참고용 legacy (.NET) | 기준 앱 (Tauri) |
|
||||
|---|---|---|
|
||||
| `{type:"window.hide"}` | `postMessage` → `type` 스위치 | `invoke("window_hide")` |
|
||||
| `{type:"window.drag"}` | `postMessage` → `type` 스위치 | `invoke("window_drag")` |
|
||||
| `{type:"window.snippetLayout", stage}` | 전송하지 않음 | `invoke("window_snippet_layout", { stage })` |
|
||||
| `{type:"paste.code", text}` | `postMessage` → `type` 스위치 | `invoke("paste_code", { text })` |
|
||||
| `{type:"route.changed", path}` | `postMessage` → `type` 스위치 | `invoke("report_route", { path })` |
|
||||
| `{type:"snippets.list", reqId}` | `postMessage` + reqId 대기 | `invoke("snippets_list")` |
|
||||
| `{type:"snippets.create", reqId, snippet}` | 〃 | `invoke("snippets_create", { snippet })` |
|
||||
| `{type:"snippets.update", reqId, snippet}` | 〃 | `invoke("snippets_update", { snippet })` |
|
||||
| `{type:"snippets.delete", reqId, name}` | 〃 | `invoke("snippets_delete", { name })` |
|
||||
| `{type:"snippets.recordUse", reqId, name}` | 〃 | `invoke("snippets_record_use", { name })` |
|
||||
|
||||
### 커맨드 이름 규칙
|
||||
|
||||
계약의 `type` 을 **점 → 밑줄, camelCase → snake_case** 로 바꾼 것. 기계적이라 외울 게 없다.
|
||||
|
||||
```
|
||||
snippets.recordUse → snippets_record_use
|
||||
route.changed → report_route ← 예외 1건
|
||||
```
|
||||
|
||||
**`route.changed` 만 예외**인 이유: 이건 "무엇이 바뀌었다"는 통보라 `route_changed` 로 하면 커맨드가 아니라 이벤트처럼 읽힌다. 껍데기가 하는 일(보고 받기)을 이름에 담았다.
|
||||
|
||||
`window.snippetLayout`의 단계·크기·복원 계약은 스니펫 [원본 계약](../../001-snippet-palette/contracts/bridge-messages.md)의 Tauri 절을 따름.
|
||||
|
||||
### reqId는 Tauri에 없다
|
||||
|
||||
legacy는 요청·응답을 `reqId`로 짝맞춘다. `invoke`는 Promise를 돌려주므로 Tauri 경로에는 `reqId`가 실리지 않는다.
|
||||
|
||||
**그래도 프론트 표면은 같다** — `request(type, payload)` 시그니처가 유지되므로 `snippets.api.ts`는 실행 환경을 모른다.
|
||||
|
||||
---
|
||||
|
||||
## 2. 데스크톱 앱 → JS (푸시)
|
||||
|
||||
| 계약 메시지 | 참고용 legacy (.NET) | 기준 앱 (Tauri) |
|
||||
|---|---|---|
|
||||
| `{type:"navigate", path}` | `PostWebMessageAsJson` | `emit("bridge", …)` |
|
||||
| `{type:"paste.target", name, app}` | 〃 | 〃 |
|
||||
| `{type:"capture.image", dataUrl}` | 〃 | 〃 |
|
||||
|
||||
### 채널은 하나, 페이로드는 동일
|
||||
|
||||
```jsonc
|
||||
// .NET 이 보내는 것
|
||||
{ "type": "navigate", "path": "/snippet" }
|
||||
|
||||
// Tauri 가 보내는 것 — 이벤트 이름만 "bridge", 페이로드는 글자 그대로 같음
|
||||
{ "type": "navigate", "path": "/snippet" }
|
||||
```
|
||||
|
||||
**메시지 종류마다 이벤트 이름을 나누지 않는다.** Tauri 관용으로는 `bridge:navigate`처럼 쪼개는 게 자연스럽지만, 그러면 프론트 `bridgeNavigate.ts`의 `type` 분기를 다시 써야 한다. **한 채널로 몰면 그 분기와 `CustomEvent` dispatch를 그대로 재사용**할 수 있다.
|
||||
|
||||
### 필드 이름은 JS 쪽 표기를 따른다
|
||||
|
||||
`capture.image` 의 `dataUrl` 은 **camelCase 그대로** 나간다. Rust 내부 변수명이 무엇이든 직렬화 결과가 계약과 같아야 한다.
|
||||
|
||||
---
|
||||
|
||||
## 3. 오류 표현
|
||||
|
||||
| 상황 | 참고용 legacy | Tauri | 프론트가 받는 것 |
|
||||
|---|---|---|---|
|
||||
| 정상 | `{ok:true, data}` | `invoke` resolve | 값 |
|
||||
| 실패 (중복 이름 등) | `{ok:false, error:"한글 메시지"}` | `invoke` reject (한글 메시지) | `Error` |
|
||||
| **아직 안 만듦** | 해당 없음 | `invoke` reject (한글 "아직 안 됨") | `Error` |
|
||||
|
||||
세 경우 모두 프론트에서는 `Error`로 도착해 react-query `onError` → sonner 토스트로 흐른다. 실행 환경별 분기가 화면에 생기지 않는다.
|
||||
|
||||
### 미구현은 성공을 흉내내지 않는다
|
||||
|
||||
Tauri에서 아직 안 만든 기능은 빈 값(`[]`, `null`)으로 답하지 않는다. 구현 중 빠진 기능을 정상 결과와 구분할 수 있어야 한다.
|
||||
|
||||
---
|
||||
|
||||
## 4. 껍데기 없음 (브라우저)
|
||||
|
||||
기존 동작 그대로. 계약상 변화 없음.
|
||||
|
||||
| 방향 | 동작 |
|
||||
|---|---|
|
||||
| JS → 껍데기 (단방향) | 조용히 no-op |
|
||||
| JS → 껍데기 (응답 필요) | 즉시 reject — "데스크톱 전용" |
|
||||
| 껍데기 → JS | 안 옴 |
|
||||
|
||||
`npm run dev` 로 브라우저에서 화면만 열어도 안 깨져야 한다 (FR-003).
|
||||
|
||||
---
|
||||
|
||||
## 5. 검증
|
||||
|
||||
이 매핑이 맞는지는 아래로 확인한다. 자동화 대상과 수동 대상이 갈린다.
|
||||
|
||||
| 항목 | 방법 |
|
||||
|---|---|
|
||||
| 호스트 3종 판별 | 자동 (vitest, mock) |
|
||||
| 브라우저에서 no-op / reject | 자동 (vitest) |
|
||||
| legacy adapter 동작 | 자동 (기존 브릿지 테스트 3개) |
|
||||
| Tauri에서 실제 왕복 | 수동 ([quickstart.md](../quickstart.md)) |
|
||||
| Tauri와 브라우저가 같은 화면 결과물 사용 | 빌드 설정 + 실제 실행 확인 |
|
||||
Reference in New Issue
Block a user