BitaKit
API reference

Quick Search

Add plugin-owned search sources, source preferences and recent history to the shared search dialog.

Quick Search opens with Mod+K or the AppBar search button. It searches navigation destinations and contributions from installed plugins. A plugin can add courses, projects or categories from its own folder without changing the search package or the host provider. The AppBar trigger uses the central input appearance and a Search icon while remaining an accessible button with its keyboard shortcut. The editable search field lives in the dialog.

Install @bitakit/quick-search in a host with BitaKit discovery. Discovery includes its plugin, translations, stylesheet and typed quick-search.sources data slot. App Preferences renders its settings at /settings/quick-search when the host mounts the existing Settings route. In manual composition, add quickSearchPlugin() and, for Settings, appPreferencesPlugin(); include the package styles in the host's root stylesheet.

Contribute a source

Create src/plugins/catalog/setup.ts. This complete local example uses existing page links; the /catalog page must belong to your plugin. The shipped examples/catalog folder includes that page and English/German catalogs and is typechecked and tested in a generated app.

import type { Setup } from '@bitakit/core';
import type { SearchSource } from '@bitakit/quick-search/sources';

const lessons = [{ id: 'composition', title: 'Composition basics', href: '/catalog' }];

const source: SearchSource = {
  title: { key: 'catalog.search.title', fallback: 'Learning catalog' },
  description: 'Find lessons in the local example catalog.',
  category: { id: 'learning', title: 'Learning' },
  defaultEnabled: true,
  search({ query, score }) {
    return lessons.filter((lesson) => !query || score(lesson.title) > 0);
  },
  resolve(id) {
    return lessons.find((lesson) => lesson.id === id) ?? null;
  },
};

const setup: Setup = ({ scope }) => {
  if (!scope.slots.has('quick-search.sources')) return;
  const handle = scope.slots.attach(
    { slot: 'quick-search.sources', id: 'lessons', data: source },
    { availability: 'wait' },
  );
  return () => handle.dispose();
};
export default setup;

Use an explicit stable contribution ID. Core qualifies this example as catalog:lessons; that identity keys the user's override and history references. Changing it creates a new source for those preferences. Core rejects duplicate contribution IDs for the same owner/slot. Registration and cleanup use the ordinary data-slot lifecycle, with no second source registry.

The source owns its title, description and category text. SearchText accepts a string (literal or translation key), or { key, fallback }. Plugin catalogs supply translations; Quick Search has no domain-specific labels. Sources sharing a category ID share the temporary filter; use consistent titles for that shared ID.

Source contract

PropertyMeaning
title, descriptionText shown in the source selection and Settings
category: { id, title }Stable filter identity and its translated label
defaultEnabledInitial preference only, when no saved override exists
search(context)Return an array of results, synchronously or asynchronously, in display order
resolve(id, context)Read current data/access for a stored result ID; return a fresh result or null

An optional icon supplies a component using Core's existing opaque PresentationType<'icon'> contract, the same as Actions. React hosts type this through @bitakit/ui; Core stays headless. Import a verified Reicon component directly, for example ClipboardCheckIcon from reicon-react/icons/ClipboardCheck, and set icon: ClipboardCheckIcon. Without an icon, Quick Search renders Reicon Search. There is no icon-name registry. Icons are decorative; the source title remains the accessible label.

Both functions receive query, signal, score, text, actions, runtime and groupTitle. Most remote sources need only query and signal; use fetch(url, { signal }) or the existing client's cancellation support. Local sources may use score(title, keywords) (cmdk's matcher) and text(value). The runtime and ActionList are the existing host objects, not copies. resolve receives an empty query during recent-result restoration and must not rely on the original search string. Its result must retain the requested ID.

A result is either:

{ id: 'course-42', title: 'Introduction', description: 'Start here', href: '/courses/42' }

or a reference to an existing navigation Action:

{ id: 'settings', actionId: 'catalog.openSettings', keywords: ['learning'] }

