create-ailk 0.22.0 → 0.24.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 (287) hide show
  1. package/README.md +27 -14
  2. package/component-catalog.json +149 -5
  3. package/component-catalog.md +7 -6
  4. package/dist/add-module.js +17 -10
  5. package/dist/add-route.d.ts +55 -1
  6. package/dist/add-route.js +301 -13
  7. package/dist/cli.js +27 -10
  8. package/dist/derive-architecture.d.ts +7 -5
  9. package/dist/derive-architecture.js +26 -32
  10. package/dist/lib/design-checks.js +4 -2
  11. package/dist/lib/env-catalog.js +0 -4
  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 +36 -2
  15. package/dist/lib/scaffold-render-sweep.js +50 -8
  16. package/dist/module-architecture.js +115 -10
  17. package/dist/paid-sections.d.ts +21 -2
  18. package/dist/paid-sections.js +69 -7
  19. package/dist/seed-flows.d.ts +4 -1
  20. package/dist/seed-flows.js +50 -4
  21. package/dist/spoke-checkout.d.ts +38 -35
  22. package/dist/spoke-checkout.js +66 -229
  23. package/dist/starter-grid.d.ts +0 -3
  24. package/dist/starter-grid.js +24 -14
  25. package/dist/surfaces.js +33 -12
  26. package/dist/sync-routes.d.ts +68 -5
  27. package/dist/sync-routes.js +455 -29
  28. package/dist/template-record.d.ts +1 -1
  29. package/dist/template-record.js +3 -2
  30. package/dist/update.js +10 -0
  31. package/package.json +3 -3
  32. package/site-templates/_base/content/en/NewsletterSignup/newsletter.mdx +4 -49
  33. package/site-templates/_base/content/en/Pricing/pricing.mdx +3 -21
  34. package/site-templates/marketing-site/TEMPLATE-MANIFEST.json +23 -20
  35. package/site-templates/marketing-site/TEMPLATE-MANIFEST.md +28 -14
  36. package/site-templates/marketing-site-free/TEMPLATE-MANIFEST.json +15 -39
  37. package/site-templates/marketing-site-free/TEMPLATE-MANIFEST.md +38 -34
  38. package/site-templates/marketing-site-free/content/en/NewsletterSignup/newsletter.mdx +6 -43
  39. package/site-templates/shapes/local-business-service/TEMPLATE-MANIFEST.json +14 -31
  40. package/site-templates/shapes/local-business-service/TEMPLATE-MANIFEST.md +26 -25
  41. package/site-templates/shapes/local-business-service/content/en/NewsletterSignup/newsletter.mdx +6 -43
  42. package/site-templates/shapes/organization-service/TEMPLATE-MANIFEST.json +14 -31
  43. package/site-templates/shapes/organization-service/TEMPLATE-MANIFEST.md +26 -25
  44. package/site-templates/shapes/organization-service/content/en/NewsletterSignup/newsletter.mdx +6 -43
  45. package/site-templates/shapes/person-service/TEMPLATE-MANIFEST.json +14 -31
  46. package/site-templates/shapes/person-service/TEMPLATE-MANIFEST.md +26 -25
  47. package/site-templates/shapes/person-service/content/en/NewsletterSignup/newsletter.mdx +6 -43
  48. package/templates/.env.example +13 -7
  49. package/templates/.github/workflows/ci.yml +13 -0
  50. package/templates/CLAUDE.md +4 -2
  51. package/templates/README.md +1 -0
  52. package/templates/_gitignore +4 -0
  53. package/templates/apps/api/.env.example +34 -0
  54. package/templates/apps/api/CLAUDE.md +94 -19
  55. package/templates/apps/api/package.json +1 -0
  56. package/templates/apps/api/src/__tests__/factories/in-memory-prisma.ts +2 -0
  57. package/templates/apps/api/src/bin/stripe-connections-rekey.ts +45 -0
  58. package/templates/apps/api/src/config/__tests__/stripe-connection-key.test.ts +185 -0
  59. package/templates/apps/api/src/config/index.ts +109 -0
  60. package/templates/apps/api/src/lib/__mocks__/prisma.ts +7 -1
  61. package/templates/apps/api/src/lib/__tests__/flow-rename-race.db.test.ts +116 -0
  62. package/templates/apps/api/src/lib/__tests__/site.test.ts +16 -0
  63. package/templates/apps/api/src/lib/error-serializer.ts +74 -7
  64. package/templates/apps/api/src/lib/site.ts +4 -1
  65. package/templates/apps/api/src/lib/stripe.ts +77 -4
  66. package/templates/apps/api/src/lib/webhook-idempotency.ts +66 -2
  67. package/templates/apps/api/src/middleware/__tests__/rate-limit.test.ts +218 -0
  68. package/templates/apps/api/src/middleware/__tests__/write-budget.test.ts +174 -0
  69. package/templates/apps/api/src/middleware/rate-limit.ts +226 -4
  70. package/templates/apps/api/src/openapi/__tests__/openapi.test.ts +127 -0
  71. package/templates/apps/api/src/openapi/spec.ts +343 -7
  72. package/templates/apps/api/src/routes/billing/plan-checkout.ts +332 -0
  73. package/templates/apps/api/src/routes/checkout/__tests__/live-key.test.ts +11 -0
  74. package/templates/apps/api/src/routes/checkout/__tests__/offerings-authorization.test.ts +1426 -0
  75. package/templates/apps/api/src/routes/checkout/__tests__/session.test.ts +11 -0
  76. package/templates/apps/api/src/routes/checkout/__tests__/sessions.test.ts +49 -0
  77. package/templates/apps/api/src/routes/checkout/session.ts +57 -25
  78. package/templates/apps/api/src/routes/checkout/sessions.ts +232 -82
  79. package/templates/apps/api/src/routes/consent/__tests__/account.test.ts +5 -5
  80. package/templates/apps/api/src/routes/consent/__tests__/sign-up-rate-limit.test.ts +160 -0
  81. package/templates/apps/api/src/routes/consent/account.ts +4 -2
  82. package/templates/apps/api/src/routes/content/__tests__/get-page.test.ts +2 -2
  83. package/templates/apps/api/src/routes/content/__tests__/offerings.test.ts +731 -0
  84. package/templates/apps/api/src/routes/content/__tests__/pricing.test.ts +753 -0
  85. package/templates/apps/api/src/routes/content/__tests__/shown-offerings.test.ts +762 -0
  86. package/templates/apps/api/src/routes/content/get-page.ts +34 -119
  87. package/templates/apps/api/src/routes/content/index.ts +16 -2
  88. package/templates/apps/api/src/routes/content/list-pages.ts +15 -36
  89. package/templates/apps/api/src/routes/content/offerings.ts +184 -0
  90. package/templates/apps/api/src/routes/content/pricing.ts +176 -0
  91. package/templates/apps/api/src/routes/content/search.ts +16 -45
  92. package/templates/apps/api/src/routes/content/shown-offerings.ts +229 -0
  93. package/templates/apps/api/src/routes/leads/index.ts +377 -308
  94. package/templates/apps/api/src/routes/project-listings/__tests__/me.test.ts +20 -9
  95. package/templates/apps/api/src/routes/project-listings/__tests__/public.test.ts +17 -6
  96. package/templates/apps/api/src/routes/project-listings/__tests__/site-answers.test.ts +1 -1
  97. package/templates/apps/api/src/routes/webhooks/README.md +66 -0
  98. package/templates/apps/api/src/routes/webhooks/__tests__/stripe-flow-checkout.test.ts +1 -0
  99. package/templates/apps/api/src/routes/webhooks/__tests__/stripe-offerings.test.ts +814 -0
  100. package/templates/apps/api/src/routes/webhooks/__tests__/stripe-org-billing.test.ts +80 -68
  101. package/templates/apps/api/src/routes/webhooks/__tests__/stripe-site-filter.test.ts +6 -5
  102. package/templates/apps/api/src/routes/webhooks/__tests__/stripe.test.ts +8 -2
  103. package/templates/apps/api/src/routes/webhooks/index.ts +23 -0
  104. package/templates/apps/api/src/routes/webhooks/stripe-offerings.ts +384 -0
  105. package/templates/apps/api/src/routes/webhooks/stripe.ts +93 -17
  106. package/templates/apps/api/src/server.ts +165 -51
  107. package/templates/apps/api/src/services/__tests__/abandon-email.test.ts +121 -4
  108. package/templates/apps/api/src/services/__tests__/checkout-plan.test.ts +313 -0
  109. package/templates/apps/api/src/services/__tests__/deliverable-fulfillment.test.ts +61 -2
  110. package/templates/apps/api/src/services/__tests__/keyless-workspace-predicate.test.ts +234 -0
  111. package/templates/apps/api/src/services/__tests__/lead-routing.test.ts +374 -44
  112. package/templates/apps/api/src/services/__tests__/person.test.ts +260 -1
  113. package/templates/apps/api/src/services/__tests__/plan-offerings.test.ts +450 -0
  114. package/templates/apps/api/src/services/__tests__/project-listings.counts.test.ts +202 -0
  115. package/templates/apps/api/src/services/__tests__/project-listings.fence.test.ts +15 -18
  116. package/templates/apps/api/src/services/abandon-email.ts +28 -5
  117. package/templates/apps/api/src/services/deliverable-fulfillment.ts +15 -12
  118. package/templates/apps/api/src/services/entitlements.ts +157 -1
  119. package/templates/apps/api/src/services/lead-routing.ts +113 -58
  120. package/templates/apps/api/src/services/offerings/__tests__/catalog.test.ts +348 -0
  121. package/templates/apps/api/src/services/offerings/__tests__/checkout-session-record.test.ts +268 -0
  122. package/templates/apps/api/src/services/offerings/__tests__/connection.test.ts +975 -0
  123. package/templates/apps/api/src/services/offerings/__tests__/fixtures.ts +416 -0
  124. package/templates/apps/api/src/services/offerings/__tests__/org-products.test.ts +400 -0
  125. package/templates/apps/api/src/services/offerings/__tests__/readers.test.ts +685 -0
  126. package/templates/apps/api/src/services/offerings/__tests__/source.test.ts +231 -0
  127. package/templates/apps/api/src/services/offerings/catalog.ts +318 -0
  128. package/templates/apps/api/src/services/offerings/checkout-authorization.ts +403 -0
  129. package/templates/apps/api/src/services/offerings/checkout-session-record.ts +192 -0
  130. package/templates/apps/api/src/services/offerings/connection.ts +1083 -0
  131. package/templates/apps/api/src/services/offerings/index.ts +614 -0
  132. package/templates/apps/api/src/services/offerings/source.ts +140 -0
  133. package/templates/apps/api/src/services/offerings/store.ts +150 -0
  134. package/templates/apps/api/src/services/person.ts +147 -9
  135. package/templates/apps/api/src/services/plan-offerings.ts +96 -30
  136. package/templates/apps/api/src/services/project-listings.ts +43 -31
  137. package/templates/apps/api/src/services/public-site-resolver.ts +19 -14
  138. package/templates/apps/mcp/CLAUDE.md +32 -18
  139. package/templates/apps/mcp/__tests__/app-entry.test.ts +2 -2
  140. package/templates/apps/mcp/__tests__/catalog-drift.test.ts +9 -4
  141. package/templates/apps/mcp/__tests__/get_offerings.test.ts +358 -0
  142. package/templates/apps/mcp/__tests__/get_pricing.test.ts +364 -0
  143. package/templates/apps/mcp/__tests__/http-transport.test.ts +95 -14
  144. package/templates/apps/mcp/__tests__/lib/content-adapter-parity.test.ts +8 -3
  145. package/templates/apps/mcp/__tests__/lib/content-adapter.test.ts +0 -25
  146. package/templates/apps/mcp/__tests__/lib/fail-closed-tools.test.ts +22 -7
  147. package/templates/apps/mcp/__tests__/list_pages.test.ts +84 -44
  148. package/templates/apps/mcp/__tests__/locale.test.ts +41 -56
  149. package/templates/apps/mcp/__tests__/module-exclusion.test.ts +2 -2
  150. package/templates/apps/mcp/__tests__/rate-limit-write.test.ts +189 -0
  151. package/templates/apps/mcp/__tests__/read_content.test.ts +115 -78
  152. package/templates/apps/mcp/__tests__/request_callback.test.ts +63 -128
  153. package/templates/apps/mcp/__tests__/search_content.test.ts +76 -61
  154. package/templates/apps/mcp/__tests__/server.test.ts +13 -11
  155. package/templates/apps/mcp/__tests__/start_purchase.test.ts +208 -12
  156. package/templates/apps/mcp/__tests__/submit_lead.test.ts +82 -167
  157. package/templates/apps/mcp/__tests__/subscribe_newsletter.test.ts +59 -130
  158. package/templates/apps/mcp/__tests__/tools/purchase.test.ts +11 -11
  159. package/templates/apps/mcp/package.json +1 -0
  160. package/templates/apps/mcp/src/abuse/index.ts +18 -11
  161. package/templates/apps/mcp/src/http-transport.ts +47 -9
  162. package/templates/apps/mcp/src/lib/content-adapter.ts +0 -26
  163. package/templates/apps/mcp/src/lib/empty-content-adapter.ts +5 -4
  164. package/templates/apps/mcp/src/lib/shown-offerings.ts +105 -0
  165. package/templates/apps/mcp/src/lib/site-config.ts +53 -0
  166. package/templates/apps/mcp/src/server.ts +5 -0
  167. package/templates/apps/mcp/src/tools/capture_lead.ts +30 -28
  168. package/templates/apps/mcp/src/tools/get_offerings.ts +47 -30
  169. package/templates/apps/mcp/src/tools/get_pricing.ts +40 -40
  170. package/templates/apps/mcp/src/tools/index.ts +22 -21
  171. package/templates/apps/mcp/src/tools/list_pages.ts +27 -33
  172. package/templates/apps/mcp/src/tools/purchase.ts +30 -2
  173. package/templates/apps/mcp/src/tools/read_content.ts +46 -40
  174. package/templates/apps/mcp/src/tools/request_callback.ts +21 -28
  175. package/templates/apps/mcp/src/tools/search_content.ts +20 -24
  176. package/templates/apps/mcp/src/tools/subscribe_newsletter.ts +23 -26
  177. package/templates/apps/mcp/src/workspace-server.ts +15 -0
  178. package/templates/apps/web/.env.example +10 -31
  179. package/templates/apps/web/CLAUDE.md +324 -42
  180. package/templates/apps/web/app/[locale]/(authed)/account/__tests__/agreements-prompt.test.tsx +3 -3
  181. package/templates/apps/web/app/[locale]/(authed)/account/__tests__/layout.test.tsx +3 -3
  182. package/templates/apps/web/app/[locale]/(authed)/account/__tests__/privacy.test.tsx +3 -3
  183. package/templates/apps/web/app/[locale]/careers/[slug]/[action]/page.tsx +189 -0
  184. package/templates/apps/web/app/[locale]/careers/[slug]/apply.actions.ts +4 -2
  185. package/templates/apps/web/app/[locale]/careers/[slug]/job-application-form.ts +8 -3
  186. package/templates/apps/web/app/[locale]/careers/[slug]/page.tsx +22 -144
  187. package/templates/apps/web/app/[locale]/careers/__tests__/apply.actions.test.ts +9 -0
  188. package/templates/apps/web/app/[locale]/careers/__tests__/careers-hub.test.tsx +172 -0
  189. package/templates/apps/web/app/[locale]/careers/__tests__/careers-set.test.tsx +149 -0
  190. package/templates/apps/web/app/[locale]/careers/__tests__/job-application-form.test.ts +22 -24
  191. package/templates/apps/web/app/[locale]/careers/__tests__/job-apply-page.test.tsx +140 -66
  192. package/templates/apps/web/app/[locale]/careers/__tests__/page-renderer-migration.test.tsx +57 -22
  193. package/templates/apps/web/app/[locale]/careers/page.tsx +129 -5
  194. package/templates/apps/web/app/[locale]/contact/__tests__/ConversationalIntakeWrapper.test.tsx +1 -1
  195. package/templates/apps/web/app/[locale]/contact/__tests__/contact-magnet-page.test.tsx +119 -11
  196. package/templates/apps/web/app/[locale]/contact/__tests__/contact.actions.test.ts +28 -5
  197. package/templates/apps/web/app/[locale]/contact/contact.actions.ts +20 -6
  198. package/templates/apps/web/app/[locale]/contact/intake-script.ts +1 -1
  199. package/templates/apps/web/app/[locale]/contact/page.tsx +99 -92
  200. package/templates/apps/web/app/[locale]/flows/[slug]/FlowStepperClient.tsx +237 -5
  201. package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/FlowStepperClient.count.test.tsx +43 -0
  202. package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/FlowStepperClient.deliverable.test.tsx +200 -0
  203. package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/FlowStepperClient.ending.test.tsx +197 -0
  204. package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/magnet-page.test.tsx +430 -0
  205. package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/two-route-tool.test.tsx +21 -9
  206. package/templates/apps/web/app/[locale]/flows/[slug]/magnet-page.tsx +369 -0
  207. package/templates/apps/web/app/[locale]/flows/[slug]/page.tsx +12 -73
  208. package/templates/apps/web/app/[locale]/flows/[slug]/start/page.tsx +10 -31
  209. package/templates/apps/web/app/[locale]/layout.tsx +55 -4
  210. package/templates/apps/web/app/[locale]/newsletter/__tests__/page.test.tsx +174 -104
  211. package/templates/apps/web/app/[locale]/newsletter/page.tsx +95 -58
  212. package/templates/apps/web/app/[locale]/pricing/__tests__/__snapshots__/unconnected.test.tsx.snap +7 -0
  213. package/templates/apps/web/app/[locale]/pricing/__tests__/offerings.test.tsx +599 -0
  214. package/templates/apps/web/app/[locale]/pricing/__tests__/page.test.tsx +216 -0
  215. package/templates/apps/web/app/[locale]/pricing/__tests__/unconnected.test.tsx +236 -0
  216. package/templates/apps/web/app/[locale]/pricing/page.tsx +367 -6
  217. package/templates/apps/web/app/[locale]/team/__tests__/team-routes.test.tsx +4 -1
  218. package/templates/apps/web/app/[locale]/unsubscribe/__tests__/unsubscribe-rsc-boundary.test.tsx +17 -0
  219. package/templates/apps/web/app/[locale]/unsubscribe/__tests__/unsubscribe.test.tsx +62 -3
  220. package/templates/apps/web/app/[locale]/unsubscribe/page.tsx +70 -18
  221. package/templates/apps/web/app/[locale]/unsubscribe/unsubscribe-button.tsx +30 -9
  222. package/templates/apps/web/app/api/offerings/checkout/[lookupKey]/route.ts +316 -0
  223. package/templates/apps/web/app/api/offerings/checkout/__tests__/route.test.ts +810 -0
  224. package/templates/apps/web/lib/__tests__/auth-routes.test.ts +27 -1
  225. package/templates/apps/web/lib/__tests__/deck-page.test.ts +2 -1
  226. package/templates/apps/web/lib/__tests__/in-page-magnet.test.ts +265 -0
  227. package/templates/apps/web/lib/__tests__/magnet-routes.test.ts +64 -0
  228. package/templates/apps/web/lib/__tests__/newsletter.actions.test.ts +293 -0
  229. package/templates/apps/web/lib/__tests__/offerings-pricing.test.ts +601 -0
  230. package/templates/apps/web/lib/__tests__/structured-data.test.tsx +67 -8
  231. package/templates/apps/web/lib/auth-routes.ts +36 -2
  232. package/templates/apps/web/lib/checkout-config.ts +9 -20
  233. package/templates/apps/web/lib/deck-page.ts +1 -0
  234. package/templates/apps/web/lib/deliverable-grant.ts +18 -0
  235. package/templates/apps/web/lib/in-page-magnet.ts +416 -0
  236. package/templates/apps/web/lib/magnet-routes.ts +44 -0
  237. package/templates/apps/web/lib/modules.generated.ts +9 -0
  238. package/templates/apps/web/lib/newsletter.actions.ts +205 -0
  239. package/templates/apps/web/lib/offerings-pricing.ts +326 -0
  240. package/templates/apps/web/lib/posting-title.ts +17 -0
  241. package/templates/apps/web/lib/sign-up-agreements.ts +4 -2
  242. package/templates/apps/web/messages/en.json +49 -15
  243. package/templates/apps/web/middleware.ts +12 -2
  244. package/templates/apps/web/security-headers.config.ts +17 -0
  245. package/templates/content/en/BlogList/blog.mdx +4 -71
  246. package/templates/content/en/BookCall/book-a-call.mdx +1 -41
  247. package/templates/content/en/BookCall/onsite-estimate.mdx +1 -17
  248. package/templates/content/en/BookCall/onsite-service.mdx +1 -17
  249. package/templates/content/en/BookCall/start-project.mdx +1 -17
  250. package/templates/content/en/ContactPage/contact.mdx +6 -63
  251. package/templates/content/en/HomePage/home.mdx +3 -21
  252. package/templates/database/CHANGELOG.md +51 -0
  253. package/templates/database/__tests__/in-page-magnets-migration.test.ts +161 -0
  254. package/templates/database/__tests__/offerings-checkout-session.db.test.ts +270 -0
  255. package/templates/database/__tests__/offerings-migration.test.ts +197 -0
  256. package/templates/database/__tests__/offerings-shown-price-ids-migration.test.ts +86 -0
  257. package/templates/database/__tests__/offerings.db.test.ts +303 -0
  258. package/templates/database/__tests__/seed.test.ts +63 -1
  259. package/templates/database/__tests__/trial-magnet-migration.test.ts +186 -0
  260. package/templates/database/auth/schema.prisma +4 -0
  261. package/templates/database/config/schema.prisma +91 -0
  262. package/templates/database/migrations/20261020120000_offerings_data_model/migration.sql +146 -0
  263. package/templates/database/migrations/20261021120000_offerings_checkout_session/migration.sql +77 -0
  264. package/templates/database/migrations/20261022120000_in_page_magnets/migration.sql +45 -0
  265. package/templates/database/migrations/20261023120000_offerings_shown_price_ids/migration.sql +47 -0
  266. package/templates/database/migrations/20261024120000_organization_plan_checkout_session/migration.sql +16 -0
  267. package/templates/database/migrations/20261025120000_trial_magnet/migration.sql +28 -0
  268. package/templates/database/migrations/20261026120000_trial_magnet_proof_line/migration.sql +23 -0
  269. package/templates/database/package.json +1 -1
  270. package/templates/database/seed.ts +57 -2
  271. package/templates/tools/lint-architecture.ts +205 -1
  272. package/versions.json +35 -35
  273. package/site-templates/marketing-site-free/content/en/Pricing/pricing.mdx +0 -199
  274. package/templates/apps/api/src/routes/content/slug.ts +0 -9
  275. package/templates/apps/web/app/[locale]/downloads/__tests__/page-renderer-migration.test.tsx +0 -93
  276. package/templates/apps/web/app/[locale]/downloads/page.tsx +0 -37
  277. package/templates/apps/web/app/[locale]/newsletter/__tests__/newsletter.actions.test.ts +0 -139
  278. package/templates/apps/web/app/[locale]/newsletter/newsletter.actions.ts +0 -93
  279. package/templates/apps/web/app/api/checkout/[offering]/route.ts +0 -48
  280. package/templates/apps/web/app/api/checkout/__tests__/route.test.ts +0 -120
  281. package/templates/apps/web/app/api/checkout/__tests__/success-path.test.ts +0 -86
  282. package/templates/apps/web/app/api/webhooks/stripe/__tests__/route.test.ts +0 -92
  283. package/templates/apps/web/app/api/webhooks/stripe/route.ts +0 -31
  284. package/templates/apps/web/lib/__tests__/checkout.test.ts +0 -243
  285. package/templates/apps/web/lib/__tests__/meeting-slugs.test.ts +0 -120
  286. package/templates/apps/web/lib/checkout.ts +0 -93
  287. package/templates/apps/web/lib/meeting-slugs.ts +0 -63
