gate-fix #5466: stamp anchor attrs on incremental node + teardown on all terminal paths

Codex gate findings (2 SILENT, both fixed):
- ui.js: incremental prose node now flows through the shared row-decoration
  block (data-anchor-scene-row/-row-id/-row-role/-source-event-type) instead of
  returning early, so live incremental rows keep the identity attrs the scene
  reconciler matches on.
- messages.js: _cancelThrottledSnapshotTimer() + _clearAnchorProseIncrementalNode()
  now run on apperror, cancel, _handleStreamError, and _restoreSettledSession
  terminal paths (previously only fallback/done/stream_end) — closes the
  snapshot-timer/anchor-cache/window-global teardown gap.
- test: extend test_messages_js_stream_perf_cleanup_lifecycle to pin teardown on
  all four additional terminal paths.

CORE equivalence (Codex flagged smd != renderMd): reconciled — Opus's 1498-frame
harness against real smd.min.js proves incremental-smd == whole-smd byte-identical;
live-smd-vs-renderMd difference is the already-shipped fidelity model (main live
body streams smd, settles renderMd), and the incremental path is gated on !settled
so settled DOM still comes from renderMd. Not a regression.
This commit is contained in:
nesquena-hermes
2026-07-03 21:24:08 +00:00
parent 819f1a1e7b
commit eaf7d1e9ab
3 changed files with 44 additions and 6 deletions
+8
View File
@@ -5510,6 +5510,8 @@ function attachLiveStream(activeSid, streamId, uploaded=[], options={}){
_clearStreamEndRecovery();
_terminalStateReached=true;
if(_persistTimer){clearTimeout(_persistTimer);_persistTimer=null;}
_cancelThrottledSnapshotTimer();
_clearAnchorProseIncrementalNode();
_streamFinalized=true;
_cancelAnimationFramePendingStreamRender();
_streamFadeCleanupReduceMotionListener();
@@ -5711,6 +5713,8 @@ function attachLiveStream(activeSid, streamId, uploaded=[], options={}){
_clearStreamEndRecovery();
_terminalStateReached=true;
if(_persistTimer){clearTimeout(_persistTimer);_persistTimer=null;}
_cancelThrottledSnapshotTimer();
_clearAnchorProseIncrementalNode();
_streamFinalized=true;
_cancelAnimationFramePendingStreamRender();
_streamFadeCleanupReduceMotionListener();
@@ -5861,6 +5865,8 @@ function attachLiveStream(activeSid, streamId, uploaded=[], options={}){
if(!session) return returnStatus?'missing':false;
if(session.active_stream_id||session.pending_user_message) return returnStatus?'active':false;
if(_persistTimer){clearTimeout(_persistTimer);_persistTimer=null;}
_cancelThrottledSnapshotTimer();
_clearAnchorProseIncrementalNode();
_streamFinalized=true;
_cancelAnimationFramePendingStreamRender();
_streamFadeCleanupReduceMotionListener();
@@ -5955,6 +5961,8 @@ function attachLiveStream(activeSid, streamId, uploaded=[], options={}){
// Opus review Q1: mirror done/apperror/cancel finalization so any pending rAF
// cannot fire after renderMessages() has settled the DOM with the error message.
if(_persistTimer){clearTimeout(_persistTimer);_persistTimer=null;}
_cancelThrottledSnapshotTimer();
_clearAnchorProseIncrementalNode();
_streamFinalized=true;
_cancelAnimationFramePendingStreamRender();
_streamFadeCleanupReduceMotionListener();
+12 -6
View File
@@ -10538,13 +10538,19 @@ function _anchorSceneNodeForRow(row, opts){
const proseKey=row.local_id||row.row_id||'';
if(!settled && proseKey && typeof window.__anchorProseIncrementalNode==='function'){
const inc=window.__anchorProseIncrementalNode(proseKey,text);
if(inc) return inc;
// Route the incremental node through the shared row-decoration block below
// (data-anchor-scene-row / -row-id / -row-role / -source-event-type) instead
// of returning early — otherwise live incremental prose rows lose the
// identity attributes the scene reconciler matches on. (Codex gate #5466)
if(inc){ node=inc; }
}
if(!node){
node=document.createElement('div');
node.className='assistant-segment';
node.setAttribute('data-anchor-scene-prose','1');
node.dataset.rawText=text;
node.innerHTML=`<div class="msg-body">${renderMd?renderMd(text):esc(text)}</div>`;
}
node=document.createElement('div');
node.className='assistant-segment';
node.setAttribute('data-anchor-scene-prose','1');
node.dataset.rawText=text;
node.innerHTML=`<div class="msg-body">${renderMd?renderMd(text):esc(text)}</div>`;
}else if(row.role==='thinking'){
if(window._showThinking===false) return null;
const text=String(row.text||row.thinking&&row.thinking.text||'').trim();
+24
View File
@@ -1062,6 +1062,30 @@ def test_messages_js_stream_perf_cleanup_lifecycle(cleanup_test_sessions):
assert "_cancelThrottledSnapshotTimer();" in done_body
assert "_clearAnchorProseIncrementalNode();" in done_body
# #5466 Codex gate: the snapshot/anchor cleanup must run on EVERY terminal
# path, not just fallback/done/stream_end — otherwise a timer/cache/global
# survives the turn on apperror, cancel, stream-error, and the settled-session
# recovery path (the PR's own stated teardown invariant).
def _terminal_body(anchor, end_marker):
a = src.find(anchor)
assert a >= 0, f"missing terminal handler: {anchor}"
b = src.find(end_marker, a)
assert b > a, f"could not bound terminal handler: {anchor}"
return src[a:b]
apperror_body = _terminal_body("source.addEventListener('apperror'", "_streamFadeCleanupReduceMotionListener();")
assert "_cancelThrottledSnapshotTimer();" in apperror_body and "_clearAnchorProseIncrementalNode();" in apperror_body, \
"apperror terminal handler must tear down the snapshot timer + anchor prose cache"
cancel_body = _terminal_body("source.addEventListener('cancel'", "_streamFadeCleanupReduceMotionListener();")
assert "_cancelThrottledSnapshotTimer();" in cancel_body and "_clearAnchorProseIncrementalNode();" in cancel_body, \
"cancel terminal handler must tear down the snapshot timer + anchor prose cache"
stream_error_body = _terminal_body("function _handleStreamError(source)", "_streamFadeCleanupReduceMotionListener();")
assert "_cancelThrottledSnapshotTimer();" in stream_error_body and "_clearAnchorProseIncrementalNode();" in stream_error_body, \
"_handleStreamError must tear down the snapshot timer + anchor prose cache"
restore_body = _terminal_body("async function _restoreSettledSession(source", "_cancelAnimationFramePendingStreamRender();")
assert "_cancelThrottledSnapshotTimer();" in restore_body and "_clearAnchorProseIncrementalNode();" in restore_body, \
"_restoreSettledSession terminal recovery must tear down the snapshot timer + anchor prose cache"
def test_messages_js_finalizes_thinking_card_before_tool_card(cleanup_test_sessions):
"""R19e: later reasoning after a tool call must render in a fresh Worklog