aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAdam Soltys <[email protected]>2026-09-26 23:21:27 -0700
committerAdam Soltys <[email protected]>2026-09-26 23:21:27 -0700
commitb9226f695c827a4b16719fd0c752801f4e768e6c (patch)
tree85b42fa9e7e135063af1fbfdfb4f0eabb5c0879a
parentdbf755968827b0d75f5e30d35a6c90da3cdc1aa2 (diff)
downloadcoinosv3-b9226f695c827a4b16719fd0c752801f4e768e6c.tar.gz
coinosv3-b9226f695c827a4b16719fd0c752801f4e768e6c.zip
Keyed morph: drop stale nodes before placing survivors, so a repaint moves nothing.
With stale nodes still in front, every kept row's position was off by the stale count and each one was pulled out and re-inserted — a remove and an add per row per repaint. The feed's window evicting rows at the top did this to every mounted row on every scroll step; a moved <video> leaves the document and comes back, restarting its loading (one clip was fetched 61 times in 40 seconds), and images were refetched. Measured with a MutationObserver on the live tab: 160 of 179 row additions during a 20-second scroll were the same key going out and back in. Co-Authored-By: Claude Fable 5.1 <[email protected]> Claude-Session: https://claude.ai/code/session_01VhUKyXKtsJ67QAFfb7QR9D
-rw-r--r--src/app.js12
1 files changed, 12 insertions, 0 deletions
diff --git a/src/app.js b/src/app.js
index 8a96334..e507776 100644
--- a/src/app.js
+++ b/src/app.js
@@ -271,6 +271,18 @@ const allKeyed = (nodes) => nodes.length > 0
function morphKeyed(parent, cur, next) {
const byKey = new Map();
for (const n of cur) if (n.nodeType === 1 && n.hasAttribute('data-key')) byKey.set(n.getAttribute('data-key'), n);
+ // Drop what the new render no longer has BEFORE placing the rest: with
+ // stale nodes still in front, every survivor's position was off by the
+ // stale count and each one was pulled out and re-inserted — a move per row
+ // per repaint (the feed's window evicting rows at the top did this to all
+ // the rows below on every scroll), and a moved <video> is taken out of
+ // the document and back, which restarts its loading.
+ const wanted = new Set(next.map((b) => b.getAttribute('data-key')));
+ for (const n of cur) {
+ if (n.parentNode !== parent) continue;
+ const k = n.nodeType === 1 ? n.getAttribute('data-key') : null;
+ if (k == null || !wanted.has(k)) n.remove();
+ }
const kept = new Set();
for (let i = 0; i < next.length; i++) {
const b = next[i];