Skip to content

Feature: Transparent Event Broadcasting Feedback (Complete UI Mode) #1682

@nrobi144

Description

@nrobi144

Problem

Currently, reactions (likes, boosts, zaps, bookmarks) use a fire-and-forget model with optimistic UI updates. Users have no visibility into:

  • Whether their event was actually delivered to relays
  • Which relays accepted/rejected the event
  • Why a relay might have rejected it (AUTH_REQUIRED, rate-limited, etc.)

This creates a "happy path heuristic" where the UI always shows success, even when events fail to propagate.

Proposed Solution

Add transparent broadcasting feedback when FeatureSetType.COMPLETE is enabled:

1. Global Progress Banner (Above Bottom Nav)

┌─────────────────────────────────────────────┐
│                  Feed Content               │
├─────────────────────────────────────────────┤
│ 🔄 Broadcasting Boost (kind 6)      [2/5]  │  ← Compact banner
│ ████████░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ │  ← Determinate progress
├─────────────────────────────────────────────┤
│           [Bottom Navigation]               │
└─────────────────────────────────────────────┘
  • Shows event type and kind number
  • Live progress as relays respond (animated transitions)
  • Collapses when multiple events in-flight: "Broadcasting 3 events... [5/15]"

2. Result Snackbar

Success (partial or full):

┌─────────────────────────────────────────────┐
│ ✓ Boost sent to 4/5 relays          [View] │
└─────────────────────────────────────────────┘

Failure (0 relays):

┌─────────────────────────────────────────────┐
│ ✗ Boost failed - 0/5 relays        [Retry] │
└─────────────────────────────────────────────┘
  • If 0/5 succeed: don't mark action as complete (button stays unpressed)
  • Tappable action opens detail sheet

3. Broadcast Details BottomSheet

┌─────────────────────────────────────────────┐
│  ──────  Broadcast Results                  │
├─────────────────────────────────────────────┤
│  Boost (kind 6) • 4/5 relays                │
├─────────────────────────────────────────────┤
│  ✓ relay.damus.io                           │
│  ✓ nos.lol                                  │
│  ✓ relay.nostr.band                         │
│  ✓ purplepag.es                             │
│  ✗ relay.snort.social    [AUTH_REQUIRED] ↻ │
├─────────────────────────────────────────────┤
│            [Dismiss]  [Retry Failed]        │
└─────────────────────────────────────────────┘
  • Shows each relay with status (animated state transitions)
  • Error code/message for failures
  • Optional: Retry individual relays or all failed
  • Optional: Undo/Delete action (sends kind 5 deletion)

Technical Implementation

Existing Infrastructure (No Changes Needed)

Component Location Purpose
OkMessage quartz/.../relay/commands/toClient/OkMessage.kt NIP-20 OK response parsing
RelayInsertConfirmationCollector quartz/.../relay/client/accessories/ Tracks relay confirmations
sendAndWaitForResponse() quartz/.../NostrClientSendAndWaitExt.kt 15s timeout, waits for all relays
DeletionEvent quartz/.../nip09Deletions/DeletionEvent.kt Kind 5 delete events

New Components

1. BroadcastTracker (State Management)

// Location: amethyst/src/main/java/com/vitorpamplona/amethyst/service/broadcast/

data class BroadcastEvent(
    val id: String,                              // Tracking ID
    val eventId: HexKey,                         // Nostr event ID
    val eventName: String,                       // "Boost", "Reaction", "Bookmark"
    val kind: Int,
    val targetRelays: List<NormalizedRelayUrl>,
    val results: Map<NormalizedRelayUrl, RelayResult> = emptyMap(),
    val status: BroadcastStatus = BroadcastStatus.IN_PROGRESS,
    val startedAt: Long = System.currentTimeMillis()
)

sealed class RelayResult {
    object Success : RelayResult()
    data class Error(val code: String, val message: String?) : RelayResult()
    object Timeout : RelayResult()
}

enum class BroadcastStatus { IN_PROGRESS, SUCCESS, PARTIAL, FAILED }

class BroadcastTracker {
    private val _activeBroadcasts = MutableStateFlow<List<BroadcastEvent>>(emptyList())
    val activeBroadcasts: StateFlow<List<BroadcastEvent>> = _activeBroadcasts.asStateFlow()
    
    private val _completedBroadcast = MutableSharedFlow<BroadcastEvent>()
    val completedBroadcast: SharedFlow<BroadcastEvent> = _completedBroadcast
    
    suspend fun trackBroadcast(
        event: Event,
        eventName: String,
        relays: Set<NormalizedRelayUrl>,
        client: INostrClient
    ): BroadcastResult
    
    fun getDetails(trackingId: String): BroadcastEvent?
    suspend fun retry(trackingId: String, relay: NormalizedRelayUrl? = null)
    fun dismiss(trackingId: String)
}

2. UI Components

Component File Description
BroadcastBanner ui/broadcast/BroadcastBanner.kt Floating progress indicator
BroadcastSnackbar ui/broadcast/BroadcastSnackbar.kt Result notification
BroadcastDetailsSheet ui/broadcast/BroadcastDetailsSheet.kt Modal bottom sheet with relay list
RelayResultRow ui/broadcast/RelayResultRow.kt Single relay status row

Scope

In Scope

  • Reactions (kind 7)
  • Boosts/Reposts (kind 6)
  • Zaps (kind 9735 request)
  • Bookmarks (kind 30001)
  • Progress indicator with live animated updates
  • Result snackbar with tap-to-expand
  • Relay status detail sheet
  • Retry failed relays
  • Only when FeatureSetType.COMPLETE
  • 15s timeout for relay responses

Optional (Nice to Have)

  • Undo/Delete action (kind 5)
  • Persist broadcast history across sessions

Out of Scope

  • SIMPLIFIED and PERFORMANCE UI modes (keep fire-and-forget)
  • Desktop app (separate implementation later)
  • NWC zap response tracking changes

Implementation Phases

Phase 1: Core Infrastructure

  1. Create BroadcastTracker class
  2. Create BroadcastEvent and RelayResult data classes
  3. Add to AccountViewModel

Phase 2: UI Components

  1. Create BroadcastBanner composable (with animations)
  2. Create BroadcastSnackbar composable
  3. Create BroadcastDetailsSheet composable

Phase 3: Integration

  1. Modify Account publish methods to support tracked mode
  2. Wire banner into MainScaffold
  3. Add FeatureSetType.COMPLETE gates

Phase 4: Polish

  1. Add retry functionality
  2. Handle edge cases (offline, rapid actions)
  3. Optional: Add delete/undo support

References

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions