fix(input): correct the accelerated backspace emoji measurement - #134
Merged
Conversation
Backspace appeared dead at the start of a paragraph in rich-text editors (MS Word, Samsung/Google Notes, Docs). Those editors model paragraphs as separate blocks and silently ignore a deleteSurroundingText that would merge two of them, while honouring a real KEYCODE_DEL - which is what other keyboards send. HeliBoard already had a KEYCODE_DEL fallback (8f80c2a) but scoped it to browsers only, so plain multi-line fields never reached it. Upstream v4.1.2 carries the same logic, so this is not fixed by merging upstream. Changes in handleBackspaceEvent: - Route deletion of a '\n' through a key event instead of deleteSurroundingText. Equivalent in a plain EditText. - Broaden the NOT_A_CODE fallback to any editor that hides context, but only when the expected cursor position is > 0, so a genuine document start stays a no-op and we never dispatch key events at apps that react to them (recipient chips, search boxes, web views). - Never chain the accelerated second delete onto a key event: key events take an asynchronous route that ignores batch edits, so a following deleteSurroundingText could race ahead and act on pre-merge text. - Fix a pre-existing bug in the accelerated path, which tested the first code point instead of codePointBeforeCursorToDeleteAgain and could split an emoji. Also present upstream; worth reporting. Tests: the fake InputConnection can now simulate a block-based editor (paragraph-scoped reads, cross-paragraph deletes that report success but do nothing). Also fixes two latent harness bugs: deleteSurroundingText did not clamp to available text (drove selectionStart negative), and setText ignored its own requireIdle parameter. The tap-into-paragraph variant is added as an @ignore'd test: it measures expectedSelectionStart == 0, making it indistinguishable from an empty field, so it needs a device trace before it can be fixed safely. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: a88bd77f-d993-44c5-9efc-c7124f0d825e
#133) Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: a88bd77f-d993-44c5-9efc-c7124f0d825e
AsafMah
added a commit
that referenced
this pull request
Aug 20, 2026
Section 5 was still headed "THE OPEN TASK - publish signed v0.2.0", which is no longer true: v0.2.0 was published on 2026-08-20 with all four signed APKs and is marked latest. The runner outage that blocked it resolved on its own; Release run 31128748928 succeeded and produced the draft. Reframes section 5 from a blocker into the verified release procedure, since the recipe itself is still what the next release should follow, and keeps the outage signature so it is recognised rather than re-debugged. Also refreshes the TL;DR table (v0.2.0 published, current dev head, the four open PRs including #134, #136 and #137) and replaces section 12's "publish v0.2.0" item with the work that is actually outstanding: device verification of #134 and #137, re-pointing LeanType-check-upstream-main to v4.1.2 to re-check the two guarded upstream defects, reporting the emoji accelerated-delete bug upstream, and deciding the fate of the unfinished worktrees whose commits exist on no remote. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: a88bd77f-d993-44c5-9efc-c7124f0d825e
This was referenced Aug 20, 2026
Device testing did not confirm the paragraph-merge theory this branch was originally built around, so the unverified behaviour change is parked on `feat/backspace-paragraph-keyevent` and only the provable fixes remain here. On a Samsung SM-S936B, on the unfixed build, both Microsoft Word and Samsung Notes merged paragraphs correctly in every flow tried: typed Enter then backspace, caret moved into the paragraph by tap, empty paragraphs, and a held backspace across the boundary. The premise that those editors ignore deleteSurroundingText across a block boundary is therefore not established, and routing '\n' deletion through KEYCODE_DEL is not justified on this evidence. What remains is independently correct: - The accelerated (held-backspace) second deletion measured its length from codePointBeforeCursor -- the code point read *before* the first deletion -- instead of codePointBeforeCursorToDeleteAgain. When the second character back is a multi-code-point emoji that picks the wrong length and can split it; in the opposite case it can over-delete. Present in upstream v4.1.2 as well, reported as LeanBitLab#423. - Two latent test-harness bugs. deleteSurroundingText did not clamp to the available text and drove selectionStart negative on an empty field. setText accepted a requireIdle parameter and then called handleMessages() without it, so reset() could not tolerate a leftover delayed message -- harmless until adding tests reshuffled JUnit's hash-based ordering, at which point it failed an unrelated test during setup rather than in its own logic. The fake InputConnection can still simulate a block-based editor (paragraph-scoped reads, cross-paragraph deletes that report success while doing nothing). The two tests that assert the parked behaviour are @ignore'd with the device findings recorded, so the hypothesis and the evidence against it stay together. Suite: 324 tests, 4 failed -- the documented Windows-only ParserTest baseline, confirmed with tools/check_test_results.py (#139). Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: a88bd77f-d993-44c5-9efc-c7124f0d825e
…paragraph-delete # Conflicts: # CHANGELOG.md
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.
Fixes #133
What this is now
This PR was originally a paragraph-backspace fix. Device testing did not confirm the bug, so the unverified behaviour change has been split out to the parked draft #140, and what remains here is only what's independently correct.
What device testing showed
On the reporter's Samsung SM-S936B, driving the real on-screen backspace key against the unfixed build:
MOVE_HOMEBoth apps honour
deleteSurroundingTextacross a paragraph boundary, contradicting the premise. The reporter's settings are all defaults (gesture_method=fallback), so no exotic fork backspace path was armed either. See #140 for the full write-up and what would un-park it.What's left here
A real bug, independently verifiable. The accelerated (held-backspace) second deletion measured its length from
codePointBeforeCursor— the code point read before the first deletion — instead ofcodePointBeforeCursorToDeleteAgain:When the second character back is a multi-code-point emoji this picks length 1 and splits it; in the opposite case it over-deletes. Only reachable once
mDeleteCount > DELETE_ACCELERATE_AT, i.e. while holding backspace, which is probably why it went unnoticed. Present in upstream v4.1.2 too — reported asLeanBitLab/LeanType#423.Two latent test-harness bugs.
deleteSurroundingTextdidn't clamp to the available text and droveselectionStartnegative on an empty field.setTextaccepted arequireIdleparameter and then calledhandleMessages()without it, soreset()couldn't tolerate a leftover delayed message. Harmless until adding tests reshuffled JUnit's hash-based method ordering, at which point it failed an unrelated test during setup rather than in its own logic.Test infrastructure kept. The fake
InputConnectioncan simulate a block-based editor: paragraph-scoped reads, and cross-paragraph deletes that report success while doing nothing (deliberately mirroring the "returnstruebut no-ops" behaviour that makes this class of bug invisible). The two tests asserting the parked behaviour are@Ignored with the device findings recorded inline, so the hypothesis and the evidence against it stay together rather than the hypothesis quietly disappearing.Verification
324 tests, 4 failed— the documented Windows-onlyParserTestbaseline, confirmed withtools/check_test_results.pyfrom #139 (which also checks the results aren't stale and that its own enumeration agrees with the declared totals).