fix(chat): prevent message send during IME composition (#107)

* fix(chat): prevent message send during IME composition

Add isComposing check to ChatInput to prevent messages from being sent
during Japanese/CJK character conversion. This fixes the issue where
pressing Enter to confirm IME composition would immediately send the
message instead of just completing the character conversion.

Changes:
- Add early return in handleKeyDown when IME is composing
- Add explicit isComposing check to Enter key handler

Fixes issue where Enter during IME composition sends incomplete text.

* fix(chat): improve IME composition detection with keyCode 229 fallback

Add isIMECompositionEvent helper function that checks both isComposing
and keyCode === 229, as recommended by MDN. This fixes IME input issues
on WebKit-based browsers (including Tauri WebView on macOS) where
compositionend may fire before keydown, causing isComposing to be false
when Enter key is processed.

Changes:
- Add isIMECompositionEvent helper function with dual detection
- Update handleKeyDown to use the new helper for IME checks

Fixes MacUI desktop app issue where pressing Enter during IME
composition would send the message instead of confirming the input.

* fix(agent-manager): prevent message send during IME composition

Apply the same IME composition fix as ChatInput to AgentManagerEmptyState.
Add isIMECompositionEvent helper and check it in handleKeyDown to prevent
accidental form submission when confirming IME input with Enter key.

* fix(agent-manager): add redundant IME check to Enter key handler

Add explicit isIMECompositionEvent check to the Enter key condition in
handleKeyDown. Although the early return already handles this, this
change ensures the implementation pattern matches ChatInput.tsx exactly
for consistency and safety.
This commit is contained in:
madebyjun
2026-01-06 19:48:18 +02:00
committed by GitHub
parent af01995c3b
commit 8aa379e313
2 changed files with 28 additions and 2 deletions
+14 -1
View File
@@ -47,6 +47,16 @@ interface ChatInputProps {
const isPrimaryMode = (mode?: string) => mode === 'primary' || mode === 'all' || mode === undefined || mode === null;
/**
* Detects if a keyboard event is part of IME composition.
* Uses both isComposing and keyCode === 229 (MDN recommended).
* WebKit may fire compositionend before keydown, causing isComposing to be false
* while keyCode remains 229, so both checks are needed.
*/
const isIMECompositionEvent = (e: React.KeyboardEvent): boolean => {
return e.nativeEvent.isComposing || e.nativeEvent.keyCode === 229;
};
export const ChatInput: React.FC<ChatInputProps> = ({ onOpenSettings, scrollToBottom }) => {
const [message, setMessage] = React.useState('');
const [isDragging, setIsDragging] = React.useState(false);
@@ -476,6 +486,9 @@ export const ChatInput: React.FC<ChatInputProps> = ({ onOpenSettings, scrollToBo
}, [sessionPhase, queuedMessages.length, currentSessionId, currentProviderId, currentModelId, sessionAbortFlags]);
const handleKeyDown = (e: React.KeyboardEvent<HTMLTextAreaElement>) => {
// Early return during IME composition to prevent interference with autocomplete
// Uses keyCode === 229 fallback for WebKit where compositionend fires before keydown
if (isIMECompositionEvent(e)) return;
if (showCommandAutocomplete && commandRef.current) {
if (e.key === 'Enter' || e.key === 'ArrowUp' || e.key === 'ArrowDown' || e.key === 'Escape' || e.key === 'Tab') {
@@ -508,7 +521,7 @@ export const ChatInput: React.FC<ChatInputProps> = ({ onOpenSettings, scrollToBo
}
// Handle Enter/Ctrl+Enter based on queue mode
if (e.key === 'Enter' && !e.shiftKey && !isMobile) {
if (e.key === 'Enter' && !e.shiftKey && !isMobile && !isIMECompositionEvent(e)) {
e.preventDefault();
const isCtrlEnter = e.ctrlKey || e.metaKey;
@@ -23,6 +23,16 @@ const MAX_FILE_SIZE = 10 * 1024 * 1024;
/** Max number of concurrent runs */
const MAX_MODELS = 5;
/**
* Detects if a keyboard event is part of IME composition.
* Uses both isComposing and keyCode === 229 (MDN recommended).
* WebKit may fire compositionend before keydown, causing isComposing to be false
* while keyCode remains 229, so both checks are needed.
*/
const isIMECompositionEvent = (e: React.KeyboardEvent): boolean => {
return e.nativeEvent.isComposing || e.nativeEvent.keyCode === 229;
};
/** Attached file for agent manager */
interface AttachedFile {
id: string;
@@ -177,8 +187,11 @@ export const AgentManagerEmptyState: React.FC<AgentManagerEmptyStateProps> = ({
};
const handleKeyDown = (e: React.KeyboardEvent<HTMLTextAreaElement>) => {
// Early return during IME composition
if (isIMECompositionEvent(e)) return;
// Enter submits if valid, Shift+Enter adds newline
if (e.key === 'Enter' && !e.shiftKey) {
if (e.key === 'Enter' && !e.shiftKey && !isIMECompositionEvent(e)) {
e.preventDefault();
if (isValid && !isSubmittingOrCreating) {
handleSubmit(e as unknown as React.FormEvent);