diff --git a/packages/server/src/ws/events.ts b/packages/server/src/ws/events.ts index 09de7c6b..365455ee 100644 --- a/packages/server/src/ws/events.ts +++ b/packages/server/src/ws/events.ts @@ -709,19 +709,24 @@ function handleReactionAdd(event: Record, userId: string): void if (!serverId || !isMember(serverId, userId)) return; const reactionId = generateSnowflake(); + const now = Date.now(); try { db.insert(schema.reactions).values({ id: reactionId, messageId, userId, emoji, - createdAt: Date.now(), + createdAt: now, }).run(); + // Include user object so remote clients can use isSelf() for identity resolution + const reactionUser = db.select().from(schema.users).where(eq(schema.users.id, userId)).get(); + const userObj = reactionUser ? sanitizeUser(reactionUser) : undefined; + connectionManager.sendToChannel(serverId, message.channelId, { type: 'reaction_added', messageId, - reaction: { id: reactionId, messageId, userId, emoji, createdAt: Date.now() }, + reaction: { id: reactionId, messageId, userId, emoji, createdAt: now, user: userObj }, }); } catch (err) { // Unique constraint violation (already reacted) @@ -734,19 +739,24 @@ function handleReactionAdd(event: Record, userId: string): void if (!dmMsg || !isDmMember(dmMsg.dmChannelId, userId)) return; const reactionId = generateSnowflake(); + const now = Date.now(); try { db.insert(schema.dmReactions).values({ id: reactionId, dmMessageId: messageId, userId, emoji, - createdAt: Date.now(), + createdAt: now, }).run(); + // Include user object so remote clients can use isSelf() for identity resolution + const reactionUser = db.select().from(schema.users).where(eq(schema.users.id, userId)).get(); + const userObj = reactionUser ? sanitizeUser(reactionUser) : undefined; + connectionManager.sendToDmMembers(dmMsg.dmChannelId, { type: 'reaction_added', messageId, - reaction: { id: reactionId, messageId, userId, emoji, createdAt: Date.now() }, + reaction: { id: reactionId, messageId, userId, emoji, createdAt: now, user: userObj }, }); } catch (err) { // Unique constraint violation (already reacted) diff --git a/packages/web/src/components/chat/Message.tsx b/packages/web/src/components/chat/Message.tsx index d54fe6e4..ce3e4949 100644 --- a/packages/web/src/components/chat/Message.tsx +++ b/packages/web/src/components/chat/Message.tsx @@ -59,8 +59,11 @@ export function Message({ message, isCompact, isFirstInGroup }: MessageProps) { const removeReaction = useChatStore((s) => s.removeReaction); const setReplyTo = useChatStore((s) => s.setReplyTo); + const isOwnReaction = (r: { userId: string; user?: { id: string; username: string; homeInstance?: string | null } | null }) => + r.user ? isSelf(r.user, currentUser) : r.userId === currentUser?.id; + const toggleReaction = (emoji: string) => { - const hasReacted = message.reactions?.some(r => r.userId === currentUser?.id && r.emoji === emoji); + const hasReacted = message.reactions?.some(r => isOwnReaction(r) && r.emoji === emoji); if (hasReacted) { removeReaction(message.id, emoji); } else { @@ -71,7 +74,7 @@ export function Message({ message, isCompact, isFirstInGroup }: MessageProps) { const reactionGroups = (message.reactions || []).reduce((acc, r) => { const group = acc[r.emoji] || { count: 0, me: false }; group.count++; - if (r.userId === currentUser?.id) { + if (isOwnReaction(r)) { group.me = true; } acc[r.emoji] = group; diff --git a/packages/web/src/hooks/useWebSocket.ts b/packages/web/src/hooks/useWebSocket.ts index d1098776..5ac21e38 100644 --- a/packages/web/src/hooks/useWebSocket.ts +++ b/packages/web/src/hooks/useWebSocket.ts @@ -118,6 +118,19 @@ function handleEvent(origin: string, event: ServerEvent): void { loadServerDetail(currentServerId); } + // For remote instances: if user was viewing one of these servers, load its details + // (fixes race condition on page reload — route params effect fires before remote WS connects) + if (!isHome) { + const { currentServerId: curServerId, loadServerDetail: loadDetail } = useServerStore.getState(); + if (curServerId && event.servers.some((s: any) => s.id === curServerId)) { + loadDetail(curServerId); + const { currentChannelId, loadMessages } = useChatStore.getState(); + if (currentChannelId) { + loadMessages(currentChannelId, true); + } + } + } + // Only force-reload the current channel on reconnect; other channels keep their cache if (isHome) { const { loadMessages: reloadMessages, currentChannelId, setReadStates } = useChatStore.getState();