From 39392226ec50ebd03f4295bf4cfa9f3727941849 Mon Sep 17 00:00:00 2001 From: Hyunbong Jang <51194127+Brightbong92@users.noreply.github.com> Date: Mon, 12 Oct 2026 22:25:41 +0900 Subject: [PATCH 1/2] fix(AiDock): ignore Enter during IME composition The dock input submits on any Enter keydown. With an IME, Enter is also the key that confirms a conversion, so the question is sent while the user is still composing. It also leaves text behind: submitQuestion() clears the field, but the IME commit is still pending, so the committed text lands in the now-empty input. Check event.nativeEvent.isComposing before submitting, matching RetryComposer, DraftBot, CommandPalette, ComposerReferences and ConversationSession. Fixes #1362 --- src/components/AiDock.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/AiDock.tsx b/src/components/AiDock.tsx index 91cfcf00b..9ebf15b74 100644 --- a/src/components/AiDock.tsx +++ b/src/components/AiDock.tsx @@ -1989,7 +1989,7 @@ function InputBar({ refine(nextQuery) }} onKeyDown={(event) => { - if (event.key === 'Enter') { + if (event.key === 'Enter' && !event.nativeEvent.isComposing) { event.preventDefault() submitQuestion() } From 879eb99d7c51f2cf4fd94adad863a5db735c9116 Mon Sep 17 00:00:00 2001 From: Hyunbong Jang <51194127+Brightbong92@users.noreply.github.com> Date: Tue, 20 Oct 2026 09:36:52 +0900 Subject: [PATCH 2/2] fix(AiDock): also skip IME Enter that arrives after compositionend Safari fires compositionend before the Enter keydown for Chinese and Japanese input, so isComposing is already false there. keyCode is still 229 on that keydown, so check it as well. Move the check into submitOnEnter() in src/utils/ai-dock-enter.ts so it can be tested. AiDock.tsx can't be imported under tsx --test because of its .svg imports. Related to #1362 --- src/components/AiDock.tsx | 8 ++------ src/utils/ai-dock-enter.ts | 19 +++++++++++++++++++ tests/ai-dock-enter.test.ts | 37 +++++++++++++++++++++++++++++++++++++ 3 files changed, 58 insertions(+), 6 deletions(-) create mode 100644 src/utils/ai-dock-enter.ts create mode 100644 tests/ai-dock-enter.test.ts diff --git a/src/components/AiDock.tsx b/src/components/AiDock.tsx index 9ebf15b74..414c9a3ce 100644 --- a/src/components/AiDock.tsx +++ b/src/components/AiDock.tsx @@ -45,6 +45,7 @@ import { CodeBlock } from '~/components/markdown/CodeBlock' import { copyTextToClipboard } from '~/utils/browser-effects' import { InlineCode } from '~/ui/InlineCode' import { isSafeHref } from '~/utils/url-boundary' +import { submitOnEnter } from '~/utils/ai-dock-enter' import { type AlgoliaHit, dedupeSearchHitsByPage, @@ -1988,12 +1989,7 @@ function InputBar({ setSearchQuery(nextQuery) refine(nextQuery) }} - onKeyDown={(event) => { - if (event.key === 'Enter' && !event.nativeEvent.isComposing) { - event.preventDefault() - submitQuestion() - } - }} + onKeyDown={(event) => submitOnEnter(event, submitQuestion)} className={twMerge( 'w-full outline-none [&::-webkit-search-cancel-button]:hidden bg-transparent text-gray-800 dark:text-gray-100 placeholder:text-gray-400 dark:placeholder:text-gray-500', isDock ? 'text-[13px]' : 'text-sm', diff --git a/src/utils/ai-dock-enter.ts b/src/utils/ai-dock-enter.ts new file mode 100644 index 000000000..cfc6c991c --- /dev/null +++ b/src/utils/ai-dock-enter.ts @@ -0,0 +1,19 @@ +type EnterKeyEvent = { + key: string + nativeEvent: Pick + preventDefault: () => void +} + +export function submitOnEnter(event: EnterKeyEvent, submit: () => void) { + if ( + event.key !== 'Enter' || + event.nativeEvent.isComposing || + // Safari can fire compositionend before this keydown, so isComposing is + // already false. keyCode is deprecated, but it is still 229 here. + event.nativeEvent.keyCode === 229 + ) { + return + } + event.preventDefault() + submit() +} diff --git a/tests/ai-dock-enter.test.ts b/tests/ai-dock-enter.test.ts new file mode 100644 index 000000000..b70196041 --- /dev/null +++ b/tests/ai-dock-enter.test.ts @@ -0,0 +1,37 @@ +import assert from 'node:assert/strict' +import test from 'node:test' +import { submitOnEnter } from '../src/utils/ai-dock-enter' + +function press(key: string, isComposing: boolean, keyCode: number) { + let prevented = 0 + let submitted = 0 + submitOnEnter( + { + key, + nativeEvent: { isComposing, keyCode }, + preventDefault: () => { + prevented++ + }, + }, + () => { + submitted++ + }, + ) + return { prevented, submitted } +} + +test('Enter during composition neither submits nor prevents default', () => { + assert.deepEqual(press('Enter', true, 229), { prevented: 0, submitted: 0 }) +}) + +test('Enter with isComposing=false and keyCode=229 neither submits nor prevents default', () => { + assert.deepEqual(press('Enter', false, 229), { prevented: 0, submitted: 0 }) +}) + +test('ordinary Enter submits once', () => { + assert.deepEqual(press('Enter', false, 13), { prevented: 1, submitted: 1 }) +}) + +test('non-Enter key does not submit', () => { + assert.deepEqual(press('a', false, 65), { prevented: 0, submitted: 0 }) +})