@userflow-tech/react-native 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (253) hide show
  1. package/README.md +360 -70
  2. package/docs/anchoring.md +58 -42
  3. package/docs/troubleshooting.md +69 -27
  4. package/expo-plugin/hostDependencyWarnings.d.ts +21 -0
  5. package/expo-plugin/hostDependencyWarnings.d.ts.map +1 -0
  6. package/expo-plugin/hostDependencyWarnings.js +101 -0
  7. package/expo-plugin/hostDependencyWarnings.js.map +1 -0
  8. package/expo-plugin/withUserflow.d.ts.map +1 -1
  9. package/expo-plugin/withUserflow.js +16 -0
  10. package/expo-plugin/withUserflow.js.map +1 -1
  11. package/lib/commonjs/anchors/AnchorRegistry.js +7 -0
  12. package/lib/commonjs/anchors/AnchorRegistry.js.map +1 -1
  13. package/lib/commonjs/anchors/elementConditionEvaluator.js +189 -55
  14. package/lib/commonjs/anchors/elementConditionEvaluator.js.map +1 -1
  15. package/lib/commonjs/anchors/elementTapMonitor.js +92 -0
  16. package/lib/commonjs/anchors/elementTapMonitor.js.map +1 -0
  17. package/lib/commonjs/anchors/elementTargetMatch.js +85 -0
  18. package/lib/commonjs/anchors/elementTargetMatch.js.map +1 -0
  19. package/lib/commonjs/anchors/fiber/findAnchorFiber.js +64 -39
  20. package/lib/commonjs/anchors/fiber/findAnchorFiber.js.map +1 -1
  21. package/lib/commonjs/anchors/fiber/touchTargetFiber.js +105 -0
  22. package/lib/commonjs/anchors/fiber/touchTargetFiber.js.map +1 -0
  23. package/lib/commonjs/anchors/resolveAnchorHost.js +2 -1
  24. package/lib/commonjs/anchors/resolveAnchorHost.js.map +1 -1
  25. package/lib/commonjs/anchors/resolveAutoAnchorHost.js +4 -2
  26. package/lib/commonjs/anchors/resolveAutoAnchorHost.js.map +1 -1
  27. package/lib/commonjs/anchors/resolveElementTargets.js +158 -0
  28. package/lib/commonjs/anchors/resolveElementTargets.js.map +1 -0
  29. package/lib/commonjs/anchors/targetPressMonitor.js +9 -3
  30. package/lib/commonjs/anchors/targetPressMonitor.js.map +1 -1
  31. package/lib/commonjs/anchors/useUserflowAnchor.js +1 -0
  32. package/lib/commonjs/anchors/useUserflowAnchor.js.map +1 -1
  33. package/lib/commonjs/anonymousId.js +16 -7
  34. package/lib/commonjs/anonymousId.js.map +1 -1
  35. package/lib/commonjs/authoring/capturePickerScreenshot.js +9 -1
  36. package/lib/commonjs/authoring/capturePickerScreenshot.js.map +1 -1
  37. package/lib/commonjs/evalJs/getWebView.js +5 -13
  38. package/lib/commonjs/evalJs/getWebView.js.map +1 -1
  39. package/lib/commonjs/hostAppVersion.js +6 -1
  40. package/lib/commonjs/hostAppVersion.js.map +1 -1
  41. package/lib/commonjs/index.js +5 -0
  42. package/lib/commonjs/index.js.map +1 -1
  43. package/lib/commonjs/installCheck.js +183 -0
  44. package/lib/commonjs/installCheck.js.map +1 -0
  45. package/lib/commonjs/nativeModules.js +44 -0
  46. package/lib/commonjs/nativeModules.js.map +1 -0
  47. package/lib/commonjs/network.js +20 -7
  48. package/lib/commonjs/network.js.map +1 -1
  49. package/lib/commonjs/pairing/deviceId.js +14 -11
  50. package/lib/commonjs/pairing/deviceId.js.map +1 -1
  51. package/lib/commonjs/pairing/tokenStorage.js +17 -4
  52. package/lib/commonjs/pairing/tokenStorage.js.map +1 -1
  53. package/lib/commonjs/runtime/TargetPressBridge.js +12 -1
  54. package/lib/commonjs/runtime/TargetPressBridge.js.map +1 -1
  55. package/lib/commonjs/runtime/UserflowProvider.js +6 -3
  56. package/lib/commonjs/runtime/UserflowProvider.js.map +1 -1
  57. package/lib/commonjs/storage/asyncStorage.js +67 -0
  58. package/lib/commonjs/storage/asyncStorage.js.map +1 -0
  59. package/lib/commonjs/surfaces/BannerSurface.js +3 -1
  60. package/lib/commonjs/surfaces/BannerSurface.js.map +1 -1
  61. package/lib/commonjs/surfaces/safeArea.js +74 -28
  62. package/lib/commonjs/surfaces/safeArea.js.map +1 -1
  63. package/lib/commonjs/version.js +1 -1
  64. package/lib/module/anchors/AnchorRegistry.js +7 -0
  65. package/lib/module/anchors/AnchorRegistry.js.map +1 -1
  66. package/lib/module/anchors/elementConditionEvaluator.js +189 -55
  67. package/lib/module/anchors/elementConditionEvaluator.js.map +1 -1
  68. package/lib/module/anchors/elementTapMonitor.js +87 -0
  69. package/lib/module/anchors/elementTapMonitor.js.map +1 -0
  70. package/lib/module/anchors/elementTargetMatch.js +78 -0
  71. package/lib/module/anchors/elementTargetMatch.js.map +1 -0
  72. package/lib/module/anchors/fiber/findAnchorFiber.js +62 -39
  73. package/lib/module/anchors/fiber/findAnchorFiber.js.map +1 -1
  74. package/lib/module/anchors/fiber/touchTargetFiber.js +100 -0
  75. package/lib/module/anchors/fiber/touchTargetFiber.js.map +1 -0
  76. package/lib/module/anchors/resolveAnchorHost.js +2 -1
  77. package/lib/module/anchors/resolveAnchorHost.js.map +1 -1
  78. package/lib/module/anchors/resolveAutoAnchorHost.js +3 -3
  79. package/lib/module/anchors/resolveAutoAnchorHost.js.map +1 -1
  80. package/lib/module/anchors/resolveElementTargets.js +152 -0
  81. package/lib/module/anchors/resolveElementTargets.js.map +1 -0
  82. package/lib/module/anchors/targetPressMonitor.js +8 -3
  83. package/lib/module/anchors/targetPressMonitor.js.map +1 -1
  84. package/lib/module/anchors/useUserflowAnchor.js +1 -0
  85. package/lib/module/anchors/useUserflowAnchor.js.map +1 -1
  86. package/lib/module/anonymousId.js +15 -5
  87. package/lib/module/anonymousId.js.map +1 -1
  88. package/lib/module/authoring/capturePickerScreenshot.js +9 -1
  89. package/lib/module/authoring/capturePickerScreenshot.js.map +1 -1
  90. package/lib/module/evalJs/getWebView.js +5 -13
  91. package/lib/module/evalJs/getWebView.js.map +1 -1
  92. package/lib/module/hostAppVersion.js +6 -1
  93. package/lib/module/hostAppVersion.js.map +1 -1
  94. package/lib/module/index.js +5 -0
  95. package/lib/module/index.js.map +1 -1
  96. package/lib/module/installCheck.js +169 -0
  97. package/lib/module/installCheck.js.map +1 -0
  98. package/lib/module/nativeModules.js +40 -0
  99. package/lib/module/nativeModules.js.map +1 -0
  100. package/lib/module/network.js +20 -7
  101. package/lib/module/network.js.map +1 -1
  102. package/lib/module/pairing/deviceId.js +14 -10
  103. package/lib/module/pairing/deviceId.js.map +1 -1
  104. package/lib/module/pairing/tokenStorage.js +17 -4
  105. package/lib/module/pairing/tokenStorage.js.map +1 -1
  106. package/lib/module/runtime/TargetPressBridge.js +12 -1
  107. package/lib/module/runtime/TargetPressBridge.js.map +1 -1
  108. package/lib/module/runtime/UserflowProvider.js +7 -4
  109. package/lib/module/runtime/UserflowProvider.js.map +1 -1
  110. package/lib/module/storage/asyncStorage.js +61 -0
  111. package/lib/module/storage/asyncStorage.js.map +1 -0
  112. package/lib/module/surfaces/BannerSurface.js +3 -1
  113. package/lib/module/surfaces/BannerSurface.js.map +1 -1
  114. package/lib/module/surfaces/safeArea.js +70 -28
  115. package/lib/module/surfaces/safeArea.js.map +1 -1
  116. package/lib/module/version.js +1 -1
  117. package/lib/typescript/commonjs/src/anchors/AnchorRegistry.d.ts +7 -0
  118. package/lib/typescript/commonjs/src/anchors/AnchorRegistry.d.ts.map +1 -1
  119. package/lib/typescript/commonjs/src/anchors/elementConditionEvaluator.d.ts +12 -16
  120. package/lib/typescript/commonjs/src/anchors/elementConditionEvaluator.d.ts.map +1 -1
  121. package/lib/typescript/commonjs/src/anchors/elementTapMonitor.d.ts +36 -0
  122. package/lib/typescript/commonjs/src/anchors/elementTapMonitor.d.ts.map +1 -0
  123. package/lib/typescript/commonjs/src/anchors/elementTargetMatch.d.ts +20 -0
  124. package/lib/typescript/commonjs/src/anchors/elementTargetMatch.d.ts.map +1 -0
  125. package/lib/typescript/commonjs/src/anchors/fiber/findAnchorFiber.d.ts +17 -5
  126. package/lib/typescript/commonjs/src/anchors/fiber/findAnchorFiber.d.ts.map +1 -1
  127. package/lib/typescript/commonjs/src/anchors/fiber/touchTargetFiber.d.ts +24 -0
  128. package/lib/typescript/commonjs/src/anchors/fiber/touchTargetFiber.d.ts.map +1 -0
  129. package/lib/typescript/commonjs/src/anchors/resolveAnchorHost.d.ts +2 -1
  130. package/lib/typescript/commonjs/src/anchors/resolveAnchorHost.d.ts.map +1 -1
  131. package/lib/typescript/commonjs/src/anchors/resolveAutoAnchorHost.d.ts +10 -1
  132. package/lib/typescript/commonjs/src/anchors/resolveAutoAnchorHost.d.ts.map +1 -1
  133. package/lib/typescript/commonjs/src/anchors/resolveElementTargets.d.ts +39 -0
  134. package/lib/typescript/commonjs/src/anchors/resolveElementTargets.d.ts.map +1 -0
  135. package/lib/typescript/commonjs/src/anchors/targetPressMonitor.d.ts +2 -0
  136. package/lib/typescript/commonjs/src/anchors/targetPressMonitor.d.ts.map +1 -1
  137. package/lib/typescript/commonjs/src/anchors/useUserflowAnchor.d.ts.map +1 -1
  138. package/lib/typescript/commonjs/src/anonymousId.d.ts +2 -0
  139. package/lib/typescript/commonjs/src/anonymousId.d.ts.map +1 -1
  140. package/lib/typescript/commonjs/src/authoring/capturePickerScreenshot.d.ts.map +1 -1
  141. package/lib/typescript/commonjs/src/evalJs/getWebView.d.ts +2 -1
  142. package/lib/typescript/commonjs/src/evalJs/getWebView.d.ts.map +1 -1
  143. package/lib/typescript/commonjs/src/hostAppVersion.d.ts +1 -1
  144. package/lib/typescript/commonjs/src/hostAppVersion.d.ts.map +1 -1
  145. package/lib/typescript/commonjs/src/index.d.ts.map +1 -1
  146. package/lib/typescript/commonjs/src/installCheck.d.ts +48 -0
  147. package/lib/typescript/commonjs/src/installCheck.d.ts.map +1 -0
  148. package/lib/typescript/commonjs/src/nativeModules.d.ts +13 -0
  149. package/lib/typescript/commonjs/src/nativeModules.d.ts.map +1 -0
  150. package/lib/typescript/commonjs/src/network.d.ts.map +1 -1
  151. package/lib/typescript/commonjs/src/pairing/deviceId.d.ts.map +1 -1
  152. package/lib/typescript/commonjs/src/pairing/tokenStorage.d.ts.map +1 -1
  153. package/lib/typescript/commonjs/src/runtime/TargetPressBridge.d.ts +2 -1
  154. package/lib/typescript/commonjs/src/runtime/TargetPressBridge.d.ts.map +1 -1
  155. package/lib/typescript/commonjs/src/runtime/UserflowProvider.d.ts.map +1 -1
  156. package/lib/typescript/commonjs/src/storage/asyncStorage.d.ts +15 -0
  157. package/lib/typescript/commonjs/src/storage/asyncStorage.d.ts.map +1 -0
  158. package/lib/typescript/commonjs/src/surfaces/BannerSurface.d.ts.map +1 -1
  159. package/lib/typescript/commonjs/src/surfaces/safeArea.d.ts +6 -2
  160. package/lib/typescript/commonjs/src/surfaces/safeArea.d.ts.map +1 -1
  161. package/lib/typescript/commonjs/src/version.d.ts +1 -1
  162. package/lib/typescript/module/src/anchors/AnchorRegistry.d.ts +7 -0
  163. package/lib/typescript/module/src/anchors/AnchorRegistry.d.ts.map +1 -1
  164. package/lib/typescript/module/src/anchors/elementConditionEvaluator.d.ts +12 -16
  165. package/lib/typescript/module/src/anchors/elementConditionEvaluator.d.ts.map +1 -1
  166. package/lib/typescript/module/src/anchors/elementTapMonitor.d.ts +36 -0
  167. package/lib/typescript/module/src/anchors/elementTapMonitor.d.ts.map +1 -0
  168. package/lib/typescript/module/src/anchors/elementTargetMatch.d.ts +20 -0
  169. package/lib/typescript/module/src/anchors/elementTargetMatch.d.ts.map +1 -0
  170. package/lib/typescript/module/src/anchors/fiber/findAnchorFiber.d.ts +17 -5
  171. package/lib/typescript/module/src/anchors/fiber/findAnchorFiber.d.ts.map +1 -1
  172. package/lib/typescript/module/src/anchors/fiber/touchTargetFiber.d.ts +24 -0
  173. package/lib/typescript/module/src/anchors/fiber/touchTargetFiber.d.ts.map +1 -0
  174. package/lib/typescript/module/src/anchors/resolveAnchorHost.d.ts +2 -1
  175. package/lib/typescript/module/src/anchors/resolveAnchorHost.d.ts.map +1 -1
  176. package/lib/typescript/module/src/anchors/resolveAutoAnchorHost.d.ts +10 -1
  177. package/lib/typescript/module/src/anchors/resolveAutoAnchorHost.d.ts.map +1 -1
  178. package/lib/typescript/module/src/anchors/resolveElementTargets.d.ts +39 -0
  179. package/lib/typescript/module/src/anchors/resolveElementTargets.d.ts.map +1 -0
  180. package/lib/typescript/module/src/anchors/targetPressMonitor.d.ts +2 -0
  181. package/lib/typescript/module/src/anchors/targetPressMonitor.d.ts.map +1 -1
  182. package/lib/typescript/module/src/anchors/useUserflowAnchor.d.ts.map +1 -1
  183. package/lib/typescript/module/src/anonymousId.d.ts +2 -0
  184. package/lib/typescript/module/src/anonymousId.d.ts.map +1 -1
  185. package/lib/typescript/module/src/authoring/capturePickerScreenshot.d.ts.map +1 -1
  186. package/lib/typescript/module/src/evalJs/getWebView.d.ts +2 -1
  187. package/lib/typescript/module/src/evalJs/getWebView.d.ts.map +1 -1
  188. package/lib/typescript/module/src/hostAppVersion.d.ts +1 -1
  189. package/lib/typescript/module/src/hostAppVersion.d.ts.map +1 -1
  190. package/lib/typescript/module/src/index.d.ts.map +1 -1
  191. package/lib/typescript/module/src/installCheck.d.ts +48 -0
  192. package/lib/typescript/module/src/installCheck.d.ts.map +1 -0
  193. package/lib/typescript/module/src/nativeModules.d.ts +13 -0
  194. package/lib/typescript/module/src/nativeModules.d.ts.map +1 -0
  195. package/lib/typescript/module/src/network.d.ts.map +1 -1
  196. package/lib/typescript/module/src/pairing/deviceId.d.ts.map +1 -1
  197. package/lib/typescript/module/src/pairing/tokenStorage.d.ts.map +1 -1
  198. package/lib/typescript/module/src/runtime/TargetPressBridge.d.ts +2 -1
  199. package/lib/typescript/module/src/runtime/TargetPressBridge.d.ts.map +1 -1
  200. package/lib/typescript/module/src/runtime/UserflowProvider.d.ts.map +1 -1
  201. package/lib/typescript/module/src/storage/asyncStorage.d.ts +15 -0
  202. package/lib/typescript/module/src/storage/asyncStorage.d.ts.map +1 -0
  203. package/lib/typescript/module/src/surfaces/BannerSurface.d.ts.map +1 -1
  204. package/lib/typescript/module/src/surfaces/safeArea.d.ts +6 -2
  205. package/lib/typescript/module/src/surfaces/safeArea.d.ts.map +1 -1
  206. package/lib/typescript/module/src/version.d.ts +1 -1
  207. package/package.json +25 -8
  208. package/src/anchors/AnchorRegistry.ts +12 -0
  209. package/src/anchors/elementConditionEvaluator.ts +235 -70
  210. package/src/anchors/elementTapMonitor.ts +95 -0
  211. package/src/anchors/elementTargetMatch.ts +90 -0
  212. package/src/anchors/fiber/findAnchorFiber.ts +66 -35
  213. package/src/anchors/fiber/touchTargetFiber.ts +125 -0
  214. package/src/anchors/resolveAnchorHost.ts +2 -1
  215. package/src/anchors/resolveAutoAnchorHost.ts +3 -3
  216. package/src/anchors/resolveElementTargets.ts +165 -0
  217. package/src/anchors/targetPressMonitor.ts +12 -3
  218. package/src/anchors/useUserflowAnchor.ts +1 -0
  219. package/src/anonymousId.ts +19 -8
  220. package/src/authoring/capturePickerScreenshot.ts +10 -1
  221. package/src/evalJs/getWebView.ts +6 -13
  222. package/src/hostAppVersion.ts +7 -1
  223. package/src/index.ts +10 -0
  224. package/src/installCheck.ts +225 -0
  225. package/src/nativeModules.ts +48 -0
  226. package/src/network.ts +27 -11
  227. package/src/pairing/deviceId.ts +14 -11
  228. package/src/pairing/tokenStorage.ts +23 -7
  229. package/src/runtime/TargetPressBridge.tsx +17 -1
  230. package/src/runtime/UserflowProvider.tsx +11 -4
  231. package/src/storage/asyncStorage.ts +88 -0
  232. package/src/surfaces/BannerSurface.tsx +7 -5
  233. package/src/surfaces/safeArea.ts +85 -40
  234. package/src/version.ts +1 -1
  235. package/vendor/core/dist/constants.d.ts +2 -0
  236. package/vendor/core/dist/constants.d.ts.map +1 -1
  237. package/vendor/core/dist/constants.js +2 -0
  238. package/vendor/core/dist/constants.js.map +1 -1
  239. package/vendor/core/dist/engine/ClientConditionEngine.d.ts +20 -1
  240. package/vendor/core/dist/engine/ClientConditionEngine.d.ts.map +1 -1
  241. package/vendor/core/dist/engine/ClientConditionEngine.js +13 -10
  242. package/vendor/core/dist/engine/ClientConditionEngine.js.map +1 -1
  243. package/vendor/core/dist/engine/UserflowCore.d.ts.map +1 -1
  244. package/vendor/core/dist/engine/UserflowCore.js +21 -2
  245. package/vendor/core/dist/engine/UserflowCore.js.map +1 -1
  246. package/vendor/core/dist/index.d.ts +1 -1
  247. package/vendor/core/dist/index.d.ts.map +1 -1
  248. package/vendor/core/dist/index.js.map +1 -1
  249. package/vendor/core/dist/version.d.ts +1 -1
  250. package/vendor/core/dist/version.js +1 -1
  251. package/vendor/core/package.json +1 -1
  252. package/vendor/protocol/package.json +1 -1
  253. package/docs/webview-custom-banner-poc.md +0 -64
