@funnelsgrove/cli 0.1.87 → 0.1.89

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 (121) hide show
  1. package/dist/apiClient.d.ts +1 -0
  2. package/dist/apiClient.js +32 -1
  3. package/funnel-contract-compatibility.json +28 -28
  4. package/package.json +3 -3
  5. package/template_docs/.funnelsgrove-docs.json +3 -3
  6. package/template_docs/docs/funnelsgrove/migrations/step-contract-v3.md +1 -1
  7. package/template_docs/funnel-docs.config.json +1 -1
  8. package/template_scaffold/.funnelsgrove-docs.json +3 -3
  9. package/template_scaffold/.funnelsgrove-scaffold.json +26 -471
  10. package/template_scaffold/PLAN.md +15 -29
  11. package/template_scaffold/context/build-context.md +3 -3
  12. package/template_scaffold/docs/funnelsgrove/migrations/step-contract-v3.md +1 -1
  13. package/template_scaffold/funnel-docs.config.json +1 -1
  14. package/template_scaffold/package-lock.json +9 -9
  15. package/template_scaffold/package.json +2 -2
  16. package/template_scaffold/public/assets/email-capture-preview.svg +5 -0
  17. package/template_scaffold/public/assets/paywall-preview.svg +6 -0
  18. package/template_scaffold/src/app/sdk/page.tsx +14 -26
  19. package/template_scaffold/src/components/FunnelFlow.tsx +8 -1
  20. package/template_scaffold/src/config/billing.plans.ts +71 -21
  21. package/template_scaffold/src/config/funnel.manifest.ts +20 -59
  22. package/template_scaffold/src/runtime/checkout-runtime-config.ts +0 -7
  23. package/template_scaffold/src/runtime/step-registry.ts +0 -28
  24. package/template_scaffold/src/steps/content/step-01.content.ts +4 -9
  25. package/template_scaffold/src/steps/content/step-32-paywall.content.ts +12 -50
  26. package/template_scaffold/src/steps/step-01.tsx +43 -50
  27. package/template_scaffold/src/steps/step-31-email-capture.tsx +18 -1
  28. package/template_scaffold/src/steps/step-32-paywall.tsx +93 -299
  29. package/template_scaffold/src/steps/step-33-subscription-started.tsx +1 -1
  30. package/template_scaffold/src/steps/step-content.registry.ts +1 -26
  31. package/template_scaffold/public/claimbee/bee.png +0 -0
  32. package/template_scaffold/public/claimbee/device-iphone.png +0 -0
  33. package/template_scaffold/public/claimbee/device-macbook.png +0 -0
  34. package/template_scaffold/public/claimbee/device-none.png +0 -0
  35. package/template_scaffold/public/claimbee/device-watch.png +0 -0
  36. package/template_scaffold/public/claimbee/glow.svg +0 -3
  37. package/template_scaffold/public/claimbee/steps-sprite-alt.png +0 -0
  38. package/template_scaffold/public/claimbee/steps-sprite-default.png +0 -0
  39. package/template_scaffold/public/claimbee/testimonial-avatar.png +0 -0
  40. package/template_scaffold/public/claimbee/testimonial-star.svg +0 -3
  41. package/template_scaffold/public/paywall/ai-claim-assistant.png +0 -0
  42. package/template_scaffold/public/paywall/claim-history.png +0 -0
  43. package/template_scaffold/public/paywall/clear-progress.png +0 -0
  44. package/template_scaffold/public/paywall/hero.png +0 -0
  45. package/template_scaffold/public/paywall/money-alerts.png +0 -0
  46. package/template_scaffold/public/paywall/money-back-badge.png +0 -0
  47. package/template_scaffold/public/paywall/paywall-image.png +0 -0
  48. package/template_scaffold/public/paywall/r1.png +0 -0
  49. package/template_scaffold/public/paywall/r2.png +0 -0
  50. package/template_scaffold/public/paywall/r3.png +0 -0
  51. package/template_scaffold/public/paywall/r4.png +0 -0
  52. package/template_scaffold/public/paywall/r5.png +0 -0
  53. package/template_scaffold/public/paywall/r6.png +0 -0
  54. package/template_scaffold/public/paywall/special-offer-gift.png +0 -0
  55. package/template_scaffold/public/please/bee.png +0 -0
  56. package/template_scaffold/public/please/device-iphone.png +0 -0
  57. package/template_scaffold/public/please/device-macbook.png +0 -0
  58. package/template_scaffold/public/please/device-none.png +0 -0
  59. package/template_scaffold/public/please/device-watch.png +0 -0
  60. package/template_scaffold/public/please/glow.svg +0 -3
  61. package/template_scaffold/public/please/steps-sprite-alt.png +0 -0
  62. package/template_scaffold/public/please/steps-sprite-default.png +0 -0
  63. package/template_scaffold/public/please/testimonial-avatar.png +0 -0
  64. package/template_scaffold/public/please/testimonial-star.svg +0 -3
  65. package/template_scaffold/public/upsell/upgrade-hero.png +0 -0
  66. package/template_scaffold/src/assets/claimbee/bee.png +0 -0
  67. package/template_scaffold/src/assets/claimbee/device-iphone.png +0 -0
  68. package/template_scaffold/src/assets/claimbee/device-macbook.png +0 -0
  69. package/template_scaffold/src/assets/claimbee/device-none.png +0 -0
  70. package/template_scaffold/src/assets/claimbee/device-watch.png +0 -0
  71. package/template_scaffold/src/assets/claimbee/glow.svg +0 -3
  72. package/template_scaffold/src/assets/claimbee/steps-sprite-alt.png +0 -0
  73. package/template_scaffold/src/assets/claimbee/steps-sprite-default.png +0 -0
  74. package/template_scaffold/src/assets/claimbee/testimonial-avatar.png +0 -0
  75. package/template_scaffold/src/assets/claimbee/testimonial-star.svg +0 -3
  76. package/template_scaffold/src/components/FunnelStepImage.tsx +0 -28
  77. package/template_scaffold/src/config/offer-sets.generated.ts +0 -4
  78. package/template_scaffold/src/steps/content/manage-subscription.content.ts +0 -62
  79. package/template_scaffold/src/steps/content/paywall.content.ts +0 -191
  80. package/template_scaffold/src/steps/content/step-02.content.ts +0 -77
  81. package/template_scaffold/src/steps/content/step-03.content.ts +0 -40
  82. package/template_scaffold/src/steps/content/step-04.content.ts +0 -49
  83. package/template_scaffold/src/steps/editor/manage-subscription.editor.ts +0 -179
  84. package/template_scaffold/src/steps/editor/paywall.editor.ts +0 -232
  85. package/template_scaffold/src/steps/editor/step-02.editor.ts +0 -41
  86. package/template_scaffold/src/steps/editor/step-03.editor.ts +0 -58
  87. package/template_scaffold/src/steps/editor/step-04.editor.ts +0 -98
  88. package/template_scaffold/src/steps/pricing/paywall-b.pricing.ts +0 -12
  89. package/template_scaffold/src/steps/step-02.tsx +0 -140
  90. package/template_scaffold/src/steps/step-03.tsx +0 -262
  91. package/template_scaffold/src/steps/step-04.tsx +0 -673
  92. package/template_scaffold/src/steps/step-32b-paywall-b.tsx +0 -35
  93. package/template_scaffold/src/steps/step-35-manage-subscription.tsx +0 -46
  94. package/template_scaffold/src/steps/use-claimbee-step-content.ts +0 -15
  95. package/template_scaffold/tests/funnel-agent-docs.test.ts +0 -751
  96. package/template_scaffold/tests/package.test.ts +0 -37
  97. package/template_scaffold/tests/src/app/[stepId]/page.test.tsx +0 -59
  98. package/template_scaffold/tests/src/app/layout.test.ts +0 -26
  99. package/template_scaffold/tests/src/components/FunnelFlow.test.tsx +0 -38
  100. package/template_scaffold/tests/src/components/FunnelStepImage.test.ts +0 -15
  101. package/template_scaffold/tests/src/config/billing.plans.test.ts +0 -143
  102. package/template_scaffold/tests/src/config/funnel.manifest.test.ts +0 -166
  103. package/template_scaffold/tests/src/contract/funnel-project.validation.test.ts +0 -79
  104. package/template_scaffold/tests/src/contract/golden/golden-fixtures.test.ts +0 -190
  105. package/template_scaffold/tests/src/contract/validate-funnel.cli.test.ts +0 -191
  106. package/template_scaffold/tests/src/runtime/checkout-runtime-config.test.ts +0 -60
  107. package/template_scaffold/tests/src/runtime/funnel-runtime.test.ts +0 -25
  108. package/template_scaffold/tests/src/runtime/step-content-context.test.ts +0 -38
  109. package/template_scaffold/tests/src/runtime/use-funnel-flow-controller.integration.test.ts +0 -203
  110. package/template_scaffold/tests/src/runtime/use-funnel-flow-controller.test.ts +0 -76
  111. package/template_scaffold/tests/src/steps/paywall-checkout-options.test.ts +0 -104
  112. package/template_scaffold/tests/src/steps/shared-header-contract.test.ts +0 -29
  113. package/template_scaffold/tests/src/steps/step-01.test.ts +0 -38
  114. package/template_scaffold/tests/src/steps/step-02.test.ts +0 -43
  115. package/template_scaffold/tests/src/steps/step-03.test.ts +0 -15
  116. package/template_scaffold/tests/src/steps/step-04.test.ts +0 -32
  117. package/template_scaffold/tests/src/steps/step-31-email-capture.test.ts +0 -22
  118. package/template_scaffold/tests/src/steps/step-32-paywall.test.ts +0 -773
  119. package/template_scaffold/tests/src/steps/step-33-subscription-started.test.ts +0 -44
  120. package/template_scaffold/tests/src/steps/step-34-upsell-form.test.ts +0 -27
  121. package/template_scaffold/tests/src/steps/step-image-migration.test.ts +0 -20
@@ -16,31 +16,15 @@ Use runtime IDs in order. Keep names human-readable.
16
16
  **Interaction:** _TBD_
17
17
  **Image:** _none | generate | provided_
18
18
 
19
- ### Step 2: step-2 - Qualification
20
- **Goal:** Collect key user context for personalization.
19
+ ### Step 2: email-capture - Unique identity
20
+ **Goal:** Establish one canonical user identity before checkout.
21
21
  **Headline:** _TBD_
22
- **Body:** _TBD_
23
- **CTA:** Continue
24
- **Interaction:** _TBD_
25
- **Image:** _none | generate | provided_
26
-
27
- ### Step 3: step-3 - Processing / Insight
28
- **Goal:** Reinforce progress and maintain momentum.
29
- **Headline:** _TBD_
30
- **Body:** _TBD_
31
- **CTA:** Continue or auto-advance
32
- **Interaction:** _TBD_
33
- **Image:** _none | generate | provided_
34
-
35
- ### Step 4: step-4 - Transformation
36
- **Goal:** Show outcome and prepare for conversion.
37
- **Headline:** _TBD_
38
- **Body:** _TBD_
22
+ **Body:** Explain that this email is used for account access, purchase recognition, and important updates.
39
23
  **CTA:** Continue
40
- **Interaction:** _TBD_
41
- **Image:** _none | generate | provided_
24
+ **Interaction:** Submit through `submitEmailCapture`. A new or unpaid email follows the manifest to `paywall`; an email with an active subscription skips checkout and opens `subscription-started` in returning-subscriber mode.
25
+ **Image:** _none_
42
26
 
