@funnelsgrove/cli 0.1.53 → 0.1.62

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 (33) hide show
  1. package/dist/cli.d.ts +1 -1
  2. package/dist/cli.js +24 -2
  3. package/dist/experimentCreate.d.ts +3 -8
  4. package/dist/experimentCreate.js +19 -18
  5. package/funnel-contract-compatibility.json +35 -35
  6. package/package.json +4 -4
  7. package/template_docs/.funnelsgrove-docs.json +4 -4
  8. package/template_docs/docs/funnelsgrove/migrations/step-contract-v3.md +1 -1
  9. package/template_docs/docs/funnelsgrove/recipes/add-experiment.md +11 -10
  10. package/template_docs/funnel-docs.config.json +1 -1
  11. package/template_scaffold/.funnelsgrove-contract-tools.json +1 -1
  12. package/template_scaffold/.funnelsgrove-docs.json +4 -4
  13. package/template_scaffold/.funnelsgrove-scaffold.json +19 -34
  14. package/template_scaffold/docs/funnelsgrove/migrations/step-contract-v3.md +1 -1
  15. package/template_scaffold/docs/funnelsgrove/recipes/add-experiment.md +11 -10
  16. package/template_scaffold/funnel-docs.config.json +1 -1
  17. package/template_scaffold/package-lock.json +14 -14
  18. package/template_scaffold/package.json +3 -3
  19. package/template_scaffold/src/components/FunnelFlow.tsx +26 -0
  20. package/template_scaffold/src/config/funnel.manifest.ts +2 -2
  21. package/template_scaffold/src/contract/funnel-project.validation.ts +1 -2
  22. package/template_scaffold/src/runtime/funnel-runtime.ts +2 -10
  23. package/template_scaffold/src/runtime/use-funnel-flow-controller.ts +15 -1
  24. package/template_scaffold/src/steps/step-32-paywall.tsx +47 -15
  25. package/template_scaffold/tests/funnel-agent-docs.test.ts +5 -4
  26. package/template_scaffold/tests/src/components/FunnelFlow.test.tsx +4 -0
  27. package/template_scaffold/tests/src/contract/funnel-project.validation.test.ts +1 -2
  28. package/template_scaffold/tests/src/steps/step-32-paywall.test.ts +6 -3
  29. package/template_validation/.funnelsgrove-contract-tools.json +1 -1
  30. package/template_validation/src/contract/funnel-project.validation.ts +1 -2
  31. package/template_scaffold/src/config/experiments.generated.ts +0 -4
  32. package/template_scaffold/src/config/experiments.ts +0 -9
  33. package/template_scaffold/tests/src/config/experiments.test.ts +0 -12
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
- "bundleVersion": "2.0.41",
3
+ "bundleVersion": "2.0.51",
4
4
  "stepContractVersion": 3,
5
5
  "contractHash": "d761e91d5ac6ff9e72c6d49c5bcd014270912473a66f99c49d726c65998085cf",
6
6
  "managedFiles": [
@@ -38,7 +38,7 @@
38
38
  },
39
39
  {
40
40
  "path": "docs/funnelsgrove/migrations/step-contract-v3.md",
41
- "sha256": "7993d09cd90636290a43ee9ed0cdebf62a4fea99c77625b7880d2641b6175ba0"
41
+ "sha256": "10cd5853ffbe09a300fe222b18d1f7d7c1dd0f61a02693dd8e6d586b283f2121"
42
42
  },
43
43
  {
44
44
  "path": "docs/funnelsgrove/qa/analytics.md",
@@ -58,7 +58,7 @@
58
58
  },
59
59
  {
60
60
  "path": "docs/funnelsgrove/recipes/add-experiment.md",
61
- "sha256": "7b2d28d84042c7b22f1cc26f0407389c4bfc6d38f82b473cb128a822f750b0d8"
61
+ "sha256": "c63a0f15884b2bf5d65f28d127345bd97641a7b38d047907d6d66f091e15a44a"
62
62
  },
63
63
  {
64
64
  "path": "docs/funnelsgrove/recipes/add-step.md",
@@ -138,7 +138,7 @@
138
138
  },
139
139
  {
140
140
  "path": "funnel-docs.config.json",
141
- "sha256": "dd7eeb6a8028af400f7c28565b622a3ae824e67c9830b71f4c83fbb2487d2159"
141
+ "sha256": "06c7a3e1b2885129c83e8dd405a9d37ef6f87a14702d92c420772e5bf733f9cc"
142
142
  }
