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,139 @@
1
+ /**
2
+ * SignupsTable — the comparison view's paginated signups table (#4665 D10).
3
+ *
4
+ * App-local composition: `Table` primitive + a "next page" link.
5
+ *
6
+ * Deviation from the spec's descriptive composition note ("Pagination (link
7
+ * form, `?cursor=`)"): the `Pagination` primitive
8
+ * (`packages/ui/src/primitives/Pagination.tsx`) is built around a
9
+ * `currentPage`/`totalPages` numeric model — the signups list is
10
+ * cursor-paginated (`nextCursor: string | null`, D6/D7) with no total-page
11
+ * count and no `prevCursor` from the server, so `Pagination`'s numeric API
12
+ * cannot express this list's actual pagination shape. Rather than force-fit
13
+ * it, this renders a single "next" `Link` (RSC-safe, no client boundary)
14
+ * when `nextCursor` is present — the honest shape of a forward-only cursor
15
+ * list. Documented in the PR body.
16
+ */
17
+
18
+ import {
19
+ Link,
20
+ Table,
21
+ TableBody,
22
+ TableCaption,
23
+ TableCell,
24
+ TableHead,
25
+ TableHeader,
26
+ TableRow,
27
+ } from "@working-theory/ui/primitives";
28
+ import type { WaitlistSignupRow } from "@working-theory/validation";
29
+ import type { ReactElement } from "react";
30
+
31
+ export interface SignupsTableLabels {
32
+ caption: string;
33
+ columnEmail: string;
34
+ columnWaitlist: string;
35
+ columnSource: string;
36
+ columnCreated: string;
37
+ columnTier: string;
38
+ columnScore: string;
39
+ unavailable: string;
40
+ next: string;
41
+ }
42
+
43
+ export interface SignupsTableProps {
44
+ locale: string;
45
+ experimentId: string;
46
+ waitlist?: string;
47
+ /**
48
+ * `created`, `score`, and `tierDisplay` are pre-formatted by the caller
49
+ * (Rule 10 — this component never formats a value itself); `score: null`
50
+ * means "not scored" and renders `labels.unavailable`, matching the
51
+ * caller's own `format.ts` sentinel handling elsewhere on this surface.
52
+ * `tierDisplay` is `signup.tier` unchanged for every experiment except
53
+ * `targeting-maturity`, where the caller maps it to its Level label
54
+ * (`format.ts#targetingMaturityLevel`, D5, AC-5) — `null` renders
55
+ * `labels.unavailable`, exactly as a raw `signup.tier` of `null` already did.
56
+ */
57
+ rows: Array<{
58
+ signup: WaitlistSignupRow;
59
+ created: string;
60
+ score: string | null;
61
+ tierDisplay: string | null;
62
+ }>;
63
+ nextCursor: string | null;
64
+ labels: SignupsTableLabels;
65
+ }
66
+
67
+ function buildNextHref(
68
+ locale: string,
69
+ experimentId: string,
70
+ waitlist: string | undefined,
71
+ nextCursor: string,
72
+ ): string {
73
+ const params = new URLSearchParams();
74
+ if (waitlist) params.set("waitlist", waitlist);
75
+ params.set("cursor", nextCursor);
76
+ return `/${locale}/waitlist/${experimentId}?${params.toString()}`;
77
+ }
78
+
79
+ export function SignupsTable({
80
+ locale,
81
+ experimentId,
82
+ waitlist,
83
+ rows,
84
+ nextCursor,
85
+ labels,
86
+ }: SignupsTableProps): ReactElement {
87
+ return (
88
+ <div data-testid="signups-table-wrapper">
89
+ <Table data-testid="signups-table">
90
+ <TableCaption>{labels.caption}</TableCaption>
91
+ <TableHeader>
92
+ <TableRow>
93
+ <TableHead scope="col">{labels.columnEmail}</TableHead>
94
+ <TableHead scope="col">{labels.columnWaitlist}</TableHead>
95
+ <TableHead scope="col">{labels.columnSource}</TableHead>
96
+ <TableHead scope="col">{labels.columnCreated}</TableHead>
97
+ <TableHead scope="col">{labels.columnTier}</TableHead>
98
+ <TableHead scope="col">{labels.columnScore}</TableHead>
99
+ </TableRow>
100
+ </TableHeader>
101
+ <TableBody>
102
+ {rows.map(({ signup, created, score, tierDisplay }) => (
103
+ <TableRow
104
+ key={signup.leadId}
105
+ data-testid={`signups-table-row-${signup.leadId}`}
106
+ >
107
+ <TableCell>
108
+ <Link
109
+ href={`/${locale}/waitlist/${experimentId}/signups/${signup.leadId}`}
110
+ >
111
+ {signup.email}
112
+ </Link>
113
+ </TableCell>
114
+ <TableCell>{signup.waitlistKey}</TableCell>
115
+ <TableCell>{signup.source}</TableCell>
116
+ <TableCell>{created}</TableCell>
117
+ <TableCell>{tierDisplay ?? labels.unavailable}</TableCell>
118
+ <TableCell>{score ?? labels.unavailable}</TableCell>
119
+ </TableRow>
120
+ ))}
121
+ </TableBody>
122
+ </Table>
123
+ {nextCursor !== null ? (
124
+ <div className="mt-3 flex justify-end">
125
+ <Link
126
+ href={buildNextHref(locale, experimentId, waitlist, nextCursor)}
127
+ data-testid="signups-table-next"
128
+ // 24px hit-area floor (Rule 14) — a bare inline link's
129
+ // line-height box falls short on its own (#4665, caught on the
130
+ // worker's own re-inspection of the render evidence).
131
+ className="inline-flex min-h-6 items-center px-2"
132
+ >
133
+ {labels.next}
134
+ </Link>
135
+ </div>
136
+ ) : null}
137
+ </div>
138
+ );
139
+ }
@@ -0,0 +1,85 @@
1
+ /**
2
+ * WaitlistComparisonTabs — the comparison view's per-waitlist tab strip
3
+ * (#4665 D10): one tab per waitlist, each showing a by-source
4
+ * MetricComparisonTable + one DistributionList per question.
5
+ *
6
+ * Composes the Tabs primitive (a client boundary — `packages/ui/src/
7
+ * primitives/Tabs.tsx` carries its own "use client") with server-rendered
8
+ * children; this file itself holds no hooks and stays a plain RSC-composable
9
+ * component.
10
+ */
11
+
12
+ import {
13
+ DistributionList,
14
+ MetricComparisonTable,
15
+ } from "@working-theory/ui/blocks";
16
+ import { Tabs, TabsContent, TabsList, TabsTrigger } from "@working-theory/ui/primitives";
17
+ import type { ReactElement } from "react";
18
+
19
+ export interface WaitlistTab {
20
+ key: string;
21
+ label: string;
22
+ bySourceCaption: string;
23
+ /** Header label for the leading (source-name) row-header column. */
24
+ bySourceRowHeaderLabel: string;
25
+ bySourceColumns: Array<{ key: string; label: string }>;
26
+ bySourceRows: Array<{
27
+ key: string;
28
+ label: string;
29
+ cells: Record<string, { display: string; value: number | null }>;
30
+ }>;
31
+ distributions: Array<{
32
+ questionId: string;
33
+ title: string;
34
+ answered: number;
35
+ answeredLabel: string;
36
+ emptyLabel: string;
37
+ items: Array<{ label: string; count: number; share: number; display: string }>;
38
+ }>;
39
+ }
40
+
41
+ export interface WaitlistComparisonTabsProps {
42
+ tabs: WaitlistTab[];
43
+ }
44
+
45
+ export function WaitlistComparisonTabs({
46
+ tabs,
47
+ }: WaitlistComparisonTabsProps): ReactElement | null {
48
+ if (tabs.length === 0) return null;
49
+
50
+ return (
51
+ <Tabs defaultValue={tabs[0]!.key} data-testid="waitlist-comparison-tabs">
52
+ <TabsList>
53
+ {tabs.map((tab) => (
54
+ <TabsTrigger key={tab.key} value={tab.key}>
55
+ {tab.label}
56
+ </TabsTrigger>
57
+ ))}
58
+ </TabsList>
59
+ {tabs.map((tab) => (
60
+ <TabsContent key={tab.key} value={tab.key}>
61
+ <div className="flex flex-col gap-6">
62
+ <MetricComparisonTable
63
+ caption={tab.bySourceCaption}
64
+ columns={tab.bySourceColumns}
65
+ rows={tab.bySourceRows}
66
+ rowHeaderLabel={tab.bySourceRowHeaderLabel}
67
+ />
68
+ <div className="flex flex-col gap-4">
69
+ {tab.distributions.map((distribution) => (
70
+ <DistributionList
71
+ key={distribution.questionId}
72
+ title={distribution.title}
73
+ answered={distribution.answered}
74
+ answeredLabel={distribution.answeredLabel}
75
+ items={distribution.items}
76
+ emptyLabel={distribution.emptyLabel}
77
+ />
78
+ ))}
79
+ </div>
80
+ </div>
81
+ </TabsContent>
82
+ ))}
83
+ </Tabs>
84
+ );
85
+ }
@@ -0,0 +1,67 @@
1
+ /**
2
+ * Shared status/mode chip mapping for the waitlist operator dashboard
3
+ * (#4665 D10) — used by ExperimentsTable's list rows and the comparison
4
+ * view's DetailHeader typeBadge.
5
+ *
6
+ * Composes the Badge primitive (visible text + a semantic variant), not the
7
+ * icon-only StatusBadge primitive — StatusBadge renders a decorative glyph
8
+ * with an `aria-label` and carries no visible text
9
+ * (`packages/ui/src/primitives/StatusBadge.tsx`), so it cannot satisfy AC1's
10
+ * "status visible" requirement on its own. This mirrors the established
11
+ * `PaymentStatusBadge` block precedent (`packages/ui/src/blocks/
12
+ * PaymentStatusBadge.tsx`): a labeled status chip is a `Badge` with a status
13
+ * → variant map, not `StatusBadge`.
14
+ */
15
+
16
+ import { Badge, type BadgeProps } from "@working-theory/ui/primitives";
17
+ import type { WaitlistMode, WaitlistStatus } from "@working-theory/validation";
18
+ import type { ReactElement } from "react";
19
+
20
+ const STATUS_VARIANT: Record<WaitlistStatus, BadgeProps["variant"]> = {
21
+ live: "success",
22
+ closed: "muted",
23
+ draft: "outline",
24
+ };
25
+
26
+ const MODE_VARIANT: Record<WaitlistMode, BadgeProps["variant"]> = {
27
+ "lead-capture": "secondary",
28
+ qualifying: "info",
29
+ paid: "featured",
30
+ // #4767 — the register's fourth mode; the engine/UI that reads it are
31
+ // sibling children of Epic #4758 (out of scope here). A placeholder
32
+ // variant only, so `Record<WaitlistMode, …>` stays exhaustive.
33
+ recommending: "success",
34
+ };
35
+
36
+ export function ExperimentStatusBadge({
37
+ status,
38
+ label,
39
+ }: {
40
+ status: WaitlistStatus;
41
+ label: string;
42
+ }): ReactElement {
43
+ return (
44
+ <Badge
45
+ variant={STATUS_VARIANT[status]}
46
+ size="sm"
47
+ role="status"
48
+ data-testid="experiment-status-badge"
49
+ >
50
+ {label}
51
+ </Badge>
52
+ );
53
+ }
54
+
55
+ export function ExperimentModeBadge({
56
+ mode,
57
+ label,
58
+ }: {
59
+ mode: WaitlistMode;
60
+ label: string;
61
+ }): ReactElement {
62
+ return (
63
+ <Badge variant={MODE_VARIANT[mode]} size="sm" data-testid="experiment-mode-badge">
64
+ {label}
65
+ </Badge>
66
+ );
67
+ }
@@ -0,0 +1,111 @@
1
+ /**
2
+ * Display formatting for the waitlist operator dashboard (#4665 D4/D10, UI).
3
+ *
4
+ * The ONE place a number, rate, or date becomes a string for this surface
5
+ * (Rule 10 — formatting happens in `apps/web`, never inside a
6
+ * `packages/ui/src/blocks/*` component). Every formatter here is
7
+ * locale-aware; the caller supplies the active `[locale]` route segment.
8
+ *
9
+ * `null` is D4's "unavailable" sentinel (an unscored / non-qualifying
10
+ * experiment) — rendered as an em dash, never `0` or an empty string, so an
11
+ * unscored row is never misread as a zero row.
12
+ */
13
+
14
+ import { formatNumber, formatPercent } from "@working-theory/web-i18n";
15
+
16
+ /** D4/D9's "no value" rendering — never `0`, never blank. */
17
+ export const UNAVAILABLE = "—";
18
+
19
+ /** Format a raw count (signups, tier counts, question `answered`). */
20
+ export function formatCount(value: number, locale: string): string {
21
+ return formatNumber(value, locale);
22
+ }
23
+
24
+ /**
25
+ * Format a rate/share (0–1) as a percentage, or the unavailable sentinel when
26
+ * `null` (D4 — `qualified`/`qualificationRate` are `null` for a
27
+ * `lead-capture` experiment; a distribution `share` is always a number).
28
+ */
29
+ export function formatRate(value: number | null, locale: string): string {
30
+ if (value === null) return UNAVAILABLE;
31
+ return formatPercent(value, locale);
32
+ }
33
+
34
+ /** Format an ISO-8601 datetime for display (list/comparison/drill-down). */
35
+ export function formatDateTime(iso: string, locale: string): string {
36
+ return new Intl.DateTimeFormat(locale, {
37
+ dateStyle: "medium",
38
+ timeStyle: "short",
39
+ }).format(new Date(iso));
40
+ }
41
+
42
+ // ─── FEAT-074 U4a (#4715) — the targeting-maturity Level display ────────────
43
+ //
44
+ // Presentation-only mapping (spec D5): `level_3` -> the integer `3`,
45
+ // `level_0` -> `0` (the Gate). Scoped to the ONE experiment this Level
46
+ // vocabulary applies to — every other experiment's tier renders unchanged
47
+ // (AC-5's "a non-targeting experiment renders exactly as before"). Pure
48
+ // parsing only, no i18n — the CALLER (the page, which already owns every
49
+ // `getTranslations` call on this surface) turns the returned number into
50
+ // the "Level N" / Gate string via its own `t()`.
51
+
52
+ /** The one experiment this Level vocabulary applies to. */
53
+ export const TARGETING_MATURITY_EXPERIMENT_KEY = "targeting-maturity";
54
+
55
+ const LEVEL_TIER_RE = /^level_(\d+)$/;
56
+
57
+ /**
58
+ * The Level number for a `targeting-maturity` `level_N` tier — `0` for the
59
+ * Gate, `N` otherwise. `null` for any other experiment, a `null` tier, or an
60
+ * unrecognized tier shape — the caller falls back to the raw tier /
61
+ * `labels.unavailable` unchanged (D5).
62
+ */
63
+ export function targetingMaturityLevel(
64
+ tier: string | null,
65
+ experimentKey: string,
66
+ ): number | null {
67
+ if (experimentKey !== TARGETING_MATURITY_EXPERIMENT_KEY || tier === null) return null;
68
+ const match = LEVEL_TIER_RE.exec(tier);
69
+ return match ? Number(match[1]) : null;
70
+ }
71
+
72
+ /**
73
+ * The `ref(<name>)` def names that DECIDED the matched tier, for the
74
+ * ScoreTrace drill-down's "deciding factors" section (AC-5) — every
75
+ * `kind: "predicate"` entry in a `RuleTraceEntry[]`-shaped `ruleTrace`
76
+ * whose `tier` equals the signup's own tier AND whose `result` is `true`,
77
+ * with every `ref(...)` segment in its `path` extracted (there is no
78
+ * separate `def` field — `evaluatePredicateNode`'s own path-building embeds
79
+ * each traversed def as `ref(<name>)`). Duck-typed against `unknown`
80
+ * (`ruleTrace` is `z.unknown()` at this boundary — `waitlist-dashboard.ts`'s
81
+ * schema comment), mirroring the identical extraction the backend's
82
+ * `playbook-compile.ts` uses for its Level-1 loop-back framing — this is
83
+ * the read-side counterpart, not a fork of it (no shared module: the two
84
+ * sides are in different apps, which may not cross-import — `architecture.yaml`).
85
+ * `[]` for any other experiment (AC-5's non-targeting byte-unchanged floor).
86
+ */
87
+ export function decidingDefsFor(
88
+ tier: string | null,
89
+ ruleTrace: unknown,
90
+ experimentKey: string,
91
+ ): string[] {
92
+ if (experimentKey !== TARGETING_MATURITY_EXPERIMENT_KEY || tier === null) return [];
93
+ if (!Array.isArray(ruleTrace)) return [];
94
+ const seen = new Set<string>();
95
+ const names: string[] = [];
96
+ for (const entry of ruleTrace) {
97
+ if (typeof entry !== "object" || entry === null) continue;
98
+ const e = entry as Record<string, unknown>;
99
+ if (e["kind"] !== "predicate" || e["tier"] !== tier || e["result"] !== true) continue;
100
+ const path = e["path"];
101
+ if (typeof path !== "string") continue;
102
+ for (const m of path.matchAll(/ref\(([^)]+)\)/g)) {
103
+ const name = m[1]!;
104
+ if (!seen.has(name)) {
105
+ seen.add(name);
106
+ names.push(name);
107
+ }
108
+ }
109
+ }
110
+ return names;
111
+ }
@@ -0,0 +1,179 @@
1
+ /**
2
+ * /[locale]/waitlist — the operator dashboard's list + rollup view
3
+ * (#4665 D10, AC1).
4
+ *
5
+ * RSC: reads `GET /v1/waitlist-experiments` through
6
+ * `@working-theory/api-client`'s `waitlistExperiments.list`, forwarding the
7
+ * caller's cookie session (`../../../../lib/server-api.js`, D9). A 401/403
8
+ * renders `ForbiddenState` (no leak of why — §2.2 Security); every other
9
+ * error rethrows to the route's error boundary.
10
+ */
11
+
12
+ import { waitlistExperiments } from "@working-theory/api-client";
13
+ import { PageHeader } from "@working-theory/ui/blocks";
14
+ import { Container, Heading, Text } from "@working-theory/ui/primitives";
15
+ import type { WaitlistMode, WaitlistStatus } from "@working-theory/validation";
16
+ import type { Metadata } from "next";
17
+ import { getTranslations } from "next-intl/server";
18
+ import type { ReactElement } from "react";
19
+
20
+ import { ApiError, forwardedCookieInit, unwrapApiResult } from "../../../../lib/server-api";
21
+
22
+ import { ExperimentsTable } from "./components/ExperimentsTable";
23
+ import { ForbiddenState } from "./components/ForbiddenState";
24
+ import { OfferFunnelTable } from "./components/OfferFunnelTable";
25
+ import { RollupTicker } from "./components/RollupTicker";
26
+ import { formatCount, formatDateTime, formatRate } from "./format";
27
+
28
+ interface Props {
29
+ params: Promise<{ locale: string }>;
30
+ }
31
+
32
+ export async function generateMetadata({ params }: Props): Promise<Metadata> {
33
+ const { locale } = await params;
34
+ const t = await getTranslations({ locale, namespace: "waitlistDashboard.list" });
35
+ return { title: t("title"), description: t("description") };
36
+ }
37
+
38
+ export default async function WaitlistListPage({ params }: Props): Promise<ReactElement> {
39
+ const { locale } = await params;
40
+ const t = await getTranslations({ locale, namespace: "waitlistDashboard" });
41
+
42
+ let data;
43
+ try {
44
+ data = await unwrapApiResult(waitlistExperiments.list(await forwardedCookieInit()));
45
+ } catch (err) {
46
+ if (err instanceof ApiError && (err.status === 401 || err.status === 403)) {
47
+ return (
48
+ <ForbiddenState
49
+ headline={t("common.forbiddenHeadline")}
50
+ description={t("common.forbiddenDescription")}
51
+ />
52
+ );
53
+ }
54
+ throw err;
55
+ }
56
+
57
+ const statusLabels: Record<WaitlistStatus, string> = {
58
+ live: t("status.live"),
59
+ closed: t("status.closed"),
60
+ draft: t("status.draft"),
61
+ };
62
+ const modeLabels: Record<WaitlistMode, string> = {
63
+ "lead-capture": t("mode.lead-capture"),
64
+ qualifying: t("mode.qualifying"),
65
+ paid: t("mode.paid"),
66
+ // #4767 — the register's fourth mode; the engine/UI that reads it are
67
+ // sibling children of Epic #4758 (out of scope here). A placeholder
68
+ // label only, so `Record<WaitlistMode, …>` stays exhaustive.
69
+ recommending: t("mode.recommending"),
70
+ };
71
+
72
+ const rows = data.experiments.map((experiment) => ({
73
+ experiment,
74
+ raw: formatCount(experiment.counts.raw, locale),
75
+ qualified:
76
+ experiment.counts.qualified === null
77
+ ? t("common.unavailable")
78
+ : formatCount(experiment.counts.qualified, locale),
79
+ rate: formatRate(experiment.counts.qualificationRate, locale),
80
+ updated: formatDateTime(experiment.updatedAt, locale),
81
+ }));
82
+
83
+ // #4839 — one per-offer funnel per recommending experiment (the register's
84
+ // `offers[]` order), rendered under the experiments table.
85
+ const offerFunnels = data.experiments.flatMap((experiment) =>
86
+ experiment.counts.offers
87
+ ? [
88
+ {
89
+ experimentId: experiment.id,
90
+ experimentKey: experiment.key,
91
+ rows: experiment.counts.offers.map((offer) => ({
92
+ offerKey: offer.offerKey,
93
+ recommended: formatCount(offer.recommended, locale),
94
+ saved: formatCount(offer.saved, locale),
95
+ checkedOut: formatCount(offer.checkedOut, locale),
96
+ })),
97
+ },
98
+ ]
99
+ : [],
100
+ );
101
+ const offerLabels = {
102
+ captionPrefix: t("list.offers.captionPrefix"),
103
+ columnOffer: t("list.offers.columnOffer"),
104
+ columnRecommended: t("list.offers.columnRecommended"),
105
+ columnSaved: t("list.offers.columnSaved"),
106
+ columnCheckedOut: t("list.offers.columnCheckedOut"),
107
+ };
108
+
109
+ return (
110
+ <main className="min-h-screen bg-surface text-fg" data-testid="waitlist-list-page">
111
+ <PageHeader />
112
+ <Container className="flex flex-col gap-6 py-section">
113
+ <div>
114
+ <Heading level={1}>{t("list.title")}</Heading>
115
+ <Text as="p" variant="body-sm" tone="muted" className="mt-1">
116
+ {t("list.description")}
117
+ </Text>
118
+ </div>
119
+
120
+ <RollupTicker
121
+ ariaLabel={t("list.rollupAriaLabel")}
122
+ labels={{
123
+ live: t("list.rollup.live"),
124
+ closed: t("list.rollup.closed"),
125
+ draft: t("list.rollup.draft"),
126
+ raw: t("list.rollup.raw"),
127
+ qualified: t("list.rollup.qualified"),
128
+ rate: t("list.rollup.rate"),
129
+ sites: t("list.rollup.sites"),
130
+ }}
131
+ values={{
132
+ live: formatCount(data.rollup.experiments.live, locale),
133
+ closed: formatCount(data.rollup.experiments.closed, locale),
134
+ draft: formatCount(data.rollup.experiments.draft, locale),
135
+ raw: formatCount(data.rollup.raw, locale),
136
+ qualified:
137
+ data.rollup.qualified === null
138
+ ? t("common.unavailable")
139
+ : formatCount(data.rollup.qualified, locale),
140
+ rate: formatRate(data.rollup.qualificationRate, locale),
141
+ ...(data.scope === "all"
142
+ ? { sites: formatCount(data.rollup.sites, locale) }
143
+ : {}),
144
+ }}
145
+ />
146
+
147
+ <ExperimentsTable
148
+ locale={locale}
149
+ rows={rows}
150
+ labels={{
151
+ caption: t("list.table.caption"),
152
+ columnKey: t("list.table.columnKey"),
153
+ columnMode: t("list.table.columnMode"),
154
+ columnStatus: t("list.table.columnStatus"),
155
+ columnWaitlists: t("list.table.columnWaitlists"),
156
+ columnRaw: t("list.table.columnRaw"),
157
+ columnQualified: t("list.table.columnQualified"),
158
+ columnRate: t("list.table.columnRate"),
159
+ columnUpdated: t("list.table.columnUpdated"),
160
+ emptyHeadline: t("list.table.emptyHeadline"),
161
+ emptyDescription: t("list.table.emptyDescription"),
162
+ statusLabel: (status) => statusLabels[status as WaitlistStatus],
163
+ modeLabel: (mode) => modeLabels[mode as WaitlistMode],
164
+ }}
165
+ />
166
+
167
+ {offerFunnels.map((funnel) => (
168
+ <OfferFunnelTable
169
+ key={funnel.experimentId}
170
+ experimentId={funnel.experimentId}
171
+ experimentKey={funnel.experimentKey}
172
+ rows={funnel.rows}
173
+ labels={offerLabels}
174
+ />
175
+ ))}
176
+ </Container>
177
+ </main>
178
+ );
179
+ }
@@ -4,8 +4,10 @@ import type {
4
4
  getMdxSource as defaultGetMdxSource,
5
5
  getPageSections as defaultGetPageSections} from "@working-theory/page-renderer";
