diff --git a/CHANGELOG.md b/CHANGELOG.md index 47b5a2af..4699cf17 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,10 @@ ## [Unreleased] +### Changed + +- **Live Compact Worklog summaries can now be expanded or collapsed while a response is still streaming.** The `Processed …` row keeps its timer and now behaves like the settled Worklog disclosure, so users can hide noisy live work details without waiting for the final answer. + ## [v0.51.603] — 2026-06-23 — Release VJ (cache the app-shell template) ### Changed diff --git a/static/style.css b/static/style.css index 81b10888..6e54bd85 100644 --- a/static/style.css +++ b/static/style.css @@ -3025,16 +3025,9 @@ body.resizing .sidebar{transition:none!important;} .tool-worklog-group[data-tool-worklog-group="1"]:not([data-run-activity-group="1"]) .tool-worklog-summary:hover{ background:transparent; } -.tool-worklog-group[data-live-tool-call-group="1"] .tool-worklog-summary[data-live-summary-static="1"]{ - cursor:default; - opacity:1; -} .tool-worklog-group[data-tool-worklog-group="1"]:not([data-run-activity-group="1"]) .as-dot{ display:none; } -.tool-worklog-group[data-live-tool-call-group="1"][data-live-activity-current="1"] .tool-call-group-chevron{ - display:none; -} .tool-worklog-running-label{display:none;color:var(--accent-text);font-family:'SF Mono',ui-monospace,monospace;font-size:12px;font-weight:600;line-height:1.4;} .tool-worklog-group[data-tool-worklog-running="1"] .tool-worklog-running-label{display:inline-flex;} .tool-worklog-body{ diff --git a/static/ui.js b/static/ui.js index 1c9f759c..38e4c2df 100644 --- a/static/ui.js +++ b/static/ui.js @@ -8953,9 +8953,6 @@ function _onLiveActivityToggle(group){ function _toggleActivityGroup(summary){ const group=summary&&summary.closest?summary.closest('.agent-activity-group,.tool-call-group'):null; if(!group) return; - if(group.getAttribute('data-live-tool-call-group')==='1'&&group.getAttribute('data-live-activity-current')==='1'){ - return; - } const collapsed=group.classList.toggle('tool-call-group-collapsed'); group.classList.toggle('open',!collapsed); summary.setAttribute('aria-expanded',String(!collapsed)); @@ -9845,15 +9842,9 @@ function ensureActivityGroup(inner, opts){ if(opts.turnStartedAt!==undefined&&opts.turnStartedAt!==null) group.setAttribute('data-turn-started-at',String(opts.turnStartedAt)); const summary=group.querySelector('.tool-worklog-summary,.tool-call-group-summary'); if(summary){ - if(live){ - summary.setAttribute('data-live-summary-static','1'); - summary.setAttribute('aria-disabled','true'); - summary.disabled=true; - }else{ - summary.removeAttribute('data-live-summary-static'); - summary.removeAttribute('aria-disabled'); - summary.disabled=false; - } + summary.removeAttribute('data-live-summary-static'); + summary.removeAttribute('aria-disabled'); + summary.disabled=false; } const anchor=opts.anchor||null; if(anchor&&anchor.parentElement===inner&&group.parentElement===inner){ diff --git a/tests/test_live_to_final_anchor_visible_order.py b/tests/test_live_to_final_anchor_visible_order.py index f3702d1c..4a2538d3 100644 --- a/tests/test_live_to_final_anchor_visible_order.py +++ b/tests/test_live_to_final_anchor_visible_order.py @@ -807,17 +807,20 @@ def test_legacy_settled_worklog_summary_uses_processed_anchor_too(): assert ".tool-worklog-group[data-tool-worklog-group=\"1\"]:not([data-run-activity-group=\"1\"]) .tool-worklog-summary" in STYLE_CSS -def test_live_processed_anchor_is_not_clickable_until_settled(): +def test_live_processed_anchor_is_clickable_while_streaming(): toggle = _function_body(UI_JS, "_toggleActivityGroup") ensure = _function_body(UI_JS, "ensureActivityGroup") finalize = _function_body(UI_JS, "_finalizeLiveActivityDisclosureGroup") close = _function_body(UI_JS, "closeCurrentLiveActivityGroup") - assert "group.getAttribute('data-live-tool-call-group')==='1'&&group.getAttribute('data-live-activity-current')==='1'" in toggle - assert "return;" in toggle - assert "summary.setAttribute('data-live-summary-static','1')" in ensure - assert "summary.setAttribute('aria-disabled','true')" in ensure - assert "summary.disabled=true" in ensure + assert "data-live-activity-current')==='1'" not in toggle + assert "_writeActivityDisclosureState(group.getAttribute('data-activity-disclosure-key'), !collapsed);" in toggle + assert "_onLiveActivityToggle(group)" in toggle + assert "summary.setAttribute('data-live-summary-static','1')" not in ensure + assert "summary.setAttribute('aria-disabled','true')" not in ensure + assert "summary.disabled=true" not in ensure + assert "summary.removeAttribute('data-live-summary-static')" in ensure + assert "summary.removeAttribute('aria-disabled')" in ensure assert "summary.disabled=false" in ensure assert "group.removeAttribute('data-live-tool-call-group')" in finalize assert "group.removeAttribute('data-live-tool-worklog-group')" in finalize @@ -828,8 +831,63 @@ def test_live_processed_anchor_is_not_clickable_until_settled(): assert "summary.disabled=false" in finalize assert "summary.setAttribute('aria-expanded',keepOpen?'true':'false')" in finalize assert "_finalizeLiveActivityDisclosureGroup(group)" in close - assert ".tool-worklog-summary[data-live-summary-static=\"1\"]" in STYLE_CSS - assert ".tool-worklog-group[data-live-tool-call-group=\"1\"][data-live-activity-current=\"1\"] .tool-call-group-chevron" in STYLE_CSS + assert ".tool-worklog-summary[data-live-summary-static=\"1\"]" not in STYLE_CSS + assert ".tool-worklog-group[data-live-tool-call-group=\"1\"][data-live-activity-current=\"1\"] .tool-call-group-chevron" not in STYLE_CSS + + +@pytest.mark.skipif(NODE is None, reason="node is required for live disclosure behavior tests") +def test_live_processed_anchor_toggle_collapses_current_worklog_group(): + script = f""" +const assert = require('assert'); +let collapsed = false; +let open = true; +let wrote = null; +let liveExpanded = null; +function _writeActivityDisclosureState(key, value) {{ wrote = [key, value]; }} +function _onLiveActivityToggle(group) {{ liveExpanded = !group.classList.contains('tool-call-group-collapsed'); }} +const group = {{ + attrs: {{ + 'data-live-tool-call-group': '1', + 'data-live-activity-current': '1', + 'data-activity-disclosure-key': 'live:stream-1' + }}, + getAttribute(name) {{ return this.attrs[name] || ''; }}, + classList: {{ + toggle(name, force) {{ + if (name === 'tool-call-group-collapsed') {{ + collapsed = force === undefined ? !collapsed : !!force; + return collapsed; + }} + if (name === 'open') {{ + open = force === undefined ? !open : !!force; + return open; + }} + throw new Error('unexpected class ' + name); + }}, + contains(name) {{ + if (name === 'tool-call-group-collapsed') return collapsed; + if (name === 'open') return open; + return false; + }} + }} +}}; +const summary = {{ + attrs: {{}}, + closest(selector) {{ return group; }}, + setAttribute(name, value) {{ this.attrs[name] = String(value); }} +}}; +function _toggleActivityGroup(summary) {{ +{_function_body(UI_JS, "_toggleActivityGroup")} +}} +_toggleActivityGroup(summary); +assert.strictEqual(collapsed, true); +assert.strictEqual(open, false); +assert.deepStrictEqual(wrote, ['live:stream-1', false]); +assert.strictEqual(liveExpanded, false); +assert.strictEqual(summary.attrs['aria-expanded'], 'false'); + console.log(JSON.stringify({{ok:true}})); +""" + _run_node_script(script) def test_pre_start_worklog_shell_shows_thinking_placeholder_immediately():