Skip to content

Commit 852a03e

Browse files
committed
fix(mothership): restore a withdrawn queued send even after the user switched chats
A queued send had already left the queue when its POST failed, and a dispatch whose epoch changed meanwhile (the user switched chats) skipped restoring it, so the message was lost. A withdrawn send was never admitted, so it now goes back to its own chat's queue regardless of the epoch.
1 parent bb6f537 commit 852a03e

2 files changed

Lines changed: 39 additions & 1 deletion

File tree

‎apps/sim/app/workspace/[workspaceId]/home/hooks/use-chat.dom.test.tsx‎

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2197,6 +2197,42 @@ describe('useChat remount send recovery', () => {
21972197
expect(state.postBodies[1].userMessageId).toBe(state.postBodies[0].userMessageId)
21982198
})
21992199

2200+
/** Switching chats while a queued send is failing must not drop it from its own chat. */
2201+
it('keeps a queued send that failed after the user switched chats', async () => {
2202+
const history = idleHistory('chat-left-mid-dispatch')
2203+
const other = idleHistory('chat-switched-to')
2204+
mockRequestJson.mockImplementation((_contract: AnyApiRouteContract, input: unknown) =>
2205+
Promise.resolve({
2206+
chat: JSON.stringify(input).includes(other.id) ? other : history,
2207+
})
2208+
)
2209+
let failPost: (() => void) | undefined
2210+
vi.stubGlobal('fetch', async (input: RequestInfo | URL, init?: RequestInit) => {
2211+
if (String(input) === '/api/mothership/chat' && init?.method === 'POST') {
2212+
state.postBodies.push(JSON.parse(String(init.body)))
2213+
return new Promise<Response>((_, reject) => {
2214+
failPost = () => reject(new TypeError('Failed to fetch'))
2215+
})
2216+
}
2217+
return fetchStub(input, init)
2218+
})
2219+
useMothershipQueueStore
2220+
.getState()
2221+
.enqueue(history.id, { id: 'queued-then-left', content: 'Sent as I switched chats' })
2222+
const { navigate } = renderUseChatInChat(history.id, history)
2223+
await waitFor(() => failPost !== undefined)
2224+
2225+
navigate(other.id, other)
2226+
await act(async () => {
2227+
failPost?.()
2228+
await sleep(100)
2229+
})
2230+
2231+
const queued = useMothershipQueueStore.getState().queues[history.id] ?? []
2232+
expect(queued.map((message) => message.content)).toEqual(['Sent as I switched chats'])
2233+
expect(queued[0].resumeUserMessageId).toBe(state.postBodies[0].userMessageId)
2234+
})
2235+
22002236
/** The `online` event can fire while no surface for the chat is mounted. */
22012237
it('sends a held message when its chat mounts after the network came back', async () => {
22022238
const history = idleHistory('chat-held-while-away')

‎apps/sim/app/workspace/[workspaceId]/home/hooks/use-chat.ts‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4799,7 +4799,9 @@ export function useChat(
47994799
if (!removedFromQueue) {
48004800
return
48014801
}
4802-
if (options.epoch !== queueDispatchEpochRef.current && !retriesOnItsOwn) {
4802+
/* A withdrawn send was never admitted, so it goes back to its chat's queue
4803+
even when the user has moved on since its dispatch started. */
4804+
if (options.epoch !== queueDispatchEpochRef.current && !withdrawn) {
48034805
return
48044806
}
48054807
// If the user explicitly removed this message during dispatch, honor

0 commit comments

Comments
 (0)