diff --git a/src/renderer/src/lib/ime-composition-keyboard-event.ts b/src/renderer/src/lib/ime-composition-keyboard-event.ts index 4d64bddefef..a5cebfa3a96 100644 --- a/src/renderer/src/lib/ime-composition-keyboard-event.ts +++ b/src/renderer/src/lib/ime-composition-keyboard-event.ts @@ -101,10 +101,15 @@ export function isImeCompositionKeyDown(event: AnyKeyboardEvent): boolean { if (keybindingInputIsImeOwned(nativeEvent)) { return true } - // Why: an Escape the IME owns is marked above, so an unmarked one means this - // derived flag has drifted. compositionend is not guaranteed, so swallowing it - // would strand every cancel path behind a flag nothing is going to clear. Same - // rule as TERMINAL_IME_OWNED_KEYS in xterm-bypass-policy.ts, which omits Escape - // for exactly this reason. + // Why: UI Events defines `isComposing` as true for any key event dispatched between + // compositionstart and compositionend, so an Escape the IME owns is marked above by + // definition. An unmarked one is the browser stating we are not between those two + // events — it disagrees with this derived flag, and the browser is the authority. + // Escape is the one key where trusting the flag instead is unrecoverable: compositionend + // is not guaranteed, so swallowing it strands every cancel path behind a flag nothing + // will clear. Same rule as TERMINAL_IME_OWNED_KEYS in xterm-bypass-policy.ts. + // + // Spec-backed, not engine-verified: CANDIDATE_ESCAPE_DISMISSAL_TRACE is derived, and a + // browser that under-reports the bit would contradict this. Recording it settles it. return nativeEvent.key !== 'Escape' && isDocumentImeCompositionActive() } diff --git a/src/renderer/src/lib/ime-composition-trace.test.ts b/src/renderer/src/lib/ime-composition-trace.test.ts index 0298def6dc1..c5ae31b6950 100644 --- a/src/renderer/src/lib/ime-composition-trace.test.ts +++ b/src/renderer/src/lib/ime-composition-trace.test.ts @@ -17,7 +17,7 @@ import { IBUS_HANGUL_RETAINED_COMMIT_TRACE, IBUS_HANGUL_TERMINAL_JAMO_COMMIT_TRACE, IME_COMPOSITION_TRACES -} from './ime-recorded-composition-traces.test-fixtures' +} from './ime-derived-composition-traces.test-fixtures' const KEY_EVENT_WITHOUT_REPEAT: ImeTraceKeyEvent = { code: 'KeyE', diff --git a/src/renderer/src/lib/ime-recorded-composition-traces.test-fixtures.ts b/src/renderer/src/lib/ime-derived-composition-traces.test-fixtures.ts similarity index 97% rename from src/renderer/src/lib/ime-recorded-composition-traces.test-fixtures.ts rename to src/renderer/src/lib/ime-derived-composition-traces.test-fixtures.ts index 50e096af549..263ea517135 100644 --- a/src/renderer/src/lib/ime-recorded-composition-traces.test-fixtures.ts +++ b/src/renderer/src/lib/ime-derived-composition-traces.test-fixtures.ts @@ -2,6 +2,10 @@ // // Adding a trace: prefer capturing one from a real engine and mark it `recorded`. // A `derived` trace pins current behavior but proves nothing about the engine. +// +// Everything defined below is `derived`. The only hardware recording in the suite is the +// ibus-hangul trace re-exported here. Read a derived trace as a written-down assumption +// waiting to be falsified by `pnpm ime:record`, not as evidence about any engine. import type { ImeCompositionTrace,