create-ailk 0.1.0 → 0.2.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.
Files changed (620) hide show
  1. package/README.md +124 -3
  2. package/component-catalog.md +456 -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 +12 -5
  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 +78 -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 +241 -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 +558 -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 +869 -0
  115. package/templates/apps/api/src/routes/project-listings/__tests__/me-route-precedence.test.ts +221 -0
  116. package/templates/apps/api/src/routes/project-listings/__tests__/me.test.ts +468 -0
  117. package/templates/apps/api/src/routes/project-listings/__tests__/public.test.ts +424 -0
  118. package/templates/apps/api/src/routes/project-listings/__tests__/site-answers.test.ts +707 -0
  119. package/templates/apps/api/src/routes/project-listings/__tests__/site-key.test.ts +375 -0
  120. package/templates/apps/api/src/routes/project-listings/__tests__/structured-address.test.ts +467 -0
  121. package/templates/apps/api/src/routes/project-listings/__tests__/tenant-isolation.test.ts +566 -0
  122. package/templates/apps/api/src/routes/project-listings/get.ts +70 -0
  123. package/templates/apps/api/src/routes/project-listings/index.ts +120 -0
  124. package/templates/apps/api/src/routes/project-listings/list.ts +47 -0
  125. package/templates/apps/api/src/routes/project-listings/me.ts +76 -0
  126. package/templates/apps/api/src/routes/project-listings/patch-copy.ts +222 -0
  127. package/templates/apps/api/src/routes/project-listings/patch-draft.ts +97 -0
  128. package/templates/apps/api/src/routes/project-listings/patch.ts +136 -0
  129. package/templates/apps/api/src/routes/project-listings/public.ts +52 -0
  130. package/templates/apps/api/src/routes/project-listings/respond.ts +37 -0
  131. package/templates/apps/api/src/routes/project-listings/resume-token.ts +22 -0
  132. package/templates/apps/api/src/routes/project-listings/start.ts +106 -0
  133. package/templates/apps/api/src/routes/project-listings/submit.ts +122 -0
  134. package/templates/apps/api/src/routes/schedule/__tests__/index.test.ts +490 -0
  135. package/templates/apps/api/src/routes/schedule/index.ts +249 -0
  136. package/templates/apps/api/src/routes/slack/__tests__/actions.test.ts +385 -0
  137. package/templates/apps/api/src/routes/slack/actions.ts +177 -0
  138. package/templates/apps/api/src/routes/slack/index.ts +38 -0
  139. package/templates/apps/api/src/routes/test/auth/__tests__/session.test.ts +16 -6
  140. package/templates/apps/api/src/routes/test/auth/session.ts +8 -6
  141. package/templates/apps/api/src/routes/waitlist-experiments/__tests__/tenant-isolation.test.ts +577 -0
  142. package/templates/apps/api/src/routes/waitlist-experiments/comparison.ts +78 -0
  143. package/templates/apps/api/src/routes/waitlist-experiments/index.ts +41 -0
  144. package/templates/apps/api/src/routes/waitlist-experiments/list.ts +64 -0
  145. package/templates/apps/api/src/routes/waitlist-experiments/respond.ts +42 -0
  146. package/templates/apps/api/src/routes/waitlist-experiments/signup.ts +84 -0
  147. package/templates/apps/api/src/routes/waitlist-experiments/signups.ts +119 -0
  148. package/templates/apps/api/src/routes/waitlist-signups/__tests__/index.test.ts +238 -0
  149. package/templates/apps/api/src/routes/waitlist-signups/index.ts +101 -0
  150. package/templates/apps/api/src/routes/webhooks/README.md +35 -12
  151. package/templates/apps/api/src/routes/webhooks/__tests__/stripe-flow-checkout.test.ts +315 -0
  152. package/templates/apps/api/src/routes/webhooks/__tests__/stripe-org-billing.test.ts +270 -0
  153. package/templates/apps/api/src/routes/webhooks/stripe.ts +248 -20
  154. package/templates/apps/api/src/routes/workspaces/__tests__/config-tenant-isolation.test.ts +356 -0
  155. package/templates/apps/api/src/routes/workspaces/__tests__/tenant-isolation.test.ts +671 -0
  156. package/templates/apps/api/src/routes/workspaces/config.ts +125 -0
  157. package/templates/apps/api/src/routes/workspaces/index.ts +194 -0
  158. package/templates/apps/api/src/routes/workspaces/sessions.ts +218 -0
  159. package/templates/apps/api/src/server.ts +307 -7
  160. package/templates/apps/api/src/services/__tests__/abandon-email.test.ts +489 -0
  161. package/templates/apps/api/src/services/__tests__/aeo-score.test.ts +475 -0
  162. package/templates/apps/api/src/services/__tests__/answer-distributions.test.ts +132 -0
  163. package/templates/apps/api/src/services/__tests__/consent-migration.test.ts +65 -0
  164. package/templates/apps/api/src/services/__tests__/consent.test.ts +282 -0
  165. package/templates/apps/api/src/services/__tests__/deliverable-fulfillment.test.ts +523 -0
  166. package/templates/apps/api/src/services/__tests__/deliverable-resend.test.ts +359 -0
  167. package/templates/apps/api/src/services/__tests__/entitlements.test.ts +142 -0
  168. package/templates/apps/api/src/services/__tests__/flow-aggregate.test.ts +425 -0
  169. package/templates/apps/api/src/services/__tests__/flow-engine.test.ts +2840 -0
  170. package/templates/apps/api/src/services/__tests__/lead-promotion.test.ts +393 -0
  171. package/templates/apps/api/src/services/__tests__/lead-routing.test.ts +8 -8
  172. package/templates/apps/api/src/services/__tests__/listing-csv-sweep.test.ts +560 -0
  173. package/templates/apps/api/src/services/__tests__/listing-promotion.test.ts +684 -0
  174. package/templates/apps/api/src/services/__tests__/playbook-compile.test.ts +406 -0
  175. package/templates/apps/api/src/services/__tests__/playbook-render.test.ts +290 -0
  176. package/templates/apps/api/src/services/__tests__/project-listing-decision.test.ts +736 -0
  177. package/templates/apps/api/src/services/__tests__/project-listing-flow.test.ts +475 -0
  178. package/templates/apps/api/src/services/__tests__/project-listing-issue.test.ts +340 -0
  179. package/templates/apps/api/src/services/__tests__/recommender-capture.test.ts +983 -0
  180. package/templates/apps/api/src/services/__tests__/slack-notify.test.ts +278 -0
  181. package/templates/apps/api/src/services/__tests__/tenant-context-cascade.test.ts +71 -0
  182. package/templates/apps/api/src/services/__tests__/tenant-context.test.ts +379 -0
  183. package/templates/apps/api/src/services/__tests__/usage-aggregation.test.ts +941 -0
  184. package/templates/apps/api/src/services/__tests__/usage-credits.test.ts +588 -0
  185. package/templates/apps/api/src/services/__tests__/usage-metering.test.ts +768 -0
  186. package/templates/apps/api/src/services/__tests__/waitlist-dashboard.test.ts +1314 -0
  187. package/templates/apps/api/src/services/__tests__/waitlist-experiments.test.ts +341 -0
  188. package/templates/apps/api/src/services/__tests__/waitlist-followup.test.ts +567 -0
  189. package/templates/apps/api/src/services/__tests__/waitlist-scoring.test.ts +474 -0
  190. package/templates/apps/api/src/services/__tests__/waitlist-signups.test.ts +354 -0
  191. package/templates/apps/api/src/services/abandon-email.ts +307 -0
  192. package/templates/apps/api/src/services/aeo-score.ts +288 -0
  193. package/templates/apps/api/src/services/answer-distributions.ts +138 -0
  194. package/templates/apps/api/src/services/consent.ts +236 -0
  195. package/templates/apps/api/src/services/deliverable-fulfillment.ts +523 -0
  196. package/templates/apps/api/src/services/entitlements.ts +102 -0
  197. package/templates/apps/api/src/services/flow-aggregate.ts +232 -0
  198. package/templates/apps/api/src/services/flow-checkouts.ts +123 -0
  199. package/templates/apps/api/src/services/flow-engine.ts +1296 -0
  200. package/templates/apps/api/src/services/lead-promotion.ts +176 -0
  201. package/templates/apps/api/src/services/lead-routing.ts +3 -3
  202. package/templates/apps/api/src/services/listing-config.ts +102 -0
  203. package/templates/apps/api/src/services/listing-csv-sweep.ts +455 -0
  204. package/templates/apps/api/src/services/listing-promotion.ts +342 -0
  205. package/templates/apps/api/src/services/playbook-compile.ts +398 -0
  206. package/templates/apps/api/src/services/playbook-render.ts +263 -0
  207. package/templates/apps/api/src/services/project-listing-decision.ts +510 -0
  208. package/templates/apps/api/src/services/project-listing-flow.ts +426 -0
  209. package/templates/apps/api/src/services/project-listing-issue.ts +251 -0
  210. package/templates/apps/api/src/services/project-listings.ts +1472 -0
  211. package/templates/apps/api/src/services/recommender-capture.ts +835 -0
  212. package/templates/apps/api/src/services/scheduling/cal-provider.ts +392 -0
  213. package/templates/apps/api/src/services/scheduling/index.ts +63 -0
  214. package/templates/apps/api/src/services/scheduling/types.ts +88 -0
  215. package/templates/apps/api/src/services/slack-notify.ts +240 -0
  216. package/templates/apps/api/src/services/tenant-context.ts +451 -0
  217. package/templates/apps/api/src/services/usage-aggregation.ts +516 -0
  218. package/templates/apps/api/src/services/usage-credits.ts +340 -0
  219. package/templates/apps/api/src/services/usage-metering.ts +699 -0
  220. package/templates/apps/api/src/services/waitlist-dashboard.ts +947 -0
  221. package/templates/apps/api/src/services/waitlist-experiments.ts +213 -0
  222. package/templates/apps/api/src/services/waitlist-followup.ts +486 -0
  223. package/templates/apps/api/src/services/waitlist-scoring.ts +166 -0
  224. package/templates/apps/api/src/services/waitlist-signups.ts +165 -0
  225. package/templates/apps/api/src/vercel-handler.ts +165 -0
  226. package/templates/apps/api/tsconfig.json +0 -12
  227. package/templates/apps/mcp/CLAUDE.md +19 -9
  228. package/templates/apps/mcp/LICENSE +201 -0
  229. package/templates/apps/mcp/__tests__/create_page.test.ts +1 -0
  230. package/templates/apps/mcp/__tests__/delete_page.test.ts +1 -0
  231. package/templates/apps/mcp/__tests__/module-exclusion.test.ts +105 -0
  232. package/templates/apps/mcp/__tests__/parity.test.ts +70 -6
  233. package/templates/apps/mcp/__tests__/request_callback.test.ts +4 -4
  234. package/templates/apps/mcp/__tests__/schedule_tools.test.ts +242 -0
  235. package/templates/apps/mcp/__tests__/submit_lead.test.ts +4 -4
  236. package/templates/apps/mcp/__tests__/subscribe_newsletter.test.ts +4 -4
  237. package/templates/apps/mcp/__tests__/tools/purchase.test.ts +47 -0
  238. package/templates/apps/mcp/__tests__/update_page.test.ts +1 -0
  239. package/templates/apps/mcp/package.json +6 -4
  240. package/templates/apps/mcp/src/config/modules.ts +37 -0
  241. package/templates/apps/mcp/src/server.ts +109 -18
  242. package/templates/apps/mcp/src/tools/capture_lead.ts +1 -1
  243. package/templates/apps/mcp/src/tools/create_booking.ts +58 -0
  244. package/templates/apps/mcp/src/tools/create_page.ts +1 -0
  245. package/templates/apps/mcp/src/tools/delete_page.ts +1 -0
  246. package/templates/apps/mcp/src/tools/get_event_meta.ts +55 -0
  247. package/templates/apps/mcp/src/tools/get_flow.ts +61 -0
  248. package/templates/apps/mcp/src/tools/index.ts +25 -0
  249. package/templates/apps/mcp/src/tools/list_availability.ts +67 -0
  250. package/templates/apps/mcp/src/tools/purchase.ts +3 -0
  251. package/templates/apps/mcp/src/tools/submit_flow_step.ts +83 -0
  252. package/templates/apps/mcp/src/tools/update_page.ts +1 -0
  253. package/templates/apps/mcp/tsconfig.json +1 -9
  254. package/templates/apps/web/.env.example +44 -0
  255. package/templates/apps/web/CLAUDE.md +28 -0
  256. package/templates/apps/web/LICENSE +201 -0
  257. package/templates/apps/web/__tests__/__mocks__/code-highlight.jest-stub.ts +24 -0
  258. package/templates/apps/web/__tests__/__mocks__/next-intl-server.ts +33 -0
  259. package/templates/apps/web/__tests__/__mocks__/next-intl.ts +18 -0
  260. package/templates/apps/web/__tests__/__mocks__/server-only.ts +13 -0
  261. package/templates/apps/web/__tests__/__mocks__/style-mock.js +4 -0
  262. package/templates/apps/web/__tests__/__mocks__/vercel-analytics-server.ts +9 -0
  263. package/templates/apps/web/__tests__/jest-support/import-meta-transformer.cjs +90 -0
  264. package/templates/apps/web/__tests__/setup.ts +22 -32
  265. package/templates/apps/web/app/.well-known/ai-plugin.json/route.ts +6 -3
  266. package/templates/apps/web/app/.well-known/mcp.json/build-manifest.ts +8 -2
  267. package/templates/apps/web/app/[locale]/(authed)/account/__tests__/page.test.tsx +260 -0
  268. package/templates/apps/web/app/[locale]/(authed)/account/page.tsx +263 -0
  269. package/templates/apps/web/app/[locale]/(authed)/account/sign-out-button.tsx +39 -0
  270. package/templates/apps/web/app/[locale]/(authed)/layout.tsx +54 -0
  271. package/templates/apps/web/app/[locale]/(authed)/sign-in-gate.tsx +96 -0
  272. package/templates/apps/web/app/[locale]/(authed)/waitlist/[experimentId]/page.tsx +309 -0
  273. package/templates/apps/web/app/[locale]/(authed)/waitlist/[experimentId]/signups/[leadId]/page.tsx +149 -0
  274. package/templates/apps/web/app/[locale]/(authed)/waitlist/__tests__/comparison.test.tsx +273 -0
  275. package/templates/apps/web/app/[locale]/(authed)/waitlist/__tests__/gate.test.tsx +172 -0
  276. package/templates/apps/web/app/[locale]/(authed)/waitlist/__tests__/list.test.tsx +171 -0
  277. package/templates/apps/web/app/[locale]/(authed)/waitlist/__tests__/signup.test.tsx +179 -0
  278. package/templates/apps/web/app/[locale]/(authed)/waitlist/components/ExperimentsTable.tsx +116 -0
  279. package/templates/apps/web/app/[locale]/(authed)/waitlist/components/ForbiddenState.tsx +24 -0
  280. package/templates/apps/web/app/[locale]/(authed)/waitlist/components/OfferFunnelTable.tsx +66 -0
  281. package/templates/apps/web/app/[locale]/(authed)/waitlist/components/RollupTicker.tsx +61 -0
  282. package/templates/apps/web/app/[locale]/(authed)/waitlist/components/ScoreTrace.tsx +114 -0
  283. package/templates/apps/web/app/[locale]/(authed)/waitlist/components/SignupsTable.tsx +139 -0
  284. package/templates/apps/web/app/[locale]/(authed)/waitlist/components/WaitlistComparisonTabs.tsx +85 -0
  285. package/templates/apps/web/app/[locale]/(authed)/waitlist/components/status-badges.tsx +67 -0
  286. package/templates/apps/web/app/[locale]/(authed)/waitlist/format.ts +111 -0
  287. package/templates/apps/web/app/[locale]/(authed)/waitlist/page.tsx +179 -0
  288. package/templates/apps/web/app/[locale]/about/page.tsx +10 -1
  289. package/templates/apps/web/app/[locale]/blog/[[...slug]]/page.tsx +6 -1
  290. package/templates/apps/web/app/[locale]/careers/[slug]/page.tsx +9 -0
  291. package/templates/apps/web/app/[locale]/careers/__tests__/page-renderer-migration.test.tsx +3 -3
  292. package/templates/apps/web/app/[locale]/careers/page.tsx +9 -1
  293. package/templates/apps/web/app/[locale]/case-studies/[slug]/page.tsx +9 -0
  294. package/templates/apps/web/app/[locale]/case-studies/__tests__/page-renderer-migration.test.tsx +3 -3
  295. package/templates/apps/web/app/[locale]/case-studies/page.tsx +13 -1
  296. package/templates/apps/web/app/[locale]/clients/__tests__/page-renderer-migration.test.tsx +11 -1
  297. package/templates/apps/web/app/[locale]/clients/page.tsx +9 -1
  298. package/templates/apps/web/app/[locale]/contact/ContactForm.tsx +21 -1
  299. package/templates/apps/web/app/[locale]/contact/ConversationalIntakeWrapper.tsx +188 -0
  300. package/templates/apps/web/app/[locale]/contact/__tests__/ConversationalIntakeWrapper.test.tsx +242 -0
  301. package/templates/apps/web/app/[locale]/contact/__tests__/contact.actions.test.ts +29 -0
  302. package/templates/apps/web/app/[locale]/contact/__tests__/intake-analytics.test.ts +51 -0
  303. package/templates/apps/web/app/[locale]/contact/__tests__/page-renderer-migration.test.tsx +39 -15
  304. package/templates/apps/web/app/[locale]/contact/contact.actions.ts +21 -3
  305. package/templates/apps/web/app/[locale]/contact/intake-analytics.ts +49 -0
  306. package/templates/apps/web/app/[locale]/contact/intake-script.ts +66 -0
  307. package/templates/apps/web/app/[locale]/contact/page.tsx +14 -4
  308. package/templates/apps/web/app/[locale]/courses/[slug]/page.tsx +13 -1
  309. package/templates/apps/web/app/[locale]/courses/__tests__/page-renderer-migration.test.tsx +3 -3
  310. package/templates/apps/web/app/[locale]/courses/page.tsx +9 -1
  311. package/templates/apps/web/app/[locale]/docs/[[...slug]]/page.tsx +1 -0
  312. package/templates/apps/web/app/[locale]/downloads/__tests__/page-renderer-migration.test.tsx +11 -1
  313. package/templates/apps/web/app/[locale]/downloads/page.tsx +13 -1
  314. package/templates/apps/web/app/[locale]/events/[slug]/page.tsx +9 -0
  315. package/templates/apps/web/app/[locale]/events/__tests__/page-renderer-migration.test.tsx +12 -2
  316. package/templates/apps/web/app/[locale]/events/page.tsx +9 -1
  317. package/templates/apps/web/app/[locale]/faq/page.tsx +10 -1
  318. package/templates/apps/web/app/[locale]/flows/[slug]/FlowStepperClient.tsx +265 -0
  319. package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/FlowStepperClient.permalink.test.tsx +445 -0
  320. package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/FlowStepperClient.test.tsx +341 -0
  321. package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/checkout.actions.test.ts +287 -0
  322. package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/flow.actions.test.ts +302 -0
  323. package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/two-route-tool.test.tsx +128 -0
  324. package/templates/apps/web/app/[locale]/flows/[slug]/checkout.actions.ts +254 -0
  325. package/templates/apps/web/app/[locale]/flows/[slug]/flow.actions.ts +144 -0
  326. package/templates/apps/web/app/[locale]/flows/[slug]/page.tsx +108 -0
  327. package/templates/apps/web/app/[locale]/flows/[slug]/results/[payload]/ResultsClient.tsx +532 -0
  328. package/templates/apps/web/app/[locale]/flows/[slug]/results/[payload]/ResultsEmailStep.tsx +121 -0
  329. package/templates/apps/web/app/[locale]/flows/[slug]/results/[payload]/__tests__/ResultsClient.checkout.test.tsx +457 -0
  330. package/templates/apps/web/app/[locale]/flows/[slug]/results/[payload]/__tests__/page.test.tsx +396 -0
  331. package/templates/apps/web/app/[locale]/flows/[slug]/results/[payload]/page.tsx +114 -0
  332. package/templates/apps/web/app/[locale]/flows/[slug]/start/page.tsx +52 -0
  333. package/templates/apps/web/app/[locale]/how-to/[slug]/page.tsx +13 -1
  334. package/templates/apps/web/app/[locale]/how-to/__tests__/page-renderer-migration.test.tsx +1 -1
  335. package/templates/apps/web/app/[locale]/integrations/[slug]/page.tsx +9 -0
  336. package/templates/apps/web/app/[locale]/integrations/__tests__/page-renderer-migration.test.tsx +3 -3
  337. package/templates/apps/web/app/[locale]/integrations/page.tsx +13 -1
  338. package/templates/apps/web/app/[locale]/launch-service/page.tsx +14 -1
  339. package/templates/apps/web/app/[locale]/layout.tsx +127 -16
  340. package/templates/apps/web/app/[locale]/page.tsx +10 -1
  341. package/templates/apps/web/app/[locale]/partners/[slug]/page.tsx +9 -0
  342. package/templates/apps/web/app/[locale]/partners/__tests__/page-renderer-migration.test.tsx +3 -3
  343. package/templates/apps/web/app/[locale]/partners/page.tsx +9 -1
  344. package/templates/apps/web/app/[locale]/products/[slug]/page.tsx +9 -0
  345. package/templates/apps/web/app/[locale]/products/__tests__/page-renderer-migration.test.tsx +3 -3
  346. package/templates/apps/web/app/[locale]/products/page.tsx +9 -1
  347. package/templates/apps/web/app/[locale]/projects/[slug]/page.tsx +13 -1
  348. package/templates/apps/web/app/[locale]/projects/__tests__/page-renderer-migration.test.tsx +10 -0
  349. package/templates/apps/web/app/[locale]/projects/apply/ListingApplicationClient.tsx +110 -0
  350. package/templates/apps/web/app/[locale]/projects/apply/__tests__/ListingApplicationClient.test.tsx +96 -0
  351. package/templates/apps/web/app/[locale]/projects/page.tsx +13 -1
  352. package/templates/apps/web/app/[locale]/resources/[slug]/page.tsx +9 -0
  353. package/templates/apps/web/app/[locale]/resources/__tests__/page-renderer-migration.test.tsx +12 -2
  354. package/templates/apps/web/app/[locale]/resources/page.tsx +13 -1
  355. package/templates/apps/web/app/[locale]/schedule/__tests__/page.test.tsx +117 -0
  356. package/templates/apps/web/app/[locale]/schedule/confirmed/__tests__/page.test.tsx +96 -0
  357. package/templates/apps/web/app/[locale]/schedule/confirmed/page.tsx +92 -0
  358. package/templates/apps/web/app/[locale]/schedule/page.tsx +82 -0
  359. package/templates/apps/web/app/[locale]/services/[slug]/page.tsx +9 -0
  360. package/templates/apps/web/app/[locale]/services/__tests__/page-renderer-migration.test.tsx +3 -3
  361. package/templates/apps/web/app/[locale]/services/page.tsx +9 -1
  362. package/templates/apps/web/app/[locale]/sign-in/__tests__/next-path.test.ts +31 -0
  363. package/templates/apps/web/app/[locale]/sign-in/__tests__/page.test.tsx +152 -0
  364. package/templates/apps/web/app/[locale]/sign-in/next-path.ts +0 -0
  365. package/templates/apps/web/app/[locale]/sign-in/page.tsx +83 -0
  366. package/templates/apps/web/app/[locale]/sign-in/sign-in-card.tsx +123 -0
  367. package/templates/apps/web/app/[locale]/software/[slug]/page.tsx +13 -1
  368. package/templates/apps/web/app/[locale]/software/__tests__/page-renderer-migration.test.tsx +1 -1
  369. package/templates/apps/web/app/[locale]/tech-articles/[slug]/page.tsx +13 -1
  370. package/templates/apps/web/app/[locale]/tech-articles/__tests__/page-renderer-migration.test.tsx +1 -1
  371. package/templates/apps/web/app/[locale]/testimonials/__tests__/page-renderer-migration.test.tsx +11 -1
  372. package/templates/apps/web/app/[locale]/testimonials/page.tsx +13 -1
  373. package/templates/apps/web/app/[locale]/thanks/[slug]/__tests__/page.test.tsx +93 -0
  374. package/templates/apps/web/app/[locale]/thanks/[slug]/page.tsx +48 -0
  375. package/templates/apps/web/app/[locale]/use-cases/__tests__/page-renderer-migration.test.tsx +11 -1
  376. package/templates/apps/web/app/[locale]/use-cases/page.tsx +13 -1
  377. package/templates/apps/web/app/[locale]/whitepapers/[slug]/page.tsx +13 -1
  378. package/templates/apps/web/app/[locale]/whitepapers/__tests__/page-renderer-migration.test.tsx +1 -1
  379. package/templates/apps/web/app/globals.css +35 -0
  380. package/templates/apps/web/app/llms.txt/route.ts +2 -1
  381. package/templates/apps/web/app/og/route.tsx +10 -2
  382. package/templates/apps/web/auth.ts +8 -0
  383. package/templates/apps/web/components/sign-out-trigger.tsx +42 -0
  384. package/templates/apps/web/content/en/blog/README.txt +25 -0
  385. package/templates/apps/web/content/en/docs/index.mdx +28 -0
  386. package/templates/apps/web/jest.config.cjs +90 -27
  387. package/templates/apps/web/lib/__tests__/site-brand.test.ts +119 -0
  388. package/templates/apps/web/lib/__tests__/site-theme.test.ts +112 -0
  389. package/templates/apps/web/lib/__tests__/structured-data.test.tsx +499 -0
  390. package/templates/apps/web/lib/account-config.ts +13 -0
  391. package/templates/apps/web/lib/server-api.ts +85 -0
  392. package/templates/apps/web/lib/site-brand.tsx +121 -0
  393. package/templates/apps/web/lib/site-theme.ts +74 -0
  394. package/templates/apps/web/messages/en.json +275 -7
  395. package/templates/apps/web/middleware.ts +11 -3
  396. package/templates/apps/web/next.config.mjs +8 -2
  397. package/templates/apps/web/package.json +17 -7
  398. package/templates/apps/web/postcss.config.mjs +23 -0
  399. package/templates/apps/web/public/.well-known/security.txt +3 -3
  400. package/templates/apps/web/src/lib/routes.ts +15 -2
  401. package/templates/apps/web/tsconfig.json +1 -16
  402. package/templates/content/_site.mdx +36 -25
  403. package/templates/content/en/AboutPage/about.mdx +24 -14
  404. package/templates/content/en/ContactPage/contact.mdx +23 -14
  405. package/templates/content/en/FAQPage/faq.mdx +10 -392
  406. package/templates/content/en/HomePage/home.mdx +14 -285
  407. package/templates/database/CHANGELOG.md +443 -0
  408. package/templates/database/LICENSE +201 -0
  409. package/templates/database/README.md +3 -1
  410. package/templates/database/__tests__/flows-listing-diagnostic.test.ts +392 -0
  411. package/templates/database/__tests__/flows-presets.test.ts +173 -0
  412. package/templates/database/__tests__/prefixed-ids.db.test.ts +146 -0
  413. package/templates/database/__tests__/seed.test.ts +88 -0
  414. package/templates/database/__tests__/user-cascade.test.ts +19 -4
  415. package/templates/database/auth/schema.prisma +521 -0
  416. package/templates/database/config/schema.prisma +234 -0
  417. package/templates/database/content/schema.prisma +263 -0
  418. package/templates/database/flows/listing-diagnostic.ts +182 -0
  419. package/templates/database/flows/presets.ts +315 -0
  420. package/templates/database/inbox/__tests__/schema.test.ts +4 -0
  421. package/templates/database/inbox/schema.prisma +574 -3
  422. package/templates/database/jest.config.cjs +8 -0
  423. package/templates/database/migrations/20260712215647_add_multi_tenant_tenancy/migration.sql +201 -0
  424. package/templates/database/migrations/20260713143320_reanchor_site_config_to_workspace/migration.sql +67 -0
  425. package/templates/database/migrations/20260713174000_add_audit_logs/migration.sql +46 -0
  426. package/templates/database/migrations/20260713190000_drop_telemetry_records/migration.sql +25 -0
  427. package/templates/database/migrations/20260713192000_add_uuidv7_function/migration.sql +49 -0
  428. package/templates/database/migrations/20260713192429_prefixed_uuid_entity_ids/migration.sql +197 -0
  429. package/templates/database/migrations/20260713200000_add_org_billing/migration.sql +32 -0
  430. package/templates/database/migrations/20260714000000_add_lead_promotion_fields/migration.sql +22 -0
  431. package/templates/database/migrations/20260714010000_add_usage_billing_content_models/migration.sql +151 -0
  432. package/templates/database/migrations/20260714020000_add_usage_billing_auth_config_models/migration.sql +223 -0
  433. package/templates/database/migrations/20260714030000_credit_balance_non_negative/migration.sql +22 -0
  434. package/templates/database/migrations/20260714030000_widen_usage_billing_report_aggregates/migration.sql +32 -0
  435. package/templates/database/migrations/20260810233516_add_flow_engine_models/migration.sql +40 -0
  436. package/templates/database/migrations/20260811020000_add_flow_session_abandon_email_columns/migration.sql +4 -0
  437. package/templates/database/migrations/20260811030000_add_flow_session_abandon_email_index/migration.sql +2 -0
  438. package/templates/database/migrations/20260811040000_add_flow_session_segment/migration.sql +2 -0
  439. package/templates/database/migrations/20260811050000_add_deliverable_models/migration.sql +29 -0
  440. package/templates/database/migrations/20260812000000_add_deliverable_grant_send_columns/migration.sql +6 -0
  441. package/templates/database/migrations/20260902222948_add_waitlist_experiment_models/migration.sql +39 -0
  442. package/templates/database/migrations/20260902224500_add_waitlist_id_format_checks/migration.sql +24 -0
  443. package/templates/database/migrations/20260903004112_add_flow_session_flags_aeo_score/migration.sql +3 -0
  444. package/templates/database/migrations/20260903010000_add_playbook_instance/migration.sql +33 -0
  445. package/templates/database/migrations/20260904000000_add_waitlist_followup_send/migration.sql +30 -0
  446. package/templates/database/migrations/20260905230000_add_flow_checkout/migration.sql +47 -0
  447. package/templates/database/migrations/20260906000000_add_flow_checkout_idempotency/migration.sql +16 -0
  448. package/templates/database/migrations/20260907120000_add_project_listing/migration.sql +47 -0
  449. package/templates/database/migrations/20260907130000_add_project_listing_slack_columns/migration.sql +10 -0
  450. package/templates/database/migrations/20260907180000_add_listing_csv_send/migration.sql +26 -0
  451. package/templates/database/migrations/20260909120000_project_listings_platform/migration.sql +90 -0
  452. package/templates/database/migrations/20260909180000_listing_draft_and_fields/migration.sql +174 -0
  453. package/templates/database/migrations/20260910120000_listing_public_site_key/migration.sql +14 -0
  454. package/templates/database/migrations/20260911120000_listing_copy_edit_audit/migration.sql +25 -0
  455. package/templates/database/migrations/20260911140000_listing_structured_address/migration.sql +32 -0
  456. package/templates/database/migrations/20260911180000_consent_grants/migration.sql +71 -0
  457. package/templates/database/migrations/20260911200000_listing_site_answers/migration.sql +30 -0
  458. package/templates/database/migrations/20260912120000_listing_owner_link/migration.sql +49 -0
  459. package/templates/database/ops/schema.prisma +27 -0
  460. package/templates/database/package.json +42 -4
  461. package/templates/database/prisma.config.ts +1 -1
  462. package/templates/database/schema.prisma +19 -435
  463. package/templates/database/seed-test.ts +14 -4
  464. package/templates/database/seed.ts +148 -0
  465. package/templates/database/tenancy.ts +45 -0
  466. package/templates/database/tsconfig.flows.json +15 -0
  467. package/templates/database/tsconfig.json +7 -1
  468. package/templates/database/tsconfig.tenancy.json +14 -0
  469. package/templates/package.json +30 -55
  470. package/templates/pnpm-workspace.yaml +0 -3
  471. package/templates/project.yaml +1 -1
  472. package/templates/tsconfig.json +1 -37
  473. package/templates/apps/web/__tests__/auth/adversarial/cookie-attributes.test.ts +0 -94
  474. package/templates/apps/web/__tests__/auth/adversarial/oauth-state-parameter.test.ts +0 -75
  475. package/templates/apps/web/__tests__/auth/adversarial/redirect-uri-allowlist.test.ts +0 -82
  476. package/templates/apps/web/__tests__/auth/env-helpers.test.ts +0 -168
  477. package/templates/apps/web/__tests__/authority-list-routes.test.tsx +0 -208
  478. package/templates/apps/web/__tests__/better-stack.test.ts +0 -66
  479. package/templates/apps/web/__tests__/blog-dir-resolution.test.ts +0 -87
  480. package/templates/apps/web/__tests__/blog-source-populated.test.ts +0 -130
  481. package/templates/apps/web/__tests__/data-testid-coverage.test.tsx +0 -60
  482. package/templates/apps/web/__tests__/google-analytics.test.tsx +0 -104
  483. package/templates/apps/web/__tests__/i18n/middleware.test.ts +0 -119
  484. package/templates/apps/web/__tests__/i18n/routing.test.ts +0 -114
  485. package/templates/apps/web/__tests__/i18n/translations.test.ts +0 -191
  486. package/templates/apps/web/__tests__/json-ld.test.ts +0 -98
  487. package/templates/apps/web/__tests__/knowledge-events-routes.test.tsx +0 -580
  488. package/templates/apps/web/__tests__/layout-stealth.test.ts +0 -39
  489. package/templates/apps/web/__tests__/lighthouse-fixtures/improvement-image-shrink.tsx +0 -103
  490. package/templates/apps/web/__tests__/lighthouse-fixtures/override-fixture.md +0 -114
  491. package/templates/apps/web/__tests__/lighthouse-fixtures/regression-bloat.tsx +0 -94
  492. package/templates/apps/web/__tests__/page.test.tsx +0 -230
  493. package/templates/apps/web/__tests__/products-services-phase1.test.tsx +0 -321
  494. package/templates/apps/web/__tests__/projects-routes.test.tsx +0 -392
  495. package/templates/apps/web/__tests__/resources-downloads-integrations-phase2.test.tsx +0 -475
  496. package/templates/apps/web/__tests__/routes/ai-plugin-json.test.ts +0 -53
  497. package/templates/apps/web/__tests__/routes/blog-route.test.tsx +0 -191
  498. package/templates/apps/web/__tests__/routes/llms-txt.test.ts +0 -123
  499. package/templates/apps/web/__tests__/routes/robots-txt.test.ts +0 -52
  500. package/templates/apps/web/__tests__/routes/sitemap-xml.test.ts +0 -78
  501. package/templates/apps/web/__tests__/routes/well-known-mcp.test.ts +0 -203
  502. package/templates/apps/web/__tests__/section-routes.test.tsx +0 -693
  503. package/templates/apps/web/__tests__/security-headers.test.ts +0 -201
  504. package/templates/apps/web/__tests__/sentry-config.test.ts +0 -116
  505. package/templates/apps/web/__tests__/sitemap-locales.test.ts +0 -415
  506. package/templates/apps/web/__tests__/standalone-informational-routes.test.tsx +0 -844
  507. package/templates/apps/web/app/[locale]/__tests__/layout.test.tsx +0 -120
  508. package/templates/apps/web/app/[locale]/__tests__/page-renderer-migration.test.tsx +0 -202
  509. package/templates/apps/web/app/__tests__/error-boundaries.test.tsx +0 -152
  510. package/templates/apps/web/content/en/blog/index.mdx +0 -15
  511. package/templates/apps/web/content/en/blog/launching-ailk.mdx +0 -20
  512. package/templates/apps/web/content/en/docs/getting-started.mdx +0 -30
  513. package/templates/content/.gitkeep +0 -0
  514. package/templates/content/CLAUDE.md +0 -85
  515. package/templates/content/ar/HomePage/home.mdx +0 -123
  516. package/templates/content/ar/_site.mdx +0 -35
  517. package/templates/content/de/_site.mdx +0 -29
  518. package/templates/content/en/.gitkeep +0 -0
  519. package/templates/content/en/AboutPage/about.schema.json +0 -82
  520. package/templates/content/en/BookCall/book-call.mdx +0 -19
  521. package/templates/content/en/BookCall/book-call.schema.json +0 -121
  522. package/templates/content/en/BreadcrumbList/site-breadcrumbs.mdx +0 -20
  523. package/templates/content/en/CareersList/careers.mdx +0 -24
  524. package/templates/content/en/CaseStudiesList/case-studies.mdx +0 -22
  525. package/templates/content/en/CaseStudiesList/showcase.mdx +0 -8
  526. package/templates/content/en/CaseStudy/ailk-aeo-rollout.mdx +0 -32
  527. package/templates/content/en/CaseStudy/schema-builder-migration.mdx +0 -41
  528. package/templates/content/en/ClientsList/clients.mdx +0 -38
  529. package/templates/content/en/ContactPage/contact.schema.json +0 -100
  530. package/templates/content/en/ContactPoint/primary.mdx +0 -14
  531. package/templates/content/en/Course/aeo-fundamentals.mdx +0 -37
  532. package/templates/content/en/Course/schema-builder-workshop.mdx +0 -62
  533. package/templates/content/en/CoursesList/courses.mdx +0 -22
  534. package/templates/content/en/DownloadsList/downloads.mdx +0 -22
  535. package/templates/content/en/Event/aeo-office-hours-may-2026.mdx +0 -39
  536. package/templates/content/en/Event/ailk-launch-day-2026.mdx +0 -35
  537. package/templates/content/en/EventsList/events.mdx +0 -26
  538. package/templates/content/en/FAQPage/faq.schema.json +0 -231
  539. package/templates/content/en/GlossaryList/glossary.mdx +0 -19
  540. package/templates/content/en/GlossaryList/glossary.schema.json +0 -300
  541. package/templates/content/en/HomePage/home.schema.json +0 -88
  542. package/templates/content/en/HomePage/launch-service.mdx +0 -475
  543. package/templates/content/en/HomePage/launch-service.schema.json +0 -138
  544. package/templates/content/en/HowTo/add-mdx-content.mdx +0 -38
  545. package/templates/content/en/HowTo/configure-aeo-scoring.mdx +0 -35
  546. package/templates/content/en/HowTo/set-up-json-ld.mdx +0 -36
  547. package/templates/content/en/Integration/github-actions.mdx +0 -31
  548. package/templates/content/en/Integration/openai-api.mdx +0 -26
  549. package/templates/content/en/Integration/vercel.mdx +0 -32
  550. package/templates/content/en/IntegrationsList/integrations.mdx +0 -22
  551. package/templates/content/en/JobPosting/content-strategist.mdx +0 -46
  552. package/templates/content/en/JobPosting/developer-advocate.mdx +0 -47
  553. package/templates/content/en/JobPosting/senior-fullstack-engineer.mdx +0 -57
  554. package/templates/content/en/LandingPage/aeo-audit.mdx +0 -19
  555. package/templates/content/en/LandingPage/aeo-audit.schema.json +0 -92
  556. package/templates/content/en/LandingPage/agency-waitlist.mdx +0 -340
  557. package/templates/content/en/LandingPage/agency-waitlist.schema.json +0 -152
  558. package/templates/content/en/LandingPage/developers.mdx +0 -19
  559. package/templates/content/en/LandingPage/developers.schema.json +0 -109
  560. package/templates/content/en/LandingPage/for-agencies.mdx +0 -390
  561. package/templates/content/en/LandingPage/for-agencies.schema.json +0 -107
  562. package/templates/content/en/LandingPage/for-ai-search-agencies.mdx +0 -417
  563. package/templates/content/en/LandingPage/for-ai-search-agencies.schema.json +0 -105
  564. package/templates/content/en/LandingPage/for-businesses.mdx +0 -19
  565. package/templates/content/en/LandingPage/for-businesses.schema.json +0 -96
  566. package/templates/content/en/LandingPage/platform.mdx +0 -369
  567. package/templates/content/en/LandingPage/platform.schema.json +0 -166
  568. package/templates/content/en/LandingPage/vs-ai-website-builders.mdx +0 -19
  569. package/templates/content/en/LandingPage/vs-ai-website-builders.schema.json +0 -102
  570. package/templates/content/en/LandingPage/vs-wordpress.mdx +0 -19
  571. package/templates/content/en/LandingPage/vs-wordpress.schema.json +0 -53
  572. package/templates/content/en/NewsletterSignup/newsletter.mdx +0 -236
  573. package/templates/content/en/NewsletterSignup/newsletter.schema.json +0 -91
  574. package/templates/content/en/Organization/site-owner.mdx +0 -16
  575. package/templates/content/en/Partner/prisma.mdx +0 -31
  576. package/templates/content/en/Partner/supabase.mdx +0 -30
  577. package/templates/content/en/Partner/vercel.mdx +0 -28
  578. package/templates/content/en/PartnersList/partners.mdx +0 -22
  579. package/templates/content/en/Person/founder.mdx +0 -18
  580. package/templates/content/en/Pricing/pricing.mdx +0 -538
  581. package/templates/content/en/Pricing/pricing.schema.json +0 -209
  582. package/templates/content/en/PrivacyPage/privacy.mdx +0 -165
  583. package/templates/content/en/PrivacyPage/privacy.schema.json +0 -64
  584. package/templates/content/en/Product/ailk.mdx +0 -19
  585. package/templates/content/en/Product/ailk.schema.json +0 -94
  586. package/templates/content/en/Product/api-launch-kit.mdx +0 -26
  587. package/templates/content/en/Product/launch-kit.mdx +0 -28
  588. package/templates/content/en/ProductsList/products.mdx +0 -26
  589. package/templates/content/en/Project/ailk-phase-2.mdx +0 -54
  590. package/templates/content/en/ProjectsList/projects.mdx +0 -25
  591. package/templates/content/en/ResourceDetail/aeo-scoring-guide.mdx +0 -27
  592. package/templates/content/en/ResourceDetail/content-model-whitepaper.mdx +0 -28
  593. package/templates/content/en/ResourceDetail/schema-org-quick-reference.mdx +0 -39
  594. package/templates/content/en/ResourcesHub/resources.mdx +0 -22
  595. package/templates/content/en/Service/consulting.mdx +0 -22
  596. package/templates/content/en/Service/implementation.mdx +0 -30
  597. package/templates/content/en/Service/launch-service.mdx +0 -232
  598. package/templates/content/en/Service/launch.mdx +0 -228
  599. package/templates/content/en/ServicesList/services.mdx +0 -84
  600. package/templates/content/en/SoftwareProduct/ailk-aeo.mdx +0 -44
  601. package/templates/content/en/SoftwareProduct/ailk-content-adapters.mdx +0 -47
  602. package/templates/content/en/SoftwareProduct/ailk-schema.mdx +0 -49
  603. package/templates/content/en/TeamList/team.mdx +0 -63
  604. package/templates/content/en/TeamMember/founder.mdx +0 -72
  605. package/templates/content/en/TermsPage/terms.mdx +0 -19
  606. package/templates/content/en/TermsPage/terms.schema.json +0 -99
  607. package/templates/content/en/TestimonialsList/testimonials.mdx +0 -44
  608. package/templates/content/en/UseCasesList/use-cases.mdx +0 -30
  609. package/templates/content/en/Whitepaper/ax-first-product-development.mdx +0 -27
  610. package/templates/content/en/Whitepaper/open-core-commercial-strategy.mdx +0 -27
  611. package/templates/content/en/Whitepaper/structured-data-for-answer-engines.mdx +0 -31
  612. package/templates/content/en-XA/AboutPage/about.mdx +0 -8
  613. package/templates/content/en-XA/FAQPage/faq.mdx +0 -8
  614. package/templates/content/en-XA/HomePage/home.mdx +0 -8
  615. package/templates/content/es/_site.mdx +0 -30
  616. package/templates/content/fr/_site.mdx +0 -30
  617. package/templates/content/ja/_site.mdx +0 -30
  618. package/templates/content/pt-BR/AboutPage/about.mdx +0 -8
  619. package/templates/content/pt-BR/FAQPage/faq.mdx +0 -8
  620. package/templates/content/pt-BR/HomePage/home.mdx +0 -23
@@ -52,6 +52,19 @@ import {
52
52
  } from "../index";
53
53
  import { prisma } from "../../../lib/prisma.js";
54
54
  import { setupLeadFileEnv } from "../../../__tests__/factories/lead-fixture";
55
+ // #3979 AC1 — the single shared fixture (packages/validation, which apps/api
56
+ // may_import_from per architecture.yaml) for the NewsletterFormInline
57
+ // emitted-body shape, also consumed by the layer-a block test
58
+ // (packages/ui/src/blocks/__tests__/NewsletterFormInline.test.tsx), so a
59
+ // change to the block's emitted keys can't silently leave this layer
60
+ // asserting a stale contract. #4017 D4 extends the same fixture module with
61
+ // the ContactForm `submit`-kind body, consumed by the layer-a section test
62
+ // (packages/ui/src/sections/__tests__/ContactForm.test.tsx).
63
+ import {
64
+ AGENCY_WAITLIST_LEAD_BODY,
65
+ CONTACT_FORM_LEAD_BODY,
66
+ NEWSLETTER_DEFAULT_LEAD_BODY,
67
+ } from "../../../../../../packages/validation/src/__tests__/fixtures/newsletter-lead-body.fixture.js";
55
68
 
56
69
  const fileEnv = setupLeadFileEnv("index");
