Clear a conversation message's failed status when a retry succeeds (#658)

This commit is contained in:
Coracle-Bot 2026-09-25 20:40:36 +00:00 committed by hodlbod
parent 31b1f7f80d
commit be6b9043b2
3 changed files with 32 additions and 4 deletions

View file

@ -1146,6 +1146,8 @@ Acceptance:
- For a chat message, a "Sending..." toast appears and becomes "Message sent!" - For a chat message, a "Sending..." toast appears and becomes "Message sent!"
once the relay confirms; a failed retry leaves no success message and can be once the relay confirms; a failed retry leaves no success message and can be
retried again. retried again.
- A successful retry clears the "Failed to send!" indicator on the message
itself, in a room and in a conversation.
- For an article or comment, a successful retry clears the "Failed to send!" - For an article or comment, a successful retry clears the "Failed to send!"
indicator and restores the normal reaction and menu actions, with no toast. indicator and restores the normal reaction and menu actions, with no toast.

View file

@ -247,12 +247,18 @@ test("US-069 see why a message failed to deliver", async ({seed, as}) => {
test("US-070 retry a failed relay", async ({seed, as}) => { test("US-070 retry a failed relay", async ({seed, as}) => {
const scenario = await seed(({relay, user}) => { const scenario = await seed(({relay, user}) => {
const space = relay("space") const space = relay("space")
const other = relay("other")
space.room("general", {name: "General"}) space.room("general", {name: "General"})
space.join(user.alice, "general") space.join(user.alice, "general")
space.join(user.bob, "general") space.join(user.bob, "general")
other.join(user.alice)
seedPerson(space, user.alice, "Alice Anderson") seedPerson(space, user.alice, "Alice Anderson")
seedPerson(space, user.bob, "Bob Barker") seedPerson(space, user.bob, "Bob Barker")
space.messagingRelayList(user.alice)
// Bob names both relays and belongs to one, so the other refuses a wrap addressed to him.
space.messagingRelayList(user.bob, [space.url, other.url])
}) })
const {url} = scenario.space("space") const {url} = scenario.space("space")
@ -292,6 +298,9 @@ test("US-070 retry a failed relay", async ({seed, as}) => {
await expect(toast(alice)).toContainText("Sending...") await expect(toast(alice)).toContainText("Sending...")
await expect(toast(alice)).toContainText("Message sent!") await expect(toast(alice)).toContainText("Message sent!")
// The message went out, so it no longer says it did not.
await expect(failure).toHaveCount(0)
const [sent] = getPublishedEvents(alice.context(), MESSAGE) const [sent] = getPublishedEvents(alice.context(), MESSAGE)
// One attempt and two retries, every one of them to the relay that failed and to nothing else. // One attempt and two retries, every one of them to the relay that failed and to nothing else.
@ -300,6 +309,24 @@ test("US-070 retry a failed relay", async ({seed, as}) => {
await bob.goto(roomPath(url, "archive")) await bob.goto(roomPath(url, "archive"))
await expect(message(bob, "is anyone here?")).toBeVisible() await expect(message(bob, "is anyone here?")).toBeVisible()
// The same retry in a conversation, where one of his two messaging relays turns her wrap away.
await alice.goto(chatPath(users.bob.pubkey))
await send(alice, "did that one arrive?")
const partial = alice.getByText("Failed to send!")
await expect(partial).toBeVisible()
await seed(({relay, user}) => {
relay("other").join(user.bob)
})
await partial.click()
await detail(alice).getByRole("button", {name: "Retry"}).click()
await expect(toast(alice)).toContainText("Message sent!")
await expect(partial).toHaveCount(0)
}) })
test("US-071 content posts show delivery status in place", async ({seed, as}) => { test("US-071 content posts show delivery status in place", async ({seed, as}) => {

View file

@ -3,7 +3,6 @@
import {formatTimestampAsTime} from "@welshman/lib" import {formatTimestampAsTime} from "@welshman/lib"
import type {Maybe} from "@welshman/lib" import type {Maybe} from "@welshman/lib"
import type {TrustedEvent, EventContent} from "@welshman/util" import type {TrustedEvent, EventContent} from "@welshman/util"
import {Thunks} from "@welshman/app"
import {isMobile} from "@lib/html" import {isMobile} from "@lib/html"
import MenuDots from "@assets/icons/menu-dots.svg?dataurl" import MenuDots from "@assets/icons/menu-dots.svg?dataurl"
import Icon from "@lib/components/Icon.svelte" import Icon from "@lib/components/Icon.svelte"
@ -20,7 +19,8 @@
import ProfileDetail from "@app/components/ProfileDetail.svelte" import ProfileDetail from "@app/components/ProfileDetail.svelte"
import ChatMessageMenu from "@app/components/ChatMessageMenu.svelte" import ChatMessageMenu from "@app/components/ChatMessageMenu.svelte"
import ChatMessageMenuMobile from "@app/components/ChatMessageMenuMobile.svelte" import ChatMessageMenuMobile from "@app/components/ChatMessageMenuMobile.svelte"
import {app, profiles, user} from "@app/core" import {profiles, thunks, user} from "@app/core"
import {noThunks, thunksByEventId} from "@app/thunks"
import {highlightedEvent} from "@app/routes" import {highlightedEvent} from "@app/routes"
import {colorFor} from "@app/theme" import {colorFor} from "@app/theme"
import {pushModal} from "@app/modal" import {pushModal} from "@app/modal"
@ -39,8 +39,7 @@
const isOwn = event.pubkey === $user.pubkey const isOwn = event.pubkey === $user.pubkey
const profileDisplay = $profiles.display(event.pubkey).$ const profileDisplay = $profiles.display(event.pubkey).$
const thunks = $app.use(Thunks).history const thunk = $derived($thunks.merge($thunksByEventId.get(event.id) ?? noThunks))
const thunk = $app.use(Thunks).merge($thunks.filter(t => t.options.event.id === event.id))
const colorValue = colorFor(event.pubkey) const colorValue = colorFor(event.pubkey)
const reply = () => replyTo(event) const reply = () => replyTo(event)