From c95cc77b658edd072785d3ac93856de3ab9ad2ec Mon Sep 17 00:00:00 2001 From: Adam Malczewski Date: Mon, 22 Jun 2026 18:08:05 +0900 Subject: fix: duplicate user message on first send in a new tab MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- src/core/chunks/reducer.test.ts | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) (limited to 'src/core/chunks/reducer.test.ts') 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", () => { -- cgit v1.2.3