diff --git a/tests/test_anchor_fallback_ownership.py b/tests/test_anchor_fallback_ownership.py index 50c041e7..6e61dd2b 100644 --- a/tests/test_anchor_fallback_ownership.py +++ b/tests/test_anchor_fallback_ownership.py @@ -416,6 +416,10 @@ def test_render_messages_keeps_anchor_owned_turn_out_of_legacy_activity_rebuilds legacy_metadata_source = _function_source( _ui_js(), "_legacySettledFallbackHasToolMetadata" ) + # #2051: renderMessages() inserts a message block through this helper, so it is + # evaluated with it. The shim's createElement() returns no template `content`, so the + # helper takes its insertAdjacentHTML fallback here, exactly as before. + insert_block_source = _function_source(_ui_js(), "_insertSegmentBlock") script = textwrap.dedent( f""" class FakeClassList {{ @@ -702,6 +706,7 @@ def test_render_messages_keeps_anchor_owned_turn_out_of_legacy_activity_rebuilds eval({json.dumps(transparent_source)}); eval({json.dumps(legacy_metadata_source)}); + eval({json.dumps(insert_block_source)}); eval({json.dumps(render_source)}); const toolResult = {{ role: 'tool', tool_call_id: 'toolu_1', content: 'tool result' }}; diff --git a/tests/test_compaction_current_summary_fallback.py b/tests/test_compaction_current_summary_fallback.py index d0ec6aaf..276ac311 100644 --- a/tests/test_compaction_current_summary_fallback.py +++ b/tests/test_compaction_current_summary_fallback.py @@ -50,6 +50,10 @@ _REAL_FUNCTIONS = ( "_insertPreservedCompressionTaskFallback", "_pinCompactionCardAtTop", "_pinSettledCompressionReferenceAtTop", + # #2051: renderMessages() inserts a message block through this helper, so it has to be + # evaluated with it. The shim's createElement() returns no template `content`, so the + # helper takes its insertAdjacentHTML fallback here, exactly as before. + "_insertSegmentBlock", "renderMessages", ) diff --git a/tests/test_issue2051_duplicate_assistant_answer.py b/tests/test_issue2051_duplicate_assistant_answer.py index 6493bf16..1eb63ac0 100644 --- a/tests/test_issue2051_duplicate_assistant_answer.py +++ b/tests/test_issue2051_duplicate_assistant_answer.py @@ -336,3 +336,35 @@ def test_both_append_branches_are_gated_by_the_ownership_check(): "the segment-swap and whole-turn replace branches must stay unguarded — " "they replace a node instead of adding one and cannot duplicate" ) + + +def test_segment_block_insertion_moves_nodes_instead_of_reparsing_html(): + """Wiring: every message-block insertion in renderMessages() goes through the + helper, and the helper MOVES parsed nodes instead of re-parsing a string. + + Appending a DocumentFragment moves its children and empties it, so a DOM-API + wrapper that executes the call twice appends nothing the second time; an + insertAdjacentHTML wrapper that does the same parses and appends a second copy. + That difference is the whole fix, so it is pinned here rather than left to the + three call sites. + """ + helper = UI_JS[UI_JS.index("function _insertSegmentBlock("):] + helper = helper[:helper.index("\nfunction renderMessages")] + assert "createElement('template')" in helper, ( + "the block must be parsed into a