Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
76 commits
Select commit Hold shift + click to select a range
280b47d
feat: use LLC message pagination and message list state optimistic up…
MartinCupela Jan 12, 2026
1003896
Merge branch 'master' into feat/message-paginator
MartinCupela Jan 13, 2026
81f9689
feat(LayoutController): implement Core Types and Controller Engine an…
MartinCupela Feb 26, 2026
64ff386
feat(LayoutController): implement ChatView Integration (Context and P…
MartinCupela Feb 26, 2026
1bf06f6
feat(LayoutController): implement Built-in Two-Step DX Layout API
MartinCupela Feb 27, 2026
4c72159
feat(LayoutController): implement Docs and Spec Alignment
MartinCupela Feb 27, 2026
28f224a
chore(LayoutController): add new requirements to layout control API
MartinCupela Feb 27, 2026
3c3d0cf
feat(LayoutController): implement Slot Parent Stack and Back Navigation
MartinCupela Feb 27, 2026
95ea03a
feat(LayoutController): implement Unify ChannelList into Slot Model
MartinCupela Feb 27, 2026
8a3d6ba
feat(LayoutController): implement Min Slots and Fallback Workspace St…
MartinCupela Feb 27, 2026
f0a9ba2
feat(LayoutController): implement Generic Slot Component with Mount-P…
MartinCupela Feb 27, 2026
3223e46
feat(LayoutController): implement Deep-Linking, Serialization, and op…
MartinCupela Feb 27, 2026
bad6681
feat(LayoutController): implement High-Level Navigation Hook and Cont…
MartinCupela Feb 27, 2026
862a0ed
feat(LayoutController): adjust Thread and ThreadContext.tsx to be ind…
MartinCupela Feb 27, 2026
4b28a74
feat(LayoutController): implement Thread Component Layout-Controller …
MartinCupela Feb 27, 2026
55a7e37
feat(LayoutController): add translations to "aria/Go back"
MartinCupela Feb 27, 2026
f5f328e
feat(LayoutController): implement Slot Self-Visibility from Slot Prop…
MartinCupela Feb 27, 2026
3a0efb6
refactor(channel): remove thread pagination from channel contexts
MartinCupela Feb 28, 2026
1e42b28
feat: remove non-pagination state from ChannelStateContext
MartinCupela Mar 3, 2026
8cacbb3
Merge branch 'master' into feat/message-paginator
MartinCupela Mar 3, 2026
9680b7a
Merge branch 'master' into feat/chat-view-layout
MartinCupela Mar 4, 2026
e36f496
feat(chatview): remove ChatContext active channel/nav routing; add us…
MartinCupela Mar 4, 2026
1e5d9ae
refactor: remove Hardcoded `channel.messagePaginator` Usage from Reac…
MartinCupela Mar 4, 2026
98231e5
feat: remove ChannelActionContext
MartinCupela Mar 4, 2026
5a81ea1
feat: keep suppressAutoscrol management inside message lists
MartinCupela Mar 4, 2026
deabbcb
feat: port highlighted message functionality and use client notificat…
MartinCupela Mar 5, 2026
2fd3327
feat: adapt bootstrap/loading/error guards, listener lifecycle, mark-…
MartinCupela Mar 5, 2026
e5d9d1b
fix: clear channelUnreadUiState upon clicking on UnreadMessagesSepara…
MartinCupela Mar 5, 2026
4b54c38
fix: prevent showing NewMessageNotification & ScrollToLatestMessageBu…
MartinCupela Mar 5, 2026
3e83138
fix: make sure to scroll to the bottom, when the first batch of messa…
MartinCupela Mar 5, 2026
7431d07
fix: update reply count in MessageRepliesCountButton
MartinCupela Mar 5, 2026
1959233
fix: reflect deleted message text in QuotedMessagePreview
MartinCupela Mar 5, 2026
ff1cb05
fix: show cursor pointer when hovering above QuotedMessage in message…
MartinCupela Mar 6, 2026
429500b
fix: make the UnreadMessagesSeparator driven by MessagePaginator unre…
MartinCupela Mar 6, 2026
fb48d09
feat: make slot layout for threads and channels
MartinCupela Mar 6, 2026
bcca6e7
Merge branch 'master' into feat/message-paginator
MartinCupela Mar 6, 2026
7bd80a1
Merge branch 'pr-2909' into feat/message-paginator-master-merge
MartinCupela Jul 8, 2026
ecc98c1
feat: introduce additive opening in a secondary slot
MartinCupela Jul 8, 2026
0faca8e
feat: flatten the Channel DOM structure and remove Window component
MartinCupela Jul 8, 2026
156b609
feat: replace ChannelListContext contents with paginator
MartinCupela Jul 9, 2026
174b6af
refactor: remove Channel component memoization
MartinCupela Jul 9, 2026
bbec104
chore(demo): prevent floating message list's date separator to be ren…
MartinCupela Jul 9, 2026
e01230b
fix: add audio player pool for Thread
MartinCupela Jul 9, 2026
f7b4996
feat: add slot geometry plugin
MartinCupela Jul 9, 2026
1db47b8
refactor: extract deeplink navigation between sent-in-channel and thr…
MartinCupela Jul 9, 2026
4fade8b
feat: add layers to the slot system
MartinCupela Jul 9, 2026
70f3175
feat: add reveal intent to the slot geometry
MartinCupela Jul 10, 2026
db2f518
refactor: drop unnecessary channel query when jumping to a reply in c…
MartinCupela Jul 10, 2026
e0b5e48
fix(MessageComposer): clear the composer before awaiting the send
MartinCupela Jul 14, 2026
ed3dcc9
refactor(MessageComposer)!: remove the overrideSubmitHandler prop
MartinCupela Jul 14, 2026
2f70fe1
fix(Message): perform mute/unmute and surface mark-unread failures
MartinCupela Jul 14, 2026
581fe54
feat(MessageList): paginator-native smooth scroll and focus re-centering
MartinCupela Jul 14, 2026
71aea3c
fix(Channel): allow a null EmptyPlaceholder prop
MartinCupela Jul 14, 2026
ac371bb
test(Channel): rewrite onto the low-level client and paginator contract
MartinCupela Jul 14, 2026
5422e0c
docs: add v14 to v15 AI migration guide
MartinCupela Jul 14, 2026
3282b38
test: fix and migrate the v15 test suite
MartinCupela Jul 14, 2026
e4b5234
refactor(navigation): introduce core WorkspaceNavigation adapter and …
MartinCupela Jul 15, 2026
c7c3d75
refactor(SlotLayout)!: move the ChatView slot system into a plugin
MartinCupela Jul 15, 2026
0878f5b
build(SlotLayout): expose the plugin under the slot-layout subpath
MartinCupela Jul 15, 2026
293705c
test(navigation): cover the core WorkspaceNavigation adapter
MartinCupela Jul 15, 2026
061f639
refactor(examples)!: import the slot system from stream-chat-react/sl…
MartinCupela Jul 15, 2026
0a8070e
chore(demo): support workspace navigation from browser URL
MartinCupela Jul 15, 2026
6bcd098
feat: allow displaying channels retrieved out of pagination order
MartinCupela Jul 16, 2026
692cf40
refactor: rename retained channels to sideloaded
MartinCupela Jul 16, 2026
a61e634
refactor: migrate readers to channel.messagePaginator
MartinCupela Jul 20, 2026
79e26c0
test(Channel): assert message removal against messagePaginator
MartinCupela Jul 20, 2026
2badfbb
refactor(ChannelDetail): source pinned-message count from channel.pin…
MartinCupela Jul 21, 2026
fdd2bbd
fix: migrate stale channel.state.messages / ThreadState.replies reade…
MartinCupela Jul 21, 2026
ab733fa
refactor(ThreadListItemUI): source latest reply from messagePaginator…
MartinCupela Jul 21, 2026
7d3c194
docs: correct CLAUDE.md message/thread storage guidance for the pagin…
MartinCupela Jul 21, 2026
c2efa17
docs: update CLAUDE.md architecture for the reducer/context removal
MartinCupela Jul 21, 2026
c5a5d5d
refactor: receive last item and its preview in thread and channel vie…
MartinCupela Jul 22, 2026
067b95a
fix: make empty channel list container align contents to the center
MartinCupela Jul 22, 2026
45d769f
feat(workspaceNavigation): allow to override worskpace navigation ada…
MartinCupela Jul 24, 2026
5373f81
refactor(pagination): replace sideloaded items with logical interval …
MartinCupela Jul 24, 2026
a0067fe
chore(demo): add ScrollToActiveChannelButton
MartinCupela Jul 24, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
114 changes: 62 additions & 52 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,60 +44,69 @@ yarn lint-fix # ALWAYS run this first

```
<Chat> # Root: provides client, theme, i18n
└─ <Channel> # State container: messages, threads, WebSocket events
└─ <Channel> # Bootstrap + WS side-effects; provides the LLC channel instance
├─ <MessageList> # Renders messages (or <VirtualizedMessageList>)
├─ <MessageInput> # Composer with attachments/mentions
└─ <Thread> # Threaded replies
```

### Context Layers (14+ contexts)
### Context Layers

```
ChatContext # Client, active channel, theme, navigation
├─ ChannelStateContext # Read-only: messages, members, loading states
├─ ChannelActionContext # Write: sendMessage, deleteMessage, openThread
├─ ComponentContext # 100+ customizable component slots
├─ ChannelInstanceContext # The LLC `channel` for this subtree (read via `useChannel()`)
├─ ComponentContext # Customizable component slots
└─ MessageContext # Per-message: actions, reactions, status
```

**All contexts have hooks:** `useChatContext()`, `useChannelStateContext()`, etc.
**Hooks:** `useChatContext()`, `useChannel()`, `useMessagePaginator()`, `useComponentContext()`, etc.

### State Management (Multi-Layer)
> **Removed in v15:** `ChannelStateContext` / `ChannelActionContext` and their
> `useChannelStateContext()` / `useCreateChannelStateContext` / `useCreateChannelActionContext`
> builders. Message/channel state is no longer copied into a React context — components read it
> directly from the LLC (`useChannel()` + `useStateStore(channel.messagePaginator.state, …)`), and
> actions are invoked on the LLC channel / navigation adapters.

1. **Local state** (`useState`) - Component UI state
2. **Reducer state** (`useReducer`) - Channel uses `makeChannelReducer` for complex message state
3. **Context state** - Global shared state
4. **External state** - `stream-chat` SDK's StateStore via `useStateStore` hook (uses `useSyncExternalStore`)
### State Management

1. **Local state** (`useState`) - only UI/lifecycle flags on `Channel` (`isBootstrapping`,
`bootstrapError`); no reducer, no React-held message list.
2. **External LLC state** - the message list, thread replies, and pinned messages live on
`channel.messagePaginator` / `thread.messagePaginator` / `channel.pinnedMessagesPaginator`
(`StateStore`s). Components subscribe via the `useStateStore` hook (backed by
`useSyncExternalStore`) — this is the primary re-render driver.
3. **Context** - the channel instance and component-slot overrides (see Context Layers).

## Critical Architectural Patterns

### 1. Optimistic Updates & Race Conditions

**File:** `src/components/Channel/Channel.tsx` + `channelState.ts`
**Owner:** the LLC (`stream-chat`), not React state.

- Messages are added to local state IMMEDIATELY when sending (optimistic)
- WebSocket events may arrive before/after API response
- **Timestamp-based conflict resolution:** Newest version always wins
- **Gotcha:** Thread state is separate from channel state - both must be updated
- Optimistic sends go through `channel.sendMessage`, which ingests the pending message into
`channel.messagePaginator` immediately (`ingestItem`, dedupe-by-id + sorted insert). Because
`MessageList` subscribes to the paginator `StateStore`, the message renders at once — no React-local
copy. The React SDK only customizes the request via `channel.configState.requestHandlers` (see
`Channel/hooks/useChannelRequestHandlers.ts`).
- WebSocket events may arrive before/after the API response; **conflict resolution is in the paginator
/ LLC** (newest version wins, dedupe by id).
- **Gotcha:** thread replies are a separate paginator (`thread.messagePaginator`) from the channel's
`channel.messagePaginator` — they are not dual-written; each is updated by its own event handling.

### 2. WebSocket Event Processing

**File:** `src/components/Channel/Channel.tsx` (`handleEvent` function)
**File:** `src/components/Channel/Channel.tsx` (`handleEvent`, registered in the bootstrap effect).

```ts
// Events are THROTTLED to 500ms to prevent excessive re-renders
throttledCopyStateFromChannel = throttle(
() => dispatch({ type: 'copyStateFromChannelOnEvent' }),
500,
{ leading: true, trailing: true },
);
```
Re-renders on events are **not** driven from `Channel`. The LLC's own event handlers write into the
paginators' `StateStore`s, and components re-render via their `useStateStore` subscriptions
(`useSyncExternalStore`). There is **no** throttled `copyStateFromChannelOnEvent` dispatch anymore
(the old reducer + 500ms throttle were removed).

**Key behaviors:**
`Channel.handleEvent` now performs only **side effects**: online-status tracking, document-title /
unread-count updates on `message.new`, latest-message bookkeeping, and a full `channel.query(...)`
re-fetch on `user.deleted`.

- Some events ignored: `user.watching.start/stop`
- Unread updates throttled separately (200ms)
- Message filtering: `parent_id` + `show_in_channel` determine thread visibility
- Message filtering: `parent_id` + `show_in_channel` determine thread visibility.

### 3. Message Enrichment Pipeline

Expand All @@ -122,36 +131,37 @@ Messages are processed in order:
- Only visible items + overscan buffer rendered
- `skipMessageDataMemoization` prop exists for channels with 1000s of messages

### 5. Performance: Memoization & Throttling
### 5. Performance: Memoization

**Critical memoization:**

- Message data serialized to string for comparison (see `useCreateChannelStateContext`)
- `areMessageUIPropsEqual` checks cheap props first (highlighted, mutes.length)
- **Gotcha:** Any prop not in serialization won't trigger updates!

**Throttling locations:**
- `useStateStore(store, selector)` selectors: return small flat objects — the hook shallow-compares
the selected slice and only re-renders on a real change. This is what scopes paginator-state
updates (e.g. `MessageList` selects `{ messages, hasMoreNewer, isLoading }`).
- `areMessageUIPropsEqual` (`src/components/Message/utils.tsx`) — per-message `React.memo` comparator;
checks cheap props first (highlighted, mutes.length).
- **Gotcha:** a change the selector or `areMessageUIPropsEqual` doesn't observe won't trigger a
re-render.

- WebSocket events: 500ms
- Unread counter updates: 200ms
- `markRead`: 500ms (leading: true, trailing: false - only fires on FIRST call)
- `loadMoreFinished`: 2000ms debounced
> The old event throttling (500ms `copyStateFromChannelOnEvent`, 200ms unread, `markRead` 500ms,
> `loadMoreFinished` debounce) and the `useCreateChannelStateContext` string-serialization memoization
> are **gone** — re-rendering is now driven by `StateStore` subscriptions, not a throttled reducer
> copy.

## Critical Gotchas & Invariants

### DO NOT:

1. **Mutate `channel.state.messages` directly** - Use `channel.state.addMessageSorted()` / `removeMessage()`
1. **Push messages into `channel.state`** - messages/threads/pinned are owned by the LLC paginators (`channel.messagePaginator`, `thread.messagePaginator`, `channel.pinnedMessagesPaginator`). Read them reactively via `useStateStore(channel.messagePaginator.state, …)`; the SDK's own event handlers perform the writes. There is no `channel.state.addMessageSorted()` / `removeMessage()` (removed in v15).
2. **Include `channel` in dependency arrays** - Use `channel.cid` only (stable), not `channel.state` (changes constantly)
3. **Modify reducer action types without updating all dispatchers** - They're tightly coupled
4. **Change message sort order** - SDK maintains order; local changes will conflict
5. **Forget to update both channel AND thread state** - Thread messages must exist in main state too
3. **Change message sort order** - the paginator maintains order; local changes will conflict
4. **Assume thread replies live in the channel's message list** - a thread's replies are an independent paginator (`thread.messagePaginator`); they are not mirrored into `channel.messagePaginator`

### Thread State Synchronization
### Thread vs. channel messages

- Main channel: `state.messages` (flat list)
- Threads: `state.threads[parentId]` (keyed by parent message ID)
- **Invariant:** Messages in threads MUST also exist in main channel state
- Main channel messages: `channel.messagePaginator` (LLC).
- Thread replies: `thread.messagePaginator`, owned by the `Thread` object (resolve via `client.threads`) — **independent** of the channel's message list.
- **No cross-store invariant:** a reply is not required to exist in the channel's message list. Whether a reply also shows in the channel is the server's `show_in_channel` flag, applied when the message is ingested.

### React Version Compatibility

Expand All @@ -174,7 +184,7 @@ useMemo(
[
channel.cid, // ✅ Stable - include this
deleteMessage, // ✅ Stable callback
// ❌ NOT channel.state.messages - causes infinite re-renders
// ❌ NOT channel.messagePaginator.state.items - changes constantly (subscribe via useStateStore)
// ❌ NOT channel.initialized - changes constantly
],
);
Expand Down Expand Up @@ -218,13 +228,13 @@ render(

1. `Message.tsx` ↔ `MessageContext` - Every message needs actions
2. `Channel.tsx` ↔ `VirtualizedMessageList` - Complex prop drilling
3. `useCreateChannelStateContext` ↔ Message memoization - String serialization fragility
3. `useStateStore` selectors ↔ Message memoization - a selector that returns an unstable/over-broad
slice defeats the shallow-compare and re-renders the list

**Integration Risks:**

- Modifying reducer actions requires updates in multiple dispatchers
- Changing message sorting conflicts with SDK updates
- Thread state isolation is error-prone
- Changing message sorting conflicts with the LLC paginator's ordering
- Reading LLC message state through anything other than `useStateStore` on the paginator (stale copies)

## Code Organization Standards

Expand Down
78 changes: 78 additions & 0 deletions ai-docs/ai-migration-v14-v15.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
# stream-chat-react v14 → v15 AI Migration Guide

Execution-only guide for a coding agent migrating a third-party app from `stream-chat-react` v14 to v15. Apply the changes below and fix each compile/runtime break before moving on. For evidence or edge cases, verify against the installed SDK source (see below).

> Status: this guide is seeded with the breaking changes confirmed while working on the v15 branch. It is **not yet exhaustive** — additional v15 breaking changes may exist. Always confirm a symbol/prop against the installed source before relying on it.

## Source of Truth

Never rely on training knowledge — the v14 surface is heavily represented in pretraining data and will mislead you. Before writing any code, verify against the user's installed packages:

1. `node_modules/stream-chat-react/dist/types/index.d.ts` — public React type surface (props, override keys, hook signatures).
2. `node_modules/stream-chat/dist/types/index.d.ts` — core client types: `Channel` methods (`messagePaginator`, `*WithLocalUpdate`, `messageOperations`), event names, etc.
3. `node_modules/stream-chat-react/package.json` — `exports` map and peer dependencies.

Rule: before claiming a symbol/prop/method exists or writing a replacement snippet, grep the installed `.d.ts` and read the current signature — do not reconstruct it from memory.

---

## The core v15 shift: channel state moved to the low-level client

The single largest v15 change: the React SDK no longer owns channel message state in a React reducer or exposes it through the `ChannelStateContext` / `ChannelActionContext`. Message data, pagination, and the write operations now live on the `stream-chat` **`channel` instance**, and the React components read it reactively via `useStateStore`. Everything else in this guide follows from that.

### Removed: `ChannelStateContext` / `ChannelActionContext` and their hooks

`ChannelStateContext`, `ChannelActionContext`, `useChannelStateContext`, and `useChannelActionContext` are **removed**. Read state and invoke actions off the `channel` instance instead.

**Reading state (was `useChannelStateContext`):**

| v14 (`useChannelStateContext`) | v15 |
| ------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `messages` | `channel.messagePaginator.state.items` (subscribe with `useStateStore(channel.messagePaginator.state, …)`); `channel.state.messages` for the raw channel state |
| `hasMore` (older) | `channel.messagePaginator.hasPrev` |
| `hasMoreNewer` | `channel.messagePaginator.hasNext` |
| `loadingMore` | `channel.messagePaginator.state` (`isLoading`) |
| `read` / `members` / `watchers` / `watcher_count` | `channel.state.read` / `channel.state.members` / `channel.state.watchers` / `channel.state.watcher_count` |

**Actions (was `useChannelActionContext`):** call the `channel` methods, which perform an optimistic local update and then the request:

- `sendMessage` → `channel.sendMessageWithLocalUpdate({ localMessage, message, options })`
- `editMessage` → `channel.updateMessageWithLocalUpdate({ localMessage, options })`
- `retrySendMessage` → `channel.retrySendMessageWithLocalUpdate({ localMessage, options })`
- `deleteMessage` → `channel.deleteMessageWithLocalUpdate({ localMessage, options })`
- `loadMore` / `loadMoreNewer` → `channel.messagePaginator.prev()` / `.next()` (and `.toHead()` / `.toTail()`)
- `jumpToMessage` → `channel.messagePaginator.jumpToMessage(id)`

These `*WithLocalUpdate` methods delegate to `channel.messageOperations`, which honours per-request overrides registered through the `Channel` props (see below).

### `MessageComposer` `overrideSubmitHandler` prop → removed

`MessageComposer` (formerly `MessageInput`) now owns the submission flow (`messageComposer.compose()` → `channel.sendMessageWithLocalUpdate()`), so the `overrideSubmitHandler` prop is gone. To customise sending:

- **Intercept the outgoing request** → pass `Channel`'s `doSendMessageRequest` (also `doUpdateMessageRequest` / `doDeleteMessageRequest` / `doMarkReadRequest`). These are wired into `channel.messageOperations` and used instead of the default request.
- **Transform the composed message** → register composition middleware on `messageComposer`.

### `ChatContext.setActiveChannel` → removed

There is no `setActiveChannel` on `ChatContext`. Bind a channel by:

- passing it directly as the `channel` prop: `<Channel channel={channel}>…</Channel>` (the `Channel` component takes `channel` as a prop; it no longer reads it from context), and/or
- opening it in a `ChatView` layout slot — e.g. `open({ key: channel.cid, kind: 'channel', source: channel })` — the mechanism `ChannelListItemUI` uses on selection.

To ingest an ad-hoc channel (e.g. navigating to a DM or search result) into the paginators, use `channelPaginatorsOrchestrator.ingestChannel(channel)`. Confirm the exact `open()` / orchestrator signatures against the installed source.

### `ChatContext.channelsQueryState` → removed

`Channel` no longer reflects the channel-list query state. Its loading / error / empty rendering is driven by the channel's own `watch()` bootstrap (`LoadingIndicator` while watching, `LoadingErrorIndicator` on watch failure, `EmptyPlaceholder` when no channel is provided). The channel-list query state is the `ChannelList`'s concern, not `Channel`'s.

### `ChannelProps.EmptyPlaceholder` accepts `null`

`Channel`'s `EmptyPlaceholder` prop is now typed `React.ReactElement | null` (the default is `null`) — pass `null` to render an empty container when no channel is set. (Non-breaking widening; noted for completeness.)

### `ChannelListItem` `getLatestMessagePreview` prop → removed; customize via `SummarizedMessagePreview`

`ChannelListItem`'s `getLatestMessagePreview` prop and the `getLatestMessagePreview` util (previously re-exported from the package root) are **removed**, along with the `latestMessagePreview` prop on `ChannelListItemUIProps`. The default `ChannelListItemUI` renders the last-message preview via the `SummarizedMessagePreview` component, which is now overridable through `ComponentContext`.

- **Customize how the preview renders** → provide a `SummarizedMessagePreview` component override (via `ComponentProvider`, or the `<Chat>` / `<Channel>` component props). It receives `SummarizedMessagePreviewProps` (`{ latestMessage, messageDeliveryStatus, participantCount }`).
- **Preview a specific message** rather than the channel's latest (e.g. a search result previewing the matched message) → pass the new `previewedMessage?: LocalMessage` prop to `ChannelListItem`. It defaults to the channel's reactive latest, `channel.messagePaginator.aggregateState.lastMessage`.
- **Behavior note:** the preview now honors the channel's `skip_last_msg_update_for_system_msgs` config (a system message no longer becomes the previewed / last message), so the preview and the channel's sort position agree.
9 changes: 3 additions & 6 deletions examples/tutorial/src/2-core-component-setup/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import {
MessageList,
Thread,
useCreateChatClient,
Window,
} from 'stream-chat-react';

import 'stream-chat-react/dist/css/index.css';
Expand Down Expand Up @@ -47,11 +46,9 @@ const App = () => {
return (
<Chat client={client}>
<Channel channel={channel}>
<Window>
<ChannelHeader />
<MessageList />
<MessageComposer />
</Window>
<ChannelHeader />
<MessageList />
<MessageComposer />
<Thread />
</Channel>
</Chat>
Expand Down
Loading