From 7e67f64fcee9340f40a1c6f912fc650aa4984510 Mon Sep 17 00:00:00 2001 From: quexiaolong Date: Mon, 24 Aug 2026 12:08:03 +0800 Subject: [PATCH] fix(desktop): bots group chat sends message on IME composition Enter MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit macOS Chinese pinyin IME: pressing Enter to confirm a candidate word in the group-chat composer submitted the draft as a message mid-composition. The GroupMentionInput onKeyDown checked only `event.key === 'Enter' && !event.shiftKey` with no IME guard, unlike the core composer which guards isComposing + keyCode 229 (#44135). Add the same guard to the three Enter handlers in the bots plugin: - GroupMentionInput (group composer + reply box) — the reported bug - GroupClarifyCard free-text answer input — same premature-submit - skill-hub search input — same premature-trigger Closes #93528 --- .../desktop/src/plugins/hermes-bots/plugin.js | 16 +++++++++++ .../hermes-bots/tests/group-room-ux.test.mjs | 27 +++++++++++++++++++ contributors/emails/quexl1@chinatelecom.cn | 1 + 3 files changed, 44 insertions(+) create mode 100644 contributors/emails/quexl1@chinatelecom.cn diff --git a/apps/desktop/src/plugins/hermes-bots/plugin.js b/apps/desktop/src/plugins/hermes-bots/plugin.js index eff1c26817..02185300a9 100644 --- a/apps/desktop/src/plugins/hermes-bots/plugin.js +++ b/apps/desktop/src/plugins/hermes-bots/plugin.js @@ -8951,6 +8951,10 @@ function HubSkillsSection({ forProfile, onInstalled }) { value: query, onChange: event => setQuery(event.target.value), onKeyDown: event => { + // IME guard: Enter confirming a composed word must not search. + if (event.nativeEvent?.isComposing || event.keyCode === 229) { + return + } if (event.key === 'Enter') { event.preventDefault() void search() @@ -11859,6 +11863,14 @@ function GroupMentionInput({ members, onChange, onSubmitDraft, value, ...inputPr }, onClick: event => refreshToken(event.target), onKeyDown: event => { + // IME composition guard (same as the core composer): Enter here + // confirms the composed Chinese/Japanese/Korean text — it must not + // insert a mention nor submit the draft. nativeEvent.isComposing + // covers Chromium; keyCode 229 covers macOS Chinese IMEs that fire + // Enter after compositionend with isComposing already false. + if (event.nativeEvent?.isComposing || event.keyCode === 229) { + return + } if (open) { if (event.key === 'ArrowDown') { event.preventDefault() @@ -12045,6 +12057,10 @@ function GroupClarifyCard({ entry, members }) { setDrafts(prev => ({ ...prev, [q.qid]: value })) }, onKeyDown: event => { + // IME guard: Enter confirming a composed word must not submit. + if (event.nativeEvent?.isComposing || event.keyCode === 229) { + return + } if (event.key === 'Enter' && questions.length === 1) { event.preventDefault() void submit() diff --git a/apps/desktop/src/plugins/hermes-bots/tests/group-room-ux.test.mjs b/apps/desktop/src/plugins/hermes-bots/tests/group-room-ux.test.mjs index cc32992b95..c52ede26d5 100644 --- a/apps/desktop/src/plugins/hermes-bots/tests/group-room-ux.test.mjs +++ b/apps/desktop/src/plugins/hermes-bots/tests/group-room-ux.test.mjs @@ -31,6 +31,33 @@ test('room composer is a textarea with Enter=submit, Shift+Enter=newline (#89884 assert.match(component, /insert\(options\[active\]\.handle\)/) }) +test('room composer swallows IME composition Enters before submit/mention (#93528)', () => { + // macOS Chinese IME: Enter that confirms a candidate word fires a keydown + // the composer mistook for a send — the message went out mid-composition. + // The guard must run BEFORE the popover branch AND the submit branch, and + // cover both Chromium's isComposing flag and the keyCode 229 legacy + // VK_PROCESSKEY that macOS IMEs emit after compositionend. + const start = source.indexOf('function GroupMentionInput') + const nextFn = source.indexOf('\nfunction ', start + 1) + const component = source.slice(start, nextFn) + + const guard = component.indexOf('event.nativeEvent?.isComposing || event.keyCode === 229') + assert.ok(guard >= 0, 'IME guard present in GroupMentionInput') + const afterGuard = component.slice(guard) + // Guard returns immediately (no mention insert, no submit)... + assert.match(afterGuard.slice(0, 80), /return/) + // ...and it sits before both the popover branch and the submit branch. + assert.ok(afterGuard.indexOf('if (open) {') > 0, 'guard precedes popover branch') + assert.ok(component.indexOf("event.key === 'Enter' && !event.shiftKey") > guard, 'guard precedes submit branch') +}) + +test('clarify free-text input swallows IME composition Enters (#93528)', () => { + const clarify = source.slice(source.indexOf('function GroupClarifyCard'), source.indexOf('function openGroupChat')) + const guard = clarify.indexOf('event.nativeEvent?.isComposing || event.keyCode === 229') + assert.ok(guard >= 0, 'IME guard present in GroupClarifyCard') + assert.ok(clarify.indexOf("event.key === 'Enter' && questions.length === 1") > guard, 'guard precedes submit branch') +}) + test('both room composers wire onSubmitDraft (#89884)', () => { assert.match(source, /onSubmitDraft: submit,/) assert.match(source, /onSubmitDraft: \(\) => submitReply\(id\),/) diff --git a/contributors/emails/quexl1@chinatelecom.cn b/contributors/emails/quexl1@chinatelecom.cn new file mode 100644 index 0000000000..0355895957 --- /dev/null +++ b/contributors/emails/quexl1@chinatelecom.cn @@ -0,0 +1 @@ +ShonnQ