create-ailk 0.21.0 → 0.23.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 (342) hide show
  1. package/component-catalog.json +186 -8
  2. package/component-catalog.md +9 -7
  3. package/dist/add-module.js +20 -14
  4. package/dist/add-route.d.ts +55 -1
  5. package/dist/add-route.js +294 -12
  6. package/dist/cli.js +27 -10
  7. package/dist/derive-architecture.d.ts +12 -3
  8. package/dist/derive-architecture.js +20 -8
  9. package/dist/design-package.js +7 -2
  10. package/dist/lib/design-checks.js +4 -2
  11. package/dist/lib/env-catalog.js +5 -5
  12. package/dist/lib/npm-auth.d.ts +9 -0
  13. package/dist/lib/npm-auth.js +48 -0
  14. package/dist/lib/scaffold-render-sweep.d.ts +3 -2
  15. package/dist/lib/scaffold-render-sweep.js +5 -8
  16. package/dist/module-architecture.js +99 -9
  17. package/dist/paid-sections.d.ts +21 -2
  18. package/dist/paid-sections.js +69 -7
  19. package/dist/programmatic.js +19 -5
  20. package/dist/seed-flows.d.ts +4 -1
  21. package/dist/seed-flows.js +50 -4
  22. package/dist/site-chrome.d.ts +8 -0
  23. package/dist/site-chrome.js +17 -1
  24. package/dist/spoke-checkout.d.ts +65 -41
  25. package/dist/spoke-checkout.js +208 -430
  26. package/dist/starter-grid.d.ts +0 -3
  27. package/dist/starter-grid.js +24 -14
  28. package/dist/surfaces.js +52 -26
  29. package/dist/sync-routes.d.ts +38 -1
  30. package/dist/sync-routes.js +187 -13
  31. package/dist/template-record.d.ts +4 -3
  32. package/dist/template-record.js +8 -4
  33. package/dist/topology.js +25 -1
  34. package/dist/update.js +15 -0
  35. package/package.json +3 -3
  36. package/site-templates/_base/content/en/NewsletterSignup/newsletter.mdx +4 -49
  37. package/site-templates/_base/content/en/Pricing/pricing.mdx +3 -21
  38. package/site-templates/designs/minimalism/DESIGN.md +1 -1
  39. package/site-templates/marketing-site/TEMPLATE-MANIFEST.json +23 -20
  40. package/site-templates/marketing-site/TEMPLATE-MANIFEST.md +28 -14
  41. package/site-templates/marketing-site-free/TEMPLATE-MANIFEST.json +15 -39
  42. package/site-templates/marketing-site-free/TEMPLATE-MANIFEST.md +38 -34
  43. package/site-templates/marketing-site-free/content/en/NewsletterSignup/newsletter.mdx +6 -43
  44. package/site-templates/shapes/local-business-service/TEMPLATE-MANIFEST.json +14 -31
  45. package/site-templates/shapes/local-business-service/TEMPLATE-MANIFEST.md +26 -25
  46. package/site-templates/shapes/local-business-service/content/en/NewsletterSignup/newsletter.mdx +6 -43
  47. package/site-templates/shapes/organization-service/TEMPLATE-MANIFEST.json +14 -31
  48. package/site-templates/shapes/organization-service/TEMPLATE-MANIFEST.md +26 -25
  49. package/site-templates/shapes/organization-service/content/en/NewsletterSignup/newsletter.mdx +6 -43
  50. package/site-templates/shapes/person-service/TEMPLATE-MANIFEST.json +14 -31
  51. package/site-templates/shapes/person-service/TEMPLATE-MANIFEST.md +26 -25
  52. package/site-templates/shapes/person-service/content/en/NewsletterSignup/newsletter.mdx +6 -43
  53. package/templates/.env.example +9 -7
  54. package/templates/.github/workflows/ci.yml +13 -0
  55. package/templates/README.md +1 -0
  56. package/templates/_gitignore +4 -0
  57. package/templates/apps/api/.env.example +27 -0
  58. package/templates/apps/api/CLAUDE.md +87 -17
  59. package/templates/apps/api/package.json +1 -0
  60. package/templates/apps/api/src/__tests__/factories/in-memory-prisma.ts +2 -0
  61. package/templates/apps/api/src/bin/stripe-connections-rekey.ts +45 -0
  62. package/templates/apps/api/src/config/__tests__/stripe-connection-key.test.ts +185 -0
  63. package/templates/apps/api/src/config/index.ts +109 -0
  64. package/templates/apps/api/src/lib/__mocks__/prisma.ts +3 -0
  65. package/templates/apps/api/src/lib/__tests__/consent-store.test.ts +19 -0
  66. package/templates/apps/api/src/lib/__tests__/flow-completion-store.test.ts +149 -0
  67. package/templates/apps/api/src/lib/__tests__/flow-rename-race.db.test.ts +116 -0
  68. package/templates/apps/api/src/lib/consent-store.ts +28 -0
  69. package/templates/apps/api/src/lib/error-serializer.ts +74 -7
  70. package/templates/apps/api/src/lib/flow-completion-store.ts +136 -0
  71. package/templates/apps/api/src/lib/hub-sign-in-store.ts +38 -2
  72. package/templates/apps/api/src/lib/proposal-paid-store.ts +21 -4
  73. package/templates/apps/api/src/lib/stripe.ts +74 -4
  74. package/templates/apps/api/src/lib/webhook-idempotency.ts +66 -2
  75. package/templates/apps/api/src/middleware/__tests__/auth.hub-session-path.test.ts +14 -3
  76. package/templates/apps/api/src/middleware/__tests__/rate-limit.test.ts +218 -0
  77. package/templates/apps/api/src/middleware/__tests__/write-budget.test.ts +174 -0
  78. package/templates/apps/api/src/middleware/auth.ts +13 -6
  79. package/templates/apps/api/src/middleware/rate-limit.ts +226 -4
  80. package/templates/apps/api/src/openapi/spec.ts +33 -3
  81. package/templates/apps/api/src/routes/auth/__tests__/hub-sign-in.test.ts +49 -0
  82. package/templates/apps/api/src/routes/auth/hub-sign-in.ts +41 -0
  83. package/templates/apps/api/src/routes/billing/plan-checkout.ts +325 -0
  84. package/templates/apps/api/src/routes/checkout/__tests__/live-key.test.ts +11 -0
  85. package/templates/apps/api/src/routes/checkout/__tests__/offerings-authorization.test.ts +1300 -0
  86. package/templates/apps/api/src/routes/checkout/__tests__/session.test.ts +11 -0
  87. package/templates/apps/api/src/routes/checkout/__tests__/sessions.test.ts +49 -0
  88. package/templates/apps/api/src/routes/checkout/session.ts +57 -25
  89. package/templates/apps/api/src/routes/checkout/sessions.ts +232 -82
  90. package/templates/apps/api/src/routes/consent/__tests__/account.test.ts +386 -0
  91. package/templates/apps/api/src/routes/consent/__tests__/sign-up-rate-limit.test.ts +160 -0
  92. package/templates/apps/api/src/routes/consent/account.ts +268 -0
  93. package/templates/apps/api/src/routes/consent/index.ts +15 -7
  94. package/templates/apps/api/src/routes/content/__tests__/get-page.test.ts +36 -0
  95. package/templates/apps/api/src/routes/content/__tests__/offerings.test.ts +731 -0
  96. package/templates/apps/api/src/routes/content/__tests__/pricing.test.ts +753 -0
  97. package/templates/apps/api/src/routes/content/__tests__/shown-offerings.test.ts +762 -0
  98. package/templates/apps/api/src/routes/content/get-page.ts +4 -0
  99. package/templates/apps/api/src/routes/content/index.ts +16 -2
  100. package/templates/apps/api/src/routes/content/offerings.ts +184 -0
  101. package/templates/apps/api/src/routes/content/pricing.ts +176 -0
  102. package/templates/apps/api/src/routes/content/shown-offerings.ts +229 -0
  103. package/templates/apps/api/src/routes/leads/__tests__/magnet-completion.test.ts +515 -0
  104. package/templates/apps/api/src/routes/leads/index.ts +164 -7
  105. package/templates/apps/api/src/routes/webhooks/README.md +66 -0
  106. package/templates/apps/api/src/routes/webhooks/__tests__/stripe-checkout-consent.test.ts +649 -0
  107. package/templates/apps/api/src/routes/webhooks/__tests__/stripe-offerings.test.ts +814 -0
  108. package/templates/apps/api/src/routes/webhooks/__tests__/stripe-org-billing.test.ts +80 -68
  109. package/templates/apps/api/src/routes/webhooks/__tests__/stripe-site-filter.test.ts +5 -5
  110. package/templates/apps/api/src/routes/webhooks/__tests__/stripe.test.ts +8 -2
  111. package/templates/apps/api/src/routes/webhooks/index.ts +23 -0
  112. package/templates/apps/api/src/routes/webhooks/stripe-offerings.ts +384 -0
  113. package/templates/apps/api/src/routes/webhooks/stripe.ts +245 -36
  114. package/templates/apps/api/src/server.ts +163 -46
  115. package/templates/apps/api/src/services/__tests__/checkout-plan.test.ts +313 -0
  116. package/templates/apps/api/src/services/__tests__/deliverable-fulfillment.test.ts +51 -2
  117. package/templates/apps/api/src/services/__tests__/keyless-workspace-predicate.test.ts +234 -0
  118. package/templates/apps/api/src/services/__tests__/lead-routing.test.ts +1383 -133
  119. package/templates/apps/api/src/services/__tests__/magnet-completion.test.ts +389 -0
  120. package/templates/apps/api/src/services/__tests__/person.test.ts +232 -1
  121. package/templates/apps/api/src/services/__tests__/plan-offerings.test.ts +444 -0
  122. package/templates/apps/api/src/services/checkout-buyer.ts +100 -4
  123. package/templates/apps/api/src/services/consent.ts +95 -0
  124. package/templates/apps/api/src/services/deliverable-fulfillment.ts +13 -5
  125. package/templates/apps/api/src/services/entitlements.ts +157 -1
  126. package/templates/apps/api/src/services/lead-routing.ts +431 -47
  127. package/templates/apps/api/src/services/magnet-completion.ts +242 -0
  128. package/templates/apps/api/src/services/offerings/__tests__/catalog.test.ts +295 -0
  129. package/templates/apps/api/src/services/offerings/__tests__/checkout-session-record.test.ts +268 -0
  130. package/templates/apps/api/src/services/offerings/__tests__/connection.test.ts +868 -0
  131. package/templates/apps/api/src/services/offerings/__tests__/fixtures.ts +416 -0
  132. package/templates/apps/api/src/services/offerings/__tests__/org-products.test.ts +396 -0
  133. package/templates/apps/api/src/services/offerings/__tests__/readers.test.ts +681 -0
  134. package/templates/apps/api/src/services/offerings/__tests__/source.test.ts +231 -0
  135. package/templates/apps/api/src/services/offerings/catalog.ts +308 -0
  136. package/templates/apps/api/src/services/offerings/checkout-authorization.ts +385 -0
  137. package/templates/apps/api/src/services/offerings/checkout-session-record.ts +192 -0
  138. package/templates/apps/api/src/services/offerings/connection.ts +1003 -0
  139. package/templates/apps/api/src/services/offerings/index.ts +614 -0
  140. package/templates/apps/api/src/services/offerings/source.ts +140 -0
  141. package/templates/apps/api/src/services/offerings/store.ts +150 -0
  142. package/templates/apps/api/src/services/person.ts +164 -17
  143. package/templates/apps/api/src/services/plan-offerings.ts +125 -0
  144. package/templates/apps/api/src/services/public-site-resolver.ts +27 -15
  145. package/templates/apps/mcp/CLAUDE.md +22 -14
  146. package/templates/apps/mcp/__tests__/catalog-drift.test.ts +9 -4
  147. package/templates/apps/mcp/__tests__/get_offerings.test.ts +358 -0
  148. package/templates/apps/mcp/__tests__/get_pricing.test.ts +364 -0
  149. package/templates/apps/mcp/__tests__/http-transport.test.ts +4 -4
  150. package/templates/apps/mcp/__tests__/module-exclusion.test.ts +2 -2
  151. package/templates/apps/mcp/__tests__/server.test.ts +13 -11
  152. package/templates/apps/mcp/__tests__/start_purchase.test.ts +208 -12
  153. package/templates/apps/mcp/__tests__/tools/purchase.test.ts +11 -11
  154. package/templates/apps/mcp/src/lib/shown-offerings.ts +105 -0
  155. package/templates/apps/mcp/src/server.ts +5 -0
  156. package/templates/apps/mcp/src/tools/get_offerings.ts +47 -30
  157. package/templates/apps/mcp/src/tools/get_pricing.ts +40 -40
  158. package/templates/apps/mcp/src/tools/purchase.ts +30 -2
  159. package/templates/apps/mcp/src/workspace-server.ts +15 -0
  160. package/templates/apps/web/.env.example +24 -31
  161. package/templates/apps/web/CLAUDE.md +393 -9
  162. package/templates/apps/web/__tests__/__mocks__/next-intl-server.ts +6 -2
  163. package/templates/apps/web/app/[locale]/(auth)/__tests__/auth-pages.test.tsx +5 -4
  164. package/templates/apps/web/app/[locale]/(auth)/forgot-password/forgot-password-card.tsx +5 -1
  165. package/templates/apps/web/app/[locale]/(auth)/forgot-password/page.tsx +2 -0
  166. package/templates/apps/web/app/[locale]/(auth)/layout.tsx +3 -14
  167. package/templates/apps/web/app/[locale]/(auth)/reset-password/page.tsx +2 -0
  168. package/templates/apps/web/app/[locale]/(auth)/reset-password/reset-password-card.tsx +5 -1
  169. package/templates/apps/web/app/[locale]/(auth)/sign-in/__tests__/page.test.tsx +160 -8
  170. package/templates/apps/web/app/[locale]/(auth)/sign-in/__tests__/sign-in-card.test.tsx +12 -9
  171. package/templates/apps/web/app/[locale]/(auth)/sign-in/page.tsx +105 -2
  172. package/templates/apps/web/app/[locale]/(auth)/sign-in/sign-in-card.tsx +36 -3
  173. package/templates/apps/web/app/[locale]/(auth)/sign-in/sign-in-text.ts +47 -1
  174. package/templates/apps/web/app/[locale]/(auth)/sign-up/__tests__/sign-up-card.test.tsx +60 -22
  175. package/templates/apps/web/app/[locale]/(auth)/sign-up/page.tsx +16 -8
  176. package/templates/apps/web/app/[locale]/(auth)/sign-up/sign-up-card.tsx +85 -31
  177. package/templates/apps/web/app/[locale]/(auth)/use-auth-card-flow.ts +18 -4
  178. package/templates/apps/web/app/[locale]/(auth)/verify-email/page.tsx +3 -1
  179. package/templates/apps/web/app/[locale]/(authed)/__tests__/gate.test.tsx +7 -4
  180. package/templates/apps/web/app/[locale]/(authed)/account/__tests__/agreements-prompt.test.tsx +227 -207
  181. package/templates/apps/web/app/[locale]/(authed)/account/__tests__/layout-sign-out.test.tsx +17 -1
  182. package/templates/apps/web/app/[locale]/(authed)/account/__tests__/layout.test.tsx +143 -1
  183. package/templates/apps/web/app/[locale]/(authed)/account/__tests__/new-menu.test.tsx +4 -0
  184. package/templates/apps/web/app/[locale]/(authed)/account/__tests__/page.test.tsx +385 -89
  185. package/templates/apps/web/app/[locale]/(authed)/account/__tests__/popups.test.tsx +5 -0
  186. package/templates/apps/web/app/[locale]/(authed)/account/__tests__/privacy.test.tsx +44 -5
  187. package/templates/apps/web/app/[locale]/(authed)/account/account-frame.tsx +68 -34
  188. package/templates/apps/web/app/[locale]/(authed)/account/actions.ts +0 -8
  189. package/templates/apps/web/app/[locale]/(authed)/account/agreements-actions.ts +39 -151
  190. package/templates/apps/web/app/[locale]/(authed)/account/agreements-gate.ts +61 -0
  191. package/templates/apps/web/app/[locale]/(authed)/account/agreements-prompt.tsx +143 -140
  192. package/templates/apps/web/app/[locale]/(authed)/account/layout.tsx +40 -6
  193. package/templates/apps/web/app/[locale]/(authed)/account/page.tsx +68 -54
  194. package/templates/apps/web/app/[locale]/(authed)/account/privacy/page.tsx +5 -1
  195. package/templates/apps/web/app/[locale]/careers/[slug]/[action]/page.tsx +189 -0
  196. package/templates/apps/web/app/[locale]/careers/[slug]/apply.actions.ts +295 -0
  197. package/templates/apps/web/app/[locale]/careers/[slug]/job-application-form.ts +120 -0
  198. package/templates/apps/web/app/[locale]/careers/[slug]/page.tsx +23 -0
  199. package/templates/apps/web/app/[locale]/careers/__tests__/apply.actions.test.ts +319 -0
  200. package/templates/apps/web/app/[locale]/careers/__tests__/careers-hub.test.tsx +172 -0
  201. package/templates/apps/web/app/[locale]/careers/__tests__/careers-set.test.tsx +149 -0
  202. package/templates/apps/web/app/[locale]/careers/__tests__/job-application-form.test.ts +141 -0
  203. package/templates/apps/web/app/[locale]/careers/__tests__/job-apply-page.test.tsx +424 -0
  204. package/templates/apps/web/app/[locale]/careers/__tests__/page-renderer-migration.test.tsx +72 -11
  205. package/templates/apps/web/app/[locale]/careers/page.tsx +129 -5
  206. package/templates/apps/web/app/[locale]/case-studies/__tests__/page-renderer-migration.test.tsx +2 -2
  207. package/templates/apps/web/app/[locale]/checkout/__tests__/pages.test.tsx +139 -0
  208. package/templates/apps/web/app/[locale]/checkout/__tests__/thank-you-account.test.tsx +136 -0
  209. package/templates/apps/web/app/[locale]/checkout/thank-you/page.tsx +114 -0
  210. package/templates/apps/web/app/[locale]/checkout/unavailable/page.tsx +51 -0
  211. package/templates/apps/web/app/[locale]/clients/__tests__/page-renderer-migration.test.tsx +1 -1
  212. package/templates/apps/web/app/[locale]/contact/ConversationalIntakeWrapper.tsx +17 -1
  213. package/templates/apps/web/app/[locale]/contact/__tests__/ConversationalIntakeWrapper.test.tsx +79 -4
  214. package/templates/apps/web/app/[locale]/contact/__tests__/contact-magnet-page.test.tsx +160 -13
  215. package/templates/apps/web/app/[locale]/contact/__tests__/contact.actions.test.ts +466 -36
  216. package/templates/apps/web/app/[locale]/contact/__tests__/page-renderer-migration.test.tsx +7 -0
  217. package/templates/apps/web/app/[locale]/contact/contact.actions.ts +172 -57
  218. package/templates/apps/web/app/[locale]/contact/intake-script.ts +26 -0
  219. package/templates/apps/web/app/[locale]/contact/page.tsx +99 -60
  220. package/templates/apps/web/app/[locale]/courses/__tests__/page-renderer-migration.test.tsx +2 -2
  221. package/templates/apps/web/app/[locale]/events/__tests__/page-renderer-migration.test.tsx +2 -2
  222. package/templates/apps/web/app/[locale]/flows/[slug]/FlowStepperClient.tsx +341 -4
  223. package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/FlowStepperClient.count.test.tsx +43 -0
  224. package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/FlowStepperClient.deliverable.test.tsx +200 -0
  225. package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/FlowStepperClient.ending.test.tsx +197 -0
  226. package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/FlowStepperClient.invite.test.tsx +257 -0
  227. package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/account-invite.actions.test.ts +69 -0
  228. package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/magnet-page.test.tsx +430 -0
  229. package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/two-route-tool.test.tsx +21 -9
  230. package/templates/apps/web/app/[locale]/flows/[slug]/account-invite.actions.ts +35 -0
  231. package/templates/apps/web/app/[locale]/flows/[slug]/magnet-page.tsx +352 -0
  232. package/templates/apps/web/app/[locale]/flows/[slug]/page.tsx +12 -73
  233. package/templates/apps/web/app/[locale]/flows/[slug]/start/page.tsx +10 -31
  234. package/templates/apps/web/app/[locale]/for/__tests__/page-renderer-migration.test.tsx +1 -1
  235. package/templates/apps/web/app/[locale]/how-to/__tests__/page-renderer-migration.test.tsx +1 -1
  236. package/templates/apps/web/app/[locale]/integrations/__tests__/page-renderer-migration.test.tsx +2 -2
  237. package/templates/apps/web/app/[locale]/layout.tsx +55 -4
  238. package/templates/apps/web/app/[locale]/newsletter/__tests__/page.test.tsx +174 -104
  239. package/templates/apps/web/app/[locale]/newsletter/page.tsx +95 -58
  240. package/templates/apps/web/app/[locale]/page.tsx +13 -1
  241. package/templates/apps/web/app/[locale]/partners/__tests__/page-renderer-migration.test.tsx +2 -2
  242. package/templates/apps/web/app/[locale]/pricing/__tests__/__snapshots__/unconnected.test.tsx.snap +7 -0
  243. package/templates/apps/web/app/[locale]/pricing/__tests__/offerings.test.tsx +599 -0
  244. package/templates/apps/web/app/[locale]/pricing/__tests__/page.test.tsx +216 -0
  245. package/templates/apps/web/app/[locale]/pricing/__tests__/unconnected.test.tsx +236 -0
  246. package/templates/apps/web/app/[locale]/pricing/page.tsx +367 -6
  247. package/templates/apps/web/app/[locale]/products/__tests__/page-renderer-migration.test.tsx +2 -2
  248. package/templates/apps/web/app/[locale]/resources/__tests__/page-renderer-migration.test.tsx +2 -2
  249. package/templates/apps/web/app/[locale]/services/__tests__/page-renderer-migration.test.tsx +2 -2
  250. package/templates/apps/web/app/[locale]/software/__tests__/page-renderer-migration.test.tsx +1 -1
  251. package/templates/apps/web/app/[locale]/team/__tests__/team-routes.test.tsx +4 -1
  252. package/templates/apps/web/app/[locale]/tech-articles/__tests__/page-renderer-migration.test.tsx +1 -1
  253. package/templates/apps/web/app/[locale]/testimonials/__tests__/page-renderer-migration.test.tsx +1 -1
  254. package/templates/apps/web/app/[locale]/thanks/[slug]/AccountInviteLink.tsx +89 -0
  255. package/templates/apps/web/app/[locale]/thanks/[slug]/__tests__/AccountInviteLink.test.tsx +104 -0
  256. package/templates/apps/web/app/[locale]/thanks/[slug]/__tests__/page.test.tsx +38 -0
  257. package/templates/apps/web/app/[locale]/thanks/[slug]/page.tsx +9 -0
  258. package/templates/apps/web/app/[locale]/unsubscribe/__tests__/unsubscribe-rsc-boundary.test.tsx +17 -0
  259. package/templates/apps/web/app/[locale]/unsubscribe/__tests__/unsubscribe.test.tsx +62 -3
  260. package/templates/apps/web/app/[locale]/unsubscribe/page.tsx +70 -18
  261. package/templates/apps/web/app/[locale]/unsubscribe/unsubscribe-button.tsx +30 -9
  262. package/templates/apps/web/app/[locale]/whitepapers/__tests__/page-renderer-migration.test.tsx +1 -1
  263. package/templates/apps/web/app/api/auth/hub/authorize/__tests__/route.test.ts +114 -0
  264. package/templates/apps/web/app/api/auth/hub/authorize/route.ts +20 -3
  265. package/templates/apps/web/app/api/hub-session/__tests__/routes.test.ts +44 -1
  266. package/templates/apps/web/app/api/hub-session/sign-in/route.ts +22 -3
  267. package/templates/apps/web/app/api/offerings/checkout/[lookupKey]/route.ts +316 -0
  268. package/templates/apps/web/app/api/offerings/checkout/__tests__/route.test.ts +810 -0
  269. package/templates/apps/web/auth.ts +28 -2
  270. package/templates/apps/web/lib/__tests__/account-invite.test.ts +63 -0
  271. package/templates/apps/web/lib/__tests__/auth-routes.test.ts +27 -1
  272. package/templates/apps/web/lib/__tests__/consent-policies.test.ts +23 -0
  273. package/templates/apps/web/lib/__tests__/deck-page.test.ts +7 -1
  274. package/templates/apps/web/lib/__tests__/in-page-magnet.test.ts +265 -0
  275. package/templates/apps/web/lib/__tests__/magnet-routes.test.ts +57 -0
  276. package/templates/apps/web/lib/__tests__/newsletter.actions.test.ts +293 -0
  277. package/templates/apps/web/lib/__tests__/offerings-pricing.test.ts +601 -0
  278. package/templates/apps/web/lib/__tests__/structured-data.test.tsx +101 -25
  279. package/templates/apps/web/lib/account-invite.ts +37 -0
  280. package/templates/apps/web/lib/auth-routes.ts +36 -2
  281. package/templates/apps/web/lib/checkout-config.ts +16 -0
  282. package/templates/apps/web/lib/consent-policies.ts +22 -0
  283. package/templates/apps/web/lib/deck-page.ts +7 -0
  284. package/templates/apps/web/lib/deliverable-grant.ts +18 -0
  285. package/templates/apps/web/lib/hub-session/__tests__/sign-in-gate.test.tsx +111 -0
  286. package/templates/apps/web/lib/hub-session/sign-in-gate.tsx +88 -32
  287. package/templates/apps/web/lib/in-page-magnet.ts +416 -0
  288. package/templates/apps/web/lib/magnet-routes.ts +42 -0
  289. package/templates/apps/web/lib/modules.generated.ts +9 -0
  290. package/templates/apps/web/lib/newsletter.actions.ts +202 -0
  291. package/templates/apps/web/lib/offerings-pricing.ts +326 -0
  292. package/templates/apps/web/lib/posting-title.ts +17 -0
  293. package/templates/apps/web/lib/prisma-for-auth.ts +8 -0
  294. package/templates/apps/web/lib/sign-up-agreements.ts +449 -0
  295. package/templates/apps/web/lib/site-chrome.ts +2 -0
  296. package/templates/apps/web/lib/telemetry-scrub.ts +5 -1
  297. package/templates/apps/web/messages/en.json +71 -20
  298. package/templates/apps/web/middleware.ts +12 -2
  299. package/templates/apps/web/package.json +1 -0
  300. package/templates/apps/web/security-headers.config.ts +17 -0
  301. package/templates/apps/web/src/lib/__tests__/lead-write.test.ts +187 -0
  302. package/templates/apps/web/src/lib/__tests__/rate-limit.test.ts +67 -0
  303. package/templates/apps/web/src/lib/lead-write.ts +90 -0
  304. package/templates/apps/web/src/lib/rate-limit.ts +79 -0
  305. package/templates/content/en/BlogList/blog.mdx +4 -71
  306. package/templates/content/en/BookCall/book-a-call.mdx +1 -41
  307. package/templates/content/en/BookCall/onsite-estimate.mdx +1 -17
  308. package/templates/content/en/BookCall/onsite-service.mdx +1 -17
  309. package/templates/content/en/BookCall/start-project.mdx +1 -17
  310. package/templates/content/en/ContactPage/contact.mdx +6 -63
  311. package/templates/content/en/HomePage/home.mdx +3 -21
  312. package/templates/database/CHANGELOG.md +69 -0
  313. package/templates/database/__tests__/consent-source-checkout-migration.test.ts +79 -0
  314. package/templates/database/__tests__/flow-completion-migration.test.ts +115 -0
  315. package/templates/database/__tests__/flow-completion.db.test.ts +136 -0
  316. package/templates/database/__tests__/in-page-magnets-migration.test.ts +161 -0
  317. package/templates/database/__tests__/offerings-checkout-session.db.test.ts +270 -0
  318. package/templates/database/__tests__/offerings-migration.test.ts +197 -0
  319. package/templates/database/__tests__/offerings-shown-price-ids-migration.test.ts +86 -0
  320. package/templates/database/__tests__/offerings.db.test.ts +303 -0
  321. package/templates/database/__tests__/seed.test.ts +64 -1
  322. package/templates/database/auth/schema.prisma +4 -0
  323. package/templates/database/config/schema.prisma +91 -0
  324. package/templates/database/inbox/schema.prisma +17 -2
  325. package/templates/database/migrations/20261018120000_flow_completion/migration.sql +52 -0
  326. package/templates/database/migrations/20261019120000_consent_source_checkout/migration.sql +27 -0
  327. package/templates/database/migrations/20261020120000_offerings_data_model/migration.sql +146 -0
  328. package/templates/database/migrations/20261021120000_offerings_checkout_session/migration.sql +77 -0
  329. package/templates/database/migrations/20261022120000_in_page_magnets/migration.sql +45 -0
  330. package/templates/database/migrations/20261023120000_offerings_shown_price_ids/migration.sql +47 -0
  331. package/templates/database/migrations/20261024120000_organization_plan_checkout_session/migration.sql +16 -0
  332. package/templates/database/package.json +1 -1
  333. package/templates/database/seed.ts +62 -3
  334. package/versions.json +35 -35
  335. package/site-templates/marketing-site-free/content/en/Pricing/pricing.mdx +0 -199
  336. package/templates/apps/web/app/[locale]/(auth)/sign-up/sign-up.actions.ts +0 -125
  337. package/templates/apps/web/app/[locale]/downloads/__tests__/page-renderer-migration.test.tsx +0 -93
  338. package/templates/apps/web/app/[locale]/downloads/page.tsx +0 -37
  339. package/templates/apps/web/app/[locale]/newsletter/__tests__/newsletter.actions.test.ts +0 -139
  340. package/templates/apps/web/app/[locale]/newsletter/newsletter.actions.ts +0 -93
  341. package/templates/apps/web/lib/__tests__/meeting-slugs.test.ts +0 -120
  342. package/templates/apps/web/lib/meeting-slugs.ts +0 -63
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Hands `NPM_TOKEN` to the scaffold's own `pnpm install` through a user-level
3
+ * npmrc (#9092).
4
+ *
5
+ * The published `@working-theory/*` scope is private, and pnpm sends an
6
+ * authorization header only when an npmrc maps the registry to a token.
7
+ * pnpm >= 11 ignores registry credentials in a PROJECT `.npmrc`, so the
8
+ * scaffold commits none; the token has to arrive at user level instead.
9
+ *
10
+ * When the caller exported `NPM_TOKEN` and has no user npmrc of its own (the
11
+ * credential-less environment: no `~/.npmrc`, no `npm login`), the install
12
+ * runs with `NPM_CONFIG_USERCONFIG` pointing at a temporary, owner-only file
13
+ * carrying only the `${NPM_TOKEN}` reference — never the value — removed when
14
+ * the install returns. A caller with its own user npmrc owns its auth, and is
15
+ * left untouched; a userconfig override naming a missing file is replaced.
16
+ *
17
+ * Pure module: no `import.meta.url`, so jest imports it directly.
18
+ */
19
+ import { existsSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
20
+ import { tmpdir } from "node:os";
21
+ import { join } from "node:path";
22
+ /** The one user-level line: the registry mapped to the env var, no value. */
23
+ export const NPM_TOKEN_AUTH_LINE = "//registry.npmjs.org/:_authToken=${NPM_TOKEN}";
24
+ /** The user npmrc npm and pnpm read for `env` — the override, else `~/.npmrc`. */
25
+ function userNpmrcPath(env, home) {
26
+ return env["NPM_CONFIG_USERCONFIG"] || env["npm_config_userconfig"] || join(home, ".npmrc");
27
+ }
28
+ /**
29
+ * Calls `run` with the env the install should use: `env` itself, or — when
30
+ * `NPM_TOKEN` is set and no user npmrc exists — `env` plus
31
+ * `NPM_CONFIG_USERCONFIG` naming a temporary reference-only npmrc, which is
32
+ * removed once `run` returns or throws.
33
+ */
34
+ export function withNpmTokenUserConfig(env, home, run) {
35
+ if (!env["NPM_TOKEN"] || existsSync(userNpmrcPath(env, home)))
36
+ return run(env);
37
+ const dir = mkdtempSync(join(tmpdir(), "create-ailk-npmrc-"));
38
+ try {
39
+ const path = join(dir, ".npmrc");
40
+ writeFileSync(path, `${NPM_TOKEN_AUTH_LINE}\n`, { mode: 0o600 });
41
+ // Both spellings: npm and pnpm read either, and a stale lower-case one
42
+ // left beside ours would make the winner depend on key order.
43
+ return run({ ...env, NPM_CONFIG_USERCONFIG: path, npm_config_userconfig: path });
44
+ }
45
+ finally {
46
+ rmSync(dir, { recursive: true, force: true });
47
+ }
48
+ }
@@ -33,8 +33,9 @@ export declare const CLAIM_GATED_ROUTES: Readonly<Record<string, string>>;
33
33
  export declare const CONFIG_GATED_ROUTES: Readonly<Record<string, string>>;
34
34
  /**
35
35
  * The placeholder heading each `CONFIG_GATED_ROUTES` entry renders while its
36
- * env var is unset — for `/meet`, the hero headline of the overlay's
37
- * `content/en/LandingPage/meet.mdx` seed. A fresh scaffold never edits it.
36
+ * env var is unset. Empty since #8843 D14: `/meet` reads the workspace's
37
+ * meetings and answers 200 with its own H1 unconfigured, so no route is
38
+ * config-gated today.
38
39
  */
39
40
  export declare const CONFIG_GATED_PLACEHOLDERS: Readonly<Record<string, string>>;
40
41
  /** Reachability only — for routes that are not pages (`/og`, `/llms.txt`, …). */
@@ -46,17 +46,14 @@ export const CLAIM_GATED_ROUTES = {
46
46
  * `CLAIM_GATED_ROUTES`, a route absent from the route table is not stale: the
47
47
  * module that owns it may simply not be installed in this leg.
48
48
  */
49
- export const CONFIG_GATED_ROUTES = {
50
- "/meet": "SCHEDULING_CAL_LINK",
51
- };
49
+ export const CONFIG_GATED_ROUTES = {};
52
50
  /**
53
51
  * The placeholder heading each `CONFIG_GATED_ROUTES` entry renders while its
54
- * env var is unset — for `/meet`, the hero headline of the overlay's
55
- * `content/en/LandingPage/meet.mdx` seed. A fresh scaffold never edits it.
52
+ * env var is unset. Empty since #8843 D14: `/meet` reads the workspace's
53
+ * meetings and answers 200 with its own H1 unconfigured, so no route is
54
+ * config-gated today.
56
55
  */
57
- export const CONFIG_GATED_PLACEHOLDERS = {
58
- "/meet": "{Book a time with us}",
59
- };
56
+ export const CONFIG_GATED_PLACEHOLDERS = {};
60
57
  const LOOPBACK_BASE = "http://localhost";
61
58
  function pathnameOf(location, relativeTo) {
62
59
  return new URL(location, new URL(relativeTo, LOOPBACK_BASE)).pathname;
@@ -793,6 +793,9 @@ export const MODULE_SLICES = {
793
793
  "apps/api/src/routes/workspaces/index.ts",
794
794
  "apps/api/src/routes/workspaces/sessions.ts",
795
795
  "apps/api/src/routes/workspaces/config.ts",
796
+ // #8926 D1 — the workspace Offerings routes, registered in the
797
+ // multi-tenant tenant scope beside the billing routes.
798
+ "apps/api/src/routes/workspaces/offerings.ts",
796
799
  // #8438 D8 — the Admin owner's consents to Working Theory: registered
797
800
  // under `isModuleEnabled("multi-tenant")` alone, the module that
798
801
  // contributes the Admin app.
@@ -805,6 +808,8 @@ export const MODULE_SLICES = {
805
808
  "apps/api/src/routes/billing/index.ts",
806
809
  "apps/api/src/routes/billing/portal.ts",
807
810
  "apps/api/src/routes/billing/read.ts",
811
+ "apps/api/src/routes/billing/stripe-connection.ts",
812
+ "apps/api/src/routes/billing/stripe-products.ts",
808
813
  "apps/api/src/routes/billing/usage.ts",
809
814
  "apps/api/src/routes/waitlist-experiments/index.ts",
810
815
  "apps/api/src/routes/waitlist-experiments/list.ts",
@@ -854,6 +859,10 @@ export const MODULE_SLICES = {
854
859
  "apps/api/src/routes/workspaces/submissions.ts",
855
860
  "apps/api/src/routes/workspaces/subscribers.ts",
856
861
  "apps/api/src/services/inbox-submissions.ts",
862
+ // #9170 — the Inbox orders read: the same gate and the same
863
+ // tenant-db.ts import, co-owned with `workspace` for the same reason.
864
+ "apps/api/src/routes/workspaces/orders.ts",
865
+ "apps/api/src/services/inbox-orders.ts",
857
866
  // #852 D8 — the People reads: the same workspace||multi-tenant gate and
858
867
  // the same tenant-db.ts import, co-owned with `workspace` for the same
859
868
  // reason.
@@ -1023,6 +1032,9 @@ export const MODULE_SLICES = {
1023
1032
  "apps/api/src/routes/workspaces/submissions.ts",
1024
1033
  "apps/api/src/routes/workspaces/subscribers.ts",
1025
1034
  "apps/api/src/services/inbox-submissions.ts",
1035
+ // Co-owned with `multi-tenant` (#9170), as the reads above.
1036
+ "apps/api/src/routes/workspaces/orders.ts",
1037
+ "apps/api/src/services/inbox-orders.ts",
1026
1038
  // Co-owned with `multi-tenant` (#852 D8), as the reads above.
1027
1039
  "apps/api/src/routes/workspaces/people.ts",
1028
1040
  // Co-owned with `multi-tenant` (#7877 D9, D11) — see that slice's entry.
@@ -1134,8 +1146,16 @@ export const MODULE_SLICES = {
1134
1146
  pattern: CAL_DOMAIN_PATTERN,
1135
1147
  // The two server-side apps: apps/api holds the provider client, and
1136
1148
  // apps/mcp's tools name booking links in their fixtures. Everything
1137
- // else — apps/web above all — is browser-reachable.
1138
- paths_excluded: ["apps/api/**", "apps/mcp/**"],
1149
+ // else — apps/web above all — is browser-reachable. The two web files
1150
+ // are the booking pages' CSP (#8843 D12): the policy file names the
1151
+ // embed's origin only to admit it on those pages, and the middleware
1152
+ // applies it. Same carve-out as the source monorepo's architecture.yaml.
1153
+ paths_excluded: [
1154
+ "apps/api/**",
1155
+ "apps/mcp/**",
1156
+ "apps/web/security-headers.config.ts",
1157
+ "apps/web/middleware.ts",
1158
+ ],
1139
1159
  reason: "Only this site's server tier addresses the provider; every browser call goes to this site's own /v1/schedule/* routes instead",
1140
1160
  },
1141
1161
  {
@@ -1224,10 +1244,22 @@ export const MODULE_SLICES = {
1224
1244
  "apps/api/src/services/scheduling/__tests__/cal-provider.test.ts",
1225
1245
  "apps/mcp/src/tools/list_availability.ts",
1226
1246
  "apps/mcp/src/tools/get_event_meta.ts",
1247
+ // #8843 D5 — the twin of GET /v1/schedule/meetings.
1248
+ "apps/mcp/src/tools/list_meetings.ts",
1227
1249
  "apps/mcp/src/tools/create_booking.ts",
1228
1250
  "apps/web/app/[locale]/meet/page.tsx",
1229
1251
  "apps/web/app/[locale]/meet/[slug]/page.tsx",
1230
1252
  "apps/web/app/[locale]/meet/confirmed/page.tsx",
1253
+ // #8843 D6–D8 — the booking pages' one builder, the index frame
1254
+ // `/meet` and `/private` share, the private index, and their suites,
1255
+ // individually (§ D12, property 1: a glob ships no test).
1256
+ "apps/web/lib/meetings.tsx",
1257
+ "apps/web/lib/__tests__/meetings.test.tsx",
1258
+ "apps/web/lib/__tests__/meetings.fixtures.ts",
1259
+ "apps/web/app/[locale]/meet/meeting-index.tsx",
1260
+ "apps/web/app/[locale]/private/page.tsx",
1261
+ "apps/web/app/[locale]/private/__tests__/page.test.tsx",
1262
+ "apps/web/app/[locale]/private/__tests__/sitemap.test.ts",
1231
1263
  // #6085 (brief H7) — the six files the webhook receiver + the
1232
1264
  // tenant-guarded appointments read add. Individually, never as a
1233
1265
  // glob (§ D12 / this card's own comment above, property 1: a glob
@@ -1269,6 +1301,9 @@ export const MODULE_SLICES = {
1269
1301
  "apps/api/src/lib/public-meetings-config.ts",
1270
1302
  "apps/api/src/routes/schedule/meeting-types.ts",
1271
1303
  "apps/api/src/routes/schedule/__tests__/meeting-types.test.ts",
1304
+ // #8843 D1 — the meetings read's own suite; the route is in
1305
+ // `routes/schedule/index.ts`, listed above.
1306
+ "apps/api/src/routes/schedule/__tests__/meetings.test.ts",
1272
1307
  // #7532 — a hub spoke's meeting types and booking, through the real
1273
1308
  // scheduling routes; it ships and strips with them.
1274
1309
  "apps/api/src/routes/schedule/__tests__/spoke.test.ts",
@@ -1301,6 +1336,10 @@ export const MODULE_SLICES = {
1301
1336
  "apps/api/src/services/scheduling/resolve.ts",
1302
1337
  "apps/api/src/services/scheduling/__tests__/connections.test.ts",
1303
1338
  "apps/api/src/services/scheduling/__tests__/provider-resolution.test.ts",
1339
+ // #9129 — the meetings-list assembly the meetings route and the member
1340
+ // meeting types share, plus its suite.
1341
+ "apps/api/src/services/scheduling/meetings.ts",
1342
+ "apps/api/src/services/scheduling/__tests__/meetings.test.ts",
1304
1343
  "apps/api/src/routes/workspaces/__tests__/meetings-connection.test.ts",
1305
1344
  "apps/api/src/routes/schedule/__tests__/public-connection.test.ts",
1306
1345
  "apps/api/src/routes/webhooks/__tests__/calcom-connection.test.ts",
@@ -1323,14 +1362,16 @@ export const MODULE_SLICES = {
1323
1362
  "apps/api/src/__tests__/schedule-forwarded-rate-limit.test.ts",
1324
1363
  "apps/api/src/__tests__/openapi-keyless-hub.test.ts",
1325
1364
  ],
1326
- // pageContributions (spec 5478; parent § D6 shape 1) — the booker, the
1327
- // per-booking-type page and the confirmation page, whose route files
1328
- // `ownedFiles` above already carries. No row names seed content: all
1329
- // three render from the /v1/schedule/* routes, not from an MDX file.
1365
+ // pageContributions (spec 5478; parent § D6 shape 1) — the meeting
1366
+ // index, the meeting page, the confirmation page and the private index
1367
+ // (#8843 D8), whose route files `ownedFiles` above already carries. No
1368
+ // row names seed content: all four render from the /v1/schedule/*
1369
+ // routes, not from an MDX file.
1330
1370
  pageContributions: [
1331
1371
  { route: "/meet" },
1332
1372
  { route: "/meet/[slug]" },
1333
1373
  { route: "/meet/confirmed" },
1374
+ { route: "/private" },
1334
1375
  // #6086 — the authed appointments page. Bracket (roster) grammar and no
1335
1376
  // `content` key, for the same reason the three /meet rows carry none:
1336
1377
  // it renders from `/v1/schedule/appointments`, not from an MDX file.
@@ -1668,6 +1709,16 @@ export const MODULE_SLICES = {
1668
1709
  // `NESTED_FOREIGN_MODULE_ENTRIES` for the companion fix that keeps its
1669
1710
  // presence from falsely reading as "projects carried".
1670
1711
  "apps/web/app/[locale]/projects/apply/ListingApplicationClient.tsx",
1712
+ // #8840 D11/D13 — the Job application magnet's two walks. Same kind as
1713
+ // `projects/apply` above: they sit under the UNRELATED "careers"
1714
+ // surface's URL segment, which `excludedRoutePaths` does not gate on
1715
+ // signal-collectors, so `ownedFiles` keeps them out of a keyless
1716
+ // scaffold, and `NESTED_FOREIGN_MODULE_ENTRIES` keeps their presence
1717
+ // from reading as "careers carried". `flows/[slug]/magnet-page.tsx`,
1718
+ // the renderer they mount, rides the `flows` surface's directory with
1719
+ // its siblings (the note above).
1720
+ "apps/web/app/[locale]/careers/apply/page.tsx",
1721
+ "apps/web/app/[locale]/careers/[slug]/apply/page.tsx",
1671
1722
  // #5581 U4 — the eleven signal-collectors components + the slice's own
1672
1723
  // re-export entry point moved to packages-paid/ui-signal-collectors;
1673
1724
  // the package is delivered to a keyed scaffold via `patches` below,
@@ -1683,6 +1734,17 @@ export const MODULE_SLICES = {
1683
1734
  // spec 6892 D2 — the /reports hub, alongside its reader above: also a
1684
1735
  // URL segment MODULE_SURFACES["signal-collectors"] does not gate.
1685
1736
  "apps/web/app/[locale]/reports/page.tsx",
1737
+ // #8848 D6, D7 — the hub deliverable at `/reports/<slug>`: its renderer,
1738
+ // the gated walk and the ungated file's byte proxy. Same segment, same
1739
+ // reason as the reader above.
1740
+ "apps/web/app/[locale]/reports/deliverable-page.tsx",
1741
+ "apps/web/app/[locale]/reports/[slug]/start/page.tsx",
1742
+ "apps/web/app/[locale]/reports/[slug]/file/route.ts",
1743
+ // #8848 D12 — `/downloads` redirects into `/reports`, so a scaffold
1744
+ // without this module ships no `/downloads` at all (the redirect target
1745
+ // is this module's) rather than the old broken list.
1746
+ "apps/web/app/[locale]/downloads/page.tsx",
1747
+ "apps/web/app/[locale]/downloads/[slug]/page.tsx",
1686
1748
  //
1687
1749
  // The two sections that route composes — `report-header` and
1688
1750
  // `reports-list` — are deliberately NOT owned here. They are
@@ -1721,6 +1783,9 @@ export const MODULE_SLICES = {
1721
1783
  // the module gets them — `moduleRosterRows` derives the code-route form
1722
1784
  // for this row rather than requiring a template to ship one (D9).
1723
1785
  { route: "/reports/[slug]", contentDir: "content/en/ReportPage" },
1786
+ // #8848 D7's walk (`/reports/[slug]/start`) is NOT a contribution: a
1787
+ // contribution needs a shipped template manifest to carry its row, and
1788
+ // none does yet. Its code ships through `ownedFiles` above.
1724
1789
  ],
1725
1790
  // installMap (#5618 D6.3) — the module's sample files, moved here out
1726
1791
  // of `site-templates/marketing-site/` so the module OWNS its samples
@@ -1750,6 +1815,12 @@ export const MODULE_SLICES = {
1750
1815
  // code of its own for either route.
1751
1816
  // `generate-sample-report.py` keeps its root-level destination
1752
1817
  // (finding 1 — AC7 forbids changing the Pro file set).
1818
+ // `diagnostic` and `join-waitlist` are the starter's two placeholder
1819
+ // flow seeds (#8850 D5): every visible string a `{brace}`, every key one
1820
+ // a spoke may seed, so `create-ailk seed-flows` sends them to a hub with
1821
+ // no edits once `spokeDestination` lands them at `flows/<slug>.json`. A
1822
+ // hub gets them as inert `database/flows/` files, as it gets
1823
+ // `fit-check` and `referral`.
1753
1824
  installMap: {
1754
1825
  "packages/create-ailk/module-templates/signal-collectors/content/en/ProjectsList/projects.mdx": "content/en/ProjectsList/projects.mdx",
1755
1826
  "packages/create-ailk/module-templates/signal-collectors/content/en/Project/fit-check-funnel.mdx": "content/en/Project/fit-check-funnel.mdx",
@@ -1765,6 +1836,8 @@ export const MODULE_SLICES = {
1765
1836
  "packages/create-ailk/module-templates/signal-collectors/content/en/LandingPage/referral.mdx": "content/en/LandingPage/referral.mdx",
1766
1837
  "packages/create-ailk/module-templates/signal-collectors/flows/fit-check.flow.json": "database/flows/fit-check.flow.json",
1767
1838
  "packages/create-ailk/module-templates/signal-collectors/flows/referral.flow.json": "database/flows/referral.flow.json",
1839
+ "packages/create-ailk/module-templates/signal-collectors/flows/diagnostic.flow.json": "database/flows/diagnostic.flow.json",
1840
+ "packages/create-ailk/module-templates/signal-collectors/flows/join-waitlist.flow.json": "database/flows/join-waitlist.flow.json",
1768
1841
  "packages/create-ailk/module-templates/signal-collectors/waitlist/launch-waitlist.experiment.json": "database/waitlist/launch-waitlist.experiment.json",
1769
1842
  "packages/create-ailk/module-templates/signal-collectors/reports/sample-report.pdf": "apps/web/public/reports/sample-report.pdf",
1770
1843
  "packages/create-ailk/module-templates/signal-collectors/reports/generate-sample-report.py": "reports/generate-sample-report.py",
@@ -1812,9 +1885,11 @@ export const MODULE_SLICES = {
1812
1885
  // checkout (`SPOKE_HUB_CHECKOUT_SEEDS`: flows/[slug]/checkout.actions.ts,
1813
1886
  // the flow results pages, dev/purchase — spoke-intake Rule 3). What lands
1814
1887
  // is the scaffold-commerce skill, the commerce-boundary rule and the
1815
- // @working-theory/ui-commerce dependency (`patches` below); the spoke's own
1816
- // checkout — two routes, checkout-config.ts and two return pages — is
1817
- // rendered by create-ailk (`spoke-checkout.ts`), not carried here.
1888
+ // @working-theory/ui-commerce dependency (`patches` below). A spoke sells
1889
+ // through its hub's Offerings buy route (#9142); its remaining checkout
1890
+ // files — `apps/web` template source (checkout-config.ts, two return
1891
+ // pages and their tests) — are copied by `writeSpokeCheckoutSlice`
1892
+ // (`spoke-checkout.ts`, #8748), not carried here.
1818
1893
  spoke: true,
1819
1894
  paidSectionsPackage: "@working-theory/ui-commerce",
1820
1895
  apps: {
@@ -2136,6 +2211,18 @@ export function coOwnedModuleDestinations() {
2136
2211
  // red the scaffold's database jest run. Matched exactly, not by basename
2137
2212
  // in `EXCLUDE_FILES`, which would drop a `build.test.ts` anywhere in the
2138
2213
  // snapshot; every other database suite still ships.
2214
+ // - the /pricing suite `monorepo-only.test.tsx` and its snapshot (#9179) —
2215
+ // it snapshots the monorepo's own `content/en/Pricing/pricing.mdx`, which
2216
+ // a scaffold replaces with its seed, and reads
2217
+ // `packages/api-client/src/offerings.ts`, which no scaffold carries. Only
2218
+ // those two assertions moved there; `unconnected.test.tsx` and
2219
+ // `offerings.test.tsx` keep every scaffold-meaningful case and ship.
2220
+ // - the Offerings `delegate-confinement.test.ts` (#9179) — it runs
2221
+ // `git grep -- apps apps-paid packages` from the repo root to prove the
2222
+ // `stripeConnection` and `offeringsCheckoutSession` delegates are each
2223
+ // named by one file. A scaffold is not a git repository. Only those two
2224
+ // cases moved there; `connection.test.ts` and
2225
+ // `checkout-session-record.test.ts` keep the stores' coverage and ship.
2139
2226
  export const EXCLUDE_TEMPLATE_DIRS = [
2140
2227
  "apps/web/__tests__",
2141
2228
  "apps/web/app/__tests__",
@@ -2161,6 +2248,9 @@ export const EXCLUDE_TEMPLATE_DIRS = [
2161
2248
  "apps/mcp/__tests__/parity.test.ts",
2162
2249
  "apps/mcp/__tests__/audit.test.ts",
2163
2250
  "database/__tests__/build.test.ts",
2251
+ "apps/web/app/[locale]/pricing/__tests__/monorepo-only.test.tsx",
2252
+ "apps/web/app/[locale]/pricing/__tests__/__snapshots__/monorepo-only.test.tsx.snap",
2253
+ "apps/api/src/services/offerings/__tests__/delegate-confinement.test.ts",
2164
2254
  ];
2165
2255
  export function paidModuleTemplateExclusions(snapshotRoots, repoRoot) {
2166
2256
  const roots = new Set(snapshotRoots);
@@ -1,5 +1,7 @@
1
1
  /** Scaffold-relative path of the generated registry. */
2
2
  export declare const PAID_SECTIONS_PATH = "apps/web/lib/paid-sections.generated.ts";
3
+ /** Scaffold-relative path of the generated module-presence flags (#8843 D10). */
4
+ export declare const MODULE_FLAGS_PATH = "apps/web/lib/modules.generated.ts";
3
5
  export interface PaidSectionRow {
4
6
  module: string;
5
7
  package: string;
@@ -15,9 +17,26 @@ export declare function paidSectionRows(): PaidSectionRow[];
15
17
  * public strip drops the file (\`check-oss-strip.sh\` condition (e)).
16
18
  */
17
19
  export declare function renderPaidSections(present: ReadonlySet<string>): string;
20
+ /**
21
+ * Render the module-presence flags. Line 1 is `// oss: false` exactly when a
22
+ * flag is on, the registry's own rule: an `on` flag names pages the public
23
+ * tree does not carry.
24
+ */
25
+ export declare function renderModuleFlags(flags: {
26
+ schedulingPages: boolean;
27
+ }): string;
28
+ /**
29
+ * Write the module-presence flags into a scaffold from its own
30
+ * `apps/web/package.json` (#8843 D10) — never copied from a template, so a
31
+ * site without the scheduling module can never carry `true`. Answers whether
32
+ * the file was `added`, `updated` (changed bytes) or `unchanged`, or
33
+ * `undefined` (writing nothing) for a scaffold with no web app.
34
+ */
35
+ export declare function writeModuleFlags(targetDir: string): "added" | "updated" | "unchanged" | undefined;
18
36
  /**
19
37
  * Write the registry into a scaffold, deriving presence from the scaffold's
20
- * own `apps/web/package.json`. Returns `false` (writing nothing) when that
21
- * file or `apps/web/lib/` is absent — a scaffold with no web app.
38
+ * own `apps/web/package.json`, and the module-presence flags beside it.
39
+ * Returns `false` (writing nothing) when that file or `apps/web/lib/` is
40
+ * absent — a scaffold with no web app.
22
41
  */
23
42
  export declare function writePaidSections(targetDir: string): boolean;
@@ -15,6 +15,13 @@
15
15
  * every layer (snapshot transform, module `patches`, `add-module`) already
16
16
  * keeps true. The monorepo's committed copy is the render with every row
17
17
  * present and is pinned byte-equal by `__tests__/paid-sections.test.ts`.
18
+ *
19
+ * Its sibling, `apps/web/lib/modules.generated.ts` (#8843 D10), is the
20
+ * module-presence flag: `SCHEDULING_PAGES` is true exactly when the site
21
+ * carries the scheduling slice's UI package, so the locale layout drops the
22
+ * marketing chrome on the booking pages only where they exist. A separate
23
+ * file, importing nothing, because the registry's `import()`s would pull UI
24
+ * packages into any edge code that reads the flag.
18
25
  */
19
26
  import { existsSync, readFileSync, writeFileSync } from "node:fs";
20
27
  import { join } from "node:path";
@@ -22,6 +29,10 @@ import { MODULE_SLICES } from "./module-architecture.js";
22
29
  import { RECOGNIZED_MODULES } from "./parse-args.js";
23
30
  /** Scaffold-relative path of the generated registry. */
24
31
  export const PAID_SECTIONS_PATH = "apps/web/lib/paid-sections.generated.ts";
32
+ /** Scaffold-relative path of the generated module-presence flags (#8843 D10). */
33
+ export const MODULE_FLAGS_PATH = "apps/web/lib/modules.generated.ts";
34
+ /** The dependency whose presence means the scheduling module's pages exist. */
35
+ const SCHEDULING_PAGES_PACKAGE = "@working-theory/ui-scheduling";
25
36
  function pascalCase(shortName) {
26
37
  return shortName
27
38
  .split("-")
@@ -93,27 +104,78 @@ export function renderPaidSections(present) {
93
104
  }
94
105
  return `${lines.join("\n")}\n`;
95
106
  }
107
+ const MODULE_FLAGS_HEADER = `// modules.generated.ts — which module pages this site carries.
108
+ //
109
+ // GENERATED by create-ailk from this app's package.json dependencies (#8843).
110
+ // Do not edit: every scaffold, \`create-ailk add-module\` and
111
+ // \`create-ailk update\` rewrites it. It imports nothing, so edge code may
112
+ // read it as well as the locale layout.
113
+ `;
96
114
  /**
97
- * Write the registry into a scaffold, deriving presence from the scaffold's
98
- * own `apps/web/package.json`. Returns `false` (writing nothing) when that
99
- * file or `apps/web/lib/` is absent — a scaffold with no web app.
115
+ * Render the module-presence flags. Line 1 is `// oss: false` exactly when a
116
+ * flag is on, the registry's own rule: an `on` flag names pages the public
117
+ * tree does not carry.
100
118
  */
101
- export function writePaidSections(targetDir) {
119
+ export function renderModuleFlags(flags) {
120
+ const lines = [];
121
+ if (flags.schedulingPages)
122
+ lines.push("// oss: false");
123
+ lines.push(MODULE_FLAGS_HEADER.trimEnd());
124
+ lines.push("");
125
+ lines.push("/** The scheduling module's booking pages (/meet, /private) exist. */");
126
+ lines.push(`export const SCHEDULING_PAGES: boolean = ${String(flags.schedulingPages)};`);
127
+ return `${lines.join("\n")}\n`;
128
+ }
129
+ /** The web app's `dependencies`, or `undefined` for a scaffold with no web app. */
130
+ function webDependencies(targetDir, writing) {
102
131
  const pkgPath = join(targetDir, "apps/web/package.json");
103
132
  if (!existsSync(pkgPath) || !existsSync(join(targetDir, "apps/web/lib"))) {
104
- return false;
133
+ return undefined;
105
134
  }
106
135
  let pkg;
107
136
  try {
108
137
  pkg = JSON.parse(readFileSync(pkgPath, "utf8"));
109
138
  }
110
139
  catch (err) {
111
- throw new Error(`cannot read ${pkgPath} to write the paid-sections registry: ${err.message}`, { cause: err });
140
+ throw new Error(`cannot read ${pkgPath} to write the ${writing}: ${err.message}`, { cause: err });
112
141
  }
113
- const deps = pkg.dependencies ?? {};
142
+ return pkg.dependencies ?? {};
143
+ }
144
+ /**
145
+ * Write the module-presence flags into a scaffold from its own
146
+ * `apps/web/package.json` (#8843 D10) — never copied from a template, so a
147
+ * site without the scheduling module can never carry `true`. Answers whether
148
+ * the file was `added`, `updated` (changed bytes) or `unchanged`, or
149
+ * `undefined` (writing nothing) for a scaffold with no web app.
150
+ */
151
+ export function writeModuleFlags(targetDir) {
152
+ const deps = webDependencies(targetDir, "module flags");
153
+ if (deps === undefined)
154
+ return undefined;
155
+ const path = join(targetDir, MODULE_FLAGS_PATH);
156
+ const before = existsSync(path) ? readFileSync(path, "utf8") : undefined;
157
+ const next = renderModuleFlags({
158
+ schedulingPages: Object.hasOwn(deps, SCHEDULING_PAGES_PACKAGE),
159
+ });
160
+ if (before === next)
161
+ return "unchanged";
162
+ writeFileSync(path, next);
163
+ return before === undefined ? "added" : "updated";
164
+ }
165
+ /**
166
+ * Write the registry into a scaffold, deriving presence from the scaffold's
167
+ * own `apps/web/package.json`, and the module-presence flags beside it.
168
+ * Returns `false` (writing nothing) when that file or `apps/web/lib/` is
169
+ * absent — a scaffold with no web app.
170
+ */
171
+ export function writePaidSections(targetDir) {
172
+ const deps = webDependencies(targetDir, "paid-sections registry");
173
+ if (deps === undefined)
174
+ return false;
114
175
  const present = new Set(paidSectionRows()
115
176
  .map((row) => row.package)
116
177
  .filter((name) => Object.hasOwn(deps, name)));
117
178
  writeFileSync(join(targetDir, PAID_SECTIONS_PATH), renderPaidSections(present));
179
+ writeModuleFlags(targetDir);
118
180
  return true;
119
181
  }
@@ -44,7 +44,7 @@ import { SITE_CHROME_PATH, renderSiteChrome, resolveSiteChrome, } from "./site-c
44
44
  import { SITE_FONTS_PATH, renderSiteFonts } from "./site-fonts.js";
45
45
  import { planSiteManifest } from "./site-manifest.js";
46
46
  import { SITE_ROSTER_PATH, planHomeRecord, planSiteManifestDesign, readSiteRoster, withMeetPlaceholderSeeds, } from "./site-roster.js";
47
- import { spokeCheckoutSite, spokeEnvRowOptions, writeSpokeCheckoutSlice, } from "./spoke-checkout.js";
47
+ import { SPOKE_CHECKOUT_TEMPLATE_PATHS, dropSpokeCheckoutDependency, spokeCheckoutSite, spokeEnvRowOptions, writeSpokeCheckoutSlice, } from "./spoke-checkout.js";
48
48
  import { GATEABLE_SURFACES, MODULE_SURFACES, NAV_CONFIG_PATH, excludedRoutePaths, isUnservedSurfaceContent, renderNavConfig, resolveSurfaces, } from "./surfaces.js";
49
49
  import { syncRoutes } from "./sync-routes.js";
50
50
  import { BASE_LAYER_DIR, SHAPE_STARTERS, TEMPLATE_MANIFESTS, TemplateError, mergeRoster, resolveTemplate, resolveTemplateLineage, rosterDeclares, templateDelivery, templateTreeDir, } from "./template-manifest.js";
@@ -182,6 +182,9 @@ export function scaffoldSync(opts, copied) {
182
182
  ...(spoke !== undefined
183
183
  ? spokeExclusions(TEMPLATES_DIR, spoke)
184
184
  : SPOKE_ONLY_FILES),
185
+ // #8748 D3 — the standard spoke checkout lands only through
186
+ // `writeSpokeCheckoutSlice`, on a spoke carrying commerce.
187
+ ...SPOKE_CHECKOUT_TEMPLATE_PATHS,
185
188
  ],
186
189
  markers: buildScaffoldMarkers(projectName),
187
190
  ...(copied !== undefined
@@ -193,6 +196,9 @@ export function scaffoldSync(opts, copied) {
193
196
  }
194
197
  : {}),
195
198
  });
199
+ // #8748 D3 — the copied apps/web depends on the checkout package; only a
200
+ // commerce spoke keeps it, re-added by `writeSpokeCheckoutSlice` in scaffold().
201
+ dropSpokeCheckoutDependency(targetAbs);
196
202
  // #7426 D5/D6 — the root files a spoke rewrites, and the spoke seams.
197
203
  if (spoke !== undefined)
198
204
  applySpokeTransforms(targetAbs, spoke);
@@ -793,18 +799,26 @@ export async function scaffold(opts) {
793
799
  writeFileSync(rosterPath, manifestText);
794
800
  }
795
801
  }
796
- // #7838 D8 — a commerce spoke's own checkout: two routes, its config and
797
- // two return pages, once every layer has landed (so the spoke filter has
798
- // already dropped the hub-checkout files) and before the manifest, whose
799
- // code-route rows then record /checkout/thank-you and /checkout/unavailable.
802
+ // #7838 D8 — a commerce spoke's own checkout: the standard spoke checkout
803
+ // copied from the template (#8748 D3), its config stamped, once every layer
804
+ // has landed (so the spoke filter has already dropped the hub-checkout
805
+ // files) and before the manifest, whose code-route rows then record
806
+ // /checkout/thank-you and /checkout/unavailable. Each copied file is
807
+ // recorded like the base copy's, so `update` keeps it current.
800
808
  // #8217 D8 — a spoke that signs in through its hub also gets the billing
801
809
  // action and the web seam render that reaches it.
802
810
  const spokeCommerce = spoke !== undefined && result.modules.includes("commerce");
803
811
  if (spokeCommerce) {
804
812
  writeSpokeCheckoutSlice(result.targetDir, {
805
813
  site: spokeCheckoutSite(projectName),
814
+ projectName,
806
815
  versions,
816
+ templatesDir: TEMPLATES_DIR,
807
817
  signIn: spoke.signIn,
818
+ onWrite: (relPath, bytes) => {
819
+ if (isRecordable(relPath))
820
+ copied.set(relPath, sha256Hex(bytes));
821
+ },
808
822
  });
809
823
  }
810
824
  // #6944 — the manifest records what this scaffold IS, once every layer has
@@ -26,6 +26,8 @@ export type SeedFlowOutcome = {
26
26
  ok: true;
27
27
  result: string;
28
28
  status: string;
29
+ /** #8850 D6 — the flow's copy is the starter's `{brace}` placeholder. */
30
+ placeholder?: true;
29
31
  } | {
30
32
  slug: string;
31
33
  ok: false;
@@ -33,6 +35,7 @@ export type SeedFlowOutcome = {
33
35
  } | {
34
36
  slug: string;
35
37
  ok: "dry-run";
38
+ placeholder?: true;
36
39
  };
37
40
  export interface SeedFlowsResult {
38
41
  /** The hub origin, when one resolved (a dry run may have none). */
@@ -49,7 +52,7 @@ export interface SeedFlowsResult {
49
52
  /** The whole report, one line per row — what `seedFlows` printed as it went. */
50
53
  export declare function formatSeedFlowsReport(result: SeedFlowsResult): string;
51
54
  export declare function seedFlows(opts: SeedFlowsOptions): Promise<SeedFlowsResult>;
52
- export declare const SPOKE_FLOWS_README = "# flows/\n\nEach file here is one **declared flow**: `flows/<slug>.json` holds one flow\nconfig, exactly as the hub stores it. This repo owns these configs \u2014 the hub\nruns them for this site.\n\n## What a spoke may declare\n\nA spoke key may seed only what this site's own flow pages show:\n`slug`, `defaultLocale`, `steps`, `thankYou` and `flags`. Anything the\nhub acts on outside these pages \u2014 emails, deliverables, pricing, registers,\nlistings \u2014 is refused (`field_not_seedable`). A flow that needs one of those\nis authored in the hub's Admin instead.\n\nThe file name must be the config's `slug` plus `.json`.\n\n## Seeding them into the hub\n\nRun this as part of each rollout, from the site's root, with the spoke key\nyour hub issued set as `AILK_SPOKE_KEY` in the environment (a shell or CI\nsecret \u2014 never a file in this repo):\n\n npx create-ailk seed-flows .\n\nThe hub origin comes from `--hub-url`, else `AILK_API_URL`, else the\n`AILK_API_URL=` line of `apps/web/.env.local`. The key is read from the\nenvironment only. Each flow answers `created`, `updated` or `unchanged`,\nso running it again is safe. `--dry-run` checks the files and sends nothing.\n\nThe hub's operator must first turn seeding on (`AILK_SPOKE_FLOW_SEED=true`\non the hub). Until it is on and this command has run, links to these flows\n(`/flows/<slug>`) reach no flow.\n\n## Who owns what\n\nThe next seed that differs overwrites the hub's copy of a flow's config, so\nedit flows here, not in the Admin. A flow's status (live or draft) is the\nhub's: switching a seeded flow to draft in the Admin keeps it draft through\nevery later seed. Removing a file here does not delete the hub's flow. If\nthe Admin adds a block only it can author (an email, a deliverable, an\noffer), the hub refuses the next seed of that flow (`slug_owned`) rather\nthan delete the block.\n";
55
+ export declare const SPOKE_FLOWS_README = "# flows/\n\nEach file here is one **declared flow**: `flows/<slug>.json` holds one flow\nconfig, exactly as the hub stores it. This repo owns these configs \u2014 the hub\nruns them for this site.\n\n## What a spoke may declare\n\nA spoke key may seed only what this site's own flow pages show:\n`slug`, `defaultLocale`, `steps`, `thankYou` and `flags`. Anything the\nhub acts on outside these pages \u2014 emails, deliverables, pricing, registers,\nlistings \u2014 is refused (`field_not_seedable`). A flow that needs one of those\nis authored in the hub's Admin instead.\n\nThe file name must be the config's `slug` plus `.json`.\n\n## Seeding them into the hub\n\nRun this as part of each rollout, from the site's root, with the spoke key\nyour hub issued set as `AILK_SPOKE_KEY` in the environment (a shell or CI\nsecret \u2014 never a file in this repo):\n\n npx create-ailk seed-flows .\n\nThe hub origin comes from `--hub-url`, else `AILK_API_URL`, else the\n`AILK_API_URL=` line of `apps/web/.env.local`. The key is read from the\nenvironment only. Each flow answers `created`, `updated` or `unchanged`,\nso running it again is safe. `--dry-run` checks the files and sends nothing.\n\nThe hub's operator must first turn seeding on (`AILK_SPOKE_FLOW_SEED=true`\non the hub). Until it is on and this command has run, links to these flows\n(`/flows/<slug>`) reach no flow.\n\n## The starter's placeholder flows\n\nThe `signal-collectors` module ships two declared flows here:\n`flows/diagnostic.json` (an email step, then two questions) and\n`flows/join-waitlist.json` (an email and an optional name). Every visible\nstring in both is a `{brace}` placeholder: replace each one with your own\ncopy. Both go live on the hub with the first seed, so each line this command\nprints for them says they still carry placeholder copy.\n\nA flow is reached at `/flows/<slug>`. To give one its own page pair, run:\n\n npx create-ailk add-route magnet --path /diagnostic --flow diagnostic\n npx create-ailk add-route magnet --path /waitlist --flow join-waitlist\n\nA job application comes with the `careers` surface. Contact, newsletter and\njob application are the hub's own standard magnets: the hub seeds them in\nevery workspace and refuses a spoke's copy of each, so they need no file\nhere. If the hub's Admin already owns a magnet under one of these two slugs,\nthe hub refuses the seed (`slug_owned`); rename the file and its `slug`.\n\n## Who owns what\n\nThe next seed that differs overwrites the hub's copy of a flow's config, so\nedit flows here, not in the Admin. A flow's status (live or draft) is the\nhub's: switching a seeded flow to draft in the Admin keeps it draft through\nevery later seed. Removing a file here does not delete the hub's flow. If\nthe Admin adds a block only it can author (an email, a deliverable, an\noffer), the hub refuses the next seed of that flow (`slug_owned`) rather\nthan delete the block.\n";
53
56
  /**
54
57
  * Write `SPOKE_FLOWS_README` to `<targetDir>/flows/README.md` unless that
55
58
  * file exists. Returns whether it wrote.