create-ailk 0.7.0 → 0.9.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 (310) hide show
  1. package/README.md +112 -10
  2. package/component-catalog.json +798 -49
  3. package/component-catalog.md +9 -6
  4. package/dist/add-module.d.ts +20 -0
  5. package/dist/add-module.js +43 -7
  6. package/dist/add-route.d.ts +56 -0
  7. package/dist/add-route.js +286 -0
  8. package/dist/apply-design-md.d.ts +7 -0
  9. package/dist/apply-design-md.js +50 -0
  10. package/dist/apply-design.d.ts +64 -0
  11. package/dist/apply-design.js +273 -0
  12. package/dist/claude-md.d.ts +111 -0
  13. package/dist/claude-md.js +334 -0
  14. package/dist/cli.js +114 -28
  15. package/dist/derive-architecture.js +41 -36
  16. package/dist/design-compile.d.ts +71 -0
  17. package/dist/design-compile.js +98 -0
  18. package/dist/design-package.d.ts +83 -0
  19. package/dist/design-package.js +468 -0
  20. package/dist/design-presets.d.ts +86 -0
  21. package/dist/design-presets.js +642 -0
  22. package/dist/design-scaffold.d.ts +76 -0
  23. package/dist/design-scaffold.js +220 -0
  24. package/dist/index.d.ts +2 -1
  25. package/dist/index.js +4 -1
  26. package/dist/legacy-nav-renderers.d.ts +45 -0
  27. package/dist/legacy-nav-renderers.js +545 -0
  28. package/dist/lib/auth-secret-file.d.ts +19 -0
  29. package/dist/lib/auth-secret-file.js +112 -0
  30. package/dist/lib/design-checks.d.ts +78 -0
  31. package/dist/lib/design-checks.js +174 -0
  32. package/dist/lib/extract-module-bundle.js +5 -1
  33. package/dist/lib/license-key-files.d.ts +9 -0
  34. package/dist/lib/license-key-files.js +2 -2
  35. package/dist/lib/module-license-gate.d.ts +89 -0
  36. package/dist/lib/module-license-gate.js +171 -12
  37. package/dist/lib/module-tier.d.ts +45 -16
  38. package/dist/lib/module-tier.js +76 -0
  39. package/dist/lib/pro-callout.js +10 -2
  40. package/dist/lib/render-verdict.d.ts +52 -0
  41. package/dist/lib/render-verdict.js +77 -0
  42. package/dist/lib/scaffold-render-sweep.d.ts +105 -0
  43. package/dist/lib/scaffold-render-sweep.js +236 -0
  44. package/dist/module-architecture.d.ts +19 -16
  45. package/dist/module-architecture.js +64 -21
  46. package/dist/parse-args.d.ts +88 -10
  47. package/dist/parse-args.js +272 -21
  48. package/dist/programmatic.d.ts +66 -12
  49. package/dist/programmatic.js +361 -34
  50. package/dist/site-chrome.d.ts +19 -9
  51. package/dist/site-chrome.js +23 -13
  52. package/dist/site-manifest.d.ts +25 -0
  53. package/dist/site-manifest.js +254 -0
  54. package/dist/site-roster.d.ts +41 -0
  55. package/dist/site-roster.js +149 -11
  56. package/dist/starter-grid.d.ts +232 -0
  57. package/dist/starter-grid.js +545 -0
  58. package/dist/surfaces.d.ts +41 -9
  59. package/dist/surfaces.js +268 -92
  60. package/dist/template-manifest.d.ts +65 -29
  61. package/dist/template-manifest.js +161 -382
  62. package/dist/template-names.d.ts +14 -12
  63. package/dist/template-names.js +13 -17
  64. package/package.json +5 -4
  65. package/site-templates/_base/content/en/Feature/feature-1.mdx +69 -0
  66. package/site-templates/_base/content/en/Feature/feature-2.mdx +69 -0
  67. package/site-templates/_base/content/en/Feature/feature-3.mdx +69 -0
  68. package/site-templates/_base/content/en/FeaturesList/features.mdx +91 -0
  69. package/site-templates/_base/content/en/GlossaryList/glossary.mdx +0 -1
  70. package/site-templates/_base/content/en/NewsletterSignup/newsletter.mdx +0 -1
  71. package/site-templates/_base/content/en/PortfolioItem/sample-four.mdx +105 -0
  72. package/site-templates/_base/content/en/PortfolioItem/sample-one.mdx +34 -0
  73. package/site-templates/_base/content/en/PortfolioItem/sample-three.mdx +94 -0
  74. package/site-templates/_base/content/en/PortfolioItem/sample-two.mdx +45 -0
  75. package/site-templates/_base/content/en/PortfolioList/portfolio.mdx +169 -0
  76. package/site-templates/_base/content/en/PrivacyPage/privacy.mdx +15 -0
  77. package/site-templates/_base/content/en/UseCase/persona-1.mdx +70 -0
  78. package/site-templates/_base/content/en/UseCase/persona-2.mdx +70 -0
  79. package/site-templates/_base/content/en/UseCase/persona-3.mdx +70 -0
  80. package/site-templates/{minimalism-site/TEMPLATE-MANIFEST.json → aliases/minimalism-site.TEMPLATE-MANIFEST.json} +72 -40
  81. package/site-templates/designs/minimalism/DESIGN.md +98 -6
  82. package/site-templates/designs/minimalism/sample/README.md +48 -0
  83. package/site-templates/designs/standard/DESIGN.md +103 -6
  84. package/site-templates/marketing-site/DESIGN.md +103 -6
  85. package/site-templates/marketing-site/TEMPLATE-MANIFEST.json +144 -47
  86. package/site-templates/marketing-site/TEMPLATE-MANIFEST.md +82 -41
  87. package/site-templates/marketing-site-free/DESIGN.md +103 -6
  88. package/site-templates/marketing-site-free/TEMPLATE-MANIFEST.json +150 -48
  89. package/site-templates/marketing-site-free/TEMPLATE-MANIFEST.md +114 -67
  90. package/site-templates/marketing-site-free/ailk-template.json +1 -1
  91. package/site-templates/marketing-site-free/content/en/NewsletterSignup/newsletter.mdx +59 -0
  92. package/site-templates/marketing-site-free/content/en/Pricing/pricing.mdx +199 -0
  93. package/site-templates/saas-product/DESIGN.md +103 -6
  94. package/site-templates/saas-product/TEMPLATE-MANIFEST.md +9 -1
  95. package/site-templates/{simple-service → shapes/local-business-service}/TEMPLATE-MANIFEST.json +210 -81
  96. package/site-templates/shapes/local-business-service/TEMPLATE-MANIFEST.md +356 -0
  97. package/site-templates/shapes/local-business-service/ailk-template.json +10 -0
  98. package/site-templates/{simple-service → shapes/local-business-service}/content/en/AboutPage/about.mdx +4 -4
  99. package/site-templates/shapes/local-business-service/content/en/HomePage/home.mdx +70 -0
  100. package/site-templates/shapes/local-business-service/content/en/NewsletterSignup/newsletter.mdx +59 -0
  101. package/site-templates/shapes/local-business-service/content/en/PortfolioList/portfolio.mdx +171 -0
  102. package/site-templates/{minimalism-site → shapes/local-business-service}/content/en/PurchasePolicyPage/purchase-policy.mdx +7 -8
  103. package/site-templates/shapes/local-business-service/content/en/Service/first-job.mdx +39 -0
  104. package/site-templates/shapes/local-business-service/content/en/Service/second-job.mdx +39 -0
  105. package/site-templates/shapes/local-business-service/content/en/Service/third-job.mdx +39 -0
  106. package/site-templates/shapes/local-business-service/content/en/ServicesList/services.mdx +61 -0
  107. package/site-templates/shapes/local-business-service/provenance.json +4 -0
  108. package/site-templates/shapes/organization-service/TEMPLATE-MANIFEST.json +471 -0
  109. package/site-templates/shapes/organization-service/TEMPLATE-MANIFEST.md +354 -0
  110. package/site-templates/shapes/organization-service/ailk-template.json +10 -0
  111. package/site-templates/shapes/organization-service/content/en/AboutPage/about.mdx +38 -0
  112. package/site-templates/shapes/organization-service/content/en/HomePage/home.mdx +85 -0
  113. package/site-templates/shapes/organization-service/content/en/NewsletterSignup/newsletter.mdx +59 -0
  114. package/site-templates/shapes/organization-service/content/en/PortfolioList/portfolio.mdx +171 -0
  115. package/site-templates/shapes/organization-service/content/en/PurchasePolicyPage/purchase-policy.mdx +123 -0
  116. package/site-templates/{minimalism-site → shapes/organization-service}/content/en/ServicesList/services.mdx +3 -4
  117. package/site-templates/shapes/organization-service/provenance.json +4 -0
  118. package/site-templates/shapes/person-service/TEMPLATE-MANIFEST.json +478 -0
  119. package/site-templates/shapes/person-service/TEMPLATE-MANIFEST.md +353 -0
  120. package/site-templates/shapes/person-service/ailk-template.json +10 -0
  121. package/site-templates/shapes/person-service/content/_site.mdx +85 -0
  122. package/site-templates/shapes/person-service/content/en/AboutPage/about.mdx +39 -0
  123. package/site-templates/shapes/person-service/content/en/HomePage/home.mdx +97 -0
  124. package/site-templates/shapes/person-service/content/en/NewsletterSignup/newsletter.mdx +59 -0
  125. package/site-templates/shapes/person-service/content/en/PortfolioList/portfolio.mdx +171 -0
  126. package/site-templates/{simple-service → shapes/person-service}/content/en/PurchasePolicyPage/purchase-policy.mdx +10 -11
  127. package/site-templates/shapes/person-service/content/en/Service/first-engagement.mdx +38 -0
  128. package/site-templates/shapes/person-service/content/en/Service/second-engagement.mdx +38 -0
  129. package/site-templates/shapes/person-service/content/en/Service/third-engagement.mdx +38 -0
  130. package/site-templates/{simple-service → shapes/person-service}/content/en/ServicesList/services.mdx +16 -16
  131. package/site-templates/shapes/person-service/provenance.json +4 -0
  132. package/templates/.claude/rules/architecture.md +7 -0
  133. package/templates/.claude/skills/README.md +5 -3
  134. package/templates/.claude/skills/add-auth/references/env-vars.md +22 -15
  135. package/templates/.claude/skills/add-auth/references/oauth-app-setup.md +3 -2
  136. package/templates/.claude/skills/add-page/SKILL.md +241 -238
  137. package/templates/.claude/skills/add-page/references/page-types.md +85 -72
  138. package/templates/.claude/skills/add-page/scripts/site-pages.mjs +87 -0
  139. package/templates/.claude/skills/add-page/templates/comparison-page.mdx.tmpl +99 -0
  140. package/templates/.claude/skills/add-page/templates/content.mdx.tmpl +8 -18
  141. package/templates/.claude/skills/add-page/templates/flow-landing-page.mdx.tmpl +43 -0
  142. package/templates/.claude/skills/add-page/templates/report-page.mdx.tmpl +52 -0
  143. package/templates/.claude/skills/add-page/templates/reports-list.mdx.tmpl +38 -0
  144. package/templates/.claude/skills/check-site/SKILL.md +23 -0
  145. package/templates/.claude/skills/fill-page/SKILL.md +26 -7
  146. package/templates/.claude/skills/fill-page/profiles/plumbing.yaml +1 -1
  147. package/templates/.claude/skills/setup-database/SKILL.md +156 -0
  148. package/templates/.claude/skills/setup-database/scripts/verify-inbox.ts +154 -0
  149. package/templates/.claude/skills/setup-deploy/SKILL.md +64 -32
  150. package/templates/.claude/skills/setup-deploy/references/env-vars-template.md +44 -36
  151. package/templates/.claude/skills/setup-deploy/references/runbook-template.md +58 -102
  152. package/templates/.claude/skills/update-page/SKILL.md +117 -0
  153. package/templates/.claude/skills/update-page/scripts/update-page.ts +640 -0
  154. package/templates/.claude/skills/update-page/templates/redirects.mjs.tmpl +31 -0
  155. package/templates/.claude/skills/upgrade-plan/SKILL.md +12 -8
  156. package/templates/.github/workflows/ci.yml +4 -0
  157. package/templates/.prettierignore +1 -0
  158. package/templates/CLAUDE.md +146 -103
  159. package/templates/DESIGN.md +103 -6
  160. package/templates/README.md +63 -43
  161. package/templates/apps/api/.env.example +2 -1
  162. package/templates/apps/api/CLAUDE.md +3 -2
  163. package/templates/apps/api/src/__tests__/commerce-claim-registration.test.ts +2 -0
  164. package/templates/apps/api/src/__tests__/server.test.ts +116 -0
  165. package/templates/apps/api/src/config/__tests__/index.test.ts +115 -14
  166. package/templates/apps/api/src/config/index.ts +35 -16
  167. package/templates/apps/api/src/lib/__tests__/lead-capture-store.test.ts +30 -0
  168. package/templates/apps/api/src/lib/__tests__/lead-delivery.test.ts +96 -0
  169. package/templates/apps/api/src/lib/lead-capture-store.ts +12 -0
  170. package/templates/apps/api/src/lib/lead-delivery.ts +52 -0
  171. package/templates/apps/api/src/lib/stripe.ts +9 -0
  172. package/templates/apps/api/src/middleware/__tests__/auth.optional-cookie.test.ts +140 -0
  173. package/templates/apps/api/src/middleware/auth.ts +54 -0
  174. package/templates/apps/api/src/openapi/spec.ts +63 -12
  175. package/templates/apps/api/src/routes/checkout/__tests__/session.test.ts +5 -2
  176. package/templates/apps/api/src/routes/checkout/__tests__/sessions.test.ts +628 -0
  177. package/templates/apps/api/src/routes/checkout/sessions.ts +222 -24
  178. package/templates/apps/api/src/routes/leads/__tests__/capture.test.ts +88 -0
  179. package/templates/apps/api/src/routes/leads/__tests__/index.test.ts +100 -0
  180. package/templates/apps/api/src/routes/leads/index.ts +46 -1
  181. package/templates/apps/api/src/routes/webhooks/__tests__/stripe-flow-checkout.test.ts +1 -1
  182. package/templates/apps/api/src/routes/webhooks/__tests__/stripe-org-billing.test.ts +5 -1
  183. package/templates/apps/api/src/routes/webhooks/__tests__/stripe.test.ts +521 -20
  184. package/templates/apps/api/src/routes/webhooks/stripe.ts +124 -32
  185. package/templates/apps/api/src/server.ts +35 -0
  186. package/templates/apps/api/src/services/__tests__/lead-routing.test.ts +194 -0
  187. package/templates/apps/api/src/services/__tests__/leads-inbox.test.ts +124 -0
  188. package/templates/apps/api/src/services/__tests__/revalidation.test.ts +90 -15
  189. package/templates/apps/api/src/services/checkout-buyer.ts +72 -0
  190. package/templates/apps/api/src/services/lead-routing.ts +140 -30
  191. package/templates/apps/api/src/services/leads-inbox.ts +48 -11
  192. package/templates/apps/api/src/services/revalidation.ts +60 -15
  193. package/templates/apps/mcp/__tests__/parity.test.ts +5 -4
  194. package/templates/apps/mcp/__tests__/tools/create_project.test.ts +30 -24
  195. package/templates/apps/mcp/src/tools/create_project.ts +21 -6
  196. package/templates/apps/mcp/src/tools/purchase.ts +4 -2
  197. package/templates/apps/web/.env.example +13 -0
  198. package/templates/apps/web/CLAUDE.md +22 -28
  199. package/templates/apps/web/app/[locale]/(auth)/layout.tsx +1 -1
  200. package/templates/apps/web/app/[locale]/(authed)/leads/__tests__/page.test.tsx +24 -0
  201. package/templates/apps/web/app/[locale]/(authed)/leads/page.tsx +17 -6
  202. package/templates/apps/web/app/[locale]/[slug]/[detail]/__tests__/page.test.tsx +110 -0
  203. package/templates/apps/web/app/[locale]/[slug]/[detail]/page.tsx +60 -0
  204. package/templates/apps/web/app/[locale]/[slug]/page.tsx +1 -1
  205. package/templates/apps/web/app/[locale]/alternatives/__tests__/page.test.tsx +161 -0
  206. package/templates/apps/web/app/[locale]/alternatives/page.tsx +104 -0
  207. package/templates/apps/web/app/[locale]/contact/ConversationalIntakeWrapper.tsx +14 -6
  208. package/templates/apps/web/app/[locale]/contact/__tests__/ConversationalIntakeWrapper.test.tsx +10 -9
  209. package/templates/apps/web/app/[locale]/contact/__tests__/contact-form-text.test.ts +32 -0
  210. package/templates/apps/web/app/[locale]/contact/__tests__/contact.actions.test.ts +129 -0
  211. package/templates/apps/web/app/[locale]/contact/__tests__/page-renderer-migration.test.tsx +227 -84
  212. package/templates/apps/web/app/[locale]/contact/contact-form-text.ts +50 -0
  213. package/templates/apps/web/app/[locale]/contact/contact.actions.ts +73 -20
  214. package/templates/apps/web/app/[locale]/contact/page.tsx +105 -15
  215. package/templates/apps/web/app/[locale]/contact/site-contact-email.ts +54 -0
  216. package/templates/apps/web/app/[locale]/features/[slug]/page.tsx +64 -0
  217. package/templates/apps/web/app/[locale]/features/page.tsx +52 -0
  218. package/templates/apps/web/app/[locale]/{use-cases → for}/[slug]/page.tsx +21 -5
  219. package/templates/apps/web/app/[locale]/for/__tests__/page-renderer-migration.test.tsx +73 -0
  220. package/templates/apps/web/app/[locale]/{use-cases → for}/page.tsx +8 -7
  221. package/templates/apps/web/app/[locale]/glossary/page.tsx +1 -1
  222. package/templates/apps/web/app/[locale]/layout.tsx +65 -17
  223. package/templates/apps/web/app/[locale]/newsletter/__tests__/newsletter.actions.test.ts +75 -0
  224. package/templates/apps/web/app/[locale]/newsletter/__tests__/page.test.tsx +163 -0
  225. package/templates/apps/web/app/[locale]/newsletter/newsletter.actions.ts +66 -0
  226. package/templates/apps/web/app/[locale]/newsletter/page.tsx +106 -14
  227. package/templates/apps/web/app/[locale]/portfolio/[slug]/page.tsx +59 -0
  228. package/templates/apps/web/app/[locale]/{book-a-call → portfolio}/page.tsx +12 -13
  229. package/templates/apps/web/app/[locale]/vs/[slug]/__tests__/page.test.tsx +112 -0
  230. package/templates/apps/web/app/[locale]/vs/[slug]/page.tsx +70 -0
  231. package/templates/apps/web/app/api/revalidate/__tests__/route.test.ts +236 -44
  232. package/templates/apps/web/app/api/revalidate/route.ts +78 -36
  233. package/templates/apps/web/lib/__tests__/site-brand.test.ts +79 -0
  234. package/templates/apps/web/lib/__tests__/site-theme.test.ts +104 -11
  235. package/templates/apps/web/lib/__tests__/structured-data.test.tsx +21 -4
  236. package/templates/apps/web/lib/locale-guard.ts +18 -0
  237. package/templates/apps/web/lib/meet-placeholder.ts +5 -0
  238. package/templates/apps/web/lib/nav-config.ts +50 -37
  239. package/templates/apps/web/lib/site-brand.tsx +13 -9
  240. package/templates/apps/web/lib/site-identity.ts +14 -0
  241. package/templates/apps/web/lib/site-logo.tsx +36 -0
  242. package/templates/apps/web/lib/site-theme.ts +46 -26
  243. package/templates/apps/web/messages/en.json +45 -5
  244. package/templates/apps/web/middleware.ts +28 -4
  245. package/templates/content/_site.mdx +4 -2
  246. package/templates/content/en/BlogList/blog.mdx +37 -16
  247. package/templates/content/en/BookCall/book-a-call.mdx +80 -0
  248. package/templates/content/en/BookCall/onsite-estimate.mdx +56 -0
  249. package/templates/content/en/BookCall/onsite-service.mdx +56 -0
  250. package/templates/content/en/BookCall/start-project.mdx +56 -0
  251. package/templates/content/en/ContactPage/contact.mdx +43 -34
  252. package/templates/content/en/HomePage/home.mdx +10 -22
  253. package/templates/content/en/LandingPage/meet.mdx +59 -0
  254. package/templates/content/en/PurchasePolicyPage/purchase-policy.mdx +0 -1
  255. package/templates/content/en/UseCasesList/{use-cases.mdx → for.mdx} +19 -18
  256. package/templates/database/CHANGELOG.md +14 -0
  257. package/templates/database/package.json +1 -1
  258. package/templates/package.json +2 -1
  259. package/templates/tools/check-claude-md.ts +128 -0
  260. package/versions.json +33 -33
  261. package/site-templates/_base/content/en/BookCall/book-call.mdx +0 -53
  262. package/site-templates/_base/content/en/UseCase/first-use-case.mdx +0 -23
  263. package/site-templates/_base/content/en/UseCase/second-use-case.mdx +0 -23
  264. package/site-templates/_base/content/en/UseCase/third-use-case.mdx +0 -23
  265. package/site-templates/minimalism-site/DESIGN.md +0 -162
  266. package/site-templates/minimalism-site/TEMPLATE-MANIFEST.md +0 -470
  267. package/site-templates/minimalism-site/ailk-template.json +0 -23
  268. package/site-templates/minimalism-site/content/en/AboutPage/about.mdx +0 -110
  269. package/site-templates/minimalism-site/content/en/BookCall/book-call.mdx +0 -53
  270. package/site-templates/minimalism-site/content/en/ContactPage/contact.mdx +0 -34
  271. package/site-templates/minimalism-site/content/en/FAQPage/faq.mdx +0 -57
  272. package/site-templates/minimalism-site/content/en/LandingPage/referral.mdx +0 -77
  273. package/site-templates/minimalism-site/content/en/LandingPage/reports.mdx +0 -73
  274. package/site-templates/minimalism-site/content/en/ReportPage/annual-letter-2024.mdx +0 -52
  275. package/site-templates/minimalism-site/content/en/ReportPage/field-notes-2023.mdx +0 -52
  276. package/site-templates/minimalism-site/content/en/ReportPage/platform-shift-2025.mdx +0 -52
  277. package/site-templates/minimalism-site/content/en/ReportPage/state-of-the-market-2026.mdx +0 -52
  278. package/site-templates/minimalism-site/flows/referral.flow.json +0 -71
  279. package/site-templates/minimalism-site/provenance.json +0 -4
  280. package/site-templates/simple-service/DESIGN.md +0 -162
  281. package/site-templates/simple-service/TEMPLATE-MANIFEST.md +0 -333
  282. package/site-templates/simple-service/ailk-template.json +0 -11
  283. package/site-templates/simple-service/content/en/BookCall/book-call.mdx +0 -53
  284. package/site-templates/simple-service/content/en/ContactPage/contact.mdx +0 -32
  285. package/site-templates/simple-service/content/en/FAQPage/faq.mdx +0 -56
  286. package/site-templates/simple-service/content/en/HomePage/home.mdx +0 -85
  287. package/site-templates/simple-service/content/en/Service/landscape-design.mdx +0 -40
  288. package/site-templates/simple-service/content/en/Service/lawn-maintenance.mdx +0 -40
  289. package/site-templates/simple-service/content/en/Service/seasonal-cleanup.mdx +0 -40
  290. package/site-templates/simple-service/provenance.json +0 -4
  291. package/templates/.claude/skills/add-page/templates/route.tsx.tmpl +0 -28
  292. package/templates/apps/web/app/[locale]/contact/ContactForm.tsx +0 -211
  293. package/templates/apps/web/app/[locale]/contact/__tests__/ContactForm.test.tsx +0 -96
  294. package/templates/apps/web/app/[locale]/use-cases/__tests__/page-renderer-migration.test.tsx +0 -72
  295. package/templates/content/en/SoftwareProduct/product.mdx +0 -175
  296. /package/site-templates/{minimalism-site → designs/minimalism/sample}/content/en/HomePage/home.mdx +0 -0
  297. /package/site-templates/{minimalism-site/assets → designs/minimalism/sample/public}/avatar-placeholder-1.svg +0 -0
  298. /package/site-templates/{minimalism-site/assets → designs/minimalism/sample/public}/avatar-placeholder-2.svg +0 -0
  299. /package/site-templates/{minimalism-site/assets → designs/minimalism/sample/public}/avatar-placeholder-3.svg +0 -0
  300. /package/site-templates/{minimalism-site/assets → designs/minimalism/sample/public}/avatar-placeholder-4.svg +0 -0
  301. /package/site-templates/{minimalism-site/assets → designs/minimalism/sample/public}/avatar-placeholder-5.svg +0 -0
  302. /package/site-templates/{minimalism-site/assets → designs/minimalism/sample/public}/avatar-placeholder-6.svg +0 -0
  303. /package/site-templates/{minimalism-site/assets → designs/minimalism/sample/public}/hero-backdrop.svg +0 -0
  304. /package/site-templates/{minimalism-site/assets → designs/minimalism/sample/public}/logo-placeholder-1.svg +0 -0
  305. /package/site-templates/{minimalism-site/assets → designs/minimalism/sample/public}/logo-placeholder-2.svg +0 -0
  306. /package/site-templates/{minimalism-site/assets → designs/minimalism/sample/public}/logo-placeholder-3.svg +0 -0
  307. /package/site-templates/{minimalism-site/assets → designs/minimalism/sample/public}/wordmark-placeholder.svg +0 -0
  308. /package/site-templates/{minimalism-site → shapes/organization-service}/content/en/Service/first-engagement.mdx +0 -0
  309. /package/site-templates/{minimalism-site → shapes/organization-service}/content/en/Service/second-engagement.mdx +0 -0
  310. /package/site-templates/{minimalism-site → shapes/organization-service}/content/en/Service/third-engagement.mdx +0 -0
