fix(sync): replace every optimistic part of a just-sent message

The server echoes a sent message part by part. The reducer replaced an
optimistic part with its server echo only while the FIRST part of the
message was still optimistic, so once the text echo had landed the file
echo no longer qualified and was appended instead: an attached image
showed twice until the next page fetch rewrote the parts.

Any remaining optimistic part of the same type is now a replacement
candidate. The scan only runs when a part with a new id arrives, which is
once per part during assistant streaming.

Testing: reducer test extended past the first echo to the file echo; ui
type-check and lint; verified the event order against a live OpenCode
(message, text part, file part within ~70ms of the accepted request).
This commit is contained in:
Bohdan Triapitsyn
2026-09-07 17:58:03 +03:00
parent e3b0088c60
commit 4915658de1
2 changed files with 16 additions and 5 deletions
@@ -128,6 +128,15 @@ describe("applyDirectoryEvent", () => {
properties: { part: serverText },
} as Event)).toBe(true)
expect(draft.part.msg_1).toEqual([serverText, optimisticFile])
// The file echo follows the text echo; it must claim the optimistic file
// even though the first slot now holds a server part.
const serverFile = { id: "prt_server_file", messageID: "msg_1", sessionID: "ses_1", type: "file", filename: "a.png" } as Part
expect(applyDirectoryEvent(draft, {
type: "message.part.updated",
properties: { part: serverFile },
} as Event)).toBe(true)
expect(draft.part.msg_1).toEqual([serverText, serverFile])
})
test("returns typed materialization when delta arrives before parts", () => {