From 7819dd722d894b572278a08b62b9fda0776d0620 Mon Sep 17 00:00:00 2001 From: Leo Date: Thu, 10 Sep 2026 14:05:32 -0400 Subject: [PATCH 1/2] fix(web): keep settings inputs focused during IME composition Enter while an IME is composing used to blur and commit a half-finished character. Settings DraftInput fields now ignore that key until composition finishes. Adapted from pingdotgg/t3code#10262. --- apps/web/src/hooks/useCommitOnBlur.test.ts | 30 ++++++++++++++++++++++ apps/web/src/hooks/useCommitOnBlur.ts | 14 +++++++--- 2 files changed, 40 insertions(+), 4 deletions(-) create mode 100644 apps/web/src/hooks/useCommitOnBlur.test.ts diff --git a/apps/web/src/hooks/useCommitOnBlur.test.ts b/apps/web/src/hooks/useCommitOnBlur.test.ts new file mode 100644 index 000000000000..c737d0fa1313 --- /dev/null +++ b/apps/web/src/hooks/useCommitOnBlur.test.ts @@ -0,0 +1,30 @@ +import { describe, expect, it } from "vite-plus/test"; + +import { shouldBlurCommitOnKeyDown } from "./useCommitOnBlur"; + +function keyEvent(overrides: { + key?: string; + keyCode?: number; + isComposing?: boolean; +}): Parameters[0] { + return { + key: overrides.key ?? "Enter", + keyCode: overrides.keyCode ?? 13, + nativeEvent: { isComposing: overrides.isComposing ?? false } as KeyboardEvent["nativeEvent"], + }; +} + +describe("shouldBlurCommitOnKeyDown", () => { + it("commits on Enter after composition has finished", () => { + expect(shouldBlurCommitOnKeyDown(keyEvent({ key: "Enter" }))).toBe(true); + }); + + it("keeps focus during IME composition", () => { + expect(shouldBlurCommitOnKeyDown(keyEvent({ key: "Enter", isComposing: true }))).toBe(false); + expect(shouldBlurCommitOnKeyDown(keyEvent({ key: "Enter", keyCode: 229 }))).toBe(false); + }); + + it("ignores other keys", () => { + expect(shouldBlurCommitOnKeyDown(keyEvent({ key: "Escape" }))).toBe(false); + }); +}); diff --git a/apps/web/src/hooks/useCommitOnBlur.ts b/apps/web/src/hooks/useCommitOnBlur.ts index d1154fbb265a..ab37a8a9b122 100644 --- a/apps/web/src/hooks/useCommitOnBlur.ts +++ b/apps/web/src/hooks/useCommitOnBlur.ts @@ -15,6 +15,13 @@ import { type ChangeEvent, type KeyboardEvent, useState } from "react"; * const bag = useCommitOnBlur(instance.displayName ?? "", (next) => {...}); * */ +export function shouldBlurCommitOnKeyDown( + event: Pick, "key" | "keyCode" | "nativeEvent">, +): boolean { + if (event.nativeEvent.isComposing || event.keyCode === 229) return false; + return event.key === "Enter"; +} + export function useCommitOnBlur(value: string, onCommit: (next: string) => void) { const [draft, setDraft] = useState(null); @@ -34,10 +41,9 @@ export function useCommitOnBlur(value: string, onCommit: (next: string) => void) } }, onKeyDown: (event: KeyboardEvent) => { - if (event.key === "Enter") { - event.preventDefault(); - (event.target as HTMLInputElement).blur(); - } + if (!shouldBlurCommitOnKeyDown(event)) return; + event.preventDefault(); + (event.target as HTMLInputElement).blur(); }, }; } From a8550317134a72b9a3b9d5d44b09fe5c261c3531 Mon Sep 17 00:00:00 2001 From: Leo Date: Thu, 10 Sep 2026 14:10:41 -0400 Subject: [PATCH 2/2] fix(web): type the IME Enter guard without DOM KeyboardEvent CI typecheck failed because the test treated React's nativeEvent as a full KeyboardEvent. The helper now takes only the composition fields it reads. --- apps/web/src/hooks/useCommitOnBlur.test.ts | 2 +- apps/web/src/hooks/useCommitOnBlur.ts | 8 +++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/apps/web/src/hooks/useCommitOnBlur.test.ts b/apps/web/src/hooks/useCommitOnBlur.test.ts index c737d0fa1313..182b5dea8694 100644 --- a/apps/web/src/hooks/useCommitOnBlur.test.ts +++ b/apps/web/src/hooks/useCommitOnBlur.test.ts @@ -10,7 +10,7 @@ function keyEvent(overrides: { return { key: overrides.key ?? "Enter", keyCode: overrides.keyCode ?? 13, - nativeEvent: { isComposing: overrides.isComposing ?? false } as KeyboardEvent["nativeEvent"], + nativeEvent: { isComposing: overrides.isComposing ?? false }, }; } diff --git a/apps/web/src/hooks/useCommitOnBlur.ts b/apps/web/src/hooks/useCommitOnBlur.ts index ab37a8a9b122..f116141fc037 100644 --- a/apps/web/src/hooks/useCommitOnBlur.ts +++ b/apps/web/src/hooks/useCommitOnBlur.ts @@ -15,9 +15,11 @@ import { type ChangeEvent, type KeyboardEvent, useState } from "react"; * const bag = useCommitOnBlur(instance.displayName ?? "", (next) => {...}); * */ -export function shouldBlurCommitOnKeyDown( - event: Pick, "key" | "keyCode" | "nativeEvent">, -): boolean { +export function shouldBlurCommitOnKeyDown(event: { + readonly key: string; + readonly keyCode: number; + readonly nativeEvent: { readonly isComposing?: boolean }; +}): boolean { if (event.nativeEvent.isComposing || event.keyCode === 229) return false; return event.key === "Enter"; }