diff --git a/.claude/skills/search/SKILL.md b/.claude/skills/search/SKILL.md index 9c2db639667..942fb16f45a 100644 --- a/.claude/skills/search/SKILL.md +++ b/.claude/skills/search/SKILL.md @@ -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 `` 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 `` 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 @@ -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 | `` (`host/app/components/card-search/search-results`) | | Render a result list from a **card** | `@context.searchResultsComponent` | @@ -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 diff --git a/docs/search.md b/docs/search.md index 6bb8ca67206..59e97730055 100644 --- a/docs/search.md +++ b/docs/search.md @@ -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 @@ -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') diff --git a/packages/base/card-api.gts b/packages/base/card-api.gts index ffa4d4a7904..e99370fcbb1 100644 --- a/packages/base/card-api.gts +++ b/packages/base/card-api.gts @@ -345,7 +345,7 @@ export interface CardContext { }; }; }>; - // 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`. diff --git a/packages/base/components/card-list.gts b/packages/base/components/card-list.gts index 4428317dd31..560bf6f3725 100644 --- a/packages/base/components/card-list.gts +++ b/packages/base/components/card-list.gts @@ -62,12 +62,12 @@ export default class CardList extends Component { @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 { diff --git a/packages/boxel-cli/plugin/skills/boxel-api/SKILL.md b/packages/boxel-cli/plugin/skills/boxel-api/SKILL.md index f7f7791a681..ecdd16b8815 100644 --- a/packages/boxel-cli/plugin/skills/boxel-api/SKILL.md +++ b/packages/boxel-cli/plugin/skills/boxel-api/SKILL.md @@ -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 diff --git a/packages/boxel-cli/plugin/skills/search/SKILL.md b/packages/boxel-cli/plugin/skills/search/SKILL.md index e35a7269b0f..7bf8d8f9006 100644 --- a/packages/boxel-cli/plugin/skills/search/SKILL.md +++ b/packages/boxel-cli/plugin/skills/search/SKILL.md @@ -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... diff --git a/packages/boxel-cli/src/commands/realm/ingest-card.ts b/packages/boxel-cli/src/commands/realm/ingest-card.ts index 171e3675afd..a5c9098b258 100644 --- a/packages/boxel-cli/src/commands/realm/ingest-card.ts +++ b/packages/boxel-cli/src/commands/realm/ingest-card.ts @@ -433,7 +433,7 @@ class RealmCardIngester extends RealmSyncBase { private async searchCards( query: Record, ): Promise { - // 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 @@ -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: { diff --git a/packages/boxel-cli/src/commands/search.ts b/packages/boxel-cli/src/commands/search.ts index 4b07e3deeed..2a69b9c7728 100644 --- a/packages/boxel-cli/src/commands/search.ts +++ b/packages/boxel-cli/src/commands/search.ts @@ -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 @@ -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, @@ -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 @@ -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, diff --git a/packages/boxel-cli/src/lib/boxel-cli-client.ts b/packages/boxel-cli/src/lib/boxel-cli-client.ts index d1fc1ec3318..00bacab5c33 100644 --- a/packages/boxel-cli/src/lib/boxel-cli-client.ts +++ b/packages/boxel-cli/src/lib/boxel-cli-client.ts @@ -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). */ diff --git a/packages/boxel-cli/tests/commands/ingest-card-graph.test.ts b/packages/boxel-cli/tests/commands/ingest-card-graph.test.ts index 0e5b53a4594..9a2ff1aae38 100644 --- a/packages/boxel-cli/tests/commands/ingest-card-graph.test.ts +++ b/packages/boxel-cli/tests/commands/ingest-card-graph.test.ts @@ -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, @@ -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 }[] { @@ -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 { diff --git a/packages/experiments-realm/app-card.gts b/packages/experiments-realm/app-card.gts index eefb0043514..024ee686a5f 100644 --- a/packages/experiments-realm/app-card.gts +++ b/packages/experiments-realm/app-card.gts @@ -331,7 +331,7 @@ class DefaultTabTemplate extends GlimmerComponent { } 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 { diff --git a/packages/experiments-realm/components/card-list.gts b/packages/experiments-realm/components/card-list.gts index 3a513d2772c..8a8b8ac3a08 100644 --- a/packages/experiments-realm/components/card-list.gts +++ b/packages/experiments-realm/components/card-list.gts @@ -21,8 +21,8 @@ interface CardListSignature { Element: HTMLElement; } export class CardList extends GlimmerComponent { - // 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 { diff --git a/packages/host/app/commands/sync-openrouter-models.ts b/packages/host/app/commands/sync-openrouter-models.ts index 29a94ecaa15..072cb223112 100644 --- a/packages/host/app/commands/sync-openrouter-models.ts +++ b/packages/host/app/commands/sync-openrouter-models.ts @@ -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: { @@ -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', ); } } diff --git a/packages/host/app/components/card-search/hydratable-card.gts b/packages/host/app/components/card-search/hydratable-card.gts index 0f0696738ba..1d4257310a9 100644 --- a/packages/host/app/components/card-search/hydratable-card.gts +++ b/packages/host/app/components/card-search/hydratable-card.gts @@ -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 }; diff --git a/packages/host/app/components/card-search/panel-content.gts b/packages/host/app/components/card-search/panel-content.gts index d0b05585b37..043d74ea95d 100644 --- a/packages/host/app/components/card-search/panel-content.gts +++ b/packages/host/app/components/card-search/panel-content.gts @@ -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 // `` 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 ``, which lays them out @@ -183,7 +183,7 @@ export default class PanelContent extends Component { 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 @@ -206,7 +206,7 @@ export default class PanelContent extends Component { ), 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 @@ -263,7 +263,7 @@ export default class PanelContent extends Component { 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 diff --git a/packages/host/app/components/card-search/search-results.gts b/packages/host/app/components/card-search/search-results.gts index f50f840b829..48da37b684d 100644 --- a/packages/host/app/components/card-search/search-results.gts +++ b/packages/host/app/components/card-search/search-results.gts @@ -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 diff --git a/packages/host/app/components/operator-mode/code-submode/playground/playground-panel.gts b/packages/host/app/components/operator-mode/code-submode/playground/playground-panel.gts index 1cb5a4dfabb..43e25792b09 100644 --- a/packages/host/app/components/operator-mode/code-submode/playground/playground-panel.gts +++ b/packages/host/app/components/operator-mode/code-submode/playground/playground-panel.gts @@ -567,7 +567,7 @@ export default class PlaygroundPanel extends Component { }; } - // 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 { @@ -959,8 +959,8 @@ export default class PlaygroundPanel extends Component { } 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; }; diff --git a/packages/host/app/components/operator-mode/create-listing-modal.gts b/packages/host/app/components/operator-mode/create-listing-modal.gts index 2aeaf7caecd..50f07091873 100644 --- a/packages/host/app/components/operator-mode/create-listing-modal.gts +++ b/packages/host/app/components/operator-mode/create-listing-modal.gts @@ -109,10 +109,10 @@ export default class CreateListingModal extends Component { 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 { diff --git a/packages/host/app/lib/prerender-fetch-headers.ts b/packages/host/app/lib/prerender-fetch-headers.ts index 470d400ffd2..f6e7e9061a3 100644 --- a/packages/host/app/lib/prerender-fetch-headers.ts +++ b/packages/host/app/lib/prerender-fetch-headers.ts @@ -48,7 +48,7 @@ export function jobIdHeader(): Record { 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 diff --git a/packages/host/app/lib/search-in-flight-key.ts b/packages/host/app/lib/search-in-flight-key.ts index 0fa94b55ed2..97c9f028ffa 100644 --- a/packages/host/app/lib/search-in-flight-key.ts +++ b/packages/host/app/lib/search-in-flight-key.ts @@ -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 @@ -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[], diff --git a/packages/host/app/resources/renderable-search-entries.ts b/packages/host/app/resources/renderable-search-entries.ts index 96af6cedc7e..4b3e93acc20 100644 --- a/packages/host/app/resources/renderable-search-entries.ts +++ b/packages/host/app/resources/renderable-search-entries.ts @@ -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 @@ -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 diff --git a/packages/host/app/resources/search-entries.ts b/packages/host/app/resources/search-entries.ts index f4d9d958b63..ca03f4dae45 100644 --- a/packages/host/app/resources/search-entries.ts +++ b/packages/host/app/resources/search-entries.ts @@ -47,11 +47,11 @@ import type StoreService from '../services/store'; const waiter = buildWaiter('search-entries-resource:search-waiter'); // `SearchEntryRendering` is the card-facing rendering view-model (it rides the -// v2 `@context` search surface), so it lives in runtime-common; re-exported +// `@context` search surface), so it lives in runtime-common; re-exported // here because this resource builds it and call sites import it from here. export type { SearchEntryRendering }; -// One v2 search result, joined from the wire document: the `search-entry` +// One search result, joined from the wire document: the `search-entry` // resource plus the `html` renderings and/or `item` serialization it // references in `included`. An empty `html` array means the entry matched but // no rendering satisfies the query's htmlQuery yet — the invalidation re-run @@ -475,7 +475,7 @@ function buildRendering( }; } -// The one v2 host live-search resource: issues the `search-entry` wire query +// The one host live-search resource: issues the `search-entry` wire query // through `StoreService.searchEntries`, subscribes to each searched realm, // and re-runs on incremental index events with a per-realm partial refresh. // Realms ride in the query's `realms` member; omitted, every available realm diff --git a/packages/host/app/resources/search.ts b/packages/host/app/resources/search.ts index c1da7600430..5bd3a546ac9 100644 --- a/packages/host/app/resources/search.ts +++ b/packages/host/app/resources/search.ts @@ -389,7 +389,7 @@ export class SearchResource< if (!isLive && seedIsAuthoritative) { // The parent document already serialized the relationship set // we are resolving, so a re-query would only re-derive the - // same data and (in prerender) burn a `_federated-search-v2` + // same data and (in prerender) burn a `_federated-search` // round-trip per field per loaded card. Skip the search and // also bypass the query/realm equality check below so a // signature drift between the parent doc's `links.search` and diff --git a/packages/host/app/routes/render.ts b/packages/host/app/routes/render.ts index fac130b1e53..47e65e8c577 100644 --- a/packages/host/app/routes/render.ts +++ b/packages/host/app/routes/render.ts @@ -152,7 +152,7 @@ export default class RenderRoute extends Route { if (isTesting()) { (globalThis as any).__boxelRenderContext = undefined; } - // Drop any pending `_federated-search-v2` in-flight entries the + // Drop any pending `_federated-search` in-flight entries the // render-context coalescer accumulated during this visit. Entries // self-clear on settle, but a deactivate while one is still // in-flight could otherwise let a same-key caller arriving in the diff --git a/packages/host/app/services/host-mode-service.ts b/packages/host/app/services/host-mode-service.ts index 34059cb97c1..9f22af39678 100644 --- a/packages/host/app/services/host-mode-service.ts +++ b/packages/host/app/services/host-mode-service.ts @@ -304,7 +304,7 @@ export default class HostModeService extends Service { ) { realmServerURL = hostModeOrigin; } - let searchURL = new URL('_federated-search-v2', realmServerURL); + let searchURL = new URL('_federated-search', realmServerURL); let cardJsonURL = cardURL.endsWith('.json') ? cardURL : `${cardURL}.json`; // The head markup is the `head` rendering of the card's `search-entry`: // an html-only query at `html.format: head`, scoped to the single card. diff --git a/packages/host/app/services/store.ts b/packages/host/app/services/store.ts index aefc249b82c..185bba9f7a5 100644 --- a/packages/host/app/services/store.ts +++ b/packages/host/app/services/store.ts @@ -247,13 +247,13 @@ export default class StoreService extends Service implements StoreInterface { > = new Map(); private inflightCardMutations: Map> = new Map(); private inflightCardLoads: Map> = new Map(); - // Coalesce concurrent same-(realms, query) `_federated-search-v2` HTTP + // Coalesce concurrent same-(realms, query) `_federated-search` HTTP // calls during a prerender. Gated on `__boxelRenderContext` so live // user searches stay uncoalesced — write-then-read freshness story // unchanged outside prerender. Entries self-clear on `.finally()` via // identity check. private inflightSearch: Map> = new Map(); - // Resolved-doc cache for same-realm `_federated-search-v2` calls during + // Resolved-doc cache for same-realm `_federated-search` calls during // a prerender. Layered *above* `inflightSearch`: a cache hit skips // the network round-trip entirely; a miss falls through to the // in-flight Map and the cache is populated on resolve. Keyed by @@ -1027,7 +1027,7 @@ export default class StoreService extends Service implements StoreInterface { return persistedResult as T | CardErrorJSONAPI; } - // Instances only: the query runs against the v2 search requesting full + // Instances only: the query runs against the search requesting full // `item` serializations, the results hydrate into the store, and the caller // gets instances back. For the raw search-entry wire format (HTML // renderings, field-limited serializations, the document itself) use @@ -1072,7 +1072,7 @@ export default class StoreService extends Service implements StoreInterface { return opts?.includeMeta ? result : result.instances; } - // The raw v2 wire format: heterogeneous `search-entry` resources with the + // The raw wire format: heterogeneous `search-entry` resources with the // `html` / `item` branches the query's `fields[search-entry]` selects. // Nothing is hydrated into the store. async searchEntries( @@ -1152,10 +1152,10 @@ export default class StoreService extends Service implements StoreInterface { } // The instances path's resolved-document layer: the `Query` runs against - // the v2 search requesting full `item` serializations, and the resulting + // the search requesting full `item` serializations, and the resulting // search-entry document (one `item` per entry in `included`) is what the // hydration pipeline and the caches below consume. - // Sits between `store.search` and `_federated-search-v2`. + // Sits between `store.search` and `_federated-search`. // // Two layers of dedup, both prerender-gated: // @@ -1303,7 +1303,7 @@ export default class StoreService extends Service implements StoreInterface { // TODO remove this assertion after multi-realm server/federated identity is supported this.realmServer.assertOwnRealmServer(realmServerURLs); let [realmServerURL] = realmServerURLs; - let searchURL = new URL('_federated-search-v2', realmServerURL); + let searchURL = new URL('_federated-search', realmServerURL); let response = await this.realmServer.maybeAuthedFetchForRealms( searchURL.href, realms, diff --git a/packages/host/tests/acceptance/host-mode-test.gts b/packages/host/tests/acceptance/host-mode-test.gts index b456e3d7533..5a6503d824a 100644 --- a/packages/host/tests/acceptance/host-mode-test.gts +++ b/packages/host/tests/acceptance/host-mode-test.gts @@ -433,7 +433,7 @@ module('Acceptance | host mode tests', function (hooks) { let realFetch = globalThis.fetch; globalThis.fetch = async (input: any, init?: any) => { let url = typeof input === 'string' ? input : (input?.url ?? ''); - if (url.includes('_federated-search-v2') && init?.body) { + if (url.includes('_federated-search') && init?.body) { let body = JSON.parse(init.body); if (body?.filter?.eq?.htmlQuery) { capturedHeadQuery = body; @@ -482,7 +482,7 @@ module('Acceptance | host mode tests', function (hooks) { try { await visit('/test/Pet/mango.json'); - // The request is the v2 head query: html-only fieldset, the `head` + // The request is the head query: html-only fieldset, the `head` // htmlQuery, scoped to the visited card. assert.deepEqual( capturedHeadQuery?.fields, diff --git a/packages/host/tests/acceptance/query-fields-test.gts b/packages/host/tests/acceptance/query-fields-test.gts index 4878e1c8a0b..123d5292684 100644 --- a/packages/host/tests/acceptance/query-fields-test.gts +++ b/packages/host/tests/acceptance/query-fields-test.gts @@ -211,7 +211,7 @@ module( let interceptedSearchRequests: string[] = []; let handler = async (request: Request) => { let url = new URL(request.url); - if (url.pathname.endsWith('/_federated-search-v2')) { + if (url.pathname.endsWith('/_federated-search')) { interceptedSearchRequests.push(request.url); } return null; @@ -283,7 +283,7 @@ module( let interceptedSearchRequests: string[] = []; let handler = async (request: Request) => { let url = new URL(request.url); - if (url.pathname.endsWith('/_federated-search-v2')) { + if (url.pathname.endsWith('/_federated-search')) { interceptedSearchRequests.push(request.url); } return null; @@ -333,7 +333,7 @@ module( let interceptedSearchRequests: string[] = []; let handler = async (request: Request) => { let url = new URL(request.url); - if (url.pathname.endsWith('/_federated-search-v2')) { + if (url.pathname.endsWith('/_federated-search')) { interceptedSearchRequests.push(request.url); } return null; @@ -410,7 +410,7 @@ module( let interceptedSearchRequests: string[] = []; let handler = async (request: Request) => { let url = new URL(request.url); - if (url.pathname.endsWith('/_federated-search-v2')) { + if (url.pathname.endsWith('/_federated-search')) { interceptedSearchRequests.push(request.url); } return null; @@ -454,7 +454,7 @@ module( let interceptedSearchRequests: string[] = []; let handler = async (request: Request) => { let url = new URL(request.url); - if (url.pathname.endsWith('/_federated-search-v2')) { + if (url.pathname.endsWith('/_federated-search')) { interceptedSearchRequests.push(request.url); } return null; @@ -641,7 +641,7 @@ module( let url = new URL(request.url); // Track client-side search requests - if (url.pathname.endsWith('/_federated-search-v2')) { + if (url.pathname.endsWith('/_federated-search')) { interceptedSearchRequests.push(request.url); } @@ -731,7 +731,7 @@ module( let handler = async (request: Request) => { let url = new URL(request.url); - if (url.pathname.endsWith('/_federated-search-v2')) { + if (url.pathname.endsWith('/_federated-search')) { interceptedSearchRequests.push(request.url); } @@ -787,7 +787,7 @@ module( let interceptedSearchRequests: string[] = []; let handler = async (request: Request) => { let url = new URL(request.url); - if (url.pathname.endsWith('/_federated-search-v2')) { + if (url.pathname.endsWith('/_federated-search')) { interceptedSearchRequests.push(request.url); } return null; @@ -865,7 +865,7 @@ module( let handler = async (request: Request) => { let url = new URL(request.url); - if (url.pathname.endsWith('/_federated-search-v2')) { + if (url.pathname.endsWith('/_federated-search')) { interceptedSearchRequests.push(request.url); } diff --git a/packages/host/tests/helpers/realm-server-mock/routes.ts b/packages/host/tests/helpers/realm-server-mock/routes.ts index 50fe0e32459..2b13ac4b28d 100644 --- a/packages/host/tests/helpers/realm-server-mock/routes.ts +++ b/packages/host/tests/helpers/realm-server-mock/routes.ts @@ -73,7 +73,7 @@ export function registerDefaultRoutes() { function registerSearchRoutes() { registerRealmServerRoute({ - path: '/_federated-search-v2', + path: '/_federated-search', handler: async (req, _url) => { let realmList: string[]; let payload: unknown; @@ -97,7 +97,7 @@ function registerSearchRoutes() { throw e; } - // Mirror the realm-server's `handle-search-v2`: read the client's + // Mirror the realm-server's `handle-search`: read the client's // correlation id off the request and thread it into searchEntryRealms, // so the real `realm:search-timing` line is emitted (and observable by // host integration tests) keyed by the id the client minted. @@ -403,10 +403,10 @@ function registerAuthRoutes() { }); } -// The v2 search-entry searchable-realm resolver. In-process registry +// The search-entry searchable-realm resolver. In-process registry // realms expose `searchEntries` directly; a live remote realm (base, skills, // catalog on localhost:4201) is reached by passing the original wire payload -// through to its per-realm `_search-v2` endpoint — the parsed query the +// through to its per-realm `_search` endpoint — the parsed query the // fan-out hands us is the server's internal form and has no wire spelling, // so the passthrough closes over the raw payload instead. function getSearchEntrySearchableRealmForURL( @@ -440,7 +440,7 @@ function getSearchEntrySearchableRealmForURL( string, unknown >; - let url = new URL('_search-v2', resolvedRealmURL); + let url = new URL('_search', resolvedRealmURL); let response = await globalThis.fetch(url.href, { method: 'QUERY', headers: { @@ -452,7 +452,7 @@ function getSearchEntrySearchableRealmForURL( if (!response.ok) { let responseText = await response.text(); throw new Error( - `Remote realm search-v2 failed for ${resolvedRealmURL}: ${response.status} ${responseText}`, + `Remote realm search failed for ${resolvedRealmURL}: ${response.status} ${responseText}`, ); } return (await response.json()) as SearchEntryCollectionDocument; diff --git a/packages/host/tests/helpers/search-cards.ts b/packages/host/tests/helpers/search-cards.ts index ed0fb8057ee..850cc25b7cc 100644 --- a/packages/host/tests/helpers/search-cards.ts +++ b/packages/host/tests/helpers/search-cards.ts @@ -9,7 +9,7 @@ import { } from '@cardstack/runtime-common'; // Test-only: fetch the card/file-meta serializations matching a card-rooted -// `Query` through the v2 search-entry engine, returning them in the +// `Query` through the search-entry engine, returning them in the // `{ data, included, meta }` collection shape index assertions read. Requests // the data-only fieldset (one full `item` per entry); the top-level items land // in `data`, their transitively-linked resources in `included`. diff --git a/packages/host/tests/integration/commands/sync-openrouter-models-test.gts b/packages/host/tests/integration/commands/sync-openrouter-models-test.gts index 51bf9f6329c..adccabb84ab 100644 --- a/packages/host/tests/integration/commands/sync-openrouter-models-test.gts +++ b/packages/host/tests/integration/commands/sync-openrouter-models-test.gts @@ -140,14 +140,14 @@ module('Integration | commands | sync-openrouter-models', function (hooks) { return capturedOperations.find((op) => op.href === href); } - test('discovers existing OpenRouterModel slugs through /_search-v2', async function (assert) { + test('discovers existing OpenRouterModel slugs through /_search', async function (assert) { // The API still lists gpt-4 (slug openai-gpt-4) and introduces a new model // (slug google-gemini-pro); the pre-existing legacy-model is absent. apiModels = [{ id: 'openai/gpt-4' }, { id: 'google/gemini-pro' }]; let result = await runSync(); - // The existing, still-listed model is discovered via v2, so it is updated + // The existing, still-listed model is discovered via search, so it is updated // in place rather than re-added. assert.strictEqual( opFor('OpenRouterModel/openai-gpt-4.json')?.op, @@ -162,8 +162,8 @@ module('Integration | commands | sync-openrouter-models', function (hooks) { 'previously unknown model resolves to an add', ); - // The existing model the API no longer lists is discovered via v2 and - // marked deprecated — this op only exists because v2 surfaced its slug. + // The existing model the API no longer lists is discovered via search and + // marked deprecated — this op only exists because the search surfaced its slug. let legacyOp = opFor('OpenRouterModel/legacy-model.json'); assert.strictEqual( legacyOp?.op, @@ -177,7 +177,7 @@ module('Integration | commands | sync-openrouter-models', function (hooks) { assert.ok( result.status.includes('1 deprecated'), - 'status reports the one deprecated model discovered via v2', + 'status reports the one deprecated model discovered via search', ); }); }); diff --git a/packages/host/tests/integration/components/card-chooser-test.gts b/packages/host/tests/integration/components/card-chooser-test.gts index 00516b40540..21fc954d10e 100644 --- a/packages/host/tests/integration/components/card-chooser-test.gts +++ b/packages/host/tests/integration/components/card-chooser-test.gts @@ -502,7 +502,7 @@ module('Integration | card-chooser', function (hooks) { await focus('[data-test-search-field]'); // Type one keystroke at a time. Each keystroke updates the search key, - // which re-runs the v2 search resource and re-renders the result + // which re-runs the search resource and re-renders the result // sections. If that churned the field — a resource rebuilt per render, or // non-memoized view-models re-mounting an ancestor — Glimmer would // replace the field with a new element and it would lose focus diff --git a/packages/host/tests/integration/components/card-context-search-results-test.gts b/packages/host/tests/integration/components/card-context-search-results-test.gts index c036d0eeadf..09dc75ce8a9 100644 --- a/packages/host/tests/integration/components/card-context-search-results-test.gts +++ b/packages/host/tests/integration/components/card-context-search-results-test.gts @@ -44,8 +44,8 @@ import { import { setupMockMatrix } from '../../helpers/mock-matrix'; import { setupRenderingTest } from '../../helpers/setup'; -// The v2 search surface is the card-facing `@context.searchResultsComponent`, -// codified at the type level: the member exists and carries the v2 component +// The search surface is the card-facing `@context.searchResultsComponent`, +// codified at the type level: the member exists and carries the component // contract. This fails the type-check (and so the suite) if that shape ever // erodes. type Assert = T; @@ -72,7 +72,7 @@ const BOOK_2 = `${testRealmURL}books/2`; // Provides the full converged card `@context` the host hands a card — // instances (`getCard` / `getCards` / `getCardCollection` / `store`) plus the -// v2 `searchResultsComponent` rendering surface — and yields it so a consumer +// `searchResultsComponent` rendering surface — and yields it so a consumer // template can render `` exactly as a card // author would. // `GetCardContextName` is provided too so the nested hydratable rows resolve @@ -142,7 +142,7 @@ module( await getService('realm').login(testRealmURL); }); - test('a card renders the v2 search-entry stream via @context.searchResultsComponent', async function (assert) { + test('a card renders the search-entry stream via @context.searchResultsComponent', async function (assert) { let query: SearchEntryWireQuery = { filter: { 'item.on': bookRef }, realms: [testRealmURL], @@ -188,7 +188,7 @@ module( ); }); - test('the converged @context exposes the v2 + deprecated rendering surfaces and the instances surface', async function (assert) { + test('the converged @context exposes the search-entry + deprecated rendering surfaces and the instances surface', async function (assert) { // The yielded `@context` carries both rendering surfaces and the // instances surface at once — a card author reads them straight off // `@context`. The compile-time witnesses below pin the same shape at the @@ -205,7 +205,7 @@ module( assert .dom('[data-test-has-search-results]') - .exists('the v2 searchResultsComponent is exposed on @context'); + .exists('the searchResultsComponent is exposed on @context'); // The instances surface (getCard / getCards / getCardCollection / // store.search) coexisting on the same `@context` is pinned by the diff --git a/packages/host/tests/integration/components/search-results-test.gts b/packages/host/tests/integration/components/search-results-test.gts index 611f059c05b..26147280b3f 100644 --- a/packages/host/tests/integration/components/search-results-test.gts +++ b/packages/host/tests/integration/components/search-results-test.gts @@ -254,7 +254,7 @@ module('Integration | Component | search-results', function (hooks) { await getService('realm').login(testRealmURL); }); - // Stub the v2 fetch with a manufactured document and short-circuit the + // Stub the fetch with a manufactured document and short-circuit the // stylesheet import for its fake css href. Returns the restore thunk. function stubSearchEntries(doc: SearchEntryResults): () => void { let originalSearchEntries = storeService.searchEntries.bind(storeService); diff --git a/packages/host/tests/integration/components/serialization-test.gts b/packages/host/tests/integration/components/serialization-test.gts index 695e651b79a..dbeb128a208 100644 --- a/packages/host/tests/integration/components/serialization-test.gts +++ b/packages/host/tests/integration/components/serialization-test.gts @@ -4382,7 +4382,7 @@ module('Integration | serialization', function (hooks) { let favoriteSearchURL = new URL(favoriteSearchLink!); assert.strictEqual( favoriteSearchURL.href.split('?')[0], - new URL('_search-v2', testRealmURL).href, + new URL('_search', testRealmURL).href, 'favorite search link points to canonical search endpoint', ); let favoriteQueryParams = parseQueryString( @@ -4411,7 +4411,7 @@ module('Integration | serialization', function (hooks) { let matchesSearchURL = new URL(matchesSearchLink!); assert.strictEqual( matchesSearchURL.href.split('?')[0], - new URL('_search-v2', testRealmURL).href, + new URL('_search', testRealmURL).href, 'matches search link points to canonical search endpoint', ); let matchesQueryParams = parseQueryString( @@ -4454,7 +4454,7 @@ module('Integration | serialization', function (hooks) { let emptyMatchesSearchURL = new URL(emptyMatchesSearchLink!); assert.strictEqual( emptyMatchesSearchURL.href.split('?')[0], - new URL('_search-v2', testRealmURL).href, + new URL('_search', testRealmURL).href, 'emptyMatches search link points to canonical search endpoint', ); diff --git a/packages/host/tests/integration/realm-test.gts b/packages/host/tests/integration/realm-test.gts index 5707ce0a8bd..0b8d48a1dbd 100644 --- a/packages/host/tests/integration/realm-test.gts +++ b/packages/host/tests/integration/realm-test.gts @@ -410,7 +410,7 @@ module('Integration | realm', function (hooks) { { let response = await handle( realm, - new Request(`${testRealmURL}root/_search-v2`, { + new Request(`${testRealmURL}root/_search`, { method: 'QUERY', headers: { Accept: 'application/vnd.card+json', @@ -3015,7 +3015,7 @@ module('Integration | realm', function (hooks) { }); let response = await handle( realm, - new Request(`${testRealmURL}_search-v2`, { + new Request(`${testRealmURL}_search`, { method: 'QUERY', headers: { Accept: 'application/vnd.card+json', @@ -3107,7 +3107,7 @@ module('Integration | realm', function (hooks) { let response = await handle( realm, - new Request(`${testRealmURL}_search-v2`, { + new Request(`${testRealmURL}_search`, { method: 'QUERY', headers: { Accept: 'application/vnd.card+json', @@ -3135,7 +3135,7 @@ module('Integration | realm', function (hooks) { let mangoCreatedAt = await getFileCreatedAt(realm, 'dir/mango.json'); let marikoCreatedAt = await getFileCreatedAt(realm, 'dir/mariko.json'); let vanGoghCreatedAt = await getFileCreatedAt(realm, 'dir/vanGogh.json'); - // v2 `/_search-v2` returns `search-entry` resources in `data` (each just an + // `/_search` returns `search-entry` resources in `data` (each just an // id + refs); the full card resources — the matched results themselves and // their `loadLinks`-expanded relationship targets — travel in `included`. let entries = json.data as any[]; diff --git a/packages/host/tests/integration/resources/search-entries-test.gts b/packages/host/tests/integration/resources/search-entries-test.gts index 1edd21860e0..408e2d97453 100644 --- a/packages/host/tests/integration/resources/search-entries-test.gts +++ b/packages/host/tests/integration/resources/search-entries-test.gts @@ -314,7 +314,7 @@ module('Integration | search-entries resource', function (hooks) { assert.strictEqual( trackedLoads[0], load, - 'the registered load is the in-flight v2 search', + 'the registered load is the in-flight search', ); assert.true( renderStore.loadGeneration > generationBefore, diff --git a/packages/host/tests/integration/search-correlation-id-test.gts b/packages/host/tests/integration/search-correlation-id-test.gts index d0cbeefefbd..8d6e44fd07e 100644 --- a/packages/host/tests/integration/search-correlation-id-test.gts +++ b/packages/host/tests/integration/search-correlation-id-test.gts @@ -27,7 +27,7 @@ import { setupRenderingTest } from '../helpers/setup'; // End-to-end coverage for the search correlation id: the in-realm browser // (the prerendered host SPA) mints `x-boxel-logging-correlation-id` on its -// `_federated-search-v2` fetch, and the realm-server's search path emits a +// `_federated-search` fetch, and the realm-server's search path emits a // `realm:search-timing` line keyed by that same id. This proves the id // threads all the way from the client that originated it through to the // server log a triage would join against. @@ -107,7 +107,7 @@ module('Integration | search correlation id', function (hooks) { // Capture the correlation id the client actually puts on the wire. let sentRequestIds: string[] = []; let spy = async (request: Request) => { - if (new URL(request.url).pathname.endsWith('/_federated-search-v2')) { + if (new URL(request.url).pathname.endsWith('/_federated-search')) { let id = request.headers.get(X_BOXEL_LOGGING_CORRELATION_ID_HEADER); if (id) { sentRequestIds.push(id); @@ -131,7 +131,7 @@ module('Integration | search correlation id', function (hooks) { assert.strictEqual( sentRequestIds.length, 1, - 'the client stamped exactly one correlation id on its _federated-search-v2 fetch', + 'the client stamped exactly one correlation id on its _federated-search fetch', ); let sentId = sentRequestIds[0]; assert.ok( @@ -169,7 +169,7 @@ module('Integration | search correlation id', function (hooks) { let sawHeader = false; let spy = async (request: Request) => { if ( - new URL(request.url).pathname.endsWith('/_federated-search-v2') && + new URL(request.url).pathname.endsWith('/_federated-search') && request.headers.get(X_BOXEL_LOGGING_CORRELATION_ID_HEADER) ) { sawHeader = true; diff --git a/packages/host/tests/integration/store-test.gts b/packages/host/tests/integration/store-test.gts index 86cd67ec76d..e5fd238da1b 100644 --- a/packages/host/tests/integration/store-test.gts +++ b/packages/host/tests/integration/store-test.gts @@ -1996,7 +1996,7 @@ module('Integration | Store', function (hooks) { // correctly-loaded full one. The route is overridden to return such a doc. function overrideSearchWith(doc: unknown) { registerRealmServerRoute({ - path: '/_federated-search-v2', + path: '/_federated-search', handler: async () => new Response(JSON.stringify(doc), { status: 200, @@ -2005,7 +2005,7 @@ module('Integration | Store', function (hooks) { }); } - // A v2 search-entry that carries only an `html` rendering — no `item`. + // A search-entry that carries only an `html` rendering — no `item`. function htmlOnlyEntryDoc(id: string, opts?: { isFileMeta?: boolean }) { let htmlId = opts?.isFileMeta ? `${id}#fitted` diff --git a/packages/realm-server/handlers/handle-search-v2.ts b/packages/realm-server/handlers/handle-search.ts similarity index 94% rename from packages/realm-server/handlers/handle-search-v2.ts rename to packages/realm-server/handlers/handle-search.ts index a1662872bbb..6e873714643 100644 --- a/packages/realm-server/handlers/handle-search-v2.ts +++ b/packages/realm-server/handlers/handle-search.ts @@ -35,17 +35,15 @@ import { sanitizePrerenderJobId, } from '../prerender/prerender-constants.ts'; -// The v2 federated search: the search-entry wire model over every requested +// The federated search: the search-entry wire model over every requested // realm. Parses the search-entry-rooted query (the `item.` membership query, // the `htmlQuery` binding, the sparse fieldset), fans out to each realm's // `searchEntries`, and merges the per-realm documents (`included` deduped by -// `(type, id)`). Cache + ETag ride the same job-scoped protocol as the -// existing federated handlers; the inner key folds every request member that -// changes the body — the membership query plus the applied htmlQuery, the -// fieldset, and any `cardUrls` subset — so two v2 requests differing on any -// of them get distinct entries + ETags, and the v2 keys can't collide with -// the other endpoints' (whose key opts carry different members). -export default function handleSearchV2(opts: { +// `(type, id)`). Cache + ETag ride the job-scoped search-cache protocol; the +// inner key folds every request member that changes the body — the membership +// query plus the applied htmlQuery, the fieldset, and any `cardUrls` subset — +// so two requests differing on any of them get distinct entries + ETags. +export default function handleSearch(opts: { reconciler: RealmRegistryReconciler; searchCache?: JobScopedSearchCache; }): (ctxt: Koa.Context) => Promise { @@ -185,7 +183,7 @@ export default function handleSearchV2(opts: { }; } -// The job-scoped cache + ETag/304 protocol for the federated v2 search +// The job-scoped cache + ETag/304 protocol for the federated search // handler. Caching is gated on: // (a) `x-boxel-job-id` present and well-formed — only the indexer worker // stamps it; live user / API callers never carry it and so always see diff --git a/packages/realm-server/middleware/index.ts b/packages/realm-server/middleware/index.ts index e3f5440eee7..f7733b400bf 100644 --- a/packages/realm-server/middleware/index.ts +++ b/packages/realm-server/middleware/index.ts @@ -25,10 +25,10 @@ import { decrementSearchInFlight, } from '../search-inflight.ts'; -// Matches the realm-server's search endpoints (`/_search-v2`, -// `/_federated-search-v2`) so the request middleware can track how +// Matches the realm-server's search endpoints (`/_search`, +// `/_federated-search`) so the request middleware can track how // many searches are in flight for the health sampler. -const SEARCH_PATH_PATTERN = /(^|\/)_(federated-)?search-v2$/; +const SEARCH_PATH_PATTERN = /(^|\/)_(federated-)?search$/; const REQUEST_BODY_STATE = 'requestBody'; diff --git a/packages/realm-server/routes.ts b/packages/realm-server/routes.ts index 44b4db9fe1c..5d650981049 100644 --- a/packages/realm-server/routes.ts +++ b/packages/realm-server/routes.ts @@ -44,7 +44,7 @@ import handleClaimBoxelDomainRequest from './handlers/handle-claim-boxel-domain. import handleDeleteBoxelClaimedDomainRequest from './handlers/handle-delete-boxel-claimed-domain.ts'; import handleUnlistedRealmPathRequest from './handlers/handle-unlisted-realm-path.ts'; import handlePrerenderProxy from './handlers/handle-prerender-proxy.ts'; -import handleSearchV2 from './handlers/handle-search-v2.ts'; +import handleSearch from './handlers/handle-search.ts'; import type { JobScopedSearchCache } from './job-scoped-search-cache.ts'; import handleRealmInfo from './handlers/handle-realm-info.ts'; import handleFederatedTypes from './handlers/handle-federated-types.ts'; @@ -201,9 +201,9 @@ export function createRoutes(args: CreateRoutesArgs) { }), ); router.all( - '/_federated-search-v2', + '/_federated-search', multiRealmAuthorization(args), - handleSearchV2({ reconciler: args.reconciler, searchCache }), + handleSearch({ reconciler: args.reconciler, searchCache }), ); router.all( '/_federated-info', diff --git a/packages/realm-server/scripts/bench-realm/bench.ts b/packages/realm-server/scripts/bench-realm/bench.ts index 3005551bac2..6d8ad9794f4 100644 --- a/packages/realm-server/scripts/bench-realm/bench.ts +++ b/packages/realm-server/scripts/bench-realm/bench.ts @@ -172,7 +172,7 @@ function searchRequest( bearerToken: string, query: unknown, ): Request { - let url = new URL('_search-v2', realmURL); + let url = new URL('_search', realmURL); return new Request(url, { method: 'QUERY', headers: { @@ -180,9 +180,9 @@ function searchRequest( 'Content-Type': 'application/json', Authorization: `Bearer ${bearerToken}`, }, - // The v2 endpoint takes a search-entry-rooted query; benchmark the + // The search endpoint takes a search-entry-rooted query; benchmark the // data-only fieldset (one full `item` per result), the closest analogue - // to what the legacy `/_search` returned. + // to the legacy live-card search response. body: JSON.stringify( searchEntryWireQueryFromQuery(query as Query, { fields: ['item'] }), ), diff --git a/packages/realm-server/scripts/bench-realm/fixtures/realm-snapshot/StickyNote/todo-green.json b/packages/realm-server/scripts/bench-realm/fixtures/realm-snapshot/StickyNote/todo-green.json index d18ba4a3b60..8938bf6b8ad 100644 --- a/packages/realm-server/scripts/bench-realm/fixtures/realm-snapshot/StickyNote/todo-green.json +++ b/packages/realm-server/scripts/bench-realm/fixtures/realm-snapshot/StickyNote/todo-green.json @@ -3,7 +3,7 @@ "type": "card", "attributes": { "title": "Try with new API", - "body": "Wire up the search bar against the v2 endpoint and benchmark p95 latency.", + "body": "Wire up the search bar against the search endpoint and benchmark p95 latency.", "color": "green", "x": 120, "y": 320, @@ -21,4 +21,4 @@ } } } -} \ No newline at end of file +} diff --git a/packages/realm-server/scripts/codemod/context-search/transform.ts b/packages/realm-server/scripts/codemod/context-search/transform.ts index 495b889dd40..798ac96da9b 100644 --- a/packages/realm-server/scripts/codemod/context-search/transform.ts +++ b/packages/realm-server/scripts/codemod/context-search/transform.ts @@ -1,14 +1,14 @@ // Codemod that migrates first-party card source off the deprecated -// `@context.prerenderedCardSearchComponent` and onto the v2 +// `@context.prerenderedCardSearchComponent` and onto the // `@context.searchResultsComponent` surface. // // A usage migrates when its `@query`/`@realms`/`@cardUrls` are simple paths. The // legacy args fold into a `search-entry`-rooted query built by a generated -// getter (wrapping the v1 `Query` through `searchEntryWireQueryFromQuery`); a +// getter (wrapping the deprecated `Query` through `searchEntryWireQueryFromQuery`); a // static or dynamic `@format` binds through the query's `htmlQuery` (a dynamic // format guarded by `isValidPrerenderedHtmlFormat`, mirroring base CardsGrid). // -// A `<:response>` body that only iterates the result list and reads v2-native +// A `<:response>` body that only iterates the result list and reads search-entry-native // per-row fields (`url`/`isError`/`component`) is rewritten minimally. A body // that yields the row onward, reaches into legacy-only fields (`cardType`/…), or // hands the whole list to other markup (a child component, …) is migrated by @@ -64,10 +64,10 @@ const KNOWN_ARGS = new Set([ '@isLive', ]); -// Per-item fields that map cleanly onto the v2 `entry`. `url` becomes `id`; +// Per-item fields that map cleanly onto the search-entry `entry`. `url` becomes `id`; // `isError` and the per-item `component` carry over unchanged. Anything else // (`cardType`/`iconHtml`/`hasHtml`/`realmUrl`/`usedRenderType`) lives under -// `entry.html` in v2 and has no safe mechanical rewrite. +// `entry.html` in the search-entry shape and has no safe mechanical rewrite. const ITEM_FIELD_RENAMES: Record = { url: 'id' }; const ALLOWED_ITEM_FIELDS = new Set(['url', 'isError', 'component']); @@ -239,7 +239,7 @@ function transformOneTemplate( // Re-point each bound `(component @context.…)` whose invocations all migrated. // A binding with any failed invocation is left on the old member, so the // file-level guard in `transformContextSearch` discards the whole module rather - // than ship one with some invocations on v2 and others stranded on v1. + // than ship one with some invocations on the search-entry member and others stranded on the deprecated one. for (let [subExpr, allOk] of bindingAllOk) { if (allOk) { subExpr.params[0] = b.path(NEW_PATH); @@ -247,7 +247,7 @@ function transformOneTemplate( } } // Rewrite `{{#if @context.…}}` / `{{#unless @context.…}}` availability guards - // (cards defensively gate the usage on the component being provided) to the v2 + // (cards defensively gate the usage on the component being provided) to the search-entry // member, so the guard tracks the component actually rendered. Same clean // param-replacement as the bindings. This is unconditional, but harmless on a // module that doesn't fully migrate: its stranded element/binding keeps the old @@ -276,7 +276,7 @@ interface Invocation { // The angle-bracket invocation whose args/blocks get reshaped. element: any; // For the `{{#let (component @context.…) as |X|}}` form, the SubExpression - // whose component path must move to the v2 member. + // whose component path must move to the search-entry member. subExpr: any | null; } @@ -321,10 +321,10 @@ function findInvocations(ast: any): Invocation[] { // invoked any number of times within the block. Each `` is an independent // usage — its own query/format/blocks — so emit one invocation per element, // all sharing the binding's SubExpression (the `(component …)` whose path the - // v2 member moves onto; re-pointing it once per invocation is idempotent). If + // search-entry member moves onto; re-pointing it once per invocation is idempotent). If // any one of them can't be migrated, the file-level guard in // `transformContextSearch` discards the whole module, so a bound component is - // never left half-migrated (some invocations on v2, some stranded on v1). + // never left half-migrated (some invocations on the search-entry member, some stranded on the deprecated one). for (let element of elements) { invocations.push({ element, subExpr: binding.subExpr }); } @@ -428,7 +428,7 @@ function tryTransformInvocation( // Its body (if any) references nothing from the list, so it migrates verbatim // with no adapter binding. let responseParam: string | undefined = responseParams[0]; - // `<:meta as |m|>` yields the same `QueryResultsMeta` v2 exposes as + // `<:meta as |m|>` yields the same `QueryResultsMeta` search-entry exposes as // `results.meta`, so it migrates to a `{{#let results.meta as |m|}}` wrapper. // Support zero or one block param; anything else is an unrecognized shape. let metaParam: string | undefined; @@ -441,7 +441,7 @@ function tryTransformInvocation( } // Decide how to migrate the <:response> body. A single direct - // `{{#each }}` whose item only touches v2-native fields + // `{{#each }}` whose item only touches search-entry-native fields // (`url`/`isError`/`component`) is rewritten minimally. Anything else — a body // that yields the row onward, reaches into legacy-only fields, or hands the // whole list to other markup (a child component, etc.) — is migrated by @@ -501,7 +501,7 @@ function tryTransformInvocation( usesArrayShim: useShim, }); - // Move the component reference to the v2 member. A direct `<@context.…>` usage + // Move the component reference to the search-entry member. A direct `<@context.…>` usage // re-points its own tag here. A bound usage (`{{#let (component @context.…) as // |X|}}`) keeps its local tag; its binding's `(component …)` is re-pointed by // the caller, and only once EVERY invocation of that binding migrated — so a @@ -690,7 +690,7 @@ function ensureRuntimeCommonImport( } // Insert the module-local legacy-shape array adapter once, after the imports. -// It maps each v2 `entry` to the legacy `PrerenderedCardLike` field names so a +// It maps each search-entry `entry` to the legacy `PrerenderedCardLike` field names so a // migrated body (and the components it hands rows to) keeps working unchanged. function insertArrayShim(ast: any, filename: string): void { let body = ast.program.body; diff --git a/packages/realm-server/tests/card-endpoints-test.ts b/packages/realm-server/tests/card-endpoints-test.ts index c1a51f46c7a..2601bdadaef 100644 --- a/packages/realm-server/tests/card-endpoints-test.ts +++ b/packages/realm-server/tests/card-endpoints-test.ts @@ -2453,7 +2453,7 @@ module(basename(import.meta.filename), function () { ); response = await request - .post('/_search-v2') + .post('/_search') .set('Accept', 'application/vnd.card+json') .set('X-HTTP-Method-Override', 'QUERY') .send( @@ -4619,7 +4619,7 @@ module(basename(import.meta.filename), function () { let favoriteSearchURL = new URL(favoriteSearchLink); assert.strictEqual( favoriteSearchURL.href.split('?')[0], - new URL('_search-v2', consumerRealmURL).href, + new URL('_search', consumerRealmURL).href, 'favorite relationship search link targets consumer realm', ); let favoriteQueryParams = parseSearchQuery(favoriteSearchURL); @@ -4701,7 +4701,7 @@ module(basename(import.meta.filename), function () { ); assert.strictEqual( matchesSearchURL.href.split('?')[0], - new URL('_search-v2', providerRealmURL).href, + new URL('_search', providerRealmURL).href, 'matches relationship search link targets provider realm', ); let matchesQueryParams = parseSearchQuery(matchesSearchURL); @@ -4754,7 +4754,7 @@ module(basename(import.meta.filename), function () { ); assert.strictEqual( failingSearchURL.href.split('?')[0], - new URL('_search-v2', UNREACHABLE_REALM_URL).href, + new URL('_search', UNREACHABLE_REALM_URL).href, 'failingMatches search link targets unreachable realm', ); let failingQueryParams = parseSearchQuery(failingSearchURL); diff --git a/packages/realm-server/tests/codemod-context-search-test.ts b/packages/realm-server/tests/codemod-context-search-test.ts index f6b179906d2..4abd222946d 100644 --- a/packages/realm-server/tests/codemod-context-search-test.ts +++ b/packages/realm-server/tests/codemod-context-search-test.ts @@ -143,7 +143,7 @@ export class AppGrid extends GlimmerComponent { `; // The body reaches into per-card fields (`cardType`, `iconHtml`) that live under -// `entry.html` in the v2 shape — no safe mechanical mapping, so it's reported. +// `entry.html` in the search-entry shape — no safe mechanical mapping, so it's reported. const COMPLEX_PER_CARD_FIELDS = `import GlimmerComponent from '@glimmer/component'; import { type CardContext } from 'https://cardstack.com/base/card-api'; @@ -266,7 +266,7 @@ export class Plain extends GlimmerComponent { // One component bound once and invoked several times — each invocation has its // own query/format and blocks. Every invocation must migrate, and the shared -// `(component @context.…)` binding must move to the v2 member exactly once. +// `(component @context.…)` binding must move to the search-entry member exactly once. const MULTI_INVOCATION = `import GlimmerComponent from '@glimmer/component'; import { type CardContext } from 'https://cardstack.com/base/card-api'; @@ -296,7 +296,7 @@ export class Board extends GlimmerComponent { } `; -// A usage with a <:meta> block — yields the same QueryResultsMeta v2 exposes as +// A usage with a <:meta> block — yields the same QueryResultsMeta search-entry exposes as // results.meta, so it migrates to a {{#let results.meta as |m|}} wrapper. const META_BLOCK = `import GlimmerComponent from '@glimmer/component'; @@ -348,7 +348,7 @@ export class Tile extends GlimmerComponent { `; // A usage gated behind an `{{#if @context.prerenderedCardSearchComponent}}` -// availability guard. The guard must move to the v2 member alongside the usage. +// availability guard. The guard must move to the search-entry member alongside the usage. const GUARDED = `import GlimmerComponent from '@glimmer/component'; import { type CardContext } from 'https://cardstack.com/base/card-api'; @@ -377,7 +377,7 @@ export class Guarded extends GlimmerComponent { // One invocation the codemod can migrate alongside one it can't (an unsupported // :error block) — both on the same bound component. A half-migration would // rewrite the first and re-point the shared binding, stranding the second next -// to a v2 component it calls with the v1 block API. The whole file must be left +// to a search-entry component it calls with the deprecated block API. The whole file must be left // untouched instead. const PARTIAL_MULTI_USAGE = `import GlimmerComponent from '@glimmer/component'; @@ -440,7 +440,7 @@ module(basename(import.meta.filename), function () { }); assert.strictEqual(status, 'transformed', reasons.join('; ')); - // component reference moves to the v2 surface + // component reference moves to the search-entry surface assert.true( output.includes('@context.searchResultsComponent'), 'invokes @context.searchResultsComponent', @@ -450,7 +450,7 @@ module(basename(import.meta.filename), function () { 'no reference to the deprecated component remains', ); - // a typed getter wraps the incoming v1 query through the sanctioned adapter + // a typed getter wraps the incoming deprecated query through the sanctioned adapter assert.true( /get searchResultsQuery\(\)\s*:\s*SearchEntryWireQuery/.test(output), 'adds a typed searchResultsQuery getter', @@ -461,7 +461,7 @@ module(basename(import.meta.filename), function () { ); assert.true( output.includes('realms: this.args.realms'), - 'carries @realms into the v2 query', + 'carries @realms into the search-entry query', ); assert.true( /import\s*\{[^}]*searchEntryWireQueryFromQuery[^}]*\}\s*from\s*['"]@cardstack\/runtime-common['"]/.test( @@ -493,7 +493,7 @@ module(basename(import.meta.filename), function () { ':response becomes {{#each results.entries}}', ); // the author's loop variable is preserved (minimal churn); only the result - // array source and the per-item `.url` field are remapped to the v2 shape + // array source and the per-item `.url` field are remapped to the search-entry shape assert.true( output.includes(' invocation are preserved', @@ -580,7 +580,7 @@ module(basename(import.meta.filename), function () { ); assert.true( output.includes('@context.searchResultsComponent'), - 'uses the v2 member', + 'uses the search-entry member', ); assert.true( output.includes('searchEntriesToPrerenderedCards'), @@ -735,7 +735,7 @@ module(basename(import.meta.filename), function () { assert.false(/<:response/.test(output), 'no :response block remains'); }); - test('rewrites an {{#if @context.…}} availability guard to the v2 member', function (assert) { + test('rewrites an {{#if @context.…}} availability guard to the search-entry member', function (assert) { let { status, output, reasons } = transformContextSearch(GUARDED, { filename: 'guarded.gts', }); @@ -746,7 +746,7 @@ module(basename(import.meta.filename), function () { ); assert.true( /\{\{#if\s+@context\.searchResultsComponent\}\}/.test(output), - `the guard tracks the v2 member: ${output}`, + `the guard tracks the search-entry member: ${output}`, ); }); diff --git a/packages/realm-server/tests/helpers/index.ts b/packages/realm-server/tests/helpers/index.ts index 5143c1e81bd..11dd26e945a 100644 --- a/packages/realm-server/tests/helpers/index.ts +++ b/packages/realm-server/tests/helpers/index.ts @@ -112,7 +112,7 @@ function environmentPortOffset(): number { /** Return a test port, shifted by a per-environment offset when needed. */ // Test-only: fetch the card/file-meta serializations matching a card-rooted -// `Query` through the v2 search-entry engine, returning them in the +// `Query` through the search-entry engine, returning them in the // `{ data, meta }` collection shape index assertions read. Requests the // data-only fieldset (one full `item` per entry). export async function searchCardsForTest( diff --git a/packages/realm-server/tests/helpers/prerender-page-patches.ts b/packages/realm-server/tests/helpers/prerender-page-patches.ts index fb2d0cc3e94..33da6574bbd 100644 --- a/packages/realm-server/tests/helpers/prerender-page-patches.ts +++ b/packages/realm-server/tests/helpers/prerender-page-patches.ts @@ -111,7 +111,7 @@ export function installDelayedRuntimeRealmSearchPatch(delayMs: number): { // Server-side deterministic delay: // This makes query timing explicit/reproducible so tests can assert that // prerender waited for query resolution instead of "winning a race" by - // chance. The prerender tab's host reaches the realm through the v2 + // chance. The prerender tab's host reaches the realm through the // federated search, so the hook point is `searchEntries`. let originalSearchEntries = RuntimeRealm.prototype.searchEntries; let delayedSearchRequestCount = 0; @@ -141,7 +141,7 @@ export function installDelayedRuntimeRealmSearchPatch(delayMs: number): { // The patch has been torn down — the test no longer cares about this // search and the realm fixture (including its pg pool) may already be // closed. Return an empty collection rather than reaching into a - // potentially-dead adapter; the caller (handle-search-v2/ + // potentially-dead adapter; the caller (handle-search/ // searchEntryRealms) will discard the result. return { data: [], @@ -192,10 +192,7 @@ export function installSearchRequestObserverPatch(): { let url = request.url?.(); if ( !url || - (!url.endsWith('/_federated-search') && - !url.endsWith('/_federated-search-v2') && - !url.endsWith('/_search') && - !url.endsWith('/_search-v2')) + (!url.endsWith('/_federated-search') && !url.endsWith('/_search')) ) { return; } diff --git a/packages/realm-server/tests/index.ts b/packages/realm-server/tests/index.ts index e80917a89c5..0bb97ffdbc4 100644 --- a/packages/realm-server/tests/index.ts +++ b/packages/realm-server/tests/index.ts @@ -288,7 +288,7 @@ const ALL_TEST_FILES: string[] = [ './realm-endpoints/cancel-indexing-job-test', './realm-endpoints/publishability-test', './realm-endpoints/reindex-test', - './realm-endpoints/search-v2-test', + './realm-endpoints/search-test', './realm-endpoints/user-test', './server-endpoints/authentication-test', './server-endpoints/bot-commands-test', @@ -302,7 +302,7 @@ const ALL_TEST_FILES: string[] = [ './server-endpoints/realm-lifecycle-test', './server-endpoints/run-command-endpoint-test', './server-endpoints/screenshot-card-endpoint-test', - './server-endpoints/search-v2-test', + './server-endpoints/search-test', './serve-index-test', './server-config-test', './server-endpoints/info-test', @@ -330,7 +330,7 @@ const ALL_TEST_FILES: string[] = [ './matches-filter-integration-test', './eq-containment-integration-test', './search-resource-helpers-test', - './pre-v2-search-surface-removed-test', + './superseded-search-surface-removed-test', './search-entry-test', './search-entries-engine-test', './coerce-error-message-test', diff --git a/packages/realm-server/tests/parse-search-url-test.ts b/packages/realm-server/tests/parse-search-url-test.ts index b8f8456206a..fc1fd4e65f0 100644 --- a/packages/realm-server/tests/parse-search-url-test.ts +++ b/packages/realm-server/tests/parse-search-url-test.ts @@ -15,7 +15,7 @@ module(basename(import.meta.filename), function () { filter: { type: { module: rri(`${realm}person`), name: 'Person' } }, }; - test('recovers the realm from a built v2 search URL', async function (assert) { + test('recovers the realm from a built search URL', async function (assert) { let { realm: recovered, query: recoveredQuery } = parseSearchURL( buildQuerySearchURL(realm, query), ); @@ -27,32 +27,18 @@ module(basename(import.meta.filename), function () { ); }); - test('strips the _search-v2 segment without leaving a double slash', async function (assert) { - let { realm: recovered } = parseSearchURL( - new URL('_search-v2', realm).href, - ); - assert.strictEqual(recovered.href, realm); - }); - - test('strips a trailing-slash _search-v2 segment without leaving a double slash', async function (assert) { - let { realm: recovered } = parseSearchURL(`${realm}_search-v2/`); - assert.strictEqual(recovered.href, realm); - }); - - test('strips the legacy _search segment', async function (assert) { + test('strips the _search segment without leaving a double slash', async function (assert) { let { realm: recovered } = parseSearchURL(new URL('_search', realm).href); assert.strictEqual(recovered.href, realm); }); - test('strips a trailing-slash legacy _search segment', async function (assert) { + test('strips a trailing-slash _search segment without leaving a double slash', async function (assert) { let { realm: recovered } = parseSearchURL(`${realm}_search/`); assert.strictEqual(recovered.href, realm); }); test('recovers a root realm', async function (assert) { - let { realm: recovered } = parseSearchURL( - 'http://example.test/_search-v2', - ); + let { realm: recovered } = parseSearchURL('http://example.test/_search'); assert.strictEqual(recovered.href, 'http://example.test/'); }); }); diff --git a/packages/realm-server/tests/prerendering-test.ts b/packages/realm-server/tests/prerendering-test.ts index 7cc0db7ac22..eb0d61be854 100644 --- a/packages/realm-server/tests/prerendering-test.ts +++ b/packages/realm-server/tests/prerendering-test.ts @@ -2658,17 +2658,17 @@ module(basename(import.meta.filename), function () { [testUserId]: ['read', 'write', 'realm-owner'], }, fileSystem: { - 'v2-search.gts': ` + 'card-search.gts': ` import { CardDef, Component, field, contains, StringField } from 'https://cardstack.com/base/card-api'; - export class V2SearchResult extends CardDef { - static displayName = 'V2 Search Result'; + export class CardSearchResult extends CardDef { + static displayName = 'Card Search Result'; @field label = contains(StringField); static fitted = class extends Component { }; @@ -2676,15 +2676,15 @@ module(basename(import.meta.filename), function () { static isolated = this.fitted; } - export class V2SearchInner extends CardDef { - static displayName = 'V2 Search Inner'; + export class CardSearchInner extends CardDef { + static displayName = 'Card Search Inner'; static isolated = class extends Component { get query() { return { filter: { 'item.on': { - module: new URL('./v2-search', import.meta.url).href, - name: 'V2SearchResult', + module: new URL('./card-search', import.meta.url).href, + name: 'CardSearchResult', }, }, realms: [new URL('./', import.meta.url).href], @@ -2692,35 +2692,35 @@ module(basename(import.meta.filename), function () { } }; } `, - 'v2-search-inner.json': { + 'card-search-inner.json': { data: { meta: { adoptsFrom: { - module: rri('./v2-search'), - name: 'V2SearchInner', + module: rri('./card-search'), + name: 'CardSearchInner', }, }, }, }, - 'v2-search-result-1.json': { + 'card-search-result-1.json': { data: { attributes: { - label: 'V2_RESULT_VALUE', + label: 'CARD_RESULT_VALUE', }, meta: { adoptsFrom: { - module: rri('./v2-search'), - name: 'V2SearchResult', + module: rri('./card-search'), + name: 'CardSearchResult', }, }, }, @@ -2785,27 +2785,27 @@ module(basename(import.meta.filename), function () { }, }); - test('v2 search prerenders the live rendered result, beating stale indexed HTML and keeping its unique scoped CSS', async function (assert) { - const cardURL = `${realmURL}v2-search-inner`; - const sentinel = 'SENTINEL_STALE_V2_HTML'; + test('search prerenders the live rendered result, beating stale indexed HTML and keeping its unique scoped CSS', async function (assert) { + const cardURL = `${realmURL}card-search-inner`; + const sentinel = 'SENTINEL_STALE_CARD_HTML'; let realmServerPatch = installRealmServerAssertOwnRealmServerBypassPatch(); try { let indexedRows = await dbAdapter.execute( `SELECT url FROM boxel_index WHERE url LIKE $1 ORDER BY url`, - { bind: [`${realmURL}%v2-search%`] }, + { bind: [`${realmURL}%card-search%`] }, ); assert.ok( indexedRows.length > 0, - `expected indexed rows for v2-search fixtures, got: ${JSON.stringify(indexedRows)}`, + `expected indexed rows for card-search fixtures, got: ${JSON.stringify(indexedRows)}`, ); // Plant a stale indexed rendering for the result so a live win is // observable: the prerendered search must re-render the result from // its card+source, not echo this indexed HTML. await overrideIndexedIsolatedHTML( - `${realmURL}v2-search-result-1`, + `${realmURL}card-search-result-1`, `
${sentinel}
`, ); @@ -2823,7 +2823,7 @@ module(basename(import.meta.filename), function () { ); assert.ok( - isolatedHTML.includes('V2_RESULT_VALUE'), + isolatedHTML.includes('CARD_RESULT_VALUE'), `isolated html includes the live result value: ${isolatedHTML}`, ); assert.notOk( @@ -2831,11 +2831,11 @@ module(basename(import.meta.filename), function () { `isolated html does not include the stale indexed sentinel: ${isolatedHTML}`, ); assert.ok( - isolatedHTML.includes('v2-search-result-sentinel'), + isolatedHTML.includes('card-search-result-sentinel'), `isolated html includes the result's unique css class: ${isolatedHTML}`, ); assert.ok( - /v2-search-result-sentinel[^>]*data-scopedcss-[a-f0-9]{10}-[a-f0-9]{10}/.test( + /card-search-result-sentinel[^>]*data-scopedcss-[a-f0-9]{10}-[a-f0-9]{10}/.test( isolatedHTML, ), `isolated html keeps the scoped css marker on the live result: ${isolatedHTML}`, @@ -2845,7 +2845,7 @@ module(basename(import.meta.filename), function () { } }); - test('v2 search prerenders a FileDef result and renders live over stale indexed HTML', async function (assert) { + test('search prerenders a FileDef result and renders live over stale indexed HTML', async function (assert) { const cardURL = `${realmURL}file-search-inner`; const sentinel = 'SENTINEL_STALE_FILE_HTML'; let realmServerPatch = @@ -2897,14 +2897,14 @@ module(basename(import.meta.filename), function () { } }); - test('a card rendering the v2 @context.searchResultsComponent prerenders with its results present, not an empty list', async function (assert) { - // The v2 search resource registers its in-flight fetch with the render + test('a card rendering the @context.searchResultsComponent prerenders with its results present, not an empty list', async function (assert) { + // The search resource registers its in-flight fetch with the render // store's readiness signal, so the /render settle loop waits for results // before HTML capture. Without that wiring the search resolves only // after capture and the prerendered html shows an empty result list — // the bug this test guards. Driven through the real prerenderer (the // host test harness can't cover the /render route). - const cardURL = `${realmURL}v2-search-inner`; + const cardURL = `${realmURL}card-search-inner`; let realmServerPatch = installRealmServerAssertOwnRealmServerBypassPatch(); @@ -2926,16 +2926,16 @@ module(basename(import.meta.filename), function () { ); assert.ok( - isolatedHTML.includes('data-test-v2-search-host-ran'), - `the v2 host template ran: ${isolatedHTML}`, + isolatedHTML.includes('data-test-card-search-host-ran'), + `the host template ran: ${isolatedHTML}`, ); assert.notOk( - isolatedHTML.includes('data-test-v2-search-component-missing'), - 'the v2 searchResultsComponent is provided in the render context', + isolatedHTML.includes('data-test-card-search-component-missing'), + 'the searchResultsComponent is provided in the render context', ); assert.ok( - isolatedHTML.includes('V2_RESULT_VALUE'), - `prerendered html includes the v2 search result — the /render settle loop waited for the search before HTML capture: ${isolatedHTML}`, + isolatedHTML.includes('CARD_RESULT_VALUE'), + `prerendered html includes the search result — the /render settle loop waited for the search before HTML capture: ${isolatedHTML}`, ); } finally { await realmServerPatch.restore(); diff --git a/packages/realm-server/tests/realm-endpoints/search-v2-test.ts b/packages/realm-server/tests/realm-endpoints/search-test.ts similarity index 99% rename from packages/realm-server/tests/realm-endpoints/search-v2-test.ts rename to packages/realm-server/tests/realm-endpoints/search-test.ts index 21b42b030dd..b7bef15b729 100644 --- a/packages/realm-server/tests/realm-endpoints/search-v2-test.ts +++ b/packages/realm-server/tests/realm-endpoints/search-test.ts @@ -11,7 +11,7 @@ import { import '@cardstack/runtime-common/helpers/code-equality-assertion'; module(`realm-endpoints/${basename(import.meta.filename)}`, function () { - module('Realm-specific Endpoints | _search-v2', function (hooks) { + module('Realm-specific Endpoints | _search', function (hooks) { let testRealm: Realm; let dbAdapter: PgAdapter; let request: SuperTest; @@ -31,7 +31,7 @@ module(`realm-endpoints/${basename(import.meta.filename)}`, function () { request = args.request; let realmURL = new URL(testRealm.url); realmHref = realmURL.href; - searchPath = `${realmURL.pathname.replace(/\/$/, '')}/_search-v2`; + searchPath = `${realmURL.pathname.replace(/\/$/, '')}/_search`; personKey = `${realmHref}person/Person`; johnId = `${realmHref}john`; janeId = `${realmHref}jane`; diff --git a/packages/realm-server/tests/server-endpoints/realm-lifecycle-test.ts b/packages/realm-server/tests/server-endpoints/realm-lifecycle-test.ts index da81f849de7..51edfc73d72 100644 --- a/packages/realm-server/tests/server-endpoints/realm-lifecycle-test.ts +++ b/packages/realm-server/tests/server-endpoints/realm-lifecycle-test.ts @@ -216,7 +216,7 @@ module(`server-endpoints/${basename(import.meta.filename)}`, function () { { // owner can search in the realm let response = await context.request - .post(`${new URL(realmURL).pathname}_search-v2`) + .post(`${new URL(realmURL).pathname}_search`) .set('Accept', 'application/vnd.card+json') .set('X-HTTP-Method-Override', 'QUERY') .set( @@ -284,7 +284,7 @@ module(`server-endpoints/${basename(import.meta.filename)}`, function () { { let response = await context.request - .post(`${new URL(realmURL).pathname}_search-v2`) + .post(`${new URL(realmURL).pathname}_search`) .set('Accept', 'application/vnd.card+json') .set('X-HTTP-Method-Override', 'QUERY') .set( diff --git a/packages/realm-server/tests/server-endpoints/search-v2-test.ts b/packages/realm-server/tests/server-endpoints/search-test.ts similarity index 97% rename from packages/realm-server/tests/server-endpoints/search-v2-test.ts rename to packages/realm-server/tests/server-endpoints/search-test.ts index 7fd5a09e98f..3772996b659 100644 --- a/packages/realm-server/tests/server-endpoints/search-v2-test.ts +++ b/packages/realm-server/tests/server-endpoints/search-test.ts @@ -25,7 +25,7 @@ import { createJWT as createRealmServerJWT } from '../../utils/jwt.ts'; import type { RealmHttpServer as Server } from '../../server.ts'; module(`server-endpoints/${basename(import.meta.filename)}`, function (_hooks) { - module('Realm Server Endpoints | /_federated-search-v2', function (hooks) { + module('Realm Server Endpoints | /_federated-search', function (hooks) { let testRealm: Realm; let secondaryRealm: Realm; let request: SuperTest; @@ -164,7 +164,7 @@ module(`server-endpoints/${basename(import.meta.filename)}`, function (_hooks) { } function postSearch(body: Record) { - let searchURL = new URL('/_federated-search-v2', testRealm.url); + let searchURL = new URL('/_federated-search', testRealm.url); return request .post(`${searchURL.pathname}${searchURL.search}`) .set('Accept', 'application/vnd.card+json') @@ -174,7 +174,7 @@ module(`server-endpoints/${basename(import.meta.filename)}`, function (_hooks) { .send(body); } - test('QUERY /_federated-search-v2 federates search-entry results across realms', async function (assert) { + test('QUERY /_federated-search federates search-entry results across realms', async function (assert) { let response = await postSearch({ filter: personFilter(), realms: [testRealm.url, secondaryRealm.url], diff --git a/packages/realm-server/tests/pre-v2-search-surface-removed-test.ts b/packages/realm-server/tests/superseded-search-surface-removed-test.ts similarity index 84% rename from packages/realm-server/tests/pre-v2-search-surface-removed-test.ts rename to packages/realm-server/tests/superseded-search-surface-removed-test.ts index dedf08289af..45c4538e91b 100644 --- a/packages/realm-server/tests/pre-v2-search-surface-removed-test.ts +++ b/packages/realm-server/tests/superseded-search-surface-removed-test.ts @@ -4,9 +4,9 @@ import { basename } from 'path'; import { readFileSync } from 'fs'; import { resolve } from 'path'; -// A grep-style guard that the superseded pre-v2 search scaffolding stays +// A grep-style guard that the superseded search scaffolding stays // removed. The platform's search relationships live on `search-entry`, so the -// pre-v2 in-place additions to the card resource and the old result mappers must +// superseded in-place additions to the card resource and the old result mappers must // not reappear. Each entry asserts a removed identifier is absent from the // source file that used to define it. @@ -25,7 +25,7 @@ const GUARDS: { file: string; forbidden: string[] }[] = [ forbidden: ['RenderedHtmlResource', "'rendered-html'", 'identityOnly'], }, { - // The pre-v2 result-mapper builders. + // The superseded result-mapper builders. file: 'runtime-common/search-resource-helpers.ts', forbidden: [ 'buildRenderedHtmlResource', @@ -34,7 +34,7 @@ const GUARDS: { file: string; forbidden: string[] }[] = [ ], }, { - // The pre-v2 shape predicates. + // The superseded shape predicates. file: 'runtime-common/card-document-shape.ts', forbidden: [ 'isRenderedHtmlResource', @@ -60,7 +60,7 @@ const GUARDS: { file: string; forbidden: string[] }[] = [ forbidden: ['searchUnified'], }, { - // The pre-v2 federated document type — narrowed back to the original + // The superseded federated document type — narrowed back to the original // `LinkableCollectionDocument`. file: 'runtime-common/document-types.ts', forbidden: [ @@ -76,9 +76,9 @@ const GUARDS: { file: string; forbidden: string[] }[] = [ ]; module(basename(import.meta.filename), function () { - module('pre-v2 search surfaces removed', function () { + module('superseded search surfaces removed', function () { for (let { file, forbidden } of GUARDS) { - test(`${file} carries no pre-v2 search surface`, function (assert) { + test(`${file} carries no superseded search surface`, function (assert) { let contents = source(file); for (let token of forbidden) { assert.false( diff --git a/packages/runtime-common/document-types.ts b/packages/runtime-common/document-types.ts index a57cc96308f..e33f6d92e3e 100644 --- a/packages/runtime-common/document-types.ts +++ b/packages/runtime-common/document-types.ts @@ -25,7 +25,7 @@ export interface CardCollectionDocument { meta: QueryResultsMeta; } -// The v2 search response: heterogeneous `search-entry` resources in `data`, +// The search response: heterogeneous `search-entry` resources in `data`, // with everything they compose — `html` renderings (plus their deduped `css` // stylesheets) and/or `card`/`file-meta` `item` serializations — riding in // `included`. Which branches appear per entry is governed by the query's @@ -48,7 +48,7 @@ export interface SearchEntryCollectionDocument { }; } -// The public-API name for the raw v2 wire format a programmatic +// The public-API name for the raw search-entry wire format a programmatic // `searchEntries` caller receives. export type SearchEntryResults = SearchEntryCollectionDocument; diff --git a/packages/runtime-common/index-query-engine.ts b/packages/runtime-common/index-query-engine.ts index 5e41ec448eb..9705d28dc62 100644 --- a/packages/runtime-common/index-query-engine.ts +++ b/packages/runtime-common/index-query-engine.ts @@ -146,7 +146,7 @@ export type QueryOptions = WIPOptions & { // the live serialization only (pristine_doc / error_doc); `renderSet` projects // each row's full rendering set (every per-format HTML column, JSONB maps // whole) plus the live serialization on every row — the caller selects -// renderings from the set (the v2 htmlQuery evaluation). +// renderings from the set (the htmlQuery evaluation). export type SearchProjection = { kind: 'dataOnly' } | { kind: 'renderSet' }; export interface WIPOptions { diff --git a/packages/runtime-common/query-field-utils.ts b/packages/runtime-common/query-field-utils.ts index f9d383ec0ec..c5b8f76e2b6 100644 --- a/packages/runtime-common/query-field-utils.ts +++ b/packages/runtime-common/query-field-utils.ts @@ -319,7 +319,7 @@ export function getValueForResourcePath( export function buildQuerySearchURL(realmHref: string, query: Query): string { let baseHref = realmHref.endsWith('/') ? realmHref : `${realmHref}/`; - let searchURL = new URL('./_search-v2', baseHref); + let searchURL = new URL('./_search', baseHref); searchURL.searchParams.set('realms', baseHref); // A query-backed field resolves to linked instances, so it asks the // search-entry engine for a data-only projection: each entry carries its diff --git a/packages/runtime-common/query.ts b/packages/runtime-common/query.ts index 5c9e6d9db4b..a3db1069960 100644 --- a/packages/runtime-common/query.ts +++ b/packages/runtime-common/query.ts @@ -726,12 +726,11 @@ export function parseSearchURL(searchURL: string | URL): { ? parseQuery(queryParam) : parseQuery(url.search.slice(1)); - // Strip the trailing search path segment — the v2 `_search-v2` or the - // legacy `_search`, with or without a trailing slash — to recover the - // realm URL. Matching the segment's leading slash and substituting a - // single `/` leaves exactly one separator (a trailing-slash input like - // `/realm/_search-v2/` must not collapse to `/realm//`). - url.pathname = url.pathname.replace(/\/_search(?:-v2)?\/?$/, '/'); + // Strip the trailing `/_search` path segment — with or without a trailing + // slash — to recover the realm URL. Matching the segment's leading slash + // and substituting a single `/` leaves exactly one separator (a trailing- + // slash input like `/realm/_search/` must not collapse to `/realm//`). + url.pathname = url.pathname.replace(/\/_search\/?$/, '/'); url.search = ''; return { query, realm: url }; diff --git a/packages/runtime-common/realm-index-query-engine.ts b/packages/runtime-common/realm-index-query-engine.ts index dee66350507..b754adea059 100644 --- a/packages/runtime-common/realm-index-query-engine.ts +++ b/packages/runtime-common/realm-index-query-engine.ts @@ -236,7 +236,7 @@ export class RealmIndexQueryEngine { return (this.#realmURL ??= new URL(this.#realm.url)); } - // The v2 search-entry engine. Runs the parsed search-entry query — the + // The search-entry engine. Runs the parsed search-entry query — the // `item.` membership query against the SQL core, then the htmlQuery // evaluated per candidate rendering in this mapper — and assembles a // heterogeneous `search-entry` document: one entry per result, with the @@ -1180,7 +1180,7 @@ export class RealmIndexQueryEngine { }; let realmList = realms ?? (realm ? [realm] : [realmHref]); // Resolve the cross-realm query-backed field against the peer realm's - // v2 `/_search-v2` endpoint, data-only: the legacy card-rooted query + // `/_search` endpoint, data-only: the legacy card-rooted query // translates to the search-entry wire grammar, and the `item` fieldset // makes every entry carry its full `card`/`file-meta` serialization. let wireQuery = searchEntryWireQueryFromQuery( diff --git a/packages/runtime-common/realm.ts b/packages/runtime-common/realm.ts index 0e889466704..ea698de02ce 100644 --- a/packages/runtime-common/realm.ts +++ b/packages/runtime-common/realm.ts @@ -941,12 +941,12 @@ export class Realm { .query('/_lint', SupportedMimeType.JSON, this.lint.bind(this)) .get('/_mtimes', SupportedMimeType.Mtimes, this.realmMtimes.bind(this)) .get( - '/_search-v2', + '/_search', SupportedMimeType.CardJson, this.searchEntriesResponse.bind(this), ) .query( - '/_search-v2', + '/_search', SupportedMimeType.CardJson, this.searchEntriesResponse.bind(this), ) @@ -5309,7 +5309,7 @@ export class Realm { return this.#realmIndexUpdater.isIgnored(url); } - // The v2 search: the parsed search-entry query (the item. membership + // The search: the parsed search-entry query (the item. membership // query + the applied htmlQuery + the sparse fieldset) against the // search-entry projection engine. Same opts threading as `search` — // `cardUrls` rides inside the SearchEntryQuery itself. diff --git a/packages/runtime-common/resource-types.ts b/packages/runtime-common/resource-types.ts index 849cb2a0cd2..d4d839a0cad 100644 --- a/packages/runtime-common/resource-types.ts +++ b/packages/runtime-common/resource-types.ts @@ -193,7 +193,7 @@ export interface HtmlQueryLeaf { renderType?: CodeRef; } -// One v2 search result. A platform resource — never a userland card — so its +// One search result. A platform resource — never a userland card — so its // relationships cannot collide with user `@field` names. Its `id` is the bare // card/file URL, shared with its `item` (`card`/`file-meta`) serialization; // `type` is the discriminator. The branches are composition: the `html` @@ -224,7 +224,7 @@ export interface SearchEntryResource { }; } -// One prerendered rendering of a card/file: a v2 resource whose `id` is the +// One prerendered rendering of a card/file: a platform resource whose `id` is the // (card URL, format, renderType) composite (see `htmlResourceId`), so each // rendering of a card — per format × render type — is an independently // cacheable/dedupable resource. The scoped CSS it needs travels as diff --git a/packages/runtime-common/search-entry.ts b/packages/runtime-common/search-entry.ts index e35b2d9eb39..0fdb2892676 100644 --- a/packages/runtime-common/search-entry.ts +++ b/packages/runtime-common/search-entry.ts @@ -45,9 +45,9 @@ import { generalSortFields } from './index-query-engine.ts'; import { ensureTrailingSlash } from './paths.ts'; // --------------------------------------------------------------------------- -// The v2 search-entry query. +// The search-entry query. // -// A v2 request is one query rooted on `search-entry`. Entry MEMBERSHIP is +// A search-entry request is one query rooted on `search-entry`. Entry MEMBERSHIP is // addressed through `item.` (the card/file serialization) with the standard // operator-keyed filter grammar (`eq` / `contains` / `in` / `range` / `any` / // `every` / `not` / `matches`) — only the addressing changes: @@ -124,7 +124,7 @@ export interface SearchEntryFieldset { itemAsFallback: boolean; } -// The parsed form of a v2 request: the legacy `Query` for the SQL core (the +// The parsed form of a search-entry request: the legacy `Query` for the SQL core (the // `item.` addressing stripped), the applied (bound or defaulted) htmlQuery, // and the parsed sparse fieldset. The compat layer constructs this directly // from a legacy request — it does not round-trip through the wire grammar. @@ -423,7 +423,7 @@ function translateFilterNode( ); } } - // A node carrying only the type anchor is the v2 spelling of a pure + // A node carrying only the type anchor is the search-entry spelling of a pure // card-type filter. let keys = Object.keys(out); if (keys.length === 1 && keys[0] === 'on') { @@ -660,8 +660,8 @@ export function parseSearchEntryQueryFromPayload( } // --------------------------------------------------------------------------- -// The wire grammar — what a v2 client sends to `_search-v2` / -// `_federated-search-v2`. `SearchEntryWireQuery` is the search-entry-rooted +// The wire grammar — what a client sends to `_search` / +// `_federated-search`. `SearchEntryWireQuery` is the search-entry-rooted // request body: entry membership addressed through `item.` paths (`item.on` // as the type anchor), the htmlQuery bound in the filter's top-level `eq`, // and the sparse fieldset under `fields[search-entry]`. @@ -669,7 +669,8 @@ export function parseSearchEntryQueryFromPayload( // `searchEntryWireQueryFromQuery` translates a legacy card-rooted `Query` // into that grammar — the exact inverse of the parser's addressing strip // (round-trip parity is pinned by test) — so an instances-level caller can -// keep authoring the legacy query shape while the request runs against v2. +// keep authoring the legacy query shape while the request runs against the +// search-entry engine. // --------------------------------------------------------------------------- export interface SearchEntryWireSortExpression { @@ -739,7 +740,7 @@ export function searchEntryWireQueryFromQuery( ): SearchEntryWireQuery { // the legacy `realm`/`realms` members are deliberately not carried — the // caller addresses realms at the request level; `asData`/`fields` are the - // legacy data path's members and have no wire spelling here (the v2 + // legacy data path's members and have no wire spelling here (the // projection is `opts.fields`, the `fields[search-entry]` sparse fieldset) let wire: SearchEntryWireQuery = {}; if (query.filter) { @@ -853,7 +854,7 @@ export async function searchEntryRealms( } // --------------------------------------------------------------------------- -// Builders for the v2 resources. The projection engine runs these per row +// Builders for the search-entry resources. The projection engine runs these per row // when assembling a `search-entry` document; keeping them pure (no SQL, no // realm state) lets the shapes be unit-tested directly. The `css` resource // builder is shared with the pre-existing search paths (`buildCssResource`). diff --git a/packages/runtime-common/search-results-component.ts b/packages/runtime-common/search-results-component.ts index f3749507a3e..81c3e2dc63f 100644 --- a/packages/runtime-common/search-results-component.ts +++ b/packages/runtime-common/search-results-component.ts @@ -34,7 +34,7 @@ export interface SearchEntryRendering { cssUrls: string[]; } -// One v2 search result as a renderable view-model. `component` renders the +// One search result as a renderable view-model. `component` renders the // result transparently — prerendered HTML inert (hydrated lazily) or a live // card — so a consumer renders `` without ever branching on // prerendered-vs-live. `html` / `item` are the raw branches, exposed for custom @@ -75,7 +75,7 @@ export interface SearchResultsYield { errors: ErrorEntry[] | undefined; } -// The card-facing contract for the v2 search component the host provides on +// The card-facing contract for the search component the host provides on // `@context` (`@context.searchResultsComponent`). It consumes the heterogeneous // `search-entry` stream for a `search-entry`-rooted query and renders it // transparently — prerendered HTML inert (hydrated lazily) or the live @@ -85,7 +85,7 @@ export interface SearchResultsYield { export interface SearchResultsComponentSignature { Element: HTMLElement; Args: { - // The `search-entry`-rooted v2 query. Re-issued live on invalidation; + // The `search-entry`-rooted query. Re-issued live on invalidation; // changing it re-runs the search. Undefined → idle (no results). query: SearchEntryWireQuery | undefined; // The hydration gesture for HTML-backed rows — a host-UX choice, never on diff --git a/packages/vscode-boxel-tools/src/skills.ts b/packages/vscode-boxel-tools/src/skills.ts index c17bd803c7a..df12ffa2ea6 100644 --- a/packages/vscode-boxel-tools/src/skills.ts +++ b/packages/vscode-boxel-tools/src/skills.ts @@ -378,12 +378,12 @@ export class SkillList extends vscode.TreeItem { headers['Authorization'] = jwt; } - // `_search-v2` speaks the search-entry wire grammar: entry membership is + // `_search` speaks the search-entry wire grammar: entry membership is // addressed through `item.` (the card serialization), so the type anchor // is `item.on` and sort keys carry the `item.` prefix. Request the // data-only fieldset (`fields[search-entry]=item`) — skill discovery never // renders HTML, so each entry carries only its full `item` serialization. - const searchUrl = new URL('./_search-v2', this.realmUrl); + const searchUrl = new URL('./_search', this.realmUrl); const query = { sort: [ {