@getrheo/rheo-skill 2.4.0 → 2.5.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.
- package/README.md +1 -1
- package/package.json +3 -3
- package/rheo/SKILL.md +1 -1
- package/rheo/rheo-best-practices/SKILL.md +2 -1
- package/rheo/rheo-best-practices/references/integrations.md +40 -2
- package/rheo/rheo-best-practices/references/react-native-bare.md +8 -2
- package/rheo/rheo-best-practices/references/react-native-expo.md +1 -1
- package/rheo/rheo-best-practices/references/swiftui.md +2 -1
- package/rheo/rheo-best-practices/references/troubleshooting.md +1 -1
- package/rheo/rheo-flow-import/references/capabilities.md +6 -3
- package/rheo/rheo-flow-import/references/flow-spec.md +12 -2
- package/rheo/rheo-flow-import/references/manifest-agent-profile-fallback.md +1 -1
- package/rheo/rheo-flow-import/references/manifest-rules.md +7 -3
- package/rheo/rheo-flow-import/references/publish-gates.md +2 -1
- package/rheo/rheo-flow-import/references/react-native-source-patterns.md +4 -3
- package/rheo/rheo-flow-import/references/swiftui-source-patterns.md +4 -3
- package/rheo/rheo-flow-import/scripts/lib/rheo-cli.mjs +49 -8
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @getrheo/rheo-skill
|
|
2
2
|
|
|
3
|
-
Current release: **`2.
|
|
3
|
+
Current release: **`2.5.0`** on npm (`PLATFORM_SDK_VERSION` in `scripts/publish-package-registry.mjs`). Public source: [getrheo/rheo-skill](https://github.com/getrheo/rheo-skill) (mirrored from this private monorepo via `pnpm extract:oss-repos`).
|
|
4
4
|
|
|
5
5
|
Source and build tooling for the **`rheo`** agent skill — a single, self-contained
|
|
6
6
|
skill with two sub-skills:
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@getrheo/rheo-skill",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.5.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Rheo agent skill — SDK install guidance and flow manifest import tooling.",
|
|
6
6
|
"main": "./src/index.ts",
|
|
@@ -9,8 +9,8 @@
|
|
|
9
9
|
".": "./src/index.ts"
|
|
10
10
|
},
|
|
11
11
|
"dependencies": {
|
|
12
|
-
"@getrheo/contracts": "2.
|
|
13
|
-
"@getrheo/flow-runtime": "2.
|
|
12
|
+
"@getrheo/contracts": "2.5.0",
|
|
13
|
+
"@getrheo/flow-runtime": "2.5.0",
|
|
14
14
|
"zod": "^3.23.8"
|
|
15
15
|
},
|
|
16
16
|
"devDependencies": {
|
package/rheo/SKILL.md
CHANGED
|
@@ -3,7 +3,7 @@ name: rheo
|
|
|
3
3
|
description: Work with Rheo, the headless onboarding/paywall flow engine for mobile apps. Use when a user wants to install or wire the Rheo SDK (React Native, Expo, or SwiftUI), follow Rheo SDK best practices, configure integrations (RevenueCat, AppsFlyer), wire auth/permissions/terminal callbacks, OR import/migrate an existing mobile flow into a compliant Rheo FlowManifest and validate it. Routes to the `rheo-best-practices` and `rheo-flow-import` sub-skills.
|
|
4
4
|
compatibility: Requires Node.js 20+. rheo-flow-import scripts are fully self-contained (no install step). Internet access fetches the latest Manifest Agent Profile; a bundled fallback works offline.
|
|
5
5
|
metadata:
|
|
6
|
-
rheo-version: "2.
|
|
6
|
+
rheo-version: "2.5.0"
|
|
7
7
|
manifest-schema-version: "7"
|
|
8
8
|
---
|
|
9
9
|
|
|
@@ -30,7 +30,8 @@ Only install packages or edit host code when the user explicitly asks for implem
|
|
|
30
30
|
- **Pass the channel public id**, not a flow id, to `Flow` / `FlowView`.
|
|
31
31
|
- **Preserve the existing onboarding** as a fallback/rollback path (feature flag or route swap) unless the user explicitly asks to remove it.
|
|
32
32
|
- **Read local conventions first** (package manager, navigation, env handling) and keep edits localized to the integration entry point and app config.
|
|
33
|
-
- **RevenueCat and AppsFlyer are host integrations**, not SDK peers — the host configures and owns those SDKs and their secrets. Wire `fallback` for every
|
|
33
|
+
- **RevenueCat and AppsFlyer are host integrations**, not SDK peers — the host configures and owns those SDKs and their secrets. Wire `fallback` for every Integration / External Surface Node.
|
|
34
|
+
- **External Surface Nodes** need a host `externalSurfaces` registry keyed by Host key (`config.hostKey` or `surf_*` id; callbacks `onComplete` / `onBack` / `onDismiss`). No App settings toggle.
|
|
34
35
|
- **Do not add `request_app_review`** prompts unless the user explicitly asks; Apple discourages prompting from raw button taps.
|
|
35
36
|
- Run the **narrowest useful verification** (typecheck or a build of the touched module), not a full app build, unless asked.
|
|
36
37
|
|
|
@@ -1,12 +1,19 @@
|
|
|
1
1
|
# Integrations
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
In the flow builder, partner paywalls and host UI are **two add-menu nodes** that share the `externalSurfaceNodes` schema:
|
|
4
|
+
|
|
5
|
+
| Builder node | Manifest `config.provider` | App settings toggle |
|
|
6
|
+
| --- | --- | --- |
|
|
7
|
+
| **Integration Node** | `revenuecat` (and future partners) | Required |
|
|
8
|
+
| **External Surface Node** | `headless` | None (host registry) |
|
|
9
|
+
|
|
10
|
+
## RevenueCat (Integration Node)
|
|
4
11
|
|
|
5
12
|
Detect source calls such as `Purchases.configure`, `react-native-purchases`, `react-native-purchases-ui`, or existing paywall presentation code.
|
|
6
13
|
|
|
7
14
|
Manifest mapping:
|
|
8
15
|
|
|
9
|
-
- Create an external surface with `config.provider: "revenuecat"`.
|
|
16
|
+
- Create an **Integration Node** / external surface with `config.provider: "revenuecat"`.
|
|
10
17
|
- Preserve offering or placement identifiers when visible in source.
|
|
11
18
|
- Wire known outcomes:
|
|
12
19
|
- `purchase_completed`
|
|
@@ -17,6 +24,37 @@ Manifest mapping:
|
|
|
17
24
|
|
|
18
25
|
The host remains responsible for configuring RevenueCat. Rheo does not own purchase SDK secrets or receipt validation.
|
|
19
26
|
|
|
27
|
+
## External Surface Node (headless host UI)
|
|
28
|
+
|
|
29
|
+
Use when a source screen is owned by the host (custom native UI, third-party screen Rheo does not ship) rather than a Rheo layer tree or RevenueCat paywall.
|
|
30
|
+
|
|
31
|
+
Manifest mapping:
|
|
32
|
+
|
|
33
|
+
- Create an **External Surface Node** with `config.provider: "headless"`.
|
|
34
|
+
- Keep a stable `surf_*` id for graph routing / analytics. Optionally set `config.hostKey` for the host `externalSurfaces` registry (defaults to the node id).
|
|
35
|
+
- Wire outcomes:
|
|
36
|
+
- `completed` ← host `onComplete`
|
|
37
|
+
- `back` ← host `onBack`
|
|
38
|
+
- `dismissed` ← host `onDismiss`
|
|
39
|
+
- `failed` ← missing host component (SDK)
|
|
40
|
+
- Always wire `fallback`.
|
|
41
|
+
|
|
42
|
+
Host wiring (required for success):
|
|
43
|
+
|
|
44
|
+
```ts
|
|
45
|
+
// React Native — key matches Host key (hostKey or surf_* id)
|
|
46
|
+
<Flow
|
|
47
|
+
channelId="…"
|
|
48
|
+
externalSurfaces={{
|
|
49
|
+
onboardingQuiz: ({ onComplete, onBack, onDismiss }) => /* host UI */,
|
|
50
|
+
}}
|
|
51
|
+
/>
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
SwiftUI / Flutter: pass the same map on `FlowView` as `externalSurfaces` (builders receive `onComplete` / `onBack` / `onDismiss`). Docs: product Developer Guide → Headless external surfaces.
|
|
55
|
+
|
|
56
|
+
Do **not** use an External Surface Node to wrap RevenueCat when you need `iap_purchase` commerce events — use an Integration Node with `provider: "revenuecat"`.
|
|
57
|
+
|
|
20
58
|
## AppsFlyer
|
|
21
59
|
|
|
22
60
|
Detect `react-native-appsflyer` or Swift attribution setup. Do not include AppsFlyer secrets in manifests.
|
|
@@ -15,7 +15,7 @@ pnpm add @getrheo/react-native-bare \
|
|
|
15
15
|
react-native-safe-area-context react-native-in-app-review react-native-video
|
|
16
16
|
```
|
|
17
17
|
|
|
18
|
-
**Integrations (host only):** `react-native-appsflyer`, `react-native-purchases`, `react-native-purchases-ui` when needed.
|
|
18
|
+
**Integrations (host only):** `react-native-appsflyer`, `react-native-purchases`, `react-native-purchases-ui` when needed. External Surface Nodes use `Flow` `externalSurfaces` (no extra native peer).
|
|
19
19
|
|
|
20
20
|
## Minimal Runtime
|
|
21
21
|
|
|
@@ -24,7 +24,13 @@ import { Flow, RheoProvider } from '@getrheo/react-native-bare';
|
|
|
24
24
|
|
|
25
25
|
export const OnboardingHost = () => (
|
|
26
26
|
<RheoProvider config={{ publishableKey: '…', userId: '…' }}>
|
|
27
|
-
<Flow
|
|
27
|
+
<Flow
|
|
28
|
+
channelId="ch_…"
|
|
29
|
+
onFlowCompleted={() => {}}
|
|
30
|
+
externalSurfaces={{
|
|
31
|
+
// surf_*: ({ onComplete, onBack, onDismiss }) => host UI
|
|
32
|
+
}}
|
|
33
|
+
/>
|
|
28
34
|
</RheoProvider>
|
|
29
35
|
);
|
|
30
36
|
```
|
|
@@ -15,7 +15,7 @@ pnpm add @getrheo/react-native-expo \
|
|
|
15
15
|
react-native-safe-area-context expo-store-review expo-video
|
|
16
16
|
```
|
|
17
17
|
|
|
18
|
-
**Integrations (host only, not SDK peers):** `react-native-appsflyer`, `react-native-purchases`, `react-native-purchases-ui` when the flow uses attribution or RevenueCat
|
|
18
|
+
**Integrations (host only, not SDK peers):** `react-native-appsflyer`, `react-native-purchases`, `react-native-purchases-ui` when the flow uses attribution or RevenueCat Integration Nodes. External Surface Nodes use `Flow` / `useFlow` `externalSurfaces` (no extra native peer).
|
|
19
19
|
|
|
20
20
|
## Minimal Runtime
|
|
21
21
|
|
|
@@ -46,7 +46,8 @@ struct OnboardingHost: View {
|
|
|
46
46
|
|
|
47
47
|
## Notes
|
|
48
48
|
|
|
49
|
-
- Use `RheoSwiftUIRevenueCat` for RevenueCat
|
|
49
|
+
- Use `RheoSwiftUIRevenueCat` for RevenueCat Integration Node presenter helpers.
|
|
50
|
+
- External Surface Nodes: pass `externalSurfaces: [surfId: { ctx in AnyView(...) }]` on `FlowView` (`ctx.onComplete` / `onBack` / `onDismiss`).
|
|
50
51
|
- Use `RheoSwiftUIAppsFlyer` for AppsFlyer attribution providers.
|
|
51
52
|
- Host apps must include Info.plist usage strings for authored permission prompts.
|
|
52
53
|
- Host apps must register branding fonts if relying on downloaded font families.
|
|
@@ -38,7 +38,7 @@ Fix workflow:
|
|
|
38
38
|
|
|
39
39
|
## Missing Fallback Edge
|
|
40
40
|
|
|
41
|
-
Every
|
|
41
|
+
Every Integration / External Surface Node needs `fallback`. Outcomes that are not explicitly mapped (RevenueCat purchase outcomes or headless `completed` / `back` / `dismissed`) fall through to this target.
|
|
42
42
|
|
|
43
43
|
## Integration Disabled
|
|
44
44
|
|
|
@@ -111,7 +111,10 @@ Valid `permissionKey` values for `request_os_permission`:
|
|
|
111
111
|
- `email_password_auth` modes: `sign_in`, `sign_up` (sign_up requires email + password + confirm fields).
|
|
112
112
|
- `icon` families: `ionicons`.
|
|
113
113
|
|
|
114
|
-
## External surface outcomes
|
|
114
|
+
## External surface outcomes
|
|
115
115
|
|
|
116
|
-
|
|
117
|
-
|
|
116
|
+
Builder add-menu kinds share `externalSurfaceNodes`: **Integration Node** (partner providers such as RevenueCat) and **External Surface Node** (`provider: "headless"`). Every node needs a `fallback` jump target.
|
|
117
|
+
|
|
118
|
+
**Integration Node / RevenueCat** (`provider: "revenuecat"`): `purchase_completed`, `purchase_cancelled`, `dismissed`, `failed`, `restore_completed`.
|
|
119
|
+
|
|
120
|
+
**External Surface Node / Headless** (`provider: "headless"`): `completed`, `back`, `dismissed`, `failed`. Host apps register UI via `externalSurfaces[hostKey]` (`config.hostKey` or node id) with `onComplete` / `onBack` / `onDismiss`.
|
|
@@ -199,7 +199,8 @@ instead of a plain footer CTA ([carousel-import.md](carousel-import.md)).
|
|
|
199
199
|
## Decisions and external surfaces
|
|
200
200
|
|
|
201
201
|
`decisions` accepts full `@getrheo/contracts` `DecisionNode` objects (the scaffold
|
|
202
|
-
passes them through).
|
|
202
|
+
passes them through). External surfaces use `externalSurfaces` (builder:
|
|
203
|
+
**Integration Node** for `revenuecat`, **External Surface Node** for `headless`):
|
|
203
204
|
|
|
204
205
|
```jsonc
|
|
205
206
|
{
|
|
@@ -208,7 +209,16 @@ passes them through). RevenueCat paywalls use `externalSurfaces`:
|
|
|
208
209
|
"offeringId": "default",
|
|
209
210
|
"presentation": "paywall",
|
|
210
211
|
"outcomes": { "purchase_completed": "scr_done", "restore_completed": "scr_done", "dismissed": "scr_offer2" },
|
|
211
|
-
"fallback": "scr_offer2" // required
|
|
212
|
+
"fallback": "scr_offer2" // required — Integration Node
|
|
213
|
+
}
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
```jsonc
|
|
217
|
+
{
|
|
218
|
+
"id": "surf_custom_step",
|
|
219
|
+
"provider": "headless",
|
|
220
|
+
"outcomes": { "completed": "scr_done", "back": "scr_welcome", "dismissed": "scr_offer2" },
|
|
221
|
+
"fallback": "scr_offer2" // required — External Surface Node; host registers externalSurfaces.surf_custom_step
|
|
212
222
|
}
|
|
213
223
|
```
|
|
214
224
|
|
|
@@ -67,7 +67,7 @@ Use only: `stack`, `text`, `image`, `lottie`, `video`, `icon`, `button`, `back_b
|
|
|
67
67
|
- Black-and-white fallback is acceptable only when the audit finds no style/token evidence and the user confirms no theme source.
|
|
68
68
|
- Use at most one input layer kind per **active path** (`single_choice`, `multiple_choice`, `text_input`, `scale_input`, `wheel_picker`) — one per screen unless sibling `conditional` branches split the path.
|
|
69
69
|
- Non-reserved `sdk.*` decision keys must be listed in `sdkAttributeKeys`.
|
|
70
|
-
- RevenueCat
|
|
70
|
+
- RevenueCat **Integration Nodes** and headless **External Surface Nodes** both live in `externalSurfaceNodes` and always need `fallback`.
|
|
71
71
|
- Emit complete graph edges for imported flows.
|
|
72
72
|
- Use placeholder UUID media ids and `rheo-import.assets.json`; never put file paths directly in `mediaAssetId`.
|
|
73
73
|
- Do not silently drop media layers. If a traced asset cannot be copied, report the missing file and do not call the import complete.
|
|
@@ -195,9 +195,13 @@ Use `conditional` when the source renders different content on **one** screen ba
|
|
|
195
195
|
|
|
196
196
|
## External Surfaces
|
|
197
197
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
-
|
|
198
|
+
In the builder UI these are two add-menu kinds sharing `externalSurfaceNodes`:
|
|
199
|
+
|
|
200
|
+
- **Integration Node** — RevenueCat paywalls become nodes with provider `revenuecat`.
|
|
201
|
+
- **External Surface Node** — Host-owned custom screens become nodes with provider `headless` (stable `surf_*` id; optional `config.hostKey` for the host `externalSurfaces` registry).
|
|
202
|
+
- Every node needs a `fallback`.
|
|
203
|
+
- Map paywall outcomes to `purchase_completed`, `restore_completed`, `dismissed`, and `failed`.
|
|
204
|
+
- Map headless outcomes to `completed`, `back`, `dismissed`, and `failed`.
|
|
201
205
|
|
|
202
206
|
## Assets
|
|
203
207
|
|
|
@@ -45,9 +45,10 @@ These mirror `apps/web/src/features/builder/validateFlow.ts` and API `preflightP
|
|
|
45
45
|
|
|
46
46
|
### Integrations (default: enabled)
|
|
47
47
|
|
|
48
|
-
- External surfaces need `config.provider` (not `unspecified`).
|
|
48
|
+
- External surfaces need `config.provider` (not `unspecified`). Integration Nodes use partner providers such as `revenuecat`; External Surface Nodes use `headless`.
|
|
49
49
|
- Every external surface needs `fallback`.
|
|
50
50
|
- RevenueCat surfaces require RevenueCat integration enabled (import assumes enabled).
|
|
51
|
+
- External Surface Nodes (`provider: "headless"`) do not require an App settings toggle; the host must supply `externalSurfaces`.
|
|
51
52
|
|
|
52
53
|
### Canvas editor gates (default: all enabled)
|
|
53
54
|
|
|
@@ -83,15 +83,16 @@ Paging is swipe by default; map the pager's own next button to a `button` with
|
|
|
83
83
|
## Integrations and native steps
|
|
84
84
|
|
|
85
85
|
- `react-native-purchases` / `react-native-purchases-ui` / `Purchases.configure`
|
|
86
|
-
/ a `<Paywall>` → a RevenueCat **
|
|
87
|
-
required `fallback`).
|
|
86
|
+
/ a `<Paywall>` → a RevenueCat **Integration Node** (`provider: "revenuecat"`,
|
|
87
|
+
required `fallback`). Host-owned custom screens → an **External Surface Node**
|
|
88
|
+
(`provider: "headless"`). See [integrations](../../rheo-best-practices/references/integrations.md).
|
|
88
89
|
- `react-native-appsflyer` → represent stable attribution branches via decision
|
|
89
90
|
nodes; add keys to `sdkAttributeKeys`. Never include AppsFlyer/RevenueCat secrets.
|
|
90
91
|
- OAuth / email-password screens → `oauth_login` / `email_password_auth` (host
|
|
91
92
|
owns the actual auth logic).
|
|
92
93
|
- `react-native-permissions` prompts → a `request_os_permission` button action.
|
|
93
94
|
- Signature pads, camera capture, custom WebViews → confirm with the user whether
|
|
94
|
-
to keep native (host-owned) or approximate; flag unmappable behavior in chat.
|
|
95
|
+
to keep native (External Surface Node / host-owned) or approximate; flag unmappable behavior in chat.
|
|
95
96
|
|
|
96
97
|
## i18n
|
|
97
98
|
|
|
@@ -80,8 +80,9 @@ page. Paging is swipe by default; an explicit Next button becomes a `button` wit
|
|
|
80
80
|
## Integrations and native steps
|
|
81
81
|
|
|
82
82
|
- RevenueCat (`Purchases.configure`, `RevenueCatUI` `PaywallView`, `.presentPaywall`)
|
|
83
|
-
→ a RevenueCat **
|
|
84
|
-
`fallback`).
|
|
83
|
+
→ a RevenueCat **Integration Node** (`provider: "revenuecat"`, required
|
|
84
|
+
`fallback`). Host-owned custom screens → an **External Surface Node**
|
|
85
|
+
(`provider: "headless"`). See [integrations](../../rheo-best-practices/references/integrations.md).
|
|
85
86
|
- AppsFlyer (`AppsFlyerLib`) → represent stable attribution branches via decision
|
|
86
87
|
nodes; add keys to `sdkAttributeKeys`. Never include secrets.
|
|
87
88
|
- "Sign in with Apple" (`SignInWithAppleButton`) / OAuth / email-password →
|
|
@@ -90,7 +91,7 @@ page. Paging is swipe by default; an explicit Next button becomes a `button` wit
|
|
|
90
91
|
`UNUserNotificationCenter`) → a `request_os_permission` button action; the host
|
|
91
92
|
must declare the matching `Info.plist` usage strings.
|
|
92
93
|
- `PencilKit` signature, camera capture, custom `WKWebView` → confirm whether to
|
|
93
|
-
keep native (host-owned) or approximate; flag unmappable behavior in chat.
|
|
94
|
+
keep native (External Surface Node / host-owned) or approximate; flag unmappable behavior in chat.
|
|
94
95
|
|
|
95
96
|
## Localization
|
|
96
97
|
|
|
@@ -8422,15 +8422,20 @@ var conditionalFieldScopeViolations = (screen) => {
|
|
|
8422
8422
|
return violations;
|
|
8423
8423
|
};
|
|
8424
8424
|
var ExternalSurfaceNodeIdSchema = external_exports.string().min(1).max(64).regex(/^surf_[a-z0-9_]+$/i, "external surface node id must look like surf_<id>");
|
|
8425
|
-
var
|
|
8425
|
+
var IAP_SURFACE_OUTCOMES = [
|
|
8426
8426
|
"purchase_completed",
|
|
8427
8427
|
"purchase_cancelled",
|
|
8428
8428
|
"dismissed",
|
|
8429
8429
|
"failed",
|
|
8430
8430
|
"restore_completed"
|
|
8431
8431
|
];
|
|
8432
|
+
var NORMALIZED_SURFACE_OUTCOMES = [
|
|
8433
|
+
...IAP_SURFACE_OUTCOMES,
|
|
8434
|
+
"completed",
|
|
8435
|
+
"back"
|
|
8436
|
+
];
|
|
8432
8437
|
var NormalizedSurfaceOutcomeSchema = external_exports.enum(NORMALIZED_SURFACE_OUTCOMES);
|
|
8433
|
-
var SurfaceProviderSchema = external_exports.enum(["unspecified", "revenuecat"]);
|
|
8438
|
+
var SurfaceProviderSchema = external_exports.enum(["unspecified", "revenuecat", "headless"]);
|
|
8434
8439
|
var UnspecifiedExternalSurfaceConfigSchema = external_exports.object({
|
|
8435
8440
|
provider: external_exports.literal("unspecified")
|
|
8436
8441
|
});
|
|
@@ -8441,16 +8446,28 @@ var RevenueCatSurfaceConfigSchema = external_exports.object({
|
|
|
8441
8446
|
placementId: external_exports.string().min(1).max(128).optional(),
|
|
8442
8447
|
presentation: RevenueCatSurfacePresentationSchema.optional()
|
|
8443
8448
|
});
|
|
8449
|
+
var ExternalSurfaceHostKeySchema = external_exports.string().min(1).max(64).regex(
|
|
8450
|
+
/^[a-zA-Z][a-zA-Z0-9_]*$/,
|
|
8451
|
+
"host key must start with a letter and contain only letters, digits, or underscores"
|
|
8452
|
+
);
|
|
8453
|
+
var HeadlessExternalSurfaceConfigSchema = external_exports.object({
|
|
8454
|
+
provider: external_exports.literal("headless"),
|
|
8455
|
+
/** Registry key for `Flow` / `FlowView` `externalSurfaces`. Defaults to the node id. */
|
|
8456
|
+
hostKey: ExternalSurfaceHostKeySchema.optional()
|
|
8457
|
+
});
|
|
8444
8458
|
var ExternalSurfaceConfigSchema = external_exports.discriminatedUnion("provider", [
|
|
8445
8459
|
UnspecifiedExternalSurfaceConfigSchema,
|
|
8446
|
-
RevenueCatSurfaceConfigSchema
|
|
8460
|
+
RevenueCatSurfaceConfigSchema,
|
|
8461
|
+
HeadlessExternalSurfaceConfigSchema
|
|
8447
8462
|
]);
|
|
8448
8463
|
var ExternalSurfaceOutcomesMapSchema = external_exports.object({
|
|
8449
8464
|
purchase_completed: FlowJumpTargetSchema2.optional(),
|
|
8450
8465
|
purchase_cancelled: FlowJumpTargetSchema2.optional(),
|
|
8451
8466
|
dismissed: FlowJumpTargetSchema2.optional(),
|
|
8452
8467
|
failed: FlowJumpTargetSchema2.optional(),
|
|
8453
|
-
restore_completed: FlowJumpTargetSchema2.optional()
|
|
8468
|
+
restore_completed: FlowJumpTargetSchema2.optional(),
|
|
8469
|
+
completed: FlowJumpTargetSchema2.optional(),
|
|
8470
|
+
back: FlowJumpTargetSchema2.optional()
|
|
8454
8471
|
}).strict();
|
|
8455
8472
|
var ExternalSurfaceNodeSchema = external_exports.object({
|
|
8456
8473
|
id: ExternalSurfaceNodeIdSchema,
|
|
@@ -8461,6 +8478,12 @@ var ExternalSurfaceNodeSchema = external_exports.object({
|
|
|
8461
8478
|
/** Required: used for any outcome not in `outcomes` (e.g. provider quirks, unmapped events). */
|
|
8462
8479
|
fallback: FlowJumpTargetSchema2
|
|
8463
8480
|
});
|
|
8481
|
+
var resolveExternalSurfaceHostKey = (node) => {
|
|
8482
|
+
if (node.config.provider === "headless" && node.config.hostKey) {
|
|
8483
|
+
return node.config.hostKey;
|
|
8484
|
+
}
|
|
8485
|
+
return node.id;
|
|
8486
|
+
};
|
|
8464
8487
|
var RESERVED_RC_SDK_KEYS = [
|
|
8465
8488
|
/** Last RC event observed by the SDK (e.g. `purchase_completed`, `purchase_cancelled`). */
|
|
8466
8489
|
"onb_rc_last_event",
|
|
@@ -8710,6 +8733,7 @@ var refineManifestGraph = (manifest, ctx, jumpTargets) => {
|
|
|
8710
8733
|
}
|
|
8711
8734
|
});
|
|
8712
8735
|
const seenSurfaceId = /* @__PURE__ */ new Set();
|
|
8736
|
+
const seenHostKey = /* @__PURE__ */ new Set();
|
|
8713
8737
|
manifest.externalSurfaceNodes.forEach((sn, si) => {
|
|
8714
8738
|
if (seenSurfaceId.has(sn.id)) {
|
|
8715
8739
|
ctx.addIssue({
|
|
@@ -8733,6 +8757,17 @@ var refineManifestGraph = (manifest, ctx, jumpTargets) => {
|
|
|
8733
8757
|
path: ["externalSurfaceNodes", si]
|
|
8734
8758
|
});
|
|
8735
8759
|
}
|
|
8760
|
+
if (sn.config.provider === "headless") {
|
|
8761
|
+
const hostKey = resolveExternalSurfaceHostKey(sn);
|
|
8762
|
+
if (seenHostKey.has(hostKey)) {
|
|
8763
|
+
ctx.addIssue({
|
|
8764
|
+
code: external_exports.ZodIssueCode.custom,
|
|
8765
|
+
message: `duplicate external surface host key "${hostKey}"`,
|
|
8766
|
+
path: ["externalSurfaceNodes", si, "config", "hostKey"]
|
|
8767
|
+
});
|
|
8768
|
+
}
|
|
8769
|
+
seenHostKey.add(hostKey);
|
|
8770
|
+
}
|
|
8736
8771
|
for (const [outcome, target] of Object.entries(sn.outcomes)) {
|
|
8737
8772
|
if (target != null && !jumpTargets.has(target)) {
|
|
8738
8773
|
ctx.addIssue({
|
|
@@ -12435,7 +12470,7 @@ var fixForBuilderMessage = (message) => {
|
|
|
12435
12470
|
return "Set externalSurfaceNodes[].config.provider to a real provider (e.g. revenuecat), not unspecified.";
|
|
12436
12471
|
}
|
|
12437
12472
|
if (message.includes("Fallback edge")) {
|
|
12438
|
-
return "Connect every external surface fallback to the next screen/decision/surface when
|
|
12473
|
+
return "Connect every external surface fallback to the next screen/decision/surface when no specific outcome is mapped.";
|
|
12439
12474
|
}
|
|
12440
12475
|
return `Resolve builder publish rule: ${message}`;
|
|
12441
12476
|
};
|
|
@@ -12449,7 +12484,7 @@ var collectIntegrationIssues = (manifest, integrations) => {
|
|
|
12449
12484
|
}
|
|
12450
12485
|
if (node.fallback == null) {
|
|
12451
12486
|
issues.push(
|
|
12452
|
-
`External surface "${node.name ?? node.id}" needs a connected Fallback edge \u2014 every
|
|
12487
|
+
`External surface "${node.name ?? node.id}" needs a connected Fallback edge \u2014 every external surface must route somewhere when no specific outcome is mapped.`
|
|
12453
12488
|
);
|
|
12454
12489
|
}
|
|
12455
12490
|
if (node.config.provider === "revenuecat" && !integrations.revenuecat.enabled) {
|
|
@@ -15004,16 +15039,19 @@ var ScreenSpecSchema = external_exports.object({
|
|
|
15004
15039
|
var ExternalSurfaceSpecSchema = external_exports.object({
|
|
15005
15040
|
id: external_exports.string().min(1).max(64),
|
|
15006
15041
|
name: external_exports.string().min(1).max(80).optional(),
|
|
15007
|
-
provider: external_exports.enum(["revenuecat", "unspecified"]),
|
|
15042
|
+
provider: external_exports.enum(["revenuecat", "headless", "unspecified"]),
|
|
15008
15043
|
offeringId: external_exports.string().min(1).max(128).optional(),
|
|
15009
15044
|
placementId: external_exports.string().min(1).max(128).optional(),
|
|
15010
15045
|
presentation: external_exports.enum(["paywall", "paywall_if_needed"]).optional(),
|
|
15046
|
+
hostKey: external_exports.string().min(1).max(64).regex(/^[a-zA-Z][a-zA-Z0-9_]*$/).optional(),
|
|
15011
15047
|
outcomes: external_exports.object({
|
|
15012
15048
|
purchase_completed: external_exports.string().min(1).nullable().optional(),
|
|
15013
15049
|
purchase_cancelled: external_exports.string().min(1).nullable().optional(),
|
|
15014
15050
|
dismissed: external_exports.string().min(1).nullable().optional(),
|
|
15015
15051
|
failed: external_exports.string().min(1).nullable().optional(),
|
|
15016
|
-
restore_completed: external_exports.string().min(1).nullable().optional()
|
|
15052
|
+
restore_completed: external_exports.string().min(1).nullable().optional(),
|
|
15053
|
+
completed: external_exports.string().min(1).nullable().optional(),
|
|
15054
|
+
back: external_exports.string().min(1).nullable().optional()
|
|
15017
15055
|
}).optional(),
|
|
15018
15056
|
fallback: external_exports.string().min(1).nullable()
|
|
15019
15057
|
});
|
|
@@ -15518,6 +15556,9 @@ var buildExternalSurface = (surface) => {
|
|
|
15518
15556
|
...surface.offeringId ? { offeringId: surface.offeringId } : {},
|
|
15519
15557
|
...surface.placementId ? { placementId: surface.placementId } : {},
|
|
15520
15558
|
...surface.presentation ? { presentation: surface.presentation } : {}
|
|
15559
|
+
} : surface.provider === "headless" ? {
|
|
15560
|
+
provider: "headless",
|
|
15561
|
+
...surface.hostKey ? { hostKey: surface.hostKey } : {}
|
|
15521
15562
|
} : { provider: "unspecified" };
|
|
15522
15563
|
const outcomes = {};
|
|
15523
15564
|
for (const [key, value] of Object.entries(surface.outcomes ?? {})) {
|