From 8d228db440bb564afef4e7d5e0a67b6848812afa Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 29 Apr 2026 22:13:36 +0000 Subject: [PATCH] fix(nests): preserve relays tag on participant updates + sync action bar with stage grid MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two related bugs the user hit while testing against nostrnests: 1. Approving a hand-raise made the Hands tab disappear (the host's local app saw the role grant) but the audience member did not appear on stage anywhere else. Cause: RoomParticipantActions.rebuild() rebuilt the kind-30312 without the original room's `relays` tag. The recently-added broadcast fan-out path (Account.computeRelayListToBroadcast) keys off `event.allRelayUrls()` for kind 30312, so a republished room event with no relays tag only reaches the host's outbox — not nostrnests's fixed five reads or the audience member subscribing on those reads. The audience member never sees their SPEAKER tag and stays absent from the participant list. Fix: copy `original.relays()` into the rebuild so every republish (approve, demote, kick → re-broadcast) keeps the relay routing that lets the room reach its full audience. 2. Tapping "Leave Stage" as the host emptied the StageGrid ("Waiting for speakers…") but the action bar still showed the Talk + Leave Stage cluster. Cause: two different definitions of "on stage" were in play. StageGrid uses ParticipantGrid (role + presence.onstage flag), so flipping onStageNow=false drops the host out. The action bar gated on the role-only `onStage: List` (the host's HOST tag never goes away), so the cluster stayed. Fix: derive isOnStageMe from participantGrid.onStage so both surfaces share the same "stepped off" semantics. The host who taps Leave Stage now drops to the audience-style mute toggle and can rejoin (kind-10312 onstage flips back to 1) without the controls lying about their state. https://claude.ai/code/session_016G7oP5BotPjUBgvMYrrrxh --- .../room/participants/RoomParticipantActions.kt | 9 +++++++++ .../loggedIn/nests/room/screen/NestFullScreen.kt | 12 +++++++++++- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/nests/room/participants/RoomParticipantActions.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/nests/room/participants/RoomParticipantActions.kt index 88311e720..bb00c422c 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/nests/room/participants/RoomParticipantActions.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/nests/room/participants/RoomParticipantActions.kt @@ -25,6 +25,7 @@ import com.vitorpamplona.quartz.nip53LiveActivities.meetingSpaces.MeetingSpaceEv import com.vitorpamplona.quartz.nip53LiveActivities.meetingSpaces.endpoint import com.vitorpamplona.quartz.nip53LiveActivities.meetingSpaces.image import com.vitorpamplona.quartz.nip53LiveActivities.meetingSpaces.participants +import com.vitorpamplona.quartz.nip53LiveActivities.meetingSpaces.relays import com.vitorpamplona.quartz.nip53LiveActivities.meetingSpaces.summary import com.vitorpamplona.quartz.nip53LiveActivities.meetingSpaces.tags.StatusTag import com.vitorpamplona.quartz.nip53LiveActivities.streaming.tags.ParticipantTag @@ -132,6 +133,14 @@ internal object RoomParticipantActions { original.endpoint()?.let { endpoint(it) } original.summary()?.takeIf { it.isNotBlank() }?.let { summary(it) } original.image()?.takeIf { it.isNotBlank() }?.let { image(it) } + // Preserve the room's `relays` tag — without it, the + // republished kind-30312 fans out only to the host's + // outbox, never reaching the room's own relay set + // (e.g. nostrnests's fixed five reads). The audience + // member would then never see their role grant and + // wouldn't appear on stage for any other participant + // listening on those relays. + original.relays().takeIf { it.isNotEmpty() }?.let { relays(it) } if (others.isNotEmpty()) participants(others) } } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/nests/room/screen/NestFullScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/nests/room/screen/NestFullScreen.kt index 1546caabd..8ed1ce968 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/nests/room/screen/NestFullScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/nests/room/screen/NestFullScreen.kt @@ -135,7 +135,6 @@ internal fun NestFullScreen( val isHost = accountViewModel.account.signer.pubKey == event.pubKey val myPubkey = accountViewModel.account.signer.pubKey - val isOnStageMe = remember(onStage, myPubkey) { onStage.any { it.pubKey == myPubkey } } val leaveScope = rememberCoroutineScope() val topBarContext = LocalContext.current @@ -152,6 +151,17 @@ internal fun NestFullScreen( presences = presences, ) } + // Tie the action bar's "am I on stage" gate to the same data + // source the StageGrid uses (role + presence.onstage flag), + // not just the kind-30312 role tag. Otherwise a host who taps + // "Leave Stage" flips presence to onstage=0 and disappears + // from the StageGrid, but the action bar keeps showing the + // Talk + Leave Stage cluster because it's still gated on + // role-only. + val isOnStageMe = + remember(participantGrid, myPubkey) { + participantGrid.onStage.any { it.pubkey == myPubkey } + } // Same logic HandRaiseQueueSection uses internally — duplicated // here so the tab label can show a count without coupling the // section to the screen.