Skip to content

[chat] Add x-chat package family#21666

Merged
hasdfa merged 179 commits intomui:masterfrom
hasdfa:chatbox/v2
Apr 8, 2026
Merged

[chat] Add x-chat package family#21666
hasdfa merged 179 commits intomui:masterfrom
hasdfa:chatbox/v2

Conversation

@hasdfa
Copy link
Copy Markdown
Member

@hasdfa hasdfa commented Mar 9, 2026

Summary

This PR introduces the @mui/x-chat package family — a headless-first, Material-styled chat UI component suite for MUI X.

Architecture

Two-layer package design:

Package Role
@mui/x-chat-headless State management, adapter contract, normalized store, headless UI primitives — zero styling
@mui/x-chat Fully styled Material UI components with theming, localization, and a one-liner <ChatBox /> API

Note: A separate @mui/x-chat-unstyled package existed earlier in this branch but was consolidated into @mui/x-chat-headless to reduce indirection.


What's implemented

@mui/x-chat-headless — Core & primitives

State & runtime

  • Core entity typesChatMessage, ChatConversation, ChatUser, and related contracts
  • AI SDK-compatible message parts — text, reasoning, file, tool-call, tool-result, and source parts; stream chunk contracts (no AI SDK dependency — local type shapes only)
  • Type facade with augmentation@mui/x-chat/typesChatbox.* namespace for consumer extension
  • Adapter contractChatAdapter with sendMessage, listMessages, subscribe, and related methods
  • Normalized internal storeChatStore with controlled/uncontrolled state ownership layer
  • Computed selectorscreateSelector / createSelectorMemoized for derived state
  • Stream processorprocessStream with dedup, sequencing, abort handling, and chunk merging
  • ChatProvider — React context for store lifecycle management
  • Variant & density systemChatVariant, ChatDensity, ChatVariantProvider, ChatDensityProvider, useChatVariant, useChatDensity

Hooks

  • useChat — runtime hook wiring adapter, store, and stream processor
  • useChatComposer — manages draft text, send action, pending state, and attachment list
  • useChatStore — escape hatch for direct store access
  • useChatPartRenderer — pluggable part-level rendering system
  • useChatOnToolCall — tool approval flow callback
  • useChatStatus — chat lifecycle status
  • useMessage, useMessageIds, useConversation, useConversations — selector hooks

Headless UI primitives (unstyled, composition-first)

  • Chat layoutChatRoot, ChatLayout
  • ComposerComposerRoot, ComposerTextArea, ComposerSendButton, ComposerAttachButton, ComposerAttachmentList, ComposerToolbar, ComposerHelperText, ComposerLabel
  • Message listMessageListRoot, MessageListDateDivider
  • Message groupsMessageGroup with automatic grouping logic and pluggable GroupKeyFn
  • MessagesMessageRoot, MessageContent, MessageAvatar, MessageAuthorLabel, MessageMeta, MessageActions, MessageActionsMenu, default part renderers
  • Conversation listConversationListRoot, ConversationListItem
  • SuggestionsSuggestionsRoot, SuggestionItem
  • IndicatorsTypingIndicator, ScrollToBottomAffordance, UnreadMarker

@mui/x-chat — Styled Material components

  • ChatBox — one-liner API composing conversation + message list + composer into a single component
  • ChatConversationChatConversationHeader, ChatConversationTitle, ChatConversationSubtitle, ChatConversationHeaderInfo, ChatConversationHeaderActions
  • ChatConversationList — styled conversation sidebar
  • ChatComposerChatComposerTextArea, ChatComposerSendButton, ChatComposerAttachButton, ChatComposerAttachmentList, ChatComposerToolbar, ChatComposerHelperText, ChatComposerLabel
  • ChatMessageChatMessageAvatar, ChatMessageAuthorLabel, ChatMessageContent, ChatMessageMeta, ChatMessageInlineMeta, ChatMessageActions
  • ChatMessageSkeleton — loading placeholder
  • ChatMessageList — scrollable message list with auto-scroll support
  • ChatMessageSources — sources and citations display
  • ChatCodeBlock — syntax-highlighted code block renderer
  • ChatSuggestions — prompt suggestion chips
  • ChatConfirmation — tool approval confirmation UI
  • ChatIndicators — typing indicator and scroll affordance
  • AI part renderers — styled renderers for text, reasoning, tool-call, tool-result, source, and file parts
  • Theme foundation — design tokens, themeAugmentation, component-level styleOverrides and defaultProps
  • Locales — localization support

Documentation

Fully structured doc site covering:

Section Pages
Getting started Overview, Getting started, Quickstart
Basics ChatBox, Composer, Layout, Messages, Variants & density
Behavior Streaming, Suggestions, Attachments, Scrolling, Typing indicators, Error handling
Display Message appearance, Message actions, Loading & empty states, Message parts (text & markdown, code blocks, files & images, sources & citations, custom parts)
AI & agents Tool calling, Tool approval, Reasoning, Step tracking
Backend Adapters, Building an adapter, Controlled state, Real-time adapters
Customization Slots & composition, Styling, Tailwind, Unstyled usage, Headless usage, Localization
Demos AI assistant, Customer support, Team messaging
Multi-conversation (in progress)
Headless Adapter examples, Advanced store access, Composer, Controlled state, Conversation history, Message parts

Test coverage

44 test files across both packages covering store, selectors, stream processor, adapter integration, hooks, and component rendering — including tool approval flow, render isolation, and hydration tests.


Key design decisions

  • Array-first consumer ergonomics (messages[], conversations[]) with normalized internal state for performance
  • Bot-first UX — streaming, tool output, reasoning display, and rich card rendering are first-class concerns
  • Backend-agnostic — adapter contract works with any transport (REST, WebSocket, SSE)
  • Composition-first base API with a one-liner Material alternative (<ChatBox />)
  • Density + variant system — uniform compact/comfortable/spacious density and bubble/plain variant controls across all components

hasdfa added 16 commits March 9, 2026 10:49
Rename the core message entity types to ChatMessage and ChatMessagePart, and add open registry interfaces for message, conversation, and user metadata plus custom message parts, tool definitions, and data parts.

Re-export the new type surface through @mui/x-chat/types and add a compile-time test that demonstrates the short-term declaration-merging path via @mui/x-chat-headless/types while consuming the public Chatbox namespace facade.
Implement the SRV-34 message part type system in x-chat-headless, including text, reasoning, file, source, data, step-start, tool, and dynamic-tool parts plus typed tool invocation state and approval models.

Split augmentation registries into a dedicated type-registry module, keep registered tool and data keys strict once consumers augment those maps, and re-export the expanded surface through @mui/x-chat/types with public alias coverage and compile-time facade tests.
Add ChatMessageChunk union and ChatStreamEnvelope for the streaming
protocol. Extract shared registry helpers (ChatToolInput, ChatToolOutput,
ChatKnownToolName, etc.) into chat-type-helpers.ts so both message-part
and stream-chunk types can reference them. Extend the x-chat type facade
with sorted stream aliases and add type-level tests for stream types.
@hasdfa hasdfa self-assigned this Mar 10, 2026
@hasdfa hasdfa added type: new feature Expand the scope of the product to solve a new problem. scope: chatbox labels Mar 10, 2026
hasdfa added 11 commits March 11, 2026 16:35
- Export ChatOnToolCallPayload and ChatOnFinishPayload from the public
  barrel (were defined in chat-callbacks.ts but omitted from index.ts)
- Add typingByConversation state to ChatInternalState and ChatStore,
  with a setTypingUser(conversationId, userId, isTyping) mutation that
  no-ops when the value is unchanged
- Add chatSelectors.typingUserIds — memoized selector that derives the
  list of typing user IDs for a given conversation (defaults to the
  active conversation)
- Implement the typing realtime event handler in useChatController
  (was an explicit no-op/return)
- Expose typingUserIds via useChatStatus so consumers can render
  typing indicators without a separate hook
- Tests: update useChatStatus toEqual assertions to include
  typingUserIds; add typing isolation test in selectorHooks.test.tsx;
  add end-to-end typing realtime event test in useChat.test.tsx
…mples

- Stabilize messageIds/conversationIds references in controlled path to
  prevent unnecessary parent re-renders when only message bodies change
- Wrap MessageRow in React.memo in selector-driven-thread example so
  only the changed row re-renders
- Move render counter to useEffect to fix SSR hydration mismatch
- Remove raw UUID leak from retry button label in streaming-lifecycle
- Add online presence stat and "Sam goes offline" button in realtime example
@flaviendelangle
Copy link
Copy Markdown
Member

By the way, you can check the ReactStore class on Base UI which is the most up to date way of doing store management.
I need to use it on the Scheduler codebase one of these days. All the new Base UI components are using it.
You can use this as a reference: https://github.com/mui/base-ui-plus/pull/4

@hasdfa
Copy link
Copy Markdown
Member Author

hasdfa commented Apr 8, 2026

In terms of a11y in my opinion the messages should be one tabs top and users should be able to navigate up and down with arrow keys - imagine having lots of message, getting from the message input to the conversations for example to the all conversations sidebar in https://deploy-preview-21666--material-ui-x.netlify.app/x/react-chat/demos/ai-assistant/ will take infinite number of Shift + Tabs.

That sounds good, I will definitely add it in next version

hasdfa added 2 commits April 8, 2026 14:23
Ensures demo links resolve to the correct version for @mui/x-chat
and @mui/x-chat-headless, matching the pattern used by other packages.
@hasdfa
Copy link
Copy Markdown
Member Author

hasdfa commented Apr 8, 2026

By the way, you can check the ReactStore class on Base UI which is the most up to date way of doing store management.

Sure, I will definitely look into it, we may use it as default in this package later. I've just tried to go with existing MUI X approach there

hasdfa and others added 12 commits April 8, 2026 14:40
…t setup

- docs/data/chatApiPages.ts: fix MuiPage import path (docs/src/MuiPage → @mui/internal-core-docs/MuiPage)
- ChatProvider.test.tsx, useChatPartRenderer.test.tsx: mock console.error around toThrow() calls to satisfy vitest-fail-on-console in React 18
- ChatComposition.test.tsx: suppress act() warnings from ScrollArea in beforeEach/afterEach
- useChat.test.tsx: mark Strict Mode duplicate-streams test as JSDOM-only
- Indicators.test.tsx, MessageListRoot.test.tsx: add missing render() calls and autoScroll prop to browser tests
- ChatScrollToBottomAffordance.test.tsx: use waitFor for async affordance checks, set height/autoScroll on MessageListRoot, add overflow messages
…sion

- Fix chatApiPages.ts import to use docs/src/MuiPage (test_static)
- Add synchronous isSending guard in sendMessageActions to prevent
  double sendMessage invocation in React 18 Strict Mode (test_unit_react_18)
- Exclude ControlledStateHeadlessChat from regression tests due to
  sinon fake timer conflict with cancelAnimationFrame (test_regressions)
The chatSettings buildApiDocs template used 'docs/src/MuiPage' while all
other settings (charts, grid, pickers, scheduler, treeView) use
'@mui/internal-core-docs/MuiPage'. This caused test_static CI to fail
because pnpm docs:api regenerated chatApiPages.ts with a different import
than committed.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@hasdfa
Copy link
Copy Markdown
Member Author

hasdfa commented Apr 8, 2026

@mnajdova I've implemented your suggestions and fixed tests related to x-chat package. The only lint CI fail is OOM. What should we do next?

@mnajdova
Copy link
Copy Markdown
Member

mnajdova commented Apr 8, 2026

@mui/infra can you check the out of memory issue? How should we proceed?

@mnajdova
Copy link
Copy Markdown
Member

mnajdova commented Apr 8, 2026

@hasdfa can you check the regression CI failing error?

@mnajdova
Copy link
Copy Markdown
Member

mnajdova commented Apr 8, 2026

I checked the file changed outside of the chat* directories and those changes makes sense. I don't have enough time to review all the code from the chat packages and demos. If @joserodolfofreitas and @oliviertassinari are good with going forward I can subscribe to it.

Copy link
Copy Markdown
Member

@joserodolfofreitas joserodolfofreitas left a comment

Choose a reason for hiding this comment

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

Let's use the announcement momentum and publish this.
It's alpha zero, we'll have time to review it in the next few days.

Janpot and others added 6 commits April 8, 2026 18:04
Streaming chat demos (MinimalHeadlessChat, RealtimeHeadlessChat, etc.)
use requestAnimationFrame internally for auto-scroll behavior. When those
native frames were later cancelled via cancelAnimationFrame, Sinon's fake
timer didn't recognise the native IDs and emitted a browser console warning,
which the regression test harness treats as a test failure.

Adding `shouldClearNativeTimers: true` tells Sinon to forward unknown
cancelAnimationFrame calls to the native implementation instead of
complaining, which is exactly what its own error message recommended.
@hasdfa hasdfa merged commit 6c6fcc2 into mui:master Apr 8, 2026
21 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

scope: chat Changes related to the AI chat. type: new feature Expand the scope of the product to solve a new problem.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

9 participants