143
143
  ]
144
144
  }
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
- "sourceTreeHash": "b107f26e31b2a06f00850b109581caf0f80844e3764d1355660dd4172fd8f0d4",
3
+ "sourceTreeHash": "f9b7bcf2ed6d8cbd3e12112af153af6b832d43a6be26364505e65c4c597b9a3a",
4
4
  "stepContractVersion": 3,
5
- "docsBundleVersion": "2.0.41",
5
+ "docsBundleVersion": "2.0.51",
6
6
  "files": [
7
7
  {
8
8
  "path": ".env.example",
@@ -11,12 +11,12 @@
11
11
  },
12
12
  {
13
13
  "path": ".funnelsgrove-contract-tools.json",
14
- "sha256": "b9ad23fb24eb97adaef5cd008c471e7529e4971244f48b756e2ed4e0a3dfc96a",
14
+ "sha256": "630aa1d0bdc4fede66d6bc1467bead2b51fd0e060569e3ec392773dce8773566",
15
15
  "mode": "100644"
16
16
  },
17
17
  {
18
18
  "path": ".funnelsgrove-docs.json",
19
- "sha256": "6624839df385e4c7494b8ce4ffb3a07a793c0668cceb8737cde9478883ac7b33",
19
+ "sha256": "ed4582b4aa0c6044b8638ec004aaedde53b35836dbe78240a14371e4252e5d70",
20
20
  "mode": "100644"
21
21
  },
22
22
  {
@@ -101,7 +101,7 @@
101
101
  },
102
102
  {
103
103
  "path": "docs/funnelsgrove/migrations/step-contract-v3.md",
104
- "sha256": "7993d09cd90636290a43ee9ed0cdebf62a4fea99c77625b7880d2641b6175ba0",
104
+ "sha256": "10cd5853ffbe09a300fe222b18d1f7d7c1dd0f61a02693dd8e6d586b283f2121",
105
105
  "mode": "100644"
106
106
  },
107
107
  {
@@ -126,7 +126,7 @@
126
126
  },
127
127
  {
128
128
  "path": "docs/funnelsgrove/recipes/add-experiment.md",
129
- "sha256": "7b2d28d84042c7b22f1cc26f0407389c4bfc6d38f82b473cb128a822f750b0d8",
129
+ "sha256": "c63a0f15884b2bf5d65f28d127345bd97641a7b38d047907d6d66f091e15a44a",
130
130
  "mode": "100644"
131
131
  },
132
132
  {
@@ -236,7 +236,7 @@
236
236
  },
237
237
  {
238
238
  "path": "funnel-docs.config.json",
239
- "sha256": "dd7eeb6a8028af400f7c28565b622a3ae824e67c9830b71f4c83fbb2487d2159",
239
+ "sha256": "06c7a3e1b2885129c83e8dd405a9d37ef6f87a14702d92c420772e5bf733f9cc",
240
240
  "mode": "100644"
241
241
  },
242
242
  {
@@ -261,12 +261,12 @@
261
261
  },
262
262
  {
263
263
  "path": "package-lock.json",
264
- "sha256": "4d1df3b0e2f67faf62f0f13f7cfacedbcc7a2ada0538b2df245ff474a4b7accb",
264
+ "sha256": "2f392e1a5801855e5ff3416da793db39ba95b1647957ddba93e127eb8aedc331",
265
265
  "mode": "100644"
266
266
  },
267
267
  {
268
268
  "path": "package.json",
269
- "sha256": "e15293d4839b59fb1d9fbab3ea4e98105a0661eac1d40a4af449265a1b9fb718",
269
+ "sha256": "28258db9fd734208b0d4cf6755f20f541977c7a0e6f10cc6160f5d22b706fa61",
270
270
  "mode": "100644"
271
271
  },
272
272
  {
@@ -531,7 +531,7 @@
531
531
  },
532
532
  {
533
533
  "path": "src/components/FunnelFlow.tsx",
534
- "sha256": "d25ac7c64cdce0758f362fcac768116d766a6841b727336c47f30b6af6c674d0",
534
+ "sha256": "80aa3d65ad9965b979a737f1c7b4a0f3da3351ee6b4b9dc5ed87f12ce58550ae",
535
535
  "mode": "100644"
536
536
  },
537
537
  {
@@ -554,16 +554,6 @@
554
554
  "sha256": "1a5ac5f14321748a7d19631adf32ca4412f2d129ed31080683ae2e25b21a1ef8",
555
555
  "mode": "100644"
556
556
  },
557
- {
558
- "path": "src/config/experiments.generated.ts",
559
- "sha256": "f25744dcebf4932ce25f378c76f2bd270c997f7792093b07ed0abdd7c74a1049",
560
- "mode": "100644"
561
- },
562
- {
563
- "path": "src/config/experiments.ts",
564
- "sha256": "f008ff0c04a267f03204e34e9d13bc0d897d14053e78287f700801484fc3f937",
565
- "mode": "100644"
566
- },
567
557
  {
568
558
  "path": "src/config/funnel.config.ts",
569
559
  "sha256": "6826189cdc984b448b9049d80f94fbe78a96cc6101b868a93554004011a3c424",
@@ -571,7 +561,7 @@
571
561
  },
572
562
  {
573
563
  "path": "src/config/funnel.manifest.ts",
574
- "sha256": "9bdb5e41da57edb5d16afde77c5d108010658160168aeee2ad65f79fd9b82604",
564
+ "sha256": "e52fea6fa35fd7ef43835d92ab15a9748ae5356b5d2c8cdee593ef0fa3eca61f",
575
565
  "mode": "100644"
576
566
  },
577
567
  {
@@ -581,7 +571,7 @@
581
571
  },
582
572
  {
583
573
  "path": "src/contract/funnel-project.validation.ts",
584
- "sha256": "b0ec02eac1bd56812eef773be71628ae4e94f80fcd88837bbc5c8bf04e73a62c",
574
+ "sha256": "99299cfb96414dd24272439cd1c57191a0863749c920a5eb03ff786f7972ab0e",
585
575
  "mode": "100644"
586
576
  },
587
577
  {
@@ -681,7 +671,7 @@
681
671
  },
682
672
  {
683
673
  "path": "src/runtime/funnel-runtime.ts",
684
- "sha256": "a657b8864df422ac16759351a6a1a221ca70f50d56d3e2b066fd5ab23eae79b9",
674
+ "sha256": "4bae48bfc47b38394d39b1f03978d12b3d2e886ea24f212269bc40ee96e6901e",
685
675
  "mode": "100644"
686
676
  },
687
677
  {
@@ -701,7 +691,7 @@
701
691
  },
702
692
  {
703
693
  "path": "src/runtime/use-funnel-flow-controller.ts",
704
- "sha256": "9f12ab00b0c0685c3b520b9cc9bad22b7b04323164358b169356dc85338e0f50",
694
+ "sha256": "12a9abc9ad0ca311abc4a9e2c2f909c6984d28417f46d7c9012e897c3d8f104f",
705
695
  "mode": "100644"
706
696
  },
707
697
  {
@@ -851,7 +841,7 @@
851
841
  },
852
842
  {
853
843
  "path": "src/steps/step-32-paywall.tsx",
854
- "sha256": "da794a1d64dba9c5b56e1967de5435d67ef490d98f807d555a9def6b09223eef",
844
+ "sha256": "86407736b37dbaa88300ba270ea1b51b7ff27bdf2244365bbdb020e5f86004a4",
855
845
  "mode": "100644"
856
846
  },
857
847
  {
@@ -926,7 +916,7 @@
926
916
  },
927
917
  {
928
918
  "path": "tests/funnel-agent-docs.test.ts",
929
- "sha256": "e4a3fbae68ca1a11dcd531d8992a84ab416060f50becc5d6d6c4aaf56f931c0e",
919
+ "sha256": "e7d621ad4e17cad954f7de6528f6ca99b7f3c0171e1e159bd2769a05d63f4255",
930
920
  "mode": "100644"
931
921
  },
932
922
  {
@@ -946,7 +936,7 @@
946
936
  },
947
937
  {
948
938
  "path": "tests/src/components/FunnelFlow.test.tsx",
949
- "sha256": "bee08eb11b30b10add7bef075e9f3af1c8ecc7e0b671407223bda134f533df8e",
939
+ "sha256": "baf2607c8dcc29e4aad7a4ee95d932f7569887f7fbdf8dc8a9a3d7566a16e39e",
950
940
  "mode": "100644"
951
941
  },
952
942
  {
@@ -959,11 +949,6 @@
959
949
  "sha256": "f91727fc8cbfe09c7aeb48231d0a8f816e87b464d9df4cdca32ac03d95d72c4d",
960
950
  "mode": "100644"
961
951
  },
962
- {
963
- "path": "tests/src/config/experiments.test.ts",
964
- "sha256": "b50890b91b7bcafd38d61794be56346e882884262140006545b6779038163ef1",
965
- "mode": "100644"
966
- },
967
952
  {
968
953
  "path": "tests/src/config/funnel.manifest.test.ts",
969
954
  "sha256": "3fac92396bb0af26be2a9fc2253fd0aad14c89eb94e9a4f08ba720d8411479b6",
@@ -971,7 +956,7 @@
971
956
  },
972
957
  {
973
958
  "path": "tests/src/contract/funnel-project.validation.test.ts",
974
- "sha256": "9fdbce6320c8f47e5fc1318a61a7272bcfa1a0258e4daff5c47954cba3576176",
959
+ "sha256": "4a5f0f3b6d11d91b900d8edfffdd7bd4ee63c31014b6093231d0fbd33f8ccc35",
975
960
  "mode": "100644"
976
961
  },
977
962
  {
@@ -1046,7 +1031,7 @@
1046
1031
  },
1047
1032
  {
1048
1033
  "path": "tests/src/steps/step-32-paywall.test.ts",
1049
- "sha256": "8cada5658a5310d1be215cf65d63a42fe489eb491c2566b13824af5b259f26f8",
1034
+ "sha256": "0306cbe4e10d1f7027d0ba114eb95c8b5b095dfad57472887a4b985f1482afda",
1050
1035
  "mode": "100644"
1051
1036
  },
1052
1037
  {
@@ -17,7 +17,7 @@ Supported read versions: `1`, `2`, `3`. Authoring and publish target version `3`
17
17
 
18
18
  ### Package release order
19
19
 
20
- Release `@funnelsgrove/runtime` `0.2.0` first, then `@funnelsgrove/analytics` `0.1.44`, then `@funnelsgrove/payments` `0.2.0`. Deploy the API and funnel template, then confirm production `/health` reports the new docs identity. Only then publish `@funnelsgrove/cli` `0.1.53`. Publishing packages and deploying production remain separately approved operational actions.
20
+ Release `@funnelsgrove/runtime` `0.7.0` first, then `@funnelsgrove/analytics` `0.1.49`, then `@funnelsgrove/payments` `0.7.0`. Deploy the API and funnel template, then confirm production `/health` reports the new docs identity. Only then publish `@funnelsgrove/cli` `0.1.62`. Publishing packages and deploying production remain separately approved operational actions.
21
21
  <!-- funnelsgrove:generated:end contract-v3/migration/step-contract-v3 -->
22
22
 
23
23
  ## Version-last policy
@@ -38,7 +38,7 @@ Use only the documented fields. Unknown top-level and variant fields are rejecte
38
38
 
39
39
  All experiment types require non-empty `id`, `name`, and `stepId`; one `primaryMetric`; at least one unique `trackedMetrics` entry containing the primary metric; and two to five variants. Each variant requires a unique non-empty `variantKey`, `label`, and `routeToStepId`, an integer `trafficPercent` from 0 through 100, and `isControl`. Exactly one variant is the control and traffic must total 100.
40
40
 
41
- Length limits are enforced after surrounding whitespace is trimmed: `variantKey` is at most 120 characters. `id`, `name`, `stepId`, `label`, `routeToStepId`, and `offerSetKey` are each at most 200 characters after trimming. `offerSetKey` applies only to pricing variants.
41
+ Length limits are enforced after surrounding whitespace is trimmed: `variantKey` is at most 120 characters. `id`, `name`, `stepId`, `label`, and `routeToStepId` are each at most 200 characters after trimming. Pricing variants additionally require `offerSetId` as a UUID.
42
42
 
43
43
  Metrics are exactly `step_completion`, `next_step_reached`, `checkout_opened`, `funnel_completed`, or `paying_customer`.
44
44
 
@@ -112,7 +112,7 @@ Like `step`, every variant has a route target. Use `type: "paywall"` when the ro
112
112
 
113
113
  ### Pricing experiment
114
114
 
115
- Each pricing variant also requires a non-empty `offerSetKey`, and every `routeToStepId` must exactly equal the top-level `stepId`.
115
+ Each pricing variant also requires the immutable UUID `offerSetId` of an active offer set in the same project. Every `routeToStepId` must exactly equal the top-level `stepId`.
116
116
 
117
117
  ```json
118
118
  {
@@ -132,7 +132,7 @@ Each pricing variant also requires a non-empty `offerSetKey`, and every `routeTo
132
132
  "routeToStepId": "paywall",
133
133
  "trafficPercent": 50,
134
134
  "isControl": true,
135
- "offerSetKey": "default-paywall"
135
+ "offerSetId": "11111111-1111-4111-8111-111111111111"
136
136
  },
137
137
  {
138
138
  "variantKey": "annual_focus",
@@ -140,7 +140,7 @@ Each pricing variant also requires a non-empty `offerSetKey`, and every `routeTo
140
140
  "routeToStepId": "paywall",
141
141
  "trafficPercent": 50,
142
142
  "isControl": false,
143
- "offerSetKey": "annual-focus"
143
+ "offerSetId": "22222222-2222-4222-8222-222222222222"
144
144
  }
145
145
  ]
146
146
  }
@@ -156,13 +156,15 @@ Creation always produces a draft. It does not create or activate a PostHog flag,
156
156
  fgrove experiments start <experiment-uuid>
157
157
  fgrove experiments stop <experiment-uuid>
158
158
  fgrove experiments restart <experiment-uuid>
159
+ fgrove experiments end <experiment-uuid>
160
+ fgrove experiments delete <experiment-uuid>
159
161
  ```
160
162
 
161
- `stop` is reversible: it pauses traffic and keeps the experiment, variants, and PostHog flag available for a later `start`. `restart` stops a running experiment when necessary and starts it again while preserving the existing definition. Each command updates PostHog and commits the final experiment status to the hosted generated funnel config. Use `--json` for agent-readable output. Run `fgrove experiments sync` afterward when the local generated config also needs to reflect the lifecycle change.
163
+ `stop` is reversible: it pauses traffic and keeps the experiment, variants, and PostHog flag available for a later `start`. `restart` stops a running experiment when necessary and starts it again while preserving the existing definition. `end` permanently archives the experiment; `delete` removes a draft. Each command updates PostHog when required and persists the final status in the control plane. Use `--json` for agent-readable output.
162
164
 
163
- The corresponding authenticated API mutations are `funnelExperiments.start`, `funnelExperiments.stop`, and `funnelExperiments.restart`; each accepts `{ "id": "<experiment-uuid>" }`.
165
+ The corresponding authenticated API mutations are agent-only. Normal browser sessions cannot call experiment lifecycle mutations.
164
166
 
165
- The API creates the experiment and variants atomically, commits the matching generated snapshot to the hosted draft, and only then returns. The draft is immediately visible in the FunnelsGrove UI and hosted previews use the same snapshot. The CLI installs the exact returned bytes at `src/config/experiments.generated.ts` and `src/config/experiments.ts`, then updates the sync manifest last. `.funnelsgrove-sync.json` stays local and ignored; never commit or push it.
167
+ The API creates the experiment and variants atomically through ordinary data services. Experiments are stored in the control plane and included in the version-bound runtime artifact when the funnel is published. `src/config/experiments.generated.ts` and `src/config/experiments.ts` are deprecated compatibility files: new funnels and experiment creation do not generate, install, or update them.
166
168
 
167
169
  ## Recovery and errors
168
170
 
@@ -170,8 +172,7 @@ The API creates the experiment and variants atomically, commits the matching gen
170
172
  - Invalid local JSON or `[FG-EXPERIMENT-SPEC]`: fix the reported field and retry with the same stable `id`.
171
173
  - `[FG-EXPERIMENT-STALE-DRAFT]`: do not overwrite remote work. Download the latest draft into a clean temporary directory, merge deliberately, rerun validation, and retry the same exact spec.
172
174
  - `[FG-EXPERIMENT-DRAFT-CONFLICT]`: the stable key already belongs to a different definition, owner, or lifecycle state. Reuse the original exact definition for a retry, or choose a new stable `id` only for a genuinely new experiment.
173
- - Locally changed generated experiment files are never overwritten. Resolve those edits before retrying.
174
- - If the API succeeded but the process stopped, retry safely with the same `id`. A ready local journal is automatically recovered before the next create or sync command; an interruption before the journal simply repeats the exact API request.
175
+ - If the API succeeded but the process stopped, retry safely with the same `id`; no source transaction is required.
175
176
 
176
177
  ## Validate and deliver
177
178
 
@@ -184,7 +185,7 @@ git status --short
184
185
 
185
186
  For a funnel without GitHub source sync, deliver all local source changes with `fgrove sync up`.
186
187
 
187
- For a GitHub-connected funnel, commit and push the generated source files with the rest of the source change, then run `fgrove github pull`. Never use `fgrove sync up` for the same GitHub-connected diff, and never add `.funnelsgrove-sync.json` to git.
188
+ For a GitHub-connected funnel, commit and push only the variant step or offer-set source changes, then run `fgrove github pull`. The experiment itself is stored in the control plane and does not create a source file. Never use `fgrove sync up` for the same GitHub-connected diff; `.funnelsgrove-sync.json` stays local and ignored.
188
189
 
189
190
  ## Pre-activation QA
190
191
 
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
- "bundleVersion": "2.0.41",
3
+ "bundleVersion": "2.0.51",
4
4
  "contractSource": "funnelsgrove-repository://apps/funnel-runtime/contracts/step-contract-v2.json",
5
5
  "fullyGenerated": [
6
6
  ".funnelsgrove-docs.json",
@@ -8,9 +8,9 @@
8
8
  "name": "funnel-template",
9
9
  "version": "0.1.0",
10
10
  "dependencies": {
11
- "@funnelsgrove/analytics": "^0.1.44",
12
- "@funnelsgrove/payments": "^0.2.0",
13
- "@funnelsgrove/runtime": "^0.2.0",
11
+ "@funnelsgrove/analytics": "^0.1.49",
12
+ "@funnelsgrove/payments": "^0.7.0",
13
+ "@funnelsgrove/runtime": "^0.7.0",
14
14
  "@stripe/react-stripe-js": "^5.6.0",
15
15
  "@stripe/stripe-js": "^8.7.0",
16
16
  "lucide-react": "^0.553.0",
@@ -891,20 +891,20 @@
891
891
  }
892
892
  },
893
893
  "node_modules/@funnelsgrove/analytics": {
894
- "version": "0.1.44",
895
- "resolved": "https://registry.npmjs.org/@funnelsgrove/analytics/-/analytics-0.1.44.tgz",
896
- "integrity": "sha512-pjobnnHWV5ODAftTOXvFbBlDPekfaWHHF1jwTtVFdGfc/RAP3gwObmIoUJMM/BGWBpaDrmQr0xDPobPagATh5A==",
894
+ "version": "0.1.49",
895
+ "resolved": "https://registry.npmjs.org/@funnelsgrove/analytics/-/analytics-0.1.49.tgz",
896
+ "integrity": "sha512-HpPsrP+HVWQj1M+T9wDX8eCBgOTx0ffe29np1BiFwJj/o4QKuu+pT8jTXYMTG/VjqdUQGxU5591H8VmQh/HsOw==",
897
897
  "dependencies": {
898
- "@funnelsgrove/runtime": "0.2.0"
898
+ "@funnelsgrove/runtime": "0.7.0"
899
899
  }
900
900
  },
901
901
  "node_modules/@funnelsgrove/payments": {
902
- "version": "0.2.0",
903
- "resolved": "https://registry.npmjs.org/@funnelsgrove/payments/-/payments-0.2.0.tgz",
904
- "integrity": "sha512-BVSYPbgyGwDBtg+kBduRTcc8sGZmB70Re4cTF3rwRmgJ9FE/axQmkPC6p3rCwJerlcXnkaHL7DQE1Ck/VKMVOw==",
902
+ "version": "0.7.0",
903
+ "resolved": "https://registry.npmjs.org/@funnelsgrove/payments/-/payments-0.7.0.tgz",
904
+ "integrity": "sha512-H44LfRjJdyOpSNgY4VzDsvibqiuqvdflojjg5qoWH5cPT4PuDR6mtV7IUp/xrWcwYQ78uedmesO2T0i4AXENdw==",
905
905
  "dependencies": {
906
906
  "@funnelsgrove/analytics": "^0.1.28",
907
- "@funnelsgrove/runtime": "^0.2.0",
907
+ "@funnelsgrove/runtime": "^0.7.0",
908
908
  "@stripe/react-stripe-js": "^5.6.0",
909
909
  "@stripe/stripe-js": "^8.7.0",
910
910
  "react": "19.2.3",
@@ -913,9 +913,9 @@
913
913
  }
914
914
  },
915
915
  "node_modules/@funnelsgrove/runtime": {
916
- "version": "0.2.0",
917
- "resolved": "https://registry.npmjs.org/@funnelsgrove/runtime/-/runtime-0.2.0.tgz",
918
- "integrity": "sha512-zTaJWAxjklNyFMiTo+BOZ8j8Zx9wCam665Ebm9ra3qVcqV4rb+Ia+vCRH4M2ExZe1972CN7ss3lt0IfIXxfS+w==",
916
+ "version": "0.7.0",
917
+ "resolved": "https://registry.npmjs.org/@funnelsgrove/runtime/-/runtime-0.7.0.tgz",
918
+ "integrity": "sha512-CFU89b3ezuVudAmg+SbsNkoxxEhxxj8+6xtZGV0c0vpHs2z5O1CAoTkZW5RTJ6QGvktvr1d+IuRRpxjuU8CqeQ==",
919
919
  "dependencies": {
920
920
  "posthog-js": "^1.369.2",
921
921
  "react": "19.2.3",
@@ -12,9 +12,9 @@
12
12
  "validate:funnel": "vite-node --config src/contract/funnel-validator.vite.config.ts src/contract/validate-funnel.cli.ts"
13
13
  },
14
14
  "dependencies": {
15
- "@funnelsgrove/analytics": "^0.1.44",
16
- "@funnelsgrove/payments": "^0.2.0",
17
- "@funnelsgrove/runtime": "^0.2.0",
15
+ "@funnelsgrove/analytics": "^0.1.49",
16
+ "@funnelsgrove/payments": "^0.7.0",
17
+ "@funnelsgrove/runtime": "^0.7.0",
18
18
  "@stripe/react-stripe-js": "^5.6.0",
19
19
  "@stripe/stripe-js": "^8.7.0",
20
20
  "lucide-react": "^0.553.0",
@@ -3,8 +3,10 @@
3
3
  import { createElement, useMemo, useSyncExternalStore, type ComponentType } from 'react';
4
4
  import {
5
5
  FunnelProvider,
6
+ FunnelRuntimeConfigBoundary,
6
7
  PrimaryButton,
7
8
  isPreviewFrameRuntime,
9
+ runtimePublicConfig,
8
10
  usePreviewDefinitionOverrideRevision,
9
11
  } from '@funnelsgrove/runtime';
10
12
  import { FunnelEditorPanel, FunnelEditorPanelStyles } from './FunnelEditorPanel';
@@ -36,6 +38,30 @@ export function FunnelFlow({
36
38
  () => isPreviewRuntime,
37
39
  () => false,
38
40
  );
41
+ return (
42
+ <FunnelRuntimeConfigBoundary
43
+ funnelId={runtimePublicConfig.funnelId}
44
+ funnelVersionId={runtimePublicConfig.funnelVersionId}
45
+ disabled={isPreviewRuntime}
46
+ >
47
+ <FunnelFlowRuntime
48
+ initialStepId={initialStepId}
49
+ lockToInitialStep={lockToInitialStep}
50
+ previewFrameMounted={previewFrameMounted}
51
+ isPreviewThumbnail={isPreviewThumbnail}
52
+ />
53
+ </FunnelRuntimeConfigBoundary>
54
+ );
55
+ }
56
+
57
+ function FunnelFlowRuntime({
58
+ initialStepId,
59
+ lockToInitialStep,
60
+ previewFrameMounted,
61
+ isPreviewThumbnail,
62
+ }: FunnelFlowProps & {
63
+ previewFrameMounted: boolean;
64
+ }) {
39
65
  const previewDefinitionRevision = usePreviewDefinitionOverrideRevision();
40
66
  const controller = useFunnelFlowController({
41
67
  initialStepId,
@@ -3,7 +3,6 @@ import {
3
3
  CURRENT_STEP_CONTRACT_VERSION,
4
4
  DEFAULT_FUNNEL_BREAKPOINTS,
5
5
  } from '@funnelsgrove/runtime';
6
- import { manifestExperiments } from './experiments';
7
6
 
8
7
  export const templateArchitectureVersion = 1;
9
8
 
@@ -121,7 +120,8 @@ export const rawFunnelManifest = {
121
120
  'paywall-b': [{ toStepId: 'subscription-started' }],
122
121
  'manage-subscription': [{ toStepId: 'step-1' }],
123
122
  },
124
- experiments: manifestExperiments,
123
+ // Experiments are delivered by the version-bound runtime artifact.
124
+ experiments: [],
125
125
  } as const satisfies FunnelManifest;
126
126
 
127
127
  // Keep the authored object readable by the project validator. Eager validation
@@ -6,7 +6,6 @@ import {
6
6
  type FunnelStepMeta,
7
7
  } from '@funnelsgrove/runtime';
8
8
  import docsManifest from '../../.funnelsgrove-docs.json';
9
- import { manifestExperiments } from '@/config/experiments';
10
9
  import { rawFunnelManifest } from '@/config/funnel.manifest';
11
10
  import { stepRegistry } from '@/runtime/step-registry';
12
11
  import { contentOptionsByStepId } from '@/steps/step-content.registry';
@@ -24,7 +23,7 @@ export const buildFunnelProjectSnapshot = (): FunnelStepContractProjectSnapshot
24
23
  registeredComponentKeys: Object.keys(stepRegistry),
25
24
  contentOptionsByStepId,
26
25
  edgesByStepId: rawFunnelManifest.edgesByStepId,
27
- experiments: manifestExperiments,
26
+ experiments: rawFunnelManifest.experiments,
28
27
  docs: {
29
28
  schemaVersion: docsManifest.schemaVersion,
30
29
  bundleVersion: docsManifest.bundleVersion,
@@ -18,16 +18,8 @@ export type FunnelExperimentConfig = FunnelManifestExperiment & {
18
18
  }
19
19
  )[];
20
20
  };
21
- export const funnelExperiments: FunnelExperimentConfig[] = funnelManifest.experiments.map(
22
- (experiment) => ({
23
- ...experiment,
24
- stepId: experiment.stepId as FunnelStepId,
25
- variants: experiment.variants.map((variant) => ({
26
- ...variant,
27
- routeToStepId: variant.routeToStepId as FunnelStepId,
28
- })),
29
- }),
30
- );
21
+ /** @deprecated Builder-only fallback. Published experiments come from runtime config. */
22
+ export const funnelExperiments: FunnelExperimentConfig[] = [];
31
23
 
32
24
  export const defaultFunnelStepId: FunnelStepId = funnelStepSequence[0];
33
25
 
@@ -2,6 +2,8 @@
2
2
 
3
3
  import {
4
4
  computeRenderSuspended,
5
+ toManifestExperiments,
6
+ useFunnelRuntimeConfig,
5
7
  useFunnelFlowController as useSharedFunnelFlowController,
6
8
  } from '@funnelsgrove/runtime';
7
9
  import { publicAnalyticsSdk } from '@funnelsgrove/analytics';
@@ -32,6 +34,18 @@ export function useFunnelFlowController({
32
34
  initialStepId = defaultFunnelStepId,
33
35
  lockToInitialStep = false,
34
36
  }: FunnelFlowControllerInput = {}) {
37
+ const publishedRuntime = useFunnelRuntimeConfig();
38
+ const activeFunnelExperiments = publishedRuntime.config
39
+ ? toManifestExperiments(publishedRuntime.config.experiments).map((experiment) => ({
40
+ ...experiment,
41
+ stepId: experiment.stepId as FunnelStepId,
42
+ variants: experiment.variants.map((variant) => ({
43
+ ...variant,
44
+ routeToStepId: variant.routeToStepId as FunnelStepId,
45
+ })),
46
+ }))
47
+ : funnelExperiments;
48
+
35
49
  return useSharedFunnelFlowController<FunnelStepId>({
36
50
  analytics: publicAnalyticsSdk,
37
51
  stepContractVersion: funnelManifest.stepContractVersion,
@@ -41,7 +55,7 @@ export function useFunnelFlowController({
41
55
  stepSequence: funnelStepSequence,
42
56
  stepById,
43
57
  stepComponentById,
44
- funnelExperiments,
58
+ funnelExperiments: activeFunnelExperiments,
45
59
  getPathForStep,
46
60
  getStepIdFromPath,
47
61
  getSequentialNextStepId,