package/README.md CHANGED
@@ -4,70 +4,125 @@ Official Userflow SDK for React Native and Expo. Deliver guided flows, tooltips,
4
4
  and modals authored in the Userflow dashboard — without shipping a new app
5
5
  release for every content change.
6
6
 
7
- > Early development. APIs may change. Packages publish to a private npm org
8
- > (`publishConfig.access: restricted`).
7
+ > Early development. APIs may change.
9
8
 
10
9
  ## Requirements
11
10
 
12
- | Peer / dependency | Required? | Why |
13
- | ------------------------------------------- | ---------- | ------------------------------------------------------------------------ |
14
- | `react`, `react-native` | Yes | Host runtime. |
15
- | `@react-native-async-storage/async-storage` | Yes (dep) | Persists the anonymous user id. Installed with this package. |
16
- | `react-native-device-info` | Yes (dep) | Reads the host app store version (bundled; native rebuild required). |
17
- | `react-native-view-shot` | Yes (peer) | Builder in-app picker screenshots. Native module; rebuild after install. |
18
- | `react-native-webview` | Yes (dep) | Evaluate JavaScript / WebView banners. Native rebuild required. |
19
- | `react-native-safe-area-context` | Optional | Correct insets for modal/tooltip surfaces. |
20
- | `@react-navigation/native` | Optional | Screen tracking + NAVIGATE routing (React Navigation). |
21
- | `expo-router` | Optional | Screen tracking + NAVIGATE routing (Expo Router). |
22
- | `@react-native-community/netinfo` | Optional | Instant offline→online reconnect (enhancement, not required). |
23
- | `expo-secure-store` | Optional | Persist Builder pairing JWT in Keychain/Keystore. |
11
+ Declared minimums: React `>=18.2.0`, React Native `>=0.76.0`. React Native
12
+ 0.74 and 0.75 cannot install this SDK: `react-native-view-shot` 5.x, which
13
+ Builder element selection needs, requires React Native 0.76 or later. This
14
+ package's test suite runs on React Native 0.81 / React 19.1. The TinyTime
15
+ sample app in this repo runs on Expo SDK 57 / React Native 0.86. Expo Go is
16
+ not supported.
17
+
18
+ `@react-native-async-storage/async-storage` is the only native module this
19
+ package depends on, so npm installs it. `react-native-webview` and
20
+ `react-native-view-shot` are required peers. npm 7+ and pnpm install required
21
+ peers. Yarn 1 and `npm install --legacy-peer-deps` do not, which is why the
22
+ install commands list WebView and view-shot explicitly.
23
+ `react-native-device-info` is an optional peer for bare React Native.
24
+
25
+ Install those packages in the **app** as well. Expo SDK 54 and later autolink
26
+ native modules found anywhere in the tree. Bare React Native, and Expo SDK 53
27
+ and older, only autolink packages listed in the app's own `package.json`. A
28
+ direct install also picks a version that matches the app's React Native, inside
29
+ the ranges below.
30
+
31
+ | Package | Kind | Why | If missing |
32
+ | ------------------------------------------------------- | -------------------------------- | ----------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
33
+ | `react`, `react-native` `>=0.76.0` | Required peers | Host runtime. | The app cannot load the SDK. React Native older than 0.76 fails install (`ERESOLVE`) because view-shot 5.x requires it. |
34
+ | `@react-native-async-storage/async-storage` `>=1.23 <3` | Dependency | Anonymous user id and the pairing device id. | `identifyAnonymous()` throws. Pairing keeps a device id in memory for the session. A dev-only warning is logged from `Userflow.init`. |
35
+ | `react-native-device-info` `>=11 <16` | Optional peer, bare React Native | Host app `app_version` / `app_build`. Expo reads `expo-application` / `expo-constants` instead. | Those attributes are omitted. A dev-only warning is logged on bare React Native. |
36
+ | `react-native-view-shot` `^5.0.0` | Required peer | Builder element-selection screenshots. | The picker has no screenshot. A dev-only warning is logged from `Userflow.init`. Version 6.x is outside the tested range and requires React Native 0.80 or later. |
37
+ | `react-native-webview` `>=13.8.0 <14` | Required peer | Evaluate JavaScript and WebView banners. | Those surfaces do not run. A dev-only warning is logged from `Userflow.init`. |
38
+ | `react-native-safe-area-context` `>=4 <6` | Optional peer | Insets for banners, flow cards, and Builder bars. | In development, `Userflow.init` warns once. Those surfaces draw under the status bar and home indicator. With the package installed but no `<SafeAreaProvider>`, cards use the device's initial insets and log a separate warning. |
39
+ | `@react-navigation/native` | Optional peer | Screen tracking and NAVIGATE routing (React Navigation). | Report screens yourself with `updateScreen` / `useUserflowScreen`. |
40
+ | `expo-router` | Optional peer | Screen tracking and NAVIGATE routing (Expo Router). | Report screens yourself with `updateScreen` / `useUserflowScreen`. |
41
+ | `@react-native-community/netinfo` `>=11 <13` | Optional peer | Instant offline→online reconnect. | Recovery still runs on the heartbeat and when the app returns to the foreground. If the JavaScript package is installed but `RNCNetInfo` is not in the binary, the SDK skips it instead of showing a red screen. |
42
+ | `expo-secure-store` | Optional peer | Persist the Builder pairing JWT in Keychain/Keystore. | The pairing token stays in memory and is lost when the process ends. The SDK loads it only when the `ExpoSecureStore` native module is registered. |
24
43
 
25
44
  Optional peers are declared `optional: true` in `peerDependenciesMeta`, so
26
- package managers will not warn if you skip them. Required peers (not marked
27
- optional) will warn — or fail the install — if they are missing.
45
+ package managers do not warn if you skip them.
46
+
47
+ npm 7+ and pnpm install required peers (`react-native-webview`,
48
+ `react-native-view-shot`) automatically. Yarn 1 and
49
+ `npm install --legacy-peer-deps` do not install peers, and they bury the
50
+ missing-peer warnings among unrelated ones. That is why the install commands
51
+ list WebView and view-shot explicitly. A clean npm install prints no warning
52
+ about optional peers, so install `react-native-safe-area-context` and NetInfo
53
+ yourself when you want the behavior in the table. An app that already depends
54
+ on an older major (view-shot 4.0.3, NetInfo 9.x, safe-area 3.x, WebView 13.6.x
55
+ or 14.x) fails install with `ERESOLVE`.
28
56
 
29
57
  ### Expo Go is not supported
30
58
 
31
- Use an **Expo development build** (or bare React Native). The SDK is not
32
- compatible with Expo Go.
59
+ Use an **Expo development build** (or bare React Native). In development,
60
+ `Userflow.init` logs:
61
+
62
+ ```text
63
+ [Userflow] This app is running in Expo Go, which cannot load the SDK native modules. Builder preview links will not open the app. Use a development build (npx expo run:ios / npx expo run:android).
64
+ ```
33
65
 
