create-ailk 0.22.0 → 0.24.0
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/README.md +27 -14
- package/component-catalog.json +149 -5
- package/component-catalog.md +7 -6
- package/dist/add-module.js +17 -10
- package/dist/add-route.d.ts +55 -1
- package/dist/add-route.js +301 -13
- package/dist/cli.js +27 -10
- package/dist/derive-architecture.d.ts +7 -5
- package/dist/derive-architecture.js +26 -32
- package/dist/lib/design-checks.js +4 -2
- package/dist/lib/env-catalog.js +0 -4
- package/dist/lib/npm-auth.d.ts +9 -0
- package/dist/lib/npm-auth.js +48 -0
- package/dist/lib/scaffold-render-sweep.d.ts +36 -2
- package/dist/lib/scaffold-render-sweep.js +50 -8
- package/dist/module-architecture.js +115 -10
- package/dist/paid-sections.d.ts +21 -2
- package/dist/paid-sections.js +69 -7
- package/dist/seed-flows.d.ts +4 -1
- package/dist/seed-flows.js +50 -4
- package/dist/spoke-checkout.d.ts +38 -35
- package/dist/spoke-checkout.js +66 -229
- package/dist/starter-grid.d.ts +0 -3
- package/dist/starter-grid.js +24 -14
- package/dist/surfaces.js +33 -12
- package/dist/sync-routes.d.ts +68 -5
- package/dist/sync-routes.js +455 -29
- package/dist/template-record.d.ts +1 -1
- package/dist/template-record.js +3 -2
- package/dist/update.js +10 -0
- package/package.json +3 -3
- package/site-templates/_base/content/en/NewsletterSignup/newsletter.mdx +4 -49
- package/site-templates/_base/content/en/Pricing/pricing.mdx +3 -21
- package/site-templates/marketing-site/TEMPLATE-MANIFEST.json +23 -20
- package/site-templates/marketing-site/TEMPLATE-MANIFEST.md +28 -14
- package/site-templates/marketing-site-free/TEMPLATE-MANIFEST.json +15 -39
- package/site-templates/marketing-site-free/TEMPLATE-MANIFEST.md +38 -34
- package/site-templates/marketing-site-free/content/en/NewsletterSignup/newsletter.mdx +6 -43
- package/site-templates/shapes/local-business-service/TEMPLATE-MANIFEST.json +14 -31
- package/site-templates/shapes/local-business-service/TEMPLATE-MANIFEST.md +26 -25
- package/site-templates/shapes/local-business-service/content/en/NewsletterSignup/newsletter.mdx +6 -43
- package/site-templates/shapes/organization-service/TEMPLATE-MANIFEST.json +14 -31
- package/site-templates/shapes/organization-service/TEMPLATE-MANIFEST.md +26 -25
- package/site-templates/shapes/organization-service/content/en/NewsletterSignup/newsletter.mdx +6 -43
- package/site-templates/shapes/person-service/TEMPLATE-MANIFEST.json +14 -31
- package/site-templates/shapes/person-service/TEMPLATE-MANIFEST.md +26 -25
- package/site-templates/shapes/person-service/content/en/NewsletterSignup/newsletter.mdx +6 -43
- package/templates/.env.example +13 -7
- package/templates/.github/workflows/ci.yml +13 -0
- package/templates/CLAUDE.md +4 -2
- package/templates/README.md +1 -0
- package/templates/_gitignore +4 -0
- package/templates/apps/api/.env.example +34 -0
- package/templates/apps/api/CLAUDE.md +94 -19
- package/templates/apps/api/package.json +1 -0
- package/templates/apps/api/src/__tests__/factories/in-memory-prisma.ts +2 -0
- package/templates/apps/api/src/bin/stripe-connections-rekey.ts +45 -0
- package/templates/apps/api/src/config/__tests__/stripe-connection-key.test.ts +185 -0
- package/templates/apps/api/src/config/index.ts +109 -0
- package/templates/apps/api/src/lib/__mocks__/prisma.ts +7 -1
- package/templates/apps/api/src/lib/__tests__/flow-rename-race.db.test.ts +116 -0
- package/templates/apps/api/src/lib/__tests__/site.test.ts +16 -0
- package/templates/apps/api/src/lib/error-serializer.ts +74 -7
- package/templates/apps/api/src/lib/site.ts +4 -1
- package/templates/apps/api/src/lib/stripe.ts +77 -4
- package/templates/apps/api/src/lib/webhook-idempotency.ts +66 -2
- package/templates/apps/api/src/middleware/__tests__/rate-limit.test.ts +218 -0
- package/templates/apps/api/src/middleware/__tests__/write-budget.test.ts +174 -0
- package/templates/apps/api/src/middleware/rate-limit.ts +226 -4
- package/templates/apps/api/src/openapi/__tests__/openapi.test.ts +127 -0
- package/templates/apps/api/src/openapi/spec.ts +343 -7
- package/templates/apps/api/src/routes/billing/plan-checkout.ts +332 -0
- package/templates/apps/api/src/routes/checkout/__tests__/live-key.test.ts +11 -0
- package/templates/apps/api/src/routes/checkout/__tests__/offerings-authorization.test.ts +1426 -0
- package/templates/apps/api/src/routes/checkout/__tests__/session.test.ts +11 -0
- package/templates/apps/api/src/routes/checkout/__tests__/sessions.test.ts +49 -0
- package/templates/apps/api/src/routes/checkout/session.ts +57 -25
- package/templates/apps/api/src/routes/checkout/sessions.ts +232 -82
- package/templates/apps/api/src/routes/consent/__tests__/account.test.ts +5 -5
- package/templates/apps/api/src/routes/consent/__tests__/sign-up-rate-limit.test.ts +160 -0
- package/templates/apps/api/src/routes/consent/account.ts +4 -2
- package/templates/apps/api/src/routes/content/__tests__/get-page.test.ts +2 -2
- package/templates/apps/api/src/routes/content/__tests__/offerings.test.ts +731 -0
- package/templates/apps/api/src/routes/content/__tests__/pricing.test.ts +753 -0
- package/templates/apps/api/src/routes/content/__tests__/shown-offerings.test.ts +762 -0
- package/templates/apps/api/src/routes/content/get-page.ts +34 -119
- package/templates/apps/api/src/routes/content/index.ts +16 -2
- package/templates/apps/api/src/routes/content/list-pages.ts +15 -36
- package/templates/apps/api/src/routes/content/offerings.ts +184 -0
- package/templates/apps/api/src/routes/content/pricing.ts +176 -0
- package/templates/apps/api/src/routes/content/search.ts +16 -45
- package/templates/apps/api/src/routes/content/shown-offerings.ts +229 -0
- package/templates/apps/api/src/routes/leads/index.ts +377 -308
- package/templates/apps/api/src/routes/project-listings/__tests__/me.test.ts +20 -9
- package/templates/apps/api/src/routes/project-listings/__tests__/public.test.ts +17 -6
- package/templates/apps/api/src/routes/project-listings/__tests__/site-answers.test.ts +1 -1
- package/templates/apps/api/src/routes/webhooks/README.md +66 -0
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-flow-checkout.test.ts +1 -0
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-offerings.test.ts +814 -0
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-org-billing.test.ts +80 -68
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe-site-filter.test.ts +6 -5
- package/templates/apps/api/src/routes/webhooks/__tests__/stripe.test.ts +8 -2
- package/templates/apps/api/src/routes/webhooks/index.ts +23 -0
- package/templates/apps/api/src/routes/webhooks/stripe-offerings.ts +384 -0
- package/templates/apps/api/src/routes/webhooks/stripe.ts +93 -17
- package/templates/apps/api/src/server.ts +165 -51
- package/templates/apps/api/src/services/__tests__/abandon-email.test.ts +121 -4
- package/templates/apps/api/src/services/__tests__/checkout-plan.test.ts +313 -0
- package/templates/apps/api/src/services/__tests__/deliverable-fulfillment.test.ts +61 -2
- package/templates/apps/api/src/services/__tests__/keyless-workspace-predicate.test.ts +234 -0
- package/templates/apps/api/src/services/__tests__/lead-routing.test.ts +374 -44
- package/templates/apps/api/src/services/__tests__/person.test.ts +260 -1
- package/templates/apps/api/src/services/__tests__/plan-offerings.test.ts +450 -0
- package/templates/apps/api/src/services/__tests__/project-listings.counts.test.ts +202 -0
- package/templates/apps/api/src/services/__tests__/project-listings.fence.test.ts +15 -18
- package/templates/apps/api/src/services/abandon-email.ts +28 -5
- package/templates/apps/api/src/services/deliverable-fulfillment.ts +15 -12
- package/templates/apps/api/src/services/entitlements.ts +157 -1
- package/templates/apps/api/src/services/lead-routing.ts +113 -58
- package/templates/apps/api/src/services/offerings/__tests__/catalog.test.ts +348 -0
- package/templates/apps/api/src/services/offerings/__tests__/checkout-session-record.test.ts +268 -0
- package/templates/apps/api/src/services/offerings/__tests__/connection.test.ts +975 -0
- package/templates/apps/api/src/services/offerings/__tests__/fixtures.ts +416 -0
- package/templates/apps/api/src/services/offerings/__tests__/org-products.test.ts +400 -0
- package/templates/apps/api/src/services/offerings/__tests__/readers.test.ts +685 -0
- package/templates/apps/api/src/services/offerings/__tests__/source.test.ts +231 -0
- package/templates/apps/api/src/services/offerings/catalog.ts +318 -0
- package/templates/apps/api/src/services/offerings/checkout-authorization.ts +403 -0
- package/templates/apps/api/src/services/offerings/checkout-session-record.ts +192 -0
- package/templates/apps/api/src/services/offerings/connection.ts +1083 -0
- package/templates/apps/api/src/services/offerings/index.ts +614 -0
- package/templates/apps/api/src/services/offerings/source.ts +140 -0
- package/templates/apps/api/src/services/offerings/store.ts +150 -0
- package/templates/apps/api/src/services/person.ts +147 -9
- package/templates/apps/api/src/services/plan-offerings.ts +96 -30
- package/templates/apps/api/src/services/project-listings.ts +43 -31
- package/templates/apps/api/src/services/public-site-resolver.ts +19 -14
- package/templates/apps/mcp/CLAUDE.md +32 -18
- package/templates/apps/mcp/__tests__/app-entry.test.ts +2 -2
- package/templates/apps/mcp/__tests__/catalog-drift.test.ts +9 -4
- package/templates/apps/mcp/__tests__/get_offerings.test.ts +358 -0
- package/templates/apps/mcp/__tests__/get_pricing.test.ts +364 -0
- package/templates/apps/mcp/__tests__/http-transport.test.ts +95 -14
- package/templates/apps/mcp/__tests__/lib/content-adapter-parity.test.ts +8 -3
- package/templates/apps/mcp/__tests__/lib/content-adapter.test.ts +0 -25
- package/templates/apps/mcp/__tests__/lib/fail-closed-tools.test.ts +22 -7
- package/templates/apps/mcp/__tests__/list_pages.test.ts +84 -44
- package/templates/apps/mcp/__tests__/locale.test.ts +41 -56
- package/templates/apps/mcp/__tests__/module-exclusion.test.ts +2 -2
- package/templates/apps/mcp/__tests__/rate-limit-write.test.ts +189 -0
- package/templates/apps/mcp/__tests__/read_content.test.ts +115 -78
- package/templates/apps/mcp/__tests__/request_callback.test.ts +63 -128
- package/templates/apps/mcp/__tests__/search_content.test.ts +76 -61
- package/templates/apps/mcp/__tests__/server.test.ts +13 -11
- package/templates/apps/mcp/__tests__/start_purchase.test.ts +208 -12
- package/templates/apps/mcp/__tests__/submit_lead.test.ts +82 -167
- package/templates/apps/mcp/__tests__/subscribe_newsletter.test.ts +59 -130
- package/templates/apps/mcp/__tests__/tools/purchase.test.ts +11 -11
- package/templates/apps/mcp/package.json +1 -0
- package/templates/apps/mcp/src/abuse/index.ts +18 -11
- package/templates/apps/mcp/src/http-transport.ts +47 -9
- package/templates/apps/mcp/src/lib/content-adapter.ts +0 -26
- package/templates/apps/mcp/src/lib/empty-content-adapter.ts +5 -4
- package/templates/apps/mcp/src/lib/shown-offerings.ts +105 -0
- package/templates/apps/mcp/src/lib/site-config.ts +53 -0
- package/templates/apps/mcp/src/server.ts +5 -0
- package/templates/apps/mcp/src/tools/capture_lead.ts +30 -28
- package/templates/apps/mcp/src/tools/get_offerings.ts +47 -30
- package/templates/apps/mcp/src/tools/get_pricing.ts +40 -40
- package/templates/apps/mcp/src/tools/index.ts +22 -21
- package/templates/apps/mcp/src/tools/list_pages.ts +27 -33
- package/templates/apps/mcp/src/tools/purchase.ts +30 -2
- package/templates/apps/mcp/src/tools/read_content.ts +46 -40
- package/templates/apps/mcp/src/tools/request_callback.ts +21 -28
- package/templates/apps/mcp/src/tools/search_content.ts +20 -24
- package/templates/apps/mcp/src/tools/subscribe_newsletter.ts +23 -26
- package/templates/apps/mcp/src/workspace-server.ts +15 -0
- package/templates/apps/web/.env.example +10 -31
- package/templates/apps/web/CLAUDE.md +324 -42
- package/templates/apps/web/app/[locale]/(authed)/account/__tests__/agreements-prompt.test.tsx +3 -3
- package/templates/apps/web/app/[locale]/(authed)/account/__tests__/layout.test.tsx +3 -3
- package/templates/apps/web/app/[locale]/(authed)/account/__tests__/privacy.test.tsx +3 -3
- package/templates/apps/web/app/[locale]/careers/[slug]/[action]/page.tsx +189 -0
- package/templates/apps/web/app/[locale]/careers/[slug]/apply.actions.ts +4 -2
- package/templates/apps/web/app/[locale]/careers/[slug]/job-application-form.ts +8 -3
- package/templates/apps/web/app/[locale]/careers/[slug]/page.tsx +22 -144
- package/templates/apps/web/app/[locale]/careers/__tests__/apply.actions.test.ts +9 -0
- package/templates/apps/web/app/[locale]/careers/__tests__/careers-hub.test.tsx +172 -0
- package/templates/apps/web/app/[locale]/careers/__tests__/careers-set.test.tsx +149 -0
- package/templates/apps/web/app/[locale]/careers/__tests__/job-application-form.test.ts +22 -24
- package/templates/apps/web/app/[locale]/careers/__tests__/job-apply-page.test.tsx +140 -66
- package/templates/apps/web/app/[locale]/careers/__tests__/page-renderer-migration.test.tsx +57 -22
- package/templates/apps/web/app/[locale]/careers/page.tsx +129 -5
- package/templates/apps/web/app/[locale]/contact/__tests__/ConversationalIntakeWrapper.test.tsx +1 -1
- package/templates/apps/web/app/[locale]/contact/__tests__/contact-magnet-page.test.tsx +119 -11
- package/templates/apps/web/app/[locale]/contact/__tests__/contact.actions.test.ts +28 -5
- package/templates/apps/web/app/[locale]/contact/contact.actions.ts +20 -6
- package/templates/apps/web/app/[locale]/contact/intake-script.ts +1 -1
- package/templates/apps/web/app/[locale]/contact/page.tsx +99 -92
- package/templates/apps/web/app/[locale]/flows/[slug]/FlowStepperClient.tsx +237 -5
- package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/FlowStepperClient.count.test.tsx +43 -0
- package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/FlowStepperClient.deliverable.test.tsx +200 -0
- package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/FlowStepperClient.ending.test.tsx +197 -0
- package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/magnet-page.test.tsx +430 -0
- package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/two-route-tool.test.tsx +21 -9
- package/templates/apps/web/app/[locale]/flows/[slug]/magnet-page.tsx +369 -0
- package/templates/apps/web/app/[locale]/flows/[slug]/page.tsx +12 -73
- package/templates/apps/web/app/[locale]/flows/[slug]/start/page.tsx +10 -31
- package/templates/apps/web/app/[locale]/layout.tsx +55 -4
- package/templates/apps/web/app/[locale]/newsletter/__tests__/page.test.tsx +174 -104
- package/templates/apps/web/app/[locale]/newsletter/page.tsx +95 -58
- package/templates/apps/web/app/[locale]/pricing/__tests__/__snapshots__/unconnected.test.tsx.snap +7 -0
- package/templates/apps/web/app/[locale]/pricing/__tests__/offerings.test.tsx +599 -0
- package/templates/apps/web/app/[locale]/pricing/__tests__/page.test.tsx +216 -0
- package/templates/apps/web/app/[locale]/pricing/__tests__/unconnected.test.tsx +236 -0
- package/templates/apps/web/app/[locale]/pricing/page.tsx +367 -6
- package/templates/apps/web/app/[locale]/team/__tests__/team-routes.test.tsx +4 -1
- package/templates/apps/web/app/[locale]/unsubscribe/__tests__/unsubscribe-rsc-boundary.test.tsx +17 -0
- package/templates/apps/web/app/[locale]/unsubscribe/__tests__/unsubscribe.test.tsx +62 -3
- package/templates/apps/web/app/[locale]/unsubscribe/page.tsx +70 -18
- package/templates/apps/web/app/[locale]/unsubscribe/unsubscribe-button.tsx +30 -9
- package/templates/apps/web/app/api/offerings/checkout/[lookupKey]/route.ts +316 -0
- package/templates/apps/web/app/api/offerings/checkout/__tests__/route.test.ts +810 -0
- package/templates/apps/web/lib/__tests__/auth-routes.test.ts +27 -1
- package/templates/apps/web/lib/__tests__/deck-page.test.ts +2 -1
- package/templates/apps/web/lib/__tests__/in-page-magnet.test.ts +265 -0
- package/templates/apps/web/lib/__tests__/magnet-routes.test.ts +64 -0
- package/templates/apps/web/lib/__tests__/newsletter.actions.test.ts +293 -0
- package/templates/apps/web/lib/__tests__/offerings-pricing.test.ts +601 -0
- package/templates/apps/web/lib/__tests__/structured-data.test.tsx +67 -8
- package/templates/apps/web/lib/auth-routes.ts +36 -2
- package/templates/apps/web/lib/checkout-config.ts +9 -20
- package/templates/apps/web/lib/deck-page.ts +1 -0
- package/templates/apps/web/lib/deliverable-grant.ts +18 -0
- package/templates/apps/web/lib/in-page-magnet.ts +416 -0
- package/templates/apps/web/lib/magnet-routes.ts +44 -0
- package/templates/apps/web/lib/modules.generated.ts +9 -0
- package/templates/apps/web/lib/newsletter.actions.ts +205 -0
- package/templates/apps/web/lib/offerings-pricing.ts +326 -0
- package/templates/apps/web/lib/posting-title.ts +17 -0
- package/templates/apps/web/lib/sign-up-agreements.ts +4 -2
- package/templates/apps/web/messages/en.json +49 -15
- package/templates/apps/web/middleware.ts +12 -2
- package/templates/apps/web/security-headers.config.ts +17 -0
- package/templates/content/en/BlogList/blog.mdx +4 -71
- package/templates/content/en/BookCall/book-a-call.mdx +1 -41
- package/templates/content/en/BookCall/onsite-estimate.mdx +1 -17
- package/templates/content/en/BookCall/onsite-service.mdx +1 -17
- package/templates/content/en/BookCall/start-project.mdx +1 -17
- package/templates/content/en/ContactPage/contact.mdx +6 -63
- package/templates/content/en/HomePage/home.mdx +3 -21
- package/templates/database/CHANGELOG.md +51 -0
- package/templates/database/__tests__/in-page-magnets-migration.test.ts +161 -0
- package/templates/database/__tests__/offerings-checkout-session.db.test.ts +270 -0
- package/templates/database/__tests__/offerings-migration.test.ts +197 -0
- package/templates/database/__tests__/offerings-shown-price-ids-migration.test.ts +86 -0
- package/templates/database/__tests__/offerings.db.test.ts +303 -0
- package/templates/database/__tests__/seed.test.ts +63 -1
- package/templates/database/__tests__/trial-magnet-migration.test.ts +186 -0
- package/templates/database/auth/schema.prisma +4 -0
- package/templates/database/config/schema.prisma +91 -0
- package/templates/database/migrations/20261020120000_offerings_data_model/migration.sql +146 -0
- package/templates/database/migrations/20261021120000_offerings_checkout_session/migration.sql +77 -0
- package/templates/database/migrations/20261022120000_in_page_magnets/migration.sql +45 -0
- package/templates/database/migrations/20261023120000_offerings_shown_price_ids/migration.sql +47 -0
- package/templates/database/migrations/20261024120000_organization_plan_checkout_session/migration.sql +16 -0
- package/templates/database/migrations/20261025120000_trial_magnet/migration.sql +28 -0
- package/templates/database/migrations/20261026120000_trial_magnet_proof_line/migration.sql +23 -0
- package/templates/database/package.json +1 -1
- package/templates/database/seed.ts +57 -2
- package/templates/tools/lint-architecture.ts +205 -1
- package/versions.json +35 -35
- package/site-templates/marketing-site-free/content/en/Pricing/pricing.mdx +0 -199
- package/templates/apps/api/src/routes/content/slug.ts +0 -9
- package/templates/apps/web/app/[locale]/downloads/__tests__/page-renderer-migration.test.tsx +0 -93
- package/templates/apps/web/app/[locale]/downloads/page.tsx +0 -37
- package/templates/apps/web/app/[locale]/newsletter/__tests__/newsletter.actions.test.ts +0 -139
- package/templates/apps/web/app/[locale]/newsletter/newsletter.actions.ts +0 -93
- package/templates/apps/web/app/api/checkout/[offering]/route.ts +0 -48
- package/templates/apps/web/app/api/checkout/__tests__/route.test.ts +0 -120
- package/templates/apps/web/app/api/checkout/__tests__/success-path.test.ts +0 -86
- package/templates/apps/web/app/api/webhooks/stripe/__tests__/route.test.ts +0 -92
- package/templates/apps/web/app/api/webhooks/stripe/route.ts +0 -31
- package/templates/apps/web/lib/__tests__/checkout.test.ts +0 -243
- package/templates/apps/web/lib/__tests__/meeting-slugs.test.ts +0 -120
- package/templates/apps/web/lib/checkout.ts +0 -93
- package/templates/apps/web/lib/meeting-slugs.ts +0 -63
|
@@ -20,22 +20,6 @@ sections:
|
|
|
20
20
|
"primaryCta": { "label": "{Get in touch}", "href": "/contact" },
|
|
21
21
|
},
|
|
22
22
|
},
|
|
23
|
-
{
|
|
24
|
-
"id": "add-scheduling",
|
|
25
|
-
"type": "cta",
|
|
26
|
-
"data":
|
|
27
|
-
{
|
|
28
|
-
"variant": "box",
|
|
29
|
-
"headline": "{What booking an onsite estimate would do for your visitors}",
|
|
30
|
-
"description": "{One or two sentences on what live scheduling adds once it's on}",
|
|
31
|
-
"primaryCta":
|
|
32
|
-
{
|
|
33
|
-
"label": "Add scheduling with Pro",
|
|
34
|
-
"href": "https://ailaunchkit.ai/pricing",
|
|
35
|
-
"external": true,
|
|
36
|
-
},
|
|
37
|
-
},
|
|
38
|
-
},
|
|
39
23
|
{
|
|
40
24
|
"id": "back-to-meet",
|
|
41
25
|
"type": "content-block",
|
|
@@ -53,4 +37,4 @@ sections:
|
|
|
53
37
|
]
|
|
54
38
|
---
|
|
55
39
|
|
|
56
|
-
{/* Rendered from frontmatter `sections[]` by the /meet/[slug] placeholder route. A
|
|
40
|
+
{/* Rendered from frontmatter `sections[]` by the /meet/[slug] placeholder route. A site that carries the scheduling module renders the hub's meeting page instead, reading this seed only for its URL, its JSON-LD and its fallback title (#8843 C5). Placeholder copy — replace every {brace} with your own words before you launch. */}
|
|
@@ -20,22 +20,6 @@ sections:
|
|
|
20
20
|
"primaryCta": { "label": "{Get in touch}", "href": "/contact" },
|
|
21
21
|
},
|
|
22
22
|
},
|
|
23
|
-
{
|
|
24
|
-
"id": "add-scheduling",
|
|
25
|
-
"type": "cta",
|
|
26
|
-
"data":
|
|
27
|
-
{
|
|
28
|
-
"variant": "box",
|
|
29
|
-
"headline": "{What booking an onsite service call would do for your visitors}",
|
|
30
|
-
"description": "{One or two sentences on what live scheduling adds once it's on}",
|
|
31
|
-
"primaryCta":
|
|
32
|
-
{
|
|
33
|
-
"label": "Add scheduling with Pro",
|
|
34
|
-
"href": "https://ailaunchkit.ai/pricing",
|
|
35
|
-
"external": true,
|
|
36
|
-
},
|
|
37
|
-
},
|
|
38
|
-
},
|
|
39
23
|
{
|
|
40
24
|
"id": "back-to-meet",
|
|
41
25
|
"type": "content-block",
|
|
@@ -53,4 +37,4 @@ sections:
|
|
|
53
37
|
]
|
|
54
38
|
---
|
|
55
39
|
|
|
56
|
-
{/* Rendered from frontmatter `sections[]` by the /meet/[slug] placeholder route. A
|
|
40
|
+
{/* Rendered from frontmatter `sections[]` by the /meet/[slug] placeholder route. A site that carries the scheduling module renders the hub's meeting page instead, reading this seed only for its URL, its JSON-LD and its fallback title (#8843 C5). Placeholder copy — replace every {brace} with your own words before you launch. */}
|
|
@@ -20,22 +20,6 @@ sections:
|
|
|
20
20
|
"primaryCta": { "label": "{Get in touch}", "href": "/contact" },
|
|
21
21
|
},
|
|
22
22
|
},
|
|
23
|
-
{
|
|
24
|
-
"id": "add-scheduling",
|
|
25
|
-
"type": "cta",
|
|
26
|
-
"data":
|
|
27
|
-
{
|
|
28
|
-
"variant": "box",
|
|
29
|
-
"headline": "{What booking a project kickoff would do for your visitors}",
|
|
30
|
-
"description": "{One or two sentences on what live scheduling adds once it's on}",
|
|
31
|
-
"primaryCta":
|
|
32
|
-
{
|
|
33
|
-
"label": "Add scheduling with Pro",
|
|
34
|
-
"href": "https://ailaunchkit.ai/pricing",
|
|
35
|
-
"external": true,
|
|
36
|
-
},
|
|
37
|
-
},
|
|
38
|
-
},
|
|
39
23
|
{
|
|
40
24
|
"id": "back-to-meet",
|
|
41
25
|
"type": "content-block",
|
|
@@ -53,4 +37,4 @@ sections:
|
|
|
53
37
|
]
|
|
54
38
|
---
|
|
55
39
|
|
|
56
|
-
{/* Rendered from frontmatter `sections[]` by the /meet/[slug] placeholder route. A
|
|
40
|
+
{/* Rendered from frontmatter `sections[]` by the /meet/[slug] placeholder route. A site that carries the scheduling module renders the hub's meeting page instead, reading this seed only for its URL, its JSON-LD and its fallback title (#8843 C5). Placeholder copy — replace every {brace} with your own words before you launch. */}
|
|
@@ -1,73 +1,16 @@
|
|
|
1
1
|
---
|
|
2
2
|
type: "ContactPage"
|
|
3
3
|
slug: "contact"
|
|
4
|
-
title: "
|
|
5
|
-
description: "
|
|
4
|
+
title: "Get in touch"
|
|
5
|
+
description: "{One sentence on who should get in touch and how soon you reply}"
|
|
6
6
|
sections:
|
|
7
7
|
[
|
|
8
8
|
{
|
|
9
|
-
"id": "
|
|
10
|
-
"type": "
|
|
11
|
-
"data":
|
|
12
|
-
{
|
|
13
|
-
"variant": "split",
|
|
14
|
-
"eyebrow": "Contact",
|
|
15
|
-
"headline": "Get every message in your email, free",
|
|
16
|
-
"subheadline": "Or upgrade to Pro to save every message as a lead in your own marketing database.",
|
|
17
|
-
"checklist":
|
|
18
|
-
[
|
|
19
|
-
"Every message arrives in your email",
|
|
20
|
-
"No database or setup to start",
|
|
21
|
-
"Upgrade any time to keep a lead inbox",
|
|
22
|
-
],
|
|
23
|
-
"secondaryCta":
|
|
24
|
-
{
|
|
25
|
-
"label": "Save my leads",
|
|
26
|
-
"href": "https://ailaunchkit.ai/pricing",
|
|
27
|
-
"variant": "outline",
|
|
28
|
-
},
|
|
29
|
-
},
|
|
30
|
-
},
|
|
31
|
-
{
|
|
32
|
-
"id": "meeting-types",
|
|
33
|
-
"type": "items",
|
|
34
|
-
"data":
|
|
35
|
-
{
|
|
36
|
-
"headline": "{Add appointment scheduling to your site}",
|
|
37
|
-
"variant": "tiles",
|
|
38
|
-
"columns": 2,
|
|
39
|
-
"items":
|
|
40
|
-
[
|
|
41
|
-
{
|
|
42
|
-
"title": "Book a Call",
|
|
43
|
-
"description": "{A quick intro call.}",
|
|
44
|
-
"link": { "label": "Book a Call", "href": "/meet/book-a-call" },
|
|
45
|
-
},
|
|
46
|
-
{
|
|
47
|
-
"title": "Onsite Estimate",
|
|
48
|
-
"description": "{We come to you and quote the work.}",
|
|
49
|
-
"link":
|
|
50
|
-
{
|
|
51
|
-
"label": "Onsite Estimate",
|
|
52
|
-
"href": "/meet/onsite-estimate",
|
|
53
|
-
},
|
|
54
|
-
},
|
|
55
|
-
{
|
|
56
|
-
"title": "Start Project",
|
|
57
|
-
"description": "{Kick off a project you're ready to start.}",
|
|
58
|
-
"link":
|
|
59
|
-
{ "label": "Start Project", "href": "/meet/start-project" },
|
|
60
|
-
},
|
|
61
|
-
{
|
|
62
|
-
"title": "Onsite Service",
|
|
63
|
-
"description": "{Book a service visit at your location.}",
|
|
64
|
-
"link":
|
|
65
|
-
{ "label": "Onsite Service", "href": "/meet/onsite-service" },
|
|
66
|
-
},
|
|
67
|
-
],
|
|
68
|
-
},
|
|
9
|
+
"id": "contact-form",
|
|
10
|
+
"type": "contact-form",
|
|
11
|
+
"data": { "variant": "column", "fields": [] },
|
|
69
12
|
},
|
|
70
13
|
]
|
|
71
14
|
---
|
|
72
15
|
|
|
73
|
-
{/* Rendered from frontmatter `sections[]`. The page's one contact surface
|
|
16
|
+
{/* Rendered from frontmatter `sections[]`. The page's one contact surface is the workspace's Contact form magnet: its heading, sentence, fields, consent, button and ending all come from the magnet, mounted by the /contact route in this section's slot. To change the form, change the magnet in Admin → Magnets. The `description` is the page's meta description — replace it before you launch. */}
|
|
@@ -39,27 +39,9 @@ sections:
|
|
|
39
39
|
"headlineStyle": "label",
|
|
40
40
|
"logos":
|
|
41
41
|
[
|
|
42
|
-
{
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
"darkSrc": "/logos/salessmyth-light.svg",
|
|
46
|
-
"width": 1118,
|
|
47
|
-
"height": 147,
|
|
48
|
-
},
|
|
49
|
-
{
|
|
50
|
-
"name": "AI Launch Kit",
|
|
51
|
-
"src": "/logos/ai-launch-kit.svg",
|
|
52
|
-
"darkSrc": "/logos/ai-launch-kit-light.svg",
|
|
53
|
-
"width": 1248,
|
|
54
|
-
"height": 144,
|
|
55
|
-
},
|
|
56
|
-
{
|
|
57
|
-
"name": "Atomic Agent",
|
|
58
|
-
"src": "/logos/atomic-agent.svg",
|
|
59
|
-
"darkSrc": "/logos/atomic-agent-light.svg",
|
|
60
|
-
"width": 1320,
|
|
61
|
-
"height": 148,
|
|
62
|
-
},
|
|
42
|
+
{ "name": "Placeholder logo", "src": "/logomark.svg" },
|
|
43
|
+
{ "name": "Placeholder logo", "src": "/logomark.svg" },
|
|
44
|
+
{ "name": "Placeholder logo", "src": "/logomark.svg" },
|
|
63
45
|
],
|
|
64
46
|
"layout": "inline",
|
|
65
47
|
"treatment": "monochrome",
|
|
@@ -1,5 +1,56 @@
|
|
|
1
1
|
# @working-theory/database
|
|
2
2
|
|
|
3
|
+
## 0.45.0
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [#9309](https://github.com/Working-Theory-Labs/ai-launch-kit-internal/pull/9309) [`42b37e2`](https://github.com/Working-Theory-Labs/ai-launch-kit-internal/commit/42b37e237d0f424573346b158290f0ecc4ae77a4) Thanks [@samuelhenry](https://github.com/samuelhenry)! - Every workspace now holds a standard trial magnet at the fixed slug `trial` ([#9301](https://github.com/working-theory-labs/ai-launch-kit-internal/issues/9301)).
|
|
8
|
+
|
|
9
|
+
- **`@working-theory/validation`**: new exports `TRIAL_FLOW_SLUG`, `TRIAL_FLOW_TITLE`, `TRIAL_FLOW_LANDING_PAGE`, `TRIAL_TERMS_STATEMENT` and `trialFlowConfig` — one step asking for first name, last name, work email and the terms box, ending on "Thanks, {name}.", with a landing whose button reads "Request a free trial". `RESERVED_FLOW_SLUGS` gains `trial`, so no flow-engine write, spoke seed or new listing can take the slug (a new listing named "Trial" now derives `trial-2`).
|
|
10
|
+
- **`@working-theory/database`**: the default workspace's seed writes the trial magnet (`diagnostic`, `live`, completion `lead`, landing page `/trial`), and the `20261025120000_trial_magnet` migration backfills it into every existing workspace, insert-only — a row already at `trial` is never changed.
|
|
11
|
+
- **`create-ailk`**: the API seeds the trial magnet when it creates a workspace and from `seed-presets`, and the magnet PATCH refuses to rename the trial magnet or rename another magnet to `trial` (`409`). A completed walk is a `flow:trial` Inbox submission whose Person carries the given name, family name, email and, when a site adds a `company`-tagged field, the company.
|
|
12
|
+
|
|
13
|
+
- [#9330](https://github.com/Working-Theory-Labs/ai-launch-kit-internal/pull/9330) [`569b348`](https://github.com/Working-Theory-Labs/ai-launch-kit-internal/commit/569b348a95aa7b1b414ed62151d810e41877f238) Thanks [@samuelhenry](https://github.com/samuelhenry)! - The trial magnet gets its page pair: `/trial` and `/trial/request` ([#9302](https://github.com/working-theory-labs/ai-launch-kit-internal/issues/9302)).
|
|
14
|
+
|
|
15
|
+
- **`create-ailk`**: a site with the `signal-collectors` module carries `/trial`, the indexable landing inside the site's Nav and Footer — the trial magnet's landing copy, one button ("Request a free trial") to `/trial/request`, and the `Assessment` JSON-LD — and `/trial/request`, the full-screen, `noindex, follow` walk of the trial magnet with a back arrow to `/trial`. When the trial flow is absent or the hub cannot answer, the walk shows the site's contact email under its own "Free trial" heading instead of a 404. The magnet landing renderer takes an optional `startPath` for a walk not at `<path>/start`.
|
|
16
|
+
- **`@working-theory/validation`**: `trialFlowConfig.landing` gains its proof line, `meta`: "A person on our team reviews every request." It is true for every site, because only an operator starts a trial, from the Inbox.
|
|
17
|
+
- **`@working-theory/database`**: the `20261026120000_trial_magnet_proof_line` migration gives that line to every workspace's trial magnet that still holds [#9301](https://github.com/working-theory-labs/ai-launch-kit-internal/issues/9301)'s exact default. A trial magnet an owner has edited is never touched.
|
|
18
|
+
- **`@working-theory/templates`**: `ROUTES` gains the `/trial` and `/trial/request` rows (module `signal-collectors`).
|
|
19
|
+
|
|
20
|
+
- Updated dependencies [[`1d9b675`](https://github.com/Working-Theory-Labs/ai-launch-kit-internal/commit/1d9b6755b3fbffebfcd7c57a93ea7cbc9941bba0), [`875714a`](https://github.com/Working-Theory-Labs/ai-launch-kit-internal/commit/875714ad871e2b9b3778383ed106e8a1586fe3a6), [`42b37e2`](https://github.com/Working-Theory-Labs/ai-launch-kit-internal/commit/42b37e237d0f424573346b158290f0ecc4ae77a4), [`569b348`](https://github.com/Working-Theory-Labs/ai-launch-kit-internal/commit/569b348a95aa7b1b414ed62151d810e41877f238), [`d648b1a`](https://github.com/Working-Theory-Labs/ai-launch-kit-internal/commit/d648b1a98a84dfee56f6847e7ec5f9fd0421a163), [`5008d1d`](https://github.com/Working-Theory-Labs/ai-launch-kit-internal/commit/5008d1d13283fe5ed04504dcfb17122d18892a18), [`a5c5d1c`](https://github.com/Working-Theory-Labs/ai-launch-kit-internal/commit/a5c5d1cc3443394d0da9507c7b11954b54e8c0da), [`67e75c8`](https://github.com/Working-Theory-Labs/ai-launch-kit-internal/commit/67e75c87519235435f578ee22c2cf8fa2f74f17d), [`16c889b`](https://github.com/Working-Theory-Labs/ai-launch-kit-internal/commit/16c889b7434ad49c3b18f900c42779c72e78f233)]:
|
|
21
|
+
- @working-theory/validation@0.45.0
|
|
22
|
+
|
|
23
|
+
## 0.44.0
|
|
24
|
+
|
|
25
|
+
### Minor Changes
|
|
26
|
+
|
|
27
|
+
- [#8955](https://github.com/Working-Theory-Labs/ai-launch-kit-internal/pull/8955) [`1a84162`](https://github.com/Working-Theory-Labs/ai-launch-kit-internal/commit/1a841626a9e5664d926a2f186425f35e8757a6b4) Thanks [@samuelhenry](https://github.com/samuelhenry)! - The Offerings data model and its typed read services ([#8876](https://github.com/working-theory-labs/ai-launch-kit-internal/issues/8876)).
|
|
28
|
+
|
|
29
|
+
- **`@working-theory/database`** — three config tables, in migration `20261020120000_offerings_data_model`: `StripeConnection` (an Organization's own Stripe account by a restricted key — mode, last four, the key and webhook secret as ciphertext the database refuses in clear, a webhook token), `OrganizationOfferings` (the Products an Organization has switched on) and `WorkspaceOfferings` (what a workspace shows, its Recommended plan and add-on, and the last-good Stripe snapshot). Deploy the migration before the API. No existing table is touched.
|
|
30
|
+
- **`@working-theory/ids`** — registers the `stripecon`, `orgoffer` and `wsoffer` prefixes for the new models.
|
|
31
|
+
- **`@working-theory/validation`** — the Offerings data contract: `parseLookupKey` (the one lookup-key parser, `<plan>_<monthly|yearly|once>`), `periodOfStripePrice`, the text bounds, and the typed shapes `offeringsProductSchema`, `offeringsSnapshotSchema`, `orgEnabledProductsSchema`, `workspaceShownSetSchema` and `stripeConnectionSummarySchema`.
|
|
32
|
+
- **`create-ailk`** — a scaffold's `apps/api` carries the Offerings read services (`getOrgEnabledProducts`, `getWorkspaceShownSet`, `getSellableProductIds`, the bounded snapshot refresh and the connection summary). No route reads them yet, so a site's behaviour is unchanged.
|
|
33
|
+
|
|
34
|
+
- [#8968](https://github.com/Working-Theory-Labs/ai-launch-kit-internal/pull/8968) [`8da1163`](https://github.com/Working-Theory-Labs/ai-launch-kit-internal/commit/8da1163fd04f083fd8d408a4a7d762b756b23274) Thanks [@samuelhenry](https://github.com/samuelhenry)! - The Offerings Checkout session record ([#8930](https://github.com/working-theory-labs/ai-launch-kit-internal/issues/8930)).
|
|
35
|
+
|
|
36
|
+
- **`@working-theory/database`** — one config table, in migration `20261021120000_offerings_checkout_session`: `OfferingsCheckoutSession`, one row per Checkout Session the hub created (its id, workspace, Organization, Price, lookup key and mode), with a CHECK on every column but `created_at`. Deploy the migration before the API. No existing table is touched.
|
|
37
|
+
- **`@working-theory/ids`** — registers the `ocsess` prefix for the new model.
|
|
38
|
+
- **`create-ailk`** — a scaffold's `apps/api` carries the record's one store (`recordCheckoutSession`, `readCheckoutSessionRecord`). No route calls it yet, so a site's behaviour is unchanged.
|
|
39
|
+
|
|
40
|
+
- [#9091](https://github.com/Working-Theory-Labs/ai-launch-kit-internal/pull/9091) [`23cf71c`](https://github.com/Working-Theory-Labs/ai-launch-kit-internal/commit/23cf71c316c9e44c6109c76439cb64281e6368d5) Thanks [@samuelhenry](https://github.com/samuelhenry)! - A workspace's Offerings shown set is chosen per Stripe Price, and the Admin's Offerings page reads like Magnets ([#9070](https://github.com/working-theory-labs/ai-launch-kit-internal/issues/9070)).
|
|
41
|
+
|
|
42
|
+
- **`@working-theory/database`** — migration `20261023120000_offerings_shown_price_ids` adds `workspace_offerings.shown_price_ids TEXT[] NOT NULL DEFAULT '{}'` and two CHECKs (at most 300 non-null ids; Price ids only beside a shown Product). No row is rewritten: an empty array means every Price of each shown Product, so every workspace saved before this release shows exactly what it showed before. **Deploy the migration before the API, and the API before the Admin.** While the API and the Admin are on different versions the Offerings page cannot save (nothing is written and nothing new is sold).
|
|
43
|
+
- **`@working-theory/validation`** — new exports `OFFERINGS_PRICE_IDS_MAX`, `offeringsPriceIdsSchema`, `workspaceOfferingsShownEntrySchema` and `workspaceOfferingsShownSchema`. Changed shapes: `workspaceOfferingsSelectionInputSchema` is now one Price's switch, `{ productId, period, shown }`, so no body names a Recommended or a whole set; `workspaceOfferingsSelectionSchema` gains `shownPriceIds`, empty exactly when `shownProductIds` is; `workspaceOfferingsViewSchema`'s available branch answers `shown` (`{ productId, periods }` per Product) in place of `shownProductIds`.
|
|
44
|
+
- **`create-ailk`** — a scaffold's `PUT /v1/workspaces/:workspaceId/offerings` takes one Price per save and keeps or clears a stored Recommended but never sets one (`recommended_not_shown` is retired; a period the Product has no Price for is `400 price_not_offered`). Its audit row's `shown`, `added` and `removed` count Prices, not Products, from this release. `/pricing`, the agent pricing tools, the plan picker and checkout show and sell only the shown Prices, so a seller can show a yearly Price without its monthly sibling; a Price replaced in Stripe stays off until it is switched on. The Admin's Offerings page is a Title / Tags / Live table, one row per Price, with a New recommender button and no Recommended control.
|
|
45
|
+
|
|
46
|
+
**Rollback.** Roll the API back, then run `UPDATE workspace_offerings SET shown_price_ids = '{}'` so every row reads at the Product grain again and the old route's saves succeed. Tell each owner who saved per Price that every Price of their shown Products — on `/pricing` and at checkout — is on sale again. Never drop the column while a per-Price row exists without that notice.
|
|
47
|
+
|
|
48
|
+
### Patch Changes
|
|
49
|
+
|
|
50
|
+
- Updated dependencies [[`6ad87d6`](https://github.com/Working-Theory-Labs/ai-launch-kit-internal/commit/6ad87d65d91013ac9c0945a62efbf28c627b1fce), [`d9c2cd3`](https://github.com/Working-Theory-Labs/ai-launch-kit-internal/commit/d9c2cd3c601d9a267d4d29cb9bcef77ec71cd518), [`407810f`](https://github.com/Working-Theory-Labs/ai-launch-kit-internal/commit/407810fbd63a02d14773aef36a10c1f9f04dd5e3), [`5875c18`](https://github.com/Working-Theory-Labs/ai-launch-kit-internal/commit/5875c189f72f66ea2af7b7c79a47baba53a329d1), [`13c0b3d`](https://github.com/Working-Theory-Labs/ai-launch-kit-internal/commit/13c0b3dd64a105292962821f5b0050c5cabc2d45), [`82bc2f1`](https://github.com/Working-Theory-Labs/ai-launch-kit-internal/commit/82bc2f1ecab1a12f23a45e326355841ba87d9f04), [`ea18e21`](https://github.com/Working-Theory-Labs/ai-launch-kit-internal/commit/ea18e2181241ba9357b4dcfa3a1d2f4c3ffe4518), [`8754f86`](https://github.com/Working-Theory-Labs/ai-launch-kit-internal/commit/8754f866b49003965c4be28cbe19b1f040aec4af), [`1a84162`](https://github.com/Working-Theory-Labs/ai-launch-kit-internal/commit/1a841626a9e5664d926a2f186425f35e8757a6b4), [`74cb7b3`](https://github.com/Working-Theory-Labs/ai-launch-kit-internal/commit/74cb7b37adea5d27adc1f3ec3ea58b9941167c4d), [`25b4b61`](https://github.com/Working-Theory-Labs/ai-launch-kit-internal/commit/25b4b6199d8632167bd5501a4e12d4774eed432a), [`8da1163`](https://github.com/Working-Theory-Labs/ai-launch-kit-internal/commit/8da1163fd04f083fd8d408a4a7d762b756b23274), [`bd1c9b5`](https://github.com/Working-Theory-Labs/ai-launch-kit-internal/commit/bd1c9b576ac337596e585145f09b08696e9f412e), [`d1dacc5`](https://github.com/Working-Theory-Labs/ai-launch-kit-internal/commit/d1dacc5d76a9cc91f6ad3b1883067e327b5e3d3c), [`fff5487`](https://github.com/Working-Theory-Labs/ai-launch-kit-internal/commit/fff5487a6d14a3010bf22cb99e7554e6542449a0), [`23cf71c`](https://github.com/Working-Theory-Labs/ai-launch-kit-internal/commit/23cf71c316c9e44c6109c76439cb64281e6368d5), [`75ecc58`](https://github.com/Working-Theory-Labs/ai-launch-kit-internal/commit/75ecc584f43793bb5234f69e0f80b15327a99236), [`85ae357`](https://github.com/Working-Theory-Labs/ai-launch-kit-internal/commit/85ae357410ebd6c11b08b04355a9ede1d2088d2d), [`059c892`](https://github.com/Working-Theory-Labs/ai-launch-kit-internal/commit/059c89249f7db3dc106f133ef37180e9eb1e318a), [`c563360`](https://github.com/Working-Theory-Labs/ai-launch-kit-internal/commit/c563360db386758aa2d25309ab693a18a41e4139)]:
|
|
51
|
+
- @working-theory/validation@0.44.0
|
|
52
|
+
- @working-theory/ids@0.15.0
|
|
53
|
+
|
|
3
54
|
## 0.43.0
|
|
4
55
|
|
|
5
56
|
### Minor Changes
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file in-page-magnets-migration.test.ts
|
|
3
|
+
*
|
|
4
|
+
* DB-free: migrations/20261022120000_in_page_magnets writes two configs as
|
|
5
|
+
* JSONB literals, because SQL cannot import TS (#8847 spec
|
|
6
|
+
* docs/specs/8847-the-in-page-form-shape-spec.md D5; AC-3, AC-5). This test
|
|
7
|
+
* holds each literal to its one TS source so they can never drift, as
|
|
8
|
+
* `member-contract-migration.test.ts` does for the request-quote backfill:
|
|
9
|
+
*
|
|
10
|
+
* - `newsletter backfill` inserts `newsletterFlowConfig` into every
|
|
11
|
+
* workspace, insert-only on `(site_id, slug)`;
|
|
12
|
+
* - `contact-form upgrade` replaces a stored Contact form with
|
|
13
|
+
* `contactFormFlowConfig` ONLY where its config equals #8661's shipped
|
|
14
|
+
* default as JSONB, so an owner's edit (or a wizard-built Contact form) is
|
|
15
|
+
* never touched, and it sets nothing but `config` and `updated_at`.
|
|
16
|
+
*/
|
|
17
|
+
import { readFileSync } from "node:fs";
|
|
18
|
+
import { join } from "node:path";
|
|
19
|
+
|
|
20
|
+
import {
|
|
21
|
+
CONTACT_FORM_FLOW_SLUG,
|
|
22
|
+
contactFormFlowConfig,
|
|
23
|
+
flowConfigSchema,
|
|
24
|
+
NEWSLETTER_FLOW_SLUG,
|
|
25
|
+
NEWSLETTER_FLOW_TITLE,
|
|
26
|
+
NEWSLETTER_LANDING_PAGE,
|
|
27
|
+
newsletterFlowConfig,
|
|
28
|
+
STANDARD_MAGNET_COMPLETIONS,
|
|
29
|
+
} from "@working-theory/validation";
|
|
30
|
+
|
|
31
|
+
const MIGRATION_SQL = readFileSync(
|
|
32
|
+
join(__dirname, "..", "migrations", "20261022120000_in_page_magnets", "migration.sql"),
|
|
33
|
+
"utf8",
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* #8661's shipped default Contact form — `contactFormFlowConfig` as it stood
|
|
38
|
+
* at commit e34e7468^ (`git show e34e7468^:packages/validation/src/schemas/flow.ts`),
|
|
39
|
+
* the only shape the #8661 seed ever wrote. Written out here, not imported:
|
|
40
|
+
* the constant has moved on, and this is the value the upgrade's guard must
|
|
41
|
+
* match exactly.
|
|
42
|
+
*/
|
|
43
|
+
const CONTACT_FORM_8661_DEFAULT = {
|
|
44
|
+
slug: "contact",
|
|
45
|
+
defaultLocale: "en",
|
|
46
|
+
steps: [
|
|
47
|
+
{
|
|
48
|
+
key: "contact",
|
|
49
|
+
type: "field",
|
|
50
|
+
copy: { title: { en: "Contact form" }, cta: { en: "Send message" } },
|
|
51
|
+
fields: [
|
|
52
|
+
{ name: "name", inputType: "text", required: true, mapsTo: "name", copy: { label: { en: "Name" } } },
|
|
53
|
+
{ name: "email", inputType: "email", required: true, mapsTo: "email", copy: { label: { en: "Email" } } },
|
|
54
|
+
{
|
|
55
|
+
name: "subject",
|
|
56
|
+
inputType: "select",
|
|
57
|
+
required: false,
|
|
58
|
+
options: [
|
|
59
|
+
{ value: "General question", label: { en: "General question" } },
|
|
60
|
+
{ value: "Working together", label: { en: "Working together" } },
|
|
61
|
+
{ value: "Support", label: { en: "Support" } },
|
|
62
|
+
{ value: "Press and partnerships", label: { en: "Press and partnerships" } },
|
|
63
|
+
{ value: "Other", label: { en: "Other" } },
|
|
64
|
+
],
|
|
65
|
+
copy: { label: { en: "Subject" }, placeholder: { en: "Choose a subject" } },
|
|
66
|
+
},
|
|
67
|
+
{ name: "message", inputType: "textarea", required: false, copy: { label: { en: "Message" } } },
|
|
68
|
+
{
|
|
69
|
+
name: "terms",
|
|
70
|
+
inputType: "consent",
|
|
71
|
+
required: true,
|
|
72
|
+
purposes: ["service_terms"],
|
|
73
|
+
copy: {
|
|
74
|
+
label: {
|
|
75
|
+
en: "I accept the [Terms](terms), [Privacy Policy](privacy), and [Working Agreement](working_agreement).",
|
|
76
|
+
},
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
],
|
|
80
|
+
},
|
|
81
|
+
],
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
/** The text between `-- BEGIN <name>` and `-- END <name>`. */
|
|
85
|
+
function block(name: string): string {
|
|
86
|
+
const match = new RegExp(`-- BEGIN ${name}\\n([\\s\\S]*?)-- END ${name}`).exec(MIGRATION_SQL);
|
|
87
|
+
if (!match?.[1]) throw new Error(`${name} markers not found`);
|
|
88
|
+
return match[1];
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** A SQL string literal's body, `''` unescaped, parsed as JSON. */
|
|
92
|
+
const parseLiteral = (body: string): unknown => JSON.parse(body.replace(/''/g, "'"));
|
|
93
|
+
|
|
94
|
+
/** The block with every string literal blanked, so only SQL structure is left. */
|
|
95
|
+
const withoutLiterals = (sql: string): string => sql.replace(/'(?:[^']|'')*'/g, "''");
|
|
96
|
+
|
|
97
|
+
describe("the newsletter backfill (#8847 D5, AC-5)", () => {
|
|
98
|
+
const sql = block("newsletter backfill");
|
|
99
|
+
|
|
100
|
+
it("carries exactly one JSONB config literal, and it deep-equals newsletterFlowConfig", () => {
|
|
101
|
+
const literals = [...sql.matchAll(/'(\{[\s\S]*?\})'::jsonb/g)];
|
|
102
|
+
expect(literals).toHaveLength(1);
|
|
103
|
+
const parsed = parseLiteral(literals[0]![1]!);
|
|
104
|
+
expect(parsed).toEqual(newsletterFlowConfig);
|
|
105
|
+
expect(flowConfigSchema.safeParse(parsed).success).toBe(true);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it("writes the constants' slug, title and landing page, kind newsletter, live, the lead completion, into every workspace", () => {
|
|
109
|
+
expect(sql).toContain(
|
|
110
|
+
`INSERT INTO "flow_definitions" ("site_id", "workspace_id", "slug", "config", "kind", "title", "status", "customers_enabled", "completion", "landing_page", "updated_at")`,
|
|
111
|
+
);
|
|
112
|
+
expect(sql).toContain(`SELECT w."id", w."id", '${NEWSLETTER_FLOW_SLUG}',`);
|
|
113
|
+
expect(STANDARD_MAGNET_COMPLETIONS[NEWSLETTER_FLOW_SLUG]).toBe("lead");
|
|
114
|
+
expect(sql).toContain(
|
|
115
|
+
`'newsletter', '${NEWSLETTER_FLOW_TITLE}', 'live', false, 'lead', '${NEWSLETTER_LANDING_PAGE}', CURRENT_TIMESTAMP`,
|
|
116
|
+
);
|
|
117
|
+
expect(sql).toContain(`FROM "workspaces" w`);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it("ends ON CONFLICT (\"site_id\", \"slug\") DO NOTHING, so an existing row is never overwritten", () => {
|
|
121
|
+
expect(sql.trimEnd().endsWith(`ON CONFLICT ("site_id", "slug") DO NOTHING;`)).toBe(true);
|
|
122
|
+
expect(sql).not.toMatch(/DO UPDATE/i);
|
|
123
|
+
});
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
describe("the contact-form upgrade (#8847 D5, C5, AC-3)", () => {
|
|
127
|
+
const sql = block("contact-form upgrade");
|
|
128
|
+
const statements = withoutLiterals(sql).split(";").filter((s) => s.trim().length > 0);
|
|
129
|
+
|
|
130
|
+
it("is one UPDATE of flow_definitions", () => {
|
|
131
|
+
expect(statements).toHaveLength(1);
|
|
132
|
+
expect(statements[0]!.trim()).toMatch(/^UPDATE "flow_definitions" SET /);
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it("its SET literal deep-equals contactFormFlowConfig", () => {
|
|
136
|
+
const set = /SET "config" = '(\{[\s\S]*?\})'::jsonb/.exec(sql);
|
|
137
|
+
expect(set?.[1]).toBeDefined();
|
|
138
|
+
const parsed = parseLiteral(set![1]!);
|
|
139
|
+
expect(parsed).toEqual(contactFormFlowConfig);
|
|
140
|
+
expect(flowConfigSchema.safeParse(parsed).success).toBe(true);
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
it("its WHERE names the contact slug and compares config with = against a ::jsonb literal equal to #8661's default", () => {
|
|
144
|
+
const where = new RegExp(
|
|
145
|
+
`WHERE "slug" = '${CONTACT_FORM_FLOW_SLUG}' AND "config" = '(\\{[\\s\\S]*?\\})'::jsonb;`,
|
|
146
|
+
).exec(sql);
|
|
147
|
+
expect(where?.[1]).toBeDefined();
|
|
148
|
+
expect(parseLiteral(where![1]!)).toEqual(CONTACT_FORM_8661_DEFAULT);
|
|
149
|
+
// The guard is a different value from what it writes, so a re-run matches nothing.
|
|
150
|
+
expect(CONTACT_FORM_8661_DEFAULT).not.toEqual(contactFormFlowConfig);
|
|
151
|
+
expect(flowConfigSchema.safeParse(CONTACT_FORM_8661_DEFAULT).success).toBe(true);
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
it("sets no column besides config and updated_at", () => {
|
|
155
|
+
const setClause = /SET ([\s\S]*?)\s+WHERE /.exec(withoutLiterals(sql))?.[1];
|
|
156
|
+
expect(setClause).toBeDefined();
|
|
157
|
+
const columns = [...setClause!.matchAll(/"([a-z_]+)"\s*=/g)].map((m) => m[1]);
|
|
158
|
+
expect(columns).toEqual(["config", "updated_at"]);
|
|
159
|
+
expect(setClause).toContain(`"updated_at" = CURRENT_TIMESTAMP`);
|
|
160
|
+
});
|
|
161
|
+
});
|