5.2 KiB
5.2 KiB
Contract: 스니펫 브릿지 메시지
기준 앱은 Tauri이고 환경별 전달은 specs/004-tauri-shell/contracts/transport-mapping.md를 따름. 아래 C# 요청·응답 설명은 참고용 legacy 채널 계약임.
Tauri 단계별 창 크기 (2026-09-11)
{type:"window.snippetLayout", stage:"search"|"results"|"preview"|"editor"} → invoke("window_snippet_layout", {stage}).
- 검색
640×84, 결과640×440, 프리뷰840×520, 편집960×600논리 픽셀. 실제 크기와 DPI·작업 영역 보정은 Rust 소유. - 호스트가 Tauri일 때만 전송. legacy·브라우저는 화면 단계만 바뀌고 실제 창 크기는 제어하지 않음.
- 다른 route 보고 시 원래 배치를 복원. 종료 직전에 복원한 크기를 저장해 축소 상태가 일반 창 크기를 덮어쓰지 않음.
- 허용되지 않은 stage와 크기 조작 실패는 reject. 기존 transport 오류 토스트로 표시.
Tauri 앱에 붙여넣기 (2026-09-11)
{type:"paste.code", text} → invoke("paste_code", {text}). 스니펫 Ctrl+Enter와 기존 코드뷰어 붙여넣기 버튼이 같은 경로를 사용함.
Enter는 기존 원문 복사 후 창 숨김, 복사 버튼은 복사만 하고 창 유지.Ctrl+Enter는 선택한 코드에만 적용하고 데스크톱·미편집·비조합 상태에서 한 번만 실행.- 호스트는 키 해제를 기다린 뒤 직전 앱으로 돌아가고, 실제 입력 직전에 대상·포커스·키 상태를 다시 확인. 대기 중 UI는 멈추지 않고 중복 붙여넣기는 실행하지 않음.
- 성공한 입력 전송 뒤에만 창을 숨김. 실패하면 창을 유지하거나 다시 표시하고 기존 bridge 오류 토스트로 안내.
- 클립보드 기록 뒤에 실패하면 원문은 그대로 남김. 클립보드 자체 기록 실패에는 복사됐다고 안내하지 않음.
- 다른 앱의 편집기가 실제로 내용을 받아들였는지까지 성공 응답이 보장하지는 않음. 별도 입력창에서 end-to-end로 검사하고, 관리자 권한 앱 등 Windows 입력 제한은 오류로 구분.
기존 (변경 없음)
| 방향 | 메시지 | 처리 |
|---|---|---|
| JS→C# | {type:"window.hide"} |
런처 창 숨김 |
| JS→C# | {type:"paste.code", text} |
직전 창에 text 붙여넣기 + 창 숨김 |
현재 Enter는 선택한 스니펫 원문을 클립보드에 복사한 뒤 창을 숨김. 기존 paste.code는 코드뷰어의 붙여넣기 버튼과 스니펫 Ctrl+Enter에서 사용함. Tauri의 오류·창 표시 동작은 위 절을 따름.
신규 — C#→JS 푸시(내비게이션)
| 방향 | 메시지 | 처리 |
|---|---|---|
| C#→JS | {type:"navigate", path} |
프론트가 react-router로 path로 이동 |
- 스니펫 핫키(Ctrl+1) → C#
ShowPalette()후SendToWeb({type:"navigate", path:"/snippet"}). - 프론트
bridgeNavigate.ts가 수신 → 등록된 navigate 콜백 호출.
신규 — 스니펫 데이터(요청 → 응답, reqId 상관)
각 요청은 프론트가 부여한 reqId(문자열, 단조 증가) 포함. C#는 같은 reqId로 결과를 돌려준다.
요청 (JS→C#)
| 메시지 | 의미 |
|---|---|
{type:"snippets.list", reqId} |
전체 스니펫(usage 병합) 반환 |
{type:"snippets.create", reqId, snippet:{name,desc,body,category}} |
생성(중복 name 거부) |
{type:"snippets.update", reqId, snippet:{name,desc,body,category}} |
수정(name=키, 나머지 갱신) |
{type:"snippets.delete", reqId, name} |
삭제 |
{type:"snippets.recordUse", reqId, name} |
usage count+1, last_used=now |
응답 (C#→JS)
// 성공
{ "type":"snippets.result", "reqId":"<echo>", "ok":true, "data": <op별 페이로드> }
// 실패
{ "type":"snippets.result", "reqId":"<echo>", "ok":false, "error":"<사람이 읽을 메시지>" }
| 요청 | 성공 data |
|---|---|
snippets.list |
Snippet[] (usageCount·lastUsed 병합) |
snippets.create / update |
저장된 Snippet (또는 갱신 후 전체 목록 — 구현 단순한 쪽) |
snippets.delete |
{name} |
snippets.recordUse |
{name, usageCount, lastUsed} |
에러 계약: 중복 name 생성/빈 필드 → ok:false + 한글 메시지. 프론트는 ok:false를 Error로 reject → react-query onError → sonner 토스트(memos 패턴).
C# 라우팅
OnWebMessageReceived가 type으로 분기:
window.hide/paste.code→ 기존.snippets.*→SnippetBridge(신규)에 위임: JSON에서reqId·snippet·name파싱 →SnippetRepository호출 →SendToWeb로snippets.result응답.- 파싱은 기존
type+text만 읽던 걸 확장(요청별 필요한 필드 추출).
프론트 리스너
snippetBridge.ts:Map<reqId,{resolve,reject}>,chrome.webview.addEventListener("message")에서snippets.result만 필터해 매칭 reqId resolve/reject. 각 op은postMessage+promise.bridgeNavigate.ts: 같은 message 이벤트에서navigate만 필터. (리스너 여러 개 가능 — 서로 독립.)- 순수 브라우저(
isWebView()===false): 요청은 즉시 빈 결과/비활성(데스크톱 전용, research R7).