@funnelsgrove/cli 0.1.14 → 0.1.18

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 (293) hide show
  1. package/contracts/step-contract-v2.json +733 -0
  2. package/dist/analyticsOutput.d.ts +30 -15
  3. package/dist/analyticsOutput.js +45 -28
  4. package/dist/apiClient.d.ts +85 -0
  5. package/dist/apiClient.js +225 -8
  6. package/dist/cli.d.ts +149 -1
  7. package/dist/cli.js +1232 -71
  8. package/dist/diagnosticOutput.d.ts +42 -0
  9. package/dist/diagnosticOutput.js +83 -0
  10. package/dist/docsOutput.d.ts +19 -0
  11. package/dist/docsOutput.js +73 -0
  12. package/dist/docsTransaction.d.ts +81 -0
  13. package/dist/docsTransaction.js +1755 -0
  14. package/dist/funnelValidation.d.ts +14 -0
  15. package/dist/funnelValidation.js +297 -0
  16. package/dist/localSync.d.ts +79 -0
  17. package/dist/localSync.js +428 -53
  18. package/dist/projectValidator.d.ts +27 -0
  19. package/dist/projectValidator.js +208 -0
  20. package/dist/reskin.js +27 -1
  21. package/dist/stepContractMigration.d.ts +71 -0
  22. package/dist/stepContractMigration.js +1323 -0
  23. package/dist/templateDocs.js +8 -25
  24. package/docs-release-history.json +66 -0
  25. package/legacy-docs-catalog.json +1012 -0
  26. package/package.json +13 -4
  27. package/template_docs/.funnelsgrove-docs.json +144 -0
  28. package/template_docs/AGENTS.md +30 -58
  29. package/template_docs/CLAUDE.md +0 -2
  30. package/template_docs/docs/funnelsgrove/START-HERE.md +73 -0
  31. package/template_docs/docs/funnelsgrove/contracts/analytics-events.md +59 -0
  32. package/template_docs/docs/funnelsgrove/contracts/content-answers.md +44 -0
  33. package/template_docs/docs/funnelsgrove/contracts/flow-routing.md +41 -0
  34. package/template_docs/docs/funnelsgrove/contracts/payments.md +42 -0
  35. package/template_docs/docs/funnelsgrove/contracts/step-metadata.md +57 -0
  36. package/template_docs/docs/funnelsgrove/migrations/step-contract-v3.md +39 -0
  37. package/template_docs/docs/funnelsgrove/qa/analytics.md +43 -0
  38. package/template_docs/docs/funnelsgrove/qa/local.md +42 -0
  39. package/template_docs/docs/funnelsgrove/qa/paywall-checkout.md +55 -0
  40. package/template_docs/docs/funnelsgrove/qa/publish.md +32 -0
  41. package/template_docs/docs/funnelsgrove/recipes/add-experiment.md +34 -0
  42. package/template_docs/docs/funnelsgrove/recipes/add-step.md +33 -0
  43. package/template_docs/docs/funnelsgrove/recipes/edit-step.md +31 -0
  44. package/template_docs/docs/funnelsgrove/steps/INDEX.md +26 -0
  45. package/template_docs/docs/funnelsgrove/steps/cancellation_offer.md +112 -0
  46. package/template_docs/docs/funnelsgrove/steps/checkout.md +120 -0
  47. package/template_docs/docs/funnelsgrove/steps/form_input.md +115 -0
  48. package/template_docs/docs/funnelsgrove/steps/intro_hero.md +110 -0
  49. package/template_docs/docs/funnelsgrove/steps/multi_select_choice.md +123 -0
  50. package/template_docs/docs/funnelsgrove/steps/paywall_offer.md +115 -0
  51. package/template_docs/docs/funnelsgrove/steps/progress_interstitial.md +111 -0
  52. package/template_docs/docs/funnelsgrove/steps/purchase_completed.md +117 -0
  53. package/template_docs/docs/funnelsgrove/steps/single_step_choice.md +122 -0
  54. package/template_docs/docs/funnelsgrove/steps/single_step_choice_emoji.md +122 -0
  55. package/template_docs/docs/funnelsgrove/steps/social_proof.md +110 -0
  56. package/template_docs/docs/funnelsgrove/steps/subscription_handoff.md +113 -0
  57. package/template_docs/docs/funnelsgrove/steps/subscription_management.md +112 -0
  58. package/template_docs/docs/funnelsgrove/steps/summary_confirmation.md +110 -0
  59. package/template_docs/docs/funnelsgrove/steps/upsell_offer.md +112 -0
  60. package/template_docs/docs/funnelsgrove/steps/value_prop_story.md +110 -0
  61. package/template_docs/funnel-docs.config.json +43 -0
  62. package/template_scaffold/.env.example +41 -0
  63. package/template_scaffold/.funnelsgrove-contract-tools.json +19 -0
  64. package/template_scaffold/.funnelsgrove-docs.json +144 -0
  65. package/template_scaffold/.funnelsgrove-scaffold.json +1078 -0
  66. package/template_scaffold/AGENTS.md +42 -0
  67. package/template_scaffold/CLAUDE.md +1 -0
  68. package/template_scaffold/PLAN.md +81 -0
  69. package/template_scaffold/PRODUCT_SENSE.md +28 -0
  70. package/template_scaffold/context/build-context.md +34 -0
  71. package/template_scaffold/docs/AGENT_FUNNEL_IMPLEMENTATION_SPEC.md +5 -0
  72. package/template_scaffold/docs/ARCHITECTURE.md +5 -0
  73. package/template_scaffold/docs/FLOW_CONFIG_AND_ROUTING.md +5 -0
  74. package/template_scaffold/docs/PAYWALL_AND_PAYMENTS.md +5 -0
  75. package/template_scaffold/docs/README.md +5 -0
  76. package/template_scaffold/docs/funnelsgrove/START-HERE.md +73 -0
  77. package/template_scaffold/docs/funnelsgrove/contracts/analytics-events.md +59 -0
  78. package/template_scaffold/docs/funnelsgrove/contracts/content-answers.md +44 -0
  79. package/template_scaffold/docs/funnelsgrove/contracts/flow-routing.md +41 -0
  80. package/template_scaffold/docs/funnelsgrove/contracts/payments.md +42 -0
  81. package/template_scaffold/docs/funnelsgrove/contracts/step-metadata.md +57 -0
  82. package/template_scaffold/docs/funnelsgrove/migrations/step-contract-v3.md +39 -0
  83. package/template_scaffold/docs/funnelsgrove/qa/analytics.md +43 -0
  84. package/template_scaffold/docs/funnelsgrove/qa/local.md +42 -0
  85. package/template_scaffold/docs/funnelsgrove/qa/paywall-checkout.md +55 -0
  86. package/template_scaffold/docs/funnelsgrove/qa/publish.md +32 -0
  87. package/template_scaffold/docs/funnelsgrove/recipes/add-experiment.md +34 -0
  88. package/template_scaffold/docs/funnelsgrove/recipes/add-step.md +33 -0
  89. package/template_scaffold/docs/funnelsgrove/recipes/edit-step.md +31 -0
  90. package/template_scaffold/docs/funnelsgrove/steps/INDEX.md +26 -0
  91. package/template_scaffold/docs/funnelsgrove/steps/cancellation_offer.md +112 -0
  92. package/template_scaffold/docs/funnelsgrove/steps/checkout.md +120 -0
  93. package/template_scaffold/docs/funnelsgrove/steps/form_input.md +115 -0
  94. package/template_scaffold/docs/funnelsgrove/steps/intro_hero.md +110 -0
  95. package/template_scaffold/docs/funnelsgrove/steps/multi_select_choice.md +123 -0
  96. package/template_scaffold/docs/funnelsgrove/steps/paywall_offer.md +115 -0
  97. package/template_scaffold/docs/funnelsgrove/steps/progress_interstitial.md +111 -0
  98. package/template_scaffold/docs/funnelsgrove/steps/purchase_completed.md +117 -0
  99. package/template_scaffold/docs/funnelsgrove/steps/single_step_choice.md +122 -0
  100. package/template_scaffold/docs/funnelsgrove/steps/single_step_choice_emoji.md +122 -0
  101. package/template_scaffold/docs/funnelsgrove/steps/social_proof.md +110 -0
  102. package/template_scaffold/docs/funnelsgrove/steps/subscription_handoff.md +113 -0
  103. package/template_scaffold/docs/funnelsgrove/steps/subscription_management.md +112 -0
  104. package/template_scaffold/docs/funnelsgrove/steps/summary_confirmation.md +110 -0
  105. package/template_scaffold/docs/funnelsgrove/steps/upsell_offer.md +112 -0
  106. package/template_scaffold/docs/funnelsgrove/steps/value_prop_story.md +110 -0
  107. package/template_scaffold/docs/references/funnel-sdk-user-answers.md +5 -0
  108. package/template_scaffold/eslint.config.mjs +16 -0
  109. package/template_scaffold/funnel-agent-docs.test.ts +699 -0
  110. package/template_scaffold/funnel-docs.config.json +43 -0
  111. package/template_scaffold/funnel.config.json +5 -0
  112. package/template_scaffold/gitignore.template +5 -0
  113. package/template_scaffold/next-env.d.ts +6 -0
  114. package/template_scaffold/next.config.ts +53 -0
  115. package/template_scaffold/package-lock.json +7737 -0
  116. package/template_scaffold/package.json +37 -0
  117. package/template_scaffold/package.test.ts +23 -0
  118. package/template_scaffold/public/claimbee/bee.png +0 -0
  119. package/template_scaffold/public/claimbee/device-iphone.png +0 -0
  120. package/template_scaffold/public/claimbee/device-macbook.png +0 -0
  121. package/template_scaffold/public/claimbee/device-none.png +0 -0
  122. package/template_scaffold/public/claimbee/device-watch.png +0 -0
  123. package/template_scaffold/public/claimbee/glow.svg +3 -0
  124. package/template_scaffold/public/claimbee/steps-sprite-alt.png +0 -0
  125. package/template_scaffold/public/claimbee/steps-sprite-default.png +0 -0
  126. package/template_scaffold/public/claimbee/testimonial-avatar.png +0 -0
  127. package/template_scaffold/public/claimbee/testimonial-star.svg +3 -0
  128. package/template_scaffold/public/paywall/ai-claim-assistant.png +0 -0
  129. package/template_scaffold/public/paywall/claim-history.png +0 -0
  130. package/template_scaffold/public/paywall/clear-progress.png +0 -0
  131. package/template_scaffold/public/paywall/hero.png +0 -0
  132. package/template_scaffold/public/paywall/money-alerts.png +0 -0
  133. package/template_scaffold/public/paywall/money-back-badge.png +0 -0
  134. package/template_scaffold/public/paywall/paywall-image.png +0 -0
  135. package/template_scaffold/public/paywall/r1.png +0 -0
  136. package/template_scaffold/public/paywall/r2.png +0 -0
  137. package/template_scaffold/public/paywall/r3.png +0 -0
  138. package/template_scaffold/public/paywall/r4.png +0 -0
  139. package/template_scaffold/public/paywall/r5.png +0 -0
  140. package/template_scaffold/public/paywall/r6.png +0 -0
  141. package/template_scaffold/public/paywall/special-offer-gift.png +0 -0
  142. package/template_scaffold/public/please/bee.png +0 -0
  143. package/template_scaffold/public/please/device-iphone.png +0 -0
  144. package/template_scaffold/public/please/device-macbook.png +0 -0
  145. package/template_scaffold/public/please/device-none.png +0 -0
  146. package/template_scaffold/public/please/device-watch.png +0 -0
  147. package/template_scaffold/public/please/glow.svg +3 -0
  148. package/template_scaffold/public/please/steps-sprite-alt.png +0 -0
  149. package/template_scaffold/public/please/steps-sprite-default.png +0 -0
  150. package/template_scaffold/public/please/testimonial-avatar.png +0 -0
  151. package/template_scaffold/public/please/testimonial-star.svg +3 -0
  152. package/template_scaffold/public/upsell/upgrade-hero.png +0 -0
  153. package/template_scaffold/src/app/[stepId]/page.test.tsx +59 -0
  154. package/template_scaffold/src/app/[stepId]/page.tsx +62 -0
  155. package/template_scaffold/src/app/globals.css +49 -0
  156. package/template_scaffold/src/app/layout.test.ts +26 -0
  157. package/template_scaffold/src/app/layout.tsx +61 -0
  158. package/template_scaffold/src/app/page.tsx +8 -0
  159. package/template_scaffold/src/app/sdk/page.tsx +673 -0
  160. package/template_scaffold/src/app/subscription-dashboard/page.tsx +274 -0
  161. package/template_scaffold/src/assets/claimbee/bee.png +0 -0
  162. package/template_scaffold/src/assets/claimbee/device-iphone.png +0 -0
  163. package/template_scaffold/src/assets/claimbee/device-macbook.png +0 -0
  164. package/template_scaffold/src/assets/claimbee/device-none.png +0 -0
  165. package/template_scaffold/src/assets/claimbee/device-watch.png +0 -0
  166. package/template_scaffold/src/assets/claimbee/glow.svg +3 -0
  167. package/template_scaffold/src/assets/claimbee/steps-sprite-alt.png +0 -0
  168. package/template_scaffold/src/assets/claimbee/steps-sprite-default.png +0 -0
  169. package/template_scaffold/src/assets/claimbee/testimonial-avatar.png +0 -0
  170. package/template_scaffold/src/assets/claimbee/testimonial-star.svg +3 -0
  171. package/template_scaffold/src/components/FunnelEditorPanel.tsx +194 -0
  172. package/template_scaffold/src/components/FunnelFlow.test.tsx +17 -0
  173. package/template_scaffold/src/components/FunnelFlow.tsx +114 -0
  174. package/template_scaffold/src/components/FunnelStepImage.test.ts +15 -0
  175. package/template_scaffold/src/components/FunnelStepImage.tsx +28 -0
  176. package/template_scaffold/src/components/step/FunnelStepHeader.tsx +35 -0
  177. package/template_scaffold/src/config/billing.plans.test.ts +143 -0
  178. package/template_scaffold/src/config/billing.plans.ts +69 -0
  179. package/template_scaffold/src/config/billing.test.plans.ts +32 -0
  180. package/template_scaffold/src/config/experiments.generated.ts +4 -0
  181. package/template_scaffold/src/config/experiments.test.ts +12 -0
  182. package/template_scaffold/src/config/experiments.ts +9 -0
  183. package/template_scaffold/src/config/funnel.config.ts +12 -0
  184. package/template_scaffold/src/config/funnel.manifest.test.ts +166 -0
  185. package/template_scaffold/src/config/funnel.manifest.ts +139 -0
  186. package/template_scaffold/src/config/offer-sets.generated.ts +4 -0
  187. package/template_scaffold/src/contract/funnel-project.validation.test.ts +80 -0
  188. package/template_scaffold/src/contract/funnel-project.validation.ts +44 -0
  189. package/template_scaffold/src/contract/funnel-validator.vite.config.ts +11 -0
  190. package/template_scaffold/src/contract/golden/cancellation_offer.fixture.ts +29 -0
  191. package/template_scaffold/src/contract/golden/checkout.fixture.ts +37 -0
  192. package/template_scaffold/src/contract/golden/form_input.fixture.ts +28 -0
  193. package/template_scaffold/src/contract/golden/golden-fixtures.test.ts +190 -0
  194. package/template_scaffold/src/contract/golden/intro_hero.fixture.ts +27 -0
  195. package/template_scaffold/src/contract/golden/multi_select_choice.fixture.ts +37 -0
  196. package/template_scaffold/src/contract/golden/paywall_offer.fixture.ts +30 -0
  197. package/template_scaffold/src/contract/golden/progress_interstitial.fixture.ts +28 -0
  198. package/template_scaffold/src/contract/golden/purchase_completed.fixture.ts +33 -0
  199. package/template_scaffold/src/contract/golden/single_step_choice.fixture.ts +36 -0
  200. package/template_scaffold/src/contract/golden/single_step_choice_emoji.fixture.ts +36 -0
  201. package/template_scaffold/src/contract/golden/social_proof.fixture.ts +27 -0
  202. package/template_scaffold/src/contract/golden/subscription_handoff.fixture.ts +30 -0
  203. package/template_scaffold/src/contract/golden/subscription_management.fixture.ts +29 -0
  204. package/template_scaffold/src/contract/golden/summary_confirmation.fixture.ts +27 -0
  205. package/template_scaffold/src/contract/golden/upsell_offer.fixture.ts +29 -0
  206. package/template_scaffold/src/contract/golden/value_prop_story.fixture.ts +27 -0
  207. package/template_scaffold/src/contract/validate-funnel.cli.test.ts +180 -0
  208. package/template_scaffold/src/contract/validate-funnel.cli.ts +199 -0
  209. package/template_scaffold/src/runtime/checkout-runtime-config.test.ts +60 -0
  210. package/template_scaffold/src/runtime/checkout-runtime-config.ts +26 -0
  211. package/template_scaffold/src/runtime/funnel-runtime.test.ts +25 -0
  212. package/template_scaffold/src/runtime/funnel-runtime.ts +118 -0
  213. package/template_scaffold/src/runtime/step-content-context.test.ts +38 -0
  214. package/template_scaffold/src/runtime/step-content-context.ts +65 -0
  215. package/template_scaffold/src/runtime/step-registry.ts +66 -0
  216. package/template_scaffold/src/runtime/step-variable-values.ts +31 -0
  217. package/template_scaffold/src/runtime/use-funnel-flow-controller.integration.test.ts +203 -0
  218. package/template_scaffold/src/runtime/use-funnel-flow-controller.test.ts +76 -0
  219. package/template_scaffold/src/runtime/use-funnel-flow-controller.ts +53 -0
  220. package/template_scaffold/src/steps/content/email-capture.content.ts +29 -0
  221. package/template_scaffold/src/steps/content/manage-subscription.content.ts +62 -0
  222. package/template_scaffold/src/steps/content/paywall.content.ts +191 -0
  223. package/template_scaffold/src/steps/content/step-01.content.ts +23 -0
  224. package/template_scaffold/src/steps/content/step-02.content.ts +77 -0
  225. package/template_scaffold/src/steps/content/step-03.content.ts +40 -0
  226. package/template_scaffold/src/steps/content/step-04.content.ts +49 -0
  227. package/template_scaffold/src/steps/content/step-32-paywall.content.ts +352 -0
  228. package/template_scaffold/src/steps/content/subscription-started.content.ts +68 -0
  229. package/template_scaffold/src/steps/editor/email-capture.editor.ts +58 -0
  230. package/template_scaffold/src/steps/editor/manage-subscription.editor.ts +179 -0
  231. package/template_scaffold/src/steps/editor/paywall.editor.ts +232 -0
  232. package/template_scaffold/src/steps/editor/step-01.editor.ts +22 -0
  233. package/template_scaffold/src/steps/editor/step-02.editor.ts +41 -0
  234. package/template_scaffold/src/steps/editor/step-03.editor.ts +58 -0
  235. package/template_scaffold/src/steps/editor/step-04.editor.ts +98 -0
  236. package/template_scaffold/src/steps/editor/step-32-paywall.editor.ts +478 -0
  237. package/template_scaffold/src/steps/editor/subscription-started.editor.ts +131 -0
  238. package/template_scaffold/src/steps/index.ts +25 -0
  239. package/template_scaffold/src/steps/paywall-checkout-options.test.ts +104 -0
  240. package/template_scaffold/src/steps/paywall-checkout-options.ts +130 -0
  241. package/template_scaffold/src/steps/pricing/paywall-b.pricing.ts +12 -0
  242. package/template_scaffold/src/steps/pricing/paywall.pricing.ts +12 -0
  243. package/template_scaffold/src/steps/pricing/shared-plan-order.ts +17 -0
  244. package/template_scaffold/src/steps/pricing/upsell-form.pricing.ts +16 -0
  245. package/template_scaffold/src/steps/shared-header-contract.test.ts +27 -0
  246. package/template_scaffold/src/steps/step-01.test.ts +38 -0
  247. package/template_scaffold/src/steps/step-01.tsx +108 -0
  248. package/template_scaffold/src/steps/step-02.test.ts +43 -0
  249. package/template_scaffold/src/steps/step-02.tsx +140 -0
  250. package/template_scaffold/src/steps/step-03.test.ts +15 -0
  251. package/template_scaffold/src/steps/step-03.tsx +262 -0
  252. package/template_scaffold/src/steps/step-04.test.ts +32 -0
  253. package/template_scaffold/src/steps/step-04.tsx +673 -0
  254. package/template_scaffold/src/steps/step-31-email-capture.test.ts +22 -0
  255. package/template_scaffold/src/steps/step-31-email-capture.tsx +195 -0
  256. package/template_scaffold/src/steps/step-32-paywall.test.ts +770 -0
  257. package/template_scaffold/src/steps/step-32-paywall.tsx +2253 -0
  258. package/template_scaffold/src/steps/step-32b-paywall-b.tsx +35 -0
  259. package/template_scaffold/src/steps/step-33-subscription-started.test.ts +26 -0
  260. package/template_scaffold/src/steps/step-33-subscription-started.tsx +86 -0
  261. package/template_scaffold/src/steps/step-34-upsell-form.test.ts +27 -0
  262. package/template_scaffold/src/steps/step-35-manage-subscription.tsx +46 -0
  263. package/template_scaffold/src/steps/step-content.registry.ts +45 -0
  264. package/template_scaffold/src/steps/step-image-migration.test.ts +20 -0
  265. package/template_scaffold/src/steps/styles/index.css +2 -0
  266. package/template_scaffold/src/steps/styles/shared/final-flow-shared.css +55 -0
  267. package/template_scaffold/src/steps/styles/shared/funnel-base.css +543 -0
  268. package/template_scaffold/src/steps/styles/shared/info-shared.css +139 -0
  269. package/template_scaffold/src/steps/styles/shared/progress-shared.css +117 -0
  270. package/template_scaffold/src/steps/styles/shared/question-shared.css +76 -0
  271. package/template_scaffold/src/steps/styles/shared/responsive-container.css +176 -0
  272. package/template_scaffold/src/steps/types.ts +7 -0
  273. package/template_scaffold/src/steps/use-claimbee-step-content.ts +15 -0
  274. package/template_scaffold/src/theme/theme.ts +81 -0
  275. package/template_scaffold/tsconfig.json +38 -0
  276. package/template_scaffold/vitest.config.ts +32 -0
  277. package/template_validation/.funnelsgrove-contract-tools.json +19 -0
  278. package/template_validation/src/contract/funnel-project.validation.ts +44 -0
  279. package/template_validation/src/contract/funnel-validator.vite.config.ts +11 -0
  280. package/template_validation/src/contract/validate-funnel.cli.ts +199 -0
  281. package/template_docs/docs/ab-experiments.md +0 -55
  282. package/template_docs/docs/analytics.md +0 -65
  283. package/template_docs/docs/editing-flow.md +0 -125
  284. package/template_docs/docs/editing-step.md +0 -125
  285. package/template_docs/docs/editor-and-content.md +0 -58
  286. package/template_docs/docs/funnel-runtime-architecture.md +0 -125
  287. package/template_docs/docs/meta-pixel-conversions-api.md +0 -50
  288. package/template_docs/docs/payment-plans-and-discounts.md +0 -106
  289. package/template_docs/docs/publishing-and-versioning.md +0 -117
  290. package/template_docs/docs/qa-checklist.md +0 -80
  291. package/template_docs/docs/sdk-api-endpoints.md +0 -78
  292. package/template_docs/docs/step-ui-guidelines.md +0 -110
  293. package/template_docs/docs/theme.md +0 -35
