From c9054ec058e5b91f4653ed0b1481977e1d3194bd Mon Sep 17 00:00:00 2001 From: cliffhall Date: Wed, 26 Aug 2026 09:38:52 -0400 Subject: [PATCH] fix(web): reject the IME keydown that only reports keyCode 229 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit isComposing covers browsers that fire compositionend after the committing keydown. WebKit is reported to fire it before, which leaves isComposing already false and the CJK user back to a stray newline on every committed word. Also reject keyCode 229, the pre-isComposing sentinel for a keydown the IME consumed, which such an event still carries. Deprecated, and kept anyway: nothing non-deprecated distinguishes that event, and it cannot swallow a real Enter, which reports 13 — tested both ways so the sentinel is provably not eating ordinary keystrokes. The ordering claim is second-hand; the guard is taken on its own merits, since its downside is bounded at zero and it covers any browser whose composition events land too late rather than one vendor. Signed-off-by: cliffhall --- .../groups/SchemaForm/SchemaForm.test.tsx | 27 +++++++++++++++++++ .../groups/SchemaForm/SchemaForm.tsx | 14 ++++++++++ 2 files changed, 41 insertions(+) diff --git a/clients/web/src/components/groups/SchemaForm/SchemaForm.test.tsx b/clients/web/src/components/groups/SchemaForm/SchemaForm.test.tsx index 992bf4ca..a11fe0b1 100644 --- a/clients/web/src/components/groups/SchemaForm/SchemaForm.test.tsx +++ b/clients/web/src/components/groups/SchemaForm/SchemaForm.test.tsx @@ -2110,6 +2110,33 @@ describe("SchemaForm enlarge keyboard access (#2138)", () => { expect(noteField().value).toBe(""); }); + // WebKit is reported to fire `compositionend` before the committing keydown, + // so `isComposing` is already false there and the guard above cannot see it. + // 229 is the older sentinel for "this key went to the IME", which that event + // still carries (#2139 review). + it("ignores a committing IME keydown that only reports keyCode 229", () => { + renderWithMantine(); + + fireEvent.keyDown(noteField(), { + key: "Enter", + isComposing: false, + keyCode: 229, + }); + + expect(noteField().tagName).toBe("INPUT"); + expect(noteField().value).toBe(""); + }); + + // The control for the sentinel: a real Enter reports 13 and must still work, + // so the guard cannot be swallowing ordinary keystrokes. + it("still enlarges on an Enter reporting keyCode 13", () => { + renderWithMantine(); + + fireEvent.keyDown(noteField(), { key: "Enter", keyCode: 13 }); + + expect(noteField().tagName).toBe("TEXTAREA"); + }); + // Same event without the flag, to prove the guard above is what turned it // away rather than fireEvent simply not reaching the handler. it("still enlarges on an Enter that is not composing", () => { diff --git a/clients/web/src/components/groups/SchemaForm/SchemaForm.tsx b/clients/web/src/components/groups/SchemaForm/SchemaForm.tsx index 1fdd748b..76603591 100644 --- a/clients/web/src/components/groups/SchemaForm/SchemaForm.tsx +++ b/clients/web/src/components/groups/SchemaForm/SchemaForm.tsx @@ -119,6 +119,12 @@ const TWO_ACTION_WIDTH = rem( ENLARGE_WIDTH + ACTION_GAP + CLEAR_WIDTH + SECTION_INSET, ); +// `keyCode` reported for a keydown the IME consumed — the pre-`isComposing` +// sentinel every browser still sets during composition. Only needed for the +// browsers whose composition events land too late for `isComposing` to help +// (#2138 review); a real Enter reports 13, so this cannot swallow one. +const IME_KEY_CODE = 229; + function serializeJson(value: unknown): string { return JSON.stringify(value, null, 2); } @@ -791,6 +797,13 @@ export function SchemaForm({ // keystroke means "accept this word", not "new line". Acting on it // would enlarge the field and insert a stray newline every time such a // user finished a word, making the field unusable for them. + // - `keyCode === 229` is the same guard again, for WebKit. It is reported + // to fire `compositionend` *before* the committing keydown, which + // leaves `isComposing` already false by the time this runs; 229 is the + // pre-`isComposing` sentinel for "this key went to the IME" and is + // still set there. Kept despite `keyCode` being deprecated because + // nothing non-deprecated distinguishes that event, and it cannot misfire + // on a real Enter, which reports 13. // - The Ctrl/Cmd/Alt chords are deliberately left alone: those read as // "submit" in a form, and a consumer binding one (run the tool) must // not be overridden into enlarging a field instead. @@ -798,6 +811,7 @@ export function SchemaForm({ if ( event.key !== "Enter" || event.nativeEvent.isComposing || + event.keyCode === IME_KEY_CODE || event.ctrlKey || event.metaKey || event.altKey