Deal with reconciliation conflict from colliding WS fetches

This commit is contained in:
Jack Kingsman
2026-03-11 20:52:48 -07:00
parent bc7506b0d9
commit d36c5e3e32
2 changed files with 40 additions and 2 deletions

View File

@@ -195,8 +195,12 @@ export function useConversationMessages(
}
const messagesWithPendingAck = data.map((msg) => applyPendingAck(msg));
setMessages(messagesWithPendingAck);
syncSeenContent(messagesWithPendingAck);
const merged = messageCache.reconcile(messagesRef.current, messagesWithPendingAck);
const nextMessages = merged ?? messagesRef.current;
if (merged) {
setMessages(merged);
}
syncSeenContent(nextMessages);
setHasOlderMessages(messagesWithPendingAck.length >= MESSAGE_PAGE_SIZE);
} catch (err) {
if (isAbortError(err)) {

View File

@@ -101,6 +101,40 @@ describe('useConversationMessages ACK ordering', () => {
expect(result.current.messages[0].paths).toEqual(paths);
});
it('preserves a WebSocket-arrived message when latest fetch resolves afterward', async () => {
const deferred = createDeferred<Message[]>();
mockGetMessages.mockReturnValueOnce(deferred.promise);
const { result } = renderHook(() => useConversationMessages(createConversation()));
await waitFor(() => expect(mockGetMessages).toHaveBeenCalledTimes(1));
act(() => {
const added = result.current.addMessageIfNew(
createMessage({
id: 99,
text: 'ws-arrived',
sender_timestamp: 1700000099,
received_at: 1700000099,
})
);
expect(added).toBe(true);
});
deferred.resolve([
createMessage({
id: 42,
text: 'rest-fetched',
sender_timestamp: 1700000000,
received_at: 1700000001,
}),
]);
await waitFor(() => expect(result.current.messagesLoading).toBe(false));
expect(result.current.messages).toHaveLength(2);
expect(result.current.messages.some((msg) => msg.text === 'rest-fetched')).toBe(true);
expect(result.current.messages.some((msg) => msg.text === 'ws-arrived')).toBe(true);
});
it('keeps highest ACK state when out-of-order ACK updates arrive', async () => {
mockGetMessages.mockResolvedValueOnce([]);