Skip to content
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions web/src/pages/ChatPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -529,6 +529,30 @@ export default function ChatPage({ isActive = true }: { isActive?: boolean }) {

term.open(host);

// IME composition guard (fixes #52111).
//
// 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 β€” most visible with Cyrillic (Ukrainian/Russian) on
// Firefox-based browsers, but affects any locale that uses composition
// events (CJK, Arabic, Hebrew).
//
// xterm.js relies on native compositionstart/compositionend on its
// internal textarea, not on keydown, so blocking the keyCode-229
// keydown from reaching React's delegation layer is safe. The listener
// sits in the *capture* phase on the terminal host so it fires before
// the event bubbles up to the React root.
const _imeCompositionGuard = (e: KeyboardEvent) => {
if (e.keyCode === 229 || e.key === "Process") {
e.stopPropagation();
}
};
host.addEventListener("keydown", _imeCompositionGuard, true);

// WebGL draws from a texture atlas sized with device pixels. On phones and
// in DevTools device mode that often produces *visually* much larger cells
// than `fontSize` suggests β€” users see "huge" text even at 7–9px settings.
Expand Down Expand Up @@ -813,6 +837,7 @@ export default function ChatPage({ isActive = true }: { isActive?: boolean }) {
// the ticket fetch resolves and ``wsRef.current`` was never assigned.
wsRef.current?.close();
wsRef.current = null;
host.removeEventListener("keydown", _imeCompositionGuard, true);
term.dispose();
termRef.current = null;
fitRef.current = null;
Expand Down
Loading