refactor(web): unify DM sorting with sortDmChannels across all triggers

This commit is contained in:
Jannis Braun
2026-04-02 17:56:26 +02:00
parent afa583df1d
commit b77aad61eb
3 changed files with 28 additions and 15 deletions
+5 -8
View File
@@ -8,6 +8,7 @@ import { useSettingsStore } from '../stores/settingsStore';
import type { ServerEvent, ClientEvent, ActiveCallInfo, Activity } from '@backspace/shared'; import type { ServerEvent, ClientEvent, ActiveCallInfo, Activity } from '@backspace/shared';
import { resolveAssetUrl, normalizeUserAssets, normalizeMessageAssets } from '../utils/assetUrls'; import { resolveAssetUrl, normalizeUserAssets, normalizeMessageAssets } from '../utils/assetUrls';
import { broadcastVoiceStatus, broadcastDeafenViaLiveKit } from '../utils/voice'; import { broadcastVoiceStatus, broadcastDeafenViaLiveKit } from '../utils/voice';
import { sortDmChannels } from '../utils/dmSorting';
import { registerSelfId } from '../utils/identity'; import { registerSelfId } from '../utils/identity';
import { getActiveRoom } from './useLiveKit'; import { getActiveRoom } from './useLiveKit';
import { useUIStore } from '../stores/uiStore'; import { useUIStore } from '../stores/uiStore';
@@ -561,8 +562,8 @@ function handleEvent(origin: string, event: ServerEvent): void {
const updatedDms = currentDmChannels.map(dm => const updatedDms = currentDmChannels.map(dm =>
dm.id === existingDm.id ? { ...dm, lastMessage: event.message } : dm, dm.id === existingDm.id ? { ...dm, lastMessage: event.message } : dm,
); );
updatedDms.sort((a, b) => (b.lastMessage?.createdAt ?? b.createdAt) - (a.lastMessage?.createdAt ?? a.createdAt)); const { unreadChannels, currentChannelId } = useChatStore.getState();
setDms(updatedDms); setDms(sortDmChannels(updatedDms, unreadChannels, currentChannelId));
break; break;
} }
} }
@@ -582,12 +583,8 @@ function handleEvent(origin: string, event: ServerEvent): void {
? { ...dm, lastMessage: event.message } ? { ...dm, lastMessage: event.message }
: dm : dm
); );
updatedDms.sort((a, b) => { const { unreadChannels: unread, currentChannelId: curCh } = useChatStore.getState();
const aTime = a.lastMessage?.createdAt ?? a.createdAt; setDms(sortDmChannels(updatedDms, unread, curCh));
const bTime = b.lastMessage?.createdAt ?? b.createdAt;
return bTime - aTime;
});
setDms(updatedDms);
} }
{ {
const { currentChannelId, markChannelUnread } = useChatStore.getState(); const { currentChannelId, markChannelUnread } = useChatStore.getState();
+15 -6
View File
@@ -4,6 +4,7 @@ import { wsSend } from '../hooks/useWebSocket';
import { isDmChannel, getChannelOrigin, getApiForOrigin, useSpaceStore } from './spaceStore'; import { isDmChannel, getChannelOrigin, getApiForOrigin, useSpaceStore } from './spaceStore';
import { useAuthStore } from './authStore'; import { useAuthStore } from './authStore';
import { normalizeMessageAssets } from '../utils/assetUrls'; import { normalizeMessageAssets } from '../utils/assetUrls';
import { sortDmChannels } from '../utils/dmSorting';
const MAX_MESSAGES_PER_CHANNEL = 200; const MAX_MESSAGES_PER_CHANNEL = 200;
const MAX_CACHED_CHANNELS = 20; const MAX_CACHED_CHANNELS = 20;
@@ -285,12 +286,7 @@ export const useChatStore = create<ChatState>((set, get) => ({
? { ...dm, lastMessage: { id: tempId, dmChannelId: channelId, userId: currentUser.id, content, createdAt: Date.now() } } ? { ...dm, lastMessage: { id: tempId, dmChannelId: channelId, userId: currentUser.id, content, createdAt: Date.now() } }
: dm : dm
); );
updatedDms.sort((a, b) => { setDmChannels(sortDmChannels(updatedDms, get().unreadChannels, get().currentChannelId));
const aTime = a.lastMessage?.createdAt ?? a.createdAt;
const bTime = b.lastMessage?.createdAt ?? b.createdAt;
return bTime - aTime;
});
setDmChannels(updatedDms);
} }
} }
@@ -589,6 +585,13 @@ export const useChatStore = create<ChatState>((set, get) => ({
} }
set({ readStates: rsMap, unreadChannels: unread }); set({ readStates: rsMap, unreadChannels: unread });
// Re-sort DM list now that unread state is known (handles initial load
// where populateFromReady runs before read states are processed)
const { dmChannels, setDmChannels } = useSpaceStore.getState();
if (dmChannels.length > 0) {
setDmChannels(sortDmChannels(dmChannels, unread, currentChannelId));
}
}, },
markChannelUnread: (channelId: string) => { markChannelUnread: (channelId: string) => {
@@ -652,6 +655,12 @@ export const useChatStore = create<ChatState>((set, get) => ({
return { readStates: newReadStates, unreadChannels: newUnread }; return { readStates: newReadStates, unreadChannels: newUnread };
}); });
// Re-sort DM list when a DM is marked as read (moves from unread to read group)
if (isDmChannel(channelId)) {
const { dmChannels, setDmChannels } = useSpaceStore.getState();
setDmChannels(sortDmChannels(dmChannels, get().unreadChannels, channelId));
}
// Send to the correct instance // Send to the correct instance
const origin = getChannelOrigin(channelId); const origin = getChannelOrigin(channelId);
wsSend({ type: 'channel_ack', channelId, messageId }, origin); wsSend({ type: 'channel_ack', channelId, messageId }, origin);
+8 -1
View File
@@ -3,6 +3,7 @@ import type { Space, Channel, ChannelCategory, MemberWithUser, SpaceWithChannels
import { api, BackspaceApiClient } from '../api/client'; import { api, BackspaceApiClient } from '../api/client';
import { resolveAssetUrl, normalizeUserAssets } from '../utils/assetUrls'; import { resolveAssetUrl, normalizeUserAssets } from '../utils/assetUrls';
import { isSelf } from '../utils/identity'; import { isSelf } from '../utils/identity';
import { sortDmChannels } from '../utils/dmSorting';
import { useAuthStore } from './authStore'; import { useAuthStore } from './authStore';
import { useChatStore } from './chatStore'; import { useChatStore } from './chatStore';
@@ -670,9 +671,15 @@ export const useSpaceStore = create<SpaceState>((set, get) => ({
}); });
const mergedDms = [...existingDmsFromOtherOrigins, ...incomingDms]; const mergedDms = [...existingDmsFromOtherOrigins, ...incomingDms];
// Sort DMs using unread-first ordering. On initial load, unreadChannels may
// still be empty (read states are processed after populateFromReady); the
// safety-net re-sort in setReadStates handles that case.
const { unreadChannels, currentChannelId } = useChatStore.getState();
const sortedDms = sortDmChannels(mergedDms, unreadChannels, currentChannelId);
const update: Partial<SpaceState> = { const update: Partial<SpaceState> = {
spaces: mergedSpaces, spaces: mergedSpaces,
dmChannels: mergedDms, dmChannels: sortedDms,
channelToSpaceMap, channelToSpaceMap,
channelLastMessageIds, channelLastMessageIds,
spacePermissions, spacePermissions,