From b522092aac2a4fb35a065aca6588bdaba7562cbc Mon Sep 17 00:00:00 2001 From: AJV20 <24819659+AJV20@users.noreply.github.com> Date: Thu, 28 May 2026 10:16:40 -0400 Subject: [PATCH 1/4] feat: add activity feed default expansion setting --- CHANGELOG.md | 4 +++ api/config.py | 2 ++ static/boot.js | 1 + static/i18n.js | 24 ++++++++++++++++++ static/index.html | 7 +++++ static/panels.js | 13 ++++++++++ static/ui.js | 38 ++++++++++++++++++++++------ tests/test_live_activity_timeline.py | 18 ++++++++++++- 8 files changed, 98 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a990d204abc..b432ba3507e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,10 @@ ## [Unreleased] +### Changed +- Activity disclosures now have a Settings → Appearance option to expand the activity feed by default, while still honoring per-turn manual collapse/expand choices. +- Live Activity waiting rows now explain the agent's current step instead of stopping at `Waiting on model`, including a prompt/context review hint before tools run and the latest tool-derived action while the model chooses the next step. + ## [v0.51.152] — 2026-05-28 — Release DX (stage-batch34 — single-PR optional gateway-backed browser chat) ### Added diff --git a/api/config.py b/api/config.py index cfe1cec8799..2327fa78dbe 100644 --- a/api/config.py +++ b/api/config.py @@ -4799,6 +4799,7 @@ def _get_session_agent_lock(session_id: str) -> threading.Lock: "font_size": "default", # small | default | large | xlarge "session_jump_buttons": False, # show Start/End transcript jump pills "session_endless_scroll": False, # auto-load older transcript pages while scrolling upward + "activity_feed_expanded_default": False, # expand Activity disclosures by default for new turns "pinned_sessions_limit": 3, # maximum active pinned sessions shown in the sidebar "inflight_state_max_sessions": 8, # max active-stream recovery snapshots kept in browser localStorage "inflight_state_max_messages": 24, # max recent messages kept per recovery snapshot @@ -4962,6 +4963,7 @@ def load_settings() -> dict: "api_redact_enabled", "session_jump_buttons", "session_endless_scroll", + "activity_feed_expanded_default", } # Language codes are validated as short alphanumeric BCP-47-like tags (e.g. 'en', 'zh', 'fr') _SETTINGS_LANG_RE = __import__("re").compile(r"^[a-zA-Z]{2,10}(-[a-zA-Z0-9]{2,8})?$") diff --git a/static/boot.js b/static/boot.js index 7bcec46ea59..7b94a074452 100644 --- a/static/boot.js +++ b/static/boot.js @@ -1538,6 +1538,7 @@ function applyBotName(){ window._whatsNewSummaryEnabled=!!s.whats_new_summary_enabled; window._showThinking=s.show_thinking!==false; window._simplifiedToolCalling=s.simplified_tool_calling!==false; + window._activityFeedExpandedDefault=!!s.activity_feed_expanded_default; window._sidebarDensity=(s.sidebar_density==='detailed'?'detailed':'compact'); window._pinnedSessionsLimit=parseInt(s.pinned_sessions_limit||3,10)||3; window._inflightStateLimits={ diff --git a/static/i18n.js b/static/i18n.js index e234c038caa..886c50f86b9 100644 --- a/static/i18n.js +++ b/static/i18n.js @@ -518,6 +518,8 @@ const LOCALES = { settings_label_session_endless_scroll: 'Load older messages while scrolling up', settings_desc_session_endless_scroll: 'When enabled, older messages load automatically as you scroll upward. When disabled, use the older-messages button.', + settings_label_activity_feed_expanded_default: 'Expand activity feed by default', + settings_desc_activity_feed_expanded_default: 'Open new Activity disclosures automatically so tool and model progress is visible without an extra click. Per-turn manual collapse/expand choices still win.', settings_label_tab_visibility: 'Sidebar tabs', settings_desc_tab_visibility: 'Choose which tabs appear in the sidebar and rail. Chat and Settings are always visible.', @@ -1810,6 +1812,8 @@ const LOCALES = { settings_label_session_endless_scroll: 'Carica messaggi precedenti scorrendo in alto', settings_desc_session_endless_scroll: 'Se abilitato, i messaggi precedenti si caricano automaticamente scorrendo in alto. Se disabilitato, usa il pulsante messaggi precedenti.', + settings_label_activity_feed_expanded_default: 'Expand activity feed by default', + settings_desc_activity_feed_expanded_default: 'Open new Activity disclosures automatically so tool and model progress is visible without an extra click. Per-turn manual collapse/expand choices still win.', settings_label_tab_visibility: 'Schede della barra laterale', settings_desc_tab_visibility: 'Scegli quali schede mostrare nella barra laterale e nel rail. Chat e Impostazioni sono sempre visibili.', @@ -3094,6 +3098,8 @@ const LOCALES = { settings_label_session_endless_scroll: '上スクロールで古いメッセージを読み込む', settings_desc_session_endless_scroll: '有効にすると、上にスクロールしたとき古いメッセージを自動で読み込みます。無効の場合は古いメッセージボタンを使います。', + settings_label_activity_feed_expanded_default: 'Expand activity feed by default', + settings_desc_activity_feed_expanded_default: 'Open new Activity disclosures automatically so tool and model progress is visible without an extra click. Per-turn manual collapse/expand choices still win.', settings_label_tab_visibility: 'サイドバータブ', settings_desc_tab_visibility: 'サイドバーとレールに表示するタブを選択します。チャットと設定は常に表示されます。', @@ -4943,6 +4949,8 @@ const LOCALES = { settings_label_session_endless_scroll: 'Загружать старые сообщения при прокрутке вверх', settings_desc_session_endless_scroll: 'Если включено, старые сообщения загружаются автоматически при прокрутке вверх. Если выключено, используйте кнопку загрузки старых сообщений.', + settings_label_activity_feed_expanded_default: 'Expand activity feed by default', + settings_desc_activity_feed_expanded_default: 'Open new Activity disclosures automatically so tool and model progress is visible without an extra click. Per-turn manual collapse/expand choices still win.', settings_label_tab_visibility: 'Вкладки боковой панели', settings_desc_tab_visibility: 'Выберите, какие вкладки отображаются на боковой панели и в рейле. Чат и настройки всегда видны.', @@ -6154,6 +6162,8 @@ const LOCALES = { settings_label_session_endless_scroll: 'Cargar mensajes antiguos al desplazarse hacia arriba', settings_desc_session_endless_scroll: 'Si está activado, los mensajes antiguos se cargan automáticamente al desplazarte hacia arriba. Si está desactivado, usa el botón de mensajes antiguos.', + settings_label_activity_feed_expanded_default: 'Expand activity feed by default', + settings_desc_activity_feed_expanded_default: 'Open new Activity disclosures automatically so tool and model progress is visible without an extra click. Per-turn manual collapse/expand choices still win.', settings_label_tab_visibility: 'Pestañas de la barra lateral', settings_desc_tab_visibility: 'Elige qué pestañas aparecen en la barra lateral y el rail. Chat y Configuración siempre están visibles.', @@ -7080,6 +7090,8 @@ const LOCALES = { settings_label_session_endless_scroll: 'Ältere Nachrichten beim Hochscrollen laden', settings_desc_session_endless_scroll: 'Wenn aktiviert, werden ältere Nachrichten beim Hochscrollen automatisch geladen. Wenn deaktiviert, nutzt du den Button für ältere Nachrichten.', + settings_label_activity_feed_expanded_default: 'Expand activity feed by default', + settings_desc_activity_feed_expanded_default: 'Open new Activity disclosures automatically so tool and model progress is visible without an extra click. Per-turn manual collapse/expand choices still win.', settings_label_tab_visibility: 'Seitenleiste-Tabs', settings_desc_tab_visibility: 'Wähle, welche Tabs in der Seitenleiste und im Rail angezeigt werden. Chat und Einstellungen sind immer sichtbar.', @@ -8608,6 +8620,8 @@ const LOCALES = { settings_label_session_endless_scroll: '向上滚动时加载更早的消息', settings_desc_session_endless_scroll: '启用后,向上滚动时会自动加载更早的消息。禁用时请使用加载更早消息按钮。', + settings_label_activity_feed_expanded_default: 'Expand activity feed by default', + settings_desc_activity_feed_expanded_default: 'Open new Activity disclosures automatically so tool and model progress is visible without an extra click. Per-turn manual collapse/expand choices still win.', settings_label_tab_visibility: '侧边栏标签', settings_desc_tab_visibility: '选择在侧边栏和导航栏中显示哪些标签。聊天和设置始终可见。', @@ -9096,6 +9110,8 @@ const LOCALES = { settings_label_session_endless_scroll: '向上捲動時載入較早訊息', settings_desc_session_endless_scroll: '啟用後,向上捲動時會自動載入較早訊息。停用時請使用載入較早訊息按鈕。', + settings_label_activity_feed_expanded_default: 'Expand activity feed by default', + settings_desc_activity_feed_expanded_default: 'Open new Activity disclosures automatically so tool and model progress is visible without an extra click. Per-turn manual collapse/expand choices still win.', settings_label_tab_visibility: '側邊欄標籤', settings_desc_tab_visibility: '選擇在側邊欄和導航列中顯示哪些標籤。聊天和設定始終可見。', @@ -10456,6 +10472,8 @@ const LOCALES = { settings_label_session_endless_scroll: 'Carregar mensagens antigas ao rolar para cima', settings_desc_session_endless_scroll: 'Quando ativado, mensagens antigas carregam automaticamente ao rolar para cima. Quando desativado, use o botão de mensagens antigas.', + settings_label_activity_feed_expanded_default: 'Expand activity feed by default', + settings_desc_activity_feed_expanded_default: 'Open new Activity disclosures automatically so tool and model progress is visible without an extra click. Per-turn manual collapse/expand choices still win.', settings_label_tab_visibility: 'Abas da barra lateral', settings_desc_tab_visibility: 'Escolha quais abas aparecem na barra lateral e no rail. Chat e Configurações estão sempre visíveis.', @@ -11643,6 +11661,8 @@ const LOCALES = { settings_label_session_endless_scroll: '위로 스크롤할 때 이전 메시지 불러오기', settings_desc_session_endless_scroll: '활성화하면 위로 스크롤할 때 이전 메시지를 자동으로 불러옵니다. 비활성화하면 이전 메시지 버튼을 사용합니다.', + settings_label_activity_feed_expanded_default: 'Expand activity feed by default', + settings_desc_activity_feed_expanded_default: 'Open new Activity disclosures automatically so tool and model progress is visible without an extra click. Per-turn manual collapse/expand choices still win.', settings_label_tab_visibility: '사이드바 탭', settings_desc_tab_visibility: '사이드바와 레일에 표시할 탭을 선택하세요. 채팅과 설정은 항상 표시됩니다.', @@ -12843,6 +12863,8 @@ const LOCALES = { settings_desc_session_jump_buttons: 'Affichez les boutons flottants de début et de fin lors de la lecture de longs historiques de session.', settings_label_session_endless_scroll: 'Charger les anciens messages en faisant défiler vers le haut', settings_desc_session_endless_scroll: 'Lorsqu\'ils sont activés, les anciens messages se chargent automatiquement lorsque vous faites défiler vers le haut. Lorsqu\'il est désactivé, utilisez le bouton des messages plus anciens.', + settings_label_activity_feed_expanded_default: 'Expand activity feed by default', + settings_desc_activity_feed_expanded_default: 'Open new Activity disclosures automatically so tool and model progress is visible without an extra click. Per-turn manual collapse/expand choices still win.', settings_label_tab_visibility: 'Onglets de la barre latérale', settings_desc_tab_visibility: 'Choisissez quels onglets apparaissent dans la barre latérale et le rail. Chat et Paramètres sont toujours visibles.', @@ -14146,6 +14168,8 @@ const LOCALES = { settings_label_session_endless_scroll: 'Yukarı kaydırırken eski mesajları yükle', settings_desc_session_endless_scroll: 'Etkinleştirildiğinde, yukarı doğru kaydırdığınızda eski mesajlar otomatik olarak yüklenir. Devre dışı bırakıldığında eski mesajlar düğmesini kullanın.', + settings_label_activity_feed_expanded_default: 'Expand activity feed by default', + settings_desc_activity_feed_expanded_default: 'Open new Activity disclosures automatically so tool and model progress is visible without an extra click. Per-turn manual collapse/expand choices still win.', settings_label_tab_visibility: 'Kenar çubuğu sekmeleri', settings_desc_tab_visibility: 'Kenar çubuğunda ve rayda hangi sekmelerin görüneceğini seçin. Sohbet ve Ayarlar her zaman görünür durumdadır.', diff --git a/static/index.html b/static/index.html index b25fef3a348..e9484965f04 100644 --- a/static/index.html +++ b/static/index.html @@ -980,6 +980,13 @@

