Skip to content

Commit 4599ed1

Browse files
authored
Merge pull request #3639 from GetStream/develop
Next Release
2 parents 1649d5e + 3ed6001 commit 4599ed1

327 files changed

Lines changed: 33396 additions & 204079 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.claude/skills/accessibility/SKILL.md

Lines changed: 83 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ Use this skill whenever code changes can affect screen-reader users (VoiceOver o
1010
## Non-negotiable rules
1111

1212
1. **Native semantics first.** Use `Pressable`, `TextInput`, `Switch`, `Image` directly. Use `accessibilityRole` only when native semantics cannot represent the widget (`menu`, `menuitem`, `progressbar`, `radio`, `checkbox`, `article`, `alert`, `tablist`, `tab`).
13-
2. **Never hardcode English** in `accessibilityLabel`/`accessibilityHint`/announcement strings. For SDK `Button`, pass `accessibilityLabelKey='a11y/...'` (and `accessibilityLabelParams` when needed). For non-Button components, use `useA11yLabel('a11y/...', params)` or `t('a11y/...')` directly when you don't need the disabled-state short-circuit. Add the key to all 12 locale files in `package/src/i18n/`.
13+
2. **Never hardcode English** in `accessibilityLabel`/`accessibilityHint`/announcement strings. For SDK `Button`, pass `accessibilityLabelKey='a11y/...'` (and `accessibilityLabelParams` when needed). For non-Button components, use `useA11yLabel('a11y/...', params)` or `t('a11y/...')` directly when you don't need the disabled-state short-circuit. Add the key to all 13 locale files in `package/src/i18n/` (`ar, en, es, fr, he, hi, it, ja, ko, nl, pt-br, ru, tr`).
1414
3. **Gate behavior on `useAccessibilityContext().enabled`.** A11y is opt-in. New listeners, subscriptions, and announcer mounts must be no-ops when `enabled` is false. New `accessibilityRole`/`accessibilityState` props are fine to render unconditionally — they cost ~zero.
1515
4. **One focusable target per action.** Don't nest `Pressable` inside `Pressable`. Mark inner decorative views with `accessibilityElementsHidden` (iOS) + `importantForAccessibility='no-hide-descendants'` (Android) so the parent carries the label.
1616
5. **Decorative visuals stay hidden from AT.** Icon-only buttons must carry an `accessibilityLabel` on the wrapper, and the SVG icon should be hidden.
@@ -21,7 +21,7 @@ Use this skill whenever code changes can affect screen-reader users (VoiceOver o
2121
- **Foundation primitives**`package/src/a11y/` (utilities + low-level hooks).
2222
- **Runtime announcer infra**`package/src/components/Accessibility/` (`NotificationAnnouncer`, `useAccessibilityAnnouncer`, `useIncomingMessageAnnouncements`).
2323
- **Config + provider**`package/src/contexts/accessibilityContext/`, mounted by `OverlayProvider`.
24-
- **i18n**`a11y/*` keys in all 12 locale JSONs (`en, es, fr, he, hi, it, ja, ko, nl, pt-br, ru, tr`).
24+
- **i18n**`a11y/*` keys in all 13 locale JSONs (`ar, en, es, fr, he, hi, it, ja, ko, nl, pt-br, ru, tr`).
2525
- **Component-level a11y attributes** → in the component itself.
2626
- **Platform divergence (iOS vs Android)** → use `Platform.OS` or `useResolvedModalAccessibilityProps`. Don't duplicate the file — RN doesn't need `.ios.tsx`/`.android.tsx` splits for a11y.
2727
- **Tests** → nearest `__tests__/` folder; use `@testing-library/react-native` semantic queries (`getByRole`, `getByLabelText`).
@@ -97,6 +97,81 @@ const transitionDuration = reduceMotion ? 0 : 250;
9797

9898
Disable spring animations and limit fade durations when this is true.
9999

100+
### 6) Curated single focus stop for visual content — `CompositeAccessibilityProbe`
101+
102+
```tsx
103+
import { CompositeAccessibilityProbe } from 'stream-chat-react-native';
104+
105+
<CompositeAccessibilityProbe label={accessibilityLabel}>
106+
{/* avatars, icons, composed graphics — visually decorative */}
107+
</CompositeAccessibilityProbe>
108+
```
109+
110+
Wraps non-Text visual content with a single, cross-platform-stable focus stop carrying the provided `label`. Renders a hidden `Text` sibling that carries the label + a `View accessibilityElementsHidden importantForAccessibility='no-hide-descendants'` around the children. Use for avatars, mute icons, isolated badges, composed graphics that should announce as one semantic unit.
111+
112+
Pass the result of `useA11yLabel(...)` directly — when `label` is `undefined` (a11y opt-out), the probe is a no-op and renders children untouched.
113+
114+
Live examples: `ChannelAvatar.tsx`, `ChannelPreviewMutedStatus.tsx`, `ChannelMessagePreviewDeliveryStatus.tsx`.
115+
116+
### 7) Splicing extra a11y info into compose — `HiddenA11yText`
117+
118+
```tsx
119+
import { HiddenA11yText } from 'stream-chat-react-native';
120+
121+
<Pressable>
122+
<Icon />
123+
{selected ? <HiddenA11yText label={useA11yLabel('a11y/you reacted')} /> : null}
124+
</Pressable>
125+
```
126+
127+
A visually-invisible `<Text>` that exists only to contribute extra information to a parent's compose loop. Use it to splice in supplementary state ("you reacted", "and N more", "unread") that doesn't have a natural visible Text in the tree.
128+
129+
Different concern from `CompositeAccessibilityProbe`:
130+
- `HiddenA11yText` — "inject extra a11y-only text into a compose chain"
131+
- `CompositeAccessibilityProbe` — "make this whole visual element one focus stop with a curated label"
132+
133+
Live examples: `MessageStatus.tsx`, `ReactionListClustered.tsx`, `ReactionListItem.tsx`.
134+
135+
### 8) Cross-platform auto-compose on a plain View
136+
137+
```tsx
138+
<View accessible accessibilityRole='text'>
139+
{/* children whose labels should auto-compose into one announcement */}
140+
</View>
141+
```
142+
143+
iOS auto-composes descendant labels when a `View` is `accessible={true}` without an explicit `accessibilityLabel`. Android requires the parent to trip a gate — set any of `accessibilityRole`, `accessibilityState`, `accessibilityActions`, or `accessibilityLabelledBy`. `accessibilityRole='text'` (or `'none'`) is the lightest gate-tripper and a no-op for iOS composition.
144+
145+
`Pressable` defaults `accessibilityRole='button'`, so it auto-trips the gate. Plain `View accessible={true}` without a role does NOT — Android falls back to its default heuristic (reads one visible Text descendant only).
146+
147+
Live example: `MessageFooter.tsx``<View accessible accessibilityRole='text'>` makes the footer one focus stop on both platforms reading `"Read 11:05 AM"`.
148+
149+
See full memory: `rn_android_a11y_compose_gate.md`.
150+
151+
### 9) Drill-in for interactive children inside a Pressable
152+
153+
```tsx
154+
<Pressable accessible={hasInteractiveContent ? false : undefined} onLongPress={...}>
155+
{/* mix of interactive children — attachments, quoted reply, poll options, etc. */}
156+
</Pressable>
157+
```
158+
159+
When a Pressable wraps mixed content that includes interactive children, the row's default single-focus-stop behavior subsumes them — screen-reader users can't activate the children individually. Setting `accessible={false}` on the Pressable removes the row stop, so VO/TalkBack drill into each interactive child. The Pressable's `onPress` / `onLongPress` still fire because VO/TalkBack synthesize taps at the focused child's coordinates, which land inside the Pressable's hit area.
160+
161+
Live example: `MessageContent.tsx``accessible={hasInteractiveContent ? false : undefined}` where `hasInteractiveContent` covers poll, quoted message, attachments, shared location.
162+
163+
### 10) Reshow announcements — `useAnnounceOnShow`
164+
165+
```tsx
166+
useAnnounceOnShow(visible, useA11yLabel('a11y/Replying to {{user}}', { user: name }));
167+
```
168+
169+
Announces `label` once each time `visible` flips from `false` to `true`. Resets on hide, so reshows re-announce — unlike `useAnnounceOnStateChange` which dedupes consecutive identical strings.
170+
171+
Use for transient surfaces that appear and disappear repeatedly within a session (modals, autocomplete pickers, reply previews) where the user benefits from hearing the affordance on every reappearance.
172+
173+
Live example: `Reply.tsx` — fires when a reply preview shows in the composer.
174+
100175
## Anti-patterns to avoid
101176

102177
- **Hardcoded English `accessibilityLabel`** strings inside component code. For SDK `Button`, use `accessibilityLabelKey='a11y/...'`; otherwise use `useA11yLabel('a11y/...')` or `t('a11y/...')`.
@@ -134,11 +209,17 @@ Recommended for non-trivial changes:
134209

135210
- `package/src/contexts/accessibilityContext/AccessibilityContext.tsx` — config schema + provider + imperative announcer context.
136211
- `package/src/components/Accessibility/hooks/useIncomingMessageAnnouncements.ts` — port of stream-chat-react's hook.
212+
- `package/src/components/Accessibility/CompositeAccessibilityProbe.tsx` — curated-single-focus-stop wrapper for visual content (avatar, icons, badges).
213+
- `package/src/components/Accessibility/HiddenA11yText.tsx` — visually-invisible Text that splices extra info into a parent's compose chain ("you reacted", "and N more", etc).
137214
- `package/src/a11y/hooks/useA11yLabel.ts` — translated-label-or-undefined.
215+
- `package/src/a11y/hooks/useAnnounceOnStateChange.ts` — announce on string-change with dedup.
216+
- `package/src/a11y/hooks/useAnnounceOnShow.ts` — announce on `visible: false → true` transitions, resets on hide (no dedup).
138217
- `package/src/a11y/hooks/useResolvedModalAccessibilityProps.ts` — modal a11y props.
139218
- `package/src/components/ui/Avatar/Avatar.tsx` — example of `name` + `useA11yLabel` usage.
140219
- `package/src/components/UIComponents/BottomSheetModal.tsx` — example of `useResolvedModalAccessibilityProps`.
141220
- `package/src/components/AITypingIndicatorView/AITypingIndicatorView.tsx` — example of `useAnnounceOnStateChange`.
221+
- `package/src/components/Message/MessageItemView/MessageFooter.tsx` — example of cross-platform auto-compose on a View (`accessible + accessibilityRole='text'`).
222+
- `package/src/components/Message/MessageItemView/MessageContent.tsx` — example of conditional drill-in (`accessible={hasInteractiveContent ? false : undefined}`).
142223

143224
## Cross-SDK parity
144225

File renamed without changes.
Lines changed: 3 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,8 @@
11
name: 'Install and Build SDK'
2-
description: 'Runs yarn install for all the packages and sample and fails if yarn lock has a change that is not committed'
2+
description: 'Runs yarn install at the workspace root and fails if yarn.lock has uncommitted changes'
33
runs:
44
using: 'composite'
55
steps:
6-
- name: Install Root repo dependencies
7-
run: yarn --frozen-lockfile
8-
shell: bash
9-
- name: Install & Build the Core Package
10-
run: |
11-
cd package/
12-
yarn --frozen-lockfile
13-
shell: bash
14-
- name: Install & Build the Native Package
15-
run: |
16-
cd package/native-package/
17-
yarn
18-
shell: bash
19-
- name: Install & Build the Sample App
20-
working-directory: examples/SampleApp
21-
run: yarn
6+
- name: Install workspace dependencies
7+
run: yarn install --immutable
228
shell: bash

.github/workflows/changelog-preview.yml

Lines changed: 11 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,6 @@ jobs:
1313
matrix:
1414
node-version: [ 24.x ]
1515
steps:
16-
- name: Use Node.js ${{ matrix.node-version }}
17-
uses: actions/setup-node@v6
18-
with:
19-
node-version: ${{ matrix.node-version }}
20-
- name: Install Linux build tools
21-
run: sudo apt-get update && sudo apt-get install -y build-essential
2216
- uses: actions/checkout@v6
2317
with:
2418
ref: develop
@@ -31,15 +25,12 @@ jobs:
3125
with:
3226
node-version: ${{ matrix.node-version }}
3327
registry-url: 'https://registry.npmjs.org'
28+
cache: 'yarn'
29+
cache-dependency-path: 'yarn.lock'
30+
- name: Install Linux build tools
31+
run: sudo apt-get update && sudo apt-get install -y build-essential
3432
- name: Installation
35-
run: |
36-
yarn --frozen-lockfile
37-
cd package/
38-
yarn --frozen-lockfile
39-
cd native-package/
40-
yarn
41-
cd ../../examples/SampleApp/
42-
yarn
33+
run: yarn install --immutable
4334

4435
- name: Generate Changelog
4536
id: generate_changelog
@@ -54,13 +45,13 @@ jobs:
5445
5546
echo "Changelog file ready! Setting up outputs"
5647
CHANGELOG_PREVIEW=$(cat NEXT_RELEASE_CHANGELOG.md)
57-
58-
CHANGELOG_PREVIEW_ESCAPED="${CHANGELOG_PREVIEW//'%'/'%25'}"
59-
CHANGELOG_PREVIEW_ESCAPED="${CHANGELOG_PREVIEW_ESCAPED//$'\n'/'%0A'}"
60-
CHANGELOG_PREVIEW_ESCAPED="${CHANGELOG_PREVIEW_ESCAPED//$'\r'/'%0D'}"
6148
62-
echo "::set-output name=exists::true"
63-
echo "::set-output name=preview::$CHANGELOG_PREVIEW_ESCAPED"
49+
echo "exists=true" >> "$GITHUB_OUTPUT"
50+
{
51+
echo "preview<<CHANGELOG_PREVIEW_EOF"
52+
echo "$CHANGELOG_PREVIEW"
53+
echo "CHANGELOG_PREVIEW_EOF"
54+
} >> "$GITHUB_OUTPUT"
6455
fi
6556
- uses: marocchino/sticky-pull-request-comment@v3
6657
if: steps.generate_changelog.outputs.exists == 'true'

.github/workflows/check-pr.yml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,15 @@ jobs:
2323
uses: actions/setup-node@v6
2424
with:
2525
node-version: ${{ matrix.node-version }}
26+
cache: 'yarn'
27+
cache-dependency-path: 'yarn.lock'
2628
- name: Install Linux build tools
2729
run: sudo apt-get update && sudo apt-get install -y build-essential
2830
- name: Install && Build - SDK and Sample App
2931
uses: ./.github/actions/install-and-build-sdk
3032
- name: Lint
31-
run: yarn lerna-workspaces run lint
33+
run: yarn lint
3234
- name: Typecheck tests
33-
run: cd package && yarn test:typecheck
35+
run: yarn workspace stream-chat-react-native-core test:typecheck
3436
- name: Test
3537
run: yarn test:coverage

.github/workflows/release.yml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ jobs:
3131
with:
3232
node-version: ${{ matrix.node-version }}
3333
registry-url: 'https://registry.npmjs.org'
34+
cache: 'yarn'
35+
cache-dependency-path: 'yarn.lock'
3436
- name: Install Linux build tools
3537
run: sudo apt-get update && sudo apt-get install -y build-essential
3638

@@ -43,7 +45,7 @@ jobs:
4345
uses: ./.github/actions/install-and-build-sdk
4446

4547
- name: Lint
46-
run: yarn lerna-workspaces run lint
48+
run: yarn lint
4749

4850
- name: Test
4951
if: github.ref == 'refs/heads/develop'

.github/workflows/sample-distribution.yml

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,13 @@ jobs:
2323
uses: webfactory/ssh-agent@v0.10.0
2424
with:
2525
ssh-private-key: ${{ secrets.BOT_SSH_PRIVATE_KEY }}
26+
- uses: actions/checkout@v6
2627
- name: Use Node.js ${{ matrix.node-version }}
2728
uses: actions/setup-node@v6
2829
with:
2930
node-version: ${{ matrix.node-version }}
30-
- uses: actions/checkout@v6
31+
cache: 'yarn'
32+
cache-dependency-path: 'yarn.lock'
3133
- uses: maxim-lobanov/setup-xcode@v1
3234
with:
3335
xcode-version: '26.2' # Update as needed
@@ -64,11 +66,13 @@ jobs:
6466
matrix:
6567
node-version: [24.x]
6668
steps:
69+
- uses: actions/checkout@v6
6770
- name: Use Node.js ${{ matrix.node-version }}
6871
uses: actions/setup-node@v6
6972
with:
7073
node-version: ${{ matrix.node-version }}
71-
- uses: actions/checkout@v6
74+
cache: 'yarn'
75+
cache-dependency-path: 'yarn.lock'
7276
- name: Install Linux build tools
7377
run: sudo apt-get update && sudo apt-get install -y build-essential
7478
- uses: actions/setup-java@v5

.gitignore

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,12 @@
55
!.yarn/sdks
66
!.yarn/versions
77

8+
# In case someone runs `yarn install` inside a workspace child, Yarn 4 will
9+
# emit a transient .yarn/install-state.gz there; the root-anchored pattern
10+
# above doesn't catch nested copies.
11+
**/.yarn/install-state.gz
12+
**/.yarn/cache
13+
814
node_modules
915
NEXT_RELEASE_CHANGELOG.md
1016
artifacts

.husky/commit-msg

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1 @@
1-
#!/bin/sh
2-
. "$(dirname "$0")/_/husky.sh"
3-
4-
npx commitlint --edit $1
51
npx commitlint --edit $1

.husky/pre-commit

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1 @@
1-
#!/bin/sh
2-
. "$(dirname "$0")/_/husky.sh"
3-
41
dotgit/hooks/pre-commit-format.sh && dotgit/hooks/pre-commit-reject-binaries.py

0 commit comments

Comments
 (0)