Commit Graph
2 Commits
Author SHA1 Message Date
Thireus ☠ 2d48f4c306 ui: give the renderMessages probes the block helper
Four upstream tests read renderMessages out of ui.js and run a slice of it against a fake segment object. Since the message block goes in through _insertSegmentBlock, three of them stopped at "ReferenceError: _insertSegmentBlock is not defined" and the fourth still pinned the old seg.insertAdjacentHTML line, which took twelve of the fifteen test shards red on this branch and nowhere else.

test_status_command_card.py defines the helper in its own harness, one line that does what the real helper does without a browser. test_compaction_current_summary_fallback.py and test_anchor_fallback_ownership.py evaluate the real helper from ui.js alongside the functions they already evaluate; their DOM shims return no template content, so it takes its insertAdjacentHTML fallback exactly as before. test_sprint49.py pins the new call, and its point is unchanged: the footer still rides in the same block as the body.

A new case in tests/test_issue2051_duplicate_assistant_answer.py pins why the helper exists, which answers a review note: the block is parsed into a template and the nodes are MOVED, so a repeated call adds nothing, and insertAdjacentHTML survives only as the fallback. All three insertions go through it.

tests/test_anchor_fallback_ownership.py, tests/test_compaction_current_summary_fallback.py, tests/test_sprint49.py, tests/test_status_command_card.py: 34 passed. The three #2051 files: 12 passed.
2026-09-21 19:28:49 +01:00
Thireus ☠ 8d3c405d68 fix(ui): stop rendering a settled assistant answer twice (#2051)
Two independent paths could put a second copy of an already-settled assistant answer on screen; the stored data always held exactly one row.

1. Dead live-turn node. The assistant row is persisted a few ms before the stream's terminal event clears S.activeStreamId, so the #6948 preserve guard still treats a finished #liveAssistantTurn node as live. Both live-turn insertion paths (renderMessages()'s re-attach and restoreLiveTurnHtmlForSession()) have one branch that APPENDS a turn the settled rebuild did not produce; the leftover node keeps id=liveAssistantTurn, is re-preserved on every later render, and the duplicate survives until a reload.

2. Settled activity scene. A process_prose row whose text IS the final answer is suppressed by the transparent-stream renderer but rebuilt as a second .assistant-segment by the compact-worklog row builder. The existing near-equality matcher never fires for short answers because of its absolute shorter>=80 floor (the reported reproduction is a 35-char greeting).

Dropping a live node is now an OWNERSHIP proof, never a text comparison: the transcript must end with a settled assistant message carrying this stream's id (_anchor_stream_id / scene identity), and the node must contain nothing the settled rebuild could not have produced (no unpersisted tool card, reasoning row, or second live segment). Unknown node shapes fail closed and keep the turn, so a genuinely live turn - including one whose text matches an earlier answer - is never dropped. The settled-scene half compares raw markdown at the single chokepoint both settled renderers share.

Adds tests/test_issue2051_duplicate_assistant_answer.py: four tests that fail on the pristine tree and pass with this change, including a wiring test that fails loudly if either append branch loses its guard.

Fixes #2051
2026-09-21 10:38:39 +01:00