This file records actions taken by AI coding agents in the LeanType repository. Each session is appended as a new entry. Agents are required to update this file at the end of every session (see Agents.md §7.4).
The repository had no Agents.md or dev-log.md. The working branch is copilot/create-or-update-agents-md. The shallow clone contains three commits: the main branch tip and a merge of the copilot/multi-part-words branch that documented the wave-5 (multi-part word composition) two-thumb typing changes.
- Explored full repository structure: README, CONTRIBUTING, docs/, .github/, app/src/ layout, settings architecture, build files, test suite, CI workflow.
- Read
docs/FEATURES.md(AI features, two-thumb typing, floating keyboard, clipboard features). - Read
docs/TWO_THUMB_TYPING_INTERNALS.mdheader to understand the combining-mode state machine. - Read key source files:
Settings.java,Defaults.kt,SettingsValues.java,AIIntegrationScreen.kt,FloatingKeyboardManager.kt,InputLogic.java(header), build configuration. - Verified no
Agents.mdexisted before this session. - Created
/Agents.md— comprehensive agent guide covering: project overview, repo layout, architecture (input pipeline, suggestions, settings 5-file pattern, AI integration, floating keyboard, build flavors), build/test commands, key source file reference, code conventions, agent workflow requirements (clarify first, mandatory tests, manual test plan format, dev log), feature-specific notes, upstream sync guidance, and useful links. - Created
/dev-log.md— this file.
- Named the file
Agents.md(sentence-case) to match the problem statement's exact wording rather than theAGENTS.mdall-caps convention — keeping consistent with the user's request. - Included the dev log as a separate file (
dev-log.md) rather than a section insideAgents.mdso that the instructions file stays stable while the log grows over time. - Captured the 5-file settings scaffolding pattern prominently (§3.3 and §6) because it is the most commonly missed convention for new contributors and agents.
- Included manual test plan format (§7.3) as a Markdown table template so agents can copy-paste it directly into PR descriptions.
- Noted the IME dialog /
EditTextlimitation (§6) because it is a non-obvious gotcha that has burned previous sessions.
- The problem statement mentions "everything from the fork" — if there are additional upstream branches or features not yet merged into this shallow clone, a full
git fetch --unshallowwould reveal them. A follow-up session could extendAgents.mdwith those details. - If the user has specific preferences about which AI providers or model IDs to highlight (e.g., a recommended default), those should be clarified and added to §8.
- The
dev-log.mdformat is a proposal; adjust the template inAgents.md §7.4if a different structure is preferred.
The standard debug APK was built and installed on a paired Android device from branch copilot/improve-two-thumb-typing-again. After restoring a backup, the new toolbar keys appeared, but Join Next only suppressed autospace and Force Next Space did not reliably suppress autospace after the following word.
- Updated
app/src/main/java/helium314/keyboard/latin/inputlogic/InputLogic.javaso Join Next resumes/removes a trailing autospace, restores the previous word as composing text when needed, and enters an explicit join mode for the next tap or gesture. - Updated
InputLogic.javaso Force Next Space avoids inserting a duplicate space when an autospace already exists, then arms suppression for the next automatic spacing decision. - Revised Force Next Space to track the next word explicitly (
mForceNextSpacePendingWord/mSuppressAutospaceForForceNextSpace) so unrelated spacing helper calls cannot consume the one-shot before that word's own autospace is due. - Moved the Force Next Space transition from "combining mode entered" to actual next tap/gesture start (
markForceNextSpaceWordStarted) so real input paths arm suppression before the next word is committed. - Fixed real-editor selection callback timing:
onUpdateSelection()now ignores belated/expected updates before clearing one-shot state, so the normal cursor update after Force Next Space inserts a space does not erase the pending force action. - Gated the spacebar combining/autospace progress indicator on
shouldInsertSpacesAutomatically()and the current language's spacing support, and hide it immediately when the toolbar Autospace toggle is turned off.
The user requested a plan and implementation for visible on/off state indicators on toolbar state keys, plus making Force Auto-Capitalize work even when normal Auto-Capitalize is off. The approved UX was an explicit active tint/background, normal icon appearance when inactive, and Autospace showing effective state including temporary suppression from Force Next Space.
- Updated
ToolbarUtils.ktso the requested state keys get a translucent accent active background while inactive icons remain normal. - Updated toolbar state refresh coverage for split-keyboard prefs and immediate toggle handlers for autocorrect, autospace, auto-cap, force auto-cap, and incognito.
- Updated Autospace state mapping so it shows inactive while Force Next Space is armed.
- Updated
Colors.ktso toolbar icon color does not dim for inactive state keys; the active background now carries the state indication. - Updated
InputLogic.getCurrentAutoCapsState()so Force Auto-Capitalize can force sentence caps when normal Auto-Capitalize is off, while preserving password/visible-password guards. - Added targeted
InputLogicTestcoverage for Force Auto-Capitalize with Auto-Cap off and password fields. - Rebuilt
:app:assembleStandardDebugand installed the APK on the paired device.
- Kept state indication centralized in
ToolbarUtilsrather than adding separate icon assets for each state key. - Used effective Autospace state for the toolbar button, including Force Next Space suppression, matching the user's requested behavior.
| # | Steps | Expected Result |
|---|---|---|
| 1 | Add/pin Incognito, One-handed, Split, Autocorrect, Auto-cap, Force Auto-cap, Autospace, Join Next, and Force Next Space toolbar keys. Toggle each state. | Active states show an accent background; inactive states show a normal icon without the active background. |
| 2 | Tap Force Next Space. | Force Next Space shows active and Autospace shows inactive until the one-shot is consumed/cancelled. |
| 3 | Turn Auto-Capitalize off, then turn Force Auto-Capitalize on. Type at the start of a sentence in a normal text field. | Sentence-start capitalization still occurs. |
| 4 | Repeat Force Auto-Capitalize in a password/visible-password field. | Force capitalization does not apply. |
- If the accent background is too subtle or too strong on a specific theme, tune the active background alpha in
ToolbarUtils.createToolbarStateBackground(). - Updated
app/src/main/java/helium314/keyboard/latin/inputlogic/OneShotSpaceAction.ktwith a targetedconsumeJoinNext()helper so join-mode consumption does not clear unrelated one-shot state. - Added regression coverage in
app/src/test/java/helium314/keyboard/latin/InputLogicTest.ktfor joining after a timer autospace and forcing a space after an existing autospace. - Rebuilt
:app:assembleStandardDebugand installed the APK on the paired device withadb install -r.
- Kept Join Next as a one-shot action, but consumed it when the next tap or gesture actually joins, so the combined word can still autospace normally afterward.
- Made Force Next Space idempotent around an existing trailing space, because the toolbar action should mean "ensure a space before the next word", not "always add another space".
- Full
InputLogicTestclass execution still has pre-existing failures under the standard debug unit-test variant (insertLetterIntoWordHangulFails,revert autocorrect on delete); the targeted one-shot key tests pass.
The branch copilot/improve-clipboard-editability implemented clipboard clip editing inside the IME by embedding an edit panel and secondary keyboard in ClipboardHistoryView. The user clarified that edit mode is only worthwhile if it behaves like a normal text box using the user's regular selected keyboard.
- Removed the inline edit panel from
app/src/main/res/layout/clipboard_history_view.xml. - Removed edit-mode state, local text insertion, and local Shift/Symbol keyboard handling from
ClipboardHistoryView.kt. - Added
ClipboardClipEditActivity.kt, a non-exported Activity with a normal focusedEditText. - Registered
ClipboardClipEditActivityinAndroidManifest.xmlwithstateAlwaysVisible|adjustResize. - Wired the clipboard long-press Edit action to launch the Activity with the clip id.
- Added
ClipboardDaoTest.ktcovering normal edit, empty-delete, duplicate merge, missing clip, and image-clip ignore behavior. - Built
:app:assembleStandardDebugand installed the standard debug APK on the connected phone.
- Chose an Activity instead of an IME-hosted view because Android only routes the selected user keyboard normally to a real focused app editor, not to a child editor inside the IME window.
- Passed only the clip id to the Activity and reloaded from
ClipboardDao, so the Activity edits the latest stored clip content. - Kept existing
ClipboardDao.updateText()semantics: empty text deletes, duplicate text merges, image clips are ignored, and normal edits bump timestamp.
| # | Steps | Expected Result |
|---|---|---|
| 1 | Open the clipboard view, long-press a text clip, and tap Edit. | A normal edit screen opens with the clip text focused. |
| 2 | Type using the keyboard that appears, including Shift and Symbols. | The user's normal keyboard handles input and stays in edit mode. |
| 3 | Tap Save after changing the text. | The clipboard clip updates and returns to the prior flow. |
| 4 | Tap Cancel after changing the text. | The clipboard clip remains unchanged. |
| 5 | Save an empty clip. | The clip is deleted through existing clipboard storage semantics. |
- User should confirm on-device whether launching a normal Activity is acceptable visually, since the editor no longer lives inside the clipboard panel.
After the Activity-based clipboard editor was installed, the user asked whether it could avoid covering the entire screen.
- Added
ClipboardClipEditActivityThemeinplatform-theme.xmlusing a dialog-style Activity theme. - Switched
ClipboardClipEditActivityto the dialog theme inAndroidManifest.xml. - Changed the editor content layout to wrap height with a fixed-height multi-line
EditText, so the editor is a floating dialog instead of fullscreen. - Rebuilt
:app:assembleStandardDebugand installed the APK on the connected phone.
- Kept the editor as an Activity, not an IME child view, so it remains a normal focused text box and continues to use the user's selected keyboard.
- User should verify whether the dialog height is comfortable on the target phone; it is currently a 280dp editor area.
The dialog-sized editor still opened with LeanType's clipboard panel as the active keyboard. The user asked for the keyboard to return from clipboard to default when the editor appears, and to return to the clipboard menu after saving.
- Updated
ClipboardHistoryView.showEditActivity()to callKeyboardSwitcher.setAlphabetKeyboard()before launchingClipboardClipEditActivity. - Updated the editor Activity save action to persist the edit, call
KeyboardSwitcher.setClipboardKeyboard(), and then finish. - Rebuilt
:app:assembleStandardDebugand installed the APK on the connected phone.
- Used
setAlphabetKeyboard()/setClipboardKeyboard()directly instead of key-event simulation so the editor flow does not accidentally type into the target app or depend on current key layout state.
- User should verify the visible transition: Edit opens with the alphabet keyboard; Save closes the editor and restores clipboard history.
The user reported the Activity-based clipboard edit flow still felt janky after switching between alphabet and clipboard modes.
- Disabled Activity open/close transitions for
ClipboardClipEditActivity. - Delayed returning to the clipboard keyboard until shortly after save starts closing the editor, rather than swapping keyboard content underneath the still-visible dialog.
- Rebuilt
:app:assembleStandardDebugand installed the APK on the connected phone.
- Kept the alphabet-before-edit and clipboard-after-save behavior, but changed the timing to reduce visible flicker.
- User should verify whether the transition now feels acceptable on-device.
The user requested that Cancel also return to the clipboard page, and that the return-to-clipboard animation/flicker be skipped.
- Changed Cancel to use the same close-and-return-to-clipboard path as Save.
- Removed the delayed clipboard restore; clipboard mode is now posted immediately after the no-animation Activity finish.
- Rebuilt
:app:assembleStandardDebugand installed the APK on the connected phone.
- Kept
overridePendingTransition(0, 0)on open/close and removed the artificial delay so returning to clipboard does not visibly animate through the normal keyboard first.
- User should verify the Save and Cancel transitions on-device.
The user requested resolution of merge conflicts in PR #3 (copilot/improve-two-thumb-typing-again). The PR had mergeable_state: "dirty" due to conflicts with changes merged into main from the clipboard editability work.
- Fetched the latest
mainbranch from origin. - Attempted merge of
origin/mainintocopilot/improve-two-thumb-typing-again. - Identified conflict in
dev-log.mdwhere both branches had added new session entries. - Resolved conflict by preserving both session entries (one-shot toolbar spacing keys from current branch, clipboard editor improvements from main).
- Completed merge commit
eae5f8b7. - Pushed resolved merge to origin.
- Verified PR now shows
mergeable_state: "clean".
- Kept all dev-log entries from both branches in chronological order, as both represent valid work done in parallel branches.
- Used standard git merge workflow rather than rebase to preserve the full history of both branches.
- PR #3 is now ready for final review and merge into main.
Review comment r3289683508 on PR #3 noted that createToolbarStateBackground() allocates new GradientDrawable/StateListDrawable instances every time toolbar state is refreshed (potentially frequently during typing). The reviewer suggested caching the drawable per Context/theme to reduce allocations/GC.
- Added
ToolbarStateBackgroundCachedata class to hold radius, activeColor, and drawableConstantState. - Modified
createToolbarStateBackground()to check cache validity (matching radius and activeColor) before creating new drawables. - When cache is valid, clone from
constantState.newDrawable()and mutate to avoid shared state. - When cache is invalid or empty, create new drawables and update cache with the new
constantState. - Ran
parallel_validation(CodeQL: no alerts, Code Review: noted potential concurrency issue but acceptable for UI-thread-only usage). - Pushed changes in commit
fcd7c1d4.
- Used
ConstantStatecloning pattern (standard Android drawable caching approach) rather than direct drawable reuse to avoid shared-state bugs. - Did not add synchronization because toolbar state updates run on the main/UI thread in current usage.
- Keyed cache on both radius and activeColor so theme changes invalidate the cache automatically.
- If toolbar state updates ever move off the main thread, add
@Volatileor synchronization to the cache variable.
The prior direct-action shortcut implementation was reverted because it did not match the requested Nintype-style behavior. The corrected approach is opt-in, layout-driven shortcut rows: a vertical swipe from an eligible source row opens a temporary one-row panel, and releasing on a panel key dispatches that key through the normal keyboard action path.
- Added
SHORTCUT_TOPandSHORTCUT_BOTTOMlayout types and default JSON row layouts underapp/src/main/assets/layouts/shortcut_top/andapp/src/main/assets/layouts/shortcut_bottom/. - Added opt-in gesture settings for shortcut rows, top-row shortcut swipe, and bottom-row shortcut swipe.
- Added
ShortcutRowKeys.ktto parse shortcut-row layouts into popup-key specs so the feature reuses existing slide-to-select popup panel behavior. - Extended
DrawingProxyandMainKeyboardViewwithshowShortcutRowKeyboard(...). - Extended
PointerTrackerwith shortcut-row swipe state, top/bottom source-row eligibility, vertical-dominant trigger thresholds, and panel open/commit/cancel handling. - Added a parser test verifying shortcut row layouts produce expected functional key codes.
- Reused
PopupKeysPanel/PopupKeysKeyboardViewfor the temporary row surface instead of adding a new full keyboard layer or hardcoded actions. - Kept the feature off by default behind a master toggle and separate top/bottom toggles.
- Excluded modifier keys and existing key swipers (space/delete) from shortcut-row source eligibility so shift/symbol/numpad chording and existing swipe behaviors keep their current release paths.
- Used existing layout/default/per-subtype infrastructure for row contents, so future customization can happen through layout files instead of serialized action maps.
| # | Steps | Expected Result |
|---|---|---|
| 1 | Open Settings → Gesture typing, enable Shortcut rows, then enable Top shortcut row swipe. | Toggles persist; no crash. |
| 2 | In a text field, swipe up from a top normal typing-row key. | A temporary shortcut row appears; releasing on Undo, Redo, Copy, Paste, Select word, Select all, or Emoji triggers that key/action. |
| 3 | With number row enabled, swipe up from the number row. | The number row is treated as the top source row and opens the top shortcut row. |
| 4 | Swipe up from shift, symbol/alpha, numpad, space, or delete. | Shortcut row does not open; existing modifier/key-swipe behavior remains unchanged. |
| 5 | Enable Bottom shortcut row swipe and swipe down from the bottom normal typing row. | The bottom shortcut row appears and selected keys dispatch normally. |
| 6 | Start a normal gesture-typing word from a letter with horizontal/diagonal motion. | Gesture typing still works; shortcut row only opens for clear vertical-dominant movement. |
| 7 | Long-press comma or period. | Existing popup menus still open. |
| 8 | Hold/slide from numpad or symbol keys to pick a symbol. | Existing momentary symbol/numpad behavior still returns to the correct keyboard state. |
| 9 | Disable Shortcut rows. | No shortcut rows open from top/bottom swipes. |
- On-device testing should confirm the popup-row placement feels correct for top-row and bottom-row swipes; if full-width positioning is not comfortable, a dedicated
ShortcutRowPanelcan replace the popup surface without changing the layout model.
The two-thumb typing settings screen mixed current user-facing features, legacy/manual-spacing controls, debugging options, and future/unimplemented toggles. Some settings were confusingly named after implementation details, while gesture_apostrophe_key and multipart_join_key_mode were exposed even though they are not wired to runtime behavior.
- Created branch
copilot/organize-two-thumb-settingsfromorigin/main. - Reorganized
TwoThumbTypingScreen.ktinto clearer user-facing sections:- Build words from taps and swipes
- Manual spacing mode
- Two-finger input
- Recognition tuning
- Troubleshooting
- Renamed visible labels/summaries for the main combining grace, tap extra time, multi-part joining, and typed-prefix swipe continuation options.
- Removed unimplemented/dead settings from the screen and global search registry:
PREF_GESTURE_APOSTROPHE_KEYPREF_MULTIPART_JOIN_KEY_MODE
- Kept existing preference keys/defaults/runtime reads in place for compatibility; only the user-facing settings registry was changed.
- Built
:app:assembleStandardDebugand ranSettingsContainerTest.
- Kept the existing runtime behavior unchanged and only reorganized/renamed the UI.
- Left legacy manual spacing visible but moved it into its own advanced-feeling section so it is not confused with the recommended combining-mode flow.
- Left debug point drawing visible under Troubleshooting rather than mixing it with recognition settings.
- Did not remove old strings yet, because other docs/translations may still refer to them and keeping them is safer than a broad cleanup.
| # | Steps | Expected Result |
|---|---|---|
| 1 | Open Settings → Two-thumb typing with gesture typing disabled. | Screen shows a simple “Enable gesture typing first” hint instead of confusing no-op toggles. |
| 2 | Enable gesture typing, then open Two-thumb typing. | Settings are grouped into the new user-facing sections. |
| 3 | Set Wait for next input to 0 ms. | Advanced combining options are hidden. |
| 4 | Set Wait for next input above 0 ms. | Follow-up timing, word joining, backspace, autocorrect, and suggestion options appear. |
| 5 | Disable Join word parts. | Sub-options for full-word suggestions, typed-prefix swipe, and fragment backspace are hidden unless manual spacing needs fragment backspace. |
| 6 | Enable Manual spacing. | Manual-spacing-related fragment backspace appears when not already shown under word joining. |
| 7 | Enable Tap letters while swiping. | Maximum tap length appears. |
| 8 | Enable Two-thumb point hinting. | Left/right hand split appears. |
| 9 | Search settings for “apostrophe” or “join next modifier”. | The removed unimplemented controls do not appear. |
- User should review the new wording on-device and decide whether debug point drawing should remain visible or move behind the separate debug settings screen later.
After trying the first reorganization, the user clarified the desired structure: a single spacing-mode selector should drive normal/manual/autospace behavior, backspace should be a single behavior selector, and implementation details like multi-part joining should be enabled automatically instead of exposed as separate confusing toggles.
- Added a synthetic Spacing mode radio/list setting with Normal spacing, Manual spacing, and Auto-space after a delay.
- Added a synthetic Backspace behavior radio/list setting shown only for manual/autospace modes.
- Mapped the spacing selector onto the existing
PREF_GESTURE_MANUAL_SPACINGandPREF_COMBINING_GRACE_MSruntime preferences. - Mapped backspace behavior onto existing fragment-backspace and whole-word-backspace preferences.
- Added whole composing-word deletion for the new Delete whole word behavior when manual spacing or autospace mode is active.
- Forced multi-part internals (join word parts, full-word suggestions, typed-prefix continuation) on whenever non-normal spacing is active, removing those implementation toggles from the user-facing screen.
- Changed the default after-autospace suggestion behavior to Alternatives, then next word on space.
- Renamed autospace timing and tap timing labels to clearer user-facing wording.
- Rebuilt
:app:assembleStandardDebugand ranSettingsContainerTest.
- Kept underlying preference keys for compatibility but made the UI present modes instead of exposing each low-level boolean.
- Defaulted the autospace mode transition to a 500 ms grace when switching from Normal/Manual to Autospace.
- Left Tap letters while swiping as a separate opt-in feature for now because it is a distinct runtime path; only the timing label/summary was clarified.
- Left Improve two-thumb recognition off by default and described the possible tradeoff, because the midline can hurt recognition if configured poorly.
| # | Steps | Expected Result |
|---|---|---|
| 1 | Open Settings → Two-thumb typing. | First control is Spacing mode with Normal, Manual, and Auto-space choices. |
| 2 | Select Normal spacing. | Autospace duration/backspace behavior controls are hidden; typing behaves normally. |
| 3 | Select Manual spacing. | Backspace behavior appears; no autospace duration controls appear. |
| 4 | Select Auto-space after a delay. | Duration, tap delay, autocorrect, after-autospace suggestions, and backspace behavior appear. |
| 5 | In Auto-space mode, check After auto-space, show… default. | New default is Alternatives, then next-word on space unless an older saved value exists. |
| 6 | Try each backspace behavior in Manual/Autospace modes. | Normal deletes characters, last part removes the latest fragment, whole word removes the composing/last swiped word. |
| 7 | Search settings for “join word parts”, “typed prefix”, or “full composing”. | These implementation toggles no longer appear separately. |
- User should confirm whether Tap letters while swiping should remain user-facing or be folded into the spacing mode later.
- Debug overlay expansion (different colors/shapes for fragments, taps, fingers, start/end) is still future work.
The user tested several tap/swipe word combinations and found they worked without the old tap-during-swipe flag. The newer combining/multi-part word system appears to cover the useful behavior, while the old flag only suppressed quick child taps and added confusion.
- Removed the tap-during-swipe setting and timing setting from the two-thumb settings screen and global settings registry.
- Removed the corresponding
Settingsconstants, defaults,SettingsValuesfields, andPointerTrackersuppression path. - Updated docs and user-facing references so the removed setting is no longer advertised.
- Built
:app:compileStandardDebugKotlin.
- Kept combining/multi-part tap seeding intact; only the obsolete simultaneous tap suppression flag was removed.
- Left the observed “giraffe” gesture-recognition issue as a separate investigation target, likely around gesture recognition/hinting rather than settings UI.
- Investigate why some
giraffeattempts stop swipe detection after the second letter. - Investigate why first attempts often recognize unrelated long words before learning improves.
The user wanted an opt-in autospace behavior for two-thumb Auto-space mode: tap-only words should commit without inserting an automatic space, while words that include a swipe should keep the existing autospace flow.
- Added
PREF_COMBINING_AUTOSPACE_ONLY_AFTER_GESTUREacross the settings 5-file pattern:Settings.java,Defaults.kt,SettingsValues.java,strings.xml, andTwoThumbTypingScreen.kt. - Added the setting to the Auto-space mode group as Only auto-space after swipes, defaulting off to preserve existing behavior.
- Updated
InputLogic.javato track whether the current combining word has received a gesture fragment, including tap-then-swipe words where the composer is later downgraded out of batch mode. - Suppressed timer-driven autospace for tap-only words when the new setting is enabled, while still committing the word and preserving suggestion-revert behavior.
- Added
InputLogicTestcoverage for tap-only suppression, gesture autospace, tap-then-gesture autospace, andSettingsContainerTestcoverage for setting registration. - Built
:app:assembleStandardDebugand installed the APK on the connected device.
- Kept explicit spaces, punctuation, Join Next, and Force Next Space paths on their existing behavior; the new setting only gates the timer's automatic space after committing a combining word.
- Used a dedicated per-word gesture flag instead of relying only on
WordComposer.isBatchMode(), because tap-then-swipe composition can unset batch mode after merging fragments. - When autospace is skipped, kept the suggestion strip in an alternatives/revert-friendly state instead of requesting next-word predictions, since there is no trailing space yet.
| # | Steps | Expected Result |
|---|---|---|
| 1 | Open Settings → Two-thumb typing, select Auto-space after a delay, and enable Only auto-space after swipes. | Toggle turns on; no crash. |
| 2 | Tap a word letter-by-letter and pause past the autospace delay. | Word commits, but no automatic space is inserted. |
| 3 | Swipe a word and pause past the autospace delay. | Word commits with the normal automatic space. |
| 4 | Tap a prefix, then swipe the rest of the word before the delay expires. | Combined word commits with the normal automatic space. |
| 5 | Disable Only auto-space after swipes and repeat a tap-only word. | Existing autospace behavior returns. |
- The targeted new unit tests pass. A full
InputLogicTestrun in the standard debug variant still reports unrelated existing failures (insertLetterIntoWordHangulFails,revert autocorrect on delete).
After enabling gesture-gated autospace, tap-only words no longer inserted an automatic space, but the first tapped letter still showed the autospace progress bar even though no autospace would happen.
- Updated
InputLogic.enterCombiningMode()so the spacebar autospace progress indicator only appears when autospace can actually be inserted under the current settings. - Added regression coverage in
InputLogicTestverifying tap-only input with Only auto-space after swipes enabled arms the combining timer without showing an autospace indicator. - Rebuilt
:app:assembleStandardDebug.
- Kept the underlying combining commit timer active for tap-only words; only the autospace visual indicator is hidden until the current word includes a gesture fragment.
- APK install was not completed because ADB reported no connected devices.
The user asked for a new PR based on main that merges changes from the configured upstream remote. origin/main and upstream/main had diverged, with upstream containing build, README, symbol-row, floating-keyboard persistence, clipboard icon, touchpad, and release/version updates.
- Created branch
copilot/merge-upstream-mainfromorigin/main. - Merged
upstream/maininto the branch. - Resolved conflicts in
KeyboardIconsSet.ktby preserving LeanType toolbar state icons while keeping the merge compatible with upstream's icon mapping changes. - Resolved conflicts in
ClipboardDao.ktby preserving clipboard edit support and upstream synchronization around pin toggling.
- Used a merge branch instead of rebasing so the PR clearly represents an upstream sync into
main. - Preserved LeanType-specific clipboard editing and toolbar state-key behavior where it overlapped with upstream changes.
- Build validation and PR creation should complete before merging.
The user asked to return to PR #6, merge current main, and resolve the review comments on the two-thumb settings PR. Merging origin/main into copilot/organize-two-thumb-settings conflicted only in this dev log.
- Merged
origin/mainintocopilot/organize-two-thumb-settingsand preserved both branches' dev-log entries. - Updated
TwoThumbTypingScreen.ktso the "Enable gesture typing first" hint renders as screen content instead of an empty settings category. - Removed the low-level whole-word backspace toggle from the settings search registry, leaving only the synthetic Backspace behavior selector visible.
- Fixed
InputLogic.javaso whole-word backspace in manual/autospace composing mode deletes the composing text from the editor before resetting the composer. - Reduced
GestureDebugPointsDrawingPreview.javaallocation churn by growing snapshot arrays amortized and reusing HSV color storage during drawing. - Added regression coverage for whole-word backspace deleting composing text and for hiding the low-level backspace setting from the registry.
- Kept the user-facing Backspace behavior mode selector as the only visible control for whole-word backspace semantics.
- Preserved the accumulated debug overlay behavior while changing its storage strategy to avoid repeated full-array copies per fragment.
- Run targeted tests and push the review fixes to PR #6.
After testing the PR branch, the user asked to make the live composing-text deletion and debug overlay accumulation behavior toggleable, and to restore the removed full-word suggestion setting.
- Added a
Delete live composing textswitch shown when Backspace behavior is set to whole-word deletion. - Added an
Accumulate debug fragmentsswitch under the gesture debug overlay setting. - Restored the
Suggestions for full composing wordsetting to the two-thumb screen and made the runtime value respect the switch instead of forcing it on for all non-normal spacing modes. - Added tests covering the new setting registration and the new whole-word backspace toggle behavior.
- Defaults preserve the current PR behavior: live composing text deletion, debug fragment accumulation, and full-word suggestions are all enabled unless the user turns them off.
- Run targeted tests and rebuild/install before handing back for device testing.
The user pointed out that the tap-during-swipe behavior had been made hardcoded, but they had asked for it to remain a toggleable option.
- Restored
PREF_GESTURE_TAP_DURING_SWIPEas a settings key/default/runtime value. - Added the
Tap during swipeswitch back to the two-thumb settings screen. - Gated
PointerTracker's pending tap-fragment behavior behind the restored preference. - Added settings registry coverage for the restored toggle.
- Built and installed the updated standard debug APK on the connected device after fully uninstalling the mismatched existing debug package.
- Defaulted the toggle to enabled so existing PR behavior remains unchanged unless the user turns it off.
- Commit and push the PR update.
After PR #6 was merged to main, the user asked to ensure this worktree was on main and reported that toolbar toggle highlighting only appeared after changing a toggle, instead of reflecting active defaults immediately.
- Freed the local
mainbranch from another worktree, checked this worktree out onmain, and fast-forwarded it to the merged PR #6 commit. - Fixed toolbar construction so state-key activation is reapplied after
setupKey()assigns the normal toolbar background. - Built and installed the updated standard debug APK on the connected device.
- Kept the existing state computation in
ToolbarUtils; the bug was ordering during initial button setup, not the active-state logic.
- Commit and push the fix on
main.
Copilot review comments on PR #6 flagged that debug overlay accumulation depended on the visual autospace indicator state, and that changing spacing modes discarded the user's configured autospace duration.
- Separated the debug accumulation state in
MainKeyboardViewfrom the visible spacebar combining indicator. - Preserved debug overlay accumulation for manual-spacing composition as well as hidden-indicator combining mode.
- Added a hidden last-autospace-duration preference and restored it when switching back to Auto-space mode.
- Ran targeted settings test, built the standard debug APK, and installed it on the connected device.
- Kept the visual indicator behavior unchanged; only the debug overlay preservation decision now uses the dedicated composition/debug state.
- Preserved the user's last positive autospace duration when switching to Normal or Manual spacing, then restored it when Auto-space is selected again.
- Commit, push, and resolve the PR review threads.
After installing PR #6, the user reported the two-thumb settings screen showed "Enable gesture typing first" even though gesture typing was enabled.
- Changed
TwoThumbTypingScreen.ktto gate the "Enable gesture typing first" hint only onPREF_GESTURE_INPUT, notJniUtils.sHaveGestureLib. - Built and installed the updated standard debug APK on the connected device.
- Kept gesture-library availability checks on the dedicated Gesture typing screen, where the library loader is shown; the two-thumb screen now reflects the actual user-facing enable toggle.
- Commit and push the fix.
After the debug app was fully uninstalled to fix a signature mismatch, gesture typing stopped working. The reinstall preserved the gesture preference, but the user-supplied native gesture library in app files was gone.
- Confirmed the device app files did not contain
libjni_latinime.so. - Downloaded the arm64 OpenBoard
libjni_latinimegoogle.so, verified its expected SHA-256 checksum, copied it into the app files aslibjni_latinime.so, and force-stopped the app so it reloads. - Updated the two-thumb settings gate to distinguish between "gesture toggle is off" and "gesture library is missing".
- Built and installed the updated standard debug APK while preserving the restored gesture library file.
- Kept the library availability gate for the two-thumb screen, but changed the missing-library message so it no longer incorrectly says only "Enable gesture typing first".
- Commit and push the corrected screen text.
After testing, the user confirmed the tap-during-swipe fragment behavior was causing the typing problem and asked to remove both the implementation and the setting.
- Removed the pending tap-fragment state machine from
PointerTracker. - Removed the
Tap during swipesetting from the two-thumb screen and settings registry coverage. - Removed the preference key/default/runtime read and the now-unused strings.
- Built and installed the updated standard debug APK on the connected device.
- Kept the rest of two-thumb composing, autospace, backspace, and debug overlay options unchanged.
- Commit and push the PR update.
The user reported that the two-thumb backspace selector had misleading wording and broken behavior: Delete last word part should be Delete last fragment, fragment mode behaved like normal character delete, and whole-word mode mishandled live composing text depending on the Delete live composing text sub-option.
- Renamed the fragment-mode label to Delete last fragment.
- Updated fragment boundary tracking in
InputLogic.javaso the first swipe fragment is recorded and extended swipe fragments record both the previous boundary and the new fragment end. - Fixed fragment backspace so an end boundary equal to the current composing length is treated as the current fragment marker, not stale state.
- Changed whole-word backspace with Delete live composing text disabled to fall back to normal one-character live composing deletion instead of consuming backspace as a no-op.
- Cleared the editor composing span after whole-word live composing deletion to avoid stale composing state.
- Preserved the committed fragment stack after delayed autospace commits so repeated Delete last fragment presses pop one committed fragment at a time instead of reverting to character deletion after the first pop.
- Added focused JVM regression tests for live fragment backspace, repeated committed fragment backspace, whole-word live composing deletion on/off, and the renamed label.
- Ran focused backspace/settings regression tests.
- Re-paired wireless ADB to
SM-S936Band installed:app:installStandardDebug.
- Kept default gesture/batch rejection behavior outside fragment mode unchanged; the fix is scoped to the two-thumb backspace selector and live composing option.
- Added a single-fragment regression test because Delete last fragment should also delete a one-swipe composing word, not only the last piece of a multi-fragment word.
| # | Steps | Expected Result |
|---|---|---|
| 1 | Open Settings → Two-thumb typing → Backspace behavior. | The fragment option is labeled Delete last fragment. |
| 2 | Enable manual spacing, choose Delete last fragment, swipe one word, then press Backspace. | The whole swiped fragment is removed in one press. |
| 3 | With Delete last fragment, swipe a word fragment and then swipe another fragment to extend it, then press Backspace. | Only the latest fragment is removed; the previous fragment remains composing. |
| 4 | With Delete last fragment, wait for delayed autospace to commit the combined word, then press Backspace twice. | The first press removes the autospace and latest fragment; the second press removes the previous fragment. |
| 5 | Choose Delete whole word, turn Delete live composing text off, type letters, then press Backspace. | One character is deleted and the remaining text stays live/composing. |
| 6 | Choose Delete whole word, turn Delete live composing text on, type letters, then press Backspace. | The whole live composing word is removed in one press, and the next typed word deletes normally. |
- Full
InputLogicTestclass execution still fails on existing unrelated tests (tapOnlyCombiningWordDoesNotShowAutospaceIndicatorWhenGestureGateEnabled,insertLetterIntoWordHangulFails,revert autocorrect on delete); the focused backspace/settings tests pass. - PR opened at #8.
The backspace fixes were ready in PR #8, and the user asked to merge them to main, merge upstream/main into our main, then merge the updated main into the shortcuts PR branch and install it.
- Merged
fix-backspace-delete-optionsintomainand pushedorigin/main. - Merged
upstream/mainintomain, resolving conflicts inClipboardHistoryView.ktandKeyboardIconsSet.ktby keeping LeanType's bottom-row geometry and clear-clipboard rounded icon while accepting upstream changes elsewhere. - Merged the updated
origin/mainintocopilot/implement-keyboard-shortcuts. - Fixed a merge artifact in
ToolbarUtils.ktwhere duplicateColorandDrawableimports broke Kotlin compilation. - Built and installed
:app:installStandardDebugfor the updated shortcuts branch onSM-S936B.
- Preserved LeanType-specific clipboard bottom-row layout behavior over upstream's new helper because the existing branch intentionally used secondary-keyboard geometry for alpha/symbol layouts.
- Kept the rounded clear-clipboard toolbar icon to match LeanType toolbar icon styling.
- Push the updated shortcuts PR branch.
With Manual spacing on (Nintype-style "never auto-commit"), tapping letters then swiping the rest of a word did not build one word reliably — e.g. tap H, tap E, swipe L→O did not yield hello. Investigation showed the multi-part merged-trail recognizer-help (in WordComposer, which prepends the prior fragment's re-timed pointer trail so the native recognizer sees one continuous stroke) was gated on the combining-grace timer only (mCombiningGraceMs > 0). In pure manual spacing (grace = 0) the gesture fell through to the legacy concat path, which feeds the recognizer only the isolated swipe fragment AND re-prepends the already-composed head — producing garbage / doubled text. The "hold a finger down while the other swipes" workaround worked because BatchInputArbiter aggregates the held pointer into the stroke, but it caps at a single held tap.
User decisions for this change: scope = just fix tap+swipe combining; manual spacing is the primary mode.
latin/settings/SettingsValues.java: addedisMultipartComposeActive()—mMultipartAutoExtendInCombining && (mCombiningGraceMs > 0 || mGestureManualSpacing)— as the single shared definition of "multi-part composition is active". (mMultipartAutoExtendInCombiningis already forced true whenever manual spacing or grace is on, see thenonNormalTwoThumbSpacingline in the ctor.)latin/inputlogic/InputLogic.java(onStartBatchInput): replaced themMultipartAutoExtendInCombining && mCombiningGraceMs > 0gate onsetExtendBatchInputBase(...)withisMultipartComposeActive(), so the merged trail is captured when extending under manual spacing too. This flipsusedMergedTrailtrue downstream inonUpdateTailBatchInputCompleted, which correctly suppresses the legacyprevTypedWordre-concat.keyboard/PointerTracker.java(onDownEvent): switchedmultipartExtendActivetosv.isMultipartComposeActive()so the single-point tap seed and the merged-trail path can never both fire and the two files share one definition.- Tests:
app/src/test/.../WordComposerTest.java:testExtendBatchInputBaseMergesAndRetimes(asserts base+gesture merge to one monotonically-retimed stream, base before gesture, gesture times preserved) andtestExtendBatchInputBaseEmptyIsNoOp(empty base does not arm the merge).app/src/test/.../InputLogicTest.kt:manualSpacingTapThenGestureBuildsOneOpenWord(taphe+ gesturehello→hello, nothehello, and the word stays open until an explicit space — this fails pre-fix) andmanualSpacingActivatesMultipartCompose(helper truth table).
res/values/strings.xml: clarifiedgesture_manual_spacing_summaryto mention mixing taps and swipes into one word.
- Reused the existing
mMultipartAutoExtendInCombiningpref as the de-facto master switch rather than renaming it (the codebase deliberately keeps the slightly-misleading "...InCombining" name to avoid churn; perTWO_THUMB_TYPING_INTERNALS.md §7). It already defaults to active under manual spacing, so users get the fix with no new pref. - Secondary (intended) effect: swipe+swipe under manual spacing now also merges (matching grace-mode), instead of the legacy concat that produced e.g.
techbiology. This is strictly better and consistent with "manual spacing primary". - Why the existing manual-spacing tests still pass: the unit-test harness injects gesture results via
setBatchInputWord, whichreset()smInputPointersto empty, so a gesture fragment leaves an empty trail →setExtendBatchInputBase(empty)is a no-op (isExtendBatchInputBaseSet()stays false) → legacy concat is preserved in tests. Tapped letters DO populate the trail, which is why the new tap-then-gesture test genuinely exercises the fix.
- NOT yet compiled/run — the dev machine has only JRE 1.8; the build needs JDK 17/21. Edits were made manually. Unit tests must be run on a Linux machine (procedure handed to the user).
| # | Steps | Expected Result |
|---|---|---|
| 1 | Settings → Two-thumb typing (experimental) → enable Manual spacing. | Toggle on; no crash. |
| 2 | In any text field, tap H, tap E, then swipe L→O (sequentially, fingers lifting between). |
Composing word becomes hello; nothing is committed yet. |
| 3 | Tap space. | hello is committed; word was open until the space. |
| 4 | Tap s, then swipe ilo. |
silo (single tap + swipe). |
| 5 | Swipe tech, then swipe nology. |
technology (swipe + swipe merges, not techbiology). |
| 6 | Build a multi-fragment word, then press backspace (with Backspace removes last fragment on). | The last fragment is popped, not one character. |
| 7 | Repeat #2 with manual spacing OFF and grace = 0. | Reverts to default behavior (no combining). |
- Run the unit tests on Linux and confirm green; then on-device validation of the manual test table above.
- After the user dailies this, evaluate the further Nintype items mentioned (faster/optional autospace, multi-tap recognition tuning) as separate changes.
Follow-up to the tap+swipe combining fix. With manual spacing on, tap-before/during-swipe worked, but ending a word with taps did not: e.g. swipe dea then tap l produced dea l (and once Auto-correction was enabled, sea l) instead of extending to deal. The tap after a gesture was finalizing the word and inserting an autospace.
mAutospaceAfterGestureTyping is read straight from its pref in SettingsValues with no manual-spacing override (the "no-op under manual spacing" behavior is only enforced at certain call sites). The post-gesture PHANTOM-set in onUpdateTailBatchInputCompleted only excluded combining-grace mode (mCombiningGraceMs <= 0), so under manual spacing (grace = 0) it still set SpaceState.PHANTOM after every gesture. The next letter tap then hit handleNonSpecialCharacterEvent's PHANTOM branch, which commits the composing word and inserts the deferred autospace before starting the new letter. This is the manual-spacing analogue of the combining-mode PHANTOM-race fixed earlier in a63e9ea3.
latin/inputlogic/InputLogic.java(onUpdateTailBatchInputCompleted): added&& !settingsValues.mGestureManualSpacingto the post-gesture PHANTOM-set condition, so manual spacing never arms a post-gesture autospace and the next tap extends the still-open word. Non-two-thumb users (grace = 0, manual spacing off) keep the normal autospace-after-gesture behavior.app/src/test/.../InputLogicTest.kt: addedmanualSpacingGestureThenTapExtendsWithoutAutospace— swipedea+ tapl→deal(fails pre-fix, which yields composingl/ textdea l).
- Scoped the guard to
!mGestureManualSpacingrather than the broaderisMultipartComposeActive(): the existingmCombiningGraceMs <= 0clause already excludes grace mode, so manual spacing is the only remaining case that needs suppressing; plain users are intentionally untouched. - Left the BEFORE-gesture autospace (
mAutospaceBeforeGestureTyping, onStartBatchInput) unchanged for now — not part of the reported symptom; flag for review if word-to-word spacing under manual spacing looks off.
- NOT yet compiled/run on this machine (JRE 1.8 only). Needs the Linux build +
:app:testOfflineDebugUnitTest.
| # | Steps | Expected Result |
|---|---|---|
| 1 | Manual spacing on; Auto-correction on. Swipe d→e→a, then tap l. |
Composing word becomes deal (extends); no premature space or commit. |
| 2 | Tap space. | deal committed. |
| 3 | Swipe a word, tap several trailing letters. | Each tap appends to the open word until an explicit separator. |
| 4 | Plain keyboard (manual spacing OFF, grace 0), autospace-after-gesture ON: swipe a word, tap a letter. | Unchanged stock behavior: gesture word commits, autospace, new letter. |
After the gesture-then-tap fix, a tap correctly extended the open word, but the EXTENSION was literal: a slow tap after a swipe appends to whatever word the gesture library resolved the (often short, mis-resolved) swipe fragment to. The native glide recognizer resolves each completed stroke to a whole word immediately, so swiping t→h alone yields a nearby 2-letter word; a later slow e tap then appends to that wrong word. A FAST e tap already works because it folds into the gesture stroke and t→h→e recognizes as the. User asked to make the slow case behave like the fast case — accumulate the stroke and re-recognize. Chosen model: live-converge (re-recognize the accumulated stroke on each new fragment), taps treated as recognizer hints, shipped as an opt-in toggle, default off.
The recognizer is driven by mWordComposer state (getInputPointers() + isBatchMode()), and performUpdateSuggestionStripSync already shows how to run it synchronously (handler + AsyncResultHolder + timeout). Since setBatchInputWord resets the composer's pointers on every commit, a persistent per-word accumulator (mLiveStroke) is required.
- New pref (5-file, default false):
PREF_MULTIPART_RERECOGNIZE_TAPSin Settings.java / Defaults.kt / SettingsValues.java (mMultipartRerecognizeTaps) / strings.xml / TwoThumbTypingScreen.kt (under the multi-part group, gated by nonNormalSpacing). latin/inputlogic/InputLogic.java:mLiveStroke(InputPointers): accumulated raw trail of the current word. Captured inonUpdateTailBatchInputCompletedjust beforesetBatchInputWordwipesmInputPointers(gated on the pref). Cleared inresetComposingStateandcommitChosenWord.getBatchSuggestionsSync(): synchronous batch recognition via the existing handler/holder pattern (INPUT_STYLE_TAIL_BATCH).tryLiveConvergeTap(event, sv): guards (pref on; manual spacing or grace; composing gesture-word; cursor at end; word codepoint; real key coords; non-empty accumulator). Builds a 1-point InputPointers at the tap's key center, merges viasetExtendBatchInputBase+setBatchInputPointers, recognizes, and reusesonUpdateTailBatchInputCompleted(extend-base set → whole-word replace). Returns false → literal-append fallback when recognition is unavailable (the tap is never lost; typed-word cache untouched).- Hook at the top of
handleNonSeparatorEvent.
- Tests (
InputLogicTest.kt):liveConvergeOffAppendsTapLiterally(baseline) andliveConvergeOnDegradesGracefullyWithoutRecognizer(graceful literal fallback when the recognizer/coords are absent — the JVM harness can't load the gesture lib).
- Pure-tap words are excluded (guard on
mCombiningWordHasGestureFragment || isBatchMode) so ordinary typing stays exact. - Taps are recognizer hints, not exact anchors (user's choice) — matches how tap-then-swipe already behaves.
- v1 accumulates one swipe stem + following taps; multi-swipe-then-tap resets the accumulator to the latest swipe (documented edge case).
- Positive recognition path is NOT unit-testable here: the native gesture lib isn't loaded in JVM tests and harness tap events carry no coordinates, so
tryLiveConvergeTapbails before recognition. Unit tests only cover the safe-fallback/no-regression property. The real behavior must be validated on-device.
- NOT compiled or run on this machine (JRE 1.8 only). Needs Linux build +
:app:testOfflineDebugUnitTest, then on-device validation. Expect possible on-device tuning (synchronous recognize-per-tap latency; recognition quality of stem+tap strokes).
| # | Steps | Expected Result |
|---|---|---|
| 1 | Two-thumb typing → enable Manual spacing and Re-recognize taps within swiped words. | Toggles on; no crash. |
| 2 | Swipe t→h (may briefly show a wrong short word), then tap e. |
Word re-recognizes to the; no premature space. |
| 3 | Swipe d→e→a, then tap l. |
deal. |
| 4 | Type a pure-tap word like hello (no swipe). |
Exact literal typing — feature does not interfere. |
| 5 | Toggle OFF, repeat #2. | Old behavior: tap appends literally to the resolved fragment. |
| 6 | Watch typing latency on the taps that trigger re-recognition. | No noticeable lag; if present, report for tuning. |
Two backspace bugs while building gesture words under manual spacing:
- Deleting part of a word did NOT clear the stored gesture stroke, so re-doing the word merged with stale geometry — e.g. type
th, swipeing→thing; deleteing; swipe again and the recognizer keeps resolving ever-growing words that still start withth. - The 3-way backspace setting (one character / last fragment / whole word) appeared to act as single-character delete; in particular "Delete whole word" did not delete the open (still-composing) word.
- The live-converge accumulator
mLiveStroke(added withPREF_MULTIPART_RERECOGNIZE_TAPS) was cleared only inresetComposingStateandcommitChosenWord.handleBackspaceEventnever cleared it, and its whole-word / batch-reject branches callmWordComposer.reset()directly (bypassingresetComposingState). So the accumulated raw stroke survived a delete and the next swipe/tap merged with it. - Whole-word delete of a composing word requires BOTH
PREF_COMBINING_BACKSPACE_DELETES_GESTURE_WORDandPREF_COMBINING_BACKSPACE_DELETES_COMPOSING_TEXT(handleBackspaceEvent, composing-word branch). The 3-way selector's "Delete whole word" option set only the former, leaving the latter at whatever it was — so if the composing-text toggle had ever been turned off, "whole word" silently degraded to one-character on the open word.
latin/inputlogic/InputLogic.java(handleBackspaceEvent): addedmLiveStroke.reset()near the top (aftercancelCombiningMode), so EVERY backspace path invalidates the accumulated stroke. A re-done word now starts from a clean stroke regardless of delete mode. (We don't attempt to trim the raw trail to a partial word — per the user, dropping it is the desired, predictable behavior.)settings/screens/TwoThumbTypingScreen.kt: the selector'sBACKSPACE_WORDbranch now also setsPREF_COMBINING_BACKSPACE_DELETES_COMPOSING_TEXT = true, so picking "Delete whole word" reliably deletes the open composing word, not just committed words. The sub-toggle remains for users who want whole-word scoped to committed words only.
- Clear
mLiveStrokeunconditionally on backspace (it's empty when the feature is off, so this is harmless) rather than trying to trim it — trimming a fuzzy stroke to match a partially-deleted word is unreliable, and the user explicitly prefers "just delete it". - Kept the runtime requirement that composing-word whole-delete needs the composing-text pref (preserves the existing
wholeWordBackspaceWithLiveComposingDeleteOffFallsBackToOneCharacterbehavior / power-user choice); fixed the footgun at the selector instead. - Did NOT change one-character / fragment delete semantics — only the stale-stroke clearing applies to them.
- NOT compiled or run here (JRE 1.8 only). Existing tests cover the behavior:
wholeWordBackspaceDeletesManualSpacingComposingWord(whole-word composing delete),wholeWordBackspaceWithLiveComposingDeleteOffFallsBackToOneCharacter(sub-toggle off → one char),fragmentBackspaceDeletesLastSwipeFragmentInMultipartWord(fragment pop). ThemLiveStrokeclear isn't JVM-unit-observable (no recognizer/coords in the harness) — validate the re-do behavior on-device. - Note for the user: because the selector only writes prefs when an item is selected, re-pick Delete whole word once after installing so the composing-text toggle gets set.
| # | Steps | Expected Result |
|---|---|---|
| 1 | Manual spacing on; backspace mode = Delete whole word (re-select it once). Type th, swipe ing → thing. Backspace. |
Whole word deleted (empty), not one character. |
| 2 | Immediately swipe ing again (or re-type the word). |
Recognizes cleanly from a fresh stroke — no ever-growing th… words. |
| 3 | Backspace mode = Delete last fragment. Build th+swipe ing, backspace. |
Last fragment (ing) popped → th; re-doing starts from a clean stroke. |
| 4 | Backspace mode = Delete one character. Build a gesture word, backspace once. | One character removed; stored stroke is dropped so a later swipe doesn't merge with stale geometry. |
With backspace mode = "Delete whole word", deleting words mid-text corrupted the text instead of deleting cleanly. Reported example: Hey there. This is pretty cool! → backspaces produced …This is precool, …Thprecoo, Hey Thprecoo, etc. — words mashing into preceding ones and the cursor desyncing. Also: stored gestures sometimes weren't cleared (incl. after the swipe-on-backspace bulk delete).
The whole-word branch for a composing word used mConnection.deleteTextBeforeCursor(wordLength). That routes through InputConnection.deleteSurroundingText, which does NOT remove an active composing region — it deletes committed text BEFORE it (documented TODO in RichInputConnection#deleteTextBeforeCursor). So backspacing the re-composed word cool deleted tty instead, giving precool, and desynced the RichInputConnection cursor cache, cascading into more corruption on subsequent keystrokes. The batch-delete branch never had this bug because it resets the composer and lets the post-block commitText("", 1) clear the composing span. This only became visible after the prior session made "Delete whole word" reliably enable DELETES_COMPOSING_TEXT.
latin/inputlogic/InputLogic.java(handleBackspaceEvent, composing-word branch): the whole-word delete now doesmWordComposer.reset()and lets the post-blockcommitText("", 1)remove the composing span — the correct primitive for deleting composing text — instead ofdeleteTextBeforeCursor. Removed the now-unuseddeletedWholeComposingWordflag; the post-block always clears the composing span when the composer is no longer composing.- Added a defensive
mLiveStroke.reset()in the composing-inconsistency recovery path (setComposingTextInternalWithBackgroundColor) so a force-cancelled composition also drops the stored stroke. - Verified
mLiveStrokeclearing is now comprehensive:handleBackspaceEvent(covers per-char, fragment, whole-word, batch, and selection/bulk delete — runs before any branch),resetComposingState(reached viaresetEntireInputStateon cursor/selection moves fromonUpdateSelection),commitChosenWord, and the inconsistency path.
- One-character delete uses
applyProcessedEvent+setComposingTextInternal(correct primitive). - Fragment delete (
tryFragmentBackspace) usessetBatchInputWord+setComposingTextInternal, and only fires for actively-built words (recorded boundaries). Re-composed committed words have no boundaries → falls back to one-char. - Only the whole-word composing branch used
deleteTextBeforeCursor, so only it corrupted.
InputLogicTest.kt:wholeWordDeleteRemovesComposingWordWithoutMashingPrecedingText— re-composecoolinThis is pretty cool, whole-word delete →This is pretty(notprecool). Caveat: the JVM mock'sdeleteSurroundingTextdoesn't reproduce the real-editor composing-region quirk, so this guards the intended outcome and thecommitTextpath; the editor-specific corruption must be confirmed on-device.- Existing
wholeWordBackspaceDeletesManualSpacingComposingWord/…DeleteOffFallsBackToOneCharacterstill hold (the new code clears the composing span viacommitText).
- NOT compiled or run here (JRE 1.8). Needs Linux build +
:app:testOfflineDebugUnitTest, then on-device:- Whole-word delete through a sentence deletes word-by-word with NO mashing/desync.
- Re-doing a word after deletion recognizes cleanly (no ever-growing
th…). - Swipe-on-backspace bulk delete leaves no stale stroke for the next word.
Bug report: gestures occasionally get "stored" after a deletion and bleed into the
next swipe — most visibly when swiping a fresh word at the start of a text box,
which produced a word fused with previously-deleted gesture geometry. Branch:
fix/extend-base-leak-on-delete off main.
There are two per-word gesture-geometry stores. mLiveStroke (live-converge, in
InputLogic) was already cleared on every word-end path. But its WordComposer-side
counterpart — mExtendBatchInputBase (the multi-part merged-trail base consumed by
WordComposer.setBatchInputPointers) — was only ever cleared by a normally
completing gesture (onUpdateTailBatchInputCompleted, the lone routine clear,
which sits AFTER two empty-recognition early-returns). An abnormal gesture end left
the mExtendBatchInputBaseSet flag armed:
onCancelBatchInputnever cleared it,- the empty-top-word early-returns in
onUpdateTailBatchInputCompletedskip the clear, WordComposer.reset()does not touch it, so NO deletion path cleared it either.
Once leaked, the flag survived every backspace / selection-delete / commit. A later
fresh gesture (no composing word ⇒ the onStartBatchInput extend branch never runs,
so it neither set nor cleared the base) hit the merge guard in
setBatchInputPointers and prepended the ghost trail. Start-of-text-box made it
maximally visible because there is no composing word there to legitimately re-set
the base.
latin/inputlogic/InputLogic.java: clear the merged-trail base (mWordComposer.setExtendBatchInputBase(null)) at the same word-end sites wheremLiveStrokeis 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 existingmLiveStroke.reset(), before any mode branch — covers all three backspace modes in one place),resetComposingState(covers the delete slider'sfinishInputand cursor-move resets),commitChosenWordand the composing-desync recovery path. Deliberately did NOT add the clear to the hotWordComposer.reset()to avoid any interaction with the mid-gesture merge timing; mirroring the reviewedmLiveStrokesites is equivalent and lower-risk.
app/src/test/.../InputLogicTest.kt: 6 base-clear regression tests (one per backspace mode — character / fragment / whole-word — plus delete slider, freshonStartBatchInput, andonCancelBatchInput), each arming the base then asserting it is dropped (each fails pre-fix). AddedarmExtendBase()helper andInputPointers/assertTrue/assertFalseimports.- Also added two reachability-guard tests pinning the (currently dead) PointerTracker
static-seed interlock:
grace > 0 ⇒ isMultipartComposeActive()and the manual-spacing equivalent. If a future settings refactor decouples them and re-arms the seed, these fail and force adding the missing stale-static cleanup first.
- Threading was verified safe: every
mExtendBatchInputBasemutation runs on the UI thread (the recognizer runs on the suggestions HandlerThread but only reads a snapshot), so the new clears need no locking and can't race the merge. - Scoped to dropping the base. The related "re-extend after a partial (character /
fragment) delete still merges the un-trimmed
mInputPointers" issue is left for a later ticket per the established "drop, don't trim" philosophy — the fix neither causes nor worsens it.
- Built and ran on this machine (newly installed JDK 21 + Android SDK platform-35 /
build-tools 35.0.0).
:app:testStandardDebugUnitTestfiltered to the 8 new tests +WordComposerTest: BUILD SUCCESSFUL, 11/11 passing. Full-suite still carries the 3 pre-existing known failures (insertLetterIntoWordHangulFails,revert autocorrect on delete,tapOnlyCombiningWordDoesNotShowAutospaceIndicator…), unrelated to this change. - On-device validation still recommended: reproduce the start-of-text-box ghost-merge (swipe a word, trigger an abnormal gesture end, delete, swipe a fresh word) and confirm it no longer fuses; re-check all three backspace modes + the delete slider.
| # | Steps | Expected Result |
|---|---|---|
| 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. | Same; no ghost merge. |
| 3 | Repeat #1 with backspace mode = Delete last fragment. | Same; no ghost merge. |
| 4 | Repeat #1 with backspace mode = Delete whole word. | Same; no ghost merge. |
| 5 | Repeat #1 using swipe-from-backspace (delete slider) to clear. | Same; no ghost merge. |
- Build the standard debug APK and install for on-device validation.
- Decide whether to also tackle the re-extend-after-partial-delete (stale
mInputPointers) follow-up.