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');