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

5.2 KiB
Raw Permalink Blame History

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:falseError로 reject → react-query onError → sonner 토스트(memos 패턴).

C# 라우팅

OnWebMessageReceivedtype으로 분기:

  • window.hide / paste.code → 기존.
  • snippets.*SnippetBridge(신규)에 위임: JSON에서 reqId·snippet·name 파싱 → SnippetRepository 호출 → SendToWebsnippets.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).