Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions .claude/skills/search/SKILL.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
name: search
description: The search surface across the platform is the `search-entry` API — realm endpoints `/_search-v2` + `/_federated-search-v2`, the host resource `getSearchEntriesResource`, the `<SearchResults>` component (provided to cards as `@context.searchResultsComponent`), and the `RenderableSearchEntryLike` row view-model. Use whenever adding a search/query call site, choosing which search API to call, reviewing or refactoring search code, or writing a card that lists/queries other cards.
description: The search surface across the platform is the `search-entry` API — realm endpoints `/_search` + `/_federated-search`, the host resource `getSearchEntriesResource`, the `<SearchResults>` component (provided to cards as `@context.searchResultsComponent`), and the `RenderableSearchEntryLike` row view-model. Use whenever adding a search/query call site, choosing which search API to call, reviewing or refactoring search code, or writing a card that lists/queries other cards.
---

# Search — the `search-entry` API
Expand All @@ -15,7 +15,7 @@ card — it renders the entry transparently.**

| When you need to… | Use |
| ------------------------------------ | ----------------------------------------------------------------------- |
| Search from the realm / over HTTP | `/_search-v2`, `/_federated-search-v2` |
| Search from the realm / over HTTP | `/_search`, `/_federated-search` |
| Fetch results in a host resource | `getSearchEntriesResource` (`host/app/resources/search-entries`) |
| Render a result list in host UI | `<SearchResults>` (`host/app/components/card-search/search-results`) |
| Render a result list from a **card** | `@context.searchResultsComponent` |
Expand All @@ -25,8 +25,8 @@ card — it renders the entry transparently.**

## Realm server / API

- `/_search-v2` (single realm — `Realm.searchEntriesResponse`) and
`/_federated-search-v2` (realm-server — `handleSearchV2`) emit the
- `/_search` (single realm — `Realm.searchEntriesResponse`) and
`/_federated-search` (realm-server — `handleSearch`) emit the
`search-entry` document natively (heterogeneous `html` / `item` results).

