From fb849aa2c2a62a4aeb8d834b25903685d24aa44d Mon Sep 17 00:00:00 2001 From: Narasimha-sc <166327228+Narasimha-sc@users.noreply.github.com> Date: Thu, 18 Jun 2026 16:13:21 +0000 Subject: [PATCH] ios: clamp unread at the apply point, not the debounced call site 342e270a1 clamped removeChatItem's decrement by reading unreadCount synchronously, but UnreadCollector debounces ~1s, so the read saw a stale value: receiving an unread item then deleting it within the window skipped the -1 and left the badge +1 too high - a regression, since the unconditional -1 had netted to 0 in the collector. Revert that call-site clamp and instead clamp in changeUnreadCounter(chatIndex:) - the point the accumulated delta is applied (its only caller is the collector flush): clamp the per-chat count at 0 and feed the badge the clamped delta. Correct regardless of debounce ordering, and still prevents the original negative drift. --- apps/ios/Shared/Model/ChatModel.swift | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/apps/ios/Shared/Model/ChatModel.swift b/apps/ios/Shared/Model/ChatModel.swift index 7a46c8764f..c95d62861f 100644 --- a/apps/ios/Shared/Model/ChatModel.swift +++ b/apps/ios/Shared/Model/ChatModel.swift @@ -795,15 +795,14 @@ final class ChatModel: ObservableObject { // update chat list // memberPending: a support item may be the main-list preview, clear it on delete/moderate (like addChatItem) if cInfo.groupChatScope() == nil || cInfo.groupInfo?.membership.memberPending ?? false { - let chat = getChat(cInfo.id) - // clamp: only decrement when there is something to decrement (matches the Android primary-context - // decrement), so deleting an item that never incremented the badge can't drive it negative - if cItem.isRcvNew, let chat, chat.chatStats.unreadCount > 0 { + if cItem.isRcvNew { unreadCollector.changeUnreadCounter(cInfo.id, by: -1, unreadMentions: cItem.meta.userMention ? -1 : 0) } // update previews - if let chat, let pItem = chat.chatItems.last, pItem.id == cItem.id { - chat.chatItems = [ChatItem.deletedItemDummy()] + if let chat = getChat(cInfo.id) { + if let pItem = chat.chatItems.last, pItem.id == cItem.id { + chat.chatItems = [ChatItem.deletedItemDummy()] + } } } // remove from current scope @@ -1110,10 +1109,13 @@ final class ChatModel: ObservableObject { func changeUnreadCounter(_ chatIndex: Int, by count: Int, unreadMentions: Int) { let wasUnread = chats[chatIndex].unreadTag let stats = chats[chatIndex].chatStats - chats[chatIndex].chatStats.unreadCount = stats.unreadCount + count - chats[chatIndex].chatStats.unreadMentions = stats.unreadMentions + unreadMentions + // clamp at 0 so an over-decrement can't drive the per-chat count or the user badge negative + // (e.g. deleting an item that never incremented the badge); feed the badge the clamped delta + let newUnreadCount = max(stats.unreadCount + count, 0) + chats[chatIndex].chatStats.unreadCount = newUnreadCount + chats[chatIndex].chatStats.unreadMentions = max(stats.unreadMentions + unreadMentions, 0) ChatTagsModel.shared.updateChatTagRead(chats[chatIndex], wasUnread: wasUnread) - changeUnreadCounter(user: currentUser!, by: count) + changeUnreadCounter(user: currentUser!, by: newUnreadCount - stats.unreadCount) } func increaseUnreadCounter(user: any UserLike) {