"IME 는 확정 전에 키들을 한 글자로 조립해. 키마다 반응하는 에디터는 그 조립을 도중에 깨뜨리고."
IME 가 실제로 하는 일
한국어를 칠 때 키보드가 보내는 건 완성된 글자가 아니야. IME 가 눈앞에서 실시간으로 음절 블록을 조립하는 자모들이지. ㅎ 치고 ㅏ 치고 ㄴ 치면 한 이 되는데, 조합이 확정된 다음에야 그래. 일본어 가나-한자 변환이나 중국어 병음도 똑같아. 키는 여럿, 확정되는 글자는 하나, 그리고 그 사이에 조립 중인 상태가 있어.
순진한 에디터가 왜 깨뜨리나
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 에서 실제로 있었던 일이야. 소스 모듈 안에 들어 있던 한국어, 예를 들면 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 로 만드는 거였어. 교훈은 여기서만 통하는 게 아니야. non-ASCII 텍스트가 프로젝트에서 진짜 비중을 갖는 순간, 아빠한텐 늘 그렇지만, 인코딩 버그 한 부류를 통째로 물려받게 돼. 빌릴 수 있는 레이어는 전부 빌리고 네가 감당할 표면적을 작게 유지해야 할 이유가 하나 더 생기는 거지.