From a87d65b05ad87fb32dd36b99b606d42a27256fbb Mon Sep 17 00:00:00 2001 From: Stefan Ceriu Date: Wed, 30 Sep 2026 13:57:03 +0300 Subject: [PATCH] Surface failed reactions as a send failure on their message A reaction that fails to send can now block the room's send queue, but the timeline only surfaced failures for the item itself, its edit or its redaction. EventTimelineItemProxy.pendingSend now also falls back to our first failed reaction, targeting it with SendTarget.reaction(key:), so the message shows the red exclamation mark and the sending failed alert can retry or remove it. In-flight reactions are deliberately ignored so reacting doesn't flash a sending indicator on the message. --- .../Services/Timeline/TimelineItemProxy.swift | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/ElementX/Sources/Services/Timeline/TimelineItemProxy.swift b/ElementX/Sources/Services/Timeline/TimelineItemProxy.swift index 918d90822..cbf876159 100644 --- a/ElementX/Sources/Services/Timeline/TimelineItemProxy.swift +++ b/ElementX/Sources/Services/Timeline/TimelineItemProxy.swift @@ -107,7 +107,7 @@ final nonisolated class EventTimelineItemProxy: Sendable { } } - /// The item's own pending send or, when it has already gone out, our pending edit or redaction of it. + /// The item's own pending send or, when it has already gone out, our pending edit, redaction or failed reaction on it. var pendingSend: (target: SendTarget, state: EventSendState)? { if let state = item.localSendState { (.event, state) @@ -115,11 +115,25 @@ final nonisolated class EventTimelineItemProxy: Sendable { (.edit, state) } else if let state = item.redactionSendState { (.redaction, state) + } else if let (key, state) = failedReaction { + (.reaction(key: key), state) } else { nil } } + /// Only failed reactions are surfaced as they block the send queue, in-flight ones would flash a sending indicator on every tap. + private var failedReaction: (key: String, state: EventSendState)? { + for reaction in item.reactions { + for sender in reaction.senders { + if let state = sender.sendState, case .sendingFailed = state { + return (reaction.key, state) + } + } + } + return nil + } + var canBeRepliedTo: Bool { item.canBeRepliedTo }