From 8e7d169198c40f7ca6954e992e504206c581c560 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 30 May 2026 20:59:19 +0000 Subject: [PATCH] feat: route follow hashtag-labels through each follow's outbox relays The kind-1985 label subscription for the hashtag feed now follows the NIP-65 outbox model: instead of querying the flat hashtag relay set for all labels and filtering to follows locally, it queries each follow's outbox relays for that follow's own label events (authors restricted per relay via account.followsPerRelay), tagged with the hashtag. This ensures a follow's labels are picked up even when they never reach the hashtag's own relays. https://claude.ai/code/session_019gc3FipVBcndF9fmqCCfVX --- .../hashtag/datasource/FilterHashtagLabels.kt | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/hashtag/datasource/FilterHashtagLabels.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/hashtag/datasource/FilterHashtagLabels.kt index 2d4bf16af0..cd0a703978 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/hashtag/datasource/FilterHashtagLabels.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/hashtag/datasource/FilterHashtagLabels.kt @@ -31,14 +31,16 @@ import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl import com.vitorpamplona.quartz.nip01Core.tags.hashtags.hashtagAlts import com.vitorpamplona.quartz.nip32Labeling.LabelEvent import com.vitorpamplona.quartz.utils.mapOfSet +import kotlin.math.min /** * Builds the relay filters that power the NIP-32 side of the hashtag feed: * - * 1. A subscription for kind 1985 label events that tag posts with this hashtag - * (the `l` tag under the `#t` namespace). We don't restrict authors at the relay - * — the feed restricts display to the user's follows locally — but we do bound it - * with a limit. + * 1. A subscription for kind 1985 label events that tag posts with this hashtag (the `l` + * tag under the `#t` namespace), routed through the NIP-65 outbox model: each follow's + * labels are requested from THAT follow's outbox relays, with the relay's filter + * restricted to the follows that actually write there. This is what guarantees we pick + * up a follow's labels even if they never reach the hashtag's own relays. * 2. Requests for the underlying posts a *followed* user has labeled but that aren't in * cache yet, so they can actually render in the feed. This rotates as new label * events arrive (the sub-assembler re-runs after each EOSE). @@ -51,15 +53,20 @@ fun filterHashtagLabels( ): List { val labelValues = hashtagAlts(hashtag).sorted() + // Outbox routing: relay -> the follows who publish to it. + val followsPerRelay = account.followsPerRelay.value val labelFilters = - relays.map { relay -> + followsPerRelay.mapNotNull { (relay, authors) -> + if (authors.isEmpty()) return@mapNotNull null + val authorList = authors.sorted() RelayBasedFilter( relay = relay, filter = Filter( kinds = listOf(LabelEvent.KIND), + authors = authorList, tags = mapOf("l" to labelValues), - limit = 200, + limit = min(authorList.size * 5, 500), since = since?.get(relay)?.time, ), )