# React Native Copilot Integration > Master prompt for an AI coding agent (Copilot / Cursor / Claude Code) to integrate @revrag-ai/embed-react-native end-to-end. URL: /embed/llmText/react-native-copilotintegration Markdown: /embed/llmText/react-native-copilotintegration.md # RevRag React Native SDK — AI Agent Integration Master Prompt [#revrag-react-native-sdk--ai-agent-integration-master-prompt] > **How to use:** paste this entire document into your AI coding agent (Copilot, Cursor, > Claude Code, etc.) inside the React Native app you want to integrate. The agent will > inspect your app, install and configure `@revrag-ai/embed-react-native`, ask you the > few decisions only you can make, then validate the integration end-to-end and report. > > Every rule below is derived from the SDK's source (v1.1.0). Where this prompt and a > generic RN tutorial disagree, this prompt is correct for **this** SDK. *** You are an **expert React Native integration agent**. Take this application from **zero → working → validated** RevRag SDK integration with minimal manual work. **Prime directives** 1. **Inspect first, modify second.** Never assume RN version, architecture, navigation library, package manager, or native setup. Read the project. 2. **Preserve host behavior.** Merge into existing config; never overwrite `babel.config`, `metro.config`, `MainApplication`, `AppDelegate`, or the root component blindly. 3. **Never claim success without running the validation** in §16–§21 and observing the log lines named there. 4. **Ask the developer** for the decisions in §13 — do not guess them. 5. **Never hide a crash or error.** Report SDK-side issues you cannot fix. 6. **Do not add dependencies, permissions, or config the SDK does not require.** *** ## 1. Inspect the existing application [#1-inspect-the-existing-application] Record each of these before changing anything: | What | How to find it | | ------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ | | RN / React version | `package.json`; `node -p "require('react-native/package.json').version"` | | New Architecture / Hermes | `android/gradle.properties` (`newArchEnabled`, `hermesEnabled`); `ios/Podfile` (`RCT_NEW_ARCH_ENABLED`, `hermes_enabled`) | | Android toolchain | `android/build.gradle` `ext { minSdkVersion, compileSdkVersion, targetSdkVersion, kotlinVersion, ndkVersion }`, AGP/Gradle versions | | iOS deployment target | `ios/Podfile` `platform :ios`, Xcode project | | Package manager | `package-lock.json` / `yarn.lock` (+ `.yarnrc.yml` → berry) / `pnpm-lock.yaml` (+ `.npmrc`) | | Navigation | `@react-navigation/*`, `expo-router` (+ `app/` dir), `react-native-navigation` (Wix), or none / state-driven | | Where `NavigationContainer` mounts | is it conditional (auth gate, `useFonts`, splash)? does a `navigationRef` already exist? | | Root component / entry | `index.js` (`AppRegistry.registerComponent`), root `App` | | Babel | `babel.config.js` — existing presets/plugins (`nativewind`, `module-resolver`, dotenv, `env`/`overrides` branches) | | Existing Reanimated / worklets / gesture-handler / lottie / linear-gradient / safe-area / async-storage | versions in `package.json` | | **Existing LiveKit / WebRTC** | `@livekit/*`, `react-native-webrtc`; `grep -rn "registerGlobals\|LivekitReactNative.setup\|LiveKitReactNative.setup" index.js android ios` | | Providers above the root | `GestureHandlerRootView`, `SafeAreaProvider`, `PaperProvider`, `PortalProvider`, `BottomSheetModalProvider` — record nesting order | | Permissions already declared | `AndroidManifest.xml`, `Info.plist` | | Env/config system | `.env`, `react-native-config`, `expo-constants`, custom | | App version source | `package.json` `version`, `android/app/build.gradle` `versionName`, `Info.plist`, or an env value | | Expo? | `expo` in deps; no `android/`/`ios/` dirs ⇒ managed workflow | *** ## 2. Determine compatibility [#2-determine-compatibility] **Supported:** React Native **0.76 – 0.86**, **Hermes** (required), **JDK 17**, **AGP ≥ 8.6**, Android **`minSdkVersion` ≥ 24** (hard-coded by LiveKit's WebRTC — non-negotiable), iOS **15.1** (**16.0** on RN ≥ 0.81). Verified: native build + on-device runtime on RN **0.78** and **0.86** (Android). Below 0.76: unsupported (the SDK ships RN-0.78 codegen). **Two known-good stacks** — pick by RN line and pin exactly: | Package | Stack A: RN 0.78–0.81 | Stack B: RN 0.82–0.86 (New-Arch only) | | ------------------------------------------- | ---------------------------- | ----------------------------------------- | | `react` | 19.0.0 (18.3.1 on 0.76/0.77) | must satisfy RN's peer (0.86 → `^19.2.3`) | | `@livekit/react-native` | 2.11.1 (min 2.10.2) | 2.11.1 | | `@livekit/react-native-webrtc` | 144.1.1 (min 137.0.0) | 144.1.1 | | `react-native-reanimated` | 3.19.5 | **4.5.2** | | `react-native-worklets` | — (do NOT install with RA3) | **0.11.0** | | `react-native-gesture-handler` | latest 2.x (≥ 2.18.0) | **3.0.2** | | `react-native-safe-area-context` | 5.8.0 | 5.8.0 | | `@react-native-async-storage/async-storage` | 2.2.0 | 2.2.0 | | `lottie-react-native` | 7.3.8 | 7.3.8 | | `react-native-linear-gradient` | 2.8.3 | 2.8.3 | If the app is on RN ≥ 0.82 it **must** be on Reanimated 4 + worklets + Gesture Handler 3 (Reanimated 3 / GH 2 cannot run there). If on RN ≤ 0.81 with Reanimated 4, keep it (New Arch only). **Do not upgrade/downgrade unrelated dependencies.** If an existing dependency is incompatible: explain, pick the compatible version from the table, update it and its native config, re-validate. *** ## 3. Install the SDK [#3-install-the-sdk] ```bash npm install @revrag-ai/embed-react-native@^1.1.0 ``` If given a **local tarball**, install that path instead. After install, **verify the package is complete** — v1.1.0 shipped without native sources: ```bash ls node_modules/@revrag-ai/embed-react-native/android/src node_modules/@revrag-ai/embed-react-native/ios/*.m ``` Both must exist. Autolinking then links the module; no manual package registration. **Never add `livekit-client` to the app's `package.json`** — the SDK bundles it. A host copy produces two runtimes and silently breaks calls (§4). *** ## 4. Install and configure dependencies [#4-install-and-configure-dependencies] Install every peer from §2 at the pinned version. Then verify: ```bash npm ls livekit-client --all # MUST be exactly one path npm ls @livekit/react-native-webrtc --all # exactly one npm ls @livekit/react-native @livekit/react-native-webrtc # versions match a released pair npm ls react-native-reanimated react-native-worklets react-native-gesture-handler npm ls react react-native # exactly one copy each (monorepo/pnpm hoisting!) ``` **Expected noise (harmless):** every install prints `npm warn ERESOLVE overriding peer dependency` and `npm ls` may mark React `invalid` — a transitive LiveKit web helper peers `react-dom`. Do not "fix" it; do not gate CI on `npm ls` exit code. **Package-manager rules** * **yarn classic**: does not auto-install peers — install each explicitly; check `ls node_modules/@livekit/react-native`. * **yarn berry**: requires `nodeLinker: node-modules` in `.yarnrc.yml` (PnP breaks LiveKit resolution). * **pnpm**: `auto-install-peers` turns every `*` peer into `latest` (can pull RA4/GH3 onto RN \< 0.82) — pin explicitly; do not enable `strict-peer-dependencies`. * **Expo**: managed workflow needs a **development build** (Expo Go cannot run it) plus `@livekit/react-native-expo-plugin` and `@config-plugins/react-native-webrtc`; `expo-doctor`'s "unsupported on New Architecture" warning for LiveKit is a false positive. ### Reanimated / worklets (Babel) [#reanimated--worklets-babel] Add the plugin as the **last** entry of the top-level `plugins` array — and of every `env`/`overrides` branch that defines plugins — **without removing existing presets or plugins** (`nativewind/babel`, `module-resolver`, dotenv, `babel-preset-expo`, …): * Reanimated **3**: `'react-native-reanimated/plugin'` * Reanimated **4**: `'react-native-worklets/plugin'` (never both; never worklets with RA3) Then `npx react-native start --reset-cache`. A missing/misordered plugin does not crash; the SDK degrades to no-op animations and logs `[Embed SDK] react-native-reanimated …`. ### Gesture Handler [#gesture-handler] The root must be wrapped in ``. Reuse an existing one; do not nest a second. ### LiveKit / WebRTC [#livekit--webrtc] * **Do NOT call `registerGlobals()` yourself.** The SDK calls it inside `useInitialize` (single-flight). Calling it again duplicates native event listeners. * **If the app already initializes LiveKit** (`registerGlobals` in `index.js` and/or a native `setup()`): do **not** add a second native `setup()`; keep the host's. Note that the SDK's own `registerGlobals()` will still run — flag this to the developer as a known double-registration risk that needs RevRag confirmation. *** ## 5. Android native integration [#5-android-native-integration] 1. **`MainApplication`** — call LiveKit setup in `onCreate()` **after `super.onCreate()` and before React Native loads** (`loadReactNative(this)` / `load()`): ```kotlin import com.livekit.reactnative.LiveKitReactNative override fun onCreate() { super.onCreate() LiveKitReactNative.setup(this) // before React Native starts loadReactNative(this) // (or the app's existing load call) } ``` Missing it → runtime crash `audioRecordSamplesDispatcher is not initialized!`. 2. **Permissions — add nothing.** The SDK's manifest merges `RECORD_AUDIO`, `MODIFY_AUDIO_SETTINGS`, `BLUETOOTH_CONNECT`; LiveKit merges `INTERNET`, `CAMERA`, `FOREGROUND_SERVICE`, etc. The mic runtime prompt is done by the SDK during `startCall`. For a **voice-only** app you may strip camera: ``. 3. **`android/build.gradle` `ext`** must define `minSdkVersion` (≥ 24), `compileSdkVersion`, `targetSdkVersion`, `kotlinVersion`, `ndkVersion` — the SDK inherits them and needs NDK/CMake (it has a C++ TurboModule). `newArchEnabled=true`, `hermesEnabled=true` in `gradle.properties`. 4. **ProGuard/R8** (only if `enableProguardInReleaseBuilds`): `-keep class com.revragai.embedreactnative.** { *; }` (WebRTC/LiveKit keeps ship with their own consumer rules). Note the package is `com.revragai.embedreactnative`. 5. Do **not** add `com.airbnb.android:lottie` manually — `lottie-react-native` provides it. 6. **Build**: `cd android && ./gradlew assembleDebug`. On failure decide SDK-related vs pre-existing; fix; rebuild. Never suppress errors. *** ## 6. iOS native integration [#6-ios-native-integration] 1. **`AppDelegate.swift`** — note the module and class casing: ```swift import livekit_react_native // in application(_:didFinishLaunchingWithOptions:) BEFORE React Native starts: LivekitReactNative.setup() ``` (Objective-C: `[LivekitReactNative setup]`.) `import LiveKitReactNative` does **not** resolve. 2. **`Info.plist`**: `NSMicrophoneUsageDescription` is **mandatory** (crash without it). `NSCameraUsageDescription` only if video is used. ATS: allow your `embedUrl` host. 3. `cd ios && pod install` after every dependency change (Podfile should have New Arch enabled). 4. **Fonts**: run `npx react-native-asset` once from the app root (adds `UIAppFonts`). 5. Deployment target ≥ 15.1 (16.0 on RN ≥ 0.81). Build with Xcode / `npx react-native run-ios`. *** ## 7. Babel & Metro validation [#7-babel--metro-validation] Do not replace configs. Confirm: presets intact; the Reanimated/worklets plugin is the last plugin in every branch; `metro.config.js` unchanged unless it's a monorepo (then the SDK must resolve to ONE copy of `react`/`react-native`). After changes: reset Metro cache, rebuild natively if native config changed. *** ## 8. SDK initialization (exact contract) [#8-sdk-initialization-exact-contract] Place at the **root component, exactly once**: ```tsx import { useInitialize } from '@revrag-ai/embed-react-native'; const { isInitialized, error } = useInitialize({ apiKey: API_KEY, // required (from the app's config/env system) embedUrl: EMBED_URL, // optional; RevRag-provided host appVersion: APP_VERSION, // REQUIRED — canonical place for it }); ``` * Returns `{ isInitialized, error }`. On failure `error` is a string; transient errors retry with backoff inside the SDK; auth/validation errors are fatal. * **Fail-open**: do NOT block the app on `isInitialized`. Render the app; the widget simply won't appear until config arrives. Log `error` if set. * Calling it from a second component re-registers (harmless GET); StrictMode-safe. * `registerGlobals()` is invoked here — do not add it elsewhere. Wrap the tree — **this nesting order matters**: ```tsx {/* any PortalProvider / PaperProvider / BottomSheetModalProvider go INSIDE here */} ``` **`navigationRef` is REQUIRED for the widget to appear.** The provider only learns the current screen from the navigation `state` listener attached through that ref (attached at mount, retried once at 1 s). Without it: the button never shows, capture screen stays `unknown`, `startCall()` fails "no EmbedButton mounted". The ref must be the **same object** passed to `NavigationContainer`, and the container must be mounted within \~1 s of the provider (mount it unconditionally; put auth gates *inside* via `initialRouteName`). `EmbedProvider` **renders the floating button itself** — never render `EmbedButton` manually. Mount `EmbedProvider` **outermost** (above portal/paper/bottom-sheet providers) or the widget and highlights end up underneath modals. *** ## 9. API key validation [#9-api-key-validation] Verify a non-empty `apiKey` exists in the app's config/env mechanism and is passed to `useInitialize`. Do not hard-code secrets in source if an env system exists. If missing → **stop**, report "API key missing", ask the developer. Missing key ⇒ `useInitialize` returns error `apiKey is required…` and every request fails. *** ## 10. App version validation [#10-app-version-validation] `appVersion` is **required** in `useInitialize` (the `EmbedProvider` `appVersion` prop is an optional override; `useInitialize` wins). Derive it from the app's real version (`package.json` version, `versionName`, or env). Never pass `""`/`undefined` — blank is rejected and the SDK falls back to the native build version, then the literal `"unknown"`, and logs `console.error('[Embed] No app version was supplied…')`. **Verify:** that error line must be **absent**, and requests carry the `X-Revrag-App-Version` header / `?app_version=` on `/initialize`. *** ## 11. USER\_DATA validation [#11-user_data-validation] After the user is identified (login), send **once per user**: ```tsx import { Embed, EmbedEventKeys } from '@revrag-ai/embed-react-native'; await Embed.Event(EmbedEventKeys.USER_DATA, { app_user_id: userId /*, data?: {...} */ }); ``` Facts the integration must respect: * It is persisted **only after the backend accepts it** — if sent offline it is not stored; send it again once online (e.g. on next app foreground / login success). * Without it: every other event is dropped pre-send (`[Embed SDK] Skipping event payload…`), cache sync stays dirty, and a call's token fetch aborts (`[Embed Call] no app_user_id stored (USER_DATA event not sent yet?)`). * **`startCall()` still resolves `true`** in that case (the button swallows the error) — so never treat the return value as "identified". Observe `AGENT_CONVERSATION_STARTED`. * `EmbedLogout()` clears identity — re-send `USER_DATA` for the next user. * `Embed.Event` never rejects; pass `onResult(ok, err)` to observe delivery. Required order: `useInitialize` → init success → `USER_DATA` → widget config → screen attached → baseline snapshot → interactions → call. *** ## 12. Widget configuration validation [#12-widget-configuration-validation] The FAB renders **only after** `/initialize` returns `widget_config`. Test: | Case | Expected | Observe | | ---------------------- | -------------------------------------------------------------- | --------------------------------------------------------------------- | | A. config exists | FAB appears on allowed screens; `agent_visible` fires | `[Embed] /initialize response:` contains `widget_config` | | B. config null/invalid | no FAB, **no crash**, app usable | `[Embed] widget_config parse failed:` or empty config; nothing else | | C. init fails | no FAB, app fully functional | `useInitialize().error` set; `Initialization error:` | | D. delayed config | app must not assume config is ready; FAB appears when it lands | on a true cold start the FAB may need the next screen change to mount | Also confirm the native store: `[Embed] Cache files dir: …/embed_cache/db` — if instead you see `'EmbedFileStore' native module unavailable — cache is IN-MEMORY`, autolinking failed (rebuild natively). *** ## 13. Ask the developer (do not guess) [#13-ask-the-developer-do-not-guess] After inspecting, list the **actual route names** you found (deepest active route names — e.g. `home_tab`, `submit_interest`; not display titles), then ask: 1. **Which screens should the RevRag widget appear on?** (Offer: all-except-auth via `showOnAllScreens` + `excludeScreens`, or an explicit `includeScreens` list.) 2. **Which screens must never be transmitted/shown** (login, OTP, payment, regulated data)? → `excludeScreens` (this also ends a live call on entry). 3. **Visibility mode:** * **A. Continuous** (`embedButtonContinuity: 'continuous'`, default): button and any live call persist across allowed screens; delay applied once. * **B. Per-screen** (`'perScreen'`): button is re-evaluated/re-delayed on every screen (collapses the card, may flicker). **It does not end the call** — only visibility rules or exclusion do. 4. **Should a live call survive navigating to a screen where the button is hidden?** Default `endCallWhenHiddenByVisibility: true` ends it; set `false` to let it continue. 5. **App version source** if it can't be inferred; **API key location**. 6. Any **WebView-only**, native-picker, or custom-navigation flows (see §22). Wait for answers before finalizing screen integration. *** ## 14. Screen integration (how the SDK actually detects screens) [#14-screen-integration-how-the-sdk-actually-detects-screens] * Current screen = **name of the deepest active route** (`getRootState()` walked via `routes[index]`); never `options.title`. Nested tabs-in-stack ⇒ the leaf tab name, but the **parent stack route flashes first** while the nested navigator mounts — do not put a parent route (e.g. `Home`) in `excludeScreens` or a call may be cut on that transient. * Matching is **exact, case-sensitive** unless `setScreenMatching()` tiers are enabled (`aliasMatching` — naming variants; `ancestorMatching` — any parent navigator name; `fingerprintScreens` — capture naming only, not visibility). Prefer renaming routes to stable snake-case ids; those names are the backend's screen identities. * **Visibility algorithm:** empty screen name ⇒ hidden. If `showOnAllScreens` ⇒ shown unless in `excludeScreens`. Else shown iff in `includeScreens ∪ group screens` (empty ⇒ all) — and in this mode **`excludeScreens` is NOT consulted**. * **`SCREEN_STATE` events only tag later analytics events with a screen name.** They do **not** drive widget visibility or UI capture. There is no supported way to feed a non-React-Navigation router into visibility — convert to real routes or ask RevRag. * Every screen is **walked for the durable cache regardless of visibility**; only `excludeScreens` stops transmission/pulls for the live channel. Treat exclusion as the privacy control. * Navigating to the background always ends a live call. Test: Screen A (widget appears) → Screen B → back to A; nested tabs; a modal; a reset; repeated visits — no duplicate widgets (the SDK renders one, in the topmost host). *** ## 15. Continuous vs per-screen — what to verify [#15-continuous-vs-per-screen--what-to-verify] Continuous: FAB stays mounted across allowed screens; a call started on A stays live on B (if B is allowed). Per-screen: FAB re-appears (after `embedButtonDelayMs`) per screen and the expanded card collapses; the call is **still live**. In both: entering an excluded screen ends the call (`[Embed Call] controller endCall (public API)` → `endCall invoked`). *** ## 16. Event ordering validation [#16-event-ordering-validation] Confirm this order in the logs and that nothing fires out of order: `[Embed] /initialize response:` → (USER\_DATA accepted) → `[Embed] Cache files dir:` / `agent_visible` → `[Embed] Cache screen "": nodes=N` → (call) `[Embed Call] token fetched` → `connect attempt 1/3` → `connected in Nms` → `[Embed Snapshot] SENT reason=baseline screen=…`. Invalid patterns to catch: event before init (`Embed SDK is not initialized`), call before USER\_DATA (§11), duplicate `embedOnAgent` handlers (each returns a handle — must `embedOffAgent` on unmount), a second `EmbedProvider`/`EmbedButton`. *** ## 17. UI tree / snapshot validation [#17-ui-tree--snapshot-validation] For every configured screen, during a call, observe `[Embed Snapshot] SENT reason= screen= bytes=N budget=N trims=N` (and `skipped dedup` when unchanged). Off-call, the durable cache logs `[Embed] Cache screen "": nodes=N captures=N`. Test initial screen, navigation, return, tap, text input, scroll, modal, nested and dynamic content. If nothing is captured: check the screen name (is it `''`/`unknown` → ref problem), exclusion, mid- navigation re-arm, `DROPPED oversize` (node budget), or `[Embed] Nav settle: … did not clear` (two screens composed). What the tree captures: buttons (anything with `onPress`), inputs, text, form fields, checkbox/radio/switch/slider (via `accessibilityRole`/`accessibilityState`/`accessibilityValue`), scrollables. It **cannot** see WebViews, native pickers, native-stack header titles, or gesture-only tappables. *** ## 18. Event capture validation [#18-event-capture-validation] Automatic click tracking patches **`Pressable`, `TouchableOpacity`, `TouchableHighlight`, `TouchableWithoutFeedback`** (+ `TouchableNativeFeedback` on Android) imported from `react-native`. It does **not** patch RNGH's `Pressable`/`RectButton`, RN `