mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2026-10-06 03:38:23 +00:00
fix(notifications): remove the 7-day since floor that emptied the tab
The notifications query pinned `since` to `oneWeekAgo()` whenever it had no EOSE timestamp. Combined with two other facts that produced a deadlock: - the EOSE `since` map is in-memory only (`SincePerRelayMap = MutableMap<..>`), so EVERY cold start re-pinned the window to 7 days; and - the backward-paging escape hatch (`lastNoteCreatedAtIfFilled()`) only arms once the feed holds a FULL page (`notes.size >= localFilter.limit()`). The feed could not fill a page because the query only asked for a week, and the query could not widen because the feed never filled. Any account whose last inbound mention was older than 7 days saw a near-empty Notifications tab forever — including a fresh install of a long-established account. Worse, the floor was applied to `filterSummaryNotificationsToPubkey` (kinds 1/7/6/9735 — the overwhelming bulk of notifications), which was not even wired to the paging fallback that the secondary per-key kinds received. Home is the precedent and does not do this: `filterHomePostsByAllFollows` passes `since ?: boundary`, i.e. plain null on a cold start, relying on the relay-side `limit` to bound the response. Notifications was the outlier. These filters are `#p`-scoped to the user's own key and carry a `limit` (2000/500/200/20), so an all-time query is one index scan returning at most `limit` events newest-first — same cost, strictly more useful. Measured on the test account before the fix: 163 events p-tagging it exist on relays and are fetchable (100 kind-1 and 100 kind-7, both hitting the query cap), 77 of which clear the follow gate — yet the tab rendered ~3, because the account's most recent inbound activity was a month old. After the fix the tab scrolls back through Feb 2025. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
4ee0416a90
commit
97b861dd5e
+23
-4
@@ -28,7 +28,6 @@ import com.vitorpamplona.quartz.nip01Core.relay.client.INostrClient
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.client.pool.RelayBasedFilter
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.client.subscriptions.Subscription
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.RelayUrlNormalizer
|
||||
import com.vitorpamplona.quartz.utils.TimeUtils
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.FlowPreview
|
||||
import kotlinx.coroutines.Job
|
||||
@@ -51,17 +50,35 @@ class AccountNotificationsEoseFromInboxRelaysManager(
|
||||
key: AccountQueryState,
|
||||
since: SincePerRelayMap?,
|
||||
): List<RelayBasedFilter> {
|
||||
// Backward-paging boundary: once the feed has filled a page, ask for everything older than
|
||||
// its oldest card. It stays null until then — see the note on the missing week floor below,
|
||||
// which is what let it stay null forever on a quiet inbox.
|
||||
val pagingBoundary = key.feedContentStates.notifications.lastNoteCreatedAtIfFilled()
|
||||
|
||||
val inbox =
|
||||
key.account.notificationRelays.flow.value.flatMap {
|
||||
// No `since` floor on the first fetch. These filters are scoped by `#p` to my own
|
||||
// key and carry a relay-side `limit`, so an all-time query costs one index scan and
|
||||
// returns at most `limit` events, newest first — exactly what Home does (it passes
|
||||
// `since ?: boundary`, i.e. null on a cold start).
|
||||
//
|
||||
// This used to fall back to `oneWeekAgo()`, which silently emptied the tab for
|
||||
// anyone whose last mention was older than a week: EOSE `since` is in-memory only,
|
||||
// so EVERY cold start re-pinned the window to 7 days, and the paging boundary above
|
||||
// could never rescue it — it only arms once the feed holds a full page, and the feed
|
||||
// could not fill because the query only ever asked for a week. A fresh install of an
|
||||
// established account hit the same deadlock.
|
||||
val notificationSince = since?.get(it)?.time ?: pagingBoundary
|
||||
|
||||
filterSummaryNotificationsToPubkey(
|
||||
relay = it,
|
||||
pubkey = user(key).pubkeyHex,
|
||||
since = since?.get(it)?.time ?: TimeUtils.oneWeekAgo(),
|
||||
since = notificationSince,
|
||||
) +
|
||||
filterNotificationsToPubkey(
|
||||
relay = it,
|
||||
pubkey = user(key).pubkeyHex,
|
||||
since = since?.get(it)?.time ?: key.feedContentStates.notifications.lastNoteCreatedAtIfFilled() ?: TimeUtils.oneWeekAgo(),
|
||||
since = notificationSince,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -76,7 +93,9 @@ class AccountNotificationsEoseFromInboxRelaysManager(
|
||||
relay = relay,
|
||||
pubkey = user(key).pubkeyHex,
|
||||
groupIds = groupIds.distinct(),
|
||||
since = since?.get(relay)?.time ?: TimeUtils.oneWeekAgo(),
|
||||
// Same reasoning as the inbox filters above: `#p` + `#h` + `limit = 200`
|
||||
// already bound this, so a week floor only hides older group activity.
|
||||
since = since?.get(relay)?.time ?: pagingBoundary,
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
+5
-3
@@ -27,7 +27,6 @@ import com.vitorpamplona.amethyst.service.relays.SincePerRelayMap
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.client.INostrClient
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.client.pool.RelayBasedFilter
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.client.subscriptions.Subscription
|
||||
import com.vitorpamplona.quartz.utils.TimeUtils
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.FlowPreview
|
||||
import kotlinx.coroutines.Job
|
||||
@@ -51,8 +50,11 @@ class AccountNotificationsEoseFromRandomRelaysManager(
|
||||
key: AccountQueryState,
|
||||
since: SincePerRelayMap?,
|
||||
): List<RelayBasedFilter> {
|
||||
// only loads this after the feed is built
|
||||
val defaultSince = key.feedContentStates.notifications.lastNoteCreatedAtIfFilled() ?: TimeUtils.oneWeekAgo()
|
||||
// only loads this after the feed is built, so it stays null on a quiet inbox. No week floor
|
||||
// behind it: this probe is `#p`-scoped to me with `limit = 20`, so relays answer with the 20
|
||||
// newest either way — the floor only ever hid notifications older than a week, and since the
|
||||
// boundary above needs a full page to arm, a quiet inbox could never page past it.
|
||||
val defaultSince = key.feedContentStates.notifications.lastNoteCreatedAtIfFilled()
|
||||
return (key.account.followsPerRelay.value.keys - key.account.notificationRelays.flow.value).flatMap {
|
||||
val since = since?.get(it)?.time ?: defaultSince
|
||||
filterJustTheLatestNotificationsToPubkeyFromRandomRelays(it, user(key).pubkeyHex, since)
|
||||
|
||||
Reference in New Issue
Block a user