From e5640a42b91972819a4f00e1a4755ba6cf470ae1 Mon Sep 17 00:00:00 2001 From: Rod Boev Date: Sat, 13 Jun 2026 14:48:07 -0400 Subject: [PATCH] Keep prompt-attention title badges from breaking title ownership --- CHANGELOG.md | 4 ++++ static/messages.js | 18 ++++++++++++++---- static/ui.js | 6 ++++-- tests/test_1694_prompt_ownership.py | 3 +++ tests/test_topbar_lazy_message_count.py | 6 +++--- 5 files changed, 28 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b92a7af8..0caf6c62 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,10 @@ ## [Unreleased] +### Fixed + +- **Desktop wrappers can now detect approval/clarify attention from the document title without stealing chat-title ownership (#4121).** Active chat tabs prepend a `● ` marker only while the current session has a pending approval or clarification prompt, and the badge clears cleanly on prompt dismissal or session switch while `syncTopbar()` keeps owning the underlying `" — "` title format. (#4121) + ## [v0.51.392] — 2026-06-13 — Release NE (align WebUI reasoning efforts to the agent's accepted set, drop "max") ### Fixed diff --git a/static/messages.js b/static/messages.js index 672a909f..28f29d1d 100644 --- a/static/messages.js +++ b/static/messages.js @@ -4144,8 +4144,10 @@ function _rememberApprovalPending(pending, pendingCount) { } function _clearApprovalPendingForSession(sid) { - if (sid) _approvalPendingBySession.delete(sid); - if (typeof syncTopbar === 'function') syncTopbar(); + if (sid) { + _approvalPendingBySession.delete(sid); + if (typeof syncTopbar === 'function') syncTopbar(); + } } function _hideApprovalCardIfOwner(sid, force=false) { @@ -4600,8 +4602,10 @@ function _rememberClarifyPending(pending) { } function _clearClarifyPendingForSession(sid) { - if (sid) _clarifyPendingBySession.delete(sid); - if (typeof syncTopbar === 'function') syncTopbar(); + if (sid) { + _clarifyPendingBySession.delete(sid); + if (typeof syncTopbar === 'function') syncTopbar(); + } } function _hideClarifyCardIfOwner(sid, force=false, reason="dismissed") { @@ -4623,8 +4627,14 @@ function showClarifyForSession(sid, pending) { } function _renderPendingPromptsForActiveSession() { + const sid = _promptActiveSessionId(); _renderPendingApprovalForActiveSession(); _renderPendingClarifyForActiveSession(); + if ( + sid && + typeof activeSessionHasPendingPromptAttention === 'function' && + activeSessionHasPendingPromptAttention() + ) return; if (typeof syncTopbar === 'function') syncTopbar(); } diff --git a/static/ui.js b/static/ui.js index cf38cbe7..7a4f27ce 100644 --- a/static/ui.js +++ b/static/ui.js @@ -6274,8 +6274,10 @@ function syncTopbar(){ } const sessionTitle=S.session.title||t('untitled'); const _topbarTitle=$('topbarTitle');if(_topbarTitle)_topbarTitle.textContent=sessionTitle; - const pendingPrefix=(typeof activeSessionHasPendingPromptAttention==='function'&&activeSessionHasPendingPromptAttention())?'● ':''; - document.title=pendingPrefix+sessionTitle+' \u2014 '+assistantDisplayName(); + document.title=sessionTitle+' \u2014 '+assistantDisplayName(); + if(typeof activeSessionHasPendingPromptAttention==='function'&&activeSessionHasPendingPromptAttention()){ + document.title='● '+document.title; + } const _topbarMeta=$('topbarMeta'); if(_topbarMeta){ let sourceLabel=(S.session&&(S.session.source_label||S.session.source_tag||S.session.raw_source))||''; diff --git a/tests/test_1694_prompt_ownership.py b/tests/test_1694_prompt_ownership.py index 669e95e2..251e151b 100644 --- a/tests/test_1694_prompt_ownership.py +++ b/tests/test_1694_prompt_ownership.py @@ -111,5 +111,8 @@ def test_prompt_rerender_hides_previous_session_cards_without_clearing_cache(): assert "_clarifySessionId && _clarifySessionId !== sid" in clarify_body assert "hideClarifyCard(true, 'session')" in clarify_body assert "syncTopbar()" in rerender_body + assert "activeSessionHasPendingPromptAttention()" in rerender_body + assert "if (sid) {" in clear_approval_body assert "syncTopbar()" in clear_approval_body + assert "if (sid) {" in clear_clarify_body assert "syncTopbar()" in clear_clarify_body diff --git a/tests/test_topbar_lazy_message_count.py b/tests/test_topbar_lazy_message_count.py index b370bc9b..ff37c2e9 100644 --- a/tests/test_topbar_lazy_message_count.py +++ b/tests/test_topbar_lazy_message_count.py @@ -24,9 +24,9 @@ def test_sync_topbar_does_not_count_only_loaded_tail_messages(): assert "const metaText=_topbarMessageMetaText();" in block assert "t('n_messages',vis.length)" not in block assert "S.messages.filter(m=>m&&m.role&&m.role!=='tool')" not in block - assert "const pendingPrefix=(typeof activeSessionHasPendingPromptAttention==='function'&&activeSessionHasPendingPromptAttention())?'● ':'';" in block - assert "document.title=pendingPrefix+sessionTitle+' \\u2014 '+assistantDisplayName();" in block - assert "document.title=sessionTitle+' \\u2014 '+assistantDisplayName();" not in block + assert "document.title=sessionTitle+' \\u2014 '+assistantDisplayName();" in block + assert "document.title='● '+document.title;" in block + assert "const pendingPrefix=(typeof activeSessionHasPendingPromptAttention==='function'&&activeSessionHasPendingPromptAttention())?'● ':'';" not in block sessions_js = (ROOT / "static" / "sessions.js").read_text() fn = sessions_js[