@userflow-tech/react-native 0.4.0 → 0.4.1

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.
package/README.md CHANGED
@@ -1,8 +1,8 @@
1
1
  # `@userflow-tech/react-native`
2
2
 
3
3
  Official Userflow SDK for React Native and Expo. Deliver guided flows, tooltips,
4
- and modals authored in the Userflow dashboard — without shipping a new app
5
- release for every content change.
4
+ modals, and banners authored in the Userflow dashboard — without shipping a new
5
+ app release for every content change.
6
6
 
7
7
  > Early development. APIs may change.
8
8
 
@@ -10,10 +10,9 @@ release for every content change.
10
10
 
11
11
  Declared minimums: React `>=18.2.0`, React Native `>=0.76.0`. React Native
12
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.
13
+ Builder element selection needs, requires React Native 0.76 or later. The SDK
14
+ is tested on React Native 0.81 / React 19.1 and on Expo SDK 57 / React Native
15
+ 0.86. Expo Go is not supported.
17
16
 
18
17
  `@react-native-async-storage/async-storage` is the only native module this
19
18
  package depends on, so npm installs it. `react-native-webview` and
@@ -44,13 +43,9 @@ the ranges below.
44
43
  Optional peers are declared `optional: true` in `peerDependenciesMeta`, so
45
44
  package managers do not warn if you skip them.
46
45
 
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
46
+ A clean npm install prints no warning about optional peers, so install
47
+ `react-native-safe-area-context` and NetInfo yourself when you want the behavior
48
+ in the table. An app that already depends
54
49
  on an older major (view-shot 4.0.3, NetInfo 9.x, safe-area 3.x, WebView 13.6.x
55
50
  or 14.x) fails install with `ERESOLVE`.
56
51
 
@@ -65,19 +60,8 @@ Use an **Expo development build** (or bare React Native). In development,
65
60
 
66
61
  ## Install
67
62
 
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:
71
-
72
- ```yaml
73
- npmScopes:
74
- userflow-tech:
75
- npmRegistryServer: 'https://registry.npmjs.org'
76
- npmAuthToken: '${NPM_TOKEN}'
77
- ```
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).
63
+ The package is public on npm. No token, `.npmrc`, or `.yarnrc.yml` setup is
64
+ needed with any package manager.
81
65
 
82
66
  ### Packages
83
67
 
@@ -130,10 +114,6 @@ npx expo install react-native-safe-area-context @react-native-community/netinfo
130
114
  # and either expo-router or @react-navigation/native, depending on your router
131
115
  ```
132
116
 
133
- For local tarball installs from this monorepo, pack with `pnpm pack:all` at the
134
- repo root and install the single `@userflow-tech/react-native` tarball. Steps:
135
- `docs/local-testing.md` in the monorepo.
136
-
137
117
  ### Install with an AI agent
138
118
 
139
119
  Cursor, Claude Code, and other agents that read Agent Skills can do this
@@ -159,7 +139,7 @@ Claude Code loads it from the plugin:
159
139
  import { Userflow, UserflowProvider } from '@userflow-tech/react-native';
160
140
 
161
141
  await Userflow.init({
162
- clientToken: 'ct_...', // from the Userflow dashboard → Environments. Never an esk_ secret key.
142
+ clientToken: 'ct_...', // from Settings → Environments in the Userflow dashboard. Never an esk_ secret key.
163
143
  // serverEndpoint: 'https://e.userflow.com', // default (US). EU: 'https://e.eu.userflow.com'
164
144
  // pairingEndpoint: 'https://e.userflow.com', // default; pairing stays on the US host
165
145
  });
@@ -178,6 +158,17 @@ NetInfo subscriptions). Call `Userflow.reset()` on logout to clear identity and
178
158
  disconnect. `reset()` does not apply a new `clientToken` or endpoint; restart
179
159
  the app to change those.
180
160
 
161
+ ### `init` options
162
+
163
+ | Option | Default | Description |
164
+ | ----------------- | ------------------------ | ------------------------------------------------------------------------------------------------------------ |
165
+ | `clientToken` | — | **Required.** The `ct_…` client token from Settings → Environments. |
166
+ | `serverEndpoint` | `https://e.userflow.com` | Userflow host. EU environments use `https://e.eu.userflow.com`. |
167
+ | `pairingEndpoint` | `https://e.userflow.com` | Builder pairing host. Stays on the US host for EU environments too. |
168
+ | `onNavigate` | — | Handler for flow NAVIGATE actions. Wins over React Navigation, Expo Router, and Linking. See NAVIGATE below. |
169
+ | `appVersion` | Read from the native app | Override the host app version sent as `app_version`. |
170
+ | `appBuild` | Read from the native app | Build number sent as `app_build`, paired with `appVersion`. |
171
+
181
172
  ### Where to put `init`, `identify`, and `UserflowProvider`