@@ -45,6 +45,7 @@ import {
45
45
  import {
46
46
  getPageSections,
47
47
  pageToJsonLd,
48
+ siteOrigin,
48
49
  type JsonLdObject,
49
50
  } from "@working-theory/page-renderer";
50
51
  import {
@@ -110,6 +111,13 @@ if (process.env.AILK_CONTENT_ROOT === undefined) {
110
111
  process.env.AILK_CONTENT_ROOT = CONTENT_ROOT;
111
112
  }
112
113
 
114
+ // A page that checks its own locale (`lib/locale-guard`, the meeting page)
115
+ // reads the enabled list next.config.mjs bakes in; pin it to the locale this
116
+ // file renders, as a built app would carry it.
117
+ if (process.env.NEXT_PUBLIC_ENABLED_LOCALES === undefined) {
118
+ process.env.NEXT_PUBLIC_ENABLED_LOCALES = DEFAULT_LOCALE;
119
+ }
120
+
113
121
  // Mirrors the locale-directory rule in tools/content-compile and the adapter.
114
122
  const LOCALE_RE = /^[a-z]{2}(-[A-Z]{2})?$/;
115
123
 
@@ -194,11 +202,8 @@ function extractJsonLdBlocks(container: HTMLElement): JsonLdObject[] {
194
202
  }
195
203
 
196
204
  const SITE_IDENTITY = readSiteIdentity(CONTENT_ROOT);
197
- const SITE_ORIGIN = (
198
- process.env.NEXT_PUBLIC_SITE_URL ??
199
- process.env.MARKETING_WEB_BASE_URL ??
200
- "http://localhost:3000"
201
- ).replace(/\/+$/, "");
205
+ // The resolver's own origin chain (#8797 D5), so this check cannot drift from it.
206
+ const SITE_ORIGIN = siteOrigin(SITE_IDENTITY);
202
207
 
203
208
  /**
204
209
  * The sitewide identity trio every rendered page carries when the site's own
@@ -305,6 +310,16 @@ const CHECKOUT_PAGE_MARKER = "Standard spoke checkout page (#8748)";
305
310
  // record), so its updated copy of this test must still exempt them.
306
311
  const LEGACY_CHECKOUT_PAGE_MARKER = "GENERATED by create-ailk (#7838)";
307
312
  const CHECKOUT_PAGE_NOINDEX_LINE = "robots: { index: false, follow: false }";
313
+ // #8840 D13 — the marker every standard magnet page carries in its header
314
+ // comment (`apps/web/app/[locale]/flows/[slug]/magnet-page.tsx`), and the
315
+ // renderer it must import. A magnet page renders no `renderPage` call this
316
+ // harness can read, and its landing's JSON-LD comes from the hub's record, so
317
+ // a page carrying both is exempt at ANY route: `create-ailk add-route magnet`
318
+ // writes a pair at a path the site picks. Keyed on the file, as #8513 is.
319
+ const MAGNET_PAGE_MARKER = "Standard magnet page (#8840)";
320
+ const MAGNET_RENDERER_IMPORT = "flows/[slug]/magnet-page";
321
+ const MAGNET_PAGE_REASON =
322
+ "standard magnet page (#8840) — rendered by flows/[slug]/magnet-page; its landing's JSON-LD comes from the hub's flow record";
308
323
 
309
324
  const NO_STRUCTURED_DATA_ROUTES: ReadonlyArray<{
310
325
  route: string;
@@ -325,12 +340,12 @@ const NO_STRUCTURED_DATA_ROUTES: ReadonlyArray<{
325
340
  // harness. Not checked here.
326
341
  {
327
342
  route: "blog",
328
- reason: "fumadocs route — emits via @working-theory/fumadocs-schema",
343
+ reason: "fumadocs route — its .source collection is not built in this harness",
329
344
  subtree: true,
330
345
  },
331
346
  {
332
347
  route: "docs",
333
- reason: "fumadocs route — emits via @working-theory/fumadocs-schema",
348
+ reason: "fumadocs route — its .source collection is not built in this harness",
334
349
  subtree: true,
335
350
  },
336
351
  // Hub routes that enumerate a detail type with no list page type of their
@@ -340,6 +355,10 @@ const NO_STRUCTURED_DATA_ROUTES: ReadonlyArray<{
340
355
  route: "how-to",
341
356
  reason: "hand-composed listing; no HowTo list type exists",
342
357
  },
358
+ {
359
+ route: "careers/[slug]/[action]",
360
+ reason: "the Free apply form for a posting; noindex, no JSON-LD (#8849 D7)",
361
+ },
343
362
  {
344
363
  route: "software",
345
364
  reason: "hand-composed listing; no SoftwareProduct list type exists",
@@ -368,11 +387,23 @@ const NO_STRUCTURED_DATA_ROUTES: ReadonlyArray<{
368
387
  reason: "interactive flow engine, API-backed",
369
388
  subtree: true,
370
389
  },
371
- { route: "meet", reason: "booking surface, API-backed", subtree: true },
390
+ // #8843 D7 — the meeting page `meet/[slug]` composes its own body from a
391
+ // `BookCall` seed (`getMdxSource` + `pageToJsonLd`), so it is checked like
392
+ // any route; the two indexes and the confirmation page are not pages.
393
+ { route: "meet", reason: "booking index, API-backed (#8843 D8)" },
394
+ { route: "meet/confirmed", reason: "booking confirmation, API-backed" },
395
+ { route: "private", reason: "private booking index, API-backed and noindex (#8843 D8)" },
372
396
  {
373
397
  route: "unsubscribe",
374
398
  reason: "app-shaped landing, API-backed; no content page behind it",
375
399
  },
400
+ // #8848 D12 — `/downloads` and `/downloads/<slug>` are permanent redirects
401
+ // into `/reports`; a redirect renders no page.
402
+ {
403
+ route: "downloads",
404
+ reason: "permanent redirect into /reports (#8848 D12); renders no page",
405
+ subtree: true,
406
+ },
376
407
  // #6084 — both purchase return pages read a Checkout Session (or read
377
408
  // nothing, for cancel) and render via SectionRenderer directly; neither
378
409
  // is a content-adapter page, and the paid render's own schema.org node
@@ -440,6 +471,9 @@ function walkRoutes(dir: string, out: RouteEntry[] = []): RouteEntry[] {
440
471
  }
441
472
 
442
473
  function exemption(route: string, src: string): string | undefined {
474
+ if (src.includes(MAGNET_PAGE_MARKER) && src.includes(MAGNET_RENDERER_IMPORT)) {
475
+ return MAGNET_PAGE_REASON;
476
+ }
443
477
  const entry = NO_STRUCTURED_DATA_ROUTES.find(
444
478
  (e) =>
445
479
  route === e.route ||
@@ -678,6 +712,31 @@ describe("structured data — exemption(route, src) keys the generated checkout
678
712
  });
679
713
  });
680
714
 
715
+ // ── 2b. exemption(route, src) — the standard magnet pages (#8840 D13) ────
716
+
717
+ describe("structured data — exemption(route, src) keys a standard magnet page on the file (#8840)", () => {
718
+ // The import line is assembled, not written out, so this file's own text
719
+ // carries no import of a path a scaffold's copy of it may not ship.
720
+ const IMPORT_LINE = ["import { magnetLanding } from ", JSON.stringify(`../${MAGNET_RENDERER_IMPORT}`), ";"].join("");
721
+ const PAIR_SRC = `// Generated by create-ailk add-route magnet.\n// ${MAGNET_PAGE_MARKER} — the landing of the /diagnostic magnet pair.\n${IMPORT_LINE}\n`;
722
+
723
+ it("exempts a page carrying the marker and the renderer import, at any route", () => {
724
+ expect(exemption("diagnostic", PAIR_SRC)).toBe(MAGNET_PAGE_REASON);
725
+ expect(exemption("diagnostic/start", PAIR_SRC)).toBe(MAGNET_PAGE_REASON);
726
+ expect(exemption("careers/writer", PAIR_SRC)).toBe(MAGNET_PAGE_REASON);
727
+ });
728
+
729
+ it("checks a page carrying the marker but no import of the renderer like any other", () => {
730
+ const noImport = `// ${MAGNET_PAGE_MARKER}\nexport default function Page() { return null; }\n`;
731
+ expect(exemption("diagnostic", noImport)).toBeUndefined();
732
+ });
733
+
734
+ it("checks a page importing the renderer without the marker like any other", () => {
735
+ const noMarker = `${IMPORT_LINE}\n`;
736
+ expect(exemption("diagnostic", noMarker)).toBeUndefined();
737
+ });
738
+ });
739
+
681
740
  // ── 3. The two defect shapes that shipped, pinned ───────────────────────────
682
741
  //
683
742
  // These prove the checks above reach the validator rule that rejects each
@@ -12,6 +12,12 @@
12
12
  * signed-in account area — `/account` and everything under it — which the
13
13
  * layout also renders without the marketing chrome.
14
14
  *
15
+ * `isBookingPath()` (#8843 D10) is the same predicate for the scheduling
16
+ * module's booking pages — `/meet`, `/meet/<slug>` and `/private`, never
17
+ * `/meet/confirmed` — which the layout renders chrome-free only where the
18
+ * module is installed (`lib/modules.generated.ts`). This module imports
19
+ * nothing, so edge code can import it too.
20
+ *
15
21
  * No Next.js import — pure functions, asserted directly by their own suite.
16
22
  */
17
23
 
@@ -62,6 +68,21 @@ export function isAccountPath(
62
68
  return firstSegment(pathname, locales) === "account";
63
69
  }
64
70
 
71
+ /**
72
+ * Whether `pathname` addresses a booking page: `/private`, `/meet`, or
73
+ * `/meet/<slug>` for any slug but `confirmed` (the booker's confirmation
74
+ * page, which keeps the marketing chrome). The same prefix rule and the same
75
+ * fail-safe direction as `isAuthPath()`.
76
+ */
77
+ export function isBookingPath(
78
+ pathname: string | undefined | null,
79
+ locales: readonly string[],
80
+ ): boolean {
81
+ const [first, second] = segments(pathname, locales);
82
+ if (first === "private") return true;
83
+ return first === "meet" && second !== "confirmed";
84
+ }
85
+
65
86
  /**
66
87
  * The first path segment after an enabled-locale prefix, or `undefined` for
67
88
  * an absent or empty pathname.
@@ -70,7 +91,18 @@ function firstSegment(
70
91
  pathname: string | undefined | null,
71
92
  locales: readonly string[],
72
93
  ): string | undefined {
73
- if (!pathname) return undefined;
94
+ return segments(pathname, locales)[0];
95
+ }
96
+
97
+ /**
98
+ * The path's segments after an enabled-locale prefix (empty for an absent or
99
+ * empty pathname). A trailing slash adds no segment.
100
+ */
101
+ function segments(
102
+ pathname: string | undefined | null,
103
+ locales: readonly string[],
104
+ ): string[] {
105
+ if (!pathname) return [];
74
106
 
75
107
  // Drop a query string / fragment — `x-pathname` carries the path, but a
76
108
  // caller passing a full request path should get the same verdict.
@@ -80,5 +112,7 @@ function firstSegment(
80
112
  ? path.replace(new RegExp(`^/(${locales.join("|")})(?=/|$)`), "")
81
113
  : path;
82
114
 
83
- return bare.split("/")[1];
115
+ const parts = bare.split("/").slice(1);
116
+ if (parts.at(-1) === "") parts.pop();
117
+ return parts;
84
118
  }
@@ -1,27 +1,16 @@
1
- // checkout-config.ts — this spoke's checkout, as data (#7838, #8748).
1
+ // checkout-config.ts — this spoke's checkout stamp, as data (#7838, #8748).
2
2
  //
3
- // The one checkout file this site edits. Written once by create-ailk and yours
4
- // from then on: `create-ailk update` never rewrites it. The standard checkout
5
- // (lib/checkout.ts, the buy route and the Stripe webhook route) reads only
6
- // `site` and `offerings` from it and is kept current by `create-ailk update`.
7
- // Keep it to data: no import, and no code.
3
+ // Written once by create-ailk and yours from then on: `create-ailk update`
4
+ // never rewrites it. Keep it to data: no import, and no code. A spoke sells
5
+ // through its hub's Offerings buy route, /api/offerings/checkout/<lookup_key>
6
+ // (#9142); the thank-you page and, on a spoke that signs in through its hub,
7
+ // the billing action read `site` from here.
8
8
  //
9
- // site Stamped on every Checkout Session as metadata.site. This site's
10
- // webhook acts only on its own stamp, and the hub ignores any stamp
11
- // that is not its own, so the stamp must be unique on the Stripe
12
- // account this site shares. Never "default". Change it only before
13
- // any price exists: every lookup key starts with it.
14
- // offerings slug -> Stripe lookup_key. The buy link is /api/checkout/<slug>,
15
- // and any other slug answers 404. Name an offering's slug after the
16
- // last segment of the Service or Product page that sells it: that
17
- // is the key the hub's plan rule and a magnet's checkoutOffering
18
- // use. scaffold-commerce replaces the placeholder below.
9
+ // site The paid-session stamp the thank-you page checks
10
+ // (`metadata.site`) before it prefills "Open your account". Never
11
+ // "default". The create-ailk writer stamps the line below.
19
12
  const SITE = "example-site";
20
13
 
21
14
  export const checkoutConfig = {
22
15
  site: SITE,
23
- offerings: {
24
- // A placeholder until scaffold-commerce creates this site's prices.
25
- example: { kind: "fixed", lookupKey: `${SITE}_example`, cancelPath: "/" },
26
- },
27
16
  };
@@ -37,6 +37,7 @@ import { siteChrome, type SiteChrome } from "./site-chrome";
37
37
  export const DECK_ROUTES = [
38
38
  "/about",
39
39
  "/alternatives",
40
+ "/careers",
40
41
  "/contact",
41
42
  "/faq",
42
43
  "/launch-service",
@@ -0,0 +1,18 @@
1
+ /**
2
+ * The shape a deliverable grant id must match before anything spends a hub
3
+ * call on it (#9125). One definition, read by the report page
4
+ * (`app/[locale]/reports/[slug]/page.tsx`, `resolveGrantId`) and its file
5
+ * route (`app/[locale]/reports/[slug]/file/route.ts`), so the page and the
6
+ * bytes can never disagree on which ids are worth probing.
7
+ *
8
+ * A grant id is minted as `dgrant_<uuidv7>` (database/inbox/schema.prisma).
9
+ * This is deliberately a charset-and-length check, not a `dgrant_` prefix
10
+ * match: the API stays the authority on which ids are real, and a future id
11
+ * scheme should not need an edit here. 128 is a generous ceiling over today's
12
+ * `dgrant_<uuid>`.
13
+ *
14
+ * Deliberately base-tree, not slice-owned: a keyed scaffold's
15
+ * signal-collectors bundle installs the page and route that import it, so it
16
+ * must already be in every scaffold.
17
+ */
18
+ export const GRANT_ID_SHAPE = /^[A-Za-z0-9_-]{1,128}$/;