Skip to content

feat(chat): customizable loader placement, rendering and timing - #7171

Merged
anjola-adeuyi merged 7 commits into
masterfrom
refactor/chat-loader-states
Aug 21, 2026
Merged

feat(chat): customizable loader placement, rendering and timing#7171
anjola-adeuyi merged 7 commits into
masterfrom
refactor/chat-loader-states

Conversation

@shaejaz

@shaejaz shaejaz commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

Why

The chat loader was effectively fixed in place and hardcoded: it could only render at the end of the messages list, its display logic lived in the UI component with no way to influence it, the suggestions row appeared out of nothing at the end of a turn, and it visibly popped in and out mid-answer.

That last one had two causes. Parts that render nothing still answered for the turn's progress — a trailing data-* payload, a text part created by text-start before its first delta — which findLastProgressPart now makes transparent. And the remaining sub-perceptual toggles are absorbed by timing: a loader returning mid-turn waits out loaderShowDelay, a visible one holds for loaderMinDuration, and honest long waits are untouched. Visibility is derived during render rather than from an effect, which would land a frame after the state it announces.

API

ChatMessages, forwarded by both the JS widget and React <Chat>:

Prop Type Default
loaderPosition 'messages-end' | 'message-inline' 'messages-end'
shouldShowLoader (ctx: ChatLoaderContext & { defaultValue: boolean }) => boolean
loaderShowDelay number (ms) 250
loaderMinDuration number (ms) 200

translations.loaderText widens from string to also accept (ctx: ChatLoaderContext) => string, and loaderComponent now receives the turn context (status, phase, message, messages, tools) plus inline. shouldShowLoader gets the built-in decision as defaultValue so an override narrows it instead of reimplementing it. ChatMessage gains loaderElement, so a custom messageComponent can place the loader itself.

New exported types: ChatLoaderPosition, ChatLoaderPhase ('submitted' | 'tool' | 'reasoning' | 'thinking'), ChatLoaderContext.

Alongside: aria-busy on the messages log, a loader fade-in with a prefers-reduced-motion override and no exit animation, and an inline loader variant without the message chrome.

Suggestions

ChatPromptSuggestions gains isLoading, skeletonCount and classNames.skeleton / classNames.skeletonItem, mirroring standalone PromptSuggestions; it also filters blank suggestions and ignores clicks while loading. connectChat exposes suggestionsStatus ('idle' | 'loading') to drive it, inferring "still coming" from a running turn with no data-suggestions part yet, gated on evidence that suggestions are expected at all (metadata.suggestionsEnabled, or an earlier turn in the conversation produced some) so an agent that never sends them shows no placeholder.

@codacy-production

codacy-production Bot commented Aug 18, 2026

Copy link
Copy Markdown

Up to standards ✅

🟢 Issues 0 issues

Results:
0 new issues

View in Codacy

🟢 Metrics 85 complexity

Metric Results
Complexity 85

View in Codacy

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

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR enhances the Chat widget across the connector, UI components, and framework wrappers (InstantSearch.js + React) by making loader behavior customizable (placement, timing, and rendering context) and by adding a “suggestions loading” state with skeleton placeholders.

Changes:

  • Add loader customization options (position, show override, show delay, min duration) and richer loader context (status/phase/message/tools), plus inline loader rendering.
  • Introduce suggestionsStatus in connectChat and propagate it to UI to drive suggestion skeleton placeholders and click-guarding while loading.
  • Add supporting CSS (loader fade-in, inline loader styling, suggestion skeleton pills) and expand/adjust test coverage (UI + common + connector tests).

Reviewed changes

Copilot reviewed 17 out of 17 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
tests/common/widgets/chat/options.tsx Adds common widget tests for loader behavior (data-part trailing) and loader customization, plus suggestions placeholders behavior.
packages/react-instantsearch/src/widgets/Chat.tsx Threads new loader props and suggestionsStatus into the React <Chat /> wrapper.
packages/instantsearch.js/src/widgets/chat/chat.tsx Threads new loader props and suggestionsStatus into the InstantSearch.js widget renderer/templates.
packages/instantsearch.js/src/connectors/chat/connectChat.ts Adds suggestionsStatus derivation logic and exposes it via render state.
packages/instantsearch.js/src/connectors/chat/tests/connectChat-test.ts Adds unit tests covering suggestionsStatus state derivation.
packages/instantsearch.css/src/components/chat/_chat-suggestions.scss Styles ChatPromptSuggestions skeleton placeholders.
packages/instantsearch.css/src/components/chat/_chat-message-loader.scss Adds loader enter animation, reduced-motion override, and inline loader styling.
packages/instantsearch-ui-components/src/lib/utils/chat.ts Adds utilities to ignore non-rendering parts for “progress” computations (findLastProgressPart).
packages/instantsearch-ui-components/src/components/chat/types.ts Introduces exported loader-related types (ChatLoaderPosition, ChatLoaderPhase, ChatLoaderContext).
packages/instantsearch-ui-components/src/components/chat/ChatPromptSuggestions.tsx Adds loading/skeleton rendering, blank filtering, and click suppression while loading.
packages/instantsearch-ui-components/src/components/chat/ChatMessages.tsx Implements loader visibility smoothing, inline loader placement, loader context/phase, aria-busy, and “pending suggestions” mounting.
packages/instantsearch-ui-components/src/components/chat/ChatMessageLoader.tsx Extends loader props to include turn context and adds inline rendering variant.
packages/instantsearch-ui-components/src/components/chat/ChatMessage.tsx Adds loaderElement slot to render an inline loader under message parts.
packages/instantsearch-ui-components/src/components/chat/Chat.tsx Wires suggestions loading into ChatMessages and updates hook wiring passed to the factory component.
packages/instantsearch-ui-components/src/components/chat/tests/ChatPromptSuggestions.test.tsx Adds tests for skeleton rendering, blank filtering, and click suppression while loading.
packages/instantsearch-ui-components/src/components/chat/tests/ChatMessages.test.tsx Adds extensive tests for loader visibility smoothing, context passing, inline rendering, and pending suggestions.
packages/instantsearch-ui-components/src/components/chat/tests/Chat.test.tsx Updates factory wiring to include the new hooks passed through to ChatMessages.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread packages/instantsearch-ui-components/src/components/chat/ChatMessages.tsx Outdated
@pkg-pr-new

pkg-pr-new Bot commented Aug 18, 2026

Copy link
Copy Markdown
More templates

algoliasearch-helper

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

instantsearch-ui-components

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

instantsearch.css

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

instantsearch.js

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

react-instantsearch

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

react-instantsearch-core

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

react-instantsearch-nextjs

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

react-instantsearch-router-nextjs

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

vue-instantsearch

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

commit: e7631e0

@github-actions

github-actions Bot commented Aug 18, 2026

Copy link
Copy Markdown

Size Change: +6.04 kB (+0.49%)

Total Size: 1.24 MB

📦 View Changed
Filename Size Change
packages/instantsearch.css/components/chat-min.css 6.45 kB +86 B (+1.35%)
packages/instantsearch.css/components/chat.css 6.91 kB +86 B (+1.26%)
packages/instantsearch.css/themes/algolia-min.css 10.9 kB +97 B (+0.9%)
packages/instantsearch.css/themes/algolia.css 11.6 kB +103 B (+0.9%)
packages/instantsearch.css/themes/nova-min.css 11 kB +110 B (+1.01%)
packages/instantsearch.css/themes/nova.css 11.7 kB +100 B (+0.86%)
packages/instantsearch.css/themes/satellite-min.css 11.8 kB +106 B (+0.91%)
packages/instantsearch.css/themes/satellite.css 12.7 kB +107 B (+0.85%)
packages/instantsearch.js/dist/instantsearch.development.js 309 kB +3.01 kB (+0.98%)
packages/instantsearch.js/dist/instantsearch.production.min.js 144 kB +1.07 kB (+0.75%)
packages/react-instantsearch-core/dist/umd/ReactInstantSearchCore.min.js 68.9 kB +101 B (+0.15%)
packages/react-instantsearch/dist/umd/ReactInstantSearch.min.js 116 kB +1.06 kB (+0.93%)
ℹ️ View Unchanged
Filename Size Change
packages/algolia-experiences/dist/algolia-experiences.development.js 179 kB 0 B
packages/algolia-experiences/dist/algolia-experiences.production.min.js 81.4 kB 0 B
packages/algoliasearch-helper/dist/algoliasearch.helper.js 44.3 kB 0 B
packages/algoliasearch-helper/dist/algoliasearch.helper.min.js 13.9 kB -1 B (-0.01%)
packages/instantsearch.css/components/ai-mode-button.css 1.75 kB 0 B
packages/instantsearch.css/components/autocomplete-min.css 4.31 kB 0 B
packages/instantsearch.css/components/autocomplete.css 4.66 kB 0 B
packages/instantsearch.css/components/button.css 1.96 kB 0 B
packages/instantsearch.css/components/filter-suggestions.css 1.88 kB 0 B
packages/instantsearch.css/themes/reset-min.css 1.3 kB 0 B
packages/instantsearch.css/themes/reset.css 1.38 kB 0 B
packages/vue-instantsearch/vue2/cjs/index.js 19.9 kB 0 B
packages/vue-instantsearch/vue2/umd/index.js 73.4 kB -6 B (-0.01%)
packages/vue-instantsearch/vue3/cjs/index.js 20.6 kB 0 B
packages/vue-instantsearch/vue3/umd/index.js 73.8 kB -5 B (-0.01%)

compressed-size-action

@shaejaz
shaejaz marked this pull request as ready for review August 20, 2026 09:44
@shaejaz
shaejaz requested a lite review from Copilot August 20, 2026 09:45

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 17 out of 17 changed files in this pull request and generated no new comments.

Suppressed comments (2)

packages/instantsearch-ui-components/src/components/chat/ChatPromptSuggestions.tsx:66

  • classNames.skeleton is declared in ChatPromptSuggestionsClassNames but never applied, so consumers can’t style the loading (skeleton) state via this prop.
    return (
      <div className={cx('ais-ChatPromptSuggestions', classNames.root)}>
        {isLoading && visibleSuggestions.length === 0

packages/instantsearch-ui-components/src/components/chat/ChatMessages.tsx:752

  • aria-busy is currently tied to showLoader (the smoothed/possibly delayed visibility). If the loader is delayed/hidden (e.g. via shouldShowLoader), the log is still updating while aria-busy stays unset. Consider deriving aria-busy from the actual turn activity (submitted/streaming) instead of loader visibility.
      <div
        {...props}
        className={cx(cssClasses.root, props.className)}
        role="log"
        aria-live="polite"
        aria-busy={showLoader ? 'true' : undefined}
      >

@shaejaz
shaejaz requested review from a team, afrencalg and anjola-adeuyi and a lite review from Copilot and removed request for a team August 20, 2026 12:35

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 18 out of 18 changed files in this pull request and generated no new comments.

Suppressed comments (2)

packages/instantsearch-ui-components/src/components/chat/ChatMessages.tsx:761

  • loaderContext.message is set to lastMessage even when the last message is from the user (common in status === 'submitted'). The type docs say this should be “the message the loader belongs to, when there is one”, so passing a user message here can confuse custom loaders (e.g., expecting an assistant message or no message when the loader is rendered as its own row).
    const loaderContext: ChatLoaderContext<TMessage> = {
      ...context,
      phase: getLoaderPhase(status, lastMessage, showReasoning),
      message: lastMessage,
    };

packages/instantsearch-ui-components/src/components/chat/ChatMessages.tsx:506

  • useLoaderVisibility mutates stateRef.current during render to derive isVisible. This pattern is not safe under React 18 concurrent rendering (renders can be started, paused, or abandoned), which can lead to inconsistent loader visibility timing when ChatMessages is used through the React wrapper.

This issue also appears on line 757 of the same file.

    const state = stateRef.current;
    const now = Date.now();

    if (!isTurnActive) {
      state.hasHiddenInTurn = false;
    }

Comment thread packages/instantsearch-ui-components/src/components/chat/ChatMessages.tsx Outdated
@shaejaz
shaejaz requested a review from anjola-adeuyi August 20, 2026 16:40
@anjola-adeuyi
anjola-adeuyi merged commit 073abbd into master Aug 21, 2026
15 checks passed
@anjola-adeuyi
anjola-adeuyi deleted the refactor/chat-loader-states branch August 21, 2026 07:28
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.

3 participants