Skip to content

[parked] fix(input): route paragraph-break deletion through KEYCODE_DEL - #140

Merged
AsafMah merged 2 commits into
devfrom
feat/backspace-paragraph-keyevent
Aug 20, 2026
Merged

[parked] fix(input): route paragraph-break deletion through KEYCODE_DEL#140
AsafMah merged 2 commits into
devfrom
feat/backspace-paragraph-keyevent

Conversation

@AsafMah

@AsafMah AsafMah commented Aug 20, 2026

Copy link
Copy Markdown
Owner

Warning

Draft, parked — do not merge. This asserts a failure mode that device testing did not reproduce. It exists so the work and the evidence against it stay together.

Relates to #133. The provable half of this work shipped separately in #134.

The theory

Block-based editors (MS Word, Samsung/Google Notes, Docs) model paragraphs as separate blocks. The theory was that they silently ignore an InputConnection.deleteSurroundingText that would merge two blocks — and still return true, so the failure is invisible to the IME — while honouring a real KEYCODE_DEL, which is what other keyboards send.

HeliBoard already had a KEYCODE_DEL fallback (8f80c2a11) but scoped it to browsers (TYPE_TEXT_VARIATION_WEB_EDIT_TEXT), so ordinary multi-line fields never reach it. Upstream v4.1.2 carries identical code.

Why it's parked

Tested on the reporter's device (Samsung SM-S936B, Android 16), driving the real on-screen backspace key against the unfixed build:

App Flow Result
Samsung Notes type → Enter → backspace merged correctly
Samsung Notes caret moved in by tap, then backspace merged correctly
MS Word type → Enter → backspace merged correctly
MS Word caret to paragraph start via MOVE_HOME merged correctly
MS Word empty paragraphs (Enter ×3) removed correctly
MS Word held backspace across the boundary deleted through correctly

Both apps honour deleteSurroundingText across a paragraph boundary. The reporter's settings are all defaults (gesture_method=fallback), so no exotic fork backspace path was armed either.

The change is not harmful — the same matrix passes on the fixed build, machine-checked by reading the editor contents back via uiautomator. But "harmless" is not a reason to ship a behaviour change to the input path.

What's here

  • '\n' deletion routed through sendDownUpKeyEvent(KEYCODE_DEL) instead of deleteSurroundingText.
  • The NOT_A_CODE fallback broadened beyond browsers, gated on getExpectedSelectionStart() > 0 so a genuine document start never dispatches a key event at the app (recipient chips, search boxes and web views react to those).
  • The accelerated second delete never chained 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.

What would un-park it

A concrete reproduction: which app, the exact sequence, and whether the text was RTL. The reporter's real content is Hebrew and I could not drive an RTL repro reliably through adb, so that's the most likely gap.

If it reproduces, un-ignore the two parked tests in InputLogicTest.kt (backspace merges paragraphs when the editor refuses deleteSurroundingText, and the tap-into-paragraph variant) and rebase onto whatever dev looks like then.

AsafMah and others added 2 commits August 20, 2026 05:57
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
AsafMah merged commit 745c22f into dev Aug 20, 2026
2 checks passed
@AsafMah

AsafMah commented Aug 20, 2026

Copy link
Copy Markdown
Owner Author

Correction: this shows "merged" but the change is not in dev

GitHub auto-closed this as merged because its head commit became an ancestor of dev — the branch was cut from the backspace branch before the speculative parts were reverted, and that earlier commit came along when #134 merged. The revert happened in a later commit on the same branch (20afdd706), so the net tree is clean.

Verified against origin/dev:

  • deletingParagraphBreak / the '\n'KEYCODE_DEL routing — not present
  • the broadened NOT_A_CODE fallback gated on getExpectedSelectionStart() > 0not present
  • the accelerated-delete emoji fix (codePointBeforeCursorToDeleteAgain) — present, as intended

So nothing unverified shipped. The status badge is a graph artifact, not a code state.

The change itself is still parked, and still available

origin/feat/backspace-paragraph-keyevent still carries it (3 references to deletingParagraphBreak in InputLogic.java). Nothing is lost.

It stays parked for the reason in the original description: device testing on a Samsung SM-S936B found the bug does not reproduce — Word and Samsung Notes both merge paragraphs correctly on the unfixed build, across typed-Enter, caret-moved-by-tap, empty paragraphs and held backspace. Shipping a behaviour change to the input path on an unreproducible bug isn't justified.

To revive it: a concrete repro — which app, exact sequence, and whether the text was RTL (the reporter's real content is Hebrew, and that's the one variant I could not drive reliably through adb).

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