From 3ed448fed7f8eb6779ddbdceec832c948e88aa31 Mon Sep 17 00:00:00 2001 From: Peter White <1788320+peterwhite@users.noreply.github.com> Date: Tue, 6 Oct 2026 14:11:41 +0200 Subject: [PATCH] Keep ?ask out of the search field while integration assistants load (#4661) --- .changeset/ask-param-no-search-flash.md | 5 +++++ .../src/components/Embeddable/EmbeddableRootLayout.tsx | 3 +++ .../src/components/Integrations/LoadIntegrations.tsx | 4 ++++ .../src/components/Search/useSearchController.tsx | 10 +++++++--- 4 files changed, 19 insertions(+), 3 deletions(-) create mode 100644 .changeset/ask-param-no-search-flash.md diff --git a/.changeset/ask-param-no-search-flash.md b/.changeset/ask-param-no-search-flash.md new file mode 100644 index 000000000..88733a4fa --- /dev/null +++ b/.changeset/ask-param-no-search-flash.md @@ -0,0 +1,5 @@ +--- +"gitbook": patch +--- + +Stop the `?ask` query briefly appearing in the search field before an integration assistant opens. diff --git a/packages/gitbook/src/components/Embeddable/EmbeddableRootLayout.tsx b/packages/gitbook/src/components/Embeddable/EmbeddableRootLayout.tsx index 4ac7a73a9..645dd450f 100644 --- a/packages/gitbook/src/components/Embeddable/EmbeddableRootLayout.tsx +++ b/packages/gitbook/src/components/Embeddable/EmbeddableRootLayout.tsx @@ -1,6 +1,7 @@ import type { CustomizationDefaultThemeMode } from '@gitbook/api'; import { SiteInsightsTrademarkPlacement } from '@gitbook/api'; +import { LoadIntegrations } from '../Integrations'; import { NavigationLoader } from '../primitives/NavigationLoader'; import { SpaceLayoutServerContext } from '../SpaceLayout'; import { Trademark } from '../TableOfContents/Trademark'; @@ -93,6 +94,8 @@ export async function EmbeddableRootLayout({ /> + {/* Embeds load no integrations; this marks them loaded so search stops waiting. */} + ); diff --git a/packages/gitbook/src/components/Integrations/LoadIntegrations.tsx b/packages/gitbook/src/components/Integrations/LoadIntegrations.tsx index b3627f610..3ea19f9cd 100644 --- a/packages/gitbook/src/components/Integrations/LoadIntegrations.tsx +++ b/packages/gitbook/src/components/Integrations/LoadIntegrations.tsx @@ -147,6 +147,10 @@ export function useCustomCookieBanner(): CustomCookieBannerStore { export function LoadIntegrations() { React.useEffect(() => { return whenIntegrationsReady(() => { + // Integrations must only receive `load` once, even if this is mounted twice. + if (integrationsStore.getState().loaded) { + return; + } dispatchGitBookIntegrationEvent('load'); integrationsStore.setState({ loaded: true }); }); diff --git a/packages/gitbook/src/components/Search/useSearchController.tsx b/packages/gitbook/src/components/Search/useSearchController.tsx index d520ec6c5..02896b4c9 100644 --- a/packages/gitbook/src/components/Search/useSearchController.tsx +++ b/packages/gitbook/src/components/Search/useSearchController.tsx @@ -5,6 +5,7 @@ import React from 'react'; import { useAI } from '../AI'; import { useTrackEvent } from '../Insights'; +import { useIntegrationsLoaded } from '../Integrations'; import { useBodyLoaded } from '../primitives'; import { clearLastSearchQuery, @@ -140,6 +141,7 @@ export function useSearchController( const trackEvent = useTrackEvent(); const resultsRef = React.useRef(null); const isLoaded = useBodyLoaded(); + const integrationsLoaded = useIntegrationsLoaded(); const restoredLastQueryForSiteSpaceRef = React.useRef(null); React.useEffect(() => { @@ -169,6 +171,8 @@ export function useSearchController( const withAI = assistants.length > 0; const withSearchAI = assistants.filter((assistant) => assistant.mode === 'search').length > 0; + // Integration assistants register on window load, so wait for it before treating `ask` as a search query. + const askIsSearchQuery = withSearchAI || (!withAI && integrationsLoaded); // Handle initial ask state on page load, once assistants are ready. // `ask=` should still bootstrap the assistant on the docs site, so we must @@ -203,7 +207,7 @@ export function useSearchController( const query = prev?.query ?? getLastSearchQuery(siteSpace.id) ?? - (withSearchAI || !withAI ? prev?.ask : null) ?? + (askIsSearchQuery ? prev?.ask : null) ?? ''; return { @@ -217,7 +221,7 @@ export function useSearchController( trackEvent({ type: 'search_open', }); - }, [state?.open, setSearchState, siteSpace.id, trackEvent, withAI, withSearchAI]); + }, [state?.open, setSearchState, siteSpace.id, trackEvent, withAI, askIsSearchQuery]); const setQuery = React.useCallback( (value: string) => { @@ -261,7 +265,7 @@ export function useSearchController( }); const searchValue = - state?.query ?? (withSearchAI || !withAI ? state?.ask : null) ?? lastSearchQuery ?? ''; + state?.query ?? (askIsSearchQuery ? state?.ask : null) ?? lastSearchQuery ?? ''; const searchResultsId = `search-results-${React.useId()}`; // Only clears the remembered last query and stops any in-flight fetch — it must