fix: prevent duplicate chat row keys

This commit is contained in:
Shakker
2026-07-15 01:12:47 +01:00
committed by Shakker
parent d660f7d38b
commit 876579fa73
2 changed files with 33 additions and 4 deletions
+26
View File
@@ -407,6 +407,32 @@ describe("buildCachedChatItems row identity", () => {
expect(appended.key).toBe(initial.key);
});
it("does not reclaim a group key naturally owned by another reordered group", () => {
resetChatThreadState();
const first = {
__openclaw: { id: "first", seq: 1 },
role: "user",
senderLabel: "same",
content: "First",
timestamp: 1,
};
const second = {
__openclaw: { id: "second", seq: 2 },
role: "user",
senderLabel: "same",
content: "Second",
timestamp: 2,
};
expect(messageGroups({ messages: [first, second] })).toHaveLength(1);
first.senderLabel = "different";
first.timestamp = 3;
const regrouped = messageGroups({ messages: [first, second] });
expect(regrouped).toHaveLength(2);
expect(new Set(regrouped.map((group) => group.key)).size).toBe(regrouped.length);
});
it("keeps a projected-sibling group stable after an unrelated prepend", () => {
resetChatThreadState();
const siblings = [
+7 -4
View File
@@ -1544,6 +1544,9 @@ function stabilizeChatItems(
previousGroupByMessageKey.set(message.key, item);
}
}
const nextNaturalGroupKeys = new Set(
next.filter((item) => item.kind === "group").map((item) => item.key),
);
const claimedGroupKeys = new Set<string>();
const reconciled = next.map((item) => {
if (item.kind !== "group") {
@@ -1583,6 +1586,9 @@ function stabilizeChatItems(
if (!best) {
return item;
}
if (best.group.key !== item.key && nextNaturalGroupKeys.has(best.group.key)) {
return item;
}
claimedGroupKeys.add(best.group.key);
return item.key === best.group.key ? item : { ...item, key: best.group.key };
});
@@ -1659,10 +1665,7 @@ function updateCachedLiveStream(
if (item.key !== expectedKey || input.stream === null) {
return false;
}
const text = trimAccumulatedStreamPrefix(
sanitizeStreamText(input.stream),
accumulatedPrefix,
);
const text = trimAccumulatedStreamPrefix(sanitizeStreamText(input.stream), accumulatedPrefix);
if (text.length === 0 || stripHeartbeatTokenForDisplay(text).shouldSkip) {
return false;
}