Cut private message path over to ConversationStore

All private-message mutations now flow through store intents:
coordinators, PrivateChatManager (its @Published dicts deleted - now
read-only views over the store), outbound sends, delivery status, and
chat migration. The O(1) store dedup replaces the full-scan duplicate
check; insertion order is maintained by the store so sanitizeChat's
re-sort is a documented no-op. Both bootstrapper Combine bridges and
the Task.yield store synchronization are deleted.

ChatViewModel.privateChats/unreadPrivateMessages become get-only derived
views (measured: naive rebuild equals a change-invalidated cache within
noise, so the simpler form stays). Feature models still read the legacy
store, fed by a coalescing LegacyConversationStoreBridge (one mirror
per burst, marked for step-5 deletion).

pipeline.privateIngest: 9.6k -> 14.7k msg/s (+53%).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
jack
2026-06-11 12:19:11 +02:00
co-authored by Claude Fable 5
parent ac3a2f2d34
commit 879d8cba12
33 changed files with 1293 additions and 545 deletions
@@ -177,7 +177,7 @@ struct ChatViewModelPrivateChatExtensionTests {
isPrivate: true,
senderPeerID: oldPeerID
)
viewModel.privateChats[oldPeerID] = [oldMessage]
viewModel.seedPrivateChat([oldMessage], for: oldPeerID)
viewModel.peerIDToPublicKeyFingerprint[oldPeerID] = fingerprint
// Setup new peer fingerprint
@@ -444,7 +444,7 @@ struct ChatViewModelNostrExtensionTests {
let convKey = PeerID(nostr_: sender.publicKeyHex)
let messageID = "geo-ack-delivered"
viewModel.privateChats[convKey] = [
viewModel.seedPrivateChat([
BitchatMessage(
id: messageID,
sender: viewModel.nickname,
@@ -456,7 +456,7 @@ struct ChatViewModelNostrExtensionTests {
senderPeerID: viewModel.meshService.myPeerID,
deliveryStatus: .sent
)
]
], for: convKey)
let content = try ackContent(type: .delivered, messageID: messageID, senderPeerID: PeerID(str: "0123456789abcdef"))
let giftWrap = try NostrProtocol.createPrivateMessage(
@@ -482,7 +482,7 @@ struct ChatViewModelNostrExtensionTests {
let convKey = PeerID(nostr_: sender.publicKeyHex)
let messageID = "geo-ack-read"
viewModel.privateChats[convKey] = [
viewModel.seedPrivateChat([
BitchatMessage(
id: messageID,
sender: viewModel.nickname,
@@ -494,7 +494,7 @@ struct ChatViewModelNostrExtensionTests {
senderPeerID: viewModel.meshService.myPeerID,
deliveryStatus: .delivered(to: "Friend", at: Date())
)
]
], for: convKey)
let content = try ackContent(type: .readReceipt, messageID: messageID, senderPeerID: PeerID(str: "0123456789abcdef"))
let giftWrap = try NostrProtocol.createPrivateMessage(
@@ -578,7 +578,7 @@ struct ChatViewModelNostrExtensionTests {
let convKey = PeerID(nostr_: sender.publicKeyHex)
let messageID = "gift-delivered"
viewModel.privateChats[convKey] = [
viewModel.seedPrivateChat([
BitchatMessage(
id: messageID,
sender: viewModel.nickname,
@@ -590,7 +590,7 @@ struct ChatViewModelNostrExtensionTests {
senderPeerID: viewModel.meshService.myPeerID,
deliveryStatus: .sent
)
]
], for: convKey)
let content = try ackContent(type: .delivered, messageID: messageID, senderPeerID: PeerID(str: "0123456789abcdef"))
let giftWrap = try NostrProtocol.createPrivateMessage(
@@ -1095,7 +1095,7 @@ struct ChatViewModelMediaTransferTests {
senderPeerID: viewModel.meshService.myPeerID,
deliveryStatus: .sending
)
viewModel.privateChats[peerID] = [message]
viewModel.seedPrivateChat([message], for: peerID)
viewModel.registerTransfer(transferId: "transfer-cancel", messageID: message.id)
viewModel.cancelMediaSend(messageID: message.id)
@@ -1124,7 +1124,7 @@ struct ChatViewModelMediaTransferTests {
senderPeerID: viewModel.meshService.myPeerID,
deliveryStatus: .sent
)
viewModel.privateChats[peerID] = [message]
viewModel.seedPrivateChat([message], for: peerID)
viewModel.registerTransfer(transferId: "transfer-delete", messageID: message.id)
viewModel.deleteMediaMessage(messageID: message.id)