Skip to content

fix(ime): ⌫ deletes the selection — empty commitText instead of caret-only deleteSurroundingText - #80

Merged
willwade merged 3 commits into
mainfrom
fix/ime-delete-selection
Oct 29, 2026
Merged

willwade merged 3 commits into
mainfrom
fix/ime-delete-selection

Conversation

@willwade

Copy link
Copy Markdown
Contributor

Heide's 2026-10-29 report: "if you select something, whether it's everything or partial, and you use the delete button on the toolbar, it doesn't do anything. It only works when you don't have anything selected."

Root cause: backspace() always called deleteSurroundingText(n, 0) — which deletes around the caret. With an active selection (including one made by our own Select All button), editors ignore or no-op it: a dead ⌫. The whole Select-All-then-delete flow — a core editing action for an AAC user — was broken.

Fix: the logic is extracted to performImeBackspace(ic) (the repo's pure-logic pattern, unit-testable with a fake InputConnection):

  • Selection present → commitText("", 1) — the canonical IME way to replace a selection with nothing; a DEL key-event fallback covers editors that refuse the empty commit
  • Caret → unchanged codepoint-correct backspace (surrogate pairs are 2 UTF-16 units)

Tests (6): selection deleted via empty commit; refusing editor gets the DEL fallback; empty-string selection falls through to caret behaviour; one-unit backspace; surrogate pair = 2 units; empty field = no-op.

(Also in her email thread: v0.1.26 confirmed working — "It's finally fixed!" — and she asked about seasonal emojis + skin tones in the Emoji alphabet, which is DasherCore data coverage, separate issue.)

Testing

  • assembleDebug + testDebugUnitTest green (90 tests)
  • Heide's device: select text (via our Select All or a partial drag) → ⌫ → selection gone

…-only deleteSurroundingText

Heide (2026-10-29): "if you select something, whether it's everything
or partial, and you use the delete button on the toolbar, it doesn't do
anything. It only works when you don't have anything selected."

backspace() always called deleteSurroundingText(n, 0), which deletes
around the CARET — with an active selection (including one made by our
own Select All button) editors ignore it or no-op: the reported
dead ⌫.

performImeBackspace (extracted, unit-testable per the repo's
pure-logic pattern):
- selection present → commitText("", 1) — the canonical IME way to
  replace a selection with nothing; DEL key-event fallback for editors
  that refuse the empty commit
- caret → unchanged codepoint-correct backspace (surrogate-aware)

Six unit tests with a fake InputConnection pin the contract: selection
deleted via empty commit; refusing editor gets the DEL fallback; empty
selection falls through; caret/surrogate/empty-field cases preserved.

Signed-off-by: will wade <willwade@gmail.com>
…ome telemetry

- I1 (the bug-#5 lesson): hostile editors (OneNote/Word class) return
  TRUE from commitText("") while doing nothing — the DEL fallback
  previously fired only on a false return, i.e. the failure mode real
  editors rarely produce. The selection path now re-verifies via
  getSelectedText: if the selection survived a true commit, the DEL
  fallback fires anyway (a lying editor that DID delete loses one char
  — the lesser evil vs. a dead ⌫). Test: the lying-editor case.
- M4: performImeBackspace returns a sealed ImeBackspaceOutcome
  (SelectionDeleted/CaretDeleted/Noop); the toolbar reports
  state=selection|char|none in the ui_action backspace event — the
  distinction that motivated this fix. Schema state enum extended.
- M2: the fake's getTextBeforeCursor now windows to the last n UTF-16
  units (real semantics); fake selection state is now mutable so
  getSelectedText reflects the commit.

Signed-off-by: will wade <willwade@gmail.com>
…telemetry symmetry, mapping pinned

- NEW-1 (merge-blocker): the indent-2 rewrite of analytics-events.json
  reverted; the state enum extension is now the ONLY line changed,
  preserving the shared schema's formatting (reviewer: churn taxes every
  cross-repo sync of this hot file)
- NEW-2: the ⌫ emits ui_action even with no connection (state
  no_connection) — a dead delete from a missing InputConnection is
  exactly the symptom class the telemetry should see
- NEW-3: the outcome→state mapping moved onto ImeBackspaceOutcome
  (telemetryState) — testable, no duplicate string at the call site
- Schema state description corrected (toggle states OR the delete kind)
- Tests: empty surviving selection = success (no fallback fired);
  telemetry mapping pinned. 92 tests green.

Signed-off-by: will wade <willwade@gmail.com>
@willwade
willwade merged commit 1164a96 into main Oct 29, 2026
2 checks passed
@willwade
willwade deleted the fix/ime-delete-selection branch October 29, 2026 17:31
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