@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,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.
@@ -0,0 +1,122 @@
1
+ # `single_step_choice`
2
+
3
+ ## Use when / do not use when
4
+
5
+ Use when exactly one standard option completes the step on selection. Do not use for emoji-led choices, multi-select, or a separate form submission.
6
+
7
+ ## Exact metadata contract
8
+
9
+ <!-- funnelsgrove:generated:start contract-v3/step/single_step_choice -->
10
+ ### Generated contract facts — do not edit
11
+
12
+ - Step contract version: `3`
13
+ - Contract hash: `d761e91d5ac6ff9e72c6d49c5bcd014270912473a66f99c49d726c65998085cf`
14
+ - Type: `single_step_choice`
15
+ - Family: `choice`
16
+ - Description: Exactly one standard option is selected.
17
+ - Action owner: `step`
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: `one`
28
+ - Presentation: `standard`
29
+ - Completion: `selection`
30
+ - Allow empty config: `false`
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: 'routine' } as const;
50
+
51
+ const manifestStep = {
52
+ id: 'routine',
53
+ name: 'routine',
54
+ path: '/routine',
55
+ filePath: 'src/steps/step-routine.tsx',
56
+ componentKey: 'stepRoutine',
57
+ type: 'single_step_choice',
58
+ choice,
59
+ title: 'Choose a routine',
60
+ } as const satisfies FunnelManifestStep;
61
+
62
+ const componentMeta = {
63
+ id: 'routine',
64
+ name: 'routine',
65
+ type: 'single_step_choice',
66
+ choice,
67
+ figmaNodeId: 'golden:single-step-choice',
68
+ title: 'Choose a routine',
69
+ description: 'Select exactly one routine.',
70
+ } as const satisfies FunnelStepMeta;
71
+
72
+ export const goldenFixture = {
73
+ stepType: 'single_step_choice',
74
+ manifestStep,
75
+ componentMeta,
76
+ options: [
77
+ { id: 'daily', label: 'Every day' },
78
+ { id: 'weekdays', label: 'Weekdays' },
79
+ ],
80
+ persistedAnswer: 'daily',
81
+ helper: 'useStepChoices',
82
+ } as const;
83
+ ```
84
+ <!-- funnelsgrove:generated:end contract-v3/step/single_step_choice -->
85
+
86
+ ## Interaction and persisted answer shape
87
+
88
+ Declare `choice.answerKey` in both metadata copies. Persist one stable option ID as a scalar string, never the label or an object.
89
+
90
+ ## Automatic lifecycle events
91
+
92
+ `useStepChoices` coordinates the answer write with the controller-owned completion lifecycle. Preview keeps provider delivery suppressed.
93
+
94
+ ## Required semantic conversions
95
+
96
+ None. Selecting an option is answer state plus automatic lifecycle completion.
97
+
98
+ ## Approved shared helpers
99
+
100
+ Use `useStepChoices`; let it complete and route after the stable ID is persisted.
101
+
102
+ ## MUST rules
103
+
104
+ - Give every option a stable ID that survives copy changes.
105
+ - Keep the manifest and component `choice.answerKey` identical.
106
+
107
+ ## NEVER rules
108
+
109
+ - Never call answer SDK methods directly from the choice component.
110
+ - Never store a display label, index, or whole option object.
111
+
112
+ ## Golden template or fixture
113
+
114
+ See the [embedded canonical fixture](#golden-fixture-source) generated from `single_step_choice.fixture.ts`.
115
+
116
+ ## Validation command
117
+
118
+ Run `fgrove validate`.
119
+
120
+ ## Manual QA and completion criteria
121
+
122
+ Select every option once; confirm exactly one stable ID is saved and each selection completes/routes only once.