C.W.K.
Stream
Lesson 02 of 04 · published

Korean IME Is Where Editors Die

~12 min · ime, composition, korean, war-story

Level 0Cold Draft
0 XP0/33 lessons0/12 achievements
0/100 XP to next level100 XP to go0% complete
"An IME assembles keystrokes into a character before it commits. An editor that reacts to every keystroke corrupts it mid-air."

What an IME Actually Does

Type Korean and you're not sending finished characters — you're sending jamo that an Input Method Editor assembles into a syllable block live, in front of you. then then becomes , but only after the composition commits. The same is true of Japanese kana-to-kanji and Chinese pinyin: multiple keystrokes, one committed character, an in-flight state in between.

Why Naive Editors Corrupt It

An editor that rewrites its document on every raw input event walks straight into the composition and stomps it — dropping jamo, duplicating characters, or committing half a syllable. The correct handling respects the composition lifecycle: know when composition starts, leave the in-progress text alone while it updates, and only apply the edit when it ends.

el.addEventListener("compositionstart", () => { composing = true; });
el.addEventListener("compositionupdate", () => { /* jamo assembling: do NOT commit */ });
el.addEventListener("compositionend", (e) => { composing = false; apply(e.data); });

CodeMirror 6 handles this entire lifecycle internally. Standing on it means you never write those three lines — and never spend the week it takes to debug them across browsers. This is the single biggest reason Rekindle borrows the core: IME is where hand-rolled editors go to die, and Dad writes in Korean every day.

The Bytes Bite Even in Your Source

Non-ASCII pain doesn't stop at the editing surface — it reaches your own source files. A real Rekindle war story: Korean sitting inside a source module (a voice-rule regex, for instance) worked in a production build and in Chromium but silently broke in tauri dev. The cause was byte decoding, not logic — and a charset=utf-8 header didn't fix it, because a module script is always UTF-8 by spec regardless of Content-Type.

Symptom:  Korean in a source module works in prod + Chromium,
          silently breaks in `tauri dev`.
Cause:    Vite serves ES modules over HTTP; WKWebView mis-decodes
          raw non-ASCII bytes on that transport.
Fix:      A Vite plugin escapes non-ASCII to \uXXXX in served output.
          Decode-invariant: no webview can corrupt \u-escaped ASCII.

The fix keeps source files readable Korean and makes only the served transport ASCII-only. The lesson generalizes: the moment non-ASCII text is real (and for Dad it always is), you inherit an entire class of encoding bugs — one more reason to borrow every layer you can and keep your own surface area small.

Code

Respecting the IME composition lifecycle (CM6 does this for you)·javascript
// BROKEN for IME: mutating the doc on every input event fights composition.
el.addEventListener("input", () => apply(el.value));

// CORRECT: respect the composition lifecycle.
let composing = false;
el.addEventListener("compositionstart", () => { composing = true; });
el.addEventListener("compositionupdate", () => { /* jamo assembling — do NOT commit */ });
el.addEventListener("compositionend", (e) => { composing = false; apply(e.data); });

// CodeMirror 6 owns this entire lifecycle internally.
// Borrowing the core means you never write — or debug — these lines.
War story: non-ASCII in a served module breaks in tauri dev·text
Symptom:  Korean in a source module works in a prod build + Chromium,
          silently breaks in `tauri dev`.
Cause:    Vite serves ES modules over HTTP; WKWebView mis-decodes raw
          non-ASCII bytes on that transport. A charset=utf-8 header does
          NOT help (a module script is always UTF-8 per spec).
Fix:      A Vite plugin escapes non-ASCII to \uXXXX in the served module
          output. Decode-invariant: no webview can corrupt \u-escaped ASCII.

# Source files stay readable Korean; only the served transport goes ASCII-only.

External links

Exercise

If you can type in Korean, Japanese, or Chinese, open any web text box and type a composed word slowly, watching the character assemble before it commits. That in-between state — visible but not yet committed — is the composition lifecycle. Now imagine an editor that saves to disk on every keystroke: at which moment would it corrupt your word?
Hint
It corrupts during 'compositionupdate' — the jamo are assembling but nothing is final. An editor that treats those intermediate keystrokes as committed edits will fight the IME and mangle the syllable.

Progress

Progress is local-only — sign in to sync across devices.
Spotted a bug or have feedback on this page?Report an Issue

Comments 0

🔔 Reply notifications (sign in)
Sign inPlease sign in to comment.

No comments yet — be the first.