fix: reduce transparent stream live row churn

This commit is contained in:
Stacey2911 2026-07-03 18:16:56 +10:00
parent 312d3faba4
commit badb93d472
2 changed files with 94 additions and 3 deletions

View file

@ -10945,6 +10945,7 @@ function _renderLiveAnchorActivitySceneTransparent(streamId, scene, opts){
});
const liveFooter=blocks.querySelector('#liveRunStatus');
let wrote=false;
const targetRenderedRows=[];
for(const row of rows){
const node=_anchorSceneTransparentNodeForRow(row,{
live:true,
@ -10960,10 +10961,35 @@ function _renderLiveAnchorActivitySceneTransparent(streamId, scene, opts){
: node;
if(existing) preserveByKey.delete(key);
if(!renderedNode) continue;
if(liveFooter&&liveFooter.parentElement===blocks) blocks.insertBefore(renderedNode,liveFooter);
else blocks.appendChild(renderedNode);
targetRenderedRows.push(renderedNode);
wrote=true;
}
const transparentLiveRowNextSibling=(node)=>{
if(!node) return null;
if(typeof node.nextSibling!=='undefined') return node.nextSibling;
const parent=node.parentElement||node.parentNode;
if(parent&&parent.children&&typeof parent.children.length==='number'){
const index=Array.prototype.indexOf.call(parent.children,node);
return index>=0 ? (parent.children[index+1]||null) : null;
}
return null;
};
const transparentLiveRowAlreadyPositioned=(node, expectedNextSibling)=>!!(
node &&
node.parentElement===blocks &&
transparentLiveRowNextSibling(node)===expectedNextSibling
);
let expectedNextSibling=(liveFooter&&liveFooter.parentElement===blocks) ? liveFooter : null;
for(let i=targetRenderedRows.length-1;i>=0;i--){
const renderedNode=targetRenderedRows[i];
if(transparentLiveRowAlreadyPositioned(renderedNode,expectedNextSibling)){
expectedNextSibling=renderedNode;
continue;
}
if(expectedNextSibling&&expectedNextSibling.parentElement===blocks) blocks.insertBefore(renderedNode,expectedNextSibling);
else blocks.appendChild(renderedNode);
expectedNextSibling=renderedNode;
}
preserveByKey.forEach(stale=>stale.remove());
if(wrote) _syncTransparentEventControls(turn);
if(typeof _moveLiveRunStatusToTurnEnd==='function') _moveLiveRunStatusToTurnEnd();
@ -11067,7 +11093,9 @@ function _refreshTransparentLiveRow(existing, node){
existing.setAttribute(name, value);
}
existing.className = node.className || '';
existing.innerHTML = node.innerHTML || '';
const newHtml = node.innerHTML || '';
const htmlChanged = existing.innerHTML !== newHtml;
if(htmlChanged) existing.innerHTML = newHtml;
_rehydrateTransparentLiveRow(existing, node, preservedState);
return existing;
}

View file

@ -70,6 +70,12 @@ class FakeElement {{
}}
get parentElement(){{ return this.parentNode; }}
get firstChild(){{ return this.children[0]||null; }}
get nextSibling(){{
if(!this.parentNode) return null;
const siblings = this.parentNode.children;
const idx = siblings.indexOf(this);
return idx >= 0 ? (siblings[idx + 1] || null) : null;
}}
get className(){{
return Array.from(this._classes).join(' ');
}}
@ -123,6 +129,7 @@ class FakeElement {{
}}
}}
appendChild(child){{
this.appendChildCount = (this.appendChildCount || 0) + 1;
if(child && child.parentNode) child.remove();
if(!child) return null;
child.parentNode = this;
@ -130,6 +137,7 @@ class FakeElement {{
return child;
}}
insertBefore(child, refNode){{
this.insertBeforeCount = (this.insertBeforeCount || 0) + 1;
if(child && child.parentNode) child.remove();
if(!child) return null;
const idx = this.children.indexOf(refNode);
@ -299,7 +307,11 @@ const keptAfterFirst = turn.querySelector('.transparent-event-row[data-anchor-ro
const staleAfterFirst = turn.querySelector('.transparent-event-row[data-anchor-row-id=\"row-stale\"]');
const firstFooter = turn.querySelector('#liveRunStatus');
turn.insertBeforeCount = 0;
turn.appendChildCount = 0;
const secondRender = _renderLiveAnchorActivitySceneTransparent('stream-1', secondScene, {{ sessionId:'session-1' }});
const secondInsertBeforeCount = turn.insertBeforeCount || 0;
const secondAppendChildCount = turn.appendChildCount || 0;
const keptAfterSecond = turn.querySelector('.transparent-event-row[data-anchor-row-id=\"row-kept\"]');
const staleAfterSecond = turn.querySelector('.transparent-event-row[data-anchor-row-id=\"row-stale\"]');
const newAfterSecond = turn.querySelector('.transparent-event-row[data-anchor-row-id=\"row-new\"]');
@ -314,6 +326,39 @@ const idxs = {{
stale: rows.findIndex((child) => child.getAttribute('data-anchor-row-id') === 'row-stale'),
}};
turn.insertBeforeCount = 0;
turn.appendChildCount = 0;
_renderLiveAnchorActivitySceneTransparent('stream-1', secondScene, {{ sessionId:'session-1' }});
const stableInsertBeforeCount = turn.insertBeforeCount || 0;
const stableAppendChildCount = turn.appendChildCount || 0;
const reorderedScene = {{
version:'activity_scene_v1',
activity_rows:[
{{ row_id:'row-new', role:'prose', source_event_type:'process_prose', text:'new row appears' }},
{{ row_id:'row-kept', role:'prose', source_event_type:'process_prose', text:'updated progress line' }},
],
}};
turn.insertBeforeCount = 0;
turn.appendChildCount = 0;
_renderLiveAnchorActivitySceneTransparent('stream-1', reorderedScene, {{ sessionId:'session-1' }});
const reorderInsertBeforeCount = turn.insertBeforeCount || 0;
const reorderedIds = turn.children
.filter((child) => child.classList.contains('transparent-event-row'))
.map((child) => child.getAttribute('data-anchor-row-id'));
firstFooter.remove();
turn.insertBefore(firstFooter, turn.children[0] || null);
turn.insertBeforeCount = 0;
turn.appendChildCount = 0;
_renderLiveAnchorActivitySceneTransparent('stream-1', reorderedScene, {{ sessionId:'session-1' }});
const footerRepairInsertBeforeCount = turn.insertBeforeCount || 0;
const footerRepairIdxs = {{
newRow: turn.children.indexOf(turn.querySelector('.transparent-event-row[data-anchor-row-id=\"row-new\"]')),
keptRow: turn.children.indexOf(turn.querySelector('.transparent-event-row[data-anchor-row-id=\"row-kept\"]')),
footer: turn.children.indexOf(firstFooter),
}};
_renderLiveAnchorActivitySceneTransparent('stream-1', thirdScene, {{ sessionId:'session-1' }});
const keylessRowsAfterThird = turn.querySelectorAll('.transparent-event-row[data-anchor-row-id=\"\"]');
_renderLiveAnchorActivitySceneTransparent('stream-1', fourthScene, {{ sessionId:'session-1' }});
@ -329,6 +374,14 @@ process.stdout.write(JSON.stringify({{
staleGone: staleAfterSecond === null,
staleAfterFirst: staleAfterFirst !== null,
idxs,
secondInsertBeforeCount,
secondAppendChildCount,
stableInsertBeforeCount,
stableAppendChildCount,
reorderInsertBeforeCount,
reorderedIds,
footerRepairInsertBeforeCount,
footerRepairIdxs,
hasNewRow: !!newAfterSecond,
newRowSession: newAfterSecond && newAfterSecond.getAttribute('data-session-id'),
keylessAfterThird: keylessRowsAfterThird.length,
@ -355,6 +408,16 @@ process.stdout.write(JSON.stringify({{
assert data["idxs"]["rowNew"] == 1
assert data["idxs"]["stale"] == -1
assert data["idxs"]["staleInVisibleRows"] == -1
assert data["secondInsertBeforeCount"] >= 1
assert data["secondAppendChildCount"] == 0
assert data["stableInsertBeforeCount"] == 0
assert data["stableAppendChildCount"] == 0
assert data["reorderInsertBeforeCount"] >= 1
assert data["reorderedIds"] == ["row-new", "row-kept"]
assert data["footerRepairInsertBeforeCount"] >= 1
assert data["footerRepairIdxs"]["newRow"] == 0
assert data["footerRepairIdxs"]["keptRow"] == 1
assert data["footerRepairIdxs"]["footer"] > data["footerRepairIdxs"]["keptRow"]
assert data["hasNewRow"] is True
assert data["newRowSession"] == "session-1"
assert data["keylessAfterThird"] == 1