Unified search: the v2 <SearchResults> component + host consumer#5237
Conversation
Add <SearchResults @query=…/>, the one v2 search component family. It consumes the heterogeneous search-entry stream from getSearchEntriesResource and renders it transparently: a prerendered html rendering shows inert and hydrates lazily on interaction, an item-only row renders as a live card. Each entry exposes a curried .component (wrapping HydratableCard) so a consumer renders <entry.component /> without branching on prerendered-vs-live, plus the raw html/item branches. Used with a block it yields a results object (entries/isLoading/meta/errors); used without one it renders the default stream itself. StoreService.inflateSearchEntryItem deposits only full item serializations; a sparse item (carrying meta.sparseFields) and the search-entry itself are never stored. <SearchResults> runs this selective inflate over the live entry set. Additive — no call site is migrated; the prerendered-card-search component and the live SearchContent tree remain until each surface moves. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 1ac96da3a4
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
There was a problem hiding this comment.
Pull request overview
Adds the first “unified search” v2 <SearchResults @query=… /> component family for host usage, providing a single render surface for heterogeneous search-entry results (prerendered inert HTML that hydrates lazily, plus item-only rows that render live), along with the supporting host-side selective Store inflation behavior and integration tests.
Changes:
- Introduces
<SearchResults>(block-yield or default rendering) that wraps each entry in a ready-to-render<entry.component />abstraction. - Adds a curried
hydratableEntryComponent()wrapper aroundHydratableCardusing a custom component manager (similar tohtml-component.ts). - Adds
StoreService.inflateSearchEntryItem()to selectively deposit only fullitemserializations (skipping sparse ones) into the Store, plus integration coverage.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
| packages/host/app/components/card-search/search-results.gts | New v2 SearchResults component: entry view-modeling, default vs block rendering, render-side selective inflate. |
| packages/host/app/lib/hydratable-entry-component.ts | New curried component wrapper that renders HydratableCard with per-entry args via a custom manager. |
| packages/host/app/services/store.ts | Adds inflateSearchEntryItem() that skips sparse items and inflates full items into the store from search data. |
| packages/host/tests/integration/components/search-results-test.gts | New integration tests covering inert HTML, hydration, heterogeneous streams, and selective inflation behavior. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
- Thread the selected HTML format through to the live/hydrated card: HydratableCard takes an optional @Format (default fitted), and <SearchResults> passes each row's format (the rendering's own for an html row, the query's requested format for an item-only fallback) so a hydrated/fallback row matches the HTML the query selected instead of always rendering fitted. - Invalidate the view-model memo when the captured render inputs (gesture mode + document-level fallback render type/format) change, so a row is not reused with stale hydration settings. - inflateFullItems now catches and logs a deposit failure instead of rejecting unhandled mid-render. - Reword the inflateSearchEntryItem doc comment (it returns void). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The one v2 search component family,
<SearchResults @query=… />, plus the host-side pieces it needs. Phase 1 of the unified-search project. Additive — it sits alongside the existingprerendered-card-searchcomponent and the liveSearchContenttree; no call site is migrated in this PR (each surface migrates as its own shippable step).What it does
<SearchResults>consumes the heterogeneoussearch-entrystream fromgetSearchEntriesResourceand renders it transparently:htmlrendering shows inert (the fast path) and hydrates lazily on interaction (hover/click/touch, ornone),item-only row renders as a live card.Each result is a
RenderableSearchEntryexposingid/isError/html/item/component. Thecomponentwraps the mergedHydratableCardmechanism, so a consumer renders<entry.component />without ever branching on prerendered-vs-live — that's what makes the split transparent. It's curried in JS via the same custom-manager patternhtml-component.tsalready uses to yield a renderable.component; provide/consume context still reaches the nestedHydratableCardbecause the glimmer VM tracks context per component instance regardless of manager.Used with a block it yields a
resultsobject (entries/isLoading/meta/errors); used without one it renders the default stream ofentry.components itself.Selective Store inflate
StoreService.inflateSearchEntryItemdeposits only fullitemserializations (so a by-URL read or the hydration GET resolves without a round-trip). A sparseitem(one carryingmeta.sparseFields) and thesearch-entryitself are never deposited — a sparse item would misrepresent the instance and could clobber a correctly-loaded full one.<SearchResults>runs this as a render-side effect over the live entry set, so an item-bearing row deposits whenever one lands on a re-run.Render type
An html row hydrates/renders as the type its rendering carries; an item-only fallback renders as the query's requested ancestor (echoed once in the document
meta) so it matches its HTML siblings; files render natively. View-models are memoized by the resource's stable entry identity, so a live re-run never re-mounts an unchanged row's component.Tests
tests/integration/components/search-results-test.gts(7 tests, all green against the realm stack):results.entries/meta/ eachentry.component);