Skip to content

feat: add multifeed composition support to EXPERIMENTAL_Autocomplete#6993

Open
e-krebs wants to merge 49 commits into
masterfrom
feat/composition-multifeed-layer6-autocomplete
Open

feat: add multifeed composition support to EXPERIMENTAL_Autocomplete#6993
e-krebs wants to merge 49 commits into
masterfrom
feat/composition-multifeed-layer6-autocomplete

Conversation

@e-krebs
Copy link
Copy Markdown
Contributor

@e-krebs e-krebs commented Apr 24, 2026

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

  • New feeds prop/option on EXPERIMENTAL_Autocomplete (React) and EXPERIMENTAL_autocomplete (JS). Mutually exclusive with the existing indices — enforced at compile time via a discriminated union (AutocompleteIndicesProps | AutocompleteFeedsProps) and at runtime as a defense-in-depth throw.
  • Feed-aware showQuerySuggestions / showPromptSuggestions — takes feedID (instead of indexName) when the widget is in feeds-mode; the corresponding section is sourced from that feed in the same response.
  • Feeds-mode widget tree — wraps the panel in an isolated composition <Index EXPERIMENTAL_isolated indexName={compositionID}>, with one <Feeds renderFeed={() => null}> child (React) or pre-registered FeedContainers driven by connectFeeds(noop, noop) (JS). Preserves query isolation: typing in the autocomplete never refreshes main-page widgets.
  • InnerAutocomplete / connectAutocomplete / createAutocompleteStorage / instantsearch-ui-components stay unchanged — three normalization points in the outer widget make feeds look like indices downstream:
    1. build indicesConfig from feeds[] with indexName = feedID;
    2. re-expose showQuerySuggestions / showPromptSuggestions with indexName set to feedID so existing dedupe keeps working;
    3. wrap transformItems to rewrite indices[i].indexName := indices[i].indexId for feeds-mode, since connectAutocomplete otherwise overwrites indexName with results.index (unreliable for FeedContainer-derived results).
  • JS: pre-register FeedContainers instead of using the full feeds() widget — the full widget lazily calls parentRef.addWidgets(feedContainer) on render, which fires a redundant scheduleLocalSearch on 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 its init-time hydrateFeedsFromInitialResultsIfNeeded call (SSR).
  • SSR works for free — rides the existing <Feeds> + hydrateFeedsFromInitialResultsIfNeeded path added in feat(instantsearch): SSR support for composition multifeed #6975. No new SSR surface.
  • Two example appsexamples/react/autocomplete-multifeed and examples/js/autocomplete-multifeed.
  • Bundlesize bump — the feeds-mode branch pulls connectFeeds / FeedContainer into the autocomplete path, pushing instantsearch.js just over the prior 123 kB / 256.5 kB ceilings.

Not in scope

  • Vue (vue-instantsearch's Autocomplete.vue targets the historical autocomplete library, not IS-based rendering).
  • A dedicated compositionID prop on the autocomplete (inherits the outer <InstantSearch>'s for v1; separate ID needs helper-layer changes).
  • Per-feed searchParameters (Composition API sends one set per request).
  • Client-side panel reordering (server-side _feedOrder only; transformFeeds not plumbed through).

Test plan

  • CI passes (type-check, unit + integration tests, bundlesize, lint)

Both example apps are preconfigured against a demo composition — just boot and type. From the repo root:

# React
yarn workspace example-react-instantsearch-autocomplete-multifeed dev
# JS
yarn workspace example-instantsearch-autocomplete-multifeed dev

🤖 Generated with Claude Code

e-krebs and others added 17 commits April 8, 2026 15:32
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>
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>
@codacy-production
Copy link
Copy Markdown

codacy-production Bot commented Apr 24, 2026

Up to standards ✅

🟢 Issues 0 issues

Results:
0 new issues

View in Codacy

🟢 Metrics 217 complexity · 0 duplication

Metric Results
Complexity 217
Duplication 0

View in Codacy

TIP This summary will be updated as you push new changes.

@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new Bot commented Apr 24, 2026

More templates

algoliasearch-helper

npm i https://pkg.pr.new/algolia/instantsearch/algoliasearch-helper@6993

instantsearch-ui-components

npm i https://pkg.pr.new/algolia/instantsearch/instantsearch-ui-components@6993

instantsearch.css

npm i https://pkg.pr.new/algolia/instantsearch/instantsearch.css@6993

instantsearch.js

npm i https://pkg.pr.new/algolia/instantsearch/instantsearch.js@6993

react-instantsearch

npm i https://pkg.pr.new/algolia/instantsearch/react-instantsearch@6993

react-instantsearch-core

npm i https://pkg.pr.new/algolia/instantsearch/react-instantsearch-core@6993

react-instantsearch-nextjs

npm i https://pkg.pr.new/algolia/instantsearch/react-instantsearch-nextjs@6993

react-instantsearch-router-nextjs

npm i https://pkg.pr.new/algolia/instantsearch/react-instantsearch-router-nextjs@6993

vue-instantsearch

npm i https://pkg.pr.new/algolia/instantsearch/vue-instantsearch@6993

commit: 9680560

@e-krebs e-krebs force-pushed the feat/composition-multifeed-layer6-autocomplete branch from 8081d92 to e086c8e Compare April 24, 2026 15:48
@e-krebs e-krebs marked this pull request as ready for review April 24, 2026 16:10
@Haroenv Haroenv requested review from a team, FabienMotte and afrencalg and removed request for a team April 27, 2026 07:41
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];
}

Copy link
Copy Markdown
Contributor Author

@e-krebs e-krebs Apr 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not super happy about this :/
but I don't have a better solution for now

@e-krebs e-krebs force-pushed the feat/composition-multifeed-layer5-remove-examples branch from 3e08a2b to 6c8f487 Compare April 27, 2026 09:23
@e-krebs e-krebs force-pushed the feat/composition-multifeed-layer6-autocomplete branch from e086c8e to a4b206d Compare April 27, 2026 09:23
e-krebs and others added 4 commits April 28, 2026 18:34
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>
@e-krebs e-krebs force-pushed the feat/composition-multifeed-layer5-remove-examples branch from 3be039f to 7fd46ae Compare April 28, 2026 16:36
@e-krebs e-krebs force-pushed the feat/composition-multifeed-layer6-autocomplete branch from 0fa6aea to 7f95acb Compare April 28, 2026 16:36
e-krebs and others added 8 commits April 28, 2026 18:43
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>
@e-krebs e-krebs force-pushed the feat/composition-multifeed-layer5-remove-examples branch from 7fd46ae to f7ce024 Compare April 28, 2026 16:45
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>
@e-krebs e-krebs force-pushed the feat/composition-multifeed-layer6-autocomplete branch from 7f95acb to ef90a85 Compare April 28, 2026 16:47
@FabienMotte FabienMotte requested a review from Copilot April 29, 2026 15:56
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 feeds option/prop (mutually exclusive with indices) and normalize feeds to look like indices downstream (including showQuerySuggestions / showPromptSuggestions behavior).
  • Add feeds-mode widget trees (React via <Feeds ... />; JS via pre-registered FeedContainers + 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.

Comment thread packages/instantsearch.js/src/widgets/autocomplete/autocomplete.tsx
Comment thread packages/react-instantsearch/src/widgets/Autocomplete.tsx
Comment thread examples/react/autocomplete-multifeed/App.tsx
Comment thread examples/js/autocomplete-multifeed/src/app.ts
Base automatically changed from feat/composition-multifeed-layer5-remove-examples to feat/composition-multifeed April 30, 2026 16:48
@drodriguln drodriguln force-pushed the feat/composition-multifeed branch from 61c8811 to cab5256 Compare April 30, 2026 17:08
Base automatically changed from feat/composition-multifeed to master May 4, 2026 08:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants