Skip to content

Add a search page to the web UI (closes #92) - #111

Merged
paulirwin merged 1 commit into
mainfrom
search-ui
Oct 26, 2026
Merged

paulirwin merged 1 commit into
mainfrom
search-ui

Conversation

@paulirwin

Copy link
Copy Markdown
Member

Closes #92.

Adds a Search page to the dashboard (/search, linked from a new Status / Search switcher in the header). It queries any index through the Azure SDK — not the searcher directly — so what it shows is what an application would get.

What the page does

  • Query options: query type (simple, full, or semantic when the index has a semantic configuration), search mode, $filter, $orderby, $select, search fields, facets, highlight fields, scoring profile, $top, $count. Choosing an index searches it immediately; the index and query are kept in the address bar so a reload or shared link returns to the same search.
  • Results: cards with title, key, score and reranker score, highlighted fragments, semantic captions and answers, facet chips, and previous/next paging. Long numeric arrays (vectors) are summarised as [1536 numbers].
  • Request / Response tabs: the raw HTTP exchange the SDK made — method and path, headers, status, timing, and indented, syntax-highlighted JSON with a copy button. Failed queries show the emulator's error message, with the full envelope in the Response tab.

How it reaches the emulator

The SDK rejects any non-https endpoint, which the default plain-HTTP binding can't satisfy. So EmulatorClientFactory hands the SDK an https URI and its transport rewrites each request onto whichever binding Kestrel reports (wildcards mapped to loopback), skipping certificate validation for that in-process hop. This works behind the container's port mapping and Aspire's proxy, where the browser's URL isn't reachable from inside the process. HttpExchangeRecorder is an SDK pipeline policy, so the recorded headers are the ones the SDK actually sent.

JSON highlighting is done server-side in JsonHighlighter, keeping the dashboard free of client-side assets beyond Blazor's own script.

Bugs fixed along the way

  • GET /indexes?$select=name returned 500. [EnableQuery] treated the repository's IAsyncEnumerable as a single entity, so SearchIndexClient.GetIndexNamesAsync failed against the emulator. The listing is now materialized into an IQueryable.
  • The Docker image shipped without blazor.web.js. The SDK only restores the package carrying it when it sees .razor files at restore time, and the Dockerfile restores from the project file alone. The dashboard prerendered but never became interactive (its auto-refresh never ran). RequiresAspNetWebAssets is now set explicitly in the project.

Other changes

  • The emulator now references Azure.Search.Documents 12.0.0 (the version the tests already use).
  • The page column widens from 880px to 1040px, which also affects the Status page.
  • README: a short paragraph on the Search page under Web UI.

Not included

  • Captions and answers are always requested as extractive; there's no separate semanticQuery field, and no vector/hybrid inputs.
  • interval facets come back from the emulator as ranges ({"from":4,"to":6}) rather than Azure's value buckets ({"value":4}). The page shows what the emulator returns; that's a separate fidelity issue.

Testing

  • Unit tests in SearchPageTests for loopback address resolution, list/facet splitting, SearchOptions mapping, the JSON tokenizer, highlight encoding, and value formatting.
  • Integration tests: /search is served, blazor.web.js is served by the built image (fails before the project change), and GetIndexNamesAsync lists a created index.
  • Full suites pass: 1,085 unit, 247 integration.
  • Driven manually in Chrome, light and dark: over HTTP, over HTTPS with the dev certificate, and from the Docker image, including a semantic query with answers and captions.

🤖 Generated with Claude Code

The dashboard gains a Search page that queries any index through the
Azure SDK rather than the searcher directly, so it shows exactly what an
application would get. It covers query type (including semantic), search
mode, $filter, $orderby, $select, search fields, facets, highlighting,
scoring profiles, $top and $count, and renders results as cards with
scores, highlights, captions, answers and facets. Request and Response
tabs show the raw HTTP exchange the SDK made, with server-side JSON
syntax highlighting.

The SDK only accepts https endpoints, so it is handed one and its
transport rewrites each request onto whichever binding Kestrel reports,
over loopback with certificate validation skipped. The exchange is
recorded by a pipeline policy so the headers are the ones the SDK sent.

Two bugs surfaced along the way:

- GET /indexes threw on $select, because [EnableQuery] treated the
  repository's IAsyncEnumerable as a single entity. The SDK's
  GetIndexNamesAsync sends $select=name, so it failed outright.
- The Docker image shipped without blazor.web.js. The SDK only restores
  the package carrying it when it sees .razor files, and the Dockerfile
  restores from the project file alone. The dashboard prerendered but
  never went interactive. RequiresAspNetWebAssets is now set explicitly.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@paulirwin
paulirwin enabled auto-merge (squash) October 26, 2026 21:48
@paulirwin
paulirwin merged commit 610cf6d into main Oct 26, 2026
5 checks passed
@paulirwin
paulirwin deleted the search-ui branch October 26, 2026 21:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Web UI: Search

1 participant