1- import type { QueryClient } from '@tanstack/react-query'
1+ import { sleep } from '@sim/utils/helpers'
2+ import { QueryClient , QueryObserver } from '@tanstack/react-query'
23import { beforeEach , describe , expect , it , vi } from 'vitest'
34
45const { suspendBrowserScope, suspendTerminalScope } = vi . hoisted ( ( ) => ( {
@@ -9,7 +10,7 @@ const { suspendBrowserScope, suspendTerminalScope } = vi.hoisted(() => ({
910vi . mock ( '@/lib/browser-agent/transport' , ( ) => ( { suspendBrowserScope } ) )
1011vi . mock ( '@/lib/terminal/transport' , ( ) => ( { suspendTerminalScope } ) )
1112
12- import { mothershipChatKeys } from '@/hooks/queries/mothership-chats'
13+ import { type MothershipChatHistory , mothershipChatKeys } from '@/hooks/queries/mothership-chats'
1314import {
1415 handleMothershipChatStatusEvent ,
1516 resyncMothershipChatCaches ,
@@ -184,6 +185,80 @@ describe('handleMothershipChatStatusEvent', () => {
184185 )
185186} )
186187
188+ describe ( 'chat detail refetches driven by status events' , ( ) => {
189+ function mountDetail ( cached : MothershipChatHistory ) {
190+ const queryClient = new QueryClient ( )
191+ const fetchTranscript = vi . fn ( async ( ) => cached )
192+ queryClient . setQueryData ( mothershipChatKeys . detail ( 'chat-1' ) , cached )
193+ const unsubscribe = new QueryObserver ( queryClient , {
194+ queryKey : mothershipChatKeys . detail ( 'chat-1' ) ,
195+ queryFn : fetchTranscript ,
196+ staleTime : Number . POSITIVE_INFINITY ,
197+ } ) . subscribe ( ( ) => { } )
198+ return { queryClient, fetchTranscript, unsubscribe }
199+ }
200+
201+ const liveStream : MothershipChatHistory = {
202+ id : 'chat-1' ,
203+ title : null ,
204+ messages : [
205+ { id : 'stream-1' } ,
206+ { id : 'live-assistant:stream-1' } ,
207+ ] as MothershipChatHistory [ 'messages' ] ,
208+ activeStreamId : 'stream-1' ,
209+ resources : [ ] ,
210+ }
211+
212+ it ( 'does not reload the transcript when the viewer finishes its own live stream' , async ( ) => {
213+ const { queryClient, fetchTranscript, unsubscribe } = mountDetail ( liveStream )
214+
215+ handleMothershipChatStatusEvent ( queryClient , 'ws-1' , {
216+ chatId : 'chat-1' ,
217+ type : 'completed' ,
218+ streamId : 'stream-1' ,
219+ } )
220+ await sleep ( 0 )
221+
222+ expect ( fetchTranscript ) . not . toHaveBeenCalled ( )
223+ unsubscribe ( )
224+ } )
225+
226+ it ( 'reloads the saved transcript when a cached mid-stream detail is opened after completion' , async ( ) => {
227+ const queryClient = new QueryClient ( )
228+ const fetchTranscript = vi . fn ( async ( ) => ( { ...liveStream , activeStreamId : null } ) )
229+ queryClient . setQueryData ( mothershipChatKeys . detail ( 'chat-1' ) , liveStream )
230+
231+ handleMothershipChatStatusEvent ( queryClient , 'ws-1' , {
232+ chatId : 'chat-1' ,
233+ type : 'completed' ,
234+ streamId : 'stream-1' ,
235+ } )
236+ const unsubscribe = new QueryObserver ( queryClient , {
237+ queryKey : mothershipChatKeys . detail ( 'chat-1' ) ,
238+ queryFn : fetchTranscript ,
239+ staleTime : Number . POSITIVE_INFINITY ,
240+ } ) . subscribe ( ( ) => { } )
241+
242+ await vi . waitFor ( ( ) => expect ( fetchTranscript ) . toHaveBeenCalledTimes ( 1 ) )
243+ unsubscribe ( )
244+ } )
245+
246+ it ( 'marks the detail stale on rename without reloading the transcript' , async ( ) => {
247+ const { queryClient, fetchTranscript, unsubscribe } = mountDetail ( {
248+ ...liveStream ,
249+ messages : [ ] ,
250+ activeStreamId : null ,
251+ } )
252+
253+ handleMothershipChatStatusEvent ( queryClient , 'ws-1' , { chatId : 'chat-1' , type : 'renamed' } )
254+ await sleep ( 0 )
255+
256+ expect ( fetchTranscript ) . not . toHaveBeenCalled ( )
257+ expect ( queryClient . getQueryState ( mothershipChatKeys . detail ( 'chat-1' ) ) ?. isInvalidated ) . toBe ( true )
258+ unsubscribe ( )
259+ } )
260+ } )
261+
187262describe ( 'resyncMothershipChatCaches' , ( ) => {
188263 const queryClient = {
189264 invalidateQueries : vi . fn ( ) . mockResolvedValue ( undefined ) ,
0 commit comments