From db6f178be3e8a641dcbbc716605b6a4d98797991 Mon Sep 17 00:00:00 2001 From: LU Date: Fri, 14 Aug 2026 13:23:32 +0900 Subject: [PATCH] Prevent IME composition from submitting chat --- packages/workshop-frontend/src/ChatInterface.tsx | 6 ++++++ .../components/chat/composer-keyboard.test.ts | 16 ++++++++++++++++ .../src/components/chat/composer-keyboard.ts | 8 ++++++++ 3 files changed, 30 insertions(+) create mode 100644 packages/workshop-frontend/src/components/chat/composer-keyboard.test.ts create mode 100644 packages/workshop-frontend/src/components/chat/composer-keyboard.ts diff --git a/packages/workshop-frontend/src/ChatInterface.tsx b/packages/workshop-frontend/src/ChatInterface.tsx index 23988f3e2..c6ea91116 100644 --- a/packages/workshop-frontend/src/ChatInterface.tsx +++ b/packages/workshop-frontend/src/ChatInterface.tsx @@ -61,6 +61,7 @@ import { getStoredSelectedModel, persistSelectedModel, } from "./modelSelection"; +import { isImeComposing } from "./components/chat/composer-keyboard"; import { Overseer, GatekeeperClient, @@ -3368,6 +3369,11 @@ export const ChatInput = ({ } }} onKeyDown={(e) => { + // Enter confirms the highlighted candidate in Chinese/Japanese/Korean IMEs. Let + // the browser finish that composition instead of selecting a slash command or + // submitting the message. keyCode 229 covers older browsers/WebViews that do not + // reliably expose KeyboardEvent.isComposing. + if (isImeComposing(e.nativeEvent)) return; if (slashCommandPicker.open && e.key === "Escape") { e.preventDefault(); slashCommandPicker.dismiss(); diff --git a/packages/workshop-frontend/src/components/chat/composer-keyboard.test.ts b/packages/workshop-frontend/src/components/chat/composer-keyboard.test.ts new file mode 100644 index 000000000..24e036a69 --- /dev/null +++ b/packages/workshop-frontend/src/components/chat/composer-keyboard.test.ts @@ -0,0 +1,16 @@ +import { describe, expect, it } from "vitest"; +import { isImeComposing } from "./composer-keyboard"; + +describe("composer keyboard handling", () => { + it("recognizes a standards-based IME composition event", () => { + expect(isImeComposing({ isComposing: true, keyCode: 13 })).toBe(true); + }); + + it("recognizes the legacy IME process key used by older browsers", () => { + expect(isImeComposing({ isComposing: false, keyCode: 229 })).toBe(true); + }); + + it("leaves an ordinary Enter key available for message submission", () => { + expect(isImeComposing({ isComposing: false, keyCode: 13 })).toBe(false); + }); +}); diff --git a/packages/workshop-frontend/src/components/chat/composer-keyboard.ts b/packages/workshop-frontend/src/components/chat/composer-keyboard.ts new file mode 100644 index 000000000..10999896b --- /dev/null +++ b/packages/workshop-frontend/src/components/chat/composer-keyboard.ts @@ -0,0 +1,8 @@ +/** + * IMEs use Enter and arrow keys while composing candidate text. React exposes the standard + * `isComposing` flag on the native keyboard event; keyCode 229 covers older browsers/WebViews + * that only report the legacy "process key" value. + */ +export function isImeComposing(event: Pick): boolean { + return event.isComposing || event.keyCode === 229; +}