From 43bede0abe89ca1b9613c789467f3d7cf75af1bb Mon Sep 17 00:00:00 2001 From: agent-p1p Date: Sun, 12 Jul 2026 05:50:26 +0200 Subject: [PATCH] fix: preserve chat state across archive transitions Compute removals across the combined active and archived chat lists so moving a chat between lists does not clear its composer draft or member cache. Fixes #466 --- .../Core/WorkspaceState+ChatList.swift | 15 +++---- whitenoise-macTests/whitenoise_macTests.swift | 43 ++++++++++++++++++- 2 files changed, 48 insertions(+), 10 deletions(-) diff --git a/whitenoise-mac/Core/WorkspaceState+ChatList.swift b/whitenoise-mac/Core/WorkspaceState+ChatList.swift index 6093d88..77915ef 100644 --- a/whitenoise-mac/Core/WorkspaceState+ChatList.swift +++ b/whitenoise-mac/Core/WorkspaceState+ChatList.swift @@ -203,18 +203,15 @@ extension WorkspaceState { let activeItems = activeRows.map { baseChatItem(from: $0, account: account) } let archivedItems = archivedRows.map { baseChatItem(from: $0, account: account) } - let previousActiveChatIds = Set((chatsByAccount[account.id] ?? []).map(\.id)) - let nextActiveChatIds = Set(activeItems.map(\.id)) - let removedActiveChatIds = previousActiveChatIds.subtracting(nextActiveChatIds) + let previousChatIds = Set((chatsByAccount[account.id] ?? []).map(\.id)) + .union((archivedChatsByAccount[account.id] ?? []).map(\.id)) + let nextChatIds = Set(activeItems.map(\.id)).union(archivedItems.map(\.id)) + let removedChatIds = previousChatIds.subtracting(nextChatIds) - let previousArchivedChatIds = Set((archivedChatsByAccount[account.id] ?? []).map(\.id)) - let nextArchivedChatIds = Set(archivedItems.map(\.id)) - let removedArchivedChatIds = previousArchivedChatIds.subtracting(nextArchivedChatIds) - - for groupId in removedActiveChatIds.union(removedArchivedChatIds) { + for groupId in removedChatIds { invalidateGroupMembers(for: groupId) } - clearComposerDrafts(for: Array(removedActiveChatIds.union(removedArchivedChatIds)), accountId: account.id) + clearComposerDrafts(for: Array(removedChatIds), accountId: account.id) setChats(sortedChatItems(activeItems), forAccountId: account.id) setArchivedChats(sortedChatItems(archivedItems), forAccountId: account.id) dismissGroupImagePickerIfSelectedChatUnavailable() diff --git a/whitenoise-macTests/whitenoise_macTests.swift b/whitenoise-macTests/whitenoise_macTests.swift index eee18d6..3ec1f04 100644 --- a/whitenoise-macTests/whitenoise_macTests.swift +++ b/whitenoise-macTests/whitenoise_macTests.swift @@ -13127,6 +13127,46 @@ struct whitenoise_macTests { #expect(!FileManager.default.fileExists(atPath: url.path)) } + @MainActor + @Test func bulkChatRowsPreserveConversationStateAcrossArchiveTransitions() async throws { + let state = WorkspaceState.preview() + let account = AccountItem.samples[0] + let chat = try #require(state.selectedChat) + let draftKey = try #require(state.selectedComposerDraftKey) + state.draftTextByConversation[draftKey] = "see you at 6" + state.storeGroupMembers([], for: chat.id) + + func row(archived: Bool) -> ChatListRowFfi { + chatListRow( + groupIdHex: chat.id, + title: chat.title, + preview: chat.preview, + sender: "alice", + timelineAt: 1_700_000_000, + archived: archived + ) + } + + await state.applyChatRows([row(archived: true)], account: account) + + #expect(state.activeChats.isEmpty) + #expect(state.archivedChats.map(\.id) == [chat.id]) + #expect(state.draftTextByConversation[draftKey] == "see you at 6") + #expect(state.groupMemberDetailsCache[chat.id] != nil) + + await state.applyChatRows([row(archived: false)], account: account) + + #expect(state.activeChats.map(\.id) == [chat.id]) + #expect(state.archivedChats.isEmpty) + #expect(state.draftTextByConversation[draftKey] == "see you at 6") + #expect(state.groupMemberDetailsCache[chat.id] != nil) + + await state.applyChatRows([], account: account) + + #expect(state.draftTextByConversation[draftKey] == nil) + #expect(state.groupMemberDetailsCache[chat.id] == nil) + } + @MainActor @Test func startingNewChatCreatesAndSelectsConversation() async throws { let account = AccountSummaryFfi( @@ -18336,12 +18376,13 @@ private func chatListRow( preview: String, sender: String, timelineAt: UInt64, + archived: Bool = false, kind: UInt64 = 9, selfMembership: SelfMembershipFfi = .member ) -> ChatListRowFfi { ChatListRowFfi( groupIdHex: groupIdHex, - archived: false, + archived: archived, pendingConfirmation: false, title: title, groupName: "",