feat: add multifeed composition support to EXPERIMENTAL_Autocomplete#6993
Open
e-krebs wants to merge 49 commits into
Open
feat: add multifeed composition support to EXPERIMENTAL_Autocomplete#6993e-krebs wants to merge 49 commits into
e-krebs wants to merge 49 commits into
Conversation
Compositions can return multiple result sets (feeds), each identified by a `feedID`. Previously, `_runComposition` spliced only 1 result per derived helper, discarding additional feeds. This changes `queriesCount` to `Infinity` so all feeds are captured, and builds a `_feedResults` map and `_feedOrder` array on the primary `SearchResults` for downstream consumption by `connectFeeds` (Layer 2). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…Infinity Use undefined queriesCount to signal "take all results" in _dispatchAlgoliaResponse, avoiding the Infinity hack. When queriesCount is undefined (composition path), use results directly; when defined (regular search path), splice as before. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The feedID, _feedResults, and _feedOrder type definitions have no consumers yet. Defer the typing decision (interface vs class, naming) to Layer 2 when connectFeeds introduces the first consumer. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
… map Replace _feedResults (Record<string, SearchResults>) and _feedOrder (string[]) with a single lastResults.feeds (CompositionSearchResults[]) ordered array. This simplifies the API and ensures feeds survive the SSR getInitialResults → JSON → hydration round-trip naturally. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Create a separate SearchResults instance for lastResults instead of reusing feeds[0], which caused circular references during JSON.stringify (lastResults.feeds[0] === lastResults). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: Haroen Viaene <hello@haroen.me>
Move storeRenderState from module-private in index.ts to render-args.ts so FeedContainer can reuse it in the feeds connector (Layer 2). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…dContainer Introduce indexWidgetTypes const and IndexWidgetType as single source of truth for index-like widget types. Update isIndexWidget, IndexWidgetDescription, BuiltinTypes, and BuiltinWidgetTypes to use them. Add 'ais.feeds' and 'ais.feedContainer' to builtin type unions. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The metadata middleware hardcoded 'ais.index' instead of using isIndexWidget, so widgets inside feed containers were invisible to the Algolia Crawler metadata. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Made-with: Cursor
…tions Made-with: Cursor
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ized flag Replace `instantSearchInstance.started` check with a local `initialized` flag so that child widgets added before `container.init()` are not eagerly initialized. This prevents double-init when the parent index tree calls `init()` on the container after widgets have already been added. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…omputation
Remove FeedContainer lifecycle management from the connector — widget layers
now own container creation, registration, and cleanup. The connector only
computes feedIDs from results.feeds and exposes them as render state.
- Remove `widgets` param and internal `feedContainers` map
- Change render state from `feeds: [{ feedID, container }]` to `feedIDs: string[]`
- Make `getWidgetRenderState` stateless (derives feedIDs from results)
- Simplify `dispose()` to only call `unmountFn()`
- Extract shared test helpers to `test/createFeedsTestHelpers.ts`
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Use `import type` for type-only imports - Fix import ordering in test files Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Up to standards ✅🟢 Issues
|
| Metric | Results |
|---|---|
| Complexity | 217 |
| Duplication | 0 |
TIP This summary will be updated as you push new changes.
More templates
algoliasearch-helper
instantsearch-ui-components
instantsearch.css
instantsearch.js
react-instantsearch
react-instantsearch-core
react-instantsearch-nextjs
react-instantsearch-router-nextjs
vue-instantsearch
commit: |
8081d92 to
e086c8e
Compare
e-krebs
commented
Apr 27, 2026
Comment on lines
+1409
to
+1467
| if (isFeedsMode) { | ||
| // Defer tree construction to `init`: we need `compositionID` from the | ||
| // InstantSearch instance, which isn't available at factory time. | ||
| // Pre-register FeedContainers (rather than letting `feeds()` create them | ||
| // lazily on render) to avoid a redundant composition search when the | ||
| // containers are added after the first results land. | ||
| const feedIDs: string[] = [ | ||
| ...(querySuggestionsKey ? [querySuggestionsKey] : []), | ||
| ...feeds.map((feed) => feed.feedID), | ||
| ...(promptSuggestionsKey ? [promptSuggestionsKey] : []), | ||
| ]; | ||
| let bootstrappedTree: IndexWidget | null = null; | ||
| const bootstrap: Widget = { | ||
| $$type: 'ais.autocomplete', | ||
| $$widgetType: 'ais.autocomplete', | ||
| init({ instantSearchInstance, parent }) { | ||
| if (!instantSearchInstance.compositionID) { | ||
| throw new Error( | ||
| withUsage( | ||
| 'feeds-mode requires a composition-based InstantSearch instance (compositionID must be set).' | ||
| ) | ||
| ); | ||
| } | ||
| bootstrappedTree = index({ | ||
| indexName: instantSearchInstance.compositionID, | ||
| indexId: `ais-autocomplete-${instanceId}`, | ||
| EXPERIMENTAL_isolated: true, | ||
| }); | ||
| const feedContainers = feedIDs.map((feedID) => | ||
| createFeedContainer(feedID, bootstrappedTree!, instantSearchInstance) | ||
| ); | ||
| bootstrappedTree.addWidgets([ | ||
| configure(searchParameters), | ||
| // Connector-only registration runs `hydrateFeedsFromInitialResultsIfNeeded` | ||
| // at init for SSR, without triggering the extra search `feeds()` would. | ||
| connectFeeds(noop, noop)({ searchScope: 'global' }), | ||
| ...feedContainers, | ||
| { | ||
| ...makeWidget({ | ||
| escapeHTML, | ||
| transformItems: effectiveTransformItems, | ||
| }), | ||
| $$widgetType: 'ais.autocomplete', | ||
| }, | ||
| ]); | ||
| parent?.addWidgets([bootstrappedTree]); | ||
| }, | ||
| render() {}, | ||
| dispose({ parent }) { | ||
| if (bootstrappedTree) { | ||
| parent?.removeWidgets([bootstrappedTree]); | ||
| bootstrappedTree = null; | ||
| } | ||
| return undefined; | ||
| }, | ||
| }; | ||
| return [connectSearchBox(() => null)({}), bootstrap]; | ||
| } | ||
|
|
Contributor
Author
There was a problem hiding this comment.
I'm not super happy about this :/
but I don't have a better solution for now
3e08a2b to
6c8f487
Compare
e086c8e to
a4b206d
Compare
Serialize per-feed results as `compositionFeedsResults` in `getInitialResults`, hydrate them in `hydrateSearchClient`, and reconstruct `lastResults.feeds` in the feeds connector so that composition multifeed works with server-side rendering. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
End-to-end integration test covering the full SSR cycle: server-side rendering with getInitialResults, hydration via hydrateSearchClient, and client-side rehydration of per-feed results. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Demo app showing composition multifeed with SSR using react-instantsearch-nextjs. Excluded from v4 type-check since it uses composition-only APIs. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…prop Update the SSR test and the Next.js App Router composition example to the new `renderFeed` prop introduced on Layer 3. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
3be039f to
7fd46ae
Compare
0fa6aea to
7f95acb
Compare
The composition multifeed SSR additions push the development bundle just over the 256.5 kB ceiling. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
These examples reference an internal Algolia application and should not be published. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Adds a `feeds` option that swaps the N-child-Index subtree for a single isolated composition Index + pre-registered FeedContainers, so all panel sections come from one multifeed composition response. `feeds` and `indices` are mutually exclusive; feeds-mode requires a `compositionID` on the outer InstantSearch. Downstream (connectAutocomplete, renderer) is unchanged — the widget normalizes feeds into the existing indices shape before handing off. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Covers the feeds-mode contract on the JS widget: per-feed section rendering, query/prompt suggestion feed wiring, recent-searches dedupe against the suggestions feed, missing/unknown feed handling, and the two configuration guards (feeds + indices conflict; feeds-mode without a compositionID). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…e widget
Mirrors the JS counterpart: adds a `feeds` prop and a discriminated
union (`AutocompleteIndicesProps | AutocompleteFeedsProps`) so the two
modes can't be mixed at compile time. Feeds-mode renders an isolated
composition Index with a `<Feeds renderFeed={() => null}>` child that
handles FeedContainer lifecycle (and SSR hydration for free).
InnerAutocomplete stays untouched — feeds are normalized into the
existing indices shape in the outer widget.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…plete feeds-mode Unit tests cover the two configuration guards (feeds + indices conflict; feeds-mode without a compositionID). The integration test exercises the full feeds-mode widget tree against a mocked composition client — per-feed sections, query/prompt suggestion feeds, and recent-searches dedupe against the suggestions feed. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Demonstrates the new `feeds` option on `EXPERIMENTAL_autocomplete` with a composition-backed autocomplete panel (query suggestions + products + articles feeds from a single multifeed response). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Mirrors the JS example: a composition-backed autocomplete panel driven by the new `feeds` prop on `EXPERIMENTAL_Autocomplete`, with query suggestions and per-feed result sections from a single multifeed composition response. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
7fd46ae to
f7ce024
Compare
The feeds-mode branch in `EXPERIMENTAL_autocomplete` pulls in the feeds connector + FeedContainer path, pushing the production bundle just over the prior 123 kB ceiling and the development bundle over 256.5 kB. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
7f95acb to
ef90a85
Compare
Contributor
There was a problem hiding this comment.
Pull request overview
Adds “feeds-mode” support to EXPERIMENTAL_Autocomplete (React) and EXPERIMENTAL_autocomplete (InstantSearch.js) so a single composition multifeed response can power all autocomplete panel sections.
Changes:
- Introduce a new
feedsoption/prop (mutually exclusive withindices) and normalize feeds to look like indices downstream (includingshowQuerySuggestions/showPromptSuggestionsbehavior). - Add feeds-mode widget trees (React via
<Feeds ... />; JS via pre-registeredFeedContainers +connectFeeds(noop, noop)for SSR hydration). - Add integration/usage tests and introduce two multifeed example apps (React + JS); update bundlesize limits accordingly.
Reviewed changes
Copilot reviewed 21 out of 21 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| tsconfig.v4.json | Excludes the new composition-based example apps from the v4 tsconfig build. |
| packages/react-instantsearch/src/widgets/Autocomplete.tsx | Adds feeds prop support + feeds-mode behavior/normalization for React autocomplete. |
| packages/react-instantsearch/src/widgets/tests/AutocompleteFeedsMode.integration.test.tsx | Adds integration coverage for React autocomplete in feeds-mode (rendering, suggestions, transformItems, empty feeds). |
| packages/react-instantsearch/src/widgets/tests/Autocomplete.test.tsx | Adds runtime guard tests for mutual exclusivity + compositionID requirement in feeds-mode. |
| packages/instantsearch.js/src/widgets/autocomplete/autocomplete.tsx | Adds feeds option support + feeds-mode bootstrap with pre-registered feed containers and downstream normalization. |
| packages/instantsearch.js/src/widgets/autocomplete/tests/autocomplete-test.ts | Adds usage + feeds-mode init/rendering tests for InstantSearch.js autocomplete. |
| packages/instantsearch.js/src/tests/common-widgets.test.tsx | Adjusts typing in the common widget test harness for the new autocomplete param union. |
| examples/react/autocomplete-multifeed/vite.config.mjs | Adds Vite config for the React multifeed example. |
| examples/react/autocomplete-multifeed/package.json | Adds workspace package for the React multifeed example. |
| examples/react/autocomplete-multifeed/index.tsx | Entry point for the React multifeed example app. |
| examples/react/autocomplete-multifeed/index.html | HTML shell for the React multifeed example app. |
| examples/react/autocomplete-multifeed/README.md | Run instructions + backend requirements for the React multifeed example. |
| examples/react/autocomplete-multifeed/App.tsx | React multifeed example implementation using EXPERIMENTAL_Autocomplete feeds-mode. |
| examples/react/autocomplete-multifeed/App.css | Styling for the React multifeed example. |
| examples/js/autocomplete-multifeed/vite.config.mjs | Adds Vite config for the JS multifeed example. |
| examples/js/autocomplete-multifeed/src/app.ts | JS multifeed example implementation using EXPERIMENTAL_autocomplete feeds-mode. |
| examples/js/autocomplete-multifeed/src/app.css | Styling for the JS multifeed example. |
| examples/js/autocomplete-multifeed/package.json | Adds workspace package for the JS multifeed example. |
| examples/js/autocomplete-multifeed/index.html | HTML shell for the JS multifeed example app. |
| examples/js/autocomplete-multifeed/README.md | Run instructions + backend requirements for the JS multifeed example. |
| bundlesize.config.json | Updates size ceilings to account for the added feeds-mode imports. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
FabienMotte
approved these changes
Apr 30, 2026
Base automatically changed from
feat/composition-multifeed-layer5-remove-examples
to
feat/composition-multifeed
April 30, 2026 16:48
61c8811 to
cab5256
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds multifeed composition support to
EXPERIMENTAL_Autocomplete(React + JS), so a single multifeed composition response can drive every panel section (query suggestions, products, articles, prompts, …) instead of one composition call per child<Index>.Builds on top of #6977 (Layer 5 — example cleanup). A follow-up layer will remove the example apps introduced here, mirroring the layer 5 pattern.
Design doc: RFC — Multifeed Composition Support in InstantSearch Autocomplete.
What this does
feedsprop/option onEXPERIMENTAL_Autocomplete(React) andEXPERIMENTAL_autocomplete(JS). Mutually exclusive with the existingindices— enforced at compile time via a discriminated union (AutocompleteIndicesProps | AutocompleteFeedsProps) and at runtime as a defense-in-depth throw.showQuerySuggestions/showPromptSuggestions— takesfeedID(instead ofindexName) when the widget is in feeds-mode; the corresponding section is sourced from that feed in the same response.<Index EXPERIMENTAL_isolated indexName={compositionID}>, with one<Feeds renderFeed={() => null}>child (React) or pre-registeredFeedContainers driven byconnectFeeds(noop, noop)(JS). Preserves query isolation: typing in the autocomplete never refreshes main-page widgets.indicesConfigfromfeeds[]withindexName = feedID;showQuerySuggestions/showPromptSuggestionswithindexNameset tofeedIDso existing dedupe keeps working;transformItemsto rewriteindices[i].indexName := indices[i].indexIdfor feeds-mode, sinceconnectAutocompleteotherwise overwritesindexNamewithresults.index(unreliable forFeedContainer-derived results).feeds()widget — the full widget lazily callsparentRef.addWidgets(feedContainer)on render, which fires a redundantscheduleLocalSearchon the isolated Index (3 composition requests at launch instead of 2). Since feeds-mode requires the user to declare all feedIDs upfront, we pre-register the containers as part of the initial widget list.connectFeeds(noop, noop)remains for itsinit-timehydrateFeedsFromInitialResultsIfNeededcall (SSR).<Feeds>+hydrateFeedsFromInitialResultsIfNeededpath added in feat(instantsearch): SSR support for composition multifeed #6975. No new SSR surface.examples/react/autocomplete-multifeedandexamples/js/autocomplete-multifeed.connectFeeds/FeedContainerinto the autocomplete path, pushinginstantsearch.jsjust over the prior 123 kB / 256.5 kB ceilings.Not in scope
vue-instantsearch'sAutocomplete.vuetargets the historical autocomplete library, not IS-based rendering).compositionIDprop on the autocomplete (inherits the outer<InstantSearch>'s for v1; separate ID needs helper-layer changes).searchParameters(Composition API sends one set per request)._feedOrderonly;transformFeedsnot plumbed through).Test plan
Both example apps are preconfigured against a demo composition — just boot and type. From the repo root:
🤖 Generated with Claude Code