From 16cfe9914ce97e296e98ba52a916d40c01be838d Mon Sep 17 00:00:00 2001 From: jack Date: Thu, 11 Sep 2025 20:53:07 +0200 Subject: [PATCH] Geohash ordering: strict in-order inserts and timestamp clamp Use channel-aware late-insert threshold with 0s for geohash to keep strict chronological order. Clamp future Nostr event timestamps to 'now' to avoid future-dated items skewing order in geohash timelines. --- bitchat/Services/TransportConfig.swift | 2 ++ bitchat/ViewModels/ChatViewModel.swift | 26 +++++++++++++++++++++----- 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/bitchat/Services/TransportConfig.swift b/bitchat/Services/TransportConfig.swift index 55c6106d..9203a548 100644 --- a/bitchat/Services/TransportConfig.swift +++ b/bitchat/Services/TransportConfig.swift @@ -37,6 +37,8 @@ enum TransportConfig { // UI thresholds static let uiLateInsertThreshold: TimeInterval = 15.0 + // Geohash public chats are more sensitive to ordering; use a tighter threshold + static let uiLateInsertThresholdGeo: TimeInterval = 0.0 static let uiProcessedNostrEventsCap: Int = 2000 static let uiChannelInactivityThresholdSeconds: TimeInterval = 9 * 60 diff --git a/bitchat/ViewModels/ChatViewModel.swift b/bitchat/ViewModels/ChatViewModel.swift index 92306087..41dac351 100644 --- a/bitchat/ViewModels/ChatViewModel.swift +++ b/bitchat/ViewModels/ChatViewModel.swift @@ -928,7 +928,9 @@ final class ChatViewModel: ObservableObject, BitchatDelegate { } let senderName = self.displayNameForNostrPubkey(event.pubkey) let content = event.content.trimmingCharacters(in: .whitespacesAndNewlines) - let timestamp = Date(timeIntervalSince1970: TimeInterval(event.created_at)) + // Clamp future timestamps to now to avoid future-dated messages skewing order + let rawTs = Date(timeIntervalSince1970: TimeInterval(event.created_at)) + let timestamp = min(rawTs, Date()) let mentions = self.parseMentions(from: content) let msg = BitchatMessage( id: event.id, @@ -1636,7 +1638,9 @@ final class ChatViewModel: ObservableObject, BitchatDelegate { content.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty { return } - let timestamp = Date(timeIntervalSince1970: TimeInterval(event.created_at)) + // Clamp future timestamps + let rawTs = Date(timeIntervalSince1970: TimeInterval(event.created_at)) + let timestamp = min(rawTs, Date()) let mentions = self.parseMentions(from: content) let msg = BitchatMessage( id: event.id, @@ -2000,7 +2004,9 @@ final class ChatViewModel: ObservableObject, BitchatDelegate { let senderSuffix = String(event.pubkey.suffix(4)) let nick = self.geoNicknames[event.pubkey.lowercased()] let senderName = (nick?.isEmpty == false ? nick! : "anon") + "#" + senderSuffix - let ts = Date(timeIntervalSince1970: TimeInterval(event.created_at)) + // Clamp future timestamps + let rawTs = Date(timeIntervalSince1970: TimeInterval(event.created_at)) + let ts = min(rawTs, Date()) let mentions = self.parseMentions(from: content) let msg = BitchatMessage( id: event.id, @@ -5978,10 +5984,20 @@ final class ChatViewModel: ObservableObject, BitchatDelegate { isBatchingPublic = true // Rough chronological order: sort the batch by timestamp before inserting added.sort { $0.timestamp < $1.timestamp } - // Insert late arrivals into approximate position; append recent ones + // Channel-aware insertion policy: geohash uses strict ordering; mesh allows small out-of-order appends + let threshold: TimeInterval = { + switch activeChannel { + case .location: return TransportConfig.uiLateInsertThresholdGeo + case .mesh: return TransportConfig.uiLateInsertThreshold + } + }() let lastTs = messages.last?.timestamp ?? .distantPast for m in added { - if m.timestamp < lastTs.addingTimeInterval(-lateInsertThreshold) { + if m.timestamp < lastTs.addingTimeInterval(-threshold) { + let idx = insertionIndexByTimestamp(m.timestamp) + if idx >= messages.count { messages.append(m) } else { messages.insert(m, at: idx) } + } else if threshold == 0 { + // Strict ordering for geohash: always insert by timestamp let idx = insertionIndexByTimestamp(m.timestamp) if idx >= messages.count { messages.append(m) } else { messages.insert(m, at: idx) } } else {