43
- ### Step N: paywall - Offer
27
+ ### Step 3: paywall - Offer
44
28
  **Goal:** Convert with clear value, pricing, and trust cues.
45
29
  **Headline:** _TBD_
46
30
  **Body:** _TBD_
@@ -48,26 +32,27 @@ Use runtime IDs in order. Keep names human-readable.
48
32
  **Interaction:** Select plan then purchase.
49
33
  **Image:** _optional_
50
34
 
51
- ### Step N+1: subscription-started - Handoff
52
- **Goal:** Confirm purchase and route user to app/home experience.
35
+ ### Step 4: subscription-started - Purchase / Returning Subscriber Handoff
36
+ **Goal:** Confirm a new purchase or recognize an existing subscriber, then route the user to the app/home experience.
53
37
  **Headline:** _TBD_
54
38
  **Body:** _TBD_
55
39
  **CTA:** _TBD_
56
- **Interaction:** Open app / deep link.
40
+ **Interaction:** Open app / deep link. When `isReturningSubscriber` is true, show the dedicated welcome-back content instead of new-purchase confirmation.
57
41
 
58
42
  ## Funnel Fuel
59
43
  | Screen | Section / role | Feeling | Fuel |
60
44
  | --- | --- | --- | --- |
61
45
 
62
46
  ## Paywall Requirements
63
- - Keep the live `paywall` aligned with ClaimBee Figma node `6992:6210`.
64
- - Preserve the prior implementation as the direct `/paywall-old` route for regression checks.
47
+ - Keep one live `paywall`; add variants only through an explicit experiment.
48
+ - Resolve plans, discounts, and experiment selection through the shared published billing runtime.
49
+ - Keep the starter `default-offer` usable locally with three plans and two discount stages.
65
50
  - Keep visible `Money-back guarantee` copy.
66
- - Keep guarantee badge image: `/paywall/money-back-badge.png`.
67
51
  - Keep pricing/trial copy explicit and easy to scan.
68
52
  - Launch Stripe checkout in a dedicated modal dialog that matches the published checkout design.
69
53
  - In the modal, prioritize Stripe Link and express wallet buttons before the manual card form.
70
54
  - After a successful payment, redirect to `subscription-started` so the app handoff screen appears immediately.
55
+ - Keep `email-capture` before `paywall`; the shared runtime owns canonical email adoption, uniqueness, active-subscription lookup, and returning-subscriber routing.
71
56
 
72
57
  ## CTA Ownership Rules
73
58
  - `FunnelFlow` owns default Continue action bar.
@@ -75,7 +60,8 @@ Use runtime IDs in order. Keep names human-readable.
75
60
  - For yes/no branches, define targets in routing config and use shared in-step controls.
76
61
 
77
62
  ## Validation Checklist
78
- - Step IDs are stable (`step-*`, `paywall`, `subscription-started`).
63
+ - Step IDs are stable (`step-1`, `email-capture`, `paywall`, `subscription-started`).
64
+ - The default graph keeps `email-capture -> paywall`; active subscribers bypass that edge through the shared runtime and land on `subscription-started`.
79
65
  - Routing covers each active step and avoids dead ends.
80
66
  - Paywall trust elements are present.
81
67
  - Mobile layout remains full-screen with safe-area spacing.
@@ -27,8 +27,8 @@
27
27
  - `PLAN.md`: required
28
28
 
29
29
  ## Sequence Contract
30
- - Keep foundation anchors first: `step-1`, `step-2`, `step-3`, `step-4`
31
- - Keep conversion path terminal steps: `paywall`, `subscription-started`
30
+ - Keep the starter route linear: `step-1`, `email-capture`, `paywall`, `subscription-started`.
31
+ - Do not add alternate paywalls or branches unless the funnel explicitly requires them.
32
32
 
33
33
  ## Generation Mode
34
- - `baseline_five_step` by default unless a wider flow is explicitly requested.
34
+ - Use the lightweight four-step starter by default unless a wider flow is explicitly requested.
@@ -17,7 +17,7 @@ Supported read versions: `1`, `2`, `3`. Authoring and publish target version `3`
17
17
 
18
18
  ### Package release order
19
19
 
20
- Release `@funnelsgrove/runtime` `0.7.9` first, then `@funnelsgrove/analytics` `0.1.58`, then `@funnelsgrove/payments` `0.7.3`. Deploy the API and funnel template, then confirm production `/health` reports the new docs identity. Only then publish `@funnelsgrove/cli` `0.1.87`. Publishing packages and deploying production remain separately approved operational actions.
20
+ Release `@funnelsgrove/runtime` `0.7.10` first, then `@funnelsgrove/analytics` `0.1.59`, then `@funnelsgrove/payments` `0.7.3`. Deploy the API and funnel template, then confirm production `/health` reports the new docs identity. Only then publish `@funnelsgrove/cli` `0.1.89`. Publishing packages and deploying production remain separately approved operational actions.
21
21
  <!-- funnelsgrove:generated:end contract-v3/migration/step-contract-v3 -->
22
22
 
23
23
  ## Version-last policy
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
- "bundleVersion": "2.0.76",
3
+ "bundleVersion": "2.0.78",
4
4
  "contractSource": "funnelsgrove-repository://apps/funnel-runtime/contracts/step-contract-v2.json",
5
5
  "fullyGenerated": [
6
6
  ".funnelsgrove-docs.json",
@@ -8,9 +8,9 @@
8
8
  "name": "funnel-template",
9
9
  "version": "0.1.0",
10
10
  "dependencies": {
11
- "@funnelsgrove/analytics": "^0.1.58",
11
+ "@funnelsgrove/analytics": "^0.1.59",
12
12
  "@funnelsgrove/payments": "^0.7.3",
13
- "@funnelsgrove/runtime": "^0.7.9",
13
+ "@funnelsgrove/runtime": "^0.7.10",
14
14
  "@stripe/react-stripe-js": "^5.6.0",
15
15
  "@stripe/stripe-js": "^8.7.0",
16
16
  "lucide-react": "^0.553.0",
@@ -891,11 +891,11 @@
891
891
  }
892
892
  },
893
893
  "node_modules/@funnelsgrove/analytics": {
894
- "version": "0.1.58",
895
- "resolved": "https://registry.npmjs.org/@funnelsgrove/analytics/-/analytics-0.1.58.tgz",
896
- "integrity": "sha512-bTSq7SO7CuicexEXUKIc3WMW7RQV1VI9pQfCOn79E6ygHa/1shoY7GZyzB/9n5Pn2pAOdZzNIy+erPPHaQROQQ==",
894
+ "version": "0.1.59",
895
+ "resolved": "https://registry.npmjs.org/@funnelsgrove/analytics/-/analytics-0.1.59.tgz",
896
+ "integrity": "sha512-VA85CXnWZGmMrDTcSgr6sRjL6NGLGWX14z12br9J1Z3ABZrDUFdCp17xAhv2AFHPpSyK/laOQ8udA3oENWZ2yQ==",
897
897
  "dependencies": {
898
- "@funnelsgrove/runtime": "0.7.9"
898
+ "@funnelsgrove/runtime": "0.7.10"
899
899
  }
900
900
  },
901
901
  "node_modules/@funnelsgrove/payments": {
@@ -913,9 +913,9 @@
913
913
  }
914
914
  },
