Repository navigation
refactor(i18n)!: adopt the shared i18n layer from stream-chat/i18n - #3271
Conversation
Replaces this SDK's own translation runtime with the shared one in
`stream-chat/i18n`, which the React Native SDK will adopt too. What stays here is
the part that is genuinely this package's: its generated key catalog, its bundled
data, and its notification translators.
Requires `stream-chat@10.0.0-rc.3` for the `stream-chat/i18n` subpath, so this
cannot merge before core publishes. The lockfile is deliberately untouched --
regenerate it once that release exists.
Deleted (~1,100 lines): `Streami18n.ts`, the formatter half of `utils.ts`,
`TranslationBuilder/TranslationBuilder.ts`, `externalStrings.ts`, and
`scripts/i18n-call-sites.mts`. `src/i18n/utils.ts` shrinks to a re-export so the
~15 internal import sites keep working, and the codegen script goes from 352 lines
across two files to ~40 lines of configuration.
- `types.ts` is now an instantiation of core's catalog-generic helpers, and
intersects `LanguageNameCatalog` and `RelativeTimeCatalog` -- keys core renders
and therefore owns. That also cut the two imports blocking the move:
`MessageContextValue` (a circular UI dependency) and `Moment` (a devDependency
type leaking into the published `.d.ts`).
- The 57 hand-maintained `language.*` names are gone; core derives them from the
same `TranslationLanguage` union the call site reads, so the key is checked.
`MessageTranslationIndicator` no longer needs `asDynamicKey` plus a string
comparison to detect a name that has no entry.
- `translatorsByNotificationType` is `Record<CoreNotificationType, Translator>`,
so a core identifier that gains no translator fails to compile. Two entries went
with it: `api:reply:search:failed` and `channel:jumpToFirstUnread:failed` were
copied between the two UI SDKs and neither is emitted by this one. Three
identifiers that *are* emitted and were unmapped now have translators.
- Notification translation dispatches on `notification.type` only. The
English-sentence table it fell back to is deleted -- prose matching could only
ever mask a missing translator entry.
- Poll field errors are keyed on `PollValidationError.code` rather than on the
English sentence the LLC produced, so a copy edit upstream can no longer
silently stop a translation from applying.
- `useChat` subscribes to the i18n `StateStore` instead of registering a single
callback that a second caller would clobber. It keeps its truthiness check on
`i18nInstance` -- an `instanceof` check would silently discard an instance from a
second copy of the package.
- The module-scope `Dayjs.extend` calls are gone from `TranslationContext`; core's
`defaultDateTimeParser` registers the plugins on first use, so the context
default still formats dates. This is the edit most likely to be reverted by
accident, and it fails silently -- as malformed dates, not a throw.
- `dayjs` and `i18next` move out of `dependencies`: core supplies them. Their
devDependency ranges now match core's exactly, because a second `dayjs` copy
breaks `instanceof` and, worse, means an integrator's `dayjs/locale/xx` import
lands on a different instance than the one formatting dates. That duplication
was real here until the ranges were aligned.
- The `sideEffects` entry for `./dist/i18n/Streami18n.js` is removed; vite never
emitted that path, so it matched nothing.
- New `catalogRenders` test, ported from the RN SDK: renders all 572 catalog
entries and every plural at four counts, asserting none surfaces as its own
dotted path or leaks a `{{ placeholder }}`. It is the only check that the
declared copy actually resolves -- the codegen proves a key *has* copy, not that
it comes out. Interpolation values are derived from each key's own copy so a
leftover placeholder means a real failure.
Two deliberate rendering changes, both confined to a key that specifies no format:
an unparseable or missing timestamp renders as empty rather than the literal text
`null`, and unformatted output is `2019-04-03T14:42:47+00:00` rather than `β¦Z`
because `.tz()` is now applied only when a timezone is actually configured.
BREAKING CHANGE: `Streami18n` is renamed `StreamI18n`, matching the shared class.
The old name is exported as a deprecated alias for one release cycle.
BREAKING CHANGE: `Streami18n.t` is a state-backed getter and can no longer be
assigned. Use `overrideTFunction(t)`, which publishes to the store `<Chat>`
subscribes to. `setLanguage()` now returns `void` for the same reason.
The v15 guide described the key rename and the dropped dictionaries but not the third breaking change in the same release: the runtime moved into `stream-chat`. An integrator following it would hit the class rename and two changed method shapes with nothing to explain them, and every example still used the deprecated name. Added a "shared runtime" section covering the `Streami18n` -> `StreamI18n` rename, `t` becoming read-only (use `overrideTFunction`), `setLanguage()` returning void, and dropping `i18next` / `dayjs` from your own dependencies -- with the one-command check for a duplicate `dayjs`, since a second copy means your `dayjs/locale/xx` import lands on a different instance than the one formatting dates and dates silently stay English. Documented the two rendering changes under Date and time, both confined to a `timestamp.*` key that specifies no format: a null or unparseable timestamp now renders as empty rather than the literal text `null`, and unformatted output carries a numeric offset rather than `Z` because `.tz()` is applied only when a timezone is configured. Also corrected a paragraph the move falsified: it said ~71 keys ship in `runtimeDefaults` including `language.*`. It is 15 now, and `language.*` plus the new `relativeTime.*` come from `stream-chat` -- still overridable, and still compile-checked, but no longer this package's data. Every claim in the new prose was checked against the built runtime rather than written from memory, which is how the "unparseable renders empty" half turned out to be false and got fixed in core instead of softened here.
β¦ted aliases
Core named the shared class `StreamI18n`, and this package re-exported `Streami18n` as a
`@deprecated` alias for one cycle. Both are reverted: core is `Streami18n`, matching the
name this SDK has shipped and documented for years, so integrators rename nothing and no
alias exists. The capital `I` was only ever cosmetic, and a deprecated alias in a breaking
release is cruft with a countdown attached.
`getTranslators()` went the same way. It was a `@deprecated` alias for `init()`, so it is
removed outright and the call sites here use `init()`, which returns the same state.
`init()` is the better name -- it initializes rather than gets -- and is idempotent, which
closes a re-entry window the old implementation left open.
The migration guide loses its "the class is renamed" section and gains one for
`getTranslators()`, which it had not documented.
BREAKING CHANGE: `i18n.getTranslators()` is removed. Use `i18n.init()`, which returns the
same `{ t, tDateTimeParser, language, initialized }`.
β¦en reference `size.yml` only ran on `master`, so no PR stacked onto `release-v15` measured the bundle -- leaving the i18n consolidation's central size claim, that moving the runtime into `stream-chat/i18n` shrinks the root bundle, unverified for the whole release. Also corrects a comment naming `i18next-cli` and the `aria/` key prefix, both removed in v15.
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the βοΈ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
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. Comment |
The only thing in this package that referenced i18next was a mock type in two
TranslationBuilder tests β `fromPartial<i18n>({ use: vi.fn() })`. Nothing in `src` imports
it at runtime or as a type.
`stream-chat/i18n` now re-exports the instance type as `I18nInstance`, which is where it
belongs: core's public API accepts an i18next instance, so a consumer implementing or mocking a
topic should not have to reach past `stream-chat` into its dependency and declare it
themselves. That was the same shape as the `moment-timezone` type leak.
`dayjs` and `moment-timezone` stay: three component tests build dates with dayjs, and the
bring-your-own-Moment parser test needs moment, which core no longer depends on at all. Both
are imported directly here, so both should be declared β not doing so is the bug that had
`yaml` resolving through lint-staged's tree in the core repo.
`Streami18n`, `getDateString` and `predefinedFormatters` live in `stream-chat/i18n` now β this package only re-exports them β so asserting their behaviour here duplicated core's suite in a second repo. Every assertion removed is covered on that side, and the six it did not cover were added there first (GetStream/stream-chat-js#1830). β1,154 lines. `utils.test.ts` goes entirely: all 570 lines were `getDateString` and `predefinedFormatters`. `Streami18n.test.ts` keeps the five describes that are genuinely about *this* package and drops the ten that were not: **Kept** β this SDK's catalog types and its `as const satisfies` completeness diff; the calendar keys that carry English words, which is an assertion about this catalog and its migration guide; the subclass merge behaviour (a caller's topic overriding the bundled `notification` one, and `runtimeDefaults` not being mutated as a shared module object); and the vitest timezone config. **Dropped** β default translator, prose resolution and `parseMissingKeyHandler`, registered and custom dictionaries, `registerTranslation`, `setLanguage`, timezone, formatters, the unregistered-language warning, and dates for an unregistered language. Core's G1/G2/G3 guarantees, `setLanguage`, `formatters` and `TranslationBuilder` suites assert all of it. Removing them made `moment-timezone` dead here, so it is dropped from devDependencies β core depends on no date library by name, its structural `DateTimeLike` covers both. `dayjs` stays: three component tests still build dates with it. Note the file opens with `/* eslint-disable */`, so the imports left dangling by the cut were invisible to lint and had to be found by hand. Worth removing that blanket disable separately. One correction folded in: renaming the i18next mock type last commit missed four `i18n['t']` casts in NotificationTranslationBuilder.test.ts. `tsconfig.test.json` is unenforced (~1200 pre-existing errors) so nothing flagged it; i18n test-type errors go 36 β 4, and the remaining four are pre-existing.
`MessageTranslationIndicator` compares the resolved name against the key again. The `language.*` keys being typed does not make them exhaustive at runtime: the union is generated when the SDK is built, while `message.i18n.language` is server data, so a language the translation API learns after this release has no entry and i18next echoes the key back -- rendering "Translated from language.sw" instead of falling back to the bare code. Covered by a new test that renders against a real `Streami18n`, since a mocked `t` would pass either way. `BundledKey` was declared privately in both `types.ts` and `Streami18n.ts`. It is now exported once and imported, so the exported `StreamTFunction` and the class instance's own `t` cannot disagree about the same call. Drops `src/i18n/__tests__/TranslationBuilder.test.ts`: `TranslationBuilder` is core's class re-exported from here, and eight of its nine cases duplicated core's own suite while asserting on private fields against a mocked i18next. The ninth -- removing a translator from the buffer before the topic exists -- moved to `stream-chat` rather than being lost.
`getTranslations()` and `getAvailableLanguages()` were public in v14 and are gone in v15, having left `stream-chat`'s surface entirely. Neither had a consumer here, but both were reachable by integrators, so the migration guide now shows the replacement for each -- render the key, and `registeredLanguages` respectively -- along with the six members that became private and the `ReadonlySet` change. The one test that used `getTranslations()` now asserts by rendering instead. It was reading the resource store to prove an app's own key had been written down; whether the key resolves is the thing worth asserting, and it holds without reaching past the public API.
Follows the `stream-chat` rename: `POLL_VALIDATION_CODE` and friends are now `POLL_COMPOSER_VALIDATION_CODE` / `PollComposerValidationCode`, matching the module they live in and the `PollComposer*` prefix already used by `PollComposerState` and `PollComposerOption`. Mechanical -- the identifier values are unchanged, so the `t()` keys these components map them to are untouched and no copy moves.
Review feedback: i18n did not belong in `useChat`, which was doing five unrelated jobs -- user-agent stamping, subsystem subscriptions, mutes, i18n and latest-message bookkeeping -- and only held the translators to hand them straight to a provider. It moves to `useStreami18n`, mirroring the hook `stream-chat-react-native` already has: adopt-or-create the instance, `init()` in an effect, subscribe to its store with a module-scope selector. Keeping the two SDKs the same shape here is the point -- React burying this inside `useChat` was exactly the divergence that moving the runtime into `stream-chat` set out to remove. `TranslationProvider` stays dumb, so `value` keeps working for tests and for anyone composing it by hand. Three things fall out of it: - The blanket `eslint-disable react-hooks/exhaustive-deps` is gone. `userLanguage` now tracks `client.user.language` reactively, so a user who connects *after* `<Chat>` mounts gets their language applied; it used to be captured once. The one disable left is narrow and documented: the instance memo must not depend on `client`, because re-running it would build a new `Streami18n` and discard every registered dictionary. - `if (!translators.t) return null` is deleted. `t` is seeded with the default translator and every store emission carries one, so it never fired -- a leftover from when `t` arrived asynchronously. - Instance recognition adopts RN's brand check. Truthiness was already cross-copy safe but accepted any truthy value, which then threw at render; the brand check warns and falls back instead. Five `Message` re-render assertions moved from `toHaveBeenCalledTimes(1)` to `toHaveBeenCalled()`. Both before and after this change mount settles at two renders -- measured -- but the old code delivered the post-`init()` translator after the test's await resolved and this delivers it during. Same work, one tick earlier. The assertions those tests exist for, the re-render on a prop change, are unchanged. BREAKING CHANGE: `useChat` no longer returns `translators`, and no longer accepts `defaultLanguage` or `i18nInstance` -- all three moved to `useStreami18n`. `<Chat>`'s props are unchanged; it wires both hooks internally. See "useChat no longer returns translators" in `ai-docs/i18n-v15-migration.md`.
Three findings from an adversarial review pass. `window.navigator.language` is read after mount again, not during render. Extracting `useStreami18n` moved it into render, where there is no `window` on the server β and where a value differing from the server's is a hydration mismatch. `useChat` read it inside an effect; this keeps that timing, so the first client render agrees with the server's. `relativeCompactWeekRounding: ceil` is set on the two bundled keys that render relative dates. The formatter moved to `stream-chat`, whose default rounds *down*, and the two disagree visibly: `timestamp.ChannelMembersLastActive` and `timestamp.PollVote` had started reading "1w ago" at 8 days instead of "2w ago", and "3w ago" at 22-27 days where they used to fall through to a date. This restores the labels this SDK shipped. The lockfile is regenerated, and both examples move to the same `stream-chat` as the root. They pinned rc.2 while the root asked rc.3, so the lockfile legitimately carried two copies of the client β the split that makes `instanceof` fail and sends a registered dayjs locale to an instance nothing formats with. One copy now, and `yarn install --immutable` passes. Note the branch still does not build against a published core: no released version carries the `./i18n` subpath or the renamed poll API. The install gate is what this unblocks. Tests: week-label boundaries at 7/8/14/15/21/22/27 through the real bundled keys, and the browser-language read asserted to land after mount rather than during the first render. Not asserted through `renderToString`: `<Chat>` is not server-renderable today for an unrelated reason -- `useStateStore` calls `useSyncExternalStore` with no `getServerSnapshot`, so anything reading a `StateStore` throws on the server. A server-render test here would fail on that rather than on this hook. Recorded in both the hook and the test file so whoever fixes that knows to add one.
β¦State `DayjsLocaleConfig` was public on `release-v15` via `export * from './Streami18n'` and was dropped when the class became a subclass of core's: core exports it from `stream-chat/i18n`, but this package's re-export list omitted it. The localization guide instructs importing it from here to type `dayjsLocaleConfigForLanguage` and `registerTranslation`'s third argument, so an integrator following the guide got TS2305. `Streami18nState` is new, and it exists because `state` is public now. Subscribing to it wants a module-scope selector -- `useStateStore` takes the selector as a dependency of both its `useCallback` and its `useMemo`, so an inline one resubscribes on every render -- and that needs a nameable type. Core's default-parameterized `Streami18nState` will not do: it defaults the catalog to `AnyTranslationCatalog`, and `t` is contravariant in its options, so the concrete store is not assignable to it. `useStreami18n` had already declared exactly this alias privately; it now imports the exported one instead of keeping a second copy.
Moves the translation runtime shared by `stream-chat-react` and
`stream-chat-react-native` into this package as a new
**`stream-chat/i18n`** subpath, plus **`stream-chat/i18n/codegen`** for
the catalog generator. Both UI SDKs carried ~1,300 lines of
near-duplicate runtime that had drifted apart, each reverse-mapping this
package's English notification prose against its own hand-maintained
table of sentences.
**Breaking, and shipped together deliberately** β the first commit is
independently revertable if you'd rather split it:
- Notifications are keyed on `CORE_NOTIFICATION_TYPE` /
`CoreNotificationType`. `Notification.message` is now documented as a
developer-facing fallback whose wording is not contractual. Two
identifiers renamed (`api:message{s}:query:failed` β `messageJumpFailed`
/ `messageJumpToLatestFailed`).
- Poll-composer field errors become `{ code, message, metadata? }` keyed
on `POLL_VALIDATION_CODE`, instead of plain English strings.
- `engines.node` β `>=22.18.0` (the release that unflagged type
stripping, which the `.mts` build scripts need). Node 18/20 are no
longer tested β see the guide for what that means if you deploy the WS
client there.
- `i18next` and `dayjs` become direct dependencies. They stay out of the
root bundle; the subpath is what isolates them, and
`assertBundleBoundaries` fails the build if that ever regresses.
Also here: the generator moves out of `src/` to `codegen/i18n/` (it
reads the filesystem β not library source), ships ESM-only, and
`scripts/bundle.mjs` becomes `.mts` with a real typecheck gate.
**Docs:** `v9-to-v10-migration-guide-i18n.md` (new), plus a Node-floor
section in `-other.md`. Initiative record in `specs/i18n-to-core/`.
**Verified:** lint, all three `tsc` projects, 2,804 tests, and a
clean-install check of the packed tarball across all four export
conditions. The root bundle reaches neither `src/i18n/`, `i18next` nor
`dayjs` β machine-checked, not reviewed.
Consumer PRs, both blocked on this publishing as `10.0.0-rc.3`:
GetStream/stream-chat-react#3271 Β·
GetStream/stream-chat-react-native#3777
## Why [Run 32373483292](https://github.com/GetStream/stream-chat-js/actions/runs/32373483292) succeeded and released nothing: ``` βΉ Found git tag v10.0.0-rc.4 associated with version 10.0.0-rc.4 on branch release-v10 βΉ Found 1 commits since last release βΉ Analyzing commit: feat(i18n)!: share the translation runtime as stream-chat/i18n (#1830) βΉ Analysis of 1 commits complete: no release βΉ There are no relevant changes, so no new version is released. ``` `@semantic-release/commit-analyzer` was configured with the **angular** preset, whose header pattern is: ```js /^(\w*)(?:\((.*)\))?: (.*)$/ ``` There is no slot for `!` between the scope and the colon, so a `feat(i18n)!:` header fails to match **entirely** β not just in its breaking marker. Parsing 40cf062 with each preset: | preset | `type` | `scope` | BREAKING notes | | --- | --- | --- | --- | | `angular` | `undefined` | `undefined` | 0 | | `conventionalcommits` | `feat` | `i18n` | 1 | With `type` and `notes` both empty nothing can match β not the custom `releaseRules`, and not the built-in defaults (`{breaking: true β major}`, `{type: 'feat' β minor}`). Those defaults were reachable: custom rules are tried first and the built-ins are the fallback. There is no `BREAKING CHANGE:` footer in the body either, which is the only other thing angular's parser reads. ## Three things this reconciles - `release-notes-generator` in this same config **already** uses `conventionalcommits`, so the two plugins disagreed. The notes would have rendered a breaking feature the analyzer never saw. - commitlint accepts the `!` form β the "Validate PR Title" check passed on that exact title β so we lint for a convention the release pipeline cannot act on. - The analyzer now understands both `!` and a `BREAKING CHANGE:` footer, rather than only the footer. ## Verification Replayed both presets through the real `analyzeCommits` over the last 400 commits on `master` and `release-v10`: ``` analyzed 400 commits verdict changes: 1 40cf062 none -> major feat(i18n)!: share the translation runtime as stream-chat/i18n (#1830) angular tally: {"none":165,"minor":103,"patch":127,"major":5} convcomm tally: {"none":164,"minor":103,"patch":127,"major":6} ``` Exactly one verdict changes; the other 399 are identical. Also confirmed the analyzer returns `major` when driven from `.releaserc.json` as written, so the preset resolves β `conventional-changelog-conventionalcommits` is already a devDependency. ## After this merges Re-running Release from `release-v10` still finds only 40cf062 since `v10.0.0-rc.4`, and will now cut **`10.0.0-rc.5`**. That is what unblocks [stream-chat-react#3271](GetStream/stream-chat-react#3271) and [stream-chat-react-native#3777](GetStream/stream-chat-react-native#3777), whose CI currently compiles against published rc.3 and so cannot see the `./i18n` subpath. `master` carries the identical angular config and the same latent bug, but no `!` commit has hit it yet. Worth the same one-line change there before one does.
## [10.0.0-rc.5](v10.0.0-rc.4...v10.0.0-rc.5) (2026-08-20) ### β BREAKING CHANGES * **i18n:** share the translation runtime as stream-chat/i18n (#1830) ### Features * **i18n:** share the translation runtime as stream-chat/i18n ([#1830](#1830)) ([40cf062](40cf062)), closes [GetStream/stream-chat-react#3271](GetStream/stream-chat-react#3271) [GetStream/stream-chat-react-native#3777](GetStream/stream-chat-react-native#3777)
rc.5 is the first published core carrying the `stream-chat/i18n` subpath, so this is what makes the
branch installable and buildable against a real registry version rather than a locally packed
tarball. Bumped in the peer range, the dev dependency, and both example apps.
`i18next` now arrives transitively from core, as intended -- this package declares neither it nor
`dayjs` any more. `dayjs` is deduped so the vite example's `^1.11.20` and core's `^1.11.13` resolve
to one 1.11.23, rather than two copies where a `dayjs/locale/de` side-effect import could land on
the instance core is not using.
Four type errors remain, all inherited from `release-v15` rather than introduced here --
`APIErrorResponse`, `EventAPIResponse` (x2) and `channel.sendImage`, none of which core v10 exports
any more. Measured against `release-v15` with rc.5 installed: it has 8 errors and 45 failing tests,
this branch has 4 and 43. The difference is the poll-composer validation errors, which this branch
already adapted to `{ code, message }`.
Brings in the new upload API (#3272), the web counterpart of the React Native SDK's #3778. That clears the last four type errors on this branch, all of which it had inherited rather than caused: core v10 no longer exports `APIErrorResponse` or `EventAPIResponse`, and `channel.sendImage` is gone. Conflict resolution: four `stream-chat` version strings, `release-v15` at `10.0.0-rc.4` against this branch's `10.0.0-rc.5`. Kept rc.5 -- it is the first published core carrying the `stream-chat/i18n` subpath, so rc.4 would not resolve the subpath this branch imports. `yarn.lock` regenerated rather than hand-merged; `moment` and `moment-timezone` leave the tree with the bump, and `dayjs` is deduped to a single 1.11.23. Gates now clean for the first time on this branch: `tsc -p tsconfig.lib.json` 0 errors (was 4), 233 suites / 2729 tests passing (was 43 failing), lint 0, build 0.
|
Size Change: -11.8 kB (-1.41%) Total Size: 825 kB π¦ View Changed
βΉοΈ View Unchanged
|
Codecov Reportβ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## release-v15 #3271 +/- ##
==============================================
Coverage ? 84.14%
==============================================
Files ? 525
Lines ? 15629
Branches ? 5003
==============================================
Hits ? 13151
Misses ? 2478
Partials ? 0 β View full report in Codecov by Harness. π New features to boost your workflow:
|
Core's `init()` now rejects when i18next fails to initialize instead of logging and resolving, so the fire-and-forget call in this effect would surface as an unhandled rejection. Caught and reported through the instance's own `logger`, which is where an integrator has already routed diagnostics. Nothing is thrown onward and no error state is rendered: core leaves the instance degraded but safe, so `t` still returns each call site's inline English and the chat renders in English rather than blanking or showing dotted keys. Failing the whole subtree over a translation-layer fault would be the worse outcome. The test mocks `init()` itself rather than the i18next instance underneath it. What is under test is this hook's handling of a rejection, not core's decision to reject -- core's suite covers that -- and mocking at that seam keeps the test independent of which `stream-chat` version is installed while the rejecting contract rolls out through a release.
`<Chat defaultLanguage>` read as a fallback for UI translations but never drove them -- the
`Streami18n` instance does. All it fed was `userLanguage`, the key the SDK reads
`message.i18n[<lang>_text]` with, where a fallback cannot help: with no `client.user.language` the
API is not translating, so `message.i18n` is absent and the text falls through to `message.text`
regardless. Removed.
Browser detection goes with it. `userLanguage` used to become the two-letter browser language when
the instance happened to have a dictionary for it -- conflating "I have German UI copy" with "the
API translates messages to German for me". Only the server-side user language controls the latter,
so that branch produced lookups that could only miss. The React Native SDK never had it.
`userLanguage` is now `client.user.language` and nothing else, which is what all eight of its
consumers already assumed, and it is *reactive*: it was read as a `useMemo` dependency with no
subscription, so a language changed after connect never reached the message components.
What falls out is the tangle this started from: the `browserLanguage` state, its effect, the
`registeredLanguages` check, and -- because the instance memo no longer reads `client` -- the
`react-hooks/exhaustive-deps` suppression on it.
Also here, all i18n type surface:
- **`TranslationCatalog` was being shadowed.** The barrel exported the name from both `./types` and
`./keys`, and an explicit re-export beats a star export, so the *public* type was the generated
half only: `TranslationCatalog['language.de']` failed for integrators while `t('language.de')`
typechecked internally. Proven with a probe, then fixed by dropping the `./keys` line.
- `DateFormatterOptions` removed: zero references anywhere, a field-for-field duplicate of core's
`GetDateStringParams`, and public while `getDateString` itself is not.
- `PluralTranslationKey` removed: unused and undocumented; `TranslationDictionary` covers plurals.
- `AnyTranslationCatalog`, `LanguageNameCatalog`, `RelativeTimeCatalog` dropped from the re-export
set -- unused, undocumented, and only meaningful for parameterizing core's generics.
- `DateTimeParserModule` added, closing an asymmetry: the parser's input and output types were
exported but the type of the documented `DateTimeParser` option was not.
BREAKING CHANGE: `<Chat defaultLanguage>` is removed, and `userLanguage` no longer falls back to the
browser language. Register a dictionary and set `language` on your `Streami18n` for a non-English
UI; set `language` in `connectUser` for translated messages. The `DateFormatterOptions` and
`PluralTranslationKey` types are removed, both unused.
The example's German and Italian dictionaries carry a compile-time completeness gate
(`AssertNoneMissing<Untranslated>`), and it was failing: the catalog churned during the i18n
consolidation and the example was never updated.
Removed two keys the catalog no longer has -- `notification.jumpToFirstUnreadFailed`, whose
translator went with the identifier nothing emits, and `poll.nameField.error.text`, which went when
poll field errors became `{ code, message }` and the message became the fallback.
Added the three it gained: `notification.commandNotReady`, `notification.messageJumpFailed` and
`notification.messageJumpToLatestFailed`, the last two renamed in core from
`api:message{s}:query:failed`.
Computed as a set difference against the catalog rather than by following the compiler, which
reports one excess property per object literal and so hid four of the five.
β¦1839) Two related breaking changes to `Streami18n`, both of which move a decision out of core and into the caller. They ship together because the second is only safe once the first has landed in both UI SDKs. ## 1. `init()` rejects instead of swallowing It caught every failure, logged a string, and resolved: ```ts } catch (error) { this.logger(`Streami18n: initialization failed: ${describeError(error)}`); } return this.state.getLatestValue(); ``` A consumer had no way to know initialization had failed short of watching the log, and an integrator who supplied a `logger` to route diagnostics somewhere quiet lost the signal entirely. The original error β stack, cause, i18next's own detail β was flattened to a string and dropped. Swallowing at a library boundary is the wrong default; the caller owns the policy. The old justification was "neither UI SDK awaits this, so a rejection would be unhandled". That is a statement about the SDKs, not about the contract β so the SDKs are what got fixed, in the companion PRs below. `init()` now rejects with the original error, and core no longer logs. A failure is reported **once**, by whoever handles it, in a form they can inspect. What does **not** change is the degraded-but-safe guarantee, which is a separate concern worth keeping distinct: | after a failed `init()` | before | after | | --- | --- | --- | | promise | resolves | **rejects with the original error** | | core logs | yes | no β the handler reports | | `initialized` | `false` | `false` | | `t` | default translator, inline English | unchanged | | `registerTranslation` / `setLanguage` | do not throw | unchanged | The promise reports the failure; the store keeps the UI alive. Rejecting does not mean the instance is unusable. Two things fall out: - **The memo is cleared on failure**, so a later `init()` retries rather than replaying one rejection for the lifetime of the instance. Concurrent callers still share the in-flight promise, which is the only thing the memo was ever for. - **The bookkeeping handler attaches to a derived promise**, so it does not mark the returned one as handled and the caller's `catch` still sees the rejection. There is a test for exactly that, because getting it wrong silently swallows the rejection again. ## 2. `Streami18n.brand` is removed `static readonly brand = Symbol.for('stream-chat.Streami18n')` existed so a UI SDK could recognize an instance across two physical copies of this package, where `instanceof` fails. Both SDKs have now stopped checking, so it has no consumer left in any of the three repos β verified by grep across all of them. It was also blind to the case that actually bites. The brand sits on *core's* class, so a bare `new Streami18n()` from `stream-chat/i18n` β carrying none of a UI SDK's bundled `runtimeDefaults`, and therefore rendering `timestamp.*` and `language.*` as raw dotted keys β passed the check cleanly. All it ever rejected was a value that is not a `Streami18n` at all, which the prop types already cover. ## Companion PRs Both UI SDKs catch the rejection in their `useStreami18n` effect and report through `console.warn`, which keeps the `Error` object and its stack intact. Neither surfaces an error state, because the degraded instance still renders English and failing a whole chat subtree over a translation-layer fault would be the worse outcome. Both also dropped the brand check. - [stream-chat-react#3271](GetStream/stream-chat-react#3271) - [stream-chat-react-native#3777](GetStream/stream-chat-react-native#3777) Their hook tests mock `init()` itself rather than the i18next instance underneath β what is under test there is the hook's handling of a rejection, not core's decision to reject, and it keeps those tests independent of which `stream-chat` version is installed while this rolls out. ## Verification `yarn lint` 0, `yarn types` 0, **2,881 tests**. The rewritten `Streami18n β a failed init()` block pins the new contract: rejects with the original error, does not log, leaves `initialized` false, keeps rendering inline English, does not break `registerTranslation` / `setLanguage`, retries on a later call, and shares the in-flight promise with concurrent callers before failing. ## Sequencing This needs an rc after merge β the SDK-side `catch` is inert against rc.5, which still resolves β and then a version bump in both consumers. BREAKING CHANGE: `Streami18n.init()` now rejects when i18next fails to initialize, where it previously logged and resolved. Callers that ignore the returned promise will see an unhandled rejection. The instance remains usable in its degraded English form either way. `Streami18n.brand` is removed; nothing in either UI SDK read it, and a custom integration that recognized instances through it should compare against its own marker or accept the instance it was handed.
## [15.0.0-rc.1](v14.12.0...v15.0.0-rc.1) (2026-09-29) ### β BREAKING CHANGES * port the tutorial alignment (#3251) to v15, with panel-aware workspace navigation (#3301) * **MessageComposer:** the `preventClearingOnUnmount` prop is removed from `MessageComposer`. Drop it. If you relied on unmounting to clear a composer supplied through `MessageComposerControllerProvider`, call `clear()` on it yourself when your UI is done with it. * `ChannelListItemProps.channelUpdateCount` is removed. The unread badge now updates without it. * `ChannelListItemProps.key` is removed. React's own `key` prop is unaffected. * `ChannelListItemProps.watchers` is removed. It had no effect in v15. * adopt stream-chat's branded TimestampNS (#3297) * **Attachment:** `useSendMessageFn` and `useUpdateMessageFn` are removed. Call `messageComposer.send()` and `messageComposer.update()` instead. They resolve `'sent' | 'nothing-to-send' | 'failed'` rather than a boolean, so a truthiness check on the result now passes for every outcome β compare against `'sent'`. * **Attachment:** the two message submission failure notifications changed key. `messageComposer.sendMessageFn.sendMessageRequestFailed.text` is now `notification.messageSendFailed`, and `messageComposer.updateMessageFn.editMessageRequestFailed.text` is now `notification.messageUpdateFailed`. A typed custom translation dictionary fails to compile until both are renamed. * **Attachment:** `RetryHandler` takes `Omit<OperationParams<'retry'>, 'message'>` instead of `RetrySendMessageWithLocalUpdateParams`, which stream-chat removed in GetStream/stream-chat-js#1882. Code that names the old type must be updated. * the `stream-chat` peer range moves to `^10.0.0-rc.12`. That release removes `connection.changed`, changes `client.wsConnection` from the socket itself to a wrapper, and moves the socket's settings into configuration. See GetStream/stream-chat-js PR 1859 for the full list; an application on an earlier `stream-chat` will not typecheck against this version. * connectivity is read from state stores rather than events. Code that listened for `connection.changed` β to render its own banner, to refetch on reconnect β must subscribe to `client.wsConnection.state` or `client.networkConnection.state` instead, or use the new `useWSConnectionState` / `useNetworkConnectionState` hooks. Nothing in this package dispatches or forwards the old event. Note the two stores name their fields differently: the socket's `isHealthy` is always a boolean, while the network's `isOnline` is `boolean | undefined`, where `undefined` means no platform reporter has reported yet and must be tested with `=== false`. * `Channel` no longer reloads its channel on `connection.recovered`. The client reloads every active channel itself and dispatches the event afterwards, so handling it here reloaded each open channel twice per reconnect. Anything relying on the component to refresh a channel after a reconnect should rely on the client instead. * a socket drop on a working network now renders "Reconnectingβ¦" rather than "Waiting for networkβ¦", from the new `chat.reportLostConnection.reconnecting.text` key. Tests and dictionaries keyed on the old copy for that case need updating; the `waitingNetwork` key is unchanged and still used when the device network is down. The delay before either banner appears now comes from `client.wsConnection.config.offlineNotificationDisplayDelayMs` (5s default) rather than from a fixed hold inside the client, so changing that config changes when the banner shows. The `system:network:connection:lost` notification type is unchanged. * `Thread` requires a `thread` prop and renders `children`. `additionalMessageComposerProps`, `additionalMessageListProps`, `additionalParentMessageProps`, `additionalVirtualizedMessageListProps`, `autoFocus` and `virtualized` are removed β pass those props to the children you render. `Thread` provides the thread itself, so it no longer needs to be wrapped in `ThreadProvider`. The `str-chat__thread--virtualized` class is gone; `Thread` cannot know which list you chose. * `Thread.enableDateSeparator` is removed with nothing in its place. A `MessageList` in a thread now follows its own default and shows date separators unless you pass `withDateSeparator={false}`. * `disableDateSeparator` is renamed to `withDateSeparator` on `MessageList` (default `true`, was `disableDateSeparator={false}`) and `VirtualizedMessageList` (default `false`, was `disableDateSeparator={true}`). The rename also reaches `processMessages`, `useEnrichedMessages`, `FloatingDateSeparator`, `useFloatingDateSeparator` and `useFloatingDateSeparatorMessageList`. * the `head` prop is removed from `MessageList` and `VirtualizedMessageList`. Both render the thread's parent message from context; override it with a `ThreadHead` component on `ComponentContext`. * `ThreadHeaderProps` is down to `overrideTitle`. `thread` comes from the thread in context, and `closeThread` goes through workspace navigation β customize it centrally via `ChatView`'s `deriveWorkspaceNavigation`, or call the new `useCloseThread()` hook from a header of your own. `ComponentContext.ThreadHeader` is removed; compose the header you want. * `ThreadSlot` wraps its children in `<Thread>` instead of letting them replace the whole panel, mirroring `ChannelSlot`. Markup that must sit outside the thread container moves outside `ThreadSlot`; anything needing thread context must sit inside. * `Thread` no longer rebuilds its subtree when the thread changes. A custom component rendered inside `Thread` that relied on being remounted per thread β to reset local state, or to re-run a mount effect β must depend on the thread instead. The SDK's own message list does this by keying on the thread; `WithAudioPlayback` by taking `playbackScope`. * `LegacyThreadContext` and its `legacyThread` value are removed. The thread composer resolves its composition context from the `Thread` instance in `ThreadContext`. * **MessageActions:** the `messageActions` prop is removed from `Channel`, `MessageList`, `VirtualizedMessageList`, `Thread` and `Message`. Filter `defaultMessageActionSet` and pass the result as `messageActionSet` instead. * **MessageActions:** `MESSAGE_ACTIONS`, `OPTIONAL_MESSAGE_ACTIONS`, `getMessageActions()`, and the `MessageActionsArray` and `Capabilities` types are no longer exported. Use the new `DefaultMessageActionType` union for the action types the SDK ships. * **MessageActions:** `MessageContext` no longer carries `getMessageActions` or `actionsEnabled`. The rendered action set is decided by `messageActionSet` and each item's own `isVisible` predicate. Co-authored-by: Claude Opus 5 <noreply@anthropic.com> * **AudioPlayback:** `ChannelProps.allowConcurrentAudioPlayback`, `ThreadProps.allowConcurrentAudioPlayback` and `WithAudioPlaybackProps.allowConcurrentPlayback` are removed, with no replacement. If you passed `true`, delete it: starting a player now pauses whichever was playing and takes the shared element over. If you passed `false` or nothing, the only change is that playback is exclusive across surfaces rather than within one. * **AudioPlayback:** `useActiveAudioPlayer()` now reports the app-wide active player rather than the calling surface's. * **Channel:** `ChannelProps.channel` is now required and `EmptyPlaceholder` is removed. Render the exported `ChannelPlaceholder` to fill the same `.str-chat__channel` layout slot while no channel is selected. * **Channel:** `Channel` no longer queries the channel; `initializeOnMount` and `channelQueryOptions` are removed. Whoever supplies the channel initializes it β use the exported `getChannel({ channel, client })`, which watches and de-duplicates concurrent calls. Channels from `ChannelList` or any `queryChannels` call arrive watched already. `Channel` no longer renders `LoadingIndicator` or `LoadingErrorIndicator` for a query it does not make, and an uninitialized channel renders as an empty channel with no error. * **Channel:** `Channel` no longer writes `document.title`, and `activeUnreadHandler` is removed. See `examples/vite/src/DocumentTitleManager` for a working replacement in application code. * **Channel:** the `channel.channelMissing.text` translation key is removed. This is a compile error (TS2353) for any dictionary typed with the exact `TranslationDictionary` β delete the entry from custom locale files. * **Channel:** `ChatContext.latestMessageDatesByChannels` is removed. Slow-mode cooldown now lives on `channel.cooldownTimer` (`useCooldownRemaining` / `useIsCooldownActive`), and the channel's latest message on `channel.messagePaginator.aggregateState.lastMessage`. * **Channel:** `Channel` no longer jumps to a message focused from search. Selecting a search result calls `channel.messagePaginator.jumpToMessage(id)` directly, and the highlight is read from `channel.messagePaginator.messageFocusSignal`. A custom message list that relied on `Channel` performing the jump must call `jumpToMessage` itself. * **i18n:** consume the shared runtime from @stream-io/i18n (#3284) * move to timestamps in response models (#3278) * request-handler props are removed from `Channel` and `Thread`. `doSendMessageRequest`, `doUpdateMessageRequest`, `doDeleteMessageRequest` and `doMarkReadRequest` are gone, along with `useChannelRequestHandlers`, `useThreadRequestHandlers` and `useEditMessageHandler`. Register once on the client: ``` // v14 <Channel channel={channel} doSendMessageRequest={mySend}> // v15 client.config.set({ channel: { requestHandlers: { sendMessageRequest: async ({ localMessage, message, options }) => ({ message: await mySend(message, options), }), }, }, }); ``` Three differences: handlers take a single params object and return `{ message }`; thread flows register under the `thread` key; and registration is per CLIENT, not per mounted subtree β the one thing the props could do that this cannot. Per-channel behaviour now needs a branch inside one handler on the `cid` it receives. * `useChannelConfig` returns the channel's RESOLVED configuration instead of the channel type's raw server config, so field names change: channelConfig?.typing_events β channelConfig?.typingEvents.enabled channelConfig?.read_events β channelConfig?.readEvents.enabled channelConfig?.replies β channelConfig?.replies.enabled channelConfig?.user_message_reminders β channelConfig?.userMessageReminders.enabled channelConfig?.commands β channelConfig?.availableCommands Every gate is now the server flag ANDed with what is registered through `client.config`, which is what the client actually enforces. The hook also subscribes: these were getters, so a `client.config.set()` previously never reached the screen. No component reads `channel.serverConfig` any more. * `useAttachmentManagerState` replaces `hasCustomDoUploadRequest` with `customCdn`, and gains `attachmentsEnabled`, `locationEnabled` and `pollsEnabled`. * the per-domain stores on `channel.state` are removed β `readStore`, `typingStore`, `membersStore`, `watcherStore`, `ownCapabilitiesStore`, `mutedUsersStore`. Subscribe to `channel.state` itself; the selector is unchanged, because `ChannelStateData` is flat and keeps the same top-level keys: useStateStore(channel.state.ownCapabilitiesStore, sel) β useStateStore(channel.state, sel) `WatcherState` is renamed `ChannelWatchState`, and `channel.disconnected` is renamed `channel.pendingDisposal` (removed outright β no deprecated alias). * `AIStates` is no longer exported from `stream-chat-react` β import it from `stream-chat`. The values are unchanged, but it is now literal-typed, so an inferred array of its members no longer accepts the wide `AIState`: const STOPPABLE: readonly AIState[] = [AIStates.Thinking, AIStates.Generating]; * `<Channel>` marks its channel active while mounted and reloads it on `connection.recovered`. The client skips re-seeding an active channel's message list on hydration and reconnect, so the consumer owns that window. A custom channel surface that does not render `<Channel>` must call `channel.activate()` / `deactivate()` and `channel.reload()` itself, or its list goes stale after a reconnect. * **i18n:** adopt the shared i18n layer from stream-chat/i18n (#3271) * **context:** - **Context hooks no longer accept a `componentName` argument.** `useChatContext('X')` and friends stop type-checking; drop the argument. - **Required contexts throw when used outside their provider**, instead of logging a warning and returning `{}`. Affects the 14 hooks listed above. Components must be rendered within the provider they read from β e.g. anything calling `useChatContext` needs a `<Chat>` ancestor. - **`useChannelInstanceContext` returns `Partial<ChannelInstanceContextValue>`** β `channel` is now typed as possibly `undefined`. - **`ChatViewContext` has no default value** (typed `| undefined`), so `useContext(ChatViewContext)` may return `undefined`. - **`MessageComposerContext` is typed as `MessageComposerContextValue | undefined`**, correcting a narrower declaration that was papered over with casts. - **The gallery header no longer uses the `ComponentContext.MessageTimestamp` override**; it renders the item's own timestamp. ### π Implementation details Parameter removed from all 7 hooks and their 138 call sites. Missing-provider behaviour also normalized, since `src/` had three competing conventions (warn + `{} as T`, throw, silent cast). The `{} as T` fallback deferred failures to a downstream `Cannot read properties of undefined` several frames away. Everything now goes through `requireContext` (`src/context/requireContext.ts`): - **Required β throw**, naming hook and provider: `useChatContext`, `useMessageContext`, `useMessageComposerContext`, `useMessageListContext`, `useVirtualizedMessageListContext`, `useMessageBounceContext`, `usePollContext`, `useDialogManager`, `useSearchContext`, `useSearchSourceResultsContext`, `useContextMenuContext`, `useChannelListItemContext`, `useGalleryContext`, `useChatViewContext` - **Optional by design β keep default, honest type**: `useTranslationContext`, `useComponentContext`, `useChannelInstanceContext` (now `Partial`), `useModalContext`, `useAriaLiveAnnouncer`, `useMessageTranslationViewContext` - **Renders both inside and outside a provider β read the raw context**: `Audio`, `VoiceRecording`, `CardAudio`, `Timestamp`, `MessageRepliesCountButton`, `MessageComposerUI`, `ModalGallery` Two changes were needed before their hooks could throw: - `WithDragAndDropUpload` detected the composer via `Object.keys(ctx).length > 0`; now uses a `useIsWithinMessageComposerContext()` predicate. Adds the test file this component lacked. - `GalleryHeader` read message context while rendering outside any `MessageProvider`. `GalleryItem` now carries `user` and `createdAt` β wrapping the call sites would be wrong, since `ChannelMediaView` flattens many messages into one list. Adds a `timestamp.GalleryTimestamp` key. `ChatViewContext` also loses its module-level `LayoutController` default, which let unrelated subtrees write to one shared instance. `src/context/__tests__/missingProviderContract.test.tsx` pins the required/optional split. ### π¨ UI Changes The channel-media gallery header now shows a per-item sender and timestamp, which it previously couldn't display at all. No screenshots: the demo users on the shared environment have no channels, and seeding one would write test data others would see. Covered by unit tests in `GalleryUI.test.tsx` β worth a manual look. * the <Chat> prop and the ChatContext field channelPaginatorsOrchestrator are renamed to channelManager, and the types ChannelPaginatorsOrchestrator / ChannelPaginatorsOrchestratorState are now ChannelManager / ChannelManagerState. Every useChatContext() consumer reading the old field, and any test that builds a partial ChatContext value, has to be updated. No deprecated prop or type aliases are exported. * requires stream-chat v10. * Removed `ChannelStateContext` and `ChannelActionContext` along with `useChannelStateContext()`, `useChannelActionContext()`, `useCreateChannelStateContext()` and `useCreateChannelActionContext()`. Read the channel via `useChannel()` and message state via `useMessagePaginator()`; both are backed by LLC `StateStore`s. * `ChatContext` no longer exposes the active channel or `setActiveChannel()`. Channel/thread routing is slot-driven β use `useChatViewNavigation()` from `stream-chat-react/slot-layout` (`openChannel`, `openThread`, `closeThread`). * The `TypingContext` provider path was removed from the `Channel` runtime. `TypingIndicator` now derives its state from LLC stores and takes `isMessageListScrolledToBottom` and `scrollToBottom` props. * Context mutation/pagination helpers were replaced by paginator methods: `updateMessage`/`removeMessage` -> `ingestItem`/`removeItem`; `loadMore`/`loadMoreNewer` -> `toTail()`/`toHead()`; `jumpToMessage`/`jumpToFirstUnreadMessage` -> `messagePaginator.jumpToMessage()`/ `jumpToTheFirstUnreadMessage()`. Messages are no longer in `channel.state`. * Custom send/update/delete/markRead overrides move from ChannelActionContext callbacks to `channel.configState.requestHandlers` / `thread.configState.requestHandlers`. * `ChatView` is a navigation landmark, not a WAI-ARIA Tabs widget: the selector is `role="navigation"` with `aria-current` on the active button. There is no `role="tab"`/`"tablist"`/`"tabpanel"`. `activeChatView` renamed to `activeView`. * `ChannelList` renders a `role="listbox"` whose items are `role="option"`. * v10 data shapes: channel name/image read from `channel.data.custom.name`/ `.custom.image`; user custom fields from `user.custom` (e.g. `username`); attachment `duration`/`file_size`/`mime_type`/`waveform_data` from `attachment.custom`. Message moderation uses `message.moderation` (`moderation_details` is gone). ### Bug Fixes * adapt to removed stream-chat-js hand-written types ([#3274](#3274)) ([ccccce5](ccccce5)) * **EmojiPicker:** drop @emoji-mart/react peer dependency on v15 ([#3300](#3300)) ([e7ce32f](e7ce32f)) * **MessageList:** keep the viewport still when a jumped-to window reaches the live head ([#3290](#3290)) ([351d69d](351d69d)), closes [#3027](#3027) * **MessageList:** show delivery and read state on every message that has it ([#3296](#3296)) ([e74c57a](e74c57a)) * prevent eager requests on Thread component mount ([#3282](#3282)) ([6b7dd6c](6b7dd6c)) * render stable notification host for Channel and Thread ([#3291](#3291)) ([ce59094](ce59094)) ### Features * add icons to ComponentContext adapted to stream-chat-react@15 ([#3294](#3294)) ([baf4b62](baf4b62)) * adopt stream-chat's branded TimestampNS ([#3297](#3297)) ([aaa7fa3](aaa7fa3)), closes [GetStream/stream-chat-js#1884](GetStream/stream-chat-js#1884) [GetStream/stream-chat-js#1884](GetStream/stream-chat-js#1884) [#3295](#3295) * **Attachment:** render live upload progress on messages being sent ([#3295](#3295)) ([4f1459e](4f1459e)) * **AudioPlayback:** play one voice message at a time, across the whole app ([#3287](#3287)) ([68999a7](68999a7)) * **Channel:** depend on the Channel instance, not its cid, and narrow its responsibilities ([#3286](#3286)) ([088c795](088c795)) * **context:** consolidate message UI overrides on the MessageUI slot ([#3268](#3268)) ([0766489](0766489)), closes [#3267](#3267) * **context:** drop componentName param from context consumer hooks ([#3267](#3267)) ([9194215](9194215)) * **i18n:** english-only bundle with namespaced, type-checked translation keys ([#3261](#3261)) ([17c91bc](17c91bc)) * **MessageComposer:** let integrators supply the composer MessageComposer edits ([#3299](#3299)) ([14e5860](14e5860)) * migrate components from ChannelActionContext, ChannelStateContext to StateStore instances ([#3237](#3237)) ([99cbe67](99cbe67)) * move to new upload API ([#3272](#3272)) ([04a31b1](04a31b1)) * move to timestamps in response models ([#3278](#3278)) ([cb472c3](cb472c3)) * port the tutorial alignment ([#3251](#3251)) to v15, with panel-aware workspace navigation ([#3301](#3301)) ([66557d6](66557d6)) * rename ChannelPaginatorsOrchestrator to ChannelManager ([#3258](#3258)) ([be2c8e1](be2c8e1)) * support local unread count on v15 ([#3298](#3298)) ([a42c3cf](a42c3cf)) * use configuration service API as a single source of truth ([#3273](#3273)) ([f66597a](f66597a)) * use network and WS connection observer services from LLC ([#3281](#3281)) ([3098a90](3098a90)) ### Refactors * align Thread with Channel - instance prop, provider, children ([#3289](#3289)) ([c2136f3](c2136f3)) * **i18n:** adopt the shared i18n layer from stream-chat/i18n ([#3271](#3271)) ([f68cad3](f68cad3)), closes [GetStream/stream-chat-js#1830](GetStream/stream-chat-js#1830) * **i18n:** consume the shared runtime from @stream-io/i18n ([#3284](#3284)) ([eb3ae7a](eb3ae7a)) * **MessageActions:** remove the messageActions prop in from message lists ([#3288](#3288)) ([b541c45](b541c45)) ### Performance Improvements * **build:** emit ESM per source module so consumers can tree-shake ([#3269](#3269)) ([55e76dc](55e76dc))
|
π This PR is included in version 15.0.0-rc.1 π The release is available on: Your semantic-release bot π¦π |
## [10.0.0](v9.55.0...v10.0.0) (2026-11-06) ### β BREAKING CHANGES * release v10 from master as latest (#2004) * `LocalAttachmentMetadata` requires only `id`. `file` and `uploadState` are optional, so code that reads them from a `LocalAttachment` must handle `undefined`. A `file` must come with its `uploadState`: `localMetadata: { id, file }` no longer compiles, and an attachment in that shape from untyped code is no longer sent. An attachment with neither field (a custom type or `giphy`) is sent as it is, takes no upload slot, and counts as content in `hasSendableData`. * CustomCommandData is no longer exported. Delete its augmentations and imports. A leftover augmentation still compiles and has no effect, so search for the name rather than relying on tsc. An app that wants a closed set of command names declares its own union. See ai-docs/v10-breaking-changes.md#custom-data-augmentation. * the client no longer dispatches capabilities.changed and the name is no longer an EventType. Subscribe to channel.state instead: channel.state.subscribeWithSelector(({ ownCapabilities }) => ({ ownCapabilities }), fn). See ai-docs/v10-breaking-changes.md#capabilities-changed-removed. * AttachmentManager.uploadAttachment() is removed. Retry a failed upload, or upload an attachment you built yourself such as a voice recording, with `attachmentManager.uploadFile(attachment)`. It keeps the attachment's id, fields and position, and runs the pre- and post-upload middleware, which uploadAttachment() skipped. uploadFile() does not check `isUploadEnabled`, so check it first to skip the upload as uploadAttachment() did. uploadFile() always resolves to an attachment: where a result was checked for `undefined`, check `result.localMetadata.uploadState` instead. See ai-docs/v10-breaking-changes.md#upload-attachment-removed. * the message composer installs the command middlewares by default: createActiveCommandGuardMiddleware and createCommandStringExtractionMiddleware in the text composer chain, createCommandInjectionMiddleware in the composition chain and createDraftCommandInjectionMiddleware in the draft composition chain. Typing `/giphy cats` activates giphy and leaves `cats` in the text, and a command picked with `textComposer.setCommand()` is sent with its name. Delete your own installs of these middlewares. A UI without a command chip removes all four by id: `stream-io/text-composer/active-command-guard` and `stream-io/text-composer/command-string-extraction` from `textComposer.middlewareExecutor`, `stream-io/message-composer-middleware/command-string-injection` from `compositionMiddlewareExecutor` and `stream-io/message-composer-middleware/draft-command-string-injection` from `draftCompositionMiddlewareExecutor`. See ai-docs/v10-breaking-changes.md#composer-command-middlewares-default. * Channel.watch() resolves with the channel instance to use (the channel, or the instance that superseded it) instead of the server response. - watch() sends no query for a channel that is already watched and loaded (watchStatus Watching, initialized, and messagePaginator.hasResults) when it passes no options or the same options as the last successful watch() during the current watch; other options send a query. It no longer refreshes a watched channel: use channel.reload(), and load other messages through channel.messagePaginator (jumpToMessage(), toTail(), toHead()). - A watch() while another with the same options is in flight joins it instead of sending a request. - Aborting requestOptions.signal ends only that caller's wait; the request is cancelled once every caller waiting for it has aborted. - Channel.ensureWatched() is removed (v10 release candidates only); call watch(). ``` ## Changelog - `Channel.watch()` resolves with the channel and sends no query for a channel already watched and loaded, unless it asks for options the current watch was not sent with. `Channel.ensureWatched()` is removed. π€ Generated with [Claude Code](https://claude.com/claude-code) * remove messageSetToAddToIfDoesNotExist and let the message paginator place every page (#1926) * open api hot fixes (#1927) * `StreamChatOptions` and `client.options` are removed. `new StreamChat(key, config)` and `StreamChat.getInstance(key, config)` take the configuration tree (`DeepPartial<InstanceConfigTree>`) as their second argument; the `config` wrapper is dropped. Option moves: - `allowServerSideConnect`, `axiosRequestConfig`, `baseURL`, `browser`, `persistUserOnConnectionFailure` -> `client.<same name>` - `notifications` -> `client.notificationManager` - top-level axios fields (`timeout`, `httpsAgent`, ...) -> `client.axiosRequestConfig.<key>` Example: `new StreamChat(key, { client: { allowServerSideConnect: true } })`. `client.persistUserOnConnectionFailure` is no longer a field (use `client.config.setConfig('client', { persistUserOnConnectionFailure })`); `client.browser` and `client.node` are read-only getters. `baseURL`, `axiosRequestConfig` and `notificationManager` are read at construction, and the tree copies `axiosRequestConfig` instead of keeping your object. See v9-to-v10-migration-guide-client-construction.md. * client.activeChannels is removed; use client.channelManager.get(cid) and client.channelManager.values(). * client.hydrateActiveChannels() is renamed to client.hydrateChannels(). * client.getChannelById() and client.getChannelByMembers() are removed; use client.channel() or client.channelManager.ensure({ type, id?, data? }). * thread.deactivate() is removed; thread.activate() returns the function that ends the activation. * channel._disconnect() is renamed to channel.disconnect(). * channel.disconnected is removed; read the read-only channel.pendingDisposal instead, which only channel.disconnect() sets. * when a channel created from members gets a cid another stored instance already holds, the stored instance stays, as in v9, and the new one is marked channel.supersededBy; if the new one is open and the stored one isn't, its unsent composition moves to the stored one, so UIs holding the new instance should switch to channel.supersededBy. * channel.initialized is set by query() and create() too, not only by watch(). * channel search results are not watched; watch a result when it is opened. * MessageSearchSource no longer watches the channels it fetches for its results; watch the channel when a result is opened, e.g. with jumpToMessage(id, { watchChannel: true }). * adopt TS generator changes (#1896) * reenable WS fallback (#1889) * thread paginator and manager revamp (#1888) * **MessageDeliveryReporter:** The `isLocalUnreadCountEnabled` client option is removed. Use `channel.readEvents.localUnreadCountEnabled` in the configuration service instead (see above). * brand server-sent timestamps as TimestampNS and ship the Date guard (#1884) * `connection.changed` is removed. Connectivity is published as two state stores and nothing else: `client.networkConnection.state` for the device and `client.wsConnection.state` for this client's socket. The event was silent on `closeConnection()` and two error paths, and held a drop for five seconds; the stores are written on every transition and publish immediately. * `client.wsConnection` is now a `WSConnection` wrapper rather than the `StableWSConnection` itself, and is never null. The live socket is `client.wsConnection.connection`, replaced on every connect; read `isHealthy`, `isConnecting`, `connect()` and `disconnect()` from the wrapper. `StableWSConnection` is constructed with `{ wsConnection }` instead of `{ client }`. * `client.defaultWSTimeout` is removed, along with the `WebSocketImpl` and `wsUrlParams` client options. They move to the socket's configuration as `connectTimeoutMs`, `webSocketImpl` and `urlParams`, set with `client.config.set({ client: { wsConnection: { β¦ } } })`. Unlike the fields and options they replace, these survive a reconnect. * `ThreadManagerState.lastConnectionDropAt` is removed. Read `client.wsConnection.state.lastUnhealthyAt`, which is written on every status transition, including the `disconnect()` path the old event was silent about. * requests that watch a channel or subscribe to presence now wait for a WebSocket connection id instead of silently returning unwatched data. The gate is in `ApiClient._doRequest`, so it covers every endpoint carrying `watch` or `presence`, plus `stopWatchingChannel` and `longPoll`. With no socket open and none being established the request rejects with "No connection id is available"; an explicit `watch: false` is still honoured, and a caller's `AbortSignal` abandons the wait. Test fixtures that fake a connected user without a live socket will now throw. * a UI that renders a "connection lost" banner must hold the drop itself. The socket's store publishes drops the moment they happen, where the old event delayed them by five seconds. `client.wsConnection.config.offlineNotificationDisplayDelayMs` (5s) is the shared value to wait for; nothing in this package acts on it. * `connection.recovered` is no longer dispatched when the socket drops while a recovery is running, because every reload in it can have failed. Work keyed off that event will correctly stop running for recoveries that recovered nothing. * openapi related clean up (#1870) * **SearchController:** remove InternalSearchControllerState (#1866) * remove response date transform (#1849) * `client.instanceConfigurationService` is replaced by `client.config`, and the exported class `InstanceConfigurationService` is renamed to `InstanceConfigurationRegistry`. * `client.setInstanceConfigurationFunction()` is removed. Register setup functions with `client.config.setSetupFunction(key, fn)`. `client.setMessageComposerSetupFunction()` is unchanged. * the per-class setup types are replaced by generic ones keyed by instance. Removed: `ChannelSetupFunction`, `ChannelSetupState`, `ChannelTearDownFunction`, `MessageComposerSetupFunction`, `MessageComposerSetupState`, `MessageComposerTearDownFunction`, `StreamChatSetupFunction`, `StreamChatSetupState`, `StreamChatTearDownFunction`, `ThreadSetupFunction`, `ThreadSetupState`, `ThreadTearDownFunction`, `SetInstanceConfigurationFunctions`, `SetInstanceConfigurationServiceStates` and `SetupFnOf`. Use `InstanceSetupFunction`, `InstanceSetupState`, `InstanceSetupTearDownFunction` and `InstanceSetupKey`. * `ChannelInstanceConfig` is renamed to `ChannelConfig` and `ThreadInstanceConfig` to `ThreadConfig`. * `Channel.getConfig()` is removed. Read the server's channel-type configuration from `channel.serverConfig`, or the resolved configuration from `channel.config`. * `client.configs` is renamed to `client.channelServerConfigs` and `client.configsStore` to `client.channelServerConfigsStore`. Keys are still cids, so lookups translate directly. Prefer `channel.serverConfig`. * `channel.config.commands` is renamed to `channel.config.availableCommands`. The server owns the list, so it is read-only and absent from the declarative configuration tree. * `applyCommandValidatorOverride` is no longer exported. Command validation is configured through the composer's configuration. * `MessageComposerConfig` gains a required `polls`; `AttachmentManagerConfig` gains required `enabled` and `customCdn`; `LocationComposerConfig` gains a required `minShareDurationMs`. A config object built literally must supply them. * `linkPreviews.enabled` now defaults to `true`, where it defaulted to `false`. It is ANDed with the channel type's `url_enrichment`, so `true` means "no opinion, let the server decide"; the old default double-gated the feature and kept it off even where the server had enabled it. * server flags gate the features they describe, ANDed with the registered value rather than the client value winning: `typingEvents` with `typing_events`, `readEvents` with `read_events`, `attachments` with `uploads`, `polls` with `polls`, `location` with `shared_locations`, and `linkPreviews` with `url_enrichment`. Code that set a flag to `true` and assumed it took effect may now find the feature off. * setters no longer discard a write when the server is masking the field. Previously the setter compared against the effective value, so the write was dropped and the earlier request was honoured once the server relented. Affects `linkPreviewsManager.enabled`, `textComposer.enabled`, `textComposer.maxLengthOnEdit`, `textComposer.maxLengthOnSend` and `attachmentManager.maxNumberOfFilesPerMessage`. * remove hand-written types and API wrappers (#1836) * **i18n:** `Streami18n.init()` now rejects when i18next fails to initialize, where it previously logged and resolved. Callers that ignore the returned promise will see an unhandled rejection. The instance remains usable in its degraded English form either way. `Streami18n.brand` is removed; nothing in either UI SDK read it, and a custom integration that recognized instances through it should compare against its own marker or accept the instance it was handed. * **i18n:** share the translation runtime as stream-chat/i18n (#1830) * `ChannelManager` keeps its name but is a different class table Co-authored-by: Ivan Sekovanikj <31964049+isekovanic@users.noreply.github.com> * paginator-based message storage replaces channel.state.messages and channel.state.last_message_at. The message list, thread replies and pinned messages are now owned by channel.messagePaginator, thread.messagePaginator and channel.pinnedMessagesPaginator. OpenAPI-generated clients replace the hand-written request/response types. See docs/breaking-changes-v14-v15.md for the full migration guide. ### Bug Fixes * accept poll.update data without id ([#1997](#1997)) ([c5863a9](c5863a9)) * add docs snippets; fix open api issues ([#1913](#1913)) ([53505e7](53505e7)) * add messages received after truncate to state ([#1950](#1950)) ([b178379](b178379)) * address code issues found while reviewing v10 source comments ([#1922](#1922)) ([2fc319e](2fc319e)) * adjust paginator offset when items are removed ([#1842](#1842)) ([61b5a1f](61b5a1f)) * apply thread reloads and queries to the registered thread ([#1929](#1929)) ([acb33c4](acb33c4)), closes [#1913](#1913) * avoid types from NodeJS namespace ([#1947](#1947)) ([68fb042](68fb042)) * bring back reaction apis to channel ([#1906](#1906)) ([d419fe3](d419fe3)), closes [#1897](#1897) [#1897](#1897) * channel list reconciliation ([#1942](#1942)) ([f7f9ec8](f7f9ec8)) * channel paginator merging bugs ([#1894](#1894)) ([dbbbd75](dbbbd75)), closes [#1865](#1865) * consolidate master and v10 ([#1957](#1957)) ([802a956](802a956)) * count hidden messages on each side of a jump page ([#1940](#1940)) ([487ac3d](487ac3d)) * **deps:** bump [@stream-io](https://github.com/stream-io) toolkit packages to the .mjs/.cjs builds ([#1971](#1971)) ([aa97cf4](aa97cf4)), closes [GetStream/js-toolkit#58](https://github.com/GetStream/js-toolkit/issues/58) * dispatch connection.recovered on reconnect when recovery is disabled ([#1994](#1994)) ([13bb533](13bb533)) * docs fixes ([#1949](#1949)) ([4c3e46d](4c3e46d)) * drop capabilities.changed; channel.state ownCapabilities is the change signal ([#1992](#1992)) ([6da7713](6da7713)) * drop server-side remnants, add WS interface ([#1819](#1819)) ([39c0c53](39c0c53)) * ensure API errors parsing works even with multiple axios copies ([#1909](#1909)) ([e0cfba9](e0cfba9)) * existing messages retry guards ([#1982](#1982)) ([c7c971f](c7c971f)) * handle tokenProvider rejection during reconnect token refresh ([#1963](#1963)) ([e22d218](e22d218)) * handle user.updated in threads ([#1979](#1979)) ([9064ffc](9064ffc)) * **i18n:** reject from init() and drop the Streami18n brand static ([#1839](#1839)) ([67bb1c7](67bb1c7)) * install the command middlewares in the composer by default ([#1985](#1985)) ([1dc25e2](1dc25e2)) * issues found by the RN e2e tests ([#1996](#1996)) ([f5d1a2d](f5d1a2d)) * jump to thread reply bug ([#1948](#1948)) ([5ec1b07](5ec1b07)) * let an edit clear all custom fields ([#1983](#1983)) ([9437b09](9437b09)) * narrow channel.state.typing to typing.start events ([#1960](#1960)) ([acb2345](acb2345)), closes [#1920](#1920) * notify channel state subscribers on watcher, ban and member removal events ([#1977](#1977)) ([3a7be78](3a7be78)) * odd edge cases for channel list recovery ([#1966](#1966)) ([dbc2468](dbc2468)) * offline db hydration race conditions ([#1865](#1865)) ([a4587e1](a4587e1)) * open api hot fixes ([#1927](#1927)) ([4fb4b52](4fb4b52)) * optimistic updates for translated messages ([#1975](#1975)) ([91fc290](91fc290)) * **pagination:** count a page set with setItems as loaded, even when empty ([#1995](#1995)) ([bfff3c8](bfff3c8)) * **pagination:** keep loaded items when filterQueryResults empties a later page ([#1986](#1986)) ([78a32c3](78a32c3)) * **paginator:** correct windows filled by hydration rather than by a query ([#1860](#1860)) ([93b6fa0](93b6fa0)) * **paginator:** do not issue the first-page query twice ([#1858](#1858)) ([6462cef](6462cef)) * pass full request options to hydration query ([#1959](#1959)) ([489774b](489774b)) * pending task folding ([#1873](#1873)) ([028f6e8](028f6e8)) * **poll:** keep translations from poll.updated in the poll state ([#1937](#1937)) ([e47b75a](e47b75a)), closes [#1878](#1878) * post-merge/OAPI-reconciliation adjustments ([#1812](#1812)) ([5073c67](5073c67)) * reconnect past connection timeout ([#1874](#1874)) ([2004a83](2004a83)), closes [#1760](#1760) * regenerate api layer (fix generated filter types) ([#1827](#1827)) ([e540d88](e540d88)) * remove CustomCommandData ([#1989](#1989)) ([f737d61](f737d61)) * respect predefined filters from db ([#1869](#1869)) ([cbc7707](cbc7707)) * restore composer custom data from drafts and edits, keep custom on edit ([#1978](#1978)) ([51ddcad](51ddcad)) * **search:** match message text by word prefix in MessageSearchSource ([#1987](#1987)) ([ef58ef5](ef58ef5)) * send the read request regardless of read receipt privacy settings in v10 ([#1855](#1855)) ([3b24387](3b24387)) * set thread cursor when initied from setItems ([#1898](#1898)) ([63bef43](63bef43)) * signal api ([#1828](#1828)) ([9032b1a](9032b1a)) * stability and perf fixes ([#1910](#1910)) ([080af2c](080af2c)) * unread snapshot count, pinned replies, joined and invite filters, read events check ([#1981](#1981)) ([1247d10](1247d10)) * v10 in depth review hotfixes ([#1920](#1920)) ([98a298d](98a298d)) * v10 regressions found in side-by-side QA ([#1965](#1965)) ([b87d598](b87d598)) ### Features * add instance configuration service ([#1831](#1831)) ([434966c](434966c)) * add message/channel paginator stack with thread minimal-init and state stores ([#1795](#1795)) ([62f0507](62f0507)) * adopt TS generator changes ([#1896](#1896)) ([82af6de](82af6de)) * brand server-sent timestamps as TimestampNS and ship the Date guard ([#1884](#1884)) ([e51198a](e51198a)), closes [GetStream/chat#17356](https://github.com/GetStream/chat/issues/17356) [GetStream/chat#17460](https://github.com/GetStream/chat/issues/17460) [#17356](https://github.com/GetStream/stream-chat-js/issues/17356) [GetStream/stream-chat-react#3297](GetStream/stream-chat-react#3297) [GetStream/stream-chat-react-native#3822](GetStream/stream-chat-react-native#3822) [GetStream/chat#17425](https://github.com/GetStream/chat/issues/17425) * channel state migration ([#1834](#1834)) ([9876add](9876add)) * connection recovery ([#1841](#1841)) ([8ec9a9d](8ec9a9d)) * destructive reconciliation ([#1822](#1822)) ([3c46fbf](3c46fbf)) * establish network connection observer services ([#1859](#1859)) ([4c46949](4c46949)) * generalize entity store in V10 ([#1814](#1814)) ([d066ba0](d066ba0)) * **i18n:** share the translation runtime as stream-chat/i18n ([#1830](#1830)) ([40cf062](40cf062)), closes [GetStream/stream-chat-react#3271](GetStream/stream-chat-react#3271) [GetStream/stream-chat-react-native#3777](GetStream/stream-chat-react-native#3777) * integrate OpenAPI generated clients ([#1760](#1760)) ([0776bc4](0776bc4)) * let AttachmentManager.uploadFile retry an existing attachment ([#1991](#1991)) ([cf8b1fa](cf8b1fa)) * let integrators send a poll with the message by removing the poll-only middleware ([#1970](#1970)) ([b515849](b515849)), closes [GetStream/stream-chat-react#3365](GetStream/stream-chat-react#3365) * message pruning ([696f56f](696f56f)) * message pruning ([#1875](#1875)) ([d4d2c6c](d4d2c6c)) * **MessageComposer:** add send and update message API ([#1881](#1881)) ([95b2502](95b2502)) * **MessageComposer:** reference the composer on upload notifications ([#1871](#1871)) ([d520fc1](d520fc1)) * **messageDelivery:** report receipt state from the furthest cursor ([#1883](#1883)) ([79f3ac1](79f3ac1)) * **MessageDeliveryReporter:** mark channel read locally when local read count tracking are enabled ([#1886](#1886)) ([439a018](439a018)) * migrate ChannelPaginatorsOrchestrator to ChannelManager ([#1813](#1813)) ([c694930](c694930)) * move to WS API v2 ([#1833](#1833)) ([d530f43](d530f43)) * **NotificationManager:** add ensureTimeout for callers that cannot know if notification was already triggered ([#1872](#1872)) ([e5d06aa](e5d06aa)) * openapi related clean up ([#1870](#1870)) ([b3fa906](b3fa906)) * optimistic reactions ([#1808](#1808)) ([54740f1](54740f1)) * poll optimistic votes v10 port ([#1904](#1904)) ([143446b](143446b)) * port search improvements ([#1829](#1829)) ([7276299](7276299)), closes [#1823](#1823) * reenable WS fallback ([#1889](#1889)) ([45841cc](45841cc)) * regenerate from latest api spec; docs sync ([#1969](#1969)) ([ae1e86a](ae1e86a)) * regenerate from latest open api spec ([#1821](#1821)) ([22344ca](22344ca)) * regenerate open api spec + improve tooling ([#1824](#1824)) ([a3198e7](a3198e7)) * release v10 from master as latest ([#2004](#2004)) ([40ead99](40ead99)), closes [#2003](#2003) [#1476](#1476) [#1938](#1938) [#1937](#1937) [#1962](#1962) [#1963](#1963) [#1944](#1944) [#1952](#1952) [#1953](#1953) [#1954](#1954) [#1957](#1957) [#1976](#1976) [#1908](#1908) [#2003](#2003) * remaining optimistic updates ([#1843](#1843)) ([808b2c8](808b2c8)) * remove hand-written types and API wrappers ([#1836](#1836)) ([dc49a78](dc49a78)) * remove handwritten upload methods; align method names ([#1832](#1832)) ([1a6bdb8](1a6bdb8)) * remove messageSetToAddToIfDoesNotExist and let the message paginator place every page ([#1926](#1926)) ([2b7960d](2b7960d)) * remove response date transform ([#1849](#1849)) ([ef5dcc6](ef5dcc6)) * replace StreamChat.activeChannels with entity item index for loaded channels ([#1901](#1901)) ([ec9178a](ec9178a)) * replace StreamChatOptions with the configuration tree ([#1911](#1911)) ([f05b861](f05b861)) * send and count attachments with nothing to upload ([#1993](#1993)) ([b2dfb8a](b2dfb8a)), closes [ai-docs/v10-breaking-changes.md#non-upload-attachments](https://github.com/ai-docs/v10-breaking-changes.md/issues/non-upload-attachments) * settle pending attachment uploads ([#1880](#1880)) ([59f5869](59f5869)) * thread paginator and manager revamp ([#1888](#1888)) ([d1562aa](d1562aa)) * update to latest api spec ([461eb24](461eb24)) * update to latest API spec ([#2005](#2005)) ([5668f20](5668f20)) * v10 ([cd3141e](cd3141e)) ### Refactors * call the paginator intervals intervals, not windows ([#1939](#1939)) ([69aa18a](69aa18a)) * **channel:** remove no-op overrides, dead branch and redundant id guards ([#1936](#1936)) ([050953e](050953e)), closes [#1870](#1870) * **i18n:** move the translation runtime to @stream-io/i18n ([#1861](#1861)) ([45f45bd](45f45bd)) * make Channel.watch() watch a channel only when it is not watched and loaded ([#1943](#1943)) ([3c363cb](3c363cb)) * **SearchController:** remove InternalSearchControllerState ([#1866](#1866)) ([fc64f7f](fc64f7f)) * simplify optimistic updates ([#1897](#1897)) ([8b84f02](8b84f02)), closes [#1882](#1882) * unify message operation apis ([#1882](#1882)) ([2ed1c75](2ed1c75)) ### Performance Improvements * store publish amplification ([#1876](#1876)) ([d93ee7d](d93ee7d))
## [15.0.0](https://github.com/GetStream/stream-chat-react/compare/v14.12.0...v15.0.0) (2026-11-06)
### β BREAKING CHANGES
* props and ComponentContext keys that had no effect are removed, nine slots are typed without props, and overrides of several slots render in more places.
- `additionalMessageComposerProps` is removed from `Message`, `MessageList`, `VirtualizedMessageList` and `VirtuosoContext`. It did nothing in v14; delete it. If the props were meant for the composer, pass them to the `MessageComposer` you render.
- `MessageReactions.reverse`, `MessageReactionsDetail.sort` and `MessageReactionsDetail.dialogId` are removed, and `MessageUI` no longer passes `reverse` to a `MessageReactions` override. Order reaction types with `sortReactions` and reacting users with `reactionDetailsSort`; the detail's dialog id is `MessageReactionsDetail.getDialogId({ messageId })`.
- `MessageComposer` no longer takes `emojiSearchIndex` or `parent`, the `emojiSearchIndex` override key is removed, and `useMessageComposerContext()` no longer returns `emojiSearchIndex`, `parent` or `audioRecordingConfig`. For emoji autocomplete, register `createTextComposerEmojiMiddleware(SearchIndex)` from `stream-chat-react/emojis` in a `client.config.setSetupFunction('messageComposer', ...)`.
- `WithDragAndDropUpload.acceptedFiles` is removed. Set the accepted types on the composer config, `attachments.acceptedFiles`.
- `ChannelListItem.activeChannel` is removed. A custom `ChannelListItemUI` reads `active`, or compares the app's own selected channel with its `channel`.
- `ChatView.entityInferrers`, the `ChatViewEntityInferer` type and `useChatViewContext().entityInferers` are removed.
- The `AttachmentPreviewList`, `MessageComposerUI`, `MessageEditedIndicator`, `MessageStatus`, `NotificationAnnouncer`, `QuotedMessagePreview`, `SearchSourceResultList`, `TextareaComposer` and `VoiceRecordingPreviewSlot` slots are typed `React.ComponentType` without props, because the SDK renders them without props. Rendering one from `useComponentContext()` with props, or an override with required props, no longer compiles; render the exported component with its props inside the override.
- Overrides of `FileDragAndDropContent`, `BaseImage`, `LoadingIndicator`, `Timestamp`, `Avatar`, `DateSeparator`, `Gallery` and `FileSizeIndicator` now also render where the SDK rendered the default directly: link previews, video thumbnails, galleries, quoted messages, load-more buttons, poll vote lists, thread list items, user search results, the thread's parent message, composer previews and channel detail lists. To keep the default in one place, wrap it in `WithComponents` with the exported default.
See ai-docs/v15-breaking-changes.md (BC-105 to BC-109, BC-033) and ai-docs/ai-migration-v14-v15.md.
* **ChannelListHeader:** ComponentContext.HeaderEndContent is removed. Pass the content
as EndContent to ChannelListHeader / ThreadListHeader instead, by registering a
component that renders the header with it under the ChannelListHeader or
ThreadListHeader key. One component no longer fills both headers.
* **ChannelListHeader:** list-header content renders whenever it is set. HeaderEndContent
showed only while a channel (channel list) or a thread (thread list) was open;
content that should keep that behavior returns null while
useWorkspaceNavigation().openChannels / openThreads is empty. See BC-099 in
ai-docs/v15-breaking-changes.md and "List headers: HeaderEndContent becomes an
EndContent prop" in ai-docs/ai-migration-v14-v15.md.
* **ChannelDetail:** ChannelMembersHeaderActionItem.component is renamed to
Component, matching the message and channel-list action sets.
* **ChannelDetail:** the 'menu' placement of a channel members header action is
renamed to 'dropdown'. A 'menu' item is no longer rendered.
* **ChannelDetail:** the HeaderActionsMenuTrigger prop (on ChannelMembersView and
ChannelMembersHeaderActionsProps), DefaultHeaderActionsMenuTrigger and
ChannelMembersHeaderActionsMenuTriggerProps are removed. The menu trigger is a
{ Component, placement: 'quick-dropdown-toggle' } item in headerActionSet: a
forwardRef button that receives the ref the menu anchors to, aria-expanded and
onClick, and spreads them. defaultChannelMembersHeaderActionSet starts with
DefaultChannelMembersHeaderActions.QuickDropdownToggle; a set without a toggle
item renders no dropdown menu.
* **ChannelDetail:** dropdown header actions no longer receive a closeMenu prop.
They read closeMenu from useContextMenuContext().
* **ChannelDetail:** a header action's filter is renamed to isVisible, typed with
ChannelMembersHeaderActionVisibilityFilterParams and called with { channel }, as
on message actions. A leftover filter is ignored, so the action it hid shows to
every user.
* **ChannelDetail:** ChannelMembersHeaderActionItem is a union whose
quick-dropdown-toggle member has no type or isVisible, and
ChannelMembersHeaderActionPlacement includes 'quick-dropdown-toggle'. Narrow an
item (e.g. 'type' in item) before reading type.
* **ChannelDetail:** a custom HeaderActions renders the set's quick-dropdown-toggle
item itself and filters the set with useBaseChannelMembersHeaderActionSetFilter.
It renders the dropdown items in a ContextMenu opened with
useDialogOnNearestManager, using an id unique to the component instance. See
BC-098 in ai-docs/v15-breaking-changes.md and "The channel members header action
set uses the shared action names" in ai-docs/ai-migration-v14-v15.md.
* **TypingIndicator:** translations of `typing.twoUsers` must use
`{{ firstUser }}` and `{{ secondUser }}` instead of `{{ typing }}`, for example
'{{ firstUser }} und {{ secondUser }} schreiben'. A translation that keeps
`{{ typing }}` still compiles but shows the literal text `{{ typing }}`.
See BC-100 in ai-docs/v15-breaking-changes.md and "Keys whose values changed"
in ai-docs/i18n-v15-migration.md.
* unused exports and the transliterate dependency are removed, and stream-chat-react now types channel custom data for apps.
- `Root` and `Header` are no longer exported from the package root. They are the parts of `Alert`; use `Alert.Root` and `Alert.Header`.
- `UploadIcon`, `BinIcon`, `PauseIcon` and `PlayIcon` are removed. Copy the SVG into the app, or use `IconPlus` / `IconAttachment`, `IconDelete`, `IconPauseFill` and `IconPlayFill` from the v15 icon set.
- `@stream-io/transliterate` is no longer an optional dependency. Apps that import it must add it to their own dependencies. For mention search, pass a `transliterate` function to stream-chat's mentions middleware.
- Importing `stream-chat-react` now types `channel.data.custom.name` and `.image` (`string | undefined`). Delete the app's own `name` / `image` declarations on `CustomChannelData`; a declaration with another type overrides the SDK's under `skipLibCheck` and fails with TS2430 without it.
- `DefaultChannelData` no longer has `subtitle` or `team`. Declare them on `CustomChannelData` if the app stores them under `custom`; read the channel's team from `channel.data.team`.
- The translation key `messageComposer.icons.attachFiles.text` is removed.
See ai-docs/v15-breaking-changes.md (BC-101) and ai-docs/ai-migration-v14-v15.md.
* **Message:** Removed the initialMessage property from message props and context. Use the new hook to identify thread-parent messages.
* **ChannelDetail:** outside a modal, the ChannelDetail views render no close
button unless ChannelDetail or ChannelDetailProvider gets onClose. They used to
render a close button that did nothing.
* **ChannelDetail:** ChannelDetail calls onLayoutChange on every layout change. It
used to ignore the prop.
* **AttachmentSelector:** SimpleAttachmentSelector, and the plain upload button AttachmentSelector renders when File is its only action, follow the composer's File rule instead of the upload-file capability alone. They appear without upload-file when attachments.customCdn is set, are hidden when attachments are disabled (the channel type's uploads flag is off without customCdn, or the client sets attachments.enabled: false), and are disabled once the message holds maxNumberOfFilesPerMessage files. At that limit the menu's File item also stays, disabled, where v14 hid it. See BC-065 in ai-docs/v15-breaking-changes.md and "Attachment and poll availability" in ai-docs/ai-migration-v14-v15.md.
* deprecated message, mention and list APIs are removed.
- `renderText(text, mentionedUsers, options)` is now `renderText(text, options)`, and `RenderTextFunction` is `(text, options)`. Pass mentions as `options.messageMentionEntities`, built with `getRenderTextMentionEntities(message)`.
- Custom mention renderers get `node.mentionedEntity` instead of `node.mentionedUser`.
- `onMentionsClick` / `onMentionsHover` are called with `(event, message)`; the `mentioned_users` argument and the `MentionedUserEventHandler` type are removed (use `CustomMentionHandler`). Read the clicked mention from the target's `data-user-id` / `data-mention-id`.
- `MessageContextValue.onMentionsClickMessage` / `onMentionsHoverMessage` and the handlers from `useMentionsHandler` are `undefined` unless `onMentionsClick` / `onMentionsHover` is passed. Mentions are interactive only when a click handler is set.
- `mentionsMarkdownPlugin` and `createMentionPluginAndDisplayTextSet` take mention entities only, not `UserResponse[]`.
- `useUserRole` is renamed `useMessagePermissions(message)`, and it no longer returns `isAdmin`, `isModerator` or `isOwner`; gate on the `can*` flags or `useChannelCapabilities()`.
- `disableQuotedMessages` is removed from `MessageList`, `Message` and `useMessagePermissions`. To hide quoting, pass a `messageActionSet` without the `quote` item through the `MessageActions` slot.
- `VirtualizedMessageList` drops `defaultItemHeight`, `overscan` and `scrollSeekPlaceHolder`; pass them through `additionalVirtuosoProps`.
- `PinEnabledUserRoles` and `PinPermissions` are removed; use `useMessagePermissions(message).canPin`.
See ai-docs/v15-breaking-changes.md (BC-087 to BC-095) and ai-docs/ai-migration-v14-v15.md.
* message context and grouping API changes.
- MessageProps.threadList and MessageContextValue.threadList are removed. Detect a thread
with `!!useThreadContext()`.
- ReactionSelector.getDialogId takes only `{ messageId }`; the reaction selector dialog id
no longer has a `-thread` part. Each message list has its own dialog manager.
- MessageContextValue.endOfGroup, firstOfGroup and groupedByUser are removed. Read
`useMessageContext().groupStyles[0]` (`top`, `middle`, `bottom` or `single`).
- The str-chat__virtual-message__wrapper--end, --first and --group classes are no longer
applied. Target the list item's .str-chat__li--top / --middle / --bottom / --single.
- MessageList.noGroupByUser and VirtualizedMessageList.shouldGroupByUser are replaced by
groupByUser (defaults: true and false). VirtuosoContext.shouldGroupByUser is groupByUser.
- The groupStyles function prop of both lists is renamed getGroupStyles and is called with
one GetGroupStylesParams object: { groupByUser, maxTimeBetweenGroupedMessages, message,
nextMessage, previousMessage }. The exported getGroupStyles takes the same object, and
useEnrichedMessages takes getGroupStyles and groupByUser instead of groupStyles and
noGroupByUser.
See BC-048, BC-085 and BC-086 in ai-docs/v15-breaking-changes.md.
* **MessageComposer:** useUserRole(message).canEdit is now the update permission alone
(update-any-message, or update-own-message on the user's own message). It no
longer excludes poll messages and giphies. A custom UI that gated an edit button
on it and wants that rule adds `!message.poll_id && message.command !== 'giphy'`.
* **MessageComposer:** the edit, copyMessageText and download rules about the message
moved from useBaseMessageActionSetFilter onto the items of
defaultMessageActionSet as isVisible. A hand-written edit, copyMessageText or
download item without isVisible now shows wherever the base filter allows it,
e.g. edit on a poll message the user may update. Copy the default item's
isVisible to keep the rule. Item isVisible rules also apply when
disableBaseMessageActionSetFilter is set, which previously returned the set
unfiltered, and to failed messages: a poll message or giphy that failed to send
for a network reason no longer offers Edit.
* **MessageComposer:** a message with a poll renders its text and attachments too, and
the quoted message now renders above the poll instead of below it. Such a
message gets str-chat__message--has-poll and no longer gets
str-chat__message--has-single-attachment.
* **MessageComposer:** custom AttachmentPreviewList, VoiceRecordingPreviewSlot and
LinkPreviewList components mount only while their item in
defaultMessageComposerPreviewSet is visible: attachments while the composer holds
an attachment other than a voice recording, voiceRecording while it holds a voice
recording, linkPreviews while a link preview is loading or loaded. Previously they
mounted whenever the preview area showed. A custom component that also renders
something else (a location, a poll, an empty or upload state) replaces its item
with one whose isVisible covers it.
* channel.watch() resolves with the channel instance to use (the channel, or the instance that superseded it), not the server response: read channel.data, channel.state and channel.messagePaginator instead.
- channel.watch() sends no query for a channel already watched and loaded unless it asks for options the current watch was not sent with, so it no longer refreshes one: refresh with channel.reload(), load other messages through channel.messagePaginator.
- A channel.watch() while another with the same options is in flight joins it instead of sending a request.
* **MessageList:** useMessageListScrollManager no longer takes onScrollToTop. Loading older messages keeps the message the reader was looking at in place, also when loading starts at the very top, instead of pinning the newly loaded page to the top.
- InfiniteScrollPaginator's loadNextDebounceMs now rate-limits the edge check while scrolling (the first check runs at once, then at most once per loadNextDebounceMs) instead of waiting until scrolling stops for that long. Older and newer pages start loading before the reader reaches the edge.
- MessageList and VirtualizedMessageList pass showNotification to NewMessageNotification only when a message from someone else arrives while the list is scrolled up. Being on an older message set no longer shows it; ScrollToLatestMessageButton signals that.
- In a channel, ScrollToLatestMessageButton shows the current user's unread count from the channel's read state instead of counting message.new events, so messages a reconnect brought in are included. In a thread it counts new replies from 0.
* marker: the removals are v14 API, so the branch has a
`fix!:` commit with a `BREAKING CHANGE:` footer, per the AGENTS.md
convention. The footer to keep in the squash commit:
* `MessageList` no longer accepts `messages`, `hasMore`,
`jumpToLatestMessage`, `loadingMoreNewer` or `messageLimit`, and
`VirtualizedMessageList` no longer accepts `messages`; the lists render
and paginate the message paginator. `CustomClasses.chatContainer` and
the `ComponentContext.LoadingErrorIndicator` slot are removed. Inside
`ChatView` the channel list no longer renders its own `str-chat <theme>`
scope. See ai-docs/v15-breaking-changes.md (BC-019, BC-046, BC-047,
BC-073, BC-074).
Verification: `yarn lint`, `tsc -p tsconfig.lib.json --noEmit`, `yarn
types:scripts`, `yarn validate-translations` and `yarn test` (281 files,
3090 tests) pass. `yarn build`, `validate-cjs`, `validate-esm`,
`validate-entry-isolation`, and the tutorial and vite example builds
passed before the rebase onto the guides branch, which brought in only
docs and the api-surface script. The ai-chatbot example build was not
verified: it hung locally in a sandboxed environment. The theme-scope
change was not checked visually in a browser.
### UI Changes
No intended visual change inside `ChatView`. Outside `ChatView`, the
channel list picks up the design tokens again, as in v14. The
`MessageList` loading indicator now appears while older messages load.
No screenshots: the examples need Stream credentials to run.
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **New Features**
* Gallery items can display message-specific timestamp overrides.
* Reply-count buttons honor a messageβs custom thread-opening behavior.
* Standalone channel lists now inherit the chat theme, and
channel-detail actions use workspace navigation.
* `useMessagePaginator` and `useIsCooldownActive` are available from the
package entry point.
* Message timestamps can render with an explicit message outside a
message context.
* **Bug Fixes**
* Message-list loading indicators and pagination behavior now reflect
active page loads more accurately.
* Workspace navigation warns when navigation actions are used without a
provider.
* **Breaking Changes**
* Removed legacy message-list props and the `LoadingErrorIndicator`
component override slot.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
* the `options` prop of `useCreateChatClient` is renamed to
`config`, and its type changes from `StreamChatOptions` to the `stream-chat`
configuration tree (`DeepPartial<InstanceConfigTree>`). Options that were
passed at the top level now go under the `client` key, for example
`options={{ baseURL, axiosRequestConfig }}` becomes
`config={{ client: { baseURL, axiosRequestConfig } }}`. Requires a
* requires stream-chat 10.0.0-rc.20 or later; client.activeChannels, client.getChannelById() and client.getChannelByMembers() are gone, use client.channelManager.get(cid), values() and client.channel() / client.channelManager.ensure({ type, id, data }); the thread list is client.threads.paginator, and threads come from client.threads.ensure() / get().
* getChannel() is removed; get the channel from client.channelManager.ensure() and call channel.ensureWatched(options).
* channel search results, and the channels message search fetches for its results, are no longer watched; a custom search result component watches the channel when it is opened (channel.ensureWatched(), or jumpToMessage(id, { watchChannel: true }) for a message).
* port the tutorial alignment (#3251) to v15, with panel-aware workspace navigation (#3301)
* **MessageComposer:** the `preventClearingOnUnmount` prop is removed from
`MessageComposer`. Drop it. If you relied on unmounting to clear a
composer supplied through `MessageComposerControllerProvider`, call
`clear()` on it yourself when your UI is done with it.
* `ChannelListItemProps.channelUpdateCount` is removed.
The unread badge now updates without it.
* `ChannelListItemProps.key` is removed. React's own
`key` prop is unaffected.
* `ChannelListItemProps.watchers` is removed. It had no
effect in v15.
* adopt stream-chat's branded TimestampNS (#3297)
* **Attachment:** `useSendMessageFn` and `useUpdateMessageFn` are removed. Call
`messageComposer.send()` and `messageComposer.update()` instead. They resolve
`'sent' | 'nothing-to-send' | 'failed'` rather than a boolean, so a truthiness
check on the result now passes for every outcome β compare against `'sent'`.
* **Attachment:** the two message submission failure notifications changed key.
`messageComposer.sendMessageFn.sendMessageRequestFailed.text` is now
`notification.messageSendFailed`, and
`messageComposer.updateMessageFn.editMessageRequestFailed.text` is now
`notification.messageUpdateFailed`. A typed custom translation dictionary fails
to compile until both are renamed.
* **Attachment:** `RetryHandler` takes `Omit<OperationParams<'retry'>, 'message'>`
instead of `RetrySendMessageWithLocalUpdateParams`, which stream-chat removed in
GetStream/stream-chat-js#1882. Code that names the old type must be updated.
* the `stream-chat` peer range moves to `^10.0.0-rc.12`. That release removes
`connection.changed`, changes `client.wsConnection` from the socket itself to a wrapper, and moves
the socket's settings into configuration. See GetStream/stream-chat-js PR 1859 for the full list;
an application on an earlier `stream-chat` will not typecheck against this version.
* connectivity is read from state stores rather than events. Code that listened for
`connection.changed` β to render its own banner, to refetch on reconnect β must subscribe to
`client.wsConnection.state` or `client.networkConnection.state` instead, or use the new
`useWSConnectionState` / `useNetworkConnectionState` hooks. Nothing in this package dispatches or
forwards the old event. Note the two stores name their fields differently: the socket's `isHealthy`
is always a boolean, while the network's `isOnline` is `boolean | undefined`, where `undefined`
means no platform reporter has reported yet and must be tested with `=== false`.
* `Channel` no longer reloads its channel on `connection.recovered`. The client
reloads every active channel itself and dispatches the event afterwards, so handling it here
reloaded each open channel twice per reconnect. Anything relying on the component to refresh a
channel after a reconnect should rely on the client instead.
* a socket drop on a working network now renders "Reconnectingβ¦" rather than
"Waiting for networkβ¦", from the new `chat.reportLostConnection.reconnecting.text` key. Tests and
dictionaries keyed on the old copy for that case need updating; the `waitingNetwork` key is
unchanged and still used when the device network is down. The delay before either banner appears
now comes from `client.wsConnection.config.offlineNotificationDisplayDelayMs` (5s default) rather
than from a fixed hold inside the client, so changing that config changes when the banner shows.
The `system:network:connection:lost` notification type is unchanged.
* `Thread` requires a `thread` prop and renders `children`.
`additionalMessageComposerProps`, `additionalMessageListProps`,
`additionalParentMessageProps`, `additionalVirtualizedMessageListProps`, `autoFocus` and
`virtualized` are removed β pass those props to the children you render. `Thread`
provides the thread itself, so it no longer needs to be wrapped in `ThreadProvider`. The
`str-chat__thread--virtualized` class is gone; `Thread` cannot know which list you chose.
* `Thread.enableDateSeparator` is removed with nothing in its place. A
`MessageList` in a thread now follows its own default and shows date separators unless
you pass `withDateSeparator={false}`.
* `disableDateSeparator` is renamed to `withDateSeparator` on
`MessageList` (default `true`, was `disableDateSeparator={false}`) and
`VirtualizedMessageList` (default `false`, was `disableDateSeparator={true}`). The rename
also reaches `processMessages`, `useEnrichedMessages`, `FloatingDateSeparator`,
`useFloatingDateSeparator` and `useFloatingDateSeparatorMessageList`.
* the `head` prop is removed from `MessageList` and
`VirtualizedMessageList`. Both render the thread's parent message from context; override
it with a `ThreadHead` component on `ComponentContext`.
* `ThreadHeaderProps` is down to `overrideTitle`. `thread` comes from the
thread in context, and `closeThread` goes through workspace navigation β customize it
centrally via `ChatView`'s `deriveWorkspaceNavigation`, or call the new `useCloseThread()`
hook from a header of your own. `ComponentContext.ThreadHeader` is removed; compose the
header you want.
* `ThreadSlot` wraps its children in `<Thread>` instead of letting them
replace the whole panel, mirroring `ChannelSlot`. Markup that must sit outside the thread
container moves outside `ThreadSlot`; anything needing thread context must sit inside.
* `Thread` no longer rebuilds its subtree when the thread changes. A
custom component rendered inside `Thread` that relied on being remounted per thread β to
reset local state, or to re-run a mount effect β must depend on the thread instead. The
SDK's own message list does this by keying on the thread; `WithAudioPlayback` by taking
`playbackScope`.
* `LegacyThreadContext` and its `legacyThread` value are removed. The
thread composer resolves its composition context from the `Thread` instance in
`ThreadContext`.
* **MessageActions:** the `messageActions` prop is removed from `Channel`,
`MessageList`, `VirtualizedMessageList`, `Thread` and `Message`. Filter
`defaultMessageActionSet` and pass the result as `messageActionSet`
instead.
* **MessageActions:** `MESSAGE_ACTIONS`, `OPTIONAL_MESSAGE_ACTIONS`,
`getMessageActions()`, and the `MessageActionsArray` and `Capabilities`
types are no longer exported. Use the new `DefaultMessageActionType`
union for the action types the SDK ships.
* **MessageActions:** `MessageContext` no longer carries `getMessageActions`
or `actionsEnabled`. The rendered action set is decided by
`messageActionSet` and each item's own `isVisible` predicate.
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
* **AudioPlayback:** `ChannelProps.allowConcurrentAudioPlayback`,
`ThreadProps.allowConcurrentAudioPlayback` and
`WithAudioPlaybackProps.allowConcurrentPlayback`
are removed, with no replacement. If you passed `true`, delete it:
starting a player now pauses
whichever was playing and takes the shared element over. If you passed
`false` or nothing, the
only change is that playback is exclusive across surfaces rather than
within one.
* **AudioPlayback:** `useActiveAudioPlayer()` now reports the app-wide
active player rather than the
calling surface's.
* **Channel:** `ChannelProps.channel` is now required and
`EmptyPlaceholder` is removed.
Render the exported `ChannelPlaceholder` to fill the same
`.str-chat__channel` layout slot
while no channel is selected.
* **Channel:** `Channel` no longer queries the channel;
`initializeOnMount` and
`channelQueryOptions` are removed. Whoever supplies the channel
initializes it β use the
exported `getChannel({ channel, client })`, which watches and
de-duplicates concurrent calls.
Channels from `ChannelList` or any `queryChannels` call arrive watched
already. `Channel` no
longer renders `LoadingIndicator` or `LoadingErrorIndicator` for a query
it does not make, and
an uninitialized channel renders as an empty channel with no error.
* **Channel:** `Channel` no longer writes `document.title`, and
`activeUnreadHandler` is
removed. See `examples/vite/src/DocumentTitleManager` for a working
replacement in application
code.
* **Channel:** the `channel.channelMissing.text` translation key is
removed. This is a
compile error (TS2353) for any dictionary typed with the exact
`TranslationDictionary` β
delete the entry from custom locale files.
* **Channel:** `ChatContext.latestMessageDatesByChannels` is removed.
Slow-mode cooldown now
lives on `channel.cooldownTimer` (`useCooldownRemaining` /
`useIsCooldownActive`), and the
channel's latest message on
`channel.messagePaginator.aggregateState.lastMessage`.
* **Channel:** `Channel` no longer jumps to a message focused from
search. Selecting a search
result calls `channel.messagePaginator.jumpToMessage(id)` directly, and
the highlight is read
from `channel.messagePaginator.messageFocusSignal`. A custom message
list that relied on
`Channel` performing the jump must call `jumpToMessage` itself.
* **i18n:** consume the shared runtime from @stream-io/i18n (#3284)
* move to timestamps in response models (#3278)
* request-handler props are removed from `Channel` and
`Thread`.
`doSendMessageRequest`, `doUpdateMessageRequest`,
`doDeleteMessageRequest` and
`doMarkReadRequest` are gone, along with `useChannelRequestHandlers`,
`useThreadRequestHandlers` and `useEditMessageHandler`. Register once on
the client:
```
// v14
<Channel channel={channel} doSendMessageRequest={mySend}>
// v15
client.config.set({
channel: {
requestHandlers: {
sendMessageRequest: async ({ localMessage, message, options }) => ({
message: await mySend(message, options),
}),
},
},
});
```
Three differences: handlers take a single params object and return `{
message }`; thread
flows register under the `thread` key; and registration is per CLIENT,
not per mounted
subtree β the one thing the props could do that this cannot. Per-channel
behaviour now
needs a branch inside one handler on the `cid` it receives.
* `useChannelConfig` returns the channel's RESOLVED
configuration instead
of the channel type's raw server config, so field names change:
channelConfig?.typing_events β channelConfig?.typingEvents.enabled
channelConfig?.read_events β channelConfig?.readEvents.enabled
channelConfig?.replies β channelConfig?.replies.enabled
channelConfig?.user_message_reminders β
channelConfig?.userMessageReminders.enabled
channelConfig?.commands β channelConfig?.availableCommands
Every gate is now the server flag ANDed with what is registered through
`client.config`,
which is what the client actually enforces. The hook also subscribes:
these were getters,
so a `client.config.set()` previously never reached the screen. No
component reads
`channel.serverConfig` any more.
* `useAttachmentManagerState` replaces
`hasCustomDoUploadRequest` with
`customCdn`, and gains `attachmentsEnabled`, `locationEnabled` and
`pollsEnabled`.
* the per-domain stores on `channel.state` are removed β
`readStore`,
`typingStore`, `membersStore`, `watcherStore`, `ownCapabilitiesStore`,
`mutedUsersStore`.
Subscribe to `channel.state` itself; the selector is unchanged, because
`ChannelStateData`
is flat and keeps the same top-level keys:
useStateStore(channel.state.ownCapabilitiesStore, sel) β
useStateStore(channel.state, sel)
`WatcherState` is renamed `ChannelWatchState`, and
`channel.disconnected` is renamed
`channel.pendingDisposal` (removed outright β no deprecated alias).
* `AIStates` is no longer exported from
`stream-chat-react` β import it
from `stream-chat`. The values are unchanged, but it is now
literal-typed, so an inferred
array of its members no longer accepts the wide `AIState`:
const STOPPABLE: readonly AIState[] = [AIStates.Thinking,
AIStates.Generating];
* `<Channel>` marks its channel active while mounted and
reloads it on
`connection.recovered`. The client skips re-seeding an active channel's
message list on
hydration and reconnect, so the consumer owns that window. A custom
channel surface that
does not render `<Channel>` must call `channel.activate()` /
`deactivate()` and
`channel.reload()` itself, or its list goes stale after a reconnect.
* **i18n:** adopt the shared i18n layer from stream-chat/i18n (#3271)
* **context:** - **Context hooks no longer accept a `componentName` argument.**
`useChatContext('X')` and friends stop type-checking; drop the argument.
- **Required contexts throw when used outside their provider**, instead
of logging a warning and returning `{}`. Affects the 14 hooks listed
above. Components must be rendered within the provider they read from β
e.g. anything calling `useChatContext` needs a `<Chat>` ancestor.
- **`useChannelInstanceContext` returns
`Partial<ChannelInstanceContextValue>`** β `channel` is now typed as
possibly `undefined`.
- **`ChatViewContext` has no default value** (typed `| undefined`), so
`useContext(ChatViewContext)` may return `undefined`.
- **`MessageComposerContext` is typed as `MessageComposerContextValue |
undefined`**, correcting a narrower declaration that was papered over
with casts.
- **The gallery header no longer uses the
`ComponentContext.MessageTimestamp` override**; it renders the item's
own timestamp.
### π Implementation details
Parameter removed from all 7 hooks and their 138 call sites.
Missing-provider behaviour also normalized, since `src/` had three
competing conventions (warn + `{} as T`, throw, silent cast). The `{} as
T` fallback deferred failures to a downstream `Cannot read properties of
undefined` several frames away. Everything now goes through
`requireContext` (`src/context/requireContext.ts`):
- **Required β throw**, naming hook and provider: `useChatContext`,
`useMessageContext`, `useMessageComposerContext`,
`useMessageListContext`, `useVirtualizedMessageListContext`,
`useMessageBounceContext`, `usePollContext`, `useDialogManager`,
`useSearchContext`, `useSearchSourceResultsContext`,
`useContextMenuContext`, `useChannelListItemContext`,
`useGalleryContext`, `useChatViewContext`
- **Optional by design β keep default, honest type**:
`useTranslationContext`, `useComponentContext`,
`useChannelInstanceContext` (now `Partial`), `useModalContext`,
`useAriaLiveAnnouncer`, `useMessageTranslationViewContext`
- **Renders both inside and outside a provider β read the raw context**:
`Audio`, `VoiceRecording`, `CardAudio`, `Timestamp`,
`MessageRepliesCountButton`, `MessageComposerUI`, `ModalGallery`
Two changes were needed before their hooks could throw:
- `WithDragAndDropUpload` detected the composer via
`Object.keys(ctx).length > 0`; now uses a
`useIsWithinMessageComposerContext()` predicate. Adds the test file this
component lacked.
- `GalleryHeader` read message context while rendering outside any
`MessageProvider`. `GalleryItem` now carries `user` and `createdAt` β
wrapping the call sites would be wrong, since `ChannelMediaView`
flattens many messages into one list. Adds a
`timestamp.GalleryTimestamp` key.
`ChatViewContext` also loses its module-level `LayoutController`
default, which let unrelated subtrees write to one shared instance.
`src/context/__tests__/missingProviderContract.test.tsx` pins the
required/optional split.
### π¨ UI Changes
The channel-media gallery header now shows a per-item sender and
timestamp, which it previously couldn't display at all.
No screenshots: the demo users on the shared environment have no
channels, and seeding one would write test data others would see.
Covered by unit tests in `GalleryUI.test.tsx` β worth a manual look.
* the <Chat> prop and the ChatContext field channelPaginatorsOrchestrator are renamed to channelManager, and the types ChannelPaginatorsOrchestrator / ChannelPaginatorsOrchestratorState are now ChannelManager / ChannelManagerState. Every useChatContext() consumer reading the old field, and any test that builds a partial ChatContext value, has to be updated. No deprecated prop or type aliases are exported.
* requires stream-chat v10.
* Removed `ChannelStateContext` and `ChannelActionContext` along with
`useChannelStateContext()`, `useChannelActionContext()`,
`useCreateChannelStateContext()` and `useCreateChannelActionContext()`.
Read the channel via `useChannel()` and message state via
`useMessagePaginator()`; both are backed by LLC `StateStore`s.
* `ChatContext` no longer exposes the active channel or `setActiveChannel()`.
Channel/thread routing is slot-driven β use `useChatViewNavigation()` from
`stream-chat-react/slot-layout` (`openChannel`, `openThread`, `closeThread`).
* The `TypingContext` provider path was removed from the `Channel` runtime.
`TypingIndicator` now derives its state from LLC stores and takes
`isMessageListScrolledToBottom` and `scrollToBottom` props.
* Context mutation/pagination helpers were replaced by paginator methods:
`updateMessage`/`removeMessage` -> `ingestItem`/`removeItem`;
`loadMore`/`loadMoreNewer` -> `toTail()`/`toHead()`;
`jumpToMessage`/`jumpToFirstUnreadMessage` -> `messagePaginator.jumpToMessage()`/
`jumpToTheFirstUnreadMessage()`. Messages are no longer in `channel.state`.
* Custom send/update/delete/markRead overrides move from ChannelActionContext
callbacks to `channel.configState.requestHandlers` /
`thread.configState.requestHandlers`.
* `ChatView` is a navigation landmark, not a WAI-ARIA Tabs widget: the selector
is `role="navigation"` with `aria-current` on the active button. There is no
`role="tab"`/`"tablist"`/`"tabpanel"`. `activeChatView` renamed to `activeView`.
* `ChannelList` renders a `role="listbox"` whose items are `role="option"`.
* v10 data shapes: channel name/image read from `channel.data.custom.name`/
`.custom.image`; user custom fields from `user.custom` (e.g. `username`);
attachment `duration`/`file_size`/`mime_type`/`waveform_data` from
`attachment.custom`. Message moderation uses `message.moderation`
(`moderation_details` is gone).
### Bug Fixes
* adapt to removed stream-chat-js hand-written types ([#3274](https://github.com/GetStream/stream-chat-react/issues/3274)) ([ccccce5](https://github.com/GetStream/stream-chat-react/commit/ccccce5e4bbb55340118315b2244df0f37e260d3))
* **ai-components:** fromStreamMessage takes a LocalMessage, loadOlderMessages a toTail ([#3390](https://github.com/GetStream/stream-chat-react/issues/3390)) ([20a488f](https://github.com/GetStream/stream-chat-react/commit/20a488f94128cc9c2fff6999f3afb3aa68af53aa)), closes [#3389](https://github.com/GetStream/stream-chat-react/issues/3389)
* **ai-components:** theme generated UI from AI tokens alone, add showsUI and approval arguments ([#3412](https://github.com/GetStream/stream-chat-react/issues/3412)) ([49f859c](https://github.com/GetStream/stream-chat-react/commit/49f859c800eee8f39018825804c82cc8e2281383)), closes [#3399](https://github.com/GetStream/stream-chat-react/issues/3399) [#3399](https://github.com/GetStream/stream-chat-react/issues/3399) [#3399](https://github.com/GetStream/stream-chat-react/issues/3399) [#3394](https://github.com/GetStream/stream-chat-react/issues/3394) [#3395](https://github.com/GetStream/stream-chat-react/issues/3395) [#3393](https://github.com/GetStream/stream-chat-react/issues/3393) [#3399](https://github.com/GetStream/stream-chat-react/issues/3399) [#3399](https://github.com/GetStream/stream-chat-react/issues/3399)
* **Attachment:** render giphy attachments in their own group with uni⦠([#3322](https://github.com/GetStream/stream-chat-react/issues/3322)) ([08b8c0e](https://github.com/GetStream/stream-chat-react/commit/08b8c0ea75a20d19b0d66042bb521e3b6f463567)), closes [#2934](https://github.com/GetStream/stream-chat-react/issues/2934)
* **Attachment:** render giphy attachments in their own group with unique keys ([8967e37](https://github.com/GetStream/stream-chat-react/commit/8967e37bc8ef1224ffa4d31239de4e6bf02d9803))
* **AttachmentSelector:** offer file uploads with customCdn and add StandaloneButton ([#3397](https://github.com/GetStream/stream-chat-react/issues/3397)) ([3e5af96](https://github.com/GetStream/stream-chat-react/commit/3e5af961049050f23258e2071f0e67fc50fca895))
* bubble-aware colors for quotes, location, polls and the AI stop button ([#3411](https://github.com/GetStream/stream-chat-react/issues/3411)) ([b2a8ded](https://github.com/GetStream/stream-chat-react/commit/b2a8ded2c901c8e254704a77f4e3a798edafa32d))
* **ChannelDetail:** call onLayoutChange and close through onClose outside a modal ([#3396](https://github.com/GetStream/stream-chat-react/issues/3396)) ([03796b0](https://github.com/GetStream/stream-chat-react/commit/03796b03dad983d096268784301d26d208a9a037))
* **ChannelDetail:** give the channel members header action set the shared action-set shape ([#3405](https://github.com/GetStream/stream-chat-react/issues/3405)) ([e7af1aa](https://github.com/GetStream/stream-chat-react/commit/e7af1aa3302f24159bc8a54fd29950f33bf26e54))
* **ChannelListHeader:** list headers take StartContent / EndContent props ([#3406](https://github.com/GetStream/stream-chat-react/issues/3406)) ([d60e737](https://github.com/GetStream/stream-chat-react/commit/d60e7372ace041eaccc8471767e26a2eddd741f8))
* **ChannelList:** strip markdown from the last-message preview and add listboxProps ([#3410](https://github.com/GetStream/stream-chat-react/issues/3410)) ([e614648](https://github.com/GetStream/stream-chat-react/commit/e614648afaf160bf80bd23a9bdd869f91b657e39))
* correct issues found during the comment cleanup ([#3334](https://github.com/GetStream/stream-chat-react/issues/3334)) ([7f87026](https://github.com/GetStream/stream-chat-react/commit/7f870262d8946abb1b27122227d65f3697f5a236))
* **deps:** bump [@stream-io](https://github.com/stream-io) toolkit packages to the .mjs/.cjs builds ([#3368](https://github.com/GetStream/stream-chat-react/issues/3368)) ([c22f3f8](https://github.com/GetStream/stream-chat-react/commit/c22f3f8bb4b2314c28ecb19e7fd57473afb926a8)), closes [GetStream/js-toolkit#58](https://github.com/GetStream/js-toolkit/issues/58)
* **deps:** bump stream-chat to 10.0.0-rc.28 ([#3369](https://github.com/GetStream/stream-chat-react/issues/3369)) ([bca4ce2](https://github.com/GetStream/stream-chat-react/commit/bca4ce24484cf2245f487126de449c6c4bf712dc))
* drop unset MessageContext fields and unify message grouping props ([#3386](https://github.com/GetStream/stream-chat-react/issues/3386)) ([097b474](https://github.com/GetStream/stream-chat-react/commit/097b4742a2533896b4051984af935d5c23436cd4))
* **EmojiPicker:** drop @emoji-mart/react peer dependency on v15 ([#3300](https://github.com/GetStream/stream-chat-react/issues/3300)) ([e7ce32f](https://github.com/GetStream/stream-chat-react/commit/e7ce32fa7840c54e91e8648929353f838628ef03))
* **examples:** translate the voice visualization keys in the vite example ([#3352](https://github.com/GetStream/stream-chat-react/issues/3352)) ([c88ab33](https://github.com/GetStream/stream-chat-react/commit/c88ab339f3dbd89817edb5d2251b62126f5f6520)), closes [#3347](https://github.com/GetStream/stream-chat-react/issues/3347)
* **Gallery:** play videos without a thumbnail and load the selected Giphy version ([#3407](https://github.com/GetStream/stream-chat-react/issues/3407)) ([506cb9d](https://github.com/GetStream/stream-chat-react/commit/506cb9dfacc41d719445f11aaca569a6ea97d3a2))
* **InfiniteScrollPaginator:** check the edges on resize in the next frame ([#3357](https://github.com/GetStream/stream-chat-react/issues/3357)) ([dd40f58](https://github.com/GetStream/stream-chat-react/commit/dd40f582daaa9810dcb4faa00e4a2a41b61e520c)), closes [GetStream/website-react-examples#138](https://github.com/GetStream/website-react-examples/issues/138) [#3342](https://github.com/GetStream/stream-chat-react/issues/3342)
* **LinkPreviewList:** take the preview thumbnail from image_url or thumb_url ([#3392](https://github.com/GetStream/stream-chat-react/issues/3392)) ([8b6bbd9](https://github.com/GetStream/stream-chat-react/commit/8b6bbd96bfee84748028290d72b561488a3ad191))
* **MediaRecorder:** build the voice recording with a pending upload state ([#3400](https://github.com/GetStream/stream-chat-react/issues/3400)) ([6285e3d](https://github.com/GetStream/stream-chat-react/commit/6285e3dd03d153e957de355001c96dfa49d2e0fe))
* **MediaRecorder:** stop the recording timer when the preview unmounts ([#3351](https://github.com/GetStream/stream-chat-react/issues/3351)) ([3c8b30a](https://github.com/GetStream/stream-chat-react/commit/3c8b30aa8c37cdc939cf3846f8f616bce8eaa9c6))
* **MessageActions:** show Pin only with the pin-message capability ([#3382](https://github.com/GetStream/stream-chat-react/issues/3382)) ([f31ab7a](https://github.com/GetStream/stream-chat-react/commit/f31ab7a4211af432c9cd137c86cdead5cc0fdaee))
* **MessageList:** keep the reader's place on older pages and count only arrived messages as new ([#3342](https://github.com/GetStream/stream-chat-react/issues/3342)) ([ab00d67](https://github.com/GetStream/stream-chat-react/commit/ab00d67111cd945714d5689d7cef163d0bf55f9f))
* **MessageList:** keep the viewport still when a jumped-to window reaches the live head ([#3290](https://github.com/GetStream/stream-chat-react/issues/3290)) ([351d69d](https://github.com/GetStream/stream-chat-react/commit/351d69dc71a59b6254b4c03f32d63127d67e721b)), closes [#3027](https://github.com/GetStream/stream-chat-react/issues/3027)
* **MessageList:** restore the older-page anchor on iOS WebKit ([#3316](https://github.com/GetStream/stream-chat-react/issues/3316)) ([e3247e1](https://github.com/GetStream/stream-chat-react/commit/e3247e10ac97a49c509c41da0af23b5b21acff22))
* **MessageList:** show delivery and read state on every message that has it ([#3296](https://github.com/GetStream/stream-chat-react/issues/3296)) ([e74c57a](https://github.com/GetStream/stream-chat-react/commit/e74c57acf9215421cbcb860489a4685c2d9b0474))
* **MessageList:** stop the floating date separator from stacking on the inline one ([#3370](https://github.com/GetStream/stream-chat-react/issues/3370)) ([ec1413d](https://github.com/GetStream/stream-chat-react/commit/ec1413d20c7db9e70abde78a3f730a7e0ea9e448))
* **Message:** send default reactions through messageOperations ([#3379](https://github.com/GetStream/stream-chat-react/issues/3379)) ([53f497d](https://github.com/GetStream/stream-chat-react/commit/53f497dc115dce6c4d6d4eaea00797bc647385bd))
* **Message:** show error notification when pin, mute or delete fails ([#3360](https://github.com/GetStream/stream-chat-react/issues/3360)) ([d88dbf0](https://github.com/GetStream/stream-chat-react/commit/d88dbf02a7d021a75c5a5d179875b66571c5804d)), closes [#3359](https://github.com/GetStream/stream-chat-react/issues/3359)
* **Message:** throw when a message handler is missing a parameter ([#3362](https://github.com/GetStream/stream-chat-react/issues/3362)) ([290d2db](https://github.com/GetStream/stream-chat-react/commit/290d2dbaf931a5f19e12b022d542dc8b93c389c6)), closes [#3360](https://github.com/GetStream/stream-chat-react/issues/3360) [#3360](https://github.com/GetStream/stream-chat-react/issues/3360)
* **Poll:** port the poll vote debounce removal ([#3308](https://github.com/GetStream/stream-chat-react/issues/3308)) to v15 ([#3309](https://github.com/GetStream/stream-chat-react/issues/3309)) ([1af035e](https://github.com/GetStream/stream-chat-react/commit/1af035eaf14f1579a3179ed551baff576f6bc3f7)), closes [#1904](https://github.com/GetStream/stream-chat-react/issues/1904)
* prevent eager requests on Thread component mount ([#3282](https://github.com/GetStream/stream-chat-react/issues/3282)) ([6b7dd6c](https://github.com/GetStream/stream-chat-react/commit/6b7dd6cacde3cd3a5d450dc638ae4999070c863f))
* remove dead v15 surface and restore v14 behaviors found by the API audit ([#3328](https://github.com/GetStream/stream-chat-react/issues/3328)) ([d721f40](https://github.com/GetStream/stream-chat-react/commit/d721f406675c327ada97ce1089525f05d180d20b)), closes [GetStream/stream-chat-react#3327](https://github.com/GetStream/stream-chat-react/issues/3327) [#3305](https://github.com/GetStream/stream-chat-react/issues/3305) [#3237](https://github.com/GetStream/stream-chat-react/issues/3237)
* remove props and override keys that have no effect, wire up the rest ([#3408](https://github.com/GetStream/stream-chat-react/issues/3408)) ([91b2c68](https://github.com/GetStream/stream-chat-react/commit/91b2c688536a8309db0ba6136e8922589c825c35))
* render stable notification host for Channel and Thread ([#3291](https://github.com/GetStream/stream-chat-react/issues/3291)) ([ce59094](https://github.com/GetStream/stream-chat-react/commit/ce59094bcd2347357628107b8841851cc4af8ee0))
* **renderText:** stop markdown tables from rendering an empty row under the header ([#3385](https://github.com/GetStream/stream-chat-react/issues/3385)) ([6a4f93d](https://github.com/GetStream/stream-chat-react/commit/6a4f93d99b88e54190a6baf53fcfc60e9b981c2c))
* retry attachment uploads through AttachmentManager.uploadFile ([#3384](https://github.com/GetStream/stream-chat-react/issues/3384)) ([2359ae8](https://github.com/GetStream/stream-chat-react/commit/2359ae839ed5ef354e017870be38b46808519530))
* **ShareLocationDialog:** keep one position watch and give the first fix 10 s ([#3409](https://github.com/GetStream/stream-chat-react/issues/3409)) ([37a4be3](https://github.com/GetStream/stream-chat-react/commit/37a4be336efe5816ca3933fe344e5d4b82579fe9))
* ship the CustomChannelData typing and remove dead exports and the transliterate dependency ([#3403](https://github.com/GetStream/stream-chat-react/issues/3403)) ([85d5b4e](https://github.com/GetStream/stream-chat-react/commit/85d5b4e5dadc07385c177cea89a6309a08bd0b4b))
* show list query errors with a retry in ChannelList and ThreadList ([#3381](https://github.com/GetStream/stream-chat-react/issues/3381)) ([bb1b874](https://github.com/GetStream/stream-chat-react/commit/bb1b874c263b14962f8199f46a1ea2b56b920864))
* **store:** provide getServerSnapshot to useStateStore so server rendering works ([0ccf60d](https://github.com/GetStream/stream-chat-react/commit/0ccf60db7011866068a5ae2ab025f529a1ec2581))
* system message row class, thread list slide, composer error icons, addNotification id ([#3398](https://github.com/GetStream/stream-chat-react/issues/3398)) ([a5daf2a](https://github.com/GetStream/stream-chat-react/commit/a5daf2a6a887edf2481c6c7d3ff4fa4af570d530))
* **types:** remove DefaultCommandData and the CustomCommandData augmentation ([#3380](https://github.com/GetStream/stream-chat-react/issues/3380)) ([10e2b66](https://github.com/GetStream/stream-chat-react/commit/10e2b6651cf53b36b809bfc877158df158d798e4))
* **TypingIndicator:** pass the two typing names to typing.twoUsers as separate values ([#3404](https://github.com/GetStream/stream-chat-react/issues/3404)) ([7021ab9](https://github.com/GetStream/stream-chat-react/commit/7021ab95f337d45a6b2a91aec9535c9135268adb))
* v15 regressions found in side-by-side QA ([#3364](https://github.com/GetStream/stream-chat-react/issues/3364)) ([534c503](https://github.com/GetStream/stream-chat-react/commit/534c5039c44eedd51622fa5404eec649dad0e4ea))
* **VirtualizedMessageList:** keep the last message fully visible in short lists ([#3318](https://github.com/GetStream/stream-chat-react/issues/3318)) ([ef5c384](https://github.com/GetStream/stream-chat-react/commit/ef5c384dd2d3fa0641744fb4168e4459a79bc3f9))
* **VirtualizedMessageList:** pass mention and user handlers and unsafeHTML to messages ([#3402](https://github.com/GetStream/stream-chat-react/issues/3402)) ([059f939](https://github.com/GetStream/stream-chat-react/commit/059f9392d283151143ade44e2ec9009a27a4d100))
### Features
* add icons to ComponentContext adapted to stream-chat-react@15 ([#3294](https://github.com/GetStream/stream-chat-react/issues/3294)) ([baf4b62](https://github.com/GetStream/stream-chat-react/commit/baf4b62f6e43681af1a4712be0f8e0b02609a9d0))
* adopt stream-chat's branded TimestampNS ([#3297](https://github.com/GetStream/stream-chat-react/issues/3297)) ([aaa7fa3](https://github.com/GetStream/stream-chat-react/commit/aaa7fa31e54fed4f7ac1b6d7ee6cf436a4c3234c)), closes [GetStream/stream-chat-js#1884](https://github.com/GetStream/stream-chat-js/issues/1884) [GetStream/stream-chat-js#1884](https://github.com/GetStream/stream-chat-js/issues/1884) [#3295](https://github.com/GetStream/stream-chat-react/issues/3295)
* **ai-components:** add stream-chat-react/ai-components entry point ([50fe88e](https://github.com/GetStream/stream-chat-react/commit/50fe88e27a80a9db805fe747d6d0a9e1b776afe6))
* **ai-components:** add useHeardText for voice-paced text ([#3348](https://github.com/GetStream/stream-chat-react/issues/3348)) ([ec7c956](https://github.com/GetStream/stream-chat-react/commit/ec7c956b88ff35798cd3aad89a90935ce4be6b2c)), closes [GetStream/stream-chat-react#3343](https://github.com/GetStream/stream-chat-react/issues/3343) [GetStream/stream-chat-react#3344](https://github.com/GetStream/stream-chat-react/issues/3344) [#3344](https://github.com/GetStream/stream-chat-react/issues/3344) [#3343](https://github.com/GetStream/stream-chat-react/issues/3343) [#3339](https://github.com/GetStream/stream-chat-react/issues/3339) [GetStream/stream-chat-react#3347](https://github.com/GetStream/stream-chat-react/issues/3347)
* **ai-components:** add VoiceWave and VoiceGlow voice-agent visualizations ([#3347](https://github.com/GetStream/stream-chat-react/issues/3347)) ([216d00d](https://github.com/GetStream/stream-chat-react/commit/216d00dd7aadd45d1e3a4933483f46e35db6c94c)), closes [GetStream/stream-chat-react#3339](https://github.com/GetStream/stream-chat-react/issues/3339) [#3339](https://github.com/GetStream/stream-chat-react/issues/3339) [#3339](https://github.com/GetStream/stream-chat-react/issues/3339)
* **ai-components:** agent conversations, reply steps, approvals and sign-in ([#3389](https://github.com/GetStream/stream-chat-react/issues/3389)) ([6ac68dd](https://github.com/GetStream/stream-chat-react/commit/6ac68dd2826176a5e4c581a3b56eef44764fe767)), closes [#3376](https://github.com/GetStream/stream-chat-react/issues/3376) [#3376](https://github.com/GetStream/stream-chat-react/issues/3376)
* **ai-components:** generated UI, with A2UI and OpenUI renderers ([#3393](https://github.com/GetStream/stream-chat-react/issues/3393)) ([3e808cc](https://github.com/GetStream/stream-chat-react/commit/3e808cc8a9b16216ff728850d18f54cc6eda3f38)), closes [GetStream/stream-chat-react#3388](https://github.com/GetStream/stream-chat-react/issues/3388) [#3388](https://github.com/GetStream/stream-chat-react/issues/3388) [GetStream/stream-chat-react#3394](https://github.com/GetStream/stream-chat-react/issues/3394) [#3394](https://github.com/GetStream/stream-chat-react/issues/3394) [#3394](https://github.com/GetStream/stream-chat-react/issues/3394)
* **ai-components:** port the ai-components entry point ([#3314](https://github.com/GetStream/stream-chat-react/issues/3314)) to v15 ([#3315](https://github.com/GetStream/stream-chat-react/issues/3315)) ([9d1eacb](https://github.com/GetStream/stream-chat-react/commit/9d1eacbd288865898647ef92c24052608b1444a2))
* **Attachment:** render live upload progress on messages being sent ([#3295](https://github.com/GetStream/stream-chat-react/issues/3295)) ([4f1459e](https://github.com/GetStream/stream-chat-react/commit/4f1459e54ee37980c0c0418e69ae927eee60e16a))
* **AudioPlayback:** play one voice message at a time, across the whole app ([#3287](https://github.com/GetStream/stream-chat-react/issues/3287)) ([68999a7](https://github.com/GetStream/stream-chat-react/commit/68999a7b387c05b77d25bcecd3af77c87f2cd02c))
* **Channel:** depend on the Channel instance, not its cid, and narrow its responsibilities ([#3286](https://github.com/GetStream/stream-chat-react/issues/3286)) ([088c795](https://github.com/GetStream/stream-chat-react/commit/088c795c1628da6290f3a6ae5a57aaa058013095))
* **ChannelHeader:** add a Title slot and export the default subtitle ([#3367](https://github.com/GetStream/stream-chat-react/issues/3367)) ([766a71f](https://github.com/GetStream/stream-chat-react/commit/766a71f89d1ba76b53abf2a194e8674aa5e33913))
* **context:** consolidate message UI overrides on the MessageUI slot ([#3268](https://github.com/GetStream/stream-chat-react/issues/3268)) ([0766489](https://github.com/GetStream/stream-chat-react/commit/07664893efe5da053a7430aa6f985ceb91ff2901)), closes [#3267](https://github.com/GetStream/stream-chat-react/issues/3267)
* **context:** drop componentName param from context consumer hooks ([#3267](https://github.com/GetStream/stream-chat-react/issues/3267)) ([9194215](https://github.com/GetStream/stream-chat-react/commit/9194215f01029e79f3ed573991e99686449a84cb))
* **i18n:** english-only bundle with namespaced, type-checked translation keys ([#3261](https://github.com/GetStream/stream-chat-react/issues/3261)) ([17c91bc](https://github.com/GetStream/stream-chat-react/commit/17c91bc70c4af2621870475bf62f15e0341d1ac9))
* **MessageComposer:** let integrators supply the composer MessageComposer edits ([#3299](https://github.com/GetStream/stream-chat-react/issues/3299)) ([14e5860](https://github.com/GetStream/stream-chat-react/commit/14e58601fe7b4d0e9f424468e260e03247dc5f10))
* **MessageComposer:** make composer previews a configurable set ([#3365](https://github.com/GetStream/stream-chat-react/issues/3365)) ([1c02ba0](https://github.com/GetStream/stream-chat-react/commit/1c02ba07fd4bc7a94f23fc5848c70c8405086731))
* migrate components from ChannelActionContext, ChannelStateContext to StateStore instances ([#3237](https://github.com/GetStream/stream-chat-react/issues/3237)) ([99cbe67](https://github.com/GetStream/stream-chat-react/commit/99cbe67049d45d5a0f5f3c4463989a6824f739b6))
* move to new upload API ([#3272](https://github.com/GetStream/stream-chat-react/issues/3272)) ([04a31b1](https://github.com/GetStream/stream-chat-react/commit/04a31b18eff73492e6b305dd751f315bc9637a2b))
* move to timestamps in response models ([#3278](https://github.com/GetStream/stream-chat-react/issues/3278)) ([cb472c3](https://github.com/GetStream/stream-chat-react/commit/cb472c35f0bfe940b360a8564c535b3e9c0e6b53))
* pass the stream-chat configuration tree through useCreateChatClient ([#3321](https://github.com/GetStream/stream-chat-react/issues/3321)) ([557ffcb](https://github.com/GetStream/stream-chat-react/commit/557ffcb3cface45aa152474238c85aefbd0eff53)), closes [GetStream/stream-chat-js#1911](https://github.com/GetStream/stream-chat-js/issues/1911)
* port the tutorial alignment ([#3251](https://github.com/GetStream/stream-chat-react/issues/3251)) to v15, with panel-aware workspace navigation ([#3301](https://github.com/GetStream/stream-chat-react/issues/3301)) ([66557d6](https://github.com/GetStream/stream-chat-react/commit/66557d63b9a6eb08f6cfa8b0cb74418ec2451386))
* release v15 under the latest npm tag ([#3418](https://github.com/GetStream/stream-chat-react/issues/3418)) ([4ff3c97](https://github.com/GetStream/stream-chat-react/commit/4ff3c9745f364d9b34602f080a454afb641d3a09))
* rename ChannelPaginatorsOrchestrator to ChannelManager ([#3258](https://github.com/GetStream/stream-chat-react/issues/3258)) ([be2c8e1](https://github.com/GetStream/stream-chat-react/commit/be2c8e1e7bc486495ac5c9399728f58e78a23398))
* replace StreamChat.activeChannels with entity item index for loaded channels ([#3310](https://github.com/GetStream/stream-chat-react/issues/3310)) ([a41eed5](https://github.com/GetStream/stream-chat-react/commit/a41eed56fb5d471474768a3e989e50bbd9ba62c0))
* support local unread count on v15 ([#3298](https://github.com/GetStream/stream-chat-react/issues/3298)) ([a42c3cf](https://github.com/GetStream/stream-chat-react/commit/a42c3cf9729c107f95516001942aca864eb7f7f9))
* use configuration service API as a single source of truth ([#3273](https://github.com/GetStream/stream-chat-react/issues/3273)) ([f66597a](https://github.com/GetStream/stream-chat-react/commit/f66597a131c8ea2cd7758c78b95fa1479b3d4b3e))
* use network and WS connection observer services from LLC ([#3281](https://github.com/GetStream/stream-chat-react/issues/3281)) ([3098a90](https://github.com/GetStream/stream-chat-react/commit/3098a90a658b6ba49f9831aba27ce6b6720c9c82))
### Refactors
* align Thread with Channel - instance prop, provider, children ([#3289](https://github.com/GetStream/stream-chat-react/issues/3289)) ([c2136f3](https://github.com/GetStream/stream-chat-react/commit/c2136f307021b6007f2015659eb801aed2adfc9e))
* call Channel.watch(), which replaces ensureWatched() in stream-chat ([#3346](https://github.com/GetStream/stream-chat-react/issues/3346)) ([0100d98](https://github.com/GetStream/stream-chat-react/commit/0100d98ce0cac448238afaba2d4f8ec197b04d49))
* **i18n:** adopt the shared i18n layer from stream-chat/i18n ([#3271](https://github.com/GetStream/stream-chat-react/issues/3271)) ([f68cad3](https://github.com/GetStream/stream-chat-react/commit/f68cad3322e5c9fb3e9a20f415bc26cf28c9a701)), closes [GetStream/stream-chat-js#1830](https://github.com/GetStream/stream-chat-js/issues/1830)
* **i18n:** consume the shared runtime from @stream-io/i18n ([#3284](https://github.com/GetStream/stream-chat-react/issues/3284)) ([eb3ae7a](https://github.com/GetStream/stream-chat-react/commit/eb3ae7a5d44d39f86a69476514884ae3e5d4f823))
* **MessageActions:** remove the messageActions prop in from message lists ([#3288](https://github.com/GetStream/stream-chat-react/issues/3288)) ([b541c45](https://github.com/GetStream/stream-chat-react/commit/b541c45f92e448f8f29425beeb13f15bcd262252))
* **MessageList:** render thread messages from the thread unread snapshot ([#3324](https://github.com/GetStream/stream-chat-react/issues/3324)) ([ec6ce33](https://github.com/GetStream/stream-chat-react/commit/ec6ce3397b1e9af006ade622e138bc3cb4e4c007))
* **Message:** replace initialMessage with useIsRenderedAsThreadParent ([#3401](https://github.com/GetStream/stream-chat-react/issues/3401)) ([3b616f9](https://github.com/GetStream/stream-chat-react/commit/3b616f9761a3523c4a78fcbe35a7bc139c8c01f3))
* remove deprecated message APIs before v15 GA ([#3387](https://github.com/GetStream/stream-chat-react/issues/3387)) ([80ca72d](https://github.com/GetStream/stream-chat-react/commit/80ca72d85f4845af04d8627bdae0f8cbe85061cd))
### Performance Improvements
* **build:** emit ESM per source module so consumers can tree-shake ([#3269](https://github.com/GetStream/stream-chat-react/issues/3269)) ([55e76dc](https://github.com/GetStream/stream-chat-react/commit/55e76dca1f9e8a11c3f2ccdc88cc0686d9cf3ccc))
|
π This PR is included in version 15.0.0 π The release is available on: Your semantic-release bot π¦π |
Adopts the shared i18n layer from
stream-chat/i18n, deleting ~1,100 lines of runtime this package no longer needs to own:Streami18n, the formatter/date half ofi18n/utils.ts,TranslationBuilder/TranslationBuilder.ts,externalStrings.ts, and most of the codegen script (249 lines β ~40, over the generator core now ships).What stays here is what is genuinely this SDK's: the generated key catalog,
runtimeDefaults, and the notification translation topic.Behaviour changes
Record<CoreNotificationType, Translator>, so a new identifier instream-chatis a compile error until mapped. Dead rows for identifiers this SDK never emits are gone; three previously-unmapped core identifiers now translate instead of rendering untranslated English.language.*entries move to core, generated fromTranslationLanguage, soMessageTranslationIndicatordrops itsasDynamicKey+ string-compare miss detection.StateStore.setLanguage()returnsvoid;getTranslators()is nowinit(). No deprecated aliases βStreami18nkeeps its name, so integrator code is unchanged there.ai-docs/i18n-v15-migration.md.i18next/dayjs/moment-timezonefromdependenciesβ core supplies the first two, and the third's type leak into the published.d.tsis replaced by core's structuralDateTimeLike.Also adds a
catalogRenderstest (this package had no equivalent of RN's regression net) and putsrelease-v15insize.yml's branch filter, which was only running onmaster.Verified against a locally packed core: 2,829 tests,
validate-esm,validate-cjs, lint. Adopting the shared layer surfaced seven real defects in it, all fixed in the core PR with regression tests.stream-chat@10.0.0-rc.3(GetStream/stream-chat-js#1830). The lockfile is deliberately untouched and must be regenerated once that publishes β until thenyarn install --immutablefails, hence draft.Pre-existing and not from this PR:
yarn build'stscstep fails on 3 imports (APIErrorResponse,EventAPIResponse) removed from core after rc.2. Needs fixing when this package bumps its core range.