diff --git a/client/src/components/Chat/Messages/Content/WebSearch.tsx b/client/src/components/Chat/Messages/Content/WebSearch.tsx
index 7af0811fd7..c8d76a04b2 100644
--- a/client/src/components/Chat/Messages/Content/WebSearch.tsx
+++ b/client/src/components/Chat/Messages/Content/WebSearch.tsx
@@ -141,26 +141,21 @@ export default function WebSearch({
return [];
}, [searchResults, attachments, ownTurn]);
- const processedSources = useMemo(() => {
+ // Show favicons from the raw SERP results immediately rather than waiting for
+ // each source to flip to `processed`; the agents scrape barrier would otherwise
+ // freeze the stack on "Searching the web" for the slowest scrape's duration.
+ const streamingSources = useMemo(() => {
if (complete && !finalizing) {
return [];
}
- if (!searchResults) {
- return [];
- }
- const result = searchResults[ownTurn];
+ const result = searchResults?.[ownTurn];
if (!result) {
return [];
}
- if (finalizing) {
- return [...(result.organic || []), ...(result.topStories || [])];
- }
- return [...(result.organic || []), ...(result.topStories || [])].filter(
- (source) => source.processed === true,
- );
+ return [...(result.organic || []), ...(result.topStories || [])];
}, [searchResults, complete, finalizing, ownTurn]);
- const showSources = processedSources.length > 0;
+ const showSources = streamingSources.length > 0;
const progressText = useMemo(() => {
let text: ProgressKeys =
ownTurn !== '0' ? 'com_ui_web_searching_again' : 'com_ui_web_searching';
@@ -278,7 +273,7 @@ export default function WebSearch({
{progressText}
- {showSources && }
+ {showSources && }
{progressText}
diff --git a/client/src/components/Chat/Messages/Content/__tests__/WebSearch.test.tsx b/client/src/components/Chat/Messages/Content/__tests__/WebSearch.test.tsx
index e18ad58e49..fc29f0391b 100644
--- a/client/src/components/Chat/Messages/Content/__tests__/WebSearch.test.tsx
+++ b/client/src/components/Chat/Messages/Content/__tests__/WebSearch.test.tsx
@@ -205,13 +205,13 @@ describe('WebSearch', () => {
});
});
- describe('processedSources scoping', () => {
- it('shows processed sources only from ownTurn during streaming', () => {
+ describe('streaming favicons', () => {
+ it('renders favicons for all ownTurn sources during streaming, before they are processed', () => {
const searchResults = makeSearchResults({
0: {
organic: [
{ link: 'https://a.com', title: 'A', processed: true } as ValidSource,
- { link: 'https://b.com', title: 'B', processed: false } as ValidSource,
+ { link: 'https://b.com', title: 'B' } as ValidSource,
],
},
1: {
@@ -229,11 +229,27 @@ describe('WebSearch', () => {
initialProgress: 0.5,
});
- const favicons = screen.queryAllByTestId('stacked-favicons');
- if (favicons.length > 0) {
- const count = Number(favicons[0].getAttribute('data-count'));
- expect(count).toBeLessThanOrEqual(2);
- }
+ const favicons = screen.getByTestId('stacked-favicons');
+ // Both turn-0 sources show immediately — including the unprocessed one —
+ // while the turn-1 source stays scoped out.
+ expect(Number(favicons.getAttribute('data-count'))).toBe(2);
+ expect(screen.getAllByText('Processing results').length).toBeGreaterThanOrEqual(1);
+ });
+
+ it('stays on "Searching the web" until any source for the turn arrives', () => {
+ const searchResults = makeSearchResults({ 0: { organic: [] } });
+ const attachments = [makeAttachment(0, searchResults['0'])];
+
+ renderWebSearch({
+ searchResults,
+ attachments,
+ isSubmitting: true,
+ isLast: true,
+ initialProgress: 0.5,
+ });
+
+ expect(screen.queryByTestId('stacked-favicons')).not.toBeInTheDocument();
+ expect(screen.getAllByText('Searching the web').length).toBeGreaterThanOrEqual(1);
});
});