Skip to content

Next Release - #3904

Merged
isekovanic merged 7 commits into
mainfrom
develop
Oct 28, 2026
Merged

isekovanic merged 7 commits into
mainfrom
develop

Conversation

@isekovanic

Copy link
Copy Markdown
Contributor

🎯 Goal

πŸ›  Implementation details

🎨 UI Changes

iOS
Before After
Android
Before After

πŸ§ͺ Testing

β˜‘οΈ Checklist

  • I have signed the Stream CLA (required)
  • PR targets the develop branch
  • Documentation is updated
  • New code is tested in main example apps, including all possible scenarios
    • SampleApp iOS and Android
    • Expo iOS and Android

oliverlaz and others added 7 commits October 28, 2026 09:09
…ations (#3895)

Ticket: https://linear.app/stream/issue/RN-468

## 🎯 Goal

Android (GetStream/stream-chat-android#6848,
GetStream/stream-chat-android#6849, GetStream/stream-chat-android#6851),
iOS (GetStream/stream-chat-swift#4338) and web
(GetStream/stream-chat-react#3339) added two voice-agent visualizations
to their AI components. This brings them to
`stream-chat-react-native/ai-components` (and
`stream-chat-expo/ai-components`) with the same motion, so the platforms
look and move alike, and shows them in a new voice mode in
`ExpoChatGPTSample`.

## πŸ›  Implementation details

**Public API** (from the `ai-components` entry point):

- `VoiceWave`: a single string carrying a wave packet that erupts with
speech, its colors flowing along it.
- `VoiceGlow`: a soft globe whose color masses orbit inside it and swell
with speech.
- `useDictationAudioLevel()`: the microphone's live level while
dictation listens, as a Reanimated `SharedValue<number | null>`.
- Types `VoiceAgentState`, `VoiceVisualizationProps`, `VoiceWaveProps`,
`VoiceGlowProps`, `DictationLevel`; `useDictationState` and
`useDictationTranscript` are now exported too.
- An optional `voiceVisualization` theme key: `colors` (default palette,
the accent color) and `backdropColor` (light or dark look; the system
color scheme when unset).

Both components take `state` (`idle | connecting | listening | thinking
| speaking`), `colors` (one to five colors), `expressivity` (0 to 2) and
`audioLevel` (a `SharedValue`, read once a frame on the UI thread;
`null` animates speech on its own). Other `View` props go to the root.

**How it's built:**

- One motion driver, ported from Android's `VoiceVisualizationDriver`
(per-state motion table, frame-rate-independent easing, reduced motion
at 0.07x, tap bounce, HSV palette, and the `Steady`/`Smooth` level
responses for glow and wave). It's plain objects and worklets, advanced
in a Reanimated `useFrameCallback`, so the motion runs on the UI thread
and keeps going while JS is busy streaming a reply.
- `VoiceGlow` is a Skia `RuntimeEffect`: Android's AGSL shader is SkSL,
so it's ported nearly verbatim and runs on every Android version. Every
glow shares one compiled effect. There is no Canvas/CSS fallback and no
`rendering` prop.
- `VoiceWave` is a Skia path with a 13-stop gradient, as Android draws
it; its speech envelope uses the shader's float32 simplex noise (as on
iOS and the web).
- Drawn at most 2x for the glow and 3x for the wave, as on iOS.
- Touches are only observed on a hit area the size of the shape, so a
parent `Pressable` still fires and touches beside the shape pass
through. Hover ripples with a mouse or pen.
- Accessibility follows native: "Voice agent" as the label and the state
as the value; Reduce Motion is honoured.
- Where native and web differ, this follows native (e.g. no web-only
wave smoothing).

**Native dictation:**

- New `AIDictationOnLevel` event (no codegen spec change; it goes
through the existing `NativeEventEmitter`). Android maps the
recognizer's `onRmsChanged`; iOS measures the audio tap in dBFS,
throttled to ~30 Hz, tuned on an iPhone for `.measurement` mode (-60 to
-35 dBFS).
- Fix: iOS dictation now remembers the `AVAudioSession` configuration it
found and restores it when dictation ends. It used to leave the session
in the `.record` category, which muted all playback in the app after the
first dictation and kept other apps' audio ducked.

**Sample:** `ExpoChatGPTSample` gets a full-screen voice mode
(`VoiceGlow` for the agent, `VoiceWave` on the microphone level). It
listens with dictation, sends the transcript, follows the agent's AI
state, reads the reply aloud with `expo-speech` and listens again. The
send path moved into a shared `useSendAIMessage`, and the demo backend
is now called with `platform: 'openai'`, as in the React example (its
Anthropic key currently fails `/summarize`).

**Entry isolation:** everything lives under `src/ai-components`;
`validate-entry-isolation` passes.

## 🎨 UI Changes

Voice mode in `ExpoChatGPTSample`, on a physical iPhone 15 Pro Max and
the iOS simulator. Screenshots and a recording to be added.

## πŸ§ͺ Testing

- Unit tests: Android's 22 driver tests and the bounce tests ported;
palette, input clamping, simplex noise against GPU readback values, hit
areas, wave geometry, rendering, accessibility label and value, theme
fallbacks, reduced motion, and the dictation level hook. `yarn
test:unit`, `yarn lint`, `yarn typecheck` and `yarn build` pass.
- The SkSL shader compiles and renders under CanvasKit, matching
Android's Paparazzi snapshot.
- iOS simulator: both visualizations render and animate, and the
voice-mode loop runs (listening, thinking, speaking, back to listening).
Speech recognition itself fails on iOS 27 simulators ("Failed to
initialize recognizer"), so transcription was faked there.
- Physical iPhone: dictation, the mic level driving the wave, the spoken
reply and the full voice loop work.
- Not yet tested: Android on a device (the Android level mapping is
untuned), SampleApp.

## β˜‘οΈ Checklist

- [ ] I have signed the [Stream
CLA](https://docs.google.com/forms/d/e/1FAIpQLScFKsKkAJI7mhCr7K9rEIOpqIDThrWxuvxnwUq2XkHyG154vQ/viewform)
(required)
- [x] PR targets the `develop` branch
- [x] Documentation is updated (AI components README; the React Native
v9 AI integration docs are in a separate getstream.io PR)
- [ ] New code is tested in main example apps, including all possible
scenarios
  - [ ] SampleApp iOS and Android
  - [ ] Expo iOS and Android (iOS done, Android pending)
Pulls in the following fixes from stream-chat-js:
- poll crash when the last vote on an option is removed
- message pagination after message_limit 0 queries and disconnected latest pages
- custom attachments dropped when editing a message

The poll preview label test now expects the emptied option to be removed
from latest_votes_by_option instead of left as an empty array, which is
how the LLC handles it now.
fix: adjusted message composer bottom offset calculation
## 🎯 Goal

E2E test automation for the SDK, on par with the native iOS/Android and
Flutter suites, running on PRs.

Linear: [RN-450](https://linear.app/stream/issue/RN-450),
[RN-451](https://linear.app/stream/issue/RN-451),
[RN-452](https://linear.app/stream/issue/RN-452),
[RN-453](https://linear.app/stream/issue/RN-453),
[RN-454](https://linear.app/stream/issue/RN-454),
[RN-455](https://linear.app/stream/issue/RN-455),
[RN-457](https://linear.app/stream/issue/RN-457) (parent
[RN-449](https://linear.app/stream/issue/RN-449))

## πŸ›  Implementation details

**Dedicated app: `examples/E2ETestApp`.** A new bare RN 0.88 workspace
with only the SDK and its required peer dependencies (no Firebase, maps,
video or navigation library), like the native SDKs'
`StreamChatUITestsApp`. It has three screens: a user selector, the
channel list, and a channel stacked over the list. It reads a
`MOCK_SERVER_URL` launch argument (`react-native-launch-arguments`),
never persists a login and runs without offline storage, so every test
starts clean. A clean Release build takes ~2 min on an M-series Mac; the
SampleApp took ~15 min on Android. **The SampleApp app is unchanged**;
only its Fastfile gains the e2e lanes.

**Framework ([RN-450](https://linear.app/stream/issue/RN-450)):** Detox
20 + Jest 30 in `examples/E2ETestApp/e2e/`, with `ios.sim.*`,
`android.emu.*` and `android.att.*` configs (debug and release).

**Mock server ([RN-451](https://linear.app/stream/issue/RN-451)):** the
shared
[`stream-chat-test-mock-server`](https://github.com/GetStream/stream-chat-test-mock-server),
one server per test through its driver (port 4569).

**Page objects ([RN-452](https://linear.app/stream/issue/RN-452)) and
robots ([RN-453](https://linear.app/stream/issue/RN-453),
[RN-454](https://linear.app/stream/issue/RN-454),
[RN-455](https://linear.app/stream/issue/RN-455)):** structure and
naming follow the native suites:

- `BackendRobot` / `ParticipantRobot`: 1:1 ports of the Android robots
- `UserRobot` + `UserRobot*Asserts`: assert extensions, like Android's
- `LoginPage` / `ChannelListPage` / `MessageListPage`
- `setUpStreamTestCase()`, `step()`, and uiautomator-style element
helpers

Robots chain fluently like the native ones: `await
userRobot.login().openChannel().assertMessage('Hi')`.

**Smoke suite:** 6 tests (MessageList Γ—3, ChannelList Γ—3), named after
their Android counterparts and keeping the native expectations.

**SDK (additive testIDs only):** `channel-preview-title`,
`channel-preview-message`, `channel-preview-sender`, and
`message-status-{read,delivered,sending,sent}`, with a unit test.

**CI ([RN-457](https://linear.app/stream/issue/RN-457)):**
`.github/workflows/e2e-test.yml`.

- **Android** runs on PRs: `ubuntu-latest`, KVM, API 36 AOSP emulator,
`pixel` profile (the native Android SDK's setup).
- **iOS** runs only on demand (`workflow_dispatch` with `ios: true`),
not on PRs, to save CI time and macOS runners: `macos-15`, Xcode 26.2,
iPhone 17 Pro.
- Gated like the iOS SDK: a cheap `Check E2E Required` job runs
`is_check_required` over the e2e sources (Fastfile `impact_analysis`),
and `test_e2e` checks it too.
- **`Test E2E Result` is the check to require in branch protection.** It
covers Android (it doesn't wait for iOS) and passes when the Android job
passes or is skipped as not required.
- Caching:
- the built `.app` / `.apk`, keyed on the app and SDK sources but not on
`e2e/`, so a test-only change skips the native build
  - `node_modules` of `yarn workspaces focus e2etestapp`
- CocoaPods, ccache (with `CCACHE_BINARY` exported, without which RN's
ccache wrappers silently skip ccache), the Detox framework, Gradle
(`cache-read-only: false`, since `setup-gradle` otherwise only writes on
the default branch), and the mock server's gems
- each PR fills its own caches (the workflow doesn't run on `develop`):
its first run builds cold, and later pushes reuse them
- On failure, uploads the mock server logs, Detox's logs and
screenshots, and the emulator's crash data.

**Fastlane** (in `examples/SampleApp/fastlane`, next to the repo's other
lanes): `start_mock_server`, `stop_mock_server`, `e2e_pod_install`,
`test_e2e`, `e2e_check_required`.

**Notable findings**, documented in `examples/E2ETestApp/e2e/README.md`:

- Fabric mounts some testID'd views (`message-list-item-*`,
`message-text-container`) empty, with their content as siblings, so page
objects scope to `message-content-wrapper` or the list instead.
- iOS runs without Detox's idle synchronization, which can wedge on RN's
UIManager shadow queue. Robots wait explicitly, and taps retry while the
target isn't hittable yet.
- `launchApp({ permissions })` makes Detox restart SpringBoard once per
permission on every launch. On CI that cost 1.5–3 min per test, so the
suite doesn't pass any.
- Android sets the composer text directly (as the native suite does).
Typing through Gboard left the text in a composing state that the
composer didn't send.

How to run and extend the suite: `examples/E2ETestApp/e2e/README.md`.

## 🎨 UI Changes

None (testIDs only).

## πŸ§ͺ Testing

- Locally (release builds): iOS simulator 6/6, 31s for the whole suite;
Android emulator 6/6.
- CI: green on
[39459700744](https://github.com/GetStream/stream-chat-react-native/actions/runs/39459700744)
and
[39467760315](https://github.com/GetStream/stream-chat-react-native/actions/runs/39467760315).
On the latter the iOS test step took 3m 12s (31 min before the
launch-permission fix), and Android 3m 12s.
- `e2e_check_required` against real PRs: no PR β†’ `true`, #3870 (docs
only) β†’ `false`, #3879 (`package/`) β†’ `true`.
- `yarn build`, `yarn lint`, `yarn typecheck` (now including
`e2etestapp`), `cd package && yarn test:typecheck`, `yarn test:unit`
green.

Not covered yet: Allure TestOps reporting, full suite ports, and iOS on
PRs (manual runs only, to save CI time and macOS runners).

## β˜‘οΈ Checklist

- [ ] I have signed the [Stream
CLA](https://docs.google.com/forms/d/e/1FAIpQLScFKsKkAJI7mhCr7K9rEIOpqIDThrWxuvxnwUq2XkHyG154vQ/viewform)
(required)
- [x] PR targets the `develop` branch
- [x] Documentation is updated
- [ ] New code is tested in main example apps, including all possible
scenarios
  - [ ] SampleApp iOS and Android
  - [ ] Expo iOS and Android
@github-actions

Copy link
Copy Markdown

Next releases

v9.11.0

9.11.0 (2026-10-28)

Bug Fixes

  • adjusted message composer bottom offset calculation (0e9a0c0)
  • adjusted message composer bottom offset calculation (9bc37a3)
  • bump stream-chat to 9.54.2 (fc6b6e6)
  • bump stream-chat to 9.54.2 (a6bb08f)

Features

sampleapp@v4.16.3

4.16.3 (2026-10-28)

Bug Fixes

  • bump stream-chat to 9.54.2 (fc6b6e6)
  • bump stream-chat to 9.54.2 (a6bb08f)

@Stream-SDK-Bot

Copy link
Copy Markdown
Contributor

SDK Size

title develop branch diff status
js_bundle_size 2036 KB 2036 KB 0 B 🟒
ai_components_size 163 KB 163 KB 0 B 🟒

@isekovanic
isekovanic merged commit e88d018 into main Oct 28, 2026
20 of 23 checks passed
@stream-ci-bot

Copy link
Copy Markdown
Contributor

πŸŽ‰ This PR is included in version 9.11.0 πŸŽ‰

The release is available on:

Your semantic-release bot πŸ“¦πŸš€

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

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants