create-ailk 0.6.1 → 0.8.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 (324) hide show
  1. package/README.md +87 -14
  2. package/component-catalog.json +735 -95
  3. package/component-catalog.md +13 -7
  4. package/dist/add-module-core.d.ts +22 -0
  5. package/dist/add-module-core.js +50 -0
  6. package/dist/add-module.d.ts +89 -0
  7. package/dist/add-module.js +334 -0
  8. package/dist/category-profile.d.ts +126 -0
  9. package/dist/category-profile.js +262 -0
  10. package/dist/cli.js +89 -29
  11. package/dist/index.d.ts +4 -2
  12. package/dist/index.js +6 -1
  13. package/dist/lib/extract-module-bundle.d.ts +56 -1
  14. package/dist/lib/extract-module-bundle.js +170 -14
  15. package/dist/lib/license-key-files.d.ts +15 -0
  16. package/dist/lib/license-key-files.js +102 -0
  17. package/dist/lib/module-license-gate.d.ts +79 -0
  18. package/dist/lib/module-license-gate.js +160 -12
  19. package/dist/lib/module-tier.d.ts +45 -16
  20. package/dist/lib/module-tier.js +76 -0
  21. package/dist/lib/pro-callout.d.ts +16 -10
  22. package/dist/lib/pro-callout.js +34 -3
  23. package/dist/license-set.d.ts +34 -0
  24. package/dist/license-set.js +194 -0
  25. package/dist/module-architecture.d.ts +20 -0
  26. package/dist/module-architecture.js +77 -19
  27. package/dist/modules/commerce/0.6.1.tar.gz +0 -0
  28. package/dist/modules/docs-blog/0.6.1.tar.gz +0 -0
  29. package/dist/modules/marketing-site/0.6.1.tar.gz +0 -0
  30. package/dist/modules/minimalism-site/0.6.1.tar.gz +0 -0
  31. package/dist/modules/multi-tenant/0.6.1.tar.gz +0 -0
  32. package/dist/modules/saas-product/0.6.1.tar.gz +0 -0
  33. package/dist/modules/scheduling/0.6.1.tar.gz +0 -0
  34. package/dist/modules/signal-collectors/0.6.1.tar.gz +0 -0
  35. package/dist/modules/simple-service/0.6.1.tar.gz +0 -0
  36. package/dist/modules/workspace/0.6.1.tar.gz +0 -0
  37. package/dist/parse-args.d.ts +85 -10
  38. package/dist/parse-args.js +227 -15
  39. package/dist/programmatic.d.ts +81 -5
  40. package/dist/programmatic.js +291 -32
  41. package/dist/site-chrome.d.ts +18 -7
  42. package/dist/site-chrome.js +32 -14
  43. package/dist/site-roster.d.ts +127 -0
  44. package/dist/site-roster.js +499 -0
  45. package/dist/starter-grid.d.ts +216 -0
  46. package/dist/starter-grid.js +449 -0
  47. package/dist/surfaces.d.ts +20 -7
  48. package/dist/surfaces.js +204 -24
  49. package/dist/sync-routes.d.ts +9 -0
  50. package/dist/sync-routes.js +2 -2
  51. package/dist/template-manifest.d.ts +94 -19
  52. package/dist/template-manifest.js +170 -289
  53. package/dist/template-names.d.ts +14 -12
  54. package/dist/template-names.js +13 -17
  55. package/package.json +6 -4
  56. package/site-templates/_base/content/en/BookCall/book-call.mdx +52 -0
  57. package/site-templates/_base/content/en/Feature/feature-1.mdx +69 -0
  58. package/site-templates/_base/content/en/Feature/feature-2.mdx +69 -0
  59. package/site-templates/_base/content/en/Feature/feature-3.mdx +69 -0
  60. package/site-templates/_base/content/en/FeaturesList/features.mdx +91 -0
  61. package/site-templates/_base/content/en/GlossaryList/glossary.mdx +65 -0
  62. package/site-templates/_base/content/en/NewsletterSignup/newsletter.mdx +67 -0
  63. package/site-templates/_base/content/en/PortfolioItem/sample-four.mdx +105 -0
  64. package/site-templates/_base/content/en/PortfolioItem/sample-one.mdx +34 -0
  65. package/site-templates/_base/content/en/PortfolioItem/sample-three.mdx +94 -0
  66. package/site-templates/_base/content/en/PortfolioItem/sample-two.mdx +45 -0
  67. package/site-templates/_base/content/en/PortfolioList/portfolio.mdx +169 -0
  68. package/site-templates/_base/content/en/Pricing/pricing.mdx +71 -62
  69. package/site-templates/_base/content/en/PrivacyPage/privacy.mdx +17 -3
  70. package/site-templates/_base/content/en/TermsPage/terms.mdx +2 -3
  71. package/site-templates/_base/content/en/UseCase/persona-1.mdx +69 -0
  72. package/site-templates/_base/content/en/UseCase/persona-2.mdx +69 -0
  73. package/site-templates/_base/content/en/UseCase/persona-3.mdx +69 -0
  74. package/site-templates/designs/minimalism/DESIGN.md +162 -0
  75. package/site-templates/designs/standard/DESIGN.md +167 -0
  76. package/site-templates/marketing-site/DESIGN.md +59 -40
  77. package/site-templates/marketing-site/TEMPLATE-MANIFEST.json +376 -57
  78. package/site-templates/marketing-site/TEMPLATE-MANIFEST.md +230 -90
  79. package/site-templates/marketing-site/ailk-template.json +1 -0
  80. package/site-templates/marketing-site-free/DESIGN.md +59 -40
  81. package/site-templates/marketing-site-free/TEMPLATE-MANIFEST.json +323 -63
  82. package/site-templates/marketing-site-free/TEMPLATE-MANIFEST.md +323 -113
  83. package/site-templates/marketing-site-free/ailk-template.json +3 -2
  84. package/site-templates/marketing-site-free/content/en/NewsletterSignup/newsletter.mdx +59 -0
  85. package/site-templates/marketing-site-free/content/en/Pricing/pricing.mdx +199 -0
  86. package/site-templates/minimalism-site/DESIGN.md +54 -40
  87. package/site-templates/minimalism-site/TEMPLATE-MANIFEST.json +246 -19
  88. package/site-templates/minimalism-site/TEMPLATE-MANIFEST.md +173 -51
  89. package/site-templates/minimalism-site/ailk-template.json +3 -2
  90. package/site-templates/minimalism-site/content/en/BookCall/book-call.mdx +52 -0
  91. package/site-templates/minimalism-site/content/en/ContactPage/contact.mdx +3 -37
  92. package/site-templates/minimalism-site/content/en/PurchasePolicyPage/purchase-policy.mdx +123 -0
  93. package/site-templates/saas-product/DESIGN.md +59 -40
  94. package/site-templates/saas-product/TEMPLATE-MANIFEST.md +9 -1
  95. package/site-templates/shapes/local-business-service/TEMPLATE-MANIFEST.json +369 -0
  96. package/site-templates/shapes/local-business-service/TEMPLATE-MANIFEST.md +302 -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/shapes/local-business-service/content/en/PurchasePolicyPage/purchase-policy.mdx +123 -0
  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 +362 -0
  109. package/site-templates/shapes/organization-service/TEMPLATE-MANIFEST.md +300 -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/shapes/organization-service/content/en/Service/first-engagement.mdx +38 -0
  117. package/site-templates/shapes/organization-service/content/en/Service/second-engagement.mdx +38 -0
  118. package/site-templates/shapes/organization-service/content/en/Service/third-engagement.mdx +38 -0
  119. package/site-templates/shapes/organization-service/content/en/ServicesList/services.mdx +72 -0
  120. package/site-templates/shapes/organization-service/provenance.json +4 -0
  121. package/site-templates/shapes/person-service/TEMPLATE-MANIFEST.json +369 -0
  122. package/site-templates/shapes/person-service/TEMPLATE-MANIFEST.md +299 -0
  123. package/site-templates/shapes/person-service/ailk-template.json +10 -0
  124. package/site-templates/shapes/person-service/content/_site.mdx +85 -0
  125. package/site-templates/shapes/person-service/content/en/AboutPage/about.mdx +39 -0
  126. package/site-templates/shapes/person-service/content/en/HomePage/home.mdx +97 -0
  127. package/site-templates/shapes/person-service/content/en/NewsletterSignup/newsletter.mdx +59 -0
  128. package/site-templates/shapes/person-service/content/en/PortfolioList/portfolio.mdx +169 -0
  129. package/site-templates/shapes/person-service/content/en/PurchasePolicyPage/purchase-policy.mdx +123 -0
  130. package/site-templates/shapes/person-service/content/en/Service/first-engagement.mdx +38 -0
  131. package/site-templates/shapes/person-service/content/en/Service/second-engagement.mdx +38 -0
  132. package/site-templates/shapes/person-service/content/en/Service/third-engagement.mdx +38 -0
  133. package/site-templates/{simple-service → shapes/person-service}/content/en/ServicesList/services.mdx +16 -16
  134. package/site-templates/shapes/person-service/provenance.json +4 -0
  135. package/templates/.claude/settings.json +15 -0
  136. package/templates/.claude/skills/README.md +10 -9
  137. package/templates/.claude/skills/{scaffold-auth → add-auth}/SKILL.md +7 -7
  138. package/templates/.claude/skills/{scaffold-auth → add-auth}/references/env-vars.md +2 -2
  139. package/templates/.claude/skills/{scaffold-auth → add-auth}/references/oauth-app-setup.md +3 -3
  140. package/templates/.claude/skills/{scaffold-page → add-page}/SKILL.md +76 -62
  141. package/templates/.claude/skills/add-page/references/page-types.md +115 -0
  142. package/templates/.claude/skills/{scaffold-page → add-page}/templates/content.mdx.tmpl +4 -4
  143. package/templates/.claude/skills/{scaffold-page → add-page}/templates/route.tsx.tmpl +1 -1
  144. package/templates/.claude/skills/{audit-site → check-site}/SKILL.md +16 -17
  145. package/templates/.claude/skills/{audit-site → check-site}/references/common-errors.md +3 -3
  146. package/templates/.claude/skills/{audit-site → check-site}/references/findings-glossary.md +16 -14
  147. package/templates/.claude/skills/{audit-site → check-site}/references/severity-levels.md +1 -1
  148. package/templates/.claude/skills/fill-page/SKILL.md +180 -0
  149. package/templates/.claude/skills/fill-page/profiles/plumbing.yaml +80 -0
  150. package/templates/.claude/skills/fill-page/references/section-sources.md +41 -0
  151. package/templates/.claude/skills/setup-database/SKILL.md +156 -0
  152. package/templates/.claude/skills/setup-database/scripts/verify-inbox.ts +154 -0
  153. package/templates/.claude/skills/{provision-config → setup-deploy}/SKILL.md +73 -41
  154. package/templates/.claude/skills/{provision-config → setup-deploy}/references/env-vars-template.md +21 -30
  155. package/templates/.claude/skills/setup-deploy/references/runbook-template.md +204 -0
  156. package/templates/.claude/skills/{provision-config → setup-deploy}/references/vercel-json-template.md +1 -1
  157. package/templates/.claude/skills/upgrade-plan/SKILL.md +117 -0
  158. package/templates/.claude/skills/upgrade-plan/references/options.md +24 -0
  159. package/templates/.claude/skills/upgrade-plan/scripts/upgrade-plan.ts +527 -0
  160. package/templates/.github/workflows/ci.yml +5 -0
  161. package/templates/CLAUDE.md +8 -2
  162. package/templates/DESIGN.md +56 -44
  163. package/templates/README.md +116 -46
  164. package/templates/apps/api/.env.example +2 -1
  165. package/templates/apps/api/CLAUDE.md +3 -2
  166. package/templates/apps/api/package.json +1 -1
  167. package/templates/apps/api/src/config/__tests__/index.test.ts +146 -10
  168. package/templates/apps/api/src/config/index.ts +46 -14
  169. package/templates/apps/api/src/lib/__tests__/lead-capture-store.test.ts +30 -0
  170. package/templates/apps/api/src/lib/__tests__/lead-delivery.test.ts +96 -0
  171. package/templates/apps/api/src/lib/lead-capture-store.ts +12 -0
  172. package/templates/apps/api/src/lib/lead-delivery.ts +52 -0
  173. package/templates/apps/api/src/lib/license-key-store.ts +45 -0
  174. package/templates/apps/api/src/lib/site.ts +5 -3
  175. package/templates/apps/api/src/middleware/__tests__/auth.database-not-configured.test.ts +97 -0
  176. package/templates/apps/api/src/middleware/auth.ts +9 -0
  177. package/templates/apps/api/src/openapi/spec.ts +63 -12
  178. package/templates/apps/api/src/routes/content/__tests__/get-page.test.ts +127 -5
  179. package/templates/apps/api/src/routes/content/get-page.ts +86 -2
  180. package/templates/apps/api/src/routes/leads/__tests__/capture.test.ts +88 -0
  181. package/templates/apps/api/src/routes/leads/__tests__/index.test.ts +100 -0
  182. package/templates/apps/api/src/routes/leads/index.ts +46 -1
  183. package/templates/apps/api/src/server.ts +13 -0
  184. package/templates/apps/api/src/services/__tests__/lead-routing.test.ts +194 -0
  185. package/templates/apps/api/src/services/__tests__/leads-inbox.test.ts +124 -0
  186. package/templates/apps/api/src/services/lead-routing.ts +140 -30
  187. package/templates/apps/api/src/services/leads-inbox.ts +48 -11
  188. package/templates/apps/mcp/__tests__/audit-single-page.test.ts +75 -1
  189. package/templates/apps/mcp/__tests__/lib/audit-single-page-readiness-scan.test.ts +57 -9
  190. package/templates/apps/mcp/__tests__/parity.test.ts +5 -4
  191. package/templates/apps/mcp/package.json +2 -1
  192. package/templates/apps/mcp/src/index.ts +7 -0
  193. package/templates/apps/mcp/src/lib/audit-single-page.ts +36 -10
  194. package/templates/apps/mcp/src/lib/license-key-store.ts +45 -0
  195. package/templates/apps/mcp/src/tools/create_project.ts +21 -6
  196. package/templates/apps/web/__tests__/setup.ts +18 -0
  197. package/templates/apps/web/app/[locale]/(auth)/forgot-password/forgot-password-card.tsx +10 -2
  198. package/templates/apps/web/app/[locale]/(auth)/layout.tsx +2 -2
  199. package/templates/apps/web/app/[locale]/(auth)/reset-password/reset-password-card.tsx +10 -4
  200. package/templates/apps/web/app/[locale]/(auth)/sign-in/sign-in-card.tsx +22 -14
  201. package/templates/apps/web/app/[locale]/(auth)/sign-up/sign-up-card.tsx +26 -18
  202. package/templates/apps/web/app/[locale]/(authed)/account/page.tsx +12 -4
  203. package/templates/apps/web/app/[locale]/[slug]/__tests__/page.test.tsx +35 -4
  204. package/templates/apps/web/app/[locale]/[slug]/page.tsx +49 -9
  205. package/templates/apps/web/app/[locale]/book-a-call/page.tsx +50 -0
  206. package/templates/apps/web/app/[locale]/careers/__tests__/page-renderer-migration.test.tsx +3 -3
  207. package/templates/apps/web/app/[locale]/case-studies/__tests__/page-renderer-migration.test.tsx +3 -3
  208. package/templates/apps/web/app/[locale]/clients/__tests__/page-renderer-migration.test.tsx +3 -4
  209. package/templates/apps/web/app/[locale]/contact/ConversationalIntakeWrapper.tsx +67 -29
  210. package/templates/apps/web/app/[locale]/contact/__tests__/ConversationalIntakeWrapper.test.tsx +94 -11
  211. package/templates/apps/web/app/[locale]/contact/__tests__/contact-form-text.test.ts +32 -0
  212. package/templates/apps/web/app/[locale]/contact/__tests__/contact.actions.test.ts +129 -0
  213. package/templates/apps/web/app/[locale]/contact/__tests__/page-renderer-migration.test.tsx +228 -86
  214. package/templates/apps/web/app/[locale]/contact/contact-form-text.ts +50 -0
  215. package/templates/apps/web/app/[locale]/contact/contact.actions.ts +73 -20
  216. package/templates/apps/web/app/[locale]/contact/page.tsx +115 -7
  217. package/templates/apps/web/app/[locale]/contact/site-contact-email.ts +54 -0
  218. package/templates/apps/web/app/[locale]/courses/__tests__/page-renderer-migration.test.tsx +3 -3
  219. package/templates/apps/web/app/[locale]/downloads/__tests__/page-renderer-migration.test.tsx +3 -4
  220. package/templates/apps/web/app/[locale]/events/__tests__/page-renderer-migration.test.tsx +4 -5
  221. package/templates/apps/web/app/[locale]/features/[slug]/page.tsx +64 -0
  222. package/templates/apps/web/app/[locale]/features/page.tsx +52 -0
  223. package/templates/apps/web/app/[locale]/glossary/page.tsx +50 -0
  224. package/templates/apps/web/app/[locale]/how-to/__tests__/page-renderer-migration.test.tsx +1 -1
  225. package/templates/apps/web/app/[locale]/how-to/page.tsx +25 -15
  226. package/templates/apps/web/app/[locale]/integrations/__tests__/page-renderer-migration.test.tsx +3 -3
  227. package/templates/apps/web/app/[locale]/layout.tsx +146 -52
  228. package/templates/apps/web/app/[locale]/newsletter/__tests__/newsletter.actions.test.ts +75 -0
  229. package/templates/apps/web/app/[locale]/newsletter/__tests__/page.test.tsx +163 -0
  230. package/templates/apps/web/app/[locale]/newsletter/newsletter.actions.ts +66 -0
  231. package/templates/apps/web/app/[locale]/newsletter/page.tsx +142 -0
  232. package/templates/apps/web/app/[locale]/partners/__tests__/page-renderer-migration.test.tsx +3 -3
  233. package/templates/apps/web/app/[locale]/portfolio/[slug]/page.tsx +59 -0
  234. package/templates/apps/web/app/[locale]/portfolio/page.tsx +49 -0
  235. package/templates/apps/web/app/[locale]/products/__tests__/page-renderer-migration.test.tsx +3 -3
  236. package/templates/apps/web/app/[locale]/projects/__tests__/page-renderer-migration.test.tsx +4 -5
  237. package/templates/apps/web/app/[locale]/{cancellation → purchase-policy}/page.tsx +15 -10
  238. package/templates/apps/web/app/[locale]/resources/__tests__/page-renderer-migration.test.tsx +4 -5
  239. package/templates/apps/web/app/[locale]/services/__tests__/page-renderer-migration.test.tsx +3 -3
  240. package/templates/apps/web/app/[locale]/software/[slug]/page.tsx +14 -1
  241. package/templates/apps/web/app/[locale]/software/__tests__/page-renderer-migration.test.tsx +1 -1
  242. package/templates/apps/web/app/[locale]/software/page.tsx +25 -15
  243. package/templates/apps/web/app/[locale]/tech-articles/__tests__/page-renderer-migration.test.tsx +1 -1
  244. package/templates/apps/web/app/[locale]/tech-articles/page.tsx +25 -15
  245. package/templates/apps/web/app/[locale]/testimonials/__tests__/page-renderer-migration.test.tsx +3 -4
  246. package/templates/apps/web/app/[locale]/use-cases/[slug]/page.tsx +63 -0
  247. package/templates/apps/web/app/[locale]/use-cases/__tests__/page-renderer-migration.test.tsx +3 -4
  248. package/templates/apps/web/app/[locale]/use-cases/page.tsx +15 -2
  249. package/templates/apps/web/app/[locale]/whitepapers/__tests__/page-renderer-migration.test.tsx +1 -1
  250. package/templates/apps/web/app/[locale]/whitepapers/page.tsx +25 -15
  251. package/templates/apps/web/app/error-fallback.tsx +17 -45
  252. package/templates/apps/web/app/not-found.tsx +14 -38
  253. package/templates/apps/web/app/og/route.tsx +6 -0
  254. package/templates/apps/web/eslint.config.mjs +3 -0
  255. package/templates/apps/web/instrumentation.ts +14 -0
  256. package/templates/apps/web/lib/__tests__/structured-data.test.tsx +38 -0
  257. package/templates/apps/web/lib/license-key-store.ts +52 -0
  258. package/templates/apps/web/lib/nav-config.ts +82 -18
  259. package/templates/apps/web/lib/prisma-for-auth.ts +9 -6
  260. package/templates/apps/web/lib/site-brand.tsx +1 -1
  261. package/templates/apps/web/lib/site-chrome.ts +3 -3
  262. package/templates/apps/web/lib/site-identity.ts +148 -0
  263. package/templates/apps/web/lib/site-theme.ts +11 -0
  264. package/templates/apps/web/messages/en.json +78 -37
  265. package/templates/apps/web/public/android-chrome-192x192.png +0 -0
  266. package/templates/apps/web/public/android-chrome-512x512.png +0 -0
  267. package/templates/apps/web/public/apple-touch-icon.png +0 -0
  268. package/templates/apps/web/public/favicon-16x16.png +0 -0
  269. package/templates/apps/web/public/favicon-32x32.png +0 -0
  270. package/templates/apps/web/public/favicon.ico +0 -0
  271. package/templates/apps/web/public/favicon.svg +3 -3
  272. package/templates/apps/web/public/logomark.svg +6 -3
  273. package/templates/apps/web/public/logos/ai-launch-kit-light.svg +4 -0
  274. package/templates/apps/web/public/logos/ai-launch-kit.svg +4 -0
  275. package/templates/apps/web/public/logos/atomic-agent-light.svg +4 -0
  276. package/templates/apps/web/public/logos/atomic-agent.svg +4 -0
  277. package/templates/apps/web/public/logos/salessmyth-light.svg +4 -0
  278. package/templates/apps/web/public/logos/salessmyth.svg +4 -0
  279. package/templates/apps/web/public/site.webmanifest +4 -4
  280. package/templates/apps/web/public/wordmark-light.svg +4 -0
  281. package/templates/apps/web/public/wordmark.svg +3 -3
  282. package/templates/apps/web/security-headers.config.ts +5 -1
  283. package/templates/content/_site.mdx +25 -0
  284. package/templates/content/en/AboutPage/about.mdx +150 -144
  285. package/templates/content/en/BlogList/blog.mdx +126 -0
  286. package/templates/content/en/ContactPage/contact.mdx +21 -92
  287. package/templates/content/en/FAQPage/faq.mdx +86 -57
  288. package/templates/content/en/HomePage/home.mdx +268 -217
  289. package/templates/content/en/PurchasePolicyPage/purchase-policy.mdx +181 -0
  290. package/templates/content/en/UseCasesList/use-cases.mdx +170 -0
  291. package/templates/database/CHANGELOG.md +14 -0
  292. package/templates/database/package.json +1 -1
  293. package/templates/package.json +3 -1
  294. package/templates/tools/lint-architecture.ts +315 -0
  295. package/templates/tools/tsconfig.json +14 -0
  296. package/versions.json +33 -33
  297. package/site-templates/simple-service/DESIGN.md +0 -148
  298. package/site-templates/simple-service/TEMPLATE-MANIFEST.json +0 -240
  299. package/site-templates/simple-service/TEMPLATE-MANIFEST.md +0 -278
  300. package/site-templates/simple-service/ailk-template.json +0 -10
  301. package/site-templates/simple-service/content/en/ContactPage/contact.mdx +0 -67
  302. package/site-templates/simple-service/content/en/FAQPage/faq.mdx +0 -56
  303. package/site-templates/simple-service/content/en/HomePage/home.mdx +0 -85
  304. package/site-templates/simple-service/content/en/Service/landscape-design.mdx +0 -40
  305. package/site-templates/simple-service/content/en/Service/lawn-maintenance.mdx +0 -40
  306. package/site-templates/simple-service/content/en/Service/seasonal-cleanup.mdx +0 -40
  307. package/site-templates/simple-service/provenance.json +0 -4
  308. package/templates/.claude/skills/add-schema/SKILL.md +0 -277
  309. package/templates/.claude/skills/add-schema/references/conflict-handling.md +0 -200
  310. package/templates/.claude/skills/add-schema/references/file-finding.md +0 -123
  311. package/templates/.claude/skills/add-schema/references/schema-types.md +0 -234
  312. package/templates/.claude/skills/provision-config/references/runbook-template.md +0 -248
  313. package/templates/.claude/skills/scaffold-page/references/page-types.md +0 -169
  314. package/templates/.claude/skills/suggest-site-pages/SKILL.md +0 -242
  315. package/templates/.claude/skills/suggest-site-pages/references/categories.md +0 -287
  316. package/templates/apps/web/app/[locale]/contact/ContactForm.tsx +0 -150
  317. package/templates/apps/web/app/[locale]/returns/page.tsx +0 -44
  318. package/templates/apps/web/app/[locale]/shipping/page.tsx +0 -44
  319. package/templates/content/en/CancellationPolicyPage/cancellation.mdx +0 -108
  320. package/templates/content/en/LandingPage/blog.mdx +0 -83
  321. package/templates/content/en/LandingPage/features.mdx +0 -103
  322. package/templates/content/en/LandingPage/how-it-works.mdx +0 -143
  323. package/templates/content/en/ReturnsPolicyPage/returns.mdx +0 -108
  324. package/templates/content/en/ShippingPolicyPage/shipping.mdx +0 -108
