diff --git a/frontend/taskdeck-web/src/components/workspace/WorkspaceSetupModal.vue b/frontend/taskdeck-web/src/components/workspace/WorkspaceSetupModal.vue index fc87d0d33..acdc20b3c 100644 --- a/frontend/taskdeck-web/src/components/workspace/WorkspaceSetupModal.vue +++ b/frontend/taskdeck-web/src/components/workspace/WorkspaceSetupModal.vue @@ -71,6 +71,18 @@ async function applyStarterPack(boardId: string, starterPackId: string): Promise } } +function onBoardNameKeydown(event: KeyboardEvent) { + // A composing Enter commits an IME candidate; leave it uncancelled so the commit lands + // (the same contract as PaperCaptureNib and ApplyToBoardDialog). Only an ordinary Enter + // is claimed as the submit gesture. + if (event.isComposing) { + return + } + + event.preventDefault() + void submitSetup() +} + async function submitSetup() { if (!canSubmit.value || !selectedSetup.value) { return @@ -154,7 +166,7 @@ watch( type="text" maxlength="100" placeholder="For example: Product Sprint" - @keydown.enter.prevent="submitSetup" + @keydown.enter="onBoardNameKeydown" /> diff --git a/frontend/taskdeck-web/src/tests/components/WorkspaceSetupModal.spec.ts b/frontend/taskdeck-web/src/tests/components/WorkspaceSetupModal.spec.ts index fd5ef5de9..0b54c4221 100644 --- a/frontend/taskdeck-web/src/tests/components/WorkspaceSetupModal.spec.ts +++ b/frontend/taskdeck-web/src/tests/components/WorkspaceSetupModal.spec.ts @@ -120,6 +120,39 @@ describe('WorkspaceSetupModal', () => { expect(mocks.push).toHaveBeenCalledWith('/workspace/boards/board-1') }) + it('ignores Enter while IME composition is active, then submits ordinary Enter once', async () => { + const wrapper = mount(WorkspaceSetupModal, { + props: { + isOpen: true, + }, + }) + + const input = wrapper.get('input[placeholder="For example: Product Sprint"]') + await input.setValue('IME Board') + + const composingEnter = new KeyboardEvent('keydown', { + key: 'Enter', + isComposing: true, + bubbles: true, + cancelable: true, + }) + // Not cancelled: the composing keystroke must reach the IME so the candidate commits. + expect(input.element.dispatchEvent(composingEnter)).toBe(true) + await waitForUi() + expect(mocks.createBoard).not.toHaveBeenCalled() + + const ordinaryEnter = new KeyboardEvent('keydown', { + key: 'Enter', + bubbles: true, + cancelable: true, + }) + expect(input.element.dispatchEvent(ordinaryEnter)).toBe(false) + await waitForUi() + + expect(mocks.createBoard).toHaveBeenCalledTimes(1) + expect(mocks.createBoard).toHaveBeenCalledWith({ name: 'IME Board' }) + }) + it('cancels on a dispatched Escape while allowing the submitting lock to hold', async () => { const wrapper = mount(WorkspaceSetupModal, { props: {