34
66
  ## Install
35
67
 
36
- Packages publish to the private `@userflow-tech` npm org. Create a **granular
37
- access token** on npm (Read-only, limited to `@userflow-tech`) and put it in
38
- the host app’s `.npmrc` via an env var — do not commit the token:
68
+ The package is public on npm; no token or `.npmrc` setup is needed.
69
+
70
+ Yarn 2 and later ignore `.npmrc`. Add this to `.yarnrc.yml` instead:
39
71
 
40
- ```ini
41
- @userflow-tech:registry=https://registry.npmjs.org/
42
- //registry.npmjs.org/:_authToken=${NPM_TOKEN}
72
+ ```yaml
73
+ npmScopes:
74
+ userflow-tech:
75
+ npmRegistryServer: 'https://registry.npmjs.org'
76
+ npmAuthToken: '${NPM_TOKEN}'
43
77
  ```
44
78
 
79
+ Set `NPM_TOKEN` in the environment that installs dependencies: a local shell,
80
+ CI, and EAS (an EAS secret named `NPM_TOKEN`, available to the install step).
81
+
82
+ ### Packages
83
+
45
84
  ```bash
46
85
  # Expo (dev build)
47
- npx expo install @userflow-tech/react-native react-native-view-shot@^5.0.0 react-native-webview
86
+ npx expo install @userflow-tech/react-native @react-native-async-storage/async-storage react-native-webview react-native-view-shot@^5.0.0
48
87
 
