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
+29
View File
@@ -21,6 +21,18 @@ function handleEvent(event) {
if (currentServerId) {
loadServerDetail(currentServerId);
}
// Clear stale message cache on reconnect, then re-fetch current channel
{
const { clearAllMessages, loadMessages: reloadMessages, currentChannelId, setReadStates } = useChatStore.getState();
clearAllMessages();
if (currentChannelId) {
reloadMessages(currentChannelId, true);
}
const { channelLastMessageIds } = useServerStore.getState();
if (event.readStates) {
setReadStates(event.readStates, channelLastMessageIds);
}
}
// Clear stale voice state, then populate from server truth
clearAllVoiceUsers();
if (event.voiceStates) {
@@ -31,6 +43,12 @@ function handleEvent(event) {
break;
case 'message_created':
addMessage(event.message.channelId, event.message);
{
const { currentChannelId, markChannelUnread } = useChatStore.getState();
if (event.message.channelId !== currentChannelId) {
markChannelUnread(event.message.channelId);
}
}
break;
case 'message_updated':
updateMessage(event.message);
@@ -73,6 +91,12 @@ function handleEvent(event) {
return bTime - aTime;
});
setDmChannels(updatedDms);
{
const { currentChannelId, markChannelUnread } = useChatStore.getState();
if (event.message.dmChannelId !== currentChannelId) {
markChannelUnread(event.message.dmChannelId);
}
}
break;
}
case 'dm_message_updated':
@@ -100,6 +124,11 @@ function handleEvent(event) {
addFriendFromAccepted(event.friend, event.requestId);
break;
}
case 'channel_ack': {
const { onChannelAck } = useChatStore.getState();
onChannelAck(event.channelId, event.messageId);
break;
}
case 'error':
console.error('WebSocket error:', event.message);
break;
+32
View File
@@ -25,6 +25,19 @@ function handleEvent(event: ServerEvent): void {
if (currentServerId) {
loadServerDetail(currentServerId);
}
// Clear stale message cache on reconnect, then re-fetch current channel
{
const { clearAllMessages, loadMessages: reloadMessages, currentChannelId, setReadStates } = useChatStore.getState();
clearAllMessages();
if (currentChannelId) {
reloadMessages(currentChannelId, true);
}
// Initialize unread tracking from ready payload
const { channelLastMessageIds } = useServerStore.getState();
if (event.readStates) {
setReadStates(event.readStates, channelLastMessageIds);
}
}
// Clear stale voice state, then populate from server truth
clearAllVoiceUsers();
if (event.voiceStates) {
@@ -36,6 +49,12 @@ function handleEvent(event: ServerEvent): void {
case 'message_created':
addMessage(event.message.channelId, event.message);
{
const { currentChannelId, markChannelUnread } = useChatStore.getState();
if (event.message.channelId !== currentChannelId) {
markChannelUnread(event.message.channelId);
}
}
break;
case 'message_updated':
@@ -86,6 +105,13 @@ function handleEvent(event: ServerEvent): void {
return bTime - aTime;
});
setDmChannels(updatedDms);
// Mark DM as unread if not currently viewing it
{
const { currentChannelId, markChannelUnread } = useChatStore.getState();
if (event.message.dmChannelId !== currentChannelId) {
markChannelUnread(event.message.dmChannelId);
}
}
break;
}
@@ -121,6 +147,12 @@ function handleEvent(event: ServerEvent): void {
break;
}
case 'channel_ack': {
const { onChannelAck } = useChatStore.getState();
onChannelAck(event.channelId, event.messageId);
break;
}
case 'error':
console.error('WebSocket error:', event.message);
break;