Skip to content

fix(dashboard): prevent React from dropping first keystroke during IME composition - #52117

Closed
liuhao1024 wants to merge 1 commit into
NousResearch:mainfrom
liuhao1024:fix/dashboard-cyrillic-ime-first-keystroke
Closed

fix(dashboard): prevent React from dropping first keystroke during IME composition#52117
liuhao1024 wants to merge 1 commit into
NousResearch:mainfrom
liuhao1024:fix/dashboard-cyrillic-ime-first-keystroke

Conversation

@liuhao1024

Copy link
Copy Markdown
Contributor

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 an onCompositionStart that 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

  • 🐛 Bug fix (non-breaking change that fixes an issue)

Changes Made

  • web/src/pages/ChatPage.tsx: Added a capture-phase keydown listener on the terminal host element that calls stopPropagation() for events with keyCode === 229 or key === "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

  1. cd web && npx tsc -p . --noEmit — TypeScript compiles cleanly
  2. Open the Hermes Web Dashboard in Firefox: hermes dashboard --host 0.0.0.0 --port 9119
  3. Switch keyboard layout to Ukrainian (or any Cyrillic / CJK / IME-based layout)
  4. Click into the chat input (xterm.js terminal)
  5. Type a Cyrillic word, e.g. "Привіт" — every character should appear on the first keystroke
  6. Verify that English/Latin input still works normally
  7. Verify copy/paste (Cmd+C / Cmd+Shift+V) still works
  8. Existing tests pass: cd web && npx vitest run

Checklist

Code

  • I've read the Contributing Guide
  • My commit messages follow Conventional Commits (fix(scope):, feat(scope):, etc.)
  • I searched for existing PRs to make sure this isn't a duplicate
  • My PR contains only changes related to this fix/feature (no unrelated commits)
  • I've run cd web && npx vitest run and all tests pass
  • I've added tests for my changes — or N/A (DOM event guard; manual IME testing required; existing test suite covers unrelated utilities)
  • I've tested on my platform: macOS (TypeScript compiles, tests pass; IME reproduction requires Firefox + Cyrillic layout per issue reporter's environment)

Documentation & Housekeeping

  • I've updated relevant documentation (README, docs/, docstrings) — or N/A
  • I've updated cli-config.yaml.example if I added/changed config keys — or N/A
  • I've updated CONTRIBUTING.md or AGENTS.md if I changed architecture or workflows — or N/A
  • I've considered cross-platform impact (Windows, macOS) per the compatibility guide — or N/A (capture-phase keydown guard is standard DOM, platform-independent)
  • I've updated tool descriptions/schemas if I changed tool behavior — or N/A

Code Intelligence

  • Analyzed: web/src/pages/ChatPage.tsx — terminal setup effect (term.open → cleanup)
  • Blast radius: LOW — scoped to terminal host element, only intercepts keyCode 229 / key "Process"
  • Related patterns: React 18 event delegation + xterm.js IME composition; apps/desktop/src/app/chat/composer/ime-composition-dom-repro.test.tsx has a related composition test for the desktop contentEditable composer

…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
@alt-glitch alt-glitch added type/bug Something isn't working comp/dashboard Web dashboard / control panel UI (dashboard/, landing) P3 Low — cosmetic, nice to have labels Jun 24, 2026
@alt-glitch

Copy link
Copy Markdown
Collaborator

This was generated by AI during triage.

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 ChatPage.tsx vs Electron composer).

@teknium1

Copy link
Copy Markdown
Contributor

Thanks for isolating this to the dashboard terminal host. Current main still has no keydown/keyCode === 229 propagation guard: after term.open(host) at web/src/pages/ChatPage.tsx:713, it only installs beforeinput and compositionend listeners on xterm's textarea at :741-742.

Problems

  • The PR attributes the failure to React 18, while current main declares React/ReactDOM 19.2.4 in web/package.json:32-33. The claimed event path and the effect of stopping capture propagation before the xterm textarea need current-runtime verification.
  • Existing coverage in web/src/lib/pty-mobile-input.test.ts:9-100 tests mobile replacement normalization, not this host-level keydown guard or cleanup.

Suggested changes

  • Reproduce on current main in Firefox/Zen with a composition keyboard, then add a focused DOM regression test for Process/keyCode-229 blocking, ordinary-key passthrough, and cleanup.
  • Salvage against the current textarea composition setup at web/src/pages/ChatPage.tsx:715-746; the PR branch is stale relative to that path.

Automated hermes-sweeper review.

@teknium1 teknium1 added sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform labels Jul 15, 2026
@teknium1

Copy link
Copy Markdown
Contributor

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.

@teknium1 teknium1 closed this Aug 15, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/dashboard Web dashboard / control panel UI (dashboard/, landing) P3 Low — cosmetic, nice to have sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Web Dashboard drops first keystroke when typing Cyrillic (Ukrainian/Russian) on Firefox/Zen

3 participants