Skip to content

Add documentation search UI to the Reference drawer #422

Description

@BorisTyshkevich

Parent design: #420
Depends on: #421

Purpose

Add the user-facing documentation search experience to the existing persistent Reference drawer, using the SchemaCatalogService.docSearch() API from #421.

This issue owns drawer chrome, search submission, result rendering, focus and keyboard behavior, stale-response suppression, Back-stack snapshots, responsive layout, accessibility, and regression coverage. It must not execute SQL or duplicate catalog ranking/normalization logic.

Header design

Add a persistent search form to the Reference drawer header:

Reference   [ Search functions, engines, types, settings… ] [Search]   ×

Requirements:

  • Keep “Reference” visible as the drawer title.
  • Keep the close button as the rightmost control.
  • The search form survives every body-state replacement.
  • The input grows to use available width.
  • At narrow drawer widths, allow the title/close row and search form to wrap onto two rows.
  • Never shrink the close target or make the search input unusably narrow.

Use semantic controls:

<form role="search" aria-label="Search ClickHouse documentation">
  <input
    type="search"
    aria-label="Search documentation"
    autocomplete="off"
    spellcheck="false"
  >
  <button type="submit" aria-label="Search documentation">Search</button>
</form>

Suggested placeholder:

Search functions, engines, types, settings…

Submission behavior

  • Submit explicitly with Enter or the Search button.
  • Do not issue a request for empty, whitespace-only, or one-character normalized input.
  • Do not search on every keystroke.
  • Keep focus in the input after results, no-results, or unavailable settles.
  • Disable or mark the button busy while the identical query is in flight.
  • A different submitted query supersedes the earlier one.
  • The pane token must suppress any late result from an older search.

The UI delegates to:

app.catalog.docSearch(query)

No SQL or source-table knowledge belongs in doc-pane.ts.

Keyboard behavior

  • Enter in the search input submits.
  • ArrowDown in the input focuses the first result when one exists.
  • / while focus is inside the drawer but not in a text-editing control focuses the search input.
  • Escape with non-empty search text clears the input and current search state first.
  • Escape with an empty search input preserves the drawer’s existing close behavior.
  • Escape handled inside the drawer must continue to prevent the global shortcut handler from also cancelling a running query.
  • Browser-reserved shortcuts remain browser-owned.

Search result state

Add a new body state alongside loading, found entry, disambiguation, missing, and unavailable.

Example:

23 results for “array”

[aggregate function] groupArray
Creates an array of argument values…

[function] arrayJoin
Unfolds an array into rows…

[data type] Array
An array of values of type T…

Render with semantic list markup:

<div role="status" aria-live="polite">23 results for “array”</div>
<ul aria-label="Documentation search results">
  <li><button type="button"></button></li>
</ul>

Each result button contains:

  • logical DocKind badge;
  • canonical title/name;
  • one or two lines of plain-text summary;
  • optional safe visual highlighting of matched name fragments.

Do not render server result text as HTML.

Physical source_table provenance remains in the model for diagnostics/testing but does not replace the logical kind badge in the primary UI.

Result selection

Selecting a result must:

  1. capture the current search snapshot;
  2. push that snapshot onto the existing bounded Back stack;
  3. call the existing target-aware entry lookup path with the result’s DocTarget;
  4. render the existing structured/Markdown documentation entry;
  5. preserve all existing alias, related-entry, and disambiguation behavior.

Search results are not full documentation entries. Do not add another entry renderer.

Back-stack search snapshots

Extend the pane’s existing BackEntry union:

type BackEntry =
  | { kind: 'target'; target: DocTarget }
  | { kind: 'disambiguation'; name: string }
  | {
      kind: 'search';
      query: string;
      response: DocSearchResponse;
      selectedIndex: number;
      scrollTop: number;
      connectionGeneration: number;
    };

When navigating from results to an entry, store:

  • normalized query;
  • bounded search response;
  • selected result index;
  • result-body scroll position;
  • active connection generation.

Back from the entry restores:

  • input text;
  • the same result list without another SQL request;
  • result count;
  • scroll position;
  • focus to the previously selected result when still connected.

If the connection generation changed, do not restore stale results; clear the invalid snapshot and require a new search.

The existing Back-stack cap remains authoritative.

Loading state

During a search:

  • retain the query in the input;
  • set aria-busy="true" on the result/body region;
  • show a button spinner or disabled busy state;
  • prevent duplicate manual submission of the identical in-flight query.

Using the existing full-body loading state is acceptable initially. Retaining the previous body until the replacement settles is preferred but not required.

No-results state

When docSearch() returns missing, render:

No documentation found for “xyz”.

Keep the query and input focus. This is distinct from a target-specific missing documentation entry.

Unavailable state

When docSearch() returns unavailable, reuse the drawer’s quiet unavailable/Retry pattern:

Reference search isn't available on this server or connection.

Retry reruns the current normalized query through docSearch().

Do not show a toast for capability or permission failures.

Pane state and lifecycle

