Universal search
Design and implementation plan
Core owns versioned contracts, provider coordination and a compact grouped search dialog. Apps own queries, data and resource views. Initial providers are Memory and Bookmarks; Todo is outside this change.
- Define
resource-view:v1and canonical encoded resource links, aligned withCapabilityResourceRef; opening only reads/displays a resource. - Define
search:v1: bounded plain-text results, nullable context, opaque pagination cursor and provider notices. Source metadata comes from core. - Register Memory and Bookmarks providers and advertise package capabilities.
- Coordinate concurrent searches with per-source deadlines, incremental updates, isolated failures, source filters and stale-query suppression.
- Add header search, grouped results, keyboard navigation, pagination and resource opening through existing capability modal rendering.
- Validate with targeted ESLint and TypeScript; reconcile documentation.
- User verification in an authenticated context after a deliberate host restart.
Verification: core and web tsc --noEmit pass. Targeted ESLint passes. Repo-wide
bun run lint reports unrelated unused variables in
plugins/nr/db/evaluation-queue.ts (NostrEvent) and
plugins/translate/service.ts (settings). A one-off check opened both existing
plugin databases read-only and validated search results, next pages, encoded-address
round trips and resource views through a fresh capability registry. Each provider
returned three hits with a next page; both first-hit views passed the shared schema.
No indexes or data were changed. No tests were added/run and no browser session was
used. The authenticated UI still needs user verification: header entry, typing,
source selection, keyboard navigation, pagination, copying/opening links and
missing/deleted-resource feedback. Host restart is required; no restart signal was
created in the active chat.
Contracts
Contracts live in src/capabilities/, using the existing
capability registry. resource-view:v1.open
accepts a resource type/ID and returns either a provider-owned WebNodeRoot or
an explicit not-found result. It never mutates data. Providers may implement
resource views independently of search.
Addresses are complete CapabilityResourceRef values for resource-view:v1.
Provider IDs use registry identity, not configurable aliases. The canonical
browser link is #/resource/v1/<encoded-provider>/<encoded-type>/<encoded-id>;
each segment is encoded separately. Core routes exactly to the addressed
provider, never substitutes another provider, and uses existing capability
missing-provider/error UI. Addresses are scoped to an AppWeaver installation
and its active workspace; they do not transfer data between installations.
search:v1.search accepts {query, limit, cursor} and returns
{results, nextCursor, notices}. Results contain {id, text, context, address}.
Text is plain text. Cursors are provider-owned, query-scoped strings; null starts
a search or indicates no next page. Providers must use deterministic ordering,
bound database work/results, and return only resources belonging to themselves.
Core uses registry metadata for source labels/icons and deduplicates result IDs
within each source. It preserves provider ordering rather than comparing scores.
Providers opt into this capability as an interactive search service; remote
providers must make network-query behavior explicit to their users.
Example result (source metadata is attached separately from the registry):
{
"results": [{
"id": "53",
"text": "Release engineering guide",
"context": "Engineering · https://example.com/releases",
"address": {
"capability": { "name": "resource-view", "version": 1 },
"providerId": "appweaver-bm-plugin/resource-view/v1",
"resourceType": "bookmark",
"resourceId": "53"
}
}],
"nextCursor": null,
"notices": []
}
Providers register SearchV1 and, independently, ResourceViewV1 with
defineCapabilityProvider, declare both under appweaver.capabilities.provides,
and set coreApiVersion to ^14.11.1 or newer. Core binds source identity at
registration. Every result's resource provider must belong to that same owner.
The reusable coordinator is in src/core/search/coordinator.ts and takes an
injected transport; the web dialog uses the authenticated generic capability
WebSocket path. The coordinator does not depend on browser state or plugin code.
Surfaces and behavior
The authenticated web header opens a compact search dialog. Queries are debounced; each provider progresses independently, with at most four simultaneous calls and a per-call deadline. New queries, workspace/session changes, disconnection and dialog cleanup discard obsolete work. Timeouts stop waiting; they cannot preempt synchronous in-process plugin code. Search does not write timeline history. Selecting a result opens the owning app's view through the generic capability modal. Copyable resource links can also be opened directly after authentication.
Memory searches existing committed code/Markdown snapshots in the active workspace, never refreshes indexes, and displays snapshot provenance. Bookmarks searches saved bookmark fields using bound SQL and opens the existing detail view. See Memory and Bookmarks.