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
@@ -0,0 +1,242 @@
1
+ /**
2
+ * Tests for ConversationalIntakeWrapper (#4534, epic #4521's integration
3
+ * leg).
4
+ *
5
+ * AC1 (integration test) — a completed conversation lands a lead through
6
+ * the EXISTING lead-capture endpoint. Only the outermost network
7
+ * boundary is mocked (`@working-theory/api-client`'s `leads.submit`) —
8
+ * the REAL `ConversationalIntake` block (#4543) and its engine (#4532),
9
+ * the REAL `captureLeadAction` (the SAME action `<ContactForm/>` already
10
+ * uses), and the REAL `submitLeadInputSchema` validation all run, proving
11
+ * the whole chain end to end. No file under apps/api is imported, mocked,
12
+ * or touched by this test or by the wiring it exercises.
13
+ * AC2 — Skip renders the existing form, carrying over answers already
14
+ * captured as `initialValues`.
15
+ * AC3 — per-turn abandonment analytics: `recordIntakeTurn`
16
+ * (`./intake-analytics.ts`, mocked at the `track()` boundary) fires once
17
+ * per turn with an increasing `turnIndex`, plus one terminal call tagging
18
+ * the outcome.
19
+ */
20
+ import { act, fireEvent, render, screen } from "@testing-library/react";
21
+
22
+ jest.mock("next-intl", () => ({
23
+ useTranslations: () => (key: string) => key,
24
+ useLocale: () => "en",
25
+ useMessages: () => ({}),
26
+ }));
27
+
28
+ jest.mock("@working-theory/api-client", () => ({
29
+ leads: {
30
+ submit: jest.fn(),
31
+ },
32
+ }));
33
+
34
+ jest.mock("@working-theory/analytics/server", () => ({
35
+ track: jest.fn().mockResolvedValue(undefined),
36
+ }));
37
+
38
+ // ConversationalIntakeWrapper's own responsibility under test here is the
39
+ // submission/skip/analytics wiring around the block — not ContactForm's own
40
+ // rendering, which apps/web's existing tests already document as crashing
41
+ // in jsdom (useFormState/useFormStatus; see page-renderer-migration.test.tsx).
42
+ // Narrow internal-module mock, same precedent as that file's ContactForm stub.
43
+ // eslint-disable-next-line no-restricted-syntax -- test-seam mock required; ContactForm uses useFormState/useFormStatus which crash in jsdom (see page-renderer-migration.test.tsx); narrowest possible scope
44
+ jest.mock("../ContactForm", () => ({
45
+ ContactForm: (props: { initialValues?: Record<string, string> }) =>
46
+ require("react").createElement("div", {
47
+ "data-testid": "contact-form-stub",
48
+ "data-initial-values": JSON.stringify(props.initialValues ?? {}),
49
+ }),
50
+ }));
51
+
52
+ import { leads } from "@working-theory/api-client";
53
+ import { track } from "@working-theory/analytics/server";
54
+
55
+ import { ConversationalIntakeWrapper } from "../ConversationalIntakeWrapper.js";
56
+
57
+ const mockSubmit = leads.submit as jest.Mock;
58
+ const mockTrack = track as jest.Mock;
59
+
60
+ const TEST_ID = "contact-conversational-intake";
61
+
62
+ beforeEach(() => {
63
+ jest.useFakeTimers();
64
+ });
65
+
66
+ afterEach(() => {
67
+ act(() => {
68
+ jest.runOnlyPendingTimers();
69
+ });
70
+ jest.useRealTimers();
71
+ jest.clearAllMocks();
72
+ });
73
+
74
+ /** Advances past the thinking pause (600–900ms) and flushes the reveal. */
75
+ function advanceThinking(): void {
76
+ act(() => {
77
+ jest.advanceTimersByTime(1000);
78
+ });
79
+ }
80
+
81
+ /**
82
+ * Flushes the microtask queue — needed after a turn transition that
83
+ * triggers an async Server Action call (`onComplete`'s `await
84
+ * captureLeadAction(...)`), whose own state updates (fallback to the form,
85
+ * the submit-error message) land a tick after the timer-driven reveal
86
+ * `advanceThinking()` already flushed.
87
+ */
88
+ async function flushMicrotasks(): Promise<void> {
89
+ await act(async () => {
90
+ await Promise.resolve();
91
+ await Promise.resolve();
92
+ });
93
+ }
94
+
95
+ function answer(text: string): void {
96
+ const input = screen.getByTestId(`${TEST_ID}-free-text-input`);
97
+ fireEvent.change(input, { target: { value: text } });
98
+ fireEvent.click(screen.getByTestId(`${TEST_ID}-free-text-submit`));
99
+ }
100
+
101
+ describe("ConversationalIntakeWrapper — completion (AC1)", () => {
102
+ it("submits through the existing lead-capture endpoint's api-client surface", () => {
103
+ mockSubmit.mockResolvedValue({
104
+ ok: true,
105
+ body: { id: "lead_abc123", persisted: "db" },
106
+ });
107
+
108
+ render(<ConversationalIntakeWrapper />);
109
+ advanceThinking(); // turn 1 (name) revealed
110
+
111
+ answer("Jane Smith");
112
+ advanceThinking(); // turn 2 (message) revealed
113
+
114
+ answer("I'd like a demo.");
115
+ advanceThinking(); // turn 3 (email) revealed
116
+
117
+ answer("jane@example.com");
118
+ advanceThinking(); // completion revealed, onComplete fires
119
+
120
+ expect(mockSubmit).toHaveBeenCalledTimes(1);
121
+ const body = mockSubmit.mock.calls[0][0] as {
122
+ siteId: string;
123
+ source: string;
124
+ email: string;
125
+ payload: { name: string; message?: string };
126
+ };
127
+ expect(body.siteId).toBe("default");
128
+ expect(body.source).toBe("conversational-intake");
129
+ expect(body.email).toBe("jane@example.com");
130
+ expect(body.payload.name).toBe("Jane Smith");
131
+ expect(body.payload.message).toBe("I'd like a demo.");
132
+
133
+ // The block stays mounted on success — its own completion message is
134
+ // already the confirmation; no fallback form appears.
135
+ expect(screen.queryByTestId("contact-form-stub")).toBeNull();
136
+ });
137
+
138
+ it("falls back to the existing form, prefilled, when the submit fails", async () => {
139
+ mockSubmit.mockResolvedValue({ ok: false, status: 503, body: {} });
140
+
141
+ render(<ConversationalIntakeWrapper />);
142
+ advanceThinking();
143
+ answer("Jane Smith");
144
+ advanceThinking();
145
+ answer("I'd like a demo.");
146
+ advanceThinking();
147
+ answer("jane@example.com");
148
+ advanceThinking();
149
+ await flushMicrotasks();
150
+
151
+ const stub = screen.getByTestId("contact-form-stub");
152
+ const initialValues = JSON.parse(
153
+ stub.getAttribute("data-initial-values") ?? "{}",
154
+ );
155
+ expect(initialValues).toMatchObject({
156
+ name: "Jane Smith",
157
+ email: "jane@example.com",
158
+ message: "I'd like a demo.",
159
+ });
160
+ expect(
161
+ screen.getByTestId("contact-intake-submit-error"),
162
+ ).toBeInTheDocument();
163
+ });
164
+ });
165
+
166
+ describe("ConversationalIntakeWrapper — Skip (AC2)", () => {
167
+ it("renders the existing form on Skip, carrying over the answers captured so far", () => {
168
+ render(<ConversationalIntakeWrapper />);
169
+ advanceThinking(); // turn 1 (name) revealed
170
+
171
+ answer("Jane Smith");
172
+ advanceThinking(); // turn 2 (message) revealed
173
+
174
+ fireEvent.click(screen.getByTestId(`${TEST_ID}-skip`));
175
+ advanceThinking(); // skip message revealed, onSkip fires
176
+
177
+ const stub = screen.getByTestId("contact-form-stub");
178
+ const initialValues = JSON.parse(
179
+ stub.getAttribute("data-initial-values") ?? "{}",
180
+ );
181
+ expect(initialValues).toMatchObject({ name: "Jane Smith" });
182
+ expect(mockSubmit).not.toHaveBeenCalled();
183
+ });
184
+ });
185
+
186
+ describe("ConversationalIntakeWrapper — per-turn abandonment analytics (AC3)", () => {
187
+ it("fires one in_progress event per turn, then a terminal completed event, with increasing turnIndex", () => {
188
+ mockSubmit.mockResolvedValue({
189
+ ok: true,
190
+ body: { id: "lead_abc123", persisted: "db" },
191
+ });
192
+
193
+ render(<ConversationalIntakeWrapper />);
194
+ advanceThinking();
195
+ answer("Jane Smith");
196
+ advanceThinking();
197
+ answer("I'd like a demo.");
198
+ advanceThinking();
199
+ answer("jane@example.com");
200
+ advanceThinking();
201
+
202
+ const calls = mockTrack.mock.calls.map(([name, props]) => ({
203
+ name,
204
+ ...(props as { turnIndex: number; status: string; locale: string }),
205
+ }));
206
+
207
+ expect(calls.every((c) => c.name === "intake-turn-abandonment")).toBe(
208
+ true,
209
+ );
210
+ const inProgress = calls.filter((c) => c.status === "in_progress");
211
+ expect(inProgress.map((c) => c.turnIndex)).toEqual([1, 2, 3, 4]);
212
+
213
+ const terminal = calls[calls.length - 1];
214
+ expect(terminal).toMatchObject({ status: "completed", turnIndex: 4 });
215
+
216
+ // PII-free: only turnIndex/status/locale ever leave the client.
217
+ for (const call of mockTrack.mock.calls) {
218
+ const props = call[1] as Record<string, unknown>;
219
+ expect(Object.keys(props).sort()).toEqual(
220
+ ["turnIndex", "status", "locale"].sort(),
221
+ );
222
+ }
223
+ });
224
+
225
+ it("tags an abandoned (skipped) session with the turn it stopped at", () => {
226
+ render(<ConversationalIntakeWrapper />);
227
+ advanceThinking();
228
+ answer("Jane Smith");
229
+ advanceThinking();
230
+
231
+ fireEvent.click(screen.getByTestId(`${TEST_ID}-skip`));
232
+ advanceThinking();
233
+
234
+ const calls = mockTrack.mock.calls.map(([, props]) => props) as Array<{
235
+ status: string;
236
+ turnIndex: number;
237
+ }>;
238
+ const terminal = calls[calls.length - 1];
239
+ expect(terminal).toMatchObject({ status: "skipped" });
240
+ expect(terminal?.turnIndex).toBeGreaterThan(0);
241
+ });
242
+ });
@@ -149,4 +149,33 @@ describe("captureLeadAction — API error", () => {
149
149
  message: expect.any(String),
150
150
  });