49
88
  # Bare React Native
50
- npm install @userflow-tech/react-native react-native-view-shot react-native-webview
89
+ npm install @userflow-tech/react-native @react-native-async-storage/async-storage react-native-device-info react-native-webview react-native-view-shot@^5.0.0
51
90
  cd ios && pod install
52
91
  ```
53
92
 
54
- `react-native-view-shot` is a **required peer** (`>=5.0.0`). It is a native
55
- module — rebuild after install (Metro reload is not enough). On Expo SDK 54,
93
+ `react-native-webview` and `react-native-view-shot` (`^5.0.0`) are **required
94
+ peers**. npm 7+ and pnpm install them. Yarn 1 and
95
+ `npm install --legacy-peer-deps` do not, so the commands above install them in
96
+ the app. Both are native modules — rebuild after install (a Metro reload is not
97
+ enough). On Expo SDK 54,
56
98
  unpinned `npx expo install react-native-view-shot` resolves **4.0.3**, which
57
- does not satisfy the peer and breaks New Architecture / bridgeless. Pin
58
- `^5.0.0` (or `@latest`) as in the command above.
99
+ does not satisfy the peer. Pin `^5.0.0` as in the command above. If the app
100
+ already depends on 4.0.3, that same command is what fixes it: installing the
101
+ SDK alone fails with
59
102
 
60
- `@react-native-async-storage/async-storage` and `react-native-device-info` are
61
- dependencies of this package and are installed automatically with a normal npm
62
- install. After adding or upgrading the SDK, rebuild the native app (`cd ios &&
63
- pod install`, then run again). If autolinking misses a bundled native module
64
- (common with local `file:` links), install it in the host app explicitly:
103
+ ```text
104
+ npm error Found: react-native-view-shot@4.0.3
105
+ npm error peer react-native-view-shot@"^5.0.0" from @userflow-tech/react-native
106
+ ```
65
107
 
66
- ```bash
67
- npm install @react-native-async-storage/async-storage react-native-device-info
68
- cd ios && pod install # iOS
108
+ Do not retry with `--force` or `--legacy-peer-deps`. That installs the SDK and
109
+ leaves 4.0.3 in place. If that already happened, `Userflow.init` warns in
110
+ development and names the installed version. If `expo doctor`
111
+ reports the `^5.0.0` pin, exclude it from Expo's version check:
112
+
113
+ ```json
114
+ {
115
+ "expo": {
116
+ "install": {
117
+ "exclude": ["react-native-view-shot"]
118
+ }
119
+ }
120
+ }
69
121
  ```
70
122
 
123
+ On a bare React Native app, `expo-secure-store` needs Expo modules installed
124
+ first (`npx install-expo-modules@latest`), then `npx expo install expo-secure-store`.
125
+
71
126
  Optional peers as needed:
72
127
 
73
128
  ```bash
@@ -79,42 +134,116 @@ For local tarball installs from this monorepo, pack with `pnpm pack:all` at the
79
134
  repo root and install the single `@userflow-tech/react-native` tarball. Steps:
80
135
  `docs/local-testing.md` in the monorepo.
81
136
 
137
+ ### Install with an AI agent
138
+
139
+ Cursor, Claude Code, and other agents that read Agent Skills can do this
140
+ install. The skill is `install-react-native-with-ai` in
141
+ [userflow/ai-skills-library](https://github.com/userflow/ai-skills-library).
142
+ Web apps use `install-with-ai` from that library instead.
143
+
144
+ ```bash
145
+ npx skills add userflow/ai-skills-library@install-react-native-with-ai
146
+ ```
147
+
148
+ Claude Code loads it from the plugin:
149
+
150
+ ```text
151
+ /plugin marketplace add userflow/ai-skills-library
152
+ /plugin install userflow@ai-skills-library
153
+ /userflow:install-react-native-with-ai
154
+ ```
155
+
82
156
  ## Quick start
83
157
 
84
158
  ```tsx
85
159
  import { Userflow, UserflowProvider } from '@userflow-tech/react-native';
86
160
 