Likely additions:

interface PaneState {
  // existing fields
  searchInput: HTMLInputElement;
  searchButton: HTMLButtonElement;
  currentSearch: {
    query: string;
    response: DocSearchResponse;
  } | null;
}

Required stale-response behavior:

  • submitting a new search invalidates the previous search paint;
  • selecting a result invalidates an in-flight search paint;
  • external openDocEntry() or openDocDisambiguation() invalidates in-flight search paint and begins its existing fresh browsing session;
  • closing the drawer invalidates pending search responses;
  • sign-out/reconnect invalidates pending search responses;
  • no late response may replace newer body content.

Focus behavior

  • Opening an external entry continues to record the initiating focus target.
  • Search submission does not unexpectedly move focus.
  • Result count is announced through aria-live.
  • ArrowDown explicitly moves to the first result.
  • Selecting a result moves focus according to the existing entry-rendering convention.
  • Back restores result focus when possible.
  • Closing the drawer restores focus through the existing initiator logic.
  • Search controls remain usable after entry, alias, related, disambiguation, missing, unavailable, and Back transitions.

Styling

Add styles for:

  • flexible/wrapping Reference header;
  • search input and button sizing;
  • busy/disabled search state;
  • result count/status;
  • semantic result list reset;
  • full-width result buttons;
  • kind badge/title/summary layout;
  • one/two-line summary truncation;
  • hover, keyboard focus, and restored-selection states;
  • no-results and unavailable states;
  • narrow drawer widths.

Do not alter the drawer’s persisted-width behavior or make the pane modal.

Suggested implementation boundary

src/ui/doc-pane.ts

Own:

  • persistent search form;
  • submission and keyboard handling;
  • pane-token stale checks;
  • result/loading/missing/unavailable rendering;
  • result selection through existing lookup;
  • search Back snapshots;
  • focus and scroll restoration;
  • live-region announcements.

src/styles.css

Own only layout and visual states.

src/ui/app.types.ts or narrow pane contract

Expose catalog.docSearch() without broadening dependencies unnecessarily.

Tests

Header

  • Reference title remains present.
  • Search form has role="search" and an accessible name.
  • Input and button are labelled.
  • Close remains rightmost.
  • Header safely wraps at constrained width.
  • Search form survives body replacement.

Submission

  • Enter submits.
  • Button submits.
  • empty/whitespace/one-character input does not call the service.
  • normalized query is passed to the service.
  • duplicate identical submission while in flight does not cause another request.
  • a different second query supersedes the first.

Stale responses

  • late first search cannot replace second search results.
  • late search cannot replace a selected entry.
  • late search cannot repaint a closed pane.
  • reconnect/sign-out prevents old search results from rendering.
  • external F1/hover/completion open suppresses an in-flight search.

Results

  • count and query render correctly.
  • count is announced with aria-live.
  • results use semantic list/buttons.
  • kind, title, and summary render.
  • summaries are text, not HTML.
  • physical source remains available in the model but logical kind is primary.
  • missing renders the search no-results state.
  • unavailable renders Retry.
  • Retry repeats the current query.

Selection and Back

  • selecting a result calls the existing target-aware lookup.
  • search snapshot is pushed before entry navigation.
  • Back restores query, result rows, selected index, focus, and scroll.
  • Back does not rerun SQL while the snapshot remains generation-valid.
  • changed generation rejects the snapshot.
  • existing Back-stack cap still applies.

Keyboard/focus

  • ArrowDown focuses first result.
  • / focuses search only from appropriate non-text drawer targets.
  • Escape clears non-empty search before closing.
  • Escape with empty search closes the pane.
  • Escape does not also cancel a running query.
  • drawer close restores the valid initiator.

Regressions

  • editor hover opening remains unchanged.
  • completion info opening remains unchanged.
  • F1 entry/disambiguation remains unchanged.
  • aliases and related entries remain unchanged.
  • existing loading, missing, unavailable, Retry, and Markdown rendering remain unchanged.
  • CodeViewer teardown remains correct.
  • drawer resizing and persisted width remain unchanged.

Acceptance criteria

  • The persistent Reference header contains a responsive, accessible search form.
  • Search is explicitly submitted and delegates only to catalog.docSearch().
  • Results render as a safe semantic list with kind, name, and summary.
  • Selecting a result uses the existing full-entry lookup/rendering path.
  • Search Back snapshots restore query, results, selection, focus, and scroll without rerunning SQL.
  • Pane tokens and connection generation prevent stale search repaint.
  • Empty, loading, no-results, and unavailable states behave distinctly.
  • Escape, ArrowDown, /, close, and focus restoration are accessible and do not conflict with global shortcuts.
  • Existing Reference entry/disambiguation/navigation behavior remains intact.

Non-goals

  • SQL construction, capability probing, ranking, merge, or caching logic.
  • Search-as-you-type.
  • Kind filter UI.
  • Position/proximity-aware ranking.
  • Remote documentation search.
  • New full-entry rendering.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions