Fix blank notifications on app startup

The notifications stream used streamIndefinitely(), discarding EOSE signals. This caused a race condition where:

1. NotificationsView.onAppear fires and calls flush().
2. flush() finds an empty queue (events haven’t arrived yet).
3. Events arrive and queue up in incoming_events.
4. No second flush happens, so notifications stay blank.

Fix: Switch to advancedStream() and handle .ndbEose to flush queued notifications once the local database finishes loading. This mirrors how the Home timeline handles initial data loading.

The ndbEose handler also disables queuing (set_should_queue(false)), so any events arriving after the initial load display immediately.

When the Notifications tab becomes visible, disable queuing so any events arriving afterward insert immediately into the displayed list.

This defensive measure complements the ndbEose flush in HomeModel. Together, they provide belt-and-suspenders protection:

1. ndbEose flush (primary): Triggers when local DB finishes loading, flushing queued events and disabling queuing.
2. onAppear disable (safety net): If the user navigates to notifications before ndbEose fires, this ensures new events aren’t queued forever.

Whichever fires first wins - both set should_queue=false, and the second call is a harmless no-op.

Closes: https://github.com/damus-io/damus/issues/3399
Changelog-Fixed: Fixed an issue where notifications view would occasionally appear blank when the app started.
Signed-off-by: alltheseas <64376233+alltheseas@users.noreply.github.com>
This commit is contained in:
alltheseas
2025-12-10 17:26:18 -06:00
committed by GitHub
parent f5e5da25eb
commit 674d4683c3
2 changed files with 28 additions and 2 deletions

View File

@@ -198,6 +198,12 @@ struct NotificationsView: View {
}
.onAppear {
let _ = notifications.flush(state)
// Disable queuing once the tab is visible. This ensures any events
// arriving after onAppear insert immediately rather than being queued
// indefinitely. Acts as a safety net alongside the ndbEose flush in
// HomeModel - whichever fires first will disable queuing.
notifications.set_should_queue(false)
}
}
}

View File

@@ -527,8 +527,28 @@ class HomeModel: ContactsDelegate, ObservableObject {
self.notificationsHandlerTask?.cancel()
self.notificationsHandlerTask = Task {
for await event in damus_state.nostrNetwork.reader.streamIndefinitely(filters: notifications_filters) {
await event.justUseACopy({ await process_event(ev: $0, context: .notifications) })
// Use advancedStream (not streamIndefinitely) so we receive EOSE signals.
// This lets us flush queued notifications once the local database finishes loading,
// fixing the race condition where onAppear fires before events arrive.
for await item in damus_state.nostrNetwork.reader.advancedStream(
filters: notifications_filters,
streamMode: .ndbAndNetworkParallel(optimizeNetworkFilter: true)
) {
switch item {
case .event(let lender):
await lender.justUseACopy({ await process_event(ev: $0, context: .notifications) })
case .ndbEose:
// Local database finished loading. Flush any queued notifications
// and disable queuing so subsequent events display immediately.
await MainActor.run {
self.notifications.flush(damus_state)
self.notifications.set_should_queue(false)
}
case .eose, .networkEose:
break
}
}
}
self.generalHandlerTask?.cancel()