87
161
  await Userflow.init({
88
- clientToken: 'ct_...', // from the Userflow dashboard → Environments
89
- // serverEndpoint: 'https://e.userflow.com', // default; set for staging/local
90
- // pairingEndpoint: 'https://e.userflow.com', // default; set for local Builder pairing
162
+ clientToken: 'ct_...', // from the Userflow dashboard → Environments. Never an esk_ secret key.
163
+ // serverEndpoint: 'https://e.userflow.com', // default (US). EU: 'https://e.eu.userflow.com'
164
+ // pairingEndpoint: 'https://e.userflow.com', // default; pairing stays on the US host
91
165
  });
92
166
 
93
167
  // Identified user — signature is computed by YOUR backend, never in the app.
94
168
  // `locale_code` is the same user attribute as web (see Localization below).
95
169
  await Userflow.identify('user-123', { name: 'Ada', locale_code: 'da-DK' }, { signature });
96
170
 
97
- // …or anonymous (id stored in AsyncStorage):
171
+ // Anonymous users count toward MAU. Call this only when you intend to identify
172
+ // signed-out users. The id is stored in AsyncStorage.
98
173
  await Userflow.identifyAnonymous();
99
174
  ```
100
175
 
101
- Wrap the app tree so flows can render:
176
+ Call `Userflow.teardown()` when tearing the SDK down (removes AppState and
177
+ NetInfo subscriptions). Call `Userflow.reset()` on logout to clear identity and
178
+ disconnect. `reset()` does not apply a new `clientToken` or endpoint; restart
179
+ the app to change those.
180
+
181
+ ### Where to put `init`, `identify`, and `UserflowProvider`
182
+
183
+ Call `Userflow.init` once, as early as you can, from the root component
184
+ (`app/_layout.tsx` with Expo Router, `App.tsx` with bare React Native or React
185
+ Navigation). `UserflowProvider` can mount before that promise resolves. Put it
186
+ once, near the top of the tree, inside `SafeAreaProvider`. Call `identify` after
187
+ sign-in. `identifyAnonymous` identifies signed-out users and counts them toward
188
+ MAU, so use it only when you mean to. Call `Userflow.reset()` on logout.
189
+
190
+ Expo Router:
102
191
 
103
192
  ```tsx
104
- import { UserflowProvider } from '@userflow-tech/react-native';
193
+ import { useEffect } from 'react';
194
+ import { Userflow, UserflowProvider } from '@userflow-tech/react-native';
195
+ import { UserflowExpoRouterScreenTracker } from '@userflow-tech/react-native/expo-router';
196
+ import { Stack } from 'expo-router';
197
+ import { SafeAreaProvider } from 'react-native-safe-area-context';
105
198
 
106
199
  export default function RootLayout() {
200
+ useEffect(() => {
201
+ void Userflow.init({ clientToken: 'ct_...' });
202
+ }, []);
203
+
107
204
  return (
108
- <UserflowProvider>
109
- <App />
110
- </UserflowProvider>
205
+ <SafeAreaProvider>
206
+ <UserflowProvider>
207
+ <UserflowExpoRouterScreenTracker />
208
+ <Stack />
209
+ </UserflowProvider>
210
+ </SafeAreaProvider>
111
211
  );
112
212
  }
113
213
  ```
114
214
 
115
- Call `Userflow.teardown()` when tearing the SDK down (removes AppState and
116
- NetInfo subscriptions). Call `Userflow.reset()` on logout to clear identity and
117
- disconnect.
215
+ React Navigation:
216
+
217
+ ```tsx
218
+ import { useEffect } from 'react';
219
+ import { Userflow, UserflowProvider } from '@userflow-tech/react-native';
220
+ import { WithUserflowReactNavigation } from '@userflow-tech/react-native/navigation';
221
+ import { NavigationContainer } from '@react-navigation/native';
222
+ import { SafeAreaProvider } from 'react-native-safe-area-context';
223
+
224
+ export default function App() {
225
+ useEffect(() => {
226
+ void Userflow.init({ clientToken: 'ct_...' });
227
+ }, []);
228
+
229
+ return (
230
+ <SafeAreaProvider>
231
+ <UserflowProvider>
232
+ <WithUserflowReactNavigation>
233
+ <NavigationContainer>{/* navigators */}</NavigationContainer>
234
+ </WithUserflowReactNavigation>
235
+ </UserflowProvider>
236
+ </SafeAreaProvider>
237
+ );
238
+ }
239
+ ```
240
+
241
+ Without `SafeAreaProvider`, development logs this once and flow cards fall back
242
+ to the device's initial insets:
243
+
244
+ ```text
245
+ [Userflow] UserflowProvider is not inside a <SafeAreaProvider>, so flow cards use the device's initial safe-area insets. Wrap your app in <SafeAreaProvider> from react-native-safe-area-context.
246
+ ```
118
247
 
119
248
  ### Call order
120
249
 
@@ -129,9 +258,15 @@ disconnect.
129
258
 
130
259
  Other rules:
131
260
 
132
- - `init()` is **idempotent**. A second call returns the cached promise and does
133
- **not** apply new options. Restart the process (or call `reset()` then
134
- re-init carefully) if you need different credentials.
261
+ - `init()` is **idempotent** once it has succeeded. A later call returns the
262
+ cached promise and does **not** apply new options. In development, a call
263
+ with a different `clientToken`, `serverEndpoint`, or `pairingEndpoint` while
264
+ that init is in flight or has succeeded logs
265
+ `[Userflow] Userflow.init() already ran with different options. The new clientToken, serverEndpoint, and pairingEndpoint are ignored because init runs once per process. Userflow.reset() does not apply new credentials. Restart the app to change them.`
266
+ Restart the app to change them after a successful init. If `init` rejects
267
+ (wrong token, or the EU endpoint hint), call it again with the corrected
268
+ options; that retry runs. `Userflow.reset()` clears the signed-in user;
269
+ it does not apply new credentials.
135
270
  - `identify()` with a **different** user id resets the previous identity first.
136
271
  Same-id re-identify merges attributes.
137
272
  - Identity verification `signature` is optional in the type, but **required by
@@ -191,13 +326,16 @@ subpath under `UserflowProvider` (the tracker is a separate entry so apps withou
191
326
  import { UserflowProvider } from '@userflow-tech/react-native';
192
327
  import { UserflowExpoRouterScreenTracker } from '@userflow-tech/react-native/expo-router';
193
328
  import { Stack } from 'expo-router';
329
+ import { SafeAreaProvider } from 'react-native-safe-area-context';
194
330
 
195
331
  export default function RootLayout() {
196
332
  return (
197
- <UserflowProvider>
198
- <UserflowExpoRouterScreenTracker />
199
- <Stack />
200
- </UserflowProvider>
333
+ <SafeAreaProvider>
334
+ <UserflowProvider>
335
+ <UserflowExpoRouterScreenTracker />
336
+ <Stack />
337
+ </UserflowProvider>
338
+ </SafeAreaProvider>
201
339
  );
202
340
  }
203
341
  ```