The title and description of direct links are display text returned by the source; translate them there when appropriate. An Action result never supplies copied presentation or guards. Quick Search resolves its Action live and renders it with ActionLink. Actions without URLs, hidden/internal Actions and unsafe URLs are excluded; disabled/pending Actions remain disabled. Commands and dialogs are not search results.

Returned arrays are already filtered/ranked by their source. Quick Search does not apply a second filter to remote results. search is also called with an empty query, allowing useful initial destinations. Sources can return [] then and rely on recent results instead.

Requests and availability

Enabled sources execute concurrently. Each publishes results, loading state and a generic error independently, so a slow or failed source does not block other results. Recent-reference resolution also runs independently of that source's normal search. Sources are application code, not a sandbox; they must yield during asynchronous work.

Query changes, temporary category changes, saved source choices, identity changes, contribution removal and dialog/runtime cleanup abort and invalidate obsolete work. Late replies are ignored even when a source ignores signal. Disabled sources are neither searched nor resolved for history. Reopening the dialog clears its temporary category selection.

The renderer deduplicates safe destination URLs. Built-in destinations first merge duplicate Actions, retaining a shortcut when available and the more specific Settings/plugin destination kind. Native links retain modified-click semantics; Mod+Enter opens the selected enabled link in a new tab. Sources and their backend must enforce data access, and destination routes must check authorization again. A stored ID, active organization or visible result never grants access.

Source preferences and compatibility

The dialog has one compact category dropdown below its input, defaulting to All categories. It lists categories from enabled sources, narrows only the current dialog and resets on reopening. Persistent source enablement is edited in App Preferences at /settings/quick-search, using icon/title/description rows with a Switch on the right. These controls edit the user-scoped quick-search.sources setting. Its value is a record of explicit source ID → boolean overrides. Absence means use that source's own defaultEnabled; adding a source never resets prior choices. A default of true does not force the source on. Failed saves retain the confirmed preference.

Show result source edits the boolean quick-search.showSource setting, which defaults to false. Enable it to show the existing small, muted source label on the right of results. Ownership, search matching, source enablement and shortcuts are unchanged.

Pages, Settings and plugins are ordinary sources with IDs quick-search:pages, quick-search:settings and quick-search:plugins. All default to enabled. The existing QuickSearchOptions.results switches continue to set their defaults:

// src/configs/quick-search.ts
import { defineConfig } from '@bitakit/core';
import type { QuickSearchOptions } from '@bitakit/quick-search';

export default defineConfig({
  defaults: { results: { plugins: false } } satisfies QuickSearchOptions,
});

A saved user choice takes precedence over these defaults. The other existing options are title, placeholder, empty and shortcut (false disables the default Mod+K shortcut). Plugin destinations are projected by App Preferences; without its runtime plugin they do not exist. Turning plugin results off does not hide that plugin's pages or Settings.

History and persistence

Opening a result remembers its stable source/result IDs and the nonempty query used to find it. The empty search shows recent queries and freshly resolved recent destinations. Selecting a recent query searches again. Up to 20 distinct queries and 20 opened references are retained per user, across that user's organizations; the current organization's entries alone are shown. History stores no cached title, URL, result payload or permission claim.

quick-search.showRecentResults defaults to false and independently controls whether the Recently opened results section is displayed. It changes visibility only; recording and stored history are unchanged. Unavailable or unauthorized results remain excluded.

quick-search.historyEnabled controls recording and recent-query display. Turning it off retains existing history until Clear history removes it. Clearing removes that user's history across their organizations, including entries for sources no longer installed. Saves are serialized and queued operations cannot cross an identity change. Source overrides and history use Core's existing settings storage and auth identity; anonymous use has the standard anonymous scope.

All declarations are available from the headless @bitakit/quick-search/settings entry as quickSearchSettings. A server settings adapter must explicitly trust these declarations and apply its own access policy. No additional localStorage/session or confirmed-value store is used. Source/backend authorization remains independent of preference persistence.

On this page