Merge pull request #3586 from vitorpamplona/claude/nip29-message-pinning-elzck9

NIP-29 message pinning: UI bar, moderation events, and relay group integration
This commit is contained in:
Vitor Pamplona
2026-07-15 19:56:53 -04:00
committed by GitHub
18 changed files with 701 additions and 22 deletions
@@ -31,6 +31,7 @@ import com.vitorpamplona.quartz.nip29RelayGroups.GroupId
import com.vitorpamplona.quartz.nip29RelayGroups.metadata.GroupAdminsEvent
import com.vitorpamplona.quartz.nip29RelayGroups.metadata.GroupMembersEvent
import com.vitorpamplona.quartz.nip29RelayGroups.metadata.GroupMetadataEvent
import com.vitorpamplona.quartz.nip29RelayGroups.metadata.GroupPinnedEvent
import com.vitorpamplona.quartz.nip29RelayGroups.tags.GroupAdminTag
import com.vitorpamplona.quartz.utils.cache.LargeCache
import kotlinx.coroutines.flow.MutableStateFlow
@@ -70,6 +71,14 @@ class RelayGroupChannel(
private set
private var adminsUpdatedAt: Long = 0
/**
* Relay-signed pinned message ids (kind 39005), in the relay's display order.
* Pinning replaces the whole list, so this is always the full current set.
*/
var pinnedEventIds: List<HexKey> = emptyList()
private set
private var pinnedUpdatedAt: Long = 0
/**
* Members admins, recomputed only when a roster event lands. [memberCount] and the discovery
* feed read this per note / per recomposition, so caching it avoids rebuilding the set each read.
@@ -158,6 +167,17 @@ class RelayGroupChannel(
updateChannelInfo()
}
fun updatePinned(event: GroupPinnedEvent) {
// Only newer lists supersede; equal-or-older is dropped so a duplicate
// arrival isn't reprocessed (no redundant emit).
if (event.createdAt <= pinnedUpdatedAt) return
pinnedEventIds = event.pinnedEventIds()
pinnedUpdatedAt = event.createdAt
updateChannelInfo()
}
fun isPinned(eventId: HexKey): Boolean = eventId in pinnedEventIds
/**
* The shareable NIP-19 `naddr` coordinate for this group's metadata (kind
* 39000, authored by the relay's own key, with the host relay as a hint), or