summaryrefslogtreecommitdiffhomepage
path: root/src/core/chunks/reducer.test.ts
diff options
context:
space:
mode:
authorAdam Malczewski <[email protected]>2026-06-22 18:08:05 +0900
committerAdam Malczewski <[email protected]>2026-06-22 18:08:05 +0900
commitc95cc77b658edd072785d3ac93856de3ab9ad2ec (patch)
tree1a8c8a84c0c233ebe0aea0d9309ab3a84c4856be /src/core/chunks/reducer.test.ts
parent2a7708bd492a5a78794c76ee43355cabe786943e (diff)
downloaddispatch-web-dev.tar.gz
dispatch-web-dev.zip
fix: duplicate user message on first send in a new tabdev
When a draft is promoted to a real conversation, send() appends the user message as a provisional chunk (optimistic echo). But load() also fires syncTail, which fetches the CR-6 persisted user message as a committed chunk — showing the message twice until turn seal. Fix: applyHistory now removes provisional chunks that duplicate the last committed chunk (matching role + text content) when new committed chunks arrive during generation. The optimistic echo is dropped once the authoritative committed version arrives. 684 tests green.
Diffstat (limited to 'src/core/chunks/reducer.test.ts')
-rw-r--r--src/core/chunks/reducer.test.ts20
1 files changed, 20 insertions, 0 deletions
diff --git a/src/core/chunks/reducer.test.ts b/src/core/chunks/reducer.test.ts
index a346545..883461e 100644
--- a/src/core/chunks/reducer.test.ts
+++ b/src/core/chunks/reducer.test.ts
@@ -551,6 +551,26 @@ describe("applyHistory", () => {
expect(s.committed).toHaveLength(2);
expect(s.committed.map((c) => c.seq)).toEqual([1, 2]);
});
+
+ it("removes provisional duplicate when committed user message arrives during generation", () => {
+ // Simulate: send() appends provisional user message, then syncTail
+ // fetches the same message as committed (CR-6: persisted at turn start).
+ let s = initialState();
+ s = appendUserMessage(s, "hello");
+ s = foldEvent(s, turnStart("t1"));
+ expect(s.provisional).toHaveLength(1);
+ expect(s.provisional[0]?.role).toBe("user");
+ expect(s.generating).toBe(true);
+
+ // syncTail fetches the persisted user message as committed
+ s = applyHistory(s, [storedChunk(1, "user", { type: "text", text: "hello" })]);
+
+ // The provisional duplicate is removed — no double render
+ expect(s.provisional).toEqual([]);
+ expect(s.committed).toHaveLength(1);
+ expect(s.committed[0]?.role).toBe("user");
+ expect(s.committed[0]?.chunk).toEqual({ type: "text", text: "hello" });
+ });
});
describe("selectChunks", () => {