From 26d0f457918170e9ed45f80fd0104ca055be1a49 Mon Sep 17 00:00:00 2001 From: nesquena-hermes <226517000+nesquena-hermes@users.noreply.github.com> Date: Sat, 2 May 2026 00:52:41 +0000 Subject: [PATCH 1/2] fix: new-chat guard ignores in-flight streams (#1432) + profile form auto-capitalizes typed values (#1423) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two unrelated UX bugs, both small surgical fixes with regression tests. Issue #1432 — "+" button doesn't open new chat during streaming ================================================================ Reported by @Olyno: clicking "+" after sending a first message keeps redirecting to the same chat instead of opening a new blank conversation, making parallel chats impossible until the first response finishes. Root cause: static/boot.js:691 (and the Cmd/Ctrl+K branch at :844) had an empty-session guard from #1171 that skipped newSession() when message_count===0: if(S.session && (S.session.message_count||0)===0){ $('msg').focus(); closeMobileSidebar(); return; } But during the first user turn of a brand-new session, message_count is still 0 server-side because the user message hasn't been merged into s.messages yet. The guard treated that as "empty" and silently dropped the click, blocking parallel chats for the entire stream duration. Fix: Tighten the predicate to also exclude in-flight state: if(S.session && (S.session.message_count||0)===0 && !S.busy && !S.session.active_stream_id && !S.session.pending_user_message){ $('msg').focus(); closeMobileSidebar(); return; } Same predicate applied to the Cmd/Ctrl+K handler at :844. The in-flight signal (active_stream_id || pending_user_message) is the same one _restoreSettledSession() in messages.js:1081 already uses to decide whether a session is "settled" — keeping both call sites aligned. Verified end-to-end: with S.busy=true and pending_user_message set, the old guard returned `block=true` (= the bug), the new guard returns `block=false` (= fixed). With a truly empty session (no busy, no pending), both old and new guards still block — preserving #1171 behavior. Issue #1423 — Profile name field auto-capitalizes typed values ============================================================== Self-reported (Mac app, May 1 2026): typing `hello` into the New Profile "Name" field shows `Hello` after blur/autofill, contradicting the "Lowercase letters, numbers, hyphens, underscores only" hint right next to it. The form lowercases on submit so stored data is correct, but the displayed value during typing is misleading. Root cause: static/panels.js:2532 had only autocomplete="off": Missing three attributes that actually prevent the misbehavior: - autocapitalize="none" — mobile keyboards (iOS Safari, Android Chrome, WKWebView in the Mac app) auto-capitalize the first letter without it - autocorrect="off" — Safari runs autocorrect on blur, can rewrite hello→Hello - spellcheck="false" — desktop browsers may run spellcheck on blur Fix: Add the three attributes to profileFormName. Also added to profileFormBaseUrl since URLs are similarly bad targets for autocapitalize/autocorrect. profileFormApiKey is type="password" and already has correct browser behavior. Verified end-to-end against the live DOM: openProfileCreate() → getElementById('profileFormName').getAttribute(...) returns the new attributes correctly, with required preserved. Tests ----- 3648 passed, 2 skipped, 3 xpassed (was 3640 — added 8 new regression tests in test_1432_newchat_and_1423_profile_input.py). One pre-existing test had to be widened: tests/test_mobile_layout.py test_new_conversation_closes_mobile_sidebar grabbed only the first 500 chars of the btnNewChat handler block to scan for closeMobileSidebar. The new comment block pushed closeMobileSidebar past that window even though both calls are still present. Bumped the window to 1500 chars and the shortcut-block lines from 12 to 24 to match the multi-line guard. Closes #1432 Closes #1423 Reported by @Olyno (#1432, GitHub) --- CHANGELOG.md | 6 + static/boot.js | 35 ++++- static/panels.js | 4 +- ...est_1432_newchat_and_1423_profile_input.py | 122 ++++++++++++++++++ tests/test_mobile_layout.py | 6 +- 5 files changed, 163 insertions(+), 10 deletions(-) create mode 100644 tests/test_1432_newchat_and_1423_profile_input.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 8bd401e1..543e7e67 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Hermes Web UI -- Changelog +## [Unreleased] + +### Fixed +- **New-chat button (`+`) and Cmd/Ctrl+K were no-ops while the first message was streaming** (#1432, closes #1432) — the empty-session guard from #1171 (`message_count===0` → focus composer instead of creating a new session) didn't account for in-flight streams, where the user's message hasn't been merged into `s.messages` server-side yet. Clicking `+` during the first response of a brand-new session was silently dropped, so users couldn't actually start a parallel conversation. The guard now also requires `!S.busy && !S.session.active_stream_id && !S.session.pending_user_message` — the same in-flight signal already used by `_restoreSettledSession()` in `messages.js:1081`. (`static/boot.js`) +- **Profile-name field auto-capitalized typed values despite the "lowercase only" hint** (#1423, closes #1423) — the input had `autocomplete="off"` but was missing `autocapitalize="none"`, `autocorrect="off"`, and `spellcheck="false"`, so mobile keyboards (iOS Safari/WKWebView, Android Chrome) silently capitalized the first letter and desktop spellcheck could rewrite the value on blur. The form lowercases on submit, so stored data was always correct — the bug was a misleading display during typing. Same attributes added to the Base URL field for the same reason (URLs are not natural-language text). The API key field is `type="password"` and already has correct browser behavior. (`static/panels.js`) + ## [v0.50.261] — 2026-05-02 ### Changed diff --git a/static/boot.js b/static/boot.js index e4134258..15e51e9d 100644 --- a/static/boot.js +++ b/static/boot.js @@ -689,9 +689,24 @@ window._micPendingSend=window._micPendingSend||false; })(); $('fileInput').onchange=e=>{addFiles(Array.from(e.target.files));e.target.value='';}; $('btnNewChat').onclick=async()=>{ - // 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();closeMobileSidebar();return;} + // If the current session has no messages AND nothing is in flight, just focus + // the composer rather than creating another empty session that will clutter the + // sidebar list (#1171). + // + // The "nothing in flight" half is critical (#1432): if the user clicks + while + // their first message is still streaming (or queued), `message_count` is still 0 + // server-side because the user turn hasn't been merged yet. The old guard treated + // that as "empty" and made + a no-op for the entire stream duration, so users + // couldn't actually start a parallel chat. Use the same in-flight signal as + // `_restoreSettledSession()` in messages.js: an active stream id or a queued + // pending user message means the session is real, not empty. + if(S.session + && (S.session.message_count||0)===0 + && !S.busy + && !S.session.active_stream_id + && !S.session.pending_user_message){ + $('msg').focus();closeMobileSidebar();return; + } await newSession();await renderSessionList();closeMobileSidebar();$('msg').focus(); }; $('btnDownload').onclick=()=>{ @@ -839,9 +854,17 @@ document.addEventListener('keydown',async e=>{ } if((e.metaKey||e.ctrlKey)&&e.key==='k'){ e.preventDefault(); - // 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 the current session has no messages AND nothing is in flight, just focus + // the composer rather than creating another empty session that will clutter + // the sidebar list (#1171). See the matching guard in $('btnNewChat').onclick + // and bug #1432 for why the in-flight check is needed. + if(S.session + && (S.session.message_count||0)===0 + && !S.busy + && !S.session.active_stream_id + && !S.session.pending_user_message){ + $('msg').focus();return; + } // 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 diff --git a/static/panels.js b/static/panels.js index b744cebb..2be1fa33 100644 --- a/static/panels.js +++ b/static/panels.js @@ -2529,7 +2529,7 @@ function _renderProfileForm(){