fix: discard reverted optimistic messages after send

Removes reverted branch messages from the optimistic shadow after a successful send
Prevents deleted messages from being merged back in on the next tail refresh
Covers the cleanup behavior with an updated test
This commit is contained in:
Bohdan Triapitsyn
2026-07-29 01:50:22 +03:00
parent 3cf1d82106
commit 840431b49a
2 changed files with 17 additions and 1 deletions
+6 -1
View File
@@ -626,7 +626,7 @@ describe("optimisticSend target directory", () => {
expect(currentStore.getState().session_status["session-new"]).toBe(undefined) expect(currentStore.getState().session_status["session-new"]).toBe(undefined)
}) })
test("commits the new branch locally when sending after a revert", async () => { test("commits the new branch locally and discards its optimistic shadow when sending after a revert", async () => {
const retainedMessage = { id: "msg_1", role: "user", sessionID: "session-reverted" } as Message const retainedMessage = { id: "msg_1", role: "user", sessionID: "session-reverted" } as Message
const revertedMessage = { id: "msg_2", role: "user", sessionID: "session-reverted" } as Message const revertedMessage = { id: "msg_2", role: "user", sessionID: "session-reverted" } as Message
const targetStore = createStore({}, { const targetStore = createStore({}, {
@@ -636,18 +636,21 @@ describe("optimisticSend target directory", () => {
}) })
const childStores = createChildStores([["/target/project", targetStore]]) const childStores = createChildStores([["/target/project", targetStore]])
let optimisticMessage: Message | null = null let optimisticMessage: Message | null = null
const optimisticShadow = new Set([revertedMessage.id])
const { optimisticSend, setActionRefs, setOptimisticRefs } = await import("./session-actions") const { optimisticSend, setActionRefs, setOptimisticRefs } = await import("./session-actions")
setActionRefs(mockSdk as unknown as OpencodeClient, childStores, () => "/target/project") setActionRefs(mockSdk as unknown as OpencodeClient, childStores, () => "/target/project")
setOptimisticRefs( setOptimisticRefs(
(input) => { (input) => {
optimisticMessage = input.message optimisticMessage = input.message
optimisticShadow.add(input.message.id)
targetStore.setState((state) => ({ targetStore.setState((state) => ({
message: { ...state.message, [input.sessionID]: [...(state.message[input.sessionID] ?? []), input.message] }, message: { ...state.message, [input.sessionID]: [...(state.message[input.sessionID] ?? []), input.message] },
part: { ...state.part, [input.message.id]: input.parts }, part: { ...state.part, [input.message.id]: input.parts },
})) }))
}, },
() => {}, () => {},
(input) => optimisticShadow.delete(input.messageID),
) )
await optimisticSend({ await optimisticSend({
@@ -665,6 +668,8 @@ describe("optimisticSend target directory", () => {
(optimisticMessage as unknown as Message).id, (optimisticMessage as unknown as Message).id,
]) ])
expect(targetStore.getState().part.msg_2).toBe(undefined) expect(targetStore.getState().part.msg_2).toBe(undefined)
expect(optimisticShadow.has(revertedMessage.id)).toBe(false)
expect(optimisticShadow.has((optimisticMessage as unknown as Message).id)).toBe(true)
}) })
test("restores the reverted branch when sending fails", async () => { test("restores the reverted branch when sending fails", async () => {
+11
View File
@@ -937,6 +937,17 @@ export async function optimisticSend(input: {
const part = { ...stateBeforeSend.part } const part = { ...stateBeforeSend.part }
for (const revertedMessage of revertedMessages) delete part[revertedMessage.id] for (const revertedMessage of revertedMessages) delete part[revertedMessage.id]
store.setState({ session, message, part }) store.setState({ session, message, part })
// A server-backed user message can still remain in the loader's optimistic
// shadow until a page fetch confirms it. Forget the reverted branch there
// too, or the next tail refresh will merge those deleted messages back in.
for (const revertedMessage of revertedMessages) {
_optimisticConfirm?.({
sessionID: input.sessionId,
directory: targetDirectory,
messageID: revertedMessage.id,
})
}
} }
const messageID = ascendingId("msg") const messageID = ascendingId("msg")