fix(ui): preserve thinking detail scroll on rebuild

This commit is contained in:
Frank Song 2026-06-23 00:11:51 +08:00
parent c5bb1fcaa0
commit 87ab74d1b9
2 changed files with 42 additions and 2 deletions

View file

@ -8224,6 +8224,10 @@ function _worklogDetailBaseKey(el){
function _worklogDetailDisclosureIsOpen(el){
return !!(el&&el.classList&&el.classList.contains('open'));
}
function _worklogDetailScrollableBody(el){
if(!el||!el.querySelector) return null;
return el.querySelector('.thinking-card-body,.tool-card-detail');
}
function _setWorklogDetailDisclosureOpen(el, open){
if(!el||!el.classList) return;
el.classList.toggle('open', !!open);
@ -8252,7 +8256,12 @@ function _captureWorklogDetailDisclosureState(root){
const counts=Object.create(null);
root.querySelectorAll(_worklogDetailDisclosureSelector).forEach(el=>{
const key=_worklogDetailDisclosureKeyForElement(el, counts);
if(key) state.set(key, _worklogDetailDisclosureIsOpen(el));
if(!key) return;
const body=_worklogDetailScrollableBody(el);
state.set(key,{
open:_worklogDetailDisclosureIsOpen(el),
scrollTop:body?Math.max(0,Number(body.scrollTop)||0):0,
});
});
return state;
}
@ -8264,7 +8273,14 @@ function _restoreWorklogDetailDisclosureState(root, state){
root.querySelectorAll(_worklogDetailDisclosureSelector).forEach(el=>{
const key=_worklogDetailDisclosureKeyForElement(el, counts);
if(!key||!state.has(key)) return;
_setWorklogDetailDisclosureOpen(el, state.get(key));
const saved=state.get(key);
const open=(saved&&typeof saved==='object'&&'open' in saved)?saved.open:saved;
_setWorklogDetailDisclosureOpen(el, open);
const scrollTop=(saved&&typeof saved==='object')?Number(saved.scrollTop):0;
if(open&&Number.isFinite(scrollTop)&&scrollTop>0){
const body=_worklogDetailScrollableBody(el);
if(body) body.scrollTop=Math.min(scrollTop, Math.max(0, body.scrollHeight-body.clientHeight));
}
});
}
function _thinkingCardHtml(text, open){

View file

@ -279,6 +279,30 @@ class TestToolCallGroupingStatic:
"Restoring multi-tool groups must sync both CSS state and accessibility state."
)
def test_render_rebuild_preserves_nested_worklog_detail_scroll_position(self):
capture_fn = _function_body(UI_JS, "_captureWorklogDetailDisclosureState")
restore_fn = _function_body(UI_JS, "_restoreWorklogDetailDisclosureState")
body_fn = _function_body(UI_JS, "_worklogDetailScrollableBody")
assert ".thinking-card-body,.tool-card-detail" in body_fn, (
"Nested scroll preservation must cover Thinking bodies and Tool details."
)
assert "_worklogDetailScrollableBody(el)" in capture_fn, (
"The rebuild-state capture must inspect each detail's nested scroll container."
)
assert "scrollTop:body?Math.max(0,Number(body.scrollTop)||0):0" in capture_fn, (
"The captured Worklog detail state must retain the nested scrollTop value."
)
assert "const saved=state.get(key)" in restore_fn, (
"Restoration should read the structured state object for each detail."
)
assert "const open=(saved&&typeof saved==='object'&&'open' in saved)?saved.open:saved" in restore_fn, (
"Restoration must remain backward-compatible with legacy boolean snapshots."
)
assert "body.scrollTop=Math.min(scrollTop, Math.max(0, body.scrollHeight-body.clientHeight))" in restore_fn, (
"Restoration must reapply nested scrollTop after the rebuilt detail is opened."
)
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")