From affc547b0e9d67714c9f69e5724fcfd42b336129 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 25 Jul 2026 00:38:34 +0000 Subject: [PATCH] fix(buzz): update Messages-list preview + unread dot for Buzz stream channels A Buzz stream-channel chat message is a kind-40002 StreamMessageV2Event, not a NIP-C7 kind-9 ChatEvent. It is `h`-scoped and attaches to the same RelayGroupChannel as a kind-9, but `isGroupChatContent()` only recognized ChatEvent/PollEvent/ThreadEvent/CommentEvent, so the Messages-list "newest message" logic (initial scan `newestChatNote` + the live additive `filterRelevantRelayGroupMessages`) and the unread-dot check all skipped it. Result: a Buzz channel's row never reflected its real chat and never updated live as new messages arrived. Include StreamMessageV2Event in `isGroupChatContent()` (the Buzz dialect of NIP-29), fixing the Messages preview and unread dot in one place. Adds a regression test asserting kind-40002 counts as group chat content while a group-scoped reaction does not. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_012dtx8Shek4sSAXmHjnNMJF --- .../quartz/nip29RelayGroups/GroupScope.kt | 8 +++++-- .../Nip29ArmadaInteropTest.kt | 21 +++++++++++++++++++ 2 files changed, 27 insertions(+), 2 deletions(-) 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)