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,499 @@
1
+ /**
2
+ * Day-one structured-data check for the site this app ships (#5021).
3
+ *
4
+ * Two checks, both delegating to `@working-theory/schema-validator` so they
5
+ * get stricter as the validator does (the #5019 `mainEntity` rule included):
6
+ *
7
+ * 1. Content-driven — every content page under `content/<locale>/<Type>/`
8
+ * is built into JSON-LD through `pageToJsonLd`, the same path the app
9
+ * takes at render time (builders + pack + breadcrumb + section
10
+ * contributions), flattened, and validated for its type.
11
+ * 2. Route-emission — every `page.tsx` under `app/[locale]/` is rendered
12
+ * with the site's own content and must emit a
13
+ * `<script type="application/ld+json">` that validates for the route's
14
+ * type. A route that renders nothing, or that is not built on
15
+ * `renderPage`, fails unless it is listed in `NO_STRUCTURED_DATA_ROUTES`
16
+ * with a reason.
17
+ *
18
+ * Nothing here is a fixture list: the content walk reads whatever the site
19
+ * has under `content/`, and the route walk reads whatever the app has under
20
+ * `app/[locale]/`. A page written today is checked today.
21
+ *
22
+ * Lives in `lib/__tests__/` because `create-ailk`'s template snapshot strips
23
+ * `apps/web/__tests__` and `apps/web/app/[locale]/__tests__` (#4154) — a
24
+ * scaffolded site keeps this file. `packages/create-ailk/__tests__/
25
+ * structured-data-check.test.ts` pins that.
26
+ *
27
+ * Pages are enumerated through the content adapter rather than
28
+ * `content/.compiled/manifest.json`: the manifest is written by
29
+ * `tools/content-compile`, which the scaffold does not ship, and the adapter
30
+ * reads the same files with the same frontmatter schema the manifest is
31
+ * compiled from. See docs/internal/decisions/scaffold-structured-data-check.md.
32
+ */
33
+ import { existsSync, readdirSync, readFileSync } from "node:fs";
34
+ import { basename, dirname, join, relative, resolve, sep } from "node:path";
35
+
36
+ import { render } from "@testing-library/react";
37
+ import type {
38
+ ContentPage,
39
+ SchemaOrgType as PageType,
40
+ } from "@working-theory/content-adapters";
41
+ import {
42
+ SCHEMA_ORG_TYPES,
43
+ createMdxAdapter,
44
+ } from "@working-theory/content-adapters";
45
+ import {
46
+ getPageSections,
47
+ pageToJsonLd,
48
+ type JsonLdObject,
49
+ } from "@working-theory/page-renderer";
50
+ import {
51
+ knownSchemaOrgTypes,
52
+ validatePageGraph,
53
+ type SchemaOrgType,
54
+ type ValidationError,
55
+ } from "@working-theory/schema-validator";
56
+ import type { ReactElement } from "react";
57
+ import { parse as parseYaml } from "yaml";
58
+
59
+ // Detail routes call notFound() on a missing page; a thrown error is how the
60
+ // test sees it (the same shape the per-surface suites use).
61
+ jest.mock("next/navigation", () => ({
62
+ __esModule: true,
63
+ ...jest.requireActual<Record<string, unknown>>("next/navigation"),
64
+ notFound: jest.fn(() => {
65
+ throw new Error("NEXT_NOT_FOUND");
66
+ }),
67
+ }));
68
+
69
+ // Telemetry is a side-effect boundary (the contact route's intake wrapper
70
+ // reports through it); the same mock the contact suites use.
71
+ jest.mock("@working-theory/analytics/server", () => ({
72
+ track: jest.fn().mockResolvedValue(undefined),
73
+ }));
74
+
75
+ // ── Where the site's content and routes live ────────────────────────────────
76
+
77
+ const CONTENT_ROOT =
78
+ process.env.AILK_CONTENT_ROOT ?? resolve(__dirname, "../../../../content");
79
+ const ROUTE_ROOT = resolve(__dirname, "../../app/[locale]");
80
+ const SITE_ROOT = resolve(__dirname, "../../../..");
81
+
82
+ /**
83
+ * The site's default locale, read the way `next.config.mjs` bakes it
84
+ * (`project.yaml` → `product.default_locale`, "en" when absent). The env var
85
+ * is the baked value at build time; under jest it is normally unset, so the
86
+ * file is the source and the env var only an override.
87
+ */
88
+ function readDefaultLocale(): string {
89
+ const fromEnv = process.env.NEXT_PUBLIC_DEFAULT_LOCALE;
90
+ if (fromEnv !== undefined && fromEnv.length > 0) return fromEnv;
91
+ const file = join(SITE_ROOT, "project.yaml");
92
+ if (!existsSync(file)) return "en";
93
+ const doc = parseYaml(readFileSync(file, "utf8")) as {
94
+ product?: { default_locale?: unknown };
95
+ } | null;
96
+ const value = doc?.product?.default_locale;
97
+ return typeof value === "string" && value.length > 0 ? value : "en";
98
+ }
99
+ const DEFAULT_LOCALE = readDefaultLocale();
100
+
101
+ // `pageToJsonLd`'s content selector resolves `content/` from cwd; pin it to
102
+ // the same root this file walks so the route check renders the same pages.
103
+ if (process.env.AILK_CONTENT_ROOT === undefined) {
104
+ process.env.AILK_CONTENT_ROOT = CONTENT_ROOT;
105
+ }
106
+
107
+ // Mirrors the locale-directory rule in tools/content-compile and the adapter.
108
+ const LOCALE_RE = /^[a-z]{2}(-[A-Z]{2})?$/;
109
+
110
+ function subdirs(dir: string): string[] {
111
+ if (!existsSync(dir)) return [];
112
+ return readdirSync(dir, { withFileTypes: true })
113
+ .filter((e) => e.isDirectory())
114
+ .map((e) => e.name)
115
+ .sort();
116
+ }
117
+
118
+ /** `content/<locale>/<TypeDir>` → every (locale, type dir) pair on disk. */
119
+ function contentTypeDirs(): Array<{ locale: string; typeDir: string }> {
120
+ const out: Array<{ locale: string; typeDir: string }> = [];
121
+ for (const locale of subdirs(CONTENT_ROOT).filter((n) => LOCALE_RE.test(n))) {
122
+ for (const typeDir of subdirs(join(CONTENT_ROOT, locale))) {
123
+ // `_drafts/`-style hidden dirs are invisible to the adapter too.
124
+ if (typeDir.startsWith("_") || typeDir.startsWith(".")) continue;
125
+ out.push({ locale, typeDir });
126
+ }
127
+ }
128
+ return out;
129
+ }
130
+
131
+ const adapter = createMdxAdapter({
132
+ contentRoot: CONTENT_ROOT,
133
+ defaultLocale: DEFAULT_LOCALE,
134
+ });
135
+
136
+ // ── JSON-LD flatten + validate (the ten lines from tools/scrape-jsonld.ts) ──
137
+
138
+ function flattenBlocks(parsed: unknown): JsonLdObject[] {
139
+ if (parsed === null || typeof parsed !== "object") return [];
140
+ if (Array.isArray(parsed))
141
+ return parsed.flatMap((item) => flattenBlocks(item));
142
+ const obj = parsed as Record<string, unknown>;
143
+ if (Array.isArray(obj["@graph"])) {
144
+ const ctx = obj["@context"] as JsonLdObject["@context"] | undefined;
145
+ return (obj["@graph"] as unknown[]).flatMap((child) =>
146
+ flattenBlocks(child).map((b) =>
147
+ ctx === undefined || b["@context"] !== undefined
148
+ ? b
149
+ : { ...b, "@context": ctx },
150
+ ),
151
+ );
152
+ }
153
+ return [obj as JsonLdObject];
154
+ }
155
+
156
+ function formatErrors(errors: ValidationError[]): string {
157
+ return errors
158
+ .map(
159
+ (e) => ` [${e.rule}] ${e.path}: expected ${e.expected}, got ${e.actual}`,
160
+ )
161
+ .join("\n");
162
+ }
163
+
164
+ /** Validate flattened blocks for a page type; returns a readable failure or null. */
165
+ function validateFor(
166
+ blocks: JsonLdObject[],
167
+ type: string,
168
+ label: string,
169
+ ): string | null {
170
+ if (!knownSchemaOrgTypes.has(type)) {
171
+ return `${label}: type "${type}" has no validator rule — it is not a registered page type (see packages/schema-validator/src/rules.ts)`;
172
+ }
173
+ const result = validatePageGraph(blocks, type as SchemaOrgType);
174
+ if (result.valid) return null;
175
+ return `${label} (${type}):\n${formatErrors(result.errors)}`;
176
+ }
177
+
178
+ /** Every `application/ld+json` block in rendered HTML, flattened. */
179
+ function extractJsonLdBlocks(container: HTMLElement): JsonLdObject[] {
180
+ const scripts = container.querySelectorAll<HTMLScriptElement>(
181
+ 'script[type="application/ld+json"]',
182
+ );
183
+ const blocks: JsonLdObject[] = [];
184
+ for (const script of Array.from(scripts)) {
185
+ blocks.push(...flattenBlocks(JSON.parse(script.textContent ?? "")));
186
+ }
187
+ return blocks;
188
+ }
189
+
190
+ // ── 1. Content-driven: every content page builds valid JSON-LD ──────────────
191
+
192
+ describe("structured data — every content page builds valid JSON-LD", () => {
193
+ const typeDirs = contentTypeDirs();
194
+
195
+ it("finds content to check", () => {
196
+ // A site with no content directory at all is misconfigured, not clean.
197
+ expect(existsSync(CONTENT_ROOT)).toBe(true);
198
+ expect(typeDirs.length).toBeGreaterThan(0);
199
+ });
200
+
201
+ it.each(typeDirs)(
202
+ "content/$locale/$typeDir — every page validates as its type",
203
+ async ({ locale, typeDir }) => {
204
+ if (!(SCHEMA_ORG_TYPES as readonly string[]).includes(typeDir)) {
205
+ // Identity + sub-entity types (Organization, Person, BreadcrumbList,
206
+ // ContactPoint) hold sub-objects other pages' builders embed — never
207
+ // rendered as pages, and skipped by tools/content-compile the same
208
+ // way. Anything else is a directory nothing will ever render.
209
+ if (!knownSchemaOrgTypes.has(typeDir)) {
210
+ throw new Error(
211
+ `content/${locale}/${typeDir}: "${typeDir}" is not a registered page type — no route or builder will ever render it (see packages/content-adapters/src/types.ts SCHEMA_ORG_TYPES)`,
212
+ );
213
+ }
214
+ console.info(
215
+ `structured-data: content/${locale}/${typeDir} holds sub-objects, not pages — not checked`,
216
+ );
217
+ return;
218
+ }
219
+ const type = typeDir as PageType;
220
+
221
+ const pages: ContentPage[] = await adapter.listDetail(type, locale);
222
+ const dirPath = join(CONTENT_ROOT, locale, typeDir);
223
+ const mdxCount = readdirSync(dirPath).filter(
224
+ (n) => n.endsWith(".mdx") && !n.startsWith("_"),
225
+ ).length;
226
+ // The adapter drops a page whose frontmatter fails the schema; that
227
+ // page would render nothing, so count it here rather than lose it.
228
+ expect(pages.length).toBe(mdxCount);
229
+
230
+ const failures: string[] = [];
231
+ for (const page of pages) {
232
+ const sections = await getPageSections(page);
233
+ const jsonLd = await pageToJsonLd(page, locale, sections);
234
+ const failure = validateFor(
235
+ flattenBlocks(jsonLd),
236
+ page.type,
237
+ `${locale}/${page.type}/${page.slug}`,
238
+ );
239
+ if (failure !== null) failures.push(failure);
240
+ }
241
+ expect(failures).toEqual([]);
242
+ },
243
+ );
244
+ });
245
+
246
+ // ── 2. Route-emission: every route renders a valid JSON-LD script ───────────
247
+
248
+ /**
249
+ * Routes (relative to `app/[locale]/`) that do not emit page-level JSON-LD
250
+ * through `renderPage`, each with the reason. An entry matches its own
251
+ * directory only; `subtree: true` extends it to every route below it. A route
252
+ * that is neither a `renderPage` route nor listed here fails — that is the
253
+ * "fourteen routes emitting nothing" failure class this check exists for.
254
+ *
255
+ * Listing a route here is a statement that its JSON-LD is someone else's job
256
+ * (or that it has none to emit) — not a way to make a red route green: an
257
+ * exempt route whose page.tsx does render through `renderPage` fails too,
258
+ * because the reason given for it is false.
259
+ */
260
+ const NO_STRUCTURED_DATA_ROUTES: ReadonlyArray<{
261
+ route: string;
262
+ reason: string;
263
+ subtree?: true;
264
+ }> = [
265
+ // Fumadocs routes: JSON-LD is emitted by @working-theory/fumadocs-schema
266
+ // from the fumadocs `.source` collection, which is not built in this
267
+ // harness. Not checked here.
268
+ {
269
+ route: "blog",
270
+ reason: "fumadocs route — emits via @working-theory/fumadocs-schema",
271
+ subtree: true,
272
+ },
273
+ {
274
+ route: "docs",
275
+ reason: "fumadocs route — emits via @working-theory/fumadocs-schema",
276
+ subtree: true,
277
+ },
278
+ // Hub routes that enumerate a detail type with no list page type of their
279
+ // own; they hand-compose a listing and emit no JSON-LD today. The hub only:
280
+ // each one's `[slug]` detail route renders through renderPage and is checked.
281
+ {
282
+ route: "how-to",
283
+ reason: "hand-composed listing; no HowTo list type exists",
284
+ },
285
+ {
286
+ route: "software",
287
+ reason: "hand-composed listing; no SoftwareProduct list type exists",
288
+ },
289
+ {
290
+ route: "tech-articles",
291
+ reason: "hand-composed listing; no TechArticle list type exists",
292
+ },
293
+ {
294
+ route: "whitepapers",
295
+ reason: "hand-composed listing; no Whitepaper list type exists",
296
+ },
297
+ // App-shaped routes with no content page behind them.
298
+ {
299
+ route: "(authed)",
300
+ reason: "authenticated app surface, not a content page",
301
+ subtree: true,
302
+ },
303
+ {
304
+ route: "dev",
305
+ reason: "development-only purchase harness",
306
+ subtree: true,
307
+ },
308
+ {
309
+ route: "flows",
310
+ reason: "interactive flow engine, API-backed",
311
+ subtree: true,
312
+ },
313
+ { route: "schedule", reason: "booking surface, API-backed", subtree: true },
314
+ { route: "sign-in", reason: "auth surface, not a content page" },
315
+ ];
316
+
317
+ interface RouteEntry {
318
+ /** Route directory relative to `app/[locale]/` ("" for the home page). */
319
+ route: string;
320
+ file: string;
321
+ }
322
+
323
+ function walkRoutes(dir: string, out: RouteEntry[] = []): RouteEntry[] {
324
+ for (const entry of readdirSync(dir, { withFileTypes: true }).sort((a, b) =>
325
+ a.name.localeCompare(b.name),
326
+ )) {
327
+ if (entry.name === "__tests__" || entry.name === "node_modules") continue;
328
+ const full = join(dir, entry.name);
329
+ if (entry.isDirectory()) walkRoutes(full, out);
330
+ else if (entry.name === "page.tsx") {
331
+ out.push({
332
+ route: relative(ROUTE_ROOT, dirname(full)).split(sep).join("/"),
333
+ file: full,
334
+ });
335
+ }
336
+ }
337
+ return out;
338
+ }
339
+
340
+ function exemption(route: string): string | undefined {
341
+ return NO_STRUCTURED_DATA_ROUTES.find(
342
+ (e) =>
343
+ route === e.route ||
344
+ (e.subtree === true && route.startsWith(e.route + "/")),
345
+ )?.reason;
346
+ }
347
+
348
+ // The page identity a route renders — the two `renderPage({ type, slug })`
349
+ // shapes tools/lint-check-content-renders.ts recognises (a literal slug, or a
350
+ // slug taken from params). A route that reads its identity another way
351
+ // (e.g. a page-type manifest) is not recognised and must be declared.
352
+ const RENDER_PAGE_EXACT_RE =
353
+ /renderPage\s*\(\s*\{[^}]*type\s*:\s*["']([A-Za-z]+)["'][^}]*slug\s*:\s*["']([^"']+)["']/s;
354
+ const RENDER_PAGE_DYNAMIC_RE =
355
+ /renderPage\s*\(\s*\{[^}]*type\s*:\s*["']([A-Za-z]+)["'][^}]*\bslug\s*(?!:\s*["'])/s;
356
+
357
+ function routeIdentity(
358
+ file: string,
359
+ ): { type: string; slug: string | null } | null {
360
+ const src = readFileSync(file, "utf8");
361
+ const exact = RENDER_PAGE_EXACT_RE.exec(src);
362
+ if (exact) return { type: exact[1] as string, slug: exact[2] as string };
363
+ const dynamic = RENDER_PAGE_DYNAMIC_RE.exec(src);
364
+ if (dynamic) return { type: dynamic[1] as string, slug: null };
365
+ return null;
366
+ }
367
+
368
+ type PageModule = {
369
+ default: (props: {
370
+ params: Promise<{ locale: string; slug?: string }>;
371
+ }) => Promise<ReactElement>;
372
+ };
373
+
374
+ async function renderRoute(
375
+ file: string,
376
+ slug: string | undefined,
377
+ ): Promise<HTMLElement> {
378
+ // Resolved at test time from the walk above — a static import would dangle
379
+ // in a scaffold that gated the surface off.
380
+ const mod = require(file) as PageModule;
381
+ const params: { locale: string; slug?: string } = { locale: DEFAULT_LOCALE };
382
+ if (slug !== undefined) params.slug = slug;
383
+ const element = await mod.default({ params: Promise.resolve(params) });
384
+ return render(element).container;
385
+ }
386
+
387
+ describe("structured data — every route emits a valid JSON-LD script", () => {
388
+ const routes = walkRoutes(ROUTE_ROOT);
389
+
390
+ it("finds routes to check", () => {
391
+ expect(routes.length).toBeGreaterThan(0);
392
+ });
393
+
394
+ it.each(routes)("$route", async ({ route, file }) => {
395
+ const label = `/${DEFAULT_LOCALE}${route === "" ? "" : "/" + route}`;
396
+ const exempt = exemption(route);
397
+ const identity = routeIdentity(file);
398
+ if (exempt !== undefined) {
399
+ // Declared, with a reason, in NO_STRUCTURED_DATA_ROUTES above. The
400
+ // reason is false for a route that does render through renderPage —
401
+ // that route is checkable, so declaring it would only hide it.
402
+ if (identity !== null) {
403
+ throw new Error(
404
+ `${label} is listed in NO_STRUCTURED_DATA_ROUTES but renders through renderPage (${identity.type}); remove it from the list so it is checked.`,
405
+ );
406
+ }
407
+ console.info(`structured-data: ${label} not checked — ${exempt}`);
408
+ return;
409
+ }
410
+
411
+ if (identity === null) {
412
+ throw new Error(
413
+ `${label}: ${basename(dirname(file))}/page.tsx has no renderPage({ type, slug }) call this check can read, so nothing is known to emit its JSON-LD. ` +
414
+ `Render it through renderPage, or add it to NO_STRUCTURED_DATA_ROUTES in ${basename(__filename)} with the reason.`,
415
+ );
416
+ }
417
+ expect(knownSchemaOrgTypes.has(identity.type)).toBe(true);
418
+ const type = identity.type as PageType;
419
+
420
+ // The pages this route serves from the site's own content, in the
421
+ // default locale. A fixed route names one page: if that page is missing
422
+ // the route 404s in production, which for `/` or `/about` is a broken
423
+ // site, not a route with nothing to check. A `[slug]` route with no
424
+ // pages of its type is simply empty — say so, do not fail.
425
+ let slugs: string[];
426
+ if (identity.slug !== null) {
427
+ const page = await adapter.getPageSource(
428
+ type,
429
+ identity.slug,
430
+ DEFAULT_LOCALE,
431
+ );
432
+ if (page === null) {
433
+ throw new Error(
434
+ `${label}: renders ${type}/${identity.slug}, but content/${DEFAULT_LOCALE}/${type}/${identity.slug}.mdx does not exist — the route serves 404 in production.`,
435
+ );
436
+ }
437
+ slugs = [identity.slug];
438
+ } else {
439
+ slugs = (await adapter.listDetail(type, DEFAULT_LOCALE)).map(
440
+ (p: ContentPage) => p.slug,
441
+ );
442
+ if (slugs.length === 0) {
443
+ console.info(
444
+ `structured-data: ${label} has no ${type} content in ${DEFAULT_LOCALE}; every slug serves 404 in production — nothing to check`,
445
+ );
446
+ return;
447
+ }
448
+ }
449
+
450
+ const failures: string[] = [];
451
+ for (const slug of slugs) {
452
+ const container = await renderRoute(
453
+ file,
454
+ identity.slug === null ? slug : undefined,
455
+ );
456
+ const blocks = extractJsonLdBlocks(container);
457
+ const pageLabel =
458
+ identity.slug === null
459
+ ? `${label.replace(/\[slug\]$/, "")}${slug}`
460
+ : label;
461
+ if (blocks.length === 0) {
462
+ failures.push(
463
+ `${pageLabel}: rendered no <script type="application/ld+json">`,
464
+ );
465
+ continue;
466
+ }
467
+ const failure = validateFor(blocks, type, pageLabel);
468
+ if (failure !== null) failures.push(failure);
469
+ }
470
+ expect(failures).toEqual([]);
471
+ });
472
+ });
473
+
474
+ // ── 3. The two defect shapes that shipped, pinned ───────────────────────────
475
+ //
476
+ // These prove the checks above reach the validator rule that rejects each
477
+ // shape, so a change that quietly loosens the validator or the flatten step
478
+ // shows up here rather than in a customer's launch review.
479
+
480
+ describe("structured data — the check rejects the shapes that shipped", () => {
481
+ it("a rendered route with no JSON-LD script yields no blocks", () => {
482
+ const { container } = render(<main>{"no structured data here"}</main>);
483
+ expect(extractJsonLdBlocks(container)).toEqual([]);
484
+ });
485
+
486
+ it("a mainEntity carrying only @type and headline fails validation (#5019)", () => {
487
+ const thin: JsonLdObject = {
488
+ "@context": "https://schema.org",
489
+ "@type": "WebPage",
490
+ inLanguage: "en",
491
+ name: "x",
492
+ description: "x",
493
+ mainEntity: { "@type": "BlogPosting", headline: "x" },
494
+ };
495
+ const failure = validateFor([thin], "BlogPosting", "fixture");
496
+ expect(failure).not.toBeNull();
497
+ expect(failure).toContain("mainEntity.description");
498
+ });
499
+ });
@@ -0,0 +1,13 @@
1
+ // account-config.ts — Experiment registry for the /account Results section (#4984)
2
+ //
3
+ // Mirrors `nav-config.ts`: the OSS template declares NO experiments — nothing
4
+ // in `apps/web` runs a diagnostic today, so this list ships empty and the
5
+ // Results section renders its empty state. A consuming site lists the
6
+ // `experimentKey`s of the diagnostics it runs (the `key` of each
7
+ // `waitlistExperiment` it registers) so `/account` can read the signed-in
8
+ // respondent's own newest signup for each via `waitlistSignups.me(key)`
9
+ // (`GET /v1/waitlist-signups/me?experimentKey=…`, #4731 D5). Keys the caller
10
+ // has no signup for come back 404 and are skipped, so listing every
11
+ // experiment a site runs is safe.
12
+
13
+ export const accountExperimentKeys: string[] = [];
@@ -0,0 +1,85 @@
1
+ /**
2
+ * Server-side (RSC) helper for reading the operator-dashboard read API
3
+ * (#4665 D9).
4
+ *
5
+ * Forwards the incoming request's cookies to `@working-theory/api-client`'s
6
+ * `waitlistExperiments` namespace (B0, #4671) so `apps/api`'s cookie-session
7
+ * auth preHandler resolves the SAME Better Auth session the browser holds —
8
+ * both apps share the session store. GETs only, every page is an RSC; there
9
+ * is no client fetch and no `NEXT_PUBLIC_` credential (D9).
10
+ *
11
+ * `apps/web` may not import `apps/api` directly (architecture.yaml) — every
12
+ * read goes through `@working-theory/api-client`'s typed namespace, never a
13
+ * hand-rolled `fetch`. This module supplies exactly the two things that
14
+ * namespace doesn't own: the cookie-forwarding `init` (B0's additive
15
+ * `init?: { headers }` on `http.get`) and the envelope→`ApiError` unwrap
16
+ * (mirrors `apps-paid/workspace/lib/api-error.ts`'s `unwrapEnvelope`, adapted to
17
+ * the `@working-theory/api-client` `ApiResult<T>` shape B0 already returns
18
+ * rather than re-parsing the wire envelope by hand).
19
+ */
20
+
21
+ import { cookies } from "next/headers";
22
+
23
+ /**
24
+ * Structural mirror of `@working-theory/api-client`'s `ApiResult<T>`
25
+ * (`packages/api-client/src/http.ts`) — not imported, because that type is
26
+ * not re-exported from the package root and this module's scope (#4665 UI)
27
+ * does not touch `packages/api-client` (B0's). Every `waitlistExperiments.*`
28
+ * call already returns exactly this shape, so TypeScript's structural typing
29
+ * accepts it here without a cast.
30
+ */
31
+ type ApiResult<T> =
32
+ | { ok: true; body: T }
33
+ | { ok: false; status: number; body: unknown };
34
+
35
+ /**
36
+ * ApiError — thrown when the API responds with `{ ok: false }` (or the
37
+ * response body doesn't parse as the expected envelope). Carries the HTTP
38
+ * status + the envelope's `error.code` so callers can branch (404 →
39
+ * `notFound()`, 401/403 → an `EmptyState`) without string-matching on
40
+ * messages.
41
+ */
42
+ export class ApiError extends Error {
43
+ readonly status: number;
44
+ readonly code: string;
45
+
46
+ constructor(status: number, code: string, message: string) {
47
+ super(message);
48
+ this.name = "ApiError";
49
+ this.status = status;
50
+ this.code = code;
51
+ }
52
+ }
53
+
54
+ /** The subset of an error response body every route's envelope carries. */
55
+ interface ErrorBody {
56
+ error?: { code?: string; message?: string };
57
+ }
58
+
59
+ /**
60
+ * Forward the incoming request's cookies as the `init` every
61
+ * `waitlistExperiments.*` call accepts. Call once per request — `cookies()`
62
+ * reads the current RSC request context.
63
+ */
64
+ export async function forwardedCookieInit(): Promise<{
65
+ headers: { cookie: string };
66
+ }> {
67
+ const cookieHeader = (await cookies()).toString();
68
+ return { headers: { cookie: cookieHeader } };
69
+ }
70
+
71
+ /**
72
+ * Unwrap an `ApiResult<T>` — the shape every `@working-theory/api-client`
73
+ * call returns — into `T`, throwing `ApiError` on `{ ok: false }`.
74
+ */
75
+ export async function unwrapApiResult<T>(
76
+ result: Promise<ApiResult<T>>,
77
+ ): Promise<T> {
78
+ const res = await result;
79
+ if (res.ok) return res.body;
80
+
81
+ const body = res.body as ErrorBody | null;
82
+ const code = body?.error?.code ?? "invalid_response";
83
+ const message = body?.error?.message ?? "Malformed API response";
84
+ throw new ApiError(res.status, code, message);
85
+ }