Merge #5005 (futureworld678 Shift+Enter) into stage

This commit is contained in:
nesquena-hermes committed 2026-06-30 21:51:42 +00:00
commit 78ca4d43a1
5 files changed
+71 -4

No files matched your search

+2 -2
View File
@@ -7792,7 +7792,7 @@ def _get_session_agent_lock(session_id: str) -> threading.Lock:
_SETTINGS_DEFAULTS = {
"default_workspace": str(DEFAULT_WORKSPACE),
"onboarding_completed": False,
"send_key": "enter", # 'enter' or 'ctrl+enter'
"send_key": "enter", # 'enter', 'ctrl+enter', or 'shift+enter'
"show_token_usage": False, # show input/output token badge below assistant messages
"show_quota_chip": False, # show ambient provider quota chip in composer footer (default off; wide desktop only when enabled, see style.css @media)
"show_conversation_outline": False, # show opt-in desktop jump-to-question outline panel
@@ -8022,7 +8022,7 @@ _SETTINGS_ALLOWED_KEYS = set(_SETTINGS_DEFAULTS.keys()) - {
"simplified_tool_calling",
}
_SETTINGS_ENUM_VALUES = {
"send_key": {"enter", "ctrl+enter"},
"send_key": {"enter", "ctrl+enter", "shift+enter"},
"sidebar_density": {"compact", "detailed"},
"font_size": {"small", "default", "large", "xlarge"},
"auto_title_refresh_every": {"0", "5", "10", "20"},
+8 -2
View File
@@ -1737,6 +1737,9 @@ $('msg').addEventListener('keydown',e=>{
if(e.key==='Escape'){e.preventDefault();e.stopPropagation();hideCmdDropdown();return;}
if(e.key==='Enter'&&!e.shiftKey){
if(_isImeEnter(e)){return;}
if(window._sendKey==='shift+enter'){
return;
}
e.preventDefault();
selectCmdDropdownItem();
return;
@@ -1750,7 +1753,8 @@ $('msg').addEventListener('keydown',e=>{
// doesn't consistently reduce vv.height by >120px. The pointer media query
// pair is a sufficient and more reliable signal for "software keyboard only".
// Hardware keyboards on tablets are covered by _hasFinePointerCoexisting.
// The 'ctrl+enter' setting also uses this behavior (Enter = newline).
// The 'ctrl+enter' and 'shift+enter' settings also use this behavior
// (plain Enter = newline).
// Users can override in Settings by explicitly choosing 'enter' mode.
if(e.key==='Enter'){
if(_isImeEnter(e)){return;}
@@ -1758,7 +1762,9 @@ $('msg').addEventListener('keydown',e=>{
const _mobileDefault=matchMedia('(pointer:coarse)').matches
&&!_hasFinePointerCoexisting()
&&window._sendKey==='enter';
if(window._sendKey==='ctrl+enter'||_mobileDefault){
if(window._sendKey==='shift+enter'){
if(e.shiftKey){e.preventDefault();send();}
} else if(window._sendKey==='ctrl+enter'||_mobileDefault){
if(isNumpadEnter||e.ctrlKey||e.metaKey){e.preventDefault();send();}
} else {
if(!e.shiftKey){e.preventDefault();send();}
+1
View File
@@ -1153,6 +1153,7 @@
<select id="settingsSendKey" style="width:100%;padding:8px;background:var(--code-bg);color:var(--text);border:1px solid var(--border2);border-radius:6px">
<option value="enter">Enter (Shift+Enter for newline)</option>
<option value="ctrl+enter">Ctrl+Enter (Enter for newline)</option>
<option value="shift+enter">Shift+Enter (Enter for newline)</option>
</select>
</div>
<div class="settings-field">
+54
View File
@@ -0,0 +1,54 @@
"""Keyboard contract for the Shift+Enter send-key preference."""
from pathlib import Path
REPO = Path(__file__).resolve().parents[1]
BOOT_JS = (REPO / "static" / "boot.js").read_text(encoding="utf-8")
INDEX_HTML = (REPO / "static" / "index.html").read_text(encoding="utf-8")
CONFIG_PY = (REPO / "api" / "config.py").read_text(encoding="utf-8")
def _listener_body() -> str:
signature = "$('msg').addEventListener('keydown',e=>"
start = BOOT_JS.index(signature)
brace = BOOT_JS.index("{", start)
depth = 0
for idx in range(brace, len(BOOT_JS)):
char = BOOT_JS[idx]
if char == "{":
depth += 1
elif char == "}":
depth -= 1
if depth == 0:
return BOOT_JS[start : idx + 1]
raise AssertionError("could not extract composer keydown listener")
def test_settings_expose_shift_enter_send_key_option():
assert '<option value="shift+enter">Shift+Enter (Enter for newline)</option>' in INDEX_HTML
assert '"send_key": {"enter", "ctrl+enter", "shift+enter"}' in CONFIG_PY
def test_shift_enter_mode_sends_only_with_shift_enter():
handler = _listener_body()
shift_branch = handler.split("if(window._sendKey==='shift+enter')", 1)[1]
shift_branch = shift_branch.split("} else if(window._sendKey==='ctrl+enter'||_mobileDefault)", 1)[0]
assert "if(e.shiftKey){e.preventDefault();send();}" in shift_branch
assert "if(!e.shiftKey){e.preventDefault();send();}" not in shift_branch
def test_shift_enter_mode_leaves_plain_enter_to_textarea_when_autocomplete_open():
handler = _listener_body()
dropdown_enter = handler[
handler.index("if(e.key==='Enter'&&!e.shiftKey)") :
handler.index("// Send key: respect user preference.")
]
assert "if(window._sendKey==='shift+enter')" in dropdown_enter
guarded = dropdown_enter.split("if(window._sendKey==='shift+enter')", 1)[1].split("e.preventDefault();", 1)[0]
assert "return;" in guarded
def test_existing_enter_and_ctrl_enter_modes_stay_available():
handler = _listener_body()
assert "if(!e.shiftKey){e.preventDefault();send();}" in handler
assert "if(isNumpadEnter||e.ctrlKey||e.metaKey){e.preventDefault();send();}" in handler
+6
View File
@@ -47,6 +47,12 @@ def test_settings_save_send_key():
# Verify it persisted
data, _ = get("/api/settings")
assert data["send_key"] == "ctrl+enter"
# Save shift+enter
_, status = post("/api/settings", {"send_key": "shift+enter"})
assert status == 200
# Verify it persisted
data, _ = get("/api/settings")
assert data["send_key"] == "shift+enter"
finally:
# Always restore default
post("/api/settings", {"send_key": "enter"})