Fix chat composer IME Enter handling (#1730)

Co-authored-by: Steven Enamakel <enamakel@tinyhumans.ai>
This commit is contained in:
Aqil Aziz
2026-05-15 14:58:01 -07:00
committed by GitHub
co-authored by Steven Enamakel
parent b4c19b105b
commit b5f5b7e207
2 changed files with 49 additions and 0 deletions
@@ -4,6 +4,7 @@ import {
evaluateComposerSend,
getComposerBlockedSendFeedback,
handleComposerSlashCommand,
shouldSendComposerKeyDown,
} from './composerSendDecision';
describe('evaluateComposerSend', () => {
@@ -104,6 +105,30 @@ describe('handleComposerSlashCommand', () => {
});
});
describe('shouldSendComposerKeyDown', () => {
it('allows Enter to send when IME composition is inactive', () => {
expect(shouldSendComposerKeyDown({ key: 'Enter' })).toBe(true);
});
it('does not send on Shift+Enter', () => {
expect(shouldSendComposerKeyDown({ key: 'Enter', shiftKey: true })).toBe(false);
});
it('does not send while React reports IME composition', () => {
expect(shouldSendComposerKeyDown({ key: 'Enter', nativeEvent: { isComposing: true } })).toBe(
false
);
});
it('does not send while the browser reports legacy IME keyCode 229', () => {
expect(shouldSendComposerKeyDown({ key: 'Enter', nativeEvent: { keyCode: 229 } })).toBe(false);
});
it('does not send while textarea composition state is active', () => {
expect(shouldSendComposerKeyDown({ key: 'Enter' }, true)).toBe(false);
});
});
describe('getComposerBlockedSendFeedback', () => {
it('returns modal and error feedback for usage-limit blocking', () => {
expect(getComposerBlockedSendFeedback('usage_limit_reached')).toEqual({
@@ -28,6 +28,14 @@ export interface ComposerBlockedSendFeedback {
error: { code: 'usage_limit_reached' | 'socket_disconnected'; message: string };
}
export interface ComposerKeyDownEventLike {
key: string;
shiftKey?: boolean;
isComposing?: boolean;
keyCode?: number;
nativeEvent?: { isComposing?: boolean; keyCode?: number };
}
export const handleComposerSlashCommand = (
command: string,
welcomeLocked: boolean
@@ -65,6 +73,22 @@ export const evaluateComposerSend = (args: ComposerSendDecisionArgs): ComposerSe
return { shouldSend: true, trimmedText };
};
export const isComposerImeComposing = (
event: ComposerKeyDownEventLike,
compositionActive = false
): boolean =>
compositionActive ||
event.isComposing === true ||
event.keyCode === 229 ||
event.nativeEvent?.isComposing === true ||
event.nativeEvent?.keyCode === 229;
export const shouldSendComposerKeyDown = (
event: ComposerKeyDownEventLike,
compositionActive = false
): boolean =>
event.key === 'Enter' && !event.shiftKey && !isComposerImeComposing(event, compositionActive);
export const getComposerBlockedSendFeedback = (
blockReason: ComposerSendBlockReason | undefined
): ComposerBlockedSendFeedback | null => {