Fix feed OOM: cap follow feed at 30, WebKit software rendering, dedup notification fetches

- followNotes capped at 30 (was 80) — following feed was rendering 2.7x more
  notes than global, causing 4GB+ spike on media-heavy follow content
- fetchFollowFeed limit 80→30 to match
- WEBKIT_FORCE_SOFTWARE_RENDERING=1 replaces WEBKIT_DISABLE_COMPOSITING_MODE=1
  (compositing mode killed Wayland path → blank window on Hyprland)
- HardwareAccelerationPolicy::Never → OnDemand (Never also caused blank screen)
- set_enable_page_cache(false) — SPA never navigates, bfcache is pure waste
- Removed duplicate fetchNotifications calls on login (was firing 3x in 8s)
- First notification poll delayed 8s→90s to avoid competing with feed load
- Result: login 3600MB→453MB, following feed crash→737MB, plateau at ~950MB
This commit is contained in:
Jure
2026-04-16 11:43:48 +02:00
parent 0894389fe0
commit 5fe3554579
7 changed files with 30 additions and 29 deletions
+1 -1
View File
@@ -89,7 +89,7 @@ export function Feed() {
try {
await ensureConnected();
const events = await diagWrapFetch("follow_fetch", () => fetchFollowFeed(follows));
setFollowNotes(events);
setFollowNotes(events.slice(0, 30));
const prev = useFeedStore.getState().lastUpdated;
useFeedStore.setState({ lastUpdated: { ...prev, following: Date.now() } });
} finally {
+1 -1
View File
@@ -71,7 +71,7 @@ export const NoteCard = memo(function NoteCard({ event, focused, onReplyInThread
<article
ref={cardRef}
data-note-id={event.id}
className={`border-b border-border px-4 py-3 hover:bg-bg-hover transition-colors cursor-pointer group/card [content-visibility:auto] [contain-intrinsic-size:auto_120px]${focused ? " bg-accent/10 border-l-2 border-l-accent" : ""}`}
className={`border-b border-border px-4 py-3 hover:bg-bg-hover transition-colors cursor-pointer group/card${focused ? " bg-accent/10 border-l-2 border-l-accent" : ""}`}
onClick={(e) => {
// Don't navigate if clicking on interactive elements
const target = e.target as HTMLElement;
+1 -1
View File
@@ -20,7 +20,7 @@ export async function fetchMediaFeed(limit: number = 500): Promise<NDKEvent[]> {
return Array.from(events).sort((a, b) => (b.created_at ?? 0) - (a.created_at ?? 0));
}
export async function fetchFollowFeed(pubkeys: string[], limit = 80): Promise<NDKEvent[]> {
export async function fetchFollowFeed(pubkeys: string[], limit = 30): Promise<NDKEvent[]> {
if (pubkeys.length === 0) return [];
const instance = getNDK();
const since = Math.floor(Date.now() / 1000) - 24 * 3600; // last 24h for follows
+10 -13
View File
@@ -44,9 +44,9 @@ async function pollOnce(pubkey: string) {
const name = await getProfileName(e.pubkey);
notifyMention(name, e.content?.slice(0, 120) || "mentioned you").catch(() => {});
}
// Only refresh the full store when there's actually something new to show
useNotificationsStore.getState().fetchNotifications(pubkey).catch(() => {});
}
// Also update the notifications store
useNotificationsStore.getState().fetchNotifications(pubkey).catch(() => {});
} catch { /* non-critical */ }
// Zaps
@@ -107,18 +107,15 @@ export function startNotificationPoller(pubkey: string) {
// Instant: load cached notifications from DB (no flicker)
useNotificationsStore.getState().loadFromDb(pubkey);
// Then connect to relays and fetch new data in background
(async () => {
try {
const connected = await ensureConnected();
debug.log("notif:poller ensureConnected →", connected);
} catch { /* continue anyway */ }
debug.log("notif:poller initial fetch for", pubkey.slice(0, 8));
useNotificationsStore.getState().fetchNotifications(pubkey).catch(() => {});
})();
// The full fetchNotifications() sweep is already called by the login flow
// (loginWithNsec / loginWithPubkey / restoreSession) before this function runs.
// Starting it again here would fire two concurrent 7-day sweeps on every login.
// We only need the incremental pollOnce() loop from here on.
// Run first full poll after a longer delay (give relays more time)
setTimeout(() => pollOnce(pubkey).catch(() => {}), 8000);
// Delay the first poll to give the app and relays time to fully initialize.
// 8s was too aggressive — it would fire a heavy fetchNotifications during the
// initial feed load, contributing to the login memory spike.
setTimeout(() => pollOnce(pubkey).catch(() => {}), 90_000);
intervalId = setInterval(() => pollOnce(pubkey).catch(() => {}), POLL_INTERVAL);
}