fix(members): show mobile badges and fresh profiles in lists (#3370)

This commit is contained in:
Hampus authored and GitHub committed 2026-10-10 17:00:30 +02:00
1 parent 0f0ff5f3df
commit c14b7d4cb3
13 files changed
+171 -10

No files matched your search

@@ -34,6 +34,7 @@ export function useThreadRosterSubscription(thread: Channel | undefined): void {
interface RosterRow {
user: User;
status: StatusType;
mobile?: boolean;
customStatus: CustomStatus | null;
}
@@ -66,7 +67,7 @@ const RosterGroup = observer(({id, label, members, thread, ownerId}: RosterGroup
</span>
</div>
<div className={styles.membersList} data-flx="threads.thread-members-panel.roster-group.members-list">
{members.map(({user, status, customStatus}) => (
{members.map(({user, status, mobile, customStatus}) => (
<MemberListItem
key={user.id}
user={user}
@@ -74,6 +75,7 @@ const RosterGroup = observer(({id, label, members, thread, ownerId}: RosterGroup
guildId={thread.guildId}
guildMember={thread.guildId ? (GuildMembers.getMember(thread.guildId, user.id) ?? undefined) : undefined}
status={status}
isMobile={mobile}
customStatus={customStatus}
isOwner={user.id === ownerId}
disableBackdrop={true}
@@ -95,7 +97,7 @@ function resolveMembers(guildId: string | undefined, roster: ReadonlyArray<Threa
const status = LocalPresence.getStatus();
members.push({user, status, customStatus: isOfflineStatus(status) ? null : LocalPresence.customStatus});
} else {
members.push({user, status: entry.status, customStatus: entry.customStatus});
members.push({user, status: entry.status, mobile: entry.mobile, customStatus: entry.customStatus});
}
}
members.sort((a, b) =>
@@ -53,7 +53,7 @@ vi.mock('@app/features/ui/commands/ContextMenuCommands', () => ({close: vi.fn()}
vi.mock('@app/features/ui/commands/PopoutCommands', () => ({closeAll: vi.fn()}));
vi.mock('@app/features/guild/state/Guilds', () => ({default: {getGuild: () => ({ownerId: 'owner', mfaLevel: 0})}}));
vi.mock('@app/features/member/state/GuildMembers', () => ({
default: {getMember: () => null, hydrateIfMissing: () => {}},
default: {getMember: () => null, hydrateOrRefresh: () => {}},
}));
vi.mock('@app/features/permissions/state/Permission', () => ({
default: {
@@ -616,7 +616,7 @@ describe('thread roster', () => {
const release = ThreadRoster.subscribe(GUILD, THREAD_A);
ThreadRoster.handleListUpdate(
listUpdate([
{user_id: ONLINE, presence: {status: 'online', afk: false, mobile: false, custom_status: null}},
{user_id: ONLINE, presence: {status: 'online', afk: false, mobile: true, custom_status: null}},
{
user_id: IDLE,
presence: {
@@ -630,14 +630,15 @@ describe('thread roster', () => {
]),
);
expect(ThreadRoster.getMembers(THREAD_A)).toEqual([
{userId: ONLINE, joinTimestamp: null, status: 'online', customStatus: null},
{userId: ONLINE, joinTimestamp: null, status: 'online', mobile: true, customStatus: null},
{
userId: IDLE,
joinTimestamp: null,
status: 'idle',
mobile: false,
customStatus: expect.objectContaining({text: 'im spooky goop', emojiName: 'nahua_smug'}),
},
{userId: OFFLINE, joinTimestamp: null, status: 'offline', customStatus: null},
{userId: OFFLINE, joinTimestamp: null, status: 'offline', mobile: false, customStatus: null},
]);
release();
});
@@ -652,7 +653,7 @@ describe('thread roster', () => {
);
ThreadRoster.handleListUpdate(listUpdate([{user_id: ONLINE, presence: {status: 'dnd'}}]));
expect(ThreadRoster.getMembers(THREAD_A)).toEqual([
{userId: ONLINE, joinTimestamp: null, status: 'dnd', customStatus: null},
{userId: ONLINE, joinTimestamp: null, status: 'dnd', mobile: false, customStatus: null},
]);
release();
ThreadRoster.handleListUpdate(listUpdate([{user_id: ONLINE, presence: {status: 'online'}}]));
@@ -20,7 +20,11 @@ export interface ThreadMemberListPayload {
join_timestamp: string | null;
flags: number;
member: GuildMemberData | null;
presence?: {status?: string | null; custom_status?: GatewayCustomStatusPayload | null} | null;
presence?: {
status?: string | null;
mobile?: boolean;
custom_status?: GatewayCustomStatusPayload | null;
} | null;
}>;
}
@@ -28,6 +32,7 @@ export interface ThreadRosterMember {
readonly userId: string;
readonly joinTimestamp: string | null;
readonly status: StatusType;
readonly mobile: boolean;
readonly customStatus: CustomStatus | null;
}
@@ -78,13 +83,14 @@ class ThreadRoster {
const members: Array<ThreadRosterMember> = [];
for (const entry of payload.members) {
if (entry.member) {
GuildMembers.hydrateIfMissing(payload.guild_id, entry.member);
GuildMembers.hydrateOrRefresh(payload.guild_id, entry.member);
}
const status = normalizeStatus(entry.presence?.status);
members.push({
userId: entry.user_id,
joinTimestamp: entry.join_timestamp,
status,
mobile: !isOfflineStatus(status) && entry.presence?.mobile === true,
customStatus: isOfflineStatus(status) ? null : fromGatewayCustomStatus(entry.presence?.custom_status),
});
}