From f2abe50b7c4abf7b0f90252eba8a790426e858a8 Mon Sep 17 00:00:00 2001 From: sintez Date: Tue, 23 Jun 2026 02:13:34 +0300 Subject: [PATCH 1/3] perf: skip gateway poll when hidden/streaming, re-init smd parser before innerHTML fallback --- static/messages.js | 7 +++++++ static/sessions.js | 18 +++++++++++++++++- 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/static/messages.js b/static/messages.js index cf15b25b..12b1ba88 100644 --- a/static/messages.js +++ b/static/messages.js @@ -3352,6 +3352,13 @@ function attachLiveStream(activeSid, streamId, uploaded=[], options={}){ : _stripXmlToolCalls(assistantText.slice(segmentStart)); if(_smdParser){ _smdWrite(displayText); + } else if(window.smd){ + // Parser was nulled out (e.g. by a prior segment end) but smd is + // available — recreate it on the existing element. Uses the non-fade + // renderer to match standard rendering, avoiding O(n²) innerHTML + // churn on long responses (#4704). + _smdNewParser(assistantBody, false); + if(_smdParser) _smdWrite(displayText); } else if(renderMd){ assistantBody.innerHTML=renderMd(displayText); } else { diff --git a/static/sessions.js b/static/sessions.js index 062fd639..1867768e 100644 --- a/static/sessions.js +++ b/static/sessions.js @@ -4101,7 +4101,23 @@ if(typeof window!=='undefined') window.refreshSessionList = refreshSessionList; function startGatewayPollFallback(ms){ const intervalMs = Math.max(5000, Number(ms) || _gatewayFallbackPollMs); if(_gatewayPollTimer) clearInterval(_gatewayPollTimer); - _gatewayPollTimer = setInterval(() => { renderSessionList({deferWhileInteracting:true}); }, intervalMs); + _gatewayPollTimer = setInterval(() => { + // Skip poll when tab is hidden or a stream is active — saves CPU + // and avoids redundant DOM renders during active streaming (#4704). + if(typeof document !== 'undefined' && document.hidden) return; + if(typeof S !== 'undefined' && (S.busy || S.activeStreamId)) return; + renderSessionList({deferWhileInteracting:true}); + }, intervalMs); + // Visibility catch-up: refresh immediately when tab re-gains focus, + // so no gateway updates are dropped during hidden-skip periods. + if(typeof document !== 'undefined' && !document._hermesGatewayPollVisibilityHook){ + document.addEventListener('visibilitychange', () => { + if(!document.hidden && typeof renderSessionList === 'function'){ + void renderSessionList({deferWhileInteracting:false}); + } + }); + document._hermesGatewayPollVisibilityHook = true; + } } function stopGatewayPollFallback(){ From 20c70c3505d1541382797f37e9c9f4fc3faa9c21 Mon Sep 17 00:00:00 2001 From: nesquena-hermes Date: Mon, 22 Jun 2026 23:27:23 +0000 Subject: [PATCH 2/3] fix(#4730): clear assistantBody before smd re-init to prevent duplicate live text MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Codex gate found: when window.smd becomes available after a segment already rendered through the renderMd() fallback, _smdNewParser resets _smdWrittenText to '' but does NOT clear the element, so the following _smdWrite(displayText) appends the full accumulated segment on top of the existing fallback render — duplicating the live assistant text. Clear assistantBody.innerHTML before re-init. Co-authored-by: akrhin --- static/messages.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/static/messages.js b/static/messages.js index 12b1ba88..2c1a02d0 100644 --- a/static/messages.js +++ b/static/messages.js @@ -3356,7 +3356,12 @@ function attachLiveStream(activeSid, streamId, uploaded=[], options={}){ // Parser was nulled out (e.g. by a prior segment end) but smd is // available — recreate it on the existing element. Uses the non-fade // renderer to match standard rendering, avoiding O(n²) innerHTML - // churn on long responses (#4704). + // churn on long responses (#4704). Clear any content the renderMd() + // fallback already wrote first: _smdNewParser resets _smdWrittenText to + // '' but does NOT clear the element, so a following _smdWrite(displayText) + // would append the full accumulated segment ON TOP of the existing + // fallback render and duplicate the live text. + assistantBody.innerHTML=''; _smdNewParser(assistantBody, false); if(_smdParser) _smdWrite(displayText); } else if(renderMd){ From 938ae256970f3934a21318342c40dc82286572eb Mon Sep 17 00:00:00 2001 From: nesquena-hermes Date: Mon, 22 Jun 2026 23:42:30 +0000 Subject: [PATCH 3/3] =?UTF-8?q?Release=20v0.51.592=20=E2=80=94=20Release?= =?UTF-8?q?=20UY=20(skip=20gateway=20poll=20when=20hidden/streaming=20+=20?= =?UTF-8?q?smd=20re-init,=20#4730)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0feebddb..341a2d7f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,12 @@ ## [Unreleased] +## [v0.51.592] — 2026-06-22 — Release UY (skip gateway poll when hidden/streaming + smd re-init) + +### Fixed + +- **The sidebar's gateway-poll fallback no longer burns CPU re-rendering while you're streaming or the tab is hidden, and long streamed responses render more efficiently.** The 5s gateway-poll fallback now skips its `renderSessionList` pass when the tab is hidden or a turn is actively streaming (it catches up immediately on tab refocus via a one-time `visibilitychange` listener, so no gateway updates are dropped). Separately, when the streaming markdown parser was torn down by a prior segment but `window.smd` is still available, the live renderer now recreates the parser on the cleared element instead of falling back to repeated full-`innerHTML` rebuilds — avoiding O(n²) DOM churn on long responses. Thanks @akrhin. (#4730) + ## [v0.51.591] — 2026-06-22 — Release UX (stop transcript jumping to first message on completion) ### Fixed