151
151
  });
152
+
153
+ // #4042 — the catch-all previously discarded a thrown error silently,
154
+ // leaving zero server-side trace of a production integration failure.
155
+ it("logs the thrown error server-side with a [captureLeadAction] prefix, with no PII from the submitted fields", async () => {
156
+ const thrown = new Error("Network error");
157
+ mockSubmit.mockRejectedValueOnce(thrown);
158
+ const consoleErrorSpy = jest
159
+ .spyOn(console, "error")
160
+ .mockImplementation(() => undefined);
161
+
162
+ const result = await captureLeadAction(
163
+ INITIAL_STATE,
164
+ makeFormData(VALID_FIELDS),
165
+ );
166
+
167
+ expect(consoleErrorSpy).toHaveBeenCalledWith("[captureLeadAction]", thrown);
168
+ const loggedArgs = consoleErrorSpy.mock.calls.flat();
169
+ for (const value of Object.values(VALID_FIELDS)) {
170
+ for (const arg of loggedArgs) {
171
+ expect(String(arg)).not.toContain(value);
172
+ }
173
+ }
174
+ expect(result).toMatchObject({
175
+ kind: "error",
176
+ message: expect.any(String),
177
+ });
178
+
179
+ consoleErrorSpy.mockRestore();
180
+ });
152
181
  });
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Tests for intake-analytics.ts's recordIntakeTurn (#4534, epic #4521's
3
+ * integration leg).
4
+ *
5
+ * Acceptance criteria verified here:
6
+ * - recordIntakeTurn calls track() with the turn index + status + locale.
7
+ * - Never leaks answer text (PII-free per AC3).
8
+ */
9
+
10
+ jest.mock("@working-theory/analytics/server", () => ({
11
+ track: jest.fn().mockResolvedValue(undefined),
12
+ }));
13
+
14
+ import { track } from "@working-theory/analytics/server";
15
+
16
+ import { recordIntakeTurn } from "../intake-analytics.js";
17
+
18
+ const mockTrack = track as jest.Mock;
19
+
20
+ afterEach(() => {
21
+ jest.clearAllMocks();
22
+ });
23
+
24
+ describe("recordIntakeTurn", () => {
25
+ it("reports the turn index, status, and locale — nothing else", async () => {
26
+ await recordIntakeTurn({
27
+ turnIndex: 2,
28
+ status: "in_progress",
29
+ locale: "en",
30
+ });
31
+
32
+ expect(mockTrack).toHaveBeenCalledWith("intake-turn-abandonment", {
33
+ turnIndex: 2,
34
+ status: "in_progress",
35
+ locale: "en",
36
+ });
37
+ });
38
+
39
+ it("never receives answer text (PII-free)", async () => {
40
+ await recordIntakeTurn({
41
+ turnIndex: 3,
42
+ status: "completed",
43
+ locale: "en",
44
+ });
45
+
46
+ const props = mockTrack.mock.calls[0][1] as Record<string, unknown>;
47
+ expect(Object.keys(props).sort()).toEqual(
48
+ ["turnIndex", "status", "locale"].sort(),
49
+ );
50
+ });
51
+ });
@@ -1,29 +1,50 @@
1
1
  /**
2
2
  * HTML snapshot regression tests for contact/page.tsx → @working-theory/page-renderer migration.
3
3
  *
4
- * Contact uses sections mode (not bodyMode: "mdx-body") with a ContactForm wrapper:
5
- * <>{renderPage output}<ContactForm /></>
4
+ * Contact uses sections mode (not bodyMode: "mdx-body") with a
5
+ * ConversationalIntakeWrapper wrapper:
6
+ * <>{renderPage output}<ConversationalIntakeWrapper /></>
6
7
  *
7
8
  * Two-commit strategy:
8
9
  * Commit 1: this file added; snapshots captured against pre-migration route (RED-passing).
9
10
  * Commit 2: route migrated; same snapshots verify diff=0 (GREEN).
10
11
  *
12
+ * #4534 (epic #4521's integration leg) swapped the page's direct
13
+ * `<ContactForm/>` mount for `<ConversationalIntakeWrapper/>` (which itself
14
+ * renders the existing ContactForm on Skip or a failed auto-submit — see
15
+ * that component's own doc-comment). The mock below + the snapshots'
16
+ * `contact-form-stub` testid moved with it, deliberately — this is the one
17
+ * legitimate case the file's own "DO NOT regenerate to pass" warning
18
+ * doesn't cover: the page's actual composition changed, not the renderer.
19
+ *
11
20
  * Two states tested:
12
- * - empty: getMdxSource returns null → renderPage empty-state + ContactForm present
13
- * - populated: getMdxSource returns populated source → renderPage sections + ContactForm present
21
+ * - empty: getMdxSource returns null → renderPage empty-state + wrapper present
22
+ * - populated: getMdxSource returns populated source → renderPage sections + wrapper present
14
23
  *
15
- * Note on ContactForm in tests: ContactForm is a "use client" component that uses
16
- * useFormState/useFormStatus hooks. jsdom does not support these hooks. The component
17
- * renders as an opaque stub via the next-intl and react-dom mocks below. The snapshot
18
- * asserts the ContactForm component is present in the output (non-null).
24
+ * Note on ConversationalIntakeWrapper in tests: it composes ContactForm
25
+ * (skip/fallback surface), which is a "use client" component using
26
+ * useFormState/useFormStatus hooks jsdom does not support these hooks.
27
+ * The wrapper renders as an opaque stub via the next-intl and react-dom
28
+ * mocks below. The snapshot asserts the wrapper is present in the output
29
+ * (non-null).
19
30
  *
20
- * Failure = snapshot diff > 0 bytes. DO NOT regenerate snapshots to pass.
31
+ * Failure = snapshot diff > 0 bytes beyond the deliberate #4534 rename.
21
32
  */
