<!-- GENERATED by scripts/build-llms.mjs from llms/retrieval.md — do not edit this file. -->

# `lr-retrieval-search`

- **Import** `import '@aceshooting/lyra-ui/components/lr-retrieval-search.js';` (stable tag alias; registers the tag)
- **Class** `LyraRetrievalSearch`, also available unregistered from `@aceshooting/lyra-ui/components/retrieval/retrieval-search/retrieval-search.class.js`
- **Family** `components/retrieval/` — see `llms/index.md` for its siblings
- **Status** `stable` since `4.1.0` — see the maturity and deprecation policy in `llms/shared.md`
- **Release history** [CHANGELOG.md](../../CHANGELOG.md); family-wide breaking-change summaries: [llms-full.txt](../../llms-full.txt)
- **Deprecations** none
- **Optional peers** none
- **Themeable via** 9 parts, 1 custom property — see this component's own `@csspart`/`@cssprop` list below
- **Library-wide behavior** (events, form association, `locale`/`strings`, tokens, TS types): `llms/shared.md`

---

## `lr-retrieval-search`

Query bar for a retrieval/RAG surface: query text, an active filter/scope chip row, a
vector/keyword/hybrid mode selector, and loading/error/empty feedback. Emits `RetrievalQuery` on
submit. Fully controlled; performs no retrieval itself. Composes `lr-input type="search"`,
`lr-segmented`, `lr-chip`/`lr-chip-group`, `lr-spinner`, and a compact `lr-empty`. The query field,
the mode selector and the submit button are all left on the shared `--lr-form-control-height` ladder
at the same size tier, so the toolbar row renders as one flush line.

**Properties:**

- `query: string = ''` — the query text. The internal `lr-input` updates it optimistically as the
  user types; a host reassignment always wins
- `mode: LyraRetrievalMode = 'hybrid'` — `LyraRetrievalMode = RetrievalQuery['mode'] = 'vector' |
'keyword' | 'hybrid'`, re-exported here rather than redefined
- `filters: Record<string, unknown> = {}` (attribute: false) — arbitrary metadata filters, rendered
  as removable `"{key}: {value}"` chips. A non-record runtime value normalizes to an empty record.
  Controlled
- `scope: string[] = []` (attribute: false) — source-scope ids/labels this query is restricted to,
  rendered as removable chips alongside `filters`. A non-array runtime value normalizes empty;
  blank entries and duplicates are omitted first-wins. Controlled
- `loading: boolean = false` (reflected) — host-driven busy flag; this component cannot know when a
  request resolves
- `errorText: string = ''` (attribute `error-text`) — last failed search's message, shown verbatim
  (caller-owned text, not localized) in a neutral visible region. A new non-empty value is announced
  through a shared assertive light-DOM region; initial and reconnect content is not replayed
- `empty: boolean = false` (reflected) — host-driven "the last completed search returned zero
  results"; never inferred, since this component holds no results data (see `lr-retrieval-results`).
  A later transition into the settled empty state announces the localized “No matches” heading
  through the shared polite light-DOM region; initial and reconnect content is not replayed
- `announce: boolean = false` (reflected) — opt-in: announce the state the search bar is **already
  presenting** the first time it mounts, rather than only announcing later transitions into it.
  Urgency follows the state, in the same branch order the row renders: a non-empty `errorText` wins
  and is announced verbatim through the shared assertive light-DOM region, otherwise an `empty`
  search that is not `loading` announces the localized “No matches” text through the shared polite
  region. A search that is still `loading`, or that has settled on neither state, announces
  nothing. The announcement is deferred one frame past the first update so the shared region exists
  before its text lands, and any live transition arriving first retires the pending mount-time
  announcement so nothing is read twice. Set it where the search is rendered in response to a query
  the user just ran and nothing else reports the outcome; leave it unset for a search that is part
  of the page a user is arriving on, whose visible error or empty state is already read in document
  order. Read once, when the search first mounts: a later reconnection or adoption stages the
  existing state again rather than replaying it, and later transitions are announced either way.
  Remove any host `role="status"`/`role="alert"` hand-added before this property existed once it
  is set — otherwise the initial state is announced twice, through the native role and again
  through the shared sink
- `placeholder: string = ''` — falls back to the localized generic "Search" placeholder, which also
  becomes the field's accessible name
- `label?: string` — fallback name for the `role="search"` landmark; omission uses the localized
  retrieval-search label, while an explicit empty string stays empty
- `accessibleLabel: string | null = null` (attribute `aria-label`) — as a JS-only property while
  the host attribute is absent, overrides the search-landmark name. A non-empty authored host
  `aria-label` makes the host the sole overall owner, so the inner shell omits its duplicate
  role/name; an explicitly empty host label stays empty on the search landmark
- `size?: LyraSize` (reflected) — opt-in density tier for the whole query row, on the library's one
  six-step ladder (`2xs`/`xs`/`s`/`m`/`l`/`xl`, or `small`/`medium`/`large`). It is one property for
  all three controls deliberately: the query field, the mode selector and the submit button share
  the row's baseline, and sizing one of them alone is what makes the row ragged. With no `size` each
  control keeps its own `m` default, exactly what the row rendered before; an unsupported value
  normalizes to the omitted state and removes the attribute

**Events:**

- `lr-search` (`detail: RetrievalQuery` from `@aceshooting/lyra-ui/ai` = `{ text: string;
filters?: Record<string, unknown>; mode: 'vector' | 'keyword' | 'hybrid'; scope?: string[] }`) —
  Enter in the query field, or the submit button while not `loading`.
- `lr-cancel` (`detail: CancelEventDetail` from `@aceshooting/lyra-ui/ai` = `{ reason?: string }`) —
  either the button was clicked while `loading` (`detail: {}`), or a new submission superseded an
  in-flight one (`detail: { reason: 'superseded' }`, fired immediately _before_ the new `lr-search`).
- `lr-filters-change` (`detail: RetrievalFiltersChangeDetail` = `{ filters: Record<string, unknown>;
scope: string[] }`) — a chip's remove button was activated; the complete already-updated next
  state, not a delta. The component updates its own copy first, then emits; reassign to control.

**Slots:** none.

**CSS parts:** `base` (the `role="search"` landmark unless a non-empty host label owns the
component), `row`, `query`, `mode`, `submit` (reads
"Search" while idle, "Cancel" while `loading`), `filters` (omitted entirely when both `filters` and
`scope` are empty), `spinner` (only while `loading`), `error` (neutral visible message, only when
`errorText` is non-empty and not `loading`), `empty` (only when `empty` and neither `loading` nor
`errorText`).

**Themeable custom properties:** `--lr-retrieval-search-submit-min-height` (default
`var(--lr-icon-button-size)`, raised to the tier's shared form-control height when `size` is set) is
the submit button's minimum height; the shared tappable-target minimum always stays underneath it,
so the smallest tiers cannot shrink the button below the WCAG floor. Everything else is shared
tokens.

**Optional peer deps:** none.

**Known gotchas:**

- Submitting while `loading` is already true **supersedes** the in-flight request: `lr-cancel` fires
  immediately before the new `lr-search`. Clicking the submit button (rather than pressing Enter)
  while `loading` only emits `lr-cancel` and does not resubmit.
- Long unbroken filter keys, values, and scope labels are contained within the search allocation;
  the removable chip label truncates while its remove action remains available.
- Filter values use a cycle-aware formatter bounded to 128 visited values, six nesting levels,
  32 entries per container and 256 characters per string. Cycles use the localized invalid-value
  sentinel and budget/depth truncation uses a stable ellipsis in both SSR and browser rendering.

---
