Skip to content

feat!: release v10 from master as latest - #2004

Merged
MartinCupela merged 170 commits into
masterfrom
chore/release-v10-latest
Nov 6, 2026
Merged

MartinCupela merged 170 commits into
masterfrom
chore/release-v10-latest

Conversation

@MartinCupela

@MartinCupela MartinCupela commented Nov 6, 2026 •

Copy link
Copy Markdown
Contributor

CLA

  • I have signed the Stream CLA (required).
  • Code changes are tested

Description of the changes, What, Why and How?

This PR brings v10 from release-v10 into master so master publishes 10.0.0 to npm latest. v9 moves to release-v9 (#2003).

⚠️ Merge with a merge commit, not a squash

The repo currently allows only squash merges. Squashing would collapse the 157 v10 commits into a single commit on master, and that commit's title alone would decide the next version. v9.0.0 reached master the same way, as a merge commit of rc (#1476). Please turn on Allow merge commits for this PR, merge with Create a merge commit, and turn the setting off again.

If it is squashed anyway, the PR title is feat!:, so semantic-release still picks 10.0.0.

What the branch contains

Verification

I ran a local dry run of semantic-release 25.0.9 against a throwaway copy of the repo with real tags and notes, and merged this branch into master there with a merge commit:

Run Result
Release on master after this PR 10.0.0 on the latest channel
Release on release-v9 without the 9.55.0 release commit refused: allowed range >=9.54.4 <9.55.0
Release on release-v9 fast-forwarded to a8941342 (9.55.0), with #2003 and a fix 9.55.1 on the v9 channel

git merge-tree of this branch into master reports no conflicts. The result's tree is identical to this branch's tree.

Release order

  1. Fast-forward release-v9 to a8941342 (the 9.55.0 release commit on master), so release-v9 contains the v9.55.0 tag.
  2. Merge chore(release): make release-v9 the v9 maintenance branch #2003 into release-v9.
  3. Retarget the open v9 PRs that target master to release-v9.
  4. Merge this PR with a merge commit.
  5. Run the Release workflow on master: dry run first, then publish 10.0.0.
  6. Later v9 fixes go to release-v9 and publish as 9.55.x on v9.

Changelog

  • stream-chat v10 is generally available and published as latest. v9 continues on the v9 dist-tag.

MartinCupela and others added 30 commits July 3, 2026 11:37
…d state stores (#1795)

## Summary

This PR is a broader SDK architecture update, not just a message
pagination introduction.
In addition to the new paginator stack, it introduces minimal `Thread`
initialization (without initial server thread payload), reactive
`StateStore`-based state in `ChannelState`, new
orchestration/event-pipeline utilities.

## What’s included

### 1) New pagination foundation + message pagination
- Introduces the new paginator system under
`src/pagination/paginators/*`:
  - `BasePaginator`
  - `ChannelPaginator`
  - `MessagePaginator`
  - `MessageReplyPaginator`
  - `ReminderPaginator`
- Adds supporting pagination utilities:
  - filter/sort compilers
  - cursor derivation helpers
  - index + normalization utilities
- Adds `MessagePaginator` integration for channel and thread flows:
  - reply querying through paginator
  - message focus signal support
  - ingest/update/delete/reaction/truncation handling
  - jump-to-message state emission fixes

### 2) Multi-list channel orchestration
- Adds `ChannelPaginatorsOrchestrator` for managing multiple channel
lists/paginators.
- Adds `EventHandlerPipeline` for composable/ordered event handling.
- Supports ownership strategies when a channel matches multiple
paginators (including priority-based ownership).
- Handles list updates/removals/re-emits across relevant channel events.

### 3) Thread minimal-init support (no initial thread payload required)
- `Thread` constructor now supports initialization from:
  - `client + channel + parentMessage` (with optional `draft`)
  - or existing `threadData`
- Enables creating a usable thread instance before full hydration.
- Keeps reload/hydration and pagination bootstrap behavior available
after creation.
- Includes broader thread event/state synchronization updates
(participants/reply count/read/replies).

### 4) Reactive `ChannelState` stores
- Adds `StateStore`-backed reactive slices in `ChannelState`:
  - watchers
  - typing
  - read
  - members (+ member count sync)
  - own capabilities
  - muted users
- Preserves existing access patterns while introducing store-backed
updates.

### 5) Message operations + local update flows
- Adds `MessageOperations` and `MessageOperationStatePolicy`.
- Adds/expands optimistic local update APIs for Channel/Thread message
actions:
  - send
  - retry
  - update
  - delete
- Adds message send retry cache behavior and related test coverage.

### 6) Config/setup and request customization improvements
- Introduces and exports `InstanceConfigurationService`.
- Moves setup/config state handling to dedicated configuration module
and root exports.
- Adds custom mark-read request support for both `Channel` and `Thread`.
- Removes thread parameter from `Channel` custom mark-read request
signature.

## Testing

Large test expansion and updates across:
- paginator units (`Base`, `Channel`, `Message`, `MessageReply`,
compiler/index utilities)
- orchestrator + event pipeline
- channel/channel_state/thread behaviors
- message operations + delivery tracking
- composer/text composer interactions
- merge utility behavior

---------
Co-authored-by: Ivan Sekovanikj <ivan.sekovanikj@getstream.io>
Co-authored-by: Ivan Sekovanikj <31964049+isekovanic@users.noreply.github.com>
Co-authored-by: Anton Arnautov <arnautov.anton@gmail.com>
BREAKING CHANGE: 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.
## [10.0.0-rc.1](v9.50.2...v10.0.0-rc.1) (2026-08-03)

### ⚠ BREAKING CHANGES

* 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.

### Features

* add message/channel paginator stack with thread minimal-init and state stores ([#1795](#1795)) ([62f0507](62f0507))
* integrate OpenAPI generated clients ([#1760](#1760)) ([0776bc4](0776bc4))
* v10 ([cd3141e](cd3141e))
## CLA

- [ ] I have signed the [Stream
CLA](https://docs.google.com/forms/d/e/1FAIpQLScFKsKkAJI7mhCr7K9rEIOpqIDThrWxuvxnwUq2XkHyG154vQ/viewform)
(required).
- [x] Code changes are tested

## Description of the changes, What, Why and How?

Builds on the message paginator state layer with three related
capabilities: a client-global **reactive message store**, **optimistic
reactions** built on top of it and **state-publish throttling** for the
message list.

### 1. Reactive collections - a client-global message store

Separate PR reference:
#1811

TLDR:

A new `MessageStore` (`client.messageStore`) that holds exactly **one**
canonical `LocalMessage` per id, plus a `StoreBackedItemIndex` that lets
paginators keep message **content** in the shared store while membership
stays **local**.

Previously every collection (channel message list, thread replies,
thread parent) held its own copy of a message. A message shared across
collections - e.g. a `show_in_channel` reply that lives in both the
channel list and its thread - meant an edit/reaction had to be manually
fanned out to each copy and copies drifted. There was no single source
of truth for a message's content.

A paginator sees the same CRUD surface as a plain `ItemIndex`, but
`getItem(id)` still means "does *this* paginator hold the id" while the
object lives once in the store. Writing content links the writer as a
holder (drives notification fanout + refcount GC) and notifies every
*other* holder, which reprojects. Removal unlinks a holder rather than
hard-deleting, so a message still held elsewhere survives and the store
GCs it only when the last holder unlinks. Subscribers are notified at
most once per transaction with just the subset of their watched ids that
changed.

### 2. Optimistic reactions

`Channel.addReactionWithLocalUpdate` / `deleteReactionWithLocalUpdate`
and the `Thread` equivalents cause reactions to render instantly and
stay consistent across every collection holding the message.

Reactions previously waited on the server round trip and a reaction on a
message held in multiple places (notably the thread parent, which lives
in no paginator) had no single place to apply it consistently.

`applyReactionLocally` addresses the message purely by id in the store,
folds the reaction into `reaction_groups` / `latest_reactions` (shared
count helpers `computeOwnReactions` / `messageWithReactionAdded` /
`messageWithReactionRemoved`, so paginator and paginator less thread
parent compute identically) and writes it back and the store then
reprojects every holder. It writes memory synchronously **and** the
offline DB and returns an `undo()` that durably reverses **both** on a
terminal server failure (respecting `isEphemeral`, so transient/offline
failures stay queued rather than rolling back). The lower-level
`Channel.sendReaction` / `deleteReaction` only queue the offline replay
task; persistence of the optimistic row is owned by
`applyReactionLocally`.

### 3. State publish throttling

A shared `throttle()` utility and a global switch (`stateThrottling`)
that throttles the message list `state` publish in `BasePaginator`.

High frequency live mutations (rapid events, receipts) would publish to
`state` on every change; coalescing the window publish cuts redundant
rerenders - while a user's own optimistic action must still render
immediately.

`throttle()` (leading/trailing, returning `{ throttledFn, cancelTimer,
flush }`, ported from the [Feeds
client](https://github.com/GetStream/stream-feeds-js/tree/main/packages/feeds-client/src/utils/throttling)
and extended with `flush()`) coalesces publishes per window. Optimistic
writes bypass the delay via the store's `flushState()` hook.

## Changelog

- Add `client.messageStore` (`MessageStore`) - a client-global,
normalized, reactive store holding one canonical message per id;
exported `MessageStore`, `MessageStoreChangeBatch`,
`MessageStoreSubscriber`.
- Add optimistic reactions: `Channel.addReactionWithLocalUpdate` /
`Channel.deleteReactionWithLocalUpdate` and
`Thread.addReactionWithLocalUpdate` /
`Thread.deleteReactionWithLocalUpdate`, with durable rollback across
memory + offline DB.
- Share message content across collections (channel list, thread replies
and thread parent) so edits/reactions on a shared message stay
consistent; refcount GC keeps `show_in_channel` replies alive while any
holder remains.
- Add a throttled message list `state` publish (`throttle` utility +
`stateThrottling` switch); local/optimistic writes bypass the throttle;
disabled under test runners.

---------

Co-authored-by: martincupela <martin.cupela@gmail.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: MartinCupela <32706194+MartinCupela@users.noreply.github.com>
Co-authored-by: Anton Arnautov <arnautov.anton@gmail.com>
## CLA

- [ ] I have signed the [Stream
CLA](https://docs.google.com/forms/d/e/1FAIpQLScFKsKkAJI7mhCr7K9rEIOpqIDThrWxuvxnwUq2XkHyG154vQ/viewform)
(required).
- [x] Code changes are tested

## Description of the changes, What, Why and How?

Generalizes the `MessageStore` into a reusable generic layer, makes the
pinned message list reactive and collapses the `ItemIndex` abstraction
to a single implementation.

### What & Why

The reactive `MessageStore` and its `MessageStoreBackedItemIndex` were
message specific, but nothing about them actually depends on message and
the domain type is only used to pull an `id`. This PR lifts that into a
generic, reusable core so any collection can share the same
normalized/reactive machinery and removes redundant stuff.

**1. `MessageStore` becomes generic `EntityStore<T>`**
- New `EntityStore<T>` holds the entire former store body; its
constructor takes an options object `{ getId }`
(`EntityStoreOptions<T>`), matching the `ItemIndex({ getId })` idiom
- The `MessageStore` **subclass is deleted** since it existed only to
bind `getId`. `client.messageStore` is now `new
EntityStore<LocalMessage>({ getId: (m) => m.id })`
- `MessageStoreBackedItemIndex` becomes a generic
`StoreBackedItemIndex<T>`, with an **optional `store`** (falls back to a
private `EntityStore`, so a detached index behaves like a plain
per-instance one)

**2. Pinned message list is now store backed**
- The store backed `ItemIndex` factory has been moved up to
`MessageIntervalPaginator` (common ancestor of the main list, thread
replies and pinned list), so `PinnedMessagePaginator` now shares the
client global store instead of holding a private `ItemIndex`
- I somehow forgot to do this in the previous reactive collections PR,
even though this is exactly what prompted that architectural design in
the first place 😅

**3. Delete the standalone `ItemIndex` class**
- With the store now optional, a storeless `StoreBackedItemIndex` is
behaviorally identical to the old `ItemIndex`. `owner` is now optional
too and so the four remaining `new ItemIndex(...)` sites (Base default,
`Channel/Reminder/UserGroup` paginators) route through
`StoreBackedItemIndex`.
- The `ItemIndex` class and `ItemIndexOptions` are removed; the
`ItemIndexApi` interface (the contract `BasePaginator` depends on) is
kept, now with a single implementation

### Public API surface

| Removed | Added |
|---|---|
| `MessageStore` (class) | `EntityStore` (class) |
| `MessageStoreChangeBatch` | `EntityStoreChangeBatch` |
| `MessageStoreSubscriber` | `EntityStoreSubscriber` |
| — | `EntityStoreOptions` |

### Testing

- `yarn types`, `yarn lint` (0 warnings), `yarn build` all clean.
- Full unit suite green. New coverage: pinned-message fan-out
(`reactions.optimistic.test.ts`) and owner-less/store-less CRUD
(`StoreBackedItemIndex.test.ts`). The `ItemIndex.test.ts` suite was
folded into `StoreBackedItemIndex.test.ts`.
- Consuming SDKs verified unaffected — the renamed symbols have no
importers in `stream-chat-react-native`.

## Changelog
BREAKING CHANGE: `ChannelManager` keeps its name but is a different class 
table

Co-authored-by: Ivan Sekovanikj <31964049+isekovanic@users.noreply.github.com>
## [10.0.0-rc.2](v10.0.0-rc.1...v10.0.0-rc.2) (2026-08-11)

### ⚠ BREAKING CHANGES

* `ChannelManager` keeps its name but is a different class
table

Co-authored-by: Ivan Sekovanikj <31964049+isekovanic@users.noreply.github.com>

### Features

* generalize entity store in V10 ([#1814](#1814)) ([d066ba0](d066ba0))
* migrate ChannelPaginatorsOrchestrator to ChannelManager ([#1813](#1813)) ([c694930](c694930))
* optimistic reactions ([#1808](#1808)) ([54740f1](54740f1))
## CLA

- [ ] I have signed the [Stream
CLA](https://docs.google.com/forms/d/e/1FAIpQLScFKsKkAJI7mhCr7K9rEIOpqIDThrWxuvxnwUq2XkHyG154vQ/viewform)
(required).
- [ ] Code changes are tested

## Description of the changes, What, Why and How?

## Changelog

-

---------

Co-authored-by: Anton Arnautov <arnautov.anton@gmail.com>
## Description of the changes, What, Why and How?

---------

Co-authored-by: Zita Szupera <szuperaz@gmail.com>
## CLA

- [ ] I have signed the [Stream
CLA](https://docs.google.com/forms/d/e/1FAIpQLScFKsKkAJI7mhCr7K9rEIOpqIDThrWxuvxnwUq2XkHyG154vQ/viewform)
(required).
- [x] Code changes are tested

## Description of the changes, What, Why and How?

Adds **destructive reconciliation** of the channel message list. On
reconnect/rehydrate, messages that were hard-deleted by anyone while the
client was offline are removed from the list, for the channel main list
and thread replies, with offline support enabled or disabled.

A hard delete emits no event to other clients, and the newest page merge
is additive (it never removes), so a message deleted while you were
offline lingers as a ghost forever after reconnect and with offline
support on, it also survives in SQLite and returns on cold start.

Reconciliation is folded into the existing newest page merge, so every
reseed path (`channel.query`, channel list hydrate, `channel.reload`,
`Thread.reload`, `recoverState`) reconciles uniformly, without a full
reload and without dropping already-loaded older pages. A loaded message
that's absent from the authoritative page is pruned; a prefetch snapshot
distinguishes a genuine hard-delete from a message that arrived live
during the fetch, and local (optimistic/failed/error) messages are
always kept. Reconciled ids are mirrored into the offline DB so a cold
start doesn't reseed them. I've also added a billion unit tests that
should capture this behaviour because it's an incredible pain to test
all of the nooks and crannies here.

## Changelog

- **Feature:** hard deletes performed by other users while you were
offline are now removed from the message list (and threads) on
reconnect/re-hydrate instead of lingering as ghosts, so no full reload
and mirrored into the offline DB so they don't return on cold start.
## CLA

- [ ] I have signed the [Stream
CLA](https://docs.google.com/forms/d/e/1FAIpQLScFKsKkAJI7mhCr7K9rEIOpqIDThrWxuvxnwUq2XkHyG154vQ/viewform)
(required).
- [ ] Code changes are tested

## Description of the changes, What, Why and How?

## Changelog

-
Co-authored-by: Zita Szupera <szuperaz@gmail.com>
## CLA


https://linear.app/stream/issue/REACT-1067/wrap-up-filter-pr-in-generator

Regenerate API layer to integrate this fix:
GetStream/chat#15704 and
GetStream/chat#15657

- [ ] I have signed the [Stream
CLA](https://docs.google.com/forms/d/e/1FAIpQLScFKsKkAJI7mhCr7K9rEIOpqIDThrWxuvxnwUq2XkHyG154vQ/viewform)
(required).
- [ ] Code changes are tested

## Description of the changes, What, Why and How?

## Changelog

-
## CLA

- [ ] I have signed the [Stream
CLA](https://docs.google.com/forms/d/e/1FAIpQLScFKsKkAJI7mhCr7K9rEIOpqIDThrWxuvxnwUq2XkHyG154vQ/viewform)
(required).
- [ ] Code changes are tested

## Description of the changes, What, Why and How?
- Generated API methods all expose a `requestOptions` param - for now,
we expose `signal`, same as `master`
- Every hand-written stream-chat-js method exposes `requestOptions` also
- `requestOptions` are also forwarded to offline tasks -> but
since`signal` can't be serialized, they will be lost if the task needs
to be persisted in db
- with this we can remove `createAbortControllerForNextRequest` 

## Changelog

-
## CLA

- [ ] I have signed the [Stream
CLA](https://docs.google.com/forms/d/e/1FAIpQLScFKsKkAJI7mhCr7K9rEIOpqIDThrWxuvxnwUq2XkHyG154vQ/viewform)
(required).
- [ ] Code changes are tested

## Description of the changes, What, Why and How?

This is a port of #1823 to V10, which was not possible to do until our
TS generator spec got extended.

## Changelog

-
## [10.0.0-rc.3](v10.0.0-rc.2...v10.0.0-rc.3) (2026-08-19)

### Bug Fixes

* drop server-side remnants, add WS interface ([#1819](#1819)) ([39c0c53](39c0c53))
* handle offline db syncing errors more gracefully ([#1818](#1818)) ([5b77c05](5b77c05))
* post-merge/OAPI-reconciliation adjustments ([#1812](#1812)) ([5073c67](5073c67))
* regenerate api layer (fix generated filter types) ([#1827](#1827)) ([e540d88](e540d88))
* signal api ([#1828](#1828)) ([9032b1a](9032b1a))
* **types:** add missing API response fields reported by scheduled type tests ([#1817](#1817)) ([e957c9d](e957c9d))

### Features

* destructive reconciliation ([#1822](#1822)) ([3c46fbf](3c46fbf))
* port search improvements ([#1829](#1829)) ([7276299](7276299)), closes [#1823](#1823)
* regenerate from latest open api spec ([#1821](#1821)) ([22344ca](22344ca))
* regenerate open api spec + improve tooling ([#1824](#1824)) ([a3198e7](a3198e7))
* **search:** adapt debounce to query length and cancel superseded requests ([#1823](#1823)) ([1ca8bf7](1ca8bf7))

### Chores

* **deps:** bump axios, form-data and dev dependencies ([#1825](#1825)) ([940f2bc](940f2bc))

### Performance Improvements

* introduce event sync limit for offline db ([#1826](#1826)) ([46cb7df](46cb7df))
## CLA

- [ ] I have signed the [Stream
CLA](https://docs.google.com/forms/d/e/1FAIpQLScFKsKkAJI7mhCr7K9rEIOpqIDThrWxuvxnwUq2XkHyG154vQ/viewform)
(required).
- [ ] Code changes are tested

## Description of the changes, What, Why and How?


https://linear.app/stream/issue/REACT-1084/remove-uploadfile-and-uploadimage-and-use-generated-method-instead

- remove handwritten `client.uploadImage_` and `client.uploadFile_`
methods
- Rename `channel.sendImage` and `channel.sendFile` to `upload*` to
match client method names
- Align method signature to rest of the API: `{ file, name }` instead of
positional args

## Changelog

-
https://linear.app/stream/issue/REACT-1086/move-to-ws-connect-api-v2

## CLA

- [ ] I have signed the [Stream
CLA](https://docs.google.com/forms/d/e/1FAIpQLScFKsKkAJI7mhCr7K9rEIOpqIDThrWxuvxnwUq2XkHyG154vQ/viewform)
(required).
- [ ] Code changes are tested

## Description of the changes, What, Why and How?

WS API v2 sends different WS event shapes, so we need to use that WS
endpoint to receive WS events where custom data is sent in
`event.channel.custom` object.

No SDK change is required; tested both with React and RN, and regular
and anon users.

Claude flagged some backend issues:
https://getstream.slack.com/archives/C07AL9Q0T2T/p1787086052824189 -
only one has a practical impact: the connected user's channel/user mutes
are not returned on WS connect, so that will be a known regression until
fixed.

Half related to this PR, but guest creation was broken, that is fixed
too.

## Breaking changes

- WS API v2 doesn't have long polling implemented, so WS fallback is
removed
- API v1 allowed providing a device ID for client connection, but it
wasn't documented, nor was it used by RN SDK/sample apps; that
functionality is removed (integrators can register a device in a
separate API call)
## [10.0.0-rc.4](v10.0.0-rc.3...v10.0.0-rc.4) (2026-08-19)

### Features

* move to WS API v2 ([#1833](#1833)) ([d530f43](d530f43))
* remove handwritten upload methods; align method names ([#1832](#1832)) ([1a6bdb8](1a6bdb8))
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)
…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.
semantic-release-bot and others added 14 commits November 4, 2026 08:13
## [10.0.0-rc.32](v10.0.0-rc.31...v10.0.0-rc.32) (2026-11-04)

### ⚠ BREAKING CHANGES

* 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.

### Bug Fixes

* drop capabilities.changed; channel.state ownCapabilities is the change signal ([#1992](#1992)) ([6da7713](6da7713))

### Features

* let AttachmentManager.uploadFile retry an existing attachment ([#1991](#1991)) ([cf8b1fa](cf8b1fa))
BREAKING CHANGE: 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.
BREAKING CHANGE: `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`.
Migration: ai-docs/v10-breaking-changes.md#non-upload-attachments
## [10.0.0-rc.33](v10.0.0-rc.32...v10.0.0-rc.33) (2026-11-05)

### ⚠ BREAKING CHANGES

* `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.

### Bug Fixes

* dispatch connection.recovered on reconnect when recovery is disabled ([#1994](#1994)) ([13bb533](13bb533))
* remove CustomCommandData ([#1989](#1989)) ([f737d61](f737d61))

### Features

* 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)
* update to latest api spec ([461eb24](461eb24))
## CLA

- [x] I have signed the [Stream
CLA](https://docs.google.com/forms/d/e/1FAIpQLScFKsKkAJI7mhCr7K9rEIOpqIDThrWxuvxnwUq2XkHyG154vQ/viewform)
(required).
- [x] Code changes are tested

## Description of the changes, What, Why and How?

Fixes for issues QA found while porting the native e2e suites to the
React Native `E2ETestApp`. One commit per ticket.

Tickets: [RN-501](https://linear.app/stream/issue/RN-501),
[RN-479](https://linear.app/stream/issue/RN-479),
[RN-495](https://linear.app/stream/issue/RN-495),
[RN-482](https://linear.app/stream/issue/RN-482),
[RN-480](https://linear.app/stream/issue/RN-480),
[RN-486](https://linear.app/stream/issue/RN-486), and partly
[REACT-1328](https://linear.app/stream/issue/REACT-1328) (same issue as
RN-482).

- `cad8fd5d` - [RN-501](https://linear.app/stream/issue/RN-501).
`client.getAppSettings()` no longer keeps a request that failed, so the
next call tries again. `getUploadConfigCheck` falls back to the app
settings in the offline DB when the request fails, so a file picked
while offline is no longer dropped.
- `6a00c9a5` - [RN-479](https://linear.app/stream/issue/RN-479). Error
and ephemeral messages don't become the channel's latest message. When
the current latest message turns into one, the message before it takes
its place.
- `b4a66276` - [RN-495](https://linear.app/stream/issue/RN-495). System
messages become the channel's latest message again, as in v9.
`skip_last_msg_update_for_system_msgs` still keeps them from moving
`last_message_at`.
- `3dccf287` - [RN-482](https://linear.app/stream/issue/RN-482), and the
first point of [REACT-1328](https://linear.app/stream/issue/REACT-1328).
A dismissed link preview on a text-only message now sends
`skip_enrich_url`, so the server doesn't add the card back. The other
points of REACT-1328 are not part of this.
- `71670bc6` - [RN-480](https://linear.app/stream/issue/RN-480).
`member.removed` drops the removed member's read state, so they no
longer count as a reader. The server drops it too, so a reload shows the
same thing.
- `93a7639c` - [RN-486](https://linear.app/stream/issue/RN-486), the
hard delete part. Deleting a message that counted as unread lowers the
own unread count. The server does this for soft and hard deletes, so
both are handled. The muted-author part of RN-486 is a mock server
issue.

Each commit has unit tests. RN-501 and RN-479 were also checked in the
RN SampleApp on an iOS simulator. The server behaviour behind RN-480 and
RN-486 was checked against the real backend.

## Changelog

- `client.getAppSettings()` retries after a failed request, and uploads
use the offline DB's app settings when the request fails
- error and ephemeral messages are never the channel's latest message
- system messages can be the channel's latest message again
- a dismissed link preview on a text-only message sends
`skip_enrich_url`
- `member.removed` removes the member's read state
- deleting an unread message lowers the own unread count
…empty (#1995)

Ticket: https://linear.app/stream/issue/REACT-1475

## CLA

- [x] I have signed the [Stream
CLA](https://docs.google.com/forms/d/e/1FAIpQLScFKsKkAJI7mhCr7K9rEIOpqIDThrWxuvxnwUq2XkHyG154vQ/viewform)
(required).
- [x] Code changes are tested

## Description of the changes, What, Why and How?

Found in the second side-by-side QA round (React `examples/vite`, v15 +
v10). The v15 "Unread" channel list could open with no request and show
only some unread channels.

**Why:** a live event can route a channel into a list that was never
queried (a message makes a channel unread before the "Unread" list was
ever shown). The channel goes into the paginator's logical head
interval, so `items` becomes defined although no page was loaded.
stream-chat-react's `ChannelList` ran its first query only while `items
=== undefined`, so it skipped it. `isInitialized` can't replace that
check: an empty `setItems` page, which the example uses to seed a
catch-all list that must never query, leaves it `false` too. From the
outside, both states looked the same.

**What / How** (`BasePaginator`):
- New getter `hasLoadedPage`: `true` after a query or any `setItems`
page, including an empty one, and `false` for items that only events
ingested. `isInitialized` is unchanged, so connection recovery
(`ChannelManager.recover`) still skips a seeded empty list.
- `setItems` sets the flag before it publishes, and `resetState` clears
it (and the query shape) before it publishes, so state subscribers such
as a UI selector read current values.

Checks: `yarn lint`, `yarn types` and `yarn test` (4950 passed). The
suite reports 2 unhandled errors from thread and poll tests, also
reported before this change. Verified in the vite example against a
local build: after a live message made a channel unread, opening
"Unread" sends the query and lists all three unread channels, where it
used to list only the event-routed one. The seeded catch-all list still
sends no query.

The stream-chat-react half is in
GetStream/stream-chat-react#3364
(`fix(ChannelList)`, same ticket). It needs a release with this getter.

## Changelog

- Added `BasePaginator.hasLoadedPage`: whether a page was loaded by a
query or `setItems` (including an empty one), as opposed to items that
only live events ingested into a never-queried list.
## [10.0.0-rc.34](v10.0.0-rc.33...v10.0.0-rc.34) (2026-11-05)

### Bug Fixes

* issues found by the RN e2e tests ([#1996](#1996)) ([f5d1a2d](f5d1a2d))
* **pagination:** count a page set with setItems as loaded, even when empty ([#1995](#1995)) ([bfff3c8](bfff3c8))
Every master-only commit is already in release-v10 or replaced by v10
code, so the v10 tree is kept as it is. This makes the PR into master
conflict-free.
master publishes 10.x to the npm latest dist-tag. release-v9 is the v9
maintenance branch on the v9 dist-tag. The release-v10 rc prerelease
branch is retired.
szuperaz and others added 3 commits November 6, 2026 06:45
…et, Date guards (#2006)

## CLA

- [x] I have signed the [Stream
CLA](https://docs.google.com/forms/d/e/1FAIpQLScFKsKkAJI7mhCr7K9rEIOpqIDThrWxuvxnwUq2XkHyG154vQ/viewform)
(required).
- [ ] Code changes are tested

## Description of the changes, What, Why and How?

Ticket: [REACT-1307](https://linear.app/stream/issue/REACT-1307).

A v14/v9 → v15/v10 migration run surfaced three things
`v10-breaking-changes.md` didn't say. Docs only.

- **Moderation needs the v2 flag.** v9's `client.muteUser` /
`unmuteUser` / `flagUser` / `flagMessage` hit the v1 `/moderation/*`
endpoints. They were removed in #1836, and the `client.moderation`
replacements only call `/api/v2/moderation/{mute,unmute,flag}`. On an
app without the v2 moderation feature flag these fail with `403` "this
endpoint needs a feature flag, contact support to get it enabled"
(reported on `10.0.0-rc.34`). The moderation entry now says so and
suggests trying a mute and a flag against the app before upgrading.
- **Partial updates keep custom fields flat.** The custom-data entry
named only `updateUsersPartial`. It now lists every partial update
(`updateMessagePartial`, `channel.updatePartial`,
`channel.updateMemberPartial`, `updateThreadPartial`), says `custom` is
a reserved key in `set` (rejected with ``field `custom` is reserved``),
and notes TypeScript can't catch it because every generated `set` is
`Record<string, any>`.
- **`ts instanceof Date`** joins the silent timestamp failures.

The "Behaviour changes that still compile" table gets matching rows.

React guide counterpart:
GetStream/stream-chat-react#3416.

## Changelog

- docs: moderation v2 feature flag, flat `set` in partial updates,
`Date` guards on timestamps
## [10.0.0-rc.35](v10.0.0-rc.34...v10.0.0-rc.35) (2026-11-06)

### Bug Fixes

* accept poll.update data without id ([#1997](#1997)) ([c5863a9](c5863a9))

### Features

* update to latest API spec ([#2005](#2005)) ([5668f20](5668f20))
@stream-ci-bot

Copy link
Copy Markdown

🎉 This PR is included in version 10.0.0-rc.35 🎉

The release is available on:

Your semantic-release bot 📦🚀

Keeps the v10 changelog and adds the 9.55.0 entry above 9.54.4 with
the other v9 releases.
@MartinCupela
MartinCupela merged commit 40ead99 into master Nov 6, 2026
9 checks passed
@MartinCupela
MartinCupela deleted the chore/release-v10-latest branch November 6, 2026 15:46
github-actions Bot pushed a commit that referenced this pull request Nov 6, 2026
## [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))
@stream-ci-bot

Copy link
Copy Markdown

🎉 This PR is included in version 10.0.0 🎉

The release is available on:

Your semantic-release bot 📦🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants