From e90832eedda60be18b4b2bf1ccdb3f8314fb3867 Mon Sep 17 00:00:00 2001 From: Mohamed Boudra Date: Tue, 4 Aug 2026 10:44:00 +0200 Subject: [PATCH] fix(app): preserve native IME composition state Clearing the hidden terminal input after multi-character commits can leave the keyboard's composing buffer intact, causing later changes to replay committed text. Keep the accumulated native value so subsequent changes emit only their appended suffix. --- .../terminal-input.native.test.ts | 31 ++++++++++++++++--- .../native-renderer/terminal-input.native.tsx | 5 +-- 2 files changed, 27 insertions(+), 9 deletions(-) diff --git a/packages/app/src/terminal/native-renderer/terminal-input.native.test.ts b/packages/app/src/terminal/native-renderer/terminal-input.native.test.ts index 78f3c934a..1b07c2553 100644 --- a/packages/app/src/terminal/native-renderer/terminal-input.native.test.ts +++ b/packages/app/src/terminal/native-renderer/terminal-input.native.test.ts @@ -30,7 +30,7 @@ describe("native terminal typed input", () => { expect(input.receiveTextChange("npm run typecheck")).toEqual({ data: "npm run typecheck", - shouldClear: true, + shouldClear: false, }); }); @@ -122,11 +122,11 @@ describe("native terminal typed input", () => { const input = createTerminalTextInputState(); const longText = "x".repeat(512); - expect(input.receiveTextChange(longText)).toEqual({ data: longText, shouldClear: true }); + expect(input.receiveTextChange(longText)).toEqual({ data: longText, shouldClear: false }); input.reset(); - expect(input.receiveTextChange(longText)).toEqual({ data: longText, shouldClear: true }); + expect(input.receiveTextChange(longText)).toEqual({ data: longText, shouldClear: false }); }); it("translates software keyboard terminal control keys", () => { @@ -197,14 +197,35 @@ describe("native terminal typed input", () => { expect(input.receiveTextChange("echo clipboard item")).toEqual({ data: "echo clipboard item", - shouldClear: true, + shouldClear: false, }); input.reset(); expect(input.receiveTextChange("echo clipboard item")).toEqual({ data: "echo clipboard item", - shouldClear: true, + shouldClear: false, + }); + }); + + it("does not replay a stale IME suggestion when the native clear leaves composing text behind", () => { + const input = createTerminalTextInputState(); + let typed = ""; + + for (const key of "resta") { + typed += key; + input.receiveKeyPress(key); + input.receiveTextChange(typed); + } + + expect(input.receiveTextChange("restaurant ")).toEqual({ + data: "urant ", + shouldClear: false, + }); + + expect(input.receiveTextChange("restaurant x")).toEqual({ + data: "x", + shouldClear: false, }); }); }); diff --git a/packages/app/src/terminal/native-renderer/terminal-input.native.tsx b/packages/app/src/terminal/native-renderer/terminal-input.native.tsx index 6aa2f7020..3cced9774 100644 --- a/packages/app/src/terminal/native-renderer/terminal-input.native.tsx +++ b/packages/app/src/terminal/native-renderer/terminal-input.native.tsx @@ -107,10 +107,7 @@ export function createTerminalTextInputState(): TerminalTextInputState { previousText = text; return { data: appendedText, - // Treat multi-character IME commits as complete units. Clearing lets - // the keyboard commit the same clipboard item again without needing - // a different intermediate value. - shouldClear: appendedText.length > 1, + shouldClear: false, }; }, reset(): void {