- {error && !isFollowing && !isTrending && (
-
- {error}
-
- )}
+ {/* Error + new-notes banners — pinned above the scroll area so they never
+ offset the virtualizer's coordinate space (its spacer must start at scrollTop 0) */}
+ {error && !isFollowing && !isTrending && (
+
+ {error}
+
+ )}
+ {tab === "global" && pendingNotes.length > 0 && (
+
+ {pendingNotes.length} new {pendingNotes.length === 1 ? "note" : "notes"} — click to load
+
+ )}
+
+ {/* Feed (virtualized scroll container) */}
+
{isLoading && filteredNotes.length === 0 && (
)}
@@ -264,22 +304,27 @@ export function Feed() {
)}
- {/* New notes banner — only shown on global tab */}
- {tab === "global" && pendingNotes.length > 0 && (
-
- {pendingNotes.length} new {pendingNotes.length === 1 ? "note" : "notes"} — click to load
-
- )}
-
- {filteredNotes.map((event, index) =>
- event.kind === 30023 ? (
-
- ) : (
-
- )
+ {/* Virtualized list — only the visible window of cards stays in the DOM */}
+ {filteredNotes.length > 0 && (
+
+ {virtualizer.getVirtualItems().map((vi) => {
+ const event = filteredNotes[vi.index];
+ return (
+
+ {event.kind === 30023 ? (
+
+ ) : (
+
+ )}
+
+ );
+ })}
+
)}