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) diff --git a/static/boot.js b/static/boot.js index 7b2ccc8d..ed99427d 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,8 +2990,14 @@ window._mirrorSpeechSettingsFromServer=_mirrorSpeechSettingsFromServer; if(typeof applyLocaleToDOM==='function')applyLocaleToDOM(); } _mirrorSpeechSettingsFromServer(s); - _applyComposerFooterVisibilitySettings(); + // 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). + // 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(); // 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){ @@ -3028,6 +3049,14 @@ 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). + // 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'); } 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(); }; } 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;