Skip to content

fix(mobile): CJK input loss — IME state machine, focus routing, and Android InputConnection recovery#143

Open
TeigenZhang wants to merge 1 commit into
Ark0N:masterfrom
TeigenZhang:pr/cjk-input-loss
Open

fix(mobile): CJK input loss — IME state machine, focus routing, and Android InputConnection recovery#143
TeigenZhang wants to merge 1 commit into
Ark0N:masterfrom
TeigenZhang:pr/cjk-input-loss

Conversation

@TeigenZhang

Copy link
Copy Markdown
Contributor

Summary

Fixes Chinese/Japanese/Korean (CJK) input silently disappearing on mobile — characters composed in the IME never reach the PTY, or land in the wrong place after switching apps. Three distinct root causes, one per layer:

  1. IME state machinecompositionstart/compositionupdate/compositionend were not tracked as a proper state machine, so a composition that was interrupted (app switch, focus steal) left cjkActive stuck or cleared at the wrong time, dropping the committed text.
  2. Focus routing — session-select and SSE-reconnect restore paths call terminal.focus(), which stole focus back to xterm's hidden textarea while the CJK field was visible. The IME then composed into a black hole (xterm's onData is gated by cjkActive). Now every terminal.focus() routes through one chokepoint that keeps the CJK field focused when it's visible.
  3. Android InputConnection recovery — some Android keyboards tear down and rebuild the InputConnection mid-composition; the textarea silently stopped emitting input events. Detect the dead connection and re-establish it.

Changes

  • input-cjk.js — composition state machine + Android InputConnection recovery
  • terminal-ui.js — focus router chokepoint so restore paths don't steal focus from the CJK field
  • app.js / server.ts — crash-diag tracing hooks used to pin down the Android case
  • test/input-cjk.test.ts — 9 new cases (241 lines) covering the state machine and focus routing

Test plan

  • npm test -- test/input-cjk.test.ts — 9/9 pass
  • npm run check:frontend-syntax
  • Verified on a real Android device (the InputConnection path does not reproduce in Chromium/Playwright)

…ndroid InputConnection recovery

Three independent root causes of intermittent Chinese character loss
(English was unaffected because it bypasses the composition path):

1. input-cjk.js state machine: stuck _composing when compositionend never
   fires (WeChat/Sogou IMEs) silently swallowed all input; the deferred
   compositionend flush could reset the textarea mid-next-composition
   (cancels the live IME composition on iOS); the 100ms keydown-echo
   window discarded ANY input regardless of content.

2. Focus stealing: session-select / SSE-reconnect paths call
   terminal.focus() (15+ call sites), landing focus on xterm's hidden
   textarea; with the CJK onData gate active, everything typed there was
   swallowed. Fix: focus router in initTerminal routes ALL
   terminal.focus() calls to the CJK field while it is visible, plus a
   self-healing onData gate that reclaims focus when it swallows input.

3. Android InputConnection wedge (9-key IMEs + Chromium): the keyboard
   composes in its own UI but delivers zero DOM events. Fix: skip
   redundant textarea value/selection writes (they race IME session
   setup), and re-tapping the focused empty field forces a blur→focus
   cycle that restarts the input session.

Diagnostics: input-cjk.js now traces every IME event/flush decision into
the crash-diag breadcrumbs; /api/crash-diag stores beacons per page-load
id (iOS PWA reloads no longer wipe the trail, concurrent clients no
longer clobber each other) and flushes on visibilitychange.

Tests: test/input-cjk.test.ts (vm-sandbox, 9 cases incl. regression
guards for all three root causes).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant