Preserve worklog detail disclosure state
This commit is contained in:
1 parent
dee23a3528
commit
9f0e76c182
7 files changed
+254
-10
No files matched your search
@@ -3,6 +3,10 @@
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### Fixed
|
||||
|
||||
- **Manual Thinking and Tool detail collapse/expand choices now survive live transcript refreshes (#4062).** `renderMessages()` captures open/closed state for Worklog Thinking cards, Tool cards, and multi-tool detail groups before rebuilding the transcript, then restores it afterward, so new streaming results no longer reset every detail row back to the global Worklog-details default.
|
||||
|
||||
## [v0.51.372] — 2026-06-12 — Release MK (markdown link-label inline code, /use skill autocomplete, mobile Worklog overflow)
|
||||
|
||||
### Fixed
|
||||
|
||||
+124
-3
@@ -6562,6 +6562,103 @@ function _applyWorklogDetailsExpandedDefault(root){
|
||||
if(summary) summary.setAttribute('aria-expanded', String(open));
|
||||
});
|
||||
}
|
||||
const _worklogDetailDisclosureSelector='.thinking-card,.tool-card,.tool-group[data-tool-worklog-tool-group="1"],.tool-worklog-tool-group';
|
||||
function _worklogDetailTextKey(text, maxLen){
|
||||
return String(text||'').replace(/\s+/g,' ').trim().slice(0,maxLen||160);
|
||||
}
|
||||
function _worklogDetailHashKey(value){
|
||||
const s=String(value||'');
|
||||
let hash=2166136261;
|
||||
for(let i=0;i<s.length;i++){
|
||||
hash^=s.charCodeAt(i);
|
||||
hash=Math.imul(hash,16777619)>>>0;
|
||||
}
|
||||
return hash.toString(36);
|
||||
}
|
||||
function _worklogDetailBaseKey(el){
|
||||
if(!el||!el.classList) return '';
|
||||
const activity=el.closest&&el.closest('.agent-activity-group,.tool-worklog-group[data-tool-worklog-group="1"],.tool-call-group[data-tool-call-group="1"],.live-worklog[data-live-worklog-shell="1"]');
|
||||
const scope=activity?[
|
||||
activity.getAttribute('data-activity-disclosure-key')||'',
|
||||
activity.getAttribute('data-tool-worklog-key')||'',
|
||||
activity.getAttribute('data-live-segment-seq')||'',
|
||||
activity.getAttribute('data-activity-burst-id')||'',
|
||||
].filter(Boolean).join('|'):'';
|
||||
if(el.classList.contains('thinking-card')){
|
||||
const row=el.closest('.agent-activity-thinking,.thinking-card-row');
|
||||
const stable=row&&(
|
||||
row.getAttribute('data-thinking-key')||
|
||||
row.getAttribute('data-live-thinking-key')||
|
||||
row.getAttribute('data-live-segment-seq')||
|
||||
row.getAttribute('data-activity-burst-id')||
|
||||
row.id||
|
||||
''
|
||||
);
|
||||
return `thinking:${scope}:${stable||'ordinal'}`;
|
||||
}
|
||||
if(el.classList.contains('tool-card')){
|
||||
const row=el.closest('.tool-card-row');
|
||||
const tid=row&&(
|
||||
row.getAttribute('data-tool-disclosure-key')||
|
||||
row.getAttribute('data-live-tid')||
|
||||
row.getAttribute('data-tool-call-id')||
|
||||
row.getAttribute('data-tool-id')||
|
||||
''
|
||||
);
|
||||
const label=row&&(row.dataset&&row.dataset.toolActionLabel)||'';
|
||||
const name=el.querySelector('.tool-card-name');
|
||||
return `tool:${scope}:${tid||label||_worklogDetailTextKey(name?name.textContent:'tool',80)}`;
|
||||
}
|
||||
if(el.matches&&el.matches('.tool-group[data-tool-worklog-tool-group="1"],.tool-worklog-tool-group')){
|
||||
const stable=
|
||||
el.getAttribute('data-tool-group-disclosure-key')||
|
||||
el.getAttribute('data-activity-disclosure-key')||
|
||||
el.getAttribute('data-tool-worklog-key')||
|
||||
el.getAttribute('data-live-segment-seq')||
|
||||
el.getAttribute('data-activity-burst-id')||
|
||||
'group';
|
||||
return `tool-group:${scope}:${stable}`;
|
||||
}
|
||||
return '';
|
||||
}
|
||||
function _worklogDetailDisclosureIsOpen(el){
|
||||
return !!(el&&el.classList&&el.classList.contains('open'));
|
||||
}
|
||||
function _setWorklogDetailDisclosureOpen(el, open){
|
||||
if(!el||!el.classList) return;
|
||||
el.classList.toggle('open', !!open);
|
||||
if(el.matches&&el.matches('.tool-group[data-tool-worklog-tool-group="1"],.tool-worklog-tool-group')){
|
||||
el.classList.toggle('tool-worklog-tool-group-collapsed', !open);
|
||||
const summary=el.querySelector('.tool-group-head,.tool-worklog-tool-group-head');
|
||||
if(summary) summary.setAttribute('aria-expanded', String(!!open));
|
||||
}
|
||||
}
|
||||
function _worklogDetailDisclosureKeyForElement(el, counts){
|
||||
const base=_worklogDetailBaseKey(el);
|
||||
if(!base) return '';
|
||||
const idx=counts[base]||0;
|
||||
counts[base]=idx+1;
|
||||
return `${base}#${idx}`;
|
||||
}
|
||||
function _captureWorklogDetailDisclosureState(root){
|
||||
const state=new Map();
|
||||
if(!root||!root.querySelectorAll) return state;
|
||||
const counts=Object.create(null);
|
||||
root.querySelectorAll(_worklogDetailDisclosureSelector).forEach(el=>{
|
||||
const key=_worklogDetailDisclosureKeyForElement(el, counts);
|
||||
if(key) state.set(key, _worklogDetailDisclosureIsOpen(el));
|
||||
});
|
||||
return state;
|
||||
}
|
||||
function _restoreWorklogDetailDisclosureState(root, state){
|
||||
if(!root||!root.querySelectorAll||!state||!state.size) return;
|
||||
const counts=Object.create(null);
|
||||
root.querySelectorAll(_worklogDetailDisclosureSelector).forEach(el=>{
|
||||
const key=_worklogDetailDisclosureKeyForElement(el, counts);
|
||||
if(!key||!state.has(key)) return;
|
||||
_setWorklogDetailDisclosureOpen(el, state.get(key));
|
||||
});
|
||||
}
|
||||
function _thinkingCardHtml(text, open){
|
||||
const clean=_sanitizeThinkingDisplayText(text);
|
||||
const copyBtn=`<button class="thinking-copy-btn" onclick="event.stopPropagation();_copyThinkingText(this)" title="${t('copy')}" aria-label="${t('copy')}">${li('copy',12)}</button>`;
|
||||
@@ -6572,10 +6669,11 @@ function _thinkingCardHtml(text, open){
|
||||
function isSimplifiedToolCalling(){
|
||||
return window._simplifiedToolCalling!==false;
|
||||
}
|
||||
function _thinkingActivityNode(text, open){
|
||||
function _thinkingActivityNode(text, open, disclosureKey){
|
||||
const row=document.createElement('div');
|
||||
row.className='agent-activity-thinking';
|
||||
row.setAttribute('data-worklog-thinking-card','1');
|
||||
if(disclosureKey) row.setAttribute('data-thinking-key', String(disclosureKey));
|
||||
row.innerHTML=_thinkingCardHtml(text, open);
|
||||
_renderThinkingInto(row,text);
|
||||
return row;
|
||||
@@ -6796,6 +6894,16 @@ function _toolIdentity(tc){
|
||||
String(tc.snippet||tc.preview||'').slice(0,160),
|
||||
].join('|');
|
||||
}
|
||||
function _toolDisclosureIdentity(tc){
|
||||
if(!tc) return '';
|
||||
const tid=tc.tid||tc.id||tc.tool_call_id||tc.tool_use_id||tc.call_id||'';
|
||||
if(tid) return `id:${tid}`;
|
||||
const stable=[
|
||||
tc.assistant_msg_idx!==undefined?`a:${tc.assistant_msg_idx}`:'',
|
||||
tc.name||'tool',
|
||||
].join('\x1f');
|
||||
return stable.trim()?`derived:${_worklogDetailHashKey(stable)}`:'';
|
||||
}
|
||||
function _filterNewWorklogTools(cards, seenTools){
|
||||
const out=[];
|
||||
for(const tc of Array.from(cards||[]).filter(Boolean)){
|
||||
@@ -6823,8 +6931,9 @@ function _appendWorklogStep(group, anchor, cards, thinkingText, opts){
|
||||
}
|
||||
if(thinkingText){
|
||||
const thinkingKey=(opts&&opts.thinkingKey)||`reason:${String(thinkingText).trim()}`;
|
||||
const thinkingDisclosureKey=(opts&&opts.thinkingDisclosureKey)||thinkingKey;
|
||||
if(!seenReasons||!seenReasons.has(thinkingKey)){
|
||||
const thinking=_thinkingActivityNode(thinkingText, false);
|
||||
const thinking=_thinkingActivityNode(thinkingText, false, thinkingDisclosureKey);
|
||||
if(thinking){
|
||||
list.appendChild(thinking);
|
||||
wroteProse=true;
|
||||
@@ -8039,6 +8148,7 @@ function renderMessages(options){
|
||||
return;
|
||||
}
|
||||
}
|
||||
const worklogDetailDisclosureState=_captureWorklogDetailDisclosureState(inner);
|
||||
|
||||
const compressionState=(()=>{
|
||||
let compressionState=_compressionStateForCurrentSession();
|
||||
@@ -8845,6 +8955,7 @@ function renderMessages(options){
|
||||
live:false,
|
||||
includeAnchorReason:!!includeAnchorReason&&!!anchorReasonHtml,
|
||||
thinkingKey:thinkingText?`thinking:${_normalizeThinkingEchoCompare(thinkingText)}`:'',
|
||||
thinkingDisclosureKey:thinkingText?`thinking:${entry.key}`:'',
|
||||
seenReasons:state.seenReasons,
|
||||
seenTools:state.seenTools,
|
||||
});
|
||||
@@ -8853,6 +8964,7 @@ function renderMessages(options){
|
||||
_syncToolCallGroupSummary(state.group);
|
||||
});
|
||||
}
|
||||
_restoreWorklogDetailDisclosureState(inner, worklogDetailDisclosureState);
|
||||
// Render per-turn duration and optional token usage on assistant messages.
|
||||
// Duration stays visible even when token usage is disabled, because it answers
|
||||
// the basic "how long did that turn take?" UX question. Only walk rendered
|
||||
@@ -9339,6 +9451,13 @@ function _syncToolRowsContainer(tools, isLiveWorklog){
|
||||
const group=document.createElement('div');
|
||||
group.className='tool-group'+(shouldOpen?' open':' tool-worklog-tool-group-collapsed');
|
||||
group.setAttribute('data-tool-worklog-tool-group','1');
|
||||
let groupKey='group';
|
||||
if(tools.parentElement){
|
||||
const steps=Array.from(tools.parentElement.children).filter(child=>child.classList&&child.classList.contains('wl-step-tools')&&child.getAttribute('data-worklog-tools')==='1');
|
||||
const stepIdx=steps.indexOf(tools);
|
||||
if(stepIdx>=0) groupKey=`step:${stepIdx}`;
|
||||
}
|
||||
group.setAttribute('data-tool-group-disclosure-key',groupKey);
|
||||
const summary=hasRunning?'Running':_toolWorklogSummary(rows,{live:isLiveWorklog, toolCount:rows.length});
|
||||
group.innerHTML=`<button type="button" class="tool-group-head tool-worklog-tool-group-head" aria-expanded="${shouldOpen?'true':'false'}" onclick="_toggleToolWorklogGroup(this)"><span class="tg-sum tool-worklog-tool-group-label">${esc(summary)}</span><span class="tool-call-group-chevron tg-caret">${li('chevron-right',12)}</span></button><div class="tool-group-body tool-worklog-tool-group-body"><div class="tg-rows tool-worklog-tool-group-rows"></div></div>`;
|
||||
const body=group.querySelector('.tg-rows');
|
||||
@@ -9450,6 +9569,8 @@ function buildToolCard(tc){
|
||||
row.dataset.toolDone=String(tc&&tc.done!==false);
|
||||
row.dataset.toolError=String(!!(tc&&tc.is_error));
|
||||
row.dataset.toolActionLabel=typeof _toolActionLabelText==='function'?_toolActionLabelText(tc):_toolDisplayName(tc);
|
||||
const disclosureKey=typeof _toolDisclosureIdentity==='function'?_toolDisclosureIdentity(tc):'';
|
||||
if(disclosureKey) row.setAttribute('data-tool-disclosure-key', disclosureKey);
|
||||
const icon=toolIcon(tc.name);
|
||||
const hasDetail=(tc.snippet&&tc.snippet!==tc.preview)||(tc.args&&Object.keys(tc.args).length>0);
|
||||
let displaySnippet='';
|
||||
@@ -10616,7 +10737,7 @@ function appendThinking(text='', options){
|
||||
if(list){
|
||||
let row=list.querySelector(`.agent-activity-thinking[data-live-thinking="1"][data-live-thinking-key="${CSS.escape(thinkingKey)}"]`);
|
||||
if(!row){
|
||||
row=_thinkingActivityNode(clean, false);
|
||||
row=_thinkingActivityNode(clean, false, thinkingKey);
|
||||
row.setAttribute('data-live-thinking','1');
|
||||
row.setAttribute('data-live-thinking-key',thinkingKey);
|
||||
if(segmentSeq) row.setAttribute('data-live-segment-seq',segmentSeq);
|
||||
|
||||
@@ -24,7 +24,7 @@ def test_settlement_loop_does_not_inline_thinking_only_messages():
|
||||
assert "!cards.length&&assistantThinking.has(aIdx)" not in UI_JS, (
|
||||
"Thinking-only messages must not use the old inline early-continue path"
|
||||
)
|
||||
assert "_thinkingActivityNode(thinkingText, false)" in UI_JS, (
|
||||
assert "_thinkingActivityNode(thinkingText, false, thinkingDisclosureKey)" in UI_JS, (
|
||||
"settled reasoning should render as a collapsed Worklog Thinking Card"
|
||||
)
|
||||
|
||||
|
||||
@@ -86,7 +86,7 @@ def test_settled_thinking_renders_through_worklog_item_path():
|
||||
assert "_appendWorklogStep(state.group, anchorRow, cards, thinkingText" in body, (
|
||||
"Settled Thinking should render through the #3401 Worklog item path."
|
||||
)
|
||||
assert "_thinkingActivityNode(thinkingText, false)" in UI_JS, (
|
||||
assert "_thinkingActivityNode(thinkingText, false, thinkingDisclosureKey)" in UI_JS, (
|
||||
"Thinking should remain a dedicated Worklog Thinking Card node."
|
||||
)
|
||||
assert "data-worklog-thinking-card" in UI_JS, (
|
||||
|
||||
@@ -53,7 +53,7 @@ def test_per_segment_tool_activity_does_not_include_run_metadata_rows():
|
||||
assert "Tool finished: ${toolName}" not in UI_JS
|
||||
assert "Running tool: ${toolName}" not in UI_JS
|
||||
assert "_worklogReasonNodeFromText(thinkingText" not in UI_JS
|
||||
assert "_thinkingActivityNode(clean, false)" in UI_JS
|
||||
assert "_thinkingActivityNode(clean, false, thinkingKey)" in UI_JS
|
||||
assert "data-live-thinking-key" in UI_JS
|
||||
|
||||
|
||||
|
||||
@@ -917,7 +917,7 @@ def test_ui_js_does_not_hide_anchor_segments_that_contain_thinking(cleanup_test_
|
||||
"provider reasoning metadata should feed the Worklog Thinking Card after exact duplicate suppression"
|
||||
assert "data-worklog-thinking-card" in src, \
|
||||
"Thinking Cards should be explicit Worklog items, not tool cards"
|
||||
assert "_thinkingActivityNode(thinkingText, false)" in src, \
|
||||
assert "_thinkingActivityNode(thinkingText, false, thinkingDisclosureKey)" in src, \
|
||||
"settled reasoning should render as a collapsed Worklog Thinking Card"
|
||||
|
||||
|
||||
|
||||
@@ -233,6 +233,122 @@ class TestToolCallGroupingStatic:
|
||||
"When tools are present, thinking is expected and should not be repeated in the label."
|
||||
)
|
||||
|
||||
def test_render_rebuild_preserves_worklog_detail_disclosure_click_state(self):
|
||||
render_fn = _function_body(UI_JS, "renderMessages")
|
||||
capture_fn = _function_body(UI_JS, "_captureWorklogDetailDisclosureState")
|
||||
restore_fn = _function_body(UI_JS, "_restoreWorklogDetailDisclosureState")
|
||||
apply_fn = _function_body(UI_JS, "_setWorklogDetailDisclosureOpen")
|
||||
capture_pos = render_fn.index("const worklogDetailDisclosureState=_captureWorklogDetailDisclosureState(inner);")
|
||||
cache_pos = render_fn.index("if(sid&&sid!==_sessionHtmlCacheSid&&!INFLIGHT[sid]&&!hasTransientTranscriptUi)")
|
||||
cache_return_pos = render_fn.index("return;", cache_pos)
|
||||
wipe_pos = render_fn.index("inner.innerHTML='';")
|
||||
restore_pos = render_fn.index("_restoreWorklogDetailDisclosureState(inner, worklogDetailDisclosureState);")
|
||||
fail_safe_pos = render_fn.find("Fail-safe invariant (#3875)")
|
||||
assert cache_pos < cache_return_pos < capture_pos, (
|
||||
"renderMessages() should not traverse the previous session DOM when "
|
||||
"the HTML-cache fast path can return early."
|
||||
)
|
||||
assert capture_pos < wipe_pos, (
|
||||
"renderMessages() must capture manual Worklog detail open/closed state "
|
||||
"before wiping msgInner for a rebuild."
|
||||
)
|
||||
assert restore_pos > wipe_pos, (
|
||||
"renderMessages() must restore manual Worklog detail state after the "
|
||||
"new Thinking/Tool DOM has been rebuilt."
|
||||
)
|
||||
assert fail_safe_pos == -1 or restore_pos < fail_safe_pos, (
|
||||
"The blank-turn fail-safe must still be allowed to expand otherwise "
|
||||
"invisible Worklog content after manual detail state is restored."
|
||||
)
|
||||
assert "_worklogDetailDisclosureSelector" in capture_fn, (
|
||||
"The rebuild-state capture should use the shared Worklog detail selector."
|
||||
)
|
||||
selector_match = re.search(r"const _worklogDetailDisclosureSelector='([^']+)'", UI_JS)
|
||||
assert selector_match, "Shared Worklog detail selector is missing."
|
||||
selector = selector_match.group(1)
|
||||
assert ".thinking-card" in selector and ".tool-card" in selector, (
|
||||
"The rebuild-state capture must cover both Thinking cards and Tool cards."
|
||||
)
|
||||
assert "data-tool-worklog-tool-group" in selector, (
|
||||
"The rebuild-state capture must cover multi-tool Worklog detail groups."
|
||||
)
|
||||
assert "_setWorklogDetailDisclosureOpen" in restore_fn, (
|
||||
"Restoration should use the shared disclosure-state applier."
|
||||
)
|
||||
assert "tool-worklog-tool-group-collapsed" in apply_fn and "aria-expanded" in apply_fn, (
|
||||
"Restoring multi-tool groups must sync both CSS state and accessibility state."
|
||||
)
|
||||
|
||||
def test_worklog_detail_keys_stay_stable_while_streaming_content_grows(self):
|
||||
key_fn = _function_body(UI_JS, "_worklogDetailBaseKey")
|
||||
append_thinking_fn = _function_body(UI_JS, "appendThinking")
|
||||
append_step_fn = _function_body(UI_JS, "_appendWorklogStep")
|
||||
build_tool_fn = _function_body(UI_JS, "buildToolCard")
|
||||
sync_tools_fn = _function_body(UI_JS, "_syncToolRowsContainer")
|
||||
|
||||
thinking_branch = re.search(
|
||||
r"if\(el\.classList\.contains\('thinking-card'\)\)\{(?P<body>.*?)\n \}\n if\(el\.classList\.contains\('tool-card'\)\)",
|
||||
key_fn,
|
||||
re.S,
|
||||
)
|
||||
assert thinking_branch, "Thinking-card disclosure-key branch is missing."
|
||||
thinking_body = thinking_branch.group("body")
|
||||
assert "data-thinking-key" in thinking_body and "data-live-thinking-key" in thinking_body, (
|
||||
"Thinking-card disclosure keys must prefer render-time stable row keys."
|
||||
)
|
||||
assert ".thinking-card-body pre" not in thinking_body and "textContent" not in thinking_body, (
|
||||
"Thinking-card disclosure keys must not depend on streaming body text."
|
||||
)
|
||||
assert "_thinkingActivityNode(clean, false, thinkingKey)" in append_thinking_fn, (
|
||||
"Live streaming Thinking rows must stamp the stable thinking key at creation time."
|
||||
)
|
||||
assert "_thinkingActivityNode(thinkingText, false, thinkingDisclosureKey)" in append_step_fn, (
|
||||
"Settled Worklog Thinking rows must stamp the stable thinking key at creation time."
|
||||
)
|
||||
assert "thinkingDisclosureKey:thinkingText?`thinking:${entry.key}`:''" in _function_body(UI_JS, "renderMessages"), (
|
||||
"Settled Worklog Thinking keys should come from activity coordinates, not text."
|
||||
)
|
||||
|
||||
tool_branch = re.search(
|
||||
r"if\(el\.classList\.contains\('tool-card'\)\)\{(?P<body>.*?)\n \}\n if\(el\.matches&&el\.matches\('\.tool-group",
|
||||
key_fn,
|
||||
re.S,
|
||||
)
|
||||
assert tool_branch, "Tool-card disclosure-key branch is missing."
|
||||
tool_body = tool_branch.group("body")
|
||||
assert "data-tool-disclosure-key" in tool_body, (
|
||||
"Tool-card disclosure keys must prefer a stable render-time tool key."
|
||||
)
|
||||
assert ".tool-card-preview" not in tool_body, (
|
||||
"Tool-card disclosure keys must not depend on result preview text."
|
||||
)
|
||||
assert "_toolDisclosureIdentity(tc)" in build_tool_fn, (
|
||||
"buildToolCard() must stamp a stable disclosure key on each tool row."
|
||||
)
|
||||
assert "tc.snippet" not in _function_body(UI_JS, "_toolDisclosureIdentity"), (
|
||||
"Derived tool disclosure keys must not include changing result snippets."
|
||||
)
|
||||
assert "tc.args" not in _function_body(UI_JS, "_toolDisclosureIdentity"), (
|
||||
"Derived tool disclosure keys must not include streaming tool arguments."
|
||||
)
|
||||
|
||||
group_branch = re.search(
|
||||
r"if\(el\.matches&&el\.matches\('\.tool-group\[data-tool-worklog-tool-group=\"1\"\],\.tool-worklog-tool-group'\)\)\{(?P<body>.*?)\n \}\n return '';",
|
||||
key_fn,
|
||||
re.S,
|
||||
)
|
||||
assert group_branch, "Multi-tool Worklog group disclosure-key branch is missing."
|
||||
group_body = group_branch.group("body")
|
||||
assert "data-tool-group-disclosure-key" in group_body, (
|
||||
"Multi-tool Worklog groups must prefer a stable render-time group key."
|
||||
)
|
||||
assert "_worklogDetailTextKey" not in group_body and "textContent" not in group_body, (
|
||||
"Multi-tool Worklog group disclosure keys must not depend on changing summary text."
|
||||
)
|
||||
assert "data-tool-group-disclosure-key" in sync_tools_fn and "stepIdx" in sync_tools_fn, (
|
||||
"Grouped Worklog tool rows must stamp a stable per-step disclosure key."
|
||||
)
|
||||
|
||||
def test_live_tool_cards_use_grouping_only_when_simplified(self):
|
||||
live_fn = _function_body(UI_JS, "appendLiveToolCard")
|
||||
settled_fn = _function_body(UI_JS, "renderMessages")
|
||||
@@ -455,8 +571,11 @@ class TestToolCallGroupingStatic:
|
||||
)
|
||||
render_min = re.sub(r"\s+", "", render_fn)
|
||||
assert "thinkingKey:thinkingText?`thinking:${_normalizeThinkingEchoCompare(thinkingText)}`:''" in render_min, (
|
||||
"Settled Worklog should key Thinking Cards by normalized content so exact duplicate "
|
||||
"Thinking from sibling messages does not render twice."
|
||||
"Settled Worklog should keep normalized-content Thinking dedupe so sibling messages do not duplicate cards."
|
||||
)
|
||||
assert "thinkingDisclosureKey:thinkingText?`thinking:${entry.key}`:''" in render_min, (
|
||||
"Settled Worklog should separately key disclosure state by stable activity coordinates "
|
||||
"so streaming text growth does not reset manual collapse state."
|
||||
)
|
||||
assert "_appendWorklogStep" in render_fn, (
|
||||
"Visible assistant anchors, Thinking Cards, and tools should still build the compact Worklog disclosure."
|
||||
@@ -475,7 +594,7 @@ class TestToolCallGroupingStatic:
|
||||
assert "_worklogReasonNodeFromText(thinkingText" not in live_thinking_fn, (
|
||||
"Provider reasoning should not render as live Worklog process prose."
|
||||
)
|
||||
assert "_thinkingActivityNode(clean, false)" in live_thinking_fn and "data-live-thinking" in live_thinking_fn, (
|
||||
assert "_thinkingActivityNode(clean, false, thinkingKey)" in live_thinking_fn and "data-live-thinking" in live_thinking_fn, (
|
||||
"Live provider thinking should render as a collapsed Worklog Thinking Card."
|
||||
)
|
||||
assert "ensureLiveWorklogContainer" in live_thinking_fn, (
|
||||
|
||||
Reference in new issue
Block a user