diff --git a/packages/chat/src/context.tsx b/packages/chat/src/context.tsx index 38ee454..09d65c3 100644 --- a/packages/chat/src/context.tsx +++ b/packages/chat/src/context.tsx @@ -266,7 +266,11 @@ export default function Provider(props: { children: ReactNode }) { return updated ? next : prev; }); - const queryKey = ["chat-messages", String(userIdValue)] as const; + const queryKey = [ + "chat-messages", + String(userIdValue), + currentSharedSecret.length > 0, + ] as const; queryClient.setQueryData>( queryKey, (current) => { @@ -301,7 +305,7 @@ export default function Provider(props: { children: ReactNode }) { }, ); }); - }, [subscribePush, userIdValue]); + }, [currentSharedSecret, subscribePush, userIdValue]); return ( diff --git a/packages/chat/src/screen.tsx b/packages/chat/src/screen.tsx index 85df087..f6bcff5 100644 --- a/packages/chat/src/screen.tsx +++ b/packages/chat/src/screen.tsx @@ -12,8 +12,16 @@ import { PAGE_SIZE, FALLBACK_MESSAGE_HEIGHT, MESSAGES_PER_VIRTUAL_ROW, + type LiveMessage, + type RawMessage, } from "./values"; +type MessageChunk = { + key: string; + messages: Array; + startIndex: number; +}; + function getEstimatedMessageHeight(message: { height?: number }) { return typeof message.height === "number" && Number.isFinite(message.height) ? Math.max(1, Math.ceil(message.height)) @@ -39,6 +47,34 @@ function shouldFetchPreviousPage({ ].every(Boolean); } +function getMessageRenderKey(message: RawMessage | LiveMessage) { + return "localId" in message ? message.localId : String(message.send_time); +} + +function buildMessageChunks( + messages: Array, + keyPrefix: string, + startOffset = 0, +) { + const chunks: MessageChunk[] = []; + + for (let end = messages.length; end > 0; end -= MESSAGES_PER_VIRTUAL_ROW) { + const start = Math.max(0, end - MESSAGES_PER_VIRTUAL_ROW); + const chunkMessages = messages.slice(start, end); + const firstMessage = chunkMessages[0]; + + if (firstMessage) { + chunks.push({ + key: `${keyPrefix}-${getMessageRenderKey(firstMessage)}`, + messages: chunkMessages, + startIndex: startOffset + start, + }); + } + } + + return chunks; +} + /** * Renders the chat screen with virtualized history and live message updates. * @returns Chat screen JSX. @@ -148,49 +184,52 @@ export default function Screen() { const historicalMessages = React.useMemo(() => { const pages = messagesQuery.data?.pages ?? []; - return [...pages].reverse().flat(); + const seenSendTimes = new Set(); + const dedupedMessages: RawMessage[] = []; + + for (const message of [...pages].reverse().flat()) { + if (seenSendTimes.has(message.send_time)) { + continue; + } + + seenSendTimes.add(message.send_time); + dedupedMessages.push(message); + } + + return dedupedMessages; }, [messagesQuery.data]); const liveMessagesSnapshot = liveMessages(); - const messages = React.useMemo(() => { - if (historicalMessages.length === 0) { - return liveMessagesSnapshot; - } - + const liveWithoutDuplicates = React.useMemo(() => { const historicalSendTimes = new Set( historicalMessages.map((message) => message.send_time), ); - const liveWithoutDuplicates = liveMessagesSnapshot.filter( + + return liveMessagesSnapshot.filter( (message) => !historicalSendTimes.has(message.send_time), ); - - return [...historicalMessages, ...liveWithoutDuplicates]; }, [historicalMessages, liveMessagesSnapshot]); + const messages = React.useMemo(() => { + return [...historicalMessages, ...liveWithoutDuplicates]; + }, [historicalMessages, liveWithoutDuplicates]); + + const historicalMessageChunks = React.useMemo(() => { + return buildMessageChunks(historicalMessages, "history"); + }, [historicalMessages]); + + const liveMessageChunks = React.useMemo(() => { + return buildMessageChunks( + liveWithoutDuplicates, + "live", + historicalMessages.length, + ); + }, [historicalMessages.length, liveWithoutDuplicates]); + const messageChunks = React.useMemo(() => { - const chunks: Array<{ - key: number; - messages: typeof messages; - startIndex: number; - }> = []; - - for (let end = messages.length; end > 0; end -= MESSAGES_PER_VIRTUAL_ROW) { - const start = Math.max(0, end - MESSAGES_PER_VIRTUAL_ROW); - const chunkMessages = messages.slice(start, end); - const firstMessage = chunkMessages[0]; - - if (firstMessage) { - chunks.push({ - key: firstMessage.send_time, - messages: chunkMessages, - startIndex: start, - }); - } - } - - return chunks; - }, [messages]); + return [...liveMessageChunks, ...historicalMessageChunks]; + }, [historicalMessageChunks, liveMessageChunks]); const shouldShowConversationStart = !!messagesQuery.data && !messagesQuery.hasNextPage; @@ -530,7 +569,7 @@ export default function Screen() { return (