Skip to content

fix(tui): guard Enter submit against IME composition via timestamp - #39246

Open
wuyang9311 wants to merge 1 commit into
NousResearch:mainfrom
wuyang9311:fix/tui-ime-enter-submit
Open

fix(tui): guard Enter submit against IME composition via timestamp#39246
wuyang9311 wants to merge 1 commit into
NousResearch:mainfrom
wuyang9311:fix/tui-ime-enter-submit

Conversation

@wuyang9311

Copy link
Copy Markdown

What does this PR do?

  • Fixes the TUI (--tui mode) rejecting CJK/IME users: pressing Enter to confirm an IME candidate was treated as message submit because the terminal does not expose an isComposing property.
  • Adds a 30ms timestamp guard: when Enter follows a non-paste printable input within 30ms, the input and the Enter came from the same IME commit -- treat it as composition confirmation (absorb the Enter), not submit.

Related Issue

Fixes #39195

Type of Change

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

Changes Made

  • ui-tui/src/components/textInput.tsx: add IME_GUARD_MS constant (30ms), lastInputAt ref, guard in Enter handler, and timestamp recording in non-paste printable input path. 19 lines added.

Why 30ms?

30ms is well below human key-repeat speed (50ms+) but safely above IME commit + Enter synchrony (~0-5ms). Pastes are excluded via isPasted.

How to Test

  1. Run hermes --tui
  2. Type Chinese/Japanese/Korean with an IME
  3. Press Enter to confirm IME candidate -- should be absorbed (no submit, no buffer modification)
  4. Press Enter again -- should submit normally
  5. Paste text + Enter -- should submit normally

When using an IME (Chinese/Japanese/Korean/etc.) in TUI mode, pressing

Enter to confirm the IME candidate and pressing Enter to submit the

message are the same keystroke. In a terminal there is no isComposing

property to distinguish them.

Fix: track when the last non-paste printable input arrived. If Enter

follows within 30ms, the text and the Enter came in the same event-

loop tick -- this is an IME commit, not a user submit. Reset the

guard and return without modifying the buffer content.

The next Enter submits normally.

30ms is well below human key-repeat speed (50ms+) but above the

IME commit + Enter synchrony (~0-5ms on all tested platforms).

Paste operations are excluded (event.keypress.isPasted) so pasting

and pressing Enter still works.

Fixes: NousResearch#39195
@alt-glitch alt-glitch added type/bug Something isn't working P3 Low — cosmetic, nice to have comp/tui Terminal UI (ui-tui/ + tui_gateway/) labels Jun 4, 2026

@teknium1 teknium1 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for tackling a real TUI IME problem. Current main still submits unconditionally in ui-tui/src/components/textInput.tsx:990-1000, so the premise remains live.

Problems

  • The new guard is evaluated before the modified-Return branch (ui-tui/src/components/textInput.tsx:980 in this diff). A printable input followed within 30ms by Shift/Ctrl/Meta Return is swallowed instead of taking the existing newline path.
  • The timestamp is recorded only below the early Return path (ui-tui/src/components/textInput.tsx:1127 in this diff). It therefore cannot handle a commit and Return delivered in the same input event. The linked PR #39695 documents that xterm burst shape and preserves its printable prefix before submission.
  • This behavior change has no focused regression coverage.

Suggested changes

  • Limit any composition-confirmation guard to unmodified plain Return, after preserving modified-newline behavior.
  • Preserve printable text carried by a Return event before submitting, and add tests for separate-event IME confirmation, same-event commit-plus-Return, modified Return, and paste.

Automated hermes-sweeper review.

if (k.return) {
flushKeyBurst()

const imeGuard = Date.now() - lastInputAt.current < IME_GUARD_MS

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This guard runs before the existing Shift/Ctrl/Meta Return branch. A printable input followed within 30ms by a modified Return will now be absorbed instead of inserting the configured newline; restrict this guard to unmodified plain Return.

const bracketed = event.keypress.isPasted || inp.includes('[200~')
const text = inp.replace(BRACKET_PASTE, '').replace(/\r\n/g, '\n').replace(/\r/g, '\n')

if (!event.keypress.isPasted) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This timestamp is never reached when xterm delivers finalized IME text together with Return in one input event: the k.return branch returns first. The timeline-linked PR #39695 covers that event shape by preserving the printable Return payload before submission.

@teknium1 teknium1 added sweeper:risk-message-delivery Sweeper risk: may drop, duplicate, misroute, or suppress messages 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 14, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/tui Terminal UI (ui-tui/ + tui_gateway/) 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 sweeper:risk-message-delivery Sweeper risk: may drop, duplicate, misroute, or suppress messages type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Hermes Desktop TUI 中文输入法 IME 状态下按 Enter/Return 直接提交,而非确认候选词

3 participants