From 5b54eb88a516da4e6570c232d8c9fa947dad8048 Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Tue, 14 Jul 2026 21:51:47 -0400 Subject: [PATCH] fix(concord): notify on reactions to my Concord messages MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit NotificationFeedFilter derived `isConcord` from the note's own gatherers (is it in a joined ConcordChannel), but LocalCache.consumeConcordRumor only attaches kind-9 messages and kind-1111 replies to the channel — never a kind-7 reaction. So a reaction's `isConcord` was always false, it didn't bypass the follow filter, and since a fellow member usually isn't a follow it was dropped in Curated/Selected mode. Recognize a reaction/repost as Concord through its TARGET instead: if `replyTo.lastOrNull()` is a message in a community I've joined, it bypasses the follow filter exactly like a reply. Relevance is still the existing p-tag gate, so only reactions that actually tag me notify (a well-formed NIP-25 kind-7 p-tags the reacted author, which is what our own ChannelChat.reaction writes). The "Messages in notifications" toggle now gates only Concord messages, not reactions — a like isn't a message, so it follows the same rule as any other reaction. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../dal/NotificationFeedFilter.kt | 26 +++++++++++++++---- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/notifications/dal/NotificationFeedFilter.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/notifications/dal/NotificationFeedFilter.kt index 5618fbcaf1..a75a45fc40 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/notifications/dal/NotificationFeedFilter.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/notifications/dal/NotificationFeedFilter.kt @@ -449,14 +449,30 @@ class NotificationFeedFilter( // gatherer reference from every account/community that ever touched them, so require the // community to be one THIS account has currently joined (mirrors the Marmot check above) — // otherwise a note from a prior account or a left community would leak onto Notifications. - val isConcord = - it.inGatherers?.any { g -> + fun Note?.inJoinedConcordCommunity() = + this?.inGatherers?.any { g -> g is ConcordChannel && account.concordSessions.sessionFor(g.channelId.communityId) != null } == true - // Concord is a messaging feature, so honor the same "Messages in notifications" toggle that - // silences DMs and Marmot groups above. - if (isConcord && !showMessages) return false + val isConcordMessage = it.inJoinedConcordCommunity() + + // A like/repost is NOT itself attached to the channel gatherer — only chat messages/replies are + // (see LocalCache.consumeConcordRumor) — so `inGatherers` never flags it as Concord, and its + // author (a fellow member) usually isn't a follow, so it falls through the follow filter and is + // dropped. Recognize it through its TARGET: a reaction/repost pointing at a message in a + // community I've joined is a Concord reaction, and bypasses the follow filter like a reply does. + // Relevance (does it target ME) is still enforced below by the p-tag gate — a well-formed kind-7 + // p-tags the reacted author (NIP-25), which is exactly what our own ChannelChat.reaction writes. + val isConcordReaction = + (noteEvent is ReactionEvent || noteEvent is RepostEvent || noteEvent is GenericRepostEvent) && + it.replyTo?.lastOrNull().inJoinedConcordCommunity() + + val isConcord = isConcordMessage || isConcordReaction + + // Concord CHAT (a message/reply) honors the "Messages in notifications" toggle that silences DMs + // and Marmot groups above. A reaction isn't a message — regular reactions ignore that toggle, so + // Concord reactions do too (only isConcordMessage is gated). + if (isConcordMessage && !showMessages) return false // Global keeps every event that p-tags the user; Selected (and the // follow/list modes) also applies the per-kind relevance heuristics.