feat(topbar): badge pending prompts in document title (#4121)

This commit is contained in:
Rod Boev committed 2026-06-13 15:04:20 -04:00
1 parent ebe9fff809
commit 4329768288
4 files changed
+27 -1

No files matched your search

+13
View File
@@ -4126,6 +4126,14 @@ function _approvalPromptBelongsToActiveSession(sid) {
return !!(sid && _promptActiveSessionId() === sid);
}
function activeSessionHasPendingPromptAttention() {
const sid = _promptActiveSessionId();
return !!(sid && (
_approvalPendingBySession.has(sid) ||
_clarifyPendingBySession.has(sid)
));
}
function _rememberApprovalPending(pending, pendingCount) {
if (!pending) return null;
const sid = pending._session_id || _promptActiveSessionId();
@@ -4137,6 +4145,7 @@ function _rememberApprovalPending(pending, pendingCount) {
function _clearApprovalPendingForSession(sid) {
if (sid) _approvalPendingBySession.delete(sid);
if (typeof syncTopbar === 'function') syncTopbar();
}
function _hideApprovalCardIfOwner(sid, force=false) {
@@ -4204,6 +4213,7 @@ function showApprovalCard(pending, pendingCount) {
if (onceBtn && document.activeElement !== $('msg')) {
setTimeout(() => onceBtn.focus({preventScroll: true}), 50);
}
if (typeof syncTopbar === 'function') syncTopbar();
}
function _syncApprovalCollapseButton(card) {
@@ -4591,6 +4601,7 @@ function _rememberClarifyPending(pending) {
function _clearClarifyPendingForSession(sid) {
if (sid) _clarifyPendingBySession.delete(sid);
if (typeof syncTopbar === 'function') syncTopbar();
}
function _hideClarifyCardIfOwner(sid, force=false, reason="dismissed") {
@@ -4614,6 +4625,7 @@ function showClarifyForSession(sid, pending) {
function _renderPendingPromptsForActiveSession() {
_renderPendingApprovalForActiveSession();
_renderPendingClarifyForActiveSession();
if (typeof syncTopbar === 'function') syncTopbar();
}
function _ensureClarifyCardDom() {
@@ -4966,6 +4978,7 @@ function showClarifyCard(pending) {
if (input && !sameClarify && document.activeElement !== $('msg')) {
input.focus({preventScroll: true});
}
if (typeof syncTopbar === 'function') syncTopbar();
}
async function respondClarify(response) {
+2 -1
View File
@@ -6274,7 +6274,8 @@ function syncTopbar(){
}
const sessionTitle=S.session.title||t('untitled');
const _topbarTitle=$('topbarTitle');if(_topbarTitle)_topbarTitle.textContent=sessionTitle;
document.title=sessionTitle+' \u2014 '+assistantDisplayName();
const pendingPrefix=(typeof activeSessionHasPendingPromptAttention==='function'&&activeSessionHasPendingPromptAttention())?'● ':'';
document.title=pendingPrefix+sessionTitle+' \u2014 '+assistantDisplayName();
const _topbarMeta=$('topbarMeta');
if(_topbarMeta){
let sourceLabel=(S.session&&(S.session.source_label||S.session.source_tag||S.session.raw_source))||'';
+9
View File
@@ -62,8 +62,10 @@ def test_approval_card_render_is_gated_to_active_session_and_cached():
body = _function_body(MESSAGES_JS, "showApprovalCard")
assert "_rememberApprovalPending(" in body
assert "_approvalPromptBelongsToActiveSession(sid)" in body
assert "syncTopbar()" in body
assert "return;" in body
assert "let _approvalPendingBySession" in MESSAGES_JS
assert "function activeSessionHasPendingPromptAttention()" in MESSAGES_JS
assert "function _renderPendingPromptsForActiveSession()" in MESSAGES_JS
@@ -71,6 +73,7 @@ def test_clarify_card_render_is_gated_to_active_session_and_cached():
body = _function_body(MESSAGES_JS, "showClarifyCard")
assert "_rememberClarifyPending(" in body
assert "_clarifyPromptBelongsToActiveSession(sid)" in body
assert "syncTopbar()" in body
assert "return;" in body
assert "let _clarifyPendingBySession" in MESSAGES_JS
assert "function _renderPendingPromptsForActiveSession()" in MESSAGES_JS
@@ -100,7 +103,13 @@ def test_load_session_rerenders_cached_prompt_for_new_active_session():
def test_prompt_rerender_hides_previous_session_cards_without_clearing_cache():
approval_body = _function_body(MESSAGES_JS, "_renderPendingApprovalForActiveSession")
clarify_body = _function_body(MESSAGES_JS, "_renderPendingClarifyForActiveSession")
rerender_body = _function_body(MESSAGES_JS, "_renderPendingPromptsForActiveSession")
clear_approval_body = _function_body(MESSAGES_JS, "_clearApprovalPendingForSession")
clear_clarify_body = _function_body(MESSAGES_JS, "_clearClarifyPendingForSession")
assert "_approvalSessionId && _approvalSessionId !== sid" in approval_body
assert "hideApprovalCard(true)" in approval_body
assert "_clarifySessionId && _clarifySessionId !== sid" in clarify_body
assert "hideClarifyCard(true, 'session')" in clarify_body
assert "syncTopbar()" in rerender_body
assert "syncTopbar()" in clear_approval_body
assert "syncTopbar()" in clear_clarify_body
+3
View File
@@ -24,6 +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
sessions_js = (ROOT / "static" / "sessions.js").read_text()
fn = sessions_js[