@@ -211,14 +349,17 @@ Wrap `NavigationContainer` with `WithUserflowReactNavigation` from the
211
349
  import { UserflowProvider } from '@userflow-tech/react-native';
212
350
  import { WithUserflowReactNavigation } from '@userflow-tech/react-native/navigation';
213
351
  import { NavigationContainer } from '@react-navigation/native';
352
+ import { SafeAreaProvider } from 'react-native-safe-area-context';
214
353
 
215
354
  export default function App() {
216
355
  return (
217
- <UserflowProvider>
218
- <WithUserflowReactNavigation>
219
- <NavigationContainer>{/* navigators */}</NavigationContainer>
220
- </WithUserflowReactNavigation>
221
- </UserflowProvider>
356
+ <SafeAreaProvider>
357
+ <UserflowProvider>
358
+ <WithUserflowReactNavigation>
359
+ <NavigationContainer>{/* navigators */}</NavigationContainer>
360
+ </WithUserflowReactNavigation>
361
+ </UserflowProvider>
362
+ </SafeAreaProvider>
222
363
  );
223
364
  }
224
365
  ```
@@ -236,9 +377,11 @@ useUserflowScreen(route.name);
236
377
  ## Element targeting (anchors)
237
378
 
238
379
  Tooltip steps and ELEMENT client conditions resolve a dashboard selector like
239
- `#invite-teammates` to a host view. Prefer a plain `testID` (or `nativeID` /
240
- `accessibilityLabel`) — no SDK hook required. A misspelled or missing anchor can
241
- mean the tooltip misses **or** a gated flow never starts at all.
380
+ `#invite-teammates` to a host view. Tag it with `userflowId` (strongest), then
381
+ `testID`, then `nativeID`, then `accessibilityLabel` — no SDK hook required. A
382
+ misspelled or missing anchor can mean the tooltip misses **or** a gated flow
383
+ never starts at all. `PRESENT`, `NOT_PRESENT`, `CLICK`, and `NOT_CLICK`
384
+ auto-start conditions are evaluated on device.
242
385
 
243
386
  ```tsx
244
387
  import { Pressable, Text } from 'react-native';
@@ -352,8 +495,8 @@ Screen tracking ≠ action routing. React Navigation apps need
352
495
  | `setOnNavigate(handler)` | Override or clear the host NAVIGATE handler. |
353
496
  | `reportActivity()` | Reset the idle timer; reconnect an idle-suspended socket. |
354
497
  | `didHandleURL(url)` | Handle a Userflow preview deep link (`…://?token=…`). |
355
- | `handlePairingUrl(url)` | Handle a Builder pairing deep link (`…://?pairing_token=…`; dev builds only). |
356
- | `exitPreview()` | Leave Builder preview mode; disconnect the pairing websocket (dev only). |
498
+ | `handlePairingUrl(url)` | Handle a Builder pairing deep link (`…://?pairing_token=…`). Works in every build; the signed JWT gates the session. |
499
+ | `exitPreview()` | Leave Builder preview mode; disconnect the pairing websocket. Works in every build. |
357
500
  | `inspectPairingDeepLink(url)` | Classify a URL as pairing without consuming it; reports rejection reason. |
358
501
  | `isPairingAttempt(reason)` | True when a rejection was a recognisable pairing attempt worth reporting. |
359
502
  | `parsePairingDeepLink(url)` / `isUserflowPairingUrl(url)` | Parse / detect a pairing deep link. |
@@ -374,7 +517,7 @@ Screen tracking ≠ action routing. React Navigation apps need
374
517
  | `useUserflow()` | Provider context (`{ engine }`); throws outside the provider. |
375
518
  | `useFlowSessionState()` | Reactive view of the active mid-flow session (advanced / custom UI). |
376
519
  | `useUserflowScreen(name)` | Report the active screen from a React tree. |
377
- | `useUserflowAnchor(id)` | Optional override: register a view as a MANUAL target (beats `testID`). |
520
+ | `useUserflowAnchor(id)` | Optional override: register a view as a MANUAL target (beats `userflowId` / `testID`). |
378
521
  | `useUserflowScrollContainer(onScroll?)` | Optional override: register a `ScrollView` for tooltip scroll-into-view. |
379
522
  | `Userflow.setScrollPadding(padding)` | Sticky chrome insets when scrolling targets into view. |
380
523
  | `Userflow.setCustomScrollIntoView(fn)` | Replace scroll-into-view (e.g. unsupported scroll libraries). |
@@ -411,11 +554,158 @@ Screen tracking ≠ action routing. React Navigation apps need
411
554
  - AUTO / codeless element targeting (exact-id fast path + scored matcher;
412
555
  scored path needs Babel full mode to populate the auto-anchor registry).
413
556
  See [docs/anchoring.md](docs/anchoring.md).
