Unified search: host consumer + selective Store inflate + live render type#5176
Conversation
…nflate + live render type Stream B base — the host reads the unified search document, keeps the Store honest, and renders live cards as the resolved render type. Develops against fixtures; live call-site wiring follows. - lib/unified-search-results.ts: buildRenderableSearchItems maps a UnifiedSearchCollectionDocument to renderable rows — an identity-only card (resolved from its rendered-html in included) becomes an inert htmlComponent with its scoped css imported via the injected loader; a full live card becomes a no-component row the caller renders live. css is deduped across rows. + unit tests. - services/store.ts: addResourceFromSearchData skips any meta.identityOnly resource — an attribute-less stub is never deposited, so it can't misrepresent the instance or clobber a correctly-loaded full one. - components/card-search/item-button.gts: a @renderType arg drives the live CardRenderer's @CodeRef, replacing the hardcoded CardDef (which remains the default only when no render type is threaded), so a live row renders as the same ancestor type as its prerendered-HTML siblings. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Verify that a prefer-HTML search never deposits an identity-only row into the Store and never clobbers a pre-resident full instance for the same id, by overriding the mock /_federated-search route to return a unified doc with an identity-only card. Exports registerRealmServerRoute so a test can swap a single realm-server route and restore it via registerDefaultRoutes. 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: ac321b967e
ℹ️ 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".
When a prefer-HTML search returns an identity-only row for a card already fully loaded in the Store, return that resident instance instead of dropping it: it stays represented in the results and a hydrated row keeps its live presentation. A not-yet-loaded identity-only row is still skipped (renders from its HTML); the stub is never deposited and a resident instance is never clobbered. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Preview deploymentsHost Test Results 1 files ± 0 1 suites ±0 13m 49s ⏱️ - 1h 36m 22s Results for commit 26b70c1. ± Comparison against earlier commit 05d23d3. Realm Server Test Results 1 files 1 suites 10m 30s ⏱️ Results for commit 05d23d3. |
There was a problem hiding this comment.
Pull request overview
Adds the host-side “unified search” consumer utilities and related wiring to support mixed search rows (HTML-backed identity-only vs live Store-backed), along with Store behavior to avoid depositing identity-only stubs and a render-type threading hook for live rendering parity.
Changes:
- Introduces
buildRenderableSearchItems()to turn a unified search document into renderable rows, including CSS dedupe/import for HTML-backed rows. - Updates
StoreService.addResourceFromSearchData()to skip depositingmeta.identityOnlyresources while still returning an already-resident full instance when present. - Extends
card-search/item-buttonto accept a@renderTypearg and use it forCardRenderer @codeRef(with a CardDef default).
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/host/app/lib/unified-search-results.ts | New unified search document consumer that builds renderable items and imports deduped CSS for HTML-backed results. |
| packages/host/app/services/store.ts | Skips depositing identity-only search resources to avoid Store stubs/clobbers; returns resident full instance when already loaded. |
| packages/host/app/components/card-search/item-button.gts | Adds @renderType arg and uses it for live CardRenderer @codeRef with a default fallback. |
| packages/host/tests/unit/unified-search-results-test.ts | Unit coverage for identity-only vs live rows, CSS dedupe, missing rendered-html, and error propagation. |
| packages/host/tests/integration/store-test.gts | Integration coverage ensuring identity-only search rows don’t enter/clobber the Store. |
| packages/host/tests/helpers/realm-server-mock/routes.ts | Exports registerRealmServerRoute so integration tests can override the mock search route. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| // not-yet-loaded identity-only row is skipped and renders from its HTML. | ||
| if (resource.meta?.identityOnly === true) { | ||
| let existingInstance = this.peek(resource.id); | ||
| return existingInstance && isCardInstance(existingInstance) |
There was a problem hiding this comment.
[Claude Code 🤖]
Minor edge case (not a crash) in the selective inflate: this identity-only branch is card-shaped, so it never returns a resident FileDef. this.peek(resource.id) resolves against the card type and isCardInstance is false for a FileDef, so an already-loaded file whose row comes back identity-only falls through to undefined — it renders inert-from-HTML rather than keeping its live presentation, even though the live instance is resident.
The full file-meta branch right below already does the type-aware thing (this.peek(resource.id, { type: 'file-meta' }) + isFileDefInstance). The identity-only branch could mirror it for file rows — e.g. branch on isFileMetaResource(resource) to pick the peek type and the instance check.
Harmless for the card path and for not-yet-loaded files (those render from HTML by design), so this is a follow-up, not a blocker. Noting it here since it's the selective-inflate's concern; surfaced while wiring the host file-meta consumer in #5180.
There was a problem hiding this comment.
[Claude Code 🤖] Fixed on this branch (26b70c1). The identity-only inflate branch is now type-aware: a file row peeks (and type-checks) as a FileDef, mirroring the full file-meta branch below it — so an already-resident FileDef whose row comes back identity-only keeps its live instance instead of dropping to inert HTML. Added a store-test sibling of the resident-card test for the FileDef path.
Landing it here (cs-11434), the lowest branch where both the inflate branch and the file-meta infra live, rather than on the host FileDef-parity branch — it reaches that branch via the stack.
…ile-meta row The selective inflate's identity-only branch peeked card-typed, so an already-resident FileDef whose search row came back identity-only failed `isCardInstance` and dropped to `undefined` — rendering inert from its HTML instead of keeping its live instance. Make the resident lookup type-aware: a file row peeks (and type-checks) as a FileDef, mirroring the full file-meta branch directly below it. Test: an identity-only file-meta row for an already-resident FileDef returns the live FileDef (a file row is not dropped to HTML). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Phase 1 / Stream B base of Unify prerendered-card-search and card-search: the host-side reader for the unified search document. Based on
main(which already has the Phase-0 contracts + render-type rule).Additive — it generalizes the consumer and tightens the Store deposit without removing the old path, and develops against fixtures (the live call-site wiring follows in a later Stream B ticket).
Changes
lib/unified-search-results.ts—buildRenderableSearchItems(doc, importCss)reads aUnifiedSearchCollectionDocumentinto renderable rows: an identity-onlycard(resolved from itsrendered-htmlinincluded) becomes an inerthtmlComponentwith its scopedcssimported via the loader; a full livecardbecomes a no-component row the caller renders live from the Store.cssis deduped across rows.services/store.ts—addResourceFromSearchDataskips anymeta.identityOnlyresource. An attribute-less stub is never deposited, so it can't misrepresent the instance or clobber a correctly-loaded full one; the Store is populated for an HTML-backed row only at hydration.components/card-search/item-button.gts— a@renderTypearg drives the liveCardRenderer's@codeRef, replacing the hardcodedCardDef(kept only as the default when no render type is threaded), so a live/fallback row renders as the same ancestor type as its prerendered-HTML siblings.Tests
rendered-html→ no component (graceful); an errorrendered-html→isError./_federated-searchroute override returning a unified doc).Locally: host type-check + lint clean; unit tests pass standalone, the Store integration tests pass against the base-realm test stack.
The render-type visual parity (a live row rendered via
@codeRefmatching its HTML sibling) lands with the component-consolidation work that wires real unified data through the leaf; the render-type arg it depends on is in place here.🤖 Generated with Claude Code