diff --git a/AGENTS.md b/AGENTS.md index e8b3190044..ce40fa75b5 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,191 +1,401 @@ -# Guidance for AI coding agents +# AGENTS.md -File purpose: operational rules for automated or assisted code changes. Human-facing conceptual docs belong in `README.md` or the docs site. +Guidance for AI coding agents (Claude Code, Copilot, Cursor, Codex, Aider, etc.) working in this repository. Human readers are welcome, but this file is written for tools. + +> **Single source of truth.** `CLAUDE.md` contains nothing but `@AGENTS.md`, which Claude Code expands into this file. Edit this file only — never fork guidance into `CLAUDE.md`. + +Agents should prioritize backwards compatibility, API stability, accessibility, performance discipline and high test coverage when changing code. ## Repository purpose -Stream Chat SDKs for: +Stream Chat React Native SDK monorepo. The core UI SDK lives in `package/` (published as `stream-chat-react-native-core`) and is built on top of the `stream-chat` JS client. Two thin wrappers ship it to the two supported toolchains: -- React Native CLI -- Expo +- `stream-chat-react-native` (`package/native-package/`) — React Native CLI / bare RN +- `stream-chat-expo` (`package/expo-package/`) — Expo -Goals: API stability, backward compatibility, predictable releases, strong test coverage, accessibility, and performance discipline. +Targets iOS and Android. ## Tech & toolchain -- Languages: TypeScript, React (web + native) -- Runtime: Node (use `nvm use` with `.nvmrc`) -- Package manager: Yarn (V1) -- Testing: Jest (unit) -- Lint/Format: ESLint + Prettier -- Build: Package-local build scripts (composed via root) -- Release: Conventional Commits -> automated versioning/publishing -- Platforms: - - React Native: iOS and Android +- **Languages:** TypeScript + React Native +- **Runtime:** Node 24 (`.nvmrc` is `v24`; root `engines.node` is `>=20.19.4`; CI runs `24.x`) +- **Package manager:** Yarn 4.15.0 (Berry). The binary lives at `.yarn/releases/yarn-4.15.0.cjs` and is activated via `yarnPath` in `.yarnrc.yml`. Any globally installed `yarn` (even the Homebrew classic 1.x) acts only as a launcher — no Corepack required. +- **Workspaces:** single root `yarn.lock`; workspaces are `configs/typescript-config`, `package`, `package/native-package`, `package/expo-package`, `examples/SampleApp`, `examples/ExpoMessaging`, `examples/TypeScriptMessaging`. **No Lerna.** +- **Testing:** Jest with the `@react-native/jest-preset` + `@testing-library/react-native`. +- **Build:** `react-native-builder-bob` → CommonJS (`lib/commonjs`), ESM (`lib/module`), types (`lib/typescript`) +- **Lint/format:** ESLint 9 flat config + Prettier, strict (`--max-warnings 0`) +- **CI:** GitHub Actions — PR validation on build + lint + typecheck + tests +- **Release:** Conventional Commits + semantic-release, driven by `yarn workspaces foreach` + +### Root configuration files + +`.nvmrc` · `.yarnrc.yml` · `eslint.config.mjs` · `.prettierrc` / `.prettierignore` · `commitlint.config.js` · `configs/typescript-config/` (`base.json`, `library.json` — the shared presets `package/tsconfig.json` extends) · `.editorconfig` · `.husky/` + +Per-package: `package/tsconfig.json` (library) · `package/tsconfig.test.json` (tests) · `package/jest.config.js` · `package/babel.config.js` · `package/i18next.config.ts` + +Respect repo-specific rules. Do not suppress lint rules broadly; justify and scope every exception with an inline comment. + +## Project layout + +- `package/` — core SDK (`stream-chat-react-native-core`) + - `native-package/` — bare RN wrapper (`stream-chat-react-native`) + - `expo-package/` — Expo wrapper (`stream-chat-expo`) + - `shared-native/{ios,android}` — native source shared by both wrappers; synced into them, never edited in place +- `examples/` — `SampleApp` (full-featured), `ExpoMessaging`, `TypeScriptMessaging` +- `configs/typescript-config/` — shared `tsconfig` presets +- `ai-docs/` — agent-facing deep dives (see [References](#references)) +- `perf/` — on-device performance toolkit (see [Accessibility, RTL & performance](#accessibility-rtl--performance)) +- `release/` — semantic-release scripts +- `bin/`, `dotgit/hooks/` — release and git-hook scripts + +### SDK source (`package/src/`) + +- `components/` — 27 component directories (`ChannelList`, `MessageList`, `MessageInput`, `Thread`, `Poll`, `ImageGallery`, `ChannelDetails`, `MessageMenu`, …) +- `contexts/` — 40 React Context providers. The primary way components receive state and callbacks. Key ones: `chatContext`, `channelContext`, `messagesContext`, `themeContext`, `translationContext` +- `hooks/` — shared custom hooks; component-specific hooks live in that component's `hooks/` +- `state-store/` — client-side stores on `useSyncExternalStore` with a selector pattern (audio player, video player, image gallery, message overlay, attachment picker, …) +- `store/` — offline SQLite persistence: `OfflineDB.ts`, `SqliteClient.ts`, `schema.ts`, `mappers/`, `apis/` +- `theme/` — theming system + `topologicalResolution.ts` + `generated/` tokens +- `i18n/` — the 13 translation JSON files (the `Streami18n` wrapper class lives in `utils/i18n/`) +- `a11y/` — accessibility primitives (`a11yUtils.ts`, `hooks/`) +- `middlewares/` — command UI middlewares (`attachments.ts`, `emojiControl.ts`) +- `icons/` — SVG icon components +- `mock-builders/` — test fixtures and fakes (also aliased as `mock-builders` in Jest) +- `native.ts` — native-capability interfaces + `registerNativeHandlers()` + +Use the closest folder's patterns and conventions when editing. ## Environment setup -1. `nvm use` -2. `yarn install` -3. (Optional) Verify: `node -v` matches `.nvmrc` -4. `cd package` -5. `yarn install-all && cd ..` -6. Run tests: `yarn test:unit` - -## Project layout (high-level) - -- `package/` - - `native-package/` (`react-native` CLI specific bundle) - - `expo-package/` (`Expo` specific bundle) - - `.` (core UI SDK, shared between both bundles) -- `examples/` - - `ExpoMessaging/` - - `SampleApp/` - - `TypeScriptMessaging/` -- Config roots: linting, tsconfig, playwright, babel -- Do not edit generated output (`lib/`, build artifacts) - -## Core commands (Runbook) - -| Action | Command | -|-------------------------|------------------------------| -| Install deps | `yarn install` | -| Full build | `yarn install-all` | -| Watch (if available) | `yarn start` (add if absent) | -| Lint | `yarn lint` | -| Fix lint (if separate) | `yarn lint --fix` | -| Unit tests (CI profile) | `yarn test:unit` | +```bash +nvm use # Node 24 +yarn install # every workspace, single root lockfile +yarn test:unit # smoke-check the setup +``` + +## Essential commands + +All commands run from the repo root unless noted. + +```bash +# Install +yarn install # every workspace (single root lockfile) +yarn install --immutable # CI-style; fails if yarn.lock would change + +# Build +yarn build # SDK build (commonjs + esm + types) via builder-bob + +# Test +yarn test:unit # all unit tests (sets TZ=UTC) +yarn test:coverage # with coverage — what CI runs +cd package && TZ=UTC npx jest path/to/file.test.tsx # single file + +# Type checking +yarn typecheck # every workspace + example app, in parallel +cd package && yarn test:typecheck # SDK src + tests + mock-builders + +# Lint / format +yarn lint # prettier --list-different + eslint --max-warnings 0 + validate-translations +yarn lint-fix # ALWAYS run this before committing +yarn eslint # eslint a single path + +# Translations +yarn workspace stream-chat-react-native-core build-translations # i18next-cli sync + +# Shared native sync (after editing package/shared-native/) +yarn workspace stream-chat-react-native-core shared-native:sync + +# Sample app +yarn workspace sampleapp start # Metro bundler +yarn workspace sampleapp ios +yarn workspace sampleapp android +``` + +**Type gates — know which one is strict.** `yarn typecheck` fans out to every workspace; each SDK workspace runs `tsc --noEmit -p tsconfig.test.json`, which includes tests and `mock-builders` but relaxes `noUnusedLocals` / `noUnusedParameters`. `package`'s `typecheck` and `test:typecheck` are currently the same command. The **strictest** gate is `yarn build`: bob type-checks with `package/tsconfig.json`, which keeps the unused-symbol rules on and excludes `__tests__` / `mock-builders`. Always run `cd package && yarn test:typecheck` after code changes — `yarn lint` and `yarn test:unit` do not catch all type errors. + +**Adding dependencies.** `.yarnrc.yml` sets `npmMinimalAgeGate: 3d`, so packages published within the last three days are refused unless listed under `npmPreapprovedPackages` (currently `stream-chat`, `react-native-teleport`). `enableScripts: false` disables install scripts globally; per-package opt-ins live in root `dependenciesMeta` (`@swc/core`, `better-sqlite3`, `react-native-nitro-modules`, `unrs-resolver`). `nmHoistingLimits: workspaces` keeps workspace deps unhoisted — expect duplicated copies under each workspace's `node_modules`. + +## Architecture: core concepts + +### Component hierarchy + +``` + # gesture/overlay host, accessibility config + └─ # root: SDK metadata, offline DB, subscriptions + ├─ + └─ # state container: messages, threads, composer + ├─ + ├─ + └─ +``` + +`` is the entry point. It sets SDK metadata on the `stream-chat` client (identifier, device info), disables the JS client's `recoverStateOnReconnect` (the SDK handles recovery itself), registers subscriptions for threads/polls/reminders (cleaned up on unmount), initializes `OfflineDB` when `enableOfflineSupport` is set, and wraps children in `ChatProvider` → `TranslationProvider` → `ThemeProvider` → `ChannelsStateProvider`. + +### Context three-layer pattern + +Every context in `package/src/contexts/` follows the same shape: + +1. `createContext()` with a sentinel default (`DEFAULT_BASE_CONTEXT_VALUE`) +2. an `` wrapper component +3. a `useXContext()` hook that throws when used outside the provider (suppressed in tests via `isTestEnvironment()`) + +Context values are assembled in dedicated `useCreateXContext()` hooks (e.g. `useCreateChannelContext`) that memoize with **selective** dependencies to avoid unnecessary re-renders. + +### Customization: `WithComponents`, not component props + +`ChannelProps` **does not** accept component overrides (that was the v8 API — see `ai-docs/ai-migration.md` §3.1). Slots come from `ComponentsContext`, populated by ``, which merges over the parent context so nesting works (closest wins) and deep-merges the nested `icons` map: + +```tsx + + + + + + +``` + +`package/src/contexts/componentsContext/defaultComponents.ts` is the authority: it exports `DEFAULT_COMPONENTS` with ~173 slots plus a nested `icons` map of ~92 icons, and `ComponentOverrides` is *derived* from it — **adding a default automatically makes it overridable.** Read slots with `useComponentsContext()`, which merges user overrides over the defaults so every slot is guaranteed defined and callers destructure without fallbacks. + +Two mechanics not to "fix": + +- Both `WithComponents` and `useComponentsContext` memoize with `[]` — overrides are read once at mount and **must be stable**. Do not inline an override object that changes identity per render. +- `defaultComponents` is `require`d lazily inside `getDefaults()` to break a circular import (`defaultComponents` → components → `useComponentsContext`). Do not convert it to a static top-level import. + +`Channel`'s own props are behavioral escape hatches instead, typed as `Pick<…ContextValue, …>` over the contexts it provides (handlers like `handleDelete` / `handleReaction`, `messageActions`, `supportedReactions`, `myMessageTheme`, `overrideOwnCapabilities`, …). + +When adding a customizable component: add it to `DEFAULT_COMPONENTS`, then read it via `useComponentsContext()`. + +### State stores + +`state-store/` holds `useSyncExternalStore`-based stores consumed with `useStateStore(store, selector)` for fine-grained subscriptions outside the context system. Define selectors at module scope so they stay referentially stable — an inline selector re-subscribes on every render. + +### Native module abstraction + +`package/src/native.ts` declares TypeScript interfaces for every platform-specific capability (image picking, compression, haptics, audio/video, clipboard, share). Implementations are injected at runtime via `registerNativeHandlers()`: `stream-chat-expo` supplies Expo implementations, `stream-chat-react-native` supplies bare-RN ones. Calling an unregistered handler throws with a message naming the package to import. + +Platform branching uses runtime `Platform.select()` / `Platform.OS` checks. There is **no** `moduleSuffixes` in any tsconfig, and the only platform-suffixed source files are the generated theme tokens (`theme/generated/*/StreamTokens.{ios,android,web}.ts`), resolved by Metro's platform extensions. Do not introduce new `.ios.ts` / `.android.ts` splits. + +### Native / Expo wrapper relationship + +Both wrappers are thin. They: + +1. call `registerNativeHandlers()` with platform-specific implementations +2. export optional dependency wrappers (`Audio`, `Video`, `FlatList`) from `src/optionalDependencies/` +3. re-export everything: `export * from 'stream-chat-react-native-core'` + +Native code shared by both wrappers lives in `package/shared-native/{ios,android}` and is copied into each wrapper by `shared-native:sync`. **Edit `shared-native/`, never the synced copies.** + +## Critical architectural patterns + +- **Memoization:** components use `React.memo()` with custom `areEqual` comparators (not HOCs). Comparators check cheap props before deep message comparison — keep that ordering when extending one. +- **Offline-first:** SQLite-backed persistence with sync-status tracking and a pending-task queue. Writes must go through `OfflineDB`, not raw SQL. +- **Selective memo dependencies:** `useCreateXContext` hooks intentionally omit unstable values. Adding a dependency there can cause a re-render storm; removing one can cause stale UI. Profile before changing. +- **Cancel stale async work:** media and network operations must be cancelled on unmount (`AbortController` for fetch-like APIs, unsubscribe listeners). Check instance IDs / timestamps before applying async results to state to avoid races. + +## Critical gotchas & invariants + +### DO NOT + +1. **Edit generated or synced files** (see below) — regenerate them instead. +2. **Add `channel` or `channel.state` to dependency arrays** — use `channel.cid`, which is stable. +3. **Mutate `channel.state.messages` directly** — go through the `stream-chat` client's state API. +4. **Inline a `useStateStore` selector** — define it at module scope. +5. **Use unguarded web-only APIs in shared code** — it runs on Hermes, not a browser. +6. **Bypass lint or type errors** with broad disables or force merges. + +### Generated / synced files — never hand-edit + +- `package/lib/` and all build artifacts +- `package/src/theme/generated/{light,dark}/StreamTokens.{ios,android,web}.ts` → regenerate with `package/sync-theme.sh` +- `package/{native,expo}-package/{ios,android}/**/shared/` → regenerate with `shared-native:sync` +- `examples/ExpoMessaging/{ios,android}` (prebuild output); `ios/build` and `android/build` in the other sample apps +- `node_modules/` everywhere + +### React Native specifics + +- Clear Metro cache on module-resolution weirdness: `yarn react-native start --reset-cache` (RN CLI) or `yarn expo start --dev-client -c` (Expo) +- Test on **both** iOS and Android for native-module or platform-specific UI changes +- If an example app fails to build or install: + - `watchman watch-del-all && rm -rf ~/Library/Developer/Xcode/DerivedData/*` + - `(cd ios && bundle exec pod install)` (RN CLI sample apps) + - `npx expo prebuild` (after changing `ExpoMessaging`'s `app.json`) + - `rm -rf ios && rm -rf android` (after installing new native modules in `ExpoMessaging`) + +## Testing + +**Policy:** add or extend tests in the matching module's `__tests__/` folder. Cover new public API, bug fixes (as regression tests), and performance-sensitive utilities. Reuse the repo's fakes and mock builders instead of hand-rolling new ones. Do not let global coverage drop. + +**Runner:** Jest (`package/jest.config.js`) with the `@react-native/jest-preset`, `testEnvironment: 'node'`, `TZ=UTC` forced by `yarn test:unit`, `maxWorkers: 2` on CI. `mock-builders(.*)` is aliased to `src/mock-builders`. Test files live alongside source at `src/**/__tests__/*.test.ts(x)`. + +`package/jest-setup.tsx` calls `registerNativeHandlers()` with test doubles and `jest.mock()`s every peer native module (reanimated, worklets, gesture-handler, netinfo, `@gorhom/bottom-sheet`, `@op-engineering/op-sqlite`, `@shopify/flash-list`, safe-area-context, `react-native-teleport`, `RefreshControl`). Add new peer native modules there or tests will fail to resolve them. + +To run one test file, prefer `cd package && TZ=UTC npx jest path/to/file.test.tsx`. The `testRegex` array in `jest.config.js` also accepts a temporary path — revert it before committing. + +**Mock builders** (`package/src/mock-builders/`): + +- `api/initiateClientWithChannels` — creates a test client + channels in one call (fastest path) +- `api/` — response builders (`getOrCreateChannel`, `queryChannels`, `queryMembers`, `sendMessage`, `sendReaction`, `threadReplies`, `error`) plus `useMockedApis` +- `generator/` — `generateMessage()`, `generateChannel()`, `generateUser()`, `generateMember()`, `generateReaction()`, `generateStaticMessage(seed)` (deterministic via UUID v5) +- `attachments.ts` — `generateImageAttachment()`, `generateFileAttachment()`, `generateAudioAttachment()` +- `event/`, `DB/` — event dispatchers and offline-DB fakes + +Tests use `render()` / `renderHook()` from `@testing-library/react-native`. Components and hooks must be wrapped in the required provider stack (e.g. `Chat` → `Channel` → feature provider). Mock methods on the channel/client — never replace the whole object. + +## Build system + +`yarn build` runs `package`'s build: `rimraf lib` → `build-translations` (`i18next-cli sync`) → `bob build` → `copy-translations` (copies `src/i18n` into `lib/typescript/i18n`). + +`react-native-builder-bob` emits three targets from `src`: + +| Target | Output | Entry point in `package.json` | +| ------------ | ---------------- | ----------------------------- | +| `commonjs` | `lib/commonjs` | `main` | +| `module` | `lib/module` | `module` | +| `typescript` | `lib/typescript` | `types` | + +`shared-native:sync` is **not** wired into install or build — run it manually after editing `package/shared-native/`. + +## Theming + +Three-tier token architecture: **primitives** (raw colors) → **semantics** (e.g. `colors.error.primary`) → **components** (per-component overrides). Token references use a `$key` string syntax (e.g. `"$blue500"`) resolved by a topological sort in `package/src/theme/topologicalResolution.ts`, so declaration order does not matter. + +Platform-specific tokens are **generated**: `package/src/theme/generated/{light,dark}/StreamTokens.{ios,android,web}.ts`. Regenerate via `package/sync-theme.sh` when design tokens change — never hand-edit them. + +Custom themes are passed as the `style` prop to ``. `mergeThemes()` deep-merges the custom style over the base theme (deep-cloned via `JSON.parse(JSON.stringify())`). Light/dark mode is auto-detected via `useColorScheme()`. + +## i18n + +- **13 locales** in `package/src/i18n/*.json`: `ar`, `en`, `es`, `fr`, `he`, `hi`, `it`, `ja`, `ko`, `nl`, `pt-br`, `ru`, `tr` +- `Streami18n` (`package/src/utils/i18n/Streami18n.ts`) wraps i18next with per-locale calendar formats (`calendarFormats.ts`); access `t` via `useTranslationContext()` +- Extraction: `yarn workspace stream-chat-react-native-core build-translations` (`i18next-cli sync`, configured in `package/i18next.config.ts`) +- Validation: `validate-translations` runs inside `yarn lint` and in CI — **zero tolerance for empty translation values** +- Adding a string: use `t()` → run `build-translations` → fill in every locale file + +## Offline DB + +The SQLite schema lives in `package/src/store/schema.ts`. Versioning uses `PRAGMA user_version`; a mismatch triggers a **full DB reinit** (there are no incremental migrations). The current version is `SqliteClient.dbVersion` (`package/src/store/SqliteClient.ts`) — bump it whenever the schema changes, or existing installs will read a stale schema. + +`OfflineDB` owns channels, messages, reactions, members, drafts and reminders through `mappers/`. Offline support is opt-in via ``. + +## Accessibility, RTL & performance + +This repo ships three project skills — load the relevant one **before** touching these areas rather than improvising: + +- `.claude/skills/accessibility` — VoiceOver/TalkBack work: interactive components, gestures, modals, lists, media controls, focus behavior, live announcements +- `.claude/skills/rtl` — anything with a horizontal or directional axis: styles, positioning, flex, swipe gestures, animated transforms, icons, text alignment +- `.claude/skills/perf-benchmarking` — on-device measurement: Hermes CPU profiles, render profiling, deterministic call counting, memory/jank capture. Drives `examples/SampleApp` on a connected Android device via the `perf/` toolkit (`scenario-lib.sh`, `capture-hermes-profile.js`, `analyze-react-profile.js`, `analyze-cpuprofile.js`, `android-heap-dump.sh`; see `perf/README.md`). + +Accessibility is **opt-in** — see `ai-docs/accessibility.md` for the full contract. New interactive UI should reuse the primitives in `package/src/a11y/`. + +**Performance guidelines:** minimize re-renders (memoization, stable refs); reach for `React.memo` / `useCallback` / `useMemo` when profiling justifies it, not reflexively; clean up side effects; prefer lazy loading for optional heavy modules; monitor bundle size and justify increases over 2% per package (tracked by the `sdk-size-metrics` workflow). ## API design principles -- Semantic versioning -- Use `@deprecated` JSDoc with replacement guidance +- Semantic versioning; avoid breaking changes, prefer additive evolution +- Public surfaces get explicit TypeScript types/interfaces +- Consistent naming: `camelCase` for functions and properties, `PascalCase` for components and types +- Mark removals with `@deprecated` JSDoc plus replacement guidance - Provide migration docs for breaking changes -- Avoid breaking changes; prefer additive evolution -- Public surfaces: explicit TypeScript types/interfaces -- Consistent naming: `camelCase` for functions/properties, `PascalCase` for components/types ### Deprecation lifecycle -1. Mark with `@deprecated` + rationale + alternative. -2. Maintain for at least one minor release unless security-critical. -3. Add to migration documentation. -4. Remove only in next major. +1. Mark with `@deprecated` + rationale + alternative +2. Maintain for at least one minor release unless security-critical +3. Add to migration documentation +4. Remove only in the next major -## Performance guidelines +## Error & logging policy -- Minimize re-renders (memoization, stable refs) -- Use `React.memo` / `useCallback` / `useMemo` when profiling justifies -- Clean up side effects (`AbortController` for network calls, unsubscribe listeners when unmounting) -- Monitor bundle size; justify increases > 2% per package -- Prefer lazy loading for optional heavy modules -- Avoid unnecessary large dependency additions +- Public API: throw descriptive errors or return typed error results, consistent with existing patterns +- No console noise in production builds; gate internal debug logging behind an env flag +- Never leak credentials or user data in errors -## Error & logging policy +## Contribution rules -- Public API: throw descriptive errors or return typed error results (consistent with existing patterns) -- No console noise in production builds -- Internal debug logging gated behind env flag (if present) -- Never leak credentials/user data in errors +### Linting & formatting -## Concurrency & async +Run `yarn lint-fix` before every commit. Follow the zero-warnings policy — fix new warnings, never introduce any. Scope `eslint-disable` narrowly with an inline rationale; no broad rule disabling. -- Cancel stale async operations (media, network) when components unmount -- Use `AbortController` for fetch-like APIs -- Avoid race conditions: check instance IDs / timestamps before state updates +Prettier: single quotes, trailing commas, 100-char width (120 for Markdown) — see `.prettierrc`. -## Testing strategy +### Git hooks -- Unit: pure functions, small components -- React Native: target minimal smoke + platform logic (avoid flakiness) -- Mocks/fakes: prefer shared test helpers -- Coverage target: maintain or improve existing percentage (fail PR if global coverage drops) -- File naming: `*.test.ts` / `*.spec.ts(x)` -- Add tests for: new public API, bug fixes (regression test), performance-sensitive utilities +- `.husky/commit-msg` → `commitlint --edit` (Conventional Commits enforced) +- `.husky/pre-commit` → `dotgit/hooks/pre-commit-format.sh && dotgit/hooks/pre-commit-reject-binaries.py` +- Root `postinstall` runs `husky` -## CI expectations +### Commits -- Mandatory: build, lint, type check, unit/integration tests, (optionally) E2E smoke -- Node versions: those listed in matrix (see workflow YAML files under `.github/workflows/`) -- Failing or flaky tests: fix or quarantine with justification PR comment (temporary) -- Zero new warnings +[Conventional Commits](https://www.conventionalcommits.org/): `feat:`, `fix:`, `docs:`, `refactor:`, `chore:`, … -## Release workflow (high-level) +``` +feat(MessageInput): add audio recording support -1. Conventional Commit messages on PR merge -2. Release automation aggregates commits -3. Version bump + changelog + tag -4. Publish to registry -5. Deprecations noted in CHANGELOG -6. Ensure docs updated prior to publishing breaking changes +Implement MediaRecorder integration with MP3 encoding. -## Dependency policy +Closes #123 +``` -- Avoid adding large deps without justification (size, maintenance) -- Prefer existing utility packages -- Run `yarn audit` (or equivalent) if adding security-impacting deps -- Keep upgrades separate from feature changes when possible +- Never commit directly to `develop` or `main` — always create a feature branch +- PRs target `develop`; `main` is production releases only +- Never commit unless explicitly requested -## Samples & docs +### Pull requests -- New public feature: update at least one sample app -- Breaking changes: provide migration snippet -- Keep code snippets compilable -- Use placeholder keys (`YOUR_STREAM_KEY`) +Follow `PULL_REQUEST_TEMPLATE.md`. Keep PRs small and focused. -## React Native specifics +- [ ] `yarn lint-fix` passed +- [ ] `yarn test:unit` passed +- [ ] `cd package && yarn test:typecheck` passed +- [ ] `yarn build` succeeds +- [ ] Tests added for changes +- [ ] No new warnings (zero tolerance) +- [ ] Screenshot or video (before/after) for UI changes +- [ ] Public API changes documented +- [ ] Breaking changes labeled clearly in the description -- Clear Metro cache if module resolution has issues: `yarn react-native start --reset-cache` (for RN CLI) or `yarn expo start --dev-client -c` (for `Expo`) -- Test on iOS + Android for native module or platform-specific UI changes -- Avoid unguarded web-only APIs in shared code -- If the apps in `/examples` are failing to build or install, run: - - `watchman watch-del-all && rm -rf ~/Library/Developer/Xcode/DerivedData/*` - - `(cd ios && bundle exec pod install)` (for RN CLI based sample apps) - - `npx expo prebuild` (if changes have been done in `app.json` of `ExpoMessaging`) - - `rm -rf ios && rm -rf android` (if new native modules have been installed in `ExpoMessaging`) +### CI expectations + +`.github/workflows/check-pr.yml` (Node 24): `yarn install --immutable` → `yarn build` → `yarn lint` → `yarn typecheck` → `yarn test:coverage`. Other workflows: `changelog-preview`, `lint-pr-title`, `release`, `sample-distribution`, `sdk-size-metrics`. + +Failing or flaky tests: fix them, or quarantine with a justification comment and a follow-up. + +### Release + +Conventional Commits feed semantic-release; the pipeline uses `yarn workspaces foreach` directly (no Lerna). Release-participating workspaces (core SDK + SampleApp) are hardcoded in `release/release.config.js`. Version bump → changelog → tag → publish; deprecations are noted in `CHANGELOG`. Ensure docs are updated before publishing breaking changes. See `RELEASE_PROCESS.md`. -## Linting & formatting +### Dependency policy -- Run `yarn lint` before commit -- Narrowly scope `eslint-disable` with inline comments and rationale -- No broad rule disabling +Avoid large dependencies without justification (size, maintenance). Prefer existing utilities. Keep upgrades separate from feature changes. Respect the `npmMinimalAgeGate` rule above. -## Commit / PR conventions +### Samples & docs -- Small, focused PRs -- Include tests for changes -- Screenshot or video for UI changes (before/after) -- Label breaking changes clearly in description -- Document public API changes +New public feature: update at least one sample app. Breaking change: provide a migration snippet. Keep code snippets compilable. Use placeholder keys (`YOUR_STREAM_KEY`). -## Security +### Security -- No credentials or real user data -- Use placeholders in examples -- Scripts must error on missing critical env vars -- Avoid introducing unmaintained dependencies +Never commit API keys or real user data. Example code must use obvious placeholders. Scripts must fail closed on missing env vars. Avoid introducing unmaintained dependencies. See `SECURITY.md`. -## Prohibited edits +## Quick agent checklist (per change) -- Do not edit build artifacts - - `package/lib` - - `ios` and `android` directories in `ExpoMessaging` - - `ios/build` and `android/build` in the other sample apps - - `node_modules` everywhere -- Do not bypass lint/type errors with force merges +- `yarn build` succeeds +- `yarn lint` clean, no new warnings +- `cd package && yarn test:typecheck` clean +- `yarn test:unit` green, coverage not reduced +- No generated or synced files modified by hand +- Public API docs updated if the API changed +- Samples updated if a feature surfaced +- Both platforms checked for native or platform-specific UI changes -## Quick agent checklist (per commit) +## References -- Build succeeds -- Lint clean -- Type check clean -- Tests (unit/integration) green -- Coverage not reduced -- Public API docs updated if changed -- Samples updated if feature surfaced -- No new warnings -- No generated files modified +- **Agent deep dives:** `ai-docs/ai-migration.md` (v8 → v9 migration reference — load this instead of the prose upgrade guide for agent-driven migrations), `ai-docs/accessibility.md` (opt-in a11y layer) +- **Repo skills:** `.claude/skills/{accessibility,rtl,perf-benchmarking}`, `perf/README.md` +- **Contributing / process:** `CONTRIBUTING.md`, `RELEASE_PROCESS.md`, `PULL_REQUEST_TEMPLATE.md`, `SECURITY.md` +- **Component docs:** https://getstream.io/chat/docs/sdk/reactnative/ +- **Stream Chat API:** https://getstream.io/chat/docs/javascript/ +- **Stream agent skills** (installed via `getstream init`): https://getstream.io/agent-skills/docs/installation/ --- -Refine this file iteratively for agent clarity; keep human-facing explanations in docs site / `README.md`. +End of machine guidance. Edit this file to refine agent behavior over time; keep human-facing explanations in `README.md` and the docs site. diff --git a/CLAUDE.md b/CLAUDE.md index fbcc43d4e9..c504304a45 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -2,165 +2,6 @@ This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. -## Repository Overview +All guidance lives in `AGENTS.md` — the single source shared with every other agent (Copilot, Cursor, Codex, …). The import below pulls it in; do not duplicate content here. -Stream Chat React Native SDK monorepo. The main SDK code lives in `package/` (published as `stream-chat-react-native-core`). Built on top of the `stream-chat` JS client library. - -This is a **Yarn 4 (Berry)** workspace monorepo. The Yarn binary lives in `.yarn/releases/yarn-4.15.0.cjs` and is invoked via `yarnPath` in `.yarnrc.yml`; any globally-installed Yarn launcher (e.g. the Homebrew Yarn 1.x) auto-delegates to it. No Corepack required. - -Workspaces: `package`, `package/native-package`, `package/expo-package`, `examples/SampleApp`, `examples/ExpoMessaging`, `examples/TypeScriptMessaging`. There is a single root `yarn.lock`. - -## Common Commands - -All commands below run from the repo root. - -### Install - -```bash -yarn install # Set up every workspace (single root lockfile) -yarn install --immutable # CI-style; fail if yarn.lock would change -``` - -The root `package/`'s `postinstall` runs `husky install` and `yarn shared-native:sync` automatically. - -### Build - -```bash -yarn build # SDK build (commonjs + esm + types) -yarn workspace stream-chat-react-native-core build # Same, explicit form -``` - -### Lint & Format - -```bash -yarn lint # prettier + eslint + translation validation (max-warnings 0) -yarn lint-fix # Auto-fix lint and formatting issues -``` - -### Test - -```bash -yarn test:unit # All unit tests (sets TZ=UTC) -yarn test:coverage # With coverage report -yarn test:typecheck # Type-check tests against tsconfig.test.json (run after any code change) -yarn workspace stream-chat-react-native-core test:unit # Same as `yarn test:unit` -cd package && TZ=UTC npx jest path/to/test.test.tsx # Single test file -``` - -Always run `yarn test:typecheck` after making code changes — `yarn lint` and `yarn test:unit` do not catch all type errors. - -Tests use Jest with `react-native` preset and `@testing-library/react-native`. Test files live alongside source at `src/**/__tests__/*.test.ts(x)`. Mock builders are in `src/mock-builders/`. - -To run a single test, you can also temporarily add the file path to the `testRegex` array in `package/jest.config.js`. - -### Sample App - -```bash -yarn workspace sampleapp start # Metro bundler (alias: cd examples/SampleApp && yarn start) -yarn workspace sampleapp ios # Run iOS -yarn workspace sampleapp android # Run Android -``` - -## Architecture - -### Package Structure - -- `package/` — Main SDK (`stream-chat-react-native-core`) -- `package/native-package/` — React Native native module wrappers -- `package/expo-package/` — Expo-compatible wrapper -- `examples/SampleApp/` — Full sample app with navigation -- `release/` — Semantic release scripts - -### SDK Source (`package/src/`) - -**Component hierarchy**: `` → `` → `` / `` / `` - -- `components/` — UI components (~28 major ones: ChannelList, MessageList, MessageInput, Thread, Poll, ImageGallery, etc.) -- `contexts/` — React Context providers (~33 contexts). The primary way components receive state and callbacks. Key contexts: `ChatContext`, `ChannelContext`, `MessagesContext`, `ThemeContext`, `TranslationContext` -- `hooks/` — Custom hooks (~27+). Access contexts via `useChannelContext()`, `useMessageContext()`, etc. -- `state-store/` — Client-side state stores using `useSyncExternalStore` with selector pattern (audio player, image gallery, message overlay, etc.) -- `store/` — Offline SQLite persistence layer. `OfflineDB` class with mappers for channels, messages, reactions, members, drafts, reminders. Schema in `store/schema.ts` -- `theme/` — Deep theming system (colors, typography, spacing, per-component overrides) via `ThemeContext` -- `i18n/` — Internationalization with i18next (14 languages). `Streami18n` wrapper class -- `middlewares/` — Command UI middlewares (attachments, emoji) -- `icons/` — SVG icon components - -### Key Patterns - -**Component override pattern**: Nearly every UI element is replaceable via props. Parent components (e.g., `Channel`) accept 50+ `React.ComponentType` props for sub-components (`Message`, `MessageContent`, `DateHeader`, `TypingIndicator`, etc.). These props are forwarded into Context providers so deeply nested children can access them without prop drilling. - -**Context three-layer pattern**: Each context follows the same structure: - -1. `createContext()` with a sentinel default value (`DEFAULT_BASE_CONTEXT_VALUE`) -2. A `` wrapper component -3. A `useXContext()` hook that throws if used outside the provider (suppressed in test env via `isTestEnvironment()`) - -Context values are assembled in dedicated `useCreateXContext()` hooks (e.g., `useCreateChannelContext`) that carefully memoize with selective dependencies to avoid unnecessary re-renders. - -**Native module abstraction**: `native.ts` defines TypeScript interfaces for all platform-specific capabilities (image picking, compression, haptics, audio/video, clipboard). Implementations are injected at runtime via `registerNativeHandlers()` — `stream-chat-expo` provides Expo implementations, `stream-chat-react-native` provides bare RN ones. Calling an unregistered handler throws with a message to import the right package. - -**State stores**: `useSyncExternalStore`-based stores in `state-store/` with `useStateStore(store, selector)` for fine-grained subscriptions outside the context system. - -**Memoization**: Components use `React.memo()` with custom `areEqual` comparators (not HOCs) to prevent re-renders. - -**Offline-first**: SQLite-backed persistence with sync status tracking and pending task management. - -**Builder-bob builds**: Outputs CommonJS (`lib/commonjs`), ESM (`lib/module`), and TypeScript declarations (`lib/typescript`). - -### Testing Patterns - -Tests use `renderHook()` and `render()` from `@testing-library/react-native`. Components/hooks must be wrapped in the required provider stack (e.g., `Chat` → `Channel` → feature provider). - -**Mock builders** (`src/mock-builders/`): - -- `api/initiateClientWithChannels.js` — creates a test client + channels in one call -- `generator/` — factories: `generateMessage()`, `generateChannel()`, `generateUser()`, `generateMember()`, `generateStaticMessage(seed)` (deterministic via UUID v5) -- `attachments.js` — `generateImageAttachment()`, `generateFileAttachment()`, `generateAudioAttachment()` - -Reanimated and native modules are mocked via Proxy patterns in test setup files. - -### Theme System - -Themes follow a three-tier token architecture: **Primitives** (raw colors) → **Semantics** (e.g., `colors.error.primary`) → **Components** (per-component overrides). Token references use `$key` string syntax (e.g., `"$blue500"`) and are resolved via topological sort in `theme/topologicalResolution.ts`, so declaration order doesn't matter. - -Platform-specific tokens are **generated** files in `src/theme/generated/{light,dark}/StreamTokens.{ios,android,web}.ts` — regenerate via `sync-theme.sh` if design tokens change; don't hand-edit. - -Custom themes are passed as `style` prop to ``. `mergeThemes()` deep-merges custom style over base theme (deep-cloned via `JSON.parse(JSON.stringify())`). Light/dark mode is auto-detected via `useColorScheme()`. - -### Native / Expo Package Relationship - -`native-package/` and `expo-package/` are thin wrappers around `stream-chat-react-native-core`. They: - -1. Call `registerNativeHandlers()` with platform-specific implementations (native modules vs Expo APIs) -2. Export optional dependency wrappers (`Audio`, `Video`, `FlatList`) from `src/optionalDependencies/` -3. Re-export everything from core: `export * from 'stream-chat-react-native-core'` - -Platform branching uses runtime `Platform.select()` / `Platform.OS` checks — there are no `.ios.ts` / `.android.ts` source file splits. - -### Chat Component (Root Provider) - -`` is the entry point. It: - -- Sets SDK metadata on the `stream-chat` client (identifier, device info) -- Disables the JS client's `recoverStateOnReconnect` (the SDK handles recovery itself) -- Registers subscriptions for threads, polls, and reminders (cleaned up on unmount) -- Initializes `OfflineDB` if `enableOfflineSupport` is true -- Wraps children in: `ChatProvider` → `TranslationProvider` → `ThemeProvider` → `ChannelsStateProvider` - -### Offline DB - -SQLite schema is in `store/schema.ts`. DB versioning uses `PRAGMA user_version` — a version mismatch triggers full DB reinit (no incremental migrations). Current version is tracked in `SqliteClient.dbVersion`. - -### Translations - -Translation JSON files live in `src/i18n/`. `validate-translations` (run as part of `yarn lint`) checks that no translation key has an empty string value. When adding/updating translations, run `yarn build-translations` (i18next-cli sync) to keep files in sync. - -## Conventions - -- **Conventional commits** enforced by commitlint: `feat:`, `fix:`, `docs:`, `refactor:`, etc. -- **ESLint 9 flat config** at `package/eslint.config.mjs`, strict (max-warnings 0) -- **Prettier**: single quotes, trailing commas, 100 char width (see `.prettierrc`) -- **TypeScript strict mode** with platform-specific module suffixes (`.ios`, `.android`, `.web`) -- Git branches: PRs target `develop`, `main` is production releases only -- **Shared native sync**: Root `yarn install`'s postinstall runs `yarn shared-native:sync` automatically. Re-run manually with `yarn workspace stream-chat-react-native-core shared-native:sync` after modifying `package/shared-native/`. -- **No Lerna**: the release pipeline uses `yarn workspaces foreach` directly. Release-participating workspaces (core SDK + SampleApp) are hardcoded in `release/release.config.js`. +@AGENTS.md diff --git a/README.md b/README.md index 64abe39f7e..4918384f49 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,7 @@ - [Stream Chat API](https://getstream.io/chat/) product overview - [Register](https://getstream.io/chat/trial/) to get an API key for Stream Chat - [React Native Chat Tutorial](https://getstream.io/chat/react-native-chat/tutorial/) +- [AI Agent Skills](#-build-with-ai-agents) for Claude Code, Cursor, and Codex - [Chat UI Kit](https://getstream.io/chat/ui-kit/) - [Documentation](https://getstream.io/chat/docs/sdk/reactnative) - [Release Notes](https://github.com/GetStream/stream-chat-react-native/releases) @@ -28,6 +29,7 @@ - [Official React Native SDK for Stream Chat](#official-react-native-sdk-for-stream-chat) - [Contents](#contents) - [📖 React Native Chat Tutorial](#-react-native-chat-tutorial) + - [🤖 Build with AI Agents](#-build-with-ai-agents) - [Free for Makers](#free-for-makers) - [🔮 Example Apps](#-example-apps) - [💬 Keep in mind](#-keep-in-mind) @@ -39,6 +41,26 @@ The best place to start is the [React Native Chat Tutorial](https://getstream.io/chat/react-native-chat/tutorial/). It teaches you how to use this SDK and also shows how to make frequently required changes. +## 🤖 Build with AI Agents + +If you build with an AI coding agent, our [agent skills](https://getstream.io/agent-skills/docs/installation/) teach it how to use this SDK correctly. Install them once: + +```bash +curl -fsSL https://getstream.io/cli.sh | bash +getstream init +``` + +Then reach for the [`/stream-react-native`](https://getstream.io/agent-skills/docs/skills/stream-react-native/) skill: + +``` +/stream-react-native create a new Expo chat app +/stream-react-native upgrade stream-chat-react-native to v9 +``` + +It can scaffold a new Expo or React Native CLI app with the SDK wired up, add Stream to an app you already have, audit an existing integration, or migrate between SDK major versions (including from Sendbird). Works with Claude Code, Cursor, Codex, and any other agent that reads the universal `.agents` location. + +Contributing to this repository with an agent instead? See [AGENTS.md](./AGENTS.md) for repository structure, commands, and conventions. + ## Free for Makers Stream is free for most side and hobby projects. To qualify your project/company needs to have < 5 team members and < $10k in monthly revenue.