Skip to content

fix(tui,dashboard): consolidated IME input fixes — Korean/Telex recomposition, return-burst commits, PTY composition forwarding - #86761

Merged
teknium1 merged 18 commits into
mainfrom
salvage/w2-ime-tui
Aug 15, 2026
Merged

fix(tui,dashboard): consolidated IME input fixes — Korean/Telex recomposition, return-burst commits, PTY composition forwarding#86761
teknium1 merged 18 commits into
mainfrom
salvage/w2-ime-tui

Conversation

@teknium1

Copy link
Copy Markdown
Contributor

Consolidates the TUI/Ink and dashboard-PTY IME cluster (7 PRs) into one branch: Korean/Chinese/Vietnamese IME input no longer drops or duplicates characters in the TUI, and the web dashboard terminal no longer loses the first keystroke of a composition or committed dead-key text.

Changes

TUI (ui-tui)

  • Korean IME submit leaves stale text (from fix(tui): clear input after Korean IME submit #38245, @sweetcornna): the parent's clear after submit was misclassified as the input's own echo and ignored; the own-echo short-circuit now also requires the incoming value to match, so the post-submit '' clear lands.
  • IME commit text dropped when it arrives fused with Return (from fix(tui): preserve IME text before return submit #39695, @InphinitiZ): a commit burst like "会丢失内容\r" now has its printable prefix inserted before the Return submits, via a new pure valueForReturnSubmit helper.
  • Vietnamese Telex recomposition drops characters / strands spaces (from fix(tui): stop Vietnamese Telex IME from dropping characters #55415, @hanhvs, incl. the review-tightened tests): parse-keypress splits control bytes fused with text ("\x7fô", "ab\bç") so recomposed characters survive; multi-char IME inserts commit synchronously instead of through the 16 ms key-burst race; fast-echo backspace is suppressed for one keystroke after an Ink repaint (the OpenKey U+202F "hạ␣␣" bug).

Dashboard PTY (web)

Excluded

Regression tests

  • textInputSubmitClear.test.tsx (new): Korean commit + Enter, parent clear must land.
  • textInputReturnBurst.test.ts (new): fused IME-commit+Return keeps the committed text.
  • imeVietnameseTelex.test.tsx (new): real OpenKey/EVKey byte captures for "vương sỹ hạnh", sync-commit and fast-echo-suppression coverage.
  • parse-keypress{,-drop-probe,-noregress}.test.ts: control-byte splitting, no-printable-loss probe, EVKey no-regress.
  • pty-composition.test.ts (new): forwarder lifecycle — fallback fires, xterm-authoritative suppression, chunked/unrelated data, disposal.

Validation

Check Result
npx vitest run — 6 TUI/ink test files ✅ 51 passed
npx vitest run src/lib/pty-composition.test.ts (web) ✅ 10 passed
npx tsc --noEmit (ui-tui, web) ✅ clean
scripts/audit_pr_attribution.py --fix ✅ all emails mapped

Credits

Commits cherry-picked with authorship preserved: @sweetcornna (#38245), @InphinitiZ (#39695), @hanhvs (#55415), @liuhao1024 (#52117), @dplush (#76257). Related earlier reports by @energypantry (#30775) and @wuyang9311 (#39246) — see Excluded.

Issues

Fixes #75960 (TUI side). Fixes #77559. Covers the TUI/dashboard side of #69555 and #71813, and improves the IME latency reported in #84349 by removing the deferred key-burst commit for IME inserts.

Infographic

IME Fixes — TUI & PTY

sweetcornna and others added 17 commits August 14, 2026 23:07
Preserve printable IME commit text when xterm delivers it in the same input burst as Return, so Dashboard/TUI submits the visible draft instead of dropping the final segment.

Also fixes the TUI type-check stdio tuple typing and adds focused regression coverage.
Third-party Vietnamese IMEs (OpenKey/Unikey/EVKey in Telex mode) recompose
a syllable by emitting an erase burst followed by the finished characters.
Two layers of the TUI input pipeline mishandled this, dropping letters and
leaving a stray space mid-syllable (e.g. "hạnh" rendered as "hạ  ", and
"vương sỹ hạnh" as "vương sỹ hạ  ").

Root causes, both confirmed from real captured byte streams:

1. parse-keypress: an IME often fuses a control byte (\x7f/\b, or even the
   U+202F marker OpenKey injects) with the recomposed text in a single stdin
   read. parseKeypress only recognizes a control key when the whole string is
   exactly that byte, so a mixed chunk fell through every branch, returned
   name:"" with a non-printable sequence, and the composer's printable gate
   discarded the entire chunk — taking the surrounding letters with it. Split
   text tokens on every control byte so the printable runs survive.
   CR/LF are deliberately not split, preserving paste/return semantics.

2. textInput: multi-character (IME/paste) inserts were committed through the
   16ms deferred key-burst path, which raced an interleaved re-render and
   snapped the buffer back to a stale value, dropping the recomposed tail.
   Commit them synchronously. Additionally, the fast-echo "\b \b" backspace
   shortcut desynced the screen when it ran right after an Ink repaint (forced
   by the U+202F marker), stranding the marker glyph; suppress fast-echo for
   the recompose burst that follows an Ink repaint and resume it on the next
   real keystroke.

Tested with real OpenKey and EVKey captures of "vương sỹ hạnh" across read
timings, plus parser unit coverage and an EVKey no-regression guard.
- Remove unconditional 60ms wait that let deferred path pass sync-commit test
- Use fake timers (setTimeout/setInterval/Date only, NOT setImmediate)
- Assert immediately after final read — no trailing wait/advance
- Add deterministic coverage for 60ms fast-echo suppression reset:
  * suppresses backspace after Ink repaint (IME recompose)
  * does NOT suppress on normal ASCII typing
- Verified: revert sync commit -> deferred path makes 4/6 tests fail
…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 #52111
@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/) comp/dashboard Web dashboard / control panel UI (dashboard/, landing) labels Aug 15, 2026
@github-actions

github-actions Bot commented Aug 15, 2026

Copy link
Copy Markdown
Contributor

૮ >ﻌ< ა ci review

ran on 176fe01 — style: eslint --fix import ordering in salvaged IME tests

⚠️ Warnings

CI timings · View report · View job

Wall time 5m3s vs 3m56s (+28.4%). 17 job(s) slower, 19 faster, 1 unchanged.

  • Python tests / Run tests slice 3/12: +120.0s
  • JS & TS checks / apps/desktop / check:test:ui:shard-2of3: -27.0s
  • Python tests / Run tests slice 1/12: -21.0s
  • Python tests / Run tests slice 11/12: +15.0s
  • JS & TS checks / apps/desktop / check:test:ui:shard-3of3: -13.0s

OSV vulnerability scan · View job

5 known vulnerabilities found in pinned dependencies.

How to fix:

Review the findings in the Security tab. Update the affected dependencies if a patched version is available.

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) comp/tui Terminal UI (ui-tui/ + tui_gateway/) P3 Low — cosmetic, nice to have type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug] T9 (九方) IME: typing creates new session automatically [Bug]: Desktop IME preedit starts after visible composer placeholder

7 participants