Skip to content

fix(two-thumb): clear stale merged-trail extend-base on delete + gesture lifecycle#61

Merged
AsafMah merged 2 commits into
AsafMah:devfrom
SHAWNERZZ:fix/two-thumb-clear-extend-base
Jun 7, 2026
Merged

fix(two-thumb): clear stale merged-trail extend-base on delete + gesture lifecycle#61
AsafMah merged 2 commits into
AsafMah:devfrom
SHAWNERZZ:fix/two-thumb-clear-extend-base

Conversation

@SHAWNERZZ

Copy link
Copy Markdown

Fixes #60.

What

WordComposer.mExtendBatchInputBase (the multi-part merged-trail base) was only cleared by a normally-completing gesture, so an abnormal gesture end (cancel / empty-top recognition) left it armed — and no deletion path cleared it. A later fresh swipe then merged with the stale "ghost" trail, most visibly at the start of a text box (where no composing word exists to re-set the base). Full root-cause writeup in #60.

Change

Clear mExtendBatchInputBase at the same word-end sites where mLiveStroke is already dropped, plus the two gesture-lifecycle origins:

  • onStartBatchInput (top, before the extend decision — guarantees every gesture starts from a clean base; the extend branch re-arms it from the real composing word when appropriate)
  • onCancelBatchInput (a cancelled gesture never reaches the routine clear)
  • handleBackspaceEvent (next to the existing mLiveStroke.reset(), before any mode branch — covers all three backspace modes in one place)
  • resetComposingState (covers the delete slider's finishInput and cursor-move resets)
  • commitChosenWord and the composing-desync recovery path

Deliberately not added to the hot WordComposer.reset() path — mirroring the already-reviewed mLiveStroke clearing is equivalent and avoids any interaction with the mid-gesture merge timing.

Tests

8 new JVM tests in InputLogicTest.kt:

  • base cleared after each backspace mode — character / fragment / whole-word
  • base cleared after the delete slider (finishInput)
  • base cleared after a fresh onStartBatchInput
  • base cleared after onCancelBatchInput
  • 2 reachability guards pinning the (currently dead) PointerTracker static-seed interlock (grace > 0 ⇒ isMultipartComposeActive()), so a future settings refactor can't silently re-arm that path without adding the missing stale-static cleanup

:app:testStandardDebugUnitTest (the 8 new tests + WordComposerTest): green. :app:assembleStandardDebug: builds. (The 3 pre-existing known-failing tests — insertLetterIntoWordHangulFails, revert autocorrect on delete, tapOnlyCombiningWordDoesNotShowAutospaceIndicator… — are unrelated.)

Manual test plan

# Steps Expected
1 Two-thumb on. Swipe a word; cancel/abort a follow-up gesture; delete everything; swipe a fresh word at the start of the box. Fresh word recognizes alone — no fusion with the deleted gesture.
2 Repeat #1 with backspace mode = Delete one character. No ghost merge.
3 Repeat #1 with backspace mode = Delete last fragment. No ghost merge.
4 Repeat #1 with backspace mode = Delete whole word. No ghost merge.
5 Repeat #1 clearing via swipe-from-backspace (delete slider). No ghost merge.

Notes

  • Threading: every mExtendBatchInputBase mutation runs on the UI thread (the recognizer runs on the suggestions HandlerThread but only reads a snapshot), so these clears need no locking and can't race the merge.
  • This touches the same area as the recent re-recognize-taps (live-converge) remove (feat(two-thumb): remove the re-recognize-taps (live-converge) feature #47) → revert (Revert: restore the re-recognize-taps (live-converge) opt-in #48); the fix is independent of that feature toggle and hardens the merged-trail lifecycle either way.
  • Out of scope (separate follow-ups): re-extending a truncated word after a partial (character/fragment) backspace still merges the un-trimmed WordComposer.mInputPointers; and the text-expander early-returns in commitChosenWord skip the trail clears for expanded shortcut words.

🤖 Generated with Claude Code

AsafMah and others added 2 commits June 6, 2026 14:32
release: LeanTypeDual v3.8.5
…ure lifecycle

WordComposer.mExtendBatchInputBase (the multi-part merged-trail base) was only
cleared by a normally-completing gesture, so an abnormal end (cancel / empty-top
recognition) left it armed, and no deletion path cleared it. A later fresh swipe then
merged with the ghost trail - most visibly at the start of a text box.

Clear it at the same word-end sites where mLiveStroke is already dropped
(handleBackspaceEvent, resetComposingState, commitChosenWord, desync) plus the two
gesture-lifecycle origins (onStartBatchInput top, onCancelBatchInput). Does not touch
the hot WordComposer.reset() path.

Adds 8 tests: base cleared after each backspace mode (character/fragment/whole-word),
the delete slider, fresh onStartBatchInput, and onCancelBatchInput; plus 2 guards
pinning the (currently dead) static-seed interlock.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@AsafMah
AsafMah changed the base branch from main to dev June 6, 2026 17:41
@AsafMah

AsafMah commented Jun 7, 2026

Copy link
Copy Markdown
Owner

On-device verified (S24+, standard debug build) alongside the rest of the integration branch: fresh swipe after delete no longer fuses with the deleted gesture's trail. ✅

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.

Two-thumb: deleted gesture trail leaks into the next fresh swipe (ghost-merge, esp. at start of a text box)

2 participants