From 11d39c34de984eaaf0f029afe132fbd6f39115a4 Mon Sep 17 00:00:00 2001 From: Oliver Lazoroski Date: Mon, 19 Oct 2026 08:59:26 +0200 Subject: [PATCH 1/2] =?UTF-8?q?docs:=20add=20v10=20breaking-changes=20inve?= =?UTF-8?q?ntory=20and=20audit=20the=20v9=E2=86=92v10=20ledger?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add ai-docs/v10-breaking-changes.md, the single list of public API breaks between develop (v9) and V10, built from a TypeScript surface diff of both branches, the v9 sample apps type-checked against v10, and a review of all PRs merged into V10. Add ai-docs/v10-docs-impact.md, a per-page assessment of the getstream.io React Native docs (99 of 302 pages affected, new pages needed). Fix stale guidance in ai-docs/ai-migration-v9-to-v10.md (WithLocalUpdate methods, deactivate(), client.activeChannels, queryChannels, dbVersion, channel.markRead semantics, and others) and add Part R covering the gaps the audit found. Correct two claims in i18n-v10-migration.md. --- .claude/plans/v10-breaking-changes.md | 115 ++++ ai-docs/ai-migration-v9-to-v10.md | 741 +++++++++++++++++++++----- ai-docs/i18n-v10-migration.md | 12 +- ai-docs/v10-breaking-changes.md | 583 ++++++++++++++++++++ ai-docs/v10-docs-impact.md | 644 ++++++++++++++++++++++ 5 files changed, 1957 insertions(+), 138 deletions(-) create mode 100644 .claude/plans/v10-breaking-changes.md create mode 100644 ai-docs/v10-breaking-changes.md create mode 100644 ai-docs/v10-docs-impact.md diff --git a/.claude/plans/v10-breaking-changes.md b/.claude/plans/v10-breaking-changes.md new file mode 100644 index 0000000000..636c9e4239 --- /dev/null +++ b/.claude/plans/v10-breaking-changes.md @@ -0,0 +1,115 @@ +# v10 breaking-changes inventory — plan + +## Context + +v10 moves much of the SDK's state logic into `stream-chat` v10 so it can be shared with `stream-chat-react`. Before release we need one authoritative list of every breaking change to the public API (exported components, props, contexts, hooks, utilities, types, state stores, theme, i18n, peer deps), to drive the migration guide and the docs update. + +What exists today: + +- v10 code lives on branch **`V10`** (origin/V10 = `c994ffd00`). This worktree's branch sits on `develop` (`42662cdb1`) and contains none of it. +- The running ledger `ai-docs/ai-migration-v9-to-v10.md` exists only on `V10` (~2.7k lines, sections §2–§19 plus Parts I–O). Companions: `ai-docs/channel-state-ui-migration.md`, `ai-docs/i18n-v10-migration.md`, `ai-docs/i18n-v10-key-map.json`. +- LLC guides already exist in `../stream-chat-js/` (`v9-to-v10-migration-guide-client-construction.md`, `…-dates.md`, `react-thread-paginator-migration.md`) — link to them, don't duplicate. +- Docs: `../getstream.io/content/docs/chat-sdk/react-native/v9-latest/` (302 pages; no v10 folder yet). +- Baseline caveats: V10 merges develop via **squash** PRs (#3865), so git ancestry and `develop...V10` three-dot diffs are wrong — compare **tree tips** instead. Three develop commits are not yet in V10 (#3862 host layers, #3863 CI, #3864 RN 0.88/Expo 58); anything only they introduced is filtered out and noted. +- 40 PRs merged into `V10` (plus some direct commits) — the source of behavioral changes and rationale. + +Decisions (from user): cover the RN surface **plus** the stream-chat changes an RN integrator hits through the SDK (linking to LLC guides for the rest); **patch the ledger** where it is incomplete or stale; docs impact goes to a **separate report file**, no edits to getstream.io. + +## Approach — mechanical diff first, human review second + +The ledger was written incrementally by hand, so it is the thing being verified, not the input. Ground truth comes from a type-level diff of the two public surfaces, then commit/PR review catches what types can't see (behavior, defaults, runtime). Two extra **oracles** catch what a surface diff alone misses (see Step 1b). + +## Execution order — what runs in parallel + +- **Wave A (all concurrent):** both `yarn install`s in the background · 3 PR-review agents (Step 2, need no install) · 1 agent on non-TS surfaces + `examples/` diff + test diffs (Step 1c) · 1 agent extracting `stream-chat-react`'s v10 breaking-change notes for cross-reference. +- **Wave B (concurrent, after installs):** snapshot script in both checkouts · compile-the-v9-app oracle (Step 1b) · LLC type-closure snapshot. +- **Wave C (concurrent):** ledger reconciliation (me, Step 3) · docs-impact scan agent (Step 5) fed the merged item list from Waves A+B. +- **Wave D (sequential):** write the final file, completeness gate, verification. + +~5–6 agents total via the Agent tool, run in one message per wave. (A Workflow script could orchestrate the same fan-out deterministically if you'd prefer — say "use a workflow".) + +### Step 0 — Setup + +- Move this worktree's branch onto `V10` (it has no commits of its own): `git switch -C claude/sdk-v10-breaking-changes-b84ec2 V10`, then `yarn install`. +- Baseline checkout in the scratchpad: `git worktree add /develop develop` + `yarn install` (needs stream-chat 9.x types, so a separate install is unavoidable). +- Copy this plan into the repo at `.claude/plans/v10-breaking-changes.md` (repo convention). + +### Step 1 — Public-surface snapshot (scratchpad script, not committed) + +Write `/api-snapshot.ts` using the TypeScript compiler API (`typescript` is already a dependency). For each entry point — `package/src/index.ts` and `package/src/ai-components/index.ts`, plus the wrapper extras in `package/{native,expo}-package/src/index.js` (`Audio`, `Video`, `FlatList`, …) — it: + +- walks `checker.getExportsOfModule()` (following `export *` chains) and records each export's kind: component / hook / function / context / const / type / interface / enum; +- for functions & hooks: parameter names, optionality, types, and return type (`typeToString` with `NoTruncation`); +- for interfaces & type aliases (props, context values, `Theme`, `ComponentOverrides`): **flattened** members via `getPropertiesOfType`, so `Pick<…>`/`Omit<…>`-derived types like `ChannelProps` are compared property-by-property (name, optional, type string); recurse into `Theme` to full depth; +- special-cases `DEFAULT_COMPONENTS` keys and its `icons` map, the `*Translations` / i18n exports, and `state-store` classes (public members). + +Emits `snapshot.json`. Run it in both checkouts (`yarn tsx` / `node --experimental-strip-types` from the scratchpad, passing the checkout path as an arg), then a small diff script classifies every change: + +- **removed export**, **added export** (non-breaking, tracked for docs), +- **likely rename** (removed+added pairs with similar names/shapes, flagged for confirmation), +- **prop/member removed**, **optional → required**, **type changed** (narrowed/retyped), **signature changed** (params, return type). + +Output: `/surface-diff.md`, grouped by kind. This is the completeness backbone — every breaking line must end up either in the final file or explicitly dismissed (e.g. internal-only, came from develop-only commits). + +**LLC type closure.** A prop typed `MessageResponse` prints the same string in v9 and v10 even though stream-chat reshaped it (`.custom`, timestamps). So the snapshot also collects every `stream-chat` type referenced from RN public signatures, snapshots those types (flattened) from stream-chat 9.x vs 10 rc, and flags RN exports whose referenced LLC types changed shape. + +### Step 1b — Oracles (catch what the surface diff misses) + +- **Compile v9 consumer code against v10.** Copy `develop`'s `examples/SampleApp/src` and `examples/ExpoMessaging` app code into a scratch dir that resolves `stream-chat-react-native` to the V10 checkout and run `tsc --noEmit`. Every error is a break a real integrator hits — cluster errors by symbol and require each cluster to map to an entry. +- **The team's own migration.** `git diff develop V10 -- examples/` is the migration already performed on the sample apps; every changed SDK usage there must map to an entry. +- **Test diffs.** Deleted/rewritten tests under `package/src/**/__tests__` between the tips point at removed behavior; feed them to the Step 2 agents. +- **Deep imports.** The 54 deleted / moved `package/src` files break `stream-chat-react-native-core/src/...` and `lib/...` deep imports; list them (low severity) and check the docs for deep-import paths. +- **Cross-SDK check.** `../stream-chat-react`'s v10/v14 migration notes list the shared LLC-driven changes; anything there that also applies to RN and is missing here gets investigated. + +### Step 1c — Non-TS surfaces + +Diff directly: `package.json` peer deps / deps (`stream-chat` ^10, new `@stream-io/i18n`, removed `@stream-io/state-store`?), wrapper `package.json`s, native module/Podfile flags, `SqliteClient.dbVersion`, theme generated tokens, i18n keys (already covered by `i18n-v10-migration.md` — cross-link only). + +### Step 2 — Behavioral & runtime changes (parallel review) + +Types miss default-value changes, removed side effects, moved logic, event semantics. Using `gh pr list --repo GetStream/stream-chat-react-native --base V10 --state merged` (40 PRs) plus direct commits on V10, run **3 parallel Explore/general agents**, each owning a slice of PRs (by area: channel/message-list/paginator & state; composer/attachments/offline/i18n; channel list/threads/connection/config). Each reads PR descriptions + diffs and returns a structured list: `{symbol, v9 behavior, v10 behavior, migration, PR}` for anything not already visible in the type diff (prop defaults, removed side effects, changed callback timing, things that now require `client.config`). + +### Step 3 — Reconcile with the ledger + +Build a coverage matrix (symbol × ledger §): for every item from Steps 1–2, grep the ledger. + +- **Covered** → link the § in the new file. +- **Missing** → add a section to `ai-migration-v9-to-v10.md` in its existing style (Problem / Before / After / Detection; renames as bullets, not tables — per the ai-docs convention). +- **Stale** (ledger claims a removal/rename the diff doesn't confirm, or the replacement API changed again later, e.g. paginator follow-ups) → fix in place. +- Also fix the ledger's ordering quirks (e.g. §K.8 before §K.6, two "§13.1") only if trivially safe; otherwise leave. + +### Step 4 — Write `ai-docs/v10-breaking-changes.md` + +Source of truth for docs/migration authors (human + agent readable). Structure: + +1. Scope & how it was produced (baseline `develop@` vs `V10@`, regeneration recipe pointing at the snapshot approach). +2. Dependencies & platform (stream-chat v10, peer deps, native/Expo, offline DB version bump). +3. One section per surface kind — Components & props · `` / `` / `` props · Contexts (removed contexts, removed/retyped context fields) · Hooks · Utilities & functions · Types · State stores · `WithComponents` slots & icons · Theme · i18n (summary + link to `i18n-v10-migration.md`). +4. stream-chat changes an RN integrator hits (custom fields under `.custom`, timestamps, `client.config`, connection recovery, `channel.state`, OfflineDB APIs) — short entries linking to the LLC guides. +5. Behavioral changes (no symbol change). +6. Additive APIs worth documenting (from "added exports") — kept brief, needed for the docs update. + +Each entry, as bullets: **symbol** — what changed (v9 → v10) — replacement / migration one-liner — ledger § link — PR #. + +### Step 5 — Docs impact report `ai-docs/v10-docs-impact.md` + +Script over `getstream.io/content/docs/chat-sdk/react-native/v9-latest/**`: for every removed/renamed/retyped symbol and removed prop name from the final list, grep page bodies, code fences and import lines (word-boundary match; prop names matched in context of their component to avoid noise like `channel`). Then manually triage hits. Report: + +- per page: breaking items referenced + what must change (rewrite / delete section / update snippet); +- pages to delete outright (e.g. docs for removed contexts like `TypingContext`, `PaginatedMessageListContext`), pages needing new content (paginator, `client.config`, `channel.state`), and the migration-guide page to create (`06-migration-guides/…upgrading-from-v9`); +- summary counts so the docs effort can be sized. + +## Critical files + +- Read: `V10:ai-docs/ai-migration-v9-to-v10.md`, `package/src/index.ts`, `package/src/ai-components/index.ts`, `package/src/contexts/componentsContext/defaultComponents.ts`, `package/{native,expo}-package/src/index.js`, `package/package.json`, `package/src/store/SqliteClient.ts` +- Write: `ai-docs/v10-breaking-changes.md` (new), `ai-docs/v10-docs-impact.md` (new), `ai-docs/ai-migration-v9-to-v10.md` (patched), `.claude/plans/v10-breaking-changes.md` (plan copy) +- Scratch only: snapshot/diff scripts and their JSON/MD output. + +## Verification + +- Oracle gate: every `tsc` error cluster from compiling the v9 sample apps against V10, and every SDK-usage change in the `examples/` diff, maps to an entry (target: 0 unmapped). +- Completeness gate: every line in `surface-diff.md` classified as breaking is mapped to an entry in `v10-breaking-changes.md` or listed in a "dismissed" appendix with a reason — the script re-runs and reports unmapped symbols (target: 0). +- Spot-check 10 random entries against the code on `V10` (symbol really gone / retyped as stated). +- Every replacement API named in the file exists in `V10` exports (`grep` against the V10 snapshot). +- `yarn lint` passes (prettier on the new markdown). +- Nothing committed without showing the diff and getting approval. diff --git a/ai-docs/ai-migration-v9-to-v10.md b/ai-docs/ai-migration-v9-to-v10.md index 9a850ab61e..c2e6c2d121 100644 --- a/ai-docs/ai-migration-v9-to-v10.md +++ b/ai-docs/ai-migration-v9-to-v10.md @@ -106,7 +106,7 @@ rg '\b(useMessageActions|useMessageActionHandlers)\(\s*\{|\bMessageActionsHookPr rg '\b(sendMessage|SendMessageDisallowedIndicator)\b' src/ # §18 — ChannelList event-override props + channelManager (orchestrator) -rg '\b(onAddedToChannel|onRemovedFromChannel|onChannelDeleted|onChannelHidden|onChannelVisible|onChannelUpdated|onChannelTruncated|onChannelMemberUpdated|onNewMessage|onNewMessageNotification|ChannelListEventHandler|useChannelUpdated|queryChannelsOverride)\b' src/ +rg '\b(onAddedToChannel|onRemovedFromChannel|onChannelDeleted|onChannelHidden|onChannelVisible|onChannelUpdated|onChannelTruncated|onChannelMemberUpdated|onNewMessage|onNewMessageNotification|useChannelUpdated|queryChannelsOverride|createChannelManager)\b' src/ rg 'useChatContext\(\)' -A6 src/ | rg '\bchannelManager\b' rg '=0.79, expo >=53) +rg "from 'stream-chat'" src/ | rg '\b(StateStore|Unsubscribe)\b' + +# §R.2 — connection status off ChatContext +rg '\b(isOnline|connectionRecovering)\b' src/ + +# §R.3 — delivery receipts +rg '\b(readBy|deliveredToCount)\b' src/ + +# §R.4 — more removed / retyped context fields and props +rg '\b(maximumMessageLimit|uploadAbortControllerRef|doFileUploadRequest|threadInstance|forceUpdate|useCreateMessageContext|InlineLoadingMoreIndicator|InlineLoadingMoreRecentIndicator|LoadingErrorIndicator|CommandSuggestionItem)\b' src/ +rg '\bAIStates\.\w+\s*\]\.includes\b' src/ + +# §R.5 — / behaviour +rg '\b(initializeOnMount|isLocalUnreadCountEnabled|supersededBy|UnreadMessagesNotification)\b' src/ + +# §R.6 — channel list query +rg '` | `client.config.set({ channel: { requestHandlers: { markReadRequest } } })` | §13.1 | | `` | `…{ requestHandlers: { updateMessageRequest } }` | §13.1 | | `` | `…{ requestHandlers: { sendMessageRequest } }` (retry: `retrySendMessageRequest`) | §13.1 | @@ -163,7 +201,7 @@ means changed. Details in the linked section. | `useTargetedMessage()` / `setTargetedMessage(id)` | `useMessagePaginator()?.jumpToMessage(id, { focusReason: 'jump-to-message', focusSignalTtlMs: DEFAULT_HIGHLIGHT_DURATION })`; read with `useIsTargetedMessage(id)` | §6 | | `useChannelContext().channelUnreadStateStore` / `setChannelUnreadState` | `channel.messagePaginator.unreadStateSnapshot` | §7 | | `` | self-derived from `channel.state` `read` (override the component to control) | §7 | -| app-wide unread from `event.total_unread_count` | sum `channel.countUnread()` over `client.activeChannels` | §7.1 | +| app-wide unread from `event.total_unread_count` | sum `channel.countUnread()` over `client.channelManager.values()` | §7.1 | | `useMessagesContext()` → `sendReaction` / `deleteReaction` / `deleteMessage` / `removeMessage` / `retrySendMessage` / `updateMessage` | `useMessageOperations()` (same names) | §9 | | `useMessagesContext().targetedMessage` | `useIsTargetedMessage(messageId)` (per row) | §6 | | `useThreadContext().thread` (parent message) | `thread` is now the `Thread`; parent via `useStateStore(thread.state, s => ({ parentMessage: s.parentMessage }))` | §10 | @@ -176,30 +214,30 @@ means changed. Details in the linked section. | `` | ``, no `` around it | §10.1 | | `threadList` (prop or context field) | `useThreadContext().thread` | §10.2 | | `ThreadMessageComposer` slot / `additionalMessageInputProps` | a `MessageComposer` child of `` with its own props | §10.2 | -| `useMessageComposerContext().thread` (parent message) | `thread` is now the `Thread`; parent on `thread.state` | §10.2 | +| `useMessageComposerContext().thread` (parent message; deep import only) | `thread` is now the `Thread`; parent on `thread.state` | §10.2 | | `editMessage()` on the message input context | removed: `useMessageInputContext().sendMessage()` saves an edit; intercept with `updateMessageRequest` | P.17 | -| `` props | removed — list reads the `Thread` from `ThreadContext` + paginator internally | §12 | +| `` props | removed — list reads the `Thread` from `ThreadContext` + paginator internally; `thread` stays, retyped to `Thread \| null` | §12 | | custom `FooterComponent` / `HeaderComponent` reading `loadingMore` from context | receive `{ loadingMore?: boolean }` as a prop | §12.1 | | `` props | removed — state lives in the paginator | §13 | | `useMutedUsers(channel)` | `useMutedUsers()` | §14 | | `useCreateTypingContext` / `useCreatePaginatedMessageListContext` | removed (their contexts are gone) | §15 | | `useMessageActions({ channel, client, message, … })` / `useMessageActionHandlers({ … })` | `useMessageActions()` / `useMessageActionHandlers()` inside a message; `MessageActionsHookProps` removed | §15.1 | -| initial message-list page size ~100 | 25 — override via `channel.messagePaginator.pageSize` | §16.1 | +| initial message-list page size ~100 | 25 — ``'s default when no `pageSize` is registered through `client.config` | §16.1 | | `sendMessage` errors swallowed | `sendMessage` throws — handle the rejection | §16.2 | | `stream-chat` v9 | `stream-chat` `^10.x` required | §17 | | `` event callbacks | `client.channelManager.addEventHandler(...)` / `client.on(...)`; membership+order via the paginator's `filters`/`sort` | §18 | | `` | `` with an app-owned `ChannelPaginator` registered on `client.channelManager` | §18.6 | | `queryChannelsOverride` returning `Channel[]` | removed - the paginator's `paginatorOptions.doRequest` → `return { items }` | §18.1 | | `useChannelUpdated()` | removed — `channel.updated` is handled by the orchestrator | §18.2 | -| `loadNextPage(filters, sort, options)` | `loadNextPage()` (no args) | §18.3 | -| `` / `useChatContext().channelManager` | `client.channelManager` | §18.4 | +| `loadNextPage(queryType?, retryCount?)` | `loadNextPage()` (no args; a no-op until the first page has loaded) | §18.3 | +| `client.createChannelManager(…)` | `client.channelManager` — one per client | §18.4 | | `client.recoverState()` | `client.connectionRecovery.recover()` | §L.6 | | `useChannelContext().error` | `useStateStore((thread ?? channel).messagePaginator.state, sel).lastQueryError` | §L.7 | | `` refreshing the channel / open thread on reconnect | `client.connectionRecovery`; `` only marks them active | §L.4 | -| a custom channel/thread view built on the contexts | call `channel.activate()` / `thread.activate()`, balanced with `deactivate()`, or recovery cannot see them (`` and `` do it for you) | §L.4 | +| a custom channel/thread view built on the contexts | `useEffect(() => channel.activate(), [channel])` (same for `thread.activate()`) — `activate()` returns its release function; there is no `deactivate()`. Without it recovery cannot see them (`` and `` do it for you) | §L.4 | | `new StreamChat(key, { recoverStateOnReconnect: false })` | `client.config.set({ client: { connectionRecovery: { enabled: false } } })` | §L.1 | | `new StreamChat(key, { axiosRequestConfig, persistUserOnConnectionFailure, … })` / `useCreateChatClient({ options })` | the same fields under `client`: `new StreamChat(key, { client: { … } })` | §17.7 | -| a `connection.changed` listener re-querying a list / re-watching a channel | delete it — `client.connectionRecovery` owns reconnect | §L | +| a `connection.changed` listener | delete it — the event is **no longer dispatched** (it compiles and never fires). Reconnect is `client.connectionRecovery`'s job; status is `client.wsConnection.state` / `client.networkConnection.state` (`useWSConnectionState()`, `useNetworkConnectionState()`, `useSettledWSConnectionHealth()`) | §L, §R.2 | | `connection.recovered` as "the `_reconnect()` path fired" | now dispatched on **every** reconnect path, after the recovery reload | §L.2 | | `useStateStore(channel.state.readStore / typingStore / membersStore / watcherStore / ownCapabilitiesStore, sel)` | `useStateStore(channel.state, sel)` — drop the `.Store`, keep the selector | §K.1 | | `channel.state.mutedUsersStore` | `client.mutedUsersStore` (via `useMutedUsers()`) | §K.2 | @@ -208,8 +246,7 @@ means changed. Details in the linked section. | `WatcherState` type | `ChannelWatchState` (now also carries `watchStatus`) | §K.3 | | `t('Send Message')` — the key *was* the English copy | `t('messageInput.sendMessage.accessibilityLabel', 'Send Message')` — stable dotted key, copy inline | §19 | | `t('a11y/Send message')` | the `.accessibilityLabel` leaf of the owning component's key | §19 | -| `import { deTranslations } from 'stream-chat-react-native'` | removed — English is the only bundled language; supply your own dictionary | §19 | -| `new Streami18n('nl')` | `new Streami18n({ language: 'nl' })` — options object; the class name is unchanged | §19 | +| `import { frTranslations } from 'stream-chat-react-native'` | removed — English is the only bundled language; supply your own dictionary | §19 | | `new Streami18n(opts, i18nextConfig)` | `new Streami18n({ ...opts, i18nextConfigOverrides: i18nextConfig })` | §19 | | `const t = await i18n.setLanguage('de')` | `await i18n.setLanguage('de')` → `void`; read `i18n.t` or subscribe to `i18n.state` | §19 | | `i18n.addOnLanguageChangeListener(fn)` | `i18n.state.subscribeWithSelector(({ t }) => ({ t }), fn)` | §19 | @@ -218,6 +255,24 @@ means changed. Details in the linked section. | `getDateString({ date, … })` | `getDateString({ messageCreatedAt, … })`; returns `null`, not `undefined`, when unrenderable | §19 | | `getDateStringForA11y({ date, … })` | `getCalendarDateStringForA11y({ messageCreatedAt, … })` — same behaviour under a new name | §19 | | poll `state.errors.x` as an English string | a `PollComposerValidationError` — key your copy on `error.code`, fall back to `error.message` | §19 | +| React Native 0.76–0.78 / Expo SDK 52 | React Native ≥0.79 / Expo SDK ≥53 | §R.1 | +| `import { StateStore, Unsubscribe } from 'stream-chat'` | the same names from `@stream-io/state-store` (one copy) | §R.1 | +| `useChatContext().isOnline` / `.connectionRecovering` | `useNetworkConnectionState()?.isOnline === false` (device) / `useSettledWSConnectionHealth()` (socket) | §R.2 | +| `useMessageContext().readBy` / `.deliveredToCount` | `useIsMessageReadByOthers({ message })` / `useIsMessageDeliveredToOthers({ message })` | §R.3 | +| `` | `` | §R.3 | +| `useMessageContext().members` | `useStateStore(channel.state, (s) => ({ members: s.members }))` | §4 | +| `` | ignored — the list pages itself; `options.limit` is the page size | §R.6 | +| `` | `filters={{ 'custom.myField': x }}` | §R.6 | +| `channel.state.messages` / `channel.lastMessage()` | `channel.messagePaginator.state` (`items`) / `channel.messagePaginator.lastMessage` | §R.7 | +| `client.activeChannels[cid]` | `client.channelManager.get(cid)` | §R.7 | +| `client.threads.state` `threads` / `client.threads.loadNextPage()` | `client.threads.paginator.state` `items` / `client.threads.paginator.toTail()` | §R.7 | +| `client.addDevice(token, provider)` / `getDevices()` / `removeDevice(id)` | `client.createDevice({ id: token, push_provider })` / `listDevices()` / `deleteDevice({ id })` | §R.8 | +| `client.queryChannels(filters, sort, options)` returning `Channel[]` | `client.queryChannelsAndHydrate({ filter_conditions: filters, sort, ...options })` | §R.8 | +| `client.muteUser(id)` / `client.banUser(id)` / `client.flagMessage(id)` | `client.moderation.mute({ target_ids: [id] })` / `.ban({ target_user_id: id })` / `.flagMessage(id)` | §R.8 | +| `message.ai_generated` | `message.custom?.ai_generated` | §R.8 | +| custom `AbstractOfflineDB.getChannelsForQuery({ filters, sort })` | `getChannelsForQuery({ userId, options })` — `options.filter_conditions`, `options.sort` | §R.9 | +| `i18n.geti18Instance()` | `i18n.i18nInstance` | §R.10 | +| `t('timestamp/ReminderNotification', …)` and the other app-callable formatter keys | register the expression yourself in a `LooseTranslationDictionary` | §R.10 | --- @@ -259,7 +314,7 @@ unchanged from any earlier pre-release that used `channel.state.typingStore` — `threadId?: string` instead. `typing` is retyped `TypingContextValue['typing']` → `TypingUsersState['typing']` (same runtime shape). -- **`filterTypingUsers` util**: signature changed +- **`filterTypingUsers` util** (deep import only in v9 and v10): signature changed `({ client, thread, typing })` → `({ client, threadId, typing })` where `typing: TypingUsersState['typing']`. Pass `threadId: thread?.id`. @@ -360,12 +415,12 @@ const { members } = useStateStore(channel.state, membersSelector) ?? { members: const { watcherCount } = useStateStore(channel.state, watcherSelector) ?? {}; ``` -Note: `members` remains available on the per-message `MessageContext` -(`useMessageContext().members`, retyped to `MembersState['members']` — same -shape) for message-level consumers such as a custom message footer. Only the -**channel-level** `ChannelContext.members` was removed. +`members` is gone from the per-message surface too: `MessageContextValue.members`, the `members` prop of +``, `MessageFooter` and `MessageItemView`, and the `members` param of `useCreateMessageContext`. A custom +message footer reads the channel state itself, as above. The built-in footer only needs to know whether the channel is +a 1:1, so it reads `memberCount` (see §R.5). -## 5. `ChannelContext.markRead` removed → `useMarkRead(channel)` / `channel.markRead()` +## 5. `ChannelContext.markRead` removed → `useMarkRead(channel)` / `channel.markReadViaReporter()` `markRead` is removed from `ChannelContextValue`. Mark-read now fires automatically inside the SDK (the `useMarkRead` hook is wired into `MessageList` / @@ -376,7 +431,10 @@ throttle/debounce is gone. > **Replacement hook: `useMarkRead(channel)`** (exported; new in v10). It returns > a stable `markRead(options?)` callback — the drop-in replacement for the old > context `markRead`. Most apps can just **drop manual calls** (mark-read is -> automatic now). `channel.markRead()` also works for a one-off imperative call. +> automatic now). For a one-off imperative call use `channel.markReadViaReporter()`. +> **`channel.markRead()` is now the direct request**: it skips the delivery reporter and +> any `markReadRequest` handler you registered, and it throws when read events are +> disabled. **Before (v9):** @@ -393,7 +451,7 @@ import { useChannelContext, useMarkRead } from 'stream-chat-react-native'; const { channel } = useChannelContext(); const markRead = useMarkRead(channel); // 1:1 replacement for the old context markRead markRead(); -// or, imperatively (no hook): await channel.markRead(); +// or, imperatively (no hook): await channel.markReadViaReporter(); ``` A custom mark-read handler is still honored, but it is no longer a `Channel` prop — @@ -471,12 +529,12 @@ active channel reports `0` locally while the server total may still briefly coun it, so the server total can transiently over-count. ```tsx -const total = Object.values(client.activeChannels).reduce( - (n, c) => n + c.countUnread(), - 0, -); +const total = client.channelManager.values().reduce((n, c) => n + c.countUnread(), 0); ``` +`client.activeChannels` no longer exists (§R.7): `client.channelManager.values()` lists the stored channels and +`client.channelManager.get(cid)` returns one. + ## 8. `ChannelContext` loaders removed — jump through the paginator Removed symbols: `ChannelContext.loadChannelAroundMessage`, @@ -557,10 +615,10 @@ moved to the exported `useMessageOperations()` hook (returning a > context methods had — the drop-in replacement for the removed > `useMessagesContext()` ops. **Custom `Message` overrides also still receive all > six as props** (the built-in `Message` spreads them in), so override users need -> no change. Prefer the hook over the raw `channel.*` methods below: for -> **reactions** especially, the hook applies the optimistic paginator ingest that -> raw `channel.sendReaction` skips (raw `channel.sendReaction` only renders once -> the WS echo lands). +> no change. Prefer the hook over raw `channel.*` requests: for **reactions** +> especially, the hook applies the optimistic update that raw +> `channel.sendReaction` skips (raw `channel.sendReaction` only renders once the +> WS echo lands). The hook's op signatures: @@ -578,10 +636,11 @@ updateMessage(updatedMessage: MessageResponse | LocalMessage): void; // single-a `MessageProps.updateMessage` overrides. `useMessageActions` now takes no arguments at all (§15.1). -> **Note:** optimistic reactions are applied only when `enableOfflineSupport` is -> on — this is **unchanged from v9** (`develop` gated the same optimistic block -> on `enableOfflineSupport`), so it is not a v10 regression. With it off, -> reactions render on the WS echo either way. +> **Note (behavioural):** reactions are now **always** optimistic, with or without +> `enableOfflineSupport` (v9 applied them optimistically only with offline support +> on). A reaction that fails definitively is rolled back and the promise rejects; one +> queued for offline replay keeps its optimistic state. Remove any reaction +> optimism of your own, or it applies twice. **Before (v9):** @@ -606,17 +665,20 @@ await deleteMessage(message); const MyMessage = ({ sendReaction, deleteMessage, updateMessage }) => { /* … */ }; ``` -**Lower level (non-React call sites):** `channel.deleteMessageWithLocalUpdate`, -`channel.retrySendMessageWithLocalUpdate`, `channel.sendReaction` / -`deleteReaction` remain available — but `channel.sendReaction` does **not** apply -the optimistic update, so use the hook in UI code. +**Lower level (non-React call sites):** `channel.messageOperations` — `send`, +`update`, `retry`, `delete({ localMessage, options })`, +`addReaction({ messageId, reaction })`, `deleteReaction({ messageId, type })` — +runs the same optimistic lifecycle as the hook; `thread.messageOperations` does +the same for replies loaded in a thread. `stream-chat` v10 has **no** +`*WithLocalUpdate` methods. Plain `channel.sendMessage` / `channel.sendReaction` +are HTTP-only and apply no optimistic update, so use the hook in UI code. ### 9.1 Optimistic lifecycle is LLC-owned (behavioral) Do **not** hand-roll optimistic message/reaction state. The -`*WithLocalUpdate` engine + `messagePaginator.ingestItem/removeItem` + the -offline DB own the pending → received/failed lifecycle. Custom optimistic layers -will double-apply or conflict. +`channel.messageOperations` / `thread.messageOperations` engine + the message +paginators + the offline DB own the pending → received/failed lifecycle. Custom +optimistic layers will double-apply or conflict. --- @@ -626,7 +688,8 @@ will double-apply or conflict. This is the largest behavioral shift. A thread is a `stream-chat` `Thread` instance, and `` renders it on its own, next to `` rather than inside it. -`ThreadContextValue` is reduced to **two fields**; everything else is derived from `thread`. +`ThreadContextValue` is reduced to **two fields**; everything else is derived from `thread`. The separate +`threadInstance` field is gone: `thread` is the instance now. Opening and closing a thread is up to the app (no imperative `openThread` / `closeThread`). **`ThreadContextValue` now = `{ thread?: Thread, allowThreadMessagesInChannel: boolean }`.** @@ -653,6 +716,7 @@ Removed or retyped fields (all derivable from `thread`): | `threadHasMore` | `thread.messagePaginator.state.hasMoreTail` | | `threadLoadingMore` / `threadLoadingMoreRecent` | `thread.messagePaginator.state.isLoading` | | `reloadThread` | `thread.reload()` | +| `threadInstance` | `thread` | | `openThread` / `closeThread` / `setThreadLoadingMore` | removed - see 10.1 | | `parentMessagePreventPress` | removed from the context - a `` prop, default `true` | | `onAlsoSentToChannelHeaderPress` | moved to `MessagesContext`; still a prop on `` and `` | @@ -744,7 +808,7 @@ registers a new one. - **``**: `thread` and `threadList` removed. A `` only ever renders its channel's main list; render a `` for replies. -- **`ThreadProps`**: removed `closeThread`, `loadMoreThread`, `reloadThread`, +- **`ThreadProps`**: removed `closeThread`, `loadMoreThread`, `reloadThread`, `threadInstance`, `additionalMessageListProps`, `additionalMessageInputProps`, `shouldUseFlashList`, `autoFocus`, `disabled`, `closeThreadOnDismount` and `parentMessagePreventPress`. `thread` is required and is the `Thread` instance (it was the parent message). Configure the list and composer through their @@ -758,7 +822,7 @@ registers a new one. Use `useThreadContext().thread` to tell whether a subtree renders a thread. Components that took a `threadList` prop now take `thread` (`Thread | undefined`): `MessageItemView`, `SentToChannelHeader` and `ShowThreadMessageInChannelButton`. -- **`MessageComposerContextValue`**: `thread` is now the `Thread` instance (it was the parent +- **`MessageComposerContextValue`** (deep import only in v9 and v10): `thread` is now the `Thread` instance (it was the parent message). - **`ThreadFooterComponentProps`**: `thread` removed; the type is now `{ parentMessagePreventPress?: boolean }`, and `parentMessagePreventPress` defaults to `true`. Read @@ -793,11 +857,15 @@ in. Submitting goes through the composer; see P.17. ## 12. `MessageList` / `MessageFlashList` props Removed props (from both list components): `channelUnreadStateStore`, -`setChannelUnreadState`, `setTargetedMessage`, `targetedMessage`, `thread` -(old `ThreadType` prop), `threadHasMore`, `loadMoreThread`, -`loadMoreRecentThread`. The list now reads thread state from the `Thread` in `ThreadContext`, -unread from the paginator snapshot (§7), and targeting from the focus signal -(§6) — all internally. +`setChannelUnreadState`, `setTargetedMessage`, `targetedMessage`, `threadHasMore`, `loadMoreThread`, +`loadMoreRecentThread`, `threadInstance`, `loadChannelAroundMessage`, `loading`, `reloadChannel`, +`maximumMessageLimit` (O.1), plus `highlightedMessageId` on `MessageFlashList`. The list now reads +thread state from the `Thread` in `ThreadContext`, unread from the paginator snapshot (§7), and +targeting from the focus signal (§6) — all internally. `allowSendBeforeAttachmentsUpload` became +`pendingUploadsEnabled` (O.3). + +`thread` is **not** removed: it is retyped `LocalMessage | null` → `Thread | null`. Pass the instance, +or leave it out and the list reads the one in `ThreadContext`. The paging props `loadMore`, `loadMoreRecent`, `hasMore`, `loadingMore` and `loadingMoreRecent` are removed too: the list pages its own paginator (P.7). `markRead` is still passed. @@ -832,6 +900,11 @@ const MyFooter = ({ loadingMore }: { loadingMore?: boolean }) => No-prop override components remain assignable (the prop is optional), so overrides that don't read the flag need no change. +The **newer** end gets nothing: a custom `HeaderComponent` (`MessageList`) or `FooterComponent` +(`MessageFlashList`) is rendered with no props, and with `PaginatedMessageListContext` gone there is no +SDK source for `loadingMoreRecent`. Read `isLoading` from the paginator instead +(`useStateStore(useMessagePaginator()?.state, selector)`); it is not direction-specific. + ## 13. `ChannelProps` Removed props: `messages`, `loadingMore`, `loadingMoreRecent`, `threadMessages`, @@ -1037,6 +1110,9 @@ the key names. - **`pageSize` is not `channelQueryOptions.messages.limit`.** The prop sizes the *initial* channel query; `pageSize` sizes every *subsequent* page. They are different numbers and you usually want both. +- **``'s 25-message default is itself an imperative patch**, applied on mount only + when no `pageSize` is registered under `channel.messagePaginator` or `messagePaginator` + (§16.1). Register a value to opt out of it. - **Imperative `updateConfig()` does not survive a re-derivation**, except on `MessageComposer`. `channel.messagePaginator.updateConfig({ pageSize: 200 })` is dropped the next time anything re-resolves that paginator's configuration — including a @@ -1127,7 +1203,8 @@ const handlers = useMessageActionHandlers(); // same shape: { handleCopyMessage, - **16.1 Initial message-list page size 100 → 25.** The `stream-chat` default is 100; v10 resolves 25. Apps that assumed ~100 messages loaded on open now get 25 and - paginate the rest in. To change it, **register** the value — + paginate the rest in. `` applies the 25 on mount only when no `pageSize` is + registered. To change it, **register** the value — `client.config.set({ channel: { messagePaginator: { pageSize: 50 } } })` (§13.1). Do **not** re-set `channel.messagePaginator.pageSize` after mount: that is an imperative patch and is dropped the next time the paginator's configuration @@ -1135,7 +1212,7 @@ const handlers = useMessageActionHandlers(); // same shape: { handleCopyMessage, sizes only the initial query. - **16.2 `sendMessage` throws on failure** (previously swallowed). The built-in `MessageInput` catches the rejection and shows a notification. Any custom code - that calls the context `sendMessage` (or `channel.sendMessageWithLocalUpdate`) + that calls the context `sendMessage` (or `channel.messageOperations.send`) directly must handle the rejected promise itself. - **16.3 `SendMessageDisallowedIndicator` is gated on `channel.initialized`** — it no longer flashes during the pre-init window (before capabilities resolve). @@ -1148,8 +1225,9 @@ const handlers = useMessageActionHandlers(); // same shape: { handleCopyMessage, ## 17. `stream-chat` must be v10 v10 SDK code hard-depends on `stream-chat` v10 APIs (`channel.messagePaginator`, -the unified reactive `channel.state`, `*WithLocalUpdate`, `messageDeliveryReporter`, -`channel.messageFocusSignal`). Ensure your app resolves `stream-chat` to `^10.x`. +the unified reactive `channel.state`, `channel.messageOperations`, `messageDeliveryReporter`, +`messagePaginator.messageFocusSignal`). Ensure your app resolves `stream-chat` to `^10.x`. +Platform floors and other packaging changes are in §R.1. > Repo note (for SDK maintainers, not integrators): the published `stream-chat` > range in `package/package.json` must be bumped to `^10.x`, and the local @@ -1216,7 +1294,9 @@ detection won't fire.** If you call the `stream-chat` client/channel directly or annotate with its types, see the LLC's own guides in `stream-chat-js`: `v9-to-v10-migration-guide-{type-renames,other,sort,methods,logging,client-construction}.md`. -Highlights that hit integrator code: +The long-term reference is the consolidated list `ai-docs/v10-breaking-changes.md` in `stream-chat-js` (branch +`claude/sdk-v10-breaking-changes-f64baf`), which replaces those per-topic guides once it lands. The calls RN apps +make most are in §R.7 and §R.8. Highlights that hit integrator code: - **Server-sent dates are unix-nanosecond numbers** on every response and event type (`created_at`, `updated_at`, `last_read`, …) — not `Date` objects and not ISO strings. They are typed @@ -1246,8 +1326,8 @@ Highlights that hit integrator code: sort the raw numbers directly; only convert where a `Date` is actually required. What changed on **this SDK's** own surface: - - **`findInMessagesByDate(messages, targetTimestamp)`** takes a `TimestampNS` (was a `Date`). - Exported from the package root. Pass a server timestamp, or `msToNs(ms)` / `dateToNs(date)`. + - **`findInMessagesByDate`** is removed (P.1); compare `created_at` numbers yourself, or jump with + `channel.messagePaginator.jumpToMessage(id)`. - **`getChannelUnreadState`** (internal) and the `ChannelUnreadState` type are removed (P.9). The snapshot's `lastReadAt` is a `TimestampNS | null`; the lists treat `null` as the epoch, meaning the whole channel is unread. Guard it with `!= null`, never with truthiness. @@ -1271,7 +1351,9 @@ Highlights that hit integrator code: - **Type renames** — `EventTypes`→`EventType`, `Mute`→`UserMuteResponse`, `PollOption`→`PollOptionResponseData`, `ReadResponse`→`ReadStateResponse`, `AppSettingsAPIResponse`→`GetApplicationResponse`, `FormatMessageResponse`→`LocalMessage`, - `Role`→`RoleName`, `Logger`→`Sink`, `*SortBase`→`*Sort`, `TranslationLanguages`→`TranslationLanguage`. + `Logger`→`Sink`, `*SortBase`→`*Sort`, `TranslationLanguages`→`TranslationLanguage`. `Role` is **not** + renamed: it now names the generated role *object*, and role names are plain `string` (there is no + `RoleName`). - **`Event` is a discriminated union** — narrow with `EventPayload<'the.type'>`, or drop the `: Event` annotation so `client.on('x', cb)` narrows automatically. - **Method signatures collapsed to request objects, with path parameters split out** — a method whose @@ -1293,7 +1375,7 @@ Highlights that hit integrator code: be explicit, it just lives on the descriptor now; the same shape applies to `client.uploadFile` and `channel.uploadFile` / `channel.uploadImage` (which replace `channel.sendFile` / `sendImage`); the `client` constructor is 1–2 args; `client.listeners` is a `Map`; - `createAbortControllerForNextRequest` moved to `client.api`. + `createAbortControllerForNextRequest` is removed — pass `{ signal }` as the request options instead. - **Sort is `SortParamRequest[]`** — `{ last_message_at: -1 }` → `[{ field: 'last_message_at', direction: -1 }]`. ## 17.6 Attachments mid-upload carry `localMetadata`, not `custom.*` @@ -1396,8 +1478,11 @@ exported from `stream-chat`), so move whatever you passed there under `client`, wrapper if you used one. `client.persistUserOnConnectionFailure` is no longer a field, so assigning it does nothing. `` -already writes it from `enableOfflineSupport`. To set it yourself, use -`client.config.setConfig('client', { persistUserOnConnectionFailure: true })`. +already writes it from `enableOfflineSupport`: on mount (and whenever `client` or `enableOfflineSupport` +changes) it runs `client.config.setConfig('client', { persistUserOnConnectionFailure: enableOfflineSupport })`, +which **overwrites** a value you registered at construction or with `setConfig` yourself. With +`enableOfflineSupport={false}` it is written back to `false`; to keep it `true`, set it again after `` has +mounted, or enable offline support. --- @@ -1413,8 +1498,8 @@ query; that is intentionally gone (a footgun removed). What you actually need is ## 18. `` per-event override props removed -Removed props (and the exported `ChannelListEventHandler` type): -`onAddedToChannel`, `onRemovedFromChannel`, `onChannelDeleted`, `onChannelHidden`, +Removed props (the v9 options type `ChannelListEventListenerOptions` is still exported, but nothing +takes it any more): `onAddedToChannel`, `onRemovedFromChannel`, `onChannelDeleted`, `onChannelHidden`, `onChannelVisible`, `onChannelUpdated`, `onChannelTruncated`, `onChannelMemberUpdated`, `onNewMessage`, `onNewMessageNotification`. @@ -1452,8 +1537,10 @@ const unsubscribe = client.channelManager.addEventHandler({ ## 18.1 `queryChannelsOverride` removed → `paginatorOptions.doRequest` The prop is gone with the other query props (§18.6). Its replacement is the paginator's `doRequest`, -which receives the query params the paginator would have sent and must return `{ items }`, not a -`Channel[]`. Call `client.queryChannels(...)` inside so client state stays in sync: +which receives the query params the paginator would have sent (a `QueryChannelsRequest` plus an optional +`stateOptions`, which is not part of the request) and must return `{ items }`, not a `Channel[]`. Call +`client.queryChannelsAndHydrate(...)` inside so client state stays in sync. Plain `client.queryChannels` is the +raw call in v10: it returns the response object, creates no `Channel` instances and stores nothing. ```tsx // v9: Promise} /> @@ -1462,10 +1549,9 @@ const paginator = new ChannelPaginator({ client, id: 'channels:main', paginatorOptions: { - doRequest: async (queryParams) => { - const items = await client.queryChannels(queryParams); - return { items }; - }, + doRequest: async ({ stateOptions, ...request }) => ({ + items: await client.queryChannelsAndHydrate(request, stateOptions), + }), }, }); ``` @@ -1480,29 +1566,21 @@ the usage. ## 18.3 `ChannelsContextValue.loadNextPage` retyped -`loadNextPage` is now `() => Promise` (it dropped its optional query-type arguments): +`loadNextPage` is now `() => Promise` (it dropped its optional `queryType` / `retryCount` +arguments). It is also a **no-op until the list has finished its first query**: ```tsx -// Before: loadNextPage(filters, sort, options) +// Before: loadNextPage(queryType?, retryCount?) // After: loadNextPage(); ``` -## 18.4 `` prop + `ChatContext.channelManager` removed - -`ChannelManager` is a singleton per client, so `` no longer accepts a `channelManager` prop and -`useChatContext()` no longer returns one. Read `client.channelManager` directly: - -```tsx -// Before -const { channelManager } = useChatContext(); -// After -const { client } = useChatContext(); -const channelManager = client.channelManager; -``` +## 18.4 One `client.channelManager` per client -Configure the shared manager through its own API (`client.channelManager.setEventHandlers(...)`, -`setOwnershipResolver(...)`) instead of the removed prop. +No v9 prop or context field existed for this: v9's `` created a manager of its own with +`client.createChannelManager(...)`. That method is gone. There is one `ChannelManager` per client, every +list shares it, and you read it as `client.channelManager`. Configure it through its own API +(`client.channelManager.setEventHandlers(...)`, `setOwnershipResolver(...)`). ## 18.5 Behavioral: channel-list ordering & watch defaults @@ -1642,14 +1720,18 @@ The `MutedUsersState` type and the `channel.state.mutedUsersStore` handle are re const name = useStateStore(channel.state, s => ({ name: s.data?.custom?.name }))?.name; ``` -## K.4 `channel.activate()` / `deactivate()` / `channel.active` (additive) +## K.4 `channel.activate()` / `channel.active` (additive) -New refcounted lifecycle API mirroring `thread.activate()`. `` calls -`activate()` on mount and `deactivate()` on unmount for you; `channel.active` is a reactive -boolean. While active, the channel auto-marks messages read on focus and the channel-list -hydration does not destructively re-seed the open channel's message list on reconnect. Custom -navigation that mounts a channel outside `` can call these directly (they refcount, so a -shared instance stays active until the last holder deactivates). +New refcounted lifecycle API mirroring `thread.activate()`. `activate()` **returns its release +function** — there is no `deactivate()` on a channel or a thread. `` activates on mount and +releases on unmount for you; `channel.active` is a reactive boolean. While active, the channel +auto-marks messages read on focus and the channel-list hydration does not destructively re-seed the +open channel's message list on reconnect. Custom navigation that mounts a channel outside `` +can do the same (it refcounts, so a shared instance stays active until the last holder releases): + +```tsx +useEffect(() => channel.activate(), [channel]); // the returned release runs on unmount +``` ## K.5 In-place mutation of `channel.data.member_count` / `own_capabilities` no longer syncs (behavioral) @@ -1670,10 +1752,10 @@ if you need subscribers to update.) ## K.8 `channel.disconnected` → `channel.pendingDisposal` (breaking) -The flag is one-way and terminal: `Channel._disconnect()` disposes the paginators, unregisters the -subscriptions, and the client drops the channel from `activeChannels` right after — the instance is -never reconnected. `disconnected` read like something that could come back, so the state key and the -getter/setter are now `pendingDisposal`. +The flag is one-way and terminal: `channel.disconnect()` (v9's `_disconnect()`) disposes the paginators +and unregisters the subscriptions, and the instance is never reconnected — +`client.channelManager.ensure(...)` gives a fresh instance for the cid. `disconnected` read like something +that could come back, so the state key and the getter/setter are now `pendingDisposal`. ```tsx // Before @@ -1750,25 +1832,29 @@ favour of it. 2. **Every `active` channel reloads itself** — `channel.reload()`. A list page carries far fewer messages per channel than an open channel's loaded window, so the open channel cannot be served by the list query. `` marks its channel `active` on mount (§K.4). -3. **Every active thread reloads its replies** — `thread.reload()`, for threads in - `client.threads.threadsById` whose `state.active` is set. Nothing else covers them: a channel - reload refreshes the main message list, and `ThreadManager`'s own recovery refreshes the thread - *list*, reusing thread instances without rehydrating them unless something separately marked them - stale — which only `user.watching.stop` does, never a reconnect. Note the current limitation: a - thread opened from a message list only enters `threadsById` once `` adopts it (after its - replies load), so a reconnect before that — or after `ThreadManager.reload()` evicts it — skips it. +3. **Every active thread reloads its replies** — `thread.reload()`, for threads registered in + `client.threads` (the ones `client.threads.get(id)` resolves) whose `state.active` is set. Nothing + else covers them: a channel reload refreshes the main message list, and `ThreadManager`'s own + recovery refreshes the thread *list*, reusing thread instances without rehydrating them unless + something separately marked them stale. A thread is registered as soon as `thread.activate()` or + `client.threads.ensure(...)` runs, so a thread opened from a message list is covered from the + moment `` mounts. Registered threads that are not active are only flagged stale, and reload + on their next `activate()`. 4. **Every other previously-watched channel self-heals on demand** — §L.3. -It is deliberately **not** a sweep over `client.activeChannels`: watches are a bounded server -resource, and after any scrolling that cache holds far more channels than a query page. +It is deliberately **not** a sweep over the channel store (`client.channelManager.values()`; v9's +`client.activeChannels`): watches are a bounded server resource, and after any scrolling that store +holds far more channels than a query page. With offline support enabled, active-channel recovery is triggered off the offline DB's sync-status edge, so the ordering `executePendingTasks()` → `sync()` → reload holds on every reconnect path — including mobile backgrounding (`closeConnection()` → `openConnection()`), which v9's client-side recovery never covered at all. -**No app code is required for any of this.** Do not add a `connection.changed` listener that -re-queries a list or re-watches a channel — that now duplicates the client. +**No app code is required for any of this.** Do not add a listener that re-queries a list or +re-watches a channel on reconnect — that now duplicates the client. Note that `connection.changed` is +**no longer dispatched** at all: `client.on('connection.changed', …)` compiles and never fires. Connection +status lives on `client.wsConnection.state` and `client.networkConnection.state` (§R.2). ## L.1 `recoverStateOnReconnect` removed — configure recovery instead (breaking — `stream-chat`) @@ -1848,13 +1934,15 @@ thread's replies. **Both are gone**; the component's only remaining part is decl screen: ```tsx -// what does now — the rest is client.connectionRecovery's job -useEffect(() => { channel?.activate?.(); return () => channel?.deactivate?.(); }, [channel]); +// what does now — the rest is client.connectionRecovery's job. +// activate() returns its release function, which runs on unmount. +useEffect(() => channel?.activate(), [channel]); ``` The open thread is declared the same way, but by **``**, which already did this before v10 — -`thread.activate()` on mount, `deactivate()` on unmount. `` renders on its own, next to -``, so the channel and the thread are each activated by their own component. +`useEffect(() => thread?.activate(), [thread])` (`activate()` returns its release function; there is no +`deactivate()`). `` renders on its own, next to ``, so the channel and the thread are each +activated by their own component. Neither reconnect handler was ever exported, so there is no symbol to migrate — this is here because the behaviour relocated, and because the *capability* is unchanged: an integrator who relied on @@ -1862,8 +1950,9 @@ the behaviour relocated, and because the *capability* is unchanged: an integrato and on more reconnect paths than before. **If you render `` and `` you get this for free.** If you built your own views on the -contexts, call `channel.activate()` / `thread.activate()` (each balanced with `deactivate()`) or those -surfaces will not be recovered — being active is the one thing recovery cannot infer. +contexts, call `channel.activate()` / `thread.activate()` and call the release function each returns +when the view goes away (there is no `deactivate()`), or those surfaces will not be recovered — being +active is the one thing recovery cannot infer. One thing stays deliberately UI-side: **mark-read after the reload**, on `connection.recovered`. It has to be post-reload so the "is the window at the newest?" check reflects the refreshed window. Read @@ -1892,10 +1981,11 @@ reconnect, or until a query the consumer itself issues records its own error. replacement for the removed `recoverStateOnReconnect` option (§L.1). - **`client.channelManager.recover()`** — re-runs every initialized list's first-page query, non-destructively. Sibling of the destructive `reload()`, which is unchanged. -- **`thread.activate()` / `deactivate()` are now refcounted**, matching `channel.activate()`, so a - thread held by more than one mount stays active until the last holder releases it. `active` is what - recovery filters on, so an unbalanced `deactivate()` now costs a missed reload as well as a missed - auto-read. +- **`thread.activate()` is now refcounted and returns its release function**, matching + `channel.activate()` (neither has a `deactivate()`), so a thread held by more than one mount stays + active until the last holder releases it. `active` is what recovery filters on, so a release that + never runs keeps the thread in every reconnect reload, and one that runs too early costs a missed + reload as well as a missed auto-read. There is deliberately **no** channel-level or thread-level error field. Read the paginator backing whatever you are rendering — the thread's replies when one is open, the channel's messages otherwise: @@ -1955,8 +2045,11 @@ The built-in `NetworkDownIndicator` and ``'s own error screen both do e that never read `error` itself needs no change. **`NetworkDownIndicator` no longer reports load errors at all** — it reports the connection, and -nothing else. It renders "Reconnecting…" while offline and nothing while online. In v9 it also showed -"Error loading messages for this channel…", driven by a failed `watch()`/resync and by a failed thread +nothing else. It renders "Waiting for network..." (`common.waitingForNetwork.text`, a new key) when the +device network is known to be offline, "Reconnecting..." (`common.reconnecting.text`) when the socket is +unhealthy for longer than `offlineNotificationDisplayDelayMs` (default 5000 ms, §R.2), and nothing +otherwise. In v9 it also showed "Error loading messages for this channel…", driven by a failed +`watch()`/resync and by a failed thread reply pagination; that full-width, channel-wide message overstated what is usually one failed page of history, with the messages you already have rendered fine underneath it. @@ -1980,9 +2073,10 @@ yourself after a reconnect, that workaround is now redundant. For threads there was a second hole: the preservation relied on `Thread`'s `failedRepliesMap`, which is only written by `upsertReplyLocally` — whose callers are the thread's own subscriptions and the -offline-DB path keyed on `ThreadManager.threadsById`. Neither covers a thread constructed directly and -never registered, which is the common path. `thread.reload()` now reads the failed replies out of the -reply paginator instead, so it holds for managed and unmanaged threads alike. +offline-DB path that looks threads up in `client.threads`. Neither covers a thread constructed directly +with `new Thread()` and never registered (`thread.activate()` and `client.threads.ensure()` register one +immediately; `client.threads.get(id)` resolves it). `thread.reload()` now reads the failed replies out of +the reply paginator instead, so it holds for managed and unmanaged threads alike. ## L.9 The reconnect refresh now finds messages you never had (bug fix) @@ -2077,7 +2171,7 @@ Every send now writes the message to the offline DB **before** the request, pess `failed`, and overwrites it with `received` on success. A process death anywhere between composing and the server's ack therefore leaves a message that hydrates as failed and retryable, instead of one that silently disappears. This is v9's write-ahead (`Channel.tsx`), which v10 dropped when `sendMessage` -collapsed into `sendMessageWithLocalUpdate`. +moved into `stream-chat`'s message operations (`channel.messageOperations.send`). No schema change and no `dbVersion` bump: `status` already round-trips through the storable's `extraData` blob. @@ -2188,11 +2282,17 @@ and restored together, in one read, because they are one cache row. it, and return it from `getChannelsForQuery`. If you persist nothing for it, the list still works — it falls back to exactly the pre-v10 behaviour. -## N.2 `SqliteClient.dbVersion` 17 → 18 (not breaking; one-time cache loss) +## N.2 `SqliteClient.dbVersion` 16 → 18 (not breaking; one-time cache loss) + +v9 ships `dbVersion` 16; v10 ships 18 (17 was a v10 pre-release). Two schema changes are behind it: + +- every date column that holds server data (channels, messages, members, reads, reactions, polls, + drafts, reminders, locations, users) changed `TEXT` (ISO string) → `INTEGER` (unix nanoseconds, §17.5). + `pendingTasks.createdAt` and `userSyncStatus.lastSyncedAt` stay `TEXT`; +- the RN `channelQueries` table gained a `predefinedFilter TEXT` column for §N.1. -The RN `channelQueries` table gained a `predefinedFilter TEXT` column for §N.1. Schema versioning uses -`PRAGMA user_version` and there are **no incremental migrations**, so the first launch after upgrading -reinitializes the database. +Schema versioning uses `PRAGMA user_version` and there are **no incremental migrations**, so the first +launch after upgrading reinitializes the database. That is a one-time loss of the **offline cache**, not of user data: channels, messages and reads are re-fetched from the server on the next query. Queued offline writes (pending tasks) are in the same @@ -2379,10 +2479,12 @@ value. ### The default -`` writes `pendingUploadsEnabled: enableOfflineSupport` into `client.config`, **only if -nothing is registered there yet**. That's the same default the prop had. If `enableOfflineSupport` -changes later, the SDK-written value follows it. A value anyone else registered is never -overwritten. +With `enableOfflineSupport` on, `` writes `pendingUploadsEnabled: true` into `client.config`, +**only if nothing is registered there yet**. With it off, nothing is written and `stream-chat`'s own +default (`false`) applies. That's the same default the prop had. The write happens once and is +**never taken back**: if `enableOfflineSupport` later turns off, pending uploads stay on, because the +SDK cannot tell its own write from yours. If you toggle offline support at runtime, set +`pendingUploadsEnabled` yourself. A value anyone else registered is never overwritten. ### Turning it on or off for every channel @@ -2567,6 +2669,8 @@ The context value now only changes with the `client` or `isMessageAIGenerated` p | `useChatContext().channel`, `.setActiveChannel` | Keep the open channel in your own navigation state and render ``. Inside a ``, `useChannelContext().channel`. | | `useChatContext().mutedUsers` | `useMutedUsers()` | | `useChatContext().enableOfflineSupport` | `client.offlineDb !== undefined` | +| `useChatContext().isOnline` | `useNetworkConnectionState()?.isOnline === false` for the device, `useSettledWSConnectionHealth()` for the socket (§R.2) | +| `useChatContext().connectionRecovering` | `useSettledWSConnectionHealth()` / `useWSConnectionState()` (§R.2) | `getAppSettings()` works like the one in `stream-chat-react`: the first call fetches the app settings and later calls reuse them. `` already calls it once the user is connected, so it usually @@ -2689,7 +2793,8 @@ Moderation commands (`/mute`, `/unmute`, `/ban`, `/unban`) are sent as typed. Th the mentioned name into the user id (`/mute @Jane Doe` → `/mute @jane-id`) to work around a backend bug with names containing spaces. The backend now takes the target from the message's mentioned users, so the rewrite is gone, and so is the `patchMessageTextCommand` export (breaking). To keep rewriting command -text, add a composition middleware through `client.setMessageComposerSetupFunction`. +text, add a composition middleware through `client.config.setSetupFunction('messageComposer', fn)` +(§13.1; `client.setMessageComposerSetupFunction` is deprecated). ## P.18 Editing a message @@ -2835,6 +2940,371 @@ the message contexts. --- +# Part R — Gaps found in the v10 breaking-change audit + +A v9 → v10 audit of the public surface, the PRs and the sample apps (the v9 SampleApp and ExpoMessaging compiled +against v10) found the changes below that the parts above did not cover. Each one is checked against the v10 source. +Detection greps are in §1; the quick-reference table has rows for the common ones. + +## R.1 Platform floors and packaging (breaking) + +- **`react-native` peer `>=0.76` → `>=0.79`** in `stream-chat-react-native-core`, `stream-chat-react-native` and + `stream-chat-expo`; **`expo` peer `>=52` → `>=53`** in `stream-chat-expo`. `@stream-io/i18n` and `stream-chat` + resolve through package `exports` subpaths, which Metro enables by default only from metro-config 0.82, the version + RN 0.79 pins. Upgrade React Native / Expo first. +- **`stream-chat` declares `engines.node >=22.18.0`** (v9: `>=18`). Yarn 1 enforces `engines` and fails the install + on an older Node; npm and pnpm only warn. Node is not your app's runtime, but it is your install and CI runtime. +- **`StateStore` and `Unsubscribe` are no longer exported from `stream-chat`.** They come from + `@stream-io/state-store`, which the SDK already depends on. Add it to the app if you build your own stores, and make + sure **one copy** resolves: `StateStore` is compared nominally, so a second copy makes your stores unassignable to + `useStateStore`. + + ```diff + - import { StateStore, type Unsubscribe } from 'stream-chat'; + + import { StateStore, type Unsubscribe } from '@stream-io/state-store'; + ``` + +- **`dayjs` is `^1.11.23`** (v9 pinned `1.11.13` exactly) and must resolve to a single copy — see §19. + +## R.2 Connection status: `ChatContext.isOnline` / `connectionRecovering` removed (breaking) + +`useChatContext()` no longer returns `isOnline` or `connectionRecovering` (P.11). Status lives in two `stream-chat` +stores, and the SDK exports hooks over them: + +- `useNetworkConnectionState()` — the **device** network (`client.networkConnection.state`). `isOnline` is + **tri-state**: `undefined` means nobody has reported yet, so test `isOnline === false`, never `!isOnline`. +- `useWSConnectionState()` — the raw **socket** state (`client.wsConnection.state`: `isHealthy`, `lastHealthyAt`, + …). +- `useSettledWSConnectionHealth()` — socket health for UI: a drop counts only once it outlasts + `offlineNotificationDisplayDelayMs` (default 5000 ms; `0` disables the wait) or the device is known offline, a + socket that has not connected yet counts as healthy until that delay runs out, and recovery counts at once. +- `netInfoStatusReporter` — the NetInfo reporter `` installs. Building the client yourself, pass it at + construction to skip the window before `` mounts: + `new StreamChat(apiKey, { client: { networkConnection: { statusReporter: netInfoStatusReporter } } })`. + +```diff +- const { isOnline, connectionRecovering } = useChatContext(); +- if (!isOnline) showBanner(); ++ const isDeviceOffline = useNetworkConnectionState()?.isOnline === false; ++ const isSocketHealthy = useSettledWSConnectionHealth(); ++ if (isDeviceOffline || !isSocketHealthy) showBanner(); +``` + +Related changes: + +- **`connection.changed` is no longer dispatched.** `client.on('connection.changed', …)` compiles and never fires. + Subscribe to the stores above (`client.wsConnection.state.subscribe(…)` outside React). For "recovery finished" use + `connection.recovered` (§L.2). +- **The banner delay is configuration**, not a fixed 5 s inside the client: + `client.config.set({ client: { wsConnection: { offlineNotificationDisplayDelayMs: 3000 } } })`. +- **`` registers `netInfoStatusReporter` on every mount** through + `client.config.set({ client: { networkConnection: { statusReporter } } })`, with no teardown. A custom + `statusReporter` you registered is replaced; register it again after `` mounts. +- **`OutputButtons` / `MessageComposer` `isOnline` prop** is `boolean` (settled socket health), not `boolean | null` + from `ChatContext`. +- **`ChannelListHeaderNetworkDownIndicator`** shows when the device is offline or the settled socket health is false; + **`NotificationAnnouncer`** follows settled socket health and announces "Offline" only when the device is known + offline, "Reconnecting" otherwise; **`NetworkDownIndicator`** — see §L.7. +- **`useLoadingImage`** retries a failed image when the **device** network comes back (it followed `ChatContext`). + +## R.3 Delivery receipts: `readBy` / `deliveredToCount` removed (breaking) + +Removed from `MessageContextValue`, the `` props and `MessageStatus`'s props. `MessageStatus` now takes +booleans and works them out itself when you pass nothing: + +```diff +- ++ +``` + +```diff +- const { readBy, deliveredToCount } = useMessageContext(); +- const isRead = readBy === true || readBy > 1; ++ const { message } = useMessageContext(); ++ const isRead = useIsMessageReadByOthers({ message }); ++ const isDelivered = useIsMessageDeliveredToOthers({ message }); +``` + +`useIsMessageReadByOthers` and `useIsMessageDeliveredToOthers` are new. For the counts, use the user lists v9 counted: +`useMessageReadData({ message }).length` / `useMessageDeliveredData({ message }).length`. All four need a channel in +context. + +## R.4 More context fields and props removed or retyped (breaking, type-level) + +Removed: + +- `ChannelContextValue.maximumMessageLimit` — paginator configuration now, §O.1. +- `ChannelContextValue.uploadAbortControllerRef` — uploads belong to the composer's attachment manager; cancel by + removing the attachment. +- `InputMessageInputContextValue.doFileUploadRequest` (a custom `MessageInputProvider` value) → + `client.config.set({ messageComposer: { attachments: { doUploadRequest } } })`, §13.1. +- `ThreadContextValue.threadInstance` and `ThreadProps.threadInstance` — `thread` is the instance, §10. +- `ChannelPreviewProps.forceUpdate` and `useChannelPreviewData`'s third argument (`forceUpdateOverride`); preview data + is reactive. +- `ChannelListViewProps.forceUpdate` (and `ChannelsContextValue.forceUpdate`, P.20). +- `useCreateMessageContext` params `members`, `readBy`, `deliveredToCount`, `isMessageAIGenerated` (§4, R.3, Q.3). + +Retyped: + +- `LoadingErrorProps.error` (`LoadingErrorIndicator`): `boolean | Error` → `Error`. `error={true}` no longer compiles. +- `InlineLoadingMoreIndicator` / `InlineLoadingMoreRecentIndicator` take `loadingMore` / `loadingMoreRecent` as + props; they read context in v9. Rendered without the prop, they render nothing. +- `ChannelListViewProps`: `channels`, `channelListInitialized`, `hasNextPage`, `loadingChannels`, `loadingNextPage` + and `refreshing` are required (`error` stays optional); new optional `paginator`. +- `CommandSuggestionItem` (`stream-chat`'s `CommandSuggestion`): `args`, `description`, `name` and `set` are required + `string`s; `created_at` / `updated_at` are `TimestampNS`. +- `giphyVersion` (`Giphy`, `GiphyImage`, `OverlayProvider`, …) is typed `keyof Images`. `stream-chat`'s + `GiphyVersions` is now an alias of it, with the same seven values. +- `URLPreview` / `URLPreviewCompact` / `Giphy` / `GiphyImage` `attachment` is `stream-chat` v10's `Attachment` + (metadata under `custom`, §17.1). +- `TypingIndicatorContainer`: `thread` → `threadId` (§2.1). +- `AIStates` is a `const` object with literal values, so `[AIStates.Thinking, AIStates.Generating].includes(aiState)` + no longer compiles. Compare with `===`: + + ```diff + - const busy = [AIStates.Thinking, AIStates.Generating].includes(aiState); + + const busy = aiState === AIStates.Thinking || aiState === AIStates.Generating; + ``` + +- `ImageGalleryStateStore` `assets[]`: `created_at` is `Date | undefined` (converted from nanoseconds), `channelId` + is `string`, `user` is `UserResponse`. Guard `created_at` in a custom gallery header or footer. + +## R.5 `` and `` behaviour changes (behavioural) + +``: + +- **`initializeOnMount={false}` still queries** when the channel's paginator is empty: it skips `watch()` but runs + `channel.messagePaginator.reload()`, a non-watching channel query. Seed the paginator yourself before mounting if no + request may go out. +- **A provisional channel is not queried on mount.** A members-only channel with no id that is already initialized + is created on its first send, not when `` opens it. Call `channel.watch()` yourself if you relied on the + open creating it. +- **`` may render a different instance than you passed.** When `channel.supersededBy` is set (a + members-only channel whose real cid another instance already holds), `` renders the successor, unless the + successor is open elsewhere and this composer has content. Inside ``, read the channel from + `useChannelContext()`, not from your own prop. +- **Sending no longer removes `type: 'error'` messages.** v9 ran `channel.state.filterErrorMessages()` on every send; + remove an error message you want gone with `useMessageOperations().removeMessage(message)`. +- **`client.options.isLocalUnreadCountEnabled` is not read.** The local unread count with read events off is + `client.config.set({ channel: { readEvents: { localUnreadCountEnabled: true } } })`. +- **`UnreadMessagesNotification` dismisses through its `markRead` prop.** A custom override that wraps the built-in + must forward `markRead`, or dismissing stops marking the channel read. +- **The poll button can show before the server's `polls` flag arrives.** It follows + `messageComposer.config.polls.enabled`, which defaults to `true` and turns off once the server config says no. + Hide it with `client.config.set({ messageComposer: { polls: { enabled: false } } })`. +- **The input's `maxLength` follows `messageComposer.config.text.maxLengthOnSend`**, so a smaller registered value + caps the input too (the channel type's `max_message_length` stays the upper bound). +- **Deleting a failed *edit* deletes the message on the server.** v9 removed every `failed` message locally only; v10 + does that only for a failed *send* (a failed message with no `message_text_updated_at`). +- **Initial page size is 25 unless you register one** through `client.config`: §16.1. + +Message rows and previews: + +- **`MessageFooter` shows the sender name when `memberCount !== 2`** (v9: more than two *loaded* members), so it now + also shows when the count is 0 or 1. +- **The channel preview's typing indicator ignores typing in threads** (events with a `parent_id`). + +``: + +- **A failed first page replaces the thread UI.** When the reply paginator has a `lastQueryError` and no items, + `` renders `LoadingErrorIndicator` (with a retry) instead of the list and composer. Override + `LoadingErrorIndicator` through `WithComponents` to change it. + +## R.6 Channel list (breaking/behavioural) + +- **`options.offset` is ignored and `options.limit` is the page size.** The paginator owns the offset; the rest of + `options` becomes the request options. +- **`filters` are `stream-chat` v10's generated filter types**: custom fields are `'custom.'` keys, and each + key takes one operator. + + ```diff + - + + + ``` + +- **Channels can leave a list on events.** Every routed event (`message.new`, `member.updated`, + `channel.updated`, …) re-checks the list's filters, so a channel that stops matching drops out until the next query. +- **`loadNextPage()` is a no-op before the first page has loaded** (§18.3). +- Sort is `SortParamRequest[]`, and an empty sort means the backend default (§17.5, §N.4). + +## R.7 `stream-chat` state you may read directly (breaking — `stream-chat`) + +v9 → v10: + +- `channel.state.messages` → `useStateStore(channel.messagePaginator.state, (s) => ({ items: s.items }))`. +- `channel.state.latestMessages` / `channel.lastMessage()` → `channel.messagePaginator.lastMessage` (reactive through + `messagePaginator.aggregateState`). +- `channel.state.threads[id]` → `client.threads.get(id)?.messagePaginator`. +- `channel.state.findMessage(id)` → `channel.messagePaginator.getItem(id)`, or `client.messageStore` for any loaded + message. +- `channel.state.loadMessageIntoState(id)` → `channel.messagePaginator.jumpToMessage(id)`. +- `channel.state.isUpToDate` → `channel.messagePaginator.isActiveIntervalAtHead`. +- `channel.state.pinnedMessages` → `channel.pinnedMessagesPaginator`. +- `channel.state.mutedUsers` → `client.mutedUsersStore` (`useMutedUsers()`). +- `client.activeChannels` → `client.channelManager.get(cid)` / `client.channelManager.values()`. Channels are not + evicted automatically; `client.channelManager.releaseUnusedChannels()` releases the ones nothing uses. +- `client.hydrateActiveChannels(…)` → `client.hydrateChannels(…)`. +- `client.threads.state` `threads` / `pagination` / `ready` and `client.threads.loadNextPage()` → + `client.threads.paginator` (`state.items`, `isLoading`, `lastQueryError`; `toTail()`). `client.threads.threadsById` + → `client.threads.get(id)`. +- `thread.state.replies` / `pagination`, `thread.loadNextPage()` / `loadPrevPage()` / `queryReplies()` → + `thread.messagePaginator` (`state.items`, `toTail()` for older, `toHead()` for newer). + +```diff +- const { replies } = useStateStore(thread.state, (s) => ({ replies: s.replies })); ++ const { replies } = useStateStore(thread.messagePaginator.state, (s) => ({ replies: s.items })); +``` + +Two request behaviours: + +- **`channel.query(…)` no longer updates the message list.** It returns the page; only the latest-page paths + (`watch()`, `create()`) seed the list. Page with `channel.messagePaginator.toTail()` / `toHead()` instead of + `channel.query({ messages: { id_lt } })`. +- **A request with `watch` or `presence` waits for a WebSocket connection id** — `channel.watch()`, `queryChannels*`, + `queryUsers({ payload: { presence } })`. v9 silently downgraded it to a non-watching request. With no socket open + or opening it throws `No connection id is available…`, so call `connectUser()` (or `openConnection()` after a + `closeConnection()`) first. + +## R.8 `stream-chat` client calls RN apps make (breaking — `stream-chat`) + +The ones the v9 sample apps hit. The full list is `stream-chat`'s own (§17.5). + +- **Push devices** — the device is no longer sent on connect, and the user comes from the connection: + + ```diff + - await client.addDevice(token, 'firebase', userId, 'my-provider'); + + await client.createDevice({ id: token, push_provider: 'firebase', push_provider_name: 'my-provider' }); + - const { devices } = await client.getDevices(userId); + + const { devices } = await client.listDevices(); + - await client.removeDevice(token, userId); + + await client.deleteDevice({ id: token }); + ``` + + `client.setLocalDevice` and the `device` client option are removed. + +- **`client.queryChannels(request)` is the raw call**: it returns the response, creates no `Channel` instances and + stores nothing. v9's behaviour is `client.queryChannelsAndHydrate({ filter_conditions, sort, ...options })`, which + returns `Channel[]`. +- **Creating and updating channels** — custom fields under `custom`, members as objects: + + ```diff + - client.channel('messaging', id, { name, members: [userA, userB] }); + + client.channel('messaging', id, { custom: { name }, members: [{ user_id: userA }, { user_id: userB }] }); + - await channel.update({ name }); + + await channel.updatePartial({ set: { name } }); // or channel.update({ data: { custom: { name } } }) + ``` + +- **Request objects** — `channel.sendMessage({ message, ...options })`, `client.search({ payload })`, + `client.queryUsers({ payload })`, `channel.queryMembers({ payload })`. The `payload` carries `filter_conditions`, + `sort` (an array), `limit`, `offset`, … +- **Moderation moved to `client.moderation`** — `client.muteUser(id)` → + `client.moderation.mute({ target_ids: [id] })`, `client.banUser(id)` → + `client.moderation.ban({ target_user_id: id })`, `flagMessage` / `flagUser` → `client.moderation.flagMessage` / + `flagUser`. +- **Reminders** — `client.reminders.upsertReminder({ message_id }, { remind_at: date })` (`remind_at` is a `Date`; + omit it to clear), sort as `[{ field: 'remind_at', direction: 1 }]`, `ReminderResponse` → `ReminderResponseData`. +- **Live location** — `channel.stopLiveLocationSharing({ message_id })` (it took the location object), + `SharedLocationResponse` → `SharedLocationResponseData`. Call `LiveLocationManager.dispose()` when you are done with + a manager you created. +- **Custom events** — `channel.sendEvent({ type, ...fields })` → + `channel.sendEvent({ event: { type, custom: {…} } })`. +- **`connectUser` takes a `ClientUser`**: your own user fields go under `custom`. A `UserResponse` fixture no longer + type-checks (it requires server-owned fields such as `created_at`); type it as `ClientUser`. +- **Type renames the sample apps hit**: `MessageResponseBase` → `MessageResponse`; search results are + `SearchResultMessage` and `result.message` is optional; `DeliveredMessageConfirmation` → `DeliveredMessagePayload`; + `DraftFilters` / `DraftSort` / `Pager` → `QueryDraftsRequest`. +- **Same name, different meaning** — some names still compile but mean something else now, e.g. `User`, `Reaction`, + `PollResponse` and `ChannelConfig`. Check what an annotation resolves to before keeping it. +- **Errors** — `ErrorFromResponse` → `StreamAPIError`. A request answered with HTTP 429 or `TOKEN_EXPIRED` is + retried up to 10 times with backoff, then throws `RetryError`. +- **Logging** — the `logger` option and `client.logger` are replaced by the process-wide `chatLoggerSystem`, and it + is **on by default**: a console sink at `info`. Quiet it with `chatLoggerSystem.configureLoggers({ default: { level: + 'warn' } })`. +- **`message.ai_generated` → `message.custom?.ai_generated`** — the usual body of `isMessageAIGenerated` (Q.3): + + ```diff + - !!message.ai_generated}> + + !!message.custom?.ai_generated}> + ``` + +## R.9 Offline DB contract (breaking for custom offline code) + +Only for a custom `AbstractOfflineDB`, a subclass of `OfflineDB`, or direct `OfflineStoreApis` / SQLite access. + +- **`getChannelsForQuery` input** (`DBGetChannelsForQueryType`): `{ userId, filters, options, sort }` → + `{ userId, options }`, with the filters in `options.filter_conditions` and the sort in `options.sort`. The return + type changed too (§N.1). +- **`OfflineDB` lost `handleOfflineFailedUpdateMessagePendingTask` and + `mergeFailedMessageUpdateIntoPendingSendMessage`.** New: `handleUpdateMessagePendingTask`, `hardDeleteMessages`, + `queriesBehindChannelGuard`, `upsertMessageBehindChannelGuard`, plus the concrete helpers in §M.6. Port overrides + of the removed hooks to the new pending-task handler. +- **`deleteMessagesForChannel({ truncated_at })`** takes a `TimestampNS` (v9: an ISO string), defaulting to `nowNs()`. +- **`insertReaction` / `updateReaction` skip a reaction whose message is not cached** (v9's write failed on the + foreign key and aborted the batch). +- **`OfflineStoreApis` retyped** to `stream-chat` v10 types: sort is `SortParamRequest[]`, channel rows are + `ChannelStateResponseFields` (was `ChannelAPIResponse`), polls `PollResponseData`, locations + `SharedLocationResponseData`, app settings `GetApplicationResponse`. `currentUserId` → `userId` is P.6. +- **`SqliteClient.logger`** is a `stream-chat` `Sink` (was `Logger`); the call shape `(level, message, extra)` is + unchanged. +- **v9's queued pending tasks are dropped** by the reinit that `dbVersion` 16 → 18 forces (§N.2). + +## R.10 i18n gaps + +Not covered by §19 or `i18n-v10-migration.md`: + +- **`Streami18n.geti18Instance()` is removed** → the `i18n.i18nInstance` property. `getAvailableLanguages()` and + `getTranslations()` (see the i18n guide) and `addOnTFunctionOverrideListener` are removed too; subscribe to + `i18n.state` instead of the listener. +- **`TranslationContextValue.userLanguage` is `string`** (v9: `stream-chat`'s `TranslationLanguages`), and + `DEFAULT_USER_LANGUAGE` is `'en'`. Narrow it before passing it to `stream-chat`'s translation API, whose `language` + is a closed union: `client.translateMessage({ id }, { language })`. +- **`isDayOrMoment`** is re-exported from `@stream-io/i18n`: `(value) => value is DateTimeLike` (v9: + `output is Dayjs | Moment`). +- **Four app-callable formatter keys left the catalog** and are not in the key map: `duration/Location end at`, + `duration/Remind Me`, `timestamp/ReminderNotification`, `timestamp/MessageEditedTimestamp`. The SDK never rendered + them, but v9 shipped them in `en.json`, so apps called them. Register the expression yourself, for every language + you register (English included): + + ```ts + import type { LooseTranslationDictionary } from 'stream-chat-react-native'; + + const appFormatterExpressions: LooseTranslationDictionary = { + 'duration.locationEndAt': '{{ milliseconds | durationFormatter(withSuffix: false) }}', + 'duration.remindMe': '{{ milliseconds | durationFormatter(withSuffix: true) }}', + 'timestamp.reminderNotification': '{{ timestamp | timestampFormatter(calendar: true) }}', + }; + streami18n.registerTranslation('en', appFormatterExpressions); + ``` + + `examples/SampleApp/src/i18n/appKeys.ts` does this. `duration/Message reminder` is still bundled, as + `duration.messageReminder`. +- **`t()` only accepts catalog keys.** `TranslationContextValue.t` is `StreamTFunction`, so an app key needs + `asDynamicKey('…')` (`timestamp.*` / `duration.*` keys are accepted as they are). See the i18n guide's "Custom keys + of your own". +- **`SCROLL_TO_BOTTOM_ACCESSIBILITY_LABEL_KEY` / `SCROLL_TO_BOTTOM_WITH_COUNT_ACCESSIBILITY_LABEL_KEY` hold the dotted + keys** (`messageList.scrollToBottom.accessibilityLabel`, `messageList.scrollToBottom.withCount.accessibilityLabel`), + not `a11y/…`. + +## R.11 Deep imports (low) + +Only affects imports from `stream-chat-react-native-core/src/…` or `…/lib/…`. None of these was exported from the +package root in v9. Deleted, with no public replacement: + +- `ChannelsStateProvider`, `useChannelsStateContext`, `useChannelState` (`contexts/channelsStateContext`) +- `ActiveChannelsRefContext` (`contexts/activeChannelsRefContext`) +- `useChannelDataState` (`components/Channel/hooks/useChannelDataState`) +- `ChannelUnreadStateStore` (`state-store/channel-unread-state`) — §7 +- `MessagePreviousAndNextMessageStore` (`state-store/message-list-prev-next-state`) +- `ChannelList/hooks/utils` — the pin / archive sort helpers (`isChannelPinned`, `isChannelArchived`, …) +- `useCreateMessageComposer` (`contexts/messageInputContext/hooks`) +- the `utils/i18n/*` modules (`getDateString`, `predefinedFormatters`, `calendarFormats`, `types`) — their functions + are root exports now (§19) + +Still there but still deep-only, with changed signatures: `filterTypingUsers` (§2.1) and `MessageComposerContext` / +`useMessageComposerContext` (§10.2). + +--- + # Part I — i18n ## 19. English-only bundle, dotted keys, shared runtime @@ -2849,11 +3319,12 @@ agent grepping this file finds i18n at all, and knows the three shapes of change error. 2. **English is the only bundled language.** The 12 non-English dictionaries and the `*Translations` exports are gone. An integrator supplies their own, additively; a key they omit still renders English. -3. **The runtime moved to `@stream-io/i18n`**, shared with the React SDK. Imports are unchanged — everything - is still exported from `stream-chat-react-native` / `stream-chat-expo`, and `Streami18n` keeps its name - — but reactivity is a `StateStore` rather than listeners, `setLanguage` returns `void`, - `getTranslators()` is now `init()`, and a few date-helper parameters were renamed to the one name both - SDKs use. Nothing is kept as a deprecated alias. See the quick-reference rows above. +3. **The runtime moved to `@stream-io/i18n`**, shared with the React SDK. `Streami18n` is still exported from + `stream-chat-react-native` / `stream-chat-expo` and keeps its name; the date helpers (`getDateString`, + `predefinedFormatters`, …), deep imports in v9, are root exports now — but reactivity is a `StateStore` + rather than listeners, `setLanguage` returns `void`, `getTranslators()` is now `init()`, and a few + date-helper parameters were renamed to the one name both SDKs use. Nothing is kept as a deprecated alias. + See the quick-reference rows above. Two dependency rules that produce silent breakage rather than errors: diff --git a/ai-docs/i18n-v10-migration.md b/ai-docs/i18n-v10-migration.md index f4dae54de4..4e82a46651 100644 --- a/ai-docs/i18n-v10-migration.md +++ b/ai-docs/i18n-v10-migration.md @@ -373,6 +373,10 @@ timestampFormatter(...) }}` breaks date rendering outright. Pass `--all` for the Every old key maps to exactly one new key. The full table (389 rows) is [`i18n-v10-key-map.json`](./i18n-v10-key-map.json). Keys absent from it were dead in v9 and have been removed. +The exception is four formatter expressions the SDK never rendered but shipped in `en.json`, so apps called them +directly: `duration/Location end at`, `duration/Remind Me`, `timestamp/ReminderNotification` and +`timestamp/MessageEditedTimestamp`. They are gone with no replacement key; register the expressions yourself (see +§R.10 of [`ai-migration-v9-to-v10.md`](./ai-migration-v9-to-v10.md)). Shape — `key` is the replacement, `prose` is `false` for the formatter expressions, and `plural` marks the keys that live in the catalog as `_one` / `_other`: @@ -422,9 +426,11 @@ the `timestamp/` keys it replaces. `Streami18n` is no longer implemented here. The class, the formatters and the whole date layer live in `@stream-io/i18n`, shared with the React SDK, and this package exports a thin subclass that injects its own -bundled copy. **Your imports do not change** — `Streami18n`, `getDateString`, `predefinedFormatters` and every -type are still exported from `stream-chat-react-native` / `stream-chat-expo`, and the class keeps its name. -What changes is behaviour and a handful of signatures. +bundled copy. **`Streami18n`'s import does not change** — it and every type are still exported from +`stream-chat-react-native` / `stream-chat-expo`, and the class keeps its name. `getDateString`, +`getCalendarDateStringForA11y` (v9's `getDateStringForA11y`) and `predefinedFormatters` were never root exports in +v9 — only deep imports from `utils/i18n/*`, which are deleted. v10 exports them from the package root, so import +them from there. What changes is behaviour and a handful of signatures. Why: both UI SDKs carried ~1,300 lines of near-duplicate runtime that had drifted, each reverse-mapping `stream-chat`'s English notification prose against its own hand-maintained table. A fix landed in one SDK and diff --git a/ai-docs/v10-breaking-changes.md b/ai-docs/v10-breaking-changes.md new file mode 100644 index 0000000000..74e01abd70 --- /dev/null +++ b/ai-docs/v10-breaking-changes.md @@ -0,0 +1,583 @@ +# stream-chat-react-native v10 — breaking changes + +The single list of every breaking change between v9 and v10 of `stream-chat-react-native-core`, +`stream-chat-react-native` and `stream-chat-expo`. It is the source of truth for the v10 migration guide and the docs +update. Each entry says what changed and how to migrate in one line, and links to the detailed write-up: + +- **`§…`** → [`ai-migration-v9-to-v10.md`](./ai-migration-v9-to-v10.md), the agent migration ledger (Before/After code, + detection greps). `§R.x` sections were added by the audit that produced this file. +- **i18n** → [`i18n-v10-migration.md`](./i18n-v10-migration.md) and [`i18n-v10-key-map.json`](./i18n-v10-key-map.json). +- **LLC** → `stream-chat` v10's own list: `stream-chat-js` `ai-docs/v10-breaking-changes.md` (branch + `claude/sdk-v10-breaking-changes-f64baf`) and the `v9-to-v10-migration-guide-*.md` files on `release-v10`. +- **#NNNN** → the `GetStream/stream-chat-react-native` pull request that made the change. + +Docs impact (which getstream.io pages change) is in [`v10-docs-impact.md`](./v10-docs-impact.md). + +## Scope and method + +- **Baseline:** `develop` @ `42662cdb1` (v9.10.x, `stream-chat` 9.54.0). **Target:** `V10` @ `c994ffd00` + (`stream-chat` 10.0.0-rc.22). The two trees were compared tip to tip; `V10` squash-merges `develop`, so ancestry + diffs are not reliable. The three `develop` commits not yet on `V10` (#3862, #3863, #3864) change no public API. +- **Public API** = everything reachable from `package/src/index.ts`, the `ai-components` entry, the wrappers' + `src/index.js`, `DEFAULT_COMPONENTS`, the theme, translations and `package.json` peer/dependency ranges. +- **How it was collected:** + 1. a TypeScript-compiler snapshot of both public surfaces (≈1,285 exports, every prop / context field / class + member flattened, plus the 207 `stream-chat` types those signatures reference), diffed mechanically; + 2. the v9 `SampleApp` and `ExpoMessaging` sources type-checked against v10 (0 errors on v9, 144 on v10 — every error + cluster maps to an entry below); + 3. a review of all 40 PRs merged into `V10` for behaviour that types cannot show; + 4. the `examples/` diff (the migration the team already did on its own apps), the deleted/rewritten tests, and a + cross-check against the `stream-chat` and `stream-chat-react` v10 breaking-change lists. +- Not breaking (checked): theme shape (`Theme` is identical), `DEFAULT_COMPONENTS` slot names and the `icons` map, + native modules, podspecs, the Expo config plugin, `registerNativeHandlers` and the native handler interfaces. + +--- + +## 1. Dependencies and platform + +- **`stream-chat` ^9 → ^10** (currently `^10.0.0-rc.22`). Every `stream-chat` change in [§12](#12-stream-chat-v10-changes-an-rn-integrator-hits) + applies. Apps that depend on `stream-chat` directly must move to the same major. — §17 +- **`react-native` peer `>=0.76` → `>=0.79`** (core, native and expo packages). `@stream-io/i18n` and `stream-chat` + resolve through package `exports`, which needs Metro ≥0.82. — §R.1 · #3777 +- **`expo` peer `>=52` → `>=53`** (`stream-chat-expo`). — §R.1 · #3777 +- **`stream-chat` requires Node `>=22.18.0`** at install time (`engines`). Yarn 1 refuses the install on older Node. — + §R.1 · LLC +- **New dependency `@stream-io/i18n`**; `i18next` is no longer a direct dependency; `dayjs` `1.11.13` (pinned) → + `^1.11.23` and must resolve to a single copy. — §19 · i18n · #3777 #3801 +- **`StateStore` / `Unsubscribe` are no longer exported by `stream-chat`.** Apps that build their own stores import + them from `@stream-io/state-store` (add it to the app; exactly one copy may resolve). — §R.1 +- **Offline DB `SqliteClient.dbVersion` 16 → 18.** The first launch after the upgrade wipes and rebuilds the SQLite + cache, including v9's queued pending tasks (unsent offline writes). — §N.2 · §R.9 · #3794 #3804 + +## 2. Removed exports + +Everything below was exported from the package root in v9 and is gone in v10. + +**Contexts** + +- `TypingContext`, `TypingProvider`, `useTypingContext`, `TypingContextValue` → + `useStateStore(channel.state, (s) => ({ typing: s.typing }))`. — §2 · #3736 +- `PaginatedMessageListContext`, `PaginatedMessageListProvider`, `usePaginatedMessageListContext`, + `PaginatedMessageListContextValue` → `useMessagePaginator()` / `channel.messagePaginator`. — §3 · #3736 +- `DebugContext`, `DebugContextProvider`, `useDebugContext`, `DebugContextValue`, `DebugDataType` → nothing (fed the + Flipper plugin). — P.1 + +**Hooks** + +- `useCreateTypingContext`, `useCreatePaginatedMessageListContext` → nothing; their contexts are gone. — §15 +- `useTargetedMessage` → `messagePaginator.jumpToMessage(id, { focusReason: 'jump-to-message', … })`; read with the + new `useIsTargetedMessage(id)`. — §6 +- `useChannelUpdated` → nothing; `client.channelManager` handles `channel.updated`. — §18.2 · #3770 +- `useIsOnline` → `useConnectionLifecycle` (returns nothing); status from `useNetworkConnectionState()` / + `useWSConnectionState()` / `useSettledWSConnectionHealth()`. — P.2 · §R.2 · #3808 +- `useAppSettings` → `useChatContext().getAppSettings()`. — P.12 +- `useClientMutedUsers` → `useMutedUsers()`. — P.12 +- `useSelectedChannelState` → `useStateStore(channel.state, selector)`. — P.1 + +**Utilities** + +- `getLastReceivedMessage` → `messages.find((m) => m.status !== 'failed')`. — P.1 +- `reduceMessagesToString`, `findInMessagesById`, `findInMessagesByDate` → `channel.messagePaginator.getItem(id)` / + `jumpToMessage(id)`. — P.1 +- `patchMessageTextCommand` → nothing (the composer submits through `stream-chat`). — P.17 +- `createAttachmentsCompositionMiddleware`, `createDraftAttachmentsCompositionMiddleware`, + `localAttachmentToAttachment` → `stream-chat`'s own `createAttachmentsCompositionMiddleware` (installed by default). + — §17.6 · #3821 + +**Types** + +- `ThreadType` → a `Thread` instance (`client.threads.ensure({ channel, parentMessage })` builds one). — §10.1 · #3861 +- `ChannelUnreadState` → `channel.messagePaginator.unreadStateSnapshot` (`UnreadSnapshotState`). — §7 · P.9 + +**Translations** + +- `enTranslations`, `esTranslations`, `frTranslations`, `heTranslations`, `hiTranslations`, `itTranslations`, + `jaTranslations`, `koTranslations`, `nlTranslations`, `ptBRTranslations`, `ruTranslations`, `trTranslations` → + English is the only bundled language; register your own dictionaries with dotted keys. — §19 · i18n · #3776 + +## 3. Component props + +Each removal or retype below also applies to the component's exported `XProps` / `XPropsWithContext` types and to +its slot type in `ComponentOverrides`. Every component and hook that accepts `t` now types it as `StreamTFunction` +(see [§10](#10-i18n-and-translations)). + +### `` + +- No props removed. Behaviour: `` no longer sets `client.recoverStateOnReconnect = false` / + `client.preventThreadCleanup = true` (recovery is `client.connectionRecovery`, on by default). — §L.1 · #3785 +- `` writes `persistUserOnConnectionFailure: enableOfflineSupport` into `client.config` on mount, overwriting a + value you registered. — §17.7 · #3855 +- `` registers the NetInfo `statusReporter` on every mount, replacing a custom one you set on the client. — + §R.2 · #3808 + +### `` + +Removed props: + +- `messages`, `loadingMore`, `loadingMoreRecent`, `threadMessages`, `setThreadMessages` → state lives in the + paginators. — §13 · #3736 +- `messageId` → jump with `channel.messagePaginator.jumpToMessage(id, …)` (thread: `thread.messagePaginator`) and pass + `initialScrollToFirstUnreadMessage={!messageId}`. — §8 · #3820 +- `doMarkReadRequest`, `doSendMessageRequest`, `doUpdateMessageRequest`, `preSendMessageRequest` → + `client.config.set({ channel: { requestHandlers: { markReadRequest, sendMessageRequest, retrySendMessageRequest, + updateMessageRequest } } })`. — §13.1 · P.17 · #3784 #3821 +- `doFileUploadRequest` → `client.config.set({ messageComposer: { attachments: { doUploadRequest, customCdn: true } } })`. + — §13.1 · #3784 +- `stateUpdateThrottleInterval`, `newMessageStateUpdateThrottleInterval` → + `client.config.set({ channel: { messagePaginator: { stateThrottleMs } } })`. — §13.1 · #3784 +- `maximumMessageLimit` → `client.config.set({ channel: { messagePaginator: { maxLoadedItems } } })`. — §O.1 · #3815 +- `allowSendBeforeAttachmentsUpload`, `enableOfflineSupport` → + `client.config.set({ messageComposer: { attachments: { pendingUploadsEnabled } } })`; offline support is set on + ``. — §O.3 · #3821 +- `isMessageAIGenerated` → `` only. — Q.3 · #3852 +- `isOnline` → nothing (was ignored). — P.5 + +Retyped props: + +- `thread`: `LocalMessage | ThreadType | null` → `Thread | null`. — §10.1 · #3861 +- `channel`, `customMessageSwipeAction`: the `Channel` type is `stream-chat` v10's. — §17.5 +- `t`: i18next `TFunction` → `StreamTFunction`. — §R.10 + +### `` + +- Removed `onAddedToChannel`, `onRemovedFromChannel`, `onChannelDeleted`, `onChannelHidden`, `onChannelVisible`, + `onChannelUpdated`, `onChannelTruncated`, `onChannelMemberUpdated`, `onNewMessage`, `onNewMessageNotification` → + membership and order follow `filters` / `sort`; global behaviour via `client.channelManager.addEventHandler(...)` / + `client.on(...)`. — §18 · #3770 +- `sort`: `ChannelSort` object → `SortParamRequest[]` (`[{ field: 'last_message_at', direction: -1 }]`); an empty + `sort` now means the backend default. — §17.5 · §N.4 · #3749 #3783 +- `filters`: generated filter type; custom fields are `'custom.'` keys, one operator per key. — §R.6 · LLC +- `queryChannelsOverride`: `(filters, sort, options, stateOptions) => Promise` → + `(queryShape) => Promise<{ items: Channel[] }>`; use `client.queryChannelsAndHydrate(request, stateOptions)` inside. + — §18.1 · #3770 +- `options.offset` is ignored; `options.limit` becomes the page size. — §R.6 + +### ``, `` + +- `ChannelListView`: `forceUpdate` removed; `channels`, `channelListInitialized`, `hasNextPage`, `loadingChannels`, + `loadingNextPage`, `refreshing` are now required; `loadNextPage` is `() => Promise`; new `paginator` prop. — + §R.4 · P.20 +- `ChannelPreview`: `forceUpdate` removed (preview data is reactive). — §R.4 · #3740 + +### `` and message components + +- `Message`: removed `members`, `readBy`, `deliveredToCount`, `isMessageAIGenerated`, `openThread`, `chatContext`. — + §R.3 · §R.4 · Q.3 · P.13 · §10.1 +- `Message`: `onThreadSelect` `(message, targetedMessageId?)` → `(thread: Thread)`; `updateMessage` takes one argument. + — §10.1 · §9 +- `MessageStatus`: `readBy` / `deliveredToCount` (counts) → `read` / `delivered` (booleans, self-derived by default). — + §R.3 · #3823 +- `MessageFooter`, `MessageItemView`: `members` removed. — §4 · §R.4 · #3779 +- `MessageContent`: `isMessageAIGenerated` removed. — Q.3 +- `Giphy`, `GiphyImage`, `URLPreview`, `URLPreviewCompact`: `attachment` is `stream-chat`'s `Attachment` (custom + metadata under `attachment.custom`). — §17.1 · §R.4 + +### `` / `` + +- Removed: `channelUnreadStateStore`, `setChannelUnreadState`, `targetedMessage`, `setTargetedMessage`, + `highlightedMessageId` (FlashList), `loadMore`, `loadMoreRecent`, `hasMore` (`MessageList`), `threadHasMore`, + `loadMoreThread`, `loadMoreRecentThread`, `loadChannelAroundMessage`, `loading`, `reloadChannel`, `threadInstance`, + `maximumMessageLimit`, `allowSendBeforeAttachmentsUpload`, `scrollToFirstUnreadThreshold`, + `shouldShowUnreadUnderlay`, `FlatList` (FlashList). The lists read their own paginator. — §12 · P.5 · P.7 · §O.1 +- `thread`: kept, retyped `LocalMessage | null` → `Thread | null`. — §12 +- `onThreadSelect`: `(message) => void` → `(thread: Thread) => void`. — §10.1 +- Older-end loading component (`FooterComponent` on `MessageList`, `HeaderComponent` on `MessageFlashList`) now + receives `{ loadingMore?: boolean }`. The newer-end one gets no props and has no SDK source for `loadingMoreRecent`. + — §12.1 +- New: `pendingUploadsEnabled`. — §O.3 +- `ScrollToBottomButton`: `unreadCount` removed (self-derived from `channel.state`). — §7 +- `TypingIndicatorContainer`: `thread` → `threadId`. — §2.1 +- `InlineLoadingMoreIndicator` / `InlineLoadingMoreRecentIndicator`: now take `loadingMore` / `loadingMoreRecent` + props and render nothing without them. — §R.4 +- `UnreadMessagesNotification`: `channelUnreadStateStore` / `unreadCount` removed; dismiss calls the new `markRead` + prop (a wrapper must forward it). — §7 · §R.5 + +### `` and thread components + +- `Thread`: removed `closeThread`, `loadMoreThread`, `reloadThread`, `threadInstance`; `thread` is `Thread | null`; + `closeThreadOnDismount` is still typed but no longer read. — §10.2 · §R.4 +- `ThreadFooterComponent`: `thread` prop removed (reads the thread from context). — §10.2 +- `ThreadList`: `onThreadSelect(thread: ThreadType, channel)` → `onThreadSelect(thread: Thread)` (`thread.channel`). — + §10.1 + +### Composer + +- `MessageComposer`, `OutputButtons`: `isOnline` is `boolean` (settled socket health), not `boolean | null` from + `ChatContext`. — §R.2 +- `AutoCompleteInput`, `InputView`: `t` is `StreamTFunction`. — §R.10 +- `CommandSuggestionItem`: `args`, `description`, `name`, `set` are required strings; `created_at` / `updated_at` are + `TimestampNS`. — §R.4 + +### Indicators, polls, misc + +- `LoadingErrorIndicator`: `error` `boolean | Error` → `Error`. — §R.4 · #3785 +- Poll components: `PollOption` → `PollOptionResponseData`, `PollAnswer` / `PollVote` → `PollVoteResponseData` + (`ShowAllVotesButton`, `PollResultsItem`, `PollOptionFullResults(Content)`, `PollAnswerListItem`, + `PollAnswersList(Content)`); `CreatePollOption.error` is a `PollComposerValidationError`. — §17.5 · §19 + +## 4. Contexts + +Each field change applies to the context's `XContextValue` type (and to `InputMessageInputContextValue` for the +message input). + +### Removed contexts + +`TypingContext`, `PaginatedMessageListContext`, `DebugContext` — see [§2](#2-removed-exports). + +### `ChannelContext` (`useChannelContext()`) + +- Removed `members`, `read`, `watchers`, `watcherCount` → `useStateStore(channel.state, selector)`. — §4 +- Removed `markRead` → `useMarkRead(channel)`. — §5 +- Removed `targetedMessage`, `setTargetedMessage`, `highlightedMessageId` → `messagePaginator.messageFocusSignal`, + `useIsTargetedMessage(id)`. — §6 +- Removed `channelUnreadStateStore`, `setChannelUnreadState` → `channel.messagePaginator.unreadStateSnapshot`. — §7 +- Removed `loadChannelAroundMessage`, `loadChannelAtFirstUnreadMessage`, `reloadChannel`, `loading` → paginator + `jumpToMessage` / `jumpToTheFirstUnreadMessage` / `jumpToTheLatestMessage`, `state.isLoading`. — §8 +- Removed `error` → `(thread ?? channel).messagePaginator.state.lastQueryError`. — §L.7 +- Removed `isChannelActive`, `scrollToFirstUnreadThreshold`. — P.4 +- Removed `maximumMessageLimit`, `uploadAbortControllerRef`. — §R.4 + +### `ChatContext` (`useChatContext()`) + +- Now only `{ client, getAppSettings, isMessageAIGenerated }` (`isMessageAIGenerated` is required, defaults to + `() => false`). +- Removed `isOnline`, `connectionRecovering` → `useNetworkConnectionState()`, `useWSConnectionState()`, + `useSettledWSConnectionHealth()`. — §R.2 · #3808 +- Removed `appSettings` (→ `getAppSettings()`), `mutedUsers` (→ `useMutedUsers()`), `enableOfflineSupport`, + `channel`, `setActiveChannel` (own navigation state). — P.11 + +### `ChannelsContext` (`useChannelsContext()`) + +- Removed `channels`, `channelListInitialized`, `hasNextPage`, `loadingChannels`, `loadingNextPage`, `refreshing`, + `error`, `forceUpdate` → read the list's `paginator` (new field) with `useStateStore`. — P.20 +- `loadNextPage`: `(queryType?, retryCount?)` → `()`; a no-op until the first page has loaded. — §18.3 · #3854 + +### `MessagesContext` (`useMessagesContext()`) + +- Removed `sendReaction`, `deleteReaction`, `deleteMessage`, `removeMessage`, `retrySendMessage`, `updateMessage` → + `useMessageOperations()` (same names; `deleteMessage` options are `{ hard, delete_for_me }`). — §9 · §17.2 +- Removed `targetedMessage` → `useIsTargetedMessage(id)`. — §6 +- Removed `initialScrollToFirstUnreadMessage`, `quotedMessage`. — P.4 +- Removed `isMessageAIGenerated` → `useChatContext()`. — Q.3 + +### `MessageContext` (`useMessageContext()`) + +- Removed `members`, `readBy`, `deliveredToCount`, `isMessageAIGenerated`. — §4 · §R.3 · Q.3 +- `onThreadSelect`: `(message, targetedMessageId?)` → `(thread: Thread)`. — §10.1 + +### `ThreadContext` (`useThreadContext()`) + +- Now `{ allowThreadMessagesInChannel, thread?: Thread | null, onAlsoSentToChannelHeaderPress? }`. `thread` was the + parent `LocalMessage`; it is now the `Thread` (parent via `thread.state`). +- Removed `threadMessages`, `threadHasMore`, `threadLoadingMore`, `threadLoadingMoreRecent`, `loadMoreThread`, + `loadMoreRecentThread`, `reloadThread`, `setThreadLoadingMore`, `openThread`, `closeThread`, `threadInstance`, + `parentMessagePreventPress` → `thread.messagePaginator`, `thread.reload()`, prop-driven open/close. — §10 · §10.1 · + §R.4 +- `onAlsoSentToChannelHeaderPress` payload: `{ parentMessage }` → `{ thread }`. — §10.1 + +### `ThreadsContext` + +- `onThreadSelect(thread: ThreadType, channel)` → `onThreadSelect(thread: Thread)`. — §10.1 + +### `MessageInputContext` (`useMessageInputContext()`) + +- Removed `editMessage` → `sendMessage()` saves an edit. — §11 · P.17 +- Removed `allowSendBeforeAttachmentsUpload` → `usePendingUploadsEnabled()`. — §O.3 +- Removed `doFileUploadRequest` → `client.config` `messageComposer.attachments.doUploadRequest`. — §R.4 · #3784 + +### `TranslationContext` (`useTranslationContext()`) + +- `t` is `StreamTFunction`: only catalog keys type-check; app keys need `asDynamicKey(...)`. — i18n +- `userLanguage`: `TranslationLanguages` → `string`. — §R.10 + +## 5. Hooks — changed signatures + +- `useMutedUsers(channel?)` → `useMutedUsers()`; returns `UserMuteResponse[]` (was `Mute[]`). — §14 +- `useMutedChannels(channel)` → `useMutedChannels()`. — P.3 +- `useChannelPreviewData(channel, client, forceUpdateOverride?)` → `(channel, client)`; `lastMessage` is + `LocalMessage | undefined`. — §R.4 +- `useIsChannelMuted(channel)` → returns `ChannelMuteStatus` (`createdAt` / `expiresAt` are `TimestampNS | null`). — + §17.5 +- `useChannelMembersState(channel)` → `(channel, pick?)` (additive). — #3854 +- `useCreateChatClient({ options })`: `StreamChatOptions` → `DeepPartial` (former options go under + `client`). — §17.7 · #3855 +- `useQueryReminders()`: data typed `ReminderResponseData[]` (was `ReminderResponse[]`), timestamps in ns. — §R.8 +- `useMessageList(params)`: `{ threadList }` → `{ paginator }` (no longer reads context); also returns + `maxLoadedItems`. — P.8 +- `useMessageActions(params)` (`MessageActionsHookProps` / `MessageActionsParams`): `openThread` removed, + `onThreadSelect` takes a `Thread`, `updateMessage` takes one argument. — §9 · §10.1 +- `useMessageActionHandlers(params)`: message ops come from `MessageOperations`, not `MessagesContextValue`. — §9 +- `useCreateMessagesContext`, `useCreateThreadContext`, `useCreateMessageInputContext`, `useCreateMessageContext`: + inputs narrowed to the new context shapes. — §15 · §R.4 +- `useChannelOwnCapabilities(channel)`: one overload `(channel?)`. — K.6 +- `usePollState` / `usePollStateSelector` / `usePollAnswers` / `usePollVotes`: poll types renamed (see §3); + `addComment()` → `Promise`, `endVote()` → `Promise`. — §17.5 + +## 6. Utilities and functions — changed + +- `getIndicatorTypeForFileState(fileState, allowSendBeforeAttachmentsUpload)` → second parameter renamed + `pendingUploadsEnabled` (positional, no call-site break). +- `isLocalUrl(url?)` checks the `http(s)://` scheme and returns `false` for `undefined`; `makeImageCompatibleUrl(url?)` + returns `undefined` for `undefined`. — §17.6 +- `removeReservedFields`, `checkMessageEquality`, `checkQuotedMessageEquality`: now generic / accept + `MessageResponse | LocalMessage` (widening). +- `isDayOrMoment`: re-exported from `@stream-io/i18n`; type guard is `value is DateTimeLike`. — §R.10 +- `defaultTranslatorFunction`: typed `StreamTFunction`; returns the English default instead of echoing the key. — + i18n +- `isBlockedMessage`, `isBouncedMessage`: only V2 `message.moderation.action` is recognised. — §17.4 +- `SCROLL_TO_BOTTOM_ACCESSIBILITY_LABEL_KEY`, `SCROLL_TO_BOTTOM_WITH_COUNT_ACCESSIBILITY_LABEL_KEY`: values are the + dotted keys. — §R.10 +- `DEFAULT_USER_LANGUAGE`: type `TranslationLanguages` → `'en'`. — §R.10 + +## 7. Types + +- `AIStates`: `const` object with literal types; `[AIStates.Thinking, AIStates.Generating].includes(aiState)` no longer + compiles (compare with `===`). — K.7 · §R.4 +- `AudioAttachmentType`, `AudioUpload`, `LocalAudioAttachmentType`: `waveform_data`, `mime_type`, `file_size`, + `duration` moved under `custom`. — §17.1 +- `DefaultAttachmentData`: now empty (`localId`, `originalFile` removed); `AudioUpload` / `LocalAudioAttachmentType` + also lose `localId`, `originalFile`, `latitude`, `longitude`. Unsent attachments carry `localMetadata` (`id`, + `file`, `previewUri`, `uploadState`), with no local URL in `image_url` / `asset_url`. — §17.6 +- `ChannelListEventListenerOptions`: `sort` is `SortParamRequest[]`. — §17.5 +- `UseFetchReactionParams.sort`: `SortParamRequest[]`. — §17.5 +- `LastMessageType` (`LocalMessage | MessageResponse`): unchanged alias, but both underlying `stream-chat` types + changed (timestamps, `.custom`, required fields). — §17.5 +- `TDateTimeParserInput` / `TDateTimeParserOutput`: re-typed from `@stream-io/i18n`. — §19 +- `ImageGalleryStateStore` `assets[]`: `created_at: Date | undefined`, `channelId: string`, `user: UserResponse`. — + §R.4 +- Every `stream-chat` type used in props and callbacks follows `stream-chat` v10 (see [§12](#12-stream-chat-v10-changes-an-rn-integrator-hits)). + +## 8. Classes and state stores + +**`Streami18n`** — §19 · i18n · §R.10 + +- Constructor `(options, i18nextConfig)` → `(options)` with `i18nextConfigOverrides`. +- `setLanguage()` → `Promise`; `getTranslators()` → `init()`; `addOnLanguageChangeListener` → + `i18n.state.subscribeWithSelector(...)`. +- Removed: `geti18Instance()` (→ `i18nInstance`), `getAvailableLanguages()`, `getTranslations()`, + `addOnTFunctionOverrideListener`, `isCustomDateTimeParser`, `Dayjs`. +- `registerTranslation(lang, dict, customDayjsLocale?)` merges a `TranslationDictionary` and takes a + `DayjsLocaleConfig`; new `state` store and `registeredLanguages`. +- `{{ … | relativeCompactDateFormatter }}` → `timestampFormatter(relativeCompact: true)`. + +**`OfflineDB`** — §R.9 + +- Removed `handleOfflineFailedUpdateMessagePendingTask`, `mergeFailedMessageUpdateIntoPendingSendMessage`. +- Added `handleUpdateMessagePendingTask`, `hardDeleteMessages`, `upsertMessageBehindChannelGuard`, + `queriesBehindChannelGuard`. +- `getChannelsForQuery` takes `{ userId, options }` (filters/sort inside `options`) and returns + `{ channels, predefinedFilter }`. — §N.1 +- Event handlers and upserts retyped to `stream-chat` v10 types (`EventPayload<'…'>`, `ChannelStateResponseFields`, + `PollResponseData`, `ReadStateResponse`, …). + +**`OfflineStoreApis`** — P.6 · §N.1 · §R.9 + +- `currentUserId` → `userId`; `deleteReactionsForMessage` removed; `sort` is `SortParamRequest[]`; + `deleteMessagesForChannel({ truncated_at })` takes `TimestampNS`; `getChannelsForFilterSort` returns + `{ channels, predefinedFilter }`; `getAppSettings` returns `GetApplicationResponse`. + +**`SqliteClient`** — `logger` is a `stream-chat` `Sink`; `dbVersion` 18. — §R.9 + +## 9. Component slots, icons and theme + +- No `DEFAULT_COMPONENTS` slot or icon was added, removed or renamed, and the `Theme` type is identical. What changed is + the **props and context** those slots receive (sections 3–4) — a custom slot written against v9 contexts must be + re-checked. +- Behaviour of default slots: `NetworkDownIndicator` shows "Waiting for network..." (device offline, new key + `common.waitingForNetwork.text`) or "Reconnecting..." (socket down after the settle delay) and no longer shows a + load error. `ChannelListHeaderNetworkDownIndicator` and `NotificationAnnouncer` follow the same signals. — §L.7 · + §R.2 + +## 10. i18n and translations + +- English-only bundle; no locale dictionaries are exported or bundled, nor are dayjs locales. — §19 · i18n · #3776 +- Keys are stable dotted keys (`'a11y/Send message'` → `messageInput.sendMessage.accessibilityLabel`); every v9 key is mapped in + `i18n-v10-key-map.json`. Old keys silently never match. — §19 +- `a11y/…` keys became `.accessibilityLabel` leaves; count strings are `_one` / `_other` plural keys. — §19 · Q.1 +- `t()` is typed to the catalog; app keys need `asDynamicKey('…')`; app dictionaries use `LooseTranslationDictionary`. + — i18n +- App-callable formatter keys removed from the catalog: `duration/Location end at`, `duration/Remind Me`, + `timestamp/ReminderNotification`, `timestamp/MessageEditedTimestamp` → register them yourself. — §R.10 +- `getDateString({ date })` → `getDateString({ messageCreatedAt })` (returns `null` when unrenderable); + `getDateStringForA11y` → `getCalendarDateStringForA11y`. These were deep imports in v9 and are root exports in v10. + — §19 +- Timestamps passed to `t('timestamp.X', { timestamp })` must be converted from nanoseconds. — §17.5 +- Notification copy is keyed on `notification.type`; dictionaries keyed by the English prose no longer apply. — i18n + +## 11. Offline storage + +- `dbVersion` 16 → 18; full reinit on first launch, v9 pending tasks dropped. — §N.2 +- Date columns holding server data TEXT (ISO) → INTEGER (unix ns); `pendingTasks.createdAt` and + `userSyncStatus.lastSyncedAt` stay TEXT. — §N.2 · §17.5 +- `AbstractOfflineDB.getChannelsForQuery` input/output reshaped; `syncManager.syncStatus` → `isSynced`. — §N.1 · §N.3 · + §R.9 +- `insertReaction` / `updateReaction` skip reactions whose message is not cached. — §R.9 +- `channelExists` now answers correctly (was always `true`). — §M.7 + +## 12. `stream-chat` v10 changes an RN integrator hits + +The full list is `stream-chat`'s own (LLC link above). These are the ones RN apps meet through the SDK or the sample +apps' own code. + +**State the SDK used to own** — §K · §R.7 + +- `channel.state` is a `StateStore` (`members`, `read`, `typing`, `watchers`, `ownCapabilities`, `data`, …); subscribe + with `useStateStore`. In-place mutation does not notify. +- Message storage moved off `channel.state` (`messages`, `latestMessages`, `threads`, `pinnedMessages`, + `isUpToDate`, `findMessage`, `addMessageSorted`, `loadMessageIntoState`, …) → `channel.messagePaginator`, + `channel.pinnedMessagesPaginator`, `client.messageStore`, `client.threads.get(id)`. +- `channel.lastMessage()` → `channel.messagePaginator.lastMessage`. +- `channel.state.*Store` handles (pre-release) → `channel.state`; `mutedUsersStore` → `client.mutedUsersStore`. — §K.1 · + §K.2 +- `channel.disconnected` → `channel.pendingDisposal`; `` keeps rendering a deleted channel. — §K.8 · P.15 +- `client.activeChannels` → `client.channelManager.get(cid)` / `.values()`; `releaseUnusedChannels()` frees them. +- `client.threads.state.threads` / `loadNextPage()` / `threadsById` → `client.threads.paginator`, `client.threads.get(id)`; + `thread.state.replies` / `loadNextPage` / `loadPrevPage` → `thread.messagePaginator`. +- `channel.activate()` / `thread.activate()` return a release function (there is no `deactivate()`); custom + channel/thread views must activate or recovery ignores them. — §K.4 · §L.4 + +**Connection** — §L · §R.2 + +- `recoverStateOnReconnect` / `client.recoverState()` → `client.connectionRecovery` (`client.config`). — §L.1 · §L.6 +- `connection.changed` is never dispatched → `client.wsConnection.state`, `client.networkConnection.state`. +- `connection.recovered` fires on every reconnect path. — §L.2 +- Requests with `watch` / `presence` wait for a connection id and throw when no socket is open. — §R.7 + +**Client construction and configuration** — §13.1 · §17.7 + +- `new StreamChat(key, secret?, options?)` → `new StreamChat(key, configTree)`; options move under `client`. +- `channel.getConfig()` → `channel.serverConfig` / resolved `channel.config`; `client.configs[cid]` → + `client.channelServerConfigs[cid]`. +- `client.setMessageComposerSetupFunction` → `client.config.setSetupFunction('messageComposer', fn)`. +- Link previews are on by default (where `url_enrichment` is on); a custom `doUploadRequest` needs `customCdn: true`. + +**Data shape** — §17.1 · §17.4 · §17.5 + +- Custom fields live under `.custom` on every entity, request and event (`channel.data.custom.name`, + `message.custom.ai_generated`, attachment `custom.mime_type` …); `Custom*Data` augmentations type the `custom` bag. +- Server dates are `TimestampNS` (unix ns): `convertTimestampToDate`, `nsToDate`, `nowNs`, `asTimestampNS`. +- Sort is `SortParamRequest[]` everywhere; filters use `'custom.'` keys. +- `message.moderation_details` → `message.moderation`. +- Hand-written types replaced by generated ones (renames such as `ReminderResponse` → `ReminderResponseData`, + `SharedLocationResponse` → `SharedLocationResponseData`, `Mute` → `UserMuteResponse`, `MessageResponseBase` → + `MessageResponse`, `ChannelSort` → `SortParamRequest[]`, `MessageRequest` → `SendMessageRequest`, + `DeliveredMessageConfirmation` → `DeliveredMessagePayload`, `DraftFilters` / `DraftSort` / `Pager` → + `QueryDraftsRequest`, search results are `SearchResultMessage` with an optional `message`, `connectUser` fixtures are + `ClientUser`); some names kept with a different meaning (`User`, `Reaction`, `PollResponse`, `ChannelConfig`). — §R.8 +- `Event` is a discriminated union: fields such as `e.me` or `e.total_unread_count` exist only on the event types that + carry them; narrow with `EventPayload<'…'>`. — §17.5 +- Client options such as `timeout` move under `client.axiosRequestConfig`. — §17.7 + +**Methods** — §17.2 · §17.3 · §R.8 + +- Path params first, then a request object: `channel.sendMessage({ message, ...options })`, `search({ payload })`, + `queryUsers({ payload })`, `channel.queryMembers({ payload })`, `sendEvent({ event: { type, custom } })`. +- `client.queryChannels(request)` returns the raw response; `queryChannelsAndHydrate` returns `Channel[]`. +- `client.channel(type, id, { custom: { name }, members: [{ user_id }] })`; `channel.update({ name })` → + `updatePartial({ set: { name } })` or `update({ data: { custom } })`. +- Push devices: `addDevice` → `createDevice({ id, push_provider, push_provider_name })`, `getDevices` → + `listDevices()`, `removeDevice` → `deleteDevice({ id })`; `setLocalDevice` and the `device` option are removed. +- Moderation: `muteUser`, `flagMessage`, `flagUser`, `banUser`, `shadowBan` → `client.moderation.*`. +- Reminders: `upsertReminder({ message_id }, { remind_at: Date })`; live location: + `stopLiveLocationSharing({ message_id })`, `LiveLocationManager.dispose()`. +- `channel.markRead()` is the direct request; the reporter/handler path is `channel.markReadViaReporter()` (or + `useMarkRead`). `markRead` / `markUnread` throw when read events are disabled. — §5 +- No `*WithLocalUpdate` methods: optimistic send/edit/delete/reactions are `channel.messageOperations.*` (UI: + `useMessageOperations()`). — §9 +- `deleteMessage` options `{ hardDelete, deleteForMe }` → `{ hard, delete_for_me }`. — §17.2 +- Uploads: `uploadFile` / `uploadImage({ file: { uri, name, type } })`. — §17.5 + +**Errors and logging** — §R.8 + +- `ErrorFromResponse` → `StreamAPIError`; 429 and `TOKEN_EXPIRED` are retried (up to ~100 s) and then throw + `RetryError`. +- Logging moved to `chatLoggerSystem` and is on by default (console sink at `info`). + +## 13. Behavioural changes (no symbol changed) + +**Messages** — §9.1 · §16 · §M · §R.5 + +- Reactions are always optimistic and roll back on definitive failure (v9: only with offline support). +- Edits and deletes are optimistic; a failed delete is reverted; hard delete removes the row. +- Deleting a failed *edit* now deletes the message on the server (v9 removed every failed message locally). +- `sendMessage` errors reject instead of being swallowed. — §16.2 +- Sending no longer clears `type: 'error'` messages from the list. +- Initial page size is 25 (v9 ≈100). — §16.1 + +**``** — §L.4 · §R.5 + +- No event listener, reconnect resync or `channel.deleted` unmount; `client.connectionRecovery` reloads active channels. +- A failed mount `watch()` no longer shows the full-screen error; the error screen follows the paginator's + `lastQueryError`. +- `initializeOnMount={false}` still runs a non-watching query when the paginator is empty. +- A provisional (members-only, id-less) channel is not queried on mount. +- `` may render `channel.supersededBy` instead of the instance you passed. +- `client.options.isLocalUnreadCountEnabled` is no longer read → `channel.config.readEvents.localUnreadCountEnabled`. +- Poll creation button shows before the server `polls` flag arrives. +- Composer `maxLength` follows `messageComposer.config.text.maxLengthOnSend`. + +**``** — §L.10 · §R.5 + +- Shows `LoadingErrorIndicator` instead of the list and composer when the first reply page fails. +- Does not mark a thread with no replies as read. + +**Channel list** — §18.5 · §N.4 · §R.6 + +- One shared `client.channelManager`; lists are a projection of `filters` / `sort` (no promote-to-top, channels can + leave a list when they stop matching). +- Recovery re-runs each list's first page; `` has no reconnect listener. +- An empty `sort` means the backend default. +- Channel preview typing indicator ignores typing inside threads; `MessageFooter` shows the sender name when + `memberCount !== 2`. + +**Composer / uploads** — §O.3 · §13.1 + +- `pendingUploadsEnabled` written by `` once; it does not follow later toggles. +- The LLC waits for pending uploads before sending (no SDK send wrapper). + +## 14. Deep imports + +Only affects imports from `stream-chat-react-native-core/src/…` or `lib/…`. Deleted with no public replacement: +`ChannelsStateProvider` / `useChannelsStateContext` / `useChannelState`, `ActiveChannelsRefContext`, +`useChannelDataState`, `ChannelUnreadStateStore`, `MessagePreviousAndNextMessageStore`, `ChannelList/hooks/utils` +(pin/archive sort helpers), `useCreateMessageComposer`, and the +`utils/i18n/*` modules (their functions are root exports now). — §R.11 + +## 15. New public APIs (for the docs) + +These replace removed APIs and need documentation: + +- Hooks: `useMessagePaginator`, `useMessageOperations` (+ `MessageOperations` type), `useMarkRead`, + `useIsTargetedMessage`, `useChannel`, `useIsMessageReadByOthers`, `useIsMessageDeliveredToOthers`, + `usePendingUploadsEnabled`, `useConnectionLifecycle`, `useNetworkConnectionState(Selector)`, + `useWSConnectionState(Selector)`, `useSettledWSConnectionHealth`. +- Values: `DEFAULT_HIGHLIGHT_DURATION`, `netInfoStatusReporter`, `pendingUploadsEnabledSelector`, + `getPlayableVideoUrl`, `ChannelListState`, `ChannelMemberPick`. +- i18n: `asDynamicKey`, `runtimeDefaults`, `englishCalendarFormats`, `predefinedFormatters`, `defaultDateTimeParser`, + `getDateString`, `getDateStringForA11y`, `getCalendarDateStringForA11y`, `isDate`, `isNumberOrString`, and the types + `TranslationKey`, `TranslationCatalog`, `TranslationDictionary`, `LooseTranslationDictionary`, `StreamTFunction`, + `DynamicTranslationKey`, `Streami18nOptions`, `CustomFormatters`, `FormatterFactory`, `PredefinedFormatters`, + `DayjsLocaleConfig`, `DateTimeParserModule`, `DurationFormatterOptions`, `TimestampFormatterOptions`. +- Context fields / props: `ChannelsContext.paginator`, `ChatContext.getAppSettings`, `ChannelListView.paginator`, + `MessageStatus.read` / `delivered`, `MessageList.pendingUploadsEnabled`, `TypingIndicatorContainer.threadId`. + +## 16. Known issues found by the audit + +Fix these before release, or document them as limitations: + +- `stream-chat-js`'s consolidated breaking-change branch deletes the root `v9-to-v10-migration-guide-*.md` files that + the ledger links to; repoint the links when it merges. + +## Regenerating this list + +The audit scripts are not checked in; the recipe is: + +1. Check out `develop` and `V10` side by side, `yarn install` in both, `yarn build` in both. +2. Snapshot each public surface with the TypeScript compiler API: walk `checker.getExportsOfModule()` for + `package/src/index.ts`, `package/src/ai-components/index.ts` and `contexts/componentsContext/defaultComponents.ts`; + flatten props (first call-signature parameter), `Context.Provider` `value` members, interface/type members (`Theme` + recursively) and class members; collect referenced `stream-chat` types. Diff the two snapshots (normalise union + ordering). +3. Copy `develop`'s `examples/SampleApp` and `examples/ExpoMessaging` sources next to `V10`'s `node_modules` and run + `tsc --noEmit`; every error must map to an entry here. +4. Review PRs merged into `V10` (`gh pr list --base V10 --state merged`) for behaviour changes. diff --git a/ai-docs/v10-docs-impact.md b/ai-docs/v10-docs-impact.md new file mode 100644 index 0000000000..3b58fe53a6 --- /dev/null +++ b/ai-docs/v10-docs-impact.md @@ -0,0 +1,644 @@ +# stream-chat-react-native v10 — docs impact assessment + +Scope: `getstream.io/content/docs/chat-sdk/react-native/v9-latest/` (302 pages), the source the v10 docs will be derived from. The breaking changes themselves are listed in [`v10-breaking-changes.md`](./v10-breaking-changes.md); section references like §4 or P.11 point to [`ai-migration-v9-to-v10.md`](./ai-migration-v9-to-v10.md). Inputs: the machine-generated v9.54 -> v10.0.0-rc.22 public-surface diff (1,295 entries), compile errors from building the v9 sample apps against v10 (SampleApp, ExpoMessaging), the agent migration ledger (`ai-docs/ai-migration-v9-to-v10.md`, `i18n-v10-migration.md`, `channel-state-ui-migration.md`) and the `stream-chat` v10 migration guides. + +Method: a term scanner (`scan.js`) matched every removed export, removed/retyped prop (scoped to its component: JSX attribute, props-table row, or heading on the owning page), removed context field (scoped to `useXContext()` / `useContext(XContext)` destructuring and the context page table), changed hook/function signatures, i18n keys/APIs, `stream-chat` method/type changes, Date usage on server timestamps, custom fields read at the root, sort objects and deep imports. 119 pages had raw hits (~900); each was triaged by hand (false positives removed, missed items added as "manual" findings). Line numbers refer to the v9-latest files. + +## Summary + +**99 pages need changes**, out of 302: + +| Severity | Pages | Meaning | +| --- | --- | --- | +| Delete / replace page | 6 | Documents an API removed in v10; delete or replace with a page for the successor API | +| Major rewrite | 20 | Core concept or most snippets on the page no longer apply | +| Snippet / prop-table update | 54 | Specific snippets, props/values tables or types need edits | +| Mention only | 19 | Wording, a type name, a version number or a cross-reference | + +Rough sizing (assumption: a writer familiar with v10; excludes review): DELETE ~0.5 h each incl. redirect (6); MAJOR ~0.5-1 day each (20); UPDATE ~1-2 h each (54); MENTION ~15 min each (19); plus 12 new pages/sections below (~0.5-1 day each, the v9->v10 migration guide ~3 days). Total roughly 6-8 writer-weeks. + +The remaining ~200 pages had no hits or only false positives (e.g. `ChannelEditDetails*` still takes `doFileUploadRequest`; `localId` props on upload indicators are unchanged; `client.blockUser`/`muteUser` calls are unchanged). + +### Top 10 most impacted pages + +1. `02-ui-components/10-thread/02-thread-context.md` — Major rewrite (25 distinct breaking items, 29 hits) +2. `02-ui-components/06-channel/02-channel-context.md` — Major rewrite (23 distinct breaking items, 33 hits) +3. `02-ui-components/06-channel/01-channel.md` — Major rewrite (21 distinct breaking items, 27 hits) +4. `02-ui-components/05-channel-list/01-channel-list.md` — Major rewrite (15 distinct breaking items, 19 hits) +5. `05-advanced-guides/09-translations.md` — Major rewrite (5 distinct breaking items, 46 hits) +6. `03-ui-cookbook/03-customize-channel-list.md` — Major rewrite (13 distinct breaking items, 15 hits) +7. `02-ui-components/10-thread/01-thread.md` — Major rewrite (11 distinct breaking items, 14 hits) +8. `02-ui-components/04-chat/02-chat-context.md` — Major rewrite (10 distinct breaking items, 12 hits) +9. `02-ui-components/05-channel-list/14-channels-context.md` — Major rewrite (8 distinct breaking items, 10 hits) +10. `05-advanced-guides/17-channel-read-state.md` — Major rewrite (8 distinct breaking items, 10 hits) + +### Pages to delete / replace + +- `02-ui-components/04-chat/03-hooks/02-use-app-settings.md` — `useAppSettings` removed (P.12). Redirect to ChatContext `getAppSettings()` (`await useChatContext().getAppSettings()`); `AppSettingsAPIResponse` type is now `GetApplicationResponse`. +- `02-ui-components/04-chat/03-hooks/04-use-is-online.md` — `useIsOnline` removed -> `useConnectionLifecycle(client, closeConnectionOnBackground)` (returns nothing). Replace with a new "Connection state" page documenting `useNetworkConnectionState(Selector)`, `useWSConnectionState(Selector)`, `useSettledWSConnectionHealth`, `useConnectionLifecycle`, `netInfoStatusReporter`. +- `02-ui-components/05-channel-list/15-hooks/01-channel-list/01-use-channel-updated.md` — `useChannelUpdated` removed (§18.2); `channel.updated` handled by `client.channelManager`. +- `02-ui-components/13-contexts/06-paginated-message-list-context.md` — Context removed (§3). Replace with a new "Message paginator" page (`useMessagePaginator`, `channel.messagePaginator`, `toTail/toHead`, `hasMoreTail/hasMoreHead`, `isLoading`). +- `02-ui-components/13-contexts/10-typing-context.md` — Context removed (§2) -> `useStateStore(channel.state, (s) => ({ typing: s.typing }))`; fold into the new channel-state page. +- `02-ui-components/18-channel-state-hooks/11-use-selected-channel-state.md` — `useSelectedChannelState` removed (P.1) -> `useStateStore(channel.state, selector)`; `EventTypes` (L32) also gone. + +### Major rewrites + +- `02-ui-components/04-chat/02-chat-context.md` +- `02-ui-components/04-chat/03-hooks/03-use-create-chat-context.md` +- `02-ui-components/05-channel-list/01-channel-list.md` +- `02-ui-components/05-channel-list/14-channels-context.md` +- `02-ui-components/06-channel/01-channel.md` +- `02-ui-components/06-channel/02-channel-context.md` +- `02-ui-components/10-thread/01-thread.md` +- `02-ui-components/10-thread/02-thread-context.md` +- `03-ui-cookbook/03-customize-channel-list.md` +- `03-ui-cookbook/06-message-input-customization.md` +- `03-ui-cookbook/10-file-compression.md` +- `03-ui-cookbook/27-date-separators.md` +- `04-state-and-offline-support/01-state-overview.md` +- `05-advanced-guides/02-date-time-formatting.md` +- `05-advanced-guides/05-typescript.md` +- `05-advanced-guides/09-translations.md` +- `05-advanced-guides/13-push-notifications.md` +- `05-advanced-guides/14-accessibility.md` +- `05-advanced-guides/17-channel-read-state.md` +- `05-advanced-guides/19-message-reminders.md` + +### Cross-cutting themes (drive most of the edits) + +1. **State moved out of React context into `stream-chat` stores.** `TypingContext`, `PaginatedMessageListContext` and most of `ChannelContext`, `ThreadContext`, `ChannelsContext`, `ChatContext`, `MessagesContext` values are gone; replaced by `useStateStore(channel.state | channel.messagePaginator.state | thread.state | paginator.state, selector)` plus new hooks `useMessagePaginator`, `useMessageOperations`, `useMarkRead`, `useIsTargetedMessage`. Every context reference page and every snippet that destructures those values changes. +2. **Threads are `Thread` instances everywhere.** ``, ``, `ThreadContext.thread`, `onThreadSelect` on `MessageList`/`MessageFlashList`/`Message`/`ThreadList` all take/yield `Thread`; `ThreadType` and the `{ thread, threadInstance }, channel` callback shape are gone; imperative `openThread`/`closeThread`/`loadMoreThread`/`reloadThread` removed. Touches thread pages, navigation, troubleshooting, overview, livestream, custom thread list. +3. **`` behaviour props -> `client.config`.** `doMarkReadRequest`, `doSendMessageRequest`, `preSendMessageRequest`, `doUpdateMessageRequest`, `doFileUploadRequest`, throttle props, `maximumMessageLimit`, `allowSendBeforeAttachmentsUpload`, `messageId` are removed; replacements are `client.config.set({ channel: { requestHandlers, messagePaginator }, messageComposer: { attachments } })` and paginator jumps. Channel page, performance guide, file compression, message input customization, date separators. +4. **ChannelList is a projection of `filters` + `sort`.** 10 per-event override props and `useChannelUpdated` removed; `sort` is `SortParamRequest[]`; `queryChannelsOverride` is a paginator `doRequest`; `ChannelsContext` holds config + `paginator`. +5. **i18n v10.** English-only bundle (12 `*Translations` exports gone), stable dotted keys (v9 keys were the English copy; old keys fail silently), typed `t` (`asDynamicKey` for app keys), `a11y/*` and `timestamp/*` namespaces renamed, `Streami18n` API changes (`setLanguage` -> void, `getTranslators` -> `init`, listeners -> `state` store, single options object). Translations, date-time formatting, accessibility, reminders and every snippet calling `t("English copy")`. +6. **`stream-chat` v10 surface in snippets.** Server timestamps are unix-ns `TimestampNS` numbers (`created_at.toDateString()`, `.getTime()`, `new Date(ts)` break); custom fields live under `.custom` (`channel.data.custom.name`, `message.custom.ai_generated`, `attachment.custom.mime_type`); methods take request objects (`channel.sendMessage({ message })`, `queryChannelsAndHydrate({ filter_conditions, sort })`, `createDevice/deleteDevice`, `getMessage({ id })`); `client.activeChannels` -> `client.channelManager`; options are a config tree; `connection.changed` removed; server-side APIs removed (`api_secret`, `updateAppSettings`); `moderation_details` -> `moderation`. + +### New pages / sections needed (concepts introduced in v10 with no docs) + +| New page / section | Suggested location | Replaces / absorbs | +| --- | --- | --- | +| **Upgrading from v9** migration guide (human version of the ledger: contexts -> stores, threads, Channel props -> `client.config`, ChannelList orchestrator, i18n, `stream-chat` v10 type surface, behavioural changes) | `06-migration-guides/01-upgrading-from-v9.md` | — (link from every DELETE page) | +| **Message paginator**: `useMessagePaginator`, `channel.messagePaginator` / `thread.messagePaginator`, `toTail`/`toHead`, `hasMoreTail`/`hasMoreHead`/`isLoading`/`lastQueryError`, `jumpToMessage` / `jumpToTheFirstUnreadMessage` / `jumpToTheLatestMessage`, `messageFocusSignal` + `useIsTargetedMessage` + `DEFAULT_HIGHLIGHT_DURATION`, `unreadStateSnapshot`, `maxLoadedItems`, page size 25 | `02-ui-components/07-message-list/` or `04-state-and-offline-support/` | `13-contexts/06-paginated-message-list-context.md`, `Channel.messageId`, `maximumMessageLimit` | +| **Instance configuration (`client.config`)**: `client.config.set(...)`, `setSetupFunction("messageComposer", fn)`, request handlers (`markReadRequest`, `sendMessageRequest`, `retrySendMessageRequest`, `updateMessageRequest`), `messageComposer.attachments.{doUploadRequest, customCdn, pendingUploadsEnabled}`, `channel.messagePaginator.{pageSize, stateThrottleMs, maxLoadedItems}`, resolved gates (`channel.config.typingEvents.enabled`, `channel.serverConfig`), client options as config tree | `05-advanced-guides/` (new) | Channel `do*Request` examples, throttle props, `allowSendBeforeAttachmentsUpload`, `setMessageComposerSetupFunction` | +| **Channel state & `useStateStore` patterns**: unified `channel.state` store (`members`, `read`, `watchers`, `watcherCount`, `typing`, `membership`, `aiState`, `pendingDisposal`, `active`), module-scope selectors, `channel.activate()` (returns its release function), `useChannel()`, thread `thread.state` | `04-state-and-offline-support/01-state-overview.md` (rewrite) + `18-channel-state-hooks/` index | `13-contexts/10-typing-context.md`, `useSelectedChannelState`, ChannelContext scalar fields | +| **Connection state & recovery**: `useNetworkConnectionState(Selector)`, `useWSConnectionState(Selector)`, `useSettledWSConnectionHealth`, `useConnectionLifecycle`, `netInfoStatusReporter`, `client.connectionRecovery` (enable/disable, `recover()`), `connection.recovered` semantics | `02-ui-components/04-chat/03-hooks/` (replace use-is-online) + advanced guide | `04-use-is-online.md`, `ChatContext.isOnline/connectionRecovering`, `recoverStateOnReconnect` | +| **Message operations**: `useMessageOperations()` (`sendReaction`, `deleteReaction`, `deleteMessage` with snake_case options, `retrySendMessage`, `removeMessage`, `updateMessage`), optimistic lifecycle owned by `stream-chat` | hooks section near MessagesContext | removed `MessagesContext` ops | +| **Mark read**: `useMarkRead(channel)`, reporter-driven cadence, `channel.markReadLocally()` | merge into rewritten `17-channel-read-state.md` | `ChannelContext.markRead`, `doMarkReadRequest` | +| **Thread paginator / Thread instance**: `client.threads.ensure({ channel, parentMessage })`, `thread.messagePaginator`, `thread.state.parentMessage`, prop-driven open/close, `client.threads.paginator` for the list | `02-ui-components/10-thread/` | ThreadContext loaders, `ThreadType` | +| **Channel list orchestrator**: `client.channelManager` (`addEventHandler`, `get`, `values`), list paginator (`boost`, `state.items/hasMoreTail/lastQueryError`), `lockItemOrder`, new `queryChannelsOverride` contract | `02-ui-components/05-channel-list/` | event override props, `useChannelUpdated`, `ChannelsContext` state fields | +| **i18n v10**: dotted keys + key-map table, `TranslationDictionary` / `LooseTranslationDictionary`, `asDynamicKey`, registering a language (dictionary + dayjs locale + calendar formats), plurals, `Streami18n` state store / `init()` / `setLanguage` void, `@stream-io/i18n`, dayjs single copy, `getDateString({ messageCreatedAt })`, `getCalendarDateStringForA11y` | rewrite `05-advanced-guides/09-translations.md` (+ `02-date-time-formatting.md`) | bundled language list, v9 API sections | +| **Timestamps in v10**: `TimestampNS`, `nsToDate`/`convertTimestampToDate`/`msToNs`/`nowNs`, request fields stay `Date` | `05-advanced-guides/05-typescript.md` or a short guide | ad-hoc `Date` usage in cookbook snippets | +| **Custom data under `.custom`** (reading `channel.data.custom.*`, `message.custom.*`, `attachment.custom.*`, custom event payload under `event.custom`) | `05-advanced-guides/05-typescript.md` | — | +| **Deleted / disposed channels**: `` keeps rendering; read `pendingDisposal` | troubleshooting + channel page | "Blank screen when channel gets deleted" | + +### SDK follow-ups found while scanning (not docs work) + +- `useCreateChatClient` types `userData` as `ClientUser` (what `connectUser` takes), so the documented `userData: { id, name, image }` literals keep type-checking; no docs page needs to change for it. (Earlier v10 pre-releases typed it `OwnUserResponse | UserResponse`, which in `stream-chat` v10 require server fields such as `created_at` and `role`.) +- `client.setMessageComposerSetupFunction` still works (deprecated); docs can migrate lazily to `client.config.setSetupFunction('messageComposer', fn)`. +- The ledger errors found during the scan (§18.1 `queryChannels`, §4 `MessageContext.members`, §7.1 `client.activeChannels`) were fixed in the ledger in the same change as this report. + +--- + +## Per-page impact + +Format: severity · what must change · scanner-detected breaking items with line numbers (`id (Lx)`) · manual findings. + +### 01-basics/ + +#### `01-overview.md` — Mention only + +- **Change:** Version banner: "V9 requires React Native 0.76+" -> v10 requires React Native >=0.79 (peer bumped) and `stream-chat` ^10. +- **Detected:** `rnVersion` (L60) + +#### `02-installation.md` — Mention only + +- **Change:** Add v10 install notes: `stream-chat` ^10 comes with the SDK (do not pin v9 alongside it); react-native peer >=0.79; `dayjs` must resolve to a single copy (^1.11.23) and apps should not declare `i18next` (runtime now in `@stream-io/i18n`). +- **Manual:** (no scanner hit) dependency notes + +### 02-ui-components/ + +#### `01-overview.md` — Snippet / prop-table update + +- **Change:** Fix channel-creation snippets (custom fields under `custom`, members as `{ user_id }` objects); drop "read it from `client.activeChannels`" (L240) -> `client.channelManager.get(cid)`; Thread example: `useState` -> `useState()` since `onThreadSelect` now yields a `Thread` instance and `` takes a `Thread`. +- **Detected:** `channelCreateMembers` (L111); `activeChannels` (L240); `Channel.thread` (L374); `MessageList.onThreadSelect` (L381) +- **Manual:** L97-99, L110-113: `client.channel(type, id?, { name, members: [ids] })` -> `{ custom: { name }, members: [{ user_id }] }` + +### 02-ui-components/04-chat/ + +#### `01-chat.md` — Mention only + +- **Change:** Note `isMessageAIGenerated` is now only a `Chat` prop (removed from `Channel`); `useCreateChatClient` options are the config tree. No prop removals on `Chat` itself (`channelManager` prop is gone but was never documented). + +#### `02-chat-context.md` — Major rewrite + +- **Change:** ChatContext now holds only `client`, `getAppSettings`, `isMessageAIGenerated` (P.11). Remove rows `appSettings`, `channel`, `connectionRecovering`, `enableOfflineSupport`, `isOnline`, `mutedUsers`, `setActiveChannel`; add `getAppSettings()`; fix both snippets (`const { client, isOnline }`) and point to `useNetworkConnectionState` / `useWSConnectionState`, `useMutedUsers()`, `client.offlineDb`. +- **Detected:** `ChatContext.isOnline` (L18, L26, L38); `typeRenames` (L34); `ChatContext.appSettings` (L34); `ChatContext.channel` (L35); `ChatContext.connectionRecovering` (L36); `ChatContext.enableOfflineSupport` (L37); `ChatContext.mutedUsers` (L39); `ChatContext.setActiveChannel` (L40) + +#### `03-hooks/01-use-create-chat-client.md` — Snippet / prop-table update + +- **Change:** `options` type `StreamChatOptions` -> `DeepPartial` (former options now live under `client: {...}`, e.g. `{ client: { axiosRequestConfig: { timeout } } }`). SDK caveat: `userData` is still typed `OwnUserResponse | UserResponse`, which in stream-chat v10 requires server fields, so the documented `{ id, name, image }` literal fails TS unless the SDK retypes it to `ClientUser` (see SDK follow-ups). +- **Detected:** `StreamChatOptions` (L60) + +#### `03-hooks/02-use-app-settings.md` — Delete / replace page + +- **Change:** `useAppSettings` removed (P.12). Redirect to ChatContext `getAppSettings()` (`await useChatContext().getAppSettings()`); `AppSettingsAPIResponse` type is now `GetApplicationResponse`. +- **Detected:** `useAppSettings` (L12, L13, L15); `typeRenames` (L46) + +#### `03-hooks/03-use-create-chat-context.md` — Major rewrite + +- **Change:** `useCreateChatContext` now takes `{ client, getAppSettings, isMessageAIGenerated }` only. Rewrite snippet and params table (drop appSettings, channel, connectionRecovering, enableOfflineSupport, isOnline, mutedUsers, setActiveChannel). Consider dropping the page (internal hook). +- **Detected:** `connectionRecovering` (L19, L43); `setActiveChannel` (L24, L48); `typeRenames` (L40) + +#### `03-hooks/04-use-is-online.md` — Delete / replace page + +- **Change:** `useIsOnline` removed -> `useConnectionLifecycle(client, closeConnectionOnBackground)` (returns nothing). Replace with a new "Connection state" page documenting `useNetworkConnectionState(Selector)`, `useWSConnectionState(Selector)`, `useSettledWSConnectionHealth`, `useConnectionLifecycle`, `netInfoStatusReporter`. +- **Detected:** `connectionRecovering` (L6, L16, L18, L29, L45); `useIsOnline` (L13, L14, L16) + +#### `03-hooks/05-use-muted-users.md` — Snippet / prop-table update + +- **Change:** Remove ChatContext `mutedUsers` reference; return type `UserMuteResponse[]` (`Mute` type renamed). Duplicate of 05-channel-list/15-hooks/01-channel-list/11-use-muted-users.md - consolidate. +- **Manual:** L20: "available through ChatContext via `mutedUsers`" (removed) +- **Manual:** L31: returns `Mute[]` -> `UserMuteResponse[]` + +### 02-ui-components/05-channel-list/ + +#### `01-channel-list.md` — Major rewrite + +- **Change:** Remove the 10 per-event override props (`onAddedToChannel`, `onChannelDeleted`, `onChannelHidden`, `onChannelMemberUpdated`, `onChannelTruncated`, `onChannelUpdated`, `onChannelVisible`, `onNewMessage`, `onNewMessageNotification`, `onRemovedFromChannel`) and the event-behaviour prose (L142) -> list is a projection of `filters` + `sort`; global handling via `client.channelManager.addEventHandler`, floating via `paginator.boost`, `lockItemOrder`. `sort` is now `SortParamRequest[]` (`[{ field: "last_message_at", direction: -1 }]`) - fix L31, L73 and table rows L157/L261. `queryChannelsOverride` retyped to paginator `doRequest` `(queryParams) => Promise<{ items }>` - rewrite row L181 and example L190-200 (`client.queryChannels(filters, sort, options)` is gone; use `client.queryChannelsAndHydrate(queryParams)`). +- **Detected:** `sortObject` (L31, L73); `onNewMessageNotification` (L142, L173); `onNewMessage` (L142, L174); `ChannelList.sort` (L157, L261); `onAddedToChannel` (L166); `onChannelDeleted` (L167); `onChannelHidden` (L168); `onChannelMemberUpdated` (L169); `onChannelVisible` (L170); `onChannelTruncated` (L171); `onChannelUpdated` (L172); `onRemovedFromChannel` (L175); `typeRenames` (L181); `ChannelList.queryChannelsOverride` (L181); `queryChannels` (L196) + +#### `03-channel-list-view.md` — Snippet / prop-table update + +- **Change:** `ChannelListView` now receives the query state as required props (`channels`, `channelListInitialized`, `error`, `hasNextPage`, `loadingChannels`, `loadingNextPage`, `refreshing`) from `ChannelList`; `loadNextPage` is `() => Promise` (L21); `forceUpdate` removed. +- **Detected:** `ChannelListView.loadNextPage` (L21) + +#### `13-channel-details-bottom-sheet.md` — Snippet / prop-table update + +- **Change:** Custom channel fields moved under `channel.data.custom` in v10. +- **Manual:** L53: `channel.data?.name` -> `channel.data?.custom?.name` (or `useChannelName(channel)`) + +#### `14-channels-context.md` — Major rewrite + +- **Change:** ChannelsContext holds configuration + `paginator` + stable `loadNextPage`/`refreshList`/`reloadList` (P.20). Remove rows `channels`, `channelListInitialized`, `error`, `hasNextPage`, `loadingChannels`, `loadingNextPage`, `refreshing` (and `forceUpdate`); add `paginator`; `loadNextPage()` takes no args; rewrite snippets (`const { channels } = useChannelsContext()` -> `useStateStore(useChannelsContext().paginator.state, selector)`). +- **Detected:** `ChannelsContext.channels` (L19, L27, L35); `ChannelsContext.channelListInitialized` (L36); `ChannelsContext.error` (L37); `ChannelsContext.hasNextPage` (L38); `ChannelsContext.loadingChannels` (L39); `ChannelsContext.loadingNextPage` (L41); `ChannelsContext.loadNextPage` (L42); `ChannelsContext.refreshing` (L49) + +#### `15-hooks/01-channel-list/01-use-channel-updated.md` — Delete / replace page + +- **Change:** `useChannelUpdated` removed (§18.2); `channel.updated` handled by `client.channelManager`. +- **Detected:** `onChannelUpdated` (L6, L18, L34); `useChannelUpdated` (L14, L16) + +#### `15-hooks/01-channel-list/06-use-channel-members-state.md` — Mention only + +- **Change:** Hook gained optional `pick?: ChannelMemberPick` 2nd param (additive); return type references `ChannelMemberResponse` (still valid). + +#### `15-hooks/01-channel-list/10-use-muted-channels.md` — Snippet / prop-table update + +- **Change:** `useMutedChannels()` takes no argument and always returns `ChannelMute[]` (P.3). +- **Manual:** L16: `useMutedChannels(channel) ?? []` -> `useMutedChannels()` + +#### `15-hooks/01-channel-list/11-use-muted-users.md` — Snippet / prop-table update + +- **Change:** `useMutedUsers()` takes no argument; returns `UserMuteResponse[]`. +- **Manual:** L23: deprecated `channel` param row - remove + +#### `15-hooks/02-channel-preview/01-use-channel-preview-data.md` — Snippet / prop-table update + +- **Change:** Signature `(channel, client)`; drop third param row and fix return type. +- **Manual:** L35: `forceUpdateOverride` param removed +- **Manual:** L41: `lastMessage` type is `LocalMessage | undefined` + +#### `15-hooks/02-channel-preview/06-use-is-channel-muted.md` — Snippet / prop-table update + +- **Change:** Return type is core `ChannelMuteStatus`; timestamps are unix-ns numbers. +- **Manual:** L34-35: `createdAt` / `expiresAt` are `TimestampNS | null` (were `Date | null`) + +### 02-ui-components/06-channel/ + +#### `01-channel.md` — Major rewrite + +- **Change:** Provided-contexts list: remove `PaginatedMessageListContext` (L7) and `TypingContext` (L11). Props table: remove `allowSendBeforeAttachmentsUpload`, `doFileUploadRequest`, `doMarkReadRequest`, `doSendMessageRequest`, `doUpdateMessageRequest`, `isMessageAIGenerated`, `loadingMore`, `loadingMoreRecent`, `maximumMessageLimit`, `messageId`, `newMessageStateUpdateThrottleInterval`, `preSendMessageRequest`, `stateUpdateThrottleInterval`; `thread` is now a `Thread | null` (L220, drop `ThreadType`). Rewrite the whole Examples section (L225-282: file-upload override, send-before-upload, mark-read, send/update overrides) as `client.config.set({ channel: { requestHandlers: { markReadRequest, sendMessageRequest, updateMessageRequest } }, messageComposer: { attachments: { doUploadRequest, pendingUploadsEnabled } } })`; add "open at a message" via `channel.messagePaginator.jumpToMessage` in `useLayoutEffect` (replaces `messageId`); add `maxLoadedItems` pruning pointer; mention `` keeps rendering a deleted channel (`pendingDisposal`). +- **Detected:** `PaginatedMessageListContext` (L7); `usePaginatedMessageListContext` (L7); `TypingContext` (L11); `useTypingContext` (L11); `allowSendBeforeAttachmentsUpload` (L133, L239, L244); `doFileUploadRequest` (L151, L229); `doMarkReadRequest` (L152, L255); `doSendMessageRequest` (L153, L265); `doUpdateMessageRequest` (L154, L276); `Channel.isMessageAIGenerated` (L187); `Channel.loadingMore` (L190); `loadingMoreRecent` (L191); `maximumMessageLimit` (L194); `Channel.messageId` (L198); `newMessageStateUpdateThrottleInterval` (L203); `preSendMessageRequest` (L212); `stateUpdateThrottleInterval` (L218); `ThreadType` (L220); `Channel.thread` (L220); `sendMessage` (L269); `msgOps` (L279) + +#### `02-channel-context.md` — Major rewrite + +- **Change:** Remove 19 value rows (`error`, `highlightedMessageId`, `isChannelActive`, `loadChannelAroundMessage`, `loadChannelAtFirstUnreadMessage`, `loading`, `markRead`, `members`, `read`, `reloadChannel`, `scrollToFirstUnreadThreshold`, `setTargetedMessage`, `targetedMessage`, `watchers`, `watcherCount`, plus `channelUnreadStateStore`, `setChannelUnreadState`, `uploadAbortControllerRef`, `maximumMessageLimit` if present). Rewrite usage snippets (L19, L27 destructure `reloadChannel`, `watcherCount`), Best Practices (L5-9: markRead/loading/error/read/members) and the `loadChannelAroundMessage` section (L86-100) -> `useStateStore(channel.state, sel)`, `useMarkRead(channel)`, `useMessagePaginator().jumpToMessage/jumpToTheFirstUnreadMessage/jumpToTheLatestMessage`, `useIsTargetedMessage`, paginator `lastQueryError`; `last_read: Date` (L100) -> `TimestampNS`. +- **Detected:** `ChannelContext.reloadChannel` (L19, L27, L56); `ChannelContext.watcherCount` (L19, L27, L61); `ChannelContext.error` (L44); `ChannelContext.highlightedMessageId` (L47); `ChannelContext.isChannelActive` (L48); `loadChannelAroundMessage` (L49, L59, L86, L89); `ChannelContext.loadChannelAroundMessage` (L49); `ChannelContext.loadChannelAtFirstUnreadMessage` (L50); `ChannelContext.loading` (L51); `ChannelContext.markRead` (L52); `ChannelContext.members` (L54); `ChannelContext.read` (L55); `ChannelContext.scrollToFirstUnreadThreshold` (L57); `targetedMessage` (L58, L59); `ChannelContext.setTargetedMessage` (L58); `ChannelContext.targetedMessage` (L59); `ChannelContext.watchers` (L60) + +### 02-ui-components/07-message-list/ + +#### `01-message-list.md` — Snippet / prop-table update + +- **Change:** `FooterComponent` now receives `{ loadingMore?: boolean }` (L39); `HeaderComponent` row (L40) (`MessageFlashList` passes it there); `isListActive` description references removed `isChannelActive` (L42); `onThreadSelect(thread: Thread)` (L45); `thread` prop is a `Thread`. Note the list pages its own paginator (no `loadMore`/`hasMore` props). +- **Detected:** `MessageList.FooterComponent` (L39); `MessageList.HeaderComponent` (L40); `isChannelActive` (L42); `MessageList.onThreadSelect` (L45) + +#### `02-scroll-to-bottom-button.md` — Snippet / prop-table update + +- **Change:** Remove `unreadCount` prop row (L34) and best-practice L7; the count is self-derived from `channel.state` read state. +- **Detected:** `ScrollToBottomButton.unreadCount` (L34) + +#### `04-messages-context.md` — Snippet / prop-table update + +- **Change:** Remove rows `deleteMessage`, `deleteReaction`, `isMessageAIGenerated`, `initialScrollToFirstUnreadMessage`, `quotedMessage`, `removeMessage`, `retrySendMessage`, `sendReaction`, `targetedMessage`, `updateMessage`; add pointer to `useMessageOperations()` (§9) and `useChatContext().isMessageAIGenerated`; `giphyVersion` type `keyof Images`. +- **Detected:** `MessagesContext.deleteMessage` (L15); `MessagesContext.deleteReaction` (L16); `MessagesContext.isMessageAIGenerated` (L38); `MessagesContext.initialScrollToFirstUnreadMessage` (L39); `MessagesContext.quotedMessage` (L44); `MessagesContext.removeMessage` (L52); `MessagesContext.retrySendMessage` (L53); `MessagesContext.sendReaction` (L54); `MessagesContext.targetedMessage` (L58); `MessagesContext.updateMessage` (L59) + +### 02-ui-components/08-message-composer/ + +#### `01-message-composer.md` — Mention only + +- **Change:** `members` / `watchers` props still exist (now typed from `channel.state`); `isOnline` prop retyped to `boolean`. Verify descriptions only. + +#### `components/01-attachment-preview/03-attachment-upload-progress-indicator.md` — Mention only + +- **Change:** `localId` props unchanged; clarify that `localId` is `attachment.localMetadata.id` and that pending uploads are now controlled by `messageComposer.attachments.pendingUploadsEnabled` (was `allowSendBeforeAttachmentsUpload`). +- **Detected:** `attachmentMeta` (L3) + +#### `composer/10-message_composer.md` — Snippet / prop-table update + +- **Change:** `client.setMessageComposerSetupFunction` is deprecated -> `client.config.setSetupFunction("messageComposer", fn)` (L50 heading, L79, L202-205). "Message Composer Configuration" section should move to `client.config.set({ messageComposer: {...} })` (new required `attachments.customCdn`, `polls`; `linkPreviews.enabled` defaults true). +- **Detected:** `setMessageComposerSetupFunction` (L50, L79, L202, L205) + +#### `composer/12-message_composer_middleware.md` — Snippet / prop-table update + +- **Change:** L368-391: `UserSort` / `MemberSort` types removed -> `SortParamRequest[]`; L399-400 `userSort = { name: -1 }` -> `[{ field: "name", direction: -1 }]`; L1035 setup function deprecated -> `client.config.setSetupFunction`. Middleware tables (L848, L863) describe `stream-chat` factories that still exist - no change. +- **Detected:** `typeRenames` (L372, L375, L383, L389); `sortObject` (L399, L400); `setMessageComposerSetupFunction` (L1035) + +#### `contexts/01-message-input-context.md` — Snippet / prop-table update + +- **Change:** Remove rows `allowSendBeforeAttachmentsUpload` (L35), `doFileUploadRequest` (L48), `editMessage` (L49); `sendMessage` (L63) now submits the composer (`messageComposer.update()` while editing, `send()` otherwise); `thread` (L70) is a `Thread` instance (drop `ThreadType`). Snippets using `sendMessage` remain valid. +- **Detected:** `MessageInputContext.allowSendBeforeAttachmentsUpload` (L35); `MessageInputContext.doFileUploadRequest` (L48); `MessageInputContext.editMessage` (L49); `MessageInputContext.sendMessage` (L63); `ThreadType` (L70) + +### 02-ui-components/09-message/ + +#### `01-message-item-view.md` — Snippet / prop-table update + +- **Change:** Remove `members` prop row (L26). +- **Detected:** `MessageItemView.members` (L26) + +#### `03-audio-attachment.md` — Mention only + +- **Change:** Item metadata (`mime_type`, `file_size`) now lives under `attachment.custom`; `duration` is voice-recording only (L15). +- **Detected:** `attachmentMeta` (L15) + +#### `12-message-content.md` — Snippet / prop-table update + +- **Change:** Remove `isMessageAIGenerated` prop row (L23) -> ``. +- **Detected:** `MessageContent.isMessageAIGenerated` (L23) + +#### `14-message-edited-timestamp.md` — Snippet / prop-table update + +- **Change:** `t("Edited")` (L11, L70) -> `t("message.edited.text")`; Streami18n override example (L23-35) must use the dotted key. +- **Detected:** `tEnglishKey` (L11, L70); `newStreami18n` (L25) + +#### `16-message-footer.md` — Snippet / prop-table update + +- **Change:** Remove `members` prop row (L19). +- **Detected:** `MessageFooter.members` (L19) + +#### `20-message-status.md` — Snippet / prop-table update + +- **Change:** Remove `readBy` / `deliveredToCount` rows (L15-16); mention `useIsMessageReadByOthers` / `useIsMessageDeliveredToOthers`. +- **Detected:** `MessageStatus.deliveredToCount` (L15); `MessageStatus.readBy` (L16) + +#### `25-message-context.md` — Snippet / prop-table update + +- **Change:** Remove rows `isMessageAIGenerated` (L50), `readBy` (L65), `deliveredToCount` (L66), `members` (L80); `onThreadSelect` (L75) receives a `Thread`. +- **Detected:** `MessageContext.isMessageAIGenerated` (L50); `MessageContext.readBy` (L65); `MessageContext.deliveredToCount` (L66); `MessageContext.onThreadSelect` (L75); `MessageContext.members` (L80) + +### 02-ui-components/10-thread/ + +#### `01-thread.md` — Major rewrite + +- **Change:** `thread` is a `Thread` instance: fix snippets `` (L25, L68) and row L52 (drop `ThreadType`); remove `closeThread`, `loadMoreThread`, `reloadThread`, `threadInstance` rows (L46-53); `closeThreadOnDismount` is inert in v10 (row + best practice L6); best practice L9 -> `thread.messagePaginator` / `thread.reload()`. Show `client.threads.ensure({ channel, parentMessage })` for opening from a message. +- **Detected:** `loadMoreThread` (L9, L47); `reloadThread` (L9, L51); `Channel.thread` (L25, L68); `Thread.closeThread` (L46); `Thread.loadMoreThread` (L47); `Thread.reloadThread` (L51); `ThreadType` (L52); `Thread.thread` (L52); `Thread.threadInstance` (L53) + +#### `02-thread-context.md` — Major rewrite + +- **Change:** ThreadContext is now `{ thread, allowThreadMessagesInChannel, parentMessagePreventPress?, onAlsoSentToChannelHeaderPress? }` with `thread: Thread`. Remove 12 rows (L35-48: closeThread, loadMoreThread, loadMoreRecentThread, openThread, reloadThread, setThreadLoadingMore, threadHasMore, threadInstance, threadLoadingMore, threadLoadingMoreRecent, threadMessages, and parentMessagePreventPress-from-context); rewrite Best Practices (L6-7) and add the paginator/`thread.state` recipes. +- **Detected:** `closeThread` (L6, L35); `openThread` (L6, L39); `threadHasMore` (L7, L44); `threadLoadingMore` (L7, L46); `ThreadContext.closeThread` (L35); `ThreadContext.loadMoreThread` (L36); `ThreadContext.loadMoreRecentThread` (L37); `ThreadContext.openThread` (L39); `ThreadContext.parentMessagePreventPress` (L40); `ThreadContext.reloadThread` (L41); `ThreadContext.setThreadLoadingMore` (L42); `ThreadType` (L43); `ThreadContext.thread` (L43); `ThreadContext.threadHasMore` (L44); `ThreadContext.threadInstance` (L45); `ThreadContext.threadLoadingMore` (L46); `ThreadContext.threadLoadingMoreRecent` (L47); `ThreadContext.threadMessages` (L48) + +### 02-ui-components/11-thread-list/ + +#### `01-thread-list.md` — Snippet / prop-table update + +- **Change:** `onThreadSelect(thread: Thread)` - single arg; the `{ thread, threadInstance }` + `channel` shape is gone (L32). Channel via `thread.channel`. +- **Detected:** `threadInstance` (L32); `ThreadList.onThreadSelect` (L32) + +### 02-ui-components/13-contexts/ + +#### `01-contexts.md` — Snippet / prop-table update + +- **Change:** Remove `PaginatedMessageListContext`, `TypingContext`, `DebugContext` from the list (L25, L28, L55) and table (L96, L105, L112); fix custom `MessageStatus` example (L131-145) reading `message.readBy` -> `useIsMessageReadByOthers({ message })`; add a short "state now comes from `channel.state` / paginators" note. +- **Detected:** `PaginatedMessageListContext` (L25, L105); `TypingContext` (L28, L112); `DebugContext` (L55, L96); `useDebugContext` (L96); `usePaginatedMessageListContext` (L105); `useTypingContext` (L112); `readBy` (L133, L145) + +#### `06-paginated-message-list-context.md` — Delete / replace page + +- **Change:** Context removed (§3). Replace with a new "Message paginator" page (`useMessagePaginator`, `channel.messagePaginator`, `toTail/toHead`, `hasMoreTail/hasMoreHead`, `isLoading`). +- **Detected:** `PaginatedMessageListContext` (L1, L13, L17, L20); `usePaginatedMessageListContext` (L5, L24, L27, L30); `loadingMoreRecent` (L39, L45) + +#### `09-translation-context.md` — Snippet / prop-table update + +- **Change:** `t` is a typed `StreamTFunction` (catalog keys; runtime keys need `asDynamicKey`); `userLanguage` type `TranslationLanguages` -> `string` (L36). +- **Detected:** `typeRenames` (L36) + +#### `10-typing-context.md` — Delete / replace page + +- **Change:** Context removed (§2) -> `useStateStore(channel.state, (s) => ({ typing: s.typing }))`; fold into the new channel-state page. +- **Detected:** `TypingContext` (L1, L13, L17, L19); `useTypingContext` (L5, L22, L25, L27) + +#### `11-threads-context.md` — Snippet / prop-table update + +- **Change:** `onThreadSelect` is `(thread: Thread) => void`: rewrite the "onThreadSelect arguments" section (L55-80: `{ thread, threadInstance }` object + `channel`) and the custom `ThreadListItem` snippet (L100-110) -> `onThreadSelect?.(thread)`. +- **Detected:** `ThreadsContext.onThreadSelect` (L42, L100); `threadInstance` (L65, L71, L106) + +### 02-ui-components/14-AI/ + +#### `02-ui-components.md` — Snippet / prop-table update + +- **Change:** `message.ai_generated` -> `message.custom?.ai_generated` (L28, L37, L49). +- **Detected:** `aiGenerated` (L28, L37, L49) + +#### `03-ai-hooks.md` — Snippet / prop-table update + +- **Change:** `message.ai_generated` -> `message.custom?.ai_generated` (L24, L62); `AIStates` is now a readonly literal map that adds `Stop`; `useAIState` reads `channel.state.aiState`. +- **Detected:** `aiGenerated` (L24, L62) + +### 02-ui-components/15-in-app-notifications/ + +#### `01-hooks/04-use-notification-target.md` — Mention only + +- **Change:** L19 "a `threadInstance` on `ThreadContext`" -> "a `thread` on `ThreadContext`". +- **Detected:** `threadInstance` (L19) + +### 02-ui-components/16-base-ui-components/ + +#### `04-channel-avatar.md` — Mention only + +- **Change:** Prose "`channel.data.image`" (L3, L21) -> `channel.data.custom.image`. + +### 02-ui-components/17-channel-details/ + +#### `08-hooks/08-use-user-activity-status.md` — Mention only + +- **Change:** Mentions `getDateString` pipeline (L1): parameter renamed `date` -> `messageCreatedAt`; `last_active` is `TimestampNS`; key `timestamp/UserActivityStatus` -> `timestamp.UserActivityStatus`. +- **Detected:** `dateHelpers` (L1) + +#### `08-hooks/09-use-member-roles.md` — Snippet / prop-table update + +- **Change:** `t("VIP")` (L32) is an app key -> `t(asDynamicKey("myApp.roles.vip"), "VIP")`. +- **Detected:** `tEnglishKey` (L32) + +### 02-ui-components/18-channel-state-hooks/ + +#### `01-use-channel-name.md` — Mention only + +- **Change:** Prose "Reads `channel.data?.name`" (L1, L8) -> `channel.data.custom.name` (hook itself unchanged). + +#### `02-use-channel-image.md` — Mention only + +- **Change:** Prose "Reads `channel.data?.image`" (L1, L8) -> `channel.data.custom.image`. + +#### `05-use-channel-own-capabilities.md` — Mention only + +- **Change:** Signature normalized to `(channel?: Channel)`; now sourced from `channel.state`. + +#### `11-use-selected-channel-state.md` — Delete / replace page + +- **Change:** `useSelectedChannelState` removed (P.1) -> `useStateStore(channel.state, selector)`; `EventTypes` (L32) also gone. +- **Detected:** `useSelectedChannelState` (L15, L17); `typeRenames` (L32) + +### 02-ui-components/99-experimental/ + +#### `01-flash-list.md` — Snippet / prop-table update + +- **Change:** `maximumMessageLimit` advice (L19, L31) -> paginator `maxLoadedItems` config; `FlatList` prop removed from `MessageFlashList`; custom `HeaderComponent` receives `{ loadingMore }`. +- **Detected:** `maximumMessageLimit` (L19, L31) + +### 03-ui-cookbook/ + +#### `01-custom_components.md` — Snippet / prop-table update + +- **Change:** Replace the `usePaginatedMessageListContext` example (L83-100) with `useStateStore(channel.messagePaginator.state, selector)` (or `useMessagePaginator()`); update the warning at L159. `sendMessage` from `useMessageInputContext` and `localId` usages are still valid. +- **Detected:** `PaginatedMessageListContext` (L83, L159); `usePaginatedMessageListContext` (L91, L95); `attachmentMeta` (L119) + +#### `02-custom-attachment.md` — Snippet / prop-table update + +- **Change:** `attachment.mime_type` / `attachment.file_size` (L143, L145) -> `attachment.custom?.mime_type` / `.custom?.file_size`. +- **Detected:** `attachmentMeta` (L143, L145) + +#### `03-customize-channel-list.md` — Major rewrite + +- **Change:** Event-handler override table + `onNewMessageNotification(setChannels, event)` example (L11-75) are gone: rewrite around `filters`/`sort` (list is a projection of the query), `channelRenderFilterFn`, `paginator.boost(cid)`, and `client.channelManager.addEventHandler`. "Load More" button (L88-100) reads `loadingNextPage`/`hasNextPage` from ChannelsContext - now from `useStateStore(paginator.state)` / `ChannelListView` props. Multiple-lists section prose about `client.activeChannels` (L116) is obsolete. +- **Detected:** `onChannelDeleted` (L21); `onChannelHidden` (L22); `onChannelTruncated` (L23); `onChannelUpdated` (L24); `onChannelVisible` (L25); `onNewMessage` (L26); `onAddedToChannel` (L27); `onNewMessageNotification` (L28, L45, L71); `onRemovedFromChannel` (L29); `ChannelsContext.hasNextPage` (L93); `ChannelsContext.loadingNextPage` (L93); `ChannelsContext.loadNextPage` (L93); `activeChannels` (L116) + +#### `04-customize-message-actions.md` — Snippet / prop-table update + +- **Change:** Custom label keys `t("Custom Mute User")` (L272) need `asDynamicKey` (or plain strings); `client.userID` (L258) is deprecated -> `client.userId`. `client.muteUser(id)` / `unmuteUser(id)` calls are still valid. +- **Detected:** `userID` (L258); `msgOps` (L265, L267); `tEnglishKey` (L272) + +#### `05-livestream-messagelist.md` — Snippet / prop-table update + +- **Change:** `StreamChat.getInstance(key, { isLocalUnreadCountEnabled: true })` (L49-51) -> `{ channel: { readEvents: { localUnreadCountEnabled: true } } }`; thread state now a `Thread` (L104, L154, L171). +- **Detected:** `localUnread` (L50); `Channel.thread` (L104, L154); `MessageList.onThreadSelect` (L171) + +#### `06-message-input-customization.md` — Major rewrite + +- **Change:** Custom CDN section (L230-250) uses `` -> `client.config.set({ messageComposer: { attachments: { doUploadRequest, customCdn: true } } })`. Replace the whole "Uploading attachments asynchronously" section (L540-660: hand-written `localAttachmentToAttachment` / attachment composition middlewares + `setMessageComposerSetupFunction`) with the built-in `messageComposer.attachments.pendingUploadsEnabled` switch. "Overriding send" example (L670-700) uses `channel.sendMessage(message, sendOptions)` -> `channel.sendMessage({ message, ...sendOptions })` or a `sendMessageRequest` handler. +- **Detected:** `doFileUploadRequest` (L235, L239); `localAttachmentToAttachment` (L557, L593, L628); `createAttachmentsCompositionMiddleware` (L580, L652); `createDraftAttachmentsCompositionMiddleware` (L615, L656); `setMessageComposerSetupFunction` (L646, L650); `sendMessage` (L685) + +#### `09-message-customization.md` — Snippet / prop-table update + +- **Change:** L63 `useChannelContext()` destructures removed `members`, `watchers`, `error`, `loading` -> `useStateStore(channel.state, ...)` / paginator; L439 `useMessageContext().members` removed -> `useStateStore(channel.state, s => ({ members: s.members }))`; L442 `client.userID`; L627 `client.deleteMessage(id)` -> `client.deleteMessage({ id })` or `useMessageOperations().deleteMessage(message)`; thread state typing (L414, L474). +- **Detected:** `ChannelContext.error` (L63); `ChannelContext.members` (L63); `ChannelContext.loading` (L63); `ChannelContext.watchers` (L63); `Channel.thread` (L414, L474); `MessageContext.members` (L439); `userID` (L442); `msgOps` (L627) + +#### `10-file-compression.md` — Major rewrite + +- **Change:** Whole recipe is built on `` (L5, L46, L64) and `channel.sendFile(uri, name, type)` (L61). Rewrite with `client.config.set({ messageComposer: { attachments: { doUploadRequest } } })` and `channel.uploadFile({ file: { uri, name, type } })`; `waveform_data` (L25) moved under `custom`. +- **Detected:** `doFileUploadRequest` (L5, L46, L64); `attachmentMeta` (L25); `sendFileImage` (L61) + +#### `13-custom-thread-list.md` — Snippet / prop-table update + +- **Change:** `onThreadSelect={(thread, channel) => ...}` (L75, L134, L197, L231) -> `(thread) => ...` with `thread.channel`; custom item calls `onThreadSelect({ thread, threadInstance }, channel)` (L107-116) -> `onThreadSelect?.(thread)`. Also uses `ThreadList ThreadListItem={...}` prop (v8 style) -> `WithComponents`. +- **Detected:** `ThreadList.onThreadSelect` (L75, L134, L197, L231); `ThreadsContext.onThreadSelect` (L107); `threadInstance` (L115) + +#### `15-customize-message-reactions.md` — Snippet / prop-table update + +- **Change:** `ReactionSortBase` removed; `useFetchReactions` `sort` is `SortParamRequest[]`. +- **Manual:** L282-284: `const reactionSort: ReactionSortBase = { created_at: -1 }` -> `SortParamRequest[]` `[{ field: "created_at", direction: -1 }]` + +#### `16-channel-pinning-and-archiving.md` — Snippet / prop-table update + +- **Change:** Best practice L8 tells readers to keep `onChannelMemberUpdated` wired (prop removed; the orchestrator handles `member.updated`); sort L200 `[{ pinned_at: -1 }, ...]` -> `[{ field: "pinned_at", direction: -1 }, ...]`. `channel.pin()` / `archive()` unchanged. +- **Detected:** `onChannelMemberUpdated` (L8); `sortObject` (L200) + +#### `18-handle-commands.md` — Snippet / prop-table update + +- **Change:** `chatClient.setMessageComposerSetupFunction` (L22) deprecated -> `client.config.setSetupFunction("messageComposer", fn)`. +- **Detected:** `setMessageComposerSetupFunction` (L22) + +#### `19-emoji-suggestions.md` — Snippet / prop-table update + +- **Change:** Same setup-function deprecation (L53). +- **Detected:** `setMessageComposerSetupFunction` (L53) + +#### `22-hide-channel-history-before.md` — Snippet / prop-table update + +- **Change:** Request date fields are `Date`; `addMembers([ids], undefined, options)` still valid. +- **Manual:** L19-22, L85, L253: `hide_history_before = date.toISOString()` -> request field is typed `Date` in v10; pass the `Date` + +#### `23-snackbar.md` — Snippet / prop-table update + +- **Change:** `t("a11y/Dismiss notification")` (L169) -> `t("notifications.dismiss.accessibilityLabel")`. +- **Detected:** `tEnglishKey` (L169); `a11yKey` (L169) + +#### `27-date-separators.md` — Major rewrite + +- **Change:** Example is built on `usePaginatedMessageListContext` (L28-33, L62) and `message.created_at.toDateString()` (L40, L46, L84 - `created_at` is now a `TimestampNS` number); `channel.state.messages` / `messagePagination.hasPrev` (L62) no longer exist. Rewrite with `useStateStore(channel.messagePaginator.state, s => ({ items: s.items, hasMoreTail: s.hasMoreTail }))` and `nsToDate`. `maximumMessageLimit` pointer (L95) -> `maxLoadedItems`. +- **Detected:** `usePaginatedMessageListContext` (L28, L33); `methodOnField` (L40, L46); `PaginatedMessageListContext` (L62); `maximumMessageLimit` (L95) + +### 03-ui-cookbook/24-channel-details/ + +#### `02-custom-channel-actions.md` — Snippet / prop-table update + +- **Change:** `t("Pin")` / `t("Unpin")` / `t("Delete")` (L36, L121, L129) are v9 English keys; use dotted catalog keys or `asDynamicKey` + default copy. +- **Detected:** `tEnglishKey` (L36, L121, L129) + +#### `05-custom-member-roles.md` — Snippet / prop-table update + +- **Change:** `t("Admin")` (L76) -> `t("channelDetails.memberRoles.admin.label")`. +- **Detected:** `tEnglishKey` (L76) + +### 03-ui-cookbook/25-channel/ + +#### `01-channel-header.md` — Snippet / prop-table update + +- **Change:** `useChatContext().isOnline` (L19 prose, L59) removed -> `useNetworkConnectionState` / `useWSConnectionState`; `client.on((e) => e.me ?? e ...)` (L41-48) no longer type-checks (`Event` is a discriminated union) - subscribe to specific event types. +- **Detected:** `eventMe` (L18, L44, L45, L46); `ChatContext.isOnline` (L59) + +### 04-state-and-offline-support/ + +#### `01-state-overview.md` — Major rewrite + +- **Change:** LLC snippets: `chatClient.queryChannels(filter, sort, opts)` (L238-244) -> `client.queryChannelsAndHydrate({ filter_conditions, sort: [{ field, direction }], watch, state })`; `channel.sendMessage({ text })` (L253) -> `channel.sendMessage({ message: { text } })` and the L137 note (channel.sendMessage is HTTP-only; optimistic path is `channel.messageOperations` / `useMessageOperations`). L403 `threadInstance`. This page is the natural home for the new v10 state model: unified `channel.state` store, `channel.messagePaginator`, `thread.messagePaginator`, `client.config`, `client.channelManager`. +- **Detected:** `sendMessage` (L137, L253); `channelCreateMembers` (L198); `sortObject` (L238); `queryChannels` (L240); `threadInstance` (L403) +- **Manual:** L184-200: `client.channel(type, id, { name })` / `{ members: [ids], name }` -> `{ custom: { name } }`, `members: [{ user_id }]` + +#### `02-offline-support.md` — Mention only + +- **Change:** Add v10 notes: one-time offline cache reset (DB version 17 -> 18), pending uploads default on with `enableOfflineSupport` (`pendingUploadsEnabled`), unsent messages persist across restarts. + +### 05-advanced-guides/ + +#### `02-date-time-formatting.md` — Major rewrite + +- **Change:** All `timestamp/*` keys renamed to dotted keys (`timestamp/MessageSystem` -> `timestamp.MessageSystem`, etc.; L42-137); "defaults stored in the JSON translation files" no longer true (English runtime defaults; per-locale calendar formats removed - import the dayjs locale). Custom `timestampFormatter` (L155-160) receives unix-ns values: `new Date(val).getTime()` is wrong -> convert with `nsToDate`. `relativeCompactDateFormatter` -> `timestampFormatter(relativeCompact: true)`. +- **Detected:** `timestampKeys` (L42, L66, L72, L78, L127, L137); `newStreami18n` (L155); `getTime` (L158) + +#### `04-new-architecture.md` — Mention only + +- **Change:** React Native `>=0.76.0` (L3) -> `>=0.79.0`. +- **Detected:** `rnVersion` (L3) + +#### `05-typescript.md` — Major rewrite + +- **Change:** Augmentation interfaces still exist, but custom data now lives under a `.custom` bag at runtime: the "properly typed" example (L170-186: `channel.data.topic`, `user.nickname`, `message.isSecret`, `attachment.lat`) must read `.custom?.x`. `EventTypes` -> `EventType`; custom event data under `event.custom` (L52-56); `StreamMessage` alias gone. Add `TranslationDictionary` / `LooseTranslationDictionary` / `asDynamicKey` typing notes. +- **Detected:** `declareModule` (L30, L31, L35, L78, L97, L100, L102, L106, … +14); `typeRenames` (L52, L56) + +#### `06-troubleshooting.md` — Snippet / prop-table update + +- **Change:** Thread snippets (L17-69) still work but `thread` is a `Thread` instance (`onThreadSelect` yields it). Rewrite the deleted-channel section. Flipper mention (L7) can go. +- **Detected:** `Channel.thread` (L17, L26, L62, L69); `MessageList.onThreadSelect` (L18, L63) +- **Manual:** L262-275 "Blank screen when channel gets deleted": `` no longer returns null on delete (P.15); read `pendingDisposal` from `channel.state` instead + +#### `07-stream_chat_with_navigation.md` — Snippet / prop-table update + +- **Change:** `useState()` (L171) -> `Thread`; `onThreadSelect` yields a `Thread`; passing the instance in navigation params (L174) is non-serializable - recommend passing ids + `client.threads.ensure` / app context. +- **Detected:** `Channel.thread` (L144, L157, L184); `MessageList.onThreadSelect` (L159, L186) + +#### `09-translations.md` — Major rewrite + +- **Change:** Near-total rewrite. Built-in language list (L3-13) -> English only; supply dictionaries. Keys are dotted (`TranslationDictionary`); registerTranslation examples (L84-110, L145, L406) use v9 English keys. Constructor 2nd param `i18nextConfig` (L56, L307) -> `i18nextConfigOverrides` in the options object. Remove API sections `getAvailableLanguages`, `geti18Instance`, `getTranslations`, `getTranslators` (L367-396) -> `init()`, `i18n.state`, `i18n.t`; `setLanguage` returns `void` (L426); `enTranslations` link (L403). Add `asDynamicKey`, plurals (`_one`/`_other`), dayjs single-copy, moment/timezone section check. +- **Detected:** `translationJson` (L3, L4, L5, L6, L7, L8, L9, L10, … +5); `newStreami18n` (L31, L43, L61, L69, L83, L99, L112, L128, … +5); `registerTranslation` (L64, L75, L84, L101, L116, L140, L145, L399, … +3); `api` (L367, L372, L375, L380, L383, L388, L391, L396); `xTranslations` (L403) + +#### `11-moderation.md` — Snippet / prop-table update + +- **Change:** `message.moderation_details` (L9, L36, L40) -> `message.moderation` (V2: `action: "remove" | "bounce"`). +- **Detected:** `moderation_details` (L9, L36, L40) + +#### `12-going-live-checklist.md` — Snippet / prop-table update + +- **Change:** Re-render example (L40-80) uses `usePaginatedMessageListContext` -> `useStateStore(channel.messagePaginator.state, selector)` (selector already limits re-renders). +- **Detected:** `PaginatedMessageListContext` (L40); `usePaginatedMessageListContext` (L43, L47, L59, L74) + +#### `13-push-notifications.md` — Major rewrite + +- **Change:** `client.addDevice(token, provider, USER_ID, name)` / `removeDevice` (L95-145) -> `client.createDevice({ id, push_provider, push_provider_name })` / `deleteDevice({ id })`; server snippets `StreamChat.getInstance("api_key", "api_secret")` + `client.updateAppSettings(...)` (L392-510) - server-side API removed from `stream-chat` v10 -> `@stream-io/node-sdk`; `chatClient.getMessage(id)` (L548) -> `getMessage({ id })`. +- **Detected:** `devices` (L95, L131, L137, L145); `serverSide` (L392, L400, L460, L474, L500, L508) + +#### `14-accessibility.md` — Major rewrite + +- **Change:** The `a11y/*` key namespace is gone: best practices (L9-10), Localization section (L67-81: `registerTranslation("nl", { "a11y/Avatar of {{name}}": ... })`, `en.json` pointer) and `accessibilityLabelKey="a11y/Send message"` (L91) must use dotted `.accessibilityLabel` keys (e.g. `avatar.accessibilityLabel`, `messageInput.sendMessage.accessibilityLabel`, `a11y.newMessages.withCount.accessibilityLabel`). `client.userID` (L160). +- **Detected:** `tEnglishKey` (L9); `a11yKey` (L9, L75, L76, L77, L91); `newStreami18n` (L72); `registerTranslation` (L74); `translationJson` (L81); `userID` (L160) + +#### `15-performance-guide.md` — Snippet / prop-table update + +- **Change:** "State update throttling" section (L19-42) documents removed `newMessageStateUpdateThrottleInterval` / `stateUpdateThrottleInterval` -> `client.config.set({ channel: { messagePaginator: { stateThrottleMs } } })`; add `maxLoadedItems` pruning and default page size (25). +- **Detected:** `newMessageStateUpdateThrottleInterval` (L26, L33); `stateUpdateThrottleInterval` (L27, L34, L40) + +#### `17-channel-read-state.md` — Major rewrite + +- **Change:** Whole model changed: `channelUnreadState` in ChannelContext -> `channel.messagePaginator.unreadStateSnapshot`; `read` from context -> `useStateStore(channel.state, s => ({ read: s.read }))`; `markRead` from context + `updateChannelUnreadState` option -> `useMarkRead(channel)` / `channel.markRead()` (reporter-driven cadence); `loadChannelAtFirstUnreadMessage(...)` (L264) -> `paginator.jumpToTheFirstUnreadMessage()`; `last_read: Date` (L23, L34) -> `TimestampNS`; `isLocalUnreadCountEnabled` (L146) -> config `channel.readEvents.localUnreadCountEnabled`; `getInstance(key, { timeout })` (L275) -> `{ client: { axiosRequestConfig: { timeout } } }`; `client.activeChannels` (L294) -> `client.channelManager.values()`; event union narrowing for `total_unread_count`. +- **Detected:** `ChannelContext.read` (L49); `ChannelContext.markRead` (L73); `localUnread` (L147); `setChannelUnreadState` (L264); `loadChannelAtFirstUnreadMessage` (L264); `setTargetedMessage` (L264); `eventMe` (L283, L291, L292); `activeChannels` (L294) + +#### `18-draft-message.md` — Snippet / prop-table update + +- **Change:** `client.setMessageComposerSetupFunction` (L23) deprecated -> `client.config.setSetupFunction`. +- **Detected:** `setMessageComposerSetupFunction` (L23) + +#### `19-message-reminders.md` — Major rewrite + +- **Change:** `ReminderState` dates (L32-37) are `TimestampNS`; `ReminderResponse` (L27, L73, L78, L82) -> `ReminderResponseData`; `paginator.sort = { remind_at: 1 }` (L94) -> `[{ field: "remind_at", direction: 1 }]`; `reminder.remindAt.getTime()` (L174) - `remindAt` is unix-ns; header example keys `t("Due since ...")`, `t("timestamp/ReminderNotification")`, `t("duration/Message reminder")`, `t("Saved For Later")` (L190-213) -> dotted keys (`duration.messageReminder`, `message.savedForLater.text`, ...). +- **Detected:** `typeRenames` (L27, L73, L78, L82); `sortObject` (L94); `getTime` (L174); `tEnglishKey` (L190, L195, L213); `timestampKeys` (L191, L196) + +#### `21-ai-integrations/01-overview.md` — Snippet / prop-table update + +- **Change:** `message.ai_generated` -> `message.custom?.ai_generated` (L369-405, sample `isMessageAIGenerated`); drop the "Channel also accepts `isMessageAIGenerated`" caveat (L426) - prop removed. Node snippets (L374 `channel.sendMessage({ ..., user_id, ai_generated })`, L539 `client.partialUpdateMessage(..., botId)`) are server-side: point to `@stream-io/node-sdk` (server APIs removed from `stream-chat` v10). +- **Detected:** `aiGenerated` (L369, L377, L389, L396, L405); `sendMessage` (L374); `declareModule` (L405, L410, L411); `msgOps` (L539) + +#### `21-ai-integrations/02-chat-sdk-integration.md` — Snippet / prop-table update + +- **Change:** `message.ai_generated` -> `message.custom?.ai_generated` (L15, L20). +- **Detected:** `aiGenerated` (L15, L20) + +#### `21-ai-integrations/03-stream-chat-ai-sdk.md` — Mention only + +- **Change:** Backend snippet `channel.sendEvent({ type, orderId })` (L170): if this runs on `stream-chat` v10 it becomes `sendEvent({ event: { type, custom: { orderId } } })`. Verify which SDK the agent snippet targets. + +#### `21-ai-integrations/04-stream-chat-langchain-sdk.md` — Mention only + +- **Change:** Same `sendEvent` shape note (L172). + +#### `21-audio-playback.md` — Snippet / prop-table update + +- **Change:** Attachment metadata moved under `.custom`. +- **Detected:** `attachmentMeta` (L72) +- **Manual:** L71-72: `item.duration` / `item.mime_type` -> `item.custom?.mime_type`; `duration` is voice-recording only + +#### `22-in-app-notifications.md` — Mention only + +- **Change:** L95 "Translation keys live in `package/src/i18n/.json`" -> runtime defaults + dotted keys; `notification.message` is no longer matched against English copy (i18n guide). + +#### `23-search-sources.md` — Snippet / prop-table update + +- **Change:** `sort: [{ name: 1 }]` (L99) -> `[{ field: "name", direction: 1 }]`; `client.userID` (L97) -> `client.userId`. +- **Detected:** `userID` (L97); `sortObject` (L99) + +### 06-migration-guides/ + +#### `02-upgrading-from-v8.md` — Mention only + +- **Change:** Historical v8->v9 guide. Keep, but add a banner pointing to the new v9->v10 guide: several v9 replacements it recommends are removed in v10 (`useClientMutedUsers` L908/L1175/L1892, `channelUnreadStateStore` L456); RN 0.76 requirement (L42) is now 0.79. +- **Detected:** `deepImport` (L21); `rnVersion` (L42); `channelUnreadStateStore` (L456); `useClientMutedUsers` (L908, L1175, L1892) + +--- + +_Artifacts: `scan.js` (term scanner), `hits.json` (raw hits), `triage.js` (manual triage), `report.js` (this report), all in `SCRATCH/docs/`._ From 94e16cb54382a3eaf12cfc537683896e4ff70bd8 Mon Sep 17 00:00:00 2001 From: Oliver Lazoroski Date: Mon, 19 Oct 2026 10:34:29 +0200 Subject: [PATCH 2/2] =?UTF-8?q?docs:=20fold=20the=20stacked=20PRs=20#3866?= =?UTF-8?q?=E2=80=93#3869=20into=20the=20v10=20breaking-change=20docs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The inventory was taken against V10; the stack underneath this PR adds more public API changes. Re-ran the surface diff and the v9 sample-app compile against the stack tip (refactor/stop-creating-sdk-paginator) and covered them: - v10-breaking-changes.md: standalone next to (removed Channel thread/threadList/allowThreadMessagesInChannel/shouldSyncChannel, Thread props, threadList everywhere, ThreadMessageComposer slot, ThreadContext shape, onAlsoSentToChannelHeaderPress move), message actions built on demand (argument-less useMessageActions/useMessageActionHandlers, removed Message handleX/messageActions/supportedReactions), and the app-owned (removed filters/sort/options/ lockChannelOrder/queryChannelsOverride). - ai-migration-v9-to-v10.md: correct the removed Thread prop names (additionalMessageComposerProps, additionalMessageFlashListProps), add shouldSyncChannel, and move R.6 and its quick-reference rows onto the ChannelPaginator. - v10-docs-impact.md: re-scan the docs for the stack's changes (109 pages affected, was 99). --- ai-docs/ai-migration-v9-to-v10.md | 34 +++-- ai-docs/v10-breaking-changes.md | 115 +++++++++++----- ai-docs/v10-docs-impact.md | 222 +++++++++++++++++++----------- 3 files changed, 244 insertions(+), 127 deletions(-) diff --git a/ai-docs/ai-migration-v9-to-v10.md b/ai-docs/ai-migration-v9-to-v10.md index c2e6c2d121..87e1cdda33 100644 --- a/ai-docs/ai-migration-v9-to-v10.md +++ b/ai-docs/ai-migration-v9-to-v10.md @@ -77,7 +77,7 @@ rg 'useMessagesContext\(\)' -A10 src/ | rg '\b(sendReaction|deleteReaction|delet rg '\buseMessageActions\b' src/ # §10 — thread API (instance + standalone ) -rg '\b(useThreadContext|openThread|closeThread|reloadThread|loadMoreThread|threadMessages|closeThreadOnDismount|ThreadFooterComponent|threadList|ThreadMessageComposer|additionalMessageListProps|additionalMessageInputProps|shouldUseFlashList)\b' src/ +rg '\b(useThreadContext|openThread|closeThread|reloadThread|loadMoreThread|threadMessages|closeThreadOnDismount|ThreadFooterComponent|threadList|ThreadMessageComposer|additionalMessageListProps|additionalMessageFlashListProps|additionalMessageComposerProps|shouldUseFlashList|shouldSyncChannel)\b' src/ rg '` | §10.1 | -| `ThreadType` / `` | a `Thread` instance; `client.threads.ensure({ channel, parentMessage })` builds one from a message | §10.1 | +| `ThreadType` / `` | `` with a `Thread` instance; `client.threads.ensure({ channel, parentMessage })` builds one from a message | §10.1 | | `` | ``, no `` around it | §10.1 | | `threadList` (prop or context field) | `useThreadContext().thread` | §10.2 | -| `ThreadMessageComposer` slot / `additionalMessageInputProps` | a `MessageComposer` child of `` with its own props | §10.2 | +| `ThreadMessageComposer` slot / `additionalMessageComposerProps` | a `MessageComposer` child of `` with its own props | §10.2 | | `useMessageComposerContext().thread` (parent message; deep import only) | `thread` is now the `Thread`; parent on `thread.state` | §10.2 | | `editMessage()` on the message input context | removed: `useMessageInputContext().sendMessage()` saves an edit; intercept with `updateMessageRequest` | P.17 | | `` props | removed — list reads the `Thread` from `ThreadContext` + paginator internally; `thread` stays, retyped to `Thread \| null` | §12 | @@ -261,8 +261,8 @@ means changed. Details in the linked section. | `useMessageContext().readBy` / `.deliveredToCount` | `useIsMessageReadByOthers({ message })` / `useIsMessageDeliveredToOthers({ message })` | §R.3 | | `` | `` | §R.3 | | `useMessageContext().members` | `useStateStore(channel.state, (s) => ({ members: s.members }))` | §4 | -| `` | ignored — the list pages itself; `options.limit` is the page size | §R.6 | -| `` | `filters={{ 'custom.myField': x }}` | §R.6 | +| `` | the `ChannelPaginator` owns paging: `requestOptions` takes neither; page size is `paginatorOptions.pageSize` | §R.6 | +| `` | `new ChannelPaginator({ client, filters: { 'custom.myField': x } })` | §R.6 | | `channel.state.messages` / `channel.lastMessage()` | `channel.messagePaginator.state` (`items`) / `channel.messagePaginator.lastMessage` | §R.7 | | `client.activeChannels[cid]` | `client.channelManager.get(cid)` | §R.7 | | `client.threads.state` `threads` / `client.threads.loadNextPage()` | `client.threads.paginator.state` `items` / `client.threads.paginator.toTail()` | §R.7 | @@ -806,10 +806,13 @@ registers a new one. ### 10.2 Removed thread-related props / context fields -- **``**: `thread` and `threadList` removed. A `` only ever renders its channel's - main list; render a `` for replies. +- **``**: `thread`, `threadList`, `allowThreadMessagesInChannel` (now a `` prop) and + `shouldSyncChannel` removed. A `` only ever renders its channel's main list and always runs its own + lifecycle (v9 apps passed `shouldSyncChannel={false}` to a `` that only hosted a thread); render a + `` for replies. - **`ThreadProps`**: removed `closeThread`, `loadMoreThread`, `reloadThread`, `threadInstance`, - `additionalMessageListProps`, `additionalMessageInputProps`, `shouldUseFlashList`, `autoFocus`, + `additionalMessageListProps`, `additionalMessageFlashListProps`, `additionalMessageComposerProps`, + `shouldUseFlashList`, `autoFocus`, `disabled`, `closeThreadOnDismount` and `parentMessagePreventPress`. `thread` is required and is the `Thread` instance (it was the parent message). Configure the list and composer through their own props (they are your children now), use `onThreadDismount` for cleanup on unmount, and pass @@ -864,8 +867,8 @@ thread state from the `Thread` in `ThreadContext`, unread from the paginator sna targeting from the focus signal (§6) — all internally. `allowSendBeforeAttachmentsUpload` became `pendingUploadsEnabled` (O.3). -`thread` is **not** removed: it is retyped `LocalMessage | null` → `Thread | null`. Pass the instance, -or leave it out and the list reads the one in `ThreadContext`. +`thread` is **not** removed: it is retyped `LocalMessage | null` → `Thread | undefined`. Leave it out inside a +`` (the list reads the one in `ThreadContext`); `threadList` is gone (§10.2). The paging props `loadMore`, `loadMoreRecent`, `hasMore`, `loadingMore` and `loadingMoreRecent` are removed too: the list pages its own paginator (P.7). `markRead` is still passed. @@ -3112,14 +3115,17 @@ Message rows and previews: ## R.6 Channel list (breaking/behavioural) -- **`options.offset` is ignored and `options.limit` is the page size.** The paginator owns the offset; the rest of - `options` becomes the request options. +The list's query now lives on the app-owned `ChannelPaginator` (§18.6); these are the ways its inputs differ from +v9's `` props. + +- **The paginator owns paging.** `requestOptions` (`ChannelPaginatorRequestOptions`) takes neither `offset` nor + `limit`; set the page size with `paginatorOptions.pageSize`. v9's `options={{ offset }}` has no equivalent. - **`filters` are `stream-chat` v10's generated filter types**: custom fields are `'custom.'` keys, and each key takes one operator. ```diff - - + + + new ChannelPaginator({ client, filters: { type: 'messaging', members: { $in: [userId] }, 'custom.myField': 'x' } }) ``` - **Channels can leave a list on events.** Every routed event (`message.new`, `member.updated`, diff --git a/ai-docs/v10-breaking-changes.md b/ai-docs/v10-breaking-changes.md index 74e01abd70..398e27e70b 100644 --- a/ai-docs/v10-breaking-changes.md +++ b/ai-docs/v10-breaking-changes.md @@ -16,7 +16,9 @@ Docs impact (which getstream.io pages change) is in [`v10-docs-impact.md`](./v10 ## Scope and method - **Baseline:** `develop` @ `42662cdb1` (v9.10.x, `stream-chat` 9.54.0). **Target:** `V10` @ `c994ffd00` - (`stream-chat` 10.0.0-rc.22). The two trees were compared tip to tip; `V10` squash-merges `develop`, so ancestry + (`stream-chat` 10.0.0-rc.22) plus the stacked PRs #3866–#3869 (`refactor/stop-creating-sdk-paginator` @ + `251a76dfe`: message actions built on demand, a standalone ``, the `` lifecycle refactor and an + app-owned ``). The trees were compared tip to tip; `V10` squash-merges `develop`, so ancestry diffs are not reliable. The three `develop` commits not yet on `V10` (#3862, #3863, #3864) change no public API. - **Public API** = everything reachable from `package/src/index.ts`, the `ai-components` entry, the wrappers' `src/index.js`, `DEFAULT_COMPONENTS`, the theme, translations and `package.json` peer/dependency ranges. @@ -28,8 +30,9 @@ Docs impact (which getstream.io pages change) is in [`v10-docs-impact.md`](./v10 3. a review of all 40 PRs merged into `V10` for behaviour that types cannot show; 4. the `examples/` diff (the migration the team already did on its own apps), the deleted/rewritten tests, and a cross-check against the `stream-chat` and `stream-chat-react` v10 breaking-change lists. -- Not breaking (checked): theme shape (`Theme` is identical), `DEFAULT_COMPONENTS` slot names and the `icons` map, - native modules, podspecs, the Expo config plugin, `registerNativeHandlers` and the native handler interfaces. +- Not breaking (checked): theme shape (`Theme` is identical), the `icons` map, `DEFAULT_COMPONENTS` slot names except + the removed `ThreadMessageComposer`, native modules, podspecs, the Expo config plugin, `registerNativeHandlers` and + the native handler interfaces. --- @@ -88,6 +91,7 @@ Everything below was exported from the package root in v9 and is gone in v10. - `ThreadType` → a `Thread` instance (`client.threads.ensure({ channel, parentMessage })` builds one). — §10.1 · #3861 - `ChannelUnreadState` → `channel.messagePaginator.unreadStateSnapshot` (`UnreadSnapshotState`). — §7 · P.9 +- `MessageActionsHookProps` → nothing; `useMessageActions()` reads the message's contexts. — §15.1 · #3866 **Translations** @@ -131,26 +135,34 @@ Removed props: ``. — §O.3 · #3821 - `isMessageAIGenerated` → `` only. — Q.3 · #3852 - `isOnline` → nothing (was ignored). — P.5 +- `thread`, `threadList` → render `` next to ``, not inside it. — §10.1 · #3867 +- `allowThreadMessagesInChannel` → a `` prop. — §10.2 · #3867 +- `shouldSyncChannel` → nothing; `` always runs its lifecycle (a thread no longer needs a non-syncing + `` around it). — §10.2 · #3867 Retyped props: -- `thread`: `LocalMessage | ThreadType | null` → `Thread | null`. — §10.1 · #3861 - `channel`, `customMessageSwipeAction`: the `Channel` type is `stream-chat` v10's. — §17.5 - `t`: i18next `TFunction` → `StreamTFunction`. — §R.10 ### `` +- New required `paginator` prop: the app creates a `ChannelPaginator`, registers it with + `client.channelManager.insertPaginator({ paginator })` and removes and disposes it on logout. The list never + disposes it and does not query again when it remounts. — §18.6 · #3869 +- Removed `filters`, `sort`, `options`, `lockChannelOrder`, `queryChannelsOverride` (and the + `ChannelListQueryChannelsOverride` type) → set them on the `ChannelPaginator`: `filters` / `sort` / + `requestOptions` in its constructor, `paginatorOptions.lockItemOrder`, `paginatorOptions.doRequest`. To change the + query, update the paginator's filters / sort and `reload()`, or pass a new paginator. — §18.1 · §18.6 · #3869 +- What moved onto the paginator also changed shape since v9: `sort` is `SortParamRequest[]` + (`[{ field: 'last_message_at', direction: -1 }]`) and an empty `sort` means the backend default; filters use + `'custom.'` keys with one operator per key; `doRequest` returns `{ items }` — call + `client.queryChannelsAndHydrate(request, stateOptions)` inside; `options.offset` is ignored and `options.limit` is + the page size. — §17.5 · §N.4 · §R.6 - Removed `onAddedToChannel`, `onRemovedFromChannel`, `onChannelDeleted`, `onChannelHidden`, `onChannelVisible`, `onChannelUpdated`, `onChannelTruncated`, `onChannelMemberUpdated`, `onNewMessage`, `onNewMessageNotification` → membership and order follow `filters` / `sort`; global behaviour via `client.channelManager.addEventHandler(...)` / `client.on(...)`. — §18 · #3770 -- `sort`: `ChannelSort` object → `SortParamRequest[]` (`[{ field: 'last_message_at', direction: -1 }]`); an empty - `sort` now means the backend default. — §17.5 · §N.4 · #3749 #3783 -- `filters`: generated filter type; custom fields are `'custom.'` keys, one operator per key. — §R.6 · LLC -- `queryChannelsOverride`: `(filters, sort, options, stateOptions) => Promise` → - `(queryShape) => Promise<{ items: Channel[] }>`; use `client.queryChannelsAndHydrate(request, stateOptions)` inside. - — §18.1 · #3770 -- `options.offset` is ignored; `options.limit` becomes the page size. — §R.6 ### ``, `` @@ -165,6 +177,14 @@ Retyped props: §R.3 · §R.4 · Q.3 · P.13 · §10.1 - `Message`: `onThreadSelect` `(message, targetedMessageId?)` → `(thread: Thread)`; `updateMessage` takes one argument. — §10.1 · §9 +- `Message`: removed `handleBan`, `handleBlockUser`, `handleCopy`, `handleDelete`, `handleDeleteForMe`, `handleEdit`, + `handleFlag`, `handleMarkUnread`, `handleMute`, `handlePinMessage`, `handleQuotedReply`, `handleRetry`, + `messageActions`, `supportedReactions` — set on a `` (e.g. from a custom `Message`), they no longer reached + the action menu. Set them on ``; customise per message through the `messageActions` function. — §15.1 · + #3866 +- `threadList` removed from `Message`, `MessageContent`, `MessageReplies` and `MessageComposer`; `MessageItemView`, + `SentToChannelHeader` and `ShowThreadMessageInChannelButton` take `thread` instead. Inside a thread, + `useThreadContext().thread` is set. — §10.2 · #3867 - `MessageStatus`: `readBy` / `deliveredToCount` (counts) → `read` / `delivered` (booleans, self-derived by default). — §R.3 · #3823 - `MessageFooter`, `MessageItemView`: `members` removed. — §4 · §R.4 · #3779 @@ -179,7 +199,8 @@ Retyped props: `loadMoreThread`, `loadMoreRecentThread`, `loadChannelAroundMessage`, `loading`, `reloadChannel`, `threadInstance`, `maximumMessageLimit`, `allowSendBeforeAttachmentsUpload`, `scrollToFirstUnreadThreshold`, `shouldShowUnreadUnderlay`, `FlatList` (FlashList). The lists read their own paginator. — §12 · P.5 · P.7 · §O.1 -- `thread`: kept, retyped `LocalMessage | null` → `Thread | null`. — §12 +- `thread`: kept, retyped `LocalMessage | null` → `Thread | undefined`. — §12 +- `threadList` removed; a list inside `` renders the thread's replies. — §10.2 · #3867 - `onThreadSelect`: `(message) => void` → `(thread: Thread) => void`. — §10.1 - Older-end loading component (`FooterComponent` on `MessageList`, `HeaderComponent` on `MessageFlashList`) now receives `{ loadingMore?: boolean }`. The newer-end one gets no props and has no SDK source for `loadingMoreRecent`. @@ -194,9 +215,16 @@ Retyped props: ### `` and thread components -- `Thread`: removed `closeThread`, `loadMoreThread`, `reloadThread`, `threadInstance`; `thread` is `Thread | null`; - `closeThreadOnDismount` is still typed but no longer read. — §10.2 · §R.4 -- `ThreadFooterComponent`: `thread` prop removed (reads the thread from context). — §10.2 +- `` renders next to ``, not inside it, and renders its children + (``). — §10.1 · #3867 +- `Thread`: `thread` is required and is the `Thread` instance (it was the parent message). Removed `client`, + `closeThread`, `loadMoreThread`, `reloadThread`, `threadInstance`, `additionalMessageListProps`, + `additionalMessageFlashListProps`, `additionalMessageComposerProps`, `shouldUseFlashList`, `autoFocus`, `disabled`, + `closeThreadOnDismount` (use `onThreadDismount`) and `parentMessagePreventPress` (a `ThreadFooterComponent` prop). + Configure the list and composer through their own props; `` accepts the same config props as ``. — + §10.2 · #3736 #3861 #3867 +- `ThreadFooterComponent`: `thread` prop removed (reads the thread from context); props are + `{ parentMessagePreventPress?: boolean }`, default `true`. — §10.2 - `ThreadList`: `onThreadSelect(thread: ThreadType, channel)` → `onThreadSelect(thread: Thread)` (`thread.channel`). — §10.1 @@ -226,6 +254,7 @@ message input). ### `ChannelContext` (`useChannelContext()`) +- Removed `threadList` → `useThreadContext().thread`. — §10.2 · #3867 - Removed `members`, `read`, `watchers`, `watcherCount` → `useStateStore(channel.state, selector)`. — §4 - Removed `markRead` → `useMarkRead(channel)`. — §5 - Removed `targetedMessage`, `setTargetedMessage`, `highlightedMessageId` → `messagePaginator.messageFocusSignal`, @@ -259,20 +288,25 @@ message input). - Removed `targetedMessage` → `useIsTargetedMessage(id)`. — §6 - Removed `initialScrollToFirstUnreadMessage`, `quotedMessage`. — P.4 - Removed `isMessageAIGenerated` → `useChatContext()`. — Q.3 +- New `onAlsoSentToChannelHeaderPress` (moved from `ThreadContext`). — §10.2 · #3867 ### `MessageContext` (`useMessageContext()`) -- Removed `members`, `readBy`, `deliveredToCount`, `isMessageAIGenerated`. — §4 · §R.3 · Q.3 +- Removed `members`, `readBy`, `deliveredToCount`, `isMessageAIGenerated`, `threadList`. — §4 · §R.3 · Q.3 · §10.2 - `onThreadSelect`: `(message, targetedMessageId?)` → `(thread: Thread)`. — §10.1 +- New `messageActionHandlers` (`MessageActionHandlerCallbacks`, stable for the row's lifetime) and + `isVeryLastMessage`. — §15.1 · #3866 ### `ThreadContext` (`useThreadContext()`) -- Now `{ allowThreadMessagesInChannel, thread?: Thread | null, onAlsoSentToChannelHeaderPress? }`. `thread` was the - parent `LocalMessage`; it is now the `Thread` (parent via `thread.state`). +- Now `{ thread?: Thread, allowThreadMessagesInChannel }`. `thread` was the parent `LocalMessage`; it is now the + `Thread` (parent via `thread.state`), and `null` is no longer part of the type — use `undefined` for "no thread". Only `` provides it, and `useThreadContext()` no longer throws: outside + a thread it returns `{ thread: undefined, allowThreadMessagesInChannel: false }`. — §10 · #3867 +- `onAlsoSentToChannelHeaderPress` moved to `MessagesContext`. — §10.2 · #3867 - Removed `threadMessages`, `threadHasMore`, `threadLoadingMore`, `threadLoadingMoreRecent`, `loadMoreThread`, `loadMoreRecentThread`, `reloadThread`, `setThreadLoadingMore`, `openThread`, `closeThread`, `threadInstance`, - `parentMessagePreventPress` → `thread.messagePaginator`, `thread.reload()`, prop-driven open/close. — §10 · §10.1 · - §R.4 + `parentMessagePreventPress` → `thread.messagePaginator`, `thread.reload()`, render `` to open. — §10 · + §10.1 · §R.4 - `onAlsoSentToChannelHeaderPress` payload: `{ parentMessage }` → `{ thread }`. — §10.1 ### `ThreadsContext` @@ -300,13 +334,16 @@ message input). §17.5 - `useChannelMembersState(channel)` → `(channel, pick?)` (additive). — #3854 - `useCreateChatClient({ options })`: `StreamChatOptions` → `DeepPartial` (former options go under - `client`). — §17.7 · #3855 + `client`); `userData` is typed `ClientUser`, what `connectUser` accepts. — §17.7 · #3855 #3871 - `useQueryReminders()`: data typed `ReminderResponseData[]` (was `ReminderResponse[]`), timestamps in ns. — §R.8 - `useMessageList(params)`: `{ threadList }` → `{ paginator }` (no longer reads context); also returns `maxLoadedItems`. — P.8 -- `useMessageActions(params)` (`MessageActionsHookProps` / `MessageActionsParams`): `openThread` removed, - `onThreadSelect` takes a `Thread`, `updateMessage` takes one argument. — §9 · §10.1 -- `useMessageActionHandlers(params)`: message ops come from `MessageOperations`, not `MessagesContextValue`. — §9 +- `useMessageActions(params)` → `useMessageActions()` and `useMessageActionHandlers(params)` → + `useMessageActionHandlers()` (returns `MessageActionHandlerCallbacks`): no arguments; both read the contexts of the + message they are called in, so they must run inside a message (a custom `Message` or `MessageActionList`). The + `messageActions` function's `MessageActionsParams` lost `openThread`; its `onThreadSelect` takes a `Thread` and + `updateMessage` one argument. — §15.1 · §9 · §10.1 · #3866 +- `useMessagePaginator()`: returns `MessagePaginator` (no longer `| undefined`). — #3867 - `useCreateMessagesContext`, `useCreateThreadContext`, `useCreateMessageInputContext`, `useCreateMessageContext`: inputs narrowed to the new context shapes. — §15 · §R.4 - `useChannelOwnCapabilities(channel)`: one overload `(channel?)`. — K.6 @@ -380,9 +417,13 @@ message input). ## 9. Component slots, icons and theme -- No `DEFAULT_COMPONENTS` slot or icon was added, removed or renamed, and the `Theme` type is identical. What changed is - the **props and context** those slots receive (sections 3–4) — a custom slot written against v9 contexts must be - re-checked. +- One slot was removed: `ThreadMessageComposer`. Render the composer you want as a child of ``. — §10.2 · + #3867 +- No other `DEFAULT_COMPONENTS` slot or icon was added, removed or renamed, and the `Theme` type is identical. What + changed is the **props and context** those slots receive (sections 3–4) — a custom slot written against v9 contexts + must be re-checked. +- `MessageActionList` receives the same props as before, but its list is now built only while a message's menu is + open. — §15.1 · #3866 - Behaviour of default slots: `NetworkDownIndicator` shows "Waiting for network..." (device offline, new key `common.waitingForNetwork.text`) or "Reconnecting..." (socket down after the settle delay) and no longer shows a load error. `ChannelListHeaderNetworkDownIndicator` and `NotificationAnnouncer` follow the same signals. — §L.7 · @@ -503,6 +544,7 @@ apps' own code. - `sendMessage` errors reject instead of being swallowed. — §16.2 - Sending no longer clears `type: 'error'` messages from the list. - Initial page size is 25 (v9 ≈100). — §16.1 +- The `messageActions` function runs when a message's menu opens, not on every render of every row. — §15.1 · #3866 **``** — §L.4 · §R.5 @@ -516,15 +558,20 @@ apps' own code. - Poll creation button shows before the server `polls` flag arrives. - Composer `maxLength` follows `messageComposer.config.text.maxLengthOnSend`. -**``** — §L.10 · §R.5 +**``** — §10.3 · §L.10 · §R.5 +- Renders on its own next to ``; opening a thread, or pressing scroll-to-bottom in one, no longer marks the + channel read. — #3867 - Shows `LoadingErrorIndicator` instead of the list and composer when the first reply page fails. - Does not mark a thread with no replies as read. **Channel list** — §18.5 · §N.4 · §R.6 -- One shared `client.channelManager`; lists are a projection of `filters` / `sort` (no promote-to-top, channels can - leave a list when they stop matching). +- One shared `client.channelManager`; each list is a projection of its paginator's `filters` / `sort` (no + promote-to-top, channels can leave a list when they stop matching). +- The app owns the list's `ChannelPaginator`: the list loads a first page only when the paginator has none (first + mount, after `disconnectUser` empties it, or a new paginator), never re-queries on prop changes and never disposes + it. `disconnectUser` empties registered paginators but keeps them registered. — §18.6 · #3869 - Recovery re-runs each list's first page; `` has no reconnect listener. - An empty `sort` means the backend default. - Channel preview typing indicator ignores typing inside threads; `MessageFooter` shows the sender name when @@ -547,6 +594,7 @@ Only affects imports from `stream-chat-react-native-core/src/…` or `lib/…`. These replace removed APIs and need documentation: +- Components: `` as a standalone, sibling-of-`` component that renders its children. - Hooks: `useMessagePaginator`, `useMessageOperations` (+ `MessageOperations` type), `useMarkRead`, `useIsTargetedMessage`, `useChannel`, `useIsMessageReadByOthers`, `useIsMessageDeliveredToOthers`, `usePendingUploadsEnabled`, `useConnectionLifecycle`, `useNetworkConnectionState(Selector)`, @@ -558,8 +606,11 @@ These replace removed APIs and need documentation: `TranslationKey`, `TranslationCatalog`, `TranslationDictionary`, `LooseTranslationDictionary`, `StreamTFunction`, `DynamicTranslationKey`, `Streami18nOptions`, `CustomFormatters`, `FormatterFactory`, `PredefinedFormatters`, `DayjsLocaleConfig`, `DateTimeParserModule`, `DurationFormatterOptions`, `TimestampFormatterOptions`. -- Context fields / props: `ChannelsContext.paginator`, `ChatContext.getAppSettings`, `ChannelListView.paginator`, - `MessageStatus.read` / `delivered`, `MessageList.pendingUploadsEnabled`, `TypingIndicatorContainer.threadId`. +- Context fields / props: `` (required), `ChannelsContext.paginator`, + `ChatContext.getAppSettings`, `ChannelListView.paginator`, `MessageContext.messageActionHandlers` (type + `MessageActionHandlerCallbacks`), `MessageContext.isVeryLastMessage`, `MessagesContext.onAlsoSentToChannelHeaderPress`, + `Thread.allowThreadMessagesInChannel` and the `` config props on ``, `MessageStatus.read` / + `delivered`, `MessageList.pendingUploadsEnabled`, `TypingIndicatorContainer.threadId`. ## 16. Known issues found by the audit diff --git a/ai-docs/v10-docs-impact.md b/ai-docs/v10-docs-impact.md index 3b58fe53a6..31e70bf3ee 100644 --- a/ai-docs/v10-docs-impact.md +++ b/ai-docs/v10-docs-impact.md @@ -1,36 +1,38 @@ # stream-chat-react-native v10 — docs impact assessment -Scope: `getstream.io/content/docs/chat-sdk/react-native/v9-latest/` (302 pages), the source the v10 docs will be derived from. The breaking changes themselves are listed in [`v10-breaking-changes.md`](./v10-breaking-changes.md); section references like §4 or P.11 point to [`ai-migration-v9-to-v10.md`](./ai-migration-v9-to-v10.md). Inputs: the machine-generated v9.54 -> v10.0.0-rc.22 public-surface diff (1,295 entries), compile errors from building the v9 sample apps against v10 (SampleApp, ExpoMessaging), the agent migration ledger (`ai-docs/ai-migration-v9-to-v10.md`, `i18n-v10-migration.md`, `channel-state-ui-migration.md`) and the `stream-chat` v10 migration guides. +Covers the `V10` branch plus the stacked PRs #3866 (message actions take no arguments), #3867 (standalone ``), #3868 (internal `` lifecycle refactor, no public API change) and #3869 (app-owned `ChannelPaginator` on ``). -Method: a term scanner (`scan.js`) matched every removed export, removed/retyped prop (scoped to its component: JSX attribute, props-table row, or heading on the owning page), removed context field (scoped to `useXContext()` / `useContext(XContext)` destructuring and the context page table), changed hook/function signatures, i18n keys/APIs, `stream-chat` method/type changes, Date usage on server timestamps, custom fields read at the root, sort objects and deep imports. 119 pages had raw hits (~900); each was triaged by hand (false positives removed, missed items added as "manual" findings). Line numbers refer to the v9-latest files. +Scope: `getstream.io/content/docs/chat-sdk/react-native/v9-latest/` (302 pages), the source the v10 docs will be derived from. The breaking changes themselves are listed in [`v10-breaking-changes.md`](./v10-breaking-changes.md); section references like §4 or P.11 point to [`ai-migration-v9-to-v10.md`](./ai-migration-v9-to-v10.md). Inputs: the machine-generated v9.54 -> v10.0.0-rc.22 public-surface diff (1,295 entries), compile errors from building the v9 sample apps against v10 (SampleApp, ExpoMessaging), the agent migration ledger (`ai-docs/ai-migration-v9-to-v10.md`, `i18n-v10-migration.md`, `channel-state-ui-migration.md`) and the `stream-chat` v10 migration guides. The stacked PRs were added from their descriptions, the ledger sections written with them (§10, §10.1-10.3, §15.1, §18.1, §18.6) and the stacked code. + +Method: a term scanner (`scan.js`) matched every removed export, removed/retyped prop (scoped to its component: JSX attribute, props-table row, or heading on the owning page), removed context field (scoped to `useXContext()` / `useContext(XContext)` destructuring and the context page table), changed hook/function signatures, i18n keys/APIs, `stream-chat` method/type changes, Date usage on server timestamps, custom fields read at the root, sort objects and deep imports. 119 pages had raw hits (~900); each was triaged by hand (false positives removed, missed items added as "manual" findings). The stacked PRs were covered by a second grep pass (`` inside ``, ``, `threadList`, `ThreadMessageComposer`, the removed `` props, `` and every bare ``, message-action handlers / `messageActions` / `supportedReactions` outside ``, `useMessageActions(`/`useMessageActionHandlers(` with arguments, `onAlsoSentToChannelHeaderPress`, `allowThreadMessagesInChannel`), triaged the same way (false positives: `supportedReactions` props on the reaction-list components and `handleRetry` on upload previews are unchanged; `` in a custom wrapper still compiles). Line numbers refer to the v9-latest files. ## Summary -**99 pages need changes**, out of 302: +**109 pages need changes**, out of 302 (99 on `V10` alone; the stacked PRs add 10 pages and raise 4): | Severity | Pages | Meaning | | --- | --- | --- | | Delete / replace page | 6 | Documents an API removed in v10; delete or replace with a page for the successor API | -| Major rewrite | 20 | Core concept or most snippets on the page no longer apply | -| Snippet / prop-table update | 54 | Specific snippets, props/values tables or types need edits | -| Mention only | 19 | Wording, a type name, a version number or a cross-reference | +| Major rewrite | 22 | Core concept or most snippets on the page no longer apply | +| Snippet / prop-table update | 56 | Specific snippets, props/values tables or types need edits | +| Mention only | 25 | Wording, a type name, a version number, a cross-reference, or a one-attribute snippet edit (`paginator={paginator}` on a bare ``) | -Rough sizing (assumption: a writer familiar with v10; excludes review): DELETE ~0.5 h each incl. redirect (6); MAJOR ~0.5-1 day each (20); UPDATE ~1-2 h each (54); MENTION ~15 min each (19); plus 12 new pages/sections below (~0.5-1 day each, the v9->v10 migration guide ~3 days). Total roughly 6-8 writer-weeks. +Rough sizing (assumption: a writer familiar with v10; excludes review): DELETE ~0.5 h each incl. redirect (6); MAJOR ~0.5-1 day each (22); UPDATE ~1-2 h each (56); MENTION ~15 min each (25); plus 12 new pages/sections below (~0.5-1 day each, the v9->v10 migration guide ~3 days). Total roughly 6.5-8.5 writer-weeks (the stacked PRs add about half a week: two more major rewrites, and heavier thread and channel-list rewrites). The remaining ~200 pages had no hits or only false positives (e.g. `ChannelEditDetails*` still takes `doFileUploadRequest`; `localId` props on upload indicators are unchanged; `client.blockUser`/`muteUser` calls are unchanged). ### Top 10 most impacted pages -1. `02-ui-components/10-thread/02-thread-context.md` — Major rewrite (25 distinct breaking items, 29 hits) +1. `02-ui-components/10-thread/02-thread-context.md` — Major rewrite (27 distinct breaking items, 32 hits) 2. `02-ui-components/06-channel/02-channel-context.md` — Major rewrite (23 distinct breaking items, 33 hits) -3. `02-ui-components/06-channel/01-channel.md` — Major rewrite (21 distinct breaking items, 27 hits) -4. `02-ui-components/05-channel-list/01-channel-list.md` — Major rewrite (15 distinct breaking items, 19 hits) -5. `05-advanced-guides/09-translations.md` — Major rewrite (5 distinct breaking items, 46 hits) -6. `03-ui-cookbook/03-customize-channel-list.md` — Major rewrite (13 distinct breaking items, 15 hits) -7. `02-ui-components/10-thread/01-thread.md` — Major rewrite (11 distinct breaking items, 14 hits) +3. `02-ui-components/06-channel/01-channel.md` — Major rewrite (24 distinct breaking items, 30 hits) +4. `02-ui-components/10-thread/01-thread.md` — Major rewrite (22 distinct breaking items, 30 hits) +5. `02-ui-components/05-channel-list/01-channel-list.md` — Major rewrite (19 distinct breaking items, 39 hits) +6. `03-ui-cookbook/03-customize-channel-list.md` — Major rewrite (16 distinct breaking items, 23 hits) +7. `05-advanced-guides/09-translations.md` — Major rewrite (5 distinct breaking items, 46 hits) 8. `02-ui-components/04-chat/02-chat-context.md` — Major rewrite (10 distinct breaking items, 12 hits) -9. `02-ui-components/05-channel-list/14-channels-context.md` — Major rewrite (8 distinct breaking items, 10 hits) -10. `05-advanced-guides/17-channel-read-state.md` — Major rewrite (8 distinct breaking items, 10 hits) +9. `02-ui-components/05-channel-list/14-channels-context.md` — Major rewrite (9 distinct breaking items, 12 hits) +10. `05-advanced-guides/17-channel-read-state.md` — Major rewrite (9 distinct breaking items, 12 hits) ### Pages to delete / replace @@ -43,6 +45,7 @@ The remaining ~200 pages had no hits or only false positives (e.g. `ChannelEditD ### Major rewrites +- `02-ui-components/01-overview.md` (new with the stacked PRs) - `02-ui-components/04-chat/02-chat-context.md` - `02-ui-components/04-chat/03-hooks/03-use-create-chat-context.md` - `02-ui-components/05-channel-list/01-channel-list.md` @@ -58,6 +61,7 @@ The remaining ~200 pages had no hits or only false positives (e.g. `ChannelEditD - `04-state-and-offline-support/01-state-overview.md` - `05-advanced-guides/02-date-time-formatting.md` - `05-advanced-guides/05-typescript.md` +- `05-advanced-guides/07-stream_chat_with_navigation.md` (new with the stacked PRs) - `05-advanced-guides/09-translations.md` - `05-advanced-guides/13-push-notifications.md` - `05-advanced-guides/14-accessibility.md` @@ -67,25 +71,26 @@ The remaining ~200 pages had no hits or only false positives (e.g. `ChannelEditD ### Cross-cutting themes (drive most of the edits) 1. **State moved out of React context into `stream-chat` stores.** `TypingContext`, `PaginatedMessageListContext` and most of `ChannelContext`, `ThreadContext`, `ChannelsContext`, `ChatContext`, `MessagesContext` values are gone; replaced by `useStateStore(channel.state | channel.messagePaginator.state | thread.state | paginator.state, selector)` plus new hooks `useMessagePaginator`, `useMessageOperations`, `useMarkRead`, `useIsTargetedMessage`. Every context reference page and every snippet that destructures those values changes. -2. **Threads are `Thread` instances everywhere.** ``, ``, `ThreadContext.thread`, `onThreadSelect` on `MessageList`/`MessageFlashList`/`Message`/`ThreadList` all take/yield `Thread`; `ThreadType` and the `{ thread, threadInstance }, channel` callback shape are gone; imperative `openThread`/`closeThread`/`loadMoreThread`/`reloadThread` removed. Touches thread pages, navigation, troubleshooting, overview, livestream, custom thread list. +2. **Threads are `Thread` instances, rendered by a standalone `` next to ``.** `` (required `Thread` instance) renders its children (`` / `` + ``) and needs no `` above it; the v9 pattern `` is gone, and so are ``, `threadList` everywhere (ChannelContext, MessageContext, the `Message`/`MessageList`/`MessageFlashList`/`MessageComposer`/`MessageContent`/`MessageReplies` props; use `useThreadContext().thread`), the `ThreadMessageComposer` slot and ``'s list/composer props (`additionalMessage*Props`, `shouldUseFlashList`, `autoFocus`, `disabled`, `closeThreadOnDismount`, `client`, the loaders). `ThreadContext` is `{ thread?, allowThreadMessagesInChannel }`, provided only by `` (no throw outside it). `onThreadSelect` on `MessageList`/`MessageFlashList`/`Message`/`ThreadList` yields a `Thread`; `ThreadType` and the `{ thread, threadInstance }, channel` callback shape are gone; imperative `openThread`/`closeThread`/`loadMoreThread`/`reloadThread` removed. Consequences for every guide: the channel screen no longer needs to know about the open thread (the "keep `thread` on `` in sync" advice in navigation/troubleshooting is obsolete), opening a thread no longer marks the channel read, and **configuration props set on `` (`messageActions`, `handleX`, `supportedReactions`, `keyboardVerticalOffset`, `topInset`, `onAlsoSentToChannelHeaderPress`, …) no longer reach the thread: pass the same set to ``**. Touches thread pages, overview, navigation, troubleshooting, livestream, message customization, message actions/reactions/blocking recipes, custom thread list, composer/message prop tables. 3. **`` behaviour props -> `client.config`.** `doMarkReadRequest`, `doSendMessageRequest`, `preSendMessageRequest`, `doUpdateMessageRequest`, `doFileUploadRequest`, throttle props, `maximumMessageLimit`, `allowSendBeforeAttachmentsUpload`, `messageId` are removed; replacements are `client.config.set({ channel: { requestHandlers, messagePaginator }, messageComposer: { attachments } })` and paginator jumps. Channel page, performance guide, file compression, message input customization, date separators. -4. **ChannelList is a projection of `filters` + `sort`.** 10 per-event override props and `useChannelUpdated` removed; `sort` is `SortParamRequest[]`; `queryChannelsOverride` is a paginator `doRequest`; `ChannelsContext` holds config + `paginator`. -5. **i18n v10.** English-only bundle (12 `*Translations` exports gone), stable dotted keys (v9 keys were the English copy; old keys fail silently), typed `t` (`asDynamicKey` for app keys), `a11y/*` and `timestamp/*` namespaces renamed, `Streami18n` API changes (`setLanguage` -> void, `getTranslators` -> `init`, listeners -> `state` store, single options object). Translations, date-time formatting, accessibility, reminders and every snippet calling `t("English copy")`. -6. **`stream-chat` v10 surface in snippets.** Server timestamps are unix-ns `TimestampNS` numbers (`created_at.toDateString()`, `.getTime()`, `new Date(ts)` break); custom fields live under `.custom` (`channel.data.custom.name`, `message.custom.ai_generated`, `attachment.custom.mime_type`); methods take request objects (`channel.sendMessage({ message })`, `queryChannelsAndHydrate({ filter_conditions, sort })`, `createDevice/deleteDevice`, `getMessage({ id })`); `client.activeChannels` -> `client.channelManager`; options are a config tree; `connection.changed` removed; server-side APIs removed (`api_secret`, `updateAppSettings`); `moderation_details` -> `moderation`. +4. **`` renders an app-owned `ChannelPaginator`.** `paginator` is a required prop; `filters`, `sort`, `options`, `lockChannelOrder`, `queryChannelsOverride` (and the `ChannelListQueryChannelsOverride` type) are removed. The app creates `new ChannelPaginator({ client, id, filters, sort, requestOptions: options, paginatorOptions: { lockItemOrder, doRequest } })` (from `stream-chat`) with the user, registers it with `client.channelManager.insertPaginator({ paginator })` (unregistered = no live updates), and on logout calls `removePaginator(paginator)` + `paginator.dispose()`. Changing the query is a paginator change (`paginator.staticFilters` / `sort` + `reload()`, or a new paginator with a new `id`); a remounted list shows its channels without re-querying. Also: 10 per-event override props and `useChannelUpdated` removed (the list is a projection of the paginator's query; global handling via `client.channelManager.addEventHandler`, floating via `paginator.boost`); `sort` is `SortParamRequest[]`; `ChannelsContext` holds config + `paginator`. Every `` snippet in the docs (16 pages) needs `paginator={paginator}`; 7 pages where that is the only change are graded Mention only. +5. **Message actions are built when the menu opens.** `useMessageActions()` / `useMessageActionHandlers()` take no arguments and must be called inside a message (custom `Message`, custom `MessageActionList`, overlay content); `MessageActionsHookProps` removed; the row's handlers are on `MessageContext.messageActionHandlers` (`MessageActionHandlerCallbacks`). `handleX`, `messageActions` and `supportedReactions` are no longer `` props (passing them to a `` does not reach the menu): set them on `` (and ``); per-message customisation goes through the `messageActions` function, which now runs when a message's menu opens rather than on every render. The v9 docs already configure all of this on ``, so the docs impact is small (message actions recipe, MessagesContext/MessageContext tables). +6. **i18n v10.** English-only bundle (12 `*Translations` exports gone), stable dotted keys (v9 keys were the English copy; old keys fail silently), typed `t` (`asDynamicKey` for app keys), `a11y/*` and `timestamp/*` namespaces renamed, `Streami18n` API changes (`setLanguage` -> void, `getTranslators` -> `init`, listeners -> `state` store, single options object). Translations, date-time formatting, accessibility, reminders and every snippet calling `t("English copy")`. +7. **`stream-chat` v10 surface in snippets.** Server timestamps are unix-ns `TimestampNS` numbers (`created_at.toDateString()`, `.getTime()`, `new Date(ts)` break); custom fields live under `.custom` (`channel.data.custom.name`, `message.custom.ai_generated`, `attachment.custom.mime_type`); methods take request objects (`channel.sendMessage({ message })`, `queryChannelsAndHydrate({ filter_conditions, sort })`, `createDevice/deleteDevice`, `getMessage({ id })`); `client.activeChannels` -> `client.channelManager`; options are a config tree; `connection.changed` removed; server-side APIs removed (`api_secret`, `updateAppSettings`); `moderation_details` -> `moderation`. ### New pages / sections needed (concepts introduced in v10 with no docs) | New page / section | Suggested location | Replaces / absorbs | | --- | --- | --- | -| **Upgrading from v9** migration guide (human version of the ledger: contexts -> stores, threads, Channel props -> `client.config`, ChannelList orchestrator, i18n, `stream-chat` v10 type surface, behavioural changes) | `06-migration-guides/01-upgrading-from-v9.md` | — (link from every DELETE page) | +| **Upgrading from v9** migration guide (human version of the ledger: contexts -> stores, threads (standalone `` next to ``), Channel props -> `client.config`, app-owned `ChannelPaginator`, message-action hooks without arguments, i18n, `stream-chat` v10 type surface, behavioural changes) | `06-migration-guides/01-upgrading-from-v9.md` | — (link from every DELETE page) | | **Message paginator**: `useMessagePaginator`, `channel.messagePaginator` / `thread.messagePaginator`, `toTail`/`toHead`, `hasMoreTail`/`hasMoreHead`/`isLoading`/`lastQueryError`, `jumpToMessage` / `jumpToTheFirstUnreadMessage` / `jumpToTheLatestMessage`, `messageFocusSignal` + `useIsTargetedMessage` + `DEFAULT_HIGHLIGHT_DURATION`, `unreadStateSnapshot`, `maxLoadedItems`, page size 25 | `02-ui-components/07-message-list/` or `04-state-and-offline-support/` | `13-contexts/06-paginated-message-list-context.md`, `Channel.messageId`, `maximumMessageLimit` | | **Instance configuration (`client.config`)**: `client.config.set(...)`, `setSetupFunction("messageComposer", fn)`, request handlers (`markReadRequest`, `sendMessageRequest`, `retrySendMessageRequest`, `updateMessageRequest`), `messageComposer.attachments.{doUploadRequest, customCdn, pendingUploadsEnabled}`, `channel.messagePaginator.{pageSize, stateThrottleMs, maxLoadedItems}`, resolved gates (`channel.config.typingEvents.enabled`, `channel.serverConfig`), client options as config tree | `05-advanced-guides/` (new) | Channel `do*Request` examples, throttle props, `allowSendBeforeAttachmentsUpload`, `setMessageComposerSetupFunction` | | **Channel state & `useStateStore` patterns**: unified `channel.state` store (`members`, `read`, `watchers`, `watcherCount`, `typing`, `membership`, `aiState`, `pendingDisposal`, `active`), module-scope selectors, `channel.activate()` (returns its release function), `useChannel()`, thread `thread.state` | `04-state-and-offline-support/01-state-overview.md` (rewrite) + `18-channel-state-hooks/` index | `13-contexts/10-typing-context.md`, `useSelectedChannelState`, ChannelContext scalar fields | | **Connection state & recovery**: `useNetworkConnectionState(Selector)`, `useWSConnectionState(Selector)`, `useSettledWSConnectionHealth`, `useConnectionLifecycle`, `netInfoStatusReporter`, `client.connectionRecovery` (enable/disable, `recover()`), `connection.recovered` semantics | `02-ui-components/04-chat/03-hooks/` (replace use-is-online) + advanced guide | `04-use-is-online.md`, `ChatContext.isOnline/connectionRecovering`, `recoverStateOnReconnect` | -| **Message operations**: `useMessageOperations()` (`sendReaction`, `deleteReaction`, `deleteMessage` with snake_case options, `retrySendMessage`, `removeMessage`, `updateMessage`), optimistic lifecycle owned by `stream-chat` | hooks section near MessagesContext | removed `MessagesContext` ops | +| **Message operations**: `useMessageOperations()` (`sendReaction`, `deleteReaction`, `deleteMessage` with snake_case options, `retrySendMessage`, `removeMessage`, `updateMessage`), optimistic lifecycle owned by `stream-chat`; message-action hooks `useMessageActions()` / `useMessageActionHandlers()` (no arguments, call inside a message or a custom `MessageActionList`), `MessageContext.messageActionHandlers` | hooks section near MessagesContext | removed `MessagesContext` ops, `MessageActionsHookProps` | | **Mark read**: `useMarkRead(channel)`, reporter-driven cadence, `channel.markReadLocally()` | merge into rewritten `17-channel-read-state.md` | `ChannelContext.markRead`, `doMarkReadRequest` | -| **Thread paginator / Thread instance**: `client.threads.ensure({ channel, parentMessage })`, `thread.messagePaginator`, `thread.state.parentMessage`, prop-driven open/close, `client.threads.paginator` for the list | `02-ui-components/10-thread/` | ThreadContext loaders, `ThreadType` | -| **Channel list orchestrator**: `client.channelManager` (`addEventHandler`, `get`, `values`), list paginator (`boost`, `state.items/hasMoreTail/lastQueryError`), `lockItemOrder`, new `queryChannelsOverride` contract | `02-ui-components/05-channel-list/` | event override props, `useChannelUpdated`, `ChannelsContext` state fields | +| **Standalone `` / Thread instance**: `` rendered as a sibling of `` (own screen or in place of the channel UI), composing ``/`` + `` as children; app-owned selected-thread state (`onThreadSelect` -> state/navigation params, `onThreadDismount` to clear it); passing the same configuration props as ``; `allowThreadMessagesInChannel` / `notificationHostId` / `onThreadDismount`; `parentMessagePreventPress` on `ThreadFooterComponent`; `useThreadContext().thread` to detect a thread subtree; `client.threads.ensure({ channel, parentMessage })` from a message/push/deep link, `thread.messagePaginator`, `thread.state.parentMessage`, thread read state (marks itself read while open; opening it does not mark the channel read), `client.threads.paginator` for the list | `02-ui-components/10-thread/` | `` pattern, `` list/composer props, `ThreadMessageComposer`, ThreadContext loaders, `ThreadType` | +| **App-owned `ChannelPaginator`**: creating it with the user (`new ChannelPaginator({ client, id, filters, sort, requestOptions, paginatorOptions: { pageSize, lockItemOrder, doRequest } })`, with a `useCreateChatClient` variant: `useMemo` on `client` + an effect that inserts/removes it), registering it (`client.channelManager.insertPaginator({ paginator })`; unregistered lists get no events), disposing on logout (`removePaginator` + `dispose()` before `disconnectUser()`), changing the query (`staticFilters` / `sort` + `reload()`, or a new paginator with a new `id`), unique `id` per list for multiple lists, remount without re-query; plus the orchestrator around it: `client.channelManager` (`addEventHandler`, `get`, `values`), `paginator.boost`, `state.items/hasMoreTail/lastQueryError`, `doRequest` returning `{ items }` via `queryChannelsAndHydrate` | `02-ui-components/05-channel-list/` | `filters`/`sort`/`options`/`lockChannelOrder`/`queryChannelsOverride` props, event override props, `useChannelUpdated`, `ChannelsContext` state fields | | **i18n v10**: dotted keys + key-map table, `TranslationDictionary` / `LooseTranslationDictionary`, `asDynamicKey`, registering a language (dictionary + dayjs locale + calendar formats), plurals, `Streami18n` state store / `init()` / `setLanguage` void, `@stream-io/i18n`, dayjs single copy, `getDateString({ messageCreatedAt })`, `getCalendarDateStringForA11y` | rewrite `05-advanced-guides/09-translations.md` (+ `02-date-time-formatting.md`) | bundled language list, v9 API sections | | **Timestamps in v10**: `TimestampNS`, `nsToDate`/`convertTimestampToDate`/`msToNs`/`nowNs`, request fields stay `Date` | `05-advanced-guides/05-typescript.md` or a short guide | ad-hoc `Date` usage in cookbook snippets | | **Custom data under `.custom`** (reading `channel.data.custom.*`, `message.custom.*`, `attachment.custom.*`, custom event payload under `event.custom`) | `05-advanced-guides/05-typescript.md` | — | @@ -117,17 +122,31 @@ Format: severity · what must change · scanner-detected breaking items with lin ### 02-ui-components/ -#### `01-overview.md` — Snippet / prop-table update +#### `01-overview.md` — Major rewrite -- **Change:** Fix channel-creation snippets (custom fields under `custom`, members as `{ user_id }` objects); drop "read it from `client.activeChannels`" (L240) -> `client.channelManager.get(cid)`; Thread example: `useState` -> `useState()` since `onThreadSelect` now yields a `Thread` instance and `` takes a `Thread`. -- **Detected:** `channelCreateMembers` (L111); `activeChannels` (L240); `Channel.thread` (L374); `MessageList.onThreadSelect` (L381) +- **Change:** Fix channel-creation snippets (custom fields under `custom`, members as `{ user_id }` objects); drop "read it from `client.activeChannels`" (L240) -> `client.channelManager.get(cid)`. Change (stacked PRs): this walkthrough's flow changes at two steps. "Connecting a User" (L32-56) gains creating + registering a `ChannelPaginator` (and removing/disposing it next to `disconnectUser`, L51-54), and every `` snippet (L226, L258, L292, L335, L387) passes `paginator={paginator}`; the "See the ChannelList docs for filtering and sorting" pointer (L211) now points at the paginator. The Thread section (L345-393) is rewritten: `` with `` + `` as children renders next to `` (e.g. `thread ? : `), not inside it; drop `thread` / `threadList` on `` (L374-375); `useState` -> `useState()`; `keyboardVerticalOffset` / `topInset` must be passed to `` as well. +- **Detected:** `channelCreateMembers` (L111); `activeChannels` (L240); `Channel.thread` (L374); `MessageList.onThreadSelect` (L381); `ChannelList.paginator` required (L226, L258, L292, L335, L387); `Channel.threadList` (L375); `` inside `` (L378) - **Manual:** L97-99, L110-113: `client.channel(type, id?, { name, members: [ids] })` -> `{ custom: { name }, members: [{ user_id }] }` +- **Manual:** L51-54: logout snippet should also `client.channelManager.removePaginator(paginator)` + `paginator.dispose()` + +#### `02-theming.md` — Mention only + +- **Change:** (stacked PRs) Bare `` snippets need the now-required `paginator` prop. +- **Detected:** `ChannelList.paginator` required (L68, L110) + +### 02-ui-components/03-overlay-provider/ + +#### `01-overlay-provider.md` — Mention only + +- **Change:** (stacked PRs) Bare `` snippet needs the now-required `paginator` prop. +- **Detected:** `ChannelList.paginator` required (L26) ### 02-ui-components/04-chat/ #### `01-chat.md` — Mention only -- **Change:** Note `isMessageAIGenerated` is now only a `Chat` prop (removed from `Channel`); `useCreateChatClient` options are the config tree. No prop removals on `Chat` itself (`channelManager` prop is gone but was never documented). +- **Change:** Note `isMessageAIGenerated` is now only a `Chat` prop (removed from `Channel`); `useCreateChatClient` options are the config tree. No prop removals on `Chat` itself (`channelManager` prop is gone but was never documented). Change (stacked PRs): the `` snippet needs `paginator={paginator}`. +- **Detected:** `ChannelList.paginator` required (L56) #### `02-chat-context.md` — Major rewrite @@ -136,7 +155,7 @@ Format: severity · what must change · scanner-detected breaking items with lin #### `03-hooks/01-use-create-chat-client.md` — Snippet / prop-table update -- **Change:** `options` type `StreamChatOptions` -> `DeepPartial` (former options now live under `client: {...}`, e.g. `{ client: { axiosRequestConfig: { timeout } } }`). SDK caveat: `userData` is still typed `OwnUserResponse | UserResponse`, which in stream-chat v10 requires server fields, so the documented `{ id, name, image }` literal fails TS unless the SDK retypes it to `ClientUser` (see SDK follow-ups). +- **Change:** `options` type `StreamChatOptions` -> `DeepPartial` (former options now live under `client: {...}`, e.g. `{ client: { axiosRequestConfig: { timeout } } }`). `userData` is typed `ClientUser`, so the documented `{ id, name, image }` literal is fine (see SDK follow-ups). Change (stacked PRs): add a short "channel list paginator" note or link — with `useCreateChatClient` there is no `connectUser` call site, so the paginator is created in a `useMemo` on the returned `client` and inserted/removed+disposed in an effect (the ExpoMessaging sample's `AppProvider` pattern). - **Detected:** `StreamChatOptions` (L60) #### `03-hooks/02-use-app-settings.md` — Delete / replace page @@ -164,23 +183,49 @@ Format: severity · what must change · scanner-detected breaking items with lin #### `01-channel-list.md` — Major rewrite -- **Change:** Remove the 10 per-event override props (`onAddedToChannel`, `onChannelDeleted`, `onChannelHidden`, `onChannelMemberUpdated`, `onChannelTruncated`, `onChannelUpdated`, `onChannelVisible`, `onNewMessage`, `onNewMessageNotification`, `onRemovedFromChannel`) and the event-behaviour prose (L142) -> list is a projection of `filters` + `sort`; global handling via `client.channelManager.addEventHandler`, floating via `paginator.boost`, `lockItemOrder`. `sort` is now `SortParamRequest[]` (`[{ field: "last_message_at", direction: -1 }]`) - fix L31, L73 and table rows L157/L261. `queryChannelsOverride` retyped to paginator `doRequest` `(queryParams) => Promise<{ items }>` - rewrite row L181 and example L190-200 (`client.queryChannels(filters, sort, options)` is gone; use `client.queryChannelsAndHydrate(queryParams)`). -- **Detected:** `sortObject` (L31, L73); `onNewMessageNotification` (L142, L173); `onNewMessage` (L142, L174); `ChannelList.sort` (L157, L261); `onAddedToChannel` (L166); `onChannelDeleted` (L167); `onChannelHidden` (L168); `onChannelMemberUpdated` (L169); `onChannelVisible` (L170); `onChannelTruncated` (L171); `onChannelUpdated` (L172); `onRemovedFromChannel` (L175); `typeRenames` (L181); `ChannelList.queryChannelsOverride` (L181); `queryChannels` (L196) +- **Change:** Remove the 10 per-event override props (`onAddedToChannel`, `onChannelDeleted`, `onChannelHidden`, `onChannelMemberUpdated`, `onChannelTruncated`, `onChannelUpdated`, `onChannelVisible`, `onNewMessage`, `onNewMessageNotification`, `onRemovedFromChannel`) and the event-behaviour prose (L142) -> the list is a projection of its paginator's query; global handling via `client.channelManager.addEventHandler`, floating via `paginator.boost`. Change (stacked PRs): `` no longer takes the query at all. It renders a required, app-owned `paginator` (`ChannelPaginator` from `stream-chat`); the `filters`, `sort`, `options`, `lockChannelOrder` and `queryChannelsOverride` props are removed (and the "Channel List Event Listener Options" `filters`/`sort` table, L258-261). Rewrite the intro (L3 "Pass `filters`, `sort`, and `options` via props"), Best Practices (L13 "Memoize `filters` and `sort`" -> create the paginator once with the user, not in the screen; change the query by setting `staticFilters` / `sort` and `reload()`), General Usage (L28-43) and the navigation example (L66-82) as: create `new ChannelPaginator({ client, id: "channel-list", filters, sort: [{ field: "last_message_at", direction: -1 }], requestOptions: options })` next to `connectUser`, `client.channelManager.insertPaginator({ paginator })`, ``, and `removePaginator` + `dispose()` on logout. Props table: remove rows L156-158, L163 (`lockChannelOrder` -> `paginatorOptions.lockItemOrder`), L181; add `paginator` (required). Replace the `queryChannelsOverride` example (L185-213) with a `paginatorOptions.doRequest` example returning `{ items: await client.queryChannelsAndHydrate(request, stateOptions) }` (`client.queryChannels(filters, sort, options)` is gone; `options.limit` -> `paginatorOptions.pageSize`). The `WithComponents` example (L228) passes `paginator`. +- **Detected:** `sortObject` (L31, L73); `onNewMessageNotification` (L142, L173); `onNewMessage` (L142, L174); `ChannelList.sort` (L38, L78, L157, L228, L261); `onAddedToChannel` (L166); `onChannelDeleted` (L167); `onChannelHidden` (L168); `onChannelMemberUpdated` (L169); `onChannelVisible` (L170); `onChannelTruncated` (L171); `onChannelUpdated` (L172); `onRemovedFromChannel` (L175); `typeRenames` (L181); `ChannelList.queryChannelsOverride` (L181, L185-213); `queryChannels` (L196); `ChannelList.filters` (L3, L13, L37, L77, L156, L206, L228, L260); `ChannelList.options` (L3, L39, L158, L206); `ChannelList.lockChannelOrder` (L163); `ChannelList.paginator` required (L36, L76, L206, L228) + +#### `02-channel-avatar.md` — Mention only + +- **Change:** (stacked PRs) Bare `` in the override snippet needs the now-required `paginator` prop. +- **Detected:** `ChannelList.paginator` required (L37) #### `03-channel-list-view.md` — Snippet / prop-table update -- **Change:** `ChannelListView` now receives the query state as required props (`channels`, `channelListInitialized`, `error`, `hasNextPage`, `loadingChannels`, `loadingNextPage`, `refreshing`) from `ChannelList`; `loadNextPage` is `() => Promise` (L21); `forceUpdate` removed. -- **Detected:** `ChannelListView.loadNextPage` (L21) +- **Change:** `ChannelListView` now receives the query state as required props (`channels`, `channelListInitialized`, `error`, `hasNextPage`, `loadingChannels`, `loadingNextPage`, `refreshing`) from `ChannelList`; `loadNextPage` is `() => Promise` (L21); `forceUpdate` removed. Change (stacked PRs): `` (L58) -> ``; the other two snippets (L34, L42) also need `paginator`. +- **Detected:** `ChannelListView.loadNextPage` (L21); `ChannelList.filters` / `ChannelList.sort` (L58); `ChannelList.paginator` required (L34, L42, L58) + +#### `07-channel-preview-status.md` — Mention only + +- **Change:** (stacked PRs) Bare `` needs the now-required `paginator` prop. +- **Detected:** `ChannelList.paginator` required (L35) + +#### `10-channel-last-message-preview.md` — Mention only + +- **Change:** (stacked PRs) Bare `` needs the now-required `paginator` prop. +- **Detected:** `ChannelList.paginator` required (L30) + +#### `11-channel-preview-typing-indicator.md` — Mention only + +- **Change:** (stacked PRs) Bare `` needs the now-required `paginator` prop. +- **Detected:** `ChannelList.paginator` required (L33) + +#### `12-channel-preview-message-delivery-status.md` — Mention only + +- **Change:** (stacked PRs) Bare `` needs the now-required `paginator` prop. +- **Detected:** `ChannelList.paginator` required (L33) #### `13-channel-details-bottom-sheet.md` — Snippet / prop-table update -- **Change:** Custom channel fields moved under `channel.data.custom` in v10. +- **Change:** Custom channel fields moved under `channel.data.custom` in v10. Change (stacked PRs): `` (L31, L59) needs `paginator`. +- **Detected:** `ChannelList.paginator` required (L31, L59) - **Manual:** L53: `channel.data?.name` -> `channel.data?.custom?.name` (or `useChannelName(channel)`) #### `14-channels-context.md` — Major rewrite -- **Change:** ChannelsContext holds configuration + `paginator` + stable `loadNextPage`/`refreshList`/`reloadList` (P.20). Remove rows `channels`, `channelListInitialized`, `error`, `hasNextPage`, `loadingChannels`, `loadingNextPage`, `refreshing` (and `forceUpdate`); add `paginator`; `loadNextPage()` takes no args; rewrite snippets (`const { channels } = useChannelsContext()` -> `useStateStore(useChannelsContext().paginator.state, selector)`). -- **Detected:** `ChannelsContext.channels` (L19, L27, L35); `ChannelsContext.channelListInitialized` (L36); `ChannelsContext.error` (L37); `ChannelsContext.hasNextPage` (L38); `ChannelsContext.loadingChannels` (L39); `ChannelsContext.loadingNextPage` (L41); `ChannelsContext.loadNextPage` (L42); `ChannelsContext.refreshing` (L49) +- **Change:** ChannelsContext holds configuration + `paginator` + stable `loadNextPage`/`refreshList`/`reloadList` (P.20). Remove rows `channels`, `channelListInitialized`, `error`, `hasNextPage`, `loadingChannels`, `loadingNextPage`, `refreshing` (and `forceUpdate`); add `paginator`; `loadNextPage()` takes no args; rewrite snippets (`const { channels } = useChannelsContext()` -> `useStateStore(useChannelsContext().paginator.state, selector)`). Change (stacked PRs): `paginator` is the app's own instance (the one passed to ``), so components outside the list can subscribe to it directly; `hasNextPage` description "for the current filters" (L38) -> the paginator's query; `` snippets (L61, L75) need `paginator`. +- **Detected:** `ChannelsContext.channels` (L19, L27, L35); `ChannelsContext.channelListInitialized` (L36); `ChannelsContext.error` (L37); `ChannelsContext.hasNextPage` (L38); `ChannelsContext.loadingChannels` (L39); `ChannelsContext.loadingNextPage` (L41); `ChannelsContext.loadNextPage` (L42); `ChannelsContext.refreshing` (L49); `ChannelList.paginator` required (L61, L75) #### `15-hooks/01-channel-list/01-use-channel-updated.md` — Delete / replace page @@ -216,8 +261,8 @@ Format: severity · what must change · scanner-detected breaking items with lin #### `01-channel.md` — Major rewrite -- **Change:** Provided-contexts list: remove `PaginatedMessageListContext` (L7) and `TypingContext` (L11). Props table: remove `allowSendBeforeAttachmentsUpload`, `doFileUploadRequest`, `doMarkReadRequest`, `doSendMessageRequest`, `doUpdateMessageRequest`, `isMessageAIGenerated`, `loadingMore`, `loadingMoreRecent`, `maximumMessageLimit`, `messageId`, `newMessageStateUpdateThrottleInterval`, `preSendMessageRequest`, `stateUpdateThrottleInterval`; `thread` is now a `Thread | null` (L220, drop `ThreadType`). Rewrite the whole Examples section (L225-282: file-upload override, send-before-upload, mark-read, send/update overrides) as `client.config.set({ channel: { requestHandlers: { markReadRequest, sendMessageRequest, updateMessageRequest } }, messageComposer: { attachments: { doUploadRequest, pendingUploadsEnabled } } })`; add "open at a message" via `channel.messagePaginator.jumpToMessage` in `useLayoutEffect` (replaces `messageId`); add `maxLoadedItems` pruning pointer; mention `` keeps rendering a deleted channel (`pendingDisposal`). -- **Detected:** `PaginatedMessageListContext` (L7); `usePaginatedMessageListContext` (L7); `TypingContext` (L11); `useTypingContext` (L11); `allowSendBeforeAttachmentsUpload` (L133, L239, L244); `doFileUploadRequest` (L151, L229); `doMarkReadRequest` (L152, L255); `doSendMessageRequest` (L153, L265); `doUpdateMessageRequest` (L154, L276); `Channel.isMessageAIGenerated` (L187); `Channel.loadingMore` (L190); `loadingMoreRecent` (L191); `maximumMessageLimit` (L194); `Channel.messageId` (L198); `newMessageStateUpdateThrottleInterval` (L203); `preSendMessageRequest` (L212); `stateUpdateThrottleInterval` (L218); `ThreadType` (L220); `Channel.thread` (L220); `sendMessage` (L269); `msgOps` (L279) +- **Change:** Provided-contexts list: remove `PaginatedMessageListContext` (L7) and `TypingContext` (L11). Props table: remove `allowSendBeforeAttachmentsUpload`, `doFileUploadRequest`, `doMarkReadRequest`, `doSendMessageRequest`, `doUpdateMessageRequest`, `isMessageAIGenerated`, `loadingMore`, `loadingMoreRecent`, `maximumMessageLimit`, `messageId`, `newMessageStateUpdateThrottleInterval`, `preSendMessageRequest`, `stateUpdateThrottleInterval`. Change (stacked PRs): `` no longer renders threads - remove `thread` (L220; it is not retyped, it is gone), `threadList` (L221) and `allowThreadMessagesInChannel` (L134, now a `` prop); remove `ThreadContext` from the provided-contexts list (L10; only `` provides it); add a note that `` accepts the same configuration props (`handleX` rows L163-175, `messageActions` L196, `onAlsoSentToChannelHeaderPress` L206, `supportedReactions` L219, keyboard/inset props) and they must be passed to it as well, since a thread is no longer rendered inside the ``. The `handleX` / `messageActions` / `supportedReactions` rows themselves stay valid (they are the only place these reach the action menu now; `messageActions` runs when a message's menu opens). Rewrite the whole Examples section (L225-282: file-upload override, send-before-upload, mark-read, send/update overrides) as `client.config.set({ channel: { requestHandlers: { markReadRequest, sendMessageRequest, updateMessageRequest } }, messageComposer: { attachments: { doUploadRequest, pendingUploadsEnabled } } })`; add "open at a message" via `channel.messagePaginator.jumpToMessage` in `useLayoutEffect` (replaces `messageId`); add `maxLoadedItems` pruning pointer; mention `` keeps rendering a deleted channel (`pendingDisposal`). +- **Detected:** `PaginatedMessageListContext` (L7); `usePaginatedMessageListContext` (L7); `TypingContext` (L11); `useTypingContext` (L11); `allowSendBeforeAttachmentsUpload` (L133, L239, L244); `doFileUploadRequest` (L151, L229); `doMarkReadRequest` (L152, L255); `doSendMessageRequest` (L153, L265); `doUpdateMessageRequest` (L154, L276); `Channel.isMessageAIGenerated` (L187); `Channel.loadingMore` (L190); `loadingMoreRecent` (L191); `maximumMessageLimit` (L194); `Channel.messageId` (L198); `newMessageStateUpdateThrottleInterval` (L203); `preSendMessageRequest` (L212); `stateUpdateThrottleInterval` (L218); `ThreadType` (L220); `Channel.thread` (L220); `sendMessage` (L269); `msgOps` (L279); `ThreadContext` provided by Channel (L10); `Channel.allowThreadMessagesInChannel` (L134); `Channel.threadList` (L221) #### `02-channel-context.md` — Major rewrite @@ -228,8 +273,8 @@ Format: severity · what must change · scanner-detected breaking items with lin #### `01-message-list.md` — Snippet / prop-table update -- **Change:** `FooterComponent` now receives `{ loadingMore?: boolean }` (L39); `HeaderComponent` row (L40) (`MessageFlashList` passes it there); `isListActive` description references removed `isChannelActive` (L42); `onThreadSelect(thread: Thread)` (L45); `thread` prop is a `Thread`. Note the list pages its own paginator (no `loadMore`/`hasMore` props). -- **Detected:** `MessageList.FooterComponent` (L39); `MessageList.HeaderComponent` (L40); `isChannelActive` (L42); `MessageList.onThreadSelect` (L45) +- **Change:** `FooterComponent` now receives `{ loadingMore?: boolean }` (L39); `HeaderComponent` row (L40) (`MessageFlashList` passes it there); `isListActive` description references removed `isChannelActive` (L42); `onThreadSelect(thread: Thread)` (L45); `thread` prop is a `Thread`. Note the list pages its own paginator (no `loadMore`/`hasMore` props). Change (stacked PRs): remove the `threadList` row (L47) - the list renders a thread when it is a child of ``, where `FooterComponent` (and `MessageFlashList`'s `HeaderComponent`) default to `ThreadFooterComponent` (the parent message). +- **Detected:** `MessageList.FooterComponent` (L39); `MessageList.HeaderComponent` (L40); `isChannelActive` (L42); `MessageList.onThreadSelect` (L45); `MessageList.threadList` (L47) #### `02-scroll-to-bottom-button.md` — Snippet / prop-table update @@ -238,20 +283,26 @@ Format: severity · what must change · scanner-detected breaking items with lin #### `04-messages-context.md` — Snippet / prop-table update -- **Change:** Remove rows `deleteMessage`, `deleteReaction`, `isMessageAIGenerated`, `initialScrollToFirstUnreadMessage`, `quotedMessage`, `removeMessage`, `retrySendMessage`, `sendReaction`, `targetedMessage`, `updateMessage`; add pointer to `useMessageOperations()` (§9) and `useChatContext().isMessageAIGenerated`; `giphyVersion` type `keyof Images`. +- **Change:** Remove rows `deleteMessage`, `deleteReaction`, `isMessageAIGenerated`, `initialScrollToFirstUnreadMessage`, `quotedMessage`, `removeMessage`, `retrySendMessage`, `sendReaction`, `targetedMessage`, `updateMessage`; add pointer to `useMessageOperations()` (§9) and `useChatContext().isMessageAIGenerated`; `giphyVersion` type `keyof Images`. Change (stacked PRs): add `onAlsoSentToChannelHeaderPress` (moved here from `ThreadContext`; receives `{ targetedMessageId, thread? }`); note the `handleX` (L24-37), `messageActions` (L42) and `supportedReactions` (L57) rows are the only route to the action menu (they are no longer `` props) and that `` provides its own `MessagesContext` from its own props; `messageActions` (function form) runs when a message's menu opens, so the memoization advice in L7 matters less. - **Detected:** `MessagesContext.deleteMessage` (L15); `MessagesContext.deleteReaction` (L16); `MessagesContext.isMessageAIGenerated` (L38); `MessagesContext.initialScrollToFirstUnreadMessage` (L39); `MessagesContext.quotedMessage` (L44); `MessagesContext.removeMessage` (L52); `MessagesContext.retrySendMessage` (L53); `MessagesContext.sendReaction` (L54); `MessagesContext.targetedMessage` (L58); `MessagesContext.updateMessage` (L59) ### 02-ui-components/08-message-composer/ -#### `01-message-composer.md` — Mention only +#### `01-message-composer.md` — Snippet / prop-table update -- **Change:** `members` / `watchers` props still exist (now typed from `channel.state`); `isOnline` prop retyped to `boolean`. Verify descriptions only. +- **Change:** `members` / `watchers` props still exist (now typed from `channel.state`); `isOnline` prop retyped to `boolean`. Verify descriptions only. Change (stacked PRs, raised from Mention only): remove the `threadList` row (L80); a composer is a thread composer when it is a child of `` (the `ThreadMessageComposer` slot is gone too). +- **Detected:** `MessageComposer.threadList` (L80) #### `components/01-attachment-preview/03-attachment-upload-progress-indicator.md` — Mention only - **Change:** `localId` props unchanged; clarify that `localId` is `attachment.localMetadata.id` and that pending uploads are now controlled by `messageComposer.attachments.pendingUploadsEnabled` (was `allowSendBeforeAttachmentsUpload`). - **Detected:** `attachmentMeta` (L3) +#### `components/07-show-thread-message-in-channel-button.md` — Snippet / prop-table update + +- **Change:** (stacked PRs) The button takes `thread` (`Thread | undefined`, from `useThreadContext()`) instead of `threadList`: fix the prose (L3 "only renders when both `threadList` and `allowThreadMessagesInChannel` are truthy" -> `thread` and `allowThreadMessagesInChannel`) and replace the `threadList` row (L12). `allowThreadMessagesInChannel` (L11) now comes from the `` prop (default `true`), not ``. +- **Detected:** `ShowThreadMessageInChannelButton.threadList` (L3, L12); `allowThreadMessagesInChannel` (L3, L11) + #### `composer/10-message_composer.md` — Snippet / prop-table update - **Change:** `client.setMessageComposerSetupFunction` is deprecated -> `client.config.setSetupFunction("messageComposer", fn)` (L50 heading, L79, L202-205). "Message Composer Configuration" section should move to `client.config.set({ messageComposer: {...} })` (new required `attachments.customCdn`, `polls`; `linkPreviews.enabled` defaults true). @@ -281,8 +332,8 @@ Format: severity · what must change · scanner-detected breaking items with lin #### `12-message-content.md` — Snippet / prop-table update -- **Change:** Remove `isMessageAIGenerated` prop row (L23) -> ``. -- **Detected:** `MessageContent.isMessageAIGenerated` (L23) +- **Change:** Remove `isMessageAIGenerated` prop row (L23) -> ``. Change (stacked PRs): remove the `threadList` row (L38) -> `useThreadContext().thread`. +- **Detected:** `MessageContent.isMessageAIGenerated` (L23); `MessageContent.threadList` (L38) #### `14-message-edited-timestamp.md` — Snippet / prop-table update @@ -294,6 +345,11 @@ Format: severity · what must change · scanner-detected breaking items with lin - **Change:** Remove `members` prop row (L19). - **Detected:** `MessageFooter.members` (L19) +#### `19-message-replies.md` — Snippet / prop-table update + +- **Change:** (stacked PRs) Remove the `threadList` row (L23). `MessageReplies` no longer knows about threads: `MessageItemView` hides the replies row inside a ``, so a custom layout that renders `MessageReplies` directly must check `useThreadContext().thread` itself (v9 hid it). +- **Detected:** `MessageReplies.threadList` (L23) + #### `20-message-status.md` — Snippet / prop-table update - **Change:** Remove `readBy` / `deliveredToCount` rows (L15-16); mention `useIsMessageReadByOthers` / `useIsMessageDeliveredToOthers`. @@ -301,34 +357,34 @@ Format: severity · what must change · scanner-detected breaking items with lin #### `25-message-context.md` — Snippet / prop-table update -- **Change:** Remove rows `isMessageAIGenerated` (L50), `readBy` (L65), `deliveredToCount` (L66), `members` (L80); `onThreadSelect` (L75) receives a `Thread`. -- **Detected:** `MessageContext.isMessageAIGenerated` (L50); `MessageContext.readBy` (L65); `MessageContext.deliveredToCount` (L66); `MessageContext.onThreadSelect` (L75); `MessageContext.members` (L80) +- **Change:** Remove rows `isMessageAIGenerated` (L50), `readBy` (L65), `deliveredToCount` (L66), `members` (L80); `onThreadSelect` (L75) receives a `Thread`. Change (stacked PRs): remove the `threadList` row (L77) -> `useThreadContext().thread`; add `messageActionHandlers` (`MessageActionHandlerCallbacks`, the row's shared handler set, same object `useMessageActionHandlers()` returns) and the optional `isVeryLastMessage`. +- **Detected:** `MessageContext.isMessageAIGenerated` (L50); `MessageContext.readBy` (L65); `MessageContext.deliveredToCount` (L66); `MessageContext.onThreadSelect` (L75); `MessageContext.threadList` (L77); `MessageContext.members` (L80) ### 02-ui-components/10-thread/ #### `01-thread.md` — Major rewrite -- **Change:** `thread` is a `Thread` instance: fix snippets `` (L25, L68) and row L52 (drop `ThreadType`); remove `closeThread`, `loadMoreThread`, `reloadThread`, `threadInstance` rows (L46-53); `closeThreadOnDismount` is inert in v10 (row + best practice L6); best practice L9 -> `thread.messagePaginator` / `thread.reload()`. Show `client.threads.ensure({ channel, parentMessage })` for opening from a message. -- **Detected:** `loadMoreThread` (L9, L47); `reloadThread` (L9, L51); `Channel.thread` (L25, L68); `Thread.closeThread` (L46); `Thread.loadMoreThread` (L47); `Thread.reloadThread` (L51); `ThreadType` (L52); `Thread.thread` (L52); `Thread.threadInstance` (L53) +- **Change:** Whole page (stacked PRs). `` renders next to ``, not inside it, and renders its children: General Usage (L13-33) and the overrides snippet (L68) become `` with no `` around it; intro (L1 "Internally uses `MessageList` and `MessageComposer`") and Best Practices (L5 "Render `Thread` within `Channel` and pass `thread`/`threadList`", L6 `closeThreadOnDismount`, L8 `autoFocus`, L9 `reloadThread`/`loadMoreThread`) are rewritten. Props table: `thread` is required and is the `Thread` instance (L52, drop `ThreadType`/`LocalMessage`); remove `additionalMessageComposerProps`, `additionalMessageListProps`, `additionalMessageFlashListProps` (configure the children directly), `autoFocus`, `client`, `closeThreadOnDismount`, `disabled`, `closeThread`, `loadMoreThread`, `parentMessagePreventPress` (now a `ThreadFooterComponent` prop, default `true`), `shouldUseFlashList` (render `` as the child), `reloadThread`, `threadInstance` (L38-53); keep `notificationHostId`, `onThreadDismount`; add `allowThreadMessagesInChannel` (moved from ``, default `true`) and a pointer that `` accepts the same configuration props as `` (`messageActions`, `handleX`, `supportedReactions`, `keyboardVerticalOffset`, `topInset`, `onAlsoSentToChannelHeaderPress`, …). The "UI Component Overrides" section (L55-77) goes: the `ThreadMessageComposer` slot is removed and the list/composer are the app's own children. Add: opening from a message via `onThreadSelect` (yields a `Thread`) or `client.threads.ensure({ channel, parentMessage })`; `` marks the thread read while open and does not mark the channel read; paging via `thread.messagePaginator` / `thread.reload()`. +- **Detected:** `loadMoreThread` (L9, L47); `reloadThread` (L9, L51); `Channel.thread` (L25, L68); `Thread.closeThread` (L46); `Thread.loadMoreThread` (L47); `Thread.reloadThread` (L51); `ThreadType` (L52); `Thread.thread` (L52); `Thread.threadInstance` (L53); `Channel.threadList` (L5, L25, L68); `Thread.additionalMessageComposerProps` (L38); `Thread.additionalMessageListProps` (L39); `Thread.additionalMessageFlashListProps` (L40); `Thread.autoFocus` (L8, L41); `Thread.client` (L42); `Thread.closeThreadOnDismount` (L6, L43); `Thread.disabled` (L44); `Thread.parentMessagePreventPress` (L48); `Thread.shouldUseFlashList` (L49); `ThreadMessageComposer` (L65, L77) #### `02-thread-context.md` — Major rewrite -- **Change:** ThreadContext is now `{ thread, allowThreadMessagesInChannel, parentMessagePreventPress?, onAlsoSentToChannelHeaderPress? }` with `thread: Thread`. Remove 12 rows (L35-48: closeThread, loadMoreThread, loadMoreRecentThread, openThread, reloadThread, setThreadLoadingMore, threadHasMore, threadInstance, threadLoadingMore, threadLoadingMoreRecent, threadMessages, and parentMessagePreventPress-from-context); rewrite Best Practices (L6-7) and add the paginator/`thread.state` recipes. -- **Detected:** `closeThread` (L6, L35); `openThread` (L6, L39); `threadHasMore` (L7, L44); `threadLoadingMore` (L7, L46); `ThreadContext.closeThread` (L35); `ThreadContext.loadMoreThread` (L36); `ThreadContext.loadMoreRecentThread` (L37); `ThreadContext.openThread` (L39); `ThreadContext.parentMessagePreventPress` (L40); `ThreadContext.reloadThread` (L41); `ThreadContext.setThreadLoadingMore` (L42); `ThreadType` (L43); `ThreadContext.thread` (L43); `ThreadContext.threadHasMore` (L44); `ThreadContext.threadInstance` (L45); `ThreadContext.threadLoadingMore` (L46); `ThreadContext.threadLoadingMoreRecent` (L47); `ThreadContext.threadMessages` (L48) +- **Change:** ThreadContext is now `{ thread?: Thread, allowThreadMessagesInChannel }` (stacked PRs: `parentMessagePreventPress` moved to `ThreadFooterComponent`, `onAlsoSentToChannelHeaderPress` moved to `MessagesContext`). It is provided only by ``, not `` (fix L1 and L13 "Consume `ThreadContext` in any child of `Channel`"), and `useThreadContext()` no longer throws outside it: elsewhere it returns `{ thread: undefined, allowThreadMessagesInChannel: false }`, and `thread === undefined` is how a component knows it is not in a thread (the replacement for every `threadList` check). Remove 13 rows (L35-48: closeThread, loadMoreThread, loadMoreRecentThread, onAlsoSentToChannelHeaderPress, openThread, parentMessagePreventPress, reloadThread, setThreadLoadingMore, threadHasMore, threadInstance, threadLoadingMore, threadLoadingMoreRecent, threadMessages); `thread` (L43) is the `Thread` instance (parent message fields move to `thread.state.parentMessage`); `allowThreadMessagesInChannel` (L34) is set on ``. Rewrite Best Practices (L6-7: navigation is app state + ``, pagination is `thread.messagePaginator`) and add the paginator/`thread.state` recipes. +- **Detected:** `ThreadContext` provided by Channel (L1, L13); `ThreadContext.onAlsoSentToChannelHeaderPress` (L38); `closeThread` (L6, L35); `openThread` (L6, L39); `threadHasMore` (L7, L44); `threadLoadingMore` (L7, L46); `ThreadContext.closeThread` (L35); `ThreadContext.loadMoreThread` (L36); `ThreadContext.loadMoreRecentThread` (L37); `ThreadContext.openThread` (L39); `ThreadContext.parentMessagePreventPress` (L40); `ThreadContext.reloadThread` (L41); `ThreadContext.setThreadLoadingMore` (L42); `ThreadType` (L43); `ThreadContext.thread` (L43); `ThreadContext.threadHasMore` (L44); `ThreadContext.threadInstance` (L45); `ThreadContext.threadLoadingMore` (L46); `ThreadContext.threadLoadingMoreRecent` (L47); `ThreadContext.threadMessages` (L48) ### 02-ui-components/11-thread-list/ #### `01-thread-list.md` — Snippet / prop-table update -- **Change:** `onThreadSelect(thread: Thread)` - single arg; the `{ thread, threadInstance }` + `channel` shape is gone (L32). Channel via `thread.channel`. +- **Change:** `onThreadSelect(thread: Thread)` - single arg; the `{ thread, threadInstance }` + `channel` shape is gone (L32). Channel via `thread.channel`. Change (stacked PRs): the selected thread is rendered with `` directly; no `` is needed for it. - **Detected:** `threadInstance` (L32); `ThreadList.onThreadSelect` (L32) ### 02-ui-components/13-contexts/ #### `01-contexts.md` — Snippet / prop-table update -- **Change:** Remove `PaginatedMessageListContext`, `TypingContext`, `DebugContext` from the list (L25, L28, L55) and table (L96, L105, L112); fix custom `MessageStatus` example (L131-145) reading `message.readBy` -> `useIsMessageReadByOthers({ message })`; add a short "state now comes from `channel.state` / paginators" note. -- **Detected:** `PaginatedMessageListContext` (L25, L105); `TypingContext` (L28, L112); `DebugContext` (L55, L96); `useDebugContext` (L96); `usePaginatedMessageListContext` (L105); `useTypingContext` (L112); `readBy` (L133, L145) +- **Change:** Remove `PaginatedMessageListContext`, `TypingContext`, `DebugContext` from the list (L25, L28, L55) and table (L96, L105, L112); fix custom `MessageStatus` example (L131-145) reading `message.readBy` -> `useIsMessageReadByOthers({ message })`; add a short "state now comes from `channel.state` / paginators" note. Change (stacked PRs): `ThreadContext` (L27) moves from the `Channel` group to a new `Thread` group (`` provides it plus the same channel-level contexts, from `thread.channel`). +- **Detected:** `ThreadContext` listed under Channel (L27); `PaginatedMessageListContext` (L25, L105); `TypingContext` (L28, L112); `DebugContext` (L55, L96); `useDebugContext` (L96); `usePaginatedMessageListContext` (L105); `useTypingContext` (L112); `readBy` (L133, L145) #### `06-paginated-message-list-context.md` — Delete / replace page @@ -364,10 +420,10 @@ Format: severity · what must change · scanner-detected breaking items with lin ### 02-ui-components/15-in-app-notifications/ -#### `01-hooks/04-use-notification-target.md` — Mention only +#### `01-hooks/04-use-notification-target.md` — Snippet / prop-table update -- **Change:** L19 "a `threadInstance` on `ThreadContext`" -> "a `thread` on `ThreadContext`". -- **Detected:** `threadInstance` (L19) +- **Change:** L19 "a `threadInstance` on `ThreadContext`" -> "a `thread` on `ThreadContext`". Change (stacked PRs, raised from Mention only): the resolution table loses its first row (L18 "inside a `Channel` with `threadList` set on `ChannelContext`" - `threadList` is gone and `` never renders a thread); "inside a `Thread`" (L19) is the only `'thread'` case. +- **Detected:** `threadInstance` (L19); `ChannelContext.threadList` (L18) ### 02-ui-components/16-base-ui-components/ @@ -427,27 +483,27 @@ Format: severity · what must change · scanner-detected breaking items with lin #### `03-customize-channel-list.md` — Major rewrite -- **Change:** Event-handler override table + `onNewMessageNotification(setChannels, event)` example (L11-75) are gone: rewrite around `filters`/`sort` (list is a projection of the query), `channelRenderFilterFn`, `paginator.boost(cid)`, and `client.channelManager.addEventHandler`. "Load More" button (L88-100) reads `loadingNextPage`/`hasNextPage` from ChannelsContext - now from `useStateStore(paginator.state)` / `ChannelListView` props. Multiple-lists section prose about `client.activeChannels` (L116) is obsolete. -- **Detected:** `onChannelDeleted` (L21); `onChannelHidden` (L22); `onChannelTruncated` (L23); `onChannelUpdated` (L24); `onChannelVisible` (L25); `onNewMessage` (L26); `onAddedToChannel` (L27); `onNewMessageNotification` (L28, L45, L71); `onRemovedFromChannel` (L29); `ChannelsContext.hasNextPage` (L93); `ChannelsContext.loadingNextPage` (L93); `ChannelsContext.loadNextPage` (L93); `activeChannels` (L116) +- **Change:** Event-handler override table + `onNewMessageNotification(setChannels, event)` example (L11-75) are gone: rewrite around the paginator's query (the list is a projection of it), `channelRenderFilterFn`, `paginator.boost(cid)`, and `client.channelManager.addEventHandler`. "Load More" button (L88-100) reads `loadingNextPage`/`hasNextPage` from ChannelsContext - now from `useStateStore(paginator.state)` / `ChannelListView` props. Multiple-lists section prose about `client.activeChannels` (L116) is obsolete. Change (stacked PRs): there is no `filters` prop to pass (L39, L70, L127); the frozen-channels example becomes `new ChannelPaginator({ client, id: "frozen", filters: { members: { $in: ["vishal"] }, frozen: true } })`, registered and passed as `` (also L100). "Multiple Channel Lists" (L110-140) is rewritten as one paginator per list, each with a unique `id` and its own `filters`/`sort`, all inserted into `client.channelManager` (the manager keys lists by `id`; each list projects its own query, so verify whether the cross-list reordering this section works around still happens before keeping the `channelRenderFilterFn` workaround); `lockChannelOrder` (L26 link) -> `paginatorOptions.lockItemOrder`. Add a "change the query at runtime" recipe (`paginator.staticFilters = …; paginator.reload()`, or a new paginator). +- **Detected:** `onChannelDeleted` (L21); `onChannelHidden` (L22); `onChannelTruncated` (L23); `onChannelUpdated` (L24); `onChannelVisible` (L25); `onNewMessage` (L26); `onAddedToChannel` (L27); `onNewMessageNotification` (L28, L45, L71); `onRemovedFromChannel` (L29); `ChannelsContext.hasNextPage` (L93); `ChannelsContext.loadingNextPage` (L93); `ChannelsContext.loadNextPage` (L93); `activeChannels` (L116); `ChannelList.filters` (L39, L70, L127); `ChannelList.lockChannelOrder` (L26); `ChannelList.paginator` required (L39, L69, L100, L125) #### `04-customize-message-actions.md` — Snippet / prop-table update -- **Change:** Custom label keys `t("Custom Mute User")` (L272) need `asDynamicKey` (or plain strings); `client.userID` (L258) is deprecated -> `client.userId`. `client.muteUser(id)` / `unmuteUser(id)` calls are still valid. +- **Change:** Custom label keys `t("Custom Mute User")` (L272) need `asDynamicKey` (or plain strings); `client.userID` (L258) is deprecated -> `client.userId`. `client.muteUser(id)` / `unmuteUser(id)` calls are still valid. Change (stacked PRs): the page already configures everything on ``, which is now the only route (`handleX`, `messageActions` are no longer `` props). Add: (1) the `messageActions` function (L29-55, L207, L248) runs when a message's menu opens, not on every render, so it may do per-message work; (2) pass the same `messageActions` / `handleX` to ``, or thread messages get the defaults; (3) a custom `MessageActionList` (L296-345) receives the built list as its `messageActions` prop (plus `dismissOverlay`), and code that wants the default actions or handlers calls `useMessageActions()` / `useMessageActionHandlers()` with no arguments inside the message (`MessageActionsHookProps` is gone). The intercept list (L430-440) names `handleReaction`, `handleReply`, `handleThreadReply`; `handleReply` does not exist (it is `handleQuotedReply`) and `handleMarkUnread`, `handlePinMessage`, `handleBlockUser` are missing. - **Detected:** `userID` (L258); `msgOps` (L265, L267); `tEnglishKey` (L272) #### `05-livestream-messagelist.md` — Snippet / prop-table update -- **Change:** `StreamChat.getInstance(key, { isLocalUnreadCountEnabled: true })` (L49-51) -> `{ channel: { readEvents: { localUnreadCountEnabled: true } } }`; thread state now a `Thread` (L104, L154, L171). +- **Change:** `StreamChat.getInstance(key, { isLocalUnreadCountEnabled: true })` (L49-51) -> `{ channel: { readEvents: { localUnreadCountEnabled: true } } }`; thread state now a `Thread` (L171). Change (stacked PRs): drop `thread={thread}` from both `` snippets (L104, L154; the prop is removed); the "Thread" screen navigated to at L173 renders `` with its own list and composer. - **Detected:** `localUnread` (L50); `Channel.thread` (L104, L154); `MessageList.onThreadSelect` (L171) #### `06-message-input-customization.md` — Major rewrite -- **Change:** Custom CDN section (L230-250) uses `` -> `client.config.set({ messageComposer: { attachments: { doUploadRequest, customCdn: true } } })`. Replace the whole "Uploading attachments asynchronously" section (L540-660: hand-written `localAttachmentToAttachment` / attachment composition middlewares + `setMessageComposerSetupFunction`) with the built-in `messageComposer.attachments.pendingUploadsEnabled` switch. "Overriding send" example (L670-700) uses `channel.sendMessage(message, sendOptions)` -> `channel.sendMessage({ message, ...sendOptions })` or a `sendMessageRequest` handler. -- **Detected:** `doFileUploadRequest` (L235, L239); `localAttachmentToAttachment` (L557, L593, L628); `createAttachmentsCompositionMiddleware` (L580, L652); `createDraftAttachmentsCompositionMiddleware` (L615, L656); `setMessageComposerSetupFunction` (L646, L650); `sendMessage` (L685) +- **Change:** Custom CDN section (L230-250) uses `` -> `client.config.set({ messageComposer: { attachments: { doUploadRequest, customCdn: true } } })`. Replace the whole "Uploading attachments asynchronously" section (L540-660: hand-written `localAttachmentToAttachment` / attachment composition middlewares + `setMessageComposerSetupFunction`) with the built-in `messageComposer.attachments.pendingUploadsEnabled` switch. "Overriding send" example (L670-700) uses `channel.sendMessage(message, sendOptions)` -> `channel.sendMessage({ message, ...sendOptions })` or a `sendMessageRequest` handler. Change (stacked PRs): `` (L75) needs `paginator`. +- **Detected:** `ChannelList.paginator` required (L75); `doFileUploadRequest` (L235, L239); `localAttachmentToAttachment` (L557, L593, L628); `createAttachmentsCompositionMiddleware` (L580, L652); `createDraftAttachmentsCompositionMiddleware` (L615, L656); `setMessageComposerSetupFunction` (L646, L650); `sendMessage` (L685) #### `09-message-customization.md` — Snippet / prop-table update -- **Change:** L63 `useChannelContext()` destructures removed `members`, `watchers`, `error`, `loading` -> `useStateStore(channel.state, ...)` / paginator; L439 `useMessageContext().members` removed -> `useStateStore(channel.state, s => ({ members: s.members }))`; L442 `client.userID`; L627 `client.deleteMessage(id)` -> `client.deleteMessage({ id })` or `useMessageOperations().deleteMessage(message)`; thread state typing (L414, L474). +- **Change:** L63 `useChannelContext()` destructures removed `members`, `watchers`, `error`, `loading` -> `useStateStore(channel.state, ...)` / paginator; L439 `useMessageContext().members` removed -> `useStateStore(channel.state, s => ({ members: s.members }))`; L442 `client.userID`; L627 `client.deleteMessage(id)` -> `client.deleteMessage({ id })` or `useMessageOperations().deleteMessage(message)`. Change (stacked PRs): drop `thread={thread}` from the two `` snippets (L414, L474; prop removed) and note `forceAlignMessages` (L405-418) must also be passed to `` for thread screens. `` in the swipeable wrapper (L651) still compiles, but `MessageProps` no longer carries `handleX` / `messageActions` / `supportedReactions`. - **Detected:** `ChannelContext.error` (L63); `ChannelContext.members` (L63); `ChannelContext.loading` (L63); `ChannelContext.watchers` (L63); `Channel.thread` (L414, L474); `MessageContext.members` (L439); `userID` (L442); `msgOps` (L627) #### `10-file-compression.md` — Major rewrite @@ -455,20 +511,24 @@ Format: severity · what must change · scanner-detected breaking items with lin - **Change:** Whole recipe is built on `` (L5, L46, L64) and `channel.sendFile(uri, name, type)` (L61). Rewrite with `client.config.set({ messageComposer: { attachments: { doUploadRequest } } })` and `channel.uploadFile({ file: { uri, name, type } })`; `waveform_data` (L25) moved under `custom`. - **Detected:** `doFileUploadRequest` (L5, L46, L64); `attachmentMeta` (L25); `sendFileImage` (L61) +#### `11-blocking-users.md` — Mention only + +- **Change:** (stacked PRs) The custom `messageActions` set on `` (L83-100) does not reach thread messages any more; say to pass the same function to ``. The function runs when a message's menu opens. + #### `13-custom-thread-list.md` — Snippet / prop-table update -- **Change:** `onThreadSelect={(thread, channel) => ...}` (L75, L134, L197, L231) -> `(thread) => ...` with `thread.channel`; custom item calls `onThreadSelect({ thread, threadInstance }, channel)` (L107-116) -> `onThreadSelect?.(thread)`. Also uses `ThreadList ThreadListItem={...}` prop (v8 style) -> `WithComponents`. +- **Change:** `onThreadSelect={(thread, channel) => ...}` (L75, L134, L197, L231) -> `(thread) => ...` with `thread.channel`; custom item calls `onThreadSelect({ thread, threadInstance }, channel)` (L107-116) -> `onThreadSelect?.(thread)`. Also uses `ThreadList ThreadListItem={...}` prop (v8 style) -> `WithComponents`. Change (stacked PRs): the prerequisite "screen that shows a `Thread`" (L13) is `` with no ``; link the thread page. - **Detected:** `ThreadList.onThreadSelect` (L75, L134, L197, L231); `ThreadsContext.onThreadSelect` (L107); `threadInstance` (L115) #### `15-customize-message-reactions.md` — Snippet / prop-table update -- **Change:** `ReactionSortBase` removed; `useFetchReactions` `sort` is `SortParamRequest[]`. +- **Change:** `ReactionSortBase` removed; `useFetchReactions` `sort` is `SortParamRequest[]`. Change (stacked PRs): `` (L13, L31) no longer covers threads; pass the same list to ``. Reading `supportedReactions` from `useMessagesContext()` (L194, L282) is still right (it is no longer a `` prop). - **Manual:** L282-284: `const reactionSort: ReactionSortBase = { created_at: -1 }` -> `SortParamRequest[]` `[{ field: "created_at", direction: -1 }]` #### `16-channel-pinning-and-archiving.md` — Snippet / prop-table update -- **Change:** Best practice L8 tells readers to keep `onChannelMemberUpdated` wired (prop removed; the orchestrator handles `member.updated`); sort L200 `[{ pinned_at: -1 }, ...]` -> `[{ field: "pinned_at", direction: -1 }, ...]`. `channel.pin()` / `archive()` unchanged. -- **Detected:** `onChannelMemberUpdated` (L8); `sortObject` (L200) +- **Change:** Best practice L8 tells readers to keep `onChannelMemberUpdated` wired (prop removed; the orchestrator handles `member.updated`); sort L200 `[{ pinned_at: -1 }, ...]` -> `[{ field: "pinned_at", direction: -1 }, ...]`. `channel.pin()` / `archive()` unchanged. Change (stacked PRs): "Filtering Channels" / "Sorting Channels" (L171-205) say "using the `filters` / `sort` prop on `ChannelList`" - those props are gone; put `filters` / `sort` on the `ChannelPaginator` (`new ChannelPaginator({ client, id: "archived", filters: { archived: true } })`), and for an archived/pinned tab switch either keep one paginator per tab or set `paginator.staticFilters` and `reload()`. `` (L119) needs `paginator`. +- **Detected:** `onChannelMemberUpdated` (L8); `sortObject` (L200); `ChannelList.filters` (L173, L181, L190); `ChannelList.sort` (L195, L202); `ChannelList.paginator` required (L119, L181, L190, L202) #### `18-handle-commands.md` — Snippet / prop-table update @@ -518,13 +578,13 @@ Format: severity · what must change · scanner-detected breaking items with lin #### `01-state-overview.md` — Major rewrite -- **Change:** LLC snippets: `chatClient.queryChannels(filter, sort, opts)` (L238-244) -> `client.queryChannelsAndHydrate({ filter_conditions, sort: [{ field, direction }], watch, state })`; `channel.sendMessage({ text })` (L253) -> `channel.sendMessage({ message: { text } })` and the L137 note (channel.sendMessage is HTTP-only; optimistic path is `channel.messageOperations` / `useMessageOperations`). L403 `threadInstance`. This page is the natural home for the new v10 state model: unified `channel.state` store, `channel.messagePaginator`, `thread.messagePaginator`, `client.config`, `client.channelManager`. +- **Change:** LLC snippets: `chatClient.queryChannels(filter, sort, opts)` (L238-244) -> `client.queryChannelsAndHydrate({ filter_conditions, sort: [{ field, direction }], watch, state })`; `channel.sendMessage({ text })` (L253) -> `channel.sendMessage({ message: { text } })` and the L137 note (channel.sendMessage is HTTP-only; optimistic path is `channel.messageOperations` / `useMessageOperations`). L403 `threadInstance`. This page is the natural home for the new v10 state model: unified `channel.state` store, `channel.messagePaginator`, `thread.messagePaginator`, `client.config`, `client.channelManager` (with the stacked PRs: the app-owned `ChannelPaginator`s registered on it). - **Detected:** `sendMessage` (L137, L253); `channelCreateMembers` (L198); `sortObject` (L238); `queryChannels` (L240); `threadInstance` (L403) - **Manual:** L184-200: `client.channel(type, id, { name })` / `{ members: [ids], name }` -> `{ custom: { name } }`, `members: [{ user_id }]` #### `02-offline-support.md` — Mention only -- **Change:** Add v10 notes: one-time offline cache reset (DB version 17 -> 18), pending uploads default on with `enableOfflineSupport` (`pendingUploadsEnabled`), unsent messages persist across restarts. +- **Change:** Add v10 notes: one-time offline cache reset (DB version 17 -> 18), pending uploads default on with `enableOfflineSupport` (`pendingUploadsEnabled`), unsent messages persist across restarts. Change (stacked PRs): the sign-out snippet (L94-100) should also remove and dispose the app's channel-list paginator (`client.channelManager.removePaginator(paginator); paginator.dispose();`) - `disconnectUser()` empties registered lists but keeps them registered. ### 05-advanced-guides/ @@ -545,14 +605,14 @@ Format: severity · what must change · scanner-detected breaking items with lin #### `06-troubleshooting.md` — Snippet / prop-table update -- **Change:** Thread snippets (L17-69) still work but `thread` is a `Thread` instance (`onThreadSelect` yields it). Rewrite the deleted-channel section. Flipper mention (L7) can go. -- **Detected:** `Channel.thread` (L17, L26, L62, L69); `MessageList.onThreadSelect` (L18, L63) +- **Change:** Rewrite the deleted-channel section. Flipper mention (L7) can go. Change (stacked PRs): the thread snippets no longer work - `` has no `thread` / `threadList` and `` is not rendered inside it. Delete "Messages sending in thread instead of channel" (L8-35) and "Wrong images in gallery" (L53-73): both work around the v9 shared `thread` state on ``, which v10 does not have (a channel screen renders ``, a thread screen renders ``). If an entry is kept, show the sibling layout. +- **Detected:** `Channel.thread` (L17, L26, L62, L69); `Channel.threadList` (L26, L69); `` inside `` (L27, L70); `MessageList.onThreadSelect` (L18, L63) - **Manual:** L262-275 "Blank screen when channel gets deleted": `` no longer returns null on delete (P.15); read `pendingDisposal` from `channel.state` instead -#### `07-stream_chat_with_navigation.md` — Snippet / prop-table update +#### `07-stream_chat_with_navigation.md` — Major rewrite -- **Change:** `useState()` (L171) -> `Thread`; `onThreadSelect` yields a `Thread`; passing the instance in navigation params (L174) is non-serializable - recommend passing ids + `client.threads.ensure` / app context. -- **Detected:** `Channel.thread` (L144, L157, L184); `MessageList.onThreadSelect` (L159, L186) +- **Change:** `useState()` (L177) -> `Thread`; `onThreadSelect` yields a `Thread`; passing the instance in navigation params (L188) is non-serializable - recommend passing ids + `client.threads.ensure` / app context. Change (stacked PRs, raised from Snippet update): the channel/thread screen pattern this guide teaches is gone. The thread screen renders `` with no `` (L139-148), and the channel screen renders plain `` without `thread` (L157, L184). The "Image Gallery" section (L126-199: `threadList` + `ThreadContext` gating, telling the channel which thread is open, the `useFocusEffect` reset) and "Resetting Selected Images" (L120-124) describe v9 coupling that no longer exists: rewrite as "keep the selected thread in app state or params, clear it in `onThreadDismount`". Best practice L10 ("Manage `thread` state explicitly … between channel and thread screens") changes accordingly. Keyboard / inset props (L67, L87, L110) must be set on `` as well. +- **Detected:** `Channel.thread` (L144, L157, L184); `Channel.threadList` (L128, L144); `` inside `` (L145); `MessageList.onThreadSelect` (L159, L186) #### `09-translations.md` — Major rewrite @@ -566,7 +626,7 @@ Format: severity · what must change · scanner-detected breaking items with lin #### `12-going-live-checklist.md` — Snippet / prop-table update -- **Change:** Re-render example (L40-80) uses `usePaginatedMessageListContext` -> `useStateStore(channel.messagePaginator.state, selector)` (selector already limits re-renders). +- **Change:** Re-render example (L40-80) uses `usePaginatedMessageListContext` -> `useStateStore(channel.messagePaginator.state, selector)` (selector already limits re-renders). Change (stacked PRs): the logout snippet (L22-31) should remove and dispose the channel-list paginator before `disconnectUser()`. - **Detected:** `PaginatedMessageListContext` (L40); `usePaginatedMessageListContext` (L43, L47, L59, L74) #### `13-push-notifications.md` — Major rewrite @@ -576,8 +636,8 @@ Format: severity · what must change · scanner-detected breaking items with lin #### `14-accessibility.md` — Major rewrite -- **Change:** The `a11y/*` key namespace is gone: best practices (L9-10), Localization section (L67-81: `registerTranslation("nl", { "a11y/Avatar of {{name}}": ... })`, `en.json` pointer) and `accessibilityLabelKey="a11y/Send message"` (L91) must use dotted `.accessibilityLabel` keys (e.g. `avatar.accessibilityLabel`, `messageInput.sendMessage.accessibilityLabel`, `a11y.newMessages.withCount.accessibilityLabel`). `client.userID` (L160). -- **Detected:** `tEnglishKey` (L9); `a11yKey` (L9, L75, L76, L77, L91); `newStreami18n` (L72); `registerTranslation` (L74); `translationJson` (L81); `userID` (L160) +- **Change:** The `a11y/*` key namespace is gone: best practices (L9-10), Localization section (L67-81: `registerTranslation("nl", { "a11y/Avatar of {{name}}": ... })`, `en.json` pointer) and `accessibilityLabelKey="a11y/Send message"` (L91) must use dotted `.accessibilityLabel` keys (e.g. `avatar.accessibilityLabel`, `messageInput.sendMessage.accessibilityLabel`, `a11y.newMessages.withCount.accessibilityLabel`). `client.userID` (L160). Change (stacked PRs): the `useIncomingMessageAnnouncements` comment (L161-162) "pass `threadList: true` and the active `parent_id` as `activeThreadId`" -> pass `activeThreadId` only (`threadList` is gone; inside a `` read it from `useThreadContext().thread?.id`). +- **Detected:** `tEnglishKey` (L9); `a11yKey` (L9, L75, L76, L77, L91); `newStreami18n` (L72); `registerTranslation` (L74); `translationJson` (L81); `userID` (L160); `useIncomingMessageAnnouncements.threadList` (L161) #### `15-performance-guide.md` — Snippet / prop-table update @@ -586,8 +646,8 @@ Format: severity · what must change · scanner-detected breaking items with lin #### `17-channel-read-state.md` — Major rewrite -- **Change:** Whole model changed: `channelUnreadState` in ChannelContext -> `channel.messagePaginator.unreadStateSnapshot`; `read` from context -> `useStateStore(channel.state, s => ({ read: s.read }))`; `markRead` from context + `updateChannelUnreadState` option -> `useMarkRead(channel)` / `channel.markRead()` (reporter-driven cadence); `loadChannelAtFirstUnreadMessage(...)` (L264) -> `paginator.jumpToTheFirstUnreadMessage()`; `last_read: Date` (L23, L34) -> `TimestampNS`; `isLocalUnreadCountEnabled` (L146) -> config `channel.readEvents.localUnreadCountEnabled`; `getInstance(key, { timeout })` (L275) -> `{ client: { axiosRequestConfig: { timeout } } }`; `client.activeChannels` (L294) -> `client.channelManager.values()`; event union narrowing for `total_unread_count`. -- **Detected:** `ChannelContext.read` (L49); `ChannelContext.markRead` (L73); `localUnread` (L147); `setChannelUnreadState` (L264); `loadChannelAtFirstUnreadMessage` (L264); `setTargetedMessage` (L264); `eventMe` (L283, L291, L292); `activeChannels` (L294) +- **Change:** Whole model changed: `channelUnreadState` in ChannelContext -> `channel.messagePaginator.unreadStateSnapshot`; `read` from context -> `useStateStore(channel.state, s => ({ read: s.read }))`; `markRead` from context + `updateChannelUnreadState` option -> `useMarkRead(channel)` / `channel.markRead()` (reporter-driven cadence); `loadChannelAtFirstUnreadMessage(...)` (L264) -> `paginator.jumpToTheFirstUnreadMessage()`; `last_read: Date` (L23, L34) -> `TimestampNS`; `isLocalUnreadCountEnabled` (L146) -> config `channel.readEvents.localUnreadCountEnabled`; `getInstance(key, { timeout })` (L275) -> `{ client: { axiosRequestConfig: { timeout } } }`; `client.activeChannels` (L294) -> `client.channelManager.values()`; event union narrowing for `total_unread_count`. Change (stacked PRs): the thread note (L118-122: "none of the UI components related to read state are rendered in threads") and best practice L9 are outdated - a `` marks the thread read while it is open, its list shows an unread separator from `thread.messagePaginator.unreadStateSnapshot`, its scroll-to-bottom button does not mark the channel read, and opening a thread no longer marks the channel read. +- **Detected:** thread read state (L9, L120); `ChannelContext.read` (L49); `ChannelContext.markRead` (L73); `localUnread` (L147); `setChannelUnreadState` (L264); `loadChannelAtFirstUnreadMessage` (L264); `setTargetedMessage` (L264); `eventMe` (L283, L291, L292); `activeChannels` (L294) #### `18-draft-message.md` — Snippet / prop-table update @@ -636,8 +696,8 @@ Format: severity · what must change · scanner-detected breaking items with lin #### `02-upgrading-from-v8.md` — Mention only -- **Change:** Historical v8->v9 guide. Keep, but add a banner pointing to the new v9->v10 guide: several v9 replacements it recommends are removed in v10 (`useClientMutedUsers` L908/L1175/L1892, `channelUnreadStateStore` L456); RN 0.76 requirement (L42) is now 0.79. -- **Detected:** `deepImport` (L21); `rnVersion` (L42); `channelUnreadStateStore` (L456); `useClientMutedUsers` (L908, L1175, L1892) +- **Change:** Historical v8->v9 guide. Keep, but add a banner pointing to the new v9->v10 guide: several v9 replacements it recommends are removed in v10 (`useClientMutedUsers` L908/L1175/L1892, `channelUnreadStateStore` L456; stacked PRs: `additionalMessageComposerProps` L614/L654 and the `ThreadMessageComposer` slot L653/L1590/L1667/L1819/L1966, which v10 replaces with `` children); RN 0.76 requirement (L42) is now 0.79. +- **Detected:** `deepImport` (L21); `rnVersion` (L42); `channelUnreadStateStore` (L456); `useClientMutedUsers` (L908, L1175, L1892); `Thread.additionalMessageComposerProps` (L614, L654); `ThreadMessageComposer` (L653, L1590, L1667, L1819, L1966) ---