Skip to content

feat(composition): add Feeds widget to support multifeed#7004

Merged
Haroenv merged 8 commits intomasterfrom
feat/composition-multifeed
May 4, 2026
Merged

feat(composition): add Feeds widget to support multifeed#7004
Haroenv merged 8 commits intomasterfrom
feat/composition-multifeed

Conversation

@drodriguln
Copy link
Copy Markdown
Contributor

@drodriguln drodriguln commented Apr 30, 2026

Summary

Adds the Feeds widget for JS, React, and Vue, which provides support for multifeed compositions. Implements the changes from the following PRs which have been merged into this one. This does not include changes for Autocomplete--that will come later.

#6954
#6964
#6969
#6975
#6977

Result

Reference the linked PRs for details, but otherwise the CI and preview should be ✅ .

@codacy-production
Copy link
Copy Markdown

codacy-production Bot commented Apr 30, 2026

Up to standards ✅

🟢 Issues 0 issues

Results:
0 new issues

View in Codacy

🟢 Metrics 455 complexity · 62 duplication

Metric Results
Complexity 455
Duplication 62

View in Codacy

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

e-krebs and others added 4 commits April 30, 2026 11:05
* feat(helper): support multifeed composition responses

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>

* chore: bump bundlesize limits for multifeed helper changes

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* refactor(helper): omit queriesCount for composition instead of using 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>

* refactor(helper): remove composition type definitions, defer to layer 2

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>

* refactor(helper): use lastResults.feeds array instead of _feedResults 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>

* fix(helper): avoid circular ref in multifeed lastResults

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>

* apply suggestions from code review

Co-authored-by: Haroen Viaene <hello@haroen.me>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: Haroen Viaene <hello@haroen.me>
…tions (#6964)

* feat(helper): support multifeed composition responses

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>

* chore: bump bundlesize limits for multifeed helper changes

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* refactor(helper): omit queriesCount for composition instead of using 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>

* refactor(helper): remove composition type definitions, defer to layer 2

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>

* refactor(helper): use lastResults.feeds array instead of _feedResults 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>

* fix(helper): avoid circular ref in multifeed lastResults

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>

* apply suggestions from code review

Co-authored-by: Haroen Viaene <hello@haroen.me>

* refactor(instantsearch): extract storeRenderState to shared util

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>

* refactor(instantsearch): centralize index widget types, widen for feedContainer

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>

* fix(metadata): use isIndexWidget to recurse into feed containers

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>

* feat(instantsearch): add FeedContainer for per-feed widget subtrees

Made-with: Cursor

* feat(instantsearch): add connectFeeds connector for multifeed compositions

Made-with: Cursor

* test(instantsearch): add unit tests for FeedContainer and connectFeeds

Made-with: Cursor

* chore: bump bundlesize limits for multifeed connector

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(instantsearch): prevent double-init in FeedContainer with initialized 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>

* refactor(instantsearch): simplify connectFeeds to stateless feedIDs computation

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>

* fix(instantsearch): fix lint errors in feeds test helpers

- 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>

* review: use setState

* feedback: chain state when removing widgets (same as in dispose below)

* feedback: register FeedsWidgetDescription in IndexRenderState

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: Haroen Viaene <hello@haroen.me>
Co-authored-by: Dan Rodriguez <drodriguln@icloud.com>
* feat(helper): support multifeed composition responses

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>

* chore: bump bundlesize limits for multifeed helper changes

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* refactor(helper): omit queriesCount for composition instead of using 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>

* refactor(helper): remove composition type definitions, defer to layer 2

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>

* refactor(helper): use lastResults.feeds array instead of _feedResults 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>

* fix(helper): avoid circular ref in multifeed lastResults

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>

* apply suggestions from code review

Co-authored-by: Haroen Viaene <hello@haroen.me>

* refactor(instantsearch): extract storeRenderState to shared util

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>

* refactor(instantsearch): centralize index widget types, widen for feedContainer

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>

* fix(metadata): use isIndexWidget to recurse into feed containers

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>

* feat(instantsearch): add FeedContainer for per-feed widget subtrees

Made-with: Cursor

* feat(instantsearch): add connectFeeds connector for multifeed compositions

Made-with: Cursor

* test(instantsearch): add unit tests for FeedContainer and connectFeeds

Made-with: Cursor

* chore: bump bundlesize limits for multifeed connector

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(instantsearch): prevent double-init in FeedContainer with initialized 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>

* refactor(instantsearch): simplify connectFeeds to stateless feedIDs computation

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>

* fix(instantsearch): fix lint errors in feeds test helpers

- 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>

* review: use setState

* feedback: chain state when removing widgets (same as in dispose below)

* feedback: register FeedsWidgetDescription in IndexRenderState

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(instantsearch): add feeds widget for vanilla JS

Add `feeds()` widget that creates per-feed DOM containers and manages
FeedContainer lifecycle. Each feed gets a scoped `<div class="ais-Feeds-feed">`
and its own FeedContainer registered with the parent index.

- DOM creation, reuse, reorder, and cleanup per feed
- Deferred removal with coalesced timer (matches connectDynamicWidgets pattern)
- Dispose merges active + pending containers for clean teardown
- Exported from both main and UMD entrypoints
- 11 unit tests + 1 integration test with real search lifecycle

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(react-instantsearch): add Feeds component and useFeeds hook

Add `<Feeds>` component that creates per-feed `IndexContext.Provider` scopes,
and `useFeeds` hook wrapping `connectFeeds` via `useConnector`.

- FeedContainer creation + registration fused in render body for SSR compat
- Deferred removal with coalesced timer (single ref pattern from useWidget)
- Unmount cleanup merges active + pending containers
- 5 unit tests + 2 integration tests (real InstantSearch + SSR)
- Exported from react-instantsearch-core

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(vue-instantsearch): add AisFeeds component

Add `<ais-feeds>` component with per-feed `AisFeedProvider` scoping via
Vue's provide/inject for `$_ais_getParentIndex`.

- Container reconciliation in watcher (side-effect-free render)
- Deferred removal with coalesced timer (single timer pattern)
- Unmount cleanup merges active + pending containers
- 5 unit tests + 1 integration test with real InstantSearch
- Exported as AisFeeds from vue-instantsearch
- Updated mock and index tests for compositionID support

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: fix import ordering lint errors across Layer 3 files

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(instantsearch): pass renderOptions to feedContainer.render() for type safety

FeedContainer.render() ignores the argument internally but IndexWidget type
requires it. Pass renderOptionsRef to satisfy TypeScript.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* temp: add examples

* fix: exclude composition examples from v4 type-check

@algolia/composition doesn't exist under algoliasearch v4.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* refactor(react-instantsearch-core): rename Feeds children render-prop to renderFeed

Switch from a children render function to a dedicated `renderFeed` prop with
an object argument `({ feedID })`. Keeps the API consistent with other React
InstantSearch components (`hitComponent`, `itemComponent`, `bannerComponent`),
which all use explicit, non-children customization props.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(vue-instantsearch): use indexOf instead of findIndex in mock removeWidgets

findIndex expects a predicate function, not the widget object — the call
threw at runtime the first time removeWidgets was invoked.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix: copilot feedback

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: Haroen Viaene <hello@haroen.me>
Co-authored-by: Dan Rodriguez <daniel.rodriguez@algolia.com>
Co-authored-by: Dan Rodriguez <drodriguln@icloud.com>
* feat(helper): support multifeed composition responses

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>

* chore: bump bundlesize limits for multifeed helper changes

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* refactor(helper): omit queriesCount for composition instead of using 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>

* refactor(helper): remove composition type definitions, defer to layer 2

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>

* refactor(helper): use lastResults.feeds array instead of _feedResults 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>

* fix(helper): avoid circular ref in multifeed lastResults

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>

* apply suggestions from code review

Co-authored-by: Haroen Viaene <hello@haroen.me>

* refactor(instantsearch): extract storeRenderState to shared util

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>

* refactor(instantsearch): centralize index widget types, widen for feedContainer

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>

* fix(metadata): use isIndexWidget to recurse into feed containers

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>

* feat(instantsearch): add FeedContainer for per-feed widget subtrees

Made-with: Cursor

* feat(instantsearch): add connectFeeds connector for multifeed compositions

Made-with: Cursor

* test(instantsearch): add unit tests for FeedContainer and connectFeeds

Made-with: Cursor

* chore: bump bundlesize limits for multifeed connector

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(instantsearch): prevent double-init in FeedContainer with initialized 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>

* refactor(instantsearch): simplify connectFeeds to stateless feedIDs computation

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>

* fix(instantsearch): fix lint errors in feeds test helpers

- 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>

* review: use setState

* feedback: chain state when removing widgets (same as in dispose below)

* feedback: register FeedsWidgetDescription in IndexRenderState

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(instantsearch): add feeds widget for vanilla JS

Add `feeds()` widget that creates per-feed DOM containers and manages
FeedContainer lifecycle. Each feed gets a scoped `<div class="ais-Feeds-feed">`
and its own FeedContainer registered with the parent index.

- DOM creation, reuse, reorder, and cleanup per feed
- Deferred removal with coalesced timer (matches connectDynamicWidgets pattern)
- Dispose merges active + pending containers for clean teardown
- Exported from both main and UMD entrypoints
- 11 unit tests + 1 integration test with real search lifecycle

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(react-instantsearch): add Feeds component and useFeeds hook

Add `<Feeds>` component that creates per-feed `IndexContext.Provider` scopes,
and `useFeeds` hook wrapping `connectFeeds` via `useConnector`.

- FeedContainer creation + registration fused in render body for SSR compat
- Deferred removal with coalesced timer (single ref pattern from useWidget)
- Unmount cleanup merges active + pending containers
- 5 unit tests + 2 integration tests (real InstantSearch + SSR)
- Exported from react-instantsearch-core

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(vue-instantsearch): add AisFeeds component

Add `<ais-feeds>` component with per-feed `AisFeedProvider` scoping via
Vue's provide/inject for `$_ais_getParentIndex`.

- Container reconciliation in watcher (side-effect-free render)
- Deferred removal with coalesced timer (single timer pattern)
- Unmount cleanup merges active + pending containers
- 5 unit tests + 1 integration test with real InstantSearch
- Exported as AisFeeds from vue-instantsearch
- Updated mock and index tests for compositionID support

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: fix import ordering lint errors across Layer 3 files

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(instantsearch): pass renderOptions to feedContainer.render() for type safety

FeedContainer.render() ignores the argument internally but IndexWidget type
requires it. Pass renderOptionsRef to satisfy TypeScript.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* temp: add examples

* fix: exclude composition examples from v4 type-check

@algolia/composition doesn't exist under algoliasearch v4.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* refactor(react-instantsearch-core): rename Feeds children render-prop to renderFeed

Switch from a children render function to a dedicated `renderFeed` prop with
an object argument `({ feedID })`. Keeps the API consistent with other React
InstantSearch components (`hitComponent`, `itemComponent`, `bannerComponent`),
which all use explicit, non-children customization props.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(vue-instantsearch): use indexOf instead of findIndex in mock removeWidgets

findIndex expects a predicate function, not the widget object — the call
threw at runtime the first time removeWidgets was invoked.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* refactor(instantsearch): extract isTwoPassWidget utility

Replace inline `$$type === 'ais.dynamicWidgets'` checks with a shared
`isTwoPassWidget` predicate. This prepares for the feeds widget which
also requires a two-pass SSR cycle, and fixes a missing `shouldRefetch ||`
in InitializePromise.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(instantsearch): add SSR support for composition multifeed

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>

* test(instantsearch): add composition multifeed SSR integration test

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>

* chore: add Next.js App Router composition example

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>

* refactor(react-instantsearch-core): adapt Feeds usages to renderFeed 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>

* chore: bump bundlesize limit for SSR multifeed

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>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: Haroen Viaene <hello@haroen.me>
Co-authored-by: Dan Rodriguez <drodriguln@icloud.com>
@drodriguln drodriguln force-pushed the feat/composition-multifeed branch from 61c8811 to cab5256 Compare April 30, 2026 17:08
@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new Bot commented Apr 30, 2026

More templates

algoliasearch-helper

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

instantsearch-ui-components

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

instantsearch.css

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

instantsearch.js

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

react-instantsearch

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

react-instantsearch-core

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

react-instantsearch-nextjs

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

react-instantsearch-router-nextjs

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

vue-instantsearch

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

commit: fd390d6

e-krebs and others added 4 commits April 30, 2026 11:13
* feat(helper): support multifeed composition responses

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>

* chore: bump bundlesize limits for multifeed helper changes

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* refactor(helper): omit queriesCount for composition instead of using 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>

* refactor(helper): remove composition type definitions, defer to layer 2

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>

* refactor(helper): use lastResults.feeds array instead of _feedResults 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>

* fix(helper): avoid circular ref in multifeed lastResults

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>

* apply suggestions from code review

Co-authored-by: Haroen Viaene <hello@haroen.me>

* refactor(instantsearch): extract storeRenderState to shared util

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>

* refactor(instantsearch): centralize index widget types, widen for feedContainer

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>

* fix(metadata): use isIndexWidget to recurse into feed containers

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>

* feat(instantsearch): add FeedContainer for per-feed widget subtrees

Made-with: Cursor

* feat(instantsearch): add connectFeeds connector for multifeed compositions

Made-with: Cursor

* test(instantsearch): add unit tests for FeedContainer and connectFeeds

Made-with: Cursor

* chore: bump bundlesize limits for multifeed connector

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(instantsearch): prevent double-init in FeedContainer with initialized 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>

* refactor(instantsearch): simplify connectFeeds to stateless feedIDs computation

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>

* fix(instantsearch): fix lint errors in feeds test helpers

- 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>

* review: use setState

* feedback: chain state when removing widgets (same as in dispose below)

* feedback: register FeedsWidgetDescription in IndexRenderState

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(instantsearch): add feeds widget for vanilla JS

Add `feeds()` widget that creates per-feed DOM containers and manages
FeedContainer lifecycle. Each feed gets a scoped `<div class="ais-Feeds-feed">`
and its own FeedContainer registered with the parent index.

- DOM creation, reuse, reorder, and cleanup per feed
- Deferred removal with coalesced timer (matches connectDynamicWidgets pattern)
- Dispose merges active + pending containers for clean teardown
- Exported from both main and UMD entrypoints
- 11 unit tests + 1 integration test with real search lifecycle

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(react-instantsearch): add Feeds component and useFeeds hook

Add `<Feeds>` component that creates per-feed `IndexContext.Provider` scopes,
and `useFeeds` hook wrapping `connectFeeds` via `useConnector`.

- FeedContainer creation + registration fused in render body for SSR compat
- Deferred removal with coalesced timer (single ref pattern from useWidget)
- Unmount cleanup merges active + pending containers
- 5 unit tests + 2 integration tests (real InstantSearch + SSR)
- Exported from react-instantsearch-core

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(vue-instantsearch): add AisFeeds component

Add `<ais-feeds>` component with per-feed `AisFeedProvider` scoping via
Vue's provide/inject for `$_ais_getParentIndex`.

- Container reconciliation in watcher (side-effect-free render)
- Deferred removal with coalesced timer (single timer pattern)
- Unmount cleanup merges active + pending containers
- 5 unit tests + 1 integration test with real InstantSearch
- Exported as AisFeeds from vue-instantsearch
- Updated mock and index tests for compositionID support

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: fix import ordering lint errors across Layer 3 files

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(instantsearch): pass renderOptions to feedContainer.render() for type safety

FeedContainer.render() ignores the argument internally but IndexWidget type
requires it. Pass renderOptionsRef to satisfy TypeScript.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* temp: add examples

* fix: exclude composition examples from v4 type-check

@algolia/composition doesn't exist under algoliasearch v4.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* refactor(react-instantsearch-core): rename Feeds children render-prop to renderFeed

Switch from a children render function to a dedicated `renderFeed` prop with
an object argument `({ feedID })`. Keeps the API consistent with other React
InstantSearch components (`hitComponent`, `itemComponent`, `bannerComponent`),
which all use explicit, non-children customization props.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(vue-instantsearch): use indexOf instead of findIndex in mock removeWidgets

findIndex expects a predicate function, not the widget object — the call
threw at runtime the first time removeWidgets was invoked.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* refactor(instantsearch): extract isTwoPassWidget utility

Replace inline `$$type === 'ais.dynamicWidgets'` checks with a shared
`isTwoPassWidget` predicate. This prepares for the feeds widget which
also requires a two-pass SSR cycle, and fixes a missing `shouldRefetch ||`
in InitializePromise.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(instantsearch): add SSR support for composition multifeed

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>

* test(instantsearch): add composition multifeed SSR integration test

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>

* chore: add Next.js App Router composition example

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>

* refactor(react-instantsearch-core): adapt Feeds usages to renderFeed 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>

* chore: bump bundlesize limit for SSR multifeed

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>

* chore: remove internal composition examples

These examples reference an internal Algolia application and should not
be published.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: reapply removal commits after merge commit

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: Haroen Viaene <hello@haroen.me>
Co-authored-by: Dan Rodriguez <drodriguln@icloud.com>
Co-authored-by: Dan Rodriguez <daniel.rodriguez@algolia.com>
@drodriguln drodriguln marked this pull request as ready for review April 30, 2026 20:28
@Haroenv Haroenv changed the title feat: add Feeds widget to support multifeed compositions feat(composition): add Feeds widget to support multifeed May 4, 2026
@Haroenv Haroenv merged commit ecc40d9 into master May 4, 2026
15 checks passed
@Haroenv Haroenv deleted the feat/composition-multifeed branch 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