From 8be686ee758b30a58e26c94dee12c52108c64f4d Mon Sep 17 00:00:00 2001 From: happy5318 Date: Sun, 9 Aug 2026 10:25:29 +0800 Subject: [PATCH] fix(mobile): soft-keyboard Enter inserts newline, not send iOS Safari reports matchMedia('(any-pointer:fine)') as true on iPhone, so _hasFinePointerCoexisting() wrongly vetoed the touch-device default and plain Enter fell through to send(). - boot.js: _isTouchOnlyDevice() makes phone/tablet UA authoritative, no longer vetoed by any-pointer:fine; iPadOS-as-Macintosh detected via maxTouchPoints; media-query pair is fallback for non-phone touch devices. Composer handler reads live window._sendKey on every keydown so Settings changes take effect without reload. - index.html: capture-phase interceptor removed; boot.js handler alone covers the mobile newline case (initial _sendKey already synced from localStorage synchronously, closing the async race). - tests/test_send_key_preference_live_update.py: Playwright regression test verifies all three send-key modes and all transitions work after runtime preference change. --- static/boot.js | 35 ++- static/index.html | 2 +- tests/test_send_key_preference_live_update.py | 289 ++++++++++++++++++ 3 files changed, 322 insertions(+), 4 deletions(-) create mode 100644 tests/test_send_key_preference_live_update.py diff --git a/static/boot.js b/static/boot.js index d6476b60..1e2d4792 100644 --- a/static/boot.js +++ b/static/boot.js @@ -2388,9 +2388,39 @@ window._isImeEnter=_isImeEnter; function _hasFinePointerCoexisting(){ try{ return matchMedia('(any-pointer:fine)').matches; }catch(_){ return false; } } +// Detect mobile/touch-only devices. On some iOS Safari versions +// matchMedia('(pointer:coarse)') can be unreliable, so also check +// the user agent for mobile indicators as a fallback. +function _isTouchOnlyDevice(){ + // Phone/tablet UA is authoritative and checked FIRST. Do NOT veto it with + // (any-pointer:fine): several iOS Safari builds report that query as true on + // plain iPhones (Apple Pencil / pointer-emulation heuristics), which is + // exactly why the media-query-only detection failed here. On a phone the + // software keyboard's return key must insert a newline; a user with a real + // Bluetooth keyboard still has Ctrl/Cmd+Enter and the send button. + const ua=navigator.userAgent||''; + if(/iPhone|iPod|Android/i.test(ua)) return true; + // iPadOS 13+ Safari masquerades as Macintosh; touch points disambiguate it. + if(/iPad/i.test(ua)) return true; + try{ + if(/Macintosh/i.test(ua)&&(navigator.maxTouchPoints||0)>1) return true; + }catch(_){} + // Non-phone fallback: pure media-query detection for touch-primary devices. + try{ + return matchMedia('(pointer:coarse)').matches&&!matchMedia('(any-pointer:fine)').matches; + }catch(_){} + return false; +} function _isNumpadEnter(e){ return e.key==='Enter'&&(e.code==='NumpadEnter'||e.location===KeyboardEvent.DOM_KEY_LOCATION_NUMPAD); } +// Initialise _sendKey synchronously from the localStorage cache so the keydown +// handler below has the correct value before the async /api/settings call +// (line ~3231) resolves. Without this, on slow mobile networks the race window +// leaves _sendKey=undefined, _mobileDefault evaluates false, and plain Enter +// falls through to the `else { send() }` branch — sending the message instead +// of inserting a newline (issue: mobile Enter sends on fresh page load). +try{ window._sendKey=localStorage.getItem('hermes-pref-send_key')||'enter'; }catch(_){ window._sendKey='enter'; } $('msg').addEventListener('keydown',e=>{ // Autocomplete navigation when dropdown is open const dd=$('cmdDropdown'); @@ -2424,9 +2454,8 @@ $('msg').addEventListener('keydown',e=>{ if(e.key==='Enter'){ if(_isImeEnter(e)){return;} const isNumpadEnter=_isNumpadEnter(e); - const _mobileDefault=matchMedia('(pointer:coarse)').matches - &&!_hasFinePointerCoexisting() - &&window._sendKey==='enter'; + const _mobileDefault=_isTouchOnlyDevice() + &&(window._sendKey==='enter'||typeof window._sendKey==='undefined'); if(window._sendKey==='shift+enter'){ if(e.shiftKey){e.preventDefault();send();} } else if(window._sendKey==='ctrl+enter'||_mobileDefault){ diff --git a/static/index.html b/static/index.html index b95528e8..335868de 100644 --- a/static/index.html +++ b/static/index.html @@ -592,7 +592,7 @@ - +