## Host
Expand Down
4 changes: 2 additions & 2 deletions docs/search.md
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,7 @@ let { data: matching, meta } = await indexer.search({

## HTTP API

The TypeScript API described above is exposed by the realm server over HTTP as the `search-entry` API: `/_search-v2` at a realm root (a single realm) and `/_federated-search-v2` on the realm server (across realms). Both speak the `search-entry` wire query — build one from an ordinary `Query` with `searchEntryWireQueryFromQuery` — sent as the request body with the `QUERY` method. An Accept header of `application/vnd.card+json` must be sent.
The TypeScript API described above is exposed by the realm server over HTTP as the `search-entry` API: `/_search` at a realm root (a single realm) and `/_federated-search` on the realm server (across realms). Both speak the `search-entry` wire query — build one from an ordinary `Query` with `searchEntryWireQueryFromQuery` — sent as the request body with the `QUERY` method. An Accept header of `application/vnd.card+json` must be sent.

### Example

Expand All @@ -221,7 +221,7 @@ let query: Query = {
};

let response = await request
.post(`/_search-v2`)
.post(`/_search`)
.set('Accept', 'application/vnd.card+json')
.set('Content-Type', 'application/json')
.set('X-HTTP-Method-Override', 'QUERY')
Expand Down
2 changes: 1 addition & 1 deletion packages/base/card-api.gts
Original file line number Diff line number Diff line change
Expand Up @@ -345,7 +345,7 @@ export interface CardContext<T extends CardDef = CardDef> {
};
};
}>;
// The v2 search rendering surface: renders the heterogeneous `search-entry`
// The search rendering surface: renders the heterogeneous `search-entry`
// stream for a `search-entry`-rooted query — prerendered HTML inert (hydrated
// lazily) or a live card — so a card author renders results without ever
// branching on prerendered-vs-live. Supersedes `prerenderedCardSearchComponent`.
Expand Down
4 changes: 2 additions & 2 deletions packages/base/components/card-list.gts
Original file line number Diff line number Diff line change
Expand Up @@ -62,12 +62,12 @@ export default class CardList extends Component<Signature> {
@consume(CardContextName)
declare cardContext: CardContext | undefined;

// The v2 `search-entry`-rooted query, adapted from the incoming v1 `Query`.
// The `search-entry`-rooted query, adapted from the incoming `Query`.
// The default fieldset (no `fields` member) resolves to "html, falling back
// to the `item` serialization where no rendering matched" — exactly what the
// grid wants (prerendered HTML for cards; an `item`/`icon` fallback for file
// rows). `@format` binds the prerendered format through the query's
// `htmlQuery` (the v2 way to select it); CardsGrid passes `fitted`, which
// `htmlQuery` (the way to select it); CardsGrid passes `fitted`, which
// matches the default, so this is behavior-preserving there while keeping a
// non-`fitted` caller working. Only read under `{{#if @query}}`.
private get searchResultsQuery(): SearchEntryWireQuery {
Expand Down
2 changes: 1 addition & 1 deletion packages/boxel-cli/plugin/skills/boxel-api/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ All examples below assume `client` is a `BoxelCLIClient` instance.

## Federated search

Search across one or more realms via `/_federated-search-v2`. Query syntax matches the Boxel realm search format.
Search across one or more realms via `/_federated-search`. Query syntax matches the Boxel realm search format.

### CLI

Expand Down
2 changes: 1 addition & 1 deletion packages/boxel-cli/plugin/skills/search/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ description: Federated search across one or more Boxel realms. Use when the user

# Federated search

Wraps `boxel search`, which sends a query to the realm-server's `_federated-search-v2` endpoint and aggregates results across realms the active profile can read.
Wraps `boxel search`, which sends a query to the realm-server's `_federated-search` endpoint and aggregates results across realms the active profile can read.

## When the user asks to...

Expand Down
4 changes: 2 additions & 2 deletions packages/boxel-cli/src/commands/realm/ingest-card.ts
Original file line number Diff line number Diff line change
Expand Up @@ -433,7 +433,7 @@ class RealmCardIngester extends RealmSyncBase {
private async searchCards(
query: Record<string, unknown>,
): Promise<CardResource[]> {
// Query the SOURCE realm's own `_search-v2` directly rather than the
// Query the SOURCE realm's own `_search` directly rather than the
// profile-scoped federated search. A shared/published source realm (e.g.
// the catalog) isn't in the active profile's federated set, so federated
// search returns nothing for it — which is why instances and Specs went
Expand All @@ -444,7 +444,7 @@ class RealmCardIngester extends RealmSyncBase {
// for normal and published realms (the v1 `data`-vs-`included` split
// disappears — every match is an entry that references its item).
let res = await this.authenticator.authedRealmFetch(
`${this.realmRoot}_search-v2`,
`${this.realmRoot}_search`,
{
method: 'QUERY',
headers: {
Expand Down
12 changes: 6 additions & 6 deletions packages/boxel-cli/src/commands/search.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export interface SearchCommandOptions {
profileManager?: ProfileManager;
}

// `_federated-search-v2` speaks the search-entry wire grammar: one query
// `_federated-search` speaks the search-entry wire grammar: one query
// rooted on `search-entry`, where entry membership is addressed through
// `item.` (the card/file serialization). The type anchor is `item.on` and the
// field paths inside the filter operators carry the `item.` prefix. Callers
Expand Down Expand Up @@ -110,10 +110,10 @@ interface SearchEntryRequestBody {
}

/**
* Build a v2 search-entry request body from a card-rooted query: the
* Build a search-entry request body from a card-rooted query: the
* `item.`-addressed filter/sort plus the data-only fieldset. Pass `realms` for
* the federated `_federated-search-v2`; omit it to query a single realm's own
* `_search-v2`.
* the federated `_federated-search`; omit it to query a single realm's own
* `_search`.
*/
export function searchEntryRequestBody(
query: Record<string, unknown>,
Expand Down Expand Up @@ -200,7 +200,7 @@ export function itemsFromSearchEntryDoc(
}

/**
* Federated search across one or more realms via the `_federated-search-v2`
* Federated search across one or more realms via the `_federated-search`
* server endpoint.
*
* Sends the search-entry-rooted query as a QUERY request requesting the
Expand All @@ -224,7 +224,7 @@ export async function search(
}

let realmServerUrl = active.profile.realmServerUrl.replace(/\/$/, '');
let searchUrl = `${realmServerUrl}/_federated-search-v2`;
let searchUrl = `${realmServerUrl}/_federated-search`;

let realms = (Array.isArray(realmUrls) ? realmUrls : [realmUrls]).map(
ensureTrailingSlash,
Expand Down
2 changes: 1 addition & 1 deletion packages/boxel-cli/src/lib/boxel-cli-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -256,7 +256,7 @@ export class BoxelCLIClient {
}

/**
* Federated search across one or more realms via `_federated-search-v2`.
* Federated search across one or more realms via `_federated-search`.
* Delegates to the standalone `search()` in `commands/search.ts`, which
* returns the `item` serializations (the `card`/`file-meta` resources).
*/
Expand Down
8 changes: 4 additions & 4 deletions packages/boxel-cli/tests/commands/ingest-card-graph.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -140,10 +140,10 @@ function makeFakeAuthenticator(fetchedUrls: string[]): RealmAuthenticator {
);
}
// The ingester discovers instances + Specs via the source realm's own
// `_search-v2` endpoint (a data-only QUERY request), not the
// `_search` endpoint (a data-only QUERY request), not the
// profile-scoped federated search — so a shared/published source realm
// is reachable.
if (url === `${ROOT}_search-v2`) {
if (url === `${ROOT}_search`) {
let cards = fakeSearchData(String(init?.body ?? '{}'));
return new Response(JSON.stringify(searchEntryDoc(cards)), {
status: 200,
Expand All @@ -162,7 +162,7 @@ function makeFakeAuthenticator(fetchedUrls: string[]): RealmAuthenticator {
// instances of the entry card's exported classes, and all base-realm Spec
// cards (filtered by specType + ref in the ingester itself). The type anchor
// arrives `item.`-addressed (`filter['item.on']`) — the search-entry grammar
// `_search-v2` speaks.
// `_search` speaks.
function fakeSearchData(
bodyStr: string,
): { id: string; attributes?: unknown }[] {
Expand Down Expand Up @@ -206,7 +206,7 @@ function fakeSearchData(

// Wrap matched cards as a data-only search-entry document — one entry per card
// linking its `item`, with the card resources themselves in `included` (the
// shape `_search-v2` returns; a published realm carries its matches the same
// shape `_search` returns; a published realm carries its matches the same
// way, so the ingester needs no published-vs-normal special-casing).
function searchEntryDoc(cards: { id: string; attributes?: unknown }[]) {
return {
Expand Down
2 changes: 1 addition & 1 deletion packages/experiments-realm/app-card.gts
Original file line number Diff line number Diff line change
Expand Up @@ -331,7 +331,7 @@ class DefaultTabTemplate extends GlimmerComponent<DefaultTabSignature> {
} as Query;
}

// The v2 `search-entry`-rooted query, adapted from the v1 `query` above.
// The `search-entry`-rooted query, adapted from the `query` above.
// `fitted` is the default rendering, so no `htmlQuery` binding is needed.
// Undefined (no active tab ref) leaves the search component idle.
get searchResultsQuery(): SearchEntryWireQuery | undefined {
Expand Down
4 changes: 2 additions & 2 deletions packages/experiments-realm/components/card-list.gts
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ interface CardListSignature {
Element: HTMLElement;
}
export class CardList extends GlimmerComponent<CardListSignature> {
// The v2 `search-entry`-rooted query, adapted from the incoming v1 `Query`.
// `embedded` is bound through the query's `htmlQuery` field (the v2 way to
// The `search-entry`-rooted query, adapted from the incoming `Query`.
// `embedded` is bound through the query's `htmlQuery` field (the way to
// select a prerendered format); a bare `eq.format` would be read as an
// `item.` field path and rejected.
get searchResultsQuery(): SearchEntryWireQuery {
Expand Down
6 changes: 3 additions & 3 deletions packages/host/app/commands/sync-openrouter-models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -354,7 +354,7 @@ export default class SyncOpenRouterModelsCommand extends HostBaseCommand<
{ fields: ['item'] },
);
let response = await this.network.authedFetch(
new URL('_search-v2', realmURL).href,
new URL('_search', realmURL).href,
{
method: 'QUERY',
headers: {
Expand Down Expand Up @@ -383,10 +383,10 @@ export default class SyncOpenRouterModelsCommand extends HostBaseCommand<
}
} else {
// A 200 that isn't a search-entry document is unexpected for
// /_search-v2; surface it rather than silently treating every model
// /_search; surface it rather than silently treating every model
// as new (same best-effort fallback as the catch below).
console.warn(
'Unexpected /_search-v2 response shape, treating all models as new',
'Unexpected /_search response shape, treating all models as new',
);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ import CardRenderer from '../card-renderer';

import SearchResultError from './search-result-error';

// `HydrationMode` is the card-facing contract (it rides the v2 `@context`
// `HydrationMode` is the card-facing contract (it rides the `@context`
// search surface), so it lives in runtime-common; re-exported here because this
// is where it's consumed and where call sites have long imported it.
export type { HydrationMode };
Expand Down
8 changes: 4 additions & 4 deletions packages/host/app/components/card-search/panel-content.gts
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ interface Signature {
Blocks: {};
}

// The results pane of the search sheet / card chooser. Renders through the v2
// The results pane of the search sheet / card chooser. Renders through the
// `<SearchResults>` component family: one instance for the realm search, a
// nested one for recents (with the live-recents fallback layered in), then hands
// their yielded `search-entry` streams to `<SheetResults>`, which lays them out
Expand Down Expand Up @@ -183,7 +183,7 @@ export default class PanelContent extends Component<Signature> {
return this.cardResource?.isLoaded ?? false;
}

// The v2 `search-entry` query for the main realm search, built from the
// The `search-entry` query for the main realm search, built from the
// shared `Query` builder via `searchEntryWireQueryFromQuery`. Fitted is the
// default rendering, so no `htmlQuery` override is needed; realms ride
// alongside. Undefined leaves the search idle (the skip cases: empty search
Expand All @@ -206,7 +206,7 @@ export default class PanelContent extends Component<Signature> {
),
realms: this.args.realmFilter.selectedURLs,
// Cap each realm's results at the focused-section display limit — the
// most the sheet ever shows in one section. The v2 search applies
// most the sheet ever shows in one section. The search applies
// `page.size` per realm (so every realm section is still represented) and
// still reports the full match count in `meta.page.total` (which drives
// the result-count summary), so this only trims rows the sheet would
Expand Down Expand Up @@ -263,7 +263,7 @@ export default class PanelContent extends Component<Signature> {
cardUrls: this.recentCardUrls,
};
}
// No selected realm hosts a recent card. The v2 search treats an empty
// No selected realm hosts a recent card. The search treats an empty
// `realms` array as "search every realm", which — with the `cardUrls`
// constraint still matching them — would resurface recents from realms the
// user filtered out. Suppress the recents query instead, so a filtered-out
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import type { HydrationMode } from './hydratable-card';

import type StoreService from '../../services/store';

// The one v2 search component family. Consumes the heterogeneous `search-entry`
// The one search component family. Consumes the heterogeneous `search-entry`
// stream from `getSearchEntriesResource` (through the shared render-stable
// view-model layer) and renders it transparently — prerendered HTML inert (the
// fast path, hydrated lazily on interaction) or the live serialization. Used
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -567,7 +567,7 @@ export default class PlaygroundPanel extends Component<Signature> {
};
}

// The v2 `search-entry` queries for the instance chooser, adapted from the
// The `search-entry` queries for the instance chooser, adapted from the
// legacy `Query` getters above. The default fieldset resolves to fitted HTML
// (the format these searches used), so no `htmlQuery` override is needed.
private get searchResultsQuery(): SearchEntryWireQuery | undefined {
Expand Down Expand Up @@ -959,8 +959,8 @@ export default class PlaygroundPanel extends Component<Signature> {
}

let selectedCardId = this.dropdownSelection.card.id;
// `entry.id` is the bare card URL already (the v2 identity strips the
// `.json` the dropdown selection also omits), so they compare directly.
// `entry.id` is the bare card URL already (the search-entry identity strips
// the `.json` the dropdown selection also omits), so they compare directly.
let card = entries.find((c) => c.id === selectedCardId);
return card;
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,10 +109,10 @@ export default class CreateListingModal extends Component<Signature> {
return [...new Set(realms)];
}

// The v2 `search-entry` query for the selected example cards: scoped to the
// The `search-entry` query for the selected example cards: scoped to the
// chosen card URLs (matched by index file URL, hence the `.json`-suffixed
// `selectedExampleCardUrls`) and their realms, with the `atom` rendering
// bound through the filter's `htmlQuery` (the v2 way to select a prerendered
// bound through the filter's `htmlQuery` (the way to select a prerendered
// format). The eq carries only that binding, which the engine lifts out,
// leaving the result set scoped purely by `cardUrls`.
private get exampleSearchQuery(): SearchEntryWireQuery {
Expand Down
2 changes: 1 addition & 1 deletion packages/host/app/lib/prerender-fetch-headers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ export function jobIdHeader(): Record<string, string> {
return j ? { [X_BOXEL_JOB_ID_HEADER]: j } : {};
}

// Per-search correlation id. Minted fresh for each `_federated-search-v2`
// Per-search correlation id. Minted fresh for each `_federated-search`
// fetch the SPA issues while rendering inside a prerender tab, and stamped
// as `x-boxel-logging-correlation-id`. The realm-server reads it back out and keys its
// `realm:search-timing` line on it, so a search the prerender observes as
Expand Down
4 changes: 2 additions & 2 deletions packages/host/app/lib/search-in-flight-key.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import {
type Query,
} from '@cardstack/runtime-common';

// Stable digest key for store-side `_federated-search-v2` in-flight dedup.
// Stable digest key for store-side `_federated-search` in-flight dedup.
// Mirrors `runtime-common/realm-index-query-engine.ts:searchInFlightKey`
// but takes a realms array (the host fires federated searches against
// one or more realms; the realm-server engine is per-realm so its
Expand All @@ -14,7 +14,7 @@ import {
// a correctness boundary.
//
// `realms` order is preserved (not sorted): the realm-server's
// `_federated-search-v2` iterates the array and concatenates results in
// `_federated-search` iterates the array and concatenates results in
// that order, so `[a, b]` and `[b, a]` are different requests.
export function searchInFlightKey(
realms: string[],
Expand Down
4 changes: 2 additions & 2 deletions packages/host/app/resources/renderable-search-entries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ function formatFromHtmlQuery(
return 'fitted';
}

// One v2 search result as a renderable view-model. Wraps the resource's raw
// One search result as a renderable view-model. Wraps the resource's raw
// `SearchEntry`, exposing the chosen `html` rendering, the raw `item`
// serialization, and a ready-to-render `component` that renders HTML inert (and
// hydrates it lazily) or resolves a full live row — so a consumer renders
Expand Down Expand Up @@ -305,7 +305,7 @@ export class RenderableSearchEntries {
}
}

// Build the render-stable view-model layer over a v2 search. Call exactly once
// Build the render-stable view-model layer over a search. Call exactly once
// per owner (a class field), never inside a getter or during render: it creates
// one `getSearchEntriesResource` parented to `owner`, and per-render calls would
// pile up live resources. Vary the search through the `getQuery` thunk (re-read
Expand Down
Loading
Loading