fix(input): deliver a recovered keystroke before the Enter that submits it - #441
Open
shenlvkang-collab wants to merge 1 commit into
Open
shenlvkang-collab wants to merge 1 commit into
shenlvkang-collab wants to merge 1 commit into
Conversation
…ts it Every message typed on an Android phone lost its last character. An Android soft keyboard commits the last typed character and sends the Enter key in ONE InputConnection transaction, so the committed-text `input` event and the Enter keydown are both processed before any zero-delay timer runs. The orphaned-input recovery from Ark0N#388 resolved its candidate only on such a timer, and that lost the character twice over: * ORDER — xterm emits `\r` synchronously from the Enter keydown, and the local-echo composer submits `pendingText` right there. The recovered character arrived one macrotask too late to be part of the prompt. * LOSS — that same `\r` bumps the canonical counter, so by the time the candidate resolved, `canonicalCount > snapshot` read as "xterm spoke for this keystroke" and stood the recovery down. The character was not merely late, it was dropped. Drain pending candidates synchronously at the next keydown instead, from xterm's custom key handler, which runs before xterm processes that key. The counter then still holds the value it had while the candidate's own keystroke was current, so the stand-down decision is made against the right keystroke, and the recovered byte reaches the composer ahead of whatever the new key emits. The timer stays as the fallback for a keystroke with no key after it. Physical keyboards are unaffected: there the timer has already resolved the candidate long before the next key arrives. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The report
Every message typed on an Android phone arrives at the CLI with its last character missing —
帮我看看代码is submitted as帮我看看代. Reproducible on every send, in both Latin and CJK text, with the local-echo composer on (the mobile default,cjkInputEnabled: false).Root cause
An Android soft keyboard commits the last typed character and sends the Enter key in one InputConnection transaction, so the committed-text
inputevent and the Enter keydown are both processed before any zero-delay timer runs.The orphaned-input recovery added in #388 resolves its candidate only on such a timer, and that loses the character twice over:
\rsynchronously from the Enter keydown, andterminal-ui.js's local-echo branch submitspendingTextright there. The recovered character arrived one macrotask too late to be part of the prompt.\rbumpscanonicalCount, so by the time the candidate resolved,canonicalCount > candidate.snapshotread as "xterm spoke for this keystroke" and stood the recovery down. The character was not merely late, it was dropped outright.The file already guards the counter against xterm's self-generated output (DA/DSR/CPR/mouse reports) for exactly this reason, but a genuinely different keystroke's canonical data — the Enter — still falsely answered the question asked about the previous one.
The fix
Drain pending candidates synchronously at the next keydown, from xterm's custom key handler, which runs before xterm processes that key. The counter then still holds the value it had while the candidate's own keystroke was current, so the stand-down decision is made against the right keystroke, and the recovered byte reaches the composer ahead of whatever the new key emits. The zero-delay timer stays as the fallback for a keystroke with no key after it.
Physical keyboards are unaffected: there the timer has already resolved the candidate long before the next key arrives.
Tests
test/terminal-keycode229-recovery.test.tsgains two cases, both failing before the change:keydown → refused insertText → Enter keydown, the character is already emitted at the Enter keydown, exactly once, and no timer is left pending.npm run test:ciis green (7252 passed).test/terminal-keycode229-recovery.browser.test.tsdoes not start in my environment either before or after this change (the Playwright harness times out waiting forapp.terminal), so it is unverified here rather than regressed.🤖 Generated with Claude Code