What can I help with?

When enabled, older messages load automatically as you scroll upward. When disabled, use the older-messages button.
+
+ +
Open new Activity disclosures automatically so tool and model progress is visible without an extra click. Per-turn manual collapse/expand choices still win.
+
diff --git a/static/panels.js b/static/panels.js index 3261d9002aa..7507a9ccf81 100644 --- a/static/panels.js +++ b/static/panels.js @@ -5801,6 +5801,7 @@ function _appearancePayloadFromUi(){ font_size: ($('settingsFontSize')||{}).value || localStorage.getItem('hermes-font-size') || 'default', session_jump_buttons: !!($('settingsSessionJumpButtons')||{}).checked, session_endless_scroll: !!($('settingsSessionEndlessScroll')||{}).checked, + activity_feed_expanded_default: !!($('settingsActivityFeedExpandedDefault')||{}).checked, hidden_tabs: _getHiddenTabs(), }; } @@ -5854,6 +5855,9 @@ async function _autosaveAppearanceSettings(payload){ if(typeof _applySessionNavigationPrefs==='function') _applySessionNavigationPrefs(); } window._sessionEndlessScrollEnabled=!!(saved&&saved.session_endless_scroll); + if(saved&&Object.prototype.hasOwnProperty.call(saved,'activity_feed_expanded_default')){ + window._activityFeedExpandedDefault=!!saved.activity_feed_expanded_default; + } _setAppearanceAutosaveStatus('saved'); }catch(e){ console.warn('[settings] appearance autosave failed', e); @@ -6068,6 +6072,15 @@ async function loadSettingsPanel(){ _scheduleAppearanceAutosave(); }; } + const activityExpandedCb=$('settingsActivityFeedExpandedDefault'); + if(activityExpandedCb){ + activityExpandedCb.checked=!!settings.activity_feed_expanded_default; + window._activityFeedExpandedDefault=activityExpandedCb.checked; + activityExpandedCb.onchange=function(){ + window._activityFeedExpandedDefault=this.checked; + _scheduleAppearanceAutosave(); + }; + } // Tab visibility chips (dynamically populated from DOM) var hiddenTabs=[]; if(Array.isArray(settings.hidden_tabs)){ diff --git a/static/ui.js b/static/ui.js index a4bab419a75..64f0ba9bca7 100644 --- a/static/ui.js +++ b/static/ui.js @@ -5445,6 +5445,7 @@ function ensureActivityGroup(inner, opts){ if(!group){ group=document.createElement('div'); let collapsed=opts.collapsed!==false; + if(window._activityFeedExpandedDefault===true) collapsed=false; const savedState=_readActivityDisclosureState(activityKey); // Restore the user's explicit expand intent when recreating the live // activity group within the same turn (#1298), then let persisted chat/turn @@ -6990,14 +6991,32 @@ function _activityProgressLabelForToolName(name){ return 'Working'; } +function _activityLatestToolName(group){ + if(!group) return ''; + const running=group.querySelector('.tool-card.tool-card-running .tool-card-name'); + const latest=running || Array.from(group.querySelectorAll('.tool-card-name')).pop(); + return latest?String(latest.textContent||'').trim():''; +} + +function _activityWaitingDetail(group,label=''){ + const toolName=_activityLatestToolName(group); + if(toolName){ + const action=_activityProgressLabelForToolName(toolName); + if(group&&group.querySelector('.tool-card.tool-card-running')) return `${action}: ${toolName}. Results will appear here.`; + return `Last step: ${action} (${toolName}); now choosing the next action or composing a response.`; + } + if(String(label||'').toLowerCase().includes('model')) return 'Reviewing the prompt and context, then choosing the next action or composing the response.'; + return 'The agent is running; tool results and response text will appear here.'; +} + function _activityLiveProgressLabel(group){ if(!group||group.getAttribute('data-live-tool-call-group')!=='1') return ''; const idleAge=_activityLastObservedAge(group); if(idleAge!==null&&idleAge>=90) return `No recent activity for ${_formatActiveElapsedTimer(idleAge)}`; - const running=group.querySelector('.tool-card.tool-card-running .tool-card-name'); - const latest=running || Array.from(group.querySelectorAll('.tool-card-name')).pop(); + const latest=_activityLatestToolName(group); const waiting=group.querySelector('.agent-activity-status-waiting .agent-activity-status-label'); - if(latest) return _activityProgressLabelForToolName(latest.textContent); + if(latest) return _activityProgressLabelForToolName(latest); + if(waiting&&waiting.textContent&&String(waiting.textContent).toLowerCase().includes('model')) return 'Reviewing prompt and context'; if(waiting&&waiting.textContent) return waiting.textContent; return 'Starting agent'; } @@ -7076,8 +7095,13 @@ function appendLiveToolCard(tc){ status:toolDone?(tc.is_error?'error':'done'):'waiting', ts:_activityNowSeconds(), }); - const waiting=body.querySelector('.agent-activity-status[data-activity-event-id="thinking-placeholder"] .agent-activity-status-label'); - if(waiting&&!toolDone) waiting.textContent='Waiting on tool result'; + const waiting=body.querySelector('.agent-activity-status[data-activity-event-id="thinking-placeholder"]'); + if(waiting&&!toolDone){ + const labelEl=waiting.querySelector('.agent-activity-status-label'); + const detailEl=waiting.querySelector('.agent-activity-status-detail'); + if(labelEl) labelEl.textContent='Waiting on tool result'; + if(detailEl) detailEl.textContent=`${_activityProgressLabelForToolName(toolName)}: ${toolName}. Results will appear here.`; + } // Update existing card in place (tool_complete after tool_start) if(tid){ const existing=body.querySelector(`.tool-card-row[data-live-tid="${CSS.escape(tid)}"]`); @@ -7924,9 +7948,7 @@ function appendThinking(text='', options){ if(!body) return; if(!cleanThinking||cleanThinking==='Thinking…'){ const label=body.querySelector('.tool-card.tool-card-running')?'Waiting on tool result':'Waiting on model'; - const detail=body.querySelector('.tool-card-row') - ? 'The agent is running; tool results and response text will appear here.' - : 'No tool activity has been reported yet.'; + const detail=_activityWaitingDetail(group,label); _appendActivityEvent(group,{id:'thinking-placeholder',kind:'waiting',label,detail,status:'waiting',ts:_activityNowSeconds()}); const active=body.querySelector('.agent-activity-thinking[data-thinking-active="1"]'); if(active) active.removeAttribute('data-thinking-active'); diff --git a/tests/test_live_activity_timeline.py b/tests/test_live_activity_timeline.py index 21e04f2ca12..8657036655f 100644 --- a/tests/test_live_activity_timeline.py +++ b/tests/test_live_activity_timeline.py @@ -24,11 +24,27 @@ def test_live_activity_group_has_observable_baseline_events(): def test_empty_thinking_placeholder_becomes_status_row_not_raw_thinking_card(): assert "data-activity-event-id=\"thinking-placeholder\"" in UI_JS assert "Waiting on model" in UI_JS - assert "No tool activity has been reported yet." in UI_JS + assert "Reviewing the prompt and context, then choosing the next action or composing the response." in UI_JS + assert "Reviewing prompt and context" in UI_JS assert "Waiting on tool result" in UI_JS + assert "Last step: ${action} (${toolName}); now choosing the next action or composing a response." in UI_JS assert "_thinkingActivityNode(thinkingText, false)" in UI_JS +def test_activity_feed_default_expand_setting_is_wired(): + index_html = (REPO / "static" / "index.html").read_text(encoding="utf-8") + panels_js = (REPO / "static" / "panels.js").read_text(encoding="utf-8") + boot_js = (REPO / "static" / "boot.js").read_text(encoding="utf-8") + config_py = (REPO / "api" / "config.py").read_text(encoding="utf-8") + + assert 'id="settingsActivityFeedExpandedDefault"' in index_html + assert "settings_label_activity_feed_expanded_default" in index_html + assert '"activity_feed_expanded_default": False' in config_py + assert "activity_feed_expanded_default" in panels_js + assert "window._activityFeedExpandedDefault=!!s.activity_feed_expanded_default;" in boot_js + assert "if(window._activityFeedExpandedDefault===true) collapsed=false;" in UI_JS + + def test_tool_events_update_activity_timeline_and_summary(): assert "Tool finished: ${toolName}" in UI_JS assert "Running tool: ${toolName}" in UI_JS From 0f05fb5372f745b797d94a7ba443343724dbe2ef Mon Sep 17 00:00:00 2001 From: AJV20 <24819659+AJV20@users.noreply.github.com> Date: Thu, 28 May 2026 10:30:56 -0400 Subject: [PATCH 2/4] fix: keep activity progress running-tool preference --- static/ui.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/static/ui.js b/static/ui.js index 64f0ba9bca7..949f899d133 100644 --- a/static/ui.js +++ b/static/ui.js @@ -7013,7 +7013,8 @@ function _activityLiveProgressLabel(group){ if(!group||group.getAttribute('data-live-tool-call-group')!=='1') return ''; const idleAge=_activityLastObservedAge(group); if(idleAge!==null&&idleAge>=90) return `No recent activity for ${_formatActiveElapsedTimer(idleAge)}`; - const latest=_activityLatestToolName(group); + const running=group.querySelector('.tool-card.tool-card-running .tool-card-name'); + const latest=running?String(running.textContent||'').trim():_activityLatestToolName(group); const waiting=group.querySelector('.agent-activity-status-waiting .agent-activity-status-label'); if(latest) return _activityProgressLabelForToolName(latest); if(waiting&&waiting.textContent&&String(waiting.textContent).toLowerCase().includes('model')) return 'Reviewing prompt and context'; From fa3187784596de9b3c7fe32fe9848884e57c618d Mon Sep 17 00:00:00 2001 From: AJV20 <24819659+AJV20@users.noreply.github.com> Date: Thu, 28 May 2026 11:34:14 -0400 Subject: [PATCH 3/4] fix: keep default-expanded activity open after settle --- static/ui.js | 8 ++++++-- tests/test_live_activity_timeline.py | 3 +++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/static/ui.js b/static/ui.js index 949f899d133..49028e54ff8 100644 --- a/static/ui.js +++ b/static/ui.js @@ -5449,7 +5449,9 @@ function ensureActivityGroup(inner, opts){ const savedState=_readActivityDisclosureState(activityKey); // Restore the user's explicit expand intent when recreating the live // activity group within the same turn (#1298), then let persisted chat/turn - // state win across session switches and reloads. + // state win across session switches and reloads. Saved closed-state should + // override the default-expanded preference for settled groups the user has + // explicitly collapsed. if(live && _liveActivityUserExpanded === true) collapsed=false; else if(live && _liveActivityUserExpanded === false) collapsed=true; if(savedState==='open') collapsed=false; @@ -7879,7 +7881,9 @@ function finalizeThinkingCard(){ // when the user has not manually expanded this turn's activity group, or // has manually collapsed it. Otherwise the panel snaps shut whenever new // activity arrives, even mid-read. - if(_liveActivityUserExpanded !== true){ + const shouldKeepOpen = (_liveActivityUserExpanded === true) + || (window._activityFeedExpandedDefault === true && _liveActivityUserExpanded !== false); + if(!shouldKeepOpen){ group.classList.add('tool-call-group-collapsed'); const summary=group.querySelector('.tool-call-group-summary'); if(summary) summary.setAttribute('aria-expanded','false'); diff --git a/tests/test_live_activity_timeline.py b/tests/test_live_activity_timeline.py index 8657036655f..c26194590c9 100644 --- a/tests/test_live_activity_timeline.py +++ b/tests/test_live_activity_timeline.py @@ -43,6 +43,9 @@ def test_activity_feed_default_expand_setting_is_wired(): assert "activity_feed_expanded_default" in panels_js assert "window._activityFeedExpandedDefault=!!s.activity_feed_expanded_default;" in boot_js assert "if(window._activityFeedExpandedDefault===true) collapsed=false;" in UI_JS + finalize_fn = UI_JS.split("function finalizeThinkingCard")[1].split("\nfunction ")[0] + assert "_activityFeedExpandedDefault" in finalize_fn + assert "_liveActivityUserExpanded !== false" in finalize_fn def test_tool_events_update_activity_timeline_and_summary(): From 8f9c0a789b2002907b074893ca053c5535a2eeab Mon Sep 17 00:00:00 2001 From: AJV20 <24819659+AJV20@users.noreply.github.com> Date: Thu, 28 May 2026 11:37:58 -0400 Subject: [PATCH 4/4] fix: preserve activity finalize guard contract --- static/ui.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/static/ui.js b/static/ui.js index 49028e54ff8..3c0aa45ca98 100644 --- a/static/ui.js +++ b/static/ui.js @@ -7881,9 +7881,7 @@ function finalizeThinkingCard(){ // when the user has not manually expanded this turn's activity group, or // has manually collapsed it. Otherwise the panel snaps shut whenever new // activity arrives, even mid-read. - const shouldKeepOpen = (_liveActivityUserExpanded === true) - || (window._activityFeedExpandedDefault === true && _liveActivityUserExpanded !== false); - if(!shouldKeepOpen){ + if(_liveActivityUserExpanded !== true && !(window._activityFeedExpandedDefault === true && _liveActivityUserExpanded !== false)){ group.classList.add('tool-call-group-collapsed'); const summary=group.querySelector('.tool-call-group-summary'); if(summary) summary.setAttribute('aria-expanded','false');