|
1 | 1 | import type { StateCreator } from "zustand"; |
2 | 2 |
|
3 | 3 | import type { AppStore } from "../store.js"; |
4 | | -import { deleteStoredMessagesLocally, maxConversationTimelineEntries, replaceStoredMessageForRetry, saveStoredMessage } from "../../storage/messages-store.js"; |
5 | | -import { deleteStoredImageMessages } from "../../storage/image-media-store.js"; |
| 4 | +import { deleteStoredMessagesForContact, deleteStoredMessagesLocally, maxConversationTimelineEntries, replaceStoredMessageForRetry, saveStoredMessage } from "../../storage/messages-store.js"; |
| 5 | +import { deleteStoredImageMessages, deleteStoredImageMessagesForContact } from "../../storage/image-media-store.js"; |
| 6 | +import { deleteStoredMessageDeliveryTargetsForContact } from "../../storage/message-delivery-target-store.js"; |
| 7 | +import { deleteStoredOutboxForContact } from "../../storage/message-outbox-store.js"; |
| 8 | +import { deleteStoredReadReceiptsForContact } from "../../storage/read-receipt-outbox-store.js"; |
| 9 | +import { deleteStoredReadState } from "../../storage/read-state-store.js"; |
6 | 10 |
|
7 | 11 | // `received` remains an inbound/local compatibility value. Remote user-facing |
8 | 12 | // outcomes for an outgoing message are explicit: a relay may say Relayed, but |
@@ -33,6 +37,9 @@ export interface ConversationsSlice { |
33 | 37 | readonly setMessageDeliveryState: (contactId: string, messageId: string, deliveryState: MessageDeliveryState) => void; |
34 | 38 | // UI-initiated device-local removal. It has no transport/control side effect. |
35 | 39 | readonly deleteMessagesLocally: (contactId: string, messageIds: readonly string[]) => readonly MessageSummary[]; |
| 40 | + // Debug-only local test cleanup. It preserves the contact, identity, and |
| 41 | + // relay attachment while clearing this chat's visible history and work. |
| 42 | + readonly clearLocalConversation: (contactId: string) => void; |
36 | 43 | } |
37 | 44 |
|
38 | 45 | export const createConversationsSlice: StateCreator<AppStore, [], [], ConversationsSlice> = (set, get) => ({ |
@@ -141,6 +148,37 @@ export const createConversationsSlice: StateCreator<AppStore, [], [], Conversati |
141 | 148 | // removal can retry local byte cleanup when IndexedDB was unavailable. |
142 | 149 | }); |
143 | 150 | return deleted; |
| 151 | + }, |
| 152 | + clearLocalConversation: (contactId) => { |
| 153 | + // Do not reuse contact removal: a test reset must leave the user-owned |
| 154 | + // contact card and all live relay attachment state intact. |
| 155 | + get().clearImageMessageProjectionsForContact(contactId); |
| 156 | + set((state) => { |
| 157 | + const { [contactId]: removedMessages, ...messagesByContactId } = state.messagesByContactId; |
| 158 | + const { [contactId]: removedReadState, ...lastReadAtByContactId } = state.lastReadAtByContactId; |
| 159 | + const { [contactId]: removedTyping, ...typingExpiresAtByContactId } = state.typingExpiresAtByContactId; |
| 160 | + void removedMessages; |
| 161 | + void removedReadState; |
| 162 | + void removedTyping; |
| 163 | + return { |
| 164 | + messagesByContactId, |
| 165 | + outbox: state.outbox.filter((entry) => entry.contactId !== contactId), |
| 166 | + readReceiptOutbox: state.readReceiptOutbox.filter((entry) => entry.contactId !== contactId), |
| 167 | + lastReadAtByContactId, |
| 168 | + typingExpiresAtByContactId |
| 169 | + }; |
| 170 | + }); |
| 171 | + void Promise.all([ |
| 172 | + deleteStoredMessagesForContact(contactId), |
| 173 | + deleteStoredReadState(contactId), |
| 174 | + deleteStoredOutboxForContact(contactId), |
| 175 | + deleteStoredReadReceiptsForContact(contactId), |
| 176 | + deleteStoredMessageDeliveryTargetsForContact(contactId), |
| 177 | + deleteStoredImageMessagesForContact(contactId) |
| 178 | + ]).catch(() => { |
| 179 | + // The current tab has already reset its local projection. No reset |
| 180 | + // action emits transport traffic or retries storage failures. |
| 181 | + }); |
144 | 182 | } |
145 | 183 | }); |
146 | 184 |
|
|
0 commit comments