create-ailk 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (605) hide show
  1. package/README.md +124 -3
  2. package/component-catalog.md +295 -0
  3. package/dist/cli.js +92 -16
  4. package/dist/component-catalog.d.ts +11 -0
  5. package/dist/component-catalog.js +39 -0
  6. package/dist/copy.d.ts +7 -0
  7. package/dist/copy.js +24 -0
  8. package/dist/derive-architecture.d.ts +13 -0
  9. package/dist/derive-architecture.js +238 -0
  10. package/dist/index.d.ts +7 -2
  11. package/dist/index.js +19 -2
  12. package/dist/lib/extract-module-bundle.d.ts +16 -0
  13. package/dist/lib/extract-module-bundle.js +184 -0
  14. package/dist/lib/fetch-module.d.ts +55 -0
  15. package/dist/lib/fetch-module.js +125 -0
  16. package/dist/lib/module-license-gate.d.ts +48 -0
  17. package/dist/lib/module-license-gate.js +67 -0
  18. package/dist/lib/module-tier.d.ts +35 -0
  19. package/dist/lib/module-tier.js +37 -0
  20. package/dist/module-architecture.d.ts +175 -0
  21. package/dist/module-architecture.js +663 -0
  22. package/dist/parse-args.d.ts +47 -1
  23. package/dist/parse-args.js +143 -2
  24. package/dist/programmatic.d.ts +160 -3
  25. package/dist/programmatic.js +196 -7
  26. package/dist/surfaces.d.ts +211 -0
  27. package/dist/surfaces.js +532 -0
  28. package/dist/sync-routes.d.ts +92 -0
  29. package/dist/sync-routes.js +350 -0
  30. package/package.json +22 -14
  31. package/templates/.claude/rules/architecture.md +6 -2
  32. package/templates/.claude/skills/README.md +9 -9
  33. package/templates/.claude/skills/add-schema/SKILL.md +14 -7
  34. package/templates/.claude/skills/add-schema/references/schema-types.md +109 -6
  35. package/templates/.claude/skills/provision-config/SKILL.md +1 -1
  36. package/templates/.claude/skills/scaffold-commerce/SKILL.md +498 -1
  37. package/templates/.claude/skills/scaffold-commerce/templates/checkout-route.template.ts +370 -0
  38. package/templates/.claude/skills/scaffold-commerce/templates/invoice-route.template.ts +376 -0
  39. package/templates/.claude/skills/scaffold-commerce/templates/payment-link-route.template.ts +471 -0
  40. package/templates/.claude/skills/scaffold-commerce/templates/portal-route.template.ts +365 -0
  41. package/templates/.claude/skills/scaffold-commerce/templates/stripe-config.template.ts +60 -0
  42. package/templates/.claude/skills/scaffold-commerce/templates/subscription-route.template.ts +446 -0
  43. package/templates/.claude/skills/suggest-site-pages/references/categories.md +4 -4
  44. package/templates/.env.example +126 -2
  45. package/templates/CLAUDE.md +11 -5
  46. package/templates/README.md +1 -1
  47. package/templates/apps/api/.env.example +38 -0
  48. package/templates/apps/api/CLAUDE.md +75 -1
  49. package/templates/apps/api/LICENSE +201 -0
  50. package/templates/apps/api/api/index.ts +6 -141
  51. package/templates/apps/api/jest.config.cjs +16 -0
  52. package/templates/apps/api/package.json +22 -6
  53. package/templates/apps/api/src/__tests__/module-exclusion.test.ts +141 -0
  54. package/templates/apps/api/src/__tests__/server.test.ts +46 -0
  55. package/templates/apps/api/src/__tests__/vercel-handler.test.ts +211 -0
  56. package/templates/apps/api/src/bin/abandon-sweep.ts +42 -0
  57. package/templates/apps/api/src/bin/deliverable-resend.ts +144 -0
  58. package/templates/apps/api/src/bin/deliverable-upload.ts +114 -0
  59. package/templates/apps/api/src/bin/followup-sweep.ts +44 -0
  60. package/templates/apps/api/src/bin/listing-csv-sweep.ts +46 -0
  61. package/templates/apps/api/src/bin/seed-presets.ts +58 -0
  62. package/templates/apps/api/src/bin/seed-waitlist-experiments.ts +104 -0
  63. package/templates/apps/api/src/bin/session-retention-sweep.ts +46 -0
  64. package/templates/apps/api/src/cli.ts +5 -11
  65. package/templates/apps/api/src/config/index.ts +35 -0
  66. package/templates/apps/api/src/config/modules.ts +43 -0
  67. package/templates/apps/api/src/lib/__mocks__/prisma-client.js +11 -0
  68. package/templates/apps/api/src/lib/__mocks__/prisma.ts +231 -0
  69. package/templates/apps/api/src/lib/__tests__/lead-capture-store.test.ts +2 -2
  70. package/templates/apps/api/src/lib/site.ts +16 -0
  71. package/templates/apps/api/src/lib/stripe.ts +22 -0
  72. package/templates/apps/api/src/lib/tenant-db.ts +225 -0
  73. package/templates/apps/api/src/lib/ws-token.ts +91 -0
  74. package/templates/apps/api/src/middleware/__tests__/adversarial/session-fixation.test.ts +7 -1
  75. package/templates/apps/api/src/middleware/__tests__/auth.cookie-path.test.ts +7 -1
  76. package/templates/apps/api/src/middleware/__tests__/rate-limit.test.ts +91 -4
  77. package/templates/apps/api/src/middleware/auth.ts +43 -8
  78. package/templates/apps/api/src/middleware/rate-limit.ts +78 -3
  79. package/templates/apps/api/src/middleware/tenant.ts +99 -0
  80. package/templates/apps/api/src/openapi/__tests__/openapi.test.ts +92 -0
  81. package/templates/apps/api/src/openapi/spec.ts +312 -3
  82. package/templates/apps/api/src/routes/aeo/__tests__/index.test.ts +175 -0
  83. package/templates/apps/api/src/routes/aeo/index.ts +107 -0
  84. package/templates/apps/api/src/routes/auth/__tests__/auth-tokens.functional.test.ts +5 -2
  85. package/templates/apps/api/src/routes/auth/__tests__/tokens.test.ts +22 -3
  86. package/templates/apps/api/src/routes/auth/tokens.ts +13 -1
  87. package/templates/apps/api/src/routes/billing/__tests__/portal.test.ts +450 -0
  88. package/templates/apps/api/src/routes/billing/__tests__/read.test.ts +262 -0
  89. package/templates/apps/api/src/routes/billing/__tests__/usage.test.ts +436 -0
  90. package/templates/apps/api/src/routes/billing/index.ts +36 -0
  91. package/templates/apps/api/src/routes/billing/portal.ts +182 -0
  92. package/templates/apps/api/src/routes/billing/read.ts +75 -0
  93. package/templates/apps/api/src/routes/billing/usage.ts +153 -0
  94. package/templates/apps/api/src/routes/checkout/__tests__/sessions.test.ts +99 -0
  95. package/templates/apps/api/src/routes/checkout/sessions.ts +20 -8
  96. package/templates/apps/api/src/routes/content/create.ts +1 -0
  97. package/templates/apps/api/src/routes/content/delete.ts +1 -0
  98. package/templates/apps/api/src/routes/content/index.ts +42 -6
  99. package/templates/apps/api/src/routes/content/update.ts +1 -0
  100. package/templates/apps/api/src/routes/deliverables/__tests__/index.test.ts +393 -0
  101. package/templates/apps/api/src/routes/deliverables/index.ts +200 -0
  102. package/templates/apps/api/src/routes/flow-checkouts/__tests__/index.test.ts +443 -0
  103. package/templates/apps/api/src/routes/flow-checkouts/index.ts +82 -0
  104. package/templates/apps/api/src/routes/flows/README.md +147 -0
  105. package/templates/apps/api/src/routes/flows/__tests__/index.test.ts +752 -0
  106. package/templates/apps/api/src/routes/flows/__tests__/recommender.test.ts +671 -0
  107. package/templates/apps/api/src/routes/flows/index.ts +202 -0
  108. package/templates/apps/api/src/routes/flows/recommender.ts +443 -0
  109. package/templates/apps/api/src/routes/leads/__tests__/index.test.ts +438 -31
  110. package/templates/apps/api/src/routes/leads/__tests__/track.test.ts +10 -10
  111. package/templates/apps/api/src/routes/leads/index.ts +43 -9
  112. package/templates/apps/api/src/routes/project-listings/__tests__/configured-application.test.ts +547 -0
  113. package/templates/apps/api/src/routes/project-listings/__tests__/copy-edit.test.ts +815 -0
  114. package/templates/apps/api/src/routes/project-listings/__tests__/drafts.test.ts +742 -0
  115. package/templates/apps/api/src/routes/project-listings/__tests__/public.test.ts +424 -0
  116. package/templates/apps/api/src/routes/project-listings/__tests__/site-key.test.ts +364 -0
  117. package/templates/apps/api/src/routes/project-listings/__tests__/tenant-isolation.test.ts +566 -0
  118. package/templates/apps/api/src/routes/project-listings/get.ts +70 -0
  119. package/templates/apps/api/src/routes/project-listings/index.ts +105 -0
  120. package/templates/apps/api/src/routes/project-listings/list.ts +47 -0
  121. package/templates/apps/api/src/routes/project-listings/patch-copy.ts +222 -0
  122. package/templates/apps/api/src/routes/project-listings/patch-draft.ts +97 -0
  123. package/templates/apps/api/src/routes/project-listings/patch.ts +136 -0
  124. package/templates/apps/api/src/routes/project-listings/public.ts +52 -0
  125. package/templates/apps/api/src/routes/project-listings/respond.ts +37 -0
  126. package/templates/apps/api/src/routes/project-listings/resume-token.ts +22 -0
  127. package/templates/apps/api/src/routes/project-listings/start.ts +98 -0
  128. package/templates/apps/api/src/routes/project-listings/submit.ts +122 -0
  129. package/templates/apps/api/src/routes/schedule/__tests__/index.test.ts +490 -0
  130. package/templates/apps/api/src/routes/schedule/index.ts +249 -0
  131. package/templates/apps/api/src/routes/slack/__tests__/actions.test.ts +385 -0
  132. package/templates/apps/api/src/routes/slack/actions.ts +177 -0
  133. package/templates/apps/api/src/routes/slack/index.ts +38 -0
  134. package/templates/apps/api/src/routes/test/auth/__tests__/session.test.ts +16 -6
  135. package/templates/apps/api/src/routes/test/auth/session.ts +8 -6
  136. package/templates/apps/api/src/routes/waitlist-experiments/__tests__/tenant-isolation.test.ts +577 -0
  137. package/templates/apps/api/src/routes/waitlist-experiments/comparison.ts +78 -0
  138. package/templates/apps/api/src/routes/waitlist-experiments/index.ts +41 -0
  139. package/templates/apps/api/src/routes/waitlist-experiments/list.ts +64 -0
  140. package/templates/apps/api/src/routes/waitlist-experiments/respond.ts +42 -0
  141. package/templates/apps/api/src/routes/waitlist-experiments/signup.ts +84 -0
  142. package/templates/apps/api/src/routes/waitlist-experiments/signups.ts +119 -0
  143. package/templates/apps/api/src/routes/waitlist-signups/__tests__/index.test.ts +238 -0
  144. package/templates/apps/api/src/routes/waitlist-signups/index.ts +101 -0
  145. package/templates/apps/api/src/routes/webhooks/README.md +35 -12
  146. package/templates/apps/api/src/routes/webhooks/__tests__/stripe-flow-checkout.test.ts +315 -0
  147. package/templates/apps/api/src/routes/webhooks/__tests__/stripe-org-billing.test.ts +270 -0
  148. package/templates/apps/api/src/routes/webhooks/stripe.ts +248 -20
  149. package/templates/apps/api/src/routes/workspaces/__tests__/config-tenant-isolation.test.ts +356 -0
  150. package/templates/apps/api/src/routes/workspaces/__tests__/tenant-isolation.test.ts +671 -0
  151. package/templates/apps/api/src/routes/workspaces/config.ts +125 -0
  152. package/templates/apps/api/src/routes/workspaces/index.ts +194 -0
  153. package/templates/apps/api/src/routes/workspaces/sessions.ts +218 -0
  154. package/templates/apps/api/src/server.ts +293 -7
  155. package/templates/apps/api/src/services/__tests__/abandon-email.test.ts +489 -0
  156. package/templates/apps/api/src/services/__tests__/aeo-score.test.ts +475 -0
  157. package/templates/apps/api/src/services/__tests__/answer-distributions.test.ts +132 -0
  158. package/templates/apps/api/src/services/__tests__/deliverable-fulfillment.test.ts +523 -0
  159. package/templates/apps/api/src/services/__tests__/deliverable-resend.test.ts +359 -0
  160. package/templates/apps/api/src/services/__tests__/entitlements.test.ts +142 -0
  161. package/templates/apps/api/src/services/__tests__/flow-aggregate.test.ts +425 -0
  162. package/templates/apps/api/src/services/__tests__/flow-engine.test.ts +2840 -0
  163. package/templates/apps/api/src/services/__tests__/lead-promotion.test.ts +393 -0
  164. package/templates/apps/api/src/services/__tests__/lead-routing.test.ts +8 -8
  165. package/templates/apps/api/src/services/__tests__/listing-csv-sweep.test.ts +560 -0
  166. package/templates/apps/api/src/services/__tests__/playbook-compile.test.ts +406 -0
  167. package/templates/apps/api/src/services/__tests__/playbook-render.test.ts +290 -0
  168. package/templates/apps/api/src/services/__tests__/project-listing-decision.test.ts +736 -0
  169. package/templates/apps/api/src/services/__tests__/project-listing-flow.test.ts +475 -0
  170. package/templates/apps/api/src/services/__tests__/project-listing-issue.test.ts +340 -0
  171. package/templates/apps/api/src/services/__tests__/recommender-capture.test.ts +983 -0
  172. package/templates/apps/api/src/services/__tests__/slack-notify.test.ts +278 -0
  173. package/templates/apps/api/src/services/__tests__/tenant-context-cascade.test.ts +71 -0
  174. package/templates/apps/api/src/services/__tests__/tenant-context.test.ts +379 -0
  175. package/templates/apps/api/src/services/__tests__/usage-aggregation.test.ts +941 -0
  176. package/templates/apps/api/src/services/__tests__/usage-credits.test.ts +588 -0
  177. package/templates/apps/api/src/services/__tests__/usage-metering.test.ts +768 -0
  178. package/templates/apps/api/src/services/__tests__/waitlist-dashboard.test.ts +1314 -0
  179. package/templates/apps/api/src/services/__tests__/waitlist-experiments.test.ts +341 -0
  180. package/templates/apps/api/src/services/__tests__/waitlist-followup.test.ts +567 -0
  181. package/templates/apps/api/src/services/__tests__/waitlist-scoring.test.ts +474 -0
  182. package/templates/apps/api/src/services/__tests__/waitlist-signups.test.ts +354 -0
  183. package/templates/apps/api/src/services/abandon-email.ts +307 -0
  184. package/templates/apps/api/src/services/aeo-score.ts +288 -0
  185. package/templates/apps/api/src/services/answer-distributions.ts +138 -0
  186. package/templates/apps/api/src/services/deliverable-fulfillment.ts +523 -0
  187. package/templates/apps/api/src/services/entitlements.ts +102 -0
  188. package/templates/apps/api/src/services/flow-aggregate.ts +232 -0
  189. package/templates/apps/api/src/services/flow-checkouts.ts +123 -0
  190. package/templates/apps/api/src/services/flow-engine.ts +1278 -0
  191. package/templates/apps/api/src/services/lead-promotion.ts +176 -0
  192. package/templates/apps/api/src/services/lead-routing.ts +3 -3
  193. package/templates/apps/api/src/services/listing-config.ts +102 -0
  194. package/templates/apps/api/src/services/listing-csv-sweep.ts +455 -0
  195. package/templates/apps/api/src/services/playbook-compile.ts +398 -0
  196. package/templates/apps/api/src/services/playbook-render.ts +263 -0
  197. package/templates/apps/api/src/services/project-listing-decision.ts +490 -0
  198. package/templates/apps/api/src/services/project-listing-flow.ts +426 -0
  199. package/templates/apps/api/src/services/project-listing-issue.ts +251 -0
  200. package/templates/apps/api/src/services/project-listings.ts +1124 -0
  201. package/templates/apps/api/src/services/recommender-capture.ts +835 -0
  202. package/templates/apps/api/src/services/scheduling/cal-provider.ts +392 -0
  203. package/templates/apps/api/src/services/scheduling/index.ts +63 -0
  204. package/templates/apps/api/src/services/scheduling/types.ts +88 -0
  205. package/templates/apps/api/src/services/slack-notify.ts +240 -0
  206. package/templates/apps/api/src/services/tenant-context.ts +451 -0
  207. package/templates/apps/api/src/services/usage-aggregation.ts +516 -0
  208. package/templates/apps/api/src/services/usage-credits.ts +340 -0
  209. package/templates/apps/api/src/services/usage-metering.ts +699 -0
  210. package/templates/apps/api/src/services/waitlist-dashboard.ts +947 -0
  211. package/templates/apps/api/src/services/waitlist-experiments.ts +213 -0
  212. package/templates/apps/api/src/services/waitlist-followup.ts +486 -0
  213. package/templates/apps/api/src/services/waitlist-scoring.ts +166 -0
  214. package/templates/apps/api/src/services/waitlist-signups.ts +165 -0
  215. package/templates/apps/api/src/vercel-handler.ts +165 -0
  216. package/templates/apps/api/tsconfig.json +0 -12
  217. package/templates/apps/mcp/CLAUDE.md +19 -9
  218. package/templates/apps/mcp/LICENSE +201 -0
  219. package/templates/apps/mcp/__tests__/create_page.test.ts +1 -0
  220. package/templates/apps/mcp/__tests__/delete_page.test.ts +1 -0
  221. package/templates/apps/mcp/__tests__/module-exclusion.test.ts +105 -0
  222. package/templates/apps/mcp/__tests__/parity.test.ts +70 -6
  223. package/templates/apps/mcp/__tests__/request_callback.test.ts +4 -4
  224. package/templates/apps/mcp/__tests__/schedule_tools.test.ts +242 -0
  225. package/templates/apps/mcp/__tests__/submit_lead.test.ts +4 -4
  226. package/templates/apps/mcp/__tests__/subscribe_newsletter.test.ts +4 -4
  227. package/templates/apps/mcp/__tests__/tools/purchase.test.ts +47 -0
  228. package/templates/apps/mcp/__tests__/update_page.test.ts +1 -0
  229. package/templates/apps/mcp/package.json +6 -4
  230. package/templates/apps/mcp/src/config/modules.ts +37 -0
  231. package/templates/apps/mcp/src/server.ts +109 -18
  232. package/templates/apps/mcp/src/tools/capture_lead.ts +1 -1
  233. package/templates/apps/mcp/src/tools/create_booking.ts +58 -0
  234. package/templates/apps/mcp/src/tools/create_page.ts +1 -0
  235. package/templates/apps/mcp/src/tools/delete_page.ts +1 -0
  236. package/templates/apps/mcp/src/tools/get_event_meta.ts +55 -0
  237. package/templates/apps/mcp/src/tools/get_flow.ts +61 -0
  238. package/templates/apps/mcp/src/tools/index.ts +25 -0
  239. package/templates/apps/mcp/src/tools/list_availability.ts +67 -0
  240. package/templates/apps/mcp/src/tools/purchase.ts +3 -0
  241. package/templates/apps/mcp/src/tools/submit_flow_step.ts +83 -0
  242. package/templates/apps/mcp/src/tools/update_page.ts +1 -0
  243. package/templates/apps/mcp/tsconfig.json +1 -9
  244. package/templates/apps/web/.env.example +44 -0
  245. package/templates/apps/web/CLAUDE.md +28 -0
  246. package/templates/apps/web/LICENSE +201 -0
  247. package/templates/apps/web/__tests__/__mocks__/code-highlight.jest-stub.ts +24 -0
  248. package/templates/apps/web/__tests__/__mocks__/next-intl-server.ts +33 -0
  249. package/templates/apps/web/__tests__/__mocks__/next-intl.ts +18 -0
  250. package/templates/apps/web/__tests__/__mocks__/server-only.ts +13 -0
  251. package/templates/apps/web/__tests__/__mocks__/style-mock.js +4 -0
  252. package/templates/apps/web/__tests__/__mocks__/vercel-analytics-server.ts +9 -0
  253. package/templates/apps/web/__tests__/jest-support/import-meta-transformer.cjs +90 -0
  254. package/templates/apps/web/__tests__/setup.ts +22 -32
  255. package/templates/apps/web/app/.well-known/ai-plugin.json/route.ts +6 -3
  256. package/templates/apps/web/app/.well-known/mcp.json/build-manifest.ts +8 -2
  257. package/templates/apps/web/app/[locale]/(authed)/account/__tests__/page.test.tsx +260 -0
  258. package/templates/apps/web/app/[locale]/(authed)/account/page.tsx +263 -0
  259. package/templates/apps/web/app/[locale]/(authed)/account/sign-out-button.tsx +39 -0
  260. package/templates/apps/web/app/[locale]/(authed)/layout.tsx +54 -0
  261. package/templates/apps/web/app/[locale]/(authed)/sign-in-gate.tsx +96 -0
  262. package/templates/apps/web/app/[locale]/(authed)/waitlist/[experimentId]/page.tsx +309 -0
  263. package/templates/apps/web/app/[locale]/(authed)/waitlist/[experimentId]/signups/[leadId]/page.tsx +149 -0
  264. package/templates/apps/web/app/[locale]/(authed)/waitlist/__tests__/comparison.test.tsx +273 -0
  265. package/templates/apps/web/app/[locale]/(authed)/waitlist/__tests__/gate.test.tsx +172 -0
  266. package/templates/apps/web/app/[locale]/(authed)/waitlist/__tests__/list.test.tsx +171 -0
  267. package/templates/apps/web/app/[locale]/(authed)/waitlist/__tests__/signup.test.tsx +179 -0
  268. package/templates/apps/web/app/[locale]/(authed)/waitlist/components/ExperimentsTable.tsx +116 -0
  269. package/templates/apps/web/app/[locale]/(authed)/waitlist/components/ForbiddenState.tsx +24 -0
  270. package/templates/apps/web/app/[locale]/(authed)/waitlist/components/OfferFunnelTable.tsx +66 -0
  271. package/templates/apps/web/app/[locale]/(authed)/waitlist/components/RollupTicker.tsx +61 -0
  272. package/templates/apps/web/app/[locale]/(authed)/waitlist/components/ScoreTrace.tsx +114 -0
  273. package/templates/apps/web/app/[locale]/(authed)/waitlist/components/SignupsTable.tsx +139 -0
  274. package/templates/apps/web/app/[locale]/(authed)/waitlist/components/WaitlistComparisonTabs.tsx +85 -0
  275. package/templates/apps/web/app/[locale]/(authed)/waitlist/components/status-badges.tsx +67 -0
  276. package/templates/apps/web/app/[locale]/(authed)/waitlist/format.ts +111 -0
  277. package/templates/apps/web/app/[locale]/(authed)/waitlist/page.tsx +179 -0
  278. package/templates/apps/web/app/[locale]/about/page.tsx +10 -1
  279. package/templates/apps/web/app/[locale]/blog/[[...slug]]/page.tsx +6 -1
  280. package/templates/apps/web/app/[locale]/careers/[slug]/page.tsx +9 -0
  281. package/templates/apps/web/app/[locale]/careers/__tests__/page-renderer-migration.test.tsx +3 -3
  282. package/templates/apps/web/app/[locale]/careers/page.tsx +9 -1
  283. package/templates/apps/web/app/[locale]/case-studies/[slug]/page.tsx +9 -0
  284. package/templates/apps/web/app/[locale]/case-studies/__tests__/page-renderer-migration.test.tsx +3 -3
  285. package/templates/apps/web/app/[locale]/case-studies/page.tsx +13 -1
  286. package/templates/apps/web/app/[locale]/clients/__tests__/page-renderer-migration.test.tsx +11 -1
  287. package/templates/apps/web/app/[locale]/clients/page.tsx +9 -1
  288. package/templates/apps/web/app/[locale]/contact/ContactForm.tsx +21 -1
  289. package/templates/apps/web/app/[locale]/contact/ConversationalIntakeWrapper.tsx +188 -0
  290. package/templates/apps/web/app/[locale]/contact/__tests__/ConversationalIntakeWrapper.test.tsx +242 -0
  291. package/templates/apps/web/app/[locale]/contact/__tests__/contact.actions.test.ts +29 -0
  292. package/templates/apps/web/app/[locale]/contact/__tests__/intake-analytics.test.ts +51 -0
  293. package/templates/apps/web/app/[locale]/contact/__tests__/page-renderer-migration.test.tsx +39 -15
  294. package/templates/apps/web/app/[locale]/contact/contact.actions.ts +21 -3
  295. package/templates/apps/web/app/[locale]/contact/intake-analytics.ts +49 -0
  296. package/templates/apps/web/app/[locale]/contact/intake-script.ts +66 -0
  297. package/templates/apps/web/app/[locale]/contact/page.tsx +14 -4
  298. package/templates/apps/web/app/[locale]/courses/[slug]/page.tsx +13 -1
  299. package/templates/apps/web/app/[locale]/courses/__tests__/page-renderer-migration.test.tsx +3 -3
  300. package/templates/apps/web/app/[locale]/courses/page.tsx +9 -1
  301. package/templates/apps/web/app/[locale]/docs/[[...slug]]/page.tsx +1 -0
  302. package/templates/apps/web/app/[locale]/downloads/__tests__/page-renderer-migration.test.tsx +11 -1
  303. package/templates/apps/web/app/[locale]/downloads/page.tsx +13 -1
  304. package/templates/apps/web/app/[locale]/events/[slug]/page.tsx +9 -0
  305. package/templates/apps/web/app/[locale]/events/__tests__/page-renderer-migration.test.tsx +12 -2
  306. package/templates/apps/web/app/[locale]/events/page.tsx +9 -1
  307. package/templates/apps/web/app/[locale]/faq/page.tsx +10 -1
  308. package/templates/apps/web/app/[locale]/flows/[slug]/FlowStepperClient.tsx +265 -0
  309. package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/FlowStepperClient.permalink.test.tsx +445 -0
  310. package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/FlowStepperClient.test.tsx +341 -0
  311. package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/checkout.actions.test.ts +287 -0
  312. package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/flow.actions.test.ts +302 -0
  313. package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/two-route-tool.test.tsx +128 -0
  314. package/templates/apps/web/app/[locale]/flows/[slug]/checkout.actions.ts +254 -0
  315. package/templates/apps/web/app/[locale]/flows/[slug]/flow.actions.ts +144 -0
  316. package/templates/apps/web/app/[locale]/flows/[slug]/page.tsx +108 -0
  317. package/templates/apps/web/app/[locale]/flows/[slug]/results/[payload]/ResultsClient.tsx +532 -0
  318. package/templates/apps/web/app/[locale]/flows/[slug]/results/[payload]/ResultsEmailStep.tsx +121 -0
  319. package/templates/apps/web/app/[locale]/flows/[slug]/results/[payload]/__tests__/ResultsClient.checkout.test.tsx +457 -0
  320. package/templates/apps/web/app/[locale]/flows/[slug]/results/[payload]/__tests__/page.test.tsx +396 -0
  321. package/templates/apps/web/app/[locale]/flows/[slug]/results/[payload]/page.tsx +114 -0
  322. package/templates/apps/web/app/[locale]/flows/[slug]/start/page.tsx +52 -0
  323. package/templates/apps/web/app/[locale]/how-to/[slug]/page.tsx +13 -1
  324. package/templates/apps/web/app/[locale]/how-to/__tests__/page-renderer-migration.test.tsx +1 -1
  325. package/templates/apps/web/app/[locale]/integrations/[slug]/page.tsx +9 -0
  326. package/templates/apps/web/app/[locale]/integrations/__tests__/page-renderer-migration.test.tsx +3 -3
  327. package/templates/apps/web/app/[locale]/integrations/page.tsx +13 -1
  328. package/templates/apps/web/app/[locale]/launch-service/page.tsx +14 -1
  329. package/templates/apps/web/app/[locale]/layout.tsx +115 -16
  330. package/templates/apps/web/app/[locale]/page.tsx +10 -1
  331. package/templates/apps/web/app/[locale]/partners/[slug]/page.tsx +9 -0
  332. package/templates/apps/web/app/[locale]/partners/__tests__/page-renderer-migration.test.tsx +3 -3
  333. package/templates/apps/web/app/[locale]/partners/page.tsx +9 -1
  334. package/templates/apps/web/app/[locale]/products/[slug]/page.tsx +9 -0
  335. package/templates/apps/web/app/[locale]/products/__tests__/page-renderer-migration.test.tsx +3 -3
  336. package/templates/apps/web/app/[locale]/products/page.tsx +9 -1
  337. package/templates/apps/web/app/[locale]/projects/[slug]/page.tsx +13 -1
  338. package/templates/apps/web/app/[locale]/projects/__tests__/page-renderer-migration.test.tsx +10 -0
  339. package/templates/apps/web/app/[locale]/projects/apply/ListingApplicationClient.tsx +110 -0
  340. package/templates/apps/web/app/[locale]/projects/apply/__tests__/ListingApplicationClient.test.tsx +96 -0
  341. package/templates/apps/web/app/[locale]/projects/page.tsx +13 -1
  342. package/templates/apps/web/app/[locale]/resources/[slug]/page.tsx +9 -0
  343. package/templates/apps/web/app/[locale]/resources/__tests__/page-renderer-migration.test.tsx +12 -2
  344. package/templates/apps/web/app/[locale]/resources/page.tsx +13 -1
  345. package/templates/apps/web/app/[locale]/schedule/__tests__/page.test.tsx +117 -0
  346. package/templates/apps/web/app/[locale]/schedule/confirmed/__tests__/page.test.tsx +96 -0
  347. package/templates/apps/web/app/[locale]/schedule/confirmed/page.tsx +92 -0
  348. package/templates/apps/web/app/[locale]/schedule/page.tsx +82 -0
  349. package/templates/apps/web/app/[locale]/services/[slug]/page.tsx +9 -0
  350. package/templates/apps/web/app/[locale]/services/__tests__/page-renderer-migration.test.tsx +3 -3
  351. package/templates/apps/web/app/[locale]/services/page.tsx +9 -1
  352. package/templates/apps/web/app/[locale]/sign-in/__tests__/next-path.test.ts +31 -0
  353. package/templates/apps/web/app/[locale]/sign-in/__tests__/page.test.tsx +152 -0
  354. package/templates/apps/web/app/[locale]/sign-in/next-path.ts +0 -0
  355. package/templates/apps/web/app/[locale]/sign-in/page.tsx +83 -0
  356. package/templates/apps/web/app/[locale]/sign-in/sign-in-card.tsx +123 -0
  357. package/templates/apps/web/app/[locale]/software/[slug]/page.tsx +13 -1
  358. package/templates/apps/web/app/[locale]/software/__tests__/page-renderer-migration.test.tsx +1 -1
  359. package/templates/apps/web/app/[locale]/tech-articles/[slug]/page.tsx +13 -1
  360. package/templates/apps/web/app/[locale]/tech-articles/__tests__/page-renderer-migration.test.tsx +1 -1
  361. package/templates/apps/web/app/[locale]/testimonials/__tests__/page-renderer-migration.test.tsx +11 -1
  362. package/templates/apps/web/app/[locale]/testimonials/page.tsx +13 -1
  363. package/templates/apps/web/app/[locale]/thanks/[slug]/__tests__/page.test.tsx +93 -0
  364. package/templates/apps/web/app/[locale]/thanks/[slug]/page.tsx +48 -0
  365. package/templates/apps/web/app/[locale]/use-cases/__tests__/page-renderer-migration.test.tsx +11 -1
  366. package/templates/apps/web/app/[locale]/use-cases/page.tsx +13 -1
  367. package/templates/apps/web/app/[locale]/whitepapers/[slug]/page.tsx +13 -1
  368. package/templates/apps/web/app/[locale]/whitepapers/__tests__/page-renderer-migration.test.tsx +1 -1
  369. package/templates/apps/web/app/globals.css +35 -0
  370. package/templates/apps/web/app/llms.txt/route.ts +2 -1
  371. package/templates/apps/web/app/og/route.tsx +10 -2
  372. package/templates/apps/web/auth.ts +8 -0
  373. package/templates/apps/web/components/sign-out-trigger.tsx +42 -0
  374. package/templates/apps/web/content/en/blog/README.txt +25 -0
  375. package/templates/apps/web/content/en/docs/index.mdx +28 -0
  376. package/templates/apps/web/jest.config.cjs +84 -27
  377. package/templates/apps/web/lib/__tests__/site-brand.test.ts +119 -0
  378. package/templates/apps/web/lib/__tests__/structured-data.test.tsx +499 -0
  379. package/templates/apps/web/lib/account-config.ts +13 -0
  380. package/templates/apps/web/lib/server-api.ts +85 -0
  381. package/templates/apps/web/lib/site-brand.tsx +118 -0
  382. package/templates/apps/web/messages/en.json +275 -7
  383. package/templates/apps/web/middleware.ts +11 -3
  384. package/templates/apps/web/next.config.mjs +8 -2
  385. package/templates/apps/web/package.json +16 -7
  386. package/templates/apps/web/postcss.config.mjs +23 -0
  387. package/templates/apps/web/public/.well-known/security.txt +3 -3
  388. package/templates/apps/web/src/lib/routes.ts +15 -2
  389. package/templates/apps/web/tsconfig.json +1 -16
  390. package/templates/content/_site.mdx +24 -25
  391. package/templates/content/en/AboutPage/about.mdx +24 -14
  392. package/templates/content/en/ContactPage/contact.mdx +23 -14
  393. package/templates/content/en/FAQPage/faq.mdx +10 -392
  394. package/templates/content/en/HomePage/home.mdx +14 -285
  395. package/templates/database/CHANGELOG.md +382 -0
  396. package/templates/database/LICENSE +201 -0
  397. package/templates/database/README.md +3 -1
  398. package/templates/database/__tests__/flows-listing-diagnostic.test.ts +392 -0
  399. package/templates/database/__tests__/flows-presets.test.ts +173 -0
  400. package/templates/database/__tests__/prefixed-ids.db.test.ts +146 -0
  401. package/templates/database/__tests__/seed.test.ts +88 -0
  402. package/templates/database/__tests__/user-cascade.test.ts +19 -4
  403. package/templates/database/auth/schema.prisma +521 -0
  404. package/templates/database/config/schema.prisma +234 -0
  405. package/templates/database/content/schema.prisma +263 -0
  406. package/templates/database/flows/listing-diagnostic.ts +182 -0
  407. package/templates/database/flows/presets.ts +315 -0
  408. package/templates/database/inbox/__tests__/schema.test.ts +4 -0
  409. package/templates/database/inbox/schema.prisma +476 -3
  410. package/templates/database/jest.config.cjs +8 -0
  411. package/templates/database/migrations/20260712215647_add_multi_tenant_tenancy/migration.sql +201 -0
  412. package/templates/database/migrations/20260713143320_reanchor_site_config_to_workspace/migration.sql +67 -0
  413. package/templates/database/migrations/20260713174000_add_audit_logs/migration.sql +46 -0
  414. package/templates/database/migrations/20260713190000_drop_telemetry_records/migration.sql +25 -0
  415. package/templates/database/migrations/20260713192000_add_uuidv7_function/migration.sql +49 -0
  416. package/templates/database/migrations/20260713192429_prefixed_uuid_entity_ids/migration.sql +197 -0
  417. package/templates/database/migrations/20260713200000_add_org_billing/migration.sql +32 -0
  418. package/templates/database/migrations/20260714000000_add_lead_promotion_fields/migration.sql +22 -0
  419. package/templates/database/migrations/20260714010000_add_usage_billing_content_models/migration.sql +151 -0
  420. package/templates/database/migrations/20260714020000_add_usage_billing_auth_config_models/migration.sql +223 -0
  421. package/templates/database/migrations/20260714030000_credit_balance_non_negative/migration.sql +22 -0
  422. package/templates/database/migrations/20260714030000_widen_usage_billing_report_aggregates/migration.sql +32 -0
  423. package/templates/database/migrations/20260810233516_add_flow_engine_models/migration.sql +40 -0
  424. package/templates/database/migrations/20260811020000_add_flow_session_abandon_email_columns/migration.sql +4 -0
  425. package/templates/database/migrations/20260811030000_add_flow_session_abandon_email_index/migration.sql +2 -0
  426. package/templates/database/migrations/20260811040000_add_flow_session_segment/migration.sql +2 -0
  427. package/templates/database/migrations/20260811050000_add_deliverable_models/migration.sql +29 -0
  428. package/templates/database/migrations/20260812000000_add_deliverable_grant_send_columns/migration.sql +6 -0
  429. package/templates/database/migrations/20260902222948_add_waitlist_experiment_models/migration.sql +39 -0
  430. package/templates/database/migrations/20260902224500_add_waitlist_id_format_checks/migration.sql +24 -0
  431. package/templates/database/migrations/20260903004112_add_flow_session_flags_aeo_score/migration.sql +3 -0
  432. package/templates/database/migrations/20260903010000_add_playbook_instance/migration.sql +33 -0
  433. package/templates/database/migrations/20260904000000_add_waitlist_followup_send/migration.sql +30 -0
  434. package/templates/database/migrations/20260905230000_add_flow_checkout/migration.sql +47 -0
  435. package/templates/database/migrations/20260906000000_add_flow_checkout_idempotency/migration.sql +16 -0
  436. package/templates/database/migrations/20260907120000_add_project_listing/migration.sql +47 -0
  437. package/templates/database/migrations/20260907130000_add_project_listing_slack_columns/migration.sql +10 -0
  438. package/templates/database/migrations/20260907180000_add_listing_csv_send/migration.sql +26 -0
  439. package/templates/database/migrations/20260909120000_project_listings_platform/migration.sql +90 -0
  440. package/templates/database/migrations/20260909180000_listing_draft_and_fields/migration.sql +174 -0
  441. package/templates/database/migrations/20260910120000_listing_public_site_key/migration.sql +14 -0
  442. package/templates/database/migrations/20260911120000_listing_copy_edit_audit/migration.sql +25 -0
  443. package/templates/database/ops/schema.prisma +27 -0
  444. package/templates/database/package.json +42 -4
  445. package/templates/database/prisma.config.ts +1 -1
  446. package/templates/database/schema.prisma +19 -435
  447. package/templates/database/scripts/db-generate-locked.sh +0 -0
  448. package/templates/database/seed-test.ts +14 -4
  449. package/templates/database/seed.ts +148 -0
  450. package/templates/database/tenancy.ts +45 -0
  451. package/templates/database/tsconfig.flows.json +15 -0
  452. package/templates/database/tsconfig.json +7 -1
  453. package/templates/database/tsconfig.tenancy.json +14 -0
  454. package/templates/package.json +30 -55
  455. package/templates/pnpm-workspace.yaml +0 -3
  456. package/templates/project.yaml +1 -1
  457. package/templates/tsconfig.json +1 -37
  458. package/templates/apps/web/__tests__/auth/adversarial/cookie-attributes.test.ts +0 -94
  459. package/templates/apps/web/__tests__/auth/adversarial/oauth-state-parameter.test.ts +0 -75
  460. package/templates/apps/web/__tests__/auth/adversarial/redirect-uri-allowlist.test.ts +0 -82
  461. package/templates/apps/web/__tests__/auth/env-helpers.test.ts +0 -168
  462. package/templates/apps/web/__tests__/authority-list-routes.test.tsx +0 -208
  463. package/templates/apps/web/__tests__/better-stack.test.ts +0 -66
  464. package/templates/apps/web/__tests__/blog-dir-resolution.test.ts +0 -87
  465. package/templates/apps/web/__tests__/blog-source-populated.test.ts +0 -130
  466. package/templates/apps/web/__tests__/data-testid-coverage.test.tsx +0 -60
  467. package/templates/apps/web/__tests__/google-analytics.test.tsx +0 -104
  468. package/templates/apps/web/__tests__/i18n/middleware.test.ts +0 -119
  469. package/templates/apps/web/__tests__/i18n/routing.test.ts +0 -114
  470. package/templates/apps/web/__tests__/i18n/translations.test.ts +0 -191
  471. package/templates/apps/web/__tests__/json-ld.test.ts +0 -98
  472. package/templates/apps/web/__tests__/knowledge-events-routes.test.tsx +0 -580
  473. package/templates/apps/web/__tests__/layout-stealth.test.ts +0 -39
  474. package/templates/apps/web/__tests__/lighthouse-fixtures/improvement-image-shrink.tsx +0 -103
  475. package/templates/apps/web/__tests__/lighthouse-fixtures/override-fixture.md +0 -114
  476. package/templates/apps/web/__tests__/lighthouse-fixtures/regression-bloat.tsx +0 -94
  477. package/templates/apps/web/__tests__/page.test.tsx +0 -230
  478. package/templates/apps/web/__tests__/products-services-phase1.test.tsx +0 -321
  479. package/templates/apps/web/__tests__/projects-routes.test.tsx +0 -392
  480. package/templates/apps/web/__tests__/resources-downloads-integrations-phase2.test.tsx +0 -475
  481. package/templates/apps/web/__tests__/routes/ai-plugin-json.test.ts +0 -53
  482. package/templates/apps/web/__tests__/routes/blog-route.test.tsx +0 -191
  483. package/templates/apps/web/__tests__/routes/llms-txt.test.ts +0 -123
  484. package/templates/apps/web/__tests__/routes/robots-txt.test.ts +0 -52
  485. package/templates/apps/web/__tests__/routes/sitemap-xml.test.ts +0 -78
  486. package/templates/apps/web/__tests__/routes/well-known-mcp.test.ts +0 -203
  487. package/templates/apps/web/__tests__/section-routes.test.tsx +0 -693
  488. package/templates/apps/web/__tests__/security-headers.test.ts +0 -201
  489. package/templates/apps/web/__tests__/sentry-config.test.ts +0 -116
  490. package/templates/apps/web/__tests__/sitemap-locales.test.ts +0 -415
  491. package/templates/apps/web/__tests__/standalone-informational-routes.test.tsx +0 -844
  492. package/templates/apps/web/app/[locale]/__tests__/layout.test.tsx +0 -120
  493. package/templates/apps/web/app/[locale]/__tests__/page-renderer-migration.test.tsx +0 -202
  494. package/templates/apps/web/app/__tests__/error-boundaries.test.tsx +0 -152
  495. package/templates/apps/web/content/en/blog/index.mdx +0 -15
  496. package/templates/apps/web/content/en/blog/launching-ailk.mdx +0 -20
  497. package/templates/apps/web/content/en/docs/getting-started.mdx +0 -30
  498. package/templates/content/.gitkeep +0 -0
  499. package/templates/content/CLAUDE.md +0 -85
  500. package/templates/content/ar/HomePage/home.mdx +0 -123
  501. package/templates/content/ar/_site.mdx +0 -35
  502. package/templates/content/de/_site.mdx +0 -29
  503. package/templates/content/en/.gitkeep +0 -0
  504. package/templates/content/en/AboutPage/about.schema.json +0 -82
  505. package/templates/content/en/BookCall/book-call.mdx +0 -19
  506. package/templates/content/en/BookCall/book-call.schema.json +0 -121
  507. package/templates/content/en/BreadcrumbList/site-breadcrumbs.mdx +0 -20
  508. package/templates/content/en/CareersList/careers.mdx +0 -24
  509. package/templates/content/en/CaseStudiesList/case-studies.mdx +0 -22
  510. package/templates/content/en/CaseStudiesList/showcase.mdx +0 -8
  511. package/templates/content/en/CaseStudy/ailk-aeo-rollout.mdx +0 -32
  512. package/templates/content/en/CaseStudy/schema-builder-migration.mdx +0 -41
  513. package/templates/content/en/ClientsList/clients.mdx +0 -38
  514. package/templates/content/en/ContactPage/contact.schema.json +0 -100
  515. package/templates/content/en/ContactPoint/primary.mdx +0 -14
  516. package/templates/content/en/Course/aeo-fundamentals.mdx +0 -37
  517. package/templates/content/en/Course/schema-builder-workshop.mdx +0 -62
  518. package/templates/content/en/CoursesList/courses.mdx +0 -22
  519. package/templates/content/en/DownloadsList/downloads.mdx +0 -22
  520. package/templates/content/en/Event/aeo-office-hours-may-2026.mdx +0 -39
  521. package/templates/content/en/Event/ailk-launch-day-2026.mdx +0 -35
  522. package/templates/content/en/EventsList/events.mdx +0 -26
  523. package/templates/content/en/FAQPage/faq.schema.json +0 -231
  524. package/templates/content/en/GlossaryList/glossary.mdx +0 -19
  525. package/templates/content/en/GlossaryList/glossary.schema.json +0 -300
  526. package/templates/content/en/HomePage/home.schema.json +0 -88
  527. package/templates/content/en/HomePage/launch-service.mdx +0 -475
  528. package/templates/content/en/HomePage/launch-service.schema.json +0 -138
  529. package/templates/content/en/HowTo/add-mdx-content.mdx +0 -38
  530. package/templates/content/en/HowTo/configure-aeo-scoring.mdx +0 -35
  531. package/templates/content/en/HowTo/set-up-json-ld.mdx +0 -36
  532. package/templates/content/en/Integration/github-actions.mdx +0 -31
  533. package/templates/content/en/Integration/openai-api.mdx +0 -26
  534. package/templates/content/en/Integration/vercel.mdx +0 -32
  535. package/templates/content/en/IntegrationsList/integrations.mdx +0 -22
  536. package/templates/content/en/JobPosting/content-strategist.mdx +0 -46
  537. package/templates/content/en/JobPosting/developer-advocate.mdx +0 -47
  538. package/templates/content/en/JobPosting/senior-fullstack-engineer.mdx +0 -57
  539. package/templates/content/en/LandingPage/aeo-audit.mdx +0 -19
  540. package/templates/content/en/LandingPage/aeo-audit.schema.json +0 -92
  541. package/templates/content/en/LandingPage/agency-waitlist.mdx +0 -340
  542. package/templates/content/en/LandingPage/agency-waitlist.schema.json +0 -152
  543. package/templates/content/en/LandingPage/developers.mdx +0 -19
  544. package/templates/content/en/LandingPage/developers.schema.json +0 -109
  545. package/templates/content/en/LandingPage/for-agencies.mdx +0 -390
  546. package/templates/content/en/LandingPage/for-agencies.schema.json +0 -107
  547. package/templates/content/en/LandingPage/for-ai-search-agencies.mdx +0 -417
  548. package/templates/content/en/LandingPage/for-ai-search-agencies.schema.json +0 -105
  549. package/templates/content/en/LandingPage/for-businesses.mdx +0 -19
  550. package/templates/content/en/LandingPage/for-businesses.schema.json +0 -96
  551. package/templates/content/en/LandingPage/platform.mdx +0 -369
  552. package/templates/content/en/LandingPage/platform.schema.json +0 -166
  553. package/templates/content/en/LandingPage/vs-ai-website-builders.mdx +0 -19
  554. package/templates/content/en/LandingPage/vs-ai-website-builders.schema.json +0 -102
  555. package/templates/content/en/LandingPage/vs-wordpress.mdx +0 -19
  556. package/templates/content/en/LandingPage/vs-wordpress.schema.json +0 -53
  557. package/templates/content/en/NewsletterSignup/newsletter.mdx +0 -236
  558. package/templates/content/en/NewsletterSignup/newsletter.schema.json +0 -91
  559. package/templates/content/en/Organization/site-owner.mdx +0 -16
  560. package/templates/content/en/Partner/prisma.mdx +0 -31
  561. package/templates/content/en/Partner/supabase.mdx +0 -30
  562. package/templates/content/en/Partner/vercel.mdx +0 -28
  563. package/templates/content/en/PartnersList/partners.mdx +0 -22
  564. package/templates/content/en/Person/founder.mdx +0 -18
  565. package/templates/content/en/Pricing/pricing.mdx +0 -538
  566. package/templates/content/en/Pricing/pricing.schema.json +0 -209
  567. package/templates/content/en/PrivacyPage/privacy.mdx +0 -165
  568. package/templates/content/en/PrivacyPage/privacy.schema.json +0 -64
  569. package/templates/content/en/Product/ailk.mdx +0 -19
  570. package/templates/content/en/Product/ailk.schema.json +0 -94
  571. package/templates/content/en/Product/api-launch-kit.mdx +0 -26
  572. package/templates/content/en/Product/launch-kit.mdx +0 -28
  573. package/templates/content/en/ProductsList/products.mdx +0 -26
  574. package/templates/content/en/Project/ailk-phase-2.mdx +0 -54
  575. package/templates/content/en/ProjectsList/projects.mdx +0 -25
  576. package/templates/content/en/ResourceDetail/aeo-scoring-guide.mdx +0 -27
  577. package/templates/content/en/ResourceDetail/content-model-whitepaper.mdx +0 -28
  578. package/templates/content/en/ResourceDetail/schema-org-quick-reference.mdx +0 -39
  579. package/templates/content/en/ResourcesHub/resources.mdx +0 -22
  580. package/templates/content/en/Service/consulting.mdx +0 -22
  581. package/templates/content/en/Service/implementation.mdx +0 -30
  582. package/templates/content/en/Service/launch-service.mdx +0 -232
  583. package/templates/content/en/Service/launch.mdx +0 -228
  584. package/templates/content/en/ServicesList/services.mdx +0 -84
  585. package/templates/content/en/SoftwareProduct/ailk-aeo.mdx +0 -44
  586. package/templates/content/en/SoftwareProduct/ailk-content-adapters.mdx +0 -47
  587. package/templates/content/en/SoftwareProduct/ailk-schema.mdx +0 -49
  588. package/templates/content/en/TeamList/team.mdx +0 -63
  589. package/templates/content/en/TeamMember/founder.mdx +0 -72
  590. package/templates/content/en/TermsPage/terms.mdx +0 -19
  591. package/templates/content/en/TermsPage/terms.schema.json +0 -99
  592. package/templates/content/en/TestimonialsList/testimonials.mdx +0 -44
  593. package/templates/content/en/UseCasesList/use-cases.mdx +0 -30
  594. package/templates/content/en/Whitepaper/ax-first-product-development.mdx +0 -27
  595. package/templates/content/en/Whitepaper/open-core-commercial-strategy.mdx +0 -27
  596. package/templates/content/en/Whitepaper/structured-data-for-answer-engines.mdx +0 -31
  597. package/templates/content/en-XA/AboutPage/about.mdx +0 -8
  598. package/templates/content/en-XA/FAQPage/faq.mdx +0 -8
  599. package/templates/content/en-XA/HomePage/home.mdx +0 -8
  600. package/templates/content/es/_site.mdx +0 -30
  601. package/templates/content/fr/_site.mdx +0 -30
  602. package/templates/content/ja/_site.mdx +0 -30
  603. package/templates/content/pt-BR/AboutPage/about.mdx +0 -8
  604. package/templates/content/pt-BR/FAQPage/faq.mdx +0 -8
  605. package/templates/content/pt-BR/HomePage/home.mdx +0 -23