6
6
  import {
7
- renderPage
7
+ renderPage,
8
+ renderPageMetadata
8
9
  } from "@working-theory/page-renderer";
10
+ import type { Metadata } from "next";
9
11
  import type { ReactElement } from "react";
10
12
 
11
13
  interface Props {
@@ -14,6 +16,13 @@ interface Props {
14
16
  getPageSections?: typeof defaultGetPageSections;
15
17
  }
16
18
 
19
+ export async function generateMetadata({
20
+ params,
21
+ }: Pick<Props, "params">): Promise<Metadata> {
22
+ const { locale } = await params;
23
+ return renderPageMetadata({ type: "AboutPage", slug: "about", locale });
24
+ }
25
+
17
26
  export default async function Page({
18
27
  params,
19
28
  getMdxSource,
@@ -6,7 +6,7 @@
6
6
  * `docsPageJsonLd` builder is exported from `@working-theory/fumadocs-schema`.
7
7
  */
8
8
  import { blogListJsonLd, blogPostingJsonLd } from "@working-theory/fumadocs-schema";
9
- import { JsonLdScript } from "@working-theory/page-renderer";
9
+ import { JsonLdScript, emptyStateVisible } from "@working-theory/page-renderer";
10
10
  import { BlogList, BlogPost } from "@working-theory/ui";
11
11
  import type { Metadata } from "next";
12
12
  import { notFound } from "next/navigation";
@@ -144,6 +144,11 @@ export default async function BlogRoute(
144
144
  });
145
145
 
146
146
  if (pages.length === 0) {
147
+ // #4154: uniform with every other contentless surface — the empty-blog
148
+ // placeholder is an authoring affordance, so it renders outside production
149
+ // and 404s inside it rather than serving a stock page to a visitor.
150
+ if (!emptyStateVisible()) notFound();
151
+
147
152
  return (
148
153
  <>
149
154
  <JsonLdScript data={indexJsonLd} />
@@ -5,7 +5,9 @@ import {
5
5
  getMdxSource as defaultGetMdxSource,
6
6
  getMdxSlugs,
7
7
  renderPage,
8
+ renderPageMetadata,
8
9
  } from "@working-theory/page-renderer";
10
+ import type { Metadata } from "next";
9
11
  import { notFound } from "next/navigation";
10
12
  import type { ReactElement } from "react";
11
13
 
@@ -14,6 +16,13 @@ interface Props {
14
16
  getMdxSource?: typeof defaultGetMdxSource;
15
17
  }
16
18
 
19
+ export async function generateMetadata({
20
+ params,
21
+ }: Pick<Props, "params">): Promise<Metadata> {
22
+ const { locale, slug } = await params;
23
+ return renderPageMetadata({ type: "JobPosting", slug, locale });
24
+ }
25
+
17
26
  export async function generateStaticParams({
18
27
  params,
19
28
  }: {