57
70
 
@@ -82,16 +95,16 @@ describe("POST /api/leads — id isolation", () => {
82
95
  delete process.env.LEAD_CAPTURE_INCLUDE_IP;
83
96
  });
84
97
 
85
- it("POST /api/leads/lead — attacker-supplied id is not returned (server uuid used)", async () => {
86
- const serverGeneratedId = "cuid-lead-server-1";
98
+ it("POST /api/leads/submit — attacker-supplied id is not returned (server uuid used)", async () => {
99
+ const serverGeneratedId = "lead_00000000-0000-7000-8000-0000000000a1";
87
100
  (prisma.lead.create as jest.Mock).mockResolvedValue({ id: serverGeneratedId });
88
101
 
89
102
  const res = await app.inject({
90
103
  method: "POST",
91
- url: "/v1/leads/lead",
104
+ url: "/v1/leads/submit",
92
105
  payload: {
93
106
  id: "attacker",
94
- kind: "lead",
107
+ kind: "submit",
95
108
  siteId: "site-abc",
96
109
  source: "contact",
97
110
  email: "test@example.com",
@@ -110,7 +123,7 @@ describe("POST /api/leads — id isolation", () => {
110
123
  });
111
124
 
112
125
  it("POST /api/leads/subscribe — attacker-supplied id is not returned (server uuid used)", async () => {
113
- const serverGeneratedId = "cuid-sub-server-1";
126
+ const serverGeneratedId = "subscr_00000000-0000-7000-8000-0000000000a2";
114
127
  (prisma.subscriber.create as jest.Mock).mockResolvedValue({ id: serverGeneratedId });
115
128
 
116
129
  const res = await app.inject({
@@ -133,8 +146,114 @@ describe("POST /api/leads — id isolation", () => {
133
146
  }
134
147
  });
135
148
 
149
+ // #3965 D4 layer (b) — the block's exact emitted shape (layer (a) pins it
150
+ // as { siteId, source, email } via application/json) persists through the
151
+ // REAL registered /v1/leads/subscribe plugin. #3979 AC1 retargets the body
152
+ // literal at the shared fixture (packages/validation) — it had already
153
+ // drifted from layer-a's own hand-copied literal (different `email`
154
+ // values), with nothing catching it.
155
+ it("POST /v1/leads/subscribe — the block's emitted JSON shape ({ siteId, source, email }) is accepted and persisted", async () => {
156
+ const serverGeneratedId = "subscr_00000000-0000-7000-8000-0000000000b1";
157
+ (prisma.subscriber.create as jest.Mock).mockResolvedValue({ id: serverGeneratedId });
158
+
159
+ // The exact body NewsletterFormInline emits at its defaults (#3965 D1) —
160
+ // no `kind` key: buildInput() injects `kind` server-side from the route,
161
+ // never from the body.
162
+ const res = await app.inject({
163
+ method: "POST",
164
+ url: "/v1/leads/subscribe",
165
+ headers: { "content-type": "application/json" },
166
+ payload: JSON.stringify(NEWSLETTER_DEFAULT_LEAD_BODY),
167
+ });
168
+
169
+ expect(res.statusCode).toBe(201);
170
+ const body = res.json();
171
+ expect(body.id).toBe(serverGeneratedId);
172
+ expect(body.persisted).toBe("db");
173
+ expect(prisma.subscriber.create).toHaveBeenCalledTimes(1);
174
+ const record = await fileEnv.readLastCaptureRecord();
175
+ expect(record.id).toBe(serverGeneratedId);
176
+ expect(record.source).toBe(NEWSLETTER_DEFAULT_LEAD_BODY.source);
177
+ });
178
+
179
+ // #3979 AC2 — the agency-waitlist instance (content/en/LandingPage/
180
+ // agency-waitlist.mdx: action "/v1/leads/submit", source "agency-waitlist",
181
+ // siteId unset → "default") had NO layer-b case at all before this issue —
182
+ // only /v1/leads/subscribe was covered. Unconditional 201 (not the
183
+ // permissive [201, 400] idiom used by the id-isolation tests above), since
184
+ // this asserts the block's real emitted shape is accepted, not merely that
185
+ // the id-isolation walls hold regardless of validation outcome.
186
+ it("POST /v1/leads/submit — the agency-waitlist instance's emitted JSON shape ({ siteId, source, email }) is accepted and persisted", async () => {
187
+ const serverGeneratedId = "lead_00000000-0000-7000-8000-0000000000b2";
188
+ (prisma.lead.create as jest.Mock).mockResolvedValue({ id: serverGeneratedId });
189
+
190
+ // The exact body NewsletterFormInline emits for the agency-waitlist
191
+ // instance — no `kind`, no `payload` key: buildInput() injects `kind`
192
+ // server-side from the route and defaults `payload` to {} when absent.
193
+ const res = await app.inject({
194
+ method: "POST",
195
+ url: "/v1/leads/submit",
196
+ headers: { "content-type": "application/json" },
197
+ payload: JSON.stringify(AGENCY_WAITLIST_LEAD_BODY),
198
+ });
199
+
200
+ expect(res.statusCode).toBe(201);
201
+ const body = res.json();
202
+ expect(body.id).toBe(serverGeneratedId);
203
+ expect(body.persisted).toBe("db");
204
+ expect(prisma.lead.create).toHaveBeenCalledTimes(1);
205
+ const record = await fileEnv.readLastCaptureRecord();
206
+ expect(record.id).toBe(serverGeneratedId);
207
+ expect(record.source).toBe(AGENCY_WAITLIST_LEAD_BODY.source);
208
+ });
209
+
210
+ // #4017 D4 layer (b) — the ContactForm section's exact emitted shape
211
+ // (layer (a) pins it as { siteId, source, email, payload } via
212
+ // application/json) persists through the REAL registered
213
+ // /v1/leads/submit plugin. `email` is lifted by field name; every other
214
+ // declared field lands in `payload` — the shared fixture (packages/validation)
215
+ // is the single definition both layers assert against (#3979 AC1's
216
+ // pattern), so a change to the section's emitted keys can't silently leave
217
+ // this layer asserting a stale contract.
218
+ it("POST /v1/leads/submit — the ContactForm section's emitted JSON shape ({ siteId, source, email, payload }) is accepted and persisted", async () => {
219
+ const serverGeneratedId = "lead_00000000-0000-7000-8000-0000000000b3";
220
+ (prisma.lead.create as jest.Mock).mockResolvedValue({ id: serverGeneratedId });
221
+
222
+ // The exact body ContactForm emits at its component defaults (#4017 D1) —
223
+ // no `kind` key: buildInput() injects `kind` server-side from the route,
224
+ // never from the body.
225
+ const res = await app.inject({
226
+ method: "POST",
227
+ url: "/v1/leads/submit",
228
+ headers: { "content-type": "application/json" },
229
+ payload: JSON.stringify(CONTACT_FORM_LEAD_BODY),
230
+ });
231
+
232
+ expect(res.statusCode).toBe(201);
233
+ const body = res.json();
234
+ expect(body.id).toBe(serverGeneratedId);
235
+ expect(body.persisted).toBe("db");
236
+ expect(prisma.lead.create).toHaveBeenCalledTimes(1);
237
+ expect(prisma.lead.create).toHaveBeenCalledWith({
238
+ data: {
239
+ siteId: CONTACT_FORM_LEAD_BODY.siteId,
240
+ source: CONTACT_FORM_LEAD_BODY.source,
241
+ email: CONTACT_FORM_LEAD_BODY.email,
242
+ payload: CONTACT_FORM_LEAD_BODY.payload,
243
+ },
244
+ select: { id: true },
245
+ });
246
+ const record = await fileEnv.readLastCaptureRecord();
247
+ expect(record.id).toBe(serverGeneratedId);
248
+ expect(record.source).toBe(CONTACT_FORM_LEAD_BODY.source);
249
+ expect(record.data).toEqual({
250
+ email: CONTACT_FORM_LEAD_BODY.email,
251
+ ...CONTACT_FORM_LEAD_BODY.payload,
252
+ });
253
+ });
254
+
136
255
  it("POST /api/leads/callback — attacker-supplied id is not returned (server uuid used)", async () => {
137
- const serverGeneratedId = "cuid-cb-server-1";
256
+ const serverGeneratedId = "callbk_00000000-0000-7000-8000-0000000000a3";
138
257
  (prisma.callback.create as jest.Mock).mockResolvedValue({ id: serverGeneratedId });
139
258
 
140
259
  const res = await app.inject({
@@ -161,14 +280,14 @@ describe("POST /api/leads — id isolation", () => {
161
280
  // ── Lead capture store integration cases ──────────────────────────────────
162
281
 
163
282
  it("file-on-success — appendLeadRecord writes to LEAD_CAPTURE_FILE on successful DB persist", async () => {
164
- const serverGeneratedId = "cuid-file-success-1";
283
+ const serverGeneratedId = "lead_00000000-0000-7000-8000-0000000000a4";
165
284
  (prisma.lead.create as jest.Mock).mockResolvedValue({ id: serverGeneratedId });
166
285
 
167
286
  const res = await app.inject({
168
287
  method: "POST",
169
- url: "/v1/leads/lead",
288
+ url: "/v1/leads/submit",
170
289
  payload: {
171
- kind: "lead",
290
+ kind: "submit",
172
291
  siteId: "site-abc",
173
292
  source: "contact",
174
293
  email: "test@example.com",
@@ -186,14 +305,14 @@ describe("POST /api/leads — id isolation", () => {
186
305
  });
187
306
 
188
307
  it("dual-write — real lead-capture-store AND DB are both invoked on success", async () => {
189
- const serverGeneratedId = "cuid-dual-write-1";
308
+ const serverGeneratedId = "lead_00000000-0000-7000-8000-0000000000a5";
190
309
  (prisma.lead.create as jest.Mock).mockResolvedValue({ id: serverGeneratedId });
191
310
 
192
311
  const res = await app.inject({
193
312
  method: "POST",
194
- url: "/v1/leads/lead",
313
+ url: "/v1/leads/submit",
195
314
  payload: {
196
- kind: "lead",
315
+ kind: "submit",
197
316
  siteId: "site-abc",
198
317
  source: "contact",
199
318
  email: "dual@example.com",
@@ -211,14 +330,14 @@ describe("POST /api/leads — id isolation", () => {
211
330
  it("disabled — appendLeadRecord is skipped when LEAD_CAPTURE_FILE_DISABLED=true", async () => {
212
331
  process.env.LEAD_CAPTURE_FILE_DISABLED = "true";
213
332
 
214
- const serverGeneratedId = "cuid-disabled-1";
333
+ const serverGeneratedId = "lead_00000000-0000-7000-8000-0000000000a6";
215
334
  (prisma.lead.create as jest.Mock).mockResolvedValue({ id: serverGeneratedId });
216
335
 
217
336
  const res = await app.inject({
218
337
  method: "POST",
219
- url: "/v1/leads/lead",
338
+ url: "/v1/leads/submit",
220
339
  payload: {
221
- kind: "lead",
340
+ kind: "submit",
222
341
  siteId: "site-abc",
223
342
  source: "contact",
224
343
  email: "disabled@example.com",
@@ -242,9 +361,9 @@ describe("POST /api/leads — id isolation", () => {
242
361
 
243
362
  const res = await app.inject({
244
363
  method: "POST",
245
- url: "/v1/leads/lead",
364
+ url: "/v1/leads/submit",
246
365
  payload: {
247
- kind: "lead",
366
+ kind: "submit",
248
367
  siteId: "site-abc",
249
368
  source: "contact",
250
369
  email: "nocapture@example.com",
@@ -261,15 +380,15 @@ describe("POST /api/leads — id isolation", () => {
261
380
 
262
381
  it("IP-include — metadata.ip present in capture record when LEAD_CAPTURE_INCLUDE_IP=true", async () => {
263
382
  process.env.LEAD_CAPTURE_INCLUDE_IP = "true";
264
- const serverGeneratedId = "cuid-ip-include-1";
383
+ const serverGeneratedId = "lead_00000000-0000-7000-8000-0000000000a7";
265
384
  (prisma.lead.create as jest.Mock).mockResolvedValue({ id: serverGeneratedId });
266
385
 
267
386
  const res = await app.inject({
268
387
  method: "POST",
269
- url: "/v1/leads/lead",
388
+ url: "/v1/leads/submit",
270
389
  headers: { "x-forwarded-for": "1.2.3.4" },
271
390
  payload: {
272
- kind: "lead",
391
+ kind: "submit",
273
392
  siteId: "site-abc",
274
393
  source: "contact",
275
394
  email: "ip@example.com",
@@ -286,11 +405,11 @@ describe("POST /api/leads — id isolation", () => {
286
405
  // ── buildInput branch coverage ─────────────────────────────────────────────
287
406
 
288
407
  it("lead — payload absent from body defaults to empty object via ?? (payload ?? {} branch)", async () => {
289
- const serverGeneratedId = "cuid-nopayload-1";
408
+ const serverGeneratedId = "lead_00000000-0000-7000-8000-0000000000a8";
290
409
  (prisma.lead.create as jest.Mock).mockResolvedValue({ id: serverGeneratedId });
291
410
 
292
411
  const bodyWithoutPayload: Record<string, unknown> = {
293
- kind: "lead",
412
+ kind: "submit",
294
413
  siteId: "site-abc",
295
414
  source: "contact",
296
415
  email: "nopayload@example.com",
@@ -299,7 +418,7 @@ describe("POST /api/leads — id isolation", () => {
299
418
 
300
419
  const res = await app.inject({
301
420
  method: "POST",
302
- url: "/v1/leads/lead",
421
+ url: "/v1/leads/submit",
303
422
  payload: bodyWithoutPayload,
304
423
  });
305
424
 
@@ -310,7 +429,7 @@ describe("POST /api/leads — id isolation", () => {
310
429
  });
311
430
 
312
431
  it("callback — preferredTime included when provided (preferredTime spread branch)", async () => {
313
- const serverGeneratedId = "cuid-callback-ptime-1";
432
+ const serverGeneratedId = "callbk_00000000-0000-7000-8000-0000000000a9";
314
433
  (prisma.callback.create as jest.Mock).mockResolvedValue({ id: serverGeneratedId });
315
434
 
316
435
  const res = await app.inject({
@@ -333,7 +452,7 @@ describe("POST /api/leads — id isolation", () => {
333
452
  });
334
453
 
335
454
  it("callback — payload absent from body defaults to empty object via ?? (callback payload ?? {} branch)", async () => {
336
- const serverGeneratedId = "cuid-callback-nopayload-1";
455
+ const serverGeneratedId = "callbk_00000000-0000-7000-8000-0000000000aa";
337
456
  (prisma.callback.create as jest.Mock).mockResolvedValue({ id: serverGeneratedId });
338
457
 
339
458
  const bodyWithoutPayload: Record<string, unknown> = {
@@ -361,14 +480,14 @@ describe("POST /api/leads — id isolation", () => {
361
480
  it("email skipped — emailKind=skipped logged when env vars are unset", async () => {
362
481
  delete process.env.AILK_LEAD_NOTIFICATION_TO;
363
482
  delete process.env.AILK_LEAD_NOTIFICATION_FROM;
364
- const serverGeneratedId = "cuid-email-skipped-1";
483
+ const serverGeneratedId = "lead_00000000-0000-7000-8000-0000000000ab";
365
484
  (prisma.lead.create as jest.Mock).mockResolvedValue({ id: serverGeneratedId });
366
485
 
367
486
  const res = await app.inject({
368
487
  method: "POST",
369
- url: "/v1/leads/lead",
488
+ url: "/v1/leads/submit",
370
489
  payload: {
371
- kind: "lead",
490
+ kind: "submit",
372
491
  siteId: "site-abc",
373
492
  source: "contact",
374
493
  email: "skipped@example.com",
@@ -384,20 +503,178 @@ describe("POST /api/leads — id isolation", () => {
384
503
  }
385
504
  });
386
505
 
506
+ // ── #4985 — step-flow envelope ({ siteId, source, payload: <record> }) ────
507
+ // StepFlow (packages/ui/src/sections/StepFlow.tsx L548) posts email INSIDE
508
+ // `payload`, not top-level. buildInput() now lifts `email` from `payload`
509
+ // when the top-level field is absent.
510
+
511
+ it("envelope submit — { siteId, source, payload: { email, ... } } returns 201, writes one Lead with that email and the full payload, and routes the notification email", async () => {
512
+ const { sendLeadNotification } = jest.requireMock("@working-theory/email");
513
+ const serverGeneratedId = "lead_00000000-0000-7000-8000-0000000000e1";
514
+ (prisma.lead.create as jest.Mock).mockResolvedValue({ id: serverGeneratedId });
515
+
516
+ const envelopePayload = {
517
+ email: "envelope@example.com",
518
+ name: "Envelope Visitor",
519
+ message: "hello from the step-flow",
520
+ };
521
+
522
+ const res = await app.inject({
523
+ method: "POST",
524
+ url: "/v1/leads/submit",
525
+ payload: {
526
+ siteId: "site-abc",
527
+ source: "step-flow",
528
+ payload: envelopePayload,
529
+ },
530
+ });
531
+
532
+ expect(res.statusCode).toBe(201);
533
+ const body = res.json();
534
+ expect(body.id).toBe(serverGeneratedId);
535
+ expect(prisma.lead.create).toHaveBeenCalledTimes(1);
536
+ expect(prisma.lead.create).toHaveBeenCalledWith({
537
+ data: {
538
+ siteId: "site-abc",
539
+ source: "step-flow",
540
+ email: "envelope@example.com",
541
+ payload: envelopePayload,
542
+ },
543
+ select: { id: true },
544
+ });
545
+ expect(sendLeadNotification).toHaveBeenCalledTimes(1);
546
+ expect(body.email).toHaveProperty("ok", true);
547
+ });
548
+
549
+ it("envelope subscribe — { siteId, source, payload: { email } } returns 201 and writes one Subscriber", async () => {
550
+ const serverGeneratedId = "subscr_00000000-0000-7000-8000-0000000000e2";
551
+ (prisma.subscriber.create as jest.Mock).mockResolvedValue({ id: serverGeneratedId });
552
+
553
+ const res = await app.inject({
554
+ method: "POST",
555
+ url: "/v1/leads/subscribe",
556
+ payload: {
557
+ siteId: "site-abc",
558
+ source: "step-flow",
559
+ payload: { email: "sub-envelope@example.com" },
560
+ },
561
+ });
562
+
563
+ expect(res.statusCode).toBe(201);
564
+ const body = res.json();
565
+ expect(body.id).toBe(serverGeneratedId);
566
+ expect(prisma.subscriber.create).toHaveBeenCalledTimes(1);
567
+ expect(prisma.subscriber.create).toHaveBeenCalledWith({
568
+ data: {
569
+ siteId: "site-abc",
570
+ email: "sub-envelope@example.com",
571
+ source: "step-flow",
572
+ },
573
+ select: { id: true },
574
+ });
575
+ });
576
+
577
+ it("envelope submit — email absent in both the top level and payload → the existing 400 invalid_input", async () => {
578
+ const res = await app.inject({
579
+ method: "POST",
580
+ url: "/v1/leads/submit",
581
+ payload: {
582
+ siteId: "site-abc",
583
+ source: "step-flow",
584
+ payload: { message: "no email anywhere" },
585
+ },
586
+ });
587
+
588
+ expect(res.statusCode).toBe(400);
589
+ const body = res.json();
590
+ expect(body.error).toBe("invalid_input");
591
+ expect(prisma.lead.create).not.toHaveBeenCalled();
592
+ });
593
+
594
+ it("envelope subscribe — email absent in both the top level and payload → the existing 400 invalid_input", async () => {
595
+ const res = await app.inject({
596
+ method: "POST",
597
+ url: "/v1/leads/subscribe",
598
+ payload: {
599
+ siteId: "site-abc",
600
+ source: "step-flow",
601
+ payload: {},
602
+ },
603
+ });
604
+
605
+ expect(res.statusCode).toBe(400);
606
+ const body = res.json();
607
+ expect(body.error).toBe("invalid_input");
608
+ expect(prisma.subscriber.create).not.toHaveBeenCalled();
609
+ });
610
+
611
+ it("submit — a top-level email wins over a payload email", async () => {
612
+ const serverGeneratedId = "lead_00000000-0000-7000-8000-0000000000e3";
613
+ (prisma.lead.create as jest.Mock).mockResolvedValue({ id: serverGeneratedId });
614
+
615
+ const res = await app.inject({
616
+ method: "POST",
617
+ url: "/v1/leads/submit",
618
+ payload: {
619
+ siteId: "site-abc",
620
+ source: "step-flow",
621
+ email: "top-level@example.com",
622
+ payload: { email: "shadowed-payload@example.com", message: "hi" },
623
+ },
624
+ });
625
+
626
+ expect(res.statusCode).toBe(201);
627
+ expect(prisma.lead.create).toHaveBeenCalledWith({
628
+ data: {
629
+ siteId: "site-abc",
630
+ source: "step-flow",
631
+ email: "top-level@example.com",
632
+ payload: { email: "shadowed-payload@example.com", message: "hi" },
633
+ },
634
+ select: { id: true },
635
+ });
636
+ });
637
+
638
+ it("subscribe — a top-level email wins over a payload email", async () => {
639
+ const serverGeneratedId = "subscr_00000000-0000-7000-8000-0000000000e4";
640
+ (prisma.subscriber.create as jest.Mock).mockResolvedValue({ id: serverGeneratedId });
641
+
642
+ const res = await app.inject({
643
+ method: "POST",
644
+ url: "/v1/leads/subscribe",
645
+ payload: {
646
+ siteId: "site-abc",
647
+ source: "step-flow",
648
+ email: "top-level-sub@example.com",
649
+ payload: { email: "shadowed-sub@example.com" },
650
+ },
651
+ });
652
+
653
+ expect(res.statusCode).toBe(201);
654
+ expect(prisma.subscriber.create).toHaveBeenCalledWith({
655
+ data: {
656
+ siteId: "site-abc",
657
+ email: "top-level-sub@example.com",
658
+ source: "step-flow",
659
+ },
660
+ select: { id: true },
661
+ });
662
+ });
663
+
387
664
  it("email error — emailKind=error logged when sendLeadNotification returns ok:false", async () => {
388
665
  const { sendLeadNotification } = jest.requireMock("@working-theory/email");
389
666
  (sendLeadNotification as jest.Mock).mockResolvedValueOnce({
390
667
  ok: false,
391
668
  error: { kind: "send_error", message: "SMTP timeout" },
392
669
  });
393
- const serverGeneratedId = "cuid-email-error-1";
670
+ const serverGeneratedId = "lead_00000000-0000-7000-8000-0000000000ac";
394
671
  (prisma.lead.create as jest.Mock).mockResolvedValue({ id: serverGeneratedId });
395
672
 
396
673
  const res = await app.inject({
397
674
  method: "POST",
398
- url: "/v1/leads/lead",
675
+ url: "/v1/leads/submit",
399
676
  payload: {
400
- kind: "lead",
677
+ kind: "submit",
401
678
  siteId: "site-abc",
402
679
  source: "contact",
403
680
  email: "erroremail@example.com",
@@ -413,3 +690,133 @@ describe("POST /api/leads — id isolation", () => {
413
690
  }
414
691
  });
415
692
  });
693
+
694
+ // ── #4985 — PII-safe logging still holds when email arrives via `payload` ──
695
+
696
+ describe("POST /v1/leads/submit — structured log stays PII-safe for envelope input", () => {
697
+ afterEach(async () => {
698
+ delete process.env.AILK_LEAD_NOTIFICATION_TO;
699
+ delete process.env.AILK_LEAD_NOTIFICATION_FROM;
700
+ delete process.env.DATABASE_URL;
701
+ });
702
+
703
+ it("the 'lead routed' log line carries no email or payload value when email is lifted from payload", async () => {
704
+ jest.clearAllMocks();
705
+ process.env.AILK_LEAD_NOTIFICATION_TO = "owner@example.com";
706
+ process.env.AILK_LEAD_NOTIFICATION_FROM = "noreply@example.com";
707
+ process.env.DATABASE_URL = "postgres://stub";
708
+
709
+ const serverGeneratedId = "lead_00000000-0000-7000-8000-0000000000e5";
710
+ (prisma.lead.create as jest.Mock).mockResolvedValue({ id: serverGeneratedId });
711
+
712
+ const logLines: string[] = [];
713
+ const logCapture = require("fastify")({
714
+ logger: {
715
+ level: "info",
716
+ stream: { write: (msg: string) => logLines.push(msg) },
717
+ },
718
+ }) as FastifyInstance;
719
+ await logCapture.register(leadSubmitRoute);
720
+ await logCapture.ready();
721
+
722
+ try {
723
+ const res = await logCapture.inject({
724
+ method: "POST",
725
+ url: "/v1/leads/submit",
726
+ payload: {
727
+ siteId: "site-abc",
728
+ source: "step-flow",
729
+ payload: {
730
+ email: "secret-envelope@example.com",
731
+ message: "this message body must never be logged",
732
+ },
733
+ },
734
+ });
735
+
736
+ expect(res.statusCode).toBe(201);
737
+
738
+ const routedLine = logLines.find((line) => line.includes("lead routed"));
739
+ expect(routedLine).toBeDefined();
740
+ expect(routedLine).not.toMatch(/secret-envelope@example\.com/);
741
+ expect(routedLine).not.toMatch(/this message body must never be logged/);
742
+
743
+ const logged = JSON.parse(routedLine!) as Record<string, unknown>;
744
+ expect(logged).not.toHaveProperty("email");
745
+ expect(logged).not.toHaveProperty("payload");
746
+ expect(logged).toMatchObject({
747
+ siteId: "site-abc",
748
+ kind: "submit",
749
+ persisted: "db",
750
+ });
751
+ } finally {
752
+ await logCapture.close();
753
+ }
754
+ });
755
+ });
756
+
757
+ // ── D5 — registration binds the client's literal path (issue #3964) ───────────
758
+ //
759
+ // The drift class this issue fixed: every test above hand-supplied the same
760
+ // (wrong) URL the route was actually registered at, so the mismatch between
761
+ // the registered path and the path every consumer calls shipped silently.
762
+ // This suite registers the real exported plugin and asserts against the
763
+ // EXACT literal packages/api-client/src/leads.ts uses (cross-referenced
764
+ // below), so a future re-drift fails here instead of shipping.
765
+
766
+ describe("POST /v1/leads/submit — registration binds the client's literal path", () => {
767
+ let app: FastifyInstance;
768
+
769
+ beforeEach(async () => {
770
+ jest.clearAllMocks();
771
+ process.env.AILK_LEAD_NOTIFICATION_TO = "owner@example.com";
772
+ process.env.AILK_LEAD_NOTIFICATION_FROM = "noreply@example.com";
773
+ process.env.DATABASE_URL = "postgres://stub";
774
+
775
+ const fastify = require("fastify")();
776
+ await fastify.register(leadSubmitRoute);
777
+ app = fastify;
778
+ });
779
+
780
+ afterEach(async () => {
781
+ await app.close();
782
+ delete process.env.AILK_LEAD_NOTIFICATION_TO;
783
+ delete process.env.AILK_LEAD_NOTIFICATION_FROM;
784
+ delete process.env.DATABASE_URL;
785
+ });
786
+
787
+ it("resolves POST /v1/leads/submit — the exact literal packages/api-client/src/leads.ts's leads.submit() posts to", async () => {
788
+ const serverGeneratedId = "lead_00000000-0000-7000-8000-0000000000d1";
789
+ (prisma.lead.create as jest.Mock).mockResolvedValue({ id: serverGeneratedId });
790
+
791
+ // Cross-reference: packages/api-client/src/leads.ts —
792
+ // submit: (payload) => post<LeadWriteResponse>("/v1/leads/submit", payload)
793
+ const res = await app.inject({
794
+ method: "POST",
795
+ url: "/v1/leads/submit",
796
+ payload: {
797
+ siteId: "site-abc",
798
+ source: "contact",
799
+ email: "d5@example.com",
800
+ payload: { message: "hello" },
801
+ },
802
+ });
803
+
804
+ expect(res.statusCode).toBe(201);
805
+ expect(res.json().id).toBe(serverGeneratedId);
806
+ });
807
+
808
+ it("POST /v1/leads/lead — no longer registered (404); the pre-fix path", async () => {
809
+ const res = await app.inject({
810
+ method: "POST",
811
+ url: "/v1/leads/lead",
812
+ payload: {
813
+ siteId: "site-abc",
814
+ source: "contact",
815
+ email: "old-path@example.com",
816
+ payload: { message: "hello" },
817
+ },
818
+ });
819
+
820
+ expect(res.statusCode).toBe(404);
821
+ });
822
+ });
@@ -74,14 +74,14 @@ describe("POST /api/leads — track() telemetry", () => {
74
74
  });
75
75
 
76
76
  it("calls track('lead-captured') with source and locale on successful submission", async () => {
77
- const serverGeneratedId = "cuid-track-success-1";
77
+ const serverGeneratedId = "lead_00000000-0000-7000-8000-0000000000b1";
78
78
  (prisma.lead.create as jest.Mock).mockResolvedValue({ id: serverGeneratedId });
79
79
 
80
80
  const res = await app.inject({
81
81
  method: "POST",
82
- url: "/v1/leads/lead",
82
+ url: "/v1/leads/submit",
83
83
  payload: {
84
- kind: "lead",
84
+ kind: "submit",
85
85
  siteId: "site-abc",
86
86
  source: "newsletter",
87
87
  email: "test@example.com",
@@ -108,9 +108,9 @@ describe("POST /api/leads — track() telemetry", () => {
108
108
 
109
109
  const res = await app.inject({
110
110
  method: "POST",
111
- url: "/v1/leads/lead",
111
+ url: "/v1/leads/submit",
112
112
  payload: {
113
- kind: "lead",
113
+ kind: "submit",
114
114
  siteId: "site-abc",
115
115
  source: "contact",
116
116
  email: "fail@example.com",
@@ -125,10 +125,10 @@ describe("POST /api/leads — track() telemetry", () => {
125
125
  it("does NOT call track() when validation fails (missing required field → 400 or 503)", async () => {
126
126
  const res = await app.inject({
127
127
  method: "POST",
128
- url: "/v1/leads/lead",
128
+ url: "/v1/leads/submit",
129
129
  payload: {
130
130
  // missing email — required field for lead kind
131
- kind: "lead",
131
+ kind: "submit",
132
132
  siteId: "site-abc",
133
133
  source: "contact",
134
134
  payload: {},
@@ -140,14 +140,14 @@ describe("POST /api/leads — track() telemetry", () => {
140
140
  });
141
141
 
142
142
  it("track() props contain only source and locale — no PII fields", async () => {
143
- const serverGeneratedId = "cuid-pii-check-1";
143
+ const serverGeneratedId = "lead_00000000-0000-7000-8000-0000000000b2";
144
144
  (prisma.lead.create as jest.Mock).mockResolvedValue({ id: serverGeneratedId });
145
145
 
146
146
  const res = await app.inject({
147
147
  method: "POST",
148
- url: "/v1/leads/lead",
148
+ url: "/v1/leads/submit",
149
149
  payload: {
150
- kind: "lead",
150
+ kind: "submit",
151
151
  siteId: "site-abc",
152
152
  source: "landing-page",
153
153
  email: "pii@example.com",