78be534df5
Reaction-side rework after extended testing on 2026-05-13:
* Promoted users weren't seeing speaker controls in their own
Amethyst — `StageControlsBar` gates on `isOnStage && ui.onStageNow`
and `ui.onStageNow` was stuck at false (never reset back to true
after some earlier path flipped it). Added a LaunchedEffect in
NestActivityContent that mirrors `isOnStageMe → ui.onStageNow`
whenever it becomes true, symmetric to the auto-stop effect.
* Reactions overlay sat inside AvatarAndBadges' wrap-content Box,
so adding/removing the chip shifted the inner Box's centre and
the role badges drifted. Lifted the overlay out to be a sibling
of AvatarAndBadges inside the outer fixed-size Box; badges now
stay anchored regardless of chip presence or animation state.
* Reaction grouping was by `targetPubkey` (NIP-25 p-tag) — emojis
showed on the speaker being reacted to, not the reactor. Switched
RoomReactionsAggregator to group by `sourcePubkey`. AudienceGrid
threads `reactionsByPubkey` so an audience reactor sees their own
emoji float from their audience-tab avatar too.
* Reaction chip animation: progress was an `Animatable.value` that
Compose wasn't refreshing in the layout-consuming layer (visible
bug: chip "blinked" with no movement). Replaced with a manual
`withFrameNanos` loop writing to a `MutableFloatState` read inside
a `graphicsLayer { … }` lambda — frame-clock animation that works.
Each kind-7 is its own chip keyed by event-id (no more
groupBy-content collapsing same-emoji bursts into one shared chip
that restarts on every arrival). Chips stack at a fixed-size 30 dp
Box with the emoji centred, so the X position is invariant under
glyph width. Multiple concurrent chips overlap at the right-bottom
corner in a `Box(BottomEnd)` (newest on top) rather than sliding
leftward in a `Row`.
* Bottom-drawer `RoomReactionPickerSheet` (hard-coded 6 emojis, no
NIP-30) replaced by a forked `RoomReactionPopup` that reuses
`ReactionChoicePopupContent` (same NIP-30 custom-emoji support,
same user-configured reaction set) but with two semantic deviations
for live audio rooms: empty `toRemove` so all buttons stay
"fresh", and the click handler signs+broadcasts a fresh kind-7
template directly instead of going through `Account.reactTo` —
which delegates to `ReactionAction.reactTo(note, …)` and
short-circuits on `note.hasReacted(by, reaction)`. The bypass
lets the user fire the same emoji repeatedly during a moment.
* Chip rendering matches NoteCompose: `RenderReactionContent`
handles `:shortcode:url` via `InLineIconRenderer` + `CustomEmoji`,
`"+"`→❤️, `"-"`→👎, anything else as Text.
Tests updated for sender-grouped aggregator semantics.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>