diff --git a/static/ui.js b/static/ui.js
index ca6bdb565..6e75da2c3 100644
--- a/static/ui.js
+++ b/static/ui.js
@@ -453,6 +453,9 @@ const _recycleResetAttrs=[
'data-transparent-turn-toggle-bound',
'data-anchor-scene-live-owner',
'data-anchor-stream-id',
+ 'data-latest-assistant-response',
+ 'role',
+ 'aria-label',
// Defensive reset for legacy/restored shells that may still carry the fallback live-turn marker.
'data-live-assistant-turn',
];
@@ -8634,6 +8637,24 @@ function _createAssistantTurn(tsTitle='', tpsText=''){
row.innerHTML=`${_assistantRoleHtml(tsTitle, tpsText)}
`;
return row;
}
+function _setLatestAssistantTurnLandmark(turn, isLatest){
+ if(!turn) return;
+ const label='Latest Hermes response';
+ if(isLatest){
+ if(typeof document!=='undefined'){
+ document.querySelectorAll('.assistant-turn[data-latest-assistant-response="true"]').forEach(el=>{
+ if(el!==turn) _setLatestAssistantTurnLandmark(el, false);
+ });
+ }
+ turn.setAttribute('role','region');
+ turn.setAttribute('aria-label',label);
+ turn.dataset.latestAssistantResponse='true';
+ return;
+ }
+ if(turn.getAttribute('role')==='region') turn.removeAttribute('role');
+ if(turn.getAttribute('aria-label')===label) turn.removeAttribute('aria-label');
+ delete turn.dataset.latestAssistantResponse;
+}
function _assistantTurnBlocks(turn){
return turn?turn.querySelector('.assistant-turn-blocks'):null;
}
@@ -12342,6 +12363,13 @@ function renderMessages(options){
}catch(e){}
}
const transparentToolResultsByTid=_transparentModeActive?_collectToolResultSnippetsByTid(S.messages):{};
+ const latestRenderedAssistantRawIdx=(()=>{
+ for(let i=renderVisWithIdx.length-1;i>=0;i--){
+ const entry=renderVisWithIdx[i];
+ if(entry&&entry.m&&entry.m.role==='assistant'&&!entry.m._live) return entry.rawIdx;
+ }
+ return -1;
+ })();
// Windowed render loop replaces the legacy full loop:
// for(let vi=0;vi str:
+ start = source.find(f"function {name}(")
+ assert start != -1, f"{name}() not found"
+ end = source.find("\nfunction ", start + 1)
+ if end == -1:
+ end = len(source)
+ return source[start:end]
+
+
+def test_conversation_transcript_is_not_a_named_region():
+ match = re.search(r']*>', INDEX_HTML)
+ assert match, "#messages container not found"
+ tag = match.group(0)
+ assert 'role="region"' not in tag
+ assert 'Conversation transcript' not in tag
+ assert "tabindex" not in tag.lower()
+
+
+def test_latest_assistant_landmark_helper_is_named_but_not_focusable():
+ helper = _function_body(UI_JS, "_setLatestAssistantTurnLandmark")
+ assert "Latest Hermes response" in helper
+ assert "setAttribute('role','region')" in helper
+ assert "setAttribute('aria-label',label)" in helper
+ assert "data-latest-assistant-response" in helper
+ assert "tabindex" not in helper.lower()
+ assert "{" in render_messages
+ assert "if(entry&&entry.m&&entry.m.role==='assistant'&&!entry.m._live) return entry.rawIdx;" in render_messages
+ assert "_setLatestAssistantTurnLandmark(currentAssistantTurn, !m._live&&rawIdx===latestRenderedAssistantRawIdx);" in render_messages
+ assert "seg.setAttribute('role','region')" not in render_messages
+ assert "orderedSeg.setAttribute('role','region')" not in render_messages
+
+
+def test_live_assistant_turn_paths_do_not_own_final_response_landmark():
+ restore_live_turn = _function_body(UI_JS, "restoreLiveTurnHtmlForSession")
+ render_live_scene = _function_body(UI_JS, "renderLiveAnchorActivityScene")
+ append_thinking = _function_body(UI_JS, "appendThinking")
+
+ assert "_setLatestAssistantTurnLandmark(restored, true);" not in restore_live_turn
+ assert "_setLatestAssistantTurnLandmark(turn, true);" not in render_live_scene
+ assert "_setLatestAssistantTurnLandmark(turn, true);" not in append_thinking
diff --git a/tests/test_anchor_fallback_ownership.py b/tests/test_anchor_fallback_ownership.py
index 041fda845..4bec8d1f1 100644
--- a/tests/test_anchor_fallback_ownership.py
+++ b/tests/test_anchor_fallback_ownership.py
@@ -604,6 +604,7 @@ def test_render_messages_keeps_anchor_owned_turn_out_of_legacy_activity_rebuilds
return turn;
}}
function _assistantTurnBlocks(turn) {{ return turn ? turn.querySelector('.assistant-turn-blocks') : null; }}
+ function _setLatestAssistantTurnLandmark() {{}}
function _assistantRoleHtml() {{ return ''; }}
function _userMessageDomId(rawIdx) {{ return `user-${{rawIdx}}`; }}
function _messageSessionIndexForRawIdx(rawIdx) {{ return rawIdx; }}
diff --git a/tests/test_issue4793_dom_recycle_hardening.py b/tests/test_issue4793_dom_recycle_hardening.py
index 76e32b3d4..b413e4917 100644
--- a/tests/test_issue4793_dom_recycle_hardening.py
+++ b/tests/test_issue4793_dom_recycle_hardening.py
@@ -89,6 +89,8 @@ const S = { session: { session_id: 'sess-1' } };
const _assistantRoleHtml = (title, tps) => `role:${title}:${tps}`;
const isTpsDisplayEnabled = () => true;
const _formatTurnTps = value => `tps:${value}`;
+const latestRenderedAssistantRawIdx = rawIdx;
+function _setLatestAssistantTurnLandmark() {}
eval(recycleChunk);
console.log(JSON.stringify({
removedAttrs,
@@ -110,6 +112,9 @@ console.log(JSON.stringify({
"data-transparent-turn-toggle-bound",
"data-anchor-scene-live-owner",
"data-anchor-stream-id",
+ "data-latest-assistant-response",
+ "role",
+ "aria-label",
"data-live-assistant-turn",
}
assert set(result["resetAttrs"]) == expected_attrs
@@ -155,6 +160,8 @@ const S = { session: { session_id: 'sess-1' } };
const _assistantRoleHtml = (title, tps) => `role:${title}:${tps}`;
const isTpsDisplayEnabled = () => true;
const _formatTurnTps = value => `tps:${value}`;
+const latestRenderedAssistantRawIdx = rawIdx;
+function _setLatestAssistantTurnLandmark() {}
eval(recycleChunk);
console.log(JSON.stringify({
blocksInnerHTML: blocks.innerHTML,