From 907d40354c9aa8ec006eed611aaa50642e152b5e Mon Sep 17 00:00:00 2001 From: kevin9327 <5299031+kevin9327@users.noreply.github.com> Date: Sun, 13 Sep 2026 07:03:15 +0900 Subject: [PATCH] Confirm a composed character with Enter on the desktop question box, instead of sending the question Japanese, Chinese and Korean are typed through an input method, where Enter confirms the character being built. The last setup screen's question box read that Enter as a plain Enter and sent the question with its last character still unconfirmed. The box now ignores a keydown that belongs to a composition, using the guard MDN documents for it: isComposing, or key code 229 for WebKit, which fires the confirming keydown after compositionend. This matches the chat composer, whose Enter already skips it. An ordinary Enter still asks. Co-Authored-By: Claude Opus 5 --- CHANGELOG.md | 7 +++++ desktop/src/App.test.tsx | 55 +++++++++++++++++++++++++++++++++++++++- desktop/src/Ask.tsx | 15 ++++++++++- 3 files changed, 75 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6e2f952b2..620e50427 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,13 @@ Newest first. `Unreleased` is what is on `main` and not yet tagged. ## Unreleased +### The desktop setup's question box waits for a typed character to be confirmed + +Japanese, Chinese and Korean are typed through an input method, where Enter confirms the character +being built. On the last setup screen that Enter also sent the question, with its last character +still unconfirmed. The box now waits for the character, the way the chat composer already does, and +an ordinary Enter still asks. + ### A malformed page size is refused instead of silently coerced `GET /channels` and `GET /api/admin/people` read `?limit=` with `Number.parseInt`, which diff --git a/desktop/src/App.test.tsx b/desktop/src/App.test.tsx index fc1060d8f..bb582f93d 100644 --- a/desktop/src/App.test.tsx +++ b/desktop/src/App.test.tsx @@ -1,6 +1,12 @@ import { afterAll, afterEach, beforeAll, expect, mock, test } from "bun:test"; import { GlobalRegistrator } from "@happy-dom/global-registrator"; -import { act, cleanup, render, waitFor } from "@testing-library/react"; +import { + act, + cleanup, + fireEvent, + render, + waitFor, +} from "@testing-library/react"; import userEvent from "@testing-library/user-event"; import { StrictMode } from "react"; @@ -131,6 +137,53 @@ test("setup records only model categories and reaches Ask when telemetry is unav expect(view.queryByText(/Nothing here leaves this computer/)).toBeNull(); }); +/** + * Setup finished and the question box on screen, focused as a person's typing would be. Call + * `useCompatibleEndpointSetup` first, as every test here does. + */ +async function reachAsk() { + const previous = invokeHandler; + invokeHandler = async (command, args) => { + if (command === "record_setup_event") return null; + if (command === "ask_the_bot") return "391"; + return previous(command, args); + }; + const view = await enterCompatibleEndpoint("https://models.example/v1"); + await userEvent.click(view.getByRole("button", { name: "Continue" })); + await userEvent.click(view.getByRole("button", { name: "Start OpenBot" })); + const question = await view.findByLabelText("Your question"); + // Keys go to the input that has focus, so the field is entered first. + await userEvent.click(question); + const asked = () => + invokeCalls.filter((call) => call.command === "ask_the_bot"); + return { question, asked }; +} + +test("the Enter that finishes a composed character does not ask the Bot", async () => { + useCompatibleEndpointSetup({}); + const { question, asked } = await reachAsk(); + + // Japanese, Chinese and Korean are typed through an input method, and Enter is how a character is + // confirmed. That Enter is still a keydown: Chromium marks it `isComposing`, and WebKit — the + // macOS webview — sends it after `compositionend` with the key code 229 instead. + await act(async () => { + fireEvent.keyDown(question, { key: "Enter", isComposing: true }); + fireEvent.keyDown(question, { key: "Enter", keyCode: 229 }); + }); + expect(asked()).toEqual([]); +}); + +test("an ordinary Enter in the question box asks the Bot, once", async () => { + // Unchanged by the test above, and pinned so it stays that way. + useCompatibleEndpointSetup({}); + const { question, asked } = await reachAsk(); + + await act(async () => { + fireEvent.keyDown(question, { key: "Enter", keyCode: 13 }); + }); + await waitFor(() => expect(asked()).toHaveLength(1)); +}); + type StartStackPayload = { root?: unknown; apiKey?: unknown; diff --git a/desktop/src/Ask.tsx b/desktop/src/Ask.tsx index 65a6e0eab..0e376cf58 100644 --- a/desktop/src/Ask.tsx +++ b/desktop/src/Ask.tsx @@ -70,7 +70,20 @@ export function Ask({ }} disabled={asking} onKeyDown={(event) => { - if (event.key === "Enter" && !asking) { + /* + * NOT THE ENTER THAT FINISHES A COMPOSED CHARACTER. Japanese, Chinese and Korean are + * typed through an input method, and Enter is how the character being built is + * confirmed. That press is still a keydown: Chromium marks it `isComposing`, and WebKit, + * which the macOS webview is, sends it after `compositionend` with the key code 229. Read + * as a plain Enter, it sent the question with the last character still unconfirmed. The + * chat composer's own Enter already skips it; this one did not. + */ + if ( + event.key === "Enter" && + !asking && + !event.nativeEvent.isComposing && + event.keyCode !== 229 + ) { ask(); } }}