22
33
  import React from "react";
23
34
  import { render } from "@testing-library/react";
24
35
 
25
36
  // ── @working-theory/ui mock ─────────────────────────────────────────────────────────────
26
37
  jest.mock("@working-theory/ui", () => ({
38
+ // #4446/#4447: renderPage's own fallback/empty-state title now composes
39
+ // Heading (mobile type-budget classes, #4417) instead of a bare <h1>.
40
+ Heading: ({
41
+ children,
42
+ level,
43
+ }: {
44
+ children: React.ReactNode;
45
+ level: number;
46
+ }): React.ReactElement =>
47
+ React.createElement(`h${level}`, null, children),
27
48
  SectionRenderer: ({ sections }: { sections: unknown[] }) =>
28
49
  React.createElement("div", {
29
50
  "data-testid": "section-renderer",
@@ -31,12 +52,15 @@ jest.mock("@working-theory/ui", () => ({
31
52
  }),
32
53
  }));
33
54
 
34
- // ── ContactForm mock ──────────────────────────────────────────────────────────
35
- // ContactForm is "use client" and uses useFormState/useFormStatus. Mock it as a
36
- // deterministic stub so the snapshot is stable.
37
- // eslint-disable-next-line no-restricted-syntax -- test-seam mock required; ContactForm uses useFormState/useFormStatus which crash in jsdom; internal-module mock is the narrowest possible scope; tracked in #507
38
- jest.mock("../ContactForm", () => ({
39
- ContactForm: () =>
55
+ // ── ConversationalIntakeWrapper mock ────────────────────────────────────────
56
+ // ConversationalIntakeWrapper composes ContactForm ("use client",
57
+ // useFormState/useFormStatus crashes in jsdom) as its skip/fallback
58
+ // surface. Mock the wrapper itself as a deterministic stub so the snapshot
59
+ // is stable; the wrapper's own submission/skip/analytics wiring is covered
60
+ // by ConversationalIntakeWrapper.test.tsx.
61
+ // eslint-disable-next-line no-restricted-syntax -- test-seam mock required; composes ContactForm, which uses useFormState/useFormStatus that crash in jsdom; internal-module mock is the narrowest possible scope; tracked in #507
62
+ jest.mock("../ConversationalIntakeWrapper", () => ({
63
+ ConversationalIntakeWrapper: () =>
40
64
  React.createElement("div", { "data-testid": "contact-form-stub" }),
41
65
  }));
42
66
 
@@ -4,13 +4,24 @@
4
4
  * captureLeadAction — Server Action paired with ailkCaptureLead MCP tool.
5
5
  *
6
6
  * Validates contact-form input with submitLeadInputSchema (shared schema per
7
- * ADR 0013), calls POST /api/leads/submit via @working-theory/api-client, and returns
7
+ * ADR 0013), calls POST /v1/leads/submit via @working-theory/api-client, and returns
8
8
  * a discriminated ActionResult suitable for useFormState.
9
9
  *
10
10
  * Canonical filename: contact.actions.ts (*.actions.ts per ADR 0013 / U0 spec
11
11
  * parity-test regex `ACTION_FILE_RE = /\.actions\.ts$/`).
12
12
  *
13
13
  * Honeypot + rate-limit enforcement lives in apps/api; no duplication here.
14
+ *
15
+ * `source` (#4534, epic #4521's integration leg): an optional hidden
16
+ * FormData field so `ConversationalIntakeWrapper` can tag leads it submits
17
+ * as `"conversational-intake"` rather than the plain form's `"web"` —
18
+ * reusing this SAME action (built imperatively from the conversation's
19
+ * answers via `new FormData()`, not a real `<form>` submit) instead of a
20
+ * second `*Action`-suffixed export, which would trip
21
+ * `apps/mcp/__tests__/parity.test.ts`'s Server-Action↔MCP-tool 1:1 count for
22
+ * a capability (lead capture) this repo already has an MCP twin for
23
+ * (`ailkCaptureLead`). Absent (the real `<form>` never sets it) → defaults
24
+ * to `"web"`, unchanged from before.
14
25
  */
15
26
 
16
27
  import { leads } from "@working-theory/api-client";
@@ -50,9 +61,10 @@ export async function captureLeadAction(
50
61
 
51
62
  const { name, email, message, phone } = parsed.data;
52
63
  const siteId = process.env.AILK_SITE_ID ?? "default";
64
+ const source = (formData.get("source") as string | null) || "web";
53
65
  const body = {
54
66
  siteId,
55
- source: "web",
67
+ source,
56
68
  email,
57
69
  payload: {
58
70
  name,
@@ -70,7 +82,13 @@ export async function captureLeadAction(
70
82
  };
71
83
  }
72
84
  return { kind: "success" };
73
- } catch {
85
+ } catch (err) {
86
+ // Log server-side for observability (#4042, sibling of flow.actions.ts's
87
+ // #3989 fix). The api-client's post() never attaches the request payload
88
+ // to a thrown error (packages/api-client/src/http.ts), so logging `err`
89
+ // itself is safe — no submitted form fields (name/email/message/phone)
90
+ // are included.
91
+ console.error("[captureLeadAction]", err);
74
92
  return {
75
93
  kind: "error",
76
94
  message: "Something went wrong, please try again.",
@@ -0,0 +1,49 @@
1
+ "use server";
2
+
3
+ /**
4
+ * intake-analytics.ts — per-turn abandonment analytics for
5
+ * ConversationalIntakeWrapper (#4534, epic #4521's integration leg).
6
+ *
7
+ * `recordIntakeTurn` fires `track()` (`@working-theory/analytics/server`,
8
+ * the same fire-and-forget helper `apps/api/src/routes/leads/index.ts`
9
+ * already uses for "lead-captured") once per conversation turn, tagged with
10
+ * the turn index and the engine status as of that turn. A session's last
11
+ * "intake-turn-abandonment" event with `status: "in_progress"` marks the
12
+ * turn it stopped progressing past — the standard step-funnel shape for
13
+ * computing per-turn abandonment, without needing a bespoke "abandoned"
14
+ * signal (tab-close is indistinguishable from "still reading" client-side;
15
+ * the downstream analytics consumer infers abandonment from the absence of
16
+ * a later "completed"/"skipped" event for the session). PII-free by
17
+ * construction: only the turn index, status, and locale ever leave the
18
+ * client — never the answer text (honesty-by-design applies to what we
19
+ * track, too).
20
+ *
21
+ * Deliberately NOT named `recordIntakeTurnAction` / kept OUT of a
22
+ * `*.actions.ts` file: this is telemetry, not a business-capability Server
23
+ * Action, and `apps/mcp/__tests__/parity.test.ts` counts every
24
+ * `*.actions.ts` export matching `^[a-z][a-zA-Z0-9]*Action$` as one half of
25
+ * a Server-Action↔MCP-tool pair it expects to find a twin for — there is no
26
+ * (and should be no) `ailkRecordIntakeTurn` MCP tool, since this reports a
27
+ * pageview-shaped web-analytics event, not a capability an agent invokes.
28
+ */
29
+
30
+ import { track } from "@working-theory/analytics/server";
31
+
32
+ /**
33
+ * The conversation's status as of the turn being reported. `"in_progress"`
34
+ * fires once per turn while the conversation is ongoing (including turn 1,
35
+ * on mount); `"completed"`/`"skipped"` fire once, at the terminal turn.
36
+ */
37
+ export type IntakeTurnStatus = "in_progress" | "completed" | "skipped";
38
+
39
+ export async function recordIntakeTurn(input: {
40
+ turnIndex: number;
41
+ status: IntakeTurnStatus;
42
+ locale?: string;
43
+ }): Promise<void> {
44
+ await track("intake-turn-abandonment", {
45
+ turnIndex: input.turnIndex,
46
+ status: input.status,
47
+ locale: input.locale,
48
+ });
49
+ }
@@ -0,0 +1,66 @@
1
+ /**
2
+ * buildContactIntakeScript — the authored `IntakeScript` for the contact
3
+ * page's `ConversationalIntakeWrapper` (#4534, epic #4521's integration
4
+ * leg).
5
+ *
6
+ * Three open questions, deliberately keyed `name` / `message` / `email` —
7
+ * the SAME field names `submitLeadInputSchema` (packages/validation)
8
+ * expects. `ConversationalIntakeWrapper` reads `IntakeEngineState.answers`
9
+ * by these exact ids to build the `FormData` it hands to the existing
10
+ * `captureLeadAction` (`./contact.actions.ts`), so the script's question
11
+ * ids ARE the submission adapter's field-mapping contract; renaming a
12
+ * question id here requires updating that mapping too.
13
+ *
14
+ * Copy is translated (next-intl "contact" namespace, "intake.*" keys —
15
+ * see apps/web/messages/en.json) rather than hardcoded, consistent with
16
+ * ContactForm's own `useTranslations("contact")` usage. The wrapper resolves
17
+ * the strings via its own scoped translator and passes them in as plain
18
+ * data (`ContactIntakeCopy`) rather than passing the translator function
19
+ * itself — next-intl's `t` is typed to a specific literal key union per
20
+ * call site, which doesn't assign cleanly to a generic `(key: string) =>
21
+ * string` parameter; plain strings sidestep that entirely and keep this
22
+ * file decoupled from next-intl's types.
23
+ */
24
+ import { intakeScriptSchema } from "@working-theory/validation";
25
+ import type { IntakeScript } from "@working-theory/validation";
26
+
27
+ export interface ContactIntakeCopy {
28
+ nameAck: string;
29
+ namePrompt: string;
30
+ messageAck: string;
31
+ messagePrompt: string;
32
+ emailAck: string;
33
+ emailPrompt: string;
34
+ skipMessage: string;
35
+ completionMessage: string;
36
+ }
37
+
38
+ export function buildContactIntakeScript(
39
+ copy: ContactIntakeCopy,
40
+ ): IntakeScript {
41
+ return intakeScriptSchema.parse({
42
+ slug: "contact-intake",
43
+ questions: [
44
+ {
45
+ id: "name",
46
+ kind: "open",
47
+ acknowledgement: copy.nameAck,
48
+ prompt: copy.namePrompt,
49
+ },
50
+ {
51
+ id: "message",
52
+ kind: "open",
53
+ acknowledgement: copy.messageAck,
54
+ prompt: copy.messagePrompt,
55
+ },
56
+ {
57
+ id: "email",
58
+ kind: "open",
59
+ acknowledgement: copy.emailAck,
60
+ prompt: copy.emailPrompt,
61
+ },
62
+ ],
63
+ skip: { message: copy.skipMessage },
64
+ completion: { message: copy.completionMessage },
65
+ });
66
+ }
@@ -1,13 +1,16 @@
1
1
  // Contact page. Renders sections via renderPage (sections mode) then appends
2
- // ContactForm as a wrapper. JSON-LD and translations handled by renderPage.
2
+ // ConversationalIntakeWrapper the chat-style intake (#4534, epic #4521's
3
+ // integration leg), which itself renders the existing ContactForm on Skip
4
+ // or a failed auto-submit. JSON-LD and translations handled by renderPage.
3
5
  import type {
4
6
  getMdxSource as defaultGetMdxSource,
5
7
  getPageSections as defaultGetPageSections,
6
8
  } from "@working-theory/page-renderer";
7
- import { renderPage } from "@working-theory/page-renderer";
9
+ import { renderPage, renderPageMetadata } from "@working-theory/page-renderer";
10
+ import type { Metadata } from "next";
8
11
  import type { ReactElement } from "react";
9
12
 
10
- import { ContactForm } from "./ContactForm";
13
+ import { ConversationalIntakeWrapper } from "./ConversationalIntakeWrapper";
11
14
 
12
15
  interface Props {
13
16
  params: Promise<{ locale: string }>;
@@ -15,6 +18,13 @@ interface Props {
15
18
  getPageSections?: typeof defaultGetPageSections;
16
19
  }
17
20
 
21
+ export async function generateMetadata({
22
+ params,
23
+ }: Pick<Props, "params">): Promise<Metadata> {
24
+ const { locale } = await params;
25
+ return renderPageMetadata({ type: "ContactPage", slug: "contact", locale });
26
+ }
27
+
18
28
  export default async function Page({
19
29
  params,
20
30
  getMdxSource,
@@ -32,7 +42,7 @@ export default async function Page({
32
42
  return (
33
43
  <>
34
44
  {rendered}
35
- <ContactForm />
45
+ <ConversationalIntakeWrapper />
36
46
  </>
37
47
  );
38
48
  }
@@ -1,7 +1,12 @@
1
1
  // Course detail page. Reads the Course MDX for the given slug, renders via
2
2
  // @working-theory/page-renderer, and emits a JSON-LD <script>. Unknown slugs trigger 404.
3
3
  import type { getMdxSource as defaultGetMdxSource } from "@working-theory/page-renderer";
4
- import { getMdxSlugs, renderPage } from "@working-theory/page-renderer";
4
+ import {
5
+ getMdxSlugs,
6
+ renderPage,
7
+ renderPageMetadata,
8
+ } from "@working-theory/page-renderer";
9
+ import type { Metadata } from "next";
5
10
  import type { ReactElement } from "react";
6
11
 
7
12
  interface Props {
@@ -9,6 +14,13 @@ interface Props {
9
14
  getMdxSource?: typeof defaultGetMdxSource;
10
15
  }
11
16
 
17
+ export async function generateMetadata({
18
+ params,
19
+ }: Pick<Props, "params">): Promise<Metadata> {
20
+ const { locale, slug } = await params;
21
+ return renderPageMetadata({ type: "Course", slug, locale });
22
+ }
23
+
12
24
  export async function generateStaticParams({
13
25
  params,
14
26
  }: {