@@ -813,6 +813,158 @@ try {
813813 }
814814 }
815815 } )
816+ await check (
817+ 'Search keeps results usable through partial coverage and a failed request' ,
818+ async ( ) => {
819+ await page . setViewportSize ( { width : 1440 , height : 1000 } )
820+ const samples = [
821+ {
822+ documentId : 'launch-brief' ,
823+ documentName : 'Launch brief' ,
824+ connectorType : 'google_drive' ,
825+ knowledgeBaseName : 'Google Drive' ,
826+ sourceUrl : 'https://docs.example.test/launch' ,
827+ content : 'The launch checklist is complete. Final owner sign-off is due Friday.' ,
828+ } ,
829+ {
830+ documentId : 'launch-readiness' ,
831+ documentName : 'Launch readiness' ,
832+ connectorType : 'confluence' ,
833+ knowledgeBaseName : 'Confluence' ,
834+ sourceUrl : 'https://wiki.example.test/launch' ,
835+ content : 'Engineering validation is complete. The support handoff is still in progress.' ,
836+ } ,
837+ ] . map ( ( sample ) => ( {
838+ ...sample ,
839+ knowledgeBaseId : 'preview-source' ,
840+ author : 'Taylor' ,
841+ sourceModifiedAt : '2026-10-28T18:30:00.000Z' ,
842+ chunkIndex : 0 ,
843+ similarity : 0.9 ,
844+ } ) )
845+ let unavailable = false
846+ let partial = false
847+ const searchRoute = '**/api/knowledge/search'
848+ await page . route ( searchRoute , async ( route ) => {
849+ if ( unavailable ) {
850+ await route . fulfill ( { status : 503 , json : { error : 'Search is temporarily unavailable' } } )
851+ return
852+ }
853+ const body = toRecord ( route . request ( ) . postDataJSON ( ) )
854+ const source = toRecord ( body . filters ) . source
855+ await route . fulfill ( {
856+ json : searchWorkspaceKnowledgeContract . response . schema . parse ( {
857+ success : true ,
858+ data : {
859+ query : body . query ,
860+ results : samples . filter ( ( sample ) => ! source || sample . connectorType === source ) ,
861+ retrieval : { status : partial ? 'partial' : 'complete' , timedOutLegs : [ ] } ,
862+ live : {
863+ backend : 'live' ,
864+ accounts : samples . map ( ( sample ) => ( {
865+ accountId : sample . documentId ,
866+ provider : sample . connectorType ,
867+ displayName : 'taylor@example.test' ,
868+ status : partial && sample . connectorType === 'confluence' ? 'timeout' : 'ok' ,
869+ } ) ) ,
870+ guidance : '' ,
871+ } ,
872+ } ,
873+ } ) ,
874+ } )
875+ } )
876+ try {
877+ await page . goto ( `${ baseUrl . origin } ${ root } /search?q=Launch%20readiness` )
878+ await expect ( page . getByText ( 'Launch brief' , { exact : true } ) . first ( ) ) . toBeVisible ( )
879+ await capture ( 'search-populated-desktop' )
880+ await page . getByRole ( 'button' , { name : 'Filter by source: All sources' } ) . click ( )
881+ await capture ( 'search-source-filter' )
882+ await page . getByRole ( 'menuitem' , { name : 'Google Drive' , exact : true } ) . click ( )
883+ await expect ( page ) . toHaveURL ( ( url ) => url . searchParams . get ( 'source' ) === 'google_drive' )
884+ await expect ( page . getByText ( 'Launch readiness' , { exact : true } ) ) . toHaveCount ( 0 )
885+ await capture ( 'search-filtered' )
886+ await page . getByRole ( 'button' , { name : 'Filter by date: Any time' } ) . click ( )
887+ await capture ( 'search-date-filter' )
888+ await page . keyboard . press ( 'Escape' )
889+ await page . setViewportSize ( { width : 390 , height : 844 } )
890+ await capture ( 'search-populated-mobile' )
891+ await page . setViewportSize ( { width : 1440 , height : 1000 } )
892+ partial = true
893+ await page . goto ( `${ baseUrl . origin } ${ root } /search?q=Partial%20coverage` )
894+ await expect (
895+ page . getByText ( 'Some apps couldn’t be searched. Showing available results.' )
896+ ) . toBeVisible ( )
897+ await capture ( 'search-partial-coverage' )
898+ unavailable = true
899+ await page . goto ( `${ baseUrl . origin } ${ root } /search?q=Provider%20outage` )
900+ await expect ( page . getByText ( 'Search couldn’t run.' , { exact : true } ) ) . toBeVisible ( )
901+ await capture ( 'search-error-retry' )
902+ unavailable = false
903+ partial = false
904+ await page . getByRole ( 'button' , { name : 'Try again' , exact : true } ) . click ( )
905+ await expect ( page . getByText ( 'Launch brief' , { exact : true } ) . first ( ) ) . toBeVisible ( )
906+ await expect ( page . getByText ( 'Search couldn’t run.' , { exact : true } ) ) . toHaveCount ( 0 )
907+
908+ const citedChatId = generateId ( )
909+ const citedAnswer =
910+ 'The checklist is complete, with final owner sign-off due Friday. <source>{"id":"document:launch-brief"}</source>\n\nThe support handoff is still in progress. <source>{"id":"document:launch-readiness"}</source>'
911+ await sql `insert into copilot_chats (id, user_id, organization_id, type, title, config)
912+ values (${ citedChatId } , ${ userId } , ${ organizationId } , 'mothership', 'Cited launch update', ${ sql . json ( { conversationMode : 'assistant' } ) } )`
913+ for ( const [ seq , role , content ] of [
914+ [ 1 , 'user' , question ] ,
915+ [ 2 , 'assistant' , citedAnswer ] ,
916+ ] as const ) {
917+ const id = generateId ( )
918+ const contentBlocks =
919+ role === 'assistant'
920+ ? [
921+ {
922+ type : 'tool' ,
923+ phase : 'call' ,
924+ toolCall : {
925+ id : 'preview-retrieval' ,
926+ name : 'search_workspace' ,
927+ state : 'success' ,
928+ result : {
929+ success : true ,
930+ output : {
931+ success : true ,
932+ data : {
933+ results : samples . map ( ( sample ) => ( {
934+ ...sample ,
935+ citationId : `document:${ sample . documentId } ` ,
936+ citationUrl : sample . sourceUrl ,
937+ } ) ) ,
938+ } ,
939+ } ,
940+ } ,
941+ } ,
942+ } ,
943+ { type : 'text' , channel : 'final' , content } ,
944+ ]
945+ : undefined
946+ await sql `insert into copilot_messages (id, chat_id, message_id, role, content, seq)
947+ values (${ generateId ( ) } , ${ citedChatId } , ${ id } , ${ role } , ${ sql . json ( { id, role, content, contentBlocks, timestamp : new Date ( ) . toISOString ( ) } ) } , ${ seq } )`
948+ }
949+ await page . goto ( `${ baseUrl . origin } ${ root } /search/chat/${ citedChatId } ` )
950+ await expect ( page . getByRole ( 'button' , { name : '2 sources' , exact : true } ) ) . toBeVisible ( )
951+ await capture ( 'assistant-cited-answer' )
952+ await page . getByRole ( 'button' , { name : '2 sources' , exact : true } ) . click ( )
953+ await capture ( 'assistant-sources-dropdown' )
954+ await page . getByRole ( 'button' , { name : 'View all sources' , exact : true } ) . click ( )
955+ const sourcesPanel = page . getByLabel ( 'Cited sources' , { exact : true } )
956+ await expect ( sourcesPanel ) . toBeVisible ( )
957+ await expect ( sourcesPanel . getByText ( 'Launch brief' , { exact : true } ) ) . toBeVisible ( )
958+ await expect ( sourcesPanel . getByText ( 'Launch readiness' , { exact : true } ) ) . toBeVisible ( )
959+ await capture ( 'assistant-resources-panel' )
960+ await page . setViewportSize ( { width : 390 , height : 844 } )
961+ await expect ( sourcesPanel ) . toBeVisible ( )
962+ await capture ( 'assistant-resources-mobile' )
963+ } finally {
964+ await page . unroute ( searchRoute )
965+ }
966+ }
967+ )
816968 await check (
817969 'Search-only members can reopen assistant history without access to Home chat' ,
818970 async ( ) => {
0 commit comments