Unified search: emit rendered-html/css on /_search render requests#5171
Conversation
/_federated-search honors an explicit render member: it resolves each result to prerendered HTML where indexed and falls back to the full live card otherwise. A request without render (and dataOnly) returns the live-card document unchanged, so consumers not yet reading the unified shape are unaffected; the prerendered endpoints keep their existing path as the compat shim. - runtime-common/unified-search.ts: pure builders for the css (md5 of the encoded href), rendered-html, and identity-only card resources, plus the used-render-type / scoped-css-dep helpers the result mapper runs per row. - RealmIndexQueryEngine.searchUnified: one render projection. HTML rows ship an identity-only card + rendered-html (deduped css in included); no-HTML rows ship the full live card assembled via loadLinks over only those rows; the render type is echoed; a file-meta query resolves to the live document. The render projection now also selects types so an identity-only card carries its actual adoptsFrom (distinct from the ancestor it was rendered as). - Realm.search routes to searchUnified when render is present; SearchOpts carries the resolved render spec; searchRealms / combineSearchResults already merge the unified document by (type, id). - handle-search resolves the render type (filter.on default, "native" escape valve), gates emission on an explicitly-provided render member, and folds the resolved spec into the job-scoped cache key. Tests: the mixed-index wire test (null the HTML for one row, assert rendered-html + identity-only card for HTML rows and a full live card for the nulled row in one data array); dataOnly is byte-identical to a plain live query; cache-key segregation by render / dataOnly / cardUrls; the resource builders; and the prerendered compat goldens stay byte-identical. 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: 6407f94afa
ℹ️ 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".
… unrenderable formats, fix host mock type - searchUnified now applies the query's sparse fieldset and threads linkFields to loadLinks for the live fallback rows, so a fallback card matches exactly what the same /_search query returns (it previously over-serialized). - handle-search rejects an explicit render.format that is a valid Format but not backed by prerendered HTML (e.g. isolated / edit) with a 400 invalid- render, instead of silently coercing it to fitted. - Widen the host realm-server-mock's SearchableRealm.search return type to the unified document so a Realm (whose search now returns it) stays assignable — fixes the host lint:types failure. Tests: a render request with an empty sparse fieldset yields a fallback card with no attributes; an unsupported render.format returns 400. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Preview deploymentsHost Test Results 1 files 1 suites 1h 51m 19s ⏱️ Results for commit 41c8d00. Realm Server Test Results 1 files ± 0 1 suites ±0 10m 6s ⏱️ -41s Results for commit a4b2513. ± Comparison against earlier commit 41c8d00. |
…ch-_search-renderdataonly-members-emit-rendered # Conflicts: # packages/runtime-common/index.ts # packages/runtime-common/realm-index-query-engine.ts
There was a problem hiding this comment.
Pull request overview
This PR turns on “prefer-HTML” emission for /_federated-search only when the caller explicitly includes a render member, returning unified results where each row either (a) ships prerendered HTML (rendered-html + deduped css) alongside an identity-only card, or (b) falls back to a full live card when HTML is missing.
Changes:
- Add pure unified-search builders (
css/rendered-html/ identity-onlycard) and helpers for parsing/deriving render metadata. - Implement
RealmIndexQueryEngine.searchUnified()and routeRealm.search()to it whenopts.renderis present, while keeping the live-card response unchanged whenrenderis omitted. - Update federated handler behavior and extend endpoint/contract tests to cover mixed HTML-backed + live fallback results, cache-key segregation, and format validation.
Reviewed changes
Copilot reviewed 11 out of 11 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/runtime-common/unified-search.ts | Adds pure helper/builder functions for unified-search resources and render metadata parsing. |
| packages/runtime-common/search-utils.ts | Extends SearchOpts and updates federated search typing to return unified documents. |
| packages/runtime-common/resource-types.ts | Adds meta.renderType to identity-only card metadata. |
| packages/runtime-common/realm.ts | Routes Realm.search() to searchUnified() when opts.render is provided. |
| packages/runtime-common/realm-index-query-engine.ts | Implements searchUnified() mapping logic, CSS dedupe, and fallback loadLinks behavior. |
| packages/runtime-common/index.ts | Exports unified-search helpers from the runtime-common barrel. |
| packages/runtime-common/index-query-engine.ts | Extends the render projection to also select types for correct adoptsFrom on identity-only cards. |
| packages/realm-server/tests/unified-search-contracts-test.ts | Adds unit coverage for unified-search helper/builder functions. |
| packages/realm-server/tests/server-endpoints/search-test.ts | Adds/updates integration tests for prefer-HTML behavior, cache key behavior, and invalid formats. |
| packages/realm-server/handlers/handle-search.ts | Gates prefer-HTML emission on explicit render, validates prerenderable formats, and folds resolved render spec into cache key. |
| packages/host/tests/helpers/realm-server-mock/routes.ts | Updates mock realm typing to reflect unified search return shape. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Strip temporal phrasing from the comments added in this work (today's, not yet, unchanged) so each states the contract as timeless fact: the live-card document for a no-render request, the live /_search exclusion of error rows, and the full live card's standard wireformat. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ttachRealmInfo cast - handle-search.ts: fold `dataOnly` into the run-time `searchRealms` opts alongside `render`, so the run-time opts name the requested mode the same members the job-scoped cache key does. SearchOpts already declares `dataOnly`; absence of `render` still selects the live path, so this is contract-completeness with no behavior change. - realm-index-query-engine.ts: widen `attachRealmInfo` to accept `UnifiedSearchCollectionDocument` and narrow each resource by its `type` discriminant (card / file-meta) instead of casting the whole document to `LinkableCollectionDocument`. rendered-html / css resources are skipped by the discriminant exactly as the prior runtime guard skipped them. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
| // make every key unique and defeat the cache) and ride here instead, where | ||
| // `searchRealms` stamps the SQL + loadLinks stages onto the collector this | ||
| // handler emits. | ||
| let runSearchOpts = |
There was a problem hiding this comment.
[Claude Code 🤖]
Forwarding a Codex finding from the stacked host PR #5180.
cardUrls is folded into the cache key (cacheKeyOpts.cardUrls above) but not into runSearchOpts, which is what's passed to searchRealms → Realm.search. The query engine applies the IN (...) filter from opts.cardUrls (index-query-engine.ts ~620), so on this unified federated path a cardUrls-scoped request keys the cache correctly but isn't actually filtered at runtime — the filter is silently dropped.
Threading parsed.cardUrls into runSearchOpts when non-empty (alongside render / dataOnly) should restore it.
There was a problem hiding this comment.
[Claude Code 🤖] Fixed. cardUrls now rides the run-time search opts, not just the cache key: the handler threads parsed.cardUrls into runSearchOpts, Realm.search forwards it into the engine opts, and it's added to SearchOpts. The SQL i.url IN (...) subset filter now actually narrows the result set. Added an endpoint test: a cardUrls-scoped federated query returns only the requested card.
|
[Claude Code 🤖] Forwarding a Codex finding from the stacked host PR #5180 (the host consumer relies on this).
The resolved render type is consistent within a search, so carrying the first non-null per-realm |
…s the federated merge Two review-finding fixes on the unified federated-search server path. cardUrls subset filter: a `cardUrls`-scoped request folded the subset into the cache key but not the run-time search opts, so the SQL `i.url IN (...)` filter was never applied — the full result set came back, cached under a subset-specific key. Thread `cardUrls` from the handler through `Realm.search` into the engine opts (and add it to `SearchOpts`) so the subset actually narrows the results. combineSearchResults renderType: the federated merge initialized fresh metadata and summed only `page.total`, dropping the collection-level `meta.renderType` the per-realm docs echo. A host consumer renders live/fallback card rows under it, so they hydrated with `undefined`. Carry the first non-null per-realm `meta.renderType` onto the combined document. Tests: a cardUrls-scoped federated query returns only the requested subset; combineSearchResults preserves the resolved renderType and fabricates none for native/per-row searches. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
[Claude Code 🤖] Fixed the forwarded |
Phase 1 / Stream A step 3 (server turn-on) of Unify prerendered-card-search and card-search:
/_federated-searchstarts honoringrenderand emittingrendered-html/css.Behavior
A request with an explicit
rendermember is prefer-HTML: each result resolves to prerendered HTML where the index has it and falls back to the full livecardotherwise — both in onedataarray.card(id +metaincl.renderType+meta.identityOnly+links.self+ arendered-htmlrelationship, noattributes) plus arendered-htmlresource (and its dedupedcss, id =md5(href)) inincluded.card, exactly as/_searchreturns today (loadLinksruns over only these rows).dataOnly: true→ full live cards only.A request without
render(and withoutdataOnly) returns the live-card document unchanged. Prefer-HTML emission is gated on an explicitly-providedrendermember so consumers that don't yet read the unified shape are unaffected regardless of merge order; flipping to the project default (omittingrender→ prefer-HTML) is tracked on the cleanup ticket, once the host consumers migrate. The prerendered endpoints keep their existing path as the compat shim — byte-identical, covered by the existing prerendered goldens.Implementation
runtime-common/unified-search.ts: pure builders (css/rendered-html/ identity-onlycard) + the per-rowused-render-type/ scoped-css-dep helpers.RealmIndexQueryEngine.searchUnified: onerenderprojection, the per-row resolution policy,cssdeduped inincluded, render-type echo. The projection now also selectstypesso an identity-only card carries its actualadoptsFrom(distinct from the ancestor it was rendered as).Realm.searchroutes tosearchUnifiedwhenrenderis present;searchRealms/combineSearchResultsmerge the unified document by(type, id).handle-searchresolves the render type (filter.ondefault,"native"escape valve) and folds the resolved spec into the job-scoped cache key.Tests
rendered-html+ identity-onlycardfor HTML rows and a full livecardfor the nulled row, all in onedataarray.dataOnlyis byte-identical to a plain live query; cache-key segregation byrender/dataOnly/cardUrls; the resource builders;(type, id)merge dedup.Locally:
runtime-common+realm-servertype-check clean; unified-search contracts 26/26; thesearch-test+search-prerenderedshards 54/54.🤖 Generated with Claude Code