@@ -73,7 +73,7 @@ describe("IntegrationsPage — snapshot regression", () => {
73
73
  });
74
74
  const { container } = render(element);
75
75
  expect(container.innerHTML).toMatchInlineSnapshot(
76
- `"<main><h1 class="text-heading-1 font-display font-normal tracking-tighter text-fg hero-type-display">Integrations</h1><p data-testid="section-empty-state">Integrations listing coming soon.</p></main>"`,
76
+ `"<main id="main"><h1 class="text-heading-1 lg:text-display-sm font-display text-fg hero-type-display">Integrations</h1><p data-testid="section-empty-state">Integrations listing coming soon.</p></main>"`,
77
77
  );
78
78
  });
79
79
 
@@ -86,7 +86,7 @@ describe("IntegrationsPage — snapshot regression", () => {
86
86
  });
87
87
  const { container } = render(element);
88
88
  expect(container.innerHTML).toMatchInlineSnapshot(`
89
- "<script type="application/ld+json" data-testid="json-ld">{"@context":"https://schema.org","@type":"CollectionPage","inLanguage":"en","name":"Integrations","description":"First-party integrations for AI Launch Kit.","mainEntity":{"@type":"ItemList","itemListElement":[{"@type":"SoftwareApplication"}]}}</script><main><h1>Integrations</h1><p data-testid="integrations-description">First-party integrations for AI Launch Kit.</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="# Integrations
89
+ "<script type="application/ld+json" data-testid="json-ld">{"@context":"https://schema.org","@type":"CollectionPage","inLanguage":"en","name":"Integrations","description":"First-party integrations for AI Launch Kit.","mainEntity":{"@type":"ItemList","itemListElement":[{"@type":"SoftwareApplication"}]}}</script><main id="main"><h1>Integrations</h1><p data-testid="integrations-description">First-party integrations for AI Launch Kit.</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="# Integrations
90
90
 
91
91
  Body."></div></article></div></div></main>"
92
92
  `);
