Skip to content

UXS-005: Semantic Search Panel


Placement

Search is the default surface of the left query column; Explore is an alternate mode (slide transition) entered via a quiet footer control (Explore corpus →) and exited via ← Search — see VIEWER_IA.md. Mode is shell.leftPanelSurface (search | explore). / expands the column if needed, switches to Search, then focuses #search-q (LeftPanelfocusQuery). LeftPanel.vue hosts SearchPanel.vue + ExplorePanel.vue at w-72 when expanded. Search results stay visible when a hit opens in the subject rail (mode switches do not clear the subject). Deprecated: right-rail-only search, episodeRail, paneKind = tools as placement drivers.


Summary

For shell layout, the three navigation axes, subject rail persistence and clearing, status bar, and first-run empty corpus behavior, see VIEWER_IA.md. This document specifies the Search and Explore panel content only (query form, advanced filters, result cards, insights modal).

The semantic search panel provides FAISS-based corpus search in the left shell column (Semantic search card: form + scrollable results; Explore corpus → sits in LeftPanel below that card). This UXS defines the visual contract for the search form, advanced filters, result cards, and the search result insights modal. All tokens reference UXS-001.

Track shell work in GitHub #606 and RFC-062. When the viewer changes, update this Active UXS in the same PR — see Living documents and ship boundary.

RFC-075: When corpus clustering JSON is available, Show on graph from search still selects the leaf node id (e.g. topic:…) as today. The graph node rail shows Topic cluster: for Topic nodes (Phase 1; see UXS-004). Search result cards may show a Topic cluster: line when the API joined metadata.topic_cluster (canonical label and compound id). Show on graph may widen the camera to include the tc: compound parent while keeping selection on the leaf. The pan/zoom animation and minimum zoom floor for that hand-off match other graph focus paths (UXS-004 — Camera framing and selection).

Dashboard topic clusters: On the Dashboard → Intelligence sub-tab, the Topic clusters status block reflects GET /api/corpus/topic-clusters as soon as Corpus path is set and health is OK — you do not need to wait for GI/KG artifacts to finish loading into the graph. While the request is in flight, Status shows Checking…. Then: Loaded, Not built (404 — optional topic-clusters CLI), request error, or Local files only when the graph came from the file picker (no API fetch for clustering). Unknown schema_version values get a non-blocking warning line.


Primary flow

Search query field (no separate label; placeholder + Semantic search heading) — Enter submits the same as Search (disabled while loading / no corpus / API down); Shift+Enter inserts a newline; IME composition does not trigger submit. Then a filter chip bar (#671, data-testid="search-filter-bar") with four chips — Since (search-chip-since, shared DateChip), Top‑k (search-chip-topk, default 10), Doc types (search-chip-doctypes, empty = all), More (search-chip-more, opens the slim Advanced search dialog). Each chip's label switches from Label ▾ (default) to Label: detail ▾ when active; the More chip shows More: N reflecting the count of non-default fields it hosts. Replaces the legacy inline Since (date) + Top-k row, the "Advanced search" underline link, and the read-only "Active advanced filters" summary block (which is gone — chip labels carry the active state instead). Search / Clear sit below the chip bar; scrollable results (errors + hit cards) follow in the middle. Explore corpus → lives in LeftPanel.vue below the search card (data-testid="left-panel-explore-footer" / left-panel-enter-explore) so it stays visible above the status bar.


Advanced search dialog (slim, hosted by the More chip)

Opened by clicking the More filter chip; same backdrop pattern as before. After #671 the dialog hosts only the low-traffic fields that aren't worth a top-level chip:

  • Feed (substring on catalog feed_id for the API; Library -> Prefill semantic search shows the feed title from the feeds catalog when known, with hover/title for the id until edited)
  • Grounded insights only
  • Speaker contains
  • Embedding model
  • Merge duplicate KG surfaces (default on: same behavior family as graph Entity/Topic dedupe for kg_entity / kg_topic vector rows)

The legacy in-dialog Doc Types fieldset moved to the dedicated Doc types chip on the filter bar.


Search result insights

After at least one hit, an underlined Search result insights control opens a modal (same backdrop pattern as Advanced search) titled "Search result insights" -- one scroll, no tabs: a short insight line (dominant doc type); Doc types and Publish month in a two-column row (small multiples); Episodes / Feeds with top rows (episode title / feed title from hit metadata, or loaded from the episode's *.metadata.json when the index row omitted them) plus "+N other..." tail counts; Similarity bars proportional to score / max(score) in the list (captioned); Terms with a top-token insight (word frequency; heuristic, not KG).


Results

A muted "N results" / "1 result" line only (the query stays in the textarea; it is not repeated here). When the API returns lift_stats and at least one transcript row is on the page, a compact Lift: applied / transcript rows ratio appears on the same row (native title explains RFC-072 lift coverage).

Each hit can expose:

  • G (graph focus, GI token) and L (Library episode) as separate controls
  • L requires a healthy API check + corpus path and source_metadata_relative_path on the hit (vector indexer stamps it on rebuild)
  • corpus_library_api in health can still be No while L shows; the Library tab surfaces errors if catalog routes are unavailable
  • E (episode id chip) is informational
  • When Merge duplicate KG surfaces merged a row (kg_surface_match_count >= 2), G only -- L and E are hidden so actions are not tied to a single representative episode
  • Transcript hits may include a collapsible region Lifted GI insight (linked insight id/text, speaker/topic labels, quote time range) when the server attaches lifted (#528). When lifted.quote has at least one finite timestamp_*_ms but lifted.speaker has no usable display label, a muted line shows the same visible copy as supporting quotes — No speaker detected (GI_QUOTE_SPEAKER_UNAVAILABLE_HINT; #541). data-testid="search-lifted-quote-speaker-unavailable".
  • Supporting quotes (Show / Hide N supporting quote(s)): when the API returns supporting_quotes and a quote has no speaker_name / speaker_id, a muted line shows No speaker detected (same GI_QUOTE_SPEAKER_UNAVAILABLE_HINT as the graph Quote rail; #541). data-testid="supporting-quote-speaker-unavailable".

E2E contract

E2E surface map -- search panel surfaces and selectors.


Revision history

Date Change
2026-04-21 Query: Enter submits search; Shift+Enter newline; IME-safe.
2026-04-21 Explore CTA: LeftPanel footer below search card (clears status bar).
2026-04-21 Left column: Search default; Explore mode (slide); / restores Search + focus.
2026-04-21 Search: Since + Top-k row uses grid so fields stay in panel.
2026-04-21 Show on graph: UXS-004 camera + min zoom (cross-link).
2026-04-06 Initial content (in UXS-001)
2026-04-13 Extracted from UXS-001 into standalone UXS-005
2026-04-13 Document lift_stats summary line + Lifted GI insight region
2026-04-15 Supporting quotes: muted hint when speaker missing (#541)
2026-04-15 Lifted GI: muted hint + testid when speaker display missing (#541)
2026-04-15 Lifted hint only when lifted.quote has finite timestamp_*_ms (matches E2E)
2026-04-15 #541: No speaker detected (graph + Search + Explore; semantics unchanged)
2026-04-16 Lifted GI: explicit same visible string as supporting quotes (No speaker detected)
2026-04-19 Shell IA: left query column copy; topic clusters card under Dashboard workspace (#606)