feat: migrate components from ChannelActionContext and ChannelState context to dedicated StateStore instances and add layout control API#3237
Draft
MartinCupela wants to merge 76 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
Reset the composer optimistically before awaiting sendMessageWithLocalUpdate, matching master's ordering. Clearing only after the network round-trip let a fast follow-up keystroke or an async textComposer.handleChange commit race the clear, dropping or failing to clear rapidly typed-and-sent messages. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
MessageComposer now owns the submission flow (compose() -> channel.sendMessageWithLocalUpdate()). BREAKING CHANGE: the MessageComposer overrideSubmitHandler prop is removed. Intercept the outgoing request via Channel's doSendMessageRequest, or transform the composed message with composer composition middleware. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
useMuteHandler no longer gates the mute/unmute call on an optional notify callback (Message.tsx wires it as useMuteHandler(message)), so muting is no longer a silent no-op. useMarkUnreadHandler now propagates request failures instead of swallowing them, so the MarkUnread control reports the error via useNotificationApi. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Restore smooth (reduced-motion-aware) programmatic scrolling on the reactive message paginator, and re-center a focused message when the list is revealed or resized (e.g. a thread panel uncovering the channel) so jump-to-message lands correctly. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Channel already defaults EmptyPlaceholder to null and renders it; widen the prop type to React.ReactElement | null to match. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Drop the deleted ChannelStateContext/ChannelActionContext shims and their mocks; read state from channel.messagePaginator / channel.state and drive writes through channel.*WithLocalUpdate; mock requests via the mock-builder API helpers; instantiate client and channel per test via a setup() helper; remove obsolete cases and type-clean the suite. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Seed ai-migration-v14-v15.md with the confirmed v15 breaking changes: ChannelStateContext and ChannelActionContext removal, overrideSubmitHandler removal, and setActiveChannel / channelsQueryState removal. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Re-point the remaining tests onto the v15 architecture (low-level client / message paginator, notification API, ChatView slots) and complete the JS-to-TS migration. Add an error code to the erroredApi mock-builder helper used by the request-failure cases. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…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>
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>
…ws based on message paginator
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