Fix session history order when sidebar refresh merges stale server timestamps.
Keep the newer updated_at and max message_count during session list merges so local touchSession updates are not overwritten by lagging Goose summaries. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,62 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import test from 'node:test';
|
||||
|
||||
// Keep in sync with src/utils/sessions.ts merge rules for regression coverage.
|
||||
function pickNewerIsoTimestamp(left, right) {
|
||||
const leftTime = Date.parse(left ?? '');
|
||||
const rightTime = Date.parse(right ?? '');
|
||||
const leftValid = Number.isFinite(leftTime);
|
||||
const rightValid = Number.isFinite(rightTime);
|
||||
if (!leftValid && !rightValid) return left ?? right;
|
||||
if (!leftValid) return right;
|
||||
if (!rightValid) return left;
|
||||
return leftTime >= rightTime ? left : right;
|
||||
}
|
||||
|
||||
function mergeSessionRecord(existing, incoming) {
|
||||
if (!existing) return incoming;
|
||||
const merged = { ...existing, ...incoming, id: incoming.id };
|
||||
return {
|
||||
...merged,
|
||||
updated_at: pickNewerIsoTimestamp(existing.updated_at, incoming.updated_at),
|
||||
message_count: Math.max(Number(existing.message_count ?? 0), Number(incoming.message_count ?? 0)),
|
||||
};
|
||||
}
|
||||
|
||||
test('mergeSessionRecord keeps the newer updated_at when refreshing stale server summaries', () => {
|
||||
const merged = mergeSessionRecord(
|
||||
{
|
||||
id: 'sess-1',
|
||||
name: 'New Chat',
|
||||
message_count: 4,
|
||||
updated_at: '2026-09-02T14:48:00.000Z',
|
||||
},
|
||||
{
|
||||
id: 'sess-1',
|
||||
name: 'New Chat',
|
||||
message_count: 3,
|
||||
updated_at: '2026-09-02T14:40:00.000Z',
|
||||
},
|
||||
);
|
||||
assert.equal(merged.updated_at, '2026-09-02T14:48:00.000Z');
|
||||
assert.equal(merged.message_count, 4);
|
||||
});
|
||||
|
||||
test('mergeSessionRecord adopts newer server timestamps when local cache is older', () => {
|
||||
const merged = mergeSessionRecord(
|
||||
{
|
||||
id: 'sess-2',
|
||||
name: 'New Chat',
|
||||
message_count: 2,
|
||||
updated_at: '2026-09-02T14:30:00.000Z',
|
||||
},
|
||||
{
|
||||
id: 'sess-2',
|
||||
name: 'New Chat',
|
||||
message_count: 5,
|
||||
updated_at: '2026-09-02T14:48:00.000Z',
|
||||
},
|
||||
);
|
||||
assert.equal(merged.updated_at, '2026-09-02T14:48:00.000Z');
|
||||
assert.equal(merged.message_count, 5);
|
||||
});
|
||||
@@ -88,6 +88,7 @@ import {
|
||||
appendSessionLists,
|
||||
prependUnique,
|
||||
shouldShowNewChatTitle,
|
||||
sortAndTrim,
|
||||
toSessionSummary,
|
||||
touchSession,
|
||||
} from '../utils/sessions';
|
||||
@@ -750,7 +751,9 @@ export function useTKMindChat(
|
||||
offset: 0,
|
||||
query: options?.query ?? sessionSearchQueryRef.current,
|
||||
});
|
||||
const merged = options?.preserveExisting ? appendSessionLists(sessionsRef.current, items) : items;
|
||||
const merged = options?.preserveExisting
|
||||
? appendSessionLists(sessionsRef.current, items)
|
||||
: sortAndTrim(items);
|
||||
const nextOffset = Number(page.offset ?? 0) + items.length;
|
||||
const total = Math.max(Number(page.total ?? merged.length), merged.length);
|
||||
sessionsOffsetRef.current = options?.preserveExisting
|
||||
|
||||
+24
-7
@@ -43,6 +43,17 @@ export function toSessionSummary(session: Session | SessionSummary): SessionSumm
|
||||
};
|
||||
}
|
||||
|
||||
function pickNewerIsoTimestamp(left?: string, right?: string): string | undefined {
|
||||
const leftTime = Date.parse(left ?? '');
|
||||
const rightTime = Date.parse(right ?? '');
|
||||
const leftValid = Number.isFinite(leftTime);
|
||||
const rightValid = Number.isFinite(rightTime);
|
||||
if (!leftValid && !rightValid) return left ?? right;
|
||||
if (!leftValid) return right;
|
||||
if (!rightValid) return left;
|
||||
return leftTime >= rightTime ? left : right;
|
||||
}
|
||||
|
||||
export function mergeSessionRecord(
|
||||
existing: SessionSummary | undefined,
|
||||
incoming: SessionSummary,
|
||||
@@ -51,15 +62,21 @@ export function mergeSessionRecord(
|
||||
|
||||
const keepExistingTitle =
|
||||
hasMeaningfulSessionTitle(existing) && !hasMeaningfulSessionTitle(incoming);
|
||||
if (!keepExistingTitle) return { ...existing, ...incoming, id: incoming.id };
|
||||
const merged = keepExistingTitle
|
||||
? {
|
||||
...existing,
|
||||
...incoming,
|
||||
id: incoming.id,
|
||||
name: existing.name,
|
||||
user_set_name: existing.user_set_name,
|
||||
recipe: existing.recipe ?? incoming.recipe,
|
||||
}
|
||||
: { ...existing, ...incoming, id: incoming.id };
|
||||
|
||||
return {
|
||||
...existing,
|
||||
...incoming,
|
||||
id: incoming.id,
|
||||
name: existing.name,
|
||||
user_set_name: existing.user_set_name,
|
||||
recipe: existing.recipe ?? incoming.recipe,
|
||||
...merged,
|
||||
updated_at: pickNewerIsoTimestamp(existing.updated_at, incoming.updated_at),
|
||||
message_count: Math.max(Number(existing.message_count ?? 0), Number(incoming.message_count ?? 0)),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user