182
173
 
183
174
  Call `Userflow.init` once, as early as you can, from the root component
@@ -247,14 +238,14 @@ to the device's initial insets:
247
238
 
248
239
  ### Call order
249
240
 
250
- | Call | Before `init()` resolves | Before `identify()` |
251
- | -------------------------------- | ------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
252
- | `identify` / `identifyAnonymous` | **Throws** `Call Userflow.init() before identify()` | — |
253
- | `group` | **Throws** `Call Userflow.init() before group()` | **Throws** `Call Userflow.identify() before group()` |
254
- | `updateUser` | **Throws** `Call Userflow.init() before updateUser()` | **Throws** `Call Userflow.identify() before updateUser()` |
255
- | `updateGroup` | **Throws** `Call Userflow.init() before updateGroup()` | **Throws** `Call Userflow.identify() before updateGroup()`; also requires a prior `group()` |
256
- | `track` | **Throws** `Call Userflow.init() before track()` | **Throws** `Call Userflow.identify() before track()`. Network, rate-limit, and unexpected SDK errors are isolated (the promise resolves; see [isolation.md](docs/isolation.md)) |
257
- | `updateScreen` | Silently buffers; included on the next channel join | Silently buffers |
241
+ | Call | Before `init()` resolves | Before `identify()` |
242
+ | -------------------------------- | ------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
243
+ | `identify` / `identifyAnonymous` | **Throws** `Call Userflow.init() before identify()` | — |
244
+ | `group` | **Throws** `Call Userflow.init() before group()` | **Throws** `Call Userflow.identify() before group()` |
245
+ | `updateUser` | **Throws** `Call Userflow.init() before updateUser()` | **Throws** `Call Userflow.identify() before updateUser()` |
246
+ | `updateGroup` | **Throws** `Call Userflow.init() before updateGroup()` | **Throws** `Call Userflow.identify() before updateGroup()`; also requires a prior `group()` |
247
+ | `track` | **Throws** `Call Userflow.init() before track()` | **Throws** `Call Userflow.identify() before track()`. Network, rate-limit, and unexpected SDK errors are isolated (the promise resolves; see [isolation.md](https://unpkg.com/@userflow-tech/react-native/docs/isolation.md)) |
248
+ | `updateScreen` | Silently buffers; included on the next channel join | Silently buffers |
258
249
 
259
250
  Other rules:
260
251
 
@@ -271,7 +262,7 @@ Other rules:
271
262
  Same-id re-identify merges attributes.
272
263
  - Identity verification `signature` is optional in the type, but **required by
273
264
  the server** when the environment has identity verification enabled. See
274
- [docs/identity-verification.md](docs/identity-verification.md).
265
+ [docs/identity-verification.md](https://unpkg.com/@userflow-tech/react-native/docs/identity-verification.md).
275
266
 
276
267
  ## Localization
277
268
 
@@ -304,7 +295,7 @@ unmount or crash the host tree. `init` / `identify` still throw — a swallowed
304
295
  identify would look like success. Native crashes and errors in **your** children
305
296
  of `UserflowProvider` are out of scope.
306
297
 
307
- Details: [docs/isolation.md](docs/isolation.md).
298
+ Details: [docs/isolation.md](https://unpkg.com/@userflow-tech/react-native/docs/isolation.md).
308
299
 
309
300
  ## Screen tracking
310
301
 
@@ -397,13 +388,13 @@ function InviteScreen() {
397
388
 
398
389
  Targets inside a `ScrollView` / `FlatList` are scrolled into view automatically.
399
390
  `useUserflowScrollContainer` is only needed as an override for scroll libraries
400
- the fiber walk cannot drive — see [docs/anchoring.md](docs/anchoring.md).
391
+ the fiber walk cannot drive — see [docs/anchoring.md](https://unpkg.com/@userflow-tech/react-native/docs/anchoring.md).
401
392
 
402
393
  In the Userflow builder, set the MANUAL step selector CSS to `#invite-teammates`
403
394
  (or `invite-teammates`). Use `useUserflowAnchor(id)` only when you need an
404
395
  explicit override (e.g. a third-party component that does not accept `testID`).
405
396
  Full contract, prop priority, missing-target behavior, and duplicate-id rules:
406
- [docs/anchoring.md](docs/anchoring.md).
397
+ [docs/anchoring.md](https://unpkg.com/@userflow-tech/react-native/docs/anchoring.md).
407
398
 
408
399
  ## Builder preview & pairing
409
400
 
@@ -448,8 +439,8 @@ Deep-link / pairing helpers:
448
439
  | `PreviewModeBanner` | Mounted by `UserflowProvider` while paired. The top bar shows at once for a preview link, stays hidden for an element-selection link and after any pick until the next preview, and waits briefly on a link with no purpose. |
449
440
 
450
441
  Full scheme registration, Linking ownership, and verify commands:
451
- [docs/deep-linking.md](docs/deep-linking.md). Symptom table:
452
- [docs/troubleshooting.md](docs/troubleshooting.md#builder-preview--pairing).
442
+ [docs/deep-linking.md](https://unpkg.com/@userflow-tech/react-native/docs/deep-linking.md). Symptom table: the "Builder preview / pairing" section of
443
+ [docs/troubleshooting.md](https://unpkg.com/@userflow-tech/react-native/docs/troubleshooting.md).
453
444
 
454
445
  ## Connection resilience
455
446
 
@@ -480,33 +471,35 @@ Screen tracking ≠ action routing. React Navigation apps need
480
471
 
481
472
  ### `Userflow`
482
473
 
483
- | Member | Description |
484
- | --------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
485
- | `init(config)` | Validate credentials, store config, start lifecycle + network controllers. |
486
- | `identify(userId, attributes?, options?)` | Identify a user; auto-sets `sdk` / `device_os` / `device_type` and `app_version` / `app_build` from init; `options.signature` for identity verification. |
487
- | `identifyAnonymous(attributes?, options?)` | Stable anonymous id via AsyncStorage; auto-sets `sdk` / `device_os` / `device_type` and `app_version` / `app_build`. |
488
- | `group(groupId, attributes?, options?)` | Associate the current user with a group. |
489
- | `updateUser(attributes?)` | Upsert attributes for the current user without re-identifying. |
490
- | `updateGroup(attributes?, options?)` | Upsert group / membership attributes without re-grouping. |
491
- | `track(name, attributes?, options?)` | Record a custom event (literal attribute values only); `userOnly` skips group. Misuse (`UserflowAuthError`) rejects; network / rate-limit / SDK bugs are isolated. |
492
- | `reset()` | Clear identity, client conditions, flow session; disconnect. |
493
- | `teardown()` | Stop AppState / NetInfo / screen-tracking subscriptions. |
494
- | `updateScreen(name)` | Report the active screen (prefer hooks when possible). |
495
- | `setOnNavigate(handler)` | Override or clear the host NAVIGATE handler. |
496
- | `reportActivity()` | Reset the idle timer; reconnect an idle-suspended socket. |
497
- | `didHandleURL(url)` | Handle a Userflow preview deep link (`…://?token=…`). |
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. |
500
- | `inspectPairingDeepLink(url)` | Classify a URL as pairing without consuming it; reports rejection reason. |
501
- | `isPairingAttempt(reason)` | True when a rejection was a recognisable pairing attempt worth reporting. |
502
- | `parsePairingDeepLink(url)` / `isUserflowPairingUrl(url)` | Parse / detect a pairing deep link. |
503
- | `getUserId()` | Current user id, or `null` before identify. |
504
- | `getAttributes()` | Copy of attributes for the current user. |
505
- | `getFlowSessionEngine()` | Mid-flow session engine (created lazily; attach runs after identify). |
506
- | `getHostAppVersion()` | Host app native version cached at init, or `null` before init / when unavailable. |
507
- | `getDeviceContext()` | Platform metadata cached during `init` (`sdk`, `deviceOs`, `deviceType`), or `null` before init. |
508
- | `logHostAppVersion()` | Log the host app's native version (not `SDK_VERSION`) and platform OS. Uses bundled device-info on bare RN. |
509
- | `logDeviceContext()` | Log cached user-attribute values (`sdk` / `device_os` / `device_type`). Requires `init`. |
474
+ | Member | Description |
475
+ | ------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
476
+ | `init(config)` | Validate credentials, store config, start lifecycle + network controllers. |
477
+ | `identify(userId, attributes?, options?)` | Identify a user; auto-sets `sdk` / `device_os` / `device_type` and `app_version` / `app_build` from init; `options.signature` for identity verification. |
478
+ | `identifyAnonymous(attributes?, options?)` | Stable anonymous id via AsyncStorage; auto-sets `sdk` / `device_os` / `device_type` and `app_version` / `app_build`. |
479
+ | `group(groupId, attributes?, options?)` | Associate the current user with a group. |
480
+ | `updateUser(attributes?)` | Upsert attributes for the current user without re-identifying. |
481
+ | `updateGroup(attributes?, options?)` | Upsert group / membership attributes without re-grouping. |
482
+ | `track(name, attributes?, options?)` | Record a custom event (literal attribute values only); `userOnly` skips group. Misuse (`UserflowAuthError`) rejects; network / rate-limit / SDK bugs are isolated. |
483
+ | `reset()` | Clear identity, the stored anonymous id, client conditions, and flow and banner sessions; disconnect. |
484
+ | `teardown()` | Stop AppState / NetInfo / screen-tracking subscriptions. |
485
+ | `updateScreen(name)` | Report the active screen (prefer hooks when possible). |
486
+ | `setOnNavigate(handler)` | Override or clear the host NAVIGATE handler. |
487
+ | `reportActivity()` | Reset the idle timer; reconnect an idle-suspended socket. |
488
+ | `didHandleURL(url)` | Handle a Userflow preview deep link (`…://?token=…`). |
489
+ | `handlePairingUrl(url)` | Handle a Builder pairing deep link (`…://?pairing_token=…`). Works in every build; the signed JWT gates the session. |
490
+ | `exitPreview()` | Leave Builder preview mode; disconnect the pairing websocket. Works in every build. |
491
+ | `setScrollPadding(padding)` | Sticky header/footer insets used when scrolling tooltip targets into view. |
492
+ | `setCustomScrollIntoView(fn)` | Replace scroll-into-view (e.g. unsupported scroll libraries). Pass `null` to restore the default. |
493
+ | `setSurfaceInteractionBlocking(enabled)` | `true` (default) blocks taps on the app under tooltip backdrops. `false` lets them through. Modals always block. |
494
+ | `setEvalJsContext(values)` | Values exposed to Evaluate JavaScript as `window.userflowContext.host`. Replaces the previous values. |
495
+ | `getUserId()` | Current user id, or `null` before identify. |
496
+ | `getAttributes()` | Copy of attributes for the current user. |
497
+ | `getFlowSessionEngine()` | Mid-flow session engine (created lazily; attach runs after identify). |
498
+ | `getBannerSessionEngine()` | Banner session engine (created lazily; attach runs after identify). |
499
+ | `getHostAppVersion()` | Host app native version cached at init, or `null` before init / when unavailable. |
500
+ | `getDeviceContext()` | Platform metadata cached during `init` (`sdk`, `deviceOs`, `deviceType`), or `null` before init. |
501
+ | `logHostAppVersion()` | Log the host app's native version (not `SDK_VERSION`) and platform OS. Uses bundled device-info on bare RN. |
502
+ | `logDeviceContext()` | Log cached user-attribute values (`sdk` / `device_os` / `device_type`). Requires `init`. |
510
503
 
511
504
  ### Hooks and components
512
505
 
@@ -519,8 +512,19 @@ Screen tracking ≠ action routing. React Navigation apps need
519
512
  | `useUserflowScreen(name)` | Report the active screen from a React tree. |
520
513
  | `useUserflowAnchor(id)` | Optional override: register a view as a MANUAL target (beats `userflowId` / `testID`). |
521
514
  | `useUserflowScrollContainer(onScroll?)` | Optional override: register a `ScrollView` for tooltip scroll-into-view. |
522
- | `Userflow.setScrollPadding(padding)` | Sticky chrome insets when scrolling targets into view. |
523
- | `Userflow.setCustomScrollIntoView(fn)` | Replace scroll-into-view (e.g. unsupported scroll libraries). |
515
+ | `EmbedBannerPlaceholder` | Slot for a banner placed on an element (`<EmbedBannerPlaceholder id="welcome-banner" />` matches the selector `#welcome-banner`). Renders nothing until a matching banner is active. Top and bottom banners ignore it. |
516
+
517
+ ### Pairing helpers
518
+
519
+ Named exports, not members of `Userflow`. Use them when your app owns `Linking`
520
+ and forwards URLs itself.
521
+
522
+ | Export | Description |
523
+ | ----------------------------- | ------------------------------------------------------------------------- |
524
+ | `inspectPairingDeepLink(url)` | Classify a URL as pairing without consuming it; reports rejection reason. |
525
+ | `isPairingAttempt(reason)` | True when a rejection was a recognisable pairing attempt worth reporting. |
526
+ | `parsePairingDeepLink(url)` | Parse a pairing deep link. |
527
+ | `isUserflowPairingUrl(url)` | Detect a pairing deep link. |
524
528
 
525
529
  ### Subpath exports
526
530
 
@@ -540,20 +544,22 @@ Screen tracking ≠ action routing. React Navigation apps need
540
544
 
541
545
  ## Guides in this package
542
546
 
543
- | Doc | Topic |
544
- | -------------------------------------------------------------- | ----------------------------------------------------------- |
545
- | [docs/anchoring.md](docs/anchoring.md) | Manual targets, scroll containers, missing-target behavior |
546
- | [docs/isolation.md](docs/isolation.md) | What “isolated as possible” means; overlay + `track` guards |
547
- | [docs/deep-linking.md](docs/deep-linking.md) | Builder preview URLs and pairing sessions |
548
- | [docs/identity-verification.md](docs/identity-verification.md) | HMAC signature on your backend |
549
- | [docs/troubleshooting.md](docs/troubleshooting.md) | Symptom → cause for common integration failures |
550
- | [docs/theme-mapping.md](docs/theme-mapping.md) | Web → RN theme token mapping (contributor-facing) |
547
+ These guides also ship in the package, under
548
+ `node_modules/@userflow-tech/react-native/docs/`.
549
+
550
+ | Doc | Topic |
551
+ | ------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------- |
552
+ | [docs/anchoring.md](https://unpkg.com/@userflow-tech/react-native/docs/anchoring.md) | Manual targets, scroll containers, missing-target behavior |
553
+ | [docs/isolation.md](https://unpkg.com/@userflow-tech/react-native/docs/isolation.md) | What “isolated as possible” means; overlay + `track` guards |
554
+ | [docs/deep-linking.md](https://unpkg.com/@userflow-tech/react-native/docs/deep-linking.md) | Builder preview URLs and pairing sessions |
555
+ | [docs/identity-verification.md](https://unpkg.com/@userflow-tech/react-native/docs/identity-verification.md) | HMAC signature on your backend |
556
+ | [docs/troubleshooting.md](https://unpkg.com/@userflow-tech/react-native/docs/troubleshooting.md) | Symptom → cause for common integration failures |
551
557
 
552
558
  ## Experimental
553
559
 
554
560
  - AUTO / codeless element targeting (exact-id fast path + scored matcher;
555
561
  scored path needs Babel full mode to populate the auto-anchor registry).
556
- See [docs/anchoring.md](docs/anchoring.md).
562
+ See [docs/anchoring.md](https://unpkg.com/@userflow-tech/react-native/docs/anchoring.md).
557
563
  - Build-time stamping with `@userflow-tech/react-native/babel-plugin`. Jest
558
564
  runs the plugin too, which changes snapshots. Read
559
565
  [Babel plugin and snapshot tests](#babel-plugin-and-snapshot-tests) before
@@ -709,4 +715,4 @@ the files it lists, not the components a test renders.
709
715
  - `INPUT_VALUE` / `FILLED_IN_INPUT` client conditions
710
716
 
711
717
  Flows gated on unsupported client conditions stay unknown and **never start**.
712
- See [docs/troubleshooting.md](docs/troubleshooting.md).
718
+ See [docs/troubleshooting.md](https://unpkg.com/@userflow-tech/react-native/docs/troubleshooting.md).
package/docs/anchoring.md CHANGED
@@ -16,7 +16,7 @@ Dashboard MANUAL selectors store a CSS-like string on `step.selector.css` (or
16
16
  `condition.selector.css`). The SDK resolves it with:
17
17
 
18
18
  ```typescript
19
- // packages/react-native/src/anchors/resolveManualAnchorId.ts
19
+ // src/anchors/resolveManualAnchorId.ts
20
20
  return trimmed.startsWith('#') ? trimmed.slice(1) : trimmed;
21
21
  ```
22
22
 
@@ -318,7 +318,7 @@ Modals always block — the full-screen backdrop is a `Pressable` hit target.
318
318
  Pass `Userflow.setSurfaceInteractionBlocking(false)` to restore the legacy
319
319
  pass-through tooltip backdrop (`pointerEvents="none"` on dim bands) so the
320
320
  host screen stays scrollable and tappable underneath. The `tooltipTargetBlocked`
321
- hole seal is unchanged — it was independent of band pass-through before UMD-74.
321
+ hole seal is unchanged.
322
322
 
323
323
  When a tooltip step has `backdrop: false`, the cutout still mounts with dim
324
324
  opacity `0` so interaction blocking applies without a visible dim.
@@ -341,7 +341,7 @@ rect** via a pass-through bridge around app content (`TargetPressBridge` in
341
341
  ELEMENT `CLICK` / `NOT_CLICK` auto-start conditions use the same bridge
342
342
  (`elementTapMonitor`) and do not steal the press either. `MOUSEDOWN` and
343
343
  `DISABLED` conditions are still unsupported — see
344
- [client-conditions.md](client-conditions.md) / [troubleshooting.md](troubleshooting.md).
344
+ [troubleshooting.md](troubleshooting.md).
345
345
 
346
346
  ## Missing-target behavior
347
347
 
@@ -405,4 +405,3 @@ The Userflow overlay is a _sibling_ of the scan root, so SDK-owned `testID`s
405
405
 
406
406
  - [troubleshooting.md](troubleshooting.md) — “flow never appeared” checklist
407
407
  - [theme-mapping.md](theme-mapping.md) — chrome tokens that affect tooltip UI
408
- - Repo `docs/client-conditions.md` (monorepo only) — track / toggle protocol internals
@@ -7,9 +7,6 @@ Messages are quoted exactly so you can search logs for them.
7
7
 
8
8
  | Symptom / log message | Likely cause | Fix |
9
9
  | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
10
- | `404` from `npm install` or `npm view @userflow-tech/react-native` | The npm token is missing, or the npm user or org is not on the read-only `@userflow-tech` team. npm returns 404 for a private package the caller cannot see. | Ask Userflow to add the npm user or org to the read-only team, then create a granular read-only token limited to `@userflow-tech`. |
11
- | Yarn 2+ still 404s with a valid `.npmrc` | Yarn 2 and later ignore `.npmrc`. | Set `npmScopes.userflow-tech.npmRegistryServer` and `npmAuthToken` in `.yarnrc.yml`. |
12
- | EAS or CI install 404s while local install works | `NPM_TOKEN` is not available to that environment. | Add `NPM_TOKEN` as an EAS secret or CI secret. The `.npmrc` line `//registry.npmjs.org/:_authToken=${NPM_TOKEN}` reads it at install time. |
13
10
  | `expo doctor` reports `react-native-view-shot` | The app pins `^5.0.0` and Expo SDK 54's expected version is 4.0.3. | Keep the pin. Add `"react-native-view-shot"` to `expo.install.exclude`. |
14
11
  | `Unable to resolve module` for an optional peer (`netinfo`, `expo-secure-store`, `expo-router`, safe-area, navigation) | A custom Metro config does not extend `getDefaultConfig`, so `allowOptionalDependencies` is off. | Extend `getDefaultConfig` from `expo/metro-config` or `@react-native/metro-config`. |
15
12
  | `ERESOLVE` while installing `@userflow-tech/react-native` | The app already has a version outside a peer range: view-shot 4.0.3 (what unpinned `npx expo install` installs on Expo SDK 54), NetInfo 9.x, safe-area-context 3.x, WebView 13.6.x or 14.x, or React Native older than 0.76. | Move that package into the range in the package README. For view-shot, npm prints `Found: react-native-view-shot@4.0.3` and `peer react-native-view-shot@"^5.0.0" from @userflow-tech/react-native`. Install the SDK and `react-native-view-shot@^5.0.0` in one command, then add `"react-native-view-shot"` to `expo.install.exclude` on Expo SDK 54. Do not retry with `--force` or `--legacy-peer-deps`; that leaves 4.0.3 installed. Raise React Native to 0.76 or later when that is the conflict. |
@@ -135,7 +132,7 @@ Full setup: [deep-linking.md](deep-linking.md). Pairing is available in all buil
135
132
  | Symptom | Fix |
136
133
  | ----------------------------------------------------------------- | --------------------------------------------------------------------------------- |
137
134
  | Crashes or missing native modules in Expo Go | Use a **development build**; Expo Go is not supported |
138
- | Duplicate React / RN instance crashes when linking local packages | Force singletons in Metro — monorepo `docs/metro.md` |
135
+ | Duplicate React / RN instance crashes when linking local packages | Make Metro resolve `react` and `react-native` from the app's own `node_modules` |
139
136
  | Preview / pairing scheme not registered | Add the `@userflow-tech/react-native` plugin with `schemeId`, then rebuild native |
140
137
 
141
138
  ## Jest
@@ -5,5 +5,5 @@ Object.defineProperty(exports, "__esModule", {
5
5
  });
6
6
  exports.SDK_VERSION = void 0;
7
7
  /** Generated by scripts/write-version.mjs — do not edit. */
8
- const SDK_VERSION = exports.SDK_VERSION = '0.4.0';
8
+ const SDK_VERSION = exports.SDK_VERSION = '0.4.1';
9
9
  //# sourceMappingURL=version.js.map
@@ -1,5 +1,5 @@
1
1
  "use strict";
2
2
 
3
3
  /** Generated by scripts/write-version.mjs — do not edit. */
4
- export const SDK_VERSION = '0.4.0';
4
+ export const SDK_VERSION = '0.4.1';
5
5
  //# sourceMappingURL=version.js.map
@@ -1,3 +1,3 @@
1
1
  /** Generated by scripts/write-version.mjs — do not edit. */
2
- export declare const SDK_VERSION = "0.4.0";
2
+ export declare const SDK_VERSION = "0.4.1";
3
3
  //# sourceMappingURL=version.d.ts.map
@@ -1,3 +1,3 @@
1
1
  /** Generated by scripts/write-version.mjs — do not edit. */
2
- export declare const SDK_VERSION = "0.4.0";
2
+ export declare const SDK_VERSION = "0.4.1";
3
3
  //# sourceMappingURL=version.d.ts.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@userflow-tech/react-native",
3
- "version": "0.4.0",
3
+ "version": "0.4.1",
4
4
  "description": "Userflow SDK for React Native — onboarding flows, tooltips, and modals.",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "main": "./lib/commonjs/index.js",
package/src/version.ts CHANGED
@@ -1,2 +1,2 @@
1
1
  /** Generated by scripts/write-version.mjs — do not edit. */
2
- export const SDK_VERSION = '0.4.0';
2
+ export const SDK_VERSION = '0.4.1';
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@userflow-tech/core",
3
- "version": "0.4.0",
3
+ "version": "0.4.1",
4
4
  "description": "Userflow React Native SDK core — platform-agnostic engine and protocol client.",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "type": "module",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@userflow-tech/protocol",
3
- "version": "0.4.0",
3
+ "version": "0.4.1",
4
4
  "description": "Userflow React Native SDK protocol — wire contracts and shared types.",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "type": "module",
@@ -1,130 +0,0 @@
1
- # Theme mapping (web → React Native)
2
-
3
- Phase 3 maps Userflow web theme tokens onto React Native styles used by flow chrome.
4
- This is an interim visual baseline aligned with the mobile tooltip/modal design specs.
5
-
6
- ## Card chrome — `resolveBubbleChrome`
7
-
8
- Source: `src/surfaces/themeChrome.ts`
9
-
10
- | Web / theme token | RN usage |
11
- | ----------------------------------------- | ---------------------------------------------------------------- |
12
- | `backgroundColor` | Card `backgroundColor` |
13
- | `bubbleBorderRadius` (default `10`) | Card `borderRadius` |
14
- | `bubbleBorderWidth` / `bubbleBorderColor` | Card border |
15
- | `tooltipNotchSize` (default `10`) | Tooltip notch size; `0` hides notch |
16
- | Modal content padding | `24` (mobile; web default is 40) |
17
- | Tooltip / bubble content padding | `24` (web tooltip) |
18
- | Shadow | Approximate dual drop-shadow via `shadow*` + Android `elevation` |
19
-
20
- Modal cards use `overflow: 'hidden'` so the progress bar clips to the radius.
21
- Tooltip cards use `overflow: 'visible'` so the notch is not clipped.
22
-
23
- ## Progress — `ProgressBar`
24
-
25
- | Theme token | RN usage |
26
- | ---------------------------------------- | ------------------------------------------------- |
27
- | `progressBarEnabled` | Show / hide |
28
- | `progressBarPosition` (`TOP` / `BOTTOM`) | Placement flush on the card edge |
29
- | `progressBarType` | `DEFAULT` fraction bar; `NUMBERED` shows `1 of N` |
30
- | Step `progress` + step index | Fraction via `resolveStepProgress` |
31
-
32
- ## Buttons — `StepButton`
33
-
34
- | Theme token | RN usage |
35
- | ------------------------------- | ---------------------------------------------------------------- |
36
- | `primaryButton*` / widget brand | Primary filled button |
37
- | Secondary (DEFAULT) | Neutral outline: white fill, `#d1d5db` border, `fontColor` label |
38
- | Button fragment colors when set | Override defaults per button |
39
-
40
- ## Backdrop & cutout
41
-
42
- | Theme token | RN usage |
43
- | ------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------- |
44
- | `backdropColor` | Modal full-screen backdrop; tooltip cutout fill |
45
- | `backdropOpacity` | Parsed float (`parseBackdropOpacity`) |
46
- | `modalBackdropOnclick` | `DISMISS` → tap backdrop ends flow |
47
- | Step `backdrop` / `backdropPadding` / `tooltipTargetBlocked` | Tooltip cutout hole, dim, and interaction blocking (bands block by default; hole sealed when `tooltipTargetBlocked`) |
48
-
49
- ## Tooltip target highlight
50
-
51
- Source: `src/surfaces/resolveHighlightChrome.ts` + `src/surfaces/tooltip/TooltipHighlight.tsx`.
52
-
53
- Web paints the ring as one CSS `box-shadow` on `.userflowjs-tooltip-backdrop`.
54
- RN uses a **2px brand stroke + one translucent halo**, plus iOS `shadow*` /
55
- Android `boxShadow` for blur. Theme spread is a placement gap only — it is
56
- not painted as a thick solid bar.
57
-
58
- | Web / theme token | RN usage |
59
- | -------------------------- | -------------------------------------------------------------------------------- |
60
- | `backdropHighlightColor` | Stroke color. Near-white (web default `#ffffff`) is treated as unset — see below |
61
- | `backdropHighlightOpacity` | Caps the halo at `0.32` so it stays a wash |
62
- | `backdropHighlightRadius` | Halo thickness (default `4`) |
63
- | `backdropHighlightSpread` | Added to placement pad when type is `OUTSIDE` (default `0`). Not stroke width |
64
- | `backdropHighlightType` | `OUTSIDE` (default) draws outside the hole; `INSIDE` insets |
65
-
66
- Placement pad for `OUTSIDE` is `spread + strokeWidth + radius` so the tooltip
67
- sits clear of the painted ring, not only the theme spread token.
68
- | Corner radius | `8` + `backdropPadding` (web copies the target's computed radius; RN measure cannot) |
69
-
70
- Color fallback: non-white `backdropHighlightColor` → `widgetBackgroundColor` →
71
- `brandBackgroundColor` → `defaultButtonBackgroundColor` → `#1a57e6`. Web's
72
- `#ffffff` default is skipped so the dimmed cutout does not get a white frame.
73
-
74
- ## Typography
75
-
76
- | Theme token | RN usage |
77
- | ------------ | ----------------------------------- |
78
- | `fontColor` | Body / heading / close affordance |
79
- | `fontSize` | Base body size |
80
- | `fontFamily` | Applied where Text styles accept it |
81
-
82
- ## Quote / code-block — TreeDoc chrome
83
-
84
- Source of truth: web `userflowjs/styles/bubble-frame.styl` in the main Userflow
85
- web repo (blockquote / `code` / `pre > code`) and TreeDoc markup
86
- (`blockquote`, `pre > code`). Spacing tokens from web `$padding-*` assume
87
- **1rem = 16px**.
88
-
89
- Implementation: `src/content/RichContentRenderer.tsx` + `src/content/themeColor.ts`.
90
-
91
- | Token | rem | RN px |
92
- | ------------- | ----- | ----- |
93
- | `$padding-2` | 0.125 | 2 |
94
- | `$padding-4` | 0.25 | 4 |
95
- | `$padding-8` | 0.5 | 8 |
96
- | `$padding-16` | 1 | 16 |
97
-
98
- Root content uses `gap: 8` (`ROOT_CONTENT_GAP`). Quote/code add
99
- `marginBottom: 8` when not last so gap + margin ≈ web’s 16px block spacing.
100
-
101
- ### Quote (`blockquote`)
102
-
103
- | Web rule | RN |
104
- | ----------------------------------------------- | -------------------------------------------------------------------------------- |
105
- | `border-inline-start: 2px solid rgba(fg, 0.15)` | `borderStartWidth: 2`, `borderStartColor` via `foregroundAlpha(fontColor, 0.15)` |
106
- | `padding: $padding-4 $padding-16` | `paddingVertical: 4`, `paddingHorizontal: 16` |
107
- | `margin-bottom: $padding-16` when not last | `marginBottom: 8` (+ root gap 8); last painted sibling: 0 |
108
- | `min-height: var(--userflow-line-height, 24px)` | `minHeight: 24` |
109
- | Not italic by default | No forced `fontStyle: 'italic'` |
110
-
111
- ### Code-block (`pre > code`) and inline `code` mark
112
-
113
- | Web rule | RN |
114
- | -------------------------------------------------------------------------- | ------------------------------------------------------------------ |
115
- | Mono stack (`SFMono-Regular, Consolas, Liberation Mono, Menlo, monospace`) | iOS `Menlo`, Android `monospace` |
116
- | `font-size: calc(font-size * 0.875)` | `fontSize * 0.875` |
117
- | `background: rgba(fg, 0.05)` | `foregroundAlpha(fontColor, 0.05)` (hex / rgb / rgba) |
118
- | `border-radius: calc(font-size * 0.25)` | `fontSize * 0.25` |
119
- | `pre > code` padding `$padding-8` | `padding: 8` + horizontal `ScrollView` (`nestedScrollEnabled`) |
120
- | Inline `code` padding `$padding-2 $padding-4` | View chrome wrapper + `paddingVertical: 2`, `paddingHorizontal: 4` |
121
-
122
- ## Safe area
123
-
124
- Surfaces pad with `useSurfaceLayoutInsets`: optional-peer
125
- `react-native-safe-area-context` (zero fallback) plus a 24px screen margin so
126
- cards clear Dynamic Island / home indicator regions. While Builder preview is
127
- active, the measured Preview banner height is added to the top inset so the
128
- close control is not trapped under the banner. Tall cards cap at that padded
129
- window height and scroll internally (`overflow: 'hidden'` on the fill; the
130
- shadow/notch shell stays `overflow: 'visible'`).