From 19b28cf49d8e4766f81d49e36e5f72af10954e7a Mon Sep 17 00:00:00 2001 From: jack Date: Wed, 10 Jun 2026 16:32:36 +0100 Subject: [PATCH] Rebuild message location index on non-append growth The incremental index refresh assumed growth meant appended messages, but PublicMessagePipeline inserts out-of-order arrivals by timestamp: the count grows while the tail ID stays put, so the inserted message never entered the index and later delivery updates for it silently no-op'd (and, with retain-until-ack routing, left it queued for resend). Detect non-append growth by checking the previously indexed tail kept its position, and rebuild when it hasn't. Same check on the per-peer private chat arrays, which re-sort by timestamp. Found by Codex review on #1331. Co-Authored-By: Claude Fable 5 --- .../ViewModels/ChatDeliveryCoordinator.swift | 15 ++++++ .../ChatViewModelDeliveryStatusTests.swift | 53 +++++++++++++++++++ 2 files changed, 68 insertions(+) diff --git a/bitchat/ViewModels/ChatDeliveryCoordinator.swift b/bitchat/ViewModels/ChatDeliveryCoordinator.swift index fc9373bf..62492aa2 100644 --- a/bitchat/ViewModels/ChatDeliveryCoordinator.swift +++ b/bitchat/ViewModels/ChatDeliveryCoordinator.swift @@ -238,6 +238,15 @@ private extension ChatDeliveryCoordinator { } if context.messages.count > indexedPublicMessageCount { + // Growth is only a pure append if the previously indexed tail kept + // its position; a middle insertion (out-of-order timestamp arrival) + // shifts it and invalidates every indexed location after the + // insertion point. + if indexedPublicMessageCount > 0, + context.messages[indexedPublicMessageCount - 1].id != indexedPublicTailMessageID { + rebuildMessageLocationIndex() + return true + } for index in indexedPublicMessageCount.. indexedCount else { continue } + // Same append-only check as the public timeline above. + if indexedCount > 0, + messages[indexedCount - 1].id != indexedPrivateTailMessageIDs[peerID] { + rebuildMessageLocationIndex() + return true + } for index in indexedCount..