From cb4c847d416c24fdc4c0e06b051ead7150c00826 Mon Sep 17 00:00:00 2001 From: Hampus Date: Thu, 27 Aug 2026 02:35:56 +0200 Subject: [PATCH] fix(app): separate unread state from unread counts --- .../app/components/layout/ChannelItem.tsx | 2 + .../components/layout/ChannelListContent.tsx | 2 + .../layout/FavoritesChannelListContent.tsx | 2 + .../app/components/layout/GuildsLayout.tsx | 1 + .../layout/sidebar_nav/GuildListDMItem.tsx | 1 + .../layout/utils/ChannelUnreadState.test.ts | 28 +++++ .../layout/utils/ChannelUnreadState.ts | 3 + .../utils/ChannelUnreadStateMachine.test.ts | 4 + .../layout/utils/ChannelUnreadStateMachine.ts | 2 +- .../components/direct_message/DMListItem.tsx | 1 + .../read_state/state/ReadStates.test.ts | 114 ++++++++++++++++++ .../features/read_state/state/ReadStates.ts | 27 ++--- .../state/read_states/ReadStateEntry.ts | 2 +- .../state/QuickSwitcherCandidateBuilder.ts | 5 +- .../ui/action_menu/items/GuildMenuData.tsx | 2 +- .../ui/action_menu/items/GuildMenuItems.tsx | 2 +- 16 files changed, 175 insertions(+), 23 deletions(-) create mode 100644 fluxer_app/src/features/read_state/state/ReadStates.test.ts diff --git a/fluxer_app/src/features/app/components/layout/ChannelItem.tsx b/fluxer_app/src/features/app/components/layout/ChannelItem.tsx index 7c83d5407..fd91ac116 100644 --- a/fluxer_app/src/features/app/components/layout/ChannelItem.tsx +++ b/fluxer_app/src/features/app/components/layout/ChannelItem.tsx @@ -230,6 +230,7 @@ export const ChannelItem = observer( const isVoiceDragActive = draggingChannel?.channelType === ChannelTypes.GUILD_VOICE; const shouldDimForVoiceDrag = Boolean(isVoiceDragActive && channelIsText && channel.parentId !== null); const unreadCount = ReadStates.getUnreadCount(channel.id); + const hasUnread = ReadStates.hasUnread(channel.id); const connectedVoiceGuildId = channelIsVoice ? MediaEngine.guildId : null; const connectedVoiceChannelId = channelIsVoice ? MediaEngine.channelId : null; const canManageChannels = Permission.can(Permissions.MANAGE_CHANNELS, channel); @@ -265,6 +266,7 @@ export const ChannelItem = observer( type: channel.type, }); const unreadState = getChannelUnreadState({ + hasUnread, unreadCount, mentionCount, isMuted: isChannelDirectlyMuted, diff --git a/fluxer_app/src/features/app/components/layout/ChannelListContent.tsx b/fluxer_app/src/features/app/components/layout/ChannelListContent.tsx index b364313a4..52864c2d5 100644 --- a/fluxer_app/src/features/app/components/layout/ChannelListContent.tsx +++ b/fluxer_app/src/features/app/components/layout/ChannelListContent.tsx @@ -335,6 +335,7 @@ export const ChannelListContent = observer(({guild, scrollY}: {guild: Guild; scr ); const hasVisibleUnreadInChannel = (channelId: string): boolean => { const unreadCount = ReadStates.getUnreadCount(channelId); + const hasUnread = ReadStates.hasUnread(channelId); const mentionCount = ReadStates.getMentionCount(channelId); const isMuted = UserGuildSettings.isParentCategoryMuted(guild.id, channelId) || @@ -349,6 +350,7 @@ export const ChannelListContent = observer(({guild, scrollY}: {guild: Guild; scr }) : null; const unreadState = getChannelUnreadState({ + hasUnread, unreadCount, mentionCount, isMuted, diff --git a/fluxer_app/src/features/app/components/layout/FavoritesChannelListContent.tsx b/fluxer_app/src/features/app/components/layout/FavoritesChannelListContent.tsx index 684d6c4fa..022c21b56 100644 --- a/fluxer_app/src/features/app/components/layout/FavoritesChannelListContent.tsx +++ b/fluxer_app/src/features/app/components/layout/FavoritesChannelListContent.tsx @@ -197,6 +197,7 @@ const FavoriteChannelResolvedItem = observer( ); const refs = useMergeRefs([dragConnectorRef, dropConnectorRef, elementRef]); const unreadCount = ReadStates.getUnreadCount(channel.id); + const hasUnread = ReadStates.hasUnread(channel.id); const mentionCount = ReadStates.getMentionCount(channel.id); const isGroupDM = channel.isGroupDM(); const isDM = channel.isDM(); @@ -220,6 +221,7 @@ const FavoriteChannelResolvedItem = observer( }) : null; const unreadState = getChannelUnreadState({ + hasUnread, unreadCount, mentionCount, isMuted, diff --git a/fluxer_app/src/features/app/components/layout/GuildsLayout.tsx b/fluxer_app/src/features/app/components/layout/GuildsLayout.tsx index f12f101f5..74514e23b 100644 --- a/fluxer_app/src/features/app/components/layout/GuildsLayout.tsx +++ b/fluxer_app/src/features/app/components/layout/GuildsLayout.tsx @@ -663,6 +663,7 @@ function resolveGuildTargetBounds({ function getDMScrollIndicatorSeverity(channelId: string): ScrollIndicatorSeverity | null { const mentionCount = ReadStates.getPrivateChannelMentionCount(channelId); const unreadState = getChannelUnreadState({ + hasUnread: ReadStates.hasUnreadPrivateChannel(channelId), unreadCount: ReadStates.getPrivateChannelUnreadCount(channelId), mentionCount, isMuted: UserGuildSettings.isChannelDirectlyMuted(null, channelId), diff --git a/fluxer_app/src/features/app/components/layout/sidebar_nav/GuildListDMItem.tsx b/fluxer_app/src/features/app/components/layout/sidebar_nav/GuildListDMItem.tsx index f802ded0c..28a21a66b 100644 --- a/fluxer_app/src/features/app/components/layout/sidebar_nav/GuildListDMItem.tsx +++ b/fluxer_app/src/features/app/components/layout/sidebar_nav/GuildListDMItem.tsx @@ -70,6 +70,7 @@ const ACTIVE_CALL_DESCRIPTOR = msg({ export function resolveDMListItemUnreadState(channelId: string): ChannelUnreadState { return getChannelUnreadState({ + hasUnread: ReadStates.hasUnread(channelId), unreadCount: ReadStates.getUnreadCount(channelId), mentionCount: ReadStates.getMentionCount(channelId), isMuted: UserGuildSettings.isChannelDirectlyMuted(null, channelId), diff --git a/fluxer_app/src/features/app/components/layout/utils/ChannelUnreadState.test.ts b/fluxer_app/src/features/app/components/layout/utils/ChannelUnreadState.test.ts index 28ac7ca7f..8e4d7ec1a 100644 --- a/fluxer_app/src/features/app/components/layout/utils/ChannelUnreadState.test.ts +++ b/fluxer_app/src/features/app/components/layout/utils/ChannelUnreadState.test.ts @@ -7,6 +7,7 @@ import {getChannelUnreadState} from './ChannelUnreadState'; describe('getChannelUnreadState', () => { it('shows a normal unread indicator for all-messages unread badges', () => { const state = getChannelUnreadState({ + hasUnread: true, unreadCount: 3, mentionCount: 0, isMuted: false, @@ -19,6 +20,7 @@ describe('getChannelUnreadState', () => { }); it('shows a muted unread indicator for only-mentions unread badges without highlighting the channel', () => { const state = getChannelUnreadState({ + hasUnread: true, unreadCount: 3, mentionCount: 0, isMuted: false, @@ -31,6 +33,7 @@ describe('getChannelUnreadState', () => { }); it('hides unread and mention surfaces when unread badges are disabled', () => { const state = getChannelUnreadState({ + hasUnread: true, unreadCount: 3, mentionCount: 1, isMuted: false, @@ -43,6 +46,7 @@ describe('getChannelUnreadState', () => { }); it('keeps legacy muted-channel fading for channels without an unread-badges level', () => { const hiddenState = getChannelUnreadState({ + hasUnread: true, unreadCount: 3, mentionCount: 0, isMuted: true, @@ -50,6 +54,7 @@ describe('getChannelUnreadState', () => { unreadBadgesLevel: null, }); const fadedState = getChannelUnreadState({ + hasUnread: true, unreadCount: 3, mentionCount: 0, isMuted: true, @@ -60,4 +65,27 @@ describe('getChannelUnreadState', () => { expect(fadedState.shouldShowUnreadIndicator).toBe(true); expect(fadedState.isUnreadIndicatorMuted).toBe(true); }); + it('shows the indicator from the unread flag rather than the message count', () => { + const state = getChannelUnreadState({ + hasUnread: true, + unreadCount: 0, + mentionCount: 0, + isMuted: false, + showFadedUnreadOnMutedChannels: false, + unreadBadgesLevel: null, + }); + expect(state.shouldShowUnreadIndicator).toBe(true); + }); + it('hides the indicator for a read channel even if a stale count survives', () => { + const state = getChannelUnreadState({ + hasUnread: false, + unreadCount: 7, + mentionCount: 0, + isMuted: false, + showFadedUnreadOnMutedChannels: false, + unreadBadgesLevel: null, + }); + expect(state.shouldShowUnreadIndicator).toBe(false); + expect(state.hasVisibleUnread).toBe(false); + }); }); diff --git a/fluxer_app/src/features/app/components/layout/utils/ChannelUnreadState.ts b/fluxer_app/src/features/app/components/layout/utils/ChannelUnreadState.ts index 36b3a18a6..e11d62082 100644 --- a/fluxer_app/src/features/app/components/layout/utils/ChannelUnreadState.ts +++ b/fluxer_app/src/features/app/components/layout/utils/ChannelUnreadState.ts @@ -3,6 +3,7 @@ import {resolveChannelUnreadState} from './ChannelUnreadStateMachine'; export interface ChannelUnreadStateInput { + hasUnread: boolean; unreadCount: number; mentionCount: number; isMuted: boolean; @@ -20,6 +21,7 @@ export interface ChannelUnreadState { } export function getChannelUnreadState({ + hasUnread, unreadCount, mentionCount, isMuted, @@ -27,6 +29,7 @@ export function getChannelUnreadState({ unreadBadgesLevel, }: ChannelUnreadStateInput): ChannelUnreadState { return resolveChannelUnreadState({ + hasUnread, unreadCount, mentionCount, isMuted, diff --git a/fluxer_app/src/features/app/components/layout/utils/ChannelUnreadStateMachine.test.ts b/fluxer_app/src/features/app/components/layout/utils/ChannelUnreadStateMachine.test.ts index 38c616bed..52d52b2a0 100644 --- a/fluxer_app/src/features/app/components/layout/utils/ChannelUnreadStateMachine.test.ts +++ b/fluxer_app/src/features/app/components/layout/utils/ChannelUnreadStateMachine.test.ts @@ -12,6 +12,7 @@ import { function input(overrides: Partial = {}): ChannelUnreadStateInput { return { + hasUnread: false, unreadCount: 0, mentionCount: 0, isMuted: false, @@ -38,6 +39,7 @@ describe('channelUnreadStateMachine', () => { const snapshot = createChannelUnreadSnapshot( input({ unreadBadgesLevel: MessageNotifications.NO_MESSAGES, + hasUnread: true, unreadCount: 1, mentionCount: 1, }), @@ -55,6 +57,7 @@ describe('channelUnreadStateMachine', () => { it('transitions without preserving stale policy output', () => { const legacySnapshot = createChannelUnreadSnapshot( input({ + hasUnread: true, unreadCount: 2, isMuted: true, showFadedUnreadOnMutedChannels: false, @@ -66,6 +69,7 @@ describe('channelUnreadStateMachine', () => { type: 'channelUnread.updated', input: input({ unreadBadgesLevel: MessageNotifications.ALL_MESSAGES, + hasUnread: true, unreadCount: 2, isMuted: true, }), diff --git a/fluxer_app/src/features/app/components/layout/utils/ChannelUnreadStateMachine.ts b/fluxer_app/src/features/app/components/layout/utils/ChannelUnreadStateMachine.ts index f4102f05b..9f081e453 100644 --- a/fluxer_app/src/features/app/components/layout/utils/ChannelUnreadStateMachine.ts +++ b/fluxer_app/src/features/app/components/layout/utils/ChannelUnreadStateMachine.ts @@ -86,7 +86,7 @@ export function transitionChannelUnreadSnapshot( export function selectChannelUnreadState(snapshot: ChannelUnreadSnapshot): ChannelUnreadState { const context = snapshot.context; - const hasUnreadMessages = context.unreadCount > 0; + const hasUnreadMessages = context.hasUnread; const rawHasMentions = context.mentionCount > 0; switch (getUnreadStateValue(snapshot)) { case 'disabled': diff --git a/fluxer_app/src/features/channel/components/direct_message/DMListItem.tsx b/fluxer_app/src/features/channel/components/direct_message/DMListItem.tsx index bc4290c7c..a0b73316a 100644 --- a/fluxer_app/src/features/channel/components/direct_message/DMListItem.tsx +++ b/fluxer_app/src/features/channel/components/direct_message/DMListItem.tsx @@ -135,6 +135,7 @@ const ResolvedDMListItem = observer(function ResolvedDMListItem({ const isMuted = UserGuildSettings.isChannelDirectlyMuted(null, channel.id); const mentionCount = ReadStates.getMentionCount(channel.id); const unreadState = getChannelUnreadState({ + hasUnread: ReadStates.hasUnread(channel.id), unreadCount: ReadStates.getUnreadCount(channel.id), mentionCount, isMuted, diff --git a/fluxer_app/src/features/read_state/state/ReadStates.test.ts b/fluxer_app/src/features/read_state/state/ReadStates.test.ts new file mode 100644 index 000000000..c0d77b908 --- /dev/null +++ b/fluxer_app/src/features/read_state/state/ReadStates.test.ts @@ -0,0 +1,114 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + +import {beforeEach, describe, expect, it, vi} from 'vitest'; + +const makeChannel = (id: string) => ({ + id, + type: 0, + guildId: 'guild-1', + isPrivate: () => false, + getGuildId: () => 'guild-1', +}); +const loadedMessages: Array<{id: string; author: {id: string}}> = []; +let hasMoreBefore = false; +let hasNewestMessages = true; + +vi.mock('@app/features/channel/state/Channels', () => ({ + default: {getChannel: (id: string) => makeChannel(id), getBasicChannel: (id: string) => makeChannel(id)}, +})); +vi.mock('@app/features/messaging/state/MessagingMessages', () => ({ + default: { + getMessages: () => ({ + get hasMoreBefore() { + return hasMoreBefore; + }, + get length() { + return loadedMessages.length; + }, + jumpDestinationId: null, + hasNewestMessages: () => hasNewestMessages, + has: (id: string) => loadedMessages.some((m) => m.id === id), + last: () => loadedMessages[loadedMessages.length - 1], + forEachBuffered: (cb: (m: unknown) => void) => { + for (const m of loadedMessages) cb(m); + }, + }), + }, +})); +vi.mock('@app/features/user/state/Users', () => ({default: {getCurrentUser: () => ({id: 'me'})}})); +vi.mock('@app/features/relationship/state/Relationships', () => ({default: {isBlocked: () => false}})); +vi.mock('@app/features/member/state/GuildMembers', () => ({default: {getMember: () => null}})); +vi.mock('@app/features/user/state/UserGuildSettings', () => ({ + default: { + isEveryoneMentionSuppressed: () => false, + isRoleMentionSuppressed: () => false, + isGuildOrChannelMuted: () => false, + }, +})); +vi.mock('@app/features/ui/state/Dimension', () => ({default: {channelPinnedToEnd: () => false}})); +vi.mock('@app/features/notification/state/NotificationAutoAck', () => ({ + default: {isAutomaticAckEnabled: () => false}, +})); +vi.mock('@app/features/platform/transport/RestTransport', () => ({http: {post: vi.fn(), get: vi.fn()}})); + +const {default: ReadStates} = await import('@app/features/read_state/state/ReadStates'); + +const ID = { + ack: '1519773906704011264', + newer: '1519773906708205568', +}; + +let nextChannelId = 0; + +function seedReadChannel() { + const channelId = `channel-${++nextChannelId}`; + const state = ReadStates.get(channelId); + state.readStateKnown = true; + state.ackMessageId = ID.ack; + state.lastMessageId = ID.ack; + state.unreadCount = 0; + state.oldestUnreadMessageId = null; + return {channelId, state}; +} + +describe('ReadStates unread invariant', () => { + beforeEach(() => { + loadedMessages.length = 0; + hasMoreBefore = false; + hasNewestMessages = true; + }); + + it('never reports a positive unread count without an unread anchor after a passive update', () => { + const {channelId} = seedReadChannel(); + ReadStates.handlePassiveLastMessageUpdates({[channelId]: ID.newer}, 'guild-1'); + const count = ReadStates.getUnreadCount(channelId); + const anchor = ReadStates.getVisualUnreadMessageId(channelId); + expect(count > 0).toBe(anchor != null); + }); + + it('keeps the channel unread for the sidebar even with no anchor to draw a divider at', () => { + const {channelId} = seedReadChannel(); + ReadStates.handlePassiveLastMessageUpdates({[channelId]: ID.newer}, 'guild-1'); + expect(ReadStates.hasUnread(channelId)).toBe(true); + }); + + it('clears a stale unread once the server walks the last message id back', () => { + const {channelId} = seedReadChannel(); + ReadStates.handlePassiveLastMessageUpdates({[channelId]: ID.newer}, 'guild-1'); + expect(ReadStates.hasUnread(channelId)).toBe(true); + ReadStates.handlePassiveLastMessageUpdates({[channelId]: ID.ack}, 'guild-1'); + expect(ReadStates.hasUnread(channelId)).toBe(false); + expect(ReadStates.getUnreadCount(channelId)).toBe(0); + expect(ReadStates.getVisualUnreadMessageId(channelId)).toBeNull(); + }); + + it('anchors the divider when a window is loaded whose ack sits outside it', () => { + const {channelId, state} = seedReadChannel(); + state.lastMessageId = ID.newer; + hasMoreBefore = true; + loadedMessages.push({id: ID.newer, author: {id: 'someone'}}); + ReadStates.handleLoadMessages({channelId, messages: []}); + expect(ReadStates.getVisualUnreadMessageId(channelId)).toBe(ID.newer); + expect(ReadStates.getUnreadCount(channelId) > 0).toBe(true); + }); +}); diff --git a/fluxer_app/src/features/read_state/state/ReadStates.ts b/fluxer_app/src/features/read_state/state/ReadStates.ts index db30db94a..f9288e1d7 100644 --- a/fluxer_app/src/features/read_state/state/ReadStates.ts +++ b/fluxer_app/src/features/read_state/state/ReadStates.ts @@ -141,16 +141,11 @@ class ReadStates { this.refreshMentionChannel(state.channelId); } - private refreshUnreadEstimate(state: ReadStateEntry): void { + private clearUnreadStateIfRead(state: ReadStateEntry): void { if (!state.hasUnread()) { state.estimated = false; state.unreadCount = 0; state.oldestUnreadMessageId = null; - return; - } - if (state.unreadCount === 0) { - state.estimated = true; - state.unreadCount = Math.max(1, state.mentionCount); } } @@ -416,7 +411,7 @@ class ReadStates { if (!channelsWithReadState.has(channel.id as ChannelId)) { this.setMentionCount(state, 0); } - this.refreshUnreadEstimate(state); + this.clearUnreadStateIfRead(state); } this.notifyChange(undefined, {global: true}); }); @@ -436,7 +431,7 @@ class ReadStates { state.lastMessageId = channel.last_message_id ?? null; state.lastPinTimestamp = parseTimestamp(channel.last_pin_timestamp); state.storedGuildId = action.guild.id; - this.refreshUnreadEstimate(state); + this.clearUnreadStateIfRead(state); this.refreshMentionChannel(channel.id); } } @@ -452,9 +447,11 @@ class ReadStates { if (newestMessage != null && isNewerMessageId(newestMessage.id, state.lastMessageId)) { state.lastMessageId = newestMessage.id; } - if (state.hasUnread()) { + const landedOnNewestWindow = messages.hasNewestMessages(); + const landedOnAck = state.ackMessageId != null && messages.jumpDestinationId === state.ackMessageId; + if (state.hasUnread() || landedOnNewestWindow || landedOnAck) { state.rebuild(); - this.refreshUnreadEstimate(state); + this.clearUnreadStateIfRead(state); } else if (action.isAfter && state.ackMessageId != null && messages.has(state.ackMessageId, true)) { state.unreadCount += action.messages.length; if (state.oldestUnreadMessageId == null) { @@ -564,7 +561,7 @@ class ReadStates { state.readStateKnown = true; state.ackMessageId = action.channel.last_message_id; } else if (GUILD_TEXT_BASED_CHANNEL_TYPES.has(action.channel.type) && state.hasUnread()) { - this.refreshUnreadEstimate(state); + this.clearUnreadStateIfRead(state); } this.notifyChange(action.channel.id); } @@ -586,10 +583,10 @@ class ReadStates { changed = state.guildId !== guildId; state.storedGuildId = guildId; } - if (isNewerMessageId(lastMessageId, state.lastMessageId)) { + if (lastMessageId !== state.lastMessageId) { state.lastMessageId = lastMessageId; changed = true; - this.refreshUnreadEstimate(state); + this.clearUnreadStateIfRead(state); } if (changed) { changedChannels.push(channelId as ChannelId); @@ -751,7 +748,7 @@ class ReadStates { this.setMentionCount(state, mentionCount); } if (decision.shouldRefreshUnreadEstimate) { - this.refreshUnreadEstimate(state); + this.clearUnreadStateIfRead(state); } if (decision.shouldNotify) { this.notifyChange(action.channelId); @@ -1046,7 +1043,7 @@ class ReadStates { state.serverVersion = readState.version ?? state.serverVersion; this.setMentionCount(state, readState.mention_count ?? 0); state.rebuild(null, {recomputeMentions: manual}); - this.refreshUnreadEstimate(state); + this.clearUnreadStateIfRead(state); this.notifyChange(readState.id); continue; } diff --git a/fluxer_app/src/features/read_state/state/read_states/ReadStateEntry.ts b/fluxer_app/src/features/read_state/state/read_states/ReadStateEntry.ts index b564294ab..a97c04d2f 100644 --- a/fluxer_app/src/features/read_state/state/read_states/ReadStateEntry.ts +++ b/fluxer_app/src/features/read_state/state/read_states/ReadStateEntry.ts @@ -274,7 +274,7 @@ export class ReadStateEntry { }); const hasUnreadBoundary = foundAckMessage || loadedOlderMessages || !messages.hasMoreBefore; const hasNewestMessages = messages.hasNewestMessages(); - this.estimated = !hasNewestMessages || !hasUnreadBoundary; + this.estimated = !hasNewestMessages || (!hasUnreadBoundary && messages.length === loadedUnreadCount); if (this.estimated) { this.unreadCount = Math.max(previousUnreadCount, loadedUnreadCount); } else { diff --git a/fluxer_app/src/features/search/state/QuickSwitcherCandidateBuilder.ts b/fluxer_app/src/features/search/state/QuickSwitcherCandidateBuilder.ts index 62c50ad3c..c9f37bc7e 100644 --- a/fluxer_app/src/features/search/state/QuickSwitcherCandidateBuilder.ts +++ b/fluxer_app/src/features/search/state/QuickSwitcherCandidateBuilder.ts @@ -61,10 +61,7 @@ function getChannelRecency(channel: {id: string; lastMessageId: string | null}): } function getChannelSortWeight(channelId: string, baseWeight: number): number { - const unreadCount = ReadStates.getUnreadCount(channelId); - const mentionCount = ReadStates.getMentionCount(channelId); - const hasUnread = unreadCount > 0 || mentionCount > 0; - return hasUnread ? baseWeight + UNREAD_SORT_WEIGHT_BOOST : baseWeight; + return ReadStates.isUnreadOrMentioned(channelId) ? baseWeight + UNREAD_SORT_WEIGHT_BOOST : baseWeight; } export function buildChannelCandidate( diff --git a/fluxer_app/src/features/ui/action_menu/items/GuildMenuData.tsx b/fluxer_app/src/features/ui/action_menu/items/GuildMenuData.tsx index 41c96f1d1..7116dba7b 100644 --- a/fluxer_app/src/features/ui/action_menu/items/GuildMenuData.tsx +++ b/fluxer_app/src/features/ui/action_menu/items/GuildMenuData.tsx @@ -182,7 +182,7 @@ export function useGuildMenuData(guild: Guild, options: UseGuildMenuDataOptions) () => ({ handleMarkAsRead: () => { const channelIds = channels - .filter((channel) => ReadStates.getUnreadCount(channel.id) > 0) + .filter((channel) => ReadStates.isUnreadOrMentioned(channel.id)) .map((channel) => channel.id); if (channelIds.length > 0) { void ReadStateCommands.bulkAckChannels(channelIds); diff --git a/fluxer_app/src/features/ui/action_menu/items/GuildMenuItems.tsx b/fluxer_app/src/features/ui/action_menu/items/GuildMenuItems.tsx index 305da2a25..5a5537abc 100644 --- a/fluxer_app/src/features/ui/action_menu/items/GuildMenuItems.tsx +++ b/fluxer_app/src/features/ui/action_menu/items/GuildMenuItems.tsx @@ -100,7 +100,7 @@ export const MarkAsReadMenuItem: React.FC = observer(({guild }, [channels]); const handleMarkAsRead = useCallback(() => { const channelIds = channels - .filter((channel) => ReadStates.getUnreadCount(channel.id) > 0) + .filter((channel) => ReadStates.isUnreadOrMentioned(channel.id)) .map((channel) => channel.id); if (channelIds.length > 0) { void ReadStateCommands.bulkAckChannels(channelIds);