Skip to content

chore: merge master (14.11.0) into release-v15 - #3260

Open
oliverlaz wants to merge 10 commits into
release-v15from
chore/merge-master-into-release-v15
Open

chore: merge master (14.11.0) into release-v15#3260
oliverlaz wants to merge 10 commits into
release-v15from
chore/merge-master-into-release-v15

Conversation

@oliverlaz

@oliverlaz oliverlaz commented Aug 7, 2026

Copy link
Copy Markdown
Member

🎯 Goal

Merge master (14.11.0) into release-v15. The two lines had diverged along different axes and 25 files conflicted, several of them non-mechanically β€” this PR exists so the resolution gets reviewed rather than landing straight on release-v15.

Those are mostly orthogonal, so the guiding rule was keep v15's structure, layer master's work on top.

πŸ›  Implementation details

Ported from master into v15 idioms

  • Icon slots β€” every conflicted component now reads icons from useComponentContextIcons() instead of importing them statically. Also applied to src/plugins/SlotLayout/ChatView.tsx, the file that replaced the deleted src/components/ChatView/ChatView.tsx, so those icons stay overridable.
  • Local unread count (markReadLocally / isLocalUnreadCountEnabled / message.read_locally) touched 5 files on master, 4 of them conflicted, and v15 had none of it. Reimplemented in useMarkRead, Channel.handleEvent, and ChannelListItem (using v15's EventPayload + subscription-object style).
  • fix(Channel): guard render-phase channel.getConfig() against disconnected channelsΒ #3257 disconnected-channel guards β€” v15's useChannelConfig reads from client.configsStore and never calls channel.getConfig(), so the render-phase crash is structurally impossible and that part of the fix is superseded. Two effects still mattered and are kept: the channel.disconnected early-return in Channel.handleEvent (v15 still calls countUnread() / muteStatus() / query() there), and AttachmentSelector degrading to no actions when disconnected rather than offering actions that cannot succeed.

Superseded by v15, so master's side was dropped

  • Channel's reducer and throttled copyStateFromChannelOnEvent machinery.
  • useMessageDeliveryStatus's event listeners β€” status is now derived from the LLC's messageReceiptsTracker snapshot store.
  • The inline navigation in MessageAlsoSentInChannelIndicator β€” now the useMessageAlsoSentInChannelNavigation hook, with the onView escape hatch.

Fixes needed to make the merged tree work

  • Import cycle (worth a close look). feat(MessageComposer): introduce context for custom composersΒ #3249 added useMessageComposerController β†’ ../MessageComposer, an edge v15 did not have. Combined with v15's barrel imports it formed a cycle that left useMessageComposerController undefined at render and broke 163 tests. Extracted the context into MessageComposerControllerContext.tsx and re-exported it from MessageComposer.tsx, so the public API is unchanged.
  • PollOptionWithVotesHeader had been auto-resolved to master's PollOption type; every other file under src/components/Poll/ uses v15's PollOptionResponseData, so it was switched.
  • QuotedMessagePreview uses IconNoSign for deleted messages (v15-only) and master's IconSet did not cover it β€” threaded through the icon set so it stays overridable instead of remaining a static import.

Docs

master collapsed CLAUDE.md to @AGENTS.md, so v15's architecture notes had nowhere to live. Folded them into AGENTS.md: context layers, state management, WebSocket event processing, memoization, and the thread/channel message invariants. Without this the merge would have silently restored v14 guidance.

#3254 coverage

master's Channel.test.tsx regression test asserted against ChannelStateContext and loadMore, both removed in v15, so it could not be carried through the merge. Replaced with three tests written against the v15 architecture:

  • does not throw when re-rendering after the channel disconnects while mounted β€” the reported crash.
  • does not read the config off the disconnected channel during render β€” locks the mechanism rather than the symptom. useChannelConfig resolves from client.configsStore by cid so the channel instance is never touched; the guarded getChannelConfig(channel) helper would satisfy this too, an unguarded channel.getConfig() would not.
  • ignores events dispatched for a disconnected channel β€” covers the handleEvent early-return, since the user.deleted branch re-queries the channel.

Each was verified to fail against unfixed code first: reintroducing the render-phase channel.getConfig() fails the first two with the real You can't use a channel after client.disconnect() was called, and removing the handleEvent guard fails the third with 2 unexpected query calls. The useMarkRead and AttachmentSelector #3254 tests from master also survive, adapted to v15's helpers.

Verification β€” all run against the merged tree:

Check Result
vitest run 230 files, 2780 passed, 1 skipped, 0 failed
tsc -p tsconfig.lib.json --noEmit 0 errors
eslint src --max-warnings 0 clean
prettier --list-different clean

The 163-test failure from the import cycle was confirmed as merge fallout, not pre-existing, by running the same specs against pre-merge release-v15 in a scratch worktree (they passed there).

🎨 UI Changes

None intended β€” this is a merge with no new UI. The behavioural deltas are the ported local unread count and the disconnected-channel degradation described above.

oliverlaz and others added 9 commits July 28, 2026 15:28
…tutorial (#3251)

### 🎯 Goal

`examples/tutorial` had drifted out of sync with the published [React
chat tutorial](https://getstream.io/chat/sdk/react/tutorial/), which was
restructured in GetStream/getstream.io#345 into numbered steps 0-7 plus
two optional recipes. Folder numbering no longer lined up with the
tutorial's step numbers, and there was no runnable counterpart for the
theming step at all.

Two things also made the example unusable as it stood:

- It did not boot on a clean install. Two React instances, `Invalid hook
call` on first render. Reproduces on `master`.
- The preview panel clipped the chat UI at the edges and pushed the
composer below the fold.

The point of this example is that a reader can run the exact code the
tutorial gave them, so a folder here needs to map 1:1 to a heading
there.

### πŸ›  Implementation details

Three commits, each independently revertable.

**1. Step alignment** (`244f77697`)

Folders renumbered to match the tutorial, and the two non-linear steps
renamed to `optional-*`:

| Folder | Tutorial section |
| --- | --- |
| `2-client-setup` | Step 2 - Connect the client |
| `3-core-component-setup` | Step 3 - Get a working chat UI |
| `4-channel-list` | Step 4 - Add a channel list |
| `5-theming` | Step 5 - Theme it (**new**) |
| `6-custom-ui-components` | Step 6 - Replace an SDK component |
| `7-emoji-picker` | Step 7 - Enable the emoji picker and autocomplete |
| `optional-custom-attachment-type` | Optional - add a custom attachment
type |
| `optional-livestream` | Optional - a livestream-style chat app |

The tutorial's Step 0 (environment) and Step 1 (project + credentials)
have no runnable counterpart, so numbering starts at 2.

Also in this commit:

- **Preview panel layout fix.** The chrome panes are sized in viewport
units *and* padded, so under the default `content-box` the padding was
added on top of `100vh` β€” 1312px of content in a 1272px viewport. Fixed
with `border-box` on the six named chrome classes only, deliberately not
`.tutorial-browser *`, so the SDK's own box-sizing is untouched.
- **Entry cleanup.** Removed the 16 per-step `main.tsx` / `index.html`
files. They date to #2697, when the step browser did not exist and
booting a step's own HTML was the only way to run it. Since the browser
landed they have been dead weight: `vite build` only ever emitted
`dist/index.html`, nothing referenced them, and all eight `main.tsx`
were byte-identical. Each step folder now holds exactly the files the
tutorial tells you to create.

**2. React dedupe** (`4b865aabc`)

`stream-chat-react` is consumed as a workspace dependency, so Vite
serves its built output from outside the app's root and resolves that
copy's `react` import separately from the app's. The SDK and the app end
up on two React instances and the first hook call throws.
`resolve.dedupe: ['react', 'react-dom']` forces both onto a single copy.

Split out on its own because it is the one change that is not tutorial
content β€” it can be cherry-picked or reverted independently.

**3. Step stylesheet cleanup** (`6a67e4447`) β€” no rendering change

The step browser renders every step in one document, so all seven
stylesheets are live at once. Steps 3 and 4 import the SDK stylesheet
*unlayered* (as the tutorial has them, since Step 5 is where you are
taught to move it into a layer), and unlayered CSS outranks every
`@layer` regardless of specificity β€” so the tutorial's `@layer
stream-overrides { .custom-theme { ... } }` silently does nothing here.

The themed steps declare the tokens unlayered on
`.str-chat.custom-theme` instead. `Channel.tsx:163` and
`ChannelList.tsx:342` both do `clsx('str-chat', theme, ...)`, so the
theme class lands on the same element as `str-chat`; at 0,2,0 this beats
the SDK's own `.str-chat` (0,1,0) regardless of source order, and it
only matches steps that actually pass `theme="custom-theme"`, so it
cannot leak into the unthemed ones. README flags this as the one
deliberate deviation, with a pointer to keep the tokens in the layer in
your own app.

That leaves `layout.css` with exactly **two distinct versions**,
mirroring the tutorial's two, byte-identical within each group so drift
shows up in a diff.

Also: `html` / `body` / `#root` were previously declared *only* in the
step stylesheets, so the chrome silently depended on a step's CSS for
`body { margin: 0 }` and would pick up the UA margin if steps were ever
loaded lazily or in isolation (`2-client-setup` has no `layout.css` at
all). `tutorial-main.css` now declares them itself.

Parts of each `layout.css` copy are inert inside the step browser and
stay that way on purpose, since the file has to remain a faithful copy
of what the tutorial has readers write. README documents each case:

- the `custom-theme` tokens do nothing in `7-emoji-picker` and
`optional-livestream`, which do not pass `theme="custom-theme"` β€”
matching the tutorial, where the reader's single `layout.css` holds the
tokens and leaves them unused for those same two examples
- the `.str-chat__*` widths lose to `.tutorial-browser__step-shell
.str-chat__*` in `tutorial-main.css` (0,2,0 against 0,1,0); the
tutorial's widths assume the app owns the whole page, here it is sized
to fit a preview card

No bundle cost either way β€” Vite collapses the identical copies, so the
built CSS contains one `width: 30%` and one `@layer stream`.

### 🎨 UI Changes

No change to the SDK itself, and no change to any tutorial code block.
The visual deltas are all in the example app:

- **New `5-theming` step**, so the tutorial's theming milestone is now
runnable.
- **Preview panel no longer clips.** Before, the chat UI was cut off at
the card's edges and the composer sat below the fold. Now every step
reports `overflowX/Y: 0` with the preview card fully within the
viewport.
- **Step 2 corner padding.** `2-client-setup` renders bare text with no
chat chrome, so it landed inside the card's 28px corner arc and the
first glyph was clipped. Padded via the `step-client-setup` class.

Verified across all eight steps by reading computed styles after
switching:

| Step | Theme class on `.str-chat` | `--str-chat__accent-primary` |
| --- | --- | --- |
| 3, 4 | `messaging light` | `#005fff` |
| 5, 6, `optional-custom-attachment-type` | `custom-theme` | `#0d47a1` |
| 7 | `messaging light` | `#005fff` |
| `optional-livestream` | `str-chat__theme-dark` | `#4586ff` |

Step 7 intentionally shows the default accent β€” the tutorial's emoji
block renders `<Chat>` without the `theme` prop, and [notes so
explicitly](GetStream/getstream.io#345).

`tsc -b`, `vite build`, and gated prettier all pass. No console errors
on any step.

To see it: `yarn start:tutorial` from the repo root.

---

Opened without a reviewer since I am not sure who owns this area β€” happy
to add whoever should look at it.

Companion PR: GetStream/getstream.io#345


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Expanded the tutorial browser with updated step structure, channel
list, theming, custom UI components, emoji picker, and livestream
(including optional milestones).
* **Documentation**
* Refreshed the tutorial README with current folder/step mapping and
step-browser rendering notes.
* **Bug Fixes**
* Improved tutorial preview/layout sizing to avoid viewport
clipping/overflow.
  * Prevented issues from multiple React instances during development.
* **Refactor**
* Reworked step mounting and applied consistent theme overrides across
steps.
* **Chores**
* Updated the example environment key name to `VITE_STREAM_API_KEY`
(with legacy fallback).
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
### 🎯 Goal

Adds ability to override icons through the component context. 

Ref: GetStream/stream-chat-react-native#3731


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Added `useComponentContextIcons`, enabling centralized, context-based
icon customization (with SDK defaults) across messages, attachments,
dialogs, forms, media, polls, reactions, and channel views.
* **Bug Fixes**
* Improved icon override merging so nested `icons` customizations are
preserved and combined correctly instead of being replaced.
* **Documentation**
* Expanded deprecation guidance for select icon customization props in
favor of `ComponentContext` icon slots.
* **Tests**
  * Updated test mocks to support component-context icon resolution.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
### 🎯 Goal

Ref: GetStream/stream-chat-react-native#3679


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Bug Fixes**
* Improved unread-count accuracy for channels using local unread
tracking.
* Marking messages as read now updates channel lists and delivery
indicators consistently.
  * Read-state updates no longer incorrectly reset unread counts.
* Hidden-tab unread indicators now stay synchronized with local unread
activity.
* Read actions continue to work when server read events are unavailable.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
### 🎯 Goal

`@emoji-mart/react` declares `react` as `^16.8 || ^17 || ^18` in its
`peerDependencies` β€” React 19 is missing. Integrators on React 19
therefore hit peer-dependency resolution errors on install and have to
add `package.json` overrides to get past them, even though the package
works fine on React 19 in practice.

The wrapper that package provides is ~20 lines of glue around the
`emoji-mart` `Picker` custom element. Rather than asking every React 19
integrator to carry an override, we vendor it and drop the dependency.

### πŸ›  Implementation details

**Vendored the wrapper** - new `src/plugins/Emojis/Picker.tsx`, taken
from
[`@emoji-mart/react`](https://github.com/missive/emoji-mart/blob/16978d04a766eec6455e2e8bb21cd8dc0b3c7436/packages/emoji-mart-react/react.tsx)
(MIT, Copyright (c) Missive). Behaviour is identical to upstream;
### 🎯 Goal

Agent-facing documentation in this repo was spread across three files
that nothing verified, and all three had drifted.

**`AI.md`** was a hand-maintained integration guide for AI assistants,
shipped in the npm tarball. It duplicates what the official docs and
tutorial already cover. Every item below is wrong on `master` today:

| Claim in `AI.md` | Reality |
| --- | --- |
| `import 'stream-chat-react/dist/css/v2/index.css'` β€” **6 occurrences**
| `build-styling` emits `dist/css/index.css`; there is no
`dist/css/v2/`, so this import cannot resolve |
| `examples/tutorial/src/4-custom-ui-components/` |
`6-custom-ui-components` |
| `examples/tutorial/src/7-livestream/` | `optional-livestream` |
| `examples/tutorial/src/6-emoji-picker/` | `7-emoji-picker` |
| `examples/vite/src/stream-imports-theme.scss` | does not exist
(`examples/vite/src/index.scss`) |
| `stream-chat`: `^9.27.2` | `^9.50.2` |
| install `@emoji-mart/react`, add React 19 overrides | no longer a
dependency (#3255) |

An AI-facing guide that hands out a CSS import path which doesn't exist
is worse than no guide β€” it produces confidently broken integrations. We
now publish maintained [agent
skills](https://getstream.io/agent-skills/docs/installation/) that cover
integration properly and stay in sync across SDKs.

**`CLAUDE.md` and `AGENTS.md`** covered overlapping ground with no
shared source, so each drifted independently β€” `CLAUDE.md` still
documented Jest, a Playwright e2e suite, and a `MessageInput` component,
none of which exist. Two files describing one repo is the reason they
were both wrong.

**`yarn types`** silently checked nothing (details below), so the type
errors a contributor expected it to catch went unreported.

### πŸ›  Implementation details

#### 1. Deleted `AI.md`

423 lines, and removed from the `files` array in `package.json` β€” it was
being published to npm, so this drops a file from the package tarball,
not just from the repo.

**Added a `Build with AI Agents` section to `README.md`**, directly
after *React Chat Tutorial*. The tutorial is presented as the best way
to get started, so the agent-driven path belongs beside it rather than
buried further down. It documents the install (`curl -fsSL
https://getstream.io/cli.sh | bash` + `getstream init`), links
[`/stream-react`](https://getstream.io/agent-skills/docs/skills/stream-react/),
and shows example invocations. Three entry points, so it's discoverable
however someone scans the README: a `Quick Links` bullet at the top, the
section itself, and a cross-reference from the existing "Using AI
assistants" block at the bottom β€” that block points at `AGENTS.md`,
which is about *contributing to this repo*, a different audience from
someone integrating the SDK.

Content came from the live docs pages rather than memory, so the install
command, the four skill capabilities (scaffold / enhance / audit /
migrate, including Sendbird β†’ Stream Chat) and the supported-agent list
match what the docs actually say.

#### 2. `AGENTS.md` is now the single source; `CLAUDE.md` imports it

`CLAUDE.md` is reduced to a pointer ending in `@AGENTS.md`, which Claude
Code expands inline. `AGENTS.md` absorbed the architecture content and
keeps its own contribution rules, so there is one file to maintain for
every agent that reads this repo β€” and `AGENTS.md` is already the
filename Copilot, Cursor, Codex and Aider read.

An import rather than a symlink: git symlinks degrade to a plain text
file on Windows checkouts with `core.symlinks=false`, which would leave
Claude Code with no guidance at all.

Every claim in the merged file was re-derived from source rather than
carried over. Corrections:

| Was documented | Reality in `src` |
| --- | --- |
| "Run Jest tests", `yarn e2e`, `yarn e2e-fixtures` | Vitest only; no
Jest, no Playwright suite in this repo |
| `<MessageInput>` + `MessageInput/hooks/` | Directory no longer exists
β€” it's `MessageComposer`, backed by `stream-chat`'s `MessageComposer`
class |
| `<Channel Message={CustomMessage} />` | `ChannelProps` carries no
component slots; overrides go through `<WithComponents overrides={{ …
}}>` |
| `useStateStore(chatClient.state.channelsArray)` | A selector is
required; shallow-compares selected keys |
| 3 bundle entry points | 4 β€” `channel-detail` was added;
`build-styling` emits 4 stylesheets |
| `css-reset β†’ stream-new β†’ …` layers | `modern-normalize, stream-new,
stream-new-plugins, stream-overrides, stream-app-overrides` |
| `_global-theme-variables.scss` | `variable-tokens.scss` +
`light.scss`/`dark.scss` |
| Yarn binary pinned to `yarn-4.14.1.cjs` | Now unpinned by filename
(it's 4.15.0 and moves) |
| "Never commit directly to `main`" | Default branch is `master` |
| Styling / Build / i18n sections duplicated verbatim | Deduplicated |

Added, because it isn't discoverable without reading several files:
composer state ownership and the `client.messageComposerCache`
resolution order; the Vite 8 / Rolldown constraints in `vite.config.ts`
and why they must not be "simplified" (hardcoded `es`/`cjs` output dirs,
regex externals for subpath imports); the `npmMinimalAgeGate: 1d` /
`enableScripts: false` dependency gates; the Vitest setup contract and
`mock-builders` inventory; `src/a11y` primitives; i18n `keySeparator:
false` (keys legitimately contain `/`); and the CI job list.

The architectural sections that still held were each re-verified against
source before being kept: the 500ms/200ms/500ms-leading/2000ms throttles
in `Channel.tsx`, `PREPEND_OFFSET = 10 ** 7`, the `processMessages`
ordering, the string-serialization memoization FIXME in
`useCreateChannelStateContext`, `areMessageUIPropsEqual`'s cheap-prop
ordering, and the `react-compat` ESLint block.

#### 3. `yarn types` now type-checks `src`

```diff
-    "types": "tsc --emitDeclarationOnly false --noEmit",
+    "types": "tsc --project tsconfig.lib.json --noEmit",
```

Without `--project`, `tsc` resolves the root `tsconfig.json` β€” a
solution-style config with `"files": []` and project references only. It
therefore checked **no files**, exited in under a second and always
passed. `--emitDeclarationOnly false` is dropped: it only existed to
dodge the old TS5053 error when `--noEmit` met `emitDeclarationOnly`,
and under this repo's TypeScript 6 the output is byte-identical without
it (diffed both forms).

`yarn types:tests` is left alone but is now documented honestly: it is
**not run by CI** and is currently red repo-wide (~1300 errors), so
`AGENTS.md` frames it as advisory against a baseline rather than a green
gate. Worth a follow-up; out of scope here.

### βœ… Verification

- `yarn lint` β€” exit 0 (prettier covers Markdown in this repo)
- **`yarn types` was verified to actually check, not merely to run**:
injected `const __typecheck_probe: number = "not a number"` into
`src/utils/getChannel.ts`, confirmed it was reported as `TS2322`, then
reverted. Before this change the same probe produced no output.
- Nothing in CI invokes `yarn types`, so enabling it cannot turn CI red.
The same `tsconfig.lib.json` is already compiled by `yarn build` in CI
with `noEmitOnError`, so `src` type errors were failing CI before this
change too β€” this only makes the check runnable locally under a
memorable name.
- `require()` of `package.json` to confirm the earlier `files` edit kept
it valid; no remaining references to `AI.md` anywhere in the repo.

### 🎨 UI Changes

None β€” documentation and one script definition.
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Documentation**
* Added an β€œAI Agent Skills” quick link and guidance for building,
upgrading, integrating, auditing, and migrating Stream Chat React
applications with AI agents.
* Replaced outdated repository guidance with expanded documentation
covering development workflows, architecture, testing, accessibility,
styling, troubleshooting, and contribution practices.
  * Removed the obsolete AI integration guide and related references.
* Updated published package contents to reflect the documentation
changes.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
…cted channels (#3257)

### 🎯 Goal

Fixes: #3254

`ChannelInner` called `channel.getConfig()` directly in the component
body. That call throws `You can't use a channel after
client.disconnect() was called` once the channel is disconnected β€” which
happens when the current user is removed from a channel or the channel
is deleted. The flag is flipped by an async WS event while `<Channel>`
is still mounted, so the throw landed in the render phase and tore down
the surrounding subtree.

Same failure class as #2393 and #3248.

### πŸ›  Implementation details

Added an internal `getChannelConfig(channel)` helper that returns
`undefined` for a disconnected channel instead of calling `getConfig()`,
and applied it everywhere the config was read during render or in an
effect:

- `Channel.tsx` β€” the reported crash. Now also a lazy `useState`
initializer, so the call no longer re-runs on every render.
- `AttachmentSelector.tsx` and `useMessageComposerCommands.ts`
- `useMarkRead.ts`

`handleEvent` in `Channel.tsx` also early-returns for a disconnected
channel, and the composer skips draft creation on unmount.

`loadMoreNewer` picked up the `channel.disconnected` guard that
`loadMore` already had β€” without it, scrolling to the bottom of a
disconnected channel still queried a dead channel on every attempt
(caught by the existing `try`/`catch`, so only log noise and a redundant
dispatch).

Fixing `Channel` alone is not enough: the crash relocates to
`AttachmentSelector` once `ChannelInner` stops throwing and its subtree
starts rendering.

`undefined` is already part of `getConfig()`'s return type, so
degradation is graceful β€” no read events, no commands, and the
attachment selector renders nothing instead of crashing.

> **Overlaps with #3249**, which adds the same `if
(messageComposer.channel.disconnected) return;` line along with a more
complete treatment of that effect (`.catch()` on `createDraft()`, a
drafts-enabled check, and `preventClearingOnUnmount`). #3249 should own
that effect β€” the line is kept here only so this PR stays independently
mergeable. Whoever merges second should drop the duplicate.

9 tests added, each verified to fail against the unfixed code first.

### 🎨 UI Changes

None.


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

- **Bug Fixes**
  - Improved stability when channels disconnect.
- Prevented pagination, event handling, read receipts, attachment
actions, and composer cleanup from triggering errors after
disconnection.
- Preserved channel configuration safely across re-renders and user
deletion scenarios.

- **Tests**
- Added regression coverage for disconnected-channel behavior across
messaging, composer, pagination, and read-state features.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
### 🎯 Goal

Fixes: #3248 
Closes: REACT-1046

As a side feauture, adds `preventClearingOnUnmount` prop.


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Added inline message editing with an β€œEdit inline” action, prefilled
composer, and cancel support.
* Added support for preserving message composer content when it is
removed from the screen.
* Added flexible composer controller access for customized composition
flows.
* Improved composer selection across channels, threads, and parent
messages.

* **Bug Fixes**
  * Prevented unnecessary cleanup for disconnected channels.
* Improved draft handling and composer state preservation during
navigation and remounting.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
## [14.11.0](v14.10.0...v14.11.0) (2026-08-07)

### Bug Fixes

* **Channel:** guard render-phase channel.getConfig() against disconnected channels ([#3257](#3257)) ([f60273f](f60273f)), closes [#3254](#3254) [#2393](#2393) [#3249](#3249)
* **EmojiPicker:** drop @emoji-mart/react peer dependency ([#3255](#3255)) ([0820e4c](0820e4c))

### Features

* add icons to ComponentContext ([#3246](#3246)) ([972b68c](972b68c))
* localized unread count ([#3250](#3250)) ([1b8fa34](1b8fa34)), closes [GetStream/stream-chat-react-native#3679](GetStream/stream-chat-react-native#3679)
* **MessageComposer:** introduce context for custom composers ([#3249](#3249)) ([5776c16](5776c16)), closes [#3248](#3248)
Brings 14.11.0 into the v15 line: ComponentContext icon slots
(useComponentContextIcons), the local unread count feature, the custom
MessageComposer controller context (#3249), and the disconnected-channel
guards (#3257).

Conflict resolution kept v15's architecture (StateStore paginators,
useChannel, WorkspaceNavigation) and layered master's work on top.

Ported from master:

- icons: conflicted components now read icons from useComponentContextIcons()
  instead of importing them statically, including the relocated
  src/plugins/SlotLayout/ChatView.tsx
- local unread count (markReadLocally / isLocalUnreadCountEnabled /
  message.read_locally) reimplemented in v15 idioms in useMarkRead,
  Channel.handleEvent and ChannelListItem
- #3257: v15's useChannelConfig reads from client.configsStore and never calls
  channel.getConfig(), so the render-phase crash is structurally gone. Kept the
  handleEvent disconnected early-return and AttachmentSelector's
  degrade-to-no-actions behaviour

Superseded by v15 and dropped:

- Channel's reducer / throttled copyStateFromChannelOnEvent machinery
- useMessageDeliveryStatus's event listeners (now derived from the LLC's
  messageReceiptsTracker)
- the inline navigation in MessageAlsoSentInChannelIndicator (now the
  useMessageAlsoSentInChannelNavigation hook)

Also:

- extracted MessageComposerControllerContext into its own module to break an
  import cycle introduced by #3249 meeting v15's barrel imports
- CLAUDE.md is now just @AGENTS.md, so v15's architecture notes moved into
  AGENTS.md
- master's Channel.test.tsx additions test ChannelStateContext and loadMore,
  both removed in v15, so the #3254 regression test for Channel is not carried
  over; the useMarkRead and AttachmentSelector #3254 tests are

Verified on the merged tree: 2777 tests pass (230 files), tsc clean,
eslint --max-warnings 0 clean, prettier clean.
@coderabbitai

coderabbitai Bot commented Aug 7, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

βš™οΈ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 9a718d7d-966d-4212-9180-d27ec590eed6

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • πŸ” Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❀️ Share

Comment @coderabbitai help to get the list of available commands.

@oliverlaz
oliverlaz had a problem deploying to Vite Example Public (Preview) August 7, 2026 11:12 — with GitHub Actions Failure
@oliverlaz
oliverlaz had a problem deploying to Vite Example Development (Preview) August 7, 2026 11:12 — with GitHub Actions Error
@codecov

codecov Bot commented Aug 7, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 83.14176% with 44 lines in your changes missing coverage. Please review.
⚠️ Please upload report for BASE (release-v15@59b7636). Learn more about missing BASE report.

Files with missing lines Patch % Lines
src/plugins/ChannelDetail/ChannelDetail.tsx 33.33% 10 Missing ⚠️
...MemberDetailView/ChannelMemberActions.defaults.tsx 33.33% 10 Missing ⚠️
...rc/components/Attachment/LinkPreview/CardAudio.tsx 0.00% 4 Missing ⚠️
src/components/Modal/CloseButtonOnModalOverlay.tsx 0.00% 4 Missing ⚠️
src/plugins/Emojis/EmojiPicker.tsx 0.00% 3 Missing ⚠️
...ListItem/ChannelListItemActionButtons.defaults.tsx 75.00% 2 Missing ⚠️
...Views/ChannelMembersView/ChannelMembersAddView.tsx 33.33% 2 Missing ⚠️
src/components/Attachment/VisibilityDisclaimer.tsx 0.00% 1 Missing ⚠️
src/components/Dialog/components/Viewer.tsx 0.00% 1 Missing ⚠️
src/components/Message/PinIndicator.tsx 0.00% 1 Missing ⚠️
... and 6 more
Additional details and impacted files
@@              Coverage Diff               @@
##             release-v15    #3260   +/-   ##
==============================================
  Coverage               ?   83.80%           
==============================================
  Files                  ?      527           
  Lines                  ?    16088           
  Branches               ?     5063           
==============================================
  Hits                   ?    13483           
  Misses                 ?     2605           
  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.

@oliverlaz
oliverlaz marked this pull request as ready for review August 7, 2026 11:26
master's #3254 regression test asserted against ChannelStateContext and
loadMore, both removed in v15, so it could not be carried through the merge.
These replace it against the v15 architecture.

- does not throw when re-rendering after the channel disconnects while mounted
  β€” the reported crash
- does not read the config off the disconnected channel during render β€” locks
  the mechanism. useChannelConfig resolves from client.configsStore by cid, so
  the channel instance is never touched; the guarded getChannelConfig(channel)
  helper would satisfy this too, an unguarded channel.getConfig() would not
- ignores events dispatched for a disconnected channel β€” covers the
  handleEvent early-return, since the user.deleted branch re-queries

Each verified to fail against unfixed code first: reintroducing the
render-phase channel.getConfig() fails the first two with the real
"You can't use a channel after client.disconnect() was called", and removing
the handleEvent guard fails the third with 2 unexpected query calls.
@oliverlaz
oliverlaz had a problem deploying to Vite Example Development (Preview) August 7, 2026 11:51 — with GitHub Actions Error
@oliverlaz
oliverlaz had a problem deploying to Vite Example Public (Preview) August 7, 2026 11:51 — with GitHub Actions Failure
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.

3 participants