Fix feed page dropped posts: out-of-order events and race condition
prependPostCard() now inserts posts in the correct sorted position within the existing DOM instead of falling back to a full renderFeed() rebuild when a post is older than the first visible post. This handles relays that deliver events out of order without wiping the feed. Also fixed a race condition in fetchFeedWindow: the fire-and-forget ndkFetchEvents callback was calling renderFeed() after bootstrap completed, which wiped prepended live posts and caused concurrent rebuild races. Now it only calls renderFeed() if the feed hasn't been rendered yet (renderedPostIds.size === 0). Same prependPostCard sorted-insert fix applied to post.html.
This commit is contained in:
@@ -488,21 +488,33 @@ import { initPostCards } from './js/post-interactions2.mjs';
|
||||
return false;
|
||||
}
|
||||
|
||||
// Only prepend if the post is newer than the first currently visible post.
|
||||
const firstChild = divFeed.firstElementChild;
|
||||
const firstPostId = firstChild ? firstChild.getAttribute('data-post-id') : null;
|
||||
const firstPost = firstPostId ? posts.find((p) => p.id === firstPostId) : null;
|
||||
const postCreatedAt = post.created_at || 0;
|
||||
|
||||
if (!firstPost || (post.created_at || 0) < (firstPost.created_at || 0)) {
|
||||
// Post is older than the first visible post (or unknown) — full rebuild for safety.
|
||||
renderFeed();
|
||||
return false;
|
||||
// Find the correct insertion point by scanning visible children's created_at.
|
||||
// Posts are displayed newest-first, so we insert before the first child
|
||||
// that is older than (or equal to) the new post.
|
||||
const postEl = cards.createCard(post, { currentPubkey, autoWire: false, autoplayVideo: autoplayVideosEnabled });
|
||||
let inserted = false;
|
||||
|
||||
for (const child of divFeed.children) {
|
||||
const childId = child.getAttribute('data-post-id');
|
||||
if (!childId) continue;
|
||||
const childPost = posts.find((p) => p.id === childId);
|
||||
if (!childPost) continue;
|
||||
const childCreatedAt = childPost.created_at || 0;
|
||||
if (postCreatedAt >= childCreatedAt) {
|
||||
divFeed.insertBefore(postEl, child);
|
||||
inserted = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const postEl = cards.createCard(post, { currentPubkey, autoWire: false, autoplayVideo: autoplayVideosEnabled });
|
||||
divFeed.insertBefore(postEl, divFeed.firstChild);
|
||||
renderedPostIds.add(post.id);
|
||||
if (!inserted) {
|
||||
// Post is older than all visible posts — append at the bottom.
|
||||
divFeed.appendChild(postEl);
|
||||
}
|
||||
|
||||
renderedPostIds.add(post.id);
|
||||
cards.wireInteractions([post.id], { currentPubkey });
|
||||
|
||||
// Trim DOM to displayCount.
|
||||
@@ -543,7 +555,11 @@ import { initPostCards } from './js/post-interactions2.mjs';
|
||||
(Array.isArray(fresh) ? fresh : []).forEach((evt1) => {
|
||||
upsertFeedPost(evt1);
|
||||
});
|
||||
if (posts.length > 0) {
|
||||
// Only do a full re-render if the feed hasn't been rendered yet.
|
||||
// If the feed is already rendered, the posts are in the array and
|
||||
// will appear when the user clicks "See More" or on next full render.
|
||||
// This prevents the relay fetch callback from wiping prepended live posts.
|
||||
if (posts.length > 0 && renderedPostIds.size === 0) {
|
||||
renderFeed();
|
||||
}
|
||||
}).catch(() => {});
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"VERSION": "v0.7.85",
|
||||
"VERSION_NUMBER": "0.7.85",
|
||||
"BUILD_DATE": "2026-07-01T11:59:39.440Z"
|
||||
"VERSION": "v0.7.86",
|
||||
"VERSION_NUMBER": "0.7.86",
|
||||
"BUILD_DATE": "2026-07-01T13:22:33.456Z"
|
||||
}
|
||||
|
||||
@@ -667,21 +667,33 @@ import { initPostCards } from './js/post-interactions2.mjs';
|
||||
return false;
|
||||
}
|
||||
|
||||
// Only prepend if the post is newer than the first currently visible post.
|
||||
const firstChild = divFeed.firstElementChild;
|
||||
const firstPostId = firstChild ? firstChild.getAttribute('data-post-id') : null;
|
||||
const firstPost = firstPostId ? posts.find((p) => p.id === firstPostId) : null;
|
||||
const postCreatedAt = post.created_at || 0;
|
||||
|
||||
if (!firstPost || (post.created_at || 0) < (firstPost.created_at || 0)) {
|
||||
// Post is older than the first visible post (or unknown) — full rebuild for safety.
|
||||
renderFeed();
|
||||
return false;
|
||||
// Find the correct insertion point by scanning visible children's created_at.
|
||||
// Posts are displayed newest-first, so we insert before the first child
|
||||
// that is older than (or equal to) the new post.
|
||||
const postEl = cards.createCard(post, { currentPubkey, autoWire: false, autoplayVideo: false });
|
||||
let inserted = false;
|
||||
|
||||
for (const child of divFeed.children) {
|
||||
const childId = child.getAttribute('data-post-id');
|
||||
if (!childId) continue;
|
||||
const childPost = posts.find((p) => p.id === childId);
|
||||
if (!childPost) continue;
|
||||
const childCreatedAt = childPost.created_at || 0;
|
||||
if (postCreatedAt >= childCreatedAt) {
|
||||
divFeed.insertBefore(postEl, child);
|
||||
inserted = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const postEl = cards.createCard(post, { currentPubkey, autoWire: false, autoplayVideo: false });
|
||||
divFeed.insertBefore(postEl, divFeed.firstChild);
|
||||
renderedPostIds.add(post.id);
|
||||
if (!inserted) {
|
||||
// Post is older than all visible posts — append at the bottom.
|
||||
divFeed.appendChild(postEl);
|
||||
}
|
||||
|
||||
renderedPostIds.add(post.id);
|
||||
cards.wireInteractions([post.id], { currentPubkey });
|
||||
|
||||
// Trim DOM to displayCount.
|
||||
|
||||
Reference in New Issue
Block a user