fix: eliminate phantom notifications across the entire read-state pipeline

Root cause: own messages echoed by the server marked channels unread when
the user had already navigated away. Seven related bugs compounded the
problem — stale read states, missing cleanup on space/DM removal, REST
broadcast ignoring VIEW_CHANNEL, and no validation on channel_ack writes.

Frontend:
- Skip markChannelUnread for the user's own messages (federation-aware)
- Walk backward past temp_ IDs in ackChannel instead of bailing
- Re-fire ack timer when temp message is replaced by server-confirmed ID
- Add removeChannelStates to clean up unread/read/message caches
- Clean up chatStore on removeSpace, removeDmChannel, removeInstanceSpaces

Server:
- Use sendToChannel instead of sendToSpace for REST message creation
- Clean up read_states on space deletion, member kick/leave, and ban
- Validate channel membership before accepting channel_ack writes
- Clean up read_states on DM leave and DM channel deletion
This commit is contained in:
Jannis Braun
2026-03-16 20:20:25 +01:00
parent 0815963e1b
commit e9c43f21f1
8 changed files with 118 additions and 27 deletions
+4 -2
View File
@@ -284,7 +284,8 @@ function handleEvent(origin: string, event: ServerEvent): void {
addRealtimeMessage(event.message.channelId, event.message);
{
const { currentChannelId, markChannelUnread } = useChatStore.getState();
if (event.message.channelId !== currentChannelId) {
const myId = isHome ? useAuthStore.getState().user?.id : getMyUserIdForOrigin(origin);
if (event.message.channelId !== currentChannelId && event.message.userId !== myId) {
markChannelUnread(event.message.channelId);
}
}
@@ -440,7 +441,8 @@ function handleEvent(origin: string, event: ServerEvent): void {
}
{
const { currentChannelId, markChannelUnread } = useChatStore.getState();
if (event.message.dmChannelId !== currentChannelId) {
const myId = isHome ? useAuthStore.getState().user?.id : getMyUserIdForOrigin(origin);
if (event.message.dmChannelId !== currentChannelId && event.message.userId !== myId) {
markChannelUnread(event.message.dmChannelId);
}
}