refactor(chat): simplify baseDisplayMessages dedup — remove unnecessary reverse() (#2089)

* fix(chat): preserve chronological message order during history pagination

The baseDisplayMessages dedup loop iterated from tail to head (newest
to oldest), keeping the newer occurrence of each message ID. During
history pagination (prepend mode), the server returns older messages
that may overlap with the current view at the boundary. The tail-first
iteration discarded the older (prepended) duplicate in favor of the
newer (existing) one, breaking chronological ordering.

Change the loop to iterate head to tail (oldest to newest) so the
first occurrence of each time-sortable message ID is preserved. Remove
the now-unnecessary .reverse() call.

Fixes #2088

* test(chat): add dedup logic coverage for baseDisplayMessages

Covers message ID deduplication in baseDisplayMessages useMemo:
- First-occurrence preservation during dedup
- Input order maintenance
- Empty input, single-element, all-same-ID edge cases
- History pagination prepend scenario with overlapping IDs

---------

Co-authored-by: bashrusakh <bashrusakh@users.noreply.github.com>
This commit is contained in:
Leonid
2026-07-11 14:38:39 +03:00
committed by GitHub
co-authored by bashrusakh
parent d01126cf3c
commit 37768958f3
2 changed files with 173 additions and 5 deletions
@@ -1344,20 +1344,24 @@ const MessageList = React.forwardRef<MessageListHandle, MessageListProps>(({
const baseDisplayMessages = React.useMemo(() => streamPerfMeasure('ui.message_list.base_display_ms', () => {
const seenIdsFromTail = new Set<string>();
const seenIds = new Set<string>();
const dedupedMessages: ChatMessageEntry[] = [];
for (let index = messages.length - 1; index >= 0; index -= 1) {
// Deduplicate from head to tail (oldest to newest) to preserve chronological
// order during history pagination (prepend). Older messages have smaller
// time-sortable IDs and appear first in the array. Keeping the first
// occurrence ensures prepended history isn't incorrectly discarded in favor
// of newer duplicates from the existing view.
for (let index = 0; index < messages.length; index += 1) {
const message = messages[index];
const messageId = message.info?.id;
if (typeof messageId === 'string') {
if (seenIdsFromTail.has(messageId)) {
if (seenIds.has(messageId)) {
continue;
}
seenIdsFromTail.add(messageId);
seenIds.add(messageId);
}
dedupedMessages.push(getNormalizedMessageForDisplay(message));
}
dedupedMessages.reverse();
const output: ChatMessageEntry[] = [];
const compactionCommandIds = new Set<string>();