diff --git a/src/components/InputBar.test.ts b/src/components/InputBar.test.ts index 5172973..59598e0 100644 --- a/src/components/InputBar.test.ts +++ b/src/components/InputBar.test.ts @@ -5,6 +5,7 @@ import { getAvailableSlashCommands, getSlashCommandReplacement, isClearCommandText, + shouldSubmitOnEnter, } from "./input-bar"; describe("InputBar slash command helpers", () => { @@ -46,4 +47,34 @@ describe("InputBar slash command helpers", () => { getSlashCommandReplacement({ name: "fix", description: "", source: "codex-skill", defaultPrompt: "bug" }), ).toBe("$fix bug"); }); + + it("does not submit enter while IME composition is active", () => { + expect( + shouldSubmitOnEnter({ + key: "Enter", + shiftKey: false, + isComposing: true, + }), + ).toBe(false); + expect( + shouldSubmitOnEnter({ + key: "Enter", + shiftKey: false, + nativeEvent: { isComposing: true }, + }), + ).toBe(false); + expect( + shouldSubmitOnEnter({ + key: "Enter", + shiftKey: false, + nativeEvent: { keyCode: 229 }, + }), + ).toBe(false); + }); + + it("submits enter only for non-composition send shortcut", () => { + expect(shouldSubmitOnEnter({ key: "Enter", shiftKey: false })).toBe(true); + expect(shouldSubmitOnEnter({ key: "Enter", shiftKey: true })).toBe(false); + expect(shouldSubmitOnEnter({ key: "a", shiftKey: false })).toBe(false); + }); }); diff --git a/src/components/input-bar/InputBar.tsx b/src/components/input-bar/InputBar.tsx index 9378449..9dc135d 100644 --- a/src/components/input-bar/InputBar.tsx +++ b/src/components/input-bar/InputBar.tsx @@ -46,6 +46,7 @@ import { extractEditableContent, getAvailableSlashCommands, isClearCommandText, + shouldSubmitOnEnter, } from "./input-bar-utils"; import { ContextGauge } from "./ContextGauge"; import { AttachmentPreview } from "./AttachmentPreview"; @@ -543,7 +544,7 @@ export const InputBar = memo(function InputBar({ return; } - if (e.key === "Enter" && !e.shiftKey) { + if (shouldSubmitOnEnter(e)) { e.preventDefault(); if (!isSending && !isAwaitingAcpOptions) { handleSend(); diff --git a/src/components/input-bar/index.ts b/src/components/input-bar/index.ts index 1c4dc31..c8c6bb7 100644 --- a/src/components/input-bar/index.ts +++ b/src/components/input-bar/index.ts @@ -7,4 +7,5 @@ export { getAvailableSlashCommands, getSlashCommandReplacement, isClearCommandText, + shouldSubmitOnEnter, } from "./input-bar-utils"; diff --git a/src/components/input-bar/input-bar-utils.ts b/src/components/input-bar/input-bar-utils.ts index ccec8d2..d676395 100644 --- a/src/components/input-bar/input-bar-utils.ts +++ b/src/components/input-bar/input-bar-utils.ts @@ -146,6 +146,27 @@ export function fuzzyMatch( return { match: false, score: 0 }; } +type EnterKeyEventLike = { + key: string; + shiftKey: boolean; + isComposing?: boolean; + nativeEvent?: { + isComposing?: boolean; + keyCode?: number; + }; +}; + +/** Enter should submit only when not composing text via an IME. */ +export function shouldSubmitOnEnter(event: EnterKeyEventLike): boolean { + if (event.key !== "Enter" || event.shiftKey) return false; + const nativeEvent = event.nativeEvent; + return !( + event.isComposing || + nativeEvent?.isComposing || + nativeEvent?.keyCode === 229 + ); +} + // ── Slash command helpers (exported for tests + external consumers) ── export const LOCAL_CLEAR_COMMAND: SlashCommand = {