915
915
  "node_modules/@funnelsgrove/runtime": {
916
- "version": "0.7.9",
917
- "resolved": "https://registry.npmjs.org/@funnelsgrove/runtime/-/runtime-0.7.9.tgz",
918
- "integrity": "sha512-j6T7n2YZ5CaQc1IPc+2HX/gBq7U/rtNKKrBEttMMNtrC/ip1PcLdoj7qBgsyh6dgjpg+eYi6lExr8I1g5gA/ew==",
916
+ "version": "0.7.10",
917
+ "resolved": "https://registry.npmjs.org/@funnelsgrove/runtime/-/runtime-0.7.10.tgz",
918
+ "integrity": "sha512-V1S7kVu1lBLAi49f4ZP+n8Z9ZcINh/g9mQyplE+jG0ic+ct2kdZaB2x9FZ3frvWgg+GEBTiDF4aIAJcc0Z9kzQ==",
919
919
  "dependencies": {
920
920
  "posthog-js": "^1.369.2",
921
921
  "react": "19.2.3",
@@ -12,9 +12,9 @@
12
12
  "validate:funnel": "vite-node --config src/contract/funnel-validator.vite.config.ts src/contract/validate-funnel.cli.ts"
13
13
  },
14
14
  "dependencies": {
15
- "@funnelsgrove/analytics": "^0.1.58",
15
+ "@funnelsgrove/analytics": "^0.1.59",
16
16
  "@funnelsgrove/payments": "^0.7.3",
17
- "@funnelsgrove/runtime": "^0.7.9",
17
+ "@funnelsgrove/runtime": "^0.7.10",
18
18
  "@stripe/react-stripe-js": "^5.6.0",
19
19
  "@stripe/stripe-js": "^8.7.0",
20
20
  "lucide-react": "^0.553.0",
@@ -0,0 +1,5 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="160" height="120" viewBox="0 0 160 120" fill="none">
2
+ <rect width="160" height="120" rx="24" fill="#EEF1FF"/>
3
+ <rect x="28" y="30" width="104" height="60" rx="14" fill="white" stroke="#4361EE" stroke-width="4"/>
4
+ <path d="m34 39 46 34 46-34" stroke="#4361EE" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
5
+ </svg>
@@ -0,0 +1,6 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="160" height="120" viewBox="0 0 160 120" fill="none">
2
+ <rect width="160" height="120" rx="24" fill="#EEF1FF"/>
3
+ <rect x="35" y="21" width="90" height="78" rx="16" fill="white" stroke="#4361EE" stroke-width="4"/>
4
+ <path d="M53 43h54M53 58h38" stroke="#9AA7E8" stroke-width="5" stroke-linecap="round"/>
5
+ <rect x="53" y="73" width="54" height="12" rx="6" fill="#4361EE"/>
6
+ </svg>
@@ -6,19 +6,10 @@ export const metadata: Metadata = {
6
6
  };
7
7
 
8
8
  const userObjectExample = `{
9
- "selectedDevices": ["iphone", "laptop"],
10
9
  "stepChoices": {
11
- "step-1": "yes",
12
- "step-3": "no"
10
+ "step-1": "continue"
13
11
  },
14
- "upsellAccepted": true,
15
- "upsellEmail": "user@example.com",
16
- "upsellSelectedPlanId": "price_yearly_123",
17
- "selectedPaywallPlanId": "price_yearly_123",
18
- "manageSubscriptionHasActive": true,
19
- "manageSubscriptionReason": "cost",
20
- "manageSubscriptionCancelled": false,
21
- "manageSubscriptionCancelledId": ""
12
+ "selectedPaywallPlanId": "secondary"
22
13
  }`;
23
14
 
24
15
  type MethodDoc = {
@@ -37,7 +28,7 @@ const methodDocs: MethodDoc[] = [
37
28
  description: 'Current rendered step id in the runtime shell.',
38
29
  example: `const { activeStepId } = useFunnel();
39
30
 
40
- if (activeStepId === 'step-4') {
31
+ if (activeStepId === 'paywall') {
41
32
  // Step-specific behavior
42
33
  }`,
43
34
  },
@@ -47,7 +38,7 @@ if (activeStepId === 'step-4') {
47
38
  signature: 'answers: FunnelUserAnswers',
48
39
  description: 'Typed answers object persisted across steps.',
49
40
  example: `const { answers } = useFunnel();
50
- const selectedDevices = answers.selectedDevices || [];`,
41
+ const selectedPlanId = answers.selectedPaywallPlanId || null;`,
51
42
  },
52
43
  {
53
44
  id: 'set-answer',
@@ -57,8 +48,7 @@ const selectedDevices = answers.selectedDevices || [];`,
57
48
  description: 'Typed way to write answer values.',
58
49
  example: `const { setAnswer } = useFunnel();
59
50
 
60
- setAnswer('selectedDevices', ['iphone', 'watch']);
61
- setAnswer('upsellAccepted', true);`,
51
+ setAnswer('selectedPaywallPlanId', 'secondary');`,
62
52
  },
63
53
  {
64
54
  id: 'get-answer',
@@ -67,7 +57,7 @@ setAnswer('upsellAccepted', true);`,
67
57
  'getAnswer<K extends keyof FunnelUserAnswers>(key: K): FunnelUserAnswers[K] | undefined',
68
58
  description: 'Typed getter for a single answer key.',
69
59
  example: `const { getAnswer } = useFunnel();
70
- const reason = getAnswer('manageSubscriptionReason');`,
60
+ const selectedPlanId = getAnswer('selectedPaywallPlanId');`,
71
61
  },
72
62
  {
73
63
  id: 'go-next',
@@ -131,9 +121,7 @@ setUser({ ...user, email: 'new-email@example.com' });`,
131
121
  description: 'Manually emit and persist a step completion record.',
132
122
  example: `const { completeStep } = useFunnel();
133
123
 
134
- completeStep('step-2', {
135
- selectedDevices: ['iphone', 'laptop'],
136
- });`,
124
+ completeStep('email-capture');`,
137
125
  },
138
126
  {
139
127
  id: 'complete-funnel',
@@ -232,7 +220,7 @@ const analyticsUseCases: AnalyticsUseCaseDoc[] = [
232
220
 
233
221
  // ...custom async flow...
234
222
 
235
- goToStep('step-7', {
223
+ goToStep('email-capture', {
236
224
  type: 'complete',
237
225
  selected: { uploadCount: files.length },
238
226
  });`,
@@ -283,9 +271,9 @@ const analyticsMethodDocs: AnalyticsMethodDoc[] = [
283
271
  description:
284
272
  'Queues custom events only. Returns null for empty or contract-reserved event names; use their owning FunnelFlow or named helper path.',
285
273
  example: `const eventId = publicAnalyticsSdk.track({
286
- eventType: 'upsell_offer_seen',
287
- stepId: 'step-7',
288
- payload: { offerVersion: 'B' },
274
+ eventType: 'paywall_offer_seen',
275
+ stepId: 'paywall',
276
+ payload: { offerSetId: activeOfferSetId },
289
277
  });`,
290
278
  },
291
279
  {
@@ -302,7 +290,7 @@ goNext(); // FunnelFlow records the current completion and starts the next visit
302
290
  signature: 'trackStepCompleted(input: PublicSdkStepEndInput): string | null',
303
291
  description: 'FunnelFlow-owned adapter method for canonical step_end events; step components must not call it.',
304
292
  example: `const { goToStep } = useFunnel();
305
- goToStep('step-3', {
293
+ goToStep('email-capture', {
306
294
  type: 'complete',
307
295
  selected: { painScore: 7 },
308
296
  });`,
@@ -384,8 +372,8 @@ export default function SdkDocsPage() {
384
372
  <h2>Analytics SDK</h2>
385
373
  <p>
386
374
  Use `publicAnalyticsSdk` from `@funnelsgrove/analytics` for custom analytics events.
387
- It batches events and delivers them directly to PostHog through `navigator.sendBeacon` with
388
- `fetch(keepalive)` fallback.
375
+ It batches events and sends them through the first-party `/ingest` proxy using
376
+ `navigator.sendBeacon`, with `fetch(keepalive)` fallback.
389
377
  </p>
390
378
  <p>
391
379
  Standard step start/complete events are already emitted by `FunnelFlow`. Add custom events only for
@@ -1,15 +1,17 @@
1
1
  'use client';
2
2
 
3
- import { createElement, useMemo, useSyncExternalStore, type ComponentType } from 'react';
3
+ import { createElement, useEffect, useMemo, useSyncExternalStore, type ComponentType } from 'react';
4
4
  import {
5
5
  FunnelProvider,
6
6
  FunnelRuntimeConfigBoundary,
7
7
  PrimaryButton,
8
8
  isPreviewFrameRuntime,
9
9
  runtimePublicConfig,
10
+ scheduleNextStepImagePreload,
10
11
  usePreviewDefinitionOverrideRevision,
11
12
  } from '@funnelsgrove/runtime';
12
13
  import { FunnelEditorPanel, FunnelEditorPanelStyles } from './FunnelEditorPanel';
14
+ import { funnelManifest } from '@/config/funnel.manifest';
13
15
  import type { FunnelStepId } from '@/runtime/funnel-runtime';
14
16
  import { useFunnelFlowController } from '@/runtime/use-funnel-flow-controller';
15
17
 
@@ -68,6 +70,11 @@ function FunnelFlowRuntime({
68
70
  lockToInitialStep,
69
71
  });
70
72
 
73
+ useEffect(
74
+ () => scheduleNextStepImagePreload(funnelManifest, controller.activeStepId),
75
+ [controller.activeStepId],
76
+ );
77
+
71
78
  if (!controller.activeStepComponent || !controller.activeStepMeta || !controller.renderedStepMeta) {
72
79
  return null;
73
80
  }
@@ -5,10 +5,19 @@ import {
5
5
  type BillingDiscountList,
6
6
  type BillingPlanCatalog,
7
7
  } from '@funnelsgrove/payments';
8
+ import {
9
+ type GeneratedOfferSet,
10
+ type ResolvedFunnelRuntimeConfig,
11
+ } from '@funnelsgrove/runtime';
8
12
  import { billingTestPlans } from './billing.test.plans';
9
13
 
10
- const claimbeeLivePlans = {
14
+ const STARTER_OFFER_SET_ID = '11111111-1111-4111-8111-111111111111';
15
+ const STARTER_OFFER_SET_KEY = 'default-offer';
16
+
17
+ const starterLivePlans = {
11
18
  primary: {
19
+ offerSetId: STARTER_OFFER_SET_ID,
20
+ offerSetKey: STARTER_OFFER_SET_KEY,
12
21
  projectPlanId: '32b31f69-5907-4736-89c7-76d3acb38813',
13
22
  title: '1-Week Plan',
14
23
  providerPlanId: 'price_1T9WL2PPRN41Ty94g2NNOgif',
@@ -18,6 +27,8 @@ const claimbeeLivePlans = {
18
27
  checkoutSummaryLabel: '$7.99 today',
19
28
  },
20
29
  secondary: {
30
+ offerSetId: STARTER_OFFER_SET_ID,
31
+ offerSetKey: STARTER_OFFER_SET_KEY,
21
32
  projectPlanId: 'ce0a0022-7bf0-41f2-a707-edc5e6113a07',
22
33
  title: '1-Month Plan',
23
34
  providerPlanId: 'price_1TP0IIPPRN41Ty94npkzVeDR',
@@ -26,8 +37,11 @@ const claimbeeLivePlans = {
26
37
  perDayAmount: '$1.00',
27
38
  amountCents: 2999,
28
39
  checkoutSummaryLabel: '$29.99 today',
40
+ isDefault: true,
29
41
  },
30
42
  tertiary: {
43
+ offerSetId: STARTER_OFFER_SET_ID,
44
+ offerSetKey: STARTER_OFFER_SET_KEY,
31
45
  projectPlanId: 'fb623ebc-c2fd-4e6a-8eb6-b8ed92db1b21',
32
46
  title: '1-Year Plan',
33
47
  providerPlanId: 'price_1T06AEPPRN41Ty94zvVt5xrk',
@@ -38,32 +52,68 @@ const claimbeeLivePlans = {
38
52
  },
39
53
  } satisfies BillingPlanCatalog;
40
54
 
41
- export const claimbeePlansByMode = {
55
+ export const starterPlansByMode = {
42
56
  test: billingTestPlans,
43
- live: claimbeeLivePlans,
57
+ live: starterLivePlans,
44
58
  } satisfies Record<'test' | 'live', BillingPlanCatalog>;
45
59
 
60
+ const starterDiscounts = [
61
+ {
62
+ stage: 'first',
63
+ couponId: 'fg_30_off_one',
64
+ discountPercent: 30,
65
+ durationSeconds: 600,
66
+ previousDiscountPercent: null,
67
+ },
68
+ {
69
+ stage: 'second',
70
+ couponId: 'fg_46_off_once',
71
+ discountPercent: 46,
72
+ durationSeconds: 600,
73
+ previousDiscountPercent: 30,
74
+ },
75
+ ] satisfies BillingDiscountList;
76
+
77
+ export const starterDefaultOfferSet = {
78
+ id: STARTER_OFFER_SET_ID,
79
+ key: STARTER_OFFER_SET_KEY,
80
+ isActive: true,
81
+ metadata: {
82
+ discounts: starterDiscounts.map((discount, position) => ({
83
+ key: discount.stage,
84
+ testProviderCouponId: discount.couponId,
85
+ liveProviderCouponId: discount.couponId,
86
+ position,
87
+ metadata: {
88
+ stage: discount.stage,
89
+ discountPercent: discount.discountPercent,
90
+ durationSeconds: discount.durationSeconds,
91
+ previousDiscountPercent: discount.previousDiscountPercent,
92
+ },
93
+ })),
94
+ },
95
+ items: [
96
+ { funnelPlanKey: 'primary', position: 0, isDefault: false },
97
+ { funnelPlanKey: 'secondary', position: 1, isDefault: true },
98
+ { funnelPlanKey: 'tertiary', position: 2, isDefault: false },
99
+ ],
100
+ } satisfies GeneratedOfferSet;
101
+
102
+ export const starterBillingRuntimeFallback = {
103
+ revisionId: 'starter-template',
104
+ sourceDeploymentId: 'starter-template',
105
+ sourceVersionId: 'starter-template',
106
+ publishedAt: '1970-01-01T00:00:00.000Z',
107
+ offerSets: [starterDefaultOfferSet],
108
+ experiments: [],
109
+ } satisfies ResolvedFunnelRuntimeConfig;
110
+
46
111
  export const billing = {
47
- plans: createRuntimeModeBillingPlanCatalog(claimbeePlansByMode),
48
- discounts: [
49
- {
50
- stage: 'first',
51
- couponId: 'fg_30_off_one',
52
- discountPercent: 30,
53
- durationSeconds: 600,
54
- previousDiscountPercent: null,
55
- },
56
- {
57
- stage: 'second',
58
- couponId: 'fg_46_off_once',
59
- discountPercent: 46,
60
- durationSeconds: 600,
61
- previousDiscountPercent: 30,
62
- },
63
- ] satisfies BillingDiscountList,
112
+ plans: createRuntimeModeBillingPlanCatalog(starterPlansByMode),
113
+ discounts: starterDiscounts,
64
114
  } satisfies BillingConfig;
65
115
 
66
116
  export const billingPlans = billing.plans;
67
117
  export const billingDiscounts = buildBillingDiscountCatalog(billing.discounts);
68
118
  export const billingDiscountList = billing.discounts;
69
- export const billingDiscountStorageKeys = ['claimbee-paywall-discount-v1'] as const;
119
+ export const billingDiscountStorageKeys = ['starter-paywall-discount-v1'] as const;
@@ -11,14 +11,29 @@ export const rawFunnelManifest = {
11
11
  stepContractVersion: CURRENT_STEP_CONTRACT_VERSION,
12
12
  meta: {
13
13
  title: 'Funnel Project',
14
- description: 'Domain-agnostic funnel runtime with graph routing and paywall handoff.',
14
+ description: 'Lightweight starter funnel with email capture and a single paywall.',
15
15
  },
16
16
  viewport: {
17
17
  width: 430,
18
18
  height: 932,
19
19
  },
20
20
  breakpoints: DEFAULT_FUNNEL_BREAKPOINTS,
21
- assets: {},
21
+ assets: {
22
+ emailCapturePreview: {
23
+ type: 'image',
24
+ src: '/assets/email-capture-preview.svg',
25
+ width: 160,
26
+ height: 120,
27
+ preload: 'idle',
28
+ },
29
+ paywallPreview: {
30
+ type: 'image',
31
+ src: '/assets/paywall-preview.svg',
32
+ width: 160,
33
+ height: 120,
34
+ preload: 'idle',
35
+ },
36
+ },
22
37
  steps: [
23
38
  {
24
39
  id: 'step-1',
@@ -29,37 +44,6 @@ export const rawFunnelManifest = {
29
44
  type: 'intro_hero',
30
45
  title: 'Step 1',
31
46
  },
32
- {
33
- id: 'step-2',
34
- path: '/step-2',
35
- filePath: 'src/steps/step-02.tsx',
36
- componentKey: 'stepTwo',
37
- name: 'step-2',
38
- type: 'multi_select_choice',
39
- choice: {
40
- answerKey: 'selectedDevices',
41
- allowEmpty: true,
42
- },
43
- title: 'Step 2',
44
- },
45
- {
46
- id: 'step-3',
47
- path: '/step-3',
48
- filePath: 'src/steps/step-03.tsx',
49
- componentKey: 'stepThree',
50
- name: 'step-3',
51
- type: 'progress_interstitial',
52
- title: 'Step 3',
53
- },
54
- {
55
- id: 'step-4',
56
- path: '/step-4',
57
- filePath: 'src/steps/step-04.tsx',
58
- componentKey: 'stepFour',
59
- name: 'scratchpad-entry',
60
- type: 'progress_interstitial',
61
- title: 'Scratchcard Entry',
62
- },
63
47
  {
64
48
  id: 'email-capture',
65
49
  path: '/email-capture',
@@ -68,6 +52,7 @@ export const rawFunnelManifest = {
68
52
  name: 'email-capture',
69
53
  type: 'form_input',
70
54
  title: 'Email Capture',
55
+ assetIds: ['emailCapturePreview'],
71
56
  },
72
57
  {
73
58
  id: 'paywall',
@@ -78,26 +63,7 @@ export const rawFunnelManifest = {
78
63
  type: 'paywall_offer',
79
64
  kind: 'paywall',
80
65
  title: 'Paywall',
81
- },
82
- {
83
- id: 'paywall-b',
84
- path: '/paywall-b',
85
- filePath: 'src/steps/step-32b-paywall-b.tsx',
86
- componentKey: 'stepPaywallB',
87
- name: 'paywall-b',
88
- type: 'paywall_offer',
89
- kind: 'paywall',
90
- title: 'Paywall B',
91
- },
92
- {
93
- id: 'manage-subscription',
94
- path: '/manage-subscription',
95
- filePath: 'src/steps/step-35-manage-subscription.tsx',
96
- componentKey: 'stepManageSubscription',
97
- name: 'manage-subscription',
98
- type: 'subscription_management',
99
- kind: 'manage-subscription',
100
- title: 'Manage Subscription',
66
+ assetIds: ['paywallPreview'],
101
67
  },
102
68
  {
103
69
  id: 'subscription-started',
@@ -111,14 +77,9 @@ export const rawFunnelManifest = {
111
77
  },
112
78
  ],
113
79
  edgesByStepId: {
114
- 'step-1': [{ toStepId: 'step-2' }],
115
- 'step-2': [{ toStepId: 'step-3' }],
116
- 'step-3': [{ toStepId: 'step-4' }],
117
- 'step-4': [{ toStepId: 'email-capture' }],
80
+ 'step-1': [{ toStepId: 'email-capture' }],
118
81
  'email-capture': [{ toStepId: 'paywall' }],
119
82
  paywall: [{ toStepId: 'subscription-started' }],
120
- 'paywall-b': [{ toStepId: 'subscription-started' }],
121
- 'manage-subscription': [{ toStepId: 'step-1' }],
122
83
  },
123
84
  // Experiments are delivered by the version-bound runtime artifact.
124
85
  experiments: [],
@@ -1,7 +1,6 @@
1
1
  import {
2
2
  resolvePreviewPaymentPublishableKey,
3
3
  runtimePublicConfig,
4
- type RuntimeMode,
5
4
  } from '@funnelsgrove/runtime';
6
5
 
7
6
  const resolveCheckoutFunnelSdkPublishableKey = (): string | null => {
@@ -18,9 +17,3 @@ export const checkoutRuntimeConfig = {
18
17
  funnelId: runtimePublicConfig.funnelId.trim() || null,
19
18
  funnelSdkPublishableKey: resolveCheckoutFunnelSdkPublishableKey(),
20
19
  } as const;
21
-
22
- export const resolveClaimbeeCheckoutMode = (
23
- runtimeMode: RuntimeMode,
24
- ): RuntimeMode => {
25
- return runtimeMode;
26
- };
@@ -1,19 +1,11 @@
1
1
  import type { FunctionComponent } from 'react';
2
- import { StepFour, stepFour } from '@/steps/step-04';
3
2
  import { StepOne, stepOne } from '@/steps/step-01';
4
3
  import { StepEmailCapture, stepEmailCapture } from '@/steps/step-31-email-capture';
5
4
  import { StepPaywall, stepPaywall } from '@/steps/step-32-paywall';
6
- import { StepPaywallB, stepPaywallB } from '@/steps/step-32b-paywall-b';
7
5
  import {
8
6
  StepSubscriptionStarted,
9
7
  stepSubscriptionStarted,
10
8
  } from '@/steps/step-33-subscription-started';
11
- import {
12
- StepManageSubscription,
13
- stepManageSubscription,
14
- } from '@/steps/step-35-manage-subscription';
15
- import { StepThree, stepThree } from '@/steps/step-03';
16
- import { StepTwo, stepTwo } from '@/steps/step-02';
17
9
  import type { FunnelStepComponentKey } from '@/config/funnel.manifest';
18
10
  import type { FunnelStepMeta } from '@funnelsgrove/runtime';
19
11
 
@@ -27,18 +19,6 @@ export const stepRegistry: Record<FunnelStepComponentKey, StepRegistryEntry> = {
27
19
  component: StepOne,
28
20
  meta: stepOne,
29
21
  },
30
- stepTwo: {
31
- component: StepTwo,
32
- meta: stepTwo,
33
- },
34
- stepThree: {
35
- component: StepThree,
36
- meta: stepThree,
37
- },
38
- stepFour: {
39
- component: StepFour,
40
- meta: stepFour,
41
- },
42
22
  stepEmailCapture: {
43
23
  component: StepEmailCapture,
44
24
  meta: stepEmailCapture,
@@ -47,14 +27,6 @@ export const stepRegistry: Record<FunnelStepComponentKey, StepRegistryEntry> = {
47
27
  component: StepPaywall,
48
28
  meta: stepPaywall,
49
29
  },
50
- stepPaywallB: {
51
- component: StepPaywallB,
52
- meta: stepPaywallB,
53
- },
54
- stepManageSubscription: {
55
- component: StepManageSubscription,
56
- meta: stepManageSubscription,
57
- },
58
30
  stepSubscriptionStarted: {
59
31
  component: StepSubscriptionStarted,
60
32
  meta: stepSubscriptionStarted,