Convert shared coordinator state to owner-side intent operations

nostrKeyMapping, sentGeoDeliveryAcks/sentReadReceipts dedupe,
isBatchingPublic, geo subscription lifecycle, and private-chat selection
hand-off now mutate through single intent operations on ChatViewModel,
with backing storage locked down via private(set) so the single-writer
property is compiler-enforced. Context protocols downgrade to read-only
access where reads remain. 8 new contract tests.

Known remaining writers outside the protocols: sentReadReceipts is
passed inout to PrivateChatManager.syncReadReceiptsForSentMessages and
un-marked by ChatTransportEventCoordinator on disconnect.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
jack
2026-06-10 21:26:21 +02:00
co-authored by Claude Fable 5
parent 80bed1f395
commit 707b22878d
12 changed files with 358 additions and 64 deletions
@@ -384,6 +384,16 @@ private final class PerfNostrContext: ChatNostrContext {
var lastGeoNotificationAt: [String: Date] = [:]
var nostrRelayManager: NostrRelayManager? { nil }
func setGeoChatSubscriptionID(_ id: String?) { geoSubscriptionID = id }
func setGeoDmSubscriptionID(_ id: String?) { geoDmSubscriptionID = id }
func addGeoSamplingSub(_ subID: String, forGeohash geohash: String) { geoSamplingSubs[subID] = geohash }
func removeGeoSamplingSub(_ subID: String) { geoSamplingSubs.removeValue(forKey: subID) }
func clearGeoSamplingSubs() -> [String] {
defer { geoSamplingSubs.removeAll() }
return Array(geoSamplingSubs.keys)
}
var messages: [BitchatMessage] = []
func resetPublicMessagePipeline() {}
func updatePublicMessagePipelineChannel(_ channel: ChannelID) {}
@@ -403,6 +413,7 @@ private final class PerfNostrContext: ChatNostrContext {
var selectedPrivateChatPeer: PeerID?
var nostrKeyMapping: [PeerID: String] = [:]
func registerNostrKeyMapping(_ pubkey: String, for peerID: PeerID) { nostrKeyMapping[peerID] = pubkey }
func handlePrivateMessage(_ payload: NoisePayload, senderPubkey: String, convKey: PeerID, id: NostrIdentity, messageTimestamp: Date) {}
func handleDelivered(_ payload: NoisePayload, senderPubkey: String, convKey: PeerID) {}
func handleReadReceipt(_ payload: NoisePayload, senderPubkey: String, convKey: PeerID) {}
@@ -460,6 +471,12 @@ private final class PerfDeliveryContext: ChatDeliveryContext {
func notifyUIChanged() {}
func markMessageDelivered(_ messageID: String) {}
func pruneSentReadReceipts(keeping validMessageIDs: Set<String>) -> Int {
let oldCount = sentReadReceipts.count
sentReadReceipts = sentReadReceipts.intersection(validMessageIDs)
return oldCount - sentReadReceipts.count
}
/// 2000 public + `peerCount` x `messagesPerPeer` private messages with
/// deterministic IDs and timestamps.
static func makeCorpus(publicCount: Int, peerCount: Int, messagesPerPeer: Int) -> PerfDeliveryContext {