fix(dashboard): prevent React from dropping first keystroke during IME composition - #52117
Conversation
…E composition React 18's root-level event delegation intercepts keydown events with keyCode 229 (the "composition in progress" signal sent by the browser during non-Latin IME input) and synthesises an onCompositionStart event. That synthetic path sets internal composing state that interferes with xterm.js's own IME handling on its hidden textarea, causing the first keystroke of each composition chunk to be silently dropped. The fix adds a capture-phase keydown listener on the terminal host div that stops propagation of keyCode-229 events before they reach React's delegation layer. xterm.js relies on native compositionstart/ compositionend on its internal textarea — not on keydown — so blocking the propagation is safe. Fixes NousResearch#52111
Fix PR for #52111 (Web Dashboard drops first Cyrillic keystroke on Firefox/Zen). Related to the desktop CJK-IME composer cluster (#44332, #40657, #38883) — same class of React/keyCode===229 IME-composition guard, different surface (web dashboard |
|
Thanks for isolating this to the dashboard terminal host. Current main still has no Problems
Suggested changes
Automated hermes-sweeper review. |
|
Thanks for this fix! It was salvaged into #86761 (cherry-picked onto current main with your authorship preserved in the commit history) and is now merged. Closing since the work has landed. |
What does this PR do?
Adds a capture-phase keydown listener on the dashboard terminal host div that stops propagation of
keyCode === 229(IME composition in progress) events, preventing React 18's root-level event delegation from synthesising anonCompositionStartthat interferes with xterm.js's native IME handling. Fixes dropped first keystrokes for Cyrillic, CJK, Arabic, Hebrew, and other composition-based input methods.Related Issue
Fixes #52111
Type of Change
Changes Made
web/src/pages/ChatPage.tsx: Added a capture-phasekeydownlistener on the terminal host element that callsstopPropagation()for events withkeyCode === 229orkey === "Process", and removes it on cleanup. The guard prevents React's synthetic composition event path from interfering with xterm.js's own compositionstart/compositionend handling on its internal textarea.How to Test
cd web && npx tsc -p . --noEmit— TypeScript compiles cleanlyhermes dashboard --host 0.0.0.0 --port 9119cd web && npx vitest runChecklist
Code
fix(scope):,feat(scope):, etc.)cd web && npx vitest runand all tests passDocumentation & Housekeeping
docs/, docstrings) — or N/Acli-config.yaml.exampleif I added/changed config keys — or N/ACONTRIBUTING.mdorAGENTS.mdif I changed architecture or workflows — or N/ACode Intelligence
web/src/pages/ChatPage.tsx— terminal setup effect (term.open → cleanup)apps/desktop/src/app/chat/composer/ime-composition-dom-repro.test.tsxhas a related composition test for the desktop contentEditable composer