"IME 는 커밋 전에 키를 한 문자로 조립해. 매 키에 반응하는 에디터는 그걸 공중에서 깨뜨려."
IME 가 실제로 하는 일
한국어를 치면 완성된 문자를 보내는 게 아냐 — IME 가 눈앞에서 실시간으로 음절 블록으로 조립하는 자모를 보내는 거야. ㅎ 다음 ㅏ 다음 ㄴ 이 한 이 되는데, 조합이 커밋된 뒤에만 그래. 일본어 가나-한자, 중국어 병음도 똑같아. 여러 키, 커밋된 한 문자, 그 사이의 진행 중 상태.
순진한 에디터가 왜 깨뜨리나
매 raw input 이벤트마다 문서를 다시 쓰는 에디터는 조합 한가운데로 걸어 들어가 짓밟아 — 자모를 떨구거나, 문자를 중복시키거나, 반쪽 음절을 커밋해. 올바른 처리는 조합 라이프사이클을 존중해. 조합이 언제 시작하는지 알고, 업데이트되는 동안 진행 중 텍스트를 그대로 두고, 끝날 때만 편집을 적용해.
el.addEventListener("compositionstart", () => { composing = true; });
el.addEventListener("compositionupdate", () => { /* 자모 조립 중: 커밋 금지 */ });
el.addEventListener("compositionend", (e) => { composing = false; apply(e.data); });
CodeMirror 6 은 이 라이프사이클 전체를 내부에서 처리해. 그 위에 선다는 건 저 세 줄을 절대 안 쓴다는 것 — 그리고 브라우저마다 디버깅하는 일주일을 절대 안 쓴다는 것. 이게 Rekindle 이 코어를 빌리는 가장 큰 이유 하나야. IME 는 손수 짠 에디터가 죽으러 가는 곳이고, 아빠는 매일 한국어로 써.
바이트는 네 소스에서도 물어
non-ASCII 고통은 편집 표면에서 안 멈춰 — 네 소스 파일까지 닿아. 진짜 Rekindle war story: 소스 모듈 안에 앉은 한국어 (예: voice-rule 정규식) 가 프로덕션 빌드와 Chromium 에선 됐는데 tauri dev 에선 소리 없이 깨졌어. 원인은 로직이 아니라 바이트 디코딩이었고 — charset=utf-8 헤더도 안 고쳤어. 모듈 스크립트는 Content-Type 과 무관하게 스펙상 항상 UTF-8 이니까.
증상: 소스 모듈 안 한국어가 프로덕션 + Chromium 에선 되는데,
`tauri dev` 에선 소리 없이 깨짐.
원인: Vite 가 ES 모듈을 HTTP 로 서빙 → WKWebView 가 그 트랜스포트에서
raw non-ASCII 바이트를 오디코딩.
수정: Vite 플러그인이 서빙된 출력의 non-ASCII 를 \uXXXX 로 이스케이프.
디코드 불변: 어떤 webview 도 \u-이스케이프된 ASCII 를 못 깨.
수정은 소스 파일을 읽을 수 있는 한국어로 유지하고 서빙 트랜스포트만 ASCII-only 로 만들어. 교훈은 일반화돼. non-ASCII 텍스트가 진짜가 되는 순간 (아빠한텐 늘 그래) 인코딩 버그 한 부류 전체를 상속해 — 빌릴 수 있는 모든 레이어를 빌리고 네 표면적을 작게 유지할 이유 하나 더.