* fix(#5839): post-process + restore disclosure on lazily-materialized worklog rows Gate (live Chromium) found the deferred-render path skipped two things the eager path does: (1) materialized Markdown rows bypassed postProcessRenderedMessages -> lost syntax highlight / copy button / mermaid / katex / trees; (2) detail-disclosure (open tool/thinking card + scroll) was lost across rebuilds because restore ran before deferred rows existed. _materializeDeferredWorklogRows now rAF-schedules _postProcessWithAnchorSuppression + re-applies stashed disclosure state; the rebuild restore pass stashes the captured state on still-deferred groups. +2 regression tests. * changelog: #5860 lazy-worklog frontend fix (#5839, #4633) --------- Co-authored-by: t <a@b>
105 lines
5.0 KiB
Python
105 lines
5.0 KiB
Python
"""Regression coverage for #5839: lazy settled-worklog DOM (compact mode).
|
|
|
|
Reasoning-heavy turns can carry 80+ activity rows per scene. The settled
|
|
compact-worklog render used to build EVERY row's DOM even when the group is
|
|
collapsed, so a long history balloons the DOM and a later synchronous layout
|
|
(e.g. opening a dropdown) tips the tab into a multi-GB freeze (#5839).
|
|
|
|
The fix defers row construction for a COLLAPSED settled worklog until first
|
|
expand, recovering the rows from the owning message after an HTML-cache restore
|
|
(where the JS-property stash is dropped). These are source-assertion tests in
|
|
the same style as the other anchor-scene UI tests (no JS engine needed).
|
|
"""
|
|
from pathlib import Path
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
UI_JS = (ROOT / "static" / "ui.js").read_text(encoding="utf-8")
|
|
|
|
|
|
def _function_body(src: str, name: str) -> str:
|
|
marker = f"function {name}"
|
|
start = src.index(marker)
|
|
brace = src.index("{", start)
|
|
depth = 0
|
|
for idx in range(brace, len(src)):
|
|
if src[idx] == "{":
|
|
depth += 1
|
|
elif src[idx] == "}":
|
|
depth -= 1
|
|
if depth == 0:
|
|
return src[brace + 1 : idx]
|
|
raise AssertionError(f"{name} body not found")
|
|
|
|
|
|
def test_settled_collapsed_worklog_defers_row_dom():
|
|
body = _function_body(UI_JS, "_renderSettledAnchorSceneForMessage")
|
|
# A collapsed settled worklog must stash rows + mark deferred + NOT build rows.
|
|
assert "group.classList.contains('tool-call-group-collapsed')" in body
|
|
assert "group._deferredWorklogRows=rows" in body
|
|
assert "data-worklog-rows-deferred" in body
|
|
# The expanded/keep-open path still renders eagerly.
|
|
assert "_renderAnchorSceneRowsIntoWorklog(group,rows,{settled:true})" in body
|
|
|
|
|
|
def test_toggle_materializes_deferred_rows_on_expand():
|
|
body = _function_body(UI_JS, "_toggleActivityGroup")
|
|
assert "if(!collapsed) _materializeDeferredWorklogRows(group);" in body
|
|
|
|
|
|
def test_materialize_helper_recovers_rows_after_cache_restore():
|
|
body = _function_body(UI_JS, "_materializeDeferredWorklogRows")
|
|
# Idempotent: gated on the deferred marker, cleared before building.
|
|
assert "data-worklog-rows-deferred" in body
|
|
# Falls back to message-recovery when the JS stash was dropped (innerHTML).
|
|
assert "_deferredWorklogRowsFromGroup(group)" in body
|
|
assert "_renderAnchorSceneRowsIntoWorklog(group,rows,{settled:true})" in body
|
|
|
|
|
|
def test_row_recovery_maps_group_to_message_scene():
|
|
body = _function_body(UI_JS, "_deferredWorklogRowsFromGroup")
|
|
# Recovers rawIdx from the disclosure key and rebuilds from S.messages.
|
|
assert "anchor-scene:" in body
|
|
assert "S.messages" in body
|
|
assert "_anchor_activity_scene" in body
|
|
assert "_anchorSceneRowsForRendering(scene,{settled:true})" in body
|
|
|
|
|
|
def test_cache_restore_rehydrates_deferred_worklogs():
|
|
# The HTML-cache fast-path restore must re-stash deferred rows so the first
|
|
# post-restore expand works (JS properties don't survive innerHTML).
|
|
assert "_rehydrateDeferredWorklogsFromCache(inner);" in UI_JS
|
|
body = _function_body(UI_JS, "_rehydrateDeferredWorklogsFromCache")
|
|
assert '[data-worklog-rows-deferred="1"]' in body
|
|
assert "_deferredWorklogRowsFromGroup(group)" in body
|
|
# If a group can't be recovered, drop the deferred marker (no dead chip).
|
|
assert "removeAttribute('data-worklog-rows-deferred')" in body
|
|
|
|
|
|
def test_blank_turn_reveal_materializes_deferred_rows():
|
|
# The "blank turn" safety reveal force-expands a collapsed worklog; it must
|
|
# also materialize deferred rows or the revealed worklog would be empty.
|
|
assert UI_JS.count("_materializeDeferredWorklogRows(group)") >= 3
|
|
|
|
|
|
def test_materialize_postprocesses_and_restores_disclosure():
|
|
"""#5860 gate fix: lazily-materialized rows must get the same post-processing
|
|
(syntax highlight / copy button / mermaid / katex / trees) as the eager path,
|
|
and any captured detail-disclosure state must be re-applied — otherwise
|
|
expanded rows render un-enhanced and an open tool card resets closed."""
|
|
body = _function_body(UI_JS, "_materializeDeferredWorklogRows")
|
|
# post-processing scheduled after materialization (matches eager rebuild paths)
|
|
assert "_postProcessWithAnchorSuppression(group)" in body
|
|
assert "requestAnimationFrame" in body
|
|
# disclosure state stashed at defer/rebuild time is re-applied after rows exist
|
|
assert "_deferredWorklogDisclosure" in body
|
|
assert "_restoreWorklogDetailDisclosureState(group" in body
|
|
|
|
|
|
def test_rebuild_stashes_disclosure_on_deferred_groups():
|
|
"""#5860 gate fix: on a transcript rebuild the disclosure-restore pass can't
|
|
reach deferred groups (no rows yet), so the captured state is stashed on each
|
|
still-deferred group for _materializeDeferredWorklogRows to apply on expand."""
|
|
assert "group._deferredWorklogDisclosure=worklogDetailDisclosureState;" in UI_JS
|
|
# stash happens right after the main restore pass over the rebuilt transcript
|
|
assert 'querySelectorAll(\'[data-worklog-rows-deferred="1"]\')' in UI_JS
|