From 19593cd04a4f55cd1e7d158f4f98c1860c93b4eb Mon Sep 17 00:00:00 2001 From: Rachael Rose Renk <91027132+rachaelrenk@users.noreply.github.com> Date: Fri, 8 May 2026 12:26:53 -1000 Subject: [PATCH 1/6] docs: add search UX improvements (Ask AI fallback + Enter key) Add two search dialog enhancements via inline script in CustomSidebar.astro: 1. 'Ask AI instead' button: appears in the search dialog when Pagefind shows a no-results message. Clicking it closes the search dialog and opens the Kapa AI chat. Uses MutationObserver to detect Pagefind's state changes. Styled with the shared warp-control chrome palette. 2. Enter key navigation: pressing Enter when no specific result is focused navigates to the first search result. Pagefind's default UI doesn't handle this -- it only submits the search form. Note: breadcrumbs in search results (B4) is not addressed here -- it requires a Pagefind build-time indexing plugin to inject breadcrumb metadata, which is out of scope for this PR. Co-Authored-By: Oz --- src/components/CustomSidebar.astro | 84 ++++++++++++++++++++++++++++++ 1 file changed, 84 insertions(+) diff --git a/src/components/CustomSidebar.astro b/src/components/CustomSidebar.astro index 6068e373..a1d42fc9 100644 --- a/src/components/CustomSidebar.astro +++ b/src/components/CustomSidebar.astro @@ -34,6 +34,90 @@ import KapaLauncher from './KapaLauncher.astro'; + + + + 1. "Ask AI" row styled like a search result at the bottom of the + results area (GitBook-style). Always visible when results or + no-results message are shown. + 2. Arrow key navigation through results with visible selection + state. Enter opens the selected result. --> diff --git a/src/styles/warp-components.css b/src/styles/warp-components.css index 06e1a5ea..5f9102e2 100644 --- a/src/styles/warp-components.css +++ b/src/styles/warp-components.css @@ -1001,3 +1001,74 @@ site-search #starlight__search .pagefind-ui__result-nested:focus-within { outline: 1px solid var(--warp-control-border-hover); background-color: var(--warp-control-bg-hover); } + +/* Arrow-key selection state for search results. Applied by the + keyboard navigation script in CustomSidebar.astro. Matches the + hover treatment so mouse and keyboard produce the same visual. */ +site-search #starlight__search .warp-search-selected { + outline: 1px solid var(--warp-control-border-hover); + background-color: var(--warp-control-bg-hover); +} + +/* -------------------------------------------------------------------------- + 19. "Ask AI" row in the search dialog + -------------------------------------------------------------------------- + Styled as a result card so it reads as an inline option, not a separate + CTA. Sits at the bottom of the results area. Shows an Enter kbd hint + when selected via arrow keys. */ + +.warp-search-ask-ai-row { + display: flex; + align-items: center; + gap: 0.75rem; + width: 100%; + padding: 0.75rem 1.25rem; + margin-top: 0.5rem; + border: 0; + border-radius: var(--sl-radius-sm); + background: var(--warp-control-bg); + color: var(--sl-color-text-accent); + font-family: var(--__sl-font, 'Inter', sans-serif); + font-size: var(--sl-text-sm); + font-weight: 500; + cursor: pointer; + transition: background-color 0.15s ease, outline-color 0.15s ease; +} + +.warp-search-ask-ai-row:hover, +.warp-search-ask-ai-row.warp-search-selected { + outline: 1px solid var(--warp-control-border-hover); + background-color: var(--warp-control-bg-hover); +} + +.warp-search-ask-ai-row svg { + flex-shrink: 0; + opacity: 0.7; +} + +.warp-search-ask-ai-row__label { + flex: 1; + text-align: left; +} + +.warp-search-ask-ai-row__kbd { + display: none; + padding: 0.125rem 0.375rem; + border-radius: var(--sl-radius-xs); + background: rgba(255, 255, 255, 0.08); + color: var(--sl-color-gray-3); + font-family: var(--__sl-font); + font-size: var(--sl-text-2xs); + font-weight: 400; + line-height: 1.2; +} + +:root[data-theme='light'] .warp-search-ask-ai-row__kbd { + background: rgba(0, 0, 0, 0.06); +} + +/* Show the Enter hint only when the row is selected via keyboard */ +.warp-search-selected .warp-search-ask-ai-row__kbd, +.warp-search-ask-ai-row.warp-search-selected .warp-search-ask-ai-row__kbd { + display: inline-block; +} From bd27ad58c4a7a28e15418dd84dacfba9fcaa1a1f Mon Sep 17 00:00:00 2001 From: Rachael Rose Renk <91027132+rachaelrenk@users.noreply.github.com> Date: Sun, 10 May 2026 08:36:36 -1000 Subject: [PATCH 3/6] docs: fix search arrow nav -- full block highlight, scroll, Ask AI persistence Address four issues from testing feedback: 1. Full block highlight: Arrow-key selection now targets .pagefind-ui__result (the full result including title + description), not just the title row. CSS also highlights inner .pagefind-ui__result-title and .pagefind-ui__result-nested cards so there's no visual gap. 2. Scroll on wrap-around: Uses smooth scrollIntoView so wrapping from last to first item scrolls visibly back to the top. 3. Ask AI persistence: Pagefind re-renders the results area on every keystroke, which removed our injected Ask AI row. The observer now re-checks on every tick and re-injects if the element was removed from the DOM. 4. Navigation order: Doc results first (top to bottom), Ask AI last -- matching the visual order in the results area. Co-Authored-By: Oz --- src/components/CustomSidebar.astro | 30 ++++++++++++++++++++---------- src/styles/warp-components.css | 13 ++++++++++++- 2 files changed, 32 insertions(+), 11 deletions(-) diff --git a/src/components/CustomSidebar.astro b/src/components/CustomSidebar.astro index 954face2..385e2b71 100644 --- a/src/components/CustomSidebar.astro +++ b/src/components/CustomSidebar.astro @@ -66,14 +66,21 @@ import KapaLauncher from './KapaLauncher.astro'; } // --- Inject / remove the Ask AI row --- + // Pagefind re-renders the results area on every keystroke, which + // removes our injected element. We re-inject on every observer + // tick by checking if the element is still in the DOM. function injectAskAi() { - if (document.getElementById(askAiId)) return; + var existing = document.getElementById(askAiId); var resultsArea = dialog.querySelector('.pagefind-ui__results-area'); if (!resultsArea) return; // Only show when there's a query (results or no-results message) var hasResults = dialog.querySelector('.pagefind-ui__result'); var hasMessage = dialog.querySelector('.pagefind-ui__message'); - if (!hasResults && !hasMessage) return; + if (!hasResults && !hasMessage) { if (existing) existing.remove(); return; } + // If it already exists and is still in the DOM, skip + if (existing && resultsArea.contains(existing)) return; + // Re-build and append at the bottom of results + if (existing) existing.remove(); resultsArea.appendChild(buildAskAiRow()); } @@ -83,13 +90,14 @@ import KapaLauncher from './KapaLauncher.astro'; } // --- Arrow key navigation --- + // Navigation order: doc results (top to bottom), then Ask AI (last). function getNavigableItems() { var items = []; - // Collect all result links + // Collect all result links in DOM order dialog.querySelectorAll('.pagefind-ui__result-link').forEach(function (link) { items.push(link); }); - // Add the Ask AI row if present + // Add the Ask AI row as the last navigable item var askAi = document.getElementById(askAiId); if (askAi) items.push(askAi); return items; @@ -110,11 +118,14 @@ import KapaLauncher from './KapaLauncher.astro'; if (index >= items.length) index = 0; selectedIndex = index; var item = items[selectedIndex]; - // For result links, highlight the parent result card - var card = item.closest('.pagefind-ui__result-title, .pagefind-ui__result-nested'); - var target = card || item; + // For result links, highlight the FULL result block (.pagefind-ui__result) + // not just the title row. This matches the hover behavior. + var block = item.closest('.pagefind-ui__result'); + var target = block || item; target.classList.add('warp-search-selected'); - target.scrollIntoView({ block: 'nearest' }); + // Use block:'center' so wrapping from last->first scrolls + // far enough to show the full item, not just its bottom edge. + target.scrollIntoView({ block: 'nearest', behavior: 'smooth' }); } // --- Keyboard handler --- @@ -142,8 +153,7 @@ import KapaLauncher from './KapaLauncher.astro'; // --- Observer: inject Ask AI + reset selection on results change --- var observer = new MutationObserver(function () { - var hasContent = dialog.querySelector('.pagefind-ui__result') || dialog.querySelector('.pagefind-ui__message'); - if (hasContent) { injectAskAi(); } else { removeAskAi(); } + injectAskAi(); clearSelection(); }); diff --git a/src/styles/warp-components.css b/src/styles/warp-components.css index 5f9102e2..7b86233c 100644 --- a/src/styles/warp-components.css +++ b/src/styles/warp-components.css @@ -1004,12 +1004,23 @@ site-search #starlight__search .pagefind-ui__result-nested:focus-within { /* Arrow-key selection state for search results. Applied by the keyboard navigation script in CustomSidebar.astro. Matches the - hover treatment so mouse and keyboard produce the same visual. */ + hover treatment so mouse and keyboard produce the same visual. + Targets .pagefind-ui__result (the full result block including + title + description) so the entire card highlights, not just + the title row. */ site-search #starlight__search .warp-search-selected { outline: 1px solid var(--warp-control-border-hover); background-color: var(--warp-control-bg-hover); } +/* When the full result block is selected, also highlight the + title and nested sub-result cards inside it so there's no + visual gap between the block outline and the inner cards. */ +site-search #starlight__search .warp-search-selected .pagefind-ui__result-title, +site-search #starlight__search .warp-search-selected .pagefind-ui__result-nested { + background-color: var(--warp-control-bg-hover); +} + /* -------------------------------------------------------------------------- 19. "Ask AI" row in the search dialog -------------------------------------------------------------------------- From c504beed85a267ff6fe679187a22eedd9d05ca45 Mon Sep 17 00:00:00 2001 From: Rachael Rose Renk <91027132+rachaelrenk@users.noreply.github.com> Date: Sun, 10 May 2026 09:19:51 -1000 Subject: [PATCH 4/6] docs: fix search nav to match mouse hover behavior Fix five issues from testing feedback: 1. Per-row highlight: Navigate per visible row (.pagefind-ui__result-title and .pagefind-ui__result-nested) instead of per link or per block. This matches the exact same elements the CSS hover rules target, so keyboard and mouse produce identical highlight granularity. 2. Ask AI first in nav order: Ask AI is visually at the top of the results area, so arrow-down from the search input now goes to Ask AI first, then to doc results. 3. One keypress per movement: Previously collected multiple links per result block, requiring several presses to cross a block. Now collects one item per visible row. 4. Mouse clears keyboard selection: Added mousemove listener on the dialog that clears the keyboard selection, so mouse hover always supersedes arrow-key highlight. 5. Enter navigates correctly: For result rows, clicks the link inside the row. For Ask AI, clicks the row directly. Co-Authored-By: Oz --- src/components/CustomSidebar.astro | 45 +++++++++++++++++------------- src/styles/warp-components.css | 17 +++-------- 2 files changed, 30 insertions(+), 32 deletions(-) diff --git a/src/components/CustomSidebar.astro b/src/components/CustomSidebar.astro index 385e2b71..f6ef2859 100644 --- a/src/components/CustomSidebar.astro +++ b/src/components/CustomSidebar.astro @@ -90,16 +90,18 @@ import KapaLauncher from './KapaLauncher.astro'; } // --- Arrow key navigation --- - // Navigation order: doc results (top to bottom), then Ask AI (last). + // Navigate per VISIBLE ROW: each .pagefind-ui__result-title and + // .pagefind-ui__result-nested is one navigable item (matching the + // mouse hover granularity). Ask AI is first (it's at the top). function getNavigableItems() { var items = []; - // Collect all result links in DOM order - dialog.querySelectorAll('.pagefind-ui__result-link').forEach(function (link) { - items.push(link); - }); - // Add the Ask AI row as the last navigable item + // Ask AI row first (visually at top of results) var askAi = document.getElementById(askAiId); if (askAi) items.push(askAi); + // Collect each visible row: title rows and sub-result rows + dialog.querySelectorAll('.pagefind-ui__result-title:not(:where(.pagefind-ui__result-nested *)), .pagefind-ui__result-nested').forEach(function (row) { + items.push(row); + }); return items; } @@ -118,16 +120,18 @@ import KapaLauncher from './KapaLauncher.astro'; if (index >= items.length) index = 0; selectedIndex = index; var item = items[selectedIndex]; - // For result links, highlight the FULL result block (.pagefind-ui__result) - // not just the title row. This matches the hover behavior. - var block = item.closest('.pagefind-ui__result'); - var target = block || item; - target.classList.add('warp-search-selected'); - // Use block:'center' so wrapping from last->first scrolls - // far enough to show the full item, not just its bottom edge. - target.scrollIntoView({ block: 'nearest', behavior: 'smooth' }); + // Highlight the row directly (title row or sub-result row) + // -- matches the same elements the CSS hover rules target. + item.classList.add('warp-search-selected'); + item.scrollIntoView({ block: 'nearest', behavior: 'smooth' }); } + // Clear keyboard selection when mouse moves over results + // so hover supersedes arrow-key highlight. + dialog.addEventListener('mousemove', function () { + if (selectedIndex >= 0) clearSelection(); + }, { passive: true }); + // --- Keyboard handler --- dialog.addEventListener('keydown', function (e) { var items = getNavigableItems(); @@ -139,14 +143,17 @@ import KapaLauncher from './KapaLauncher.astro'; } else if (e.key === 'ArrowUp') { e.preventDefault(); selectItem(selectedIndex - 1); - } else if (e.key === 'Enter') { + } else if (e.key === 'Enter') { if (selectedIndex >= 0 && selectedIndex < items.length) { e.preventDefault(); - items[selectedIndex].click(); - } else if (items.length > 0) { - // No selection: go to first result + // For result rows, click the link inside; for Ask AI, click the row + var link = items[selectedIndex].querySelector('.pagefind-ui__result-link'); + if (link) { link.click(); } else { items[selectedIndex].click(); } + } else if (items.length > 1) { + // No selection: go to first doc result (skip Ask AI at index 0) e.preventDefault(); - items[0].click(); + var firstLink = items[1] && items[1].querySelector('.pagefind-ui__result-link'); + if (firstLink) firstLink.click(); } } }); diff --git a/src/styles/warp-components.css b/src/styles/warp-components.css index 7b86233c..83c1f871 100644 --- a/src/styles/warp-components.css +++ b/src/styles/warp-components.css @@ -1003,24 +1003,15 @@ site-search #starlight__search .pagefind-ui__result-nested:focus-within { } /* Arrow-key selection state for search results. Applied by the - keyboard navigation script in CustomSidebar.astro. Matches the - hover treatment so mouse and keyboard produce the same visual. - Targets .pagefind-ui__result (the full result block including - title + description) so the entire card highlights, not just - the title row. */ + keyboard navigation script in CustomSidebar.astro. Targets the + same per-row elements (.pagefind-ui__result-title and + .pagefind-ui__result-nested) that the hover rules above target, + so keyboard and mouse highlight produce identical visuals. */ site-search #starlight__search .warp-search-selected { outline: 1px solid var(--warp-control-border-hover); background-color: var(--warp-control-bg-hover); } -/* When the full result block is selected, also highlight the - title and nested sub-result cards inside it so there's no - visual gap between the block outline and the inner cards. */ -site-search #starlight__search .warp-search-selected .pagefind-ui__result-title, -site-search #starlight__search .warp-search-selected .pagefind-ui__result-nested { - background-color: var(--warp-control-bg-hover); -} - /* -------------------------------------------------------------------------- 19. "Ask AI" row in the search dialog -------------------------------------------------------------------------- From 7b0decb348822f70e0cbc0dc7950be4b93c494e6 Mon Sep 17 00:00:00 2001 From: Rachael Rose Renk <91027132+rachaelrenk@users.noreply.github.com> Date: Sun, 10 May 2026 09:37:11 -1000 Subject: [PATCH 5/6] docs: show Ask AI immediately on dialog open + scroll to top on wrap Two fixes: 1. Ask AI visible on open: Previously only appeared after typing. Now injects into #starlight__search (the empty-state container) when .pagefind-ui__results-area doesn't exist yet, so it shows as soon as the search dialog opens. 2. Scroll to top on wrap-around: When arrow-down wraps from the last result back to Ask AI (index 0), scroll the .dialog-frame container to scrollTop=0 so the search box and Ask AI are both visible. Previously only scrolled the selected item into view, leaving the search box hidden above. Co-Authored-By: Oz --- src/components/CustomSidebar.astro | 33 ++++++++++++++++++------------ 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/src/components/CustomSidebar.astro b/src/components/CustomSidebar.astro index f6ef2859..512d0c7c 100644 --- a/src/components/CustomSidebar.astro +++ b/src/components/CustomSidebar.astro @@ -69,19 +69,20 @@ import KapaLauncher from './KapaLauncher.astro'; // Pagefind re-renders the results area on every keystroke, which // removes our injected element. We re-inject on every observer // tick by checking if the element is still in the DOM. + // The Ask AI row is always visible when the dialog is open -- + // even before the user types a query. function injectAskAi() { var existing = document.getElementById(askAiId); - var resultsArea = dialog.querySelector('.pagefind-ui__results-area'); - if (!resultsArea) return; - // Only show when there's a query (results or no-results message) - var hasResults = dialog.querySelector('.pagefind-ui__result'); - var hasMessage = dialog.querySelector('.pagefind-ui__message'); - if (!hasResults && !hasMessage) { if (existing) existing.remove(); return; } - // If it already exists and is still in the DOM, skip - if (existing && resultsArea.contains(existing)) return; - // Re-build and append at the bottom of results + // Find the best container: results area if it exists, otherwise + // the search container (#starlight__search) for the empty state. + var container = dialog.querySelector('.pagefind-ui__results-area') + || dialog.querySelector('#starlight__search'); + if (!container) return; + // If it already exists and is still in the container, skip + if (existing && container.contains(existing)) return; + // Re-build and append if (existing) existing.remove(); - resultsArea.appendChild(buildAskAiRow()); + container.appendChild(buildAskAiRow()); } function removeAskAi() { @@ -120,10 +121,16 @@ import KapaLauncher from './KapaLauncher.astro'; if (index >= items.length) index = 0; selectedIndex = index; var item = items[selectedIndex]; - // Highlight the row directly (title row or sub-result row) - // -- matches the same elements the CSS hover rules target. item.classList.add('warp-search-selected'); - item.scrollIntoView({ block: 'nearest', behavior: 'smooth' }); + // When wrapping to the first item (Ask AI at index 0), + // scroll the entire dialog frame to the top so the search + // box and Ask AI are both visible. + if (selectedIndex === 0) { + var frame = dialog.querySelector('.dialog-frame'); + if (frame) frame.scrollTop = 0; + } else { + item.scrollIntoView({ block: 'nearest', behavior: 'smooth' }); + } } // Clear keyboard selection when mouse moves over results From bab51f7e7dfe4a411bc89069c123f95de8203e1a Mon Sep 17 00:00:00 2001 From: Rachael Rose Renk <91027132+rachaelrenk@users.noreply.github.com> Date: Sun, 10 May 2026 10:30:26 -1000 Subject: [PATCH 6/6] docs: pass search query to Kapa chat when clicking Ask AI When the user types a search query and clicks 'Ask AI' (or selects it via keyboard), the search input value is now passed to the Kapa chat and auto-submitted. Implementation: - CustomSidebar.astro: reads .pagefind-ui__search-input value and stores it on window.__warpAskAiQuery before closing the search dialog and opening Kapa. - KapaChatLauncher.tsx: openPanel() checks for the pending query, clears it from window, and calls submitQuery() after a short delay to let the panel mount. Co-Authored-By: Oz --- src/components/CustomSidebar.astro | 6 ++++++ src/components/KapaChatLauncher.tsx | 11 +++++++++++ 2 files changed, 17 insertions(+) diff --git a/src/components/CustomSidebar.astro b/src/components/CustomSidebar.astro index 512d0c7c..5c512714 100644 --- a/src/components/CustomSidebar.astro +++ b/src/components/CustomSidebar.astro @@ -58,6 +58,12 @@ import KapaLauncher from './KapaLauncher.astro'; + 'Ask AI' + ''; row.addEventListener('click', function () { + // Grab the current search query so Kapa can pre-fill it + var searchInput = dialog.querySelector('.pagefind-ui__search-input'); + var searchQuery = searchInput ? searchInput.value.trim() : ''; + if (searchQuery) { + window.__warpAskAiQuery = searchQuery; + } dialog.close(); var kapaBtn = document.querySelector('.warp-kapa-button'); if (kapaBtn) kapaBtn.click(); diff --git a/src/components/KapaChatLauncher.tsx b/src/components/KapaChatLauncher.tsx index ff43f75c..f4c02572 100644 --- a/src/components/KapaChatLauncher.tsx +++ b/src/components/KapaChatLauncher.tsx @@ -123,6 +123,17 @@ function ChatSurface({ title, welcomeMessage, autoOpen = false, onNewConversatio const openPanel = () => { setIsOpen(true); + // If a search query was passed from the search dialog's "Ask AI" + // button, auto-submit it after the panel opens. + const pendingQuery = (window as any).__warpAskAiQuery; + if (pendingQuery) { + delete (window as any).__warpAskAiQuery; + // Wait for the panel to mount and the input to be ready + setTimeout(() => { + submitQuery(pendingQuery); + setHasStartedConversation(true); + }, 100); + } }; const closePanel = () => {