@@ -0,0 +1,115 @@
1
+ # `form_input`
2
+
3
+ ## Use when / do not use when
4
+
5
+ Use for non-choice structured input. The reserved email-capture screen also uses this type. Do not use for option lists or payment-entry forms.
6
+
7
+ ## Exact metadata contract
8
+
9
+ <!-- funnelsgrove:generated:start contract-v3/step/form_input -->
10
+ ### Generated contract facts — do not edit
11
+
12
+ - Step contract version: `3`
13
+ - Contract hash: `d761e91d5ac6ff9e72c6d49c5bcd014270912473a66f99c49d726c65998085cf`
14
+ - Type: `form_input`
15
+ - Family: `form`
16
+ - Description: Non-choice structured input, including reserved email capture.
17
+ - Action owner: `either`
18
+ - Analytics role: _none_
19
+ - Terminal: `false`
20
+ - Required kind: _none_
21
+ - Allowed kinds: _None_
22
+ - Allowed semantic events: `email_captured`
23
+ - Forbidden capabilities: `choice-write`, `checkout`, `subscription-management`, `subscription-handoff`, `hardcoded-navigation`, `provider-direct-analytics`, `browser-registration-completed`
24
+
25
+ #### Choice contract
26
+
27
+ _Not a choice type._
28
+
29
+ #### Automatic lifecycle events
30
+
31
+ `first_step_clicked`, `first_step_viewed`, `funnel_started`, `step_completed`, `step_exited`, `step_started`
32
+
33
+ #### Approved capability owners
34
+
35
+ - `email-capture` must be owned by `submitEmailCapture`.
36
+
37
+ #### Reserved identities
38
+
39
+ - `email-capture`: id=`email-capture`, name=`email-capture`; type=`form_input`; unique=`true`
40
+
41
+ #### Golden fixture source
42
+
43
+ ```ts
44
+ import type { FunnelManifestStep, FunnelStepMeta } from '@funnelsgrove/runtime';
45
+
46
+ const manifestStep = {
47
+ id: 'email-capture',
48
+ name: 'email-capture',
49
+ path: '/email-capture',
50
+ filePath: 'src/steps/step-email-capture.tsx',
51
+ componentKey: 'stepEmailCapture',
52
+ type: 'form_input',
53
+ title: 'Your email',
54
+ } as const satisfies FunnelManifestStep;
55
+
56
+ const componentMeta = {
57
+ id: 'email-capture',
58
+ name: 'email-capture',
59
+ type: 'form_input',
60
+ figmaNodeId: 'golden:form-input',
61
+ title: 'Your email',
62
+ description: 'Capture an email through the transactional helper.',
63
+ } as const satisfies FunnelStepMeta;
64
+
65
+ export const goldenFixture = {
66
+ stepType: 'form_input',
67
+ manifestStep,
68
+ componentMeta,
69
+ helper: 'submitEmailCapture',
70
+ forbiddenCalls: ['trackLead', 'provider APIs', 'hard-coded paywall navigation'],
71
+ } as const;
72
+ ```
73
+ <!-- funnelsgrove:generated:end contract-v3/step/form_input -->
74
+
75
+ ## Interaction and persisted answer shape
76
+
77
+ Validate local input before submission. For the reserved email screen, pass the email to `submitEmailCapture`; the helper owns persistence, event creation, lifecycle completion, and routing.
78
+
79
+ ## Automatic lifecycle events
80
+
81
+ The controller owns lifecycle events. A failed email request leaves state, lifecycle, and navigation unchanged. Preview makes no API or provider call.
82
+
83
+ ## Required semantic conversions
84
+
85
+ The reserved email screen produces `email_captured` transactionally on the server. Only a newly created conversion may fan out browser Pixel/Google events with the returned event ID.
86
+
87
+ ## Approved shared helpers
88
+
89
+ Use `submitEmailCapture`. Use the manifest flow controller for the next route.
90
+
91
+ ## MUST rules
92
+
93
+ - Keep the reserved email identity exact.
94
+ - Disable duplicate submits while one request is in flight.
95
+ - Complete only after the API succeeds.
96
+
97
+ ## NEVER rules
98
+
99
+ - Never call `trackLead`, PostHog, Meta CAPI, Pixel, Google, or any provider directly.
100
+ - Never generate a browser event ID or hard-code paywall navigation.
101
+ - Never put raw email or other PII in analytics event metadata or logs.
102
+ - Only the server delivery boundary may use raw email as Meta hashing input.
103
+ - PostHog properties must remain non-PII.
104
+
105
+ ## Golden template or fixture
106
+
107
+ See the [embedded canonical fixture](#golden-fixture-source) generated from `form_input.fixture.ts`.
108
+
109
+ ## Validation command
110
+
111
+ Run `fgrove validate`.
112
+
113
+ ## Manual QA and completion criteria
114
+
115
+ Test success, duplicate, API failure, double-submit, preview, and stale-visit cases; verify only a newly created conversion triggers browser fanout.
@@ -0,0 +1,110 @@
1
+ # `intro_hero`
2
+
3
+ ## Use when / do not use when
4
+
5
+ Use for the opening promise or a low-friction entry screen. Do not use for a questionnaire, reveal, offer, or checkout.
6
+
7
+ ## Exact metadata contract
8
+
9
+ <!-- funnelsgrove:generated:start contract-v3/step/intro_hero -->
10
+ ### Generated contract facts — do not edit
11
+
12
+ - Step contract version: `3`
13
+ - Contract hash: `d761e91d5ac6ff9e72c6d49c5bcd014270912473a66f99c49d726c65998085cf`
14
+ - Type: `intro_hero`
15
+ - Family: `content`
16
+ - Description: Opening promise or low-friction entry screen.
17
+ - Action owner: `shared-shell`
18
+ - Analytics role: _none_
19
+ - Terminal: `false`
20
+ - Required kind: _none_
21
+ - Allowed kinds: _None_
22
+ - Allowed semantic events: _None_
23
+ - Forbidden capabilities: `choice-write`, `email-capture`, `checkout`, `subscription-management`, `subscription-handoff`, `hardcoded-navigation`, `provider-direct-analytics`, `browser-registration-completed`
24
+
25
+ #### Choice contract
26
+
27
+ _Not a choice type._
28
+
29
+ #### Automatic lifecycle events
30
+
31
+ `first_step_clicked`, `first_step_viewed`, `funnel_started`, `step_completed`, `step_exited`, `step_started`
32
+
33
+ #### Approved capability owners
34
+
35
+ _No special capability is allowed._
36
+
37
+ #### Reserved identities
38
+
39
+ _No reserved identity for this type._
40
+
41
+ #### Golden fixture source
42
+
43
+ ```ts
44
+ import type { FunnelManifestStep, FunnelStepMeta } from '@funnelsgrove/runtime';
45
+
46
+ const manifestStep = {
47
+ id: 'welcome',
48
+ name: 'welcome',
49
+ path: '/welcome',
50
+ filePath: 'src/steps/step-welcome.tsx',
51
+ componentKey: 'stepWelcome',
52
+ type: 'intro_hero',
53
+ title: 'Welcome',
54
+ } as const satisfies FunnelManifestStep;
55
+
56
+ const componentMeta = {
57
+ id: 'welcome',
58
+ name: 'welcome',
59
+ type: 'intro_hero',
60
+ figmaNodeId: 'golden:intro-hero',
61
+ title: 'Welcome',
62
+ description: 'Opening promise and low-friction entry.',
63
+ } as const satisfies FunnelStepMeta;
64
+
65
+ export const goldenFixture = {
66
+ stepType: 'intro_hero',
67
+ manifestStep,
68
+ componentMeta,
69
+ actionOwner: 'shared-shell',
70
+ } as const;
71
+ ```
72
+ <!-- funnelsgrove:generated:end contract-v3/step/intro_hero -->
73
+
74
+ ## Interaction and persisted answer shape
75
+
76
+ This content screen does not persist an answer. Put its primary progression action in the shared shell.
77
+
78
+ ## Automatic lifecycle events
79
+
80
+ The flow controller owns the applicable automatic view, start, completion, and exit events. `step_engaged` is registered as non-automatic and is not emitted by default. Only an explicit, centralized, tested engagement policy may emit it through the approved controller/helper boundary; step components never emit it manually. Preview suppresses lifecycle delivery.
81
+
82
+ ## Required semantic conversions
83
+
84
+ None. A hero CTA is navigation, not a conversion event.
85
+
86
+ ## Approved shared helpers
87
+
88
+ Use `useFunnel()` only when the view needs controller state; use the shared action bar for normal Continue behavior.
89
+
90
+ ## MUST rules
91
+
92
+ - Keep the component and manifest metadata identical.
93
+ - Keep the opening action low-friction and let the manifest choose the next step.
94
+
95
+ ## NEVER rules
96
+
97
+ - Never write answers or call an analytics provider.
98
+ - Never hard-code the next route.
99
+
100
+ ## Golden template or fixture
101
+
102
+ See the [embedded canonical fixture](#golden-fixture-source) generated from `intro_hero.fixture.ts`.
103
+
104
+ ## Validation command
105
+
106
+ Run `fgrove validate`.
107
+
108
+ ## Manual QA and completion criteria
109
+
110
+ Confirm the screen starts the funnel once, the CTA advances through manifest routing, and back/exit behavior records the correct lifecycle outcome.
@@ -0,0 +1,123 @@
1
+ # `multi_select_choice`
2
+
3
+ ## Use when / do not use when
4
+
5
+ Use when zero or more options can be selected before an explicit Continue action. Do not use for single-selection or implicit selection-to-advance behavior.
6
+
7
+ ## Exact metadata contract
8
+
9
+ <!-- funnelsgrove:generated:start contract-v3/step/multi_select_choice -->
10
+ ### Generated contract facts — do not edit
11
+
12
+ - Step contract version: `3`
13
+ - Contract hash: `d761e91d5ac6ff9e72c6d49c5bcd014270912473a66f99c49d726c65998085cf`
14
+ - Type: `multi_select_choice`
15
+ - Family: `choice`
16
+ - Description: Multiple options are selected before explicit completion.
17
+ - Action owner: `either`
18
+ - Analytics role: _none_
19
+ - Terminal: `false`
20
+ - Required kind: _none_
21
+ - Allowed kinds: _None_
22
+ - Allowed semantic events: _None_
23
+ - Forbidden capabilities: `email-capture`, `checkout`, `subscription-management`, `subscription-handoff`, `hardcoded-navigation`, `provider-direct-analytics`, `browser-registration-completed`
24
+
25
+ #### Choice contract
26
+
27
+ - Cardinality: `many`
28
+ - Presentation: `standard`
29
+ - Completion: `explicit`
30
+ - Allow empty config: `true`
31
+
32
+ #### Automatic lifecycle events
33
+
34
+ `first_step_clicked`, `first_step_viewed`, `funnel_started`, `step_completed`, `step_exited`, `step_started`
35
+
36
+ #### Approved capability owners
37
+
38
+ - `choice-write` must be owned by `useStepChoices`.
39
+
40
+ #### Reserved identities
41
+
42
+ _No reserved identity for this type._
43
+
44
+ #### Golden fixture source
45
+
46
+ ```ts
47
+ import type { FunnelManifestStep, FunnelStepMeta } from '@funnelsgrove/runtime';
48
+
49
+ const choice = { answerKey: 'priorities', allowEmpty: true } as const;
50
+
51
+ const manifestStep = {
52
+ id: 'priorities',
53
+ name: 'priorities',
54
+ path: '/priorities',
55
+ filePath: 'src/steps/step-priorities.tsx',
56
+ componentKey: 'stepPriorities',
57
+ type: 'multi_select_choice',
58
+ choice,
59
+ title: 'Choose your priorities',
60
+ } as const satisfies FunnelManifestStep;
61
+
62
+ const componentMeta = {
63
+ id: 'priorities',
64
+ name: 'priorities',
65
+ type: 'multi_select_choice',
66
+ choice,
67
+ figmaNodeId: 'golden:multi-select-choice',
68
+ title: 'Choose your priorities',
69
+ description: 'Select any priorities that apply.',
70
+ } as const satisfies FunnelStepMeta;
71
+
72
+ export const goldenFixture = {
73
+ stepType: 'multi_select_choice',
74
+ manifestStep,
75
+ componentMeta,
76
+ options: [
77
+ { id: 'sleep', label: 'Better sleep' },
78
+ { id: 'focus', label: 'More focus' },
79
+ ],
80
+ persistedAnswer: ['sleep', 'focus'],
81
+ emptyAnswer: [],
82
+ helper: 'useStepChoices',
83
+ } as const;
84
+ ```
85
+ <!-- funnelsgrove:generated:end contract-v3/step/multi_select_choice -->
86
+
87
+ ## Interaction and persisted answer shape
88
+
89
+ Declare `choice.answerKey` and the intended `allowEmpty` value. Persist an array of stable option IDs; an allowed empty selection is `[]`.
90
+
91
+ ## Automatic lifecycle events
92
+
93
+ `useStepChoices` coordinates array persistence and explicit completion. Preview suppresses provider delivery.
94
+
95
+ ## Required semantic conversions
96
+
97
+ None.
98
+
99
+ ## Approved shared helpers
100
+
101
+ Use `useStepChoices` for toggles, saving, completion, and routing.
102
+
103
+ ## MUST rules
104
+
105
+ - Preserve stable IDs and exact array shape.
106
+ - Make Continue state match `allowEmpty`.
107
+
108
+ ## NEVER rules
109
+
110
+ - Never store comma-separated text, labels, or option objects.
111
+ - Never auto-advance on the first selection.
112
+
113
+ ## Golden template or fixture
114
+
115
+ See the [embedded canonical fixture](#golden-fixture-source) generated from `multi_select_choice.fixture.ts`.
116
+
117
+ ## Validation command
118
+
119
+ Run `fgrove validate`.
120
+
121
+ ## Manual QA and completion criteria
122
+
123
+ Test select, deselect, reload, and empty-state behavior; confirm one ID array persists and explicit Continue completes once.
@@ -0,0 +1,115 @@
1
+ # `paywall_offer`
2
+
3
+ ## Use when / do not use when
4
+
5
+ Use only for a monetization screen that presents purchasable plans. Do not use for a loading, calculation, reveal, or pre-offer bridge.
6
+
7
+ ## Exact metadata contract
8
+
9
+ <!-- funnelsgrove:generated:start contract-v3/step/paywall_offer -->
10
+ ### Generated contract facts — do not edit
11
+
12
+ - Step contract version: `3`
13
+ - Contract hash: `d761e91d5ac6ff9e72c6d49c5bcd014270912473a66f99c49d726c65998085cf`
14
+ - Type: `paywall_offer`
15
+ - Family: `commerce`
16
+ - Description: A monetization offer that presents purchasable plans.
17
+ - Action owner: `step`
18
+ - Analytics role: `paywall`
19
+ - Terminal: `false`
20
+ - Required kind: `paywall`
21
+ - Allowed kinds: `paywall`
22
+ - Allowed semantic events: `checkout_started`, `add_payment_info`, `checkout_completed`
23
+ - Forbidden capabilities: `choice-write`, `email-capture`, `subscription-management`, `subscription-handoff`, `hardcoded-navigation`, `provider-direct-analytics`, `browser-registration-completed`
24
+
25
+ #### Choice contract
26
+
27
+ _Not a choice type._
28
+
29
+ #### Automatic lifecycle events
30
+
31
+ `first_step_clicked`, `first_step_viewed`, `funnel_started`, `step_completed`, `step_exited`, `step_started`
32
+
33
+ #### Approved capability owners
34
+
35
+ - `checkout` must be owned by `named-payment-helpers`.
36
+
37
+ #### Reserved identities
38
+
39
+ - `paywall`: id=`paywall`, name=`paywall`; type=`paywall_offer`; kind=`paywall`; primary=`true`; unique=`true`
40
+ - `paywall-variant`: idPattern=`^paywall-[a-z0-9-]+$`, namePattern=`^paywall-[a-z0-9-]+$`; type=`paywall_offer`; kind=`paywall`; primary=`false`; unique=`false`
41
+
42
+ #### Golden fixture source
43
+
44
+ ```ts
45
+ import type { FunnelManifestStep, FunnelStepMeta } from '@funnelsgrove/runtime';
46
+
47
+ const manifestStep = {
48
+ id: 'paywall',
49
+ name: 'paywall',
50
+ path: '/paywall',
51
+ filePath: 'src/steps/step-paywall.tsx',
52
+ componentKey: 'stepPaywall',
53
+ type: 'paywall_offer',
54
+ kind: 'paywall',
55
+ title: 'Choose your plan',
56
+ } as const satisfies FunnelManifestStep;
57
+
58
+ const componentMeta = {
59
+ id: 'paywall',
60
+ name: 'paywall',
61
+ type: 'paywall_offer',
62
+ kind: 'paywall',
63
+ figmaNodeId: 'golden:paywall-offer',
64
+ title: 'Choose your plan',
65
+ description: 'Present purchasable plans.',
66
+ } as const satisfies FunnelStepMeta;
67
+
68
+ export const goldenFixture = {
69
+ stepType: 'paywall_offer',
70
+ manifestStep,
71
+ componentMeta,
72
+ presentsPurchasablePlans: true,
73
+ semanticHelpers: ['trackCheckoutStarted', 'trackPaymentInfoSubmitted', 'trackCheckoutCompleted'],
74
+ } as const;
75
+ ```
76
+ <!-- funnelsgrove:generated:end contract-v3/step/paywall_offer -->
77
+
78
+ ## Interaction and persisted answer shape
79
+
80
+ Resolve offers and plans through shared payments configuration. Do not persist plan data as a choice answer.
81
+
82
+ ## Automatic lifecycle events
83
+
84
+ The controller owns view/start/completion/exit lifecycle. Closing or leaving without purchase is an exit, not a completion.
85
+
86
+ ## Required semantic conversions
87
+
88
+ Use named helpers for checkout started, payment info, and checkout completed at their real boundaries with one stable event ID.
89
+
90
+ ## Approved shared helpers
91
+
92
+ Use shared plan resolution, checkout, wallet, and `trackCheckoutStarted`, `trackPaymentInfoSubmitted`, and `trackCheckoutCompleted` helpers.
93
+
94
+ ## MUST rules
95
+
96
+ - Keep the primary paywall identity exact; variants use contract-compliant distinct identities.
97
+ - Present at least one purchasable plan.
98
+ - Read prices, discounts, provider IDs, and payment mode from shared configuration.
99
+
100
+ ## NEVER rules
101
+
102
+ - Never classify a reveal/interstitial as a paywall.
103
+ - Never put secrets, provider IDs, amount math, or provider analytics in JSX.
104
+
105
+ ## Golden template or fixture
106
+
107
+ See the [embedded canonical fixture](#golden-fixture-source) generated from `paywall_offer.fixture.ts`.
108
+
109
+ ## Validation command
110
+
111
+ Run `fgrove validate`.
112
+
113
+ ## Manual QA and completion criteria
114
+
115
+ Verify plans and discounts, every payment method, decline/retry, checkout event metadata/deduplication, cancel/exit, and manifest routing after success.
@@ -0,0 +1,111 @@
1
+ # `progress_interstitial`
2
+
3
+ ## Use when / do not use when
4
+
5
+ Use for progress, calculation, loading, reveal, or transition. Do not use for a screen that presents purchasable plans: that is a paywall offer.
6
+
7
+ ## Exact metadata contract
8
+
9
+ <!-- funnelsgrove:generated:start contract-v3/step/progress_interstitial -->
10
+ ### Generated contract facts — do not edit
11
+
12
+ - Step contract version: `3`
13
+ - Contract hash: `d761e91d5ac6ff9e72c6d49c5bcd014270912473a66f99c49d726c65998085cf`
14
+ - Type: `progress_interstitial`
15
+ - Family: `transition`
16
+ - Description: Progress, calculation, loading, reveal, or transition screen.
17
+ - Action owner: `either`
18
+ - Analytics role: _none_
19
+ - Terminal: `false`
20
+ - Required kind: _none_
21
+ - Allowed kinds: _None_
22
+ - Allowed semantic events: _None_
23
+ - Forbidden capabilities: `choice-write`, `email-capture`, `checkout`, `subscription-management`, `subscription-handoff`, `hardcoded-navigation`, `provider-direct-analytics`, `browser-registration-completed`
24
+
25
+ #### Choice contract
26
+
27
+ _Not a choice type._
28
+
29
+ #### Automatic lifecycle events
30
+
31
+ `first_step_clicked`, `first_step_viewed`, `funnel_started`, `step_completed`, `step_exited`, `step_started`
32
+
33
+ #### Approved capability owners
34
+
35
+ _No special capability is allowed._
36
+
37
+ #### Reserved identities
38
+
39
+ _No reserved identity for this type._
40
+
41
+ #### Golden fixture source
42
+
43
+ ```ts
44
+ import type { FunnelManifestStep, FunnelStepMeta } from '@funnelsgrove/runtime';
45
+
46
+ const manifestStep = {
47
+ id: 'building-plan',
48
+ name: 'building-plan',
49
+ path: '/building-plan',
50
+ filePath: 'src/steps/step-building-plan.tsx',
51
+ componentKey: 'stepBuildingPlan',
52
+ type: 'progress_interstitial',
53
+ title: 'Building your plan',
54
+ } as const satisfies FunnelManifestStep;
55
+
56
+ const componentMeta = {
57
+ id: 'building-plan',
58
+ name: 'building-plan',
59
+ type: 'progress_interstitial',
60
+ figmaNodeId: 'golden:progress-interstitial',
61
+ title: 'Building your plan',
62
+ description: 'Show progress while preparing the next screen.',
63
+ } as const satisfies FunnelStepMeta;
64
+
65
+ export const goldenFixture = {
66
+ stepType: 'progress_interstitial',
67
+ manifestStep,
68
+ componentMeta,
69
+ semanticHelpers: [],
70
+ isPaywall: false,
71
+ } as const;
72
+ ```
73
+ <!-- funnelsgrove:generated:end contract-v3/step/progress_interstitial -->
74
+
75
+ ## Interaction and persisted answer shape
76
+
77
+ This transition does not persist a choice answer. An automatic delay or explicit Continue may complete it through the controller.
78
+
79
+ ## Automatic lifecycle events
80
+
81
+ The controller distinguishes completion from abandonment and suppresses provider delivery in preview.
82
+
83
+ ## Required semantic conversions
84
+
85
+ None. A reveal or “your plan is ready” transition is not a paywall or checkout conversion.
86
+
87
+ ## Approved shared helpers
88
+
89
+ Use shared progress/action primitives and manifest routing.
90
+
91
+ ## MUST rules
92
+
93
+ - Keep automatic completion cancellable when the visit becomes stale.
94
+ - Classify by actual interaction, not by funnel position.
95
+
96
+ ## NEVER rules
97
+
98
+ - Never set paywall identity or emit checkout semantics.
99
+ - Never hard-code a paywall destination.
100
+
101
+ ## Golden template or fixture
102
+
103
+ See the [embedded canonical fixture](#golden-fixture-source) generated from `progress_interstitial.fixture.ts`.
104
+
105
+ ## Validation command
106
+
107
+ Run `fgrove validate`.
108
+
109
+ ## Manual QA and completion criteria
110
+
111
+ Confirm timer/manual completion fires once, leaving early records an exit, and no paywall or checkout event appears.
@@ -0,0 +1,117 @@
1
+ # `purchase_completed`
2
+
3
+ ## Use when / do not use when
4
+
5
+ Use for the canonical terminal after an initial subscription or one-time purchase flow. Do not use for a generic handoff, receipt, renewal, or pre-purchase summary. Entering this screen does not prove that the backend verified payment.
6
+
7
+ ## Exact metadata contract
8
+
9
+ <!-- funnelsgrove:generated:start contract-v3/step/purchase_completed -->
10
+ ### Generated contract facts — do not edit
11
+
12
+ - Step contract version: `3`
13
+ - Contract hash: `d761e91d5ac6ff9e72c6d49c5bcd014270912473a66f99c49d726c65998085cf`
14
+ - Type: `purchase_completed`
15
+ - Family: `terminal`
16
+ - Description: The canonical terminal shown after a verified initial purchase.
17
+ - Action owner: `step`
18
+ - Analytics role: `purchase-completed`
19
+ - Terminal: `true`
20
+ - Required kind: `subscription-handoff`
21
+ - Allowed kinds: `subscription-handoff`
22
+ - Allowed semantic events: `funnel_completed`
23
+ - Forbidden capabilities: `choice-write`, `email-capture`, `checkout`, `subscription-management`, `hardcoded-navigation`, `provider-direct-analytics`, `browser-registration-completed`
24
+
25
+ #### Choice contract
26
+
27
+ _Not a choice type._
28
+
29
+ #### Automatic lifecycle events
30
+
31
+ `first_step_clicked`, `first_step_viewed`, `funnel_started`, `step_completed`, `step_exited`, `step_started`
32
+
33
+ #### Approved capability owners
34
+
35
+ - `subscription-handoff` must be owned by `runtime-handoff-helper`.
36
+
37
+ #### Reserved identities
38
+
39
+ - `subscription-started`: id=`subscription-started`, name=`subscription-started`; type=`purchase_completed`; kind=`subscription-handoff`; unique=`true`
40
+
41
+ #### Golden fixture source
42
+
43
+ ```ts
44
+ import type { FunnelContextValue, FunnelManifestStep, FunnelStepMeta } from '@funnelsgrove/runtime';
45
+
46
+ const terminalCompletionHelper = 'completeFunnel' as const satisfies keyof FunnelContextValue;
47
+
48
+ const manifestStep = {
49
+ id: 'subscription-started',
50
+ name: 'subscription-started',
51
+ path: '/subscription-started',
52
+ filePath: 'src/steps/step-subscription-started.tsx',
53
+ componentKey: 'stepSubscriptionStarted',
54
+ type: 'purchase_completed',
55
+ kind: 'subscription-handoff',
56
+ title: 'Purchase completed',
57
+ } as const satisfies FunnelManifestStep;
58
+
59
+ const componentMeta = {
60
+ id: 'subscription-started',
61
+ name: 'subscription-started',
62
+ type: 'purchase_completed',
63
+ kind: 'subscription-handoff',
64
+ figmaNodeId: 'golden:purchase-completed',
65
+ title: 'Purchase completed',
66
+ description: 'Complete the funnel after purchase and continue into the app.',
67
+ } as const satisfies FunnelStepMeta;
68
+
69
+ export const goldenFixture = {
70
+ stepType: 'purchase_completed',
71
+ manifestStep,
72
+ componentMeta,
73
+ helper: 'runtime handoff helper',
74
+ semanticHelpers: [terminalCompletionHelper],
75
+ forbiddenBrowserEvents: ['purchase_completed', 'registration_completed'],
76
+ } as const;
77
+ ```
78
+ <!-- funnelsgrove:generated:end contract-v3/step/purchase_completed -->
79
+
80
+ ## Interaction and persisted answer shape
81
+
82
+ The runtime terminal/handoff helper owns completion and destination construction. No choice answer is persisted.
83
+
84
+ ## Automatic lifecycle events
85
+
86
+ `completeFunnel(stepId)` atomically claims the active terminal visit, emits the normal step completion and one deduplicated `funnel_completed`, then flushes before outbound handoff. Preview/editor still claims the local completion outcome while suppressing persistence and provider delivery.
87
+
88
+ ## Required semantic conversions
89
+
90
+ The browser may request lifecycle-only funnel completion. The business conversion `purchase_completed` is trusted-server-only after the backend verifies a successful initial subscription or one-time payment. `registration_completed` remains a separate later app-link claim.
91
+
92
+ ## Approved shared helpers
93
+
94
+ Use `completeFunnel` through the runtime handoff surface. The reserved terminal passes `completionMode="funnel"` to `SubscriptionHandoffScreen`; the shared component defaults to `completionMode="step"` for non-terminal handoffs.
95
+
96
+ ## MUST rules
97
+
98
+ - Preserve the reserved `subscription-started` identity.
99
+ - Complete the funnel once before the app handoff.
100
+ - Keep terminal completion mode explicit; only the current `purchase_completed` terminal may call `completeFunnel`.
101
+
102
+ ## NEVER rules
103
+
104
+ - Never emit `purchase_completed` in browser code.
105
+ - Never treat terminal reach, `funnel_completed`, a pending payment, or a renewal as purchase verification.
106
+
107
+ ## Golden template or fixture
108
+
109
+ See the [embedded canonical fixture](#golden-fixture-source) generated from `purchase_completed.fixture.ts`.
110
+
111
+ ## Validation command
112
+
113
+ Run `fgrove validate`.
114
+
115
+ ## Manual QA and completion criteria
116
+
117
+ Confirm funnel completion is deduplicated, the trusted purchase event requires backend verification, preview is silent, and the app destination works.