From 6610683a20362aeda3a446c476a4bd6618181535 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 1 May 2026 21:30:05 +0000 Subject: [PATCH] fix: hide StageControlsBar on local intent flag for responsive tap MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit After tapping Leave the Stage, the bar lingered because it was gated solely on isOnStageMe — a value derived from the presence aggregator. That aggregator only updates after a presence event signs, broadcasts to relays, and loops back through LocalCache. With the recent broadcast / mute traffic contending on the signer plus the 500 ms mute debounce in NestPresencePublisher, the round-trip ran long enough that the user perceived Leave the Stage as not firing. AND the visibility gate with ui.onStageNow (the local intent flag, which flips synchronously on setOnStage(false)) so the tap hides the bar on the next frame. Host demotion still hides it via the existing isOnStageMe path. --- .../nests/room/screen/NestActionBar.kt | 22 +++++++++++++------ 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/nests/room/screen/NestActionBar.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/nests/room/screen/NestActionBar.kt index ce8bb9c9b..4270f79c2 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/nests/room/screen/NestActionBar.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/nests/room/screen/NestActionBar.kt @@ -133,12 +133,20 @@ internal fun NestActionBar( * everything a speaker needs while on stage: Talk / MicMute / Retry + * Leave the Stage. Renders nothing when the local user isn't on stage. * - * Visibility is gated on [isOnStage] alone — a stable signal driven - * by the kind-30312 role + presence `onstage` flag — so connection - * blips, broadcast state churn, mute toggles, and permission flows - * never make the bar appear or disappear. It only shows up on a real - * promote, and only goes away on a real demote (or the user tapping - * Leave the Stage). + * Visibility uses two signals AND'd together: + * - [isOnStage] — derived from `participantGrid.onStage` (kind-30312 + * role + presence `onstage` flag); flips on a real promote/demote + * after the round-trip lands. Connection blips, broadcast state + * churn, mute toggles, and permission flows do NOT change it. + * - [ui.onStageNow] — the LOCAL intent flag. Flips synchronously on + * `setOnStage(false)` so a Leave-the-Stage tap hides the bar on + * the next frame, instead of waiting for the presence event to + * sign, broadcast, and loop back through LocalCache (a delay made + * visible in scenario "broadcast → mute → unmute → leave", where + * the signer queues behind a pending mute frame + 500 ms debounce). + * + * AND-of-both also handles host demotion correctly: intent stays true + * but the aggregator drops us, so the bar still hides. */ @Composable internal fun StageControlsBar( @@ -149,7 +157,7 @@ internal fun StageControlsBar( speakerPubkeyHex: String, modifier: Modifier = Modifier, ) { - if (!isOnStage) return + if (!isOnStage || !ui.onStageNow) return Row( modifier = modifier