From 1598f48edd993bf53bca186bb1f11468e2b74612 Mon Sep 17 00:00:00 2001 From: Hampus Date: Wed, 2 Sep 2026 17:28:44 +0200 Subject: [PATCH] fix(guild): invalidate member sidebar on role changes (#2369) --- .../features/guild/events/GuildRoleCreate.ts | 2 + .../features/guild/events/GuildRoleDelete.ts | 2 + .../features/guild/events/GuildRoleUpdate.ts | 2 + .../guild/events/GuildRoleUpdateBulk.ts | 2 + .../features/member/state/MemberSidebar.ts | 44 +++++++++++++++++++ 5 files changed, 52 insertions(+) diff --git a/fluxer_app/src/features/guild/events/GuildRoleCreate.ts b/fluxer_app/src/features/guild/events/GuildRoleCreate.ts index 5277ccbfe..65eb0ed7a 100644 --- a/fluxer_app/src/features/guild/events/GuildRoleCreate.ts +++ b/fluxer_app/src/features/guild/events/GuildRoleCreate.ts @@ -3,6 +3,7 @@ import type {GatewayHandlerContext} from '@app/features/gateway/events/EventRouter'; import GuildReadState from '@app/features/guild/state/GuildReadState'; import Guilds from '@app/features/guild/state/Guilds'; +import MemberSidebar from '@app/features/member/state/MemberSidebar'; import Permission from '@app/features/permissions/state/Permission'; import type {GuildRole} from '@fluxer/schema/src/domains/guild/GuildRoleSchemas'; @@ -13,6 +14,7 @@ interface GuildRoleCreatePayload { export function handleGuildRoleCreate(data: GuildRoleCreatePayload, _context: GatewayHandlerContext): void { Guilds.handleGuildRoleCreate({guildId: data.guild_id, role: data.role}); + MemberSidebar.handleGuildStorageIdentityChange(data.guild_id); Permission.handleGuildRole(data.guild_id); GuildReadState.handleGuildUpdate(data.guild_id); } diff --git a/fluxer_app/src/features/guild/events/GuildRoleDelete.ts b/fluxer_app/src/features/guild/events/GuildRoleDelete.ts index e436dadb9..db5854141 100644 --- a/fluxer_app/src/features/guild/events/GuildRoleDelete.ts +++ b/fluxer_app/src/features/guild/events/GuildRoleDelete.ts @@ -5,6 +5,7 @@ import type {GatewayHandlerContext} from '@app/features/gateway/events/EventRout import GuildReadState from '@app/features/guild/state/GuildReadState'; import Guilds from '@app/features/guild/state/Guilds'; import GuildMembers from '@app/features/member/state/GuildMembers'; +import MemberSidebar from '@app/features/member/state/MemberSidebar'; import Permission from '@app/features/permissions/state/Permission'; interface GuildRoleDeletePayload { @@ -16,6 +17,7 @@ export function handleGuildRoleDelete(data: GuildRoleDeletePayload, _context: Ga Guilds.handleGuildRoleDelete({guildId: data.guild_id, roleId: data.role_id}); GuildMembers.handleGuildRoleDelete(data.guild_id, data.role_id); Channels.handleGuildRoleDelete({guildId: data.guild_id, roleId: data.role_id}); + MemberSidebar.handleGuildStorageIdentityChange(data.guild_id); Permission.handleGuildRole(data.guild_id); GuildReadState.handleGuildUpdate(data.guild_id); } diff --git a/fluxer_app/src/features/guild/events/GuildRoleUpdate.ts b/fluxer_app/src/features/guild/events/GuildRoleUpdate.ts index 98cc6b22f..4eee9cfda 100644 --- a/fluxer_app/src/features/guild/events/GuildRoleUpdate.ts +++ b/fluxer_app/src/features/guild/events/GuildRoleUpdate.ts @@ -3,6 +3,7 @@ import type {GatewayHandlerContext} from '@app/features/gateway/events/EventRouter'; import GuildReadState from '@app/features/guild/state/GuildReadState'; import Guilds from '@app/features/guild/state/Guilds'; +import MemberSidebar from '@app/features/member/state/MemberSidebar'; import Permission from '@app/features/permissions/state/Permission'; import type {GuildRole} from '@fluxer/schema/src/domains/guild/GuildRoleSchemas'; @@ -13,6 +14,7 @@ interface GuildRoleUpdatePayload { export function handleGuildRoleUpdate(data: GuildRoleUpdatePayload, _context: GatewayHandlerContext): void { Guilds.handleGuildRoleUpdate({guildId: data.guild_id, role: data.role}); + MemberSidebar.handleGuildStorageIdentityChange(data.guild_id); Permission.handleGuildRole(data.guild_id); GuildReadState.handleGuildUpdate(data.guild_id); } diff --git a/fluxer_app/src/features/guild/events/GuildRoleUpdateBulk.ts b/fluxer_app/src/features/guild/events/GuildRoleUpdateBulk.ts index d25252ddd..f2a5f99e1 100644 --- a/fluxer_app/src/features/guild/events/GuildRoleUpdateBulk.ts +++ b/fluxer_app/src/features/guild/events/GuildRoleUpdateBulk.ts @@ -3,6 +3,7 @@ import type {GatewayHandlerContext} from '@app/features/gateway/events/EventRouter'; import GuildReadState from '@app/features/guild/state/GuildReadState'; import Guilds from '@app/features/guild/state/Guilds'; +import MemberSidebar from '@app/features/member/state/MemberSidebar'; import Permission from '@app/features/permissions/state/Permission'; import type {GuildRole} from '@fluxer/schema/src/domains/guild/GuildRoleSchemas'; @@ -14,6 +15,7 @@ interface GuildRoleUpdateBulkPayload { export function handleGuildRoleUpdateBulk(data: GuildRoleUpdateBulkPayload, _context: GatewayHandlerContext): void { if (data.roles.length > 0) { Guilds.handleGuildRoleUpdateBulk({guildId: data.guild_id, roles: data.roles}); + MemberSidebar.handleGuildStorageIdentityChange(data.guild_id); } Permission.handleGuildRole(data.guild_id); GuildReadState.handleGuildUpdate(data.guild_id); diff --git a/fluxer_app/src/features/member/state/MemberSidebar.ts b/fluxer_app/src/features/member/state/MemberSidebar.ts index 29f84758c..edb377e1b 100644 --- a/fluxer_app/src/features/member/state/MemberSidebar.ts +++ b/fluxer_app/src/features/member/state/MemberSidebar.ts @@ -342,6 +342,50 @@ class MemberSidebar { } } + handleGuildStorageIdentityChange(guildId: string): void { + const subscribedChannels = this.listSubscribedChannelIds[guildId]; + if (subscribedChannels == null) { + return; + } + const existingGuildLists = this.lists[guildId] ?? {}; + const nextSubscribedChannels: Record = {}; + const changedStorageKeys = new Set(); + const rekeyedRequestedRanges = new Map(); + for (const [previousStorageKey, channelId] of Object.entries(subscribedChannels)) { + const storageKey = this.resolveStorageKey(guildId, channelId); + nextSubscribedChannels[storageKey] = channelId; + if (storageKey === previousStorageKey) { + continue; + } + changedStorageKeys.add(previousStorageKey); + changedStorageKeys.add(storageKey); + rekeyedRequestedRanges.set( + storageKey, + existingGuildLists[previousStorageKey]?.requestedRanges ?? EMPTY_MEMBER_LIST_RANGES, + ); + } + if (changedStorageKeys.size === 0) { + return; + } + for (const storageKey of changedStorageKeys) { + this.clearPendingListUpdateBatch(guildId, storageKey); + } + const guildLists: Record = {...existingGuildLists}; + for (const storageKey of changedStorageKeys) { + delete guildLists[storageKey]; + } + for (const [storageKey, requestedRanges] of rekeyedRequestedRanges) { + guildLists[storageKey] = this.createEmptyListState(requestedRanges); + } + this.lists = {...this.lists, [guildId]: guildLists}; + this.listSubscribedChannelIds = {...this.listSubscribedChannelIds, [guildId]: nextSubscribedChannels}; + this.syncedMemberListGuildIds.delete(guildId); + if (this.sentMemberListGuildId === guildId) { + this.clearSentMemberListSubscription(); + } + this.memberListSubscriptionGeneration += 1; + } + handleListUpdate(params: MemberListUpdateParams): void { const {guildId} = params; if (this.isMemberListUpdatesDisabled(guildId)) {