diff --git a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip29RelayGroups/GroupScope.kt b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip29RelayGroups/GroupScope.kt index fd88ddf8b0..cb349e3efd 100644 --- a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip29RelayGroups/GroupScope.kt +++ b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip29RelayGroups/GroupScope.kt @@ -20,6 +20,7 @@ */ package com.vitorpamplona.quartz.nip29RelayGroups +import com.vitorpamplona.quartz.buzz.stream.StreamMessageV2Event import com.vitorpamplona.quartz.nip01Core.core.Event import com.vitorpamplona.quartz.nip01Core.core.TagArray import com.vitorpamplona.quartz.nip01Core.core.TagArrayBuilder @@ -69,6 +70,9 @@ fun Event.isGroupScoped(): Boolean = groupId() != null * to my group message is group-scoped too, so without this distinction it would be * picked as the group's "last message" and render as a bogus, unopenable row on the * Messages tab. Content kinds: 9 ([ChatEvent]), 1068 ([PollEvent]), 11 - * ([ThreadEvent]), 1111 ([CommentEvent]). + * ([ThreadEvent]), 1111 ([CommentEvent]), and 40002 ([StreamMessageV2Event]) — the + * Buzz dialect's stream-channel chat message, which is `h`-scoped and attaches to the + * same channel as a kind-9, so it must count as a room's newest message too (otherwise + * a Buzz channel's Messages-list preview and unread dot never reflect its real chat). */ -fun Event.isGroupChatContent(): Boolean = this is ChatEvent || this is PollEvent || this is ThreadEvent || this is CommentEvent +fun Event.isGroupChatContent(): Boolean = this is ChatEvent || this is PollEvent || this is ThreadEvent || this is CommentEvent || this is StreamMessageV2Event diff --git a/quartz/src/commonTest/kotlin/com/vitorpamplona/quartz/nip29RelayGroups/Nip29ArmadaInteropTest.kt b/quartz/src/commonTest/kotlin/com/vitorpamplona/quartz/nip29RelayGroups/Nip29ArmadaInteropTest.kt index 33703c3ae3..90daa6bd0b 100644 --- a/quartz/src/commonTest/kotlin/com/vitorpamplona/quartz/nip29RelayGroups/Nip29ArmadaInteropTest.kt +++ b/quartz/src/commonTest/kotlin/com/vitorpamplona/quartz/nip29RelayGroups/Nip29ArmadaInteropTest.kt @@ -20,6 +20,7 @@ */ package com.vitorpamplona.quartz.nip29RelayGroups +import com.vitorpamplona.quartz.buzz.stream.StreamMessageV2Event import com.vitorpamplona.quartz.nip01Core.core.Event import com.vitorpamplona.quartz.nip01Core.hints.EventHintBundle import com.vitorpamplona.quartz.nip22Comments.CommentEvent @@ -253,6 +254,26 @@ class Nip29ArmadaInteropTest { assertEquals(gid, react.groupId()) } + @Test + fun buzzStreamMessageCountsAsGroupChatContent() { + // A kind-9 chat message is group content. + val kind9 = parse(ChatEvent.KIND, arrayOf(arrayOf("h", gid)), content = "gm", pubKey = alice) + assertTrue(kind9.isGroupChatContent()) + + // A Buzz stream-channel chat message (kind 40002) is `h`-scoped and attaches to the same + // RelayGroupChannel as a kind-9, so it must count as a room's newest message too — otherwise + // a Buzz channel's Messages-list preview and unread dot never reflect its real chat. + val buzz = parse(StreamMessageV2Event.KIND, arrayOf(arrayOf("h", gid)), content = "hi from buzz", pubKey = alice) + assertTrue(buzz is StreamMessageV2Event) + assertEquals(gid, buzz.groupId()) + assertTrue(buzz.isGroupChatContent()) + + // A reaction is group-scoped but NOT content — it must never become a room's last message. + val react = parse(ReactionEvent.KIND, arrayOf(arrayOf("h", gid), arrayOf("e", "ee".repeat(32))), content = "🔥", pubKey = alice) + assertTrue(react.isGroupScoped()) + assertFalse(react.isGroupChatContent()) + } + @Test fun ungroupedEventHasNoGroupId() { val e = parse(ChatEvent.KIND, arrayOf(arrayOf("t", "hi")), content = "hi", pubKey = alice)