6.2 KiB
Research: 스니펫 팔레트 (Phase 0)
브레인스토밍·clarify에서 큰 결정은 끝났고(저장=로컬 SQLite, 핫키=Ctrl+1, UI=Raycast, 데이터=003 시드), 여기선 구현 미해결점만 결정한다.
R1. React ↔ SQLite 브릿지: 요청→응답 방식
결정: 기존 postMessage 채널을 reqId 상관관계(correlation) 왕복으로 확장. AddHostObjectToScript(COM 호스트 객체) 안 씀.
- JS→C#:
{type:"snippets.list", reqId}식으로 요청(각 호출마다 증가하는 reqId). - C#→JS:
Web.CoreWebView2.PostWebMessageAsJson({type:"snippets.result", reqId, ok, data|error})로 응답. - JS:
Map<reqId, {resolve,reject}>유지,chrome.webview.addEventListener("message")에서 매칭 reqId를 resolve.
Rationale:
- 기존 브릿지가 이미 postMessage(단방향)라 같은 메커니즘 하나로 통일 — 스타일 일관, 학습비용 0.
- COM 마샬링(hostObjects) 회피: 배열/객체 반환 시 JSON 문자열 마샬링이 번거롭고, 기존 코드와 이질적.
- SQLite 호출은 로컬·즉각이라 C#에서 동기로 처리 후 바로 응답 → async 복잡도 불필요(UI 스레드에서 안전, 기존 paste도 UI 스레드).
Alternatives 기각:
AddHostObjectToScript: 호출부는 깔끔(await hostObjects.snippets.list())하나 COM 세팅·마샬링·기존 스타일 이탈.- 별도 로컬 HTTP 서버(C#): 과함(YAGNI). 브릿지로 충분.
R2. C#→JS 방향(navigate 신호)
결정: CoreWebView2.PostWebMessageAsJson로 C#→JS 푸시. JS는 chrome.webview.addEventListener("message", …)로 수신. 이 채널을 (a) 스니펫 핫키 시 {type:"navigate", path:"/snippet"}와 (b) R1의 snippets.result 양쪽에 공용.
WebHostView에SendToWeb(object msg)추가(PostWebMessageAsJson(JsonSerializer.Serialize(msg))).- 프론트
bridgeNavigate.ts: navigate 메시지 수신 → 등록된 react-routernavigate콜백 호출. React 최상위에서 mount 시 콜백 등록.
Rationale: WebView2 기본 제공 기능, 의존성 0. 팔레트는 부팅 시 /snap으로 뜨므로(WebHostView StartPath), 스니펫은 이 navigate로 진입.
주의: 여러 addEventListener 등록 가능 → snippetBridge(결과용)와 bridgeNavigate(내비용)가 각자 리스너를 걸고 type으로 필터. 서로 결합 안 함.
R3. 멀티 핫키(C#)
결정: HotKeyService를 id별 다중 등록으로 확장.
- 현재: 단일
HotKeyId=0x1000, 단일HotKeyPressed(Action),ProcessMessage(int msg)가 wParam(=핫키 id)을 무시. - 변경:
Register(int id, uint mods, uint vk)— 등록 id를 인자로, 내부HashSet<int>로 추적, Dispose 시 전부 Unregister.event Action<int>? HotKeyPressed— 눌린 id 전달.ProcessMessage(int msg, IntPtr wParam)— wParam.ToInt32()가 핫키 id.App.WndProc가 wParam을 넘기도록 수정.
App: id0x1000=snap 토글(Ctrl+Alt+Space, 기존),0x1001=스니펫(Ctrl+1). 등록 실패해도 죽지 말고 트레이 안내(기존 패턴).
Ctrl+1 상수: MOD_CONTROL(0x2), VK_1(0x31). 핫키 조합은 App 상수 한 곳에 모아 나중에 바꾸기 쉽게(spec Edge: 전역 Ctrl+1 가로채기 감수).
스니펫 핫키 동작: 눌리면 ShowPalette()(앞창 캡처+표시, 기존) 후 SendToWeb({type:"navigate", path:"/snippet"}). 이미 /snippet에서 보이는 상태면 토글로 숨김(선택, 구현 단순하면 적용).
R4. SQLite 라이브러리·DB 경로
결정:
- 라이브러리:
Microsoft.Data.Sqlite(경량·MS 공식) 를CodeAssist.Shell.csproj에 추가. (현재 리포에 SQLite 참조 전무.) - DB 경로:
%LOCALAPPDATA%\CodeAssist\snippets.db(쓰기 가능·안정. WebView2 UserDataFolder(temp)와 별개 — temp는 캐시성). - 스키마: 003 그대로 —
snippets(name PK, desc, body, category),usage(name PK, count, last_used). (data-model.md 참조.) - 저장 방식: 003의 통짜 replace 대신 행 단위 upsert/delete로(개별 CRUD API라 그게 자연스러움). 랭킹·검색은 프론트가 하므로 C# repo는 순수 CRUD + usage upsert만.
Rationale: MS 공식·의존성 가벼움. SQLitePCLRaw 번들 포함이라 네이티브 배포도 무난.
R5. 첫 실행 시드(003 데이터 1회 복사)
결정: SnippetSeed — 앱 시작 시 대상 DB(%LOCALAPPDATA%\CodeAssist\snippets.db)가 없을 때만, 003의 snippets.db가 알려진 경로에 있으면 그 파일을 대상 경로로 복사. 있으면(=이미 시드됨) 아무것도 안 함. 003 파일이 없으면 빈 DB로 시작(에러 아님).
- 003 경로 확인은 구현 시점 lookup: 003(winkit)의 데이터 디렉토리 규칙(
winkit.shell데이터 폴더 +snippets.db)을 그때 확인해 상수화. 못 찾으면 빈 DB로 폴백(기능 안 막음). - 파일 복사 한 번이라 "임포트 기능"급 작업 아님(spec FR-024).
R6. 프리뷰 렌더링
결정: 프리뷰·body 표시는 순수 <pre> 고정폭(공백·줄바꿈 원문 보존). react-markdown 안 씀.
Rationale: body는 마크다운이 아니라 날 코드다. 마크다운 파싱하면 특수문자(*,#,`)가 깨지고 붙여넣을 원문과 화면이 달라짐. <pre>가 요구사항(원문 그대로·고정폭·no-wrap)을 정확히 만족. → docs-lib(react-markdown) 트리거 안 함.
R7. 순수 브라우저(dev) 폴백
결정: 스니펫 기능은 데스크톱(WebView2) 전용. 순수 브라우저에서 isWebView()===false면 snippets.api는 빈 목록/명확한 비활성 상태를 반환(throw 대신 graceful). 붙여넣기·핫키·SQLite 모두 호스트가 필요하므로 순수 브라우저 지원은 비목표.
Rationale: DEBUG 개발도 WebView2 안에서 vite를 물어 돌리므로(WebHostView가 localhost:15173을 WebView2로 네비) 브릿지가 살아있음 → 개발 지장 없음. 순수 크롬 탭 지원은 YAGNI.
미해결/구현 시점 확인 목록
- 003
snippets.db실제 경로(R5) — winkit 데이터 디렉토리 규칙 확인 후 상수화. Microsoft.Data.Sqlite버전 핀 — 구현 시 최신 안정.