Repository navigation
[Bug]: Cmd/Option+Arrow during IME composition moves the composing character to the cursor destination (Korean & Japanese, macOS) #12871
Description
Operating system
macOS
Orca version
v1.4.173
Details
Short summary
In the integrated terminal, pressing a cursor-movement chord (Cmd+←/→, Option+←/→) while an IME composition is active moves the cursor and takes the in-progress (preedit) character along with it. The composing character ends up committed at the cursor's destination instead of at the position where it was being typed.
With the Korean 2-Set IME the character is relocated. With the Japanese Kotoeri (Romaji) IME the relocated preedit additionally overwrites the character already at the destination, so previously committed text is lost.
What happened?
Typing 가나 다라 마바사 with the macOS Korean 2-Set input source (the final syllable 사 still composing), then pressing:
Option+←(move back one word) produces가나 '사'다라 마바Cmd+←(move to line start) produces'사'가나 다라 마바
(quotes mark the relocated syllable — they are not actually typed)
The composing 사 follows the cursor jump and is inserted at the destination.
Japanese reproduces it too. With committed text 가나 다라さ on the line and a new さ still in preedit, pressing Cmd+← produces 'さ'나 다라さ — the preedit jumped to the line start and replaced the first character (가 is gone).
How can we reproduce it?
- macOS, integrated terminal, with a TUI input line active (reproduced with the Claude Code CLI input).
- Select the Korean 2-Set input source.
- Type
가나 다라 마바사and stop — do not press Space/Enter, so사is still composing. - Press
Option+←orCmd+←. - Observe the composing syllable move to wherever the cursor lands (
가나 '사'다라 마바/'사'가나 다라 마바).
Japanese variant:
- Same terminal, some committed text already on the line.
- Select Japanese – Romaji (Kotoeri) and type
sasoさis in preedit. - Press
Cmd+←. - The preedit
さjumps to the line start and overwrites the character that was there.
Expected behavior
A cursor-movement chord during composition should behave like native macOS text fields and Terminal.app: the active composition is committed in place first (or the chord is ignored), and only then does the cursor move. The preedit must never travel with the cursor or overwrite existing text.
Actual behavior
The movement chord reaches the terminal while the composition is still open, so the composition session survives the cursor move and later commits at the new cursor position. In the Japanese case the relocated preedit replaces the glyph at the destination cell.
Observations that may help locate the cause
- A bare arrow key during composition behaves differently: it commits the pending syllable in place (as expected). Only modifier+arrow chords leak through while the composition stays active, which suggests the chord path (shortcut/keyboard resolver → escape sequence to the PTY) is evaluated without waiting for the pending composition, while the bare-key path is composition-aware.
- The preedit is rendered at the cursor cell, so once the cursor moves mid-composition the preedit visually re-anchors to the new position, and the eventual commit lands there. Possibly related rendering context: [Bug]: Korean IME preedit overlay hides the character right after the cursor when composing mid-line (macOS) #12545 (preedit overlay anchored to the cursor cell).
- Reproduced with both macOS built-in Korean 2-Set and Kotoeri Romaji, so it is IME-generic rather than Korean-specific. Vietnamese/Chinese IMEs are likely affected as well but were not tested.
Suggested fix direction
On keydown of a cursor-movement chord while a composition is active, finalize the composition at its current position before forwarding the movement escape sequence to the PTY (mirroring what already happens for bare arrow keys), or alternatively swallow the chord entirely during composition.
The pending #12732 sequences shortcut bytes behind an open composition and lists Cmd+←/→ and Alt+←/→ among the affected chords, so it looks like it would resolve this symptom as well — this report adds the user-visible reproduction for those chords (relocation, plus the Japanese overwrite variant) in case it is useful as a verification scenario. Happy to help test a build, and I am preparing regression tests that pin these two scenarios.
Environment
- macOS 26.5 (Apple Silicon)
- Orca v1.4.173, local workspace, integrated terminal
- IMEs: macOS Korean 2-Set (
com.apple.inputmethod.Korean.2SetKorean), Japanese Kotoeri Romaji (com.apple.inputmethod.Kotoeri.RomajiTyping.Japanese) - Reproduced inside a Claude Code CLI session (raw-mode TUI input line).
Activity
test(terminal): pin cursor-chord relocation regressions during IME co…
nwparker commentedon Aug 8, 2026
Still open — this one isn't fixed, and the fix path you pointed at needs rethinking.
We landed a large IME change on main (17b3dff) that removes the first-party composition layer and hands composition back to xterm. #12732 was building on
sendTerminalInputAfterComposition/deferredNewlineSender, and both are gone, so it can't land as written.Your diagnosis still holds though, and it's the useful part: bare arrows are composition-aware while modifier+arrow chords are not. That asymmetry is in the chord path, independent of the deferral machinery we removed. The replacement is to have the chord path consult composition state directly —
isImeOwnedKeyboardEventin@/lib/ime-composition-keyboard-eventis what the rest of the surfaces now use for this.Worth flagging one thing we learned the hard way: gate on
event.code, notevent.key. A CJK input source rewriteskeywhilecodekeeps the physical key, and that alone caused two separate issues (#12171, #13033).Yes please to the regression tests — the Japanese overwrite variant especially. Recorded traces beat hand-written event shapes here; real IMEs produce sequences nobody guesses right.
ethznn commentedon Aug 8, 2026
Recorded evidence update: on current
main(d64ccc71b) this no longer reproduces on macOS — in any of the five scenarios I recorded.Following your note, I captured real-IME traces (DOM events +
terminal.onData+ PTY-side bytes, boundary-probe format) plus screenshot series on stock macOS, and manually re-verified by typing on a real keyboard (Korean and Japanese) in the same dev build, for: the two Korean relocation cases (Option+←,Cmd+←with사composing),Cmd+→, the Japanese overwrite variant (Kotoeriさpreedit over committed text,Cmd+←), and a paired ABC negative.Findings:
compositionendfires with the syllable, and the PTY receives…마바사before the movement byte (onDatachunk is"사\x01"). Screenshots at keydown+1 frame and +300ms are pixel-identical: no relocation, no transient glitch.compositionend, no movement byte reaches the PTY, the preedit stays at the cursor, and the destination glyph survives. No overwrite.keyCode: 229, isComposing: true, withkey/codeunrewritten — soisImeOwnedKeyboardEventalready matches these events as-is.Your structural point stands, though: nothing in the chord path consults composition state deliberately, so the current correct ordering is a property of the OS-IME + xterm-owned composition interplay rather than anything pinned. That seems worth locking down — I'm preparing a tests-only PR that transcribes these traces into regression tests (the Korean relocation cases, the Japanese variant you asked for, and the negative). If you suspect remaining gaps on shapes I haven't recorded — Linux ibus/fcitx5, Windows, phrase-level Chinese — point me at them and I'll record those too.
ethznn commentedon Aug 8, 2026
Update on where the tests went: they are on #12732 rather than a standalone PR — the chord rework landed there, and the fixtures belong with the behavior they verify. Two commits on
ethznn:test/ime-chord-traces-v3, offered there for cherry-pick: the in-app trace fixtures pooled into that PR's replay rig, and a native macOS e2e spec asserting pty byte order for the Korean, Japanese and non-IME cases. Happy to split them into their own PR instead if you would rather tests land separately.One measurement worth surfacing here, because it widens this issue rather than closing it. The relocation symptom this was filed for is gone on current
main, but the other half — the chord being swallowed outright during composition — is IME-general rather than a Kotoeri quirk. Across ten states (Kotoeri converted / live-converted / unconverted single / unconverted multi, Simplified Pinyin with and without a candidate window, Traditional Zhuyin), the OS delivers the chord keydown (ArrowLeft, keyCode 229,isComposing: true) at window capture and then never delivers its keyup at any level, so nothing downstream can recover the press. 2-Set Korean is the exception: it ends the composition on the chord itself, and the keyup arrives withisComposing: false. Verified with synthesized keys and re-checked by hand on a real keyboard. Traces and the full table are on #12732.fix(terminal): end a composing Cmd chord on the Command release
ethznn commentedon Aug 8, 2026
Closing the loop on my last note here: the Pinyin and Zhuyin cases I flagged as affected are covered by the fix on #12732 as of
84517d74b— the Command chord now resolves on the Command key's own release, which is delivered while the composition is still live. Verified by hand as well as in the rig: with a Pinyin preedit up,Cmd+←then commit puts\x01on the pty and the cursor at line start. Nothing further needed from my side here; the remaining discussion is on the PR.fix(terminal): hold a cursor chord until the composing syllable commi…
fix(terminal): hold a cursor chord until the composing syllable commi…