@funnelsgrove/cli 0.1.14 → 0.1.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 +428 -53
- 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 -55
- package/template_docs/docs/analytics.md +0 -65
- package/template_docs/docs/editing-flow.md +0 -125
- 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
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import {
|
|
3
|
+
createFunnelContractDiagnostic,
|
|
4
|
+
CURRENT_STEP_CONTRACT_HASH,
|
|
5
|
+
CURRENT_STEP_CONTRACT_VERSION,
|
|
6
|
+
type FunnelStepContractProjectSnapshot,
|
|
7
|
+
} from '@funnelsgrove/runtime';
|
|
8
|
+
import docsManifest from '../../.funnelsgrove-docs.json';
|
|
9
|
+
import { manifestExperiments } from '@/config/experiments';
|
|
10
|
+
import { funnelManifest, rawFunnelManifest } from '@/config/funnel.manifest';
|
|
11
|
+
import { stepRegistry } from '@/runtime/step-registry';
|
|
12
|
+
import { contentOptionsByStepId } from '@/steps/step-content.registry';
|
|
13
|
+
|
|
14
|
+
const loadProjectValidation = async () => {
|
|
15
|
+
return import('./funnel-project.validation');
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
describe('funnel project validation', () => {
|
|
19
|
+
it('builds the complete project snapshot from the authored registries', async () => {
|
|
20
|
+
const projectValidation = await loadProjectValidation();
|
|
21
|
+
|
|
22
|
+
expect(projectValidation).not.toBeNull();
|
|
23
|
+
const snapshot = projectValidation.buildFunnelProjectSnapshot();
|
|
24
|
+
|
|
25
|
+
expect(snapshot).toEqual({
|
|
26
|
+
manifest: rawFunnelManifest,
|
|
27
|
+
componentMetaByKey: Object.fromEntries(
|
|
28
|
+
Object.entries(stepRegistry).map(([componentKey, entry]) => [componentKey, entry.meta]),
|
|
29
|
+
),
|
|
30
|
+
registeredComponentKeys: Object.keys(stepRegistry),
|
|
31
|
+
contentOptionsByStepId,
|
|
32
|
+
edgesByStepId: rawFunnelManifest.edgesByStepId,
|
|
33
|
+
experiments: manifestExperiments,
|
|
34
|
+
docs: {
|
|
35
|
+
schemaVersion: docsManifest.schemaVersion,
|
|
36
|
+
bundleVersion: docsManifest.bundleVersion,
|
|
37
|
+
stepContractVersion: docsManifest.stepContractVersion,
|
|
38
|
+
contractHash: docsManifest.contractHash,
|
|
39
|
+
missing: [],
|
|
40
|
+
changed: [],
|
|
41
|
+
conflicts: [],
|
|
42
|
+
},
|
|
43
|
+
});
|
|
44
|
+
expect(projectValidation.FUNNEL_PROJECT_CONTRACT_VERSION)
|
|
45
|
+
.toBe(CURRENT_STEP_CONTRACT_VERSION);
|
|
46
|
+
expect(funnelManifest).toBe(rawFunnelManifest);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('returns pure authoring diagnostics without changing their fields', async () => {
|
|
50
|
+
const { buildFunnelProjectSnapshot, validateFunnelProject } = await loadProjectValidation();
|
|
51
|
+
const snapshot = structuredClone(
|
|
52
|
+
buildFunnelProjectSnapshot(),
|
|
53
|
+
) as FunnelStepContractProjectSnapshot;
|
|
54
|
+
const paywall = snapshot.componentMetaByKey.stepPaywall;
|
|
55
|
+
snapshot.componentMetaByKey.stepPaywall = {
|
|
56
|
+
...paywall,
|
|
57
|
+
name: 'renamed-paywall',
|
|
58
|
+
};
|
|
59
|
+
const expectedDiagnostic = createFunnelContractDiagnostic({
|
|
60
|
+
code: 'FG-PARITY-001',
|
|
61
|
+
file: 'src/steps/step-32-paywall.tsx',
|
|
62
|
+
stepId: 'paywall',
|
|
63
|
+
expected: 'paywall',
|
|
64
|
+
received: 'renamed-paywall',
|
|
65
|
+
reason: 'Component metadata name does not match the manifest step.',
|
|
66
|
+
guide: 'docs/product-specs/funnel-template-step-authoring.md',
|
|
67
|
+
repair: 'Update the component metadata name to match the manifest step.',
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
expect(validateFunnelProject(snapshot)).toEqual([expectedDiagnostic]);
|
|
71
|
+
expect(snapshot.componentMetaByKey.stepPaywall.name).toBe('renamed-paywall');
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it('validates the checked-in template without diagnostics', async () => {
|
|
75
|
+
const { validateFunnelProject } = await loadProjectValidation();
|
|
76
|
+
|
|
77
|
+
expect(validateFunnelProject()).toEqual([]);
|
|
78
|
+
expect(CURRENT_STEP_CONTRACT_HASH).toBe(docsManifest.contractHash);
|
|
79
|
+
});
|
|
80
|
+
});
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CURRENT_STEP_CONTRACT_VERSION,
|
|
3
|
+
validateFunnelStepContractSnapshot,
|
|
4
|
+
type FunnelContractDiagnostic,
|
|
5
|
+
type FunnelStepContractProjectSnapshot,
|
|
6
|
+
type FunnelStepMeta,
|
|
7
|
+
} from '@funnelsgrove/runtime';
|
|
8
|
+
import docsManifest from '../../.funnelsgrove-docs.json';
|
|
9
|
+
import { manifestExperiments } from '@/config/experiments';
|
|
10
|
+
import { rawFunnelManifest } from '@/config/funnel.manifest';
|
|
11
|
+
import { stepRegistry } from '@/runtime/step-registry';
|
|
12
|
+
import { contentOptionsByStepId } from '@/steps/step-content.registry';
|
|
13
|
+
|
|
14
|
+
export const FUNNEL_PROJECT_CONTRACT_VERSION = CURRENT_STEP_CONTRACT_VERSION;
|
|
15
|
+
|
|
16
|
+
export const buildFunnelProjectSnapshot = (): FunnelStepContractProjectSnapshot => {
|
|
17
|
+
const componentMetaByKey = Object.fromEntries(
|
|
18
|
+
Object.entries(stepRegistry).map(([componentKey, entry]) => [componentKey, entry.meta]),
|
|
19
|
+
) as Record<string, FunnelStepMeta>;
|
|
20
|
+
|
|
21
|
+
return {
|
|
22
|
+
manifest: rawFunnelManifest,
|
|
23
|
+
componentMetaByKey,
|
|
24
|
+
registeredComponentKeys: Object.keys(stepRegistry),
|
|
25
|
+
contentOptionsByStepId,
|
|
26
|
+
edgesByStepId: rawFunnelManifest.edgesByStepId,
|
|
27
|
+
experiments: manifestExperiments,
|
|
28
|
+
docs: {
|
|
29
|
+
schemaVersion: docsManifest.schemaVersion,
|
|
30
|
+
bundleVersion: docsManifest.bundleVersion,
|
|
31
|
+
stepContractVersion: docsManifest.stepContractVersion,
|
|
32
|
+
contractHash: docsManifest.contractHash,
|
|
33
|
+
missing: [],
|
|
34
|
+
changed: [],
|
|
35
|
+
conflicts: [],
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
export const validateFunnelProject = (
|
|
41
|
+
snapshot: FunnelStepContractProjectSnapshot = buildFunnelProjectSnapshot(),
|
|
42
|
+
): FunnelContractDiagnostic[] => {
|
|
43
|
+
return validateFunnelStepContractSnapshot(snapshot, { mode: 'authoring' });
|
|
44
|
+
};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { FunnelManifestStep, FunnelStepMeta } from '@funnelsgrove/runtime';
|
|
2
|
+
|
|
3
|
+
const manifestStep = {
|
|
4
|
+
id: 'save-offer',
|
|
5
|
+
name: 'save-offer',
|
|
6
|
+
path: '/save-offer',
|
|
7
|
+
filePath: 'src/steps/step-save-offer.tsx',
|
|
8
|
+
componentKey: 'stepSaveOffer',
|
|
9
|
+
type: 'cancellation_offer',
|
|
10
|
+
kind: 'cancellation',
|
|
11
|
+
title: 'Stay with us',
|
|
12
|
+
} as const satisfies FunnelManifestStep;
|
|
13
|
+
|
|
14
|
+
const componentMeta = {
|
|
15
|
+
id: 'save-offer',
|
|
16
|
+
name: 'save-offer',
|
|
17
|
+
type: 'cancellation_offer',
|
|
18
|
+
kind: 'cancellation',
|
|
19
|
+
figmaNodeId: 'golden:cancellation-offer',
|
|
20
|
+
title: 'Stay with us',
|
|
21
|
+
description: 'Offer a retention path before cancellation.',
|
|
22
|
+
} as const satisfies FunnelStepMeta;
|
|
23
|
+
|
|
24
|
+
export const goldenFixture = {
|
|
25
|
+
stepType: 'cancellation_offer',
|
|
26
|
+
manifestStep,
|
|
27
|
+
componentMeta,
|
|
28
|
+
helper: 'runtime subscription helpers',
|
|
29
|
+
} as const;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { FunnelManifestStep, FunnelStepMeta } from '@funnelsgrove/runtime';
|
|
2
|
+
|
|
3
|
+
const manifestStep = {
|
|
4
|
+
id: 'checkout',
|
|
5
|
+
name: 'checkout',
|
|
6
|
+
path: '/checkout',
|
|
7
|
+
filePath: 'src/steps/step-checkout.tsx',
|
|
8
|
+
componentKey: 'stepCheckout',
|
|
9
|
+
type: 'checkout',
|
|
10
|
+
title: 'Secure checkout',
|
|
11
|
+
} as const satisfies FunnelManifestStep;
|
|
12
|
+
|
|
13
|
+
const componentMeta = {
|
|
14
|
+
id: 'checkout',
|
|
15
|
+
name: 'checkout',
|
|
16
|
+
type: 'checkout',
|
|
17
|
+
figmaNodeId: 'golden:checkout',
|
|
18
|
+
title: 'Secure checkout',
|
|
19
|
+
description: 'Collect payment through shared checkout UI.',
|
|
20
|
+
} as const satisfies FunnelStepMeta;
|
|
21
|
+
|
|
22
|
+
export const goldenFixture = {
|
|
23
|
+
stepType: 'checkout',
|
|
24
|
+
manifestStep,
|
|
25
|
+
componentMeta,
|
|
26
|
+
requiredEventMetadata: [
|
|
27
|
+
'funnelId',
|
|
28
|
+
'funnelVersionId',
|
|
29
|
+
'environment',
|
|
30
|
+
'stepContractVersion',
|
|
31
|
+
'stepId',
|
|
32
|
+
'stepName',
|
|
33
|
+
'stepType',
|
|
34
|
+
'eventId',
|
|
35
|
+
],
|
|
36
|
+
semanticHelpers: ['trackCheckoutStarted', 'trackPaymentInfoSubmitted', 'trackCheckoutCompleted'],
|
|
37
|
+
} as const;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { FunnelManifestStep, FunnelStepMeta } from '@funnelsgrove/runtime';
|
|
2
|
+
|
|
3
|
+
const manifestStep = {
|
|
4
|
+
id: 'email-capture',
|
|
5
|
+
name: 'email-capture',
|
|
6
|
+
path: '/email-capture',
|
|
7
|
+
filePath: 'src/steps/step-email-capture.tsx',
|
|
8
|
+
componentKey: 'stepEmailCapture',
|
|
9
|
+
type: 'form_input',
|
|
10
|
+
title: 'Your email',
|
|
11
|
+
} as const satisfies FunnelManifestStep;
|
|
12
|
+
|
|
13
|
+
const componentMeta = {
|
|
14
|
+
id: 'email-capture',
|
|
15
|
+
name: 'email-capture',
|
|
16
|
+
type: 'form_input',
|
|
17
|
+
figmaNodeId: 'golden:form-input',
|
|
18
|
+
title: 'Your email',
|
|
19
|
+
description: 'Capture an email through the transactional helper.',
|
|
20
|
+
} as const satisfies FunnelStepMeta;
|
|
21
|
+
|
|
22
|
+
export const goldenFixture = {
|
|
23
|
+
stepType: 'form_input',
|
|
24
|
+
manifestStep,
|
|
25
|
+
componentMeta,
|
|
26
|
+
helper: 'submitEmailCapture',
|
|
27
|
+
forbiddenCalls: ['trackLead', 'provider APIs', 'hard-coded paywall navigation'],
|
|
28
|
+
} as const;
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import {
|
|
3
|
+
CURRENT_STEP_CONTRACT_VERSION,
|
|
4
|
+
FUNNEL_STEP_TYPES,
|
|
5
|
+
resolveStepTypeContract,
|
|
6
|
+
validateFunnelManifestContract,
|
|
7
|
+
validateFunnelStepMetadataParity,
|
|
8
|
+
type FunnelContextValue,
|
|
9
|
+
type FunnelManifest,
|
|
10
|
+
} from '@funnelsgrove/runtime';
|
|
11
|
+
import { publicAnalyticsSdk } from '@funnelsgrove/analytics';
|
|
12
|
+
import { goldenFixture as cancellationOffer } from './cancellation_offer.fixture';
|
|
13
|
+
import { goldenFixture as checkout } from './checkout.fixture';
|
|
14
|
+
import { goldenFixture as purchaseCompleted } from './purchase_completed.fixture';
|
|
15
|
+
import { goldenFixture as formInput } from './form_input.fixture';
|
|
16
|
+
import { goldenFixture as introHero } from './intro_hero.fixture';
|
|
17
|
+
import { goldenFixture as multiSelectChoice } from './multi_select_choice.fixture';
|
|
18
|
+
import { goldenFixture as paywallOffer } from './paywall_offer.fixture';
|
|
19
|
+
import { goldenFixture as progressInterstitial } from './progress_interstitial.fixture';
|
|
20
|
+
import { goldenFixture as singleStepChoice } from './single_step_choice.fixture';
|
|
21
|
+
import { goldenFixture as singleStepChoiceEmoji } from './single_step_choice_emoji.fixture';
|
|
22
|
+
import { goldenFixture as socialProof } from './social_proof.fixture';
|
|
23
|
+
import { goldenFixture as subscriptionHandoff } from './subscription_handoff.fixture';
|
|
24
|
+
import { goldenFixture as subscriptionManagement } from './subscription_management.fixture';
|
|
25
|
+
import { goldenFixture as summaryConfirmation } from './summary_confirmation.fixture';
|
|
26
|
+
import { goldenFixture as upsellOffer } from './upsell_offer.fixture';
|
|
27
|
+
import { goldenFixture as valuePropStory } from './value_prop_story.fixture';
|
|
28
|
+
|
|
29
|
+
const fixtures = [
|
|
30
|
+
cancellationOffer,
|
|
31
|
+
checkout,
|
|
32
|
+
purchaseCompleted,
|
|
33
|
+
formInput,
|
|
34
|
+
introHero,
|
|
35
|
+
multiSelectChoice,
|
|
36
|
+
paywallOffer,
|
|
37
|
+
progressInterstitial,
|
|
38
|
+
singleStepChoice,
|
|
39
|
+
singleStepChoiceEmoji,
|
|
40
|
+
socialProof,
|
|
41
|
+
subscriptionHandoff,
|
|
42
|
+
subscriptionManagement,
|
|
43
|
+
summaryConfirmation,
|
|
44
|
+
upsellOffer,
|
|
45
|
+
valuePropStory,
|
|
46
|
+
];
|
|
47
|
+
|
|
48
|
+
const validationFixtures = [
|
|
49
|
+
introHero,
|
|
50
|
+
valuePropStory,
|
|
51
|
+
singleStepChoice,
|
|
52
|
+
singleStepChoiceEmoji,
|
|
53
|
+
multiSelectChoice,
|
|
54
|
+
formInput,
|
|
55
|
+
socialProof,
|
|
56
|
+
progressInterstitial,
|
|
57
|
+
paywallOffer,
|
|
58
|
+
upsellOffer,
|
|
59
|
+
checkout,
|
|
60
|
+
subscriptionManagement,
|
|
61
|
+
subscriptionHandoff,
|
|
62
|
+
cancellationOffer,
|
|
63
|
+
summaryConfirmation,
|
|
64
|
+
purchaseCompleted,
|
|
65
|
+
];
|
|
66
|
+
|
|
67
|
+
const commerceSemanticHelpers = [
|
|
68
|
+
'trackCheckoutStarted',
|
|
69
|
+
'trackPaymentInfoSubmitted',
|
|
70
|
+
'trackCheckoutCompleted',
|
|
71
|
+
] as const satisfies readonly (keyof typeof publicAnalyticsSdk)[];
|
|
72
|
+
const terminalSemanticHelpers = (
|
|
73
|
+
purchaseCompleted.semanticHelpers
|
|
74
|
+
) satisfies readonly (keyof FunnelContextValue)[];
|
|
75
|
+
|
|
76
|
+
describe('step contract golden fixtures', () => {
|
|
77
|
+
it('covers all 16 step types exactly once with component/manifest metadata parity', () => {
|
|
78
|
+
expect(fixtures).toHaveLength(16);
|
|
79
|
+
expect(new Set(fixtures.map((fixture) => fixture.stepType)).size).toBe(16);
|
|
80
|
+
expect(fixtures.map((fixture) => fixture.stepType).sort()).toEqual(
|
|
81
|
+
FUNNEL_STEP_TYPES.filter((type) => (
|
|
82
|
+
resolveStepTypeContract(CURRENT_STEP_CONTRACT_VERSION, type)
|
|
83
|
+
)).sort(),
|
|
84
|
+
);
|
|
85
|
+
for (const fixture of fixtures) {
|
|
86
|
+
expect(fixture.manifestStep.type).toBe(fixture.stepType);
|
|
87
|
+
expect(fixture.componentMeta).toMatchObject({
|
|
88
|
+
id: fixture.manifestStep.id,
|
|
89
|
+
name: fixture.manifestStep.name,
|
|
90
|
+
type: fixture.manifestStep.type,
|
|
91
|
+
});
|
|
92
|
+
expect(fixture.manifestStep.filePath).toMatch(/^src\/steps\/.+\.tsx$/);
|
|
93
|
+
expect(fixture.manifestStep.componentKey).toBeTruthy();
|
|
94
|
+
expect(fixture.manifestStep.title).toBeTruthy();
|
|
95
|
+
expect(fixture.componentMeta.figmaNodeId).toBeTruthy();
|
|
96
|
+
expect(fixture.componentMeta.title).toBeTruthy();
|
|
97
|
+
expect(fixture.componentMeta.description).toBeTruthy();
|
|
98
|
+
expect(validateFunnelStepMetadataParity(
|
|
99
|
+
fixture.manifestStep,
|
|
100
|
+
fixture.componentMeta,
|
|
101
|
+
)).toEqual([]);
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it('forms one valid self-contained contract-v3 manifest', () => {
|
|
106
|
+
const manifest = {
|
|
107
|
+
templateArchitectureVersion: 1,
|
|
108
|
+
stepContractVersion: CURRENT_STEP_CONTRACT_VERSION,
|
|
109
|
+
meta: { title: 'Golden fixture funnel', description: 'Validates all canonical types.' },
|
|
110
|
+
viewport: { width: 430, height: 932 },
|
|
111
|
+
assets: {},
|
|
112
|
+
steps: validationFixtures.map((fixture) => fixture.manifestStep),
|
|
113
|
+
edgesByStepId: Object.fromEntries(validationFixtures.slice(0, -1).map((fixture, index) => [
|
|
114
|
+
fixture.manifestStep.id,
|
|
115
|
+
[{ toStepId: validationFixtures[index + 1].manifestStep.id }],
|
|
116
|
+
])),
|
|
117
|
+
experiments: [],
|
|
118
|
+
} satisfies FunnelManifest;
|
|
119
|
+
|
|
120
|
+
expect(validateFunnelManifestContract(manifest, { mode: 'authoring' })).toEqual([]);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it('uses stable scalar and array IDs for choice answers', () => {
|
|
124
|
+
expect(singleStepChoice.persistedAnswer).toBe('daily');
|
|
125
|
+
expect(singleStepChoiceEmoji.persistedAnswer).toBe('focused');
|
|
126
|
+
expect(singleStepChoiceEmoji.options.every((option) => Boolean(option.emoji))).toBe(true);
|
|
127
|
+
expect(multiSelectChoice.persistedAnswer).toEqual(['sleep', 'focus']);
|
|
128
|
+
expect(multiSelectChoice.emptyAnswer).toEqual([]);
|
|
129
|
+
expect(multiSelectChoice.manifestStep.choice.allowEmpty).toBe(true);
|
|
130
|
+
for (const fixture of [singleStepChoice, singleStepChoiceEmoji, multiSelectChoice]) {
|
|
131
|
+
expect(fixture.manifestStep.choice.answerKey).toBeTruthy();
|
|
132
|
+
expect(fixture.helper).toBe('useStepChoices');
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it('locks reserved identities and trusted conversion ownership', () => {
|
|
137
|
+
expect(formInput.manifestStep).toMatchObject({ id: 'email-capture', name: 'email-capture' });
|
|
138
|
+
expect(formInput.helper).toBe('submitEmailCapture');
|
|
139
|
+
expect(formInput.forbiddenCalls).toEqual(expect.arrayContaining([
|
|
140
|
+
'trackLead',
|
|
141
|
+
'provider APIs',
|
|
142
|
+
'hard-coded paywall navigation',
|
|
143
|
+
]));
|
|
144
|
+
expect(paywallOffer.manifestStep).toMatchObject({
|
|
145
|
+
id: 'paywall',
|
|
146
|
+
name: 'paywall',
|
|
147
|
+
type: 'paywall_offer',
|
|
148
|
+
kind: 'paywall',
|
|
149
|
+
});
|
|
150
|
+
expect(subscriptionManagement.manifestStep).toMatchObject({
|
|
151
|
+
id: 'manage-subscription',
|
|
152
|
+
name: 'manage-subscription',
|
|
153
|
+
kind: 'manage-subscription',
|
|
154
|
+
});
|
|
155
|
+
expect(purchaseCompleted.manifestStep).toMatchObject({
|
|
156
|
+
id: 'subscription-started',
|
|
157
|
+
name: 'subscription-started',
|
|
158
|
+
type: 'purchase_completed',
|
|
159
|
+
kind: 'subscription-handoff',
|
|
160
|
+
});
|
|
161
|
+
expect(purchaseCompleted.forbiddenBrowserEvents).toEqual([
|
|
162
|
+
'purchase_completed',
|
|
163
|
+
'registration_completed',
|
|
164
|
+
]);
|
|
165
|
+
expect(terminalSemanticHelpers).toEqual(['completeFunnel']);
|
|
166
|
+
expect(subscriptionHandoff.isRegistrationCompletion).toBe(false);
|
|
167
|
+
expect(cancellationOffer.manifestStep.kind).toBe('cancellation');
|
|
168
|
+
expect(upsellOffer.manifestStep.kind).toBe('upsell');
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
it('uses named checkout helpers only on commerce steps', () => {
|
|
172
|
+
for (const fixture of [paywallOffer, upsellOffer, checkout]) {
|
|
173
|
+
expect(fixture.semanticHelpers).toEqual(commerceSemanticHelpers);
|
|
174
|
+
}
|
|
175
|
+
expect(typeof publicAnalyticsSdk.trackPaymentInfoSubmitted).toBe('function');
|
|
176
|
+
expect('trackAddPaymentInfo' in publicAnalyticsSdk).toBe(false);
|
|
177
|
+
expect(progressInterstitial.semanticHelpers).toEqual([]);
|
|
178
|
+
expect(progressInterstitial.isPaywall).toBe(false);
|
|
179
|
+
expect(checkout.requiredEventMetadata).toEqual([
|
|
180
|
+
'funnelId',
|
|
181
|
+
'funnelVersionId',
|
|
182
|
+
'environment',
|
|
183
|
+
'stepContractVersion',
|
|
184
|
+
'stepId',
|
|
185
|
+
'stepName',
|
|
186
|
+
'stepType',
|
|
187
|
+
'eventId',
|
|
188
|
+
]);
|
|
189
|
+
});
|
|
190
|
+
});
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { FunnelManifestStep, FunnelStepMeta } from '@funnelsgrove/runtime';
|
|
2
|
+
|
|
3
|
+
const manifestStep = {
|
|
4
|
+
id: 'welcome',
|
|
5
|
+
name: 'welcome',
|
|
6
|
+
path: '/welcome',
|
|
7
|
+
filePath: 'src/steps/step-welcome.tsx',
|
|
8
|
+
componentKey: 'stepWelcome',
|
|
9
|
+
type: 'intro_hero',
|
|
10
|
+
title: 'Welcome',
|
|
11
|
+
} as const satisfies FunnelManifestStep;
|
|
12
|
+
|
|
13
|
+
const componentMeta = {
|
|
14
|
+
id: 'welcome',
|
|
15
|
+
name: 'welcome',
|
|
16
|
+
type: 'intro_hero',
|
|
17
|
+
figmaNodeId: 'golden:intro-hero',
|
|
18
|
+
title: 'Welcome',
|
|
19
|
+
description: 'Opening promise and low-friction entry.',
|
|
20
|
+
} as const satisfies FunnelStepMeta;
|
|
21
|
+
|
|
22
|
+
export const goldenFixture = {
|
|
23
|
+
stepType: 'intro_hero',
|
|
24
|
+
manifestStep,
|
|
25
|
+
componentMeta,
|
|
26
|
+
actionOwner: 'shared-shell',
|
|
27
|
+
} as const;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { FunnelManifestStep, FunnelStepMeta } from '@funnelsgrove/runtime';
|
|
2
|
+
|
|
3
|
+
const choice = { answerKey: 'priorities', allowEmpty: true } as const;
|
|
4
|
+
|
|
5
|
+
const manifestStep = {
|
|
6
|
+
id: 'priorities',
|
|
7
|
+
name: 'priorities',
|
|
8
|
+
path: '/priorities',
|
|
9
|
+
filePath: 'src/steps/step-priorities.tsx',
|
|
10
|
+
componentKey: 'stepPriorities',
|
|
11
|
+
type: 'multi_select_choice',
|
|
12
|
+
choice,
|
|
13
|
+
title: 'Choose your priorities',
|
|
14
|
+
} as const satisfies FunnelManifestStep;
|
|
15
|
+
|
|
16
|
+
const componentMeta = {
|
|
17
|
+
id: 'priorities',
|
|
18
|
+
name: 'priorities',
|
|
19
|
+
type: 'multi_select_choice',
|
|
20
|
+
choice,
|
|
21
|
+
figmaNodeId: 'golden:multi-select-choice',
|
|
22
|
+
title: 'Choose your priorities',
|
|
23
|
+
description: 'Select any priorities that apply.',
|
|
24
|
+
} as const satisfies FunnelStepMeta;
|
|
25
|
+
|
|
26
|
+
export const goldenFixture = {
|
|
27
|
+
stepType: 'multi_select_choice',
|
|
28
|
+
manifestStep,
|
|
29
|
+
componentMeta,
|
|
30
|
+
options: [
|
|
31
|
+
{ id: 'sleep', label: 'Better sleep' },
|
|
32
|
+
{ id: 'focus', label: 'More focus' },
|
|
33
|
+
],
|
|
34
|
+
persistedAnswer: ['sleep', 'focus'],
|
|
35
|
+
emptyAnswer: [],
|
|
36
|
+
helper: 'useStepChoices',
|
|
37
|
+
} as const;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { FunnelManifestStep, FunnelStepMeta } from '@funnelsgrove/runtime';
|
|
2
|
+
|
|
3
|
+
const manifestStep = {
|
|
4
|
+
id: 'paywall',
|
|
5
|
+
name: 'paywall',
|
|
6
|
+
path: '/paywall',
|
|
7
|
+
filePath: 'src/steps/step-paywall.tsx',
|
|
8
|
+
componentKey: 'stepPaywall',
|
|
9
|
+
type: 'paywall_offer',
|
|
10
|
+
kind: 'paywall',
|
|
11
|
+
title: 'Choose your plan',
|
|
12
|
+
} as const satisfies FunnelManifestStep;
|
|
13
|
+
|
|
14
|
+
const componentMeta = {
|
|
15
|
+
id: 'paywall',
|
|
16
|
+
name: 'paywall',
|
|
17
|
+
type: 'paywall_offer',
|
|
18
|
+
kind: 'paywall',
|
|
19
|
+
figmaNodeId: 'golden:paywall-offer',
|
|
20
|
+
title: 'Choose your plan',
|
|
21
|
+
description: 'Present purchasable plans.',
|
|
22
|
+
} as const satisfies FunnelStepMeta;
|
|
23
|
+
|
|
24
|
+
export const goldenFixture = {
|
|
25
|
+
stepType: 'paywall_offer',
|
|
26
|
+
manifestStep,
|
|
27
|
+
componentMeta,
|
|
28
|
+
presentsPurchasablePlans: true,
|
|
29
|
+
semanticHelpers: ['trackCheckoutStarted', 'trackPaymentInfoSubmitted', 'trackCheckoutCompleted'],
|
|
30
|
+
} as const;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { FunnelManifestStep, FunnelStepMeta } from '@funnelsgrove/runtime';
|
|
2
|
+
|
|
3
|
+
const manifestStep = {
|
|
4
|
+
id: 'building-plan',
|
|
5
|
+
name: 'building-plan',
|
|
6
|
+
path: '/building-plan',
|
|
7
|
+
filePath: 'src/steps/step-building-plan.tsx',
|
|
8
|
+
componentKey: 'stepBuildingPlan',
|
|
9
|
+
type: 'progress_interstitial',
|
|
10
|
+
title: 'Building your plan',
|
|
11
|
+
} as const satisfies FunnelManifestStep;
|
|
12
|
+
|
|
13
|
+
const componentMeta = {
|
|
14
|
+
id: 'building-plan',
|
|
15
|
+
name: 'building-plan',
|
|
16
|
+
type: 'progress_interstitial',
|
|
17
|
+
figmaNodeId: 'golden:progress-interstitial',
|
|
18
|
+
title: 'Building your plan',
|
|
19
|
+
description: 'Show progress while preparing the next screen.',
|
|
20
|
+
} as const satisfies FunnelStepMeta;
|
|
21
|
+
|
|
22
|
+
export const goldenFixture = {
|
|
23
|
+
stepType: 'progress_interstitial',
|
|
24
|
+
manifestStep,
|
|
25
|
+
componentMeta,
|
|
26
|
+
semanticHelpers: [],
|
|
27
|
+
isPaywall: false,
|
|
28
|
+
} as const;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { FunnelContextValue, FunnelManifestStep, FunnelStepMeta } from '@funnelsgrove/runtime';
|
|
2
|
+
|
|
3
|
+
const terminalCompletionHelper = 'completeFunnel' as const satisfies keyof FunnelContextValue;
|
|
4
|
+
|
|
5
|
+
const manifestStep = {
|
|
6
|
+
id: 'subscription-started',
|
|
7
|
+
name: 'subscription-started',
|
|
8
|
+
path: '/subscription-started',
|
|
9
|
+
filePath: 'src/steps/step-subscription-started.tsx',
|
|
10
|
+
componentKey: 'stepSubscriptionStarted',
|
|
11
|
+
type: 'purchase_completed',
|
|
12
|
+
kind: 'subscription-handoff',
|
|
13
|
+
title: 'Purchase completed',
|
|
14
|
+
} as const satisfies FunnelManifestStep;
|
|
15
|
+
|
|
16
|
+
const componentMeta = {
|
|
17
|
+
id: 'subscription-started',
|
|
18
|
+
name: 'subscription-started',
|
|
19
|
+
type: 'purchase_completed',
|
|
20
|
+
kind: 'subscription-handoff',
|
|
21
|
+
figmaNodeId: 'golden:purchase-completed',
|
|
22
|
+
title: 'Purchase completed',
|
|
23
|
+
description: 'Complete the funnel after purchase and continue into the app.',
|
|
24
|
+
} as const satisfies FunnelStepMeta;
|
|
25
|
+
|
|
26
|
+
export const goldenFixture = {
|
|
27
|
+
stepType: 'purchase_completed',
|
|
28
|
+
manifestStep,
|
|
29
|
+
componentMeta,
|
|
30
|
+
helper: 'runtime handoff helper',
|
|
31
|
+
semanticHelpers: [terminalCompletionHelper],
|
|
32
|
+
forbiddenBrowserEvents: ['purchase_completed', 'registration_completed'],
|
|
33
|
+
} as const;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { FunnelManifestStep, FunnelStepMeta } from '@funnelsgrove/runtime';
|
|
2
|
+
|
|
3
|
+
const choice = { answerKey: 'routine' } as const;
|
|
4
|
+
|
|
5
|
+
const manifestStep = {
|
|
6
|
+
id: 'routine',
|
|
7
|
+
name: 'routine',
|
|
8
|
+
path: '/routine',
|
|
9
|
+
filePath: 'src/steps/step-routine.tsx',
|
|
10
|
+
componentKey: 'stepRoutine',
|
|
11
|
+
type: 'single_step_choice',
|
|
12
|
+
choice,
|
|
13
|
+
title: 'Choose a routine',
|
|
14
|
+
} as const satisfies FunnelManifestStep;
|
|
15
|
+
|
|
16
|
+
const componentMeta = {
|
|
17
|
+
id: 'routine',
|
|
18
|
+
name: 'routine',
|
|
19
|
+
type: 'single_step_choice',
|
|
20
|
+
choice,
|
|
21
|
+
figmaNodeId: 'golden:single-step-choice',
|
|
22
|
+
title: 'Choose a routine',
|
|
23
|
+
description: 'Select exactly one routine.',
|
|
24
|
+
} as const satisfies FunnelStepMeta;
|
|
25
|
+
|
|
26
|
+
export const goldenFixture = {
|
|
27
|
+
stepType: 'single_step_choice',
|
|
28
|
+
manifestStep,
|
|
29
|
+
componentMeta,
|
|
30
|
+
options: [
|
|
31
|
+
{ id: 'daily', label: 'Every day' },
|
|
32
|
+
{ id: 'weekdays', label: 'Weekdays' },
|
|
33
|
+
],
|
|
34
|
+
persistedAnswer: 'daily',
|
|
35
|
+
helper: 'useStepChoices',
|
|
36
|
+
} as const;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { FunnelManifestStep, FunnelStepMeta } from '@funnelsgrove/runtime';
|
|
2
|
+
|
|
3
|
+
const choice = { answerKey: 'goalFeeling' } as const;
|
|
4
|
+
|
|
5
|
+
const manifestStep = {
|
|
6
|
+
id: 'goal-feeling',
|
|
7
|
+
name: 'goal-feeling',
|
|
8
|
+
path: '/goal-feeling',
|
|
9
|
+
filePath: 'src/steps/step-goal-feeling.tsx',
|
|
10
|
+
componentKey: 'stepGoalFeeling',
|
|
11
|
+
type: 'single_step_choice_emoji',
|
|
12
|
+
choice,
|
|
13
|
+
title: 'How do you want to feel?',
|
|
14
|
+
} as const satisfies FunnelManifestStep;
|
|
15
|
+
|
|
16
|
+
const componentMeta = {
|
|
17
|
+
id: 'goal-feeling',
|
|
18
|
+
name: 'goal-feeling',
|
|
19
|
+
type: 'single_step_choice_emoji',
|
|
20
|
+
choice,
|
|
21
|
+
figmaNodeId: 'golden:single-step-choice-emoji',
|
|
22
|
+
title: 'How do you want to feel?',
|
|
23
|
+
description: 'Select exactly one emoji-led feeling.',
|
|
24
|
+
} as const satisfies FunnelStepMeta;
|
|
25
|
+
|
|
26
|
+
export const goldenFixture = {
|
|
27
|
+
stepType: 'single_step_choice_emoji',
|
|
28
|
+
manifestStep,
|
|
29
|
+
componentMeta,
|
|
30
|
+
options: [
|
|
31
|
+
{ id: 'focused', label: 'Focused', emoji: '🎯' },
|
|
32
|
+
{ id: 'calm', label: 'Calm', emoji: '🌿' },
|
|
33
|
+
],
|
|
34
|
+
persistedAnswer: 'focused',
|
|
35
|
+
helper: 'useStepChoices',
|
|
36
|
+
} as const;
|