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:
john
2026-09-02 14:59:38 +08:00
parent 1d1af5c657
commit e370583fc0
3 changed files with 90 additions and 8 deletions
+62
View File
@@ -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);
});
+4 -1
View File
@@ -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
View File
@@ -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)),
};
}