@funnelsgrove/cli 0.1.15 → 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.
- package/contracts/step-contract-v2.json +733 -0
- package/dist/analyticsOutput.d.ts +30 -15
- package/dist/analyticsOutput.js +45 -28
- package/dist/apiClient.d.ts +85 -0
- package/dist/apiClient.js +225 -8
- package/dist/cli.d.ts +149 -1
- package/dist/cli.js +1232 -71
- package/dist/diagnosticOutput.d.ts +42 -0
- package/dist/diagnosticOutput.js +83 -0
- package/dist/docsOutput.d.ts +19 -0
- package/dist/docsOutput.js +73 -0
- package/dist/docsTransaction.d.ts +81 -0
- package/dist/docsTransaction.js +1755 -0
- package/dist/funnelValidation.d.ts +14 -0
- package/dist/funnelValidation.js +297 -0
- package/dist/localSync.d.ts +79 -0
- package/dist/localSync.js +400 -29
- package/dist/projectValidator.d.ts +27 -0
- package/dist/projectValidator.js +208 -0
- package/dist/reskin.js +27 -1
- package/dist/stepContractMigration.d.ts +71 -0
- package/dist/stepContractMigration.js +1323 -0
- package/dist/templateDocs.js +8 -25
- package/docs-release-history.json +66 -0
- package/legacy-docs-catalog.json +1012 -0
- package/package.json +13 -4
- package/template_docs/.funnelsgrove-docs.json +144 -0
- package/template_docs/AGENTS.md +30 -58
- package/template_docs/CLAUDE.md +0 -2
- package/template_docs/docs/funnelsgrove/START-HERE.md +73 -0
- package/template_docs/docs/funnelsgrove/contracts/analytics-events.md +59 -0
- package/template_docs/docs/funnelsgrove/contracts/content-answers.md +44 -0
- package/template_docs/docs/funnelsgrove/contracts/flow-routing.md +41 -0
- package/template_docs/docs/funnelsgrove/contracts/payments.md +42 -0
- package/template_docs/docs/funnelsgrove/contracts/step-metadata.md +57 -0
- package/template_docs/docs/funnelsgrove/migrations/step-contract-v3.md +39 -0
- package/template_docs/docs/funnelsgrove/qa/analytics.md +43 -0
- package/template_docs/docs/funnelsgrove/qa/local.md +42 -0
- package/template_docs/docs/funnelsgrove/qa/paywall-checkout.md +55 -0
- package/template_docs/docs/funnelsgrove/qa/publish.md +32 -0
- package/template_docs/docs/funnelsgrove/recipes/add-experiment.md +34 -0
- package/template_docs/docs/funnelsgrove/recipes/add-step.md +33 -0
- package/template_docs/docs/funnelsgrove/recipes/edit-step.md +31 -0
- package/template_docs/docs/funnelsgrove/steps/INDEX.md +26 -0
- package/template_docs/docs/funnelsgrove/steps/cancellation_offer.md +112 -0
- package/template_docs/docs/funnelsgrove/steps/checkout.md +120 -0
- package/template_docs/docs/funnelsgrove/steps/form_input.md +115 -0
- package/template_docs/docs/funnelsgrove/steps/intro_hero.md +110 -0
- package/template_docs/docs/funnelsgrove/steps/multi_select_choice.md +123 -0
- package/template_docs/docs/funnelsgrove/steps/paywall_offer.md +115 -0
- package/template_docs/docs/funnelsgrove/steps/progress_interstitial.md +111 -0
- package/template_docs/docs/funnelsgrove/steps/purchase_completed.md +117 -0
- package/template_docs/docs/funnelsgrove/steps/single_step_choice.md +122 -0
- package/template_docs/docs/funnelsgrove/steps/single_step_choice_emoji.md +122 -0
- package/template_docs/docs/funnelsgrove/steps/social_proof.md +110 -0
- package/template_docs/docs/funnelsgrove/steps/subscription_handoff.md +113 -0
- package/template_docs/docs/funnelsgrove/steps/subscription_management.md +112 -0
- package/template_docs/docs/funnelsgrove/steps/summary_confirmation.md +110 -0
- package/template_docs/docs/funnelsgrove/steps/upsell_offer.md +112 -0
- package/template_docs/docs/funnelsgrove/steps/value_prop_story.md +110 -0
- package/template_docs/funnel-docs.config.json +43 -0
- package/template_scaffold/.env.example +41 -0
- package/template_scaffold/.funnelsgrove-contract-tools.json +19 -0
- package/template_scaffold/.funnelsgrove-docs.json +144 -0
- package/template_scaffold/.funnelsgrove-scaffold.json +1078 -0
- package/template_scaffold/AGENTS.md +42 -0
- package/template_scaffold/CLAUDE.md +1 -0
- package/template_scaffold/PLAN.md +81 -0
- package/template_scaffold/PRODUCT_SENSE.md +28 -0
- package/template_scaffold/context/build-context.md +34 -0
- package/template_scaffold/docs/AGENT_FUNNEL_IMPLEMENTATION_SPEC.md +5 -0
- package/template_scaffold/docs/ARCHITECTURE.md +5 -0
- package/template_scaffold/docs/FLOW_CONFIG_AND_ROUTING.md +5 -0
- package/template_scaffold/docs/PAYWALL_AND_PAYMENTS.md +5 -0
- package/template_scaffold/docs/README.md +5 -0
- package/template_scaffold/docs/funnelsgrove/START-HERE.md +73 -0
- package/template_scaffold/docs/funnelsgrove/contracts/analytics-events.md +59 -0
- package/template_scaffold/docs/funnelsgrove/contracts/content-answers.md +44 -0
- package/template_scaffold/docs/funnelsgrove/contracts/flow-routing.md +41 -0
- package/template_scaffold/docs/funnelsgrove/contracts/payments.md +42 -0
- package/template_scaffold/docs/funnelsgrove/contracts/step-metadata.md +57 -0
- package/template_scaffold/docs/funnelsgrove/migrations/step-contract-v3.md +39 -0
- package/template_scaffold/docs/funnelsgrove/qa/analytics.md +43 -0
- package/template_scaffold/docs/funnelsgrove/qa/local.md +42 -0
- package/template_scaffold/docs/funnelsgrove/qa/paywall-checkout.md +55 -0
- package/template_scaffold/docs/funnelsgrove/qa/publish.md +32 -0
- package/template_scaffold/docs/funnelsgrove/recipes/add-experiment.md +34 -0
- package/template_scaffold/docs/funnelsgrove/recipes/add-step.md +33 -0
- package/template_scaffold/docs/funnelsgrove/recipes/edit-step.md +31 -0
- package/template_scaffold/docs/funnelsgrove/steps/INDEX.md +26 -0
- package/template_scaffold/docs/funnelsgrove/steps/cancellation_offer.md +112 -0
- package/template_scaffold/docs/funnelsgrove/steps/checkout.md +120 -0
- package/template_scaffold/docs/funnelsgrove/steps/form_input.md +115 -0
- package/template_scaffold/docs/funnelsgrove/steps/intro_hero.md +110 -0
- package/template_scaffold/docs/funnelsgrove/steps/multi_select_choice.md +123 -0
- package/template_scaffold/docs/funnelsgrove/steps/paywall_offer.md +115 -0
- package/template_scaffold/docs/funnelsgrove/steps/progress_interstitial.md +111 -0
- package/template_scaffold/docs/funnelsgrove/steps/purchase_completed.md +117 -0
- package/template_scaffold/docs/funnelsgrove/steps/single_step_choice.md +122 -0
- package/template_scaffold/docs/funnelsgrove/steps/single_step_choice_emoji.md +122 -0
- package/template_scaffold/docs/funnelsgrove/steps/social_proof.md +110 -0
- package/template_scaffold/docs/funnelsgrove/steps/subscription_handoff.md +113 -0
- package/template_scaffold/docs/funnelsgrove/steps/subscription_management.md +112 -0
- package/template_scaffold/docs/funnelsgrove/steps/summary_confirmation.md +110 -0
- package/template_scaffold/docs/funnelsgrove/steps/upsell_offer.md +112 -0
- package/template_scaffold/docs/funnelsgrove/steps/value_prop_story.md +110 -0
- package/template_scaffold/docs/references/funnel-sdk-user-answers.md +5 -0
- package/template_scaffold/eslint.config.mjs +16 -0
- package/template_scaffold/funnel-agent-docs.test.ts +699 -0
- package/template_scaffold/funnel-docs.config.json +43 -0
- package/template_scaffold/funnel.config.json +5 -0
- package/template_scaffold/gitignore.template +5 -0
- package/template_scaffold/next-env.d.ts +6 -0
- package/template_scaffold/next.config.ts +53 -0
- package/template_scaffold/package-lock.json +7737 -0
- package/template_scaffold/package.json +37 -0
- package/template_scaffold/package.test.ts +23 -0
- package/template_scaffold/public/claimbee/bee.png +0 -0
- package/template_scaffold/public/claimbee/device-iphone.png +0 -0
- package/template_scaffold/public/claimbee/device-macbook.png +0 -0
- package/template_scaffold/public/claimbee/device-none.png +0 -0
- package/template_scaffold/public/claimbee/device-watch.png +0 -0
- package/template_scaffold/public/claimbee/glow.svg +3 -0
- package/template_scaffold/public/claimbee/steps-sprite-alt.png +0 -0
- package/template_scaffold/public/claimbee/steps-sprite-default.png +0 -0
- package/template_scaffold/public/claimbee/testimonial-avatar.png +0 -0
- package/template_scaffold/public/claimbee/testimonial-star.svg +3 -0
- package/template_scaffold/public/paywall/ai-claim-assistant.png +0 -0
- package/template_scaffold/public/paywall/claim-history.png +0 -0
- package/template_scaffold/public/paywall/clear-progress.png +0 -0
- package/template_scaffold/public/paywall/hero.png +0 -0
- package/template_scaffold/public/paywall/money-alerts.png +0 -0
- package/template_scaffold/public/paywall/money-back-badge.png +0 -0
- package/template_scaffold/public/paywall/paywall-image.png +0 -0
- package/template_scaffold/public/paywall/r1.png +0 -0
- package/template_scaffold/public/paywall/r2.png +0 -0
- package/template_scaffold/public/paywall/r3.png +0 -0
- package/template_scaffold/public/paywall/r4.png +0 -0
- package/template_scaffold/public/paywall/r5.png +0 -0
- package/template_scaffold/public/paywall/r6.png +0 -0
- package/template_scaffold/public/paywall/special-offer-gift.png +0 -0
- package/template_scaffold/public/please/bee.png +0 -0
- package/template_scaffold/public/please/device-iphone.png +0 -0
- package/template_scaffold/public/please/device-macbook.png +0 -0
- package/template_scaffold/public/please/device-none.png +0 -0
- package/template_scaffold/public/please/device-watch.png +0 -0
- package/template_scaffold/public/please/glow.svg +3 -0
- package/template_scaffold/public/please/steps-sprite-alt.png +0 -0
- package/template_scaffold/public/please/steps-sprite-default.png +0 -0
- package/template_scaffold/public/please/testimonial-avatar.png +0 -0
- package/template_scaffold/public/please/testimonial-star.svg +3 -0
- package/template_scaffold/public/upsell/upgrade-hero.png +0 -0
- package/template_scaffold/src/app/[stepId]/page.test.tsx +59 -0
- package/template_scaffold/src/app/[stepId]/page.tsx +62 -0
- package/template_scaffold/src/app/globals.css +49 -0
- package/template_scaffold/src/app/layout.test.ts +26 -0
- package/template_scaffold/src/app/layout.tsx +61 -0
- package/template_scaffold/src/app/page.tsx +8 -0
- package/template_scaffold/src/app/sdk/page.tsx +673 -0
- package/template_scaffold/src/app/subscription-dashboard/page.tsx +274 -0
- package/template_scaffold/src/assets/claimbee/bee.png +0 -0
- package/template_scaffold/src/assets/claimbee/device-iphone.png +0 -0
- package/template_scaffold/src/assets/claimbee/device-macbook.png +0 -0
- package/template_scaffold/src/assets/claimbee/device-none.png +0 -0
- package/template_scaffold/src/assets/claimbee/device-watch.png +0 -0
- package/template_scaffold/src/assets/claimbee/glow.svg +3 -0
- package/template_scaffold/src/assets/claimbee/steps-sprite-alt.png +0 -0
- package/template_scaffold/src/assets/claimbee/steps-sprite-default.png +0 -0
- package/template_scaffold/src/assets/claimbee/testimonial-avatar.png +0 -0
- package/template_scaffold/src/assets/claimbee/testimonial-star.svg +3 -0
- package/template_scaffold/src/components/FunnelEditorPanel.tsx +194 -0
- package/template_scaffold/src/components/FunnelFlow.test.tsx +17 -0
- package/template_scaffold/src/components/FunnelFlow.tsx +114 -0
- package/template_scaffold/src/components/FunnelStepImage.test.ts +15 -0
- package/template_scaffold/src/components/FunnelStepImage.tsx +28 -0
- package/template_scaffold/src/components/step/FunnelStepHeader.tsx +35 -0
- package/template_scaffold/src/config/billing.plans.test.ts +143 -0
- package/template_scaffold/src/config/billing.plans.ts +69 -0
- package/template_scaffold/src/config/billing.test.plans.ts +32 -0
- package/template_scaffold/src/config/experiments.generated.ts +4 -0
- package/template_scaffold/src/config/experiments.test.ts +12 -0
- package/template_scaffold/src/config/experiments.ts +9 -0
- package/template_scaffold/src/config/funnel.config.ts +12 -0
- package/template_scaffold/src/config/funnel.manifest.test.ts +166 -0
- package/template_scaffold/src/config/funnel.manifest.ts +139 -0
- package/template_scaffold/src/config/offer-sets.generated.ts +4 -0
- package/template_scaffold/src/contract/funnel-project.validation.test.ts +80 -0
- package/template_scaffold/src/contract/funnel-project.validation.ts +44 -0
- package/template_scaffold/src/contract/funnel-validator.vite.config.ts +11 -0
- package/template_scaffold/src/contract/golden/cancellation_offer.fixture.ts +29 -0
- package/template_scaffold/src/contract/golden/checkout.fixture.ts +37 -0
- package/template_scaffold/src/contract/golden/form_input.fixture.ts +28 -0
- package/template_scaffold/src/contract/golden/golden-fixtures.test.ts +190 -0
- package/template_scaffold/src/contract/golden/intro_hero.fixture.ts +27 -0
- package/template_scaffold/src/contract/golden/multi_select_choice.fixture.ts +37 -0
- package/template_scaffold/src/contract/golden/paywall_offer.fixture.ts +30 -0
- package/template_scaffold/src/contract/golden/progress_interstitial.fixture.ts +28 -0
- package/template_scaffold/src/contract/golden/purchase_completed.fixture.ts +33 -0
- package/template_scaffold/src/contract/golden/single_step_choice.fixture.ts +36 -0
- package/template_scaffold/src/contract/golden/single_step_choice_emoji.fixture.ts +36 -0
- package/template_scaffold/src/contract/golden/social_proof.fixture.ts +27 -0
- package/template_scaffold/src/contract/golden/subscription_handoff.fixture.ts +30 -0
- package/template_scaffold/src/contract/golden/subscription_management.fixture.ts +29 -0
- package/template_scaffold/src/contract/golden/summary_confirmation.fixture.ts +27 -0
- package/template_scaffold/src/contract/golden/upsell_offer.fixture.ts +29 -0
- package/template_scaffold/src/contract/golden/value_prop_story.fixture.ts +27 -0
- package/template_scaffold/src/contract/validate-funnel.cli.test.ts +180 -0
- package/template_scaffold/src/contract/validate-funnel.cli.ts +199 -0
- package/template_scaffold/src/runtime/checkout-runtime-config.test.ts +60 -0
- package/template_scaffold/src/runtime/checkout-runtime-config.ts +26 -0
- package/template_scaffold/src/runtime/funnel-runtime.test.ts +25 -0
- package/template_scaffold/src/runtime/funnel-runtime.ts +118 -0
- package/template_scaffold/src/runtime/step-content-context.test.ts +38 -0
- package/template_scaffold/src/runtime/step-content-context.ts +65 -0
- package/template_scaffold/src/runtime/step-registry.ts +66 -0
- package/template_scaffold/src/runtime/step-variable-values.ts +31 -0
- package/template_scaffold/src/runtime/use-funnel-flow-controller.integration.test.ts +203 -0
- package/template_scaffold/src/runtime/use-funnel-flow-controller.test.ts +76 -0
- package/template_scaffold/src/runtime/use-funnel-flow-controller.ts +53 -0
- package/template_scaffold/src/steps/content/email-capture.content.ts +29 -0
- package/template_scaffold/src/steps/content/manage-subscription.content.ts +62 -0
- package/template_scaffold/src/steps/content/paywall.content.ts +191 -0
- package/template_scaffold/src/steps/content/step-01.content.ts +23 -0
- package/template_scaffold/src/steps/content/step-02.content.ts +77 -0
- package/template_scaffold/src/steps/content/step-03.content.ts +40 -0
- package/template_scaffold/src/steps/content/step-04.content.ts +49 -0
- package/template_scaffold/src/steps/content/step-32-paywall.content.ts +352 -0
- package/template_scaffold/src/steps/content/subscription-started.content.ts +68 -0
- package/template_scaffold/src/steps/editor/email-capture.editor.ts +58 -0
- package/template_scaffold/src/steps/editor/manage-subscription.editor.ts +179 -0
- package/template_scaffold/src/steps/editor/paywall.editor.ts +232 -0
- package/template_scaffold/src/steps/editor/step-01.editor.ts +22 -0
- package/template_scaffold/src/steps/editor/step-02.editor.ts +41 -0
- package/template_scaffold/src/steps/editor/step-03.editor.ts +58 -0
- package/template_scaffold/src/steps/editor/step-04.editor.ts +98 -0
- package/template_scaffold/src/steps/editor/step-32-paywall.editor.ts +478 -0
- package/template_scaffold/src/steps/editor/subscription-started.editor.ts +131 -0
- package/template_scaffold/src/steps/index.ts +25 -0
- package/template_scaffold/src/steps/paywall-checkout-options.test.ts +104 -0
- package/template_scaffold/src/steps/paywall-checkout-options.ts +130 -0
- package/template_scaffold/src/steps/pricing/paywall-b.pricing.ts +12 -0
- package/template_scaffold/src/steps/pricing/paywall.pricing.ts +12 -0
- package/template_scaffold/src/steps/pricing/shared-plan-order.ts +17 -0
- package/template_scaffold/src/steps/pricing/upsell-form.pricing.ts +16 -0
- package/template_scaffold/src/steps/shared-header-contract.test.ts +27 -0
- package/template_scaffold/src/steps/step-01.test.ts +38 -0
- package/template_scaffold/src/steps/step-01.tsx +108 -0
- package/template_scaffold/src/steps/step-02.test.ts +43 -0
- package/template_scaffold/src/steps/step-02.tsx +140 -0
- package/template_scaffold/src/steps/step-03.test.ts +15 -0
- package/template_scaffold/src/steps/step-03.tsx +262 -0
- package/template_scaffold/src/steps/step-04.test.ts +32 -0
- package/template_scaffold/src/steps/step-04.tsx +673 -0
- package/template_scaffold/src/steps/step-31-email-capture.test.ts +22 -0
- package/template_scaffold/src/steps/step-31-email-capture.tsx +195 -0
- package/template_scaffold/src/steps/step-32-paywall.test.ts +770 -0
- package/template_scaffold/src/steps/step-32-paywall.tsx +2253 -0
- package/template_scaffold/src/steps/step-32b-paywall-b.tsx +35 -0
- package/template_scaffold/src/steps/step-33-subscription-started.test.ts +26 -0
- package/template_scaffold/src/steps/step-33-subscription-started.tsx +86 -0
- package/template_scaffold/src/steps/step-34-upsell-form.test.ts +27 -0
- package/template_scaffold/src/steps/step-35-manage-subscription.tsx +46 -0
- package/template_scaffold/src/steps/step-content.registry.ts +45 -0
- package/template_scaffold/src/steps/step-image-migration.test.ts +20 -0
- package/template_scaffold/src/steps/styles/index.css +2 -0
- package/template_scaffold/src/steps/styles/shared/final-flow-shared.css +55 -0
- package/template_scaffold/src/steps/styles/shared/funnel-base.css +543 -0
- package/template_scaffold/src/steps/styles/shared/info-shared.css +139 -0
- package/template_scaffold/src/steps/styles/shared/progress-shared.css +117 -0
- package/template_scaffold/src/steps/styles/shared/question-shared.css +76 -0
- package/template_scaffold/src/steps/styles/shared/responsive-container.css +176 -0
- package/template_scaffold/src/steps/types.ts +7 -0
- package/template_scaffold/src/steps/use-claimbee-step-content.ts +15 -0
- package/template_scaffold/src/theme/theme.ts +81 -0
- package/template_scaffold/tsconfig.json +38 -0
- package/template_scaffold/vitest.config.ts +32 -0
- package/template_validation/.funnelsgrove-contract-tools.json +19 -0
- package/template_validation/src/contract/funnel-project.validation.ts +44 -0
- package/template_validation/src/contract/funnel-validator.vite.config.ts +11 -0
- package/template_validation/src/contract/validate-funnel.cli.ts +199 -0
- package/template_docs/docs/ab-experiments.md +0 -111
- package/template_docs/docs/analytics.md +0 -65
- package/template_docs/docs/editing-flow.md +0 -136
- package/template_docs/docs/editing-step.md +0 -125
- package/template_docs/docs/editor-and-content.md +0 -58
- package/template_docs/docs/funnel-runtime-architecture.md +0 -125
- package/template_docs/docs/meta-pixel-conversions-api.md +0 -50
- package/template_docs/docs/payment-plans-and-discounts.md +0 -106
- package/template_docs/docs/publishing-and-versioning.md +0 -117
- package/template_docs/docs/qa-checklist.md +0 -80
- package/template_docs/docs/sdk-api-endpoints.md +0 -78
- package/template_docs/docs/step-ui-guidelines.md +0 -110
- package/template_docs/docs/theme.md +0 -35
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
# Editor and Content
|
|
2
|
-
|
|
3
|
-
Editable step content is a runtime contract. The step view should read from typed content, not from scattered hardcoded strings, when the value is copy, image, legal text, pricing label, FAQ, testimonial, or builder-editable UI.
|
|
4
|
-
|
|
5
|
-
## Content Files
|
|
6
|
-
|
|
7
|
-
Each step content file exports a typed `LocalizedStepContent<T>`:
|
|
8
|
-
|
|
9
|
-
```ts
|
|
10
|
-
export const stepContent = {
|
|
11
|
-
defaultLocale: 'en',
|
|
12
|
-
locales: {
|
|
13
|
-
en: {
|
|
14
|
-
headline: ['People getting', 'free money', 'know one secret'],
|
|
15
|
-
artwork: { src: '/figma/example.png', alt: '' },
|
|
16
|
-
},
|
|
17
|
-
},
|
|
18
|
-
} as const satisfies LocalizedStepContent<StepLocaleContent>;
|
|
19
|
-
```
|
|
20
|
-
|
|
21
|
-
The runtime validates that a default locale exists and resolves locale from funnel attributes (`browserLanguage`, `language`, `locale`) or browser locale. In preview, builder content patches are merged before the step renders.
|
|
22
|
-
|
|
23
|
-
Use runtime item types where possible:
|
|
24
|
-
|
|
25
|
-
- `StepImage` for images.
|
|
26
|
-
- `ChoiceItem`, `InfoItem`, `QuoteItem`, `QaItem`, `LinkItem`, `ReasonItem`, `StoreLinkItem`, `PlanPresentationItem` for lists.
|
|
27
|
-
- `CountryPricingProfile` for country-specific plan ordering/defaults.
|
|
28
|
-
|
|
29
|
-
## Editor Files
|
|
30
|
-
|
|
31
|
-
Each editor file exports `readonly StepEditorSection[]`. A field's `path` must match the content object path:
|
|
32
|
-
|
|
33
|
-
```ts
|
|
34
|
-
{
|
|
35
|
-
id: 'hero.image',
|
|
36
|
-
label: 'Hero image',
|
|
37
|
-
kind: 'image',
|
|
38
|
-
path: 'hero.image',
|
|
39
|
-
}
|
|
40
|
-
```
|
|
41
|
-
|
|
42
|
-
Supported field kinds are `text`, `textLines`, `textarea`, `image`, `boolean`, `select`, `list`, and `pricingProfile`. List fields must use a preset such as `choiceItems`, `infoItems`, `quoteItems`, `qaItems`, or `linkItems`.
|
|
43
|
-
|
|
44
|
-
Only expose fields the builder should edit. Do not add spare content fields for possible future use.
|
|
45
|
-
|
|
46
|
-
## Variables
|
|
47
|
-
|
|
48
|
-
For builder variables, set `supportsVariables: true` on text-capable fields only. Editor variables map `user.*` tokens to answer paths, then runtime interpolation replaces `{{ user.token }}` style values in supported fields. Universal variables also support simple `{user_id}` replacement at render time.
|
|
49
|
-
|
|
50
|
-
Keep variable use obvious. If a step depends on a value from an earlier step, make sure that earlier step writes a stable answer key.
|
|
51
|
-
|
|
52
|
-
## Agent Rules
|
|
53
|
-
|
|
54
|
-
- Keep copy, images, FAQ, testimonials, legal strings, and checkout labels in content files.
|
|
55
|
-
- Keep layout decisions in the step file.
|
|
56
|
-
- Keep editor sections aligned with the content shape.
|
|
57
|
-
- Keep `src/steps/step-content.registry.ts` in sync when adding or renaming content/editor files.
|
|
58
|
-
- Use checked-in public assets (`/paywall/...`, `/figma/...`, etc.); do not leave temporary localhost asset URLs.
|
|
@@ -1,125 +0,0 @@
|
|
|
1
|
-
# Funnel Runtime Architecture
|
|
2
|
-
|
|
3
|
-
This doc explains the runtime contracts a synced funnel should preserve. Update this file when changing funnel architecture, routing, identity handoff, SDK payloads, payments, subscriptions, or analytics.
|
|
4
|
-
|
|
5
|
-
## Runtime Boundary
|
|
6
|
-
|
|
7
|
-
A funnel is a small Next.js app that owns product-specific steps, content, assets, theme, and billing plan ids. Shared packages own the reusable runtime behavior:
|
|
8
|
-
|
|
9
|
-
- `@funnelsgrove/runtime`: routing, user state, URL user attributes, SDK calls, preview mode, content localization, subscription handoff, runtime config, and theme variables.
|
|
10
|
-
- `@funnelsgrove/payments`: Stripe plan resolution, discounts, checkout sessions, wallet/card checkout UI, and manage-subscription UI.
|
|
11
|
-
- `@funnelsgrove/analytics`: canonical analytics events and provider delivery.
|
|
12
|
-
|
|
13
|
-
Keep funnel code thin. Step files should use the shared runtime/payment/analytics surfaces instead of calling raw SDK endpoints, Stripe, PostHog, or browser routing directly.
|
|
14
|
-
|
|
15
|
-
## Flow And Routing
|
|
16
|
-
|
|
17
|
-
The local source of truth is `src/config/funnel.manifest.ts`.
|
|
18
|
-
|
|
19
|
-
- `steps[]` defines every routable step with stable `id`, `path`, `filePath`, `componentKey`, `type`, optional `kind`, and asset references.
|
|
20
|
-
- `edgesByStepId` defines normal and conditional navigation.
|
|
21
|
-
- `entryPoints` lets public routes such as `/paywall` and `/manage-subscription` land directly on a step.
|
|
22
|
-
- `src/runtime/step-registry.ts` maps manifest step ids to React components.
|
|
23
|
-
|
|
24
|
-
Use `useFunnel()` navigation (`goNext`, `goChoice`, `goToStep`) so runtime history, experiments, and step tracking remain consistent.
|
|
25
|
-
|
|
26
|
-
## User Identity
|
|
27
|
-
|
|
28
|
-
The runtime persists a local user id under the funnel-scoped local storage key `funnel:<funnelId>:user-id`. Public URLs can also pass user attributes:
|
|
29
|
-
|
|
30
|
-
- `user_id`: canonical public funnel user id when a known user is being resumed.
|
|
31
|
-
- `email`: shopper/contact email. The runtime validates it and syncs it into the funnel user profile.
|
|
32
|
-
- `name`, `fullName`, `full_name`: optional profile name aliases.
|
|
33
|
-
- `stripe_customer_id`: Stripe customer id used by subscription management links.
|
|
34
|
-
|
|
35
|
-
At startup, the flow controller bootstraps a user through `apiService.bootstrapSession(...)` with the URL or local user id, email/name, and first-touch attribution. Step answers are stored in runtime attributes and persisted through `apiService.updateUser(...)` as `document.progress.attributes` plus completed step records.
|
|
36
|
-
|
|
37
|
-
Do not invent new identity query names casually. If a new URL parameter becomes part of the funnel contract, add it to the URL attribute parser, SDK docs, and this file.
|
|
38
|
-
|
|
39
|
-
## Paywall Entry
|
|
40
|
-
|
|
41
|
-
Paywall routes can be opened directly, usually as `/paywall`.
|
|
42
|
-
|
|
43
|
-
Supported identity handoff:
|
|
44
|
-
|
|
45
|
-
- `/paywall?user_id=u_123`: resume a known funnel user and preserve that id through checkout metadata.
|
|
46
|
-
- `/paywall?email=user@example.com`: seed/sync the shopper email and pass it as Stripe `customerEmail`.
|
|
47
|
-
- `/paywall?user_id=u_123&email=user@example.com`: preferred when both are known.
|
|
48
|
-
|
|
49
|
-
Email-only links are useful for prefilled checkout and later private user lookup by email, but `user_id` is the stronger join key for existing answers, attribution, and subscription reconciliation.
|
|
50
|
-
|
|
51
|
-
Paywall checkout should use `@funnelsgrove/payments` helpers. Checkout payloads must include the selected plan, amount, coupon, checkout mode, runtime config, `user_id` when available, and `customerEmail` when available. Stripe sessions receive `client_reference_id` and FunnelsGrove metadata with plan id, price id, user id, environment, funnel id, funnel version id, coupon id, and customer email.
|
|
52
|
-
|
|
53
|
-
Success/return URLs should route to the subscription-started step and preserve `user_id`, for example `/subscription-started?user_id=u_123&source=stripe-elements`.
|
|
54
|
-
|
|
55
|
-
## Subscription Started Handoff
|
|
56
|
-
|
|
57
|
-
The subscription-started step uses `SubscriptionHandoffScreen` and `resolveSubscriptionHandoff(...)`.
|
|
58
|
-
|
|
59
|
-
The handoff requires a user id. App store URLs, universal links, and deep links may include `{user_id}` and `{email}` templates; runtime also appends `user_id` and first-touch attribution parameters when it can parse the URL.
|
|
60
|
-
|
|
61
|
-
This step is also the right place to track payment return events such as `payment_checkout_succeeded` or `payment_checkout_returned`. Keep raw payment objects and secrets out of analytics metadata.
|
|
62
|
-
|
|
63
|
-
## Manage Subscription
|
|
64
|
-
|
|
65
|
-
The manage-subscription route is a public customer support route, usually `/manage-subscription`.
|
|
66
|
-
|
|
67
|
-
Supported identity handoff:
|
|
68
|
-
|
|
69
|
-
- `/manage-subscription?user_id=u_123`: resolve the funnel user, then resolve Stripe customer/subscriptions from the linked user, user document, or billing records.
|
|
70
|
-
- `/manage-subscription?stripe_customer_id=cus_123`: resolve subscriptions directly by Stripe customer id when the funnel user id is unavailable.
|
|
71
|
-
- `/manage-subscription?user_id=u_123&stripe_customer_id=cus_123`: preferred for support/admin generated links.
|
|
72
|
-
|
|
73
|
-
The runtime calls:
|
|
74
|
-
|
|
75
|
-
- `GET /sdk/public/subscriptions?user_id=...&stripe_customer_id=...&funnelId=...`
|
|
76
|
-
- `POST /sdk/public/subscriptions/:subscriptionId/:action` with `action` of `cancel` or `renew`
|
|
77
|
-
|
|
78
|
-
If neither `user_id` nor `stripe_customer_id` is available, the list endpoint returns an empty public summary and mutation rejects the request. The UI should show an empty/error state and the support email from runtime config.
|
|
79
|
-
|
|
80
|
-
## Webhooks And Reconciliation
|
|
81
|
-
|
|
82
|
-
Stripe webhooks reconcile payment/subscription state back into the funnel user. The important join points are:
|
|
83
|
-
|
|
84
|
-
- Stripe metadata `userId` and `customerEmail`.
|
|
85
|
-
- Stripe `client_reference_id`.
|
|
86
|
-
- provider customer id (`stripe_customer_id` / `providerCustomerId`).
|
|
87
|
-
- stored funnel user subscription fields and document subscription snapshots.
|
|
88
|
-
|
|
89
|
-
Webhook reconciliation updates the funnel user subscription status, Stripe customer/subscription ids, subscription document snapshots, billing records, analytics, and project webhooks. When editing checkout metadata or URL handoff, verify the webhook still has enough information to find the correct funnel user.
|
|
90
|
-
|
|
91
|
-
## Analytics
|
|
92
|
-
|
|
93
|
-
The flow controller emits canonical runtime events:
|
|
94
|
-
|
|
95
|
-
- `step_start`
|
|
96
|
-
- `step_end`
|
|
97
|
-
- `step_engaged` on the first step after the engagement threshold
|
|
98
|
-
|
|
99
|
-
The analytics provider may receive mapped names such as `step_started` and `step_completed`.
|
|
100
|
-
Paywalls should track checkout intent and completion through `@funnelsgrove/analytics` with plan id, provider plan id, coupon id, amount, and environment. Do not send personal data, SDK keys, Stripe secrets, or raw provider responses.
|
|
101
|
-
Completed Registration is tracked by the server only when the app successfully calls `POST /sdk/public/users/:user_id/claim_subscription` after login/signup.
|
|
102
|
-
|
|
103
|
-
## Preview Runtime
|
|
104
|
-
|
|
105
|
-
Builder preview is intentionally different from production:
|
|
106
|
-
|
|
107
|
-
- normal SDK writes are skipped or replaced with local fallbacks;
|
|
108
|
-
- manage-subscription uses a preview fallback payload;
|
|
109
|
-
- runtime mode and paywall plans can be patched by the builder preview bridge;
|
|
110
|
-
- checkout may force test mode when the preview payment key is active.
|
|
111
|
-
|
|
112
|
-
Do not make preview-only shortcuts the production contract.
|
|
113
|
-
|
|
114
|
-
## Architecture Change Checklist
|
|
115
|
-
|
|
116
|
-
When a funnel architecture change touches any item below, update `template_docs` with the new behavior:
|
|
117
|
-
|
|
118
|
-
- route paths, manifest step ids, entry points, or step kinds;
|
|
119
|
-
- runtime user state, URL parameters, attribution, or answer persistence;
|
|
120
|
-
- SDK endpoint paths, query/body fields, response shape, or auth headers;
|
|
121
|
-
- payment plan mapping, checkout metadata, return URLs, discounts, or Stripe mode resolution;
|
|
122
|
-
- manage-subscription identity resolution or mutation behavior;
|
|
123
|
-
- subscription handoff links, app/deep-link templates, or post-checkout routing;
|
|
124
|
-
- analytics event names or required metadata;
|
|
125
|
-
- preview/editor runtime behavior.
|
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
# Meta Pixel and Conversions API
|
|
2
|
-
|
|
3
|
-
Meta analytics is provider-specific. Keep funnel code on the `@funnelsgrove/analytics` API surface; do not call `fbq`, Meta Pixel, or Conversions API directly from funnel steps.
|
|
4
|
-
|
|
5
|
-
## Browser Pixel Events
|
|
6
|
-
|
|
7
|
-
The browser Pixel is initialized by `@funnelsgrove/analytics` when both public env vars are set:
|
|
8
|
-
|
|
9
|
-
- `NEXT_PUBLIC_META_PIXEL_ENABLED=true`
|
|
10
|
-
- `NEXT_PUBLIC_META_PIXEL_ID=<pixel id>`
|
|
11
|
-
|
|
12
|
-
Browser events:
|
|
13
|
-
|
|
14
|
-
- `publicAnalyticsSdk.trackFirstStepViewed(...)` sends Meta `ViewContent` when the user lands on the real first funnel page.
|
|
15
|
-
- `publicAnalyticsSdk.trackFirstStepClicked(...)` is still supported for funnels or experiments that need click-specific first-step analytics. It also maps to Meta `ViewContent`.
|
|
16
|
-
- `checkout_started` maps to Meta `InitiateCheckout`.
|
|
17
|
-
- `checkout_completed` maps to Meta `AddPaymentInfo`.
|
|
18
|
-
|
|
19
|
-
Do not fire both `trackFirstStepViewed(...)` and `trackFirstStepClicked(...)` for the same intended conversion unless the product explicitly wants two separate analytics events. Preview runtime must not send normal Pixel events.
|
|
20
|
-
|
|
21
|
-
The browser Pixel owns browser attribution automatically through Meta cookies such as `_fbp` and `_fbc`.
|
|
22
|
-
|
|
23
|
-
## Server Conversions API Events
|
|
24
|
-
|
|
25
|
-
Server-side Meta events go through the API analytics service and Facebook Conversions API. Project env requires:
|
|
26
|
-
|
|
27
|
-
- `META_CONVERSIONS_ACCESS_TOKEN=<server token>`
|
|
28
|
-
- Optional test helper: `META_TEST_EVENT_CODE=<Meta test code>`
|
|
29
|
-
|
|
30
|
-
Trusted server events currently come from Stripe webhooks:
|
|
31
|
-
|
|
32
|
-
- Paid checkout session, invoice, or payment intent sends Meta `Purchase`.
|
|
33
|
-
- Active or trialing subscription webhook sends Meta `Subscribe`.
|
|
34
|
-
|
|
35
|
-
Server events use the funnel user id as Meta `external_id`; do not use the funnel user id as an event id. Event ids must be unique per event. Use one generated event id for a browser/server pair only when both sides represent the same user action and should be deduplicated.
|
|
36
|
-
|
|
37
|
-
## Attribution Matching
|
|
38
|
-
|
|
39
|
-
Runtime stores URL/referrer attribution in `funnelUser.document.attribution`.
|
|
40
|
-
|
|
41
|
-
Keep `fbclid` in attribution. The API can derive Meta `fbc` from stored `fbclid` and the attribution capture time for later Stripe webhook conversions. The API also sends known email, funnel user id, user agent, and other available match fields.
|
|
42
|
-
|
|
43
|
-
Do not invent or strip attribution URL params unless the funnel runtime attribution docs and SDK contract are updated in the same change.
|
|
44
|
-
|
|
45
|
-
## Agent Rules
|
|
46
|
-
|
|
47
|
-
- Use analytics package helpers instead of raw Meta calls.
|
|
48
|
-
- When changing first-page routing, verify `ViewContent` still fires only for the real first landing page and not preview runtime.
|
|
49
|
-
- When changing checkout or subscription behavior, verify the corresponding server CAPI event still has project id, funnel user id, event id, currency/value where available, and stored attribution.
|
|
50
|
-
- Do not send secrets, raw Stripe objects, access tokens, or full payment payloads to browser analytics.
|
|
@@ -1,106 +0,0 @@
|
|
|
1
|
-
# Payment Plans and Discounts
|
|
2
|
-
|
|
3
|
-
Payments are handled through `@funnelsgrove/payments`. Funnel steps should use the shared payment module instead of building raw Stripe calls.
|
|
4
|
-
|
|
5
|
-
## Plan Sources
|
|
6
|
-
|
|
7
|
-
Local plans live in `src/config/billing.plans.ts`. A plan catalog is keyed by stable funnel keys (`primary`, `secondary`, `tertiary`) and each plan includes:
|
|
8
|
-
|
|
9
|
-
- `projectPlanId`: FunnelsGrove project plan id.
|
|
10
|
-
- `providerPlanId`: Stripe price id.
|
|
11
|
-
- `title`, `priceLabel`, `perDayAmount`, `amountCents`.
|
|
12
|
-
- Optional `featuredTag`, `oldPriceLabel`, `checkoutSummaryLabel`, `billingInterval`, `billingIntervalCount`, `isDefault`.
|
|
13
|
-
|
|
14
|
-
ClaimBee-style funnels may keep separate test/live catalogs with `createRuntimeModeBillingPlanCatalog(...)` and choose the active catalog through `useRuntimeMode()` plus checkout mode resolution.
|
|
15
|
-
|
|
16
|
-
## Runtime Resolution
|
|
17
|
-
|
|
18
|
-
Use the shared helpers:
|
|
19
|
-
|
|
20
|
-
- `buildConfigPaywallPlans(...)` for local catalogs.
|
|
21
|
-
- `useResolvedPaywallPlans(...)` when the funnel can load synced project Stripe plans.
|
|
22
|
-
- `getDefaultPlanId(...)`, `findPaywallPlan(...)`, and `getPaywallPlanSelectionValue(...)` for selection.
|
|
23
|
-
- `usePreviewStepPaywallPlans(stepId)` so builder paywall plan edits appear in preview.
|
|
24
|
-
|
|
25
|
-
If a catalog is only a mapping of `{ projectPlanId }`, the payment module can load remote project plans from `/sdk/public/payments/plans` and map them back to funnel keys.
|
|
26
|
-
|
|
27
|
-
## Discounts
|
|
28
|
-
|
|
29
|
-
Discounts use `BillingDiscountList` or `BillingDiscountCatalog` and are resolved by `buildBillingDiscountCatalog(...)`. The current ClaimBee pattern has two stages:
|
|
30
|
-
|
|
31
|
-
- First stage: coupon id, percent, duration.
|
|
32
|
-
- Second stage: stronger coupon id, percent, duration, and previous percent.
|
|
33
|
-
|
|
34
|
-
Use `resolvePaywallDiscountState(...)`, `advancePaywallDiscountState(...)`, `activateSecondPaywallDiscount(...)`, `serializePaywallDiscountState(...)`, and `buildDiscountedPaywallPlans(...)`. Store discount state through runtime paywall state helpers (`readPaywallStateValue`, `updatePaywallStateValue`) so it is scoped by funnel id.
|
|
35
|
-
|
|
36
|
-
### Discount on Closing Checkout
|
|
37
|
-
|
|
38
|
-
The standard two-stage flow, wired in the paywall step:
|
|
39
|
-
|
|
40
|
-
1. Paywall opens with the first-stage discount applied to plan cards.
|
|
41
|
-
2. The checkout controller's `onFirstCheckoutClosed` callback fires when the visitor closes the card checkout without paying. The paywall opens `SharedCheckoutSpecialOfferDialog` (image, discount label, accept button — copy lives in the paywall content file).
|
|
42
|
-
3. Accepting calls `activateSecondPaywallDiscount(...)`, which swaps to the stronger second-stage coupon, restarts its window, and re-renders plans via `buildDiscountedPaywallPlans(...)`.
|
|
43
|
-
4. The next checkout open carries the second-stage coupon id in its payload.
|
|
44
|
-
|
|
45
|
-
Keep both stages testable: QA must verify stage one on open and stage two after a checkout close ([qa-checklist.md](qa-checklist.md), paywall item 5).
|
|
46
|
-
|
|
47
|
-
## Checkout
|
|
48
|
-
|
|
49
|
-
Prefer shared Stripe surfaces:
|
|
50
|
-
|
|
51
|
-
- `useStripeSubscriptionCheckoutSession(...)` for subscription checkout sessions.
|
|
52
|
-
- `SharedStripeCheckoutV2Dialog` for card checkout UI.
|
|
53
|
-
- `ApplePaySubscriptionCheckoutSlot` and `GooglePaySubscriptionCheckoutSlot` for wallet buttons.
|
|
54
|
-
- `StripeExpressCheckoutButton` / `StripePlanSelector` for older paywall variants.
|
|
55
|
-
|
|
56
|
-
Runtime config should come from `runtimePublicConfig` through a local `src/runtime/checkout-runtime-config.ts`. Include `apiBaseUrl`, `funnelId`, and the SDK publishable key. Preview seed keys may resolve to the preview payment key and force test checkout mode.
|
|
57
|
-
|
|
58
|
-
### Shared Checkout V2 and Wallet Slots
|
|
59
|
-
|
|
60
|
-
Subscription paywalls with on-page wallet buttons should share one `useStripeSubscriptionCheckoutSession(...)` instance across the manual card checkout and wallet checkout. Do not build funnel-local Stripe PaymentIntent or Express Checkout flows.
|
|
61
|
-
|
|
62
|
-
- Keep funnel-specific checkout orchestration local when it includes product layout, analytics callbacks, return URL branching, or email prompt copy. The shared UI boundary is `SharedStripeCheckoutV2Dialog`; do not add another shared wrapper that duplicates a funnel's paywall composition.
|
|
63
|
-
- Use `SharedStripeCheckoutV2Dialog` for the manual/card checkout modal. Open it from the paywall CTA after the checkout session has an `activeClientSecret` and `stripePromise`.
|
|
64
|
-
- Render real wallet slots on the paywall with `ApplePaySubscriptionCheckoutSlot` and `GooglePaySubscriptionCheckoutSlot`, usually above the manual/card CTA. Pass the same checkout session, selected plan amount, return URL, customer details, summary label, success handler, and any `beforeConfirm` logic used to finalize email or analytics metadata.
|
|
65
|
-
- Use `usePlatformWalletPaymentMethods()` to decide which slots to render. Desktop web can show Apple Pay and Google Pay; iOS shows Apple Pay; Android shows Google Pay.
|
|
66
|
-
- Track each slot's `onAvailabilityChange` result by the current `checkoutSession.intentKey`. When opening `SharedStripeCheckoutV2Dialog`, derive `initialWalletAvailable` from the current intent's confirmed wallet availability and pass it into the dialog so checkout can show the wallet tab immediately when a wallet is already known to be available.
|
|
67
|
-
- Treat unknown wallet availability as `null`, not `false`. Only hide/fallback from a wallet after Stripe or the shared slot reports that wallet unavailable.
|
|
68
|
-
- Treat Checkout Session/API preparation failures as retryable checkout failures, not wallet unavailability. A failed `prepareWalletCheckout()` should leave the wallet slot in unknown availability and show the real clickable fallback placeholder after loading stops; only Stripe readiness/availability reports should mark a wallet unavailable.
|
|
69
|
-
- Suspend paywall wallet slots while the shared checkout dialog or another blocking offer modal is open, so two Stripe Express Checkout elements do not compete for the same active checkout session.
|
|
70
|
-
- If checkout starts without a known customer email, first try to resolve it from a known `user_id` or `stripe_customer_id`. Only show an email prompt when there is no known identity to fetch from, or that lookup returns no email.
|
|
71
|
-
- Before publishing payment changes, run the shared wallet smoke helper from `@funnelsgrove/payments` against local and preview paywall URLs. The package script is `npm run smoke:wallet --workspace @funnelsgrove/payments -- --url <paywall-url>` and accepts selector overrides through CLI flags or `WALLET_SMOKE_*` env vars.
|
|
72
|
-
|
|
73
|
-
## Paywall Identity Handoff
|
|
74
|
-
|
|
75
|
-
Paywall steps may be opened directly by support/admin links:
|
|
76
|
-
|
|
77
|
-
- `/paywall?user_id=u_123` resumes a known funnel user and carries that id into checkout metadata.
|
|
78
|
-
- `/paywall?email=user@example.com` seeds the runtime user email and Stripe `customerEmail`.
|
|
79
|
-
- `/paywall?user_id=u_123&email=user@example.com` is preferred when both are known.
|
|
80
|
-
|
|
81
|
-
`user_id` is the strongest join key for answers, attribution, checkout metadata, and webhook reconciliation. Email-only handoff is useful for prefilled checkout and private user lookup, but do not treat it as a full replacement for `user_id` when exact progress/subscription state is required.
|
|
82
|
-
|
|
83
|
-
Checkout return URLs should route to the subscription-started step and preserve the user id, for example `/subscription-started?user_id=u_123&source=stripe-elements`.
|
|
84
|
-
|
|
85
|
-
## Manage Subscription Handoff
|
|
86
|
-
|
|
87
|
-
Manage subscription is a customer support route, usually `/manage-subscription`.
|
|
88
|
-
|
|
89
|
-
Supported links:
|
|
90
|
-
|
|
91
|
-
- `/manage-subscription?user_id=u_123`
|
|
92
|
-
- `/manage-subscription?stripe_customer_id=cus_123`
|
|
93
|
-
- `/manage-subscription?user_id=u_123&stripe_customer_id=cus_123`
|
|
94
|
-
|
|
95
|
-
The runtime calls `apiService.getManageSubscriptions()` and `apiService.updateSubscription(...)`, which send `user_id`, `stripe_customer_id`, and `funnelId` to the public subscriptions SDK endpoints. If neither id is present, the list endpoint returns an empty summary and mutation is rejected.
|
|
96
|
-
|
|
97
|
-
Generated support/admin links should include `user_id` and add `stripe_customer_id` when the paid customer id is known.
|
|
98
|
-
|
|
99
|
-
## Agent Rules
|
|
100
|
-
|
|
101
|
-
- Never change a live `projectPlanId`, `providerPlanId`, coupon id, or amount casually.
|
|
102
|
-
- Keep displayed price, discounted price, amount cents, coupon id, checkout payload, and analytics metadata in sync.
|
|
103
|
-
- Keep paywall, checkout, subscription-started, and manage-subscription identity handoff parameters in sync with [Funnel Runtime Architecture](funnel-runtime-architecture.md).
|
|
104
|
-
- Track checkout starts/completions with plan id, provider plan id, coupon id, amount, and environment.
|
|
105
|
-
- Keep legal, renewal, guarantee, support, and button labels in content files.
|
|
106
|
-
- Test test-mode and live-mode plan resolution separately when a payment change affects both.
|
|
@@ -1,117 +0,0 @@
|
|
|
1
|
-
# Publishing and Versioning
|
|
2
|
-
|
|
3
|
-
Local edits are not public until they are synced and published. Sync creates or patches a draft version. Publish deploys that draft.
|
|
4
|
-
|
|
5
|
-
## CLI Flow
|
|
6
|
-
|
|
7
|
-
```bash
|
|
8
|
-
fgrove status
|
|
9
|
-
git status --short
|
|
10
|
-
fgrove github status
|
|
11
|
-
fgrove publish --env preview --message '<summary>'
|
|
12
|
-
```
|
|
13
|
-
|
|
14
|
-
Before syncing down over an existing synced directory, check local changes. If
|
|
15
|
-
`git status --short` shows local edits, checkpoint them first. `fgrove sync
|
|
16
|
-
down` refuses to overwrite a dirty synced directory by default; use `--force`
|
|
17
|
-
only when discarding local changes is intentional.
|
|
18
|
-
|
|
19
|
-
When GitHub is connected and its branch is ahead, run `fgrove github pull` to
|
|
20
|
-
pull GitHub into the hosted draft, then poll `fgrove github status` until the
|
|
21
|
-
pull job is completed or skipped. Sync that latest draft into a clean directory
|
|
22
|
-
and merge local changes intentionally before continuing.
|
|
23
|
-
|
|
24
|
-
For GitHub-connected funnels, source changes must be pushed to GitHub first,
|
|
25
|
-
then pulled into the hosted draft:
|
|
26
|
-
|
|
27
|
-
```bash
|
|
28
|
-
git push
|
|
29
|
-
fgrove github pull
|
|
30
|
-
fgrove github status
|
|
31
|
-
fgrove publish --env preview --message '<summary>'
|
|
32
|
-
```
|
|
33
|
-
|
|
34
|
-
Poll `fgrove github status` until the pull job is completed or skipped before
|
|
35
|
-
publishing. Do not run `fgrove sync up` for the same source diff; the CLI and
|
|
36
|
-
API reject draft source sync for GitHub-connected funnels.
|
|
37
|
-
|
|
38
|
-
When GitHub is not connected, the hosted draft is the remote source of truth.
|
|
39
|
-
Download it into a temporary clean directory with `fgrove sync down --funnel
|
|
40
|
-
<id-or-slug> --dir <temp-dir>` or an already selected `fgrove use` context,
|
|
41
|
-
compare it with local changes, merge intentionally, rerun checks, then `fgrove
|
|
42
|
-
sync up`.
|
|
43
|
-
|
|
44
|
-
For funnels without GitHub, sync local source directly to the hosted draft:
|
|
45
|
-
|
|
46
|
-
```bash
|
|
47
|
-
fgrove sync up --message '<summary>'
|
|
48
|
-
fgrove publish --env preview --message '<summary>'
|
|
49
|
-
```
|
|
50
|
-
|
|
51
|
-
If `fgrove sync up` reports that the remote draft changed since the local
|
|
52
|
-
directory was synced, do not retry blindly. Download the current draft into a
|
|
53
|
-
temporary clean directory, merge local changes against it, rerun checks, and
|
|
54
|
-
sync again.
|
|
55
|
-
|
|
56
|
-
Refresh the ignored local `.env` from the remote project when project settings change:
|
|
57
|
-
|
|
58
|
-
```bash
|
|
59
|
-
fgrove env pull
|
|
60
|
-
# or, from outside the synced funnel directory:
|
|
61
|
-
fgrove env pull --dir ./path/to/local-funnel
|
|
62
|
-
```
|
|
63
|
-
|
|
64
|
-
Only publish production when explicitly requested:
|
|
65
|
-
|
|
66
|
-
```bash
|
|
67
|
-
fgrove publish --env production --domain <domain> --message '<summary>'
|
|
68
|
-
```
|
|
69
|
-
|
|
70
|
-
Production publish requires a domain. Preview publish returns a deployment URL, version sequence, and version id.
|
|
71
|
-
|
|
72
|
-
## Publish Image Optimization
|
|
73
|
-
|
|
74
|
-
Publish builds reduce raster image size before upload. Keep this path enabled:
|
|
75
|
-
PNG/JPEG sources are compressed, AVIF/WebP variants are generated for supported
|
|
76
|
-
browsers, and the original image remains the fallback. Do not bypass this with
|
|
77
|
-
remote funnel-critical image URLs.
|
|
78
|
-
|
|
79
|
-
After publishing image edits or a production candidate, check the deployment
|
|
80
|
-
metadata, CLI output, or `publishBuild.stageTimings.imageVariants`. If the
|
|
81
|
-
image-variant stage is missing or unavailable, report that explicitly before
|
|
82
|
-
calling the publish ready.
|
|
83
|
-
|
|
84
|
-
## Local Sync Contract
|
|
85
|
-
|
|
86
|
-
The CLI writes `.funnelsgrove-sync.json` into the local tree. Keep it there. It
|
|
87
|
-
records workspace id, funnel id, current draft version id, and source hashes so
|
|
88
|
-
later `sync up` can patch only changed/deleted files and detect stale remote
|
|
89
|
-
drafts before overwriting them.
|
|
90
|
-
|
|
91
|
-
Local-only files are excluded from upload:
|
|
92
|
-
|
|
93
|
-
- `.env`, `.env.local`, `.env.*` except `.env.example`.
|
|
94
|
-
- `.funnelsgrove-sync.json`.
|
|
95
|
-
- `node_modules`.
|
|
96
|
-
- `.next`.
|
|
97
|
-
- `out`.
|
|
98
|
-
|
|
99
|
-
The local `.env` is runtime material generated from remote project/funnel settings. Use `fgrove env pull` to refresh it without replacing source files.
|
|
100
|
-
|
|
101
|
-
Do not edit generated build output as the source of truth.
|
|
102
|
-
|
|
103
|
-
## Agent Rules
|
|
104
|
-
|
|
105
|
-
- Run `fgrove status` before syncing.
|
|
106
|
-
- Check local changes before `sync down`; checkpoint or merge instead of
|
|
107
|
-
overwriting.
|
|
108
|
-
- For GitHub-connected funnels, push with normal git, then run `fgrove github
|
|
109
|
-
pull`; do not run `fgrove sync up` for the same source change.
|
|
110
|
-
- Use clear sync and publish messages.
|
|
111
|
-
- Sync the hosted draft before publishing: GitHub-connected funnels use normal
|
|
112
|
-
`git push` plus `fgrove github pull`; funnels without GitHub use `fgrove sync
|
|
113
|
-
up`.
|
|
114
|
-
- Publish preview first and verify the returned URL.
|
|
115
|
-
- If preview verification fails, fix locally, run checks, sync the hosted draft
|
|
116
|
-
again through the correct source path, and publish a new preview.
|
|
117
|
-
- Do not production publish without explicit instruction and target domain.
|
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
# QA Checklist
|
|
2
|
-
|
|
3
|
-
What to test, where to test it, and what blocks a publish. QA can take as long as it needs — it is the gate before anything user-facing ships. Scale the scope to the edit: a copy tweak needs the affected step only; anything touching flow, paywall, checkout, pricing, identity, or experiments needs the full list.
|
|
4
|
-
|
|
5
|
-
## Where
|
|
6
|
-
|
|
7
|
-
| Stage | URL | When |
|
|
8
|
-
| --- | --- | --- |
|
|
9
|
-
| Local | `npm run dev` → `http://localhost:3000` | Always, before sync |
|
|
10
|
-
| Preview | URL returned by `fgrove publish --env preview` | Before any production publish |
|
|
11
|
-
| Production | Live domain | After production publish |
|
|
12
|
-
|
|
13
|
-
A production publish without a QA-passed matching preview build is a blocker unless the user explicitly accepts the risk.
|
|
14
|
-
|
|
15
|
-
## 1. Flow Coverage
|
|
16
|
-
|
|
17
|
-
- Open the first step and walk every step to the end.
|
|
18
|
-
- Exercise every branch (`goChoice` yes/no paths) — check both sides.
|
|
19
|
-
- For each experiment in `src/config/experiments.ts`, force both variants (`?editor=true&experimentVariant=control|variant_b`) and confirm each renders and advances.
|
|
20
|
-
- Submit email/identity capture with a test address and confirm advancement.
|
|
21
|
-
- Keep the browser console open the whole walk: zero uncaught errors and no failed requests (ignore third-party analytics noise).
|
|
22
|
-
|
|
23
|
-
## 2. Visual Pass
|
|
24
|
-
|
|
25
|
-
Run every visual pass at all four default breakpoints (rules in [step-ui-guidelines.md](step-ui-guidelines.md)):
|
|
26
|
-
|
|
27
|
-
| Breakpoint | Size |
|
|
28
|
-
| --- | --- |
|
|
29
|
-
| small | **375x667** |
|
|
30
|
-
| medium | **393x852** |
|
|
31
|
-
| large | **402x874** |
|
|
32
|
-
| desktop-small | **1280x800** |
|
|
33
|
-
|
|
34
|
-
- Nothing intersects or overlaps: text never collides with images, cards, badges, or the action bar; modals and dialogs fit the viewport.
|
|
35
|
-
- Continue button sits on an opaque bar at the bottom and stays visible on every step, including while content scrolls.
|
|
36
|
-
- No horizontal scroll, no clipped or truncated content, no broken images.
|
|
37
|
-
- Disabled CTA states render correctly and enable when valid.
|
|
38
|
-
|
|
39
|
-
## 3. Image Performance
|
|
40
|
-
|
|
41
|
-
For image edits and every preview-to-production candidate:
|
|
42
|
-
|
|
43
|
-
- Confirm edited step images are listed in `funnelManifest.assets` and attached
|
|
44
|
-
to the relevant manifest steps with `assetIds`.
|
|
45
|
-
- Confirm first-viewport images use the framework's priority/preload mechanism.
|
|
46
|
-
- Confirm the shell warms only likely next-step images at low priority, not the
|
|
47
|
-
full funnel image set.
|
|
48
|
-
- After preview or production publish, confirm build metadata includes the
|
|
49
|
-
`imageVariants` stage or name why it is unavailable.
|
|
50
|
-
- In the browser/network panel, confirm AVIF/WebP variants are served when the
|
|
51
|
-
browser advertises support, with the original raster image as fallback.
|
|
52
|
-
|
|
53
|
-
## 4. Paywall and Checkout (most important)
|
|
54
|
-
|
|
55
|
-
Test the paywall on every QA run that touches it, pricing, discounts, or experiments — on the paywall step **and** every live paywall variant:
|
|
56
|
-
|
|
57
|
-
1. **Plans render correctly** — titles, struck-through old price, discounted price, per-day price, featured/preselected plan match `src/config/billing.plans.ts` exactly. Verify discount math.
|
|
58
|
-
2. **Countdown / discount state** — first-stage discount applies on open; timer counts down.
|
|
59
|
-
3. **Checkout opens** — CTA opens the checkout with the selected plan; itemized totals match the selected plan and applied coupon.
|
|
60
|
-
4. **Apple Pay / Google Pay buttons** — wallet slots render (Apple Pay needs Safari/iOS context, Google Pay needs Chrome; on unsupported browsers expect the documented fallback, not a broken gap). If wallets never appear in a supported context, treat as a defect.
|
|
61
|
-
**Note:** wallet buttons silently fail when the domain and checkout return URLs are not configured in the Stripe dashboard (payment method domains + return URL allowlist). New funnels and new domains must have Stripe configured before wallet QA can pass — if it is not configured, report it as a named blocker, not a pass.
|
|
62
|
-
5. **Close-checkout discount** — close the checkout without paying: the special-offer dialog must appear; accepting it must apply the second-stage (larger) discount to the plan cards and the next checkout's payload. Verify the new prices and coupon id.
|
|
63
|
-
6. **Test payment** — complete a payment with Stripe test card `4242 4242 4242 4242` (test mode) or the approved payment path. Confirm redirect to the subscription-started step with `user_id` preserved.
|
|
64
|
-
7. **Success state** — subscription-started renders, registration/next-step handoff works.
|
|
65
|
-
|
|
66
|
-
If checkout, payment mode, or test credentials are unavailable, name the skipped item explicitly — a silent skip counts as a fail.
|
|
67
|
-
|
|
68
|
-
## 5. Registration and Links
|
|
69
|
-
|
|
70
|
-
- Complete the registration page with valid test data; confirm validation errors on invalid data.
|
|
71
|
-
- Click every legal/support/account link on paywall, checkout, and registration: terms, privacy, subscription policy, money-back, support email.
|
|
72
|
-
|
|
73
|
-
## 6. Subscription Management
|
|
74
|
-
|
|
75
|
-
- Open `/manage-subscription?user_id=<test-user>` (or with `stripe_customer_id`).
|
|
76
|
-
- Confirm the subscription list loads and the cancellation flow completes when a test subscription is available; otherwise name it as a skipped item.
|
|
77
|
-
|
|
78
|
-
## Reporting
|
|
79
|
-
|
|
80
|
-
Every QA run ends with a short report: stage + URLs tested, steps/branches/variants covered, breakpoint results for small 375x667, medium 393x852, large 402x874, and desktop-small 1280x800, image optimization/preload result when relevant, paywall items 1–7 pass/fail, payment method used, console findings, and named blockers or explicitly skipped items with the reason. Blockers block production unless the user accepts the risk in so many words.
|
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
# SDK API Endpoints
|
|
2
|
-
|
|
3
|
-
Funnel code should call shared runtime/payment/analytics services, not raw endpoints, unless a new shared helper is being built.
|
|
4
|
-
|
|
5
|
-
## Runtime Config
|
|
6
|
-
|
|
7
|
-
`@funnelsgrove/runtime` reads public config from env aliases and exposes `runtimePublicConfig`:
|
|
8
|
-
|
|
9
|
-
- SDK API base URL.
|
|
10
|
-
- funnel id and funnel version id.
|
|
11
|
-
- SDK publishable key.
|
|
12
|
-
- project id and analytics provider key.
|
|
13
|
-
- support/legal/app-store/deep-link metadata.
|
|
14
|
-
|
|
15
|
-
`buildMainApiUrl(...)` and `buildSdkHeaders(...)` add the correct SDK base URL and `x-sdk-publishable-key`. Preview frame runtime intentionally suppresses normal SDK calls.
|
|
16
|
-
|
|
17
|
-
## User and State APIs
|
|
18
|
-
|
|
19
|
-
Use `apiService` from `@funnelsgrove/runtime`:
|
|
20
|
-
|
|
21
|
-
- `bootstrapSession(...)`: create/restore the visitor using local user id, URL `user_id`, attribution, email, or name.
|
|
22
|
-
- `updateUser(...)`: persist email/name/progress attributes.
|
|
23
|
-
- `uploadTempPhoto(...)`: upload or locally preview image captures.
|
|
24
|
-
- `getManageSubscriptions()` and `updateSubscription(...)`: subscription management by `user_id` or `stripe_customer_id`.
|
|
25
|
-
- `trackFunnelEvent(...)`, `trackStepStarted(...)`, `trackStepCompleted(...)`: legacy runtime event delivery.
|
|
26
|
-
|
|
27
|
-
The flow controller already bootstraps the user, persists answer attributes, records completed steps, and connects analytics identity through the shared analytics/runtime integration. Steps should only call `apiService.updateUser(...)` directly when they collect profile data such as checkout email.
|
|
28
|
-
|
|
29
|
-
## URL User Attributes
|
|
30
|
-
|
|
31
|
-
The runtime understands these public URL attributes:
|
|
32
|
-
|
|
33
|
-
- `user_id`: canonical funnel user id.
|
|
34
|
-
- `email`: validated user/customer email.
|
|
35
|
-
- `name`, `fullName`, `full_name`: profile name aliases.
|
|
36
|
-
- `stripe_customer_id`: Stripe customer id for subscription management.
|
|
37
|
-
|
|
38
|
-
Keep these names stable. If an SDK endpoint or route starts accepting a new identity parameter, update the URL parser, shared services, and docs together.
|
|
39
|
-
|
|
40
|
-
## Payment APIs
|
|
41
|
-
|
|
42
|
-
Use `@funnelsgrove/payments`:
|
|
43
|
-
|
|
44
|
-
- `getPaywallPlans(...)` / `useResolvedPaywallPlans(...)` for `/sdk/public/payments/plans`.
|
|
45
|
-
- `createStripePaymentIntent(...)` for payment intents.
|
|
46
|
-
- `createStripeSubscriptionCheckout(...)` for subscription Elements checkout.
|
|
47
|
-
- `createStripeCheckoutSession(...)` or `redirectToStripeCheckout(...)` for hosted checkout.
|
|
48
|
-
|
|
49
|
-
Always pass runtime checkout config so published funnels, preview funnels, and local builds resolve the correct funnel id and SDK key.
|
|
50
|
-
|
|
51
|
-
Checkout endpoints receive `user_id` and `customerEmail` when available. Stripe metadata and `client_reference_id` rely on these values for webhook reconciliation, analytics, and project user support links.
|
|
52
|
-
|
|
53
|
-
When a funnel sends a configured Stripe `providerPlanId`, public checkout APIs validate that price id against Stripe and fail with a client-visible error if it is missing. Do not silently replace configured price ids with generated `price_data`; dynamic price fallback is only for legacy/catalog entries that have no provider price id.
|
|
54
|
-
|
|
55
|
-
## Subscription APIs
|
|
56
|
-
|
|
57
|
-
Use the runtime service methods instead of calling the endpoints directly:
|
|
58
|
-
|
|
59
|
-
- `GET /sdk/public/users/:user_id/subscription` or `GET /sdk/public/users/subscription?email=...` -> lightweight entitlement status with the resolved `user_id`, `active`, `activeUntil`, and active Stripe subscription ids.
|
|
60
|
-
- `POST /sdk/public/users/:user_id/claim_subscription` -> claim the first unclaimed active subscription for that `user_id`.
|
|
61
|
-
- `apiService.getManageSubscriptions()` -> `GET /sdk/public/subscriptions`.
|
|
62
|
-
- `apiService.updateSubscription(...)` -> `POST /sdk/public/subscriptions/:subscriptionId/:action`.
|
|
63
|
-
|
|
64
|
-
The status endpoint accepts `user_id` or `email` and returns the resolved `user.user_id` when a funnel user is found. The claim endpoint accepts `funnelId` in the JSON body and only resolves by path `user_id`; it returns `409` if every active subscription is already claimed.
|
|
65
|
-
|
|
66
|
-
The subscription list endpoint accepts `user_id`, `stripe_customer_id`, and `funnelId`. `user_id` resolves the funnel user first; `stripe_customer_id` can resolve the Stripe customer directly when user id is unavailable. Mutation requires `user_id` or `stripe_customer_id` and supports `cancel` or `renew`.
|
|
67
|
-
|
|
68
|
-
## Private User Reads
|
|
69
|
-
|
|
70
|
-
Private SDK user reads are for server-side/admin use only. They can resolve a funnel user by `funnelUserId`, `user_id`, `email`, or `stripeCustomerId` when scoped by the SDK secret key and funnel. This is why paywall email handoff is still useful even when a public link does not know the user id yet.
|
|
71
|
-
|
|
72
|
-
## Rules
|
|
73
|
-
|
|
74
|
-
- Keep payloads minimal and typed.
|
|
75
|
-
- Do not log personal data, payment details, SDK keys, or Stripe secrets.
|
|
76
|
-
- Handle loading, unavailable config, API failure, and final failure states in UI.
|
|
77
|
-
- Use local fallbacks only where the shared service already supports them.
|
|
78
|
-
- If an endpoint contract changes, update the shared service, callers, tests, and these docs together.
|