fix: keep the soft-keyboard recovery on Android with a fine pointer (release review, #726)

#726 attaches the keyCode 229 recovery only when (pointer: coarse)
matches, which keeps a desktop dead key from arriving twice. Chromium
reports the PRIMARY pointer as fine once a mouse, a trackpad cover or a
stylus is attached, so on such an Android device the module was off
while the text still came from the soft keyboard, and the #441 dropped
last character and the #541 autocorrect duplication came back.

The gate is now a coarse pointer OR an Android user agent. Android ran
the recovery unconditionally before #726, so this only keeps that; a
desktop (fine pointer, not Android) and a touchscreen laptop stay off.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Codeman maintainer committed 2026-10-28 09:35:20 +01:00
1 parent ca0f891c2b
commit 8074f67c6a
3 files changed
+41 -11

No files matched your search

+26 -2
View File
@@ -46,9 +46,9 @@ function inputEvent(data: string, overrides: Record<string, unknown> = {}) {
return { data, inputType: 'insertText', isComposing: false, ...overrides };
}
function harness({ screenReader = false, coarse = true, matchMedia = true } = {}) {
function harness({ screenReader = false, coarse = true, matchMedia = true, userAgent = '' } = {}) {
const source = readFileSync(new URL('../src/web/public/terminal-keycode229-recovery.js', import.meta.url), 'utf8');
const exposed: Record<string, any> = {};
const exposed: Record<string, any> = { navigator: { userAgent } };
if (matchMedia) {
// create() attaches only for a coarse pointer (#716). The recovery cases
// below are the soft-keyboard behaviour, so they opt in. A fine pointer
@@ -136,6 +136,30 @@ describe('orphaned terminal input recovery', () => {
expect(h.textarea.listenerCount()).toBe(0);
});
it('attaches on Android even when the primary pointer reports fine (a mouse or stylus attached)', () => {
// Chromium makes a mouse, a trackpad cover or an S Pen the primary pointer, but the text still comes
// from the soft keyboard, so the #441/#541 recovery must stay on there.
const h = harness({
coarse: false,
userAgent:
'Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/140.0.0.0 Mobile Safari/537.36',
});
expect(h.controller).not.toBeNull();
h.keydown();
h.input('`');
h.flushTimers();
expect(h.emitted).toEqual(['`']);
});
it('stays off on a desktop fine pointer that is not Android', () => {
const h = harness({
coarse: false,
userAgent:
'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/140.0.0.0 Safari/537.36',
});
expect(h.controller).toBeNull();
});
it('still recovers a dropped insertText on a coarse pointer', () => {
const h = harness({ coarse: true });
h.keydown();