@@ -110,7 +110,7 @@ describe("IntegrationSlugPage — snapshot regression", () => {
110
110
  });
111
111
  const { container } = render(element);
112
112
  expect(container.innerHTML).toMatchInlineSnapshot(`
113
- "<script type="application/ld+json" data-testid="json-ld">{"@context":"https://schema.org","@graph":[{"@context":"https://schema.org","@type":"WebPage","inLanguage":"en","name":"OpenAI API","description":"Connect AILK to OpenAI.","mainEntity":{"@context":"https://schema.org","@type":"SoftwareApplication","name":"OpenAI API","description":"Connect AILK to OpenAI."}},{"@context":"https://schema.org","@type":"BreadcrumbList","inLanguage":"en","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"http://localhost:3000/en/"},{"@type":"ListItem","position":2,"name":"Integrations","item":"http://localhost:3000/en/integrations"},{"@type":"ListItem","position":3,"name":"OpenAI API","item":"http://localhost:3000/en/integrations/openai-api"}]}]}</script><main><h1>OpenAI API</h1><p data-testid="integration-description">Connect AILK to OpenAI.</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="# OpenAI API
113
+ "<script type="application/ld+json" data-testid="json-ld">{"@context":"https://schema.org","@graph":[{"@context":"https://schema.org","@type":"WebPage","inLanguage":"en","name":"OpenAI API","description":"Connect AILK to OpenAI.","mainEntity":{"@context":"https://schema.org","@type":"SoftwareApplication","name":"OpenAI API","description":"Connect AILK to OpenAI."}},{"@context":"https://schema.org","@type":"BreadcrumbList","inLanguage":"en","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"http://localhost:3000/en/"},{"@type":"ListItem","position":2,"name":"Integrations","item":"http://localhost:3000/en/integrations"},{"@type":"ListItem","position":3,"name":"OpenAI API","item":"http://localhost:3000/en/integrations/openai-api"}]}]}</script><main id="main"><h1>OpenAI API</h1><p data-testid="integration-description">Connect AILK to OpenAI.</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="# OpenAI API
114
114
 
115
115
  Body."></div></article></div></div></main>"
116
116
  `);
@@ -27,7 +27,7 @@ import { Telemetry, VerificationMeta } from "@working-theory/analytics";
27
27
  import { MarketingShell } from "@working-theory/templates/shells";
28
28
  import { themeToCssVarsBothModes } from "@working-theory/theme";
29
29
  import { Link, isRtl, localeHref } from "@working-theory/ui";
30
- import { LocaleSwitcherNav } from "@working-theory/ui/blocks";
30
+ import { BRAND_PATHS, LocaleSwitcherNav } from "@working-theory/ui/blocks";
31
31
  import {
32
32
  Banner,
33
33
  Footer,
@@ -50,11 +50,14 @@ import { auth } from "../../auth";
50
50
  import { GoogleAnalytics } from "../../components/google-analytics";
51
51
  import { SignOutTrigger } from "../../components/sign-out-trigger";
52
52
  import { isAuthPath } from "../../lib/auth-routes";
53
- import { navItems } from "../../lib/nav-config";
53
+ import { footerColumns, navItems } from "../../lib/nav-config";
54
54
  import { getOgImageUrl } from "../../lib/og";
55
55
  import { SITE_NAME, siteLogo } from "../../lib/site-brand";
56
56
  import { siteChrome } from "../../lib/site-chrome";
57
- import { loadSiteBrandTokens } from "../../lib/site-theme";
57
+ // Registers the `_site.mdx` identity with page-renderer at module load, so
58
+ // every page rendered under this layout carries the sitewide JSON-LD (#6597).
59
+ import "../../lib/site-identity";
60
+ import { loadSiteBrandTokens, loadSiteShapeCss } from "../../lib/site-theme";
58
61
  import { resolveBaseUrl } from "../../src/lib/routes";
59
62
 
60
63
  // ── Constants ────────────────────────────────────────────────────────────────
@@ -224,6 +227,25 @@ export default async function LocaleLayout({
224
227
  // The Free marketing-site chrome (#5469 D5): the Product flyout's three
225
228
  // entries + descriptions, the top-level items, and the Get started CTA.
226
229
  product: tNav("product"),
230
+ useCases: tNav("useCases"),
231
+ useCasesDesc: tNav("useCasesDesc"),
232
+ footerSitePagesHeading: tNav("footerSitePagesHeading"),
233
+ footerFeaturesHeading: tNav("footerFeaturesHeading"),
234
+ footerResourcesHeading: tNav("footerResourcesHeading"),
235
+ feature1: tNav("feature1"),
236
+ feature2: tNav("feature2"),
237
+ feature3: tNav("feature3"),
238
+ feature4: tNav("feature4"),
239
+ feature5: tNav("feature5"),
240
+ allFeatures: tNav("allFeatures"),
241
+ // #6721 — the two-column Product flyout + the Use Cases footer column.
242
+ flyoutFeaturesHeading: tNav("flyoutFeaturesHeading"),
243
+ flyoutUseCasesHeading: tNav("flyoutUseCasesHeading"),
244
+ footerUseCasesHeading: tNav("footerUseCasesHeading"),
245
+ useCase1: tNav("useCase1"),
246
+ useCase2: tNav("useCase2"),
247
+ useCase3: tNav("useCase3"),
248
+ allUseCases: tNav("allUseCases"),
227
249
  howItWorks: tNav("howItWorks"),
228
250
  howItWorksDesc: tNav("howItWorksDesc"),
229
251
  features: tNav("features"),
@@ -232,7 +254,17 @@ export default async function LocaleLayout({
232
254
  faqDesc: tNav("faqDesc"),
233
255
  pricing: tNav("pricing"),
234
256
  about: tNav("about"),
257
+ // The footer's column groups (#6468): their two headings + Contact.
258
+ contact: tNav("contact"),
259
+ // The three pages every starter's roster names (#6658 D8), linked from
260
+ // the footer when their surfaces are on.
261
+ bookACall: tNav("bookACall"),
262
+ glossary: tNav("glossary"),
263
+ newsletter: tNav("newsletter"),
264
+ footerProductHeading: tNav("footerProductHeading"),
265
+ footerCompanyHeading: tNav("footerCompanyHeading"),
235
266
  getStarted: tNav("getStarted"),
267
+ signUp: tNav("signUp"),
236
268
  docs: tNav("docs"),
237
269
  blog: tNav("blog"),
238
270
  // The scheduling module's booking page, /meet (#4633; renamed from the
@@ -278,8 +310,9 @@ export default async function LocaleLayout({
278
310
  },
279
311
  ]
280
312
  : [
281
- { labelKey: "getStarted", path: "/contact", variant: "primary" },
313
+ // NO_HAND_IMPLEMENT_OK: operator-directed live Free-template pass.
282
314
  { labelKey: "signIn", path: "/sign-in", variant: "outline-strong" },
315
+ { labelKey: "signUp", path: "/sign-up", variant: "primary" },
283
316
  ];
284
317
 
285
318
  // Only compose the flow seam when there is a flow action to compose. This
@@ -294,12 +327,14 @@ export default async function LocaleLayout({
294
327
  const hasFlowAction = navActions.some((action) => action.kind === "flow");
295
328
 
296
329
  // Shared brand mark injected into both Nav and Footer chrome slots. The
297
- // mark comes from site-brand.tsx (the light/dark pair the /sign-in card
298
- // also renders); the Nav adds the shipped wordmark beside it so BrandLogo
299
- // composes mark + wordmark on desktop and mark-only below `md` (#5469 D5).
330
+ // mark comes from site-brand.tsx (the mark the /sign-in card also renders);
331
+ // both add the shipped wordmark beside it — the Nav composes mark + wordmark
332
+ // on desktop and mark-only below `md` (#5469 D5), the columns Footer shows
333
+ // the full lockup at every width (#6507).
300
334
  const brandLogo = siteLogo();
301
335
  const brandWordmark = {
302
336
  src: "/wordmark.svg",
337
+ darkSrc: "/wordmark-light.svg",
303
338
  alt: SITE_NAME,
304
339
  width: 140,
305
340
  height: 16,
@@ -351,8 +386,22 @@ export default async function LocaleLayout({
351
386
  href: "https://linkedin.com/company/{{COMPANY_HANDLE}}",
352
387
  },
353
388
  { platform: "youtube", href: "https://youtube.com/@{{COMPANY_HANDLE}}" },
389
+ // #6739 — the kit's own repo, operator-directed: the Free template links
390
+ // back to where it came from.
391
+ { platform: "github", href: "https://github.com/working-theory-labs/ai-launch-kit" },
354
392
  ] as const;
355
- const copyrightText = `© ${new Date().getFullYear()} {{COMPANY_NAME}}`;
393
+ // Footer column groups (#6468), generated beside `navItems` as label keys;
394
+ // `Footer` takes translated strings, so resolve them through the same map.
395
+ const labelFor = (key: string): string =>
396
+ (navLabels.items as Record<string, string>)[key] ?? key;
397
+ const footerColumnDefs = footerColumns.map((col) => ({
398
+ heading: labelFor(col.headingKey),
399
+ headingKey: col.headingKey,
400
+ items: col.items.map((item) => ({ ...item, label: labelFor(item.labelKey) })),
401
+ }));
402
+
403
+ // NO_HAND_IMPLEMENT_OK: operator-directed live Free-template pass (2026-09-23).
404
+ const copyrightText = `© ${new Date().getFullYear()} SalesSmyth LLC. All rights reserved.`;
356
405
 
357
406
  // Footer legal links (#5612 D3). The centered variant folds in FAQ — the
358
407
  // roster's third always-scaffolded core route (`/faq`, alongside `/privacy`
@@ -362,6 +411,10 @@ export default async function LocaleLayout({
362
411
  const legalLinks = [
363
412
  { label: "Privacy Policy", href: "/privacy" },
364
413
  { label: "Terms of Service", href: "/terms" },
414
+ // #6739 — a link, not the reopen button: the privacy page's
415
+ // #cookie-settings anchor explains the choice, and arriving there
416
+ // reopens the consent banner (BannerConsentIsland).
417
+ { label: tConsent("settingsLabel"), href: "/privacy#cookie-settings" },
365
418
  ...(siteChrome.footer === "centered"
366
419
  ? [{ label: navLabels.items.faq, href: "/faq" }]
367
420
  : []),
@@ -379,9 +432,11 @@ export default async function LocaleLayout({
379
432
  // single-mode call inlined an unconditional :root block that never
380
433
  // responded to a client-side data-theme toggle). themeMode still drives
381
434
  // the initial data-theme attribute below for a no-flash first paint.
382
- const inlineThemeStyle = themeToCssVarsBothModes(
383
- siteBrandTokens ? { brand: siteBrandTokens } : {},
384
- );
435
+ // Site shape settings (`theme.corners`, `theme.eyebrowCase`) appended after
436
+ // the token block so they win; "" for a site that sets neither.
437
+ const inlineThemeStyle =
438
+ themeToCssVarsBothModes(siteBrandTokens ? { brand: siteBrandTokens } : {}) +
439
+ loadSiteShapeCss();
385
440
 
386
441
  return (
387
442
  <html
@@ -425,6 +480,17 @@ export default async function LocaleLayout({
425
480
  <VerificationMeta />
426
481
  </head>
427
482
  <body>
483
+ {/* Skip link (operator ruling 2026-09-23): the first Tab stop on every
484
+ page jumps keyboard users past the banner and nav to <main id="main">,
485
+ which page-renderer (or the shell) owns. Visible only while focused. */}
486
+ <Link
487
+ href="#main"
488
+ variant="bare"
489
+ className="sr-only focus:not-sr-only focus:fixed focus:top-2 focus:start-2 focus:z-50 focus:rounded-control focus:bg-(--color-bg) focus:px-control-x focus:py-control-y focus:text-(--color-fg) focus:ring-2 focus:ring-(--color-accent)"
490
+ data-testid="skip-to-content"
491
+ >
492
+ {tNav("skipToContent")}
493
+ </Link>
428
494
  <NextIntlClientProvider messages={messages}>
429
495
  {/*
430
496
  * Shell + Chrome compose at the layout layer (decision: #2820, #2862).
@@ -450,7 +516,12 @@ export default async function LocaleLayout({
450
516
  ) : (
451
517
  <MarketingShell
452
518
  contentOwnsMain
453
- gutters="plain"
519
+ // #6536 — no gutter frame: header, sections and footer share one
520
+ // left/right edge (the section Container's), matching the
521
+ // reference. Nav `frame="contained"` and Footer
522
+ // `bottomBarAlign="content"` below put the header row and the
523
+ // footer divider on that same edge.
524
+ gutters="none"
454
525
  bannerPlacement="above-nav"
455
526
  {...(siteChrome.announcementBanner
456
527
  ? {
@@ -458,10 +529,30 @@ export default async function LocaleLayout({
458
529
  <Banner
459
530
  variant="contrast"
460
531
  placement="above-nav"
461
- message="Medium length banner heading with link goes here"
532
+ // NO_HAND_IMPLEMENT_OK: operator-directed live Free-template pass.
533
+ message={
534
+ <>
535
+ <a
536
+ href="https://github.com/working-theory-labs/ai-launch-kit"
537
+ className="inline-flex items-center gap-1 font-bold text-current underline underline-offset-2"
538
+ >
539
+ Get started for free
540
+ <svg
541
+ viewBox="0 0 24 24"
542
+ aria-hidden="true"
543
+ className="size-4 fill-current"
544
+ >
545
+ <path d={BRAND_PATHS.github} />
546
+ </svg>
547
+ </a>
548
+ {"\u00A0 "}
549
+ or
550
+ </>
551
+ }
552
+ messageWeight="regular"
462
553
  action={{
463
- label: "Link",
464
- href: "/pricing",
554
+ label: "upgrade to Pro",
555
+ href: "https://ailaunchkit.ai/pricing",
465
556
  kind: "link",
466
557
  }}
467
558
  routes={{ include: [homePath] }}
@@ -493,6 +584,8 @@ export default async function LocaleLayout({
493
584
  // render is byte-identical to before this branch existed.
494
585
  {...(isDeckHome ? { sticky: true, deckMorph: true } : {})}
495
586
  siteName={SITE_NAME}
587
+ frame="contained"
588
+ navAlignment="right"
496
589
  showThemeToggle={false}
497
590
  logo={brandLogo}
498
591
  wordmark={brandWordmark}
@@ -520,13 +613,16 @@ export default async function LocaleLayout({
520
613
  locale={locale}
521
614
  itemLabels={navLabels.items}
522
615
  navItems={navItems}
616
+ columns={footerColumnDefs}
523
617
  siteName={SITE_NAME}
524
618
  logo={brandLogo}
619
+ wordmark={brandWordmark}
525
620
  social={[...socialLinks]}
526
621
  {...(siteChrome.footer === "centered"
527
622
  ? { layout: "centered" as const }
528
623
  : {})}
529
624
  showThemeToggle
625
+ bottomBarAlign="content"
530
626
  copyrightText={copyrightText}
531
627
  legalLinks={legalLinks}
532
628
  languageSwitcher={
@@ -534,20 +630,9 @@ export default async function LocaleLayout({
534
630
  current={locale}
535
631
  locales={enabledLocales}
536
632
  pathname={pathname}
633
+ variant="quiet"
537
634
  />
538
635
  }
539
- // #4157 — deliberately left as a literal "Built by Working
540
- // Theory Labs" / https://ailaunchkit.ai, not SITE_NAME: this credits
541
- // the OSS TEMPLATE this site is built with (a "powered by"
542
- // link, unconditional in OSS forks per the comment above),
543
- // never the customer's own identity, so it isn't the vendor-
544
- // brand-as-default defect #4157 fixes. See
545
- // docs/specs/4157-scaffold-brand-defaults-spec.md § Design
546
- // Decisions for the fuller reasoning.
547
- attribution={{
548
- label: "Built by Working Theory Labs",
549
- href: "https://ailaunchkit.ai",
550
- }}
551
636
  />
552
637
  }
553
638
  >
@@ -561,32 +646,41 @@ export default async function LocaleLayout({
561
646
  * visitor sees it, an accepted/declined one does not, on every
562
647
  * page. Out of the shell's flow (placement `bottom` is a fixed
563
648
  * overlay), so accepting causes no layout shift.
649
+ *
650
+ * SOFT_LOCK_OK: #6510 (D3) — Banner is now ALWAYS mounted (the
651
+ * `consentDecided ? null :` gate is gone); `consentDecided` is
652
+ * passed straight through so the island's `initialConsented`
653
+ * starts from server truth instead of only its own client-side
654
+ * cookie read — same #5557 hydration-safety property, no gate
655
+ * needed. This also means the banner never blocks the page: it
656
+ * reserves its own space (D1) and carries a close-without-choosing
657
+ * control (D2) instead of a hard server-side hide.
564
658
  */}
565
- {consentDecided ? null : (
566
- <Banner
567
- variant="info"
568
- placement="bottom"
569
- consentCookieName={CONSENT_COOKIE_NAME}
570
- title={tConsent("title")}
571
- message={
572
- <>
573
- {tConsent("body")}{" "}
574
- <Link
575
- href={localeHref(locale, "/privacy")}
576
- variant="unstyled"
577
- className="font-semibold text-current underline underline-offset-4"
578
- >
579
- {tConsent("policyLinkLabel")}
580
- </Link>
581
- </>
582
- }
583
- dismissible={false}
584
- acceptLabel={tConsent("acceptLabel")}
585
- secondaryAction={{ label: tConsent("rejectLabel") }}
586
- rejectLabel={tConsent("rejectLabel")}
587
- locale={locale}
588
- />
589
- )}
659
+ <Banner
660
+ variant="info"
661
+ placement="bottom"
662
+ consentCookieName={CONSENT_COOKIE_NAME}
663
+ consentDecided={consentDecided}
664
+ title={tConsent("title")}
665
+ message={
666
+ <>
667
+ {tConsent("body")}{" "}
668
+ <Link
669
+ href={localeHref(locale, "/privacy")}
670
+ variant="unstyled"
671
+ className="font-semibold text-current underline underline-offset-4"
672
+ >
673
+ {tConsent("policyLinkLabel")}
674
+ </Link>
675
+ </>
676
+ }
677
+ dismissible={false}
678
+ dismissLabel={tConsent("dismissLabel")}
679
+ acceptLabel={tConsent("acceptLabel")}
680
+ secondaryAction={{ label: tConsent("rejectLabel") }}
681
+ rejectLabel={tConsent("rejectLabel")}
682
+ locale={locale}
683
+ />
590
684
  <Telemetry />
591
685
  </NextIntlClientProvider>
592
686
  </body>
@@ -0,0 +1,75 @@
1
+ /**
2
+ * subscribeNewsletterAction (#6730 D9) — the Server Action twin of the
3
+ * `ailkSubscribeNewsletter` MCP tool.
4
+ */
5
+
6
+ import { subscribeNewsletterAction } from "../newsletter.actions.js";
7
+
8
+ jest.mock("@working-theory/api-client", () => ({
9
+ leads: { subscribe: jest.fn() },
10
+ }));
11
+
12
+ import { leads } from "@working-theory/api-client";
13
+ const mockSubscribe = leads.subscribe as jest.Mock;
14
+
15
+ function formWith(email: string): FormData {
16
+ const fd = new FormData();
17
+ fd.append("email", email);
18
+ fd.append("consent", "true");
19
+ return fd;
20
+ }
21
+
22
+ afterEach(() => jest.clearAllMocks());
23
+
24
+ describe("subscribeNewsletterAction", () => {
25
+ it("calls leads.subscribe with source newsletter and the address", async () => {
26
+ mockSubscribe.mockResolvedValueOnce({ ok: true, body: { id: "subscr_1", persisted: "db" } });
27
+
28
+ const result = await subscribeNewsletterAction(null, formWith("ada@example.com"));
29
+
30
+ expect(result).toEqual({ kind: "success" });
31
+ expect(mockSubscribe).toHaveBeenCalledWith(
32
+ expect.objectContaining({ source: "newsletter", email: "ada@example.com" }),
33
+ );
34
+ });
35
+
36
+ it("an invalid address → a field error, no API call", async () => {
37
+ const result = await subscribeNewsletterAction(null, formWith("not-an-email"));
38
+
39
+ expect(result).toMatchObject({ kind: "error", fieldErrors: { email: expect.any(String) } });
40
+ expect(mockSubscribe).not.toHaveBeenCalled();
41
+ });
42
+
43
+ it("503 lead_capture_not_configured → unavailable", async () => {
44
+ mockSubscribe.mockResolvedValueOnce({
45
+ ok: false,
46
+ status: 503,
47
+ body: { error: "lead_capture_not_configured" },
48
+ });
49
+
50
+ const result = await subscribeNewsletterAction(null, formWith("ada@example.com"));
51
+
52
+ expect(result).toEqual({ kind: "unavailable" });
53
+ });
54
+
55
+ it("any other failure → the generic error, never success", async () => {
56
+ mockSubscribe.mockResolvedValueOnce({ ok: false, status: 503, body: { error: "persist_failed" } });
57
+ expect(await subscribeNewsletterAction(null, formWith("a@b.co"))).toMatchObject({
58
+ kind: "error",
59
+ });
60
+
61
+ mockSubscribe.mockRejectedValueOnce(new Error("ECONNREFUSED"));
62
+ jest.spyOn(console, "error").mockImplementationOnce(() => undefined);
63
+ expect(await subscribeNewsletterAction(null, formWith("a@b.co"))).toMatchObject({
64
+ kind: "error",
65
+ });
66
+ });
67
+
68
+ it("a filled honeypot → success, and nothing is sent", async () => {
69
+ const fd = formWith("ada@example.com");
70
+ fd.append("ailk_hp", "spam");
71
+
72
+ expect(await subscribeNewsletterAction(null, fd)).toEqual({ kind: "success" });
73
+ expect(mockSubscribe).not.toHaveBeenCalled();
74
+ });
75
+ });
@@ -0,0 +1,163 @@
1
+ /**
2
+ * newsletter/page.tsx — a signup only when there is somewhere to keep it
3
+ * (#6730 D9).
4
+ *
5
+ * Boundaries mocked: `@working-theory/ui` (the section tree is a stub that
6
+ * renders one element per section, with a `newsletter` section rendering the
7
+ * form its content declares), `@working-theory/api-client`
8
+ * (`leads.captureStatus`), `next-intl/server`, and the Server Action module.
9
+ */
10
+ import React from "react";
11
+ import { render, screen } from "@testing-library/react";
12
+
13
+ jest.mock("@working-theory/ui", () => ({
14
+ Heading: ({ children, level }: { children: React.ReactNode; level: number }) =>
15
+ React.createElement(`h${level}`, null, children),
16
+ SectionRenderer: ({
17
+ sections,
18
+ }: {
19
+ sections: Array<{
20
+ id: string;
21
+ type: string;
22
+ data: { action?: string; primaryCta?: { href: string } };
23
+ }>;
24
+ }) =>
25
+ React.createElement(
26
+ "div",
27
+ { "data-testid": "section-renderer" },
28
+ sections.map((s) =>
29
+ s.type === "newsletter"
30
+ ? React.createElement(
31
+ "form",
32
+ { key: s.id, "data-section-id": s.id, action: s.data.action },
33
+ "signup",
34
+ )
35
+ : React.createElement(
36
+ "div",
37
+ { key: s.id, "data-section-id": s.id },
38
+ s.data.primaryCta
39
+ ? React.createElement("a", { href: s.data.primaryCta.href }, "cta")
40
+ : null,
41
+ ),
42
+ ),
43
+ ),
44
+ localeHref: (locale: string, path: string) => `/${locale}${path}`,
45
+ LeadCaptureForm: (props: { fields?: Array<{ name: string; type: string }> }) =>
46
+ React.createElement("div", {
47
+ "data-testid": "lead-capture-form",
48
+ "data-fields": (props.fields ?? []).map((f) => `${f.name}:${f.type}`).join(","),
49
+ }),
50
+ }));
51
+
52
+ jest.mock("@working-theory/api-client", () => ({
53
+ leads: { captureStatus: jest.fn() },
54
+ }));
55
+
56
+ jest.mock("next-intl/server", () => ({
57
+ getTranslations: async () => (key: string) => key,
58
+ }));
59
+
60
+ // eslint-disable-next-line no-restricted-syntax -- test-seam mock: the Server Action module is a "use server" boundary; the page only passes it through
61
+ jest.mock("../newsletter.actions", () => ({
62
+ subscribeNewsletterAction: jest.fn(),
63
+ }));
64
+
65
+ import { leads } from "@working-theory/api-client";
66
+
67
+ import NewsletterPage from "../page";
68
+
69
+ const mockCaptureStatus = leads.captureStatus as jest.Mock;
70
+ const status = (newsletter: boolean) => ({
71
+ ok: true,
72
+ status: 200,
73
+ body: { ok: true, data: { contact: true, newsletter } },
74
+ });
75
+
76
+ const source = {
77
+ frontmatter: {
78
+ type: "NewsletterSignup" as const,
79
+ slug: "newsletter",
80
+ title: "Newsletter",
81
+ description: "What subscribers get.",
82
+ },
83
+ body: "",
84
+ };
85
+
86
+ /** `_base`'s content: a hero whose CTA points at `#signup`, the signup section, an FAQ. */
87
+ const BASE_SECTIONS = [
88
+ { id: "hero", type: "hero", data: { headline: "News", primaryCta: { label: "Subscribe", href: "#signup" } } },
89
+ { id: "signup", type: "newsletter", data: { action: "/v1/leads/subscribe", source: "newsletter" } },
90
+ { id: "faq", type: "faq", data: { items: [] } },
91
+ ];
92
+
93
+ /** #6738's Free content: no signup section. */
94
+ const FREE_SECTIONS = [
95
+ { id: "hero", type: "hero", data: { headline: "News" } },
96
+ { id: "faq", type: "faq", data: { items: [] } },
97
+ ];
98
+
99
+ async function renderWith(sections: unknown[]) {
100
+ const element = await NewsletterPage({
101
+ params: Promise.resolve({ locale: "en" }),
102
+ getMdxSource: jest.fn().mockResolvedValue(source),
103
+ getPageSections: jest.fn().mockResolvedValue(sections),
104
+ });
105
+ return render(element);
106
+ }
107
+
108
+ beforeEach(() => jest.clearAllMocks());
109
+
110
+ describe("NewsletterPage — #6730 D9", () => {
111
+ it("newsletter:true + no newsletter section → one appended form with a single email field", async () => {
112
+ mockCaptureStatus.mockResolvedValue(status(true));
113
+
114
+ await renderWith(FREE_SECTIONS);
115
+
116
+ expect(screen.getAllByTestId("lead-capture-form")).toHaveLength(1);
117
+ expect(screen.getByTestId("lead-capture-form")).toHaveAttribute(
118
+ "data-fields",
119
+ "email:email",
120
+ );
121
+ });
122
+
123
+ it("newsletter:false + no newsletter section → no form at all", async () => {
124
+ mockCaptureStatus.mockResolvedValue(status(false));
125
+
126
+ const { container } = await renderWith(FREE_SECTIONS);
127
+
128
+ expect(screen.queryByTestId("lead-capture-form")).toBeNull();
129
+ expect(container.querySelector("form")).toBeNull();
130
+ });
131
+
132
+ it("newsletter:true + _base's newsletter section → that section renders, no second form", async () => {
133
+ mockCaptureStatus.mockResolvedValue(status(true));
134
+
135
+ const { container } = await renderWith(BASE_SECTIONS);
136
+
137
+ expect(container.querySelector("form[action='/v1/leads/subscribe']")).not.toBeNull();
138
+ expect(screen.queryByTestId("lead-capture-form")).toBeNull();
139
+ expect(container.querySelector("a[href='#signup']")).not.toBeNull();
140
+ });
141
+
142
+ it.each([
143
+ ["newsletter:false", () => mockCaptureStatus.mockResolvedValue(status(false))],
144
+ ["the status read rejecting", () => mockCaptureStatus.mockRejectedValue(new Error("ECONNREFUSED"))],
145
+ ["a 404 (older API)", () => mockCaptureStatus.mockResolvedValue({ ok: false, status: 404, body: {} })],
146
+ ])(
147
+ "%s + _base's newsletter section → the section is absent, and so is the hero CTA to it",
148
+ async (_label, arrange) => {
149
+ arrange();
150
+
151
+ const { container } = await renderWith(BASE_SECTIONS);
152
+
153
+ expect(container.querySelector("[data-section-id='signup']")).toBeNull();
154
+ expect(container.querySelector("form")).toBeNull();
155
+ expect(container.innerHTML).not.toContain("/v1/leads/subscribe");
156
+ expect(container.querySelector("a[href='#signup']")).toBeNull();
157
+ expect(screen.queryByTestId("lead-capture-form")).toBeNull();
158
+ // The rest of the page still renders.
159
+ expect(container.querySelector("[data-section-id='hero']")).not.toBeNull();
160
+ expect(container.querySelector("[data-section-id='faq']")).not.toBeNull();
161
+ },
162
+ );
163
+ });