From 9ff0298b501a51f3b69f37a230919d422fd4ebf1 Mon Sep 17 00:00:00 2001 From: silent-reader-cn <759313538@qq.com> Date: Sat, 4 Jul 2026 03:19:07 +0000 Subject: [PATCH 1/5] fix(#5451): apply voice-mode pref before recomputing composer divider Clean rebase of silent-reader-cn's #5499 (rebase-first; kept both boot-order _applyVoiceModePref-before-recompute AND the voice-mode onchange recompute + autosave). Co-authored-by: silent-reader-cn (cherry picked from commit eeea3868d3b7f069dbf61470d787541b95af9181) --- static/boot.js | 26 ++++++++++++++++++++++++++ static/panels.js | 4 ++++ 2 files changed, 30 insertions(+) diff --git a/static/boot.js b/static/boot.js index 7b2ccc8d..af2240d8 100644 --- a/static/boot.js +++ b/static/boot.js @@ -2743,6 +2743,21 @@ function _applyComposerFooterVisibilitySettings(){ if(hidden.hide_composer_reasoning&&typeof closeReasoningDropdown==='function') closeReasoningDropdown(); if(hidden.hide_composer_toolsets&&typeof closeToolsetsDropdown==='function') closeToolsetsDropdown(); if(hidden.hide_composer_mobile_config&&typeof closeMobileComposerConfig==='function') closeMobileComposerConfig(); + + // Hide the divider when all left-group buttons before it are hidden + // Stops a lone vertical separator from appearing when attach/saved-prompts/mic/voice are all hidden. + const _divider=document.querySelector('.composer-divider'); + if(_divider){ + const _leftBtnSelectors=['#btnAttach','#btnSavedPrompts','#btnMic','#btnVoiceMode']; + const _allLeftHidden=_leftBtnSelectors.every(sel=>{ + const el=document.querySelector(sel); + return !el||el.classList.contains('composer-control-hidden')||el.style.display==='none'; + }); + // Use classList.toggle directly instead of _setComposerControlHidden + // so we don't strip the intentional aria-hidden="true" on the decorative divider + // when buttons are visible (Greptile feedback). + _divider.classList.toggle('composer-control-hidden',_allLeftHidden); + } } window._applyComposerFooterVisibilitySettings=_applyComposerFooterVisibilitySettings; @@ -2975,6 +2990,11 @@ window._mirrorSpeechSettingsFromServer=_mirrorSpeechSettingsFromServer; if(typeof applyLocaleToDOM==='function')applyLocaleToDOM(); } _mirrorSpeechSettingsFromServer(s); + // Apply voice-mode visibility BEFORE computing the divider so the + // .composer-divider (#5451) sees #btnVoiceMode final display even + // when a server/localStorage sync path flipped the pref between + // module init and settings-load completion (round-2 SILENT race). + if(typeof _applyVoiceModePref==='function') _applyVoiceModePref(); _applyComposerFooterVisibilitySettings(); if(typeof window._applyVoiceModePref==='function') window._applyVoiceModePref(); // TTS: apply enabled state on boot so buttons show/hide correctly (#499) @@ -3028,6 +3048,12 @@ window._mirrorSpeechSettingsFromServer=_mirrorSpeechSettingsFromServer; setLocale(_lang); if(typeof applyLocaleToDOM==='function')applyLocaleToDOM(); } + // Apply voice-mode visibility BEFORE computing the divider so the + // .composer-divider (#5451) sees #btnVoiceMode final display even when + // a server/localStorage sync path flipped the pref between module init + // and settings-load completion (round-2 SILENT race fix; safe no-op on + // the failure-fallback path because _applyVoiceModePref is idempotent). + if(typeof _applyVoiceModePref==='function') _applyVoiceModePref(); _applyComposerFooterVisibilitySettings(); if(typeof _applyTtsEnabled==='function') _applyTtsEnabled(localStorage.getItem('hermes-tts-enabled')==='true'); } diff --git a/static/panels.js b/static/panels.js index a1ecd517..0a696db9 100644 --- a/static/panels.js +++ b/static/panels.js @@ -8907,6 +8907,9 @@ async function loadSettingsPanel(){ // Voice-mode button visibility (#1488). // Toggling re-applies immediately via the boot.js helper so the user sees // the audio-waveform button appear/disappear without a reload. + // Also recomputes composer footer visibility so the .composer-divider + // (which tracks whether all left-group buttons are hidden, see #5451) + // stays in sync when #btnVoiceMode appears or disappears here. const voiceModeCb=$('settingsVoiceModeEnabled'); if(voiceModeCb){ voiceModeCb.checked=_speechBool('voice_mode_button','hermes-voice-mode-button',false); @@ -8914,6 +8917,7 @@ async function loadSettingsPanel(){ _markSpeechPreferenceChanged('voice_mode_button'); localStorage.setItem('hermes-voice-mode-button',this.checked?'true':'false'); if(typeof window._applyVoiceModePref==='function') window._applyVoiceModePref(); + if(typeof window._applyComposerFooterVisibilitySettings==='function') window._applyComposerFooterVisibilitySettings(); _schedulePreferencesAutosave(); }; } From edce1772021e1e890c898157751d0e1bfecf7075 Mon Sep 17 00:00:00 2001 From: wangyichen Date: Sun, 5 Jul 2026 15:45:55 +0800 Subject: [PATCH 2/5] =?UTF-8?q?fix(#5499):=20use=20window.=5FapplyVoiceMod?= =?UTF-8?q?ePref=20=E2=80=94=20bare=20name=20is=20closure-local?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Greptile P1: _applyVoiceModePref is declared with 'function' inside the voice-mode IIFE (line 1205) and exported to window._applyVoiceModePref at line 1212. The bare identifier _applyVoiceModePref is closure-local and not visible from the settings-load IIFE branches at line 2997 and line 3056 — so `typeof _applyVoiceModePref==='function'` was always 'undefined', the guard was permanently false, and the pre-divider call silently never fired. This undid the entire boot-order fix: divider was still computed against the pre-sync #btnVoiceMode visibility, so the round-2 SILENT race the PR was meant to close remained. Fix: use window._applyVoiceModePref in both branches, matching the already-correct call on line 2999 (which worked only by accident — it ran AFTER _applyComposerFooterVisibilitySettings so it had no effect on the bug, but at least it ran). Tests: pytest tests/test_issue4598_composer_control_visibility.py tests/test_issue1488_composer_voice_buttons.py — 26/26 pass. --- static/boot.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/static/boot.js b/static/boot.js index af2240d8..28109c62 100644 --- a/static/boot.js +++ b/static/boot.js @@ -2994,7 +2994,9 @@ window._mirrorSpeechSettingsFromServer=_mirrorSpeechSettingsFromServer; // .composer-divider (#5451) sees #btnVoiceMode final display even // when a server/localStorage sync path flipped the pref between // module init and settings-load completion (round-2 SILENT race). - if(typeof _applyVoiceModePref==='function') _applyVoiceModePref(); + // Note: must use window._applyVoiceModePref — the bare name is + // closure-local to the voice-mode IIFE and not visible here. + if(typeof window._applyVoiceModePref==='function') window._applyVoiceModePref(); _applyComposerFooterVisibilitySettings(); if(typeof window._applyVoiceModePref==='function') window._applyVoiceModePref(); // TTS: apply enabled state on boot so buttons show/hide correctly (#499) @@ -3053,7 +3055,9 @@ window._mirrorSpeechSettingsFromServer=_mirrorSpeechSettingsFromServer; // a server/localStorage sync path flipped the pref between module init // and settings-load completion (round-2 SILENT race fix; safe no-op on // the failure-fallback path because _applyVoiceModePref is idempotent). - if(typeof _applyVoiceModePref==='function') _applyVoiceModePref(); + // Note: must use window._applyVoiceModePref — the bare name is + // closure-local to the voice-mode IIFE and not visible here. + if(typeof window._applyVoiceModePref==='function') window._applyVoiceModePref(); _applyComposerFooterVisibilitySettings(); if(typeof _applyTtsEnabled==='function') _applyTtsEnabled(localStorage.getItem('hermes-tts-enabled')==='true'); } From aa201b1550d8bb7ed442bfe81a8e1f922c16db12 Mon Sep 17 00:00:00 2001 From: wangyichen Date: Sun, 5 Jul 2026 15:55:36 +0800 Subject: [PATCH 3/5] chore(#5499): drop redundant post-divider _applyVoiceModePref call MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The success branch had two adjacent calls: if(typeof window._applyVoiceModePref==='function') window._applyVoiceModePref(); // pre-divider (the actual fix) _applyComposerFooterVisibilitySettings(); if(typeof window._applyVoiceModePref==='function') window._applyVoiceModePref(); // post-divider (redundant) The post-divider one ran after _applyComposerFooterVisibilitySettings, so it couldn't affect the divider the PR was meant to fix. It looked like a defensive double-tap, but the function is idempotent and the pref source (localStorage) doesn't change between the two synchronous calls — so it was just dead code that made the diff look bigger than the actual semantic change. Drop it. Net PR footprint is now strictly: 'use the window-qualified name and apply once before divider recompute in both IIFE branches.' Re-tested: 26/26 pass on test_issue4598_composer_control_visibility + test_issue1488_composer_voice_buttons. --- static/boot.js | 1 - 1 file changed, 1 deletion(-) diff --git a/static/boot.js b/static/boot.js index 28109c62..ed99427d 100644 --- a/static/boot.js +++ b/static/boot.js @@ -2998,7 +2998,6 @@ window._mirrorSpeechSettingsFromServer=_mirrorSpeechSettingsFromServer; // closure-local to the voice-mode IIFE and not visible here. if(typeof window._applyVoiceModePref==='function') window._applyVoiceModePref(); _applyComposerFooterVisibilitySettings(); - if(typeof window._applyVoiceModePref==='function') window._applyVoiceModePref(); // TTS: apply enabled state on boot so buttons show/hide correctly (#499) if(typeof _applyTtsEnabled==='function') _applyTtsEnabled(localStorage.getItem('hermes-tts-enabled')==='true'); }catch(e){ From 8fc3179a8a852fec342da1f17ae9ab3bc7ef2927 Mon Sep 17 00:00:00 2001 From: nankingjing <1079826437@qq.com> Date: Sun, 5 Jul 2026 18:43:20 +0800 Subject: [PATCH 4/5] fix(#3874): auto-refresh sidebar session list after new session creation Co-Authored-By: Claude --- static/sessions.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/static/sessions.js b/static/sessions.js index aa478e9f..791f225a 100644 --- a/static/sessions.js +++ b/static/sessions.js @@ -1242,7 +1242,8 @@ async function newSession(flash, options={}){ const _dirP=loadDir('.'); if(_dirP&&typeof _dirP.catch==='function') _dirP.catch(()=>{}); } - // don't call renderSessionList here - callers do it when needed + // Refresh sidebar to include the newly created session (#3874). + if(typeof refreshSessionList==='function'){Promise.resolve(refreshSessionList('new-session')).catch(()=>{})} })(); try{ return await _newSessionInFlight; From d76d11abb493e1861f6929700023b52273d869ca Mon Sep 17 00:00:00 2001 From: nesquena-hermes Date: Sun, 5 Jul 2026 21:39:01 +0000 Subject: [PATCH 5/5] =?UTF-8?q?release:=20tiny-fix=20batch=20=E2=80=94=20s?= =?UTF-8?q?idebar=20auto-refresh=20on=20new=20session=20(#5632)=20+=20comp?= =?UTF-8?q?oser-divider=20hidden=20when=20left=20group=20empty=20(#5499)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Both gate-clean: Codex SAFE, Fable SHIP-UX, suite 12140/0, browser-smoke clean. #5499 supersedes #5451. Co-authored-by: nankingjing <1079826437@qq.com> Co-authored-by: wangyichen --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f0da72ff..e9d8345e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,8 @@ ### Fixed +- **The sidebar now shows a newly created session immediately.** Creating a new conversation from inside the WebUI left the sidebar list stale on some code paths until a manual refresh; `newSession()` now refreshes the list (coalesced, so no double-repaint). Thanks @nankingjing. (#5632, #3874) +- **No more stray composer divider when the left button group is fully hidden.** If you hid all four left composer controls (attach, saved prompts, mic, voice mode), the vertical divider to their right was left orphaned; it's now hidden with them. The divider visibility is also recomputed after the voice-mode preference is applied on both the settings-load boot path and the voice-mode toggle, so it no longer boots against a stale button state. Thanks @silent-reader-cn. (#5499, #5451) - **Mobile: scrolling up into history during a streaming reply no longer jumps the viewport to the top.** On touch devices, a recent change applied `content-visibility: auto` with a flat 1px intrinsic-size estimate to every off-screen message row; tall assistant rows (long answers / large tool results) collapsed to 1px, `scrollHeight` lurched down by tens of thousands of pixels, and the browser clamped `scrollTop` back to the top. `content-visibility` is now kept only for the short, size-predictable user rows (assistant rows keep layout/style containment without the height-collapse), and when you've scrolled up and the semantic anchor can't be restored, the view now holds position via native scroll-anchoring instead of snapping to a stale offset. Thanks @allenliang2022. (#5638, #5637) - **Long transcripts no longer jump to the top when the scroll anchor row is recycled out of the virtual window.** The virtual-scroll compensation used to give up (leaving the viewport uncompensated) when its anchor row had been recycled during a re-render; it now recovers the anchor by session message index and falls back to a top-padding delta, so scroll position is preserved across re-renders. Thanks @allenliang2022. (#5635, #4346) - **Compacted (archived) messages no longer creep back into the model's context.** In-place context compaction marks old messages inactive in `state.db`, but WebUI's reconciliation reader still pulled those archived rows back into the next turn's model-facing context — undoing the compaction and making long conversations re-trigger compression every turn. The reader now excludes inactive rows by default (with an explicit opt-in for recovery/audit views), so compaction actually sticks. Schemas without the active-row marker are unaffected. Thanks @ai-ag2026. (#5626)