@@ -0,0 +1,392 @@
1
+ /**
2
+ * @file flows-listing-diagnostic.test.ts
3
+ * @description The shared scoring RULE (#5001 AC2/AC3, reworked by #5036 D4):
4
+ * a register built from one listing's own questions scores its weakest path
5
+ * `curious`, its strongest `now`, and a mixed path where the 0–12 total says
6
+ * it does — evaluated through the real `evaluateWaitlistScoring`, never a
7
+ * reimplementation of the ladder.
8
+ *
9
+ * The load-bearing test here is "a reordered register scores an answer
10
+ * identically": #5036 D4 moved resolution from option POSITION to option
11
+ * VALUE, so reordering a founder's Q3/Q4 options and rebuilding the register
12
+ * leaves an already-stored answer scoring exactly what it always meant. Under
13
+ * the old `q{n}:{i}of{k}` encoding that assertion was not even expressible.
14
+ *
15
+ * The register must also parse against `waitlistScoringSchema` (the grammar
16
+ * `upsertWaitlistExperiment` validates on every write) AND survive
17
+ * `waitlistExperimentConfigSchema`'s config-level cross-validation, which is
18
+ * the only place the "every `optionWeights` key must be a declared option of
19
+ * the question it names" rule fires
20
+ * (docs/lessons/2026-09-07-shared-register-needs-a-shared-value-universe.md).
21
+ * The projection's REAL config is asserted the same way in
22
+ * `apps/api/src/services/__tests__/project-listing-flow.test.ts`; the minimal
23
+ * wrapper below exists so a register that could never be persisted fails in
24
+ * this package rather than one layer away.
25
+ */
26
+
27
+ import {
28
+ evaluateWaitlistScoring,
29
+ projectListingQuestionsSchema,
30
+ waitlistExperimentConfigSchema,
31
+ waitlistScoringSchema,
32
+ type ProjectListingQuestions,
33
+ } from "@working-theory/validation";
34
+
35
+ import {
36
+ buildListingDiagnosticScoring,
37
+ listingDiagnosticOptionWeights,
38
+ listingOptionPoints,
39
+ LISTING_DIAGNOSTIC_Q5_WEIGHTS,
40
+ LISTING_DIAGNOSTIC_Q6_WEIGHTS,
41
+ } from "../flows/listing-diagnostic";
42
+
43
+ /**
44
+ * A listing's template instance: Q3/Q4 at their 4-option maximum with
45
+ * founder-authored values, Q5's three fixed urgency values, Q6's four fixed
46
+ * bands. Parsed through the real schema so the fixture cannot drift from the
47
+ * contract the projection reads.
48
+ */
49
+ const QUESTIONS: ProjectListingQuestions = projectListingQuestionsSchema.parse({
50
+ q2: {
51
+ prompt: "What brings you here?",
52
+ options: [
53
+ { value: "shipping-now", label: "I'm shipping something now" },
54
+ { value: "just-curious", label: "Just curious" },
55
+ ],
56
+ },
57
+ q3: {
58
+ prompt: "How painful is this problem today?",
59
+ options: [
60
+ { value: "none", label: "Not a problem" },
61
+ { value: "mild", label: "Mildly annoying" },
62
+ { value: "real", label: "A real drag" },
63
+ { value: "blocking", label: "It's blocking me" },
64
+ ],
65
+ },
66
+ q4: {
67
+ prompt: "How close is this to what you need?",
68
+ options: [
69
+ { value: "off", label: "Not what I need" },
70
+ { value: "adjacent", label: "Adjacent" },
71
+ { value: "close", label: "Close" },
72
+ { value: "exact", label: "Exactly it" },
73
+ ],
74
+ },
75
+ q5: {
76
+ prompt: "When would you use this?",
77
+ options: [
78
+ { value: "urgent", label: "Right away" },
79
+ { value: "planning", label: "Next quarter" },
80
+ { value: "exploring", label: "Just looking" },
81
+ ],
82
+ },
83
+ q6: {
84
+ prompt: "What would you pay?",
85
+ options: [
86
+ { value: "low", label: "Nothing yet" },
87
+ { value: "medium", label: "A little" },
88
+ { value: "high", label: "A real budget" },
89
+ { value: "critical", label: "Whatever it takes" },
90
+ ],
91
+ },
92
+ q7: { prompt: "Anything else we should know?" },
93
+ extras: [],
94
+ });
95
+
96
+ const SCORING = buildListingDiagnosticScoring(QUESTIONS);
97
+
98
+ /** The `optionWeights` map the register declares for one scored question. */
99
+ function weightsOf(
100
+ scoring: ReturnType<typeof buildListingDiagnosticScoring>,
101
+ question: string,
102
+ ): Record<string, number> {
103
+ const point = scoring.points.find((p) => p.question === question);
104
+ if (!point || point.kind !== "optionWeights") {
105
+ throw new Error(`no optionWeights point for "${question}"`);
106
+ }
107
+ return point.weights;
108
+ }
109
+
110
+ /**
111
+ * The minimal experiment config that carries a register — enough to fire
112
+ * `waitlistExperimentConfigSchema`'s weights-key cross-validation, and
113
+ * nothing more. The question set declares THIS listing's own options, which
114
+ * is precisely what value-keyed scoring makes possible.
115
+ */
116
+ function experimentConfigFor(questions: ProjectListingQuestions): unknown {
117
+ const scored = (["q3", "q4", "q5", "q6"] as const).map((id) => ({
118
+ id,
119
+ prompt: { en: questions[id].prompt },
120
+ answerType: "single" as const,
121
+ options: questions[id].options.map((o) => ({
122
+ value: o.value,
123
+ label: { en: o.label },
124
+ })),
125
+ }));
126
+
127
+ return {
128
+ key: "orbit-notes",
129
+ mode: "qualifying",
130
+ status: "live",
131
+ waitlists: [{ key: "listing_orbit_notes", flowSlug: "orbit-notes" }],
132
+ questionSet: {
133
+ version: 1,
134
+ questions: [
135
+ {
136
+ id: "email",
137
+ prompt: { en: "Email" },
138
+ answerType: "email",
139
+ mapsTo: "email",
140
+ },
141
+ ...scored,
142
+ ],
143
+ },
144
+ scoring: buildListingDiagnosticScoring(questions),
145
+ tracking: {
146
+ version: 1,
147
+ utm: { source: "utm_source", medium: "utm_medium", campaign: "utm_campaign" },
148
+ waitlists: { listing_orbit_notes: { campaign: "orbit-notes", channels: ["direct"] } },
149
+ },
150
+ stripeConfigRef: null,
151
+ };
152
+ }
153
+
154
+ describe("listingOptionPoints — the founder's declared order → 0..3", () => {
155
+ it("spans 0..3 whatever the arity, weakest first", () => {
156
+ expect([0, 1, 2].map((i) => listingOptionPoints(i, 3))).toEqual([0, 2, 3]);
157
+ expect([0, 1, 2, 3].map((i) => listingOptionPoints(i, 4))).toEqual([0, 1, 2, 3]);
158
+ });
159
+
160
+ it("yields 0 for a question with no spread to score", () => {
161
+ expect(listingOptionPoints(0, 1)).toBe(0);
162
+ });
163
+ });
164
+
165
+ describe("listingDiagnosticOptionWeights — keyed by value, weighted by order", () => {
166
+ it("keys the founder's own values and weights them by their declared order", () => {
167
+ expect(
168
+ listingDiagnosticOptionWeights([
169
+ { value: "none" },
170
+ { value: "mild" },
171
+ { value: "real" },
172
+ { value: "blocking" },
173
+ ]),
174
+ ).toEqual({ none: 0, mild: 1, real: 2, blocking: 3 });
175
+ });
176
+
177
+ it("still spans 0..3 at three options", () => {
178
+ expect(
179
+ listingDiagnosticOptionWeights([{ value: "x" }, { value: "y" }, { value: "z" }]),
180
+ ).toEqual({ x: 0, y: 2, z: 3 });
181
+ });
182
+ });
183
+
184
+ describe("the fixed-value questions", () => {
185
+ it("scores Q5 STRONGEST-first — `urgent` 3, `exploring` 0", () => {
186
+ // #4998 pins Q5's values urgency-descending, so the positional rule
187
+ // scored `urgent` 0 (docs/lessons/2026-09-07-q5-fixed-order-is-
188
+ // strongest-first.md). Value-keyed weights say what is meant instead.
189
+ expect(LISTING_DIAGNOSTIC_Q5_WEIGHTS).toEqual({
190
+ urgent: 3,
191
+ planning: 2,
192
+ exploring: 0,
193
+ });
194
+ });
195
+
196
+ it("keeps Q6 weakest-first — `low` 0, `critical` 3", () => {
197
+ expect(LISTING_DIAGNOSTIC_Q6_WEIGHTS).toEqual({
198
+ low: 0,
199
+ medium: 1,
200
+ high: 2,
201
+ critical: 3,
202
+ });
203
+ });
204
+
205
+ it("leaves the per-question spread at 0..3, so the total stays 0..12", () => {
206
+ for (const question of ["q3", "q4", "q5", "q6"]) {
207
+ const values = Object.values(weightsOf(SCORING, question));
208
+ expect(Math.min(...values)).toBe(0);
209
+ expect(Math.max(...values)).toBe(3);
210
+ }
211
+ });
212
+ });
213
+
214
+ describe("buildListingDiagnosticScoring — the register", () => {
215
+ it("parses against waitlistScoringSchema", () => {
216
+ expect(waitlistScoringSchema.safeParse(SCORING).success).toBe(true);
217
+ });
218
+
219
+ it("parses against waitlistExperimentConfigSchema at the config level", () => {
220
+ // The weights-key cross-validation only fires here — the question set
221
+ // declares this listing's OWN options, and the register keys on the same
222
+ // values, so no value universe is needed.
223
+ const parsed = waitlistExperimentConfigSchema.safeParse(experimentConfigFor(QUESTIONS));
224
+ expect(parsed.success).toBe(true);
225
+ });
226
+
227
+ it("scores Q3–Q6 only — Q1/Q2/Q7 and the extras carry no points", () => {
228
+ expect(SCORING.points.map((p) => p.question)).toEqual(["q3", "q4", "q5", "q6"]);
229
+ });
230
+
231
+ it("keys every weight on the founder's own option values, and nothing else", () => {
232
+ for (const question of ["q3", "q4", "q5", "q6"] as const) {
233
+ expect(Object.keys(weightsOf(SCORING, question)).sort()).toEqual(
234
+ QUESTIONS[question].options.map((o) => o.value).sort(),
235
+ );
236
+ }
237
+ });
238
+
239
+ it("gives two listings the same RULE — same shape, same tiers, same fixed weights", () => {
240
+ const other = buildListingDiagnosticScoring(
241
+ projectListingQuestionsSchema.parse({
242
+ ...QUESTIONS,
243
+ q3: {
244
+ prompt: "How badly does this hurt?",
245
+ options: [
246
+ { value: "x", label: "Not at all" },
247
+ { value: "y", label: "Somewhat" },
248
+ { value: "z", label: "Badly" },
249
+ ],
250
+ },
251
+ }),
252
+ );
253
+
254
+ expect(other.points.map((p) => p.name)).toEqual(SCORING.points.map((p) => p.name));
255
+ expect(other.scores).toEqual(SCORING.scores);
256
+ expect(other.tiers).toEqual(SCORING.tiers);
257
+ expect(other.default).toEqual(SCORING.default);
258
+ // Only the founder-authored questions' KEYS differ.
259
+ expect(weightsOf(other, "q5")).toEqual(weightsOf(SCORING, "q5"));
260
+ expect(weightsOf(other, "q6")).toEqual(weightsOf(SCORING, "q6"));
261
+ expect(weightsOf(other, "q3")).toEqual({ x: 0, y: 2, z: 3 });
262
+ });
263
+
264
+ // ── The three paths (#5001 AC2) ────────────────────────────────────────
265
+
266
+ it("scores the WEAKEST path 0 → curious", () => {
267
+ const result = evaluateWaitlistScoring(SCORING, {
268
+ q3: "none",
269
+ q4: "off",
270
+ q5: "exploring",
271
+ q6: "low",
272
+ });
273
+ expect(result.score).toBe(0);
274
+ expect(result.tier).toBe("curious");
275
+ });
276
+
277
+ it("scores the STRONGEST path 12 → now", () => {
278
+ const result = evaluateWaitlistScoring(SCORING, {
279
+ q3: "blocking",
280
+ q4: "exact",
281
+ q5: "urgent",
282
+ q6: "critical",
283
+ });
284
+ expect(result.score).toBe(12);
285
+ expect(result.tier).toBe("now");
286
+ });
287
+
288
+ it("scores a MIXED path by the sum of its answers", () => {
289
+ // q3 `real` 2 · q4 `adjacent` 1 · q5 `urgent` 3 · q6 `medium` 1 = 7 →
290
+ // soon (7–9).
291
+ const result = evaluateWaitlistScoring(SCORING, {
292
+ q3: "real",
293
+ q4: "adjacent",
294
+ q5: "urgent",
295
+ q6: "medium",
296
+ });
297
+ expect(result.score).toBe(7);
298
+ expect(result.tier).toBe("soon");
299
+ });
300
+
301
+ it.each([
302
+ [{ q3: "none", q4: "off", q5: "exploring", q6: "critical" }, 3, "curious"],
303
+ [{ q3: "mild", q4: "adjacent", q5: "exploring", q6: "high" }, 4, "later"],
304
+ [{ q3: "real", q4: "close", q5: "exploring", q6: "high" }, 6, "later"],
305
+ [{ q3: "blocking", q4: "exact", q5: "exploring", q6: "critical" }, 9, "soon"],
306
+ [{ q3: "blocking", q4: "exact", q5: "planning", q6: "critical" }, 11, "now"],
307
+ ])("puts total %j (%i) in the %s band", (answers, expectedScore, expectedTier) => {
308
+ const result = evaluateWaitlistScoring(SCORING, answers as Record<string, string>);
309
+ expect(result.score).toBe(expectedScore);
310
+ expect(result.tier).toBe(expectedTier);
311
+ });
312
+
313
+ it("is total — an unanswered diagnostic still lands on a tier", () => {
314
+ const result = evaluateWaitlistScoring(SCORING, {});
315
+ expect(result.score).toBe(0);
316
+ expect(result.tier).toBe("curious");
317
+ });
318
+
319
+ // ── #5036 D4: resolution by value, not position ────────────────────────
320
+
321
+ it("a reordered register scores an answer identically", () => {
322
+ // The founder swaps the two middle Q3 options and the two middle Q4
323
+ // options — a real edit to the register, which changes what those two
324
+ // options are worth. The ANSWERED options (`blocking`, the strongest Q3;
325
+ // `off`, the weakest Q4) keep the strength the founder declared, so the
326
+ // same stored answer must score exactly what it scored before.
327
+ //
328
+ // This test is not expressible under the old `q{n}:{i}of{k}` encoding:
329
+ // there was no founder value to answer with, and a stored `q3:3of4`
330
+ // token was a POSITION, so any reorder or insert silently repointed it at
331
+ // a different option. Resolving by value is what makes the answer survive
332
+ // an edit to the register.
333
+ const answers = { q3: "blocking", q4: "off", q5: "urgent", q6: "high" };
334
+ const before = evaluateWaitlistScoring(SCORING, answers);
335
+
336
+ const reordered = buildListingDiagnosticScoring(
337
+ projectListingQuestionsSchema.parse({
338
+ ...QUESTIONS,
339
+ q3: {
340
+ prompt: QUESTIONS.q3.prompt,
341
+ options: [
342
+ { value: "none", label: "Not a problem" },
343
+ { value: "real", label: "A real drag" },
344
+ { value: "mild", label: "Mildly annoying" },
345
+ { value: "blocking", label: "It's blocking me" },
346
+ ],
347
+ },
348
+ q4: {
349
+ prompt: QUESTIONS.q4.prompt,
350
+ options: [
351
+ { value: "off", label: "Not what I need" },
352
+ { value: "close", label: "Close" },
353
+ { value: "adjacent", label: "Adjacent" },
354
+ { value: "exact", label: "Exactly it" },
355
+ ],
356
+ },
357
+ }),
358
+ );
359
+ const after = evaluateWaitlistScoring(reordered, answers);
360
+
361
+ expect(before.score).toBe(8);
362
+ expect(after.score).toBe(before.score);
363
+ expect(after.tier).toBe(before.tier);
364
+
365
+ // The reorder is not a no-op: the two moved options really did trade
366
+ // weights, and the register the answer was resolved against is a
367
+ // different object than the one it was stored under.
368
+ expect(weightsOf(reordered, "q3")).not.toEqual(weightsOf(SCORING, "q3"));
369
+ expect(weightsOf(reordered, "q4")).not.toEqual(weightsOf(SCORING, "q4"));
370
+ expect(weightsOf(reordered, "q3")).toEqual({ none: 0, real: 1, mild: 2, blocking: 3 });
371
+ });
372
+
373
+ it("still parses at the config level after a reorder", () => {
374
+ // A reordered register's keys are still exactly the (reordered) options
375
+ // the question set declares, so the cross-validation holds.
376
+ const reordered = projectListingQuestionsSchema.parse({
377
+ ...QUESTIONS,
378
+ q3: {
379
+ prompt: QUESTIONS.q3.prompt,
380
+ options: [
381
+ { value: "none", label: "Not a problem" },
382
+ { value: "real", label: "A real drag" },
383
+ { value: "mild", label: "Mildly annoying" },
384
+ { value: "blocking", label: "It's blocking me" },
385
+ ],
386
+ },
387
+ });
388
+ expect(
389
+ waitlistExperimentConfigSchema.safeParse(experimentConfigFor(reordered)).success,
390
+ ).toBe(true);
391
+ });
392
+ });
@@ -0,0 +1,173 @@
1
+ /**
2
+ * @file flows-presets.test.ts
3
+ * @description Parse-validity for the four static capture presets (#3958
4
+ * §3.5; the `roadmap` preset added by #3963 D2). This is the AC1 + AC3
5
+ * verification: each preset must parse against
6
+ * `flowConfigSchema` and every user-facing string must be a
7
+ * `LocalizedString` carrying the flow's `defaultLocale` key. A bare-string
8
+ * literal is not assignable to `LocalizedString` at the type layer — caught
9
+ * by `pnpm type-check`'s `tsc -b`, via `database/tsconfig.flows.json`'s
10
+ * flows-directory TypeScript include (built in the `db:generate` -> `build`
11
+ * chain; the root `tsconfig.json` project-references
12
+ * `database/tsconfig.build.json`, not `database/tsconfig.json`, so
13
+ * `tsconfig.flows.json` is what actually puts `flows/presets.ts` on the
14
+ * whole-monorepo `tsc -b` graph). `database/tsconfig.json`'s own matching
15
+ * include entry (added alongside) serves a SEPARATE consumer —
16
+ * `eslint.config.ts`'s typed-lint project discovery, so this file isn't an
17
+ * unlinted "orphan" — and is rejected here at the schema layer as a
18
+ * belt-and-suspenders runtime check.
19
+ */
20
+
21
+ import { flowConfigSchema } from "@working-theory/validation";
22
+
23
+ import { FLOW_PRESETS } from "../flows/presets";
24
+
25
+ describe("FLOW_PRESETS", () => {
26
+ it("declares exactly the four static presets, by slug", () => {
27
+ expect(FLOW_PRESETS.map((p) => p.slug).sort()).toEqual([
28
+ "form",
29
+ "lead-magnet",
30
+ "roadmap",
31
+ "waitlist",
32
+ ]);
33
+ });
34
+
35
+ it.each(["form", "waitlist", "lead-magnet", "roadmap"])(
36
+ "the %s preset validates against flowConfigSchema",
37
+ (slug) => {
38
+ const preset = FLOW_PRESETS.find((p) => p.slug === slug);
39
+ expect(preset?.slug).toBe(slug);
40
+
41
+ const parsed = flowConfigSchema.safeParse(preset);
42
+ expect(parsed.success).toBe(true);
43
+ },
44
+ );
45
+
46
+ it("every preset declares defaultLocale: en with a key present on every LocalizedString", () => {
47
+ for (const preset of FLOW_PRESETS) {
48
+ expect(preset.defaultLocale).toBe("en");
49
+ // A parse failure here means some LocalizedString is missing the
50
+ // defaultLocale key (flowConfigSchema's superRefine) — re-assert with
51
+ // full issue detail for a failing preset's diagnosis.
52
+ const parsed = flowConfigSchema.safeParse(preset);
53
+ if (!parsed.success) {
54
+ throw new Error(
55
+ `${preset.slug} failed validation: ${JSON.stringify(parsed.error.issues, null, 2)}`,
56
+ );
57
+ }
58
+ }
59
+ });
60
+
61
+ it("the lead-magnet preset's qualifier step carries no segments band map (spec §7 item 4)", () => {
62
+ const preset = FLOW_PRESETS.find((p) => p.slug === "lead-magnet");
63
+ const qualifier = preset?.steps.find((s) => s.key === "qualifier");
64
+ expect(qualifier?.type).toBe("segmentation");
65
+ if (qualifier?.type === "segmentation") {
66
+ expect(qualifier.segments).toBeUndefined();
67
+ }
68
+ });
69
+
70
+ it("every preset declares exactly one field mapped to mapsTo: email — some steps collapse name+email into one 'contact' step, roadmap splits them (spec #3963 D2)", () => {
71
+ for (const preset of FLOW_PRESETS) {
72
+ const emailFields = preset.steps.flatMap((step) =>
73
+ step.type === "field"
74
+ ? step.fields.filter((f) => f.mapsTo === "email")
75
+ : [],
76
+ );
77
+ expect(emailFields).toHaveLength(1);
78
+ expect(emailFields[0]?.name).toBe("email");
79
+ }
80
+ });
81
+
82
+ it("no field step in any preset declares copy.cta — the FlowStepper default fallback (#4708) is what renders their submit control", () => {
83
+ // #4708's drift guard, the other half of which is
84
+ // packages/ui/src/sections/__tests__/FlowStepper.test.tsx's
85
+ // `roadmapShapedConfig()` / `noCtaConfig()` — packages/ui cannot import
86
+ // FLOW_PRESETS directly (database/flows is not a workspace package and
87
+ // packages/ui declares no dependency on it — a first-time cross-tier
88
+ // import architecture.yaml's boundary lint would refuse), so this
89
+ // in-package assertion is the closest mechanical proof that the shape
90
+ // the UI-side mirror configs cover is the shape these real presets
91
+ // actually carry. If a preset ever gains an explicit cta, this
92
+ // assertion needs updating — re-check the UI-side mirror still matches.
93
+ for (const preset of FLOW_PRESETS) {
94
+ for (const step of preset.steps) {
95
+ if (step.type !== "field") continue;
96
+ expect(step.copy.cta).toBeUndefined();
97
+ }
98
+ }
99
+ });
100
+
101
+ it("rejects a preset carrying a bare-string title instead of a LocalizedString", () => {
102
+ const mutated = {
103
+ ...FLOW_PRESETS[0],
104
+ steps: [
105
+ {
106
+ ...FLOW_PRESETS[0]!.steps[0]!,
107
+ copy: { ...FLOW_PRESETS[0]!.steps[0]!.copy, title: "Get in touch" },
108
+ },
109
+ ...FLOW_PRESETS[0]!.steps.slice(1),
110
+ ],
111
+ };
112
+
113
+ const parsed = flowConfigSchema.safeParse(mutated);
114
+ expect(parsed.success).toBe(false);
115
+ });
116
+ });
117
+
118
+ // ─── FLOW_PRESETS — roadmap preset (#3963 D2) ────────────────────────────────
119
+
120
+ describe("roadmap preset (#3963 D2)", () => {
121
+ const preset = FLOW_PRESETS.find((p) => p.slug === "roadmap");
122
+
123
+ it("exists and validates against flowConfigSchema", () => {
124
+ expect(preset?.slug).toBe("roadmap");
125
+ expect(flowConfigSchema.safeParse(preset).success).toBe(true);
126
+ });
127
+
128
+ it("declares a two-band segments map on its segmentation step", () => {
129
+ const stage = preset?.steps.find((s) => s.key === "stage");
130
+ expect(stage?.type).toBe("segmentation");
131
+ if (stage?.type === "segmentation") {
132
+ expect(stage.segments?.bands.map((b) => b.key).sort()).toEqual([
133
+ "early",
134
+ "scaling",
135
+ ]);
136
+ }
137
+ });
138
+
139
+ it("maps each band to a distinct thankYou slug (AC2 — two different segmentation answers derive different TY slugs)", () => {
140
+ expect(preset?.thankYou?.default).toBe("roadmap-thanks");
141
+ expect(preset?.thankYou?.bySegment).toEqual({
142
+ early: "roadmap-thanks",
143
+ scaling: "roadmap-thanks-qualified",
144
+ });
145
+ // The "scaling" (qualified) arm resolves to a DIFFERENT TY page than the
146
+ // "early" arm and the default — the divergence the AC requires.
147
+ expect(preset?.thankYou?.bySegment?.scaling).not.toBe(
148
+ preset?.thankYou?.default,
149
+ );
150
+ });
151
+
152
+ it("maps each band to a distinct deliverables assetKey (AC2 — different asset grants)", () => {
153
+ const assets = preset?.deliverables?.assets ?? [];
154
+ const early = assets.find((a) => a.segment === "early");
155
+ const scaling = assets.find((a) => a.segment === "scaling");
156
+ expect(early?.assetKey).toBe("roadmap-early");
157
+ expect(scaling?.assetKey).toBe("roadmap-scaling");
158
+ expect(early?.assetKey).not.toBe(scaling?.assetKey);
159
+ });
160
+
161
+ it("declares abandonEmail authored but dark (#3962 D2 — shipped off, no send)", () => {
162
+ expect(preset?.abandonEmail?.mode).toBe("dark");
163
+ expect(preset?.abandonEmail?.delayMinutes).toBeGreaterThanOrEqual(5);
164
+ });
165
+
166
+ it("collects no phone/address fields (the acquisition-capture's §5 over-collection is deliberately not reproduced)", () => {
167
+ const fieldNames = (preset?.steps ?? []).flatMap((step) =>
168
+ step.type === "field" ? step.fields.map((f) => f.name) : [],
169
+ );
170
+ expect(fieldNames).not.toContain("phone");
171
+ expect(fieldNames).not.toContain("address");
172
+ });
173
+ });