557
+ - Build-time stamping with `@userflow-tech/react-native/babel-plugin`. Jest
558
+ runs the plugin too, which changes snapshots. Read
559
+ [Babel plugin and snapshot tests](#babel-plugin-and-snapshot-tests) before
560
+ enabling it.
561
+
562
+ ## Install diagnostics
563
+
564
+ In development, the first `Userflow.init` checks native module registration and
565
+ whether `react-native-safe-area-context` is installed. Missing native modules
566
+ share one warning. A missing safe-area package gets its own warning, because
567
+ flows still run and draw under the status bar. It does not `require` an
568
+ unlinked package (including NetInfo, whose import throws when `RNCNetInfo` is
569
+ absent). Rebuild the native app after fixing a native-module line; a Metro
570
+ reload is not enough. Installing safe-area-context also needs a rebuild, then a
571
+ `<SafeAreaProvider>` around the app. The warnings look like this (only the
572
+ missing lines are included):
573
+
574
+ ```text
575
+ [Userflow] Native setup is incomplete. Flows may not run until you fix the following, then rebuild the app (a Metro reload is not enough):
576
+ - @react-native-async-storage/async-storage is not linked. Anonymous users and Builder pairing will not persist. Add it to the app package.json (npx expo install @react-native-async-storage/async-storage), run pod install on iOS, and rebuild.
577
+ - react-native-view-shot is not linked, so Builder element selection gets no screenshot. Install react-native-view-shot@^5.0.0 and rebuild. On Expo SDK 54, pin ^5.0.0; an unpinned npx expo install resolves 4.0.3.
578
+ - react-native-webview is not linked. Evaluate JavaScript and WebView banners will not run. Install it (npx expo install react-native-webview) and rebuild.
579
+ - react-native-device-info is not linked, so app_version and app_build are not sent. npm install react-native-device-info, then rebuild.
580
+ See node_modules/@userflow-tech/react-native/docs/troubleshooting.md.
581
+ ```
582
+
583
+ ```text
584
+ [Userflow] react-native-safe-area-context is not installed, so banners, flow cards, and Builder bars draw under the status bar and home indicator. Install it (npx expo install react-native-safe-area-context) and wrap the app in <SafeAreaProvider>.
585
+ See node_modules/@userflow-tech/react-native/docs/troubleshooting.md.
586
+ ```
587
+
588
+ Passing an environment secret key as `clientToken` throws:
589
+
590
+ ```text
591
+ clientToken is an environment secret key (esk_...). That key must never ship in the app. Pass the client token (ct_...) from Settings → Environments instead.
592
+ ```
593
+
594
+ When the default US endpoint rejects the token with `invalid_client_token`, the
595
+ error also says: `For EU-hosted environments pass serverEndpoint: 'https://e.eu.userflow.com'.`
596
+ Pairing stays on `https://e.userflow.com`.
597
+
598
+ `expo prebuild` warns when the config plugin has no `schemeId`, when
599
+ `react-native-view-shot` is missing or older than 5, and (on Expo SDK 53 and
600
+ older) when AsyncStorage, WebView, or safe-area-context is not a direct
601
+ dependency. Expo SDK 54 and later autolink safe-area-context when another
602
+ package such as Expo Router brought it in, so prebuild stays quiet in that
603
+ case. `Userflow.init` still warns when the package itself cannot be loaded.
604
+ Passing an environment UUID as `schemeId` fails the prebuild:
605
+
606
+ ```text
607
+ [@userflow-tech/react-native] schemeId "550e8400-e29b-41d4-a716-446655440000" looks like an environment id. Pass the scheme_id value that starts with userflow- (an opaque id from the dashboard), not the environment UUID.
608
+ ```
609
+
610
+ ## Jest
611
+
612
+ Mock the package in the host app's Jest setup so tests do not load native
613
+ modules. Mock the router entry you import as well:
614
+
615
+ ```ts
616
+ // jest.setup.ts
617
+ import type { ReactNode } from 'react';
618
+
619
+ jest.mock('@userflow-tech/react-native', () => ({
620
+ Userflow: {
621
+ init: jest.fn(() => Promise.resolve()),
622
+ identify: jest.fn(() => Promise.resolve()),
623
+ identifyAnonymous: jest.fn(() => Promise.resolve()),
624
+ reset: jest.fn(() => Promise.resolve()),
625
+ track: jest.fn(() => Promise.resolve()),
626
+ updateScreen: jest.fn(),
627
+ },
628
+ UserflowProvider: ({ children }: { children: ReactNode }) => children,
629
+ }));
630
+
631
+ // Expo Router
632
+ jest.mock('@userflow-tech/react-native/expo-router', () => ({
633
+ UserflowExpoRouterScreenTracker: () => null,
634
+ }));
635
+
636
+ // React Navigation
637
+ jest.mock('@userflow-tech/react-native/navigation', () => ({
638
+ WithUserflowReactNavigation: ({ children }: { children: ReactNode }) => children,
639
+ }));
640
+ ```
641
+
642
+ In a JavaScript setup file, drop the `ReactNode` import and the type
643
+ annotations.
644
+
645
+ A custom Metro config that does not extend `getDefaultConfig` from `expo/metro-config`
646
+ or `@react-native/metro-config` leaves `allowOptionalDependencies` off. Optional
647
+ peers that this package loads inside `try/catch` then fail the bundle with
648
+ `Unable to resolve module`. Extend the default config so that flag stays on.
649
+
650
+ ### Babel plugin and snapshot tests
651
+
652
+ If you add the optional `@userflow-tech/react-native/babel-plugin` to
653
+ `babel.config.js`, Jest runs it too, because `babel-jest` reads the same config.
654
+ The plugin adds props to `View`, `Text`, `Pressable`, the Touchables,
655
+ `TextInput`, and `Image`, and React Native's Jest mocks render them, so they
656
+ appear in snapshots:
657
+
658
+ ```diff
659
+ <Text
660
+ + __ufid="src/SettingsScreen.tsx::SettingsScreen > View#0 > Text{text=Settings}"
661
+ + collapsable={false}
662
+ + nativeID="uf_1mcelg9"
663
+ >
664
+ Settings
665
+ </Text>
666
+ ```
667
+
668
+ - `nativeID="uf_…"` is added to elements without a `testID`, `nativeID`, or
669
+ `userflowId`.
670
+ - `collapsable={false}` is added to `View`, `Text`, `Pressable`, and the
671
+ Touchables, including elements that have a `testID`.
672
+ - `__ufid` (the element's path in the file) is added in every non-production
673
+ build, which includes Jest.
674
+ - `__ufManual="true"` is added to elements that have a `testID`, `nativeID`, or
675
+ `userflowId`.
676
+
677
+ Updating the snapshots once does not settle it. The ids come from the file path,
678
+ the component name, each element's position among siblings of the same type, and
679
+ static labels. Changing a label, adding an element above others of the same type,
680
+ or moving or renaming a file changes them again. The plugin also imports this
681
+ package into every stamped file, so any test that renders a stamped component
682
+ loads the SDK. Keep the mock above in the Jest setup file.
683
+
684
+ Skip the plugin under Jest:
685
+
686
+ ```js
687
+ // babel.config.js
688
+ module.exports = function (api) {
689
+ const isTest = api.env('test');
690
+ return {
691
+ presets: ['babel-preset-expo'],
692
+ plugins: isTest ? [] : ['@userflow-tech/react-native/babel-plugin'],
693
+ };
694
+ };
695
+ ```
696
+
697
+ Remove `api.cache(true)` if your config has it. Called before `api.env`, it makes
698
+ Babel throw `Caching has already been configured with .never or .forever()`.
699
+ `api.env` already caches the config per environment. Development and release
700
+ builds still run the plugin.
701
+
702
+ To keep some stamping under test, pass `{ stampOnly: isTest }` to the plugin
703
+ instead. Snapshots then gain only `nativeID` and `collapsable`, and stamped files
704
+ skip the SDK import. The plugin's `exclude` option does not help here: it skips
705
+ the files it lists, not the components a test renders.
414
706
 
415
707
  ## What is not available yet
416
708
 
417
- - Banners and embed (inline) surfaces
418
- - `Userflow.track()` / custom event tracking
419
709
  - `INPUT_VALUE` / `FILLED_IN_INPUT` client conditions
420
710
 
421
711
  Flows gated on unsupported client conditions stay unknown and **never start**.