fix: suppress Enter submit during IME composition in workflow creation popup (#804)

* fix: guard Enter submit against IME composition in workflow creation popup

Pressing Enter during Japanese IME conversion was triggering workflow
submission instead of only confirming the candidate. Extract
shouldSuppressEnterSubmit() and check event.isComposing before submit
in NewWorkspaceComposerModal, NewWorkspacePage, and the task search
handler. Fixes #742.

* fix: use nativeEvent.isComposing for React SyntheticEvent in task search handler

* refactor: move new-workspace-enter-guard to src/renderer/src/lib

---------

Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com>
This commit is contained in:
keiju-fujiwara 2026-04-19 13:17:24 +09:00 committed by GitHub
parent 159081c2e6
commit 5d985a6563
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 64 additions and 1 deletions

View file

@ -4,6 +4,7 @@ import { Dialog, DialogContent, DialogDescription, DialogTitle } from '@/compone
import NewWorkspaceComposerCard from '@/components/NewWorkspaceComposerCard'
import { useComposerState } from '@/hooks/useComposerState'
import type { LinkedWorkItemSummary } from '@/lib/new-workspace'
import { shouldSuppressEnterSubmit } from '@/lib/new-workspace-enter-guard'
type ComposerModalData = {
prefilledName?: string
@ -102,7 +103,7 @@ function ComposerModalBody({
if (createDisabled) {
return
}
if (target instanceof HTMLTextAreaElement && event.shiftKey) {
if (shouldSuppressEnterSubmit(event, target instanceof HTMLTextAreaElement)) {
return
}
event.preventDefault()

View file

@ -34,6 +34,7 @@ import { getLinkedWorkItemSuggestedName, getTaskPresetQuery } from '@/lib/new-wo
import type { LinkedWorkItemSummary } from '@/lib/new-workspace'
import { isGitRepoKind } from '../../../shared/repo-kind'
import type { GitHubWorkItem, TaskViewPresetId } from '../../../shared/types'
import { shouldSuppressEnterSubmit } from '@/lib/new-workspace-enter-guard'
type TaskSource = 'github' | 'linear'
type TaskQueryPreset = {
@ -332,6 +333,15 @@ export default function NewWorkspacePage(): React.JSX.Element {
const handleTaskSearchKeyDown = useCallback(
(event: React.KeyboardEvent<HTMLInputElement>): void => {
if (event.key === 'Enter') {
// React SyntheticEvent does not expose isComposing; use nativeEvent.
if (
shouldSuppressEnterSubmit(
{ isComposing: event.nativeEvent.isComposing, shiftKey: event.shiftKey },
false
)
) {
return
}
event.preventDefault()
handleApplyTaskSearch()
}

View file

@ -0,0 +1,33 @@
import { describe, it, expect } from 'vitest'
import { shouldSuppressEnterSubmit } from './new-workspace-enter-guard'
function makeEvent(overrides: Partial<{ isComposing: boolean; shiftKey: boolean }>): {
isComposing: boolean
shiftKey: boolean
} {
return { isComposing: false, shiftKey: false, ...overrides }
}
describe('shouldSuppressEnterSubmit', () => {
it('returns false for a plain Enter with no composition', () => {
expect(shouldSuppressEnterSubmit(makeEvent({}), false)).toBe(false)
})
it('returns true when IME composition is active', () => {
expect(shouldSuppressEnterSubmit(makeEvent({ isComposing: true }), false)).toBe(true)
})
it('returns true for Shift+Enter inside a textarea', () => {
expect(shouldSuppressEnterSubmit(makeEvent({ shiftKey: true }), true)).toBe(true)
})
it('returns false for Shift+Enter inside a non-textarea element', () => {
expect(shouldSuppressEnterSubmit(makeEvent({ shiftKey: true }), false)).toBe(false)
})
it('returns true when both isComposing and shiftKey are true (textarea)', () => {
expect(shouldSuppressEnterSubmit(makeEvent({ isComposing: true, shiftKey: true }), true)).toBe(
true
)
})
})

View file

@ -0,0 +1,19 @@
/**
* Returns true when an Enter keydown event should be suppressed for submit actions.
*
* Two cases must be blocked:
* 1. IME composition is active Enter only confirms the conversion candidate.
* 2. Shift+Enter inside a textarea intended as a newline, not a submit.
*/
export function shouldSuppressEnterSubmit(
event: { isComposing: boolean; shiftKey: boolean },
isTextarea: boolean
): boolean {
if (event.isComposing) {
return true
}
if (isTextarea && event.shiftKey) {
return true
}
return false
}