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,
|
appendSessionLists,
|
||||||
prependUnique,
|
prependUnique,
|
||||||
shouldShowNewChatTitle,
|
shouldShowNewChatTitle,
|
||||||
|
sortAndTrim,
|
||||||
toSessionSummary,
|
toSessionSummary,
|
||||||
touchSession,
|
touchSession,
|
||||||
} from '../utils/sessions';
|
} from '../utils/sessions';
|
||||||
@@ -750,7 +751,9 @@ export function useTKMindChat(
|
|||||||
offset: 0,
|
offset: 0,
|
||||||
query: options?.query ?? sessionSearchQueryRef.current,
|
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 nextOffset = Number(page.offset ?? 0) + items.length;
|
||||||
const total = Math.max(Number(page.total ?? merged.length), merged.length);
|
const total = Math.max(Number(page.total ?? merged.length), merged.length);
|
||||||
sessionsOffsetRef.current = options?.preserveExisting
|
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(
|
export function mergeSessionRecord(
|
||||||
existing: SessionSummary | undefined,
|
existing: SessionSummary | undefined,
|
||||||
incoming: SessionSummary,
|
incoming: SessionSummary,
|
||||||
@@ -51,15 +62,21 @@ export function mergeSessionRecord(
|
|||||||
|
|
||||||
const keepExistingTitle =
|
const keepExistingTitle =
|
||||||
hasMeaningfulSessionTitle(existing) && !hasMeaningfulSessionTitle(incoming);
|
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 {
|
return {
|
||||||
...existing,
|
...merged,
|
||||||
...incoming,
|
updated_at: pickNewerIsoTimestamp(existing.updated_at, incoming.updated_at),
|
||||||
id: incoming.id,
|
message_count: Math.max(Number(existing.message_count ?? 0), Number(incoming.message_count ?? 0)),
|
||||||
name: existing.name,
|
|
||||||
user_set_name: existing.user_set_name,
|
|
||||||
recipe: existing.recipe ?? incoming.recipe,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user