From d11898433a732de56d24b94968bb76c5b186d774 Mon Sep 17 00:00:00 2001 From: Yashovardhan Agrawal <21066442+yashovardhan@users.noreply.github.com> Date: Mon, 14 Sep 2026 16:57:42 +0400 Subject: [PATCH 1/3] Prevent stacked Ask AI Enter replays from overlapping catch-up polls. A second Enter while waiting used to start another interval, and uninstall left those timers running, so submit could fire more than once or after the listener was gone. Co-authored-by: Cursor --- src/lib/algolia-ask-ai.ts | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/src/lib/algolia-ask-ai.ts b/src/lib/algolia-ask-ai.ts index 31f81ed9b8b..f11cbc68b31 100644 --- a/src/lib/algolia-ask-ai.ts +++ b/src/lib/algolia-ask-ai.ts @@ -75,10 +75,21 @@ function highlightedAskAiQuery(): string | null { * message DocSearch renders is the one it sends. * * Replaying is abandoned if the user keeps typing or leaves, and falls through to the original - * behaviour on timeout so Enter can never be swallowed outright. + * behaviour on timeout so Enter can never be swallowed outright. A later Enter replaces any wait + * already in flight so the option is submitted once, and uninstall clears that wait so a late + * replay cannot fire after the listener is gone. */ function installStaleQuestionGuard(): () => void { let replaying = false + let poll: number | undefined + + const stopPoll = () => { + if (poll === undefined) { + return + } + window.clearInterval(poll) + poll = undefined + } const submit = (input: HTMLInputElement) => { replaying = true @@ -110,19 +121,21 @@ function installStaleQuestionGuard(): () => void { event.preventDefault() event.stopImmediatePropagation() + stopPoll() + const deadline = Date.now() + OPTION_CATCH_UP_TIMEOUT_MS - const poll = window.setInterval(() => { + poll = window.setInterval(() => { const caughtUp = normalizeQuery(highlightedAskAiQuery() ?? '') === normalizeQuery(intendedQuery) const abandoned = !input.isConnected || input.value !== intendedQuery if (abandoned) { - window.clearInterval(poll) + stopPoll() return } if (caughtUp || Date.now() > deadline) { - window.clearInterval(poll) + stopPoll() submit(input) } }, OPTION_CATCH_UP_POLL_MS) @@ -131,6 +144,7 @@ function installStaleQuestionGuard(): () => void { document.addEventListener('keydown', onKeyDown, true) return () => { + stopPoll() document.removeEventListener('keydown', onKeyDown, true) } } From 1d6b0b2befbacc698fd6b6a07a9a71f0cd132506 Mon Sep 17 00:00:00 2001 From: Yashovardhan Agrawal <21066442+yashovardhan@users.noreply.github.com> Date: Mon, 14 Sep 2026 17:05:59 +0400 Subject: [PATCH 2/3] Restore Ask AI code block contrast in light mode. DocSearch renders answer code as `
`, which the site's Prism theme claimed with `!important` and painted in the light gray meant for the dark code blocks on doc pages, leaving it near-invisible on DocSearch's light code background.

Co-authored-by: Cursor 
---
 src/scss/theme/_doc-search.scss | 9 +++++++++
 1 file changed, 9 insertions(+)

diff --git a/src/scss/theme/_doc-search.scss b/src/scss/theme/_doc-search.scss
index 93ab42ffbf5..96d021c67dd 100644
--- a/src/scss/theme/_doc-search.scss
+++ b/src/scss/theme/_doc-search.scss
@@ -236,6 +236,15 @@
   }
 }
 
+// Ask AI renders a fenced code block as `
`, which the site's Prism
+// theme claims with `!important` and paints in the light gray it uses on the dark code blocks of
+// doc pages. DocSearch backs its code blocks with the modal surface instead, so in light mode that
+// gray lands on near-white. Matched from the document because this markup is shared by the search
+// modal and the sidepanel.
+.DocSearch-Markdown-Content [class*='language-'] {
+  color: var(--docsearch-text-color) !important;
+}
+
 .main-wrapper {
   .search-page-wrapper & {
     margin: auto;

From 118c229696c70cc1c2023b736e4f30ff6fb0febe Mon Sep 17 00:00:00 2001
From: Yashovardhan Agrawal <21066442+yashovardhan@users.noreply.github.com>
Date: Mon, 14 Sep 2026 18:21:50 +0400
Subject: [PATCH 3/3] Cancel a pending Ask AI Enter wait on any later Enter.

The catch-up poll was only replaced when Enter was intercepted again. A later Enter that the guard let through (option already caught up, or no longer highlighted) left that interval running, so a replay could still fire after DocSearch had already handled the question.

Co-authored-by: Cursor 
---
 src/lib/algolia-ask-ai.ts | 12 +++++++-----
 1 file changed, 7 insertions(+), 5 deletions(-)

diff --git a/src/lib/algolia-ask-ai.ts b/src/lib/algolia-ask-ai.ts
index f11cbc68b31..4f8092d71dd 100644
--- a/src/lib/algolia-ask-ai.ts
+++ b/src/lib/algolia-ask-ai.ts
@@ -75,9 +75,9 @@ function highlightedAskAiQuery(): string | null {
  * message DocSearch renders is the one it sends.
  *
  * Replaying is abandoned if the user keeps typing or leaves, and falls through to the original
- * behaviour on timeout so Enter can never be swallowed outright. A later Enter replaces any wait
- * already in flight so the option is submitted once, and uninstall clears that wait so a late
- * replay cannot fire after the listener is gone.
+ * behaviour on timeout so Enter can never be swallowed outright. At most one wait is ever pending:
+ * a later Enter supersedes it and uninstall clears it, so a replay can never land after the
+ * question it was holding back has already been asked.
  */
 function installStaleQuestionGuard(): () => void {
   let replaying = false
@@ -112,6 +112,10 @@ function installStaleQuestionGuard(): () => void {
       return
     }
 
+    // Every Enter the user presses supersedes a wait already in flight, including one this guard
+    // lets through: DocSearch acts on it there and then, so replaying afterwards would ask twice.
+    stopPoll()
+
     const optionQuery = highlightedAskAiQuery()
     const intendedQuery = input.value
     if (optionQuery === null || normalizeQuery(optionQuery) === normalizeQuery(intendedQuery)) {
@@ -121,8 +125,6 @@ function installStaleQuestionGuard(): () => void {
     event.preventDefault()
     event.stopImmediatePropagation()
 
-    stopPoll()
-
     const deadline = Date.now() + OPTION_CATCH_UP_TIMEOUT_MS
     poll = window.setInterval(() => {
       const caughtUp =