feat: allow live worklog summary collapse

This commit is contained in:
Frank Song 2026-06-23 23:22:16 +08:00
parent 8a98626967
commit f653b1b8d9
4 changed files with 73 additions and 27 deletions

View file

@ -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

View file

@ -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{

View file

@ -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){

View file

@ -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():