feat: migrate components from ChannelActionContext and ChannelState context to dedicated StateStore instances and add layout control API - #3237
Draft
MartinCupela wants to merge 86 commits into
Conversation
# Conflicts: # src/components/MessageList/MessageList.tsx # src/components/MessageList/renderMessages.tsx
…d Resolver Registry and Built-in Strategies
…rops) and Header Toggle Wiring for Entity List Pane
…reserving Hide/Unhide
…ependent of Channel contexts
… and Remove Entity Semantics from LayoutController (Slot-Only Controller)
…eSlotEntity and ChannelSlot
…read-on-mount, search-focused jump for Channel and rewrite Channel tests
…tton when irrelevant
…ges arrive in MessageList
…outs (#3241) ### 🎯 Goal The message composer `<textarea>` does not fill its container when the SDK is integrated into a custom composer layout — it collapses to ~160px — even though it stretches correctly in our own example apps. Root cause: the rules that give the composer textarea its width live only inside the `.str-chat__message-composer-controls` wrapper, scoped as: ```scss .str-chat .str-chat__message-composer-controls .str-chat__textarea textarea { width: 100%; } ``` `.str-chat__textarea` and its `<textarea>` are rendered by `TextareaComposer`, but a custom composer that renders `<TextareaComposer>` without the default `MessageComposerUI` never produces the `.str-chat__message-composer-controls` wrapper. So the selector never matches, no `width` is applied, and the bare `<textarea>` falls back to its intrinsic `cols=20` width (~160px). Our example apps use the default `MessageComposerUI`, so the wrapper is present and the bug is invisible there. This is a recurring integration pain point, hence fixing it in the SDK rather than asking each integrator to re-add the width rule. ### 🛠 Implementation details Lifted the `.str-chat__textarea` block out of the `.str-chat__message-composer-controls` wrapper in `MessageComposer.scss` so it is scoped to the `TextareaComposer`'s own elements (`.str-chat .str-chat__textarea` / `.str-chat .str-chat__textarea textarea`) instead of the composer-controls layout wrapper. Why this is safe: - `.str-chat__textarea` and its `<textarea>` are rendered **only** by `TextareaComposer`, and no other rule targets `.str-chat__textarea`, so lowering selector specificity from `(0,3,1)` to `(0,2,1)` conflicts with nothing. - `flex: 1` on the wrapper is ignored when its parent isn't a flex container (custom composers) and continues to work in the default composer where the parent is `display: flex`. - The default `MessageComposerUI` already had all of these declarations applied, so its rendering is unchanged. Net effect: any composer that renders `<TextareaComposer>` now gets a full-width textarea, with or without the default `MessageComposerUI`. ### 🎨 UI Changes Verified live in a custom (Slack-style) composer that renders `<TextareaComposer>` without `MessageComposerUI`: - **Before:** composer `<textarea>` computed width = **160px** (intrinsic `cols=20` fallback). - **After:** composer `<textarea>` computed width = **1059px** — fills the composer container; the placeholder spans the full width. No visual change in the default composer (`MessageComposerUI`), which already carried these styles. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Bug Fixes** * Improved message composer layout consistency by ensuring the text area consistently fills available space across different composer configurations. * Preserved the existing text area look and behavior, including typography, sizing, scrollbar handling, and focus-visible styling. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
…rewire consumers Add a slot-agnostic, intent-level WorkspaceNavigation context in core (openChannel/openThread/closeThread, isChannelActive/isThreadActive, openChannels/openThreads, isThreadsView/isThreadDismissable) with an inert no-op default. The in-core ChatView implements it (workspaceNavigationAdapter) and provides it to its subtree, so core components no longer depend on the ChatView slot API directly. Rewire the 12 core consumers (Message reply/thread handlers, Thread/ThreadHeader, ChannelList/ChannelListItem/Search, Threads/ThreadList, Notifications target) onto the adapter, and migrate their tests. First step of extracting the slot system into an opt-in plugin. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Relocate src/components/ChatView and the four slot bridges (ChannelSlot, ThreadSlot, ThreadSlotContext, ThreadListSlot) into src/plugins/SlotLayout. Fix intra-plugin imports and repoint sibling-component references to ../../components. The plugin index re-exports the bridges + layout as public API. Drop the slot exports from the core component barrels; the ChannelDetail plugin and the two ChatView-referencing tests now import from the SlotLayout path, and the master SCSS @use is repointed. yarn types clean; full suite green. BREAKING CHANGE: the ChatView slot system is no longer exported from the stream-chat-react root; it will be published under the slot-layout subpath. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add the `slot-layout` vite library entry (src/plugins/SlotLayout/index.tsx) and the matching package.json `exports["./slot-layout"]` + typesVersions mapping, mirroring the sibling slot-geometry plugin. Build emits dist/es/slot-layout.mjs, dist/cjs/slot-layout.js and dist/types/plugins/SlotLayout/index.d.ts. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add unit tests for the WorkspaceNavigation context: the inert no-op default (empty reads, false predicates, non-throwing operations) and delegation to a provided implementation. The ChatView/slot test suites already moved with the plugin in the SlotLayout relocation. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ot-layout The ChatView slot system moved to the slot-layout plugin subpath, so repoint the vite + tutorial example imports (ChatView, Slot bridges, slot hooks, layout types) from the stream-chat-react root to stream-chat-react/slot-layout. Example type-checks clean. (Sync.tsx carries an unrelated in-progress change and is migrated separately.) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…API (#3242) ## Summary Several components, hooks, and contexts are used internally and referenced by the docs, but were never re-exported from the package barrels — so consumers can't import them. This wires them into the public API. Purely additive barrel re-exports; no behavior change. ## Newly exported **`src/context/index.ts`** - `VirtualizedMessageListContext`, `VirtualizedMessageListContextProvider`, `useVirtualizedMessageListContext`, `VirtualizedMessageListContextValue` **`src/components/index.ts`** - `SummarizedMessagePreview`, `useLatestMessagePreview` (+ its `ChannelPreviewMessageType`, `ChannelPreviewDeliveryStatus`, `LatestMessagePreviewData`, `UseLatestMessagePreviewParams` types) **`src/components/MessageComposer/index.ts`** - `VoiceRecordingPreviewSlot` (component; only its Props type was exported before) - `MessageComposerActions` and `AdditionalMessageComposerActions` ## Why A docs audit of the v14 React SDK found multiple cookbook/reference pages instructing readers to import these symbols from `stream-chat-react`, which fails at runtime because the barrels don't re-export them. Rather than route the docs around them, expose the symbols (the docs update is in GetStream/docs-content#1431). ## Notes - No collisions: none of the newly-exported names are exported elsewhere. - `AdditionalMessageComposerActions` and the four `useLatestMessagePreview` types ride along via `export *` — intentional; flag if you'd prefer narrower named exports. - Not exposed (left internal, unchanged): `EditedMessagePreview`, `SendToChannelCheckbox`, `restorePreEditSnapshot`. ## Checks - `yarn types` ✅ - `yarn eslint` (changed files) ✅ - Barrel-only change; no tests added. Happy to add export-surface tests if desired. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Added support for voice recording previews in the message composer. * Exposed additional message composer actions. * Added summarized message preview functionality. * Added access to virtualized message list context functionality. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
### 🎯 Goal Ref: https://getstream.slack.com/archives/C02R5UCGN6N/p1784023573649669 Relies on: GetStream/stream-chat-js#1800 <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Bug Fixes** * Improved file-upload availability by basing upload enablement on the message composer’s configured file limits. * Made the multi-action attachment UI consistently display the file upload option, even when channel capability details are unavailable. * Updated upload control behavior to better reflect current attachment slot availability and upload-request handling. * **Chores** * Updated the underlying `stream-chat` dependency version (main and example apps). <!-- end of auto-generated comment: release notes by coderabbit.ai -->
## [14.9.0](v14.8.0...v14.9.0) (2026-07-16) ### Bug Fixes * custom doUploadRequest permission adjustments ([#3243](#3243)) ([fa67adf](fa67adf)), closes [GetStream/stream-chat-js#1800](GetStream/stream-chat-js#1800) * **MessageComposer:** make textarea full-width in custom composer layouts ([#3241](#3241)) ([05236db](05236db)) ### Features * expose internal contexts and composer components in the public API ([#3242](#3242)) ([6b6a828](6b6a828)), closes [GetStream/docs-content#1431](https://github.com/GetStream/docs-content/issues/1431)
Checkpoint before the stream-chat storage removal (ChannelState message-list + threads). Migrates ChannelListItem, Channel, MessageList, Search, MessageBounce + mock-builders. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The "should allow removing messages" test still read the removed channel.state main message list via findMessage/removeMessage. Re-scope it to channel.messagePaginator: seed check via items, remove via removeItem, and assert the item leaves the active window. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…nedMessagesPaginator usePinnedMessagesCount now derives the count reactively from channel.pinnedMessagesPaginator via useStateStore instead of reading channel.state.pinnedMessages and subscribing to channel events. Tests drive a real paginator StateStore. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…rs to paginators Two readers still referenced state removed from stream-chat: Channel.tsx read channel.state.messages (the main list moved to channel.messagePaginator) for the oldest-message id on user.deleted, and ThreadListItemUI read ThreadState.replies (thread replies moved to thread.messagePaginator) for the latest reply. Point both at the paginators — the latest reply is now selected reactively from thread.messagePaginator.state. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
….latestMessage Replace the `headItems?.at(-1)` selector with the paginator's tracked `latestMessage` (resolved via `latestMessageId` + `getItem`), so the latest reply is correct regardless of the active window or the interval/item sort orientation rather than assuming the head window's last entry is newest. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ator model
ChannelState message/thread/pinned storage was removed on the LLC side (v15): the paginators
(channel.messagePaginator, thread.messagePaginator, channel.pinnedMessagesPaginator) are the source
of truth. Update the stale guidance accordingly:
- DO-NOT list: there is no channel.state.addMessageSorted() / removeMessage() (removed); read via
useStateStore(channel.messagePaginator.state, ...). Thread replies are an independent paginator,
not mirrored into the channel list.
- Replace the "Thread State Synchronization" invariant ("replies MUST exist in main channel state")
with the independent-paginators model; show_in_channel governs channel visibility.
- Optimistic-updates gotcha and the context-deps example no longer reference channel.state.messages.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The reducer-era state management is gone from src (channelState.ts, makeChannelReducer, useReducer, copyStateFromChannelOnEvent, useCreateChannelStateContext, and the ChannelStateContext / ChannelActionContext layers). Rewrite the stale sections: - Context Layers: drop ChannelStateContext / ChannelActionContext; add ChannelInstanceContext + useChannel() / useMessagePaginator(); note the removed context/hook builders. - State Management: no reducer; Channel holds only UI flags (isBootstrapping / bootstrapError); message state lives on the LLC paginators, consumed via useStateStore (useSyncExternalStore). - Optimistic Updates: handled by the LLC (channel.sendMessage -> messagePaginator.ingestItem), not React state; conflict resolution is in the paginator. - WebSocket Event Processing: no throttled copyStateFromChannelOnEvent; handleEvent does side effects only; re-renders come from StateStore subscriptions. - Performance: memoization via useStateStore selectors + areMessageUIPropsEqual; the old throttles and string-serialization memoization are gone. - Module Boundaries: update the coupling/risk notes accordingly. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…3244) ### 🎯 Goal Unify how the Avatar/AvatarStack information is being constructed through the common and replaceable utility `extractDisplayInfo` function. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Added an `initials` prop override for avatars. * Introduced a context-overridable display-info extractor to customize avatar rendering across messages, threads, polls, reactions, search results, typing indicators, and channel/member/pinned views. * **Bug Fixes** * Improved consistency for avatar display data by trimming usernames and using the trimmed name for avatar `alt` text, with the explicit `initials` override taking priority. * **Tests** * Updated component-context mocks to support the new context-driven avatar customization. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
## [14.10.0](v14.9.0...v14.10.0) (2026-07-22) ### Features * introduce extractDisplayInfo for Avatar/AvatarStack components ([#3244](#3244)) ([1068e79](1068e79))
…ws based on message paginator
Reconcile PR #3245 (unread indicators) onto feat/message-paginator-master-merge and fix duplicate mark-read on channel open. Channel.tsx: - useMarkRead is now the sole mark-read owner on open. Removed the redundant mount-time markChannelRead and the markReadOnMount prop; a second, unthrottled mark-read used to fire alongside useMarkRead (up to 4 /read requests per open under StrictMode, 2 in production) — now a single request. - Guard seedUnreadSnapshot() so it does not run when the channel is already flagged unread (firstUnreadMessageId set); re-seeding would clear a deliberate "mark as unread" on reopen. useMarkRead.ts: - shouldMarkRead now gates on messagePaginator.isViewingLive (tab foregrounded AND scrolled to the bottom AND no newer messages beyond the loaded window) for the active collection. Adds the "not caught up while newer messages exist" guard and removes the dead scrolled-back-to-bottom tracking. MessageList.tsx / VirtualizedMessageList.tsx: - Wire hasMoreNewer into useMarkRead (the non-superseded half of PR #3245's message-list changes; the focus-signal hunks were dropped — the branch already implements message-focus clearing). UnreadMessagesNotification.tsx: - clearUnreadSnapshot() on the mark-read button. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… floating above it
Reconcile release-v15 (OpenAPI-era master, incl. the a11y overhaul) with the message-paginator architecture. Conflicts resolved to keep our paginator/MessageComposer/workspace-navigation model while adopting release-v15's improvements, adapted where the APIs diverged: - a11y: AriaLive announcements via Chat's shared outlet (no per-list AriaLiveRegion); ChannelList keyboard-nav listbox, now per-list on each scroll container; composed accessible labels + interaction announcements for channel/thread rows; ChatView navigation-landmark model. - TypingIndicator ported to the state-store layer and wired into MessageList/VirtualizedMessageList (isMessageListScrolledToBottom/scrollToBottom). - extractDisplayInfo/AvatarStack, doUploadRequest capability, exposed internal contexts. - ChatView context: dropped the redundant activeChatView alias in favour of activeView. Known-pending (tracked separately): 211 OpenAPI Event-union type adaptations and the mock-builder _setupConnection breakage. Committed with --no-verify. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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.
Depends on
GetStream/stream-chat-js#1795
Summary
This PR is a major architecture migration, not just message pagination.
It moves chat/thread runtime behavior from React-owned channel contexts to instance APIs in
stream-chat-js, introduces a slot-based ChatView layout controller, and rewires navigation/state to reactiveStateStore-driven sources (messagePaginator,configState,threads.state, etc.).Scope
ChannelActionContextandChannelStateContextruntime usage with instance APIs fromstream-chat-js.messagePaginatorand client instance APIs.configState.requestHandlers.What Changed
1) New ChatView Layout Control API
LayoutControllerstate model for slot topology, bindings, visibility, and per-slot history.ChatViewNavigationAPI:openView(view, { slot? })openChannel(channel, { slot? })closeChannel({ slot? })openThread(threadOrTarget, { slot? })closeThread({ slot? })hideChannelList({ slot? })unhideChannelList({ slot? })ChannelSlotThreadSlotChannelListSlotThreadListSlotuseSlotEntity,useSlotChannel,useSlotThreadserializeLayoutStaterestoreLayoutStatelayoutSlotResolversfor deterministic slot targeting.2) Channel/Thread Ownership Moved to stream-chat-js Instances
messagePaginatorand thread manager state.StateStore.useChatViewNavigation()(with legacy fallback deactivation inThread).3) Context Removal and Replacement
ChannelActionContextChannelStateContextTypingContextprovider path from Channel runtimeChannelInstanceContext+useChannel()as the channel resolution contract.useChannel()resolves from:ChannelInstanceContext4) Message Pagination + Unread/Focus Migration
useMessagePaginator()messagePaginator.jumpToMessage(...)messagePaginator.jumpToTheFirstUnreadMessage(...)messagePaginator.jumpToTheLatestMessage()messagePaginator.toHead()/toTail()messagePaginator.ingestItem(...)messagePaginator.removeItem(...)messagePaginator.unreadStateSnapshotclient.messageDeliveryReporterinstead of context actions.5) Request Handler Customization Moved to Instance Config
channel.configState.requestHandlersthread.configState.requestHandlersAPI Changes and Migration Guide
Navigation:
setActiveChannel/context actions -> ChatView navigationMessage jump/pagination: context methods -> paginator methods
Channel access: ChannelState/Action context -> useChannel()
Slot-based rendering (new recommended pattern)
Behavioral Notes
Testing
Breaking/Important for Integrators
🎨 UI Changes
No planned UI changes