feat: unread indicators + DM bug fixes + data-driven isDmChannel

- Fix stale message cache: add force param to loadMessages, clearAllMessages action
- Fix reload race condition: URL-based isDmChannel fallback before WS ready
- Add read_states DB table for persistent unread tracking
- Add channel_ack WS event (client→server→echo) with BigInt comparison
- Wire up unread state in chatStore (readStates, unreadChannels, ackChannel)
- Auto-ack channels on MessageList view (200ms debounced)
- Unread pill indicators on server icons in ServerSidebar
- Bold text + white dot on unread channels/DMs in ChannelSidebar
- Replace all showDms reads with data-driven isDmChannel() across 8 files
- Design system, UI polish, and component fixes from previous sessions
This commit is contained in:
Jannis Braun
2026-02-18 20:48:48 +01:00
parent 7168149d98
commit 435d12e5b8
50 changed files with 711 additions and 285 deletions
+33 -1
View File
@@ -8,6 +8,8 @@ export const useServerStore = create((set, get) => ({
roles: [],
folders: [],
dmChannels: [],
channelToServerMap: new Map(),
channelLastMessageIds: new Map(),
setServers: (servers) => set({ servers }),
setCurrentServer: (serverId) => set({ currentServerId: serverId }),
setChannels: (channels) => set({ channels }),
@@ -138,10 +140,40 @@ export const useServerStore = create((set, get) => ({
inviteCode: s.inviteCode,
createdAt: s.createdAt,
}));
const channelToServerMap = new Map();
const channelLastMessageIds = new Map();
for (const srv of servers) {
for (const ch of srv.channels) {
channelToServerMap.set(ch.id, srv.id);
if (ch.lastMessageId) {
channelLastMessageIds.set(ch.id, ch.lastMessageId);
}
}
}
const dms = dmChannels || [];
for (const dm of dms) {
if (dm.lastMessage?.id) {
channelLastMessageIds.set(dm.id, dm.lastMessage.id);
}
}
set({
servers: simpleServers,
folders: folders || [],
dmChannels: dmChannels || []
dmChannels: dms,
channelToServerMap,
channelLastMessageIds,
});
},
}));
export function isDmChannel(channelId) {
const dmChannels = useServerStore.getState().dmChannels;
if (dmChannels.length > 0) {
return dmChannels.some(dm => dm.id === channelId);
}
// Before WS ready populates dmChannels, fall back to URL path
if (typeof window !== 'undefined') {
return window.location.pathname.startsWith('/channels/@me/');
}
return false;
}