Skip to content

Commit 70541f4

Browse files
committed
fix(mothership): keep reconnect resync off locally streaming chats
The resync invalidated every chat detail, including one whose stream this client is rendering optimistically. Refetching there replaces the local transcript with a server copy that does not yet hold the in-flight message, which is exactly what status events avoid via shouldSkipDetailInvalidationForStreamEvent. Filter the detail invalidation with the same isLocalOptimisticActiveStream check. Those chats reconcile when their own stream finishes.
1 parent 0c22829 commit 70541f4

2 files changed

Lines changed: 40 additions & 5 deletions

File tree

apps/sim/hooks/use-mothership-chat-events.test.ts

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -432,15 +432,41 @@ describe('resyncMothershipChatCaches', () => {
432432
vi.clearAllMocks()
433433
})
434434

435-
it('invalidates the workspace lists and every chat detail', () => {
435+
function detailPredicate() {
436+
resyncMothershipChatCaches(queryClient, 'ws-1')
437+
const predicate = queryClient.invalidateQueries.mock.calls
438+
.map(([arg]: [{ predicate?: (query: unknown) => boolean }]) => arg.predicate)
439+
.find(Boolean)
440+
if (!predicate) throw new Error('detail invalidation did not pass a predicate')
441+
return (data: unknown) => predicate({ state: { data } })
442+
}
443+
444+
it('invalidates the workspace lists and the chat details', () => {
436445
resyncMothershipChatCaches(queryClient, 'ws-1')
437446

438447
expect(queryClient.invalidateQueries).toHaveBeenCalledTimes(2)
439448
expect(queryClient.invalidateQueries).toHaveBeenCalledWith({
440449
queryKey: mothershipChatKeys.workspaceLists('ws-1'),
441450
})
442-
expect(queryClient.invalidateQueries).toHaveBeenCalledWith({
443-
queryKey: mothershipChatKeys.details(),
444-
})
451+
expect(queryClient.invalidateQueries).toHaveBeenCalledWith(
452+
expect.objectContaining({ queryKey: mothershipChatKeys.details() })
453+
)
454+
})
455+
456+
it('skips the detail of a chat whose stream this client is rendering locally', () => {
457+
expect(
458+
detailPredicate()({
459+
messages: [{ id: 'new-stream' }, { id: 'live-assistant:new-stream' }],
460+
activeStreamId: 'new-stream',
461+
})
462+
).toBe(false)
463+
})
464+
465+
it('invalidates details with no active stream, and streams not rendered locally', () => {
466+
const predicate = detailPredicate()
467+
468+
expect(predicate(undefined)).toBe(true)
469+
expect(predicate({ messages: [{ id: 'stream-1' }] })).toBe(true)
470+
expect(predicate({ messages: [{ id: 'stream-1' }], activeStreamId: 'stream-1' })).toBe(true)
445471
})
446472
})

apps/sim/hooks/use-mothership-chat-events.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,13 +138,22 @@ export function handleMothershipChatStatusEvent(
138138
* no connection was open — so any reconnect may have missed a create, rename,
139139
* delete, or completion. Invalidating the workspace lists and every chat detail
140140
* reconciles from the server; only queries that are currently mounted refetch.
141+
*
142+
* A chat whose stream this client is rendering locally is left alone, for the
143+
* same reason status events skip it: refetching mid-stream would replace the
144+
* optimistic transcript with a server copy that does not yet hold the in-flight
145+
* message. That chat reconciles when its own stream finishes.
141146
*/
142147
export function resyncMothershipChatCaches(
143148
queryClient: Pick<QueryClient, 'invalidateQueries'>,
144149
workspaceId: string
145150
): void {
146151
queryClient.invalidateQueries({ queryKey: mothershipChatKeys.workspaceLists(workspaceId) })
147-
queryClient.invalidateQueries({ queryKey: mothershipChatKeys.details() })
152+
queryClient.invalidateQueries({
153+
queryKey: mothershipChatKeys.details(),
154+
predicate: (query) =>
155+
!isLocalOptimisticActiveStream(query.state.data as MothershipChatHistory | undefined),
156+
})
148157
}
149158

150159
/**

0 commit comments

Comments
 (0)