feat(#5759): add composer focus shortcut

This commit is contained in:
Rod Boev authored and nesquena-hermes committed 2026-07-14 01:06:46 +00:00
1 parent 11eb1b7a83
commit ca5f02bbff
2 files changed
+83

No files matched your search

+9
View File
@@ -2320,6 +2320,15 @@ document.addEventListener('keydown',async e=>{
return;
}
}
// Cmd/Ctrl+Alt+/ focuses the message composer without creating a chat.
if((e.metaKey||e.ctrlKey)&&e.altKey&&!e.shiftKey&&(e.key==='/'||e.code==='Slash')){
const t=e.target;
const isText=t&&(t.tagName==='INPUT'||t.tagName==='TEXTAREA'||t.isContentEditable);
if(isText) return;
const composer=$('msg');
if(composer){e.preventDefault();composer.focus();}
return;
}
// Enter on approval card = Allow once (when a button inside the card is focused or
// card is visible and focus is not on an input/textarea/select)
if(e.key==='Enter'&&!e.metaKey&&!e.ctrlKey&&!e.shiftKey){
@@ -0,0 +1,74 @@
"""Regression tests for #5759 composer focus shortcut."""
from pathlib import Path
BOOT_JS = (Path(__file__).parent.parent / "static" / "boot.js").read_text(encoding="utf-8")
CHORD = "(e.metaKey||e.ctrlKey)&&e.altKey&&!e.shiftKey&&(e.key==='/'||e.code==='Slash')"
CTRL_K = "(e.metaKey||e.ctrlKey)&&e.key==='k'"
def _block_from(start: int) -> str:
depth = 0
end = start
for end in range(start, len(BOOT_JS)):
char = BOOT_JS[end]
if char == "{":
depth += 1
elif char == "}":
depth -= 1
if depth == 0:
return BOOT_JS[start : end + 1]
raise AssertionError("shortcut block not closed")
def _composer_focus_branch() -> str:
start = BOOT_JS.index(CHORD)
return _block_from(start)
def _ctrl_k_branch() -> str:
start = BOOT_JS.index(CTRL_K)
return _block_from(start)
def test_composer_focus_chord_uses_alt_slash_and_stays_before_ctrl_k():
assert CHORD in BOOT_JS
assert "(e.metaKey||e.ctrlKey)&&e.key==='/'" not in BOOT_JS
assert "e.code==='Slash'" in BOOT_JS
assert BOOT_JS.index("(e.metaKey||e.ctrlKey)&&!e.shiftKey&&!e.altKey&&(e.key==='b'||e.key==='B')") < BOOT_JS.index(CHORD)
assert BOOT_JS.index(CHORD) < BOOT_JS.index(CTRL_K)
def test_editable_targets_return_before_prevent_default():
branch = _composer_focus_branch()
guard = "const isText=t&&(t.tagName==='INPUT'||t.tagName==='TEXTAREA'||t.isContentEditable);"
assert "const t=e.target;" in branch
assert guard in branch
guard_idx = branch.index("if(isText) return;")
prevent_idx = branch.index("e.preventDefault();")
assert guard_idx < prevent_idx
def test_composer_focus_branch_only_focuses_msg_and_does_not_mutate_session_state():
branch = _composer_focus_branch()
assert "const composer=$('msg');" in branch
assert "composer.focus();" in branch
assert "newSession()" not in branch
assert "renderSessionList()" not in branch
assert "closeMobileSidebar()" not in branch
assert "send()" not in branch
assert "clearDraft" not in branch
assert "draft" not in branch.lower()
def test_mode_matrix_covers_focus_return_no_alt_and_unchanged_ctrl_k():
branch = _composer_focus_branch()
ctrl_k = _ctrl_k_branch()
assert "if(composer){e.preventDefault();composer.focus();}" in branch
assert "if(isText) return;" in branch
assert "(e.metaKey||e.ctrlKey)&&e.key==='/'" not in BOOT_JS
assert "if(_currentSessionIsReusableEmptyChat()){" in ctrl_k
assert "await newSession();await renderSessionList();closeMobileSidebar();$('msg').focus();" in ctrl_k