Search Bar Accessory
A search bar accessory is a dropdown the launcher renders in the top-right of the search bar while a view-mode command is active. The user picks from a list of options; the extension reacts to the selection to filter or re-fetch its content. Selections persist per-command across launches.
When to use a search bar accessory
Use an accessory when an active view has one discrete dimension the user will switch between repeatedly:
- Filter a clipboard history view by content type (text / image / file)
- Pick a Hacker News section (Front Page / Best / Jobs / Launches)
- Filter a docs browser by section (Getting Started / Plugins / Reference)
- Choose a project for a TODO list view
Prefer preferences for per-install configuration (API keys, defaults) — they persist and apply to every invocation. Prefer command arguments when the input must be collected before the view opens, or when you need free-form text or numeric input. Use the accessory when the view is already on screen and the user is switching between a small, known set of options.
Constraints
- View-mode only (
mode: "view"). Background commands and searchable extensions whose results merge into global search are not supported. The Rust manifest validator rejects accessories on non-view commands at install time. - One accessory per command. There is no "multiple dropdowns" mode.
type: "dropdown"only in v1. The discriminator field reserves room for future types.
Declaring an accessory
Add a searchBarAccessory object to a view-mode command in
manifest.json:
{
"id": "show",
"name": "Show",
"mode": "view",
"component": "MyView",
"searchBarAccessory": {
"type": "dropdown",
"default": "all",
"options": [
{ "value": "all", "title": "All Types" },
{ "value": "text", "title": "Text" },
{ "value": "images", "title": "Images" },
{ "value": "files", "title": "Files" }
]
}
}
Per-field reference
| Field | Type | Required | Description |
|---|---|---|---|
type |
"dropdown" |
✅ | Discriminator. Only "dropdown" in v1; reserves room for future types. |
options |
{ value, title }[] |
✅ | Non-empty list. value is what the extension receives; title is what the user sees. Both must be strings. |
default |
string |
❌ | Pre-selected value on first invocation. Must be one of options[].value. If omitted, the first option is the default. |
Schema constraints
optionsmust be a non-empty array.- Every option must have string
valueandtitlefields. default(when present) must equal one ofoptions[].value.- The accessory is only valid on commands with
mode: "view". Mixing it withmode: "background"is a parse error. - Validated by both the SDK CLI's manifest parser at build time and the Rust parser at install time.
How the user interacts with the accessory
- The user activates the view-mode command.
- The dropdown appears in the top-right of the search bar with the current selection visible.
- ⌘P (Cmd+P / Ctrl+P) opens the popover.
- A filter input at the top of the popover lets the user narrow options
by typing — substring match on the option's
title. - Arrow keys navigate the (filtered) options. Enter selects. Escape closes the popover and refocuses the trigger button. Tab closes.
- Clicking the trigger button toggles the popover.
SDK API
The view bag exposes a searchBarAccessory singleton with three methods.
Import it from asyar-sdk/view — the worker bag does not include it,
and a misimport from asyar-sdk/worker fails at module load.
onChange(handler): () => void
Fires once on view mount with the seed value (persisted, then manifest
default, then the first option), and on every user pick or programmatic
set({ value }).
import { searchBarAccessory } from "asyar-sdk/view";
const off = searchBarAccessory.onChange((value) => {
// re-filter or re-fetch your view's content
});
The returned disposer should be called on view teardown.
set(opts): Promise<void>
Two use cases:
-
Replace the option list at runtime — when the manifest cannot know the options ahead of time (user-defined tags, dynamically-fetched categories):
await searchBarAccessory.set({ options: [ { value: "front", title: "Front Page" }, { value: "best", title: "Best" }, { value: "comments", title: "Best Comments" }, ], }); -
Programmatically select an option — also fires
onChangehandlers:await searchBarAccessory.set({ value: "best" });
Both fields are optional; you can pass either, both, or call set({})
as a no-op.
clear(): Promise<void>
Drop the accessory from the launcher chrome mid-session. Rare — most extensions never call this; the launcher auto-clears when the view unmounts.
await searchBarAccessory.clear();
clear() removes the dropdown from the chrome but does not delete the
persisted value — the next view mount still seeds with the last
selection.
Persistence
The launcher persists the selected value per (extensionId, commandId)
in a SQLite store, separate from preferences. On view re-mount,
onChange fires with the previously-selected value rather than the
manifest default.
The persisted value is cleared automatically when the extension is uninstalled, alongside its storage, preferences, and cache.
Tier 1 vs Tier 2
For Tier 2 extensions (the typical case): import { searchBarAccessory } from "asyar-sdk/view" and use onChange / set / clear as
documented above.
For Tier 1 built-in features: same conceptual API, but consume the
launcher's searchBarAccessoryService singleton directly via
subscribe(extensionId, commandId, handler). The clipboard-history
built-in is the reference consumer — see
built-in-features/clipboard-history/
for an example.
Manifest mode rule
A command may declare both arguments (chip-row inputs collected before
the command runs) and searchBarAccessory (dropdown shown after the
view mounts) — they are orthogonal. Argument mode commandeers the search
bar visually before the view opens; the accessory takes over the right
slot once the view is up.
Relationship to other features
| Search bar accessory | Arguments | Preferences | |
|---|---|---|---|
| Scope | Per view session | Per invocation | Per install |
| UI | Top-right dropdown in search bar | Inline chip row in search bar | Settings panel |
| When shown | After view-mode command activates | Before any command runs (Tab) | Settings tab |
| Persistence | Last value per (ext, cmd) |
Last value per (ext, cmd, arg) |
All values |
| Max count | 1 per command | 3 per command | No limit |
| Reached via | searchBarAccessory.onChange |
args.arguments.<name> |
context.preferences |
Delivery guarantees
For Tier 2 extensions, filter-change pushes flow through the same
view-iframe channel as view:search and other host→view events. If the
view iframe is unmounted (e.g., the user dismissed the panel), the push
is silently dropped — the next view mount re-seeds via onChange with
the persisted value.
For Tier 1 (built-in) commands, subscriptions are direct in-process function calls with no iframe involved.
Validation at build time
The SDK CLI's asyar build validates the manifest shape — empty
options, default-not-in-options, and accessory on a non-view command all
fail before the extension can ship. Run validation explicitly with
asyar validate. See CLI reference.