From 03f9636f00d5dc7a3f00148a74e2ad41e3c505a3 Mon Sep 17 00:00:00 2001 From: eric Date: Mon, 1 Jun 2026 12:11:14 -0700 Subject: [PATCH] [eric] chat: keep the optimistic user message on remount mid-stream so it stops vanishing when you leave and come back --- frontend/src/shared/state/agentsSlice.ts | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/frontend/src/shared/state/agentsSlice.ts b/frontend/src/shared/state/agentsSlice.ts index 3bc0bb3e..c3d9d715 100644 --- a/frontend/src/shared/state/agentsSlice.ts +++ b/frontend/src/shared/state/agentsSlice.ts @@ -1227,8 +1227,28 @@ const agentsSlice = createSlice({ .addCase(fetchSession.fulfilled, (state, action) => { const session = action.payload; const existing = state.sessions[session.id]; + // Preserve optimistic-pending messages the server snapshot doesn't carry + // yet. On remount mid-stream (leave the chat + come back) this fetch's + // snapshot predates the just-sent user turn, so a blind replace wiped the + // user's own message bubble while the assistant stream (separate slice) + // kept going. Carry forward any local pending message not already echoed + // in the snapshot; the WS echo / next fetch dedupes it by client_message_id. + const incomingMsgs = session.messages ?? []; + const incomingClientIds = new Set( + incomingMsgs.map((m) => m.client_message_id).filter(Boolean), + ); + const incomingIds = new Set(incomingMsgs.map((m) => m.id)); + const survivingOptimistic = (existing?.messages ?? []).filter( + (m) => + m.optimistic_status === 'pending' && + !(m.client_message_id && incomingClientIds.has(m.client_message_id)) && + !incomingIds.has(m.id), + ); state.sessions[session.id] = { ...session, + messages: survivingOptimistic.length + ? [...incomingMsgs, ...survivingOptimistic] + : incomingMsgs, pending_approvals: session.pending_approvals ?? existing?.pending_approvals ?? [], tool_group_meta: session.tool_group_meta ?? existing?.tool_group_meta ?? {}, // mcp_suggestions live in client state only (the backend never