diff --git a/agents/react-native-engineer.md b/agents/react-native-engineer.md
index 55a41845..249a17ce 100644
--- a/agents/react-native-engineer.md
+++ b/agents/react-native-engineer.md
@@ -80,6 +80,8 @@ Do not load references for domains not relevant to the task — context is a sca
| useState, derived state, Zustand, state structure, dispatchers, ground truth | `state-management.md` |
| Conditional rendering, &&, Text components, React Compiler, memoization | `rendering-patterns.md` |
| Monorepo, fonts, imports, design system, dependency versions, autolinking | `monorepo-config.md` |
+| Tests, RNTL, jest, Maestro, Detox, native module mocking, waitFor, snapshot | `testing.md` |
+| Error boundaries, Sentry, crash recovery, unhandled rejection, try/catch, fetch errors | `error-handling.md` |
## Error Handling
@@ -93,6 +95,10 @@ Do not load references for domains not relevant to the task — context is a sca
**State sync issues**: Load `state-management.md` — stale closure or redundant derived state.
+**Production crashes, Error Boundaries, Sentry, unhandled rejections**: Load `error-handling.md` — error boundary setup, crash reporting patterns, fetch error handling.
+
+**Test setup, RNTL queries, native module mocks, async assertions**: Load `testing.md` — RNTL patterns, jest config, native mock setup, anti-patterns.
+
## References
- [list-performance.md](react-native-engineer/references/list-performance.md) — FlashList/LegendList, memoization, virtualization, stable references
@@ -102,3 +108,5 @@ Do not load references for domains not relevant to the task — context is a sca
- [state-management.md](react-native-engineer/references/state-management.md) — Minimal state, dispatch updaters, fallback patterns, ground truth
- [rendering-patterns.md](react-native-engineer/references/rendering-patterns.md) — Falsy && crash prevention, Text components, React Compiler
- [monorepo-config.md](react-native-engineer/references/monorepo-config.md) — Fonts, imports, native dep autolinking, dependency versions
+- [testing.md](react-native-engineer/references/testing.md) — RNTL patterns, jest config, native module mocking, async assertions, anti-patterns
+- [error-handling.md](react-native-engineer/references/error-handling.md) — Error boundaries, Sentry init, unhandled rejections, fetch error handling, crash recovery
diff --git a/agents/react-native-engineer/references/error-handling.md b/agents/react-native-engineer/references/error-handling.md
new file mode 100644
index 00000000..5c2bdc29
--- /dev/null
+++ b/agents/react-native-engineer/references/error-handling.md
@@ -0,0 +1,332 @@
+# Error Handling Reference
+
+
+> **Scope**: Production error handling in React Native: Error Boundaries, Sentry integration, promise rejection capture, and crash-safe rendering patterns.
+> **Version range**: React 18+, React Native 0.72+, @sentry/react-native 5+
+> **Generated**: 2026-04-12 — verify Sentry DSN config patterns against current @sentry/react-native docs
+
+---
+
+## Overview
+
+React Native apps crash hard on unhandled errors — there's no browser error overlay to recover from. A production crash closes the app. The three failure modes are: (1) synchronous render errors without an `ErrorBoundary`, (2) unhandled promise rejections that swallow failures silently, and (3) native module errors that surface as cryptic red boxes during development but silent crashes in production builds.
+
+---
+
+## Pattern Table
+
+| Pattern | Version | Use When | Avoid When |
+|---------|---------|----------|------------|
+| `ErrorBoundary` (class component) | React 16+ | catching render-phase errors | async errors inside event handlers |
+| `react-native-error-boundary` | any | quick ErrorBoundary with fallback UI | you need custom recovery logic |
+| `Sentry.init()` in app entry | `@sentry/react-native 5+` | production crash reporting | local dev — noise ratio is high |
+| `unhandledRejection` global handler | RN 0.68+ | catching all unhandled promise rejections | replacing proper `try/catch` per call |
+| `InteractionManager.runAfterInteractions` | any | deferring error-prone work past animation frames | time-sensitive data fetching |
+
+---
+
+## Correct Patterns
+
+### Wrap Screen Roots in ErrorBoundary
+
+Every screen-level component should be wrapped in an `ErrorBoundary`. A crash in one screen should not bring down the entire app.
+
+```tsx
+import { ErrorBoundary } from 'react-error-boundary'
+
+function FeedScreen() {
+ return (
+ (
+
+ Something went wrong.
+
+ Try again
+
+
+ )}
+ onError={(error, info) => {
+ // report to Sentry or your crash service
+ captureException(error, { extra: { componentStack: info.componentStack } })
+ }}
+ >
+
+
+ )
+}
+```
+
+**Why**: Without a boundary, any render-phase throw (null dereference, bad prop type, failed deserialization) crashes the entire React tree. The boundary catches it, renders fallback UI, and lets the user recover without restarting the app.
+
+---
+
+### Initialize Sentry Before the React Tree
+
+Sentry must be initialized before `AppRegistry.registerComponent` — before any React component mounts. Errors during app startup are otherwise invisible.
+
+```ts
+// index.js (app entry — before importing App)
+import * as Sentry from '@sentry/react-native'
+
+Sentry.init({
+ dsn: process.env.EXPO_PUBLIC_SENTRY_DSN,
+ environment: process.env.EXPO_PUBLIC_ENV ?? 'development',
+ // Sample at 10% in production to control event volume
+ tracesSampleRate: process.env.EXPO_PUBLIC_ENV === 'production' ? 0.1 : 1.0,
+ enabled: process.env.EXPO_PUBLIC_ENV !== 'development',
+})
+
+// Then import and register App
+import { registerRootComponent } from 'expo'
+import App from './App'
+registerRootComponent(App)
+```
+
+**Why**: Errors that occur during app initialization (config load, font loading, initial navigation mount) are lost if Sentry isn't set up first. `EXPO_PUBLIC_*` variables are safe to embed in the bundle — do not use secret keys here.
+
+---
+
+### Capture Unhandled Promise Rejections
+
+React Native 0.68+ surfaces unhandled rejections as yellow warnings in dev, but in production they silently swallow errors. Install a global handler.
+
+```ts
+// App.tsx or index.js setup
+const handleUnhandledRejection = (event: PromiseRejectionEvent) => {
+ console.error('Unhandled promise rejection:', event.reason)
+ captureException(event.reason, { tags: { type: 'unhandled_rejection' } })
+ // Don't call event.preventDefault() — let RN's default handling also run
+}
+
+// Node-style (Hermes engine, RN 0.64+)
+if (global.HermesInternal) {
+ const tracking = require('promise/setimmediate/rejection-tracking')
+ tracking.enable({
+ allRejections: true,
+ onUnhandled: (id: number, rejection: unknown) => {
+ captureException(rejection, { tags: { rejection_id: id } })
+ },
+ })
+}
+```
+
+**Why**: Fire-and-forget async calls (`fetchUser()` without `await` or `.catch()`) fail silently in production. The global handler catches them for visibility without requiring every call site to add error handling.
+
+---
+
+### Type Fetch Errors — Never Assume the Shape
+
+Network errors in React Native come in three shapes: `Error` instances from `fetch` throwing on network failure, JSON parse errors when the server returns HTML (503 page), and valid JSON with an error status code.
+
+```ts
+async function fetchUser(id: string): Promise {
+ let res: Response
+
+ try {
+ res = await fetch(`${API_URL}/users/${id}`)
+ } catch (err) {
+ // Network failure — no response at all
+ throw new Error(`Network error fetching user ${id}: ${String(err)}`)
+ }
+
+ if (!res.ok) {
+ // Server returned 4xx/5xx — body may not be JSON
+ const text = await res.text().catch(() => '')
+ throw new Error(`HTTP ${res.status} fetching user ${id}: ${text.slice(0, 200)}`)
+ }
+
+ try {
+ return res.json() as Promise
+ } catch (err) {
+ throw new Error(`Invalid JSON for user ${id}: ${String(err)}`)
+ }
+}
+```
+
+**Why**: `fetch` does NOT throw on 4xx/5xx status codes. Calling `res.json()` on a 503 HTML error page throws a parse error with a misleading message. Wrapping each phase separately gives actionable error messages in Sentry.
+
+---
+
+## Anti-Pattern Catalog
+
+### ❌ Using `console.error` as the Only Error Reporting
+
+**Detection**:
+```bash
+grep -rn 'console\.error' --include="*.tsx" --include="*.ts" | grep -v "\.test\." | grep -v "\.spec\."
+rg 'console\.error' --type ts --type tsx | grep -v test
+```
+
+**What it looks like**:
+```tsx
+try {
+ await syncData()
+} catch (err) {
+ console.error('Sync failed', err) // invisible in production
+}
+```
+
+**Why wrong**: `console.error` is stripped or suppressed in production builds. Errors logged this way are invisible to on-call and never trigger alerts. Crashes go undetected until users report them.
+
+**Fix**:
+```tsx
+import { captureException } from '@sentry/react-native'
+
+try {
+ await syncData()
+} catch (err) {
+ captureException(err, { tags: { operation: 'sync' } })
+ // optionally also console.error in dev
+ if (__DEV__) console.error('Sync failed', err)
+}
+```
+
+---
+
+### ❌ Empty Catch Blocks
+
+**Detection**:
+```bash
+grep -rn 'catch\s*(.*)\s*{\s*}' --include="*.ts" --include="*.tsx"
+rg 'catch\s*\(.*\)\s*\{\s*\}' --type ts
+```
+
+**What it looks like**:
+```ts
+try {
+ await loadUserPreferences()
+} catch (err) {
+ // TODO: handle this
+}
+```
+
+**Why wrong**: Silent swallow. The error is gone. The app is now in an inconsistent state — preferences were not loaded, but no error boundary fired, no fallback rendered, no alert triggered. These are the hardest bugs to diagnose because there's no stack trace.
+
+**Fix**: At minimum, report and reset to a safe default:
+```ts
+try {
+ await loadUserPreferences()
+} catch (err) {
+ captureException(err)
+ // explicit fallback state
+ await setDefaultPreferences()
+}
+```
+
+---
+
+### ❌ Missing Error Boundary at Navigation Root
+
+**Detection**:
+```bash
+grep -rn 'Stack.Screen\|Tabs.Screen' --include="*.tsx" | grep -v ErrorBoundary
+rg 'NavigationContainer' --type tsx | grep -B5 -A10 'NavigationContainer'
+```
+
+**What it looks like**:
+```tsx
+export default function RootLayout() {
+ return (
+
+
+
+
+ )
+}
+```
+
+**Why wrong**: If `ProfileScreen` throws during render, it unwinds the entire navigation tree. With no boundary, the app white-screens. Users must force-quit.
+
+**Fix**: Wrap each screen's content component in an ErrorBoundary, or add a root-level boundary around the entire navigator:
+```tsx
+export default function RootLayout() {
+ return (
+ } onError={captureException}>
+
+
+
+
+
+ )
+}
+```
+
+---
+
+### ❌ Accessing `.data` on an Unvalidated API Response
+
+**Detection**:
+```bash
+grep -rn '\.data\.' --include="*.ts" --include="*.tsx" | grep -v "\.test\." | grep "await fetch\|axios\|useFetch"
+rg '(await\s+\w+\(.*\))\.data\.' --type ts
+```
+
+**What it looks like**:
+```ts
+const response = await fetch('/api/user')
+const json = await response.json()
+setUser(json.data.profile.name) // throws if data or profile is undefined
+```
+
+**Why wrong**: API contracts break. A server returns `{ error: "not found" }` instead of `{ data: { profile: ... } }`. The chain `.data.profile.name` throws `Cannot read properties of undefined (reading 'profile')` — a crash with a misleading error message.
+
+**Fix**: Validate the response shape before accessing nested paths, or use optional chaining with a fallback:
+```ts
+const json = await response.json()
+if (!json.data?.profile) {
+ throw new Error(`Unexpected API shape: ${JSON.stringify(json).slice(0, 200)}`)
+}
+setUser(json.data.profile.name)
+```
+
+---
+
+## Error-Fix Mappings
+
+| Error Message | Root Cause | Fix |
+|---------------|------------|-----|
+| `TypeError: Cannot read properties of undefined (reading 'X')` | Null/undefined accessed via property chain after API response | Add optional chaining or explicit null check before deep access |
+| `Network request failed` | No network or wrong host in dev | Check `__DEV__` vs production API URL; verify device can reach the API host |
+| `JSON Parse error: Unrecognized token '<'` | Server returned HTML (error page) instead of JSON | Check `res.ok` before calling `res.json()` — server returned 4xx/5xx |
+| `Maximum update depth exceeded` | State setter called inside render or effect without dependency guard | Move setter into event handler or add correct deps array to `useEffect` |
+| `Warning: Can't perform a React state update on an unmounted component` | Async operation completes after component unmounts | Return cleanup function from `useEffect` that cancels in-flight request |
+| `Unhandled promise rejection: Error: Invariant Violation` | Native module call outside the main thread context | Move native module calls to a dedicated service, not inside callbacks |
+
+---
+
+## Version-Specific Notes
+
+| Version | Change | Impact |
+|---------|--------|--------|
+| RN 0.71 | `Promise.allSettled` enabled by default in Hermes | Use `allSettled` instead of `all` when you want partial results on failure |
+| RN 0.73 | Unhandled rejection handling improved in Hermes | Stack traces from async errors are now preserved — update Sentry sourcemap upload |
+| React 18 | `startTransition` errors fall back to nearest ErrorBoundary | Transitions that throw no longer crash the whole tree |
+| `@sentry/react-native` 5.0 | `Sentry.wrap(App)` deprecated — use `Sentry.init()` then `withSentry(App)` | Update app entry if using older Sentry integration pattern |
+
+---
+
+## Detection Commands Reference
+
+```bash
+# Find console.error used as only error reporting (not in tests)
+grep -rn 'console\.error' --include="*.tsx" --include="*.ts" | grep -v "\.test\.\|\.spec\."
+
+# Find empty catch blocks
+grep -rn 'catch\s*(.*)\s*{\s*}' --include="*.ts" --include="*.tsx"
+
+# Find fetch calls without .ok check
+grep -rn 'await fetch\|\.json()' --include="*.ts" --include="*.tsx" | grep -v 'res\.ok\|response\.ok'
+
+# Find deep property access on API responses without null guards
+grep -rn '\.data\.\|\.result\.' --include="*.ts" --include="*.tsx" | grep -v '\?\.'
+
+# Find missing ErrorBoundary around screen components (Expo Router pattern)
+grep -rn 'Stack\.Screen\|Tabs\.Screen' --include="*.tsx" | grep -v 'ErrorBoundary'
+```
+
+---
+
+## See Also
+
+- `rendering-patterns.md` — Text component crashes and conditional render crashes during render phase
+- `state-management.md` — Stale state that causes incorrect error recovery
diff --git a/agents/react-native-engineer/references/testing.md b/agents/react-native-engineer/references/testing.md
new file mode 100644
index 00000000..ea6723c9
--- /dev/null
+++ b/agents/react-native-engineer/references/testing.md
@@ -0,0 +1,271 @@
+# Testing Reference
+
+
+> **Scope**: React Native test setup, React Native Testing Library (RNTL) patterns, Maestro E2E, native module mocking, and Expo test configuration.
+> **Version range**: React Native 0.72+, RNTL 12+, Expo SDK 50+
+> **Generated**: 2026-04-12 — verify against current @testing-library/react-native release notes
+
+---
+
+## Overview
+
+React Native testing has three distinct layers: unit/component tests via RNTL, integration via jest with native module mocks, and E2E via Maestro or Detox. The most common failure mode is testing implementation details (renders, re-renders, internal state) instead of user-observable behavior. Native modules require manual mocking; RNTL's `render` does not invoke native code.
+
+---
+
+## Pattern Table
+
+| Tool | Version | Use When | Avoid When |
+|------|---------|----------|------------|
+| `@testing-library/react-native` | `12+` | component behavior, user interactions | testing animation internals, native rendering |
+| `jest-expo` | `SDK 50+` | Expo managed workflow tests | bare RN without Expo — use `react-native` preset |
+| `Maestro` | `1.36+` | E2E flows on device/simulator | unit-level component logic |
+| `Detox` | `20+` | E2E when Maestro yaml DSL is insufficient | simple flows — Maestro is faster to author |
+| `@react-native-community/async-storage/jest/async-storage-mock` | any | mocking AsyncStorage in unit tests | leave AsyncStorage unmocked — causes silent hang |
+
+---
+
+## Correct Patterns
+
+### Test User Behavior, Not Implementation
+
+Query by accessible labels and roles, not test IDs or internal component names.
+
+```tsx
+import { render, fireEvent, screen } from '@testing-library/react-native'
+import { LoginScreen } from './LoginScreen'
+
+it('submits login with valid credentials', () => {
+ render()
+
+ fireEvent.changeText(screen.getByLabelText('Email'), 'user@example.com')
+ fireEvent.changeText(screen.getByLabelText('Password'), 'hunter2')
+ fireEvent.press(screen.getByRole('button', { name: 'Log in' }))
+
+ expect(screen.getByText('Logging in...')).toBeTruthy()
+})
+```
+
+**Why**: `getByLabelText` and `getByRole` query what a screen reader sees. If you rename a component but keep its accessible label, the test still passes. If you rename the label, the test correctly fails.
+
+---
+
+### Mock Native Modules at the jest Config Level
+
+Native modules that don't exist in the jest environment must be mocked globally — not inside individual test files — so every test file gets the mock automatically.
+
+```js
+// jest.config.js
+module.exports = {
+ preset: 'jest-expo', // or 'react-native'
+ setupFilesAfterFramework: ['./jest.setup.ts'],
+ moduleNameMapper: {
+ // mock native camera module
+ 'react-native-vision-camera': '/__mocks__/react-native-vision-camera.ts',
+ },
+}
+```
+
+```ts
+// __mocks__/react-native-vision-camera.ts
+export const Camera = jest.fn(() => null)
+export const useCameraDevices = jest.fn(() => ({ back: {}, front: {} }))
+```
+
+**Why**: Native modules throw "Cannot read properties of null" in jest because the native bridge doesn't exist. Module-level mocks prevent test pollution across files.
+
+---
+
+### Use `waitFor` for Async State Changes
+
+Avoid `setTimeout` delays. Use `waitFor` from RNTL which polls until assertion passes or timeout expires.
+
+```tsx
+import { render, waitFor, screen } from '@testing-library/react-native'
+
+it('shows loaded data after fetch', async () => {
+ render()
+
+ // Wrong: arbitrary delay
+ // await new Promise(r => setTimeout(r, 500))
+
+ // Correct: poll until assertion passes
+ await waitFor(() => {
+ expect(screen.getByText('Jane Doe')).toBeTruthy()
+ })
+})
+```
+
+**Why**: Arbitrary delays make tests either flaky (too short) or slow (too long). `waitFor` exits as soon as the assertion passes, bounding wait time without hardcoding delays.
+
+---
+
+### Set Up AsyncStorage Mock Globally
+
+AsyncStorage calls that hit the real module in jest hang indefinitely with no error.
+
+```ts
+// jest.setup.ts
+import mockAsyncStorage from '@react-native-async-storage/async-storage/jest/async-storage-mock'
+jest.mock('@react-native-async-storage/async-storage', () => mockAsyncStorage)
+```
+
+**Why**: The real AsyncStorage module requires native bridge. Without the mock, `getItem`/`setItem` calls return `Promise` — the test hangs until jest timeout kills it.
+
+---
+
+## Anti-Pattern Catalog
+
+### ❌ Querying by `testID` Instead of Accessible Attributes
+
+**Detection**:
+```bash
+grep -rn 'getByTestId\|findByTestId' --include="*.test.tsx" --include="*.spec.tsx"
+rg 'getByTestId|findByTestId' --type tsx
+```
+
+**What it looks like**:
+```tsx
+const button = screen.getByTestId('submit-button')
+fireEvent.press(button)
+```
+
+**Why wrong**: `testID` is invisible to users and screen readers. Tests using it break if the component is refactored even when behavior is unchanged. They also don't verify accessibility — a button with no label passes the test but fails assistive technology users.
+
+**Fix**:
+```tsx
+const button = screen.getByRole('button', { name: 'Submit' })
+fireEvent.press(button)
+```
+
+**Version note**: RNTL 7+ requires the component to have `accessible={true}` or a role for `getByRole` to find it. Add `accessibilityRole="button"` to `Pressable` components.
+
+---
+
+### ❌ Importing from `react-native` Instead of `@testing-library/react-native`
+
+**Detection**:
+```bash
+grep -rn "from 'react-native'" --include="*.test.tsx" --include="*.spec.tsx" | grep -v "^.*//.*from 'react-native'"
+```
+
+**What it looks like**:
+```tsx
+import { render } from 'react-native' // wrong — no render export
+import { act } from 'react-native/test-utils' // old pattern
+```
+
+**Why wrong**: `react-native` doesn't export `render`. These imports cause `Cannot find module` errors or silently import the wrong `act` (react vs react-native differ on batching semantics).
+
+**Fix**:
+```tsx
+import { render, fireEvent, screen, act, waitFor } from '@testing-library/react-native'
+```
+
+---
+
+### ❌ Mocking `useNavigation` Inside Individual Test Files
+
+**Detection**:
+```bash
+grep -rn "jest.mock.*navigation\|jest.mock.*router" --include="*.test.tsx"
+```
+
+**What it looks like**:
+```tsx
+jest.mock('@react-navigation/native', () => ({
+ useNavigation: () => ({ navigate: jest.fn() }),
+}))
+```
+
+**Why wrong**: Repeated local mocks diverge over time. When the real API adds a new field (`navigation.setOptions`, `navigation.getId`), each test file needs updating separately. Components that call multiple navigation hooks get increasingly complex local mocks.
+
+**Fix**: Create a single navigation mock in `__mocks__/@react-navigation/native.ts` and let jest auto-resolve it. Or use `createNavigationContainerRef` and wrap the component in a `NavigationContainer` in the test.
+
+```tsx
+import { NavigationContainer } from '@react-navigation/native'
+
+function renderWithNavigation(ui: React.ReactElement) {
+ return render({ui})
+}
+```
+
+---
+
+### ❌ Using Snapshots for UI Components
+
+**Detection**:
+```bash
+grep -rn 'toMatchSnapshot\|toMatchInlineSnapshot' --include="*.test.tsx"
+rg 'toMatchSnapshot' --type tsx
+```
+
+**What it looks like**:
+```tsx
+it('renders correctly', () => {
+ const tree = render().toJSON()
+ expect(tree).toMatchSnapshot()
+})
+```
+
+**Why wrong**: Snapshot tests fail on every styling or structure change, creating update churn. They assert nothing about behavior — a component that renders the wrong text passes if the snapshot was wrong to begin with. They're especially noisy in monorepos.
+
+**Fix**: Test specific rendered output that reflects user-visible behavior:
+```tsx
+it('displays the title', () => {
+ render()
+ expect(screen.getByText('Hello')).toBeTruthy()
+})
+```
+
+---
+
+## Error-Fix Mappings
+
+| Error Message | Root Cause | Fix |
+|---------------|------------|-----|
+| `Invariant Violation: TurboModuleRegistry.getEnforcing(...)` | Native module accessed in jest without mock | Add module to `moduleNameMapper` in jest.config or mock in setupFilesAfterFramework |
+| `Unable to find an element with the text: ...` | Component not yet rendered or async data not resolved | Wrap assertion in `await waitFor(...)` |
+| `Warning: An update to ... inside a test was not wrapped in act(...)` | State update triggered after test ended or outside act | Use `await act(async () => { ... })` around the trigger, or use `waitFor` |
+| `Cannot find module '@testing-library/react-native'` | Package not installed | `bun add -D @testing-library/react-native` or `npm install -D @testing-library/react-native` |
+| `Element type is invalid: expected a string or class/function but got undefined` | Mocked module returns wrong shape | Check `__mocks__` file — default export may be missing or named exports wrong |
+| `jest did not exit one second after the test run has completed` | Unmocked async module (AsyncStorage, NetInfo) holds open handle | Add mock in `jest.setup.ts` for all native async modules |
+
+---
+
+## Version-Specific Notes
+
+| Version | Change | Impact |
+|---------|--------|--------|
+| RNTL 12.0 | `userEvent` API added — simulates real user gestures, not synthetic events | Prefer `userEvent.press()` over `fireEvent.press()` for interaction fidelity |
+| RNTL 12.4 | `screen` query object promoted to stable — no need to destructure `render()` return | Use `screen.getByText()` instead of `const { getByText } = render()` |
+| Expo SDK 50 | `jest-expo` preset updated to support new architecture (JSI) | If tests hang after SDK 50 upgrade, check `jest-expo` version matches SDK |
+| RN 0.73 | New Architecture (Fabric) enabled by default in new projects | Some community library mocks break under Fabric — check library's jest setup docs |
+
+---
+
+## Detection Commands Reference
+
+```bash
+# Find testID queries that should be role/label queries
+grep -rn 'getByTestId\|findByTestId' --include="*.test.tsx" --include="*.spec.tsx"
+
+# Find snapshot tests in component files
+grep -rn 'toMatchSnapshot\|toMatchInlineSnapshot' --include="*.test.tsx"
+
+# Find unmocked native module patterns
+grep -rn 'jest.mock.*native' --include="*.test.tsx" | grep -v "__mocks__"
+
+# Find setTimeout used as async delay in tests (should use waitFor)
+grep -rn 'setTimeout.*[0-9]' --include="*.test.tsx" --include="*.spec.tsx"
+
+# Find old act import pattern
+grep -rn "from 'react-native/test-utils'\|from 'react-test-renderer'" --include="*.test.tsx"
+```
+
+---
+
+## See Also
+
+- `rendering-patterns.md` — Text component rules that affect what RNTL can query
+- `list-performance.md` — FlashList/LegendList require specific test setup for virtualization