From fa6e60ad0504dc5bde4103a06cbdf080e23d47b5 Mon Sep 17 00:00:00 2001 From: Coracle-Bot Date: Thu, 10 Sep 2026 02:42:41 +0000 Subject: [PATCH] Drop a conversation from the list when its last message is removed --- e2e/specs/delivery.spec.ts | 7 +++++++ src/app/chats.ts | 10 ++++++++-- src/app/notifications.ts | 6 ++---- 3 files changed, 17 insertions(+), 6 deletions(-) diff --git a/e2e/specs/delivery.spec.ts b/e2e/specs/delivery.spec.ts index 7c4ecb48..772156f5 100644 --- a/e2e/specs/delivery.spec.ts +++ b/e2e/specs/delivery.spec.ts @@ -60,6 +60,9 @@ const message = (page: Page, text: string) => page.locator(".room__item").filter const bubble = (page: Page, text: string) => page.locator(".chat-bubble").filter({hasText: text}) +// One conversation in the sidebar list is one button; nothing inside it is one. +const chatItems = (page: Page) => page.locator(".secondary-nav .overflow-auto").locator("button") + // One toast at a time — src/app/toast.ts holds a single writable — so this is the toast. const toast = (page: Page) => page.getByRole("alert") @@ -230,6 +233,10 @@ test("US-068 watch a delayed send, and cancel it", async ({seed, as}) => { await toast(alice).getByRole("button", {name: "Cancel"}).click() + // That was her only message to him, so the conversation goes with it rather than staying in + // the list with nothing left to name it by. + await expect(chatItems(alice)).toHaveCount(0) + await send(alice, "actually, hi") await expect(bubble(bob, "actually, hi")).toBeVisible() diff --git a/src/app/chats.ts b/src/app/chats.ts index b2569d24..beb6acc1 100644 --- a/src/app/chats.ts +++ b/src/app/chats.ts @@ -100,12 +100,18 @@ export const chatsById = call(() => { // (unlike a delete, which leaves the target flagged), so `getEvent` would return nothing and // the message would linger. Replace each affected chat with a fresh object rather than mutating // its messages in place: deriveChat is deduplicated by reference (see makeDeriveItem/ - // deriveDeduplicated), so a chat whose identity is unchanged never reaches the ui. + // deriveDeduplicated), so a chat whose identity is unchanged never reaches the ui. A chat + // that loses its last message goes with it, since a chat is only ever its messages. for (const [chatId, chat] of chatsById) { const messages = chat.messages.filter(e => !removed.has(e.id)) if (messages.length !== chat.messages.length) { - chatsById.set(chatId, {...chat, messages}) + if (messages.length > 0) { + chatsById.set(chatId, {...chat, messages}) + } else { + chatsById.delete(chatId) + } + dirty = true } } diff --git a/src/app/notifications.ts b/src/app/notifications.ts index f1bb0956..8c437ae7 100644 --- a/src/app/notifications.ts +++ b/src/app/notifications.ts @@ -261,11 +261,9 @@ export const latestActivityByPath = derived( const activity = new Map() for (const {pubkeys, messages} of $chatsById.values()) { - if (messages[0]) { - const path = makeChatPath(pubkeys) + const path = makeChatPath(pubkeys) - activity.set(path, {path, pubkeys, event: messages[0]}) - } + activity.set(path, {path, pubkeys, event: messages[0]}) } const roomList = $app.user?.pubkey ? $roomLists.get($app.user.pubkey) : undefined