Repository navigation
Conversation
Vietnamese IMEs (Unikey, EVKey) send backspace + accented char in a single stdin chunk. OpenTUI splits this into separate key events, but React batches state updates, so handleDeletionKeys read stale value/cursorPosition props while insertTextAtCursor already read synchronous refs. Result: 'chào' rendered as 'chaà' — backspace deleted from the pre-batch string, never removing the base character. All keyboard handlers (deletion, enter, navigation, mouse, cursor movement) now read from valueRef.current/cursorPositionRef.current and update refs before calling onChange, matching the pattern insertTextAtCursor already used. Removes value/cursorPosition from useCallback dependency arrays.
|
The root-cause analysis is solid: The weak point is the test suite. The four new tests in Given the size of the diff (8 handlers touched) and the IME/batching subtlety involved, a maintainer porting this needs tests that actually drive the component's key event pipeline, not a parallel model of it. Please rewrite the four tests using the existing test infrastructure for |
…ndlers Review feedback: the four Vietnamese IME tests used a hand-rolled reimplementation of the ref/batch pattern instead of exercising the actual MultilineInput code paths. They would pass even if the fix were wrong or reverted. New multiline-input.ime.test.tsx uses createTestRenderer + mockInput (the same harness as queue-editing.test.tsx) to mount MultilineInput and drive real key events through handleDeletionKeys and insertTextAtCursor. Verified the tests fail when the ref update in the backspace handler is removed (produces 'chaà' as expected). Removed the four reimplementation tests from multiline-input.test.tsx.
|
Addressed the test feedback in c703b80. Replaced the four reimplementation tests with a new integration test file (multiline-input.ime.test.tsx) that uses the same harness as queue-editing.test.tsx: createTestRenderer + createRoot + mockInput. The tests now mount the real MultilineInput component and drive key events through the actual handleDeletionKeys and insertTextAtCursor handlers. Verification: temporarily removed the ref update from the backspace handler and confirmed 3 of 5 integration tests fail with output 'chaà' (the reported bug), then restored the fix. The old reimplementation tests were removed since they are now redundant with the integration coverage. |
What
All keyboard handlers in MultilineInput now read from synchronous refs (valueRef.current/cursorPositionRef.current) instead of React props, and update refs before calling onChange.
Why
The code already documents this race at multiline-input.tsx lines 237-246: when multiple key events fire in one React batch, refs are updated synchronously but props are stale. insertTextAtCursor was fixed to read refs, but no other handler was. Deletion, enter, navigation, mouse click, and cursor movement all still read stale value/cursorPosition props.
This affects any input where multiple key events arrive in one stdin chunk: Chinese, Japanese, Korean, Arabic IMEs, and fast ASCII typists. Vietnamese makes it unmissable because compose-and-replace sends base char + backspace + accented char in rapid succession, so the stale backspace visibly corrupts the text.
Repro
Root cause trace
Changes
Testing
bun test src/components/tests/multiline-input.test.tsx
76 pass, 0 fail
bun run typecheck: no errors in changed files (pre-existing errors in common/, sdk/, packages/ untouched).