fix: allow Cmd/Ctrl+K new chat while a conversation is busy

The Cmd/Ctrl+K shortcut was gated on !S.busy, which meant users had to
wait for an in-flight generation to finish before they could start a
fresh conversation — exactly the moment they want to switch context.

Removes the !S.busy guard. The in-flight stream keeps running on its
own session; the user just gets a fresh blank one in the foreground.
The existing "no messages → focus composer instead of stacking empty
sessions" behavior is preserved.

Tests: tests/test_mobile_layout.py — new test_new_conversation_shortcut_works_while_busy
guard assertion. Existing closeMobileSidebar window check widened to
12 lines to accommodate the comment block explaining the change.

Full suite: 3253 passed.

Salvaged from contributor work in PR #1084.

Co-authored-by: Hermes Agent <hermes@get-hermes.ai>
This commit is contained in:
Hermes Agent
2026-04-30 03:43:21 +00:00
parent 20ac6dfe5c
commit 2e8a239614
2 changed files with 31 additions and 2 deletions
+6 -1
View File
@@ -531,7 +531,12 @@ document.addEventListener('keydown',async e=>{
// If the current session has no messages, just focus the composer rather than
// creating another empty session that will clutter the sidebar list (#1171).
if(S.session&&(S.session.message_count||0)===0){$('msg').focus();return;}
if(!S.busy){await newSession();await renderSessionList();closeMobileSidebar();$('msg').focus();}
// Cmd/Ctrl+K should always create a new conversation, even while the current
// one is still streaming. The old !S.busy guard meant users had to wait for
// a long generation to finish before they could start something new — exactly
// the moment they want to switch context. newSession() leaves the in-flight
// stream running on its own session; the user just gets a fresh blank one.
await newSession();await renderSessionList();closeMobileSidebar();$('msg').focus();
}
if(e.key==='Escape'){
// Close onboarding overlay if open (skip/dismiss the wizard)
+25 -1
View File
@@ -157,11 +157,35 @@ def test_new_conversation_closes_mobile_sidebar():
shortcut_line = next((ln for ln in boot_js.splitlines() if "e.key==='k'" in ln or "e.key === 'k'" in ln), "")
assert shortcut_line, "Cmd/Ctrl+K new chat shortcut missing from static/boot.js"
shortcut_block = "\n".join(boot_js.splitlines()[boot_js.splitlines().index(shortcut_line):boot_js.splitlines().index(shortcut_line)+6])
shortcut_block = "\n".join(boot_js.splitlines()[boot_js.splitlines().index(shortcut_line):boot_js.splitlines().index(shortcut_line)+12])
assert "closeMobileSidebar" in shortcut_block, \
"Cmd/Ctrl+K new chat shortcut must closeMobileSidebar() after creating the new session"
def test_new_conversation_shortcut_works_while_busy():
"""Cmd/Ctrl+K should still create a new conversation while the current one is busy.
The previous behavior gated the shortcut on !S.busy, which meant users had
to wait for a long generation to finish before they could start something
new — the exact moment they want to switch context.
"""
boot_js = (REPO / "static" / "boot.js").read_text(encoding="utf-8")
shortcut_line = next((ln for ln in boot_js.splitlines() if "e.key==='k'" in ln or "e.key === 'k'" in ln), "")
assert shortcut_line, "Cmd/Ctrl+K new chat shortcut missing from static/boot.js"
# Inspect the next 10 lines after the keybinding match — the gating block
# would live there if it had been kept.
idx = boot_js.splitlines().index(shortcut_line)
shortcut_block = "\n".join(boot_js.splitlines()[idx:idx + 10])
# Strip the existing message-count guard (which is unrelated and stays) so
# we only check for an S.busy gate on the newSession() call itself.
assert "if(!S.busy)" not in shortcut_block, (
"Cmd/Ctrl+K must not be blocked by the current session's busy state"
)
assert "if (!S.busy)" not in shortcut_block, (
"Cmd/Ctrl+K must not be blocked by the current session's busy state"
)
# ── Viewport and scroll safety ────────────────────────────────────────────────
def test_body_overflow_hidden():