@@ -1,8 +1,20 @@
1
- // Contact page. Renders sections via renderPage (sections mode) with the
2
- // plain ContactForm slotted into the split hero's right column (or appended
3
- // below when no split hero leads). The chat-style intake
4
- // (ConversationalIntakeWrapper, #4534) remains available to a site that
5
- // prefers it. JSON-LD and translations handled by renderPage.
1
+ // Contact page (#6730 D8). Renders sections via renderPage (sections mode)
2
+ // and places ONE contact surface, first match wins: into the page's first
3
+ // `contact-form` section; else into a leading split hero's right column;
4
+ // else after the page.
5
+ //
6
+ // That surface is the shared `LeadCaptureForm` block, bound to
7
+ // `captureLeadAction` — unless the API says this deployment accepts no
8
+ // contact message (production with neither a database nor an email key), in
9
+ // which case it is `ContactFallback`: the site's contact email, never a form
10
+ // that would report a message as sent when it reached nobody. An unreachable
11
+ // API, an older API without the status route, or any other failed read is
12
+ // treated as accepting: the form renders, and its submit reports honestly.
13
+ //
14
+ // The chat-style intake (ConversationalIntakeWrapper, #4534) remains
15
+ // available to a site that prefers it. JSON-LD and translations handled by
16
+ // renderPage.
17
+ import { leads } from "@working-theory/api-client";
6
18
  import type {
7
19
  getMdxSource as defaultGetMdxSource,
8
20
  getPageSections as defaultGetPageSections,
@@ -13,10 +25,21 @@ import {
13
25
  renderPage,
14
26
  renderPageMetadata,
15
27
  } from "@working-theory/page-renderer";
28
+ import {
29
+ ContactFallback,
30
+ type ContactFormField,
31
+ LeadCaptureForm,
32
+ localeHref,
33
+ } from "@working-theory/ui";
16
34
  import type { Metadata } from "next";
35
+ import { getTranslations } from "next-intl/server";
17
36
  import type { ReactElement } from "react";
18
37
 
19
- import { ContactForm } from "./ContactForm";
38
+ import { contactFormText } from "./contact-form-text";
39
+ import { captureLeadAction } from "./contact.actions";
40
+ import { readSiteContactEmail } from "./site-contact-email";
41
+
42
+ export const revalidate = 60;
20
43
 
21
44
  interface Props {
22
45
  params: Promise<{ locale: string }>;
@@ -24,6 +47,41 @@ interface Props {
24
47
  getPageSections?: typeof defaultGetPageSections;
25
48
  }
26
49
 
50
+ type CaptureStatus = Extract<
51
+ Awaited<ReturnType<typeof leads.captureStatus>>,
52
+ { ok: true }
53
+ >["body"]["data"];
54
+
55
+ type Section = Awaited<ReturnType<typeof defaultGetPageSections>>[number];
56
+ type ContactFormSection = Extract<Section, { type: "contact-form" }>;
57
+
58
+ const FIELD_TYPES = ["text", "email", "tel", "textarea", "select"] as const;
59
+
60
+ /** A `contact-form` section's declared fields, as the form block renders them. */
61
+ function sectionFields(section: ContactFormSection): ContactFormField[] {
62
+ return section.data.fields.map((f) => ({
63
+ name: f.name,
64
+ label: f.label,
65
+ type: (FIELD_TYPES as readonly string[]).includes(f.type)
66
+ ? (f.type as (typeof FIELD_TYPES)[number])
67
+ : "text",
68
+ ...(f.required !== undefined ? { required: f.required } : {}),
69
+ ...(f.placeholder !== undefined ? { placeholder: f.placeholder } : {}),
70
+ ...(f.options !== undefined ? { options: f.options } : {}),
71
+ ...(f.colSpan !== undefined ? { colSpan: f.colSpan as 1 | 2 } : {}),
72
+ }));
73
+ }
74
+
75
+ /** The API's capture status, or `null` for any failed read (see header). */
76
+ async function readCaptureStatus(): Promise<CaptureStatus | null> {
77
+ try {
78
+ const res = await leads.captureStatus();
79
+ return res.ok && res.body.ok === true ? res.body.data : null;
80
+ } catch {
81
+ return null;
82
+ }
83
+ }
84
+
27
85
  export async function generateMetadata({
28
86
  params,
29
87
  }: Pick<Props, "params">): Promise<Metadata> {
@@ -37,17 +95,49 @@ export default async function Page({
37
95
  getPageSections,
38
96
  }: Props): Promise<ReactElement> {
39
97
  const { locale } = await params;
40
- // The contact form sits in the hero's right column when the page leads with
41
- // a split hero (the two-column contact layout — operator ruling 2026-09-23:
42
- // the Free template shows the plain form; the guided intake stays a Pro
43
- // option); otherwise — no content, or a different lead section — it renders
44
- // below the page, so the contact surface is never lost.
98
+ const [status, t] = await Promise.all([
99
+ readCaptureStatus(),
100
+ getTranslations({ locale, namespace: "contact" }),
101
+ ]);
102
+ const accepting = status?.contact ?? true;
103
+ const contactEmail = readSiteContactEmail();
104
+ const text = contactFormText(t);
105
+
45
106
  const source = await (getMdxSource ?? loadMdxSource)("ContactPage", "contact", locale);
46
107
  const sections = source ? await (getPageSections ?? loadPageSections)(source.frontmatter) : [];
108
+ const formSection = sections.find(
109
+ (s): s is ContactFormSection => s.type === "contact-form",
110
+ );
111
+ // The section's own published address stands in when _site.mdx has none.
112
+ const fallbackEmail = contactEmail ?? formSection?.data.contactInfo?.email;
47
113
  const lead = sections[0];
48
114
  const slotted =
49
- lead?.type === "hero" && lead.data.variant === "split" ? lead.id : undefined;
50
- const intake = <ContactForm />;
115
+ formSection?.id ??
116
+ (lead?.type === "hero" && lead.data.variant === "split" ? lead.id : undefined);
117
+
118
+ const surface = accepting ? (
119
+ <LeadCaptureForm
120
+ action={captureLeadAction}
121
+ text={text}
122
+ termsHref={localeHref(locale, "/terms")}
123
+ fallbackEmail={fallbackEmail}
124
+ {...(formSection && {
125
+ fields: sectionFields(formSection),
126
+ submitLabel: formSection.data.submitLabel,
127
+ successMessage: formSection.data.successMessage,
128
+ })}
129
+ />
130
+ ) : (
131
+ <ContactFallback
132
+ email={fallbackEmail}
133
+ text={{
134
+ heading: text.unavailableHeading,
135
+ body: text.unavailableBody,
136
+ noEmail: text.unavailableNoEmail,
137
+ }}
138
+ />
139
+ );
140
+
51
141
  const rendered = await renderPage({
52
142
  type: "ContactPage",
53
143
  slug: "contact",
@@ -55,12 +145,12 @@ export default async function Page({
55
145
  translationsNamespace: "contact",
56
146
  ...(getMdxSource !== undefined && { getMdxSource }),
57
147
  ...(getPageSections !== undefined && { getPageSections }),
58
- ...(slotted !== undefined && { sectionSlots: { [slotted]: intake } }),
148
+ ...(slotted !== undefined && { sectionSlots: { [slotted]: surface } }),
59
149
  });
60
150
  return (
61
151
  <>
62
152
  {rendered}
63
- {slotted === undefined ? intake : null}
153
+ {slotted === undefined ? surface : null}
64
154
  </>
65
155
  );
66
156
  }
@@ -0,0 +1,54 @@
1
+ /**
2
+ * The site's published contact email (#6730 D10) — `_site.mdx`'s top-level
3
+ * `contactEmail`, else a `LocalBusiness` identity's `email`, else `undefined`.
4
+ *
5
+ * `/contact` shows it in place of the form when the site has nowhere to
6
+ * deliver a message, and hands it to the form's `unavailable` state.
7
+ *
8
+ * A local frontmatter read rather than `@working-theory/site-config/server`'s
9
+ * `loadSiteConfig`: `architecture.yaml` forbids `apps/web` from importing that
10
+ * subpath (it pulls `node:fs` into a tree that also carries edge routes,
11
+ * #1567) — the same reason `lib/site-theme.ts` and `lib/site-brand.tsx` read
12
+ * locally. The value is still validated against the canonical schema from
13
+ * the client-safe `/schema` entry. Any failure (no file, no frontmatter, an
14
+ * invalid address) is `undefined`: the fallback then says the site has
15
+ * published no contact details, which is true.
16
+ */
17
+
18
+ import { readFileSync } from "node:fs";
19
+ import { join } from "node:path";
20
+
21
+ import { SiteConfigSchema } from "@working-theory/site-config/schema";
22
+ import { parse as parseYaml } from "yaml";
23
+
24
+ import { resolveContentRoot } from "../../../lib/site-brand";
25
+
26
+ const FRONTMATTER_RE = /^---\r?\n([\s\S]*?)\r?\n---/;
27
+
28
+ const EmailSchema = SiteConfigSchema.shape.contactEmail.unwrap();
29
+
30
+ function validEmail(value: unknown): string | undefined {
31
+ const parsed = EmailSchema.safeParse(value);
32
+ return parsed.success ? parsed.data : undefined;
33
+ }
34
+
35
+ export function readSiteContactEmail(): string | undefined {
36
+ try {
37
+ const raw = readFileSync(join(resolveContentRoot(), "_site.mdx"), "utf8");
38
+ const match = FRONTMATTER_RE.exec(raw);
39
+ if (!match) return undefined;
40
+ const fm: unknown = parseYaml(match[1] ?? "");
41
+ if (!fm || typeof fm !== "object") return undefined;
42
+
43
+ const { contactEmail, siteIdentity } = fm as {
44
+ contactEmail?: unknown;
45
+ siteIdentity?: { kind?: unknown; email?: unknown };
46
+ };
47
+ return (
48
+ validEmail(contactEmail) ??
49
+ (siteIdentity?.kind === "LocalBusiness" ? validEmail(siteIdentity.email) : undefined)
50
+ );
51
+ } catch {
52
+ return undefined;
53
+ }
54
+ }
@@ -0,0 +1,64 @@
1
+ // Feature detail page (#6721) — one feature at /features/<slug>, listed by
2
+ // the /features page. Reads the Feature MDX for the given slug, renders via
3
+ // @working-theory/page-renderer, and emits a JSON-LD WebPage whose mainEntity
4
+ // is the product's SoftwareApplication, named for the feature. Unknown slugs
5
+ // 404. Composed `sections[]` render as sections; a prose page renders its
6
+ // MDX body.
7
+ import type { getMdxSource as defaultGetMdxSource } from "@working-theory/page-renderer";
8
+ import {
9
+ getMdxSource as loadMdxSource,
10
+ getMdxSlugs,
11
+ renderPage,
12
+ renderPageMetadata,
13
+ } from "@working-theory/page-renderer";
14
+ import type { Metadata } from "next";
15
+ import type { ReactElement } from "react";
16
+
17
+ interface Props {
18
+ params: Promise<{ locale: string; slug: string }>;
19
+ getMdxSource?: typeof defaultGetMdxSource;
20
+ }
21
+
22
+ export async function generateMetadata({
23
+ params,
24
+ }: Pick<Props, "params">): Promise<Metadata> {
25
+ const { locale, slug } = await params;
26
+ return renderPageMetadata({ type: "Feature", slug, locale });
27
+ }
28
+
29
+ export async function generateStaticParams({
30
+ params,
31
+ }: {
32
+ params: { locale: string };
33
+ }): Promise<{ slug: string }[]> {
34
+ const slugs = await getMdxSlugs("Feature", params.locale);
35
+ return slugs.map((slug) => ({ slug }));
36
+ }
37
+
38
+ export default async function Page({
39
+ params,
40
+ getMdxSource,
41
+ }: Props): Promise<ReactElement> {
42
+ const { locale, slug } = await params;
43
+ // A page that composes `sections[]` (the starter seeds, #6721) renders
44
+ // them; a prose page renders its MDX body — the same rule the /features hub
45
+ // applies. JSON-LD is built from the frontmatter either way.
46
+ const source = await (getMdxSource ?? loadMdxSource)("Feature", slug, locale);
47
+ const composed =
48
+ source !== null &&
49
+ Array.isArray((source.frontmatter as { sections?: unknown[] }).sections) &&
50
+ ((source.frontmatter as { sections?: unknown[] }).sections?.length ?? 0) >
51
+ 0;
52
+ return renderPage({
53
+ type: "Feature",
54
+ slug,
55
+ locale,
56
+ ...(composed
57
+ ? {}
58
+ : {
59
+ bodyMode: "mdx-body" as const,
60
+ populatedDescriptionTestId: "feature-description",
61
+ }),
62
+ ...(getMdxSource !== undefined && { getMdxSource }),
63
+ });
64
+ }
@@ -0,0 +1,52 @@
1
+ // Features list page. Reads FeaturesList MDX via the content adapter and emits
2
+ // a JSON-LD CollectionPage via the dispatcher. List-only — no detail route.
3
+ import type {
4
+ getMdxSource as defaultGetMdxSource,
5
+ } from "@working-theory/page-renderer";
6
+ import {
7
+ getMdxSource as loadMdxSource,
8
+ renderPage,
9
+ renderPageMetadata,
10
+ } from "@working-theory/page-renderer";
11
+ import type { Metadata } from "next";
12
+ import type { ReactElement } from "react";
13
+
14
+ interface Props {
15
+ params: Promise<{ locale: string }>;
16
+ getMdxSource?: typeof defaultGetMdxSource;
17
+ }
18
+
19
+ export async function generateMetadata({
20
+ params,
21
+ }: Pick<Props, "params">): Promise<Metadata> {
22
+ const { locale } = await params;
23
+ return renderPageMetadata({
24
+ type: "FeaturesList",
25
+ slug: "features",
26
+ locale,
27
+ });
28
+ }
29
+
30
+ export default async function Page({
31
+ params,
32
+ getMdxSource,
33
+ }: Props): Promise<ReactElement> {
34
+ const { locale } = await params;
35
+ // A FeaturesList page that composes `sections[]` (the Free template's
36
+ // /features, operator ruling 2026-09-23) renders them; a prose-only page
37
+ // keeps the MDX-body render. The CollectionPage JSON-LD is built from the
38
+ // sections either way (buildFeaturesList).
39
+ const source = await (getMdxSource ?? loadMdxSource)("FeaturesList", "features", locale);
40
+ const composed =
41
+ source !== null &&
42
+ Array.isArray((source.frontmatter as { sections?: unknown[] }).sections) &&
43
+ ((source.frontmatter as { sections?: unknown[] }).sections?.length ?? 0) > 0;
44
+ return renderPage({
45
+ type: "FeaturesList",
46
+ slug: "features",
47
+ locale,
48
+ translationsNamespace: "features",
49
+ ...(composed ? {} : { bodyMode: "mdx-body" as const }),
50
+ ...(getMdxSource !== undefined && { getMdxSource }),
51
+ });
52
+ }
@@ -1,9 +1,12 @@
1
- // UseCase detail page (#6658 D4) — one use case at /use-cases/<slug>, listed by
2
- // the /use-cases page. Reads the UseCase MDX for the given slug, renders via
1
+ // UseCase detail page (#6658 D4; route renamed to /for by #6878 D2) — one
2
+ // customer page at /for/<slug>, listed by the /for hub. Reads the UseCase MDX
3
+ // for the given slug, renders via
3
4
  // @working-theory/page-renderer, and emits a JSON-LD WebPage whose mainEntity
4
- // is an Article. Unknown slugs 404. Same shape as software/[slug]/page.tsx.
5
+ // is an Article. Unknown slugs 404. Composed `sections[]` render as sections
6
+ // (#6721); a prose page renders its MDX body.
5
7
  import type { getMdxSource as defaultGetMdxSource } from "@working-theory/page-renderer";
6
8
  import {
9
+ getMdxSource as loadMdxSource,
7
10
  getMdxSlugs,
8
11
  renderPage,
9
12
  renderPageMetadata,
@@ -37,12 +40,25 @@ export default async function Page({
37
40
  getMdxSource,
38
41
  }: Props): Promise<ReactElement> {
39
42
  const { locale, slug } = await params;
43
+ // A page that composes `sections[]` (the starter seeds, #6721) renders
44
+ // them; a prose page renders its MDX body — the same rule the /for hub
45
+ // applies. JSON-LD is built from the frontmatter either way.
46
+ const source = await (getMdxSource ?? loadMdxSource)("UseCase", slug, locale);
47
+ const composed =
48
+ source !== null &&
49
+ Array.isArray((source.frontmatter as { sections?: unknown[] }).sections) &&
50
+ ((source.frontmatter as { sections?: unknown[] }).sections?.length ?? 0) >
51
+ 0;
40
52
  return renderPage({
41
53
  type: "UseCase",
42
54
  slug,
43
55
  locale,
44
- bodyMode: "mdx-body",
45
- populatedDescriptionTestId: "use-case-description",
56
+ ...(composed
57
+ ? {}
58
+ : {
59
+ bodyMode: "mdx-body" as const,
60
+ populatedDescriptionTestId: "use-case-description",
61
+ }),
46
62
  ...(getMdxSource !== undefined && { getMdxSource }),
47
63
  });
48
64
  }
@@ -0,0 +1,73 @@
1
+ /**
2
+ * HTML snapshot regression tests for for/page.tsx → @working-theory/page-renderer migration
3
+ * (route renamed from use-cases/ by #6878 D2).
4
+ *
5
+ * Two-commit strategy:
6
+ * Commit 1: this file added; snapshots captured against pre-migration route (RED-passing).
7
+ * Commit 2: route migrated; same snapshots verify diff=0 (GREEN).
8
+ *
9
+ * Two states tested:
10
+ * - empty: getMdxSource returns null → empty-state branch
11
+ * - populated: getMdxSource returns a minimal MdxPageSource → full render
12
+ *
13
+ * Failure = snapshot diff > 0 bytes. DO NOT regenerate snapshots to pass.
14
+ */
15
+ import React from "react";
16
+ import { render } from "@testing-library/react";
17
+
18
+ // ── @working-theory/ui mock ─────────────────────────────────────────────────────────────
19
+ jest.mock("@working-theory/ui", () => ({
20
+ // #4446/#4447: renderPage's own fallback/empty-state title now composes
21
+ // Heading (mobile type-budget classes, #4417) instead of a bare <h1>.
22
+ Heading: ({
23
+ children,
24
+ level,
25
+ }: {
26
+ children: React.ReactNode;
27
+ level: number;
28
+ }): React.ReactElement => React.createElement(`h${level}`, null, children),
29
+ SectionRenderer: ({ sections }: { sections: unknown[] }) =>
30
+ React.createElement("div", {
31
+ "data-testid": "section-renderer",
32
+ "data-section-count": String(sections.length),
33
+ }),
34
+ }));
35
+
36
+ import ForPage from "../page";
37
+
38
+ const getMdxSourceEmpty = jest.fn().mockResolvedValue(null);
39
+
40
+ describe("ForPage — snapshot regression", () => {
41
+ it("empty state renders deterministic HTML", async () => {
42
+ const element = await ForPage({
43
+ params: Promise.resolve({ locale: "en" }),
44
+ getMdxSource: getMdxSourceEmpty,
45
+ });
46
+ const { container } = render(element);
47
+ expect(container.innerHTML).toMatchInlineSnapshot(
48
+ `"<main id="main"><h1>Customers</h1><p data-testid="section-empty-state">Customer pages coming soon.</p></main>"`,
49
+ );
50
+ });
51
+
52
+ it("populated state renders deterministic HTML", async () => {
53
+ const source = {
54
+ frontmatter: {
55
+ type: "UseCasesList" as const,
56
+ slug: "for",
57
+ title: "Use Cases",
58
+ description: "How teams use our platform.",
59
+ },
60
+ body: "",
61
+ };
62
+ const getMdxSourcePopulated = jest.fn().mockResolvedValue(source);
63
+
64
+ const element = await ForPage({
65
+ params: Promise.resolve({ locale: "en" }),
66
+ getMdxSource: getMdxSourcePopulated,
67
+ });
68
+ const { container } = render(element);
69
+ expect(container.innerHTML).toMatchInlineSnapshot(
70
+ `"<script type="application/ld+json" data-testid="json-ld">{"@context":"https://schema.org","@type":"CollectionPage","inLanguage":"en","name":"Use Cases","description":"How teams use our platform.","mainEntity":{"@type":"ItemList","itemListElement":[{"@type":"Article"}]}}</script><main id="main"><h1>Use Cases</h1><p data-testid="for-description">How teams use our platform.</p><div data-testid="markdown-view-toggle" class="ailk-markdown-view-toggle"><div class="flex justify-end mb-3"><div role="radiogroup" aria-label="Page view" data-testid="markdown-view-toggle" class="inline-flex items-center gap-0.5 rounded-lg p-1 bg-(--color-surface-secondary)"><button class="inline-flex items-center justify-center gap-2 font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-(--color-accent) focus-visible:ring-offset-1 disabled:cursor-not-allowed disabled:opacity-50 [&amp;>svg]:shrink-0 text-(--color-fg) hover:bg-(--color-surface-secondary) active:bg-gray-200 rounded-control px-control-x h-(--control-height-thin) text-caption leading-none rounded-md px-3 text-body-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-(--color-accent) focus-visible:ring-offset-1 min-w-(--control-height-thin) min-h-(--control-height-big) bg-(--color-bg) text-(--color-fg) shadow-sm" type="button" role="radio" aria-checked="true" aria-label="Rendered" tabindex="0" data-testid="markdown-view-toggle-rendered">Rendered</button><button class="inline-flex items-center justify-center gap-2 font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-(--color-accent) focus-visible:ring-offset-1 disabled:cursor-not-allowed disabled:opacity-50 [&amp;>svg]:shrink-0 text-(--color-fg) hover:bg-(--color-surface-secondary) active:bg-gray-200 rounded-control px-control-x h-(--control-height-thin) text-caption leading-none rounded-md px-3 text-body-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-(--color-accent) focus-visible:ring-offset-1 min-w-(--control-height-thin) min-h-(--control-height-big) text-(--color-text-muted) hover:text-(--color-fg)" type="button" role="radio" aria-checked="false" aria-label="Markdown" tabindex="-1" data-testid="markdown-view-toggle-markdown">Markdown</button></div></div><div role="status" aria-live="polite" aria-atomic="true" class="sr-only" data-testid="markdown-view-toggle-status">Showing Rendered view</div><div data-testid="markdown-view-rendered"><article data-testid="mdx-body"><div data-testid="mdx-remote" data-source=""></div></article></div></div></main>"`,
71
+ );
72
+ });
73
+ });
@@ -1,5 +1,6 @@
1
- // Use Cases list page. Reads UseCasesList MDX via the content adapter and emits
2
- // a JSON-LD CollectionPage via the dispatcher. List-only — no detail route.
1
+ // Customers (/for) hub page (#6878 D2 — renamed from /use-cases). Reads
2
+ // UseCasesList MDX via the content adapter and emits a JSON-LD CollectionPage
3
+ // via the dispatcher. List-only — no detail route.
3
4
  import type {
4
5
  getMdxSource as defaultGetMdxSource,
5
6
  } from "@working-theory/page-renderer";
@@ -22,7 +23,7 @@ export async function generateMetadata({
22
23
  const { locale } = await params;
23
24
  return renderPageMetadata({
24
25
  type: "UseCasesList",
25
- slug: "use-cases",
26
+ slug: "for",
26
27
  locale,
27
28
  });
28
29
  }
@@ -33,19 +34,19 @@ export default async function Page({
33
34
  }: Props): Promise<ReactElement> {
34
35
  const { locale } = await params;
35
36
  // A UseCasesList page that composes `sections[]` (the Free template's
36
- // /use-cases, operator ruling 2026-09-23) renders them; a prose-only page
37
+ // /for hub, operator ruling 2026-09-23) renders them; a prose-only page
37
38
  // keeps the MDX-body render. The CollectionPage JSON-LD is built from the
38
39
  // sections either way (buildUseCasesList).
39
- const source = await (getMdxSource ?? loadMdxSource)("UseCasesList", "use-cases", locale);
40
+ const source = await (getMdxSource ?? loadMdxSource)("UseCasesList", "for", locale);
40
41
  const composed =
41
42
  source !== null &&
42
43
  Array.isArray((source.frontmatter as { sections?: unknown[] }).sections) &&
43
44
  ((source.frontmatter as { sections?: unknown[] }).sections?.length ?? 0) > 0;
44
45
  return renderPage({
45
46
  type: "UseCasesList",
46
- slug: "use-cases",
47
+ slug: "for",
47
48
  locale,
48
- translationsNamespace: "use-cases",
49
+ translationsNamespace: "for",
49
50
  ...(composed ? {} : { bodyMode: "mdx-body" as const }),
50
51
  ...(getMdxSource !== undefined && { getMdxSource }),
51
52
  });
@@ -2,7 +2,7 @@
2
2
  // `GlossaryList` MDX with slug `glossary` and emits a JSON-LD CollectionPage
3
3
  // whose mainEntity is a DefinedTermSet built from its `glossary-index`
4
4
  // sections. A module-less surface a template enables by naming it in its
5
- // roster; with no content the route 404s. Mirrors use-cases/page.tsx: a
5
+ // roster; with no content the route 404s. Mirrors for/page.tsx: a
6
6
  // sections-composed page renders its sections, a prose-only page its MDX body.
7
7
  import type { getMdxSource as defaultGetMdxSource } from "@working-theory/page-renderer";
8
8
  import {