Skip to content

fix(terminal): don't hijack Space during IME composition (Korean input) - #75

Merged
abasiri merged 1 commit into
doctly:mainfrom
HAN-oQo:fix/korean-ime-space-input
Aug 1, 2026
Merged

fix(terminal): don't hijack Space during IME composition (Korean input)#75
abasiri merged 1 commit into
doctly:mainfrom
HAN-oQo:fix/korean-ime-space-input

Conversation

@HAN-oQo

@HAN-oQo HAN-oQo commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

Problem

Korean (and any IME-composed) input is corrupted in terminals: pressing space between words drops or reorders the in-progress Hangul syllable — e.g. typing produces or loses the syllable entirely. This makes CLI sessions nearly unusable for Korean/Japanese/Chinese typists.

Root cause

The push-to-talk Space handler added in #22 (public/terminal-manager.js) intercepts every plain-Space keydown, calls e.preventDefault(), writes a raw ' ' straight to the PTY, and returns false.

The problem is ordering. In xterm's _keyDown, the custom key handler runs before the IME composition helper:

if (this._customKeyEventHandler && false === this._customKeyEventHandler(e)) return false; // ← runs FIRST
if (!t && !this._compositionHelper.keydown(e)) return ..., false;                          // ← IME check runs AFTER

During Korean composition, the Space keydown fires with isComposing === true / keyCode === 229. The handler preventDefaults it (blocking the composition commit), sends a raw space out of order, and returns false so _compositionHelper.keydown() never runs. Since Korean is space-separated, this breaks essentially every word.

Fix

Gate the direct-send path on !isImeComposing(e) so it only fires when no IME is composing — which is exactly when push-to-talk needs it. During composition the event now falls through to xterm's composition helper for correct commit behavior.

function isImeComposing(e) { return e.isComposing === true || e.keyCode === 229; }

function shouldSendSpaceDirectly(e) {
  return e.key === ' '
    && !e.ctrlKey && !e.altKey && !e.metaKey && !e.shiftKey
    && !isImeComposing(e);
}

Push-to-talk (Hold Space to record) is unaffected: with no IME composing, isComposing is false and keyCode is 32, so the direct-send path still fires on every key-repeat.

Testing

  • Extracted the decision into a pure, exported predicate and added test/terminal-input.test.js (5 cases: plain space → direct send; composing via isComposing; composing via keyCode 229; modifier combos; non-space keys).
  • npm test → all green.
  • Not yet verified with live IME typing in the running app — reviewers with a Korean/Japanese/Chinese input source can confirm end-to-end.

🤖 Generated with Claude Code

The push-to-talk handler (doctly#22) intercepted every plain-Space keydown,
calling preventDefault() and writing a raw space straight to the PTY.
xterm's _keyDown runs the custom key handler BEFORE its composition
helper, so during Korean/Japanese/Chinese composition this blocked the
commit and reordered/dropped the in-progress syllable (e.g. "녕 " came
out as " 녕").

Gate the direct-send path on !isImeComposing(e) (isComposing, or the
Chromium keyCode 229 sentinel) so it only fires when no IME is composing
— exactly when push-to-talk needs it. During composition the event now
falls through to xterm's composition helper for correct commit behavior.

Extracts the decision as a pure, exported predicate and adds unit tests.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants