Skip to content

feat: migrate components from ChannelActionContext, ChannelStateContext to StateStore instances - #3237

Merged
MartinCupela merged 94 commits into
release-v15from
feat/message-paginator-master-merge
Aug 4, 2026
Merged

feat: migrate components from ChannelActionContext, ChannelStateContext to StateStore instances#3237
MartinCupela merged 94 commits into
release-v15from
feat/message-paginator-master-merge

Conversation

@MartinCupela

Copy link
Copy Markdown
Contributor

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 reactive StateStore-driven sources (messagePaginator, configState, threads.state, etc.).

Scope

  • Introduces a new ChatView layout control API with slot-based entity binding and view-aware state.
  • Replaces ChannelActionContext and ChannelStateContext runtime usage with instance APIs from stream-chat-js.
  • Migrates message/thread interactions to messagePaginator and client instance APIs.
  • Adds React adapters/hooks for slot-resolved channel/thread rendering and navigation.
  • Reworks Channel/Thread request handler wiring to instance configState.requestHandlers.
  • Updates components/stories/tests across the SDK to use the new contracts.

What Changed

1) New ChatView Layout Control API

  • Added LayoutController state model for slot topology, bindings, visibility, and per-slot history.
  • Added ChatViewNavigation API:
    • openView(view, { slot? })
    • openChannel(channel, { slot? })
    • closeChannel({ slot? })
    • openThread(threadOrTarget, { slot? })
    • closeThread({ slot? })
    • hideChannelList({ slot? })
    • unhideChannelList({ slot? })
  • Added slot-oriented primitives:
    • ChannelSlot
    • ThreadSlot
    • ChannelListSlot
    • ThreadListSlot
    • useSlotEntity, useSlotChannel, useSlotThread
  • Added layout state serialization helpers:
    • serializeLayoutState
    • restoreLayoutState
  • Added resolver utilities in layoutSlotResolvers for deterministic slot targeting.

2) Channel/Thread Ownership Moved to stream-chat-js Instances

  • Channel/thread message state now comes from instance-level messagePaginator and thread manager state.
  • Channel and Thread flows are instance-driven and reactive via StateStore.
  • Thread open/close behavior is routed through useChatViewNavigation() (with legacy fallback deactivation in Thread).

3) Context Removal and Replacement

  • Removed runtime/public usage of:
    • ChannelActionContext
    • ChannelStateContext
    • TypingContext provider path from Channel runtime
  • Added ChannelInstanceContext + useChannel() as the channel resolution contract.
  • useChannel() resolves from:
    • active thread context first
    • otherwise ChannelInstanceContext

4) Message Pagination + Unread/Focus Migration

  • Added public hook:
    • useMessagePaginator()
  • Message list/thread operations now use:
    • messagePaginator.jumpToMessage(...)
    • messagePaginator.jumpToTheFirstUnreadMessage(...)
    • messagePaginator.jumpToTheLatestMessage()
    • messagePaginator.toHead()/toTail()
    • messagePaginator.ingestItem(...)
    • messagePaginator.removeItem(...)
    • messagePaginator.unreadStateSnapshot
  • Unread UI controls now use instance APIs and client.messageDeliveryReporter instead of context actions.

5) Request Handler Customization Moved to Instance Config

  • Channel and Thread now register custom request handlers into:
    • channel.configState.requestHandlers
    • thread.configState.requestHandlers
  • Covers custom send/update/delete/markRead paths without ChannelActionContext callbacks.

API Changes and Migration Guide

Navigation: setActiveChannel/context actions -> ChatView navigation

// Before
setActiveChannel(channel);
openThread(message);
closeThread();

// After
const { openChannel, openThread, closeThread } = useChatViewNavigation();
openChannel(channel);
openThread({ channel, message });
closeThread();

### Message list updates: context mutation helpers -> paginator reconciliation

```js
// Before
updateMessage(message);
removeMessage(message);

// After
const paginator = useMessagePaginator();
paginator.ingestItem(message);
paginator.removeItem({ item: message });

Message jump/pagination: context methods -> paginator methods

// Before
jumpToMessage(id);
jumpToFirstUnreadMessage();
loadMore();
loadMoreNewer();

// After
const paginator = useMessagePaginator();
paginator.jumpToMessage(id);
paginator.jumpToTheFirstUnreadMessage();
paginator.toTail();
paginator.toHead();

Channel access: ChannelState/Action context -> useChannel()

// Before
const { channel } = useChannelStateContext();

// After
const channel = useChannel();

Slot-based rendering (new recommended pattern)

<ChatView>
  <ChatView.Channels slots={['list', 'main', 'thread']}>
    <ChannelListSlot slot='list' />
    <ChannelSlot slot='main' />
    <ThreadSlot slot='thread' />
  </ChatView.Channels>
</ChatView>

Behavioral Notes

  • Active entity routing is now layout/slot-driven instead of ChatContext active-channel ownership.
  • Thread/channel can coexist as sibling slot entities.
  • Channel list “open on mount” flows now open via navigation API.
  • Unread and notification behavior is now aligned with instance stores/reporters.

Testing

  • Broad test migration to the new contracts across Channel, Thread, MessageActions, MessageList, ChatView -navigation/layout, and slot hooks/components.
  • Added focused tests for:
  • useChannelRequestHandlers
  • useThreadRequestHandlers
  • ChatViewNavigation
  • layout controller behavior
  • slot resolution helpers

Breaking/Important for Integrators

  • Stop relying on ChannelActionContext and ChannelStateContext APIs.
  • Use useChatViewNavigation() for open/close channel/thread flows.
  • Use useMessagePaginator() + instance APIs for list mutations/jump/pagination.
  • Use slot adapters (ChannelSlot, ThreadSlot, list slots) for deterministic multi-pane ChatView layouts.
  • Prefer instance-scoped request handler overrides (Channel/Thread props wired to configState.requestHandlers).

🎨 UI Changes

No planned UI changes

# Conflicts:
#	src/components/MessageList/MessageList.tsx
#	src/components/MessageList/renderMessages.tsx
…rops) and Header Toggle Wiring for Entity List Pane
… and Remove Entity Semantics from LayoutController (Slot-Only Controller)
…read-on-mount, search-focused jump for Channel and rewrite Channel tests
MartinCupela and others added 20 commits July 21, 2026 15:19
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))
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>
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>
@codecov

codecov Bot commented Aug 3, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 73.74517% with 68 lines in your changes missing coverage. Please review.
⚠️ Please upload report for BASE (release-v15@5d6ceff). Learn more about missing BASE report.

Files with missing lines Patch % Lines
src/components/ChannelList/ChannelList.tsx 23.07% 20 Missing ⚠️
src/components/Channel/Channel.tsx 76.19% 10 Missing ⚠️
src/components/ChannelList/ChannelLists.tsx 22.22% 7 Missing ⚠️
src/components/ChannelList/ChannelNavigation.tsx 22.22% 7 Missing ⚠️
...rc/components/AIStateIndicator/hooks/useAIState.ts 16.66% 5 Missing ⚠️
...rc/components/Attachment/LinkPreview/CardAudio.tsx 0.00% 4 Missing ⚠️
...ponents/Channel/hooks/useChannelRequestHandlers.ts 87.87% 4 Missing ⚠️
src/components/Channel/utils.ts 0.00% 4 Missing ⚠️
.../components/Channel/hooks/useEditMessageHandler.ts 0.00% 3 Missing ⚠️
src/components/ChannelHeader/ChannelHeader.tsx 77.77% 2 Missing ⚠️
... and 2 more
Additional details and impacted files
@@              Coverage Diff               @@
##             release-v15    #3237   +/-   ##
==============================================
  Coverage               ?   83.90%           
==============================================
  Files                  ?      523           
  Lines                  ?    15905           
  Branches               ?     5050           
==============================================
  Hits                   ?    13345           
  Misses                 ?     2560           
  Partials               ?        0           

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@MartinCupela MartinCupela changed the title feat: migrate components from ChannelActionContext and ChannelState context to dedicated StateStore instances and add layout control API feat: migrate components from ChannelActionContext, ChannelStateContext to StateStore instances Aug 4, 2026
@MartinCupela
MartinCupela merged commit 99cbe67 into release-v15 Aug 4, 2026
9 of 10 checks passed
@MartinCupela
MartinCupela deleted the feat/message-paginator-master-merge branch August 4, 2026 07:50
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