ui: don't match support chats in the unread filter when muted to "mute all"

supportUnreadCount never reads chatSettings, so the filter listed a group with
an unread support chat even when the user had muted it entirely - while the
profile picker's counter, which the button now reads, drops that group. Gate the
support half on enableNtfs != None so the two agree on a fully muted chat.

Mentions-only is deliberately left counting support unread. The counter takes a
mentions-only group's item only when it mentions the user; this predicate takes
any support unread, on the view that mentions-only is a statement about group
chatter and the user's own line to the admins is not chatter. "Mute all" is a
statement about the whole chat, so it applies.
This commit is contained in:
Narasimha-sc
2026-08-28 09:21:20 +00:00
parent ed5c861886
commit fa257d948b
8 changed files with 9 additions and 9 deletions
+1 -1
View File
@@ -1407,7 +1407,7 @@ final class Chat: ObservableObject, Identifiable, ChatLike {
}
}
var hasUnread: Bool { unreadTag || supportUnreadCount > 0 }
var hasUnread: Bool { unreadTag || (chatInfo.chatSettings?.enableNtfs != MsgFilter.none && supportUnreadCount > 0) }
public static var sampleData: Chat = Chat(chatInfo: ChatInfo.sampleData.direct, chatItems: [])
}
+1 -1
View File
@@ -163,7 +163,7 @@ Horizontal scrolling tab bar below the navigation bar. Tabs:
| Tab | Filter | Shows |
|-----|--------|-------|
| All | `nil` | All conversations |
| Unread | `.unread` | Conversations with unread messages or unread support chats |
| Unread | `.unread` | Conversations with unread messages, or unread support chats unless muted to "mute all" |
| Favorites | `.presetTag(.favorites)` | Favorited conversations |
| Groups | `.presetTag(.groups)` | Group conversations |
| Contacts | `.presetTag(.contacts)` | Direct conversations |
+1 -1
View File
@@ -330,7 +330,7 @@ struct ChatStats: Decodable, Hashable {
| `viewId` | Unique view identity including creation time | [L1338](../Shared/Model/ChatModel.swift#L1338) |
| `unreadTag` | Whether chat counts as "unread" based on notification settings | [L1328](../Shared/Model/ChatModel.swift#L1328) |
| `supportUnreadCount` | Unread count for group support scope | [L1340](../Shared/Model/ChatModel.swift#L1340) |
| `hasUnread` | Whether chat matches the unread filter: `unreadTag` or any support unread | [L1410](../Shared/Model/ChatModel.swift#L1410) |
| `hasUnread` | Whether chat matches the unread filter: `unreadTag`, or support unread when not muted to `.none` | [L1410](../Shared/Model/ChatModel.swift#L1410) |
---
@@ -1437,7 +1437,7 @@ data class Chat(
else -> 0
}
val hasUnread: Boolean get() = unreadTag || supportUnreadCount > 0
val hasUnread: Boolean get() = unreadTag || (chatInfo.chatSettings?.enableNtfs != None && supportUnreadCount > 0)
fun groupFeatureEnabled(feature: GroupFeature): Boolean =
if (chatInfo is ChatInfo.Group) {
@@ -65,7 +65,7 @@ Managed by `chatModel.userTags`, `chatModel.presetTags`, and `chatModel.activeCh
| Business | `BUSINESS` | Work | Business chat conversations |
| Notes | `NOTES` | Folder | Notes to self |
| Custom tags | `UserTag(ChatTag)` | Label/emoji | User-created tags with custom emoji and name |
| Unread | `ActiveFilter.Unread` | Filter list icon | Chats with unread messages or unread support chats (toggle via filter button) |
| Unread | `ActiveFilter.Unread` | Filter list icon | Chats with unread messages, or unread support chats unless muted to "mute all" (toggle via filter button) |
Display logic:
- When collapsible preset tags exceed 3 total (with user tags), they collapse into a `CollapsedTagsFilterView` dropdown menu
+1 -1
View File
@@ -143,7 +143,7 @@ The `filteredChats` function (line ~1188) applies filters in this order:
3. **Active filter:**
- `PresetTag`: Matches chat type and characteristics (e.g., `CONTACTS` filters `ChatInfo.Direct`, `GROUPS` filters `ChatInfo.Group`).
- `UserTag`: Matches chats whose `chatTags` contain the tag ID.
- `Unread`: Matches `Chat.hasUnread` — unread messages, as the chat's notification setting counts them, or unread support chats.
- `Unread`: Matches `Chat.hasUnread` — unread messages, as the chat's notification setting counts them, or unread support chats unless the chat is muted to "mute all".
### Search Bar
+1 -1
View File
@@ -282,7 +282,7 @@ data class ChatStats(
| `id` | 1349 | Chat ID derived from `chatInfo.id` |
| `unreadTag` | 1343 | Whether chat counts as "unread" for tag filtering (considers notification settings) |
| `supportUnreadCount` | 1351 | Unread count in support/moderation context |
| `hasUnread` | 1440 | Whether chat matches the unread filter: `unreadTag` or any support unread |
| `hasUnread` | 1440 | Whether chat matches the unread filter: `unreadTag`, or support unread when not muted to `None` |
| `nextSendGrpInv` | 1337 | Whether next message should send group invitation |
<a id="ChatInfo"></a>
@@ -28,7 +28,7 @@ Two consequences of taking `supportUnreadCount` whole are deliberate.
*Pending members count, and that signal is sticky.* For a moderator `supportUnreadCount` is `membersRequireAttention`, and the core computes `gmRequiresAttention` as `memberPending m || memberAttention > 0 || mentions > 0` (`Types.hs`) — so a member awaiting approval or review counts even with nothing to read. `updateSupportChatItemsRead` (`Store/Messages.hs`) zeroes the support counters but decrements the group's count only when the member stops requiring attention, which a pending member never does; approving or rejecting is what clears it. A moderator with a pending join request therefore sees the button tinted and the group listed under Unread with no unread badge until they act on the request. That is accepted: the member is genuinely waiting on the moderator, and the filter is where they will look for who is waiting. It does disagree with the chat list row, which renders `memberPending` as a grey flag rather than a call to action.
*Mute does not suppress the support half.* `unreadTag` gates on `enableNtfs`; `supportUnreadCount` never reads `chatSettings`, and this predicate does not add that gate. Muting a group silences its conversation but not its support chats — a message to or from admins still lights the button. That is intentional: mute is about group chatter, not about the user's own line to the people running the group.
*Mute all suppresses the support half; mentions-only does not.* `supportUnreadCount` never reads `chatSettings`, so the predicate adds the gate: `enableNtfs != None && supportUnreadCount > 0`. A group muted to *mute all* therefore drops out of the filter even with a support chat waiting, matching what the profile picker's counter does with it. Under *mentions only* the support half still counts, which is where the filter and the counter part company — the counter takes a mentions-only group's item only when it mentions the user, and this predicate takes any support unread. That is deliberate: mentions-only is a statement about group chatter, and the user's own line to the admins is not chatter; *mute all* is a statement about the whole chat.
Support chats belong in this predicate because `unreadTag` alone cannot see them. `chatStats.unreadCount` is filled by a query scoped to the main conversation (`group_scope_tag IS NULL AND group_scope_group_member_id IS NULL`, `Store/Messages.hs`), so a group whose only unread sits in a member support scope satisfies neither `unreadTag` nor the filter. There was no way to reach the waiting member from the chat list.
@@ -41,7 +41,7 @@ The tint is `users.any { it.user.activeUser && it.unreadCount > 0 }` — the sam
This is deliberately *not* the predicate the filter matches on, so the two can disagree. Where they do:
- **Marked unread.** `unread_chat` is a column on `contacts`/`groups`, and `getUsersInfo` never reads it. A chat you marked unread by hand lists under the filter with the button grey.
- **Muted chats.** The counter's group clause is `enable_ntfs = 1 OR IS NULL OR (enable_ntfs = 2 AND user_mention = 1)` (`MFNone = 0`, `MFAll = 1`, `MFMentions = 2`, `Types.hs`). So under *mute all* no support item counts, and under *mentions only* a support item counts only if it mentions the user an ordinary support message does not. Either way the group lists under the filter, which ignores mute for the support half, without lighting the button. The contact clause is stricter still: `enable_ntfs = 1 OR IS NULL`, with no mentions term.
- **Mentions-only chats.** The counter's group clause is `enable_ntfs = 1 OR IS NULL OR (enable_ntfs = 2 AND user_mention = 1)` (`MFNone = 0`, `MFAll = 1`, `MFMentions = 2`, `Types.hs`). The two now agree on *mute all* — neither counts it — but under *mentions only* the counter takes a support item only when it mentions the user, while the filter takes any support unread. So an ordinary support message in a mentions-only group lists under the filter without lighting the button. The contact clause is stricter still: `enable_ntfs = 1 OR IS NULL`, with no mentions term.
- **Pending members.** The counter counts messages, so a member awaiting approval with nothing written lists under the filter without lighting the button. The stickiness described above therefore never reaches the tint.
Accepted: the button is a cue that something is worth a tap, matching the count the user already recognises from the picker, and the filter is the authority on what is actually listed.