diff --git a/CHANGELOG.md b/CHANGELOG.md index 93da0a58..e6f5043f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,10 @@ ## [Unreleased] +### Fixed + +- The chat composer now treats the numeric keypad Enter key as a submit shortcut even when the send-key preference is set to Ctrl/Cmd+Enter, while preserving regular Enter-as-newline behavior in that mode. + ## [v0.51.157] — 2026-05-28 — Release EC (stage-batch39 — 5-PR mixed-risk cleanup: gateway prefill forward + prefill budget + compressed-continuation sidebar + browser-transcript memory guidance + reasoning max parity) ### Added diff --git a/static/boot.js b/static/boot.js index 072171b5..f507af88 100644 --- a/static/boot.js +++ b/static/boot.js @@ -1115,6 +1115,9 @@ function _isVirtualKeyboardLikelyOpen(){ if(!vv||!window.innerHeight)return true; return window.innerHeight-vv.height>120; } +function _isNumpadEnter(e){ + return e.key==='Enter'&&(e.code==='NumpadEnter'||e.location===KeyboardEvent.DOM_KEY_LOCATION_NUMPAD); +} $('msg').addEventListener('keydown',e=>{ // Autocomplete navigation when dropdown is open const dd=$('cmdDropdown'); @@ -1139,9 +1142,10 @@ $('msg').addEventListener('keydown',e=>{ // Users can override in Settings by explicitly choosing 'enter' mode. if(e.key==='Enter'){ if(_isImeEnter(e)){return;} + const isNumpadEnter=_isNumpadEnter(e); const _mobileDefault=matchMedia('(pointer:coarse)').matches&&window._sendKey==='enter'&&_isVirtualKeyboardLikelyOpen(); if(window._sendKey==='ctrl+enter'||_mobileDefault){ - if(e.ctrlKey||e.metaKey){e.preventDefault();send();} + if(isNumpadEnter||e.ctrlKey||e.metaKey){e.preventDefault();send();} } else { if(!e.shiftKey){e.preventDefault();send();} } diff --git a/tests/test_numpad_enter_submit.py b/tests/test_numpad_enter_submit.py new file mode 100644 index 00000000..ac4fbcc3 --- /dev/null +++ b/tests/test_numpad_enter_submit.py @@ -0,0 +1,26 @@ +"""Keyboard contract for treating Numpad Enter as a submit shortcut.""" + +from pathlib import Path + +REPO = Path(__file__).resolve().parents[1] +BOOT_JS = (REPO / "static" / "boot.js").read_text(encoding="utf-8") + + +def test_boot_defines_numpad_enter_helper(): + assert "function _isNumpadEnter(e)" in BOOT_JS + assert "e.code==='NumpadEnter'" in BOOT_JS + assert "e.location===KeyboardEvent.DOM_KEY_LOCATION_NUMPAD" in BOOT_JS + + +def test_ctrl_enter_mode_allows_numpad_enter_to_submit(): + ctrl_branch = BOOT_JS.split("if(window._sendKey==='ctrl+enter'||_mobileDefault){", 1)[1] + ctrl_branch = ctrl_branch.split("} else {", 1)[0] + assert "isNumpadEnter" in ctrl_branch + assert "if(isNumpadEnter||e.ctrlKey||e.metaKey){e.preventDefault();send();}" in ctrl_branch + + +def test_ime_guard_runs_before_numpad_enter_detection(): + enter_branch = BOOT_JS.split("if(e.key==='Enter'){")[-1] + ime_idx = enter_branch.index("if(_isImeEnter(e)){return;}") + numpad_idx = enter_branch.index("const isNumpadEnter=_isNumpadEnter